Click here to Skip to main content
Click here to Skip to main content

17 Channel Logic Analyzer

By , 31 May 2010
Rate this:
Please Sign up or sign in to vote.
Prize winner in Competition "Best C++/MFC article of April 2010"
A digital oscilloscope with serial analyzer for the parallel port.


  • Digital Oscilloscope / Logic Analyzer with up to 17 input lines
  • Uses the parallel (printer) port for input.
  • While Logic Analyzers normally are very expensive, this one is for zero cost
  • Written in speed optimized C++ to get the maximum possible sample rates
  • The program is a stand alone single EXE file which neither needs any additional DLLs nor any framework. It runs out of the box
  • Runs on all Windows platforms (95, 98, ME, NT, 2000, XP, 2003, Vista, 2008, Windows 7, 2008 R2)
  • Runs on 32 Bit Windows and 64 Bit Windows
  • When first run, installs a driver to access hardware IO ports on all NT platforms
  • Plays a sound via PC speaker to prove that the driver works correctly
  • Shows the actual state of the input lines life in the LED's of the GUI
  • Captures up to 500.000 samples / second (depending on your hardware and OS)
  • Captures to memory (rather than disk) for maximum speed
  • Capturing runs always with the maximum frequency that the hardware allows
  • Memory saving capture technology stores only the changes of the input lines rather than storing all lines with each sample that is taken
  • The capture process is independent of data analyzing so you can try various settings to display the captured data in the best way
  • The analyzer generates coloured oscilloscope diagrams with a raster grid
  • The output is written into an HTML file and one or multiple GIF files
  • So the analyzer results can easily be shared with other people who must not install any program to view a proprietary binary format
  • You can write an individual heading into each analyzer output to distinguish them later in a multitude of files
  • When analyzing data, you can choose to delete old analyzer output or keep the old files
  • The time axis shows absolute or relative time with microsecond precision (Performance Counter)
  • Automatic detection of inactive input lines shows only the channels that have activity
  • Automatic detection of idle time cuts out long phases of inactivity
  • Automatic detection of input frequency chooses the adequate raster unit like on a real oscilloscope (10 µs, 20 µs, 25 µs, 50 µs, 100 µs, etc..)
  • Automatic detection of context switches of the operating system
  • Decoded serial data (Start Bit, Parity, Acknowledge, Stop Bit) and the Byte that they represent may be printed into the diagrams and/or printed to HTML where it can be copied to the clipboard.
  • Decoded serial data (Start Bit, Parity, Acknowledge, Stop Bit) and the Byte that they represent is written into the diagram
  • Very cleanly written source code with a proper error handling and plenty of comments


  • Version 1.0: Initial version
  • Version 1.1: Serial Analyzer for asynchronous signals improved
  • Version 1.2: Support for 9-Bit asynchronous protocols
  • Version 1.3: New Option: Only display of the decoded Bytes as Html (very fast) or display below the graphics.
  • Version 1.4: Selection of a time interval where to start and stop a partial Analysis in large Capture data
  • Version 1.5: Statusbar with Progress display

Why Still Using the Good Old Parallel Port ?

Obviously the Parallel Port is much faster than the Serial Port (RS232). Additionally it offers much more input lines.
And what about USB?
There are several professional LogicAnalyzers on the market as PCI cards or USB devices. But they are very expensive (> $350). If you have to measure very high frequencies or if you need more than 17 input channels, you will have to buy one of them. But for frequencies up to 500 kSamples/s, the Parallel Port is sufficient. The big advantage is that it is gratis. You only need a printer cable and ElmüSoft LogicAnalyzer.
If your PC does not have a parallel port anymore, you can order a PCI adapter for approx $10. This is the cheapest solution to have a digital oscilloscope.

Why not using USB adapters ?

It could come into your mind to order for example a SUB-20 device from Dimax. This is a USB adapter with 32 General Purpose Input/Output lines (GPIO) and more functionality.

