#!/usr/bin/perl

# Copyright (C) 2005 John C. Luciani Jr.

# This program may be distributed or modified under the terms of
# version 0.2 of the No-Fee Software License published by
# John C. Luciani Jr.

# Creates Maxim TQFN style packages.  

# Data is from the Maxim 21-0140 Rev G and Maxim 21-10159 Rev A
# specifications.

# The TQFN (thin quad flat no-lead) packages have solder terminations
# on four sides and a thermal pad in the center. The two denser
# packages (T3255-2 and T4055-1) require smaller pads on the corner
# terminations.

use strict;
use warnings;
use Carp;

use Pcb_8; # routines to create PCB elements (packages)

my $Pcb = Pcb_8 -> new(debug => 0);

# The specifications to generate these symbols is in the __DATA__
# section of this file. Each line can be either blank, contain a global
# definition or contain a package data record.

# Global definitions are saved in @Def.
# The field names for the package data record are in @Fields.

my @Def; # Global definitions saved as key-value pairs.
my @Fields = qw(package_code
		pin_count     
		pad_spacing   
		pad_width     
		pad_length    
		corner_pad_length
		thermal_pad_width
		thermal_pad_length);

# Read the __DATA__ section and output a PCB footprint everytime a
# package data record is read. 

while (<DATA>) {
    last if /^__END__$/; 
    s/\#.*//; # Remove comments
    s/^\s*//; # Remove leading spaces
    s/\s*$//; # Revove trailing spaces
    next unless length; # Skip empty lines

    # Lines that contain an '=' are global definitions.  The key (lhs)
    # and value (rhs) are pushed onto @Def. 

    push(@Def, $1, $2), next if /(\S+)\s*=\s*(\S.*)/;

    # A line with non-zero length that is not a global definition is a
    # package data record. We split the package record and create a
    # hash %p that contains key-value pairs for all of the global
    # definitions and the current record.

    my @values = split /\s*\|\s*/; 
    my %p = ( @Def,
	      map { $_ => shift(@values) } @Fields);

    # Create a simple id using the package name, package code and pin
    # count and then start a new element.

    $p{id} = join('-', map { $p{$_} } qw(package_name package_code pin_count));
    $Pcb -> element_begin(description => $p{id},
			  output_file => "tmp/$p{id}",
			  dim   => 'mm');
    print "$p{id}\n";

    # Create a few convenient specifications from data in the package
    # data record hash. The convenetions for these packages is part
    # centroid at (0,0) and pin one is in the lower left corner.

    # Corner pads on some of the parts are shorter. This condition is
    # handled by creating a new pad length and some pad center
    # offsets.

    $p{num_pads_per_side} = $p{pin_count} / 4; # leads on four sides
    $p{corner_pad_length}  = $p{pad_length} if $p{corner_pad_length} eq '';

    my $row_center = ($p{body_width_max} - $p{pad_length}) / 2;
    my $row_end    = ($p{num_pads_per_side} - 1) * $p{pad_spacing} / 2;
    my $corner_offset = ($p{pad_length} - $p{corner_pad_length}) / 2;

    # @xy contains the staring locations for a row of pads.
    # @inc contains increment values for x and y and offsets for the
    # the corner pads. for each row of pads either x or y is incremented.

    my @xy = (x => -$row_center, y => -$row_end,    
	      x => -$row_end,    y =>  $row_center, 
	      x =>  $row_center, y =>  $row_end,    
	      x =>  $row_end,    y => -$row_center);

    my @inc= (yinc =>  $p{pad_spacing}, xoffset => -$corner_offset,
	      xinc =>  $p{pad_spacing}, yoffset =>  $corner_offset,
	      yinc => -$p{pad_spacing}, xoffset =>  $corner_offset,
	      xinc => -$p{pad_spacing}, yoffset => -$corner_offset);

    # create the rows of pads
    
    &set_pin_num(1);

    while (@xy) {
	my %xy = (splice(@inc, 0, 4),
		  map { $_ => $p{$_} } qw(pad_spacing pad_width pad_length));

	&draw_row_of_pads(splice(@xy, 0, 4),
			  %xy,
			  pad_length => $p{corner_pad_length},
			  num_pads => 1);

	# no offsets for pads that aren't on the corners

	&draw_row_of_pads(%xy,
			  xoffset => 0,
			  yoffset => 0,
			  num_pads => $p{num_pads_per_side} - 2);

	&draw_row_of_pads(%xy,
			  pad_length => $p{corner_pad_length},
			  num_pads => 1);
    }

    # Add the thermal pad

    $Pcb -> element_add_pad_rectangle(x => 0,
				      y => 0,
				      length => $p{thermal_pad_length}, 
				      width  => $p{thermal_pad_width},
				      name => '',
				      pin_number => $p{pin_count} + 1);

    # add the pin one dot

    my $dot_pos = $row_center + 0.254; #$p{pad_length} / 2;

    $Pcb -> element_add_arc(x => -$dot_pos,
			    y => -$dot_pos,
			    width => $p{pad_width} / 2,
			    height=> $p{pad_width} / 2,
			    start_angle => 0,
			    delta_angle => 360,
			    thickness => 0.254); # 10 mil lines

    # draw a silksreen rectangle around the package body.

    $Pcb -> element_add_rectangle(x => 0,
				  y => 0,
				  width => $p{body_width_max} + 1,
				  length=> $p{body_length_max} + 1);

    # Set the position of the reference designator to the upper left corner

    $Pcb -> element_set_text_xy(x => -$p{body_length_max} / 2 - 1, 
				y => -$p{body_width_max}  / 2 - 1);

    # Set the centroid mark and output the element

    $Pcb -> element_output;

}

