When things go wrong, although your program logic appears to be correct, it is time to look at the signals going into the MCU and coming out of the MCU. The best tool for that is a logic analyzer.

Introduction

A logic analyzer records and displays signals similarly as an oscilloscope does. In contrast to an oscilloscope, it samples only the logic level, i.e., whether the signal is above or below a certain threshold voltage. What it lacks in resolution, it usually makes good in the number of input channels, which are 4 up to 136 instead of 2 to 4 as provided by an oscilloscope.

The large number of channels is in particular important when you want to deal with parallel bus systems. These days, however, the parallel bus systems are usually only inside the MCU and not broken out. Most of the communication is done in a serial manner using I2C, SPI, or asynchronous communication. However, nevertheless, it is often very useful to monitor a large number of GPIOs in order to understand the temporal coordination of different signals. In particular, when you are chasing time-dependent or order-dependent bugs, this kind of monitoring can be extremely helpful.

Another advantage offered by logic analyzers is that they provide so-called protocol decoders, which translate the bit stream of a serial communication into meaningful symbols. An example is shown in the featured image of this post. The bit stream is interpreted as I2C operations. On top of that, these I2C operations are interpreted as commands for reading a real time clock.

The best way to learn about how to use a logic analyzer is to put it into action. For this purpose, I will introduce a small example. We will then use three different logic analyzers on this example:

  • An Arduino Uno turned into a logic analyzer,
  • a very low-cost (€10) analyzer which you find announced as 24Mhz 8ch logic analyzer at eBay and Amazon,
  • the Saleae Logic Pro 8, a more high-end analyzer.

Example

A friend asks you for a favor, namely, to build him a small device that translates serially sent decimal digits in ASCII code (at 19200 baud, 1 start bit, 8 data bits, 1 stop bit, no parity) into a parallel representation (using 4 output lines). Without much ado, you type the following sketch into your computer, upload it to an Arduino Uno, and hand it to your friend, telling him where to connect the wires.

// Receive decinmal digits on serial line and
// set data lines 8/9/10/11 to corresponding value 

// output pins 
byte outpin[4] = { 8, 9, 10, 11 };

void setup()
{
  Serial.begin(19200);
  for (byte i = 0; i < sizeof(outpin); i++) 
    pinMode(outpin[i], OUTPUT); // init output pins
}

void loop()
{
  char inp;
  byte out;

  while (Serial.available()) {
    out = -1;
    inp = toupper(Serial.read());
    if (inp >= '0' && inp <= '9') out = inp - '0';
    if (out >= 0) setoutpin(out);
  }
}

// set output pin corresponding to decimal digit received
void setoutpin(byte val)
{
  for (byte i = 0; i < sizeof(outpin); i++) {
    if (val&1) digitalWrite(outpin[i], HIGH);
    else digitalWrite(outpin[i], LOW);
    val = val >> 1;
  }
}

The next day, your (by now former) friend returns the Uno and bitterly complains that the board sometimes had values at the output pins that were much higher than 9, so it is definitely high time to debug the thing. For this purpose, you insert one line before the while loop

Serial.write(random(20) + '0');

in order to simulate serial communication coming in. After that, you upload the sketch again and connect Arduino pin 0 (RX) with pin 1 (TX). Now, during each loop iteration, one serial byte is sent and received, and you are all set to check what is going on with the output pins (make a mental note to remove the TX-RX shortcut when you try to upload your next sketch).

Uno turned into logic analyzer

Let us turn a second Uno (or Nano, Pro Mini, or something similar) into a logic analyzer with 6 input channels, namely, the Arduino pins 8 to 13. For this purpose, you need to download, first of all, the logic analyzer sketch by gillham. Actually, you better should download my fork of this sketch because the order of the samples in the original sketch is not what PulseView, the GUI for the logic analyzer, expects. Further, I made some improvements such as making the triggering working again, making the timing more accurate, and enlarged the capture buffer.

Install one of the PulseView versions that are offered on the sigrok download page. Now you can set up your logic analyzer Uno and your device under test (DUT) Uno as shown in the following Fritzing sketch. Because PulseView expects an immediate reply when it wants to connect to the Arduino, you need to disable the auto-reset feature of the Arduino. The simplest way of doing so is to put an electrolytic cap of 10 µF or more between RESET and GND. Put the marked negative wire into GND and the other into RESET.

Logic analyzer setup

Plug the USB plug into the logic analyzer Uno, upload the logic analyzer sketch, and start PulseView. Now you have to connect to the logic analyzer. Click on the small triangle close to the tools icon.

Connecting to Arduino logic analyzer

Set all the things as indicated above, select the right serial port, and press the button Scan for devices using driver above. Then in the bottom box, AGLArevV1 with 6 channels will show up, and you can connect by clicking on OK. AGLA stands for Arduino Generic Logic Analyzer, a name coined by the original author gillham and rev indicates that the samples are returned in reverse order.

You can now set the sample frequency on the top right (100 kHz is a good value). This is the frequency at which the logic analyzer takes samples. 100 kHz means that every 10 µs a sample is taken. Left to the sample frequency field is the memory depth field. Here, you can choose how many consecutive samples you want to take. In our case, the highest value for our Uno analyzer, namely, 1.536 k samples, is already selected. It means that for each acquisition run with 100 kHz, you can record signals for 15 ms. Finally, select the active probes (by clicking on the probe icon and then selecting the active channels). After having set up everything, let us put the logic analyzer to work by pressing Run (top left). Then you might get a result as shown below.

Capturing digital signals with the Arduino logic analyzer

In row 0, you see the serial line data. In line 1 to 4, the parallel output values are visualized. It would be good, if all these values would already be interpreted. However, this is what protocol decoders are for! Clicking on the yellow/green wave symbol on the top right provides you with a list of decoders of which you should choose Parallel and UART (by double-clicking). Both decoders can be configured by clicking on the respective symbols to the left, as shown below.

Decoder settings

After the configuration, the scanned and interpreted signals may look as follows.

Scanned data interpreted by protocol decoders

In this case, all the serial signals look reasonable. It can happen that the first serial bytes look funny since the decoder needs some time to synchronize. In any case, the interesting part is that there are small parts in the parallel decoding line that appear to be different. When you look at the part between 2 and 1, you will notice that there might be a different value. Let us zoom in on it (by using the plus-button or the mouse wheel). After that, you can activate the cursor (the two blue flags) and measure the time the part between 2 and 1 is active (by dragging the flags). It is 10 µs.

Zoomed view of value change with activated cursor

The logic analyzer appears to suggest that when changing from one value to another, we might go through a transitional state of 10 µs that is not intended. However, given that 10 µs is just the sample time, one might ask whether it is a measuring artifact.

So let us increase the sample frequency to 500 kHz, which means that we will now only see signals recorded for 3 ms. In order to focus on the important part, one can use a so-called trigger. This is a condition, the logic analyzer waits for and starts only recording when the condition is met. The trigger can be configured by clicking on the colored channel indicators on the left.

Usually one can define triggers based on the level of a signal or based on edges. Our Uno logic analyzer supports only level trigger. In order to catch one of the states when an unusual high value is output, set the trigger for line 2 and line 4 to high. Then we will catch outputs of 10 or higher. After you have done that, the trigger symbols will be displayed on the right side of the channel display.

Further, click on the tool symbol left of the probe symbol. Here, you can configure how much of the recording before the trigger fires is displayed. A good value is 20%. Start the acquisition of data again by pressing Run. Now the result might look like as follows.

Acquisition using trigger

The vertical dashed line shows the trigger time point. The serial data looks odd, probably because the decoder could not synchronize. What is interesting, though, is that we now have definitely a case with a value out of range. As above, we zoom in and apply the cursor again.

Zooming in on the problem

This state cannot be explained away as a measurement artifact. There are actually two samples now and the overlap period appears to be 4 µs. Perhaps, with a higher sample frequency, we might find out more. You can sample up to a frequency of 5 MHz. However, when going above 1 MHz, the pre-trigger samples are not shown as expected. Only the triggering sample is guaranteed to be displayed, but not the percentage of samples you have asked for. The reason is that at such high sample rates, it is not possible to do the bookkeeping for maintaining a circular buffer.

In any case, our current measurement device looks somewhat inappropriate for everyday work. Let me list a few issues that makes the Arduino Uno less than ideal working as a logic analyzer:

  • 1536 samples is not very much;
  • triggering conditions are very basic;
  • triggering is crippled when going above 1 MHz;
  • highest sample frequency is 5 MHz.

In fact, I only showed you the Uno logic analyzer in order to whet your appetite for a real logic analyzer. However, having learned about PulseView is definitely of help, since this piece of Open Source software is usable on almost all logic analyzers on the market. When you want to learn about all features, have a look at the manual.

24MHz 8ch logic analyzers

For roughly €10, you can get a logic analyzer that is more powerful than the Uno logic analyzer by many orders of magnitude. As mentioned above, you can get them from places such as eBay and Amazon. They are all based on the Cypress FX2 microcontroller. The best news is: They are all supported by PulseView and work €10(almost) flawlessly.

One version of the 24MHz 8ch logic analyzer

The specs are: 8 channels up to 24 MHz sampling frequency and the memory depth is virtually unlimited, because all captured data is streamed over USB. But beware: 24MHz is only sustainable if the communication on the USB bus is not clogged by other devices. Further, the USB cables delivered with the analyzer often are of a very bad quality and should be thrown into the trash can right away!

Comparing the technical specification with our Uno (6 channels, 1 MHz max with trigger, 1.5K memory depth), we have a clear winner here. So let us connect the logic analyzer to the DUT. Well, the cable you get with the logic analyzer is not the most convenient and if you plan to work regularly with the logic analyzer, you might want to consider buying a better cable. For instance, there are Bus Pirate cables out there, which have the right 10-pin plug for the logic analyzer and probes on the other end. It will cost you another €10, though. But then, high-end probes will cost you around €10 per piece! At the sigrok website, you find an interesting comparison of different probes.

Connecting PulseView to the logic analyzer is straight-forward. Here you need to select the fx2lafw driver and the USB interface. When you now press the Scan button, then Saleae Logic with 8 channels or something similar should show up, and you can connect to it by pressing OK.

Let us repeat the measurement from above, but choose 24 MHz sample frequency and 1 M samples. Set up the trigger such that all the data lines D1-D4 should be high (representing the value hexadecimal F), and set the Pre-trigger capture ratio to 20%. Finally, set up the UART and Parallel decoder as above. The result might look like as shown in the following picture.

Focusing on the culprit

It is clearly visible that the situation in which the trigger condition is satisfied happens when we switch from 9 to 7. So let us zoom in on it and measure how much time it takes to change the output of one output line.

Zooming in and measuring

As you can see, it takes 5 µs of switching line D4 from high to low. So, 5 µs appears to be the execution time of the function call digitalWrite(i, LOW) (but see further down!). This brings up two interesting questions. First, which sampling frequency is appropriate? Second, how can we improve on the design of our serial to parallel converter?

By the Nyquist-Shannon sampling theorem you need a sampling frequency that is at least double as high as the highest frequency, when you want to perfectly discretize an analog signal. In our case, we do not want to discretize an analog signal, but capture a digital signal. So, if we have a clock signal (of, e.g., an SPI bus) with a 50% duty cycle, then sampling with a rate that is two times the clock frequency means two sampling points per period, ideally one for each state. However, there is a high probability that a high or low state will be missed. Saleae recommends for this reason a sampling frequency at least four times the bandwidth of the digital signal, in our case four times the SPI clock frequency. Others recommend six times the bandwidth. Now, if you want to measure times, you should be aware that the worst case error is always one sample interval. So, in the case of the measurement above the time is 4.995 ± 0.041 µs, i.e., the error is neglectable.

So what can we do about our faulty design of our serial to parallel converter? In our case, we have two options. First we could set the outputs by using PORTB, the output register of the Arduino. Then all lines would change their value at exactly the same time. The code would look like as follows:

void setoutpin(byte val)
{
  PORTB = (PORTB & 0xF0) | (val & 0x0F);
}

We read the current value of the output pins, and select the higher 4 bits (because we do not want to change them) and then this is bitwise ORed with the lower 4 bits of the variable val and sent to the output pins. Let us have a look how this works.

Crosstalk

I used the same trigger as before and indeed got very quickly a result. As you can see, now all signals change at exactly the same time (as far as we can measure), but now we have crosstalk, i.e., electromagnetic interference on line D4. This is most likely caused by simultaneous switching off three lines and the flying probe wires. Well, it can probably be ignored because the crosstalk will not happen without the probe wires.

However, this phenomenon points to a general problem. At the time, when the parallel lines are changed, even if it looks as if all lines are changed at exactly the same time, the receiving unit might register the switch at different times and the bus is generally unstable in this transition period. So, it is a good idea to wait for some time until everything is stable again. In fact, parallel bus systems usually have an extra clock signal, where one edge signals that all lines are in a stable state. And this is what we probably should add to the system (also on the receiving side!).

Finally, there is the question, do we need a more powerful logic analyzer? As mentioned above, you should sample at a rate that is at least 4 times higher than the bandwidth of your system. In our case, this would mean 6 MHz is the maximum we should measure. Indeed, most of the time, the clock rates of the buses a hobbyist uses are lower. So, I guess 90% of all use cases for hobbyists can be covered by the el cheapo analyzer. And then for some, e.g., fast SPI buses, one might be able to reduce the bus frequency for debugging. But still, one may ask what one can do with a more powerful logic analyzer.

Saleae Logic Pro 8

Saleae is the company that made USB logic analyzers popular. In the beginning (in the 70s) logic analyzers were heavy artillery, something only research and development labs could buy. By streaming everything over USB to a PC, the amount of electronics necessary was reduced to a minimum, and that was what Saleae introduced. Actually, the electronics in the first Saleae logic analyzers were nothing more than you could buy on the development board of the Cypress FX2 microcontroller (plus buffers). And that is exactly what you get in the 24 MHz 8ch analyzers. They are clones of the first generation Saleae logic analyzers, and they also still work with the first generation software of Saleae, called Logic. Using the Saleae software on these clones, however, violates the license conditions of the software. But then you have PulseView these days.

Saleae has developed USB-streaming logic analyzers much further over the years, and their current high-end models use USB 3.0. They have adjustable logic levels, a maximal sample frequency of 500 MHz, analog inputs (with a bandwidth of 5 MHz), and up to 16 channels. These are the Logic Pro 8 and Logic Pro 16. There is also a more modest model called Logic 8, which uses only USB 2.0. And it is possible to get a discount for this model when you are a student or electronic enthusiast (not making money with it).

In addition to the improvements on the hardware side, the software has improved over the years as well. In fact, the software is what makes the difference. And Saleae supported right from the beginning all three platforms, which as a Mac user one values very much. Logic 2, the current software version, is very versatile and easy to use. Comparing it with PulseView, my preference would be Logic 2. However, this is, of course, a matter of personal taste. The number of decoders is definitely a plus for PulseView, while the ease of interaction counts for Logic 2. And the best thing is, you can export the raw acquisition data from Logic 2 and import it into PulseView (if you need a decoder not supported by Logic 2). It might be possible to use the Logic Pro 8 with PulseView according to the sigrok website. However, the analog part is not yet supported.

Let us set up the Logic Pro 8 analyzer in order to do some measurements on the Arduino.

Saleae Pro 8 in action

In order to show off the strength of the different logic analyzers (and GUIs) let us try to determine the time it takes to change the output signal of an Arduino. Here, we will measure how long it takes to change one bit by using port manipulation (that should take two cycles or 125 ns), to change the output of one port byte, i.e., 8 output bits at the same time by using port manipulation (1 cycle, or 67.5 ns), or by using the Arduino digitalWrite call (many cycles). We will use the following sketch.

void setup ()
{
  DDRB = 0x3F; // all PORTB pins are output (Arduino pins 8-13)
  pinMode(8, OUTPUT);
  digitalWrite(8, LOW);
}

void loop ()
{
  PORTB |= 0x01; // switch on bit 0
  PORTB |= 0x02; // switch on bit 1
  PORTB &= ~0x02; // switch off bit 1
  PORTB = 0x0F;  // switch on all 4 low bits
  PORTB = 0x00;  // switch off all 4 low bits
  digitalWrite(8, HIGH); // switch on lowest bit again
  PORTB = 0; // and everything off again
  delayMicroseconds(20); // wait 20 µsec
}

Let us a look at what the Logic Pro 8 makes out of it.

Measuring I/O times with the Saleae Logic Pro 8 on the fly

When moving the mouse over a waveform, the timing and frequency is automatically measured and displayed. As you can see, the digitalWrite operation takes 2.246 µs. In addition, you can also make explicit measurements, for example when you want to relate edges of different channels. This is shown in the next picture.

Making explicit measurement

As you can see, we get results that are to be expected. Changing a bit is 126 ns, changing the entire byte is 66 ns, which is what we expected more or less. With a sample frequency of 500 MHz, we have a worst case error of 2 ns, which fits the bill.

Let’s see what the 24 MHz 8 ch analyzer makes out of it. Here we see it in action:

The 24MHz 8ch logic analyzer in action using a Bus Pirate cable
Changing one output bit

With a sample frequency of 24 MHz, it is no surprise that we measure just 42 ns (the sample interval) for changing the entire output byte, which in reality is 67.5 ns. Changing a bit is 125 ns, which is closer to the truth, but the measurement has, of course, an error of ± 42 ns. For the execution of the digitalWrite function, we measure again 2.246 µs. Here we have to use the cursor for each measurement.

Finally, let us try out our Uno logic analyzer. Even with 5 MHz sampling frequency (which means 200 ns sampling interval), we know that we will probably miss some signals. But let us see how badly it goes.

Capturing at 5 MHz

As you can see, we miss indeed a lot of these short pulses, which is no surprise because the pulses are shorter than the sampling interval. When one measures the time it takes to execute digitalWrite, it is again 2.2 µs.

The only remaining mystery we have to solve is why we measured 2.2 µs here and in the previous section it was 5 µs for the execution time of digitalWrite. There are a couple of reasons for it. First, here we measured Arduino pin 8, which is not a PWM pin. When doing the same for pin 11, you get 3 µs. Second, here, we measured from the edge caused by a port manipulation instruction to the edge caused by digitalWrite and above we measured between two edges caused both by digitalWrite. If you do that with two simple digitalWrite calls, you get indeed 4 µs, meaning that even after having changed the output pin, digitalWrite still executes some code. Third, when you now look at the first program we used, there was a loop, index computations, etc., so probably another 8-10 or so machine instructions, which will add up to another microsecond, giving us the 5 µs that we measured.

Summary

I would not give away my logic analyzer anymore. It is definitely an instrument that is indispensable when developing embedded systems. For example, when improving the Arduino logic analyzer sketch, the real logic analyzer was very helpful in locating some of the bugs (caused by incompatibilities between PulseView and AGLA).

So, which logic analyzer is the right one? Would I recommend using the Arduino logic analyzer for everyday work? Definitely not! If your budget is tightly restricted, the 24MHz 8ch variety is the best bang for the buck. And it will probably cover almost all cases you care about. If you have more money to spend, the Saleae Logic 8 with a discount may be an option for you. However, there are tons of alternatives and most of them are supported by PulseView, which I would consider as an important criterion for choosing a logic analyzer.

Let me finally point you to the interesting YouTube video by the original author of PulseView that tells you everything about sigrok and logic analyzers. Further, if you really want to own a high-end logic analyzer, you should have a look at the KEYSIGHT 16803A 102-Channel Portable Logic Analyzer, which sells for the bargain price of 14,500 US$.