But the SUB-20 can NOT replace the Parallel Port if you need a Digital Oscilloscope. The reason is that USB is extremely slow.

  • USB has been designed in the first place to be cheap. Speed was not a major design goal. The USB cable contains only one twisted pair that does not allow bidirectional data transfer. When one data packet has been entirely sent from the PC, the transmission direction must be reverted to receive a response packet from the USB device. USB cannot transfer data in both directions at the same time like for example RS232, that has a cable for RxD and another one for TxD.
  • The primitive design does not allow to notify the PC when a USB device has data, that is waiting to be transmitted. Instead the PC must poll all the connected USB devices with a certain schedule which obviously results in the worst performance and in a waste of processor power even if all devices are idle.
  • The USB bus was not designed to transfer small amounts of data. USB is a packet based protocol so transferring 10 Megabyte goes relatively fast. When USB transfers a video from your webcam it does not matter if the image comes with a delay of 1 Millisecond. But if you read the GPIO port of the SUB-20, you read only 32 Bit = 4 Byte. (There is no FIFO available) The USB packet size is larger and the rest of the packet must be filled up with dummy zeroes.
  • Due to the high complexity of the USB protocol, the USB drivers cannot be simple and so you lose additional time in the drivers.
  Parallel Port SUB 20
Connection to PC built in USB
Price free $80 - $120
Speed of a single read operation 1,3 µs 1 ms
Maximum Samplerate 500.000 Samples/s 1.000 Samples/s

It would have been easy to design a faster USB bus if two twisted pairs would have been used and at least two status lines that allow a hardware handshake like it was usual in the older protocols like RS232 and Centronix. But this would have increased the costs of cables and USB chips by a ridiculous amount. Nowadays where low price is more important than high quality, the designers of the USB bus decided to save these tiny additional costs and replace faster hardware with slower software.

If you need Analog or Digital inputs for slow frequencies or have to communicate with a slow I2C bus, SPI bus, RS232, RS485, ModBus or SMBus where timing is not so important, then the microcontroller of the SUB-20 can do that for you. But you cannot build a useful Digital Oscilloscope or communicate with a high-speed serial bus using the SUB-20 or a similar USB device. If you need that, the only option you have is a PCI adapter.

The Parallel Port

Instead of explaining the logic of the parallel port with much bla bla, I have created a diagram.
As the saying goes: An image says more than 1000 words:

Note that this diagram is simplified: The D-Flip-Flops are missing which store the data that the processor writes to the IO ports.

Depending on the hardware...

  • Outputs may be implemented as Totem Pole outputs that put a solid LOW or HIGH level or as Open Collector outputs that consist of a Pull-Up resistor and a Pull-Down transistor.
  • The Pull-Up resistors may not exist or may have another value than 2,2kΩ.
  • The HIGH level of the outputs may be +3V instead of +5V.

Whenever you start ElmüSoft LogicAnalyzer or change the Port ComboBox, the selected Port will immediately be initialized, which means that all Open Collector transistors are released by writing a HIGH level and the Data Port output drivers are switched to 3-State mode by setting Bit 5 of the Control Port HIGH. Therefore the Port must work in "Byte Mode" which is set in the Extended Control Register (ECR Port).

The Parallel Port is normally at address 0x378. You can see in Control Panel -> Hardware -> LPT Port which port number is assigned.

  • The Status Port is a Read-Only Port. It will always work without complications.
  • The Control Port is normally implemented as Open Collector. But on some hardware, the Control Port may be Write-Only with Totem Pole outputs.
  • The Data Port is normally implemented as 3-State Totem Pole output. The output drivers can mostly be turned off.

To find out which Output Type your hardware has, first start LogicAnalyser, select the port combobox and then measure the voltage!
If the output is HIGH, connect it with a 330Ω resistor to ground:

  • If the voltage falls from +5V to +0,5V or from +3V to +0,4V then you have an Open Collector output (OK)
  • But if the voltage only falls from +5V to +4,5V or from +3V to +2,7V then you have a Totem Pole output (not usable as input)

If the output is LOW, connect it with a 10kΩ resistor to +5V.

  • If the voltage raises from 0V to +5V you have an output in 3-State mode which does not have a pull up resistor (OK)
  • If the voltage stays 0V, the output is not disabled (not usable as input)

