Professor Mark Csele

Logic Analyzer IV (USB)


When your hobby is collecting old computers (including a few 6800 processors on old arcade machines), a logic analyzer proves invaluable to diagnose a multitiude of problems. This particular project is a blend of hardware, both discrete logic and microcontrollers, and software in both assembly code and C++. The actual analyzer was inspired from the original article “High Speed Logic Analyzer for Windows 95” appearing in Circuit Cellar Dec. 97 pp. 46 by Francis Deck. The basic system uses a special FIFO chip (7204) which is dual-ported to store logic-state samples which are transferred into a PC and displayed using a Win-32 front-end. The original analyzer hardware, built back in 1998, featured a front-end written in BASIC however there are many shortcomings to this approach and the system ‘hangs’ if an invalid trigger channel is selected. In this updated project (now over twelve years old and updated many times), a multi-threaded Win-32 front-end was written allowing good user control of the hardware. The system shown here features a 50MS/s sample rate, 4K sample depth, a trigger system to start sampling on a rising or falling edge on any particular channel, 3.3V and 5V logic family compatibility, and a high-speed USB interface.

The logic analyzer packaged in a plastic box. Coloured tip-jacks on the front correspond to individual channel inputs with several extra jacks for auxiliary trigger inputs and outputs.

A screen-shot of the Win-32 front-end for the analyzer (an older version 3.100). Eight traces are shown on the wide display (optimized for a 1024 * 768 display). Pull-down menus allow the user to select sample rate, system voltage, trigger source, and other parameters. The sampling thread status is shown in the lower right corner – this indicates the status of the USB connection as well. Only 400 samples are shown on the display, but use of the arrow keys allows a left/right panning of the display to see all 4096 samples in trace memory. A planned upgrade (incomplete currently) is a pattern-matching trigger which will start a trace when the pattern on the eight inputs matches a known value (useful when monitoring microprocessor busses) – currently triggering is accomplished on one selected channel only.

Hardware and Firmware

The key element of the analyzer is the AM7204 FIFO chip. This is a 4K dual-ported RAM chip which has separate read (Qx) and write (Dx) data paths. Each data path also has an address counter. To sample logic states into the chip, one first resets the counters via the /RS line and then provides clock pulses to the write clock (/w) on the chip at which point logic states on the write bus inputs (Dx) are stored into the chip. When the FIFO is full, a ‘buffer full’ signal (/FF) is asserted from the chip. To read from the chip, one supplies a read clock (/R) – on every pulse the chip transfers a byte from the memory to the read bus outputs (Qx). Like the ‘write’ cycle, 4095 clock pulses are required to read all 4096 bytes. Refer to the AMD datasheet (P/N 14430) for full details and timing of the chip.

While the original version featured direct control of the FIFO chip via the parallel port of the PC, provisions were made even in the original version for upgrade to an intelligent processor: at one stage it was even considered to add a microcontroller (probably a Z-8, back then) allowing interface to a colour graphics terminal such as a DEC-300 series. As the years progressed, the PC became an obvious choice and in the mid-2000’s, USB became obvious (the first hint being that my new laptop lacked both a serial and a parallel connector :).

A PIC18F4550 microcontroller communicates with the PC via a USB port and controls hardware functions including sample clock rate, read clock (to read stored data from the FIFO), and trigger source. The actual sample clock is provided by a 50MHz crystal oscillator divided into many frequencies using a 74HCT4040 CMOS counter (earlier versions used a 20MHz oscillator as limited by the speed of the FIFO chip – the latest version uses a faster “L12” chip which operates much faster than previous versions). This provides the following sample rates: 50MS/s, 12.5MS/s, 3.125MS/s, 780KS/s, 195KS/s, 48.8KS/s, and 12.2KS/s. These rates can be changed by rewiring the counter outputs (and using a different crystal oscillator) but were chosen since each sample rate is four-times slower than the last.

Through the main interface the analyzer may be placed in an idle condition, reset, enabled for sampling, and data read from the FIFO. The idle (non-running) condition is asserted by bringing the enable line high (disabling clocking), reset line high, and read clock line high. Reset is effected by toggling the reset line low then high again. Data sampling is started by selecting a clock rate (via a 74HCT151 multiplexer) and enabling the clock by bringing that line low – triggering may also be selected first if used. Finally, reading data from the FIFO is accomplished by resetting the FIFO chip again, which resets the read pointer to zero, and toggling read clock which places data from the FIFO onto the ‘Q’ outputs of the chip (which are then read by the PIC).

One final planned upgrade is a pattern-match trigger can be implemented using a 74HCT688 which will trigger sampling when a pattern appears (e.g. when connected to a data bus and the ASCII letter “A” {1000001} appears or to match a condition on several enable lines which must occur simultaneously). For the pattern-match trigger, it is planned to upload the pattern to a 74HCT595 serial-in latch using control lines then have the match condition trigger the analyzer via the eigth channel (so that “channel 8” trigger source is no longer available). One could even use “>” and “<” conditions provided by the ‘688 as trigger sources as well if required (although the ‘equal’ condition is by far the most useful).

The discrete circuitry was constructed on a 44-pin plug-in proto board with a ground plane on the top for noise immunity. The original board had several unused areas which are now populated by a 18F4550 40-pin microcontroller for the USB interface, and an area reserved for future expansion of several discrete logic chips for a pattern-match trigger. A USB jack is installed in the rear beside the DB-25 connector already in place (the DB-25 connector, used in versions 1 and 2, was left in place and, in fact, can still be used if the microcontroller is removed from it’s socket).

The full UPDATED Schematic for version 4 can be found here (in PDF format). An RJ-12 connector is permanently installed on the board allowing quick upgrades using an ICD-2 debugger … when unused the connector is simply disconnected and tucked-into the case. Pins RB6 and RB7 are unused for any other purpose.

A 74AHC573 is used as an input buffer (allowing 3.3V operation), and two relays (driven from RA0 and RA1 on the microcontroller) select Vcc (3.3V, by default, or 5V) and either a pull-up (to Vcc) or pull-down (to ground) function for all unused inputs. The 74AHC573 was used since it has a very minimal propogation delay, can operate from 3.3V or 5V, and even at 3.3V can tolerate 5V inputs. The actual logic levels are defined such that at 3.3V they correspond to true TTL levels (ideally, 0.8V for low and 2.0V for high are true TTL levels but the AHC573 operating on a 3V supply has levels of 0.9V and 2.1V accordingly – see the excerpt from the NXP, formerly Philips, datasheet to the right). When 5V TTL logic is selected as the target supply level, this chip is actually run at Vcc=3.3V for true TTL compatibility, and only when CMOS 5V logic is selected is the Vcc of this chip brought to 5V (at which point the chip has logic transitions at 1.65V and 3.85V and so is MOS compatible). Even with a 3.3V supply, the output level of the AHC573 is still 2.9V minimum for a logic “high” (and 0.1V maximum for a logic “low” output) and so it is still TTL compatible and can drive the input of other chips in the circuit which are powered from a 5V supply such as the 74HCT573 already in the circuit (or the FIFO, directly, for that matter). Note that only the 74AHC573 version can do this trick: the HCT version (as used on previous versions of the logic analyzer) is strictly 5V only with logic levels of 2.0V and 0.8V – choice of logic family (and examination of the details in the datasheet) are crucial for this project. A common LD1086 regulator (in a TO-220 package) is used for the 3.3V regulator – an input of +9V is required for this regulator (+5V is not sufficient to ensure 3.3V output).

As far as speeds go, it is important that all chips be fast enough. The existing 74HC573AN has a 40ns typical propogation delay, the existing 74HCT251N has a 19ns delay. This is a good situation since, in essence, the 573 acts as a delay allowing the trigger signal to “catch up” with the signal at the data lines of the FIFO. For this reason, the new AHC573 was added in addition to the existing HCT573. Finally, the 74HC4040N is rated at 79MHz so operation at 50MHz is allowed.

Port D on the 18F4550 is used to input data from the FIFO in 8-bit parallel format (the 74157 nybble multiplexer in the previous version is no longer required). Port B handles control lines to set sample rate and enable the clock, and Port C handles control lines for trigger source selection.

The basic USB firmware utilized is Nick Christoudoulou’s interrupt-driven implementation of Bradley Minch’s assembly language USB framework. The 18F4550 is configured with only a single control pipe (EP0) and all data is passed through this interface. Messages from the PC are sent as vendor-specific requests. In addition to a command, two 16-bit parameters (wValue and Index) are sent – in the case of this project, wValue contains configuration information for the analyzer (sample rate, trigger channel, etc). Commands are defined as follows:

CMD_IDLE: idles the hardware (no sampling)
CMD_RESET: resets the FIFO counters and trigger FF
CMD_START: starts the analyzer with parameters specified in wValue
GET_FIFO_STATUS: returns a single byte describing the FIFO status (Full or Empty)
GET_FIFO_DATA: retrieves eight bytes of data from the FIFO vie the EP0 buffer

The CMD_START command accepts a single 16-bit word as an argument in which the bits are assigned as follows:

Bits D0-D2 define the Sample Rate (000=ext, 001=20M … 111=4.9K)
Bits D3-D6 define the Trigger Channel (0000=Free, 1-8=Channel#, 9=Pattern selected)
Bit D7 sets the trigger edge (with 0 signifying a Low-to-High edge)
Bit D8 defines Vcc (0=3.3V, 1=5V)
Bit D9 defines Pullup or Down of unused inputs (0=pull down, 1=pull up)

For commands where a value is sent from the PC to the analyzer, the parameter is sent on wValue which is extracted from the incoming USB buffer as follows:

    movf   USB_buffer_data+wValue, W, BANKED
    movwf  ParmwValue1,BANKED

In reality, it is extracted as two 8-bit values since wValue is actually a sixteen-bit number from the PC. Bits, which represent individual values, are then extracted. For example, Bits 0-2 of wValue are extracted then sent to PortB as follows:

    andlw  b'00000111'
    iorlw  b'00111000'  ; Ensure Clk/Rst/Enable lines stay high
    movwf  PORTB

This code ensures that bits 3-5 of PortB stay high to disable clocking.

Where a value is to be sent back to the PC the value is written to the USB buffer and the endpoint control registers set to send the buffer back to the PC. In the case of the GET_FIFO_STATUS command FSR0 is first set to point to the EP0 IN buffer, the FIFO chip is tested (PORTE, FIFO_FULL line), and POSTINC0 is set to 0x00 or 0x80 to represent the FIFO status (hence only the first of the eight bytes in the buffer are used in this case). Finally, the buffer control registers are set to send 8 bytes and the UOWN bit is set allowing the USB SIE to take control of the buffer and send it to the PC (this is the single-bit system used to control access to the buffers):

    banksel  BD0IBC
    movlw    0x08
    movwf    BD0IBC,BANKED  ; set byte count to 8
    movlw    0xC8
    movwf    BD0IST,BANKED  ; send packet as DATA1, set UOWN bit

Only eight-byte data packets are sent back to the PC (the most basic allowed in USB). Larger packets could be sent to enhance trabsfer rates however it simply did not seem to be required for a project like this.

A single LED indicates the USB status with a constant ON indicating the device has been enumerated (this occurs after the PC recognizes the device, usually accompanied by a “bing bong” sound effect) and Blinking indicating the device has been configured (which occurs when the front-end software is run and actually opens the USB channel for access). The LED is the only task which runs in main and this same LED is used as the unit power LED (so that “power” is actually “status”).

The latest Assembly Code for version 4.0 can be found here. Also available is the older Assembly Code for version 3.1 – The “point one” version indicating a bug-fix (in version 3.0 I forgot to put the chip into IDLE mode before reading data allowing data to be overwritten during a read). The USB portions were verified using a Beagle-480 protocol analyzer. The project was built with MPLAB version 7.50 and 8.43 and a linker script (for the 18F4550) is required (18F4550_g.lkr in 8.xx). Other required files include (from MPASM) and two include files from the original framework.

I entered a contest in Circuit Cellar Magazine in which I won a free Beagle-480 USB Protocol Analyzer from Total Phase, Inc. and this was the perfect opportunity to try it! The analyzer allowed a view of all data exchanged between the PC and the PIC starting with the expected device descriptor, address, configuration descriptor, and strings. Following that, one can ‘see’ the front-end program interrogating the FIFO status and starting a transfer of data:

Normal operation appears as follows: After configuring the analyzer (trigger channel, etc) and resetting it, the PC polls the FIFO status as seen in Index 395 above in which a “Get Fifo Status” command (03) is sent to the PIC. The PC normally must wait many such cycles for the FIFO but in this case (Index 398/400) the FIFO is full and so 0x80 is returned in the DATA1 packet. The front-end program on the PC the issues a “Read Fifo” command (04) at Index 408/410 and data from the PIC is sent to the PC in groups of eight bytes at Index 413/415 (seen here as all “01”s – it was a slow square wave on channel 1 only). The PC continues issuing ‘read’ commands and in response received eight data bytes from the FIFO. Data is restricted to eight bytes allowing transfer of data on endpoint EP0 at any speed (Low speed mode is the limiting factor here). Once could open another pipe (e.g. EP1) to transfer the entire contents of the FIFO in one shot however the simple approach of a single pipe was used here (hence why the Beagle analyzer interprets the packet transferring data from the PC to the PIC as “SETUP txn” …. normally one expects data to be transferred via a different pipe than EP0).

The logic analyzer during the debug process. I took-on this project on a weekend when I was required to be upstairs tending to the barbeque so I simply piled the equipment on the dining room table and did the work there – you’ve got to love the compactness of these development tools! Visible to the left is the logic analyzer itself connected to the ICD-2 debugger. The blue box in the center is the Beagle USB analyzer and the grey box at the top a signal generator.

DOWNLOADS (Hardware Portion only):

Software Elements

The actual data acquisition (i.e. the interface between the hardware and the PC) is handled by a thread which runs continually. On command from the Win-32 front end (via a semaphore), the thread begins data acquisition. When the FIFO is full, data is read into a global array and a message is sent to the Win-32 front end to force a PAINT and redraw the screen (along with new data read from the analyzer). Synchronization between the front-end and the thread, then, is accomplished via a semaphore and a Windows message (DATA_RDY).

To prevent the thread from ‘hanging’ when an unused trigger channel is selected, the current trigger channel, trigger edge, and sample rate are saved immediately before data acquisition takes place (and before parameters are sent to the logic analyzer hardware). During a wait for the FIFO, these parameters (all global) are compared to the start parameter – should any change the analyzer will be reset and data acquisition will be restarted. The user, then, always maintains control of the acquisition thread even while it is in a loop waiting for the FIFO to fill. The basic algorithm, running in the data acquisition thread, looks like this:

   //Save start parameters to detect change during data acquisition

   //Send START_ACQUISITION command to start logic analyzer
   ret = usb_control_msg(... , CMD_START, ...);
   //Wait for FIFO to fill -- Don't do this in a Win-32 proc

   while (FifoFull==FALSE) //Input(InputAddress)&FULLFIFO)
      //Check Fifo status
      ret = usb_control_msg(... , GET_FIFO_STATUS, ...);
      if (buffer[0]==0x80)	//Check the EP0 buffer returned from the device
      if(CurrentSampleRate!=SampleRate) {
      if(CurrentTriggerChannel!=TriggerChannel) {
      if(CurrentTriggerEdge!=TriggerEdge) {
   } //while FifoFull
} //while ParameterChange

The usefulness of a thread is evident here: it would not be possible to have a loop which continually checks for user changes. It might be possible to use a timer to implement this, however a thread is certainly the most convenient (and the code was already implemented for the previous version so only minor changes were required when adding the USB port).

Next, the semaphore is created to synchronize the thread:

hSyncDAQ = CreateSemaphore( NULL, 0, 1, NULL );

the semaphore is released in the main program and causes the thread to halt until released via a ‘WaitForSingleObject’ within the thread itself. The advantage of this is that while waiting, the thread does not consume copious quantities of CPU time! This requires that the thread possess the handle to the semaphore – currently this is done via a global variable however it could have been passed to the thread when the thread was created, since CreateThread allows passing of a parameter.

Upon receiving a WM_COMMAND, parameters such as sample rate are updated from the menu. When a port is selected via a menu option, the hardware is checked at that I/O address and if functioning, the data acquisition thread is started – this prevents attempt to access non-existent hardware. To test the hardware, the system is set for a 20MS/s sampling rate, free-running (no trigger), and the FIFO chip is reset. The Buffer full line (active low) should be high since the buffer is now empty (if not, the hardware is not functioning). The analyzer is now allowed to run for 1ms. In about 0.2ms, the buffer (after 4095 clocks) will fill and the buffer full line should go low – if it is, the hardware is now deemed to be operational. This is, BTW, a bit of a hack since inserting a ‘Sleep(1)’ command within a windows procedure is generally not a good idea (although it is, at least, predictable and will never wait indefinetely). Because this is only done rarely (only when setting the port initially), it is tolerable here (although using a proper windows timer would likely have been the best ‘textbook’ solution).

As usual with a Windows program, all screen draws are done withing the WM_PAINT message section. Within this section lines are drawn to demarcate the grid, text is drawn to label the grid, current system parameters such as sample rate are drawn on the screen, and traces are drawn for each channel. Drawing traces was a bit tricky since vertical lines must be drawn at each transition. After the entire screen is completely redrawn, the semaphore is released allowing the thread to acquire a new set of data to draw … releasing the semaphore only after the PAINT is complete prevents the data acquisition thread from generating messages faster than the screen can redraw them (it was also found necessary to limit the number of times per second the thread can run regardless to prevent the image from updating too quickly to prevent being read).

The most recent updates to the program include a COPY to clipboard feature allowing screen outputs to be saved in any graphics program. Previously, the user was required to press the “PrtSc” key and the paste the image into a graphics package – the Copy feature saves this step. Other added features include the ability to pan through samples using arrow keys allowing all 4000 samples to be viewed.

In this example screen shot (from version 4.1) the clock, data, and /chip select lines from an SPI interface (on a dsPIC33FJ chip) are seen on the display. Channel 3 shows the select line which goes low during the entire transmission. Sixteen clocks are visible on channel 1. Channel 2, showing data, represents {10000000} which is the command “Read register 0” on the target SPI device (an ADXL312 accelerometer chip). The sample rate of 48.8KS/s allows the entire bitstream to be viewed however only the first 400 samples are visible. By changing the sample rate, the view will be expanded and so details of the signals can be seen however another useful feature is panning …


These two screen shots show the the same bitstreams at a faster sample rate of 195KS/s. Using the panning feature (via keyboard cursor arrows), the display on the left shows the first 400 samples, the one on the right the next 400 samples (numbered 400 through 800). Incredible details can be discerned in this manner, including critical timing relationships between the clock and the data lines (whether, for example, the data on channel 2 falls low before the rising edge of the next clock pulse on channel 1). 

USB Interface

The USB interface was created using the open-source LIB-USB driver available from (I’ve had enough frustration at attempting to use the generic ‘usbser.sys’ driver that comes with Windows to emulate a virtual COM port, besides, the LIB-USB driver sports faster transfer rates). The original code from the second version was modified for use with a USB interface basically by replacing the old ‘inp’ and ‘out’ calls with the equivalent USB functions. First, the DLL (specific for Microsoft Visual C++) is linked into the project as well as the USB.H file which describes USB data structures and functions. Access to the USB device is then provided by the usb_control_msg function.

Upon WM-CREATE, the code searches for a USB device matching the VID and PID specified (it must match that used in the PIC firmware, in this case VID=0x04D8 and PID=0x0004) returning a ‘usb device’ handle udev. The code then sets the configuration, claims the interface, and calls are made to transfer data to/from the USB device as per the following examples:

ret = usb_control_msg(udev, USB_TYPE_VENDOR | USB_RECIP_DEVICE, CMD_RESET, 0, 0, buffer, 0, 100);

This call sends a vendor-specific command ‘CMD_RESET’ to the device. The buffer is unused in this case as is wValue (parameter #4), either of which could be used to send data to the USB device.

ret = usb_control_msg(udev, 0xC0, GET_FIFO_DATA, 0, 0, buffer, 8, 100);

This call issues the GET_FIFO_DATA command. On completion of the call eight bytes of ‘buffer’ will be returned with eight consecutive samples from the FIFO.

The interface is extremely simple to use.

Download the complete Logic Analyzer C++ Files (Version 4.101) in a ZIP file. Version 4 will work with version 3 hardware (and vice versa), Vcc select features will simply be unavailable.

The USB Drivers for version 4 can be found here (in a ZIP file). Required files include an INF file as well as several drivers. Unzip the files into a directory and when prompted for a driven when the device is connected, look for files in that directory (Windows will find all required drivers there). These files are a subset of version of the LibUSB-Win32 project on and are provided in the interests of simplicity only: for the current version of the LIB-USB driver, look for updates on sourceforge. This is an open-source project covered by the GNU licence and so the drivers are free.

To install the USB drivers, simply run the installer from the sourceforge project (libusb-win32-filter-bin-, plug the device into your PC, and install drivers when prompted. Be sure to run the testlibusb.exe program included with the driver (in the zip file as well) and ensure the logic analyzer appears there (with the correct VID and PID) before trying to run the LogicAnalyzer executable.

Output from the test program provided with the libusb-win32 driver. The Logic Analyzer is seen here as device 0x004. The USB device indentified ahead of that (a portion of the output is seen here as well) is a memory key plugged-into the same machine.

If the drivers are not properly installed, the logic analyzer will always respond with an error (“No matching USB device found”). So long as the test program reveals the device, it should work. Upon starting the logic analyzer you will see a red error message in the loweer-right corner of the window until the thread starts and communications is established after which the message turns green and states that the sampling thread is ready. Should an error occur, several message boxes detailing errors will be shown after which the program will sit dormant in an inactive state (it will not, however ‘hang’ and so can be terminated normally).

Sidebar: USB DeMystified

USB can seem mystical, but with the benefit of a protocol analyzer (in my case a TotalPhase Beagle 480), allow me to present the process of USB transfers in a simplified manner by demonstrating how the logic analyzer accomplishes communications with a PC.

The USB transfer process begins with enumeration in which the device is assigned an address by the host PC. Once detected upon plug-in, the PC sends the device a request for the device descriptor:

	Get Device Descriptor (64 bytes)
		  SETUP 2D 00 E8
		  DATA  C3 80 06 00 01 00 00 40 00 DD 94
		  ACK   D2

This request is directed to “device 00”, our logic analyzer which is unassigned right now. The device (analyzer) responds with a description. Even though 64 bytes (max) were requested by the PC, the device sends back only eight bytes as follows which describe the device as a USB 1.1 device with an 8-byte maximum packet size:

	  IN txn
		  12 01 10 01 00 00 00 08

The actual transfer looks like this through the protocol analyzer’s eyes:

Here we see the request from the PC for the descriptor and the 8-byte response from the PIC in our logic analyzer. The actual bytes sent are simply a table in the PIC as follows:

	db	0x12, DEVICE		; bLength, bDescriptorType
	db	0x10, 0x01			; bcdUSB (low byte), bcdUSB (high byte)
	db	0x00, 0x00			; bDeviceClass, bDeviceSubClass
	db	0x00, MAX_PACKET_SIZE	; bDeviceProtocol, bMaxPacketSize
	db	0xD8, 0x04			; idVendor (low byte), idVendor (high byte)
	db	0x04, 0x00			; idProduct (low byte), idProduct (high byte)
	db	0x00, 0x00			; bcdDevice (low byte), bcdDevice (high byte)

The PC now instructs the USB device to become address 1 with the following command (when first started, the device has no address assigned and so responds to address zero):

	Set Address (=01)
		  SETUP 2D 00 10
		  DATA  C3 80 05 01 00 00 00 00 00 EB 25
		  ACK   D2

The USB device now responds only to address 1. The PC now requests the device descriptor again (this time, from address 01 � seen in the setup portion of the packet sent from the PC as “2D 01”, and this time only 18 bytes are requested since the PC now knows the size of the descriptor packet from the first request):

	Get Device Descriptor (18 bytes)
		  SETUP 2D 01 E8
		  DATA  C3 80 06 00 01 00 00 12 00 E0 F4
		  ACK   D2

And the device responds with the entire 18-byte long descriptor in three packets (each with a maximum length of eight bytes):

	  IN txn
	    	12 01 10 01 00 00 00 08
	    	D8 04 04 00 00 00 02 02
	    	00 01

Next, the PC requests the configuration descriptor which describes how the device is powered as well as the interfaces it contains. First, the PC asks for 9 bytes then for the descriptor again, this time up to 255 bytes in length:

Once again, the descriptor packet is simply a series of bytes stored in a table in the PIC as follows which describes the interface including the class (in this case vendor, but in many cases a device may belong to a class such as communications or human interface devices such as a keyboard or mouse):

	db	0x09, CONFIGURATION	; bLength, bDescriptorType
	db	0x12, 0x00			; wTotalLength (low byte), wTotalLength (high byte)
	db	NUM_INTERFACES, 0x01	; bNumInterfaces, bConfigurationValue
	db	0x00, 0xA0			; iConfiguration (none), bmAttributes
	db	0x32, 0x09			; bMaxPower (100 mA), 
					; bLength (Interface1 descriptor starts here)
	db	INTERFACE, 0x00		; bDescriptorType, bInterfaceNumber
	db	0x00, 0x00			; bAlternateSetting, bNumEndpoints (excluding EP0)
	db	0xFF, 0x00			; bInterfaceClass (vendor specific class code),
					; bInterfaceSubClass
	db	0xFF, 0x00			; bInterfaceProtocol (vendor specific protocol used),
					; iInterface (none)

Description strings are now requested by the PC which describe the device:

	Get String Descriptor (255 bytes)
		  SETUP 2D 01 E8
		  DATA  C3 80 06 02 03 09 04 FF 00 97 DB
		  ACK   D2

And the string describes the device as “Logic Analyzer ….”

	  IN txn
		    2E 03 4C 00 6F 00 67 00	;   L o g
		    69 00 63 00 20 00 41 00	; i c _ A
		    6E 00 61 00 6C 00 79 00	; n a l y
		    7A 00 65 00 72 00 20 00	; z e r _
		    33 00 2E 00 58 00 20 00   ; 3 . X _
		    55 00 53 00 42 00	; U S B

These strings are the ones which appear in the lower-right corner of the screen when a new device is plugged-into a PC. They are for identification to the user only. The PC can request the string, device, and configuration descriptors again as required.

Finally, the Configuration is set to 1 (normal for a single device):

	Set Configuration
		  SETUP 2D 01 E8
		  DATA  C3 00 09 01 00 00 00 00 00 27 25
		  ACK   D2

The device is finally ready, and data transfer can occur with the device. In the case of the logic analyzer, data is sent bidirectionally via a single endpoint (EP0). Actual device data transfers are described elsewhere on this page.

DOWNLOADS (PC Software Portion only):

Older Versions

This project, started in 1998 with the original version written in QBASIC, has been upgraded many times. This section of the page outlines previous versions.

The second version (like the first) used the PC’s parallel port to transfer data from the analyzer. In it’s simplest form, a parallel port consists of twelve output lines and five input lines, all I/O mapped. The output lines are normally used for data as well as control lines such as strobe and reset, the input lines are normally used for reading the status of the printer (e.g. if it is out of paper). Bidirectional modes do exist, but this is more complex and is not supported by all parallel ports.

The main data port, eight bits wide, is normally used for data sent to the printer. On most PC’s it is located at I/O address 0x378. In this analyzer it is used to select sample clock rate as well as control the read functions on the FIFO chip as follows:

D0-D2: Sample clock select (0=external, 1-7 selects internal rates)
D3: Enable clock (active low)
D4: Read reset
D5: Read clock
D6: Odd/Even nybble read select
D7: Trigger Enable

The control port is only four lines and is normally used for the strobe and other control signals to the printer. It is used in this analyzer to select a trigger channel and edge:

C0-C2: Trigger channel select 
C3: Edge select

Finally, the parallel port provides five inputs for reading the status of the printer (e.g. out of paper, etc). It is used here to read the status of the FIFO buffer as well as a byte of data, four-bits (one nybble) at a time:

S3: FIFO Buffer full (active low)
S4-S7: data nybble

Eight bits of data from the FIFO are read into the PC four bits at a time by selecting the upper-four or lower-four bits via a 74HCT157 multiplexer. In the planned USB upgrade data from the FIFO will pass directly to a USB interface.

The parallel port interface to the PC was a good idea in the past but protected-mode Windows O/S’s like XP do not support direct-access to the parallel port hardware so special drivers are required.

The full Schematic for version 2 (using a parallel port interface) can be found here (in PDF format). U5 is the FIFO chip, U4 (a 74HCT573) is used as a buffer – it is socketed in case the analyzer is inadvertently connected to a high voltage and hence serves to protect the FIFO inputs (the FIFO is quite pricey), U6 is the multiplexer which selects which nybble is read via the parallel port inputs (it is scrapped in the USB version), U2 and U3 generate the sampling clock, and U7 selects which channel is to be used as a trigger.

The original front-end software (version 1, circa 1998) was written is BASIC but this is very limiting. The biggest single limitation was loss of control of the hardware when, for example, an invalid channel is selected as a trigger. Assume, for a moment, that the user selects channel 2 as the trigger, the analyzer starts, but a pulse never arrives on that channel …. the analyzer then ‘hangs’ while waiting for that pulse to arrive. A timer was used to timeout the program so that if the logic analyzer does not respond within a reasonable time, a timeout message is reported and the user may reset parameters. Still, what was needed was a multi-threaded approach where the user-interface runs continuously allowing selection of, for example, another trigger channel while a thread runs continually to interface with the analyzer hardware. Using a Win-32 platform and writing the code in C++ provides this functionality (see ‘SOFTWARE’, below …).

In the second version, the first and foremost concern was direct access to the parallel port hardware which was required but is not provided by the O/S for a 32-bit protected mode program (ever wonder what happened to the good old ‘inp()’ and ‘out()’ functions which existed on the old 16-bit compilers?). User mode I/o access was prohibited in NT (and higher) operating systems to close a possible security loophole however Microsoft never did address the issue of how to allow a program such as this analyzer to use I/O ports safely (I suppose the ultimate answer is “use USB”). Normally, attempting to write to an I/O address directly will result in an exception. To overcome this, UserPort by Tomas Franzon is used. When run in the background, before the logic analyzer is started, this driver allows hardware reads and writes to selected I/O addresses by changing the x86-processors IOPM (I/O Permission Map). This works with NT and XP but it is not known whether this scheme will work with Vista or later.



The main (front-end) program for version 2 is a standard Win-32 application. Upon creation (WM_CREATE), a DLL is loaded providing two functions ‘Output()’ and ‘Input()’ to perform I/O as follows:

hDLL = LoadLibrary( “INPOUT32” );
Output = (LPFNDLLFUNC1) GetProcAddress( hDLL, “Out32” );
Input = (LPFNDLLFUNC2) GetProcAddress( hDLL, “Inp32” );

Of course, each pointer (hDLL, Output, and Input) is checked to ensure they are not NULL … if they are a message box warns the user of the failure and the program is terminated.

Download the complete Logic Analyzer Files (Version 2) including EXE, CPP, H, and RC files in a ZIP file. This program was written in C– …. this is just like C++ only written badly :). Version 2.002 – a slightly improved version of the original with a few new features (such as copy to clipboard) but still ‘dirty’, and needs much clean-up including the elimination of large quantities of global variables.


A Compatible Analyzer built by Gianfrance Meheghetti of Italy. This is a parallel-port version which uses my software. Hardware features a XC9536 PLD to implement all functions done in discrete logic on mine so the entire analyzer requires only five chips. Included with his design is a PCB layout.