Article

Key options for analog debugging on bare-metal systems

The title “analog debugging” seems a bit enigmatic. After reading it, an embedded firmware developer may suffer from cognitive dissonance; but trust me, it will make sense later. What the title alludes to is the task of working with signals being processed in microcontrollers. Many of the tasks involving smaller MCUs have to do with processing raw signals from sensors such as microphones, hydrophones, and pressure sensors.  Some of these signals need to be cleaned up and then processed.

This processing may use multiple digital signal processing (DSP) firmware techniques such as FIR and IIR filters, mixers, and FFTs. With the signals streaming through the micro, the data we would like to validate through debugging may be extensive. For example, what does a signal look like after running through a filter, or what’s the output of a correlator as a signal passes through it. This is where analog debugging comes in. It allows you to watch a signal in real-time.

Smaller microcontrollers may lack some of the powerful debugging tools—such as BDM, J-Tag, and SWD—which larger processors have. Smaller MCUs may also be run as base-metal, not using an operating system, meaning that any debugging tools available in an operating system are missing. This lack of tools and the complexity of real-time signal processing can make debugging the code problematic. However, debugging requires some insight into what’s happening to data inside the microprocessor, and when processing streams analog signals, you may want to view what these actually look like in the analog domain.

Typically, when debugging firmware, an engineer would use a serial port on the microcontroller, if it exists, to print out variable values or indicators of the code that is being executed. There are a number of issues here. First, in a small MCU, there may not be enough room for the print routines as memory can be scarce. Second, speed could be an issue. In a DSP-type processing, we are typically doing real-time processing on the incoming signal one sample after another and we can’t stop to handle a fairly long print call. Third, a print routine will normally use an interrupt and this can cause issues in a real-time system. And lastly, dumping data to a serial port would not give you a direct analog view of the data you are processing.

For example, let’s say you take in a signal from a sensor using an analog-to-digital converter (ADC). You can hang a oscilloscope on the output of the sensor and see the signal and noise in an analog view. However, if you view that same signal via the serial port, after the ADC is read by the MCU and sent out to this serial port, you would see a bunch of numbers. Now, you could put these numbers in a spreadsheet and graph it, or set up another piece of equipment with a digital and analog converter and a display to see the data again. But it seems a bit slow and tedious and certainly not real-time.

Now, if a serial port isn’t available or isn’t appropriate for debugging, an engineer may use an LED connected to the MCU that can be turned on or off based on various conditions in the program being debugged. An oscilloscope can be connected to the LED, or an available I/O line, to view the status or measure the timing between status changes by toggling the LED or I/O line in firmware. It works very well but doesn’t fit the idea of getting an analog view of the signal as it’s being processed by various stages of filters, correlators, slicers, and mixers.

Analog debugging with DAC

What would be nice is a place to connect an oscilloscope probe where we could dump processed samples quickly in the firmware. So, what can we use? The first idea is attaching a digital-to-analog converter (DAC) to the MCU, or better yet use one that is available as a peripheral on the MCU.

To try out this technique, I attached Analog Devices’ AD7801, an 8-bit DAC, to an Arduino Nano design I was working on. The heart of the Nano is a Microchip ATmega328, which doesn’t have a DAC onboard. AD7801 uses a parallel input of 8 data lines that are clocked in by another line and are very fast to write. It’s important to note that we can view 8-bit data with this setup, but 10-bit, 12-bit, or other size could be used with other DACs or it could be scaled to fit the 8-bit DAC. I connected the 8 data lines to port DAC on the Arduino and the WR line to D13 of the Arduino as seen in Figure 1.

Figure 1 The DAC is connected to the Arduino via 8 data lines.

Now, to send data to the DAC, only 3 lines of Arduino IDE C code are required:

PORTD = data; // Put data byte on D0 thru D7

PORTB = PORTB & B11011111; // Pull D13 low to latch data into the AD7801

PORTB = PORTB | B00100000; // Pull D13 high

On the 16-MHz Arduino, this code takes about 5 cycles or about 312 ns, and settling time of the DAC is 1.2 us. So, you can see this method of data display can be accomplished relatively fast, without an interrupt, and without much code. This code can be inserted in the appropriate location of the firmware to view the data of interest. It may be cleaner to put the 3 lines of code into a macro, or a function. If you create a function for this, it should be compiled with a “always_inline” pragmam to make sure it runs fast.

Now with the DAC connected, let’s look at a few examples of debugging. Take a look at Figure 2.

Figure 2 An oscilloscope snapshot shows how analog debugging works in a DAC-enabled setup.

It’s a scope snapshot of an incoming sensor signal—graticule removed for clarity. The bottom trace (pink/purple) is the raw signal as it’s entering an ADC pin on the ATmega328. You can see significant noise on this line. The upper trace (yellow) is the same signal after some filtering and other processing is done in the MCU’s firmware.

The DAC write debug code was inserted in this flow, so the sample timing in the DAC is same as the ADC. You could also do decimation on the signal in the MCU, if needed. Ignoring the “spikes” in the signal for now, we see the processing has removed most of the noise. We now have a clean signal that we can evaluate. It should be noted that the DAC output is a continuous stream of the signal and not just some short memory buffered capture.

But what are the “spikes”? These are some debug features I intentionally put in the code to see how processing was proceeding. The signal you see is actually a proprietary digital signal being corrupted by the signaling medium. The code’s task is to read the digital packet by:

  • Discovering the preambles “start of packet” sequence of symbols
  • Keeping track of sample timing so we can slice the sample at the appropriate time
  • Continuing to collect samples until the end of the packet

Now, let’s take a look at Figure 3.

Figure 3 The processed signal is shown with addition of annotations.

It’s a view of the processed signal with annotations added. What I did in the code was scale the signal from 50 minimum to 200 maximum. This allowed for some room, in the 256 available values, to add “spikes” above and below the signal. The first thing we see is the “spike” marked “preamble detected”. It’s created when the code verifies that the preamble (B00000011) has been found, and is simple to generate with the following Arduino IDE code:

PORTD = 255; // Put 255 on D0 thru D7

PORTB = PORTB & B11011111; // Pull D13 low to latch data into the AD7801

PORTB = PORTB | B00100000; // Pull D13 high

It creates a 312-ns wide mark on the scope trace with an amplitude equal to the maximum voltage of the DAC.

The “spikes” going up and down inside the signal trace are markers indicating where the code has determined the symbol boundaries are. It’s very important in slicing the symbols at the correct time, and it becomes critical when long runs of 0’s or 1’s occur. This is because there is no transition from a 0-to-1 or 1-to-0 to be discovered. Viewing these “spikes” on a scope is very useful as it allows us to verify the actual timing and to verify that none are missing. These symbol boundary “spikes” are created by sending a 127 to the DAC using the following Arduino IDE code, which is inserted in the appropriate place in the symbol timing code:

PORTD = 127; // Put 127 on D0 thru D7

PORTB = PORTB & B11011111; // Pull D13 low to latch data into the AD7801

PORTB = PORTB | B00100000; // Pull D13 high

Symbol transitions are marked with “spikes” by sending the DAC a 0 using the following code, which is inserted in the code that watches symbol transitions from 0-to-1 or 1-to-0:

PORTD = 0; // Put 0 on D0 thru D7

PORTB = PORTB & B11011111; // Pull D13 low to latch data into the AD7801

PORTB = PORTB | B00100000; // Pull D13 high

You can see that using a DAC to view debug information overlayed on the actual processed trace can greatly assist in debugging various parts of your code. It’s many times more powerful than using an LED, an I/O line and a scope. It can also be more useful than serial port sending data as timing information.

Those of you with a keen eye may have noticed, on the right edge of Figure 3, that the probe attenuation is not x1 or x10 but x53.5. It’s a trick that can be done on many of the newer scopes and is sometimes called a custom attenuation setting. The reason it is set to 53.5 is that it allows for reading the DAC’s 8-bit input value to be read directly using the scope’s cursors. That is, if I slide my cursor up to the top of the preamble detect “spike”, the scope cursor reading is 255, or 127 if I move the cursor to the end of the symbol boundary “spike”.

When using an 8-bit DAC, the formula for this setting is 255/MaxVolts, the output voltage of the DAC when fed a maximum binary input, 255 in this case.  So, for a 5-V rail, the custom setting would be 51.0—my rail was only 4.77 V, so my number was 53.5. When using a 10:1 probe, you may need to multiply this number by 10 when entering it on your scope.

It’s very handy because you can directly read the number that the DAC was set to; in other words, the value that the internal variable had when it was used in the call to the DAC. Think about that for a second. You could, in essence, read variables “live” this way, almost as good as a print statement but faster and non-intrusive. Note that noise and resolution of your scope’s vertical scale will reduce the accuracy, so you may only get within ±1 or 2 counts of the actual value, still pretty good.

Beside streaming a signal, using this technique, an 8-bit DAC could also simultaneously represent the states of 8 binary flags, or a current value of an 8-bit variable in your program. In other words, using an 8-bit DAC gives us 8x the information as monitoring a single I/O line would give.

Analog debugging with PWM

Now, what if you don’t have a DAC to work with? You can do something similar using a pulse-width-modulator (PWM) peripheral on your microcontroller. Many small MCUs have PWMs and when they do, they usually have a number of them—often 6. One of the differences between a PWM and a DAC is that the PWM output needs to be filtered with a low-pass filter to convert the output to a voltage level. So, when you send samples of a signal to the PWM, the voltage level recreates the signal that can be displayed on an oscilloscope as was done with the DAC. The filtering can be performed with a simple RC filter.

There are a few caveats here though; the low-pass filter means that only signals with low frequency content can be displayed and the response is slower. Therefore, you should initialize the frequency of the PWM to the highest frequency that is available. On the 16 MHz ATmega328, the PWMs can be set with a maximum frequency of around 31 kHz, so the low-pass signal can be designed for frequency content of about 3-4 kHz.

Arduino IDE code for using a PWM is, after initialization, even simpler than the DAC code. The code for writing an 8-bit value to the PWM is as simple as:

analogWrite(PinNumber, data)

Here “data” is an 8-bit sample value and “PinNumber” is the pin number for the PWM output.

Although the PWM may not be as accurate or capable of displaying higher frequency signals, it has an interesting capability. Some MCUs have up to 6 PWMs, which means that up to 6 outputs can deliver live data. You could have a 4-trace scope displaying 4 variables simultaneously, leaving 2 spare PWM outputs. Also with 2 outputs, either PWM or DAC, you could deliver I & Q data that is often used in the DSP processing of signals—which allows you to explore negative frequencies. It’s important to note that, just like the DAC code, the PWM code doesn’t require interrupts.

Other debugging tools

Another powerful tool that can be used on signals being delivered by a DAC or PWM is a frequency spectrum. The scope screenshot in Figure 4 shows an example of this. The upper trace shows a waveform that is being generated in the microcontroller. This signal is actually two frequencies (f1 = 165 Hz and f2 = 135 Hz) being mixed, or multiplied, sample by sample, and then sent out to the DAC as they are generated. In a frequency mix, the result is a frequency at the sum of the frequencies and the difference of the frequencies. The original generating frequencies are suppressed by the mix operation, as can be seen in the FFT at the lower half of the scope trace. Most oscilloscopes, even of the hobbyist level, offer an FFT as one of the math operations.

Figure 4 The oscilloscope screenshot shows how a frequency spectrum mixes or multiplies frequencies.

If your system does not have a DAC or a PWM, there are still things you could use to get some information about signals in your running firmware. For instance, you could write code to bit-bang a PWM signal. Though it would be most likely useful for low-frequency signals or slowly-changing variables.

Hopefully, the idea of analog debugging is clearer now. The main concept of streaming data from the firmware and displaying it on an oscilloscope can be a powerful tool and can speed up your signal processing firmware debugging. When feasible, it may be useful to choose an MCU with a DAC peripheral or to incorporate a DAC in your first prototype PCB. It can always be removed later or made a NO-POP in the bill of materials (BOM).

Damian Bonicatto is a consulting engineer with decades of experience in embedded hardware, firmware and system design. He holds 30 patents.

Phoenix Bonicatto is a freelance writer.

Related Content

0 comments on “Key options for analog debugging on bare-metal systems

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.