When changing the voltage of a line, the corresponding LED in the LogicAnalyzer GUI will change (green or red). The threshold of the input buffers where they distinguish between LOW and HIGH is normally at 0,8V depending on the hardware.


Take care not to shortcut any lines directly with a jumper to ground or +5V to avoid damage! Always use a resistor for testing!


It is recommended to experiment first with the Status lines. Before connecting any external signals to the Data or Control lines, analyze the output type. If you cannot change the voltage with a 330Ω resistor to ground or a 10kΩ resistor to +5V, then this line cannot be used as input.


In your BIOS, you should set the Parallel Port to operate in the "ECP and EPP Mode". ElmüSoft LogicAnalyzer neither uses ECP nor EPP mode (hardware handshake) but with this BIOS setting the Extended Control Register (ECR) becomes available which allows to configure the operation mode manually.

In the folder "Documentation", you find 3 detailed PDFs about programming the Parallel Port in the SPP, EPP and ECP modes.

Buffering the Input Signals

In many cases, you can connect your input signals directly to the parallel port. This is the case if the signals come from chips that work with +5V, like for example a MAX485. If you get strange results, connect an analog oscilloscope to the input lines to see if they are clean or if they are suffering under the capacity of the printer cable.

If you have weak input signals, a long printer cable (> 1 meter) or work with high frequencies, it may be necessary to buffer the input signals. This can easily be done with a 74HC541.


74HC541 or 74HCT541 cannot be replaced with 74LS541 or 74S541!
The HC chips have extremely high input impedance (High Speed CMOS technology) while the LS chips (bipolar transistor technology) are not useful to buffer sensible input signals.


You must connect all unused inputs of the 74HC541 to Ground to avoid wild oscillations.

The Drivers

In the old days of Windows 95, 98, ME, you could directly read/write the hardware IO ports from within a C++ program with the commands _inp(Port) and _outp(Port, Value).

On all NT platforms (NT, 2000, XP, Vista, Win7), this is forbidden. A program that tries to read/write an IO port will be exited with an exception. Only drivers are allowed to access the hardware. The disadvantage is that through a driver, the hardware access is slower than directly using _inp() and _outp(). To avoid this disadvantage, LogicAnalyzer uses undocumented API calls like Ke386IoSetAccessProcess() that modify the I/O Permission Bitmap of the process. Setting the permission map makes it possible to use the commands _inp() and _outp()directly in an EXE on a NT platform. The modification of the IOPM is done via the OpenSource PortTalk Kernel driver.

On 64 Bit Windows, the PortTalk driver unfortunately cannot be used because a 64 Bit version of the driver does not exist. So on 64 Bit Windows LogicAnalyzer uses the OpenSource WinRing0 Kernel driver. Although it cannot modify the IOPM it can access the IO ports via IOCTL driver calls using DeviceIoControl(). This is slower but the only way to make it work on 64 Bit Windows. Additionally Microsoft demands that on 64 Bit Windows, all drivers must be signed. The latest version of WinRing0 meets this requirement.

Windows 95,98,ME 32 Bit Windows NT,2000,XP,2003,Vista,7,2008 64 Bit Windows XP,2003,Vista,7,2008
No driver (fast) PortTalk (fast) WinRing0 (slower)

For details about the drivers, see folder "Documentation"!

When you run LogicAnalyzer for the first time, you must start it as Administrator. Otherwise the PortTalk or WinRing0 driver cannot be installed. Once installed, every ordinary user can use it. The tiny driver files are compiled into the EXE as embedded resources and copied to the Windows driver folder. On 64 Bit Windows, LogicAnalyzer turns off the System-File-Redirection via Wow64DisableWow64FsRedirection() otherwise the files would be copied to the Wow64 folder where drivers won't load.

To check that all is OK, LogicAnalyzer will play a Blip sound via the PC speaker. For playing a sound via PC speaker, several IO ports must be accessed. (How this works is out of the scope of this article. Study the source code!) So when you start the Release version of LogicAnalyzer and hear a Blip from your PC speaker, you know that the driver works and you are ready to use the program.