# $v{x}       current x location
# $v{y}       current y location
# $v{pin_num} current pin number

my %v;   # values for draw_row_of_pads

sub set_pin_num ($) { 
    $v{pin_num} = shift;
} 

sub draw_row_of_pads { 
    my %p = (xoffset => 0,
	     yoffset => 0,
	     @_);

    foreach (qw(pin_num x y)) {
	$v{$_} = $p{$_} if defined $p{$_};
    }

    # swap pad length and width for horizontal rows

    ($p{pad_width}, $p{pad_length}) = ($p{pad_length}, $p{pad_width})
	if defined $p{xinc};

    foreach (1..$p{num_pads}) {
	$Pcb -> element_add_pad_rectangle(x      => $v{x} + $p{xoffset},
					  y      => $v{y} + $p{yoffset},
					  width  => $p{pad_width},
					  length => $p{pad_length},
					  name   => '', 
					  pin_number => $v{pin_num}++);
	$v{x} += $p{xinc} if defined $p{xinc};
	$v{y} += $p{yinc} if defined $p{yinc};
    }
}




1;



__DATA__
body_width_min  = 4.9  # E
body_width      = 5.0  # E
body_width_max  = 5.1  # E
body_length_min = 4.9  # D
body_length     = 5.0  # D
body_length_max = 5.1  # D

component_type     = ic
package_name       = TQFN-Maxim-5x5

# Final pad_length = pad_lenth + body_width_max - body_width_min

# T1655-1   e (nom)  b, L, E2, D2 (max)
# T2055-2   e (nom)  b (max) L, E2, D2 (nom)
# T2055-5   e (nom)  b (max) L (min) E2, D2 (nom)

# T2855-1   e (nom)  b (max) L (min) E2, D2 (nom)
# T2855-2   e (nom)  b (max) L (min) E2, D2 (max)

# T3255-2   e (nom)  b (max) L (max) E2, D2 (max)
# T4055-1   e (nom)  b (min) L (nom) E2, D2 (min)


#                 e      b      L     L1      E2     D2
 T1655-1  | 16 | 0.8  | 0.35 | 0.5  |      | 3.2  | 3.2 
 T2055-2  | 20 | 0.65 | 0.35 | 0.55 |      | 3.10 | 3.10
 T2055-5  | 20 | 0.65 | 0.35 | 0.45 |      | 3.25 | 3.25

 T2855-1  | 28 | 0.50 | 0.30 | 0.45 |      | 3.25 | 3.25
 T2855-2  | 28 | 0.50 | 0.30 | 0.45 |      | 2.8  | 2.8 


 T3255-2  | 32 | 0.50 | 0.30 | 0.5  | 0.25 | 3.2  | 3.2 
 T4055-1  | 40 | 0.40 | 0.2  | 0.5  | 0.25 | 3.2  | 3.2 

# Style (adapted from the Perl Cookbook, First Edition, Recipe 12.4)

# 1. Names of functions and local variables are all lowercase.
# 2. The program's persistent variables (either file lexicals
#    or package globals) are capitalized.
# 3. Identifiers with multiple words have each of these
#    separated by an underscore to make it easier to read.
# 4. Constants are all uppercase.
# 5. If the arrow operator (->) is followed by either a
#    method name or a variable containing a method name then
#    there is a space before and after the operator.


