Thoughts on RF spectrum analyzers

spectrum_analyzer

Note: I’m contemplating designing a spectrum analyzer. The following are a compilation of my own notes that I’m making public. They might not make a lot of sense as a coherent treatise but take the pieces for what they are.

The classical way of performing RF spectrum analysis is to mix the input with a local oscillator (FLO1) that ramps between two higher frequencies, producing IF1.

IF1 is sent through a narrow band filter which leaves you with a narrow piece of the input spectrum that has been shifted up. The ramping action gives you a sliding window of the input spectrum, shifted up to the center frequency of the narrow bandpass filter.

Stepping back, remember that the IF1 frequency is the result of nonlinear mixing of the input with the local oscillator.
Let’s make some assumptions:

- the input (Fin) is any signal between zero and 1000 MHz.
- the local oscillator (FLO1) ramps between 1050 and 2050 MHz
- the narrow bandpass filter (FBP) is 1050 MHz

Mixing the local oscillator and the input with a non-ideal mixer gives you:

IF1 = N*input +- M*LO

The strongest output spectra will be where N=1 and M=1. This gives you spectra centered at:

(a) Fin + FLO1
and
(b) Fin – FLO1

Since FLO1 is always greater than Fin, we get a nice strong first image (a) between 1050 and 2050 MHz.
The first negative image (b) is wrapped into the range of 0 to 1050 MHz. Subsequent energies are present at higher orders, however their amplitude is orders of magnitude lower than the main images.

FILTERING
After mixing, IF1 is filtered using a high-Q filter. Q, of course, is a measure of how wide the pass-band is.
Q = Fc/Fbw where Fbw is the -3dB bandwidth.

Common hobbyist spectrum analyzers[1][2] use cavity or helical filters.

A cavity filter has radiative elements (I’ll liberally call antennae) in a conductive can along with a resonator whose length can be changed. They can and often contain multiple cavities

Here’s a paper from M/A COM on cavity and helical filter design.


References

[1] Scotty’s Modularized Spectrum Analyzer
[2] Andrew Holme – Homebrew spectrum analyser

HF/VHF photodiode amplifier

photodiodeamp

Recently at work I was tasked with determining the frequency modulation of a laser beam. I designed and tested the above circuit in a couple hours.

I tested the circuit first with no photodiode in place using my DG8SAQ vector network analyzer — a very low cost network analyzer based on the highly respected (by me) AD9852 DDS chip.

The circuit showed >+20dB gain from about 4 MHz to the cutoff frequency of the RLP-137. Hooray for small victories!

For the photodiode, I had no choice because I just had one laying around here that I could find the part number for: a Melles Griot 13DSI001. This particular part has a responsivity of 0.45 Amps/Watt at 830nm, breakdown voltage of 60, and 10pF capacitance.

A photodiode is typically reverse biased as you see above, with a positive voltage on the cathode. In my case I don’t care very much about what voltage I’m using as long as I do not exceed the breakdown voltage (more about that coming up.)

As light becomes incident on the diode junction, carriers are created that allow current to flow from the high potential to the low potential. This holds true for high frequencies; a high frequency alternating current is allowed through the device.

When a current source is terminated into a resistor we get a voltage source having an impedance of the resistor value. The photodiode is terminated into a 50 ohm resistor, hence we have a fairly decent 50 ohm source that is converting light modulation into RF of a convenient impedance. The maximum frequency (as I understand) is limited by the 10pF intrinsic capacitance of D1, combined with the load resistance, R3. This gives a cutoff frequency of 1/(2*Pi*RC) of about 300 MHz. I may not have needed the lowpass filter (RLP-137), however with my shoddy perf-board construction I was worried about the GALI-74 potentially oscillating in the GHz range.

The 1k resistor R1 is to limit the maximum current that can pass through the photodiode. Combined with C3, this provides filtering from feedback or noise that could come through VCC.

I’d be happy to see your comments below if you have corrections or advice.

MMIC photodiode amplifier

MMIC photodiode amplifier

Setting up NFS client on Beaglebone black

Really short HOWTO here. I want to share files from my desktop running Ubuntu, to my Beaglebone Black, which is running Angstrom Linux. Here’s how I did it:

  1. Install nfs-server on the Ubuntu machine
# apt-get install nfs-kernel-server

2. Edit /etc/exports, adding this line:

/home/martin/beaglebone_nfs 192.168.1.125(rw)

3. Create the directory on the server side (as normal user)

$ mkdir /home/martin/beaglebone_nfs

4. Install nfs-utils-client on Beaglebone (Angstrom) side

root@beaglebone:~# opkg update
root@beaglebone:~# opkg install nfs-utils-client

5. Re[start] the NFS server on the Ubuntu side

# service nfs-kernel-server restart

6. Mount your NFS share on the Beaglebone side

root@beaglebone:~# mount -onolock -t nfs 192.168.1.128:/home/martin/beaglebone_nfs /mnt

7. And to make it mount every time you reboot, add a line to /etc/fstab:

192.168.1.128:/home/martin/beaglebone_nfs /mnt nfs defaults,nolock 0 0

8. Unmount and remount from fstab to ensure that you got the fstab line correct (without rebooting)

# umount /mnt
# mount -a
# df
Filesystem                                1K-blocks     Used Available Use% Mounted on
rootfs                                      1738184  1175444    472776  72% /
/dev/root                                   1738184  1175444    472776  72% /
devtmpfs                                     255280        0    255280   0% /dev
tmpfs                                        255408        0    255408   0% /dev/shm
tmpfs                                        255408      220    255188   1% /run
tmpfs                                        255408        0    255408   0% /sys/fs/cgroup
tmpfs                                        255408        4    255404   1% /tmp
192.168.1.128:/home/martin/beaglebone_nfs  22145024 15797248   5199872  76% /mnt

And there you have it.

Writing a PCI device driver

I’m writing a Linux device driver for the PCI/PCIe cards 5i25 and 6i25 available from MESA electronics[1]

I’ve never written a device driver before. Currently my driver does nothing except enable the PCI device, map the BAR0 to system address space, and check the “magic value” available in BAR0 + 0×100.

It then prints out all of the probed module descriptors that correspond to available hardware functionality.

The magic value is an arbitrary number to test that your driver is accessing the PCI device correctly.

The next step is to accept module load-time parameters and configure the board as desired. Mesa has made a very capable board with the HOSTMOT2 firmware[4]. It has many options and many configuration registers.

Beyond configuration, the driver will have to accept IOCTL calls from user programs and drive the 6i25.

Here’s the code:

/*  
 *  ni25.c
 * 
 * Author: Martin Klingensmith
 * Description: Driver for Mesa electronics 5i25 and 6i25 PCI[e] boards
 * 
 */
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/module.h>	/* Needed by all modules */
#include <linux/kernel.h>	/* Needed for KERN_INFO */

#define DRIVER_AUTHOR "Martin Klingensmith <martin@nnytech.net>"
#define DRIVER_DESC "A driver for MESA electronics 5i25/6i25 IO cards"
#define DRIVER_NAME "ni25"

#define MAGIC_NUMBER 0x55AACAFE	/* Magic number defined by Mesa */
#define LEDS_OFFSET 0x0200
#define IDROM_OFFSET 0x010C	/* Location of IDROM offset value */

struct ni25_idrom {
  u32			IDROMType;
  u32			OffsetToModules;
  u32			OffsetToPindesc;
  u32			BoardNameLow;
  u32			BoardNameHigh;
  u32			FPGASize;
  u32			FPGAPins;
  u32			IOPorts;
  u32			IOWidth;
  u32			PortWidth;
  u32			ClockLow;
  u32			ClockHigh;
  u32			InstanceStride0;
  u32			InstanceStride1;
  u32			RegisterStride0;
  u32			RegisterStride1;
};

struct module_descriptor {
  u8			GTag;		/*General function tag					*/
  u8			Version;	/*module version					*/
  u8			ClockTag;	/*Whether module uses ClockHigh or ClockLow		*/
  u8			Instances;	/*Number of instances of module in configuration	*/
  u16			BaseAddress;	/*offset to module. This is also specific register = Tag*/
  u8			Registers;	/*Number of registers per module			*/
  u8			Strides;	/*Specifies which strides to use			*/
  u32			MPBitmap;	/*bit map of which registers are multiple
					  '1' = multiple, LSb = reg(0)				*/
};