If you do NOT hear a Blip sound, you may be using the Debug version of LogicAnalyzer which stays silent or you use a Notebook. Notebooks never have PC speakers. The internal speakers of notebooks are connected to the soundcard. Some notebooks EMULATE the PC speaker, but only if the driver of the sound card supports this feature.

The Maximum Speed

The maximum speed obviously depends on your hardware. With intention LogicAnalyzer is written in C++ to run with the maximum possible speed.

On 32 Bit Windows, you can capture with a higher sample rate because the PortTalk driver allows to access the IO ports directly.
On 64 Bit Windows, the access goes through WinRing0 driver calls. Each time an IOCTL call is made to access a port, the processor must switch from ring 3 to ring 0 and afterwards switch back.

A strange thing is that even when accessing the port directly, the _inp() command is executed very slowly. The C++ _inp() command compiles directly into the following assembler code:

There is no evident reason why this should be slow. But it is definitely:
A single _inp() command takes the same time as the following loop that runs 500 times:

for (DWORD L=0; L<500; L++)
     D = (D + 3) * 2;
Execution time: 1,3µs Execution time: 1,3µs

The consequence is that the maximum capture speed also depends on how many ports you enable. For each port (Status, Control, Data) one _inp() must be executed:

Running the plain capture loop Capturing 1 Port Capturing 2 Ports Capturing 3 Ports
2,4 Mega Loops/sec 520 kilo Samples/sec 300 kilo Samples/sec 200 kilo Samples/sec

These values correspond to my machine (2 GHz) using the PortTalk driver. You can use the button "Measure Speed" to check the speed on your hardware. With the WinRing0 driver, it is approximately half the speed on the same hardware.

On my computer, I analyze a RS485 Bus that runs with 192 kBaud without problems.

The Classes

  • CDriver manages the two drivers: PortTalk and WinRing0 and installs them when the program is run for the first time
  • CPort accesses the IO ports of the printer and the PC speaker and captures data to memory
  • CGraphics is an ultra fast class to create diagrams as Bitmap images in memory and write coloured lines and text into them. At the end, it compresses the Bitmap into GIF and saves it to disk
  • CAnalyzer analyzes the data that has been captured in CPort and paints the oscilloscope diagrams using CGraphics and generates the HTML files
  • CSerial is derived from CAnalyzer (so it can access all the variables) and analyzes serial buses like PS/2, Asynchronous, etc.

Source Code

You will find a very cleanly written source code with a proper error handling and plenty of comments written by an experienced programmer. You can re-use the code for example the CGraphics class to draw your own diagrams of any kind or the CDriver and the CPort class that can be used in other applications to access IO ports.
LogicAnalyzer is very complex. Here you get the result of several !weeks! of coding for free!

Capturing Data


Before connecting any input signal, read thoroughly the chapter "The Parallel Port"!
Before connecting any input signal to the Data port or the Control port, you MUST ALWAYS start LogicAnalyzer and select the correct port from the combobox of the port that you want to scan! The lines may be switched to be output! LogicAnalyzer switches them to work as input. The Status Port is no problem because it cannot work as output. Do NOT shut down the PC while you have live input signals connected!

The GUI always displays the real state of the input lines in the LEDs. You don't have to care if a line is hardware-inverted. If the LED is green, the level is +3V or +5V. If the LED is red, it is 0V. If it is grey, the port is disabled.

While capturing the LED display is turned off to give the full processor power to the capture thread. This thread always runs with the maximum speed that your hardware allows. Captured data is stored in memory, which is faster than writing it to disk.

The memory usage is optimized: The capture loop does not write every sample to memory. Only if any of the input lines has changed its level or a glitch occurs (see below), one capture packet of 11 bytes will be written to memory. So how much memory you need depends in the first place on the activity of the input lines.

The Analyzer

The analyzer creates an HTML file and one or multiple GIF files. The GIF files contain between 1 and 17 coloured oscilloscope lines. The analyzer detects inactive lines which will not be displayed. You can chose to let the analyzer detect raster unit and idle interval automatically. If you want to zoom-in or zoom-out you can modify manually the settings for raster unit an idle interval in the GUI and click "Analyze" again.


The GUI and the oscilloscope diagrams use a more user friendly numbering of the Bits. It would be strange if the output for the Status Port would start with Bit 3. Please don't confuse the Bits displayed in the GUI and in the diagrams with the real Bits on the IO ports!
"Status 0" means the first Status line which corresponds to Pin 10 (as displayed in the GUI) although it really reads Bit 6 of the Status IO port!

Serial Protocols

The class CSerial interprets the input data as activity on a serial bus. There are several serial buses that can be analyzed:

  • I2C Bus, SMBus, ModBus
  • PS/2 Bus
  • SPI Bus
  • Asynchronous (RS232, RS422, RS485)
  • Infrared Remote Controls
  • Smartcards

Some of the protocols are already implemented, others must be added by you. If you add a new protocol, please send me an email. If it is cleanly written code, I will add it to the class and publish it on CodeProject.


It does not matter if you connect the input signals to the Data, Control or Status Port and to which Bit of them. But the first active line is interpreted to be the CLOCK line and the second active line is the DATA line for the PS/2 and I2C Protocols.
Example: You can connect the PS/2 Clock to the Status Port Line 2 (Pin12) and the PS/2 Data to the Status Port Line 3 (Pin 13) and connect the other inputs to ground or +5V. Obviously you can connect more than 2 input signals but they must follow behind the Clock and Data lines. You see immediately in the HTML output if you have connected correctly.

NOTE for Async

While the PS/2, I2C, SPI, etc. protocols have their own clock signal, the Asynchronous protocol will only be detected correctly if you enter Baudrate, Parity and Stop Bits correctly. If the Baudrate deviates a few percent this does not matter because with every start bit the serial analyzer synchronizes anew to the data stream.

NOTES for PS/2

If you measure a PS/2 keyboard, you will never see ASCII codes transferred on the lines. The bytes transferred are Scan Codes. When you hit a key, its Scan Code is sent to the computer. When you then release the key, the same Scan Code is sent again with a 0xF0 Byte indicating that the key has been released. The Windows API translates Scan Codes into Virtual Key Codes and then into ASCII or Unicode. See the MSDN documentation for MapVirtualKey().

The PS/2 protocol switches into a completely different mode when the computer sends a command to the keyboard (for example when it turns on/off a keyboard LED after hitting the CapsLock key). For details see chapter "Host to Device communication" in "Manual PS2 Bus.chm" in the folder Documentation!

If you are using a PS/2 keyboard and a PS/2 mouse at the same time, you will notice that the PC pulls down the clock line of the keyboard while you click or move the mouse. This is normal and means that the PC is busy. While the clock line is low, the keyboard cannot send data.

Turning off the graphics

When you analyze a very busy RS485 or I2C bus, you will get a Megabyte of Capture data within a few seconds. Then it may happen easily that LogicAnalyzer has to generate 5000 images. Although the GIF compression is speed optimized and written in fast C++, this will take a while. Additionally your browser will occupy much memory when you open a page with 5000 images.

In this case, you can turn off the graphics by selecting "Print ONLY decoded Bytes as HTML" from the Combobox. You will see something like this:

The analyzed data is pure HTML and can be copied to the clipboard and used elsewhere.

The generation without graphics is ultra fast but obviously you lose the information about timing and the exact position of glitches. If you want to see more details about a specific part of capture data, you simply copy the time code from your browser into the LogicAnalyzer fields "Analyze Start" and "Analyze End", choose to create images and analyze the data again. Then you get rapidly the portion of interest with all its details.

Context Switches

If you measure slow signals like PS/2 (that has a 10 kHz clock) or a 4800 Baud asynchronous communication, you will not have any problems at all. But if you have to measure higher frequencies it is obvious that during a measurement (capturing) the CPU should be as idle as possible.

Click the button "Measure Speed" to get an idea of how your computer is currently busy with background activity. This measurement will run a loop 10000 times and measure how long each loop runs. You will see a diagram with a width of 10000 pixels where each pixel on the X axis represents one loop cycle and the Y axis corresponds to the time that each loop was running:

Note that the speed depends strongly on how many ports you have enabled! In this example, one port was captured. You see that the loop normally takes 1.9 µs (= 520 kHz). At 6.3 ms, you see a glitch. Here Windows interrupted LogicAnalyser for 20 µs. During this time, the operating system has switched to other applications or drivers. This is called a "Context Switch" (see Wikipedia).

Some programs produce a permanent background activity like for example the chat program "Trillian", that generates slight glitches every millisecond:

This example shows how important it is that you stop all running programs and services if you have to measure frequencies above 10 kHz. The more applications and services you stop, the more CPU power will be available for the Capture Loop and the glitches will become less. Disconnect the network and USB devices! Background activity is always there even if Taskmanager shows you that the CPU is 100% idle.

Keep the PC that you use to measure free from other activities. It should not come into your mind to measure a high speed RS232 communication initiated on the SAME computer. This will fail due to the serial port drivers that occupy much of the CPU.

An unavoidable source for glitches is the clock interrupt that is triggered every 15,625 ms (64 Hz) by the mainboard. It is used to increment the Tick Counter and the System Time.

During a Context Switch, LogicAnalyser will not be able to capture data. So if you have high input frequencies, you may lose input data.....but only if during this period one of the input lines has changed its level at least twice! The Context Switches normally do not happen very frequently. If you have turned off as much as possible background activity you will see very few glitches. If during the Context Switch the input lines don't change their state you have luck and don't lose any input data.

You might ask now: "So I can never be sure that the data I see is not crippled ?"
Yes. You will always know that because LogicAnalyser automatically detects Context Switches.
If a Context Switch has occurred, the background colour in the diagram is painted dark red.
If this period was very long, you can assume that you may have lost data. If this period is very short, you know that it did not matter.

In this example the Context Switch surely did not matter because it is extremely short. Probably during the red interval, a driver has processed the PS/2 keystroke.

Single Context Switches are always displayed when they are longer than 5 raster units, otherwise they are ignored.

If the possible loss of data is actually a problem for you depends on what you plan to do with LogicAnalyser. If you want to analyze for example the signals that come out of an InfraRed remote control, you simply repeat the measurement until you get a clean diagram. For short measurements that can be repeated, the Context Switches don't matter at all.

When working with LogicAnalyzer, you will notice rapidly that Context Switches appear so rarely that the probability to fall into the middle of your signal is very low.

Please don't forget that LogicAnalyser is a free software that works with gratis hardware!
If you need a more reliable high-speed tool, you will have to pay for it!

Using the FIFO / DMA

The parallel port has a 16 Bit FIFO buffer that buffers the 8 Data Port lines. It would be possible to use this FIFO. But the disadvantage is the complicated handshake and an external clock that the FIFO requires to work. The Parallel Port also supports DMA. But the programming is complicated and a hardware handshake is also required. (See folder "Documentation" in the ZIP file.)

I found all this too awkward and so did not implement it. LogicAnalyser works to my full satisfaction and does all that I require for my needs. If you need more, implement it and send me the code!


Although it is not the intention to write raw captured data to disk, you can use two compiler switches while adding a new protocol to the Serial Analyzer: Compile with SAVE_CAPTURE_TO_FILE and click Capture which creates a BIN file and then compile with LOAD_CAPTURE_FROM_FILE and click Capture and then Analyze which analyzes data from the BIN file. So you can modify the code until it works and don't have to capture anew after each build.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Software Developer (Senior) ElmüSoft
Chile Chile
Software Engineer since 27 years.

Comments and Discussions

Question64 bit PingroupNewPast.Net30-May-12 5:49 
AnswerRe: 64 bit [modified] PinmemberElmue1-Jun-12 9:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140421.2 | Last Updated 31 May 2010
Article Copyright 2010 by Elmue
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid