Analog debugging on bare-metal systems - EDN

2022-04-02 09:42:14 By : Ms. Alina Shi

I admit the title “analog debugging” is 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 micros have to do with processing raw signals from sensors such as microphones, hydrophones, pressure sensors, etc.  Some of these signals need to be cleaned up or otherwise processed.  This processing may use multiple digital signal processing (DSP) firmware techniques such as FIR and IIR filters, mixers, FFTs, etc.  

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 at after running through a filter, or what is 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 ) that larger processors have. 

Smaller micros 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.  But, debugging requires some insight into what is happening to data inside the microprocessor, and when processing streaming 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 micro, if it exists, to print out variable values or indicators of the code that is being executed.  There are a number of issues here:

For example, let’s say you take in a signal from a sensor using an analog to digital converter (ADC).  You can hang a scope on the output of the sensor and see the signal and noise in an analog view but, if you view that same signal via the serial port, after the ADC is read by the micro and sent out 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 this seems a bit slow and tedious and certainly not real-time.

Now, if a serial port isn’t available or appropriate for debugging, an engineer may use an LED connected to the micro 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.  This works very well but doesn’t fit the idea of getting an analog view of our signal as it is being processed by various stages of  filters, correlators, slicers, mixers, etc.

What would be nice would be 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 to attach a DAC to the micro, or better yet, use one that is available as a peripheral on the micro.  To try out this technique, I attached an Analog Devices AD7801, 8-bit DAC to an Arduino Nano design I was working on.  The heart of the Nano is a Microchip ATmega328, which does not have a DAC onboard.  The AD7801 uses a parallel input of 8 data lines which are clocked in by one other line; very fast to write to and very simple.  (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 eight data lines to port D on the Arduino and the WR line to D13 of the Arduino as seen in Figure 1.

Figure 1 Connection of the Analog Devices AD7801, 8-bit DAC to an Arduino Nano design.

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” pragma 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 A scope snapshot of an incoming sensor signal.

This is a scope snapshot of an incoming sensor signal (graticule removed for clarity).  The bottom trace (pink/purple) is the raw signal as it is 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 done in the micro’s firmware.  Our DAC write debug code was inserted in this flow so the sample timing in the DAC is the same as the ADC.  You could also do decimation on the signal in the micro, 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 codes task is to read the digital packet by:

Let’s take a look at Figure 3:

Figure 3 A view of the processed signal with annotations added.

Figure 3 shows 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”.  This was 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

This 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.  This is very important in slicing the symbols at the correct time, and 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 boundaries “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 that is inserted in the code the watches of 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 info overlayed on the actual processed trace, can greatly assist in debugging various parts of your code.  It is 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 is included.

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.  This is 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 scopes 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.  MaxVolts is the output voltage of the DAC when fed a maximum binary input; 255 in this example.  So, for a 5v 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.

This is very handy because you can directly read the number that the DAC was set to, or 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 the resolution of your scopes 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.

Ok, 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 micro.  Many small micros have PWMs and when they do, they usually have a number of them…often six.   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.  This filtering can be done 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, i.e., the response is slower.  Because of this you should initialize the frequency of the PWM to the highest frequency that is available.  On the 16MHz ATmega328, the PWMs can be set with a maximum frequency of around 31 kHz so the low-pass signal should 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:

where “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.  As some micros have up to 6 PWMs meaning up to 6 outputs can deliver data live.  You could have a four trace scope displaying four variables simultaneously, leaving two spare PWM outputs.  Also with two outputs, either PWM or DAC, you could deliver I & Q data that is often used in the DSP processing of signals (and allows you to explore negative frequencies).  Note that, just like the DAC code, the PWM code does not require interrupts either.

Another powerful tool 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.

Figure 4 An example of using a frequency spectrum on signals being delivered by a DAC or PWM.

The upper trace shows a waveform that is being generated in the micro.  This signal is actually two frequencies (f1 = 165 Hz and f2 = 135 Hz) being mixed, or multiplied, sample by sample, and then sent out 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, clearly, in the FFT at the lower half of the scope trace.  Most oscilloscopes—even hobbyist level—offer an FFT as one of the math operations.

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 this would probably be only 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 a micro 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 BOM.

You must Sign in or Register to post a comment.