struct pin_descriptor {
  u8			SecPin;		/*Which pin of secondary function connects here 
					  eg: A,B,IDX. 
					  Output pins have bit 7 = '1'*/
  u8			SecTag;		/*Secondary function type (PWM,QCTR etc). 
					  Same as module GTag*/
  u8			SecUnit;	/*Which secondary unit or channel connects here*/
  u8			PrimaryTag;	/*Primary function tag (normally I/O port)*/
};

struct ni25_brd {
  unsigned long           paddr;
  void __iomem            *regs;	/* Board BAR0 mapped location */
  u32                     iolen;
  int                     irq;
  u16                     bus_num;
  struct device           *parent_dev;
  u16                     num_cs;         /* supported slave numbers */

};

struct ni25_dev {
  struct pci_dev *pdev;
  struct ni25_brd brd;
  struct ni25_idrom idrom;
  u32	idrom_offset;
  struct module_descriptor module[32];
  u32	num_modules;
};

/* 
 *
 * ni25_pci_probe 
 *
 */
static int ni25_pci_probe(struct pci_dev *pdev,
	const struct pci_device_id *ent)
{
	struct ni25_dev *ni25;
	struct ni25_brd *brd;
	int pci_bar = 0;
	int ret;
	int v=0;
	void __iomem *regs;
	char name[9];

	printk(KERN_INFO "NI25: found PCI card (ID: %04x:%04x)\n",
		pdev->vendor, pdev->device);

	ret = pci_enable_device(pdev);
	if (ret)
		return ret;

	ni25 = kzalloc(sizeof(struct ni25_dev), GFP_KERNEL);
	if (!ni25) {
		ret = -ENOMEM;
		goto err_disable;
	}

	ni25->pdev = pdev;
	brd = &ni25->brd;

	/* Get basic io resource and map it */
	brd->paddr = pci_resource_start(pdev,pci_bar);
	brd->iolen = pci_resource_len(pdev, pci_bar);

	printk(KERN_INFO "NI25: address %04x length %04x\n",(unsigned int)brd->paddr,(unsigned int)brd->iolen);

	ret = pci_request_region(pdev, pci_bar, dev_name(&pdev->dev));
	if (ret)
		goto err_kfree;

	regs = ioremap_nocache((unsigned long)brd->paddr,
				pci_resource_len(pdev, pci_bar));
	brd->regs = regs;

	if (!brd->regs) {
	       printk(KERN_INFO "NI25: ioremap_nocache call failed.\n");
		ret = -ENOMEM;
		goto err_release_reg;
	}

	/* Read magic number from card register */
	v = readl(regs + 0x100);
	if(v != MAGIC_NUMBER){
	  printk(KERN_INFO "NI25: unknown magic number. %04x != %04x\n",MAGIC_NUMBER,v);
	  ret = -1;
	  goto err_unmap;
	}

	/* Get configuration name */
	for(v=0;v<8;v++){
	  name[v] = ioread8(regs+0x104+v);
	}
	name[8]=0;
	printk(KERN_INFO "NI25: config name: [%s]\n",name);

	ni25->idrom_offset = ioread32(regs+IDROM_OFFSET);

	printk(KERN_INFO "NI25: idrom offset: %04x\n",ni25->idrom_offset);

	/* Read IDROM */
	memcpy(&ni25->idrom,regs+ni25->idrom_offset,sizeof(struct ni25_idrom));

	printk(KERN_INFO "NI25: modules offset: %04x\n",ni25->idrom.OffsetToModules);

	/* Read all modules descriptors */
	memcpy(ni25->module,regs+ni25->idrom.OffsetToModules + ni25->idrom_offset,0xC * 32);

	for(v=0;v<32;v++){
	    if(ni25->module[v].GTag==0){
	      ni25->num_modules = v-1;
	      break;
	    }
	    printk(KERN_INFO "NI25: module %d \n\tGTag=[%04x]\n\tVersion=[%04x]\n\tClockTag=[%04x]\n\tInstances=[%04x]\n\tBaseAddress=[%04x]\n\tRegisters=[%04x]\n\tStrides=[%04x]\n\tMPBitmap=[%04x]\n"\
							    ,v,ni25->module[v].GTag, \
							    ni25->module[v].Version, \
							    ni25->module[v].ClockTag, \
							    ni25->module[v].Instances, \
							    ni25->module[v].BaseAddress, \
							    ni25->module[v].Registers,\
							    ni25->module[v].Strides,\
							    ni25->module[v].MPBitmap);
	}

	printk(KERN_INFO "NI25: found %d modules.\n",ni25->num_modules);

	brd->parent_dev = &pdev->dev;
	brd->bus_num = 0;
	brd->num_cs = 4;
	brd->irq = pdev->irq;

	pci_set_drvdata(pdev, ni25);
	return 0;

err_unmap:
	iounmap(brd->regs);
err_release_reg:
	pci_release_region(pdev, pci_bar);
err_kfree:
	kfree(ni25);
err_disable:
	pci_disable_device(pdev);
	return ret;
}

/* 
 *
 * 
 * ni25_pci_remove 
 * 
 * 
 */
static void ni25_pci_remove(struct pci_dev *pdev)
{
	struct ni25_dev *ni25 = pci_get_drvdata(pdev);
	printk(KERN_INFO "ni25: unloading...\n");
	iounmap(ni25->brd.regs);
	pci_release_region(pdev, 0);
	kfree(ni25);
	pci_disable_device(pdev);
}
/* 
 *
 * ni25_suspend */
#ifdef CONFIG_PM
static int ni25_suspend(struct pci_dev *pdev, pm_message_t state)
{
	pci_save_state(pdev);
	pci_disable_device(pdev);
	pci_set_power_state(pdev, pci_choose_state(pdev, state));
	return 0;
}
/* ni25_resume */
static int ni25_resume(struct pci_dev *pdev)
{
	u32 ret=-1;
	pci_set_power_state(pdev, PCI_D0);
	pci_restore_state(pdev);
	ret = pci_enable_device(pdev);
	if (ret)
		return ret;
	return 0;
}
#else
#define spi_suspend	NULL
#define spi_resume	NULL
#endif

/* Documentation */
MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);

static DEFINE_PCI_DEVICE_TABLE(pci_ids) = {
	/* Intel MID platform SPI controller 0 */
	{ PCI_DEVICE(0x2718, 0x5125) },
	{},
};

static struct pci_driver ni25_driver = {
	.name =		DRIVER_NAME,
	.id_table =	pci_ids,
	.probe =	ni25_pci_probe,
	.remove =	ni25_pci_remove,
	.suspend =	ni25_suspend,
	.resume	=	ni25_resume,
};

module_pci_driver(ni25_driver);

Here are the dmesg lines when the module is loaded:

[64520.720535] NI25: found PCI card (ID: 2718:5125)
[64520.720553] NI25: address fe700000 length 10000
[64520.720606] NI25: config name: [HOSTMOT2]
[64520.720610] NI25: idrom offset: 0400
[64520.720635] NI25: modules offset: 0040
[64520.720771] NI25: module 0 
[64520.720771]  GTag=[0002]
[64520.720771]  Version=[0000]
[64520.720771]  ClockTag=[0001]
[64520.720771]  Instances=[0001]
[64520.720771]  BaseAddress=[0c00]
[64520.720771]  Registers=[0003]
[64520.720771]  Strides=[0000]
[64520.720771]  MPBitmap=[0000]
[64520.720783] NI25: module 1 
[64520.720783]  GTag=[0003]
[64520.720783]  Version=[0000]
[64520.720783]  ClockTag=[0001]
[64520.720783]  Instances=[0002]
[64520.720783]  BaseAddress=[1000]
[64520.720783]  Registers=[0005]
[64520.720783]  Strides=[0000]
[64520.720783]  MPBitmap=[001f]
[64520.720794] NI25: module 2 
[64520.720794]  GTag=[0004]
[64520.720794]  Version=[0002]
[64520.720794]  ClockTag=[0001]
[64520.720794]  Instances=[0002]
[64520.720794]  BaseAddress=[3000]
[64520.720794]  Registers=[0005]
[64520.720794]  Strides=[0000]
[64520.720794]  MPBitmap=[0003]
[64520.720805] NI25: module 3 
[64520.720805]  GTag=[00c1]
[64520.720805]  Version=[0000]
[64520.720805]  ClockTag=[0001]
[64520.720805]  Instances=[0001]
[64520.720805]  BaseAddress=[5a00]
[64520.720805]  Registers=[0006]
[64520.720805]  Strides=[0010]
[64520.720805]  MPBitmap=[003c]
[64520.720816] NI25: module 4 
[64520.720816]  GTag=[0006]
[64520.720816]  Version=[0000]
[64520.720816]  ClockTag=[0002]
[64520.720816]  Instances=[0002]
[64520.720816]  BaseAddress=[4000]
[64520.720816]  Registers=[0005]
[64520.720816]  Strides=[0000]
[64520.720816]  MPBitmap=[0003]
[64520.720828] NI25: module 5 
[64520.720828]  GTag=[0005]
[64520.720828]  Version=[0002]
[64520.720828]  ClockTag=[0001]
[64520.720828]  Instances=[0008]
[64520.720828]  BaseAddress=[2000]
[64520.720828]  Registers=[000a]
[64520.720828]  Strides=[0000]
[64520.720828]  MPBitmap=[01ff]
[64520.720839] NI25: module 6 
[64520.720839]  GTag=[0080]
[64520.720839]  Version=[0000]
[64520.720839]  ClockTag=[0001]
[64520.720839]  Instances=[0001]
[64520.720839]  BaseAddress=[0200]
[64520.720839]  Registers=[0001]
[64520.720839]  Strides=[0000]
[64520.720839]  MPBitmap=[0000]
[64520.720848] NI25: found 6 modules.

 

References

[1] Mesa Electronics
http://www.mesanet.com/

[2] The Linux Kernel Module Programming Guide
http://www.tldp.org/LDP/lkmpg/2.6/html/x121.html

[3] How To Write Linux PCI Drivers by Martin Mares <mj@suse.cz> on 07-Feb-2000
http://galileo.phys.virginia.edu/~rjh2j/l2beta/software/pci.txt

[4] LinuxCNC Documentation wiki
http://wiki.linuxcnc.org/cgi-bin/wiki.pl?HostMot2

 

New L6470 Stepper motor controller board in progress.

boardimage

Coming soon

4 Channels L6470

USB interface (micro USB)
8 MHz oscillator for L6470 and PIC microcontroller (PIC24FJ64GB002)
Header for external SPI interface if using Arduino or different microcontroller board.
Headers for step and direction interface, for microcontroller or parallel port PC interface.

 UPDATE

I haven’t worked on this since October 2, 2013 (and it’s now January 21, 2014) so I’m uploading the Eagle [6] design files for you to use as you see fit. If you actually build this board I would love to see how it comes out!

NOTE: I have NOT tested this board design but I believe it works. I am a professional but that doesn’t mean my work is flawless.

 EAGLE files for this board

DDS Board Revision 2

Recently I created revision 2 of my DDS board:

2013-09-19 14.32.40

The board is basically the same as the last one I wrote about.

On this board I used Mini-Circuits filter modules instead of my homebrew RLC filters. They offer better high frequency performance than I could achieve on my own. Here’s a plot of the filter response, as tested with my VNWA:

dds_plugin_filter_response

 

The filters allow a usable output from about 40 MHz to 110 MHz. By bypassing the high-pass filter, I can get a usable output from near-DC to 110 MHz.

 

This board is part of a much larger project. You might have noticed that one PCB is stacked onto the other PCB. The board on the left is a module that will be plugged into a large VME board. The purpose of building the right-side board was to test the module.

USB Interface and control

I’ve done several USB projects, starting back when I worked at Embed Inc. I decided it would be fun to make this a USB controlled board. The microcontroller is a PIC24FJ128GB106 from Microchip. It has a high-speed USB interface. Microchip provides sample code for various interface methods.

I chose to make this a CDC (Character Device Class) because it makes writing the PC software easier. A CDC device presents itself as a VCP (Virtual COM Port) on Windows, and I believe on Linux as well. This means I don’t have to write a device driver or utilize WinUSB or Libusb in order to communicate with the hardware.

I started out with a basic MFC program that I had written to query a laser over it’s diagnostics port, and modified it to control my PIC firmware that, in turn, controls the AD9852. The program is very basic at this point, but the structure now exists to do just about anything with it. I’m not personally invested in amateur radio, but I imagine this could be interesting for creating directly-modulated RF carriers. This board can directly output an RF signal that is amplitude, frequency, on-off, or phase keyed.

dds_prog

In my free time I am working on another version of this board that I may make available to the community. Please leave a comment if you’re interested.

 

 

DDS Board testing

 

 

I mentioned that I was building a DDS (Direct Digital Synthesis) board for work. Here’s the result:

 

2013-04-11 09.34.43

 

Yes that’s a giant block of aluminum that it’s attached to. It keeps the board from sliding around with all those cables attached to it.

It seems to work quite well. The first thing I tested was the bandpass filter. I designed it to have a center of 100 MHz and a bandwidth of 80 MHz. What do you know? Sometimes things work out:

filter_response

 

That looks pretty decent to me, but I’m not an RF expert.

Now a chart of the output with the MMIC fully powered up. The AD9852 is being clocked at 260 MHz instead of the desired 300 MHz (for a reason I’ll describe below.) The AD9852 is programmed to output a fundamental frequency of 100 MHz in this test.

output_response

 

I thought I had a screen shot zoomed in on the 100 MHz peak, alas I do not. The noise floor when zoomed in is much better than the spectrum analyzer shows in the above plot (I promise.)

I had to run the AD9852 at 260 MHz carrier instead of 300 MHz because the LMK03001C chip I am using cannot output a 300 MHz clock, given a frequency of 20 MHz. The PLL configuration for the LMK03001C is very flexible, just not quite enough for this. For the next board I am switching to the LMK03000C which has a slightly different VCO. It’ll work.

This synthesizer was a test for my next big project: a huge VME bus board that has 4 AD9852 channels on it. I’m proud to say that this test succeeded.

USB DDS board in progress

I’m working on a USB DDS (direct digital synthesis) board that will be a plug-in module for another application. Here’s a block diagram:

It has a few interesting features that I thought others might be interested in:

 

- AD9852 direct digital synthesis IC with 300 MHz adder, 48 bit accumulator, and digital inverse sinc filter
- A fairly powerful microcontroller with full speed USB
- A low jitter clock synthesizer PLL: LMK03001C
- An MCP3553 22 bit A/D converter
- Gain block chip from Mini Circuits: GALI-74+
- 6 layer PCB construction for excellent ground and VDD planes

These features should give this board possibilities in amateur radio and perhaps homebrew instrumentation. Changing the filter components could allow usage from very low frequencies up to around 125 MHz.

Utilizing upper frequency images would give you usable range into 500 MHz or possibly a bit more depending on how good the RF layout is. The PCB is FR4 and uses standard 0603/0805 discrete parts, so it probably won’t give usable signals into the microwave region.

I’ll share more once I have the schematic done.

 

Mapping Google Drive or Dropbox to a drive letter

Aside

So you’re on Windows and want to use Dropbox or Google Drive with it’s own drive letter?

Step 1:
First figure out where your Dropbox or Google Drive folder is stored. Go to ‘Computer’ and right click on Dropbox or Google Drive and copy the whole path name that is in the text field labeled Target. Mine happens to be:
“E:\googledrive\Google Drive”

But yours is probably something like:
“C:\Users\Martin\Google Drive”

Make sure you copy the quotes too.

Step 2:
Run notepad and put one line in the new file. Replace the path with the one you found in step 1.

Now, save this file on your Desktop as a file named: drive letter.bat

Close notepad.

Step 3:
Double-click the file you just saved. You should have a new drive called G:
If not, you did something wrong.

Step 4:
Drag this batch file (drive letter.bat) to the start menu, and put it in:
Start->All Programs->Startup

It’ll start up every time you login to your computer, and if everything went right you’ll always have a drive G:

 

Using Python scripts to create Eagle parts

I dread creating part models in Eagle – it’s time consuming and error prone.

Over the years I have used scripting languages to help with the tedious work. It’s quite easy to create basic part footprint algorithms given the mechanical drawing. Much more tedious would be drawing a picture on graph paper and using your calculator.

If you’ve ever made a circuit board and made a very basic error that caused you to have to remanufacture your board, you’ll understand the value of getting it right the first time around.

Take this extremely simple script for example:

#!/usr/bin/python
x=0.0
y=0.0
pins=24
print 'EDIT TSSOP_' + str(pins) + '.pac'
for p in range(1, pins/2+1):
 x = (p-1)*0.5
 y = -4.15
 print 'SMD 0.27 1.0 \'' + str(p) + '\' (' + str(x) +' ' + str(y) + ');'
 print 'SMD 0.27 1.0 \'' + str(pins+1-p) + '\' (' + str(x) +' ' + str(-y) + ');'

You could really write this in any language and it would be about the same length. There’s no reason it has to be done in Python. The value is in what it prints out:

EDIT TSSOP_24.pac
SMD 0.27 1.0 '1' (0.0 -4.15);
SMD 0.27 1.0 '24' (0.0 4.15);
SMD 0.27 1.0 '2' (0.5 -4.15);
SMD 0.27 1.0 '23' (0.5 4.15);
SMD 0.27 1.0 '3' (1.0 -4.15);
SMD 0.27 1.0 '22' (1.0 4.15);
SMD 0.27 1.0 '4' (1.5 -4.15);
SMD 0.27 1.0 '21' (1.5 4.15);
SMD 0.27 1.0 '5' (2.0 -4.15);
SMD 0.27 1.0 '20' (2.0 4.15);
SMD 0.27 1.0 '6' (2.5 -4.15);
SMD 0.27 1.0 '19' (2.5 4.15);
SMD 0.27 1.0 '7' (3.0 -4.15);
SMD 0.27 1.0 '18' (3.0 4.15);
SMD 0.27 1.0 '8' (3.5 -4.15);
SMD 0.27 1.0 '17' (3.5 4.15);
SMD 0.27 1.0 '9' (4.0 -4.15);
SMD 0.27 1.0 '16' (4.0 4.15);
SMD 0.27 1.0 '10' (4.5 -4.15);
SMD 0.27 1.0 '15' (4.5 4.15);
SMD 0.27 1.0 '11' (5.0 -4.15);
SMD 0.27 1.0 '14' (5.0 4.15);
SMD 0.27 1.0 '12' (5.5 -4.15);
SMD 0.27 1.0 '13' (5.5 4.15);

If you open up the EAGLE Library editor and paste this script into the command console, you get this:

Not a bad start.

The same technique applies for much larger parts. Imagine how much time you’ll save when you’re creating a part model for a 256+ ball grid array?

I must say I don’t claim to be an expert Python coder – I have my own specialties and I’m really a low-level embedded systems guy. If you need a turn-key shoe-phone (that’s a joke) or a radio watch (if you don’t get the joke you’re too young) or an accelerometer based movement tracker with 6 months battery life, I’m your man.

I’m just rambling – back on topic: there’s another script I wrote that you can access via pastebin (click “see original”)

This script takes a CSV file and a model name as command line options, i.e:

$ ~/bin/csv2model.py sn74.csv sn74vme22501a

And produces helpful Eagle SCR data that you can call from Eagle to make a schematic symbol, and also to CONNECT the schematic part model PINs to the physical layout PADs. i.e:

EDIT sn74vme22501a.sym;
PIN '1OEBY' Short (0 0.0)
PIN '1A' Short (0 -0.1)
PIN '1Y' Short (0 -0.2)
PIN 'GND' Short (0 -0.3)
PIN '2A' Short (0 -0.4)
PIN '2Y' Short (0 -0.5)
PIN 'VCC' Short (0 -0.6)
PIN '!2OEBY' Short (0 -0.7)
PIN '3A1' Short (0 -0.8)
PIN 'GND_2' Short (0 -0.9)
PIN 'LE' Short (0 -1.0)
PIN '3A2' Short (0 -1.1)
PIN '3A3' Short (0 -1.2)
PIN 'OE' Short (0 -1.3)
PIN 'GND_3' Short (0 -1.4)

and:

CONNECT sn74vme22501a.1OEBY 1;
CONNECT sn74vme22501a.1A 2;
CONNECT sn74vme22501a.1Y 3;
CONNECT sn74vme22501a.GND 4;
CONNECT sn74vme22501a.2A 5;
CONNECT sn74vme22501a.2Y 6;
CONNECT sn74vme22501a.VCC 7;
CONNECT sn74vme22501a.!2OEBY 8;
CONNECT sn74vme22501a.3A1 9;
CONNECT sn74vme22501a.GND_2 10;
CONNECT sn74vme22501a.LE 11;
CONNECT sn74vme22501a.3A2 12;
CONNECT sn74vme22501a.3A3 13;
CONNECT sn74vme22501a.OE 14;
CONNECT sn74vme22501a.GND_3 15;

The CSV file contains (partially):

PIN,NAME
1,1OEBY
2,1A
3,1Y
4,GND
5,2A
6,2Y
7,VCC
8,!2OEBY
9,3A1
10,GND
11,LE
12,3A2
13,3A3
14,OE
15,GND

 

 


Here’s another script I generated for the FT256 / FTG256 package which is used by some Xilinx FPGAs:

#!/usr/bin/python
# Create BGA SCR for Eagle 5.x
print 'GRID MM;'
rows = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'R', 'T']
cols = ['1', '2', '3', '4', '5', '6', '7', '8', '9' ,'10', '11', '12', '13', '14', '15', '16']
nrows = 16
ncols = 16
pitch = 1.0 # ball spacing in millimeters
padsize = 0.4 # SMD pad size in millimeters
xstart = -(nrows - 1) / 2.0
ystart = (ncols - 1) / 2.0
x = xstart
y = ystart
for row in rows:
 x=xstart
 for col in cols:
 print 'SMD ' + str(padsize) + ' ' + str(padsize) + ' -100 \'' + row + col + '\' (' + str(x) +' ' + str(y) + ');'
 x=x+pitch
 y=y-pitch
print 'LAYER 21;' #tplace
print 'WIRE .254MM (' + str(x) + ' ' + str(y) + ') (' + str(-x) + ' ' + str(y) + '); '
print 'WIRE .254MM (' + str(-x) + ' ' + str(y) + ') (' + str(-x) + ' ' + str(-y) + '); '
print 'WIRE .254MM (' + str(-x) + ' ' + str(-y) + ') (' + str(x) + ' ' + str(-y) + '); '
print 'WIRE .254MM (' + str(x) + ' ' + str(-y) + ') (' + str(x) + ' ' + str(y) + '); '
 
print 'LAYER 25; \n\
TEXT \'>NAME\' ('+ str(-x) +' '+ str(-y+.5) +'); \n\
CHANGE SIZE 0.05IN ('+ str(-x) +' '+ str(-y+.5) +'); \n\
CHANGE RATIO 20% ('+ str(-x) +' '+ str(-y+.5) +'); \n\
CHANGE FONT VECTOR ('+ str(-x) +' '+ str(-y+.5) +'); \n\
LAYER 27; \n\
TEXT \'>VALUE\' ('+ str(-x) +' '+ str(-y+2.5) +'); \n\
CHANGE SIZE 0.05IN ('+ str(-x) +' '+ str(-y+2.5) +');\n\
CHANGE RATIO 20% ('+ str(-x) +' '+ str(-y+2.5) +'); \n\
CHANGE FONT VECTOR ('+ str(-x) +' '+ str(-y+2.5) +');\n\
LAYER 51; \n\
TEXT \'>NAME\' (0 0); \n\
CHANGE SIZE 0.03IN (0 0); \n\
CHANGE RATIO 10% (0 0); \n\
CHANGE FONT VECTOR (0 0); \n\
'

And here’s what it produces, which can be run right in the Eagle Library editor:

 
Which produces this in Eagle 5.x: