In some ways you’ve got more options for getting debug out of an embedded system than you have a desktop one. Just the other day I posted an example of using a single digital output pin to convey external information about how busy a system is – something that’s rather more involved to achieve on the desktop, so let’s do a brief survey of some of the options that let you figure out what that inscrutable lump of highly refined sand on your bench is actually doing.
The basic option, that’s been around as long as embedded systems themselves, is the single I/O bit. For output it can be used to indicate entry into a specific bit of code, how loaded the system is, any number of error conditions and a thousand and one other things. Most designs feature at least one LED hooked onto that output pin to give a visual indication to the user without needing any special equipment beyond a Mk.1 eyeball. In my designs I always have a minimum of one (not red) LED which does normal service as a ‘heartbeat’ showing that the system is alive. It serves double duty as the exception indication LED when the system ends up in an unrecoverable situation. Believe me, it can be difficult to see that situation quickly (you’ll implement that on your second design, immediately after you’ve spent an hour staring at your board wondering why its not responding)….don’t underestimate how useful that is. Frankly, if you can spare the bits, put a RGB LED on there (A Wurth 150141M173100 is only about 30c) and you’ve got eight different conditions you can show, even if you chose to not provision it for production. Stick that LED on PWM outputs and you’ve got any colour under the rainbow. Perhaps not really too useful, but cool anyway.
On the input side a single bit lets you trigger certain paths through the code or change runtime conditions. Its very difficult to get a clean switching action on an input bit without a bit of additional circuitry…generally you can work around that by sampling twice (Google ‘Switch Debouncing’ for more than enough examples on how to do that) and software is always cheaper than hardware – in per unit cost at least. The lack of clean switching action can bite you if you sample very quickly or use an interrupt to capture the state change event though …. and it’s considerably worse if you just use a shorting wire, pair of tweezers or whatever other conductive implement you happen to have on your desk. The one liner algorithm description for a software debounce is simple enough; After sensing a state change wait for 20mS or so and check if it’s still changed..if it is then the transition is valid.
Moving on from the single I/O bit approaches, we very quickly end up wanting to spit out serial data; Debug strings, data values, error conditions and operational conditions really help colour in what a system is really doing as opposed to what you think it should be doing. There’s so much value in a system reporting what it’s up to that we often find output serial ports for debug application with no corresponding input, and there are multiple options for getting that.
Lets consider what the various options for serial I/O are;
- A real serial port. Normally this is configured for asynchronous operation (i.e. with start and stop bits) and it’s sometimes referred as a UART (Universal Asynchronous Receiver/Transmitter), which is often used to implement the RS232 communications protocol. It’s not really though, ‘cos RS232 specifies a lot of things that are ‘interpreted liberally’ in a debug port; The signaling levels might be 0 & 3v rather than that positive and negative 12V signaling that a ‘real’ RS232 port generally uses. With the advent of uber-cheap USB ‘TTL Serial’ interfaces from FTDI and others this kind of debug port has become very popular, and you’ll often find Logic Level serial interfaces on debug probes like the Black Magic Probe or the Segger JTAG.
- Overlaid functionality on a debug channel. If we’ve got debug communication established with a CPU via JTAG or SWD then that channel can also be used for bidirectional debug communication. On ARM it’s generally known as ‘Semihosting’ and its a virtually no-cost channel in hardware terms, but fast it isn’t. It does have a few distinct advantages to it though and we’ll talk about those later.
- Single Wire Output. When the JTAG interface is in SWD mode there are spare pins, one of which (the one thats normally used for TDO) can be used for serial debug output. There’s quite a sophisticated infrastructure behind this pin on chip and its a powerful capability. We’ll start to investigate that in a series of future posts. The big problem with SWO is that it’s output only, and if you’ve got a minimal debug setup on your board (SWCLK, SWDIO, Gnd) then SWO needs another pin. The big brother of SWO is TRACE Output, which is effectively parallel SWO, but that’s for discussion quite a lot later on.
- Real Time Terminal (RTT). This one isn’t as well known as the other options, but it levers the Segger hardware in a very clever way to deliver high speed communication with minimal target overhead. Basically, you put aside an area of memory on the target for ring buffers and then the debugger dips into those buffers while the target is running to exchange data. Since the debug capability on a CORTEX CPU doesn’t impact on the runtime speed of the target this is a pretty quick mechanism, the target ‘cost’ is limited to the ring buffers and simple memory copies to get the stuff to/from the buffers. Other probes could do this, but generally don’t, at least today.
So, that’s a quick overview of the various techniques I’m aware of, but perhaps there are more (or variations on a theme) that are worth documenting too? Of course, no one of these has to be used exclusively, and its quite common to see them used in combination on any given target. As a quick example, when I have a system that gets into a panic condition, I call the following routine;
/* Lock up tighter than a Ducks A?? and spin, flashing the error LED */
dbgprint("%s: %s line %d" EOL,(msg==NULL)?"Assert Fail":msg,file,line);
GPIO_WriteBit((GPIO_TypeDef *)GPIOport[GETGPIOPORT(PIN_HB_LED)], (1<<GETGPIOPIN(PIN_HB_LED)),(isSet=!isSet));
…this is just a few lines of code, but they’re mostly there for a good reason. The first thing we do in a panic is to switch off all the tasks, then dump an error message out of the debug serial port, before inverting the state of the error LED. We delay in a busy loop with the NOPs to avoid relying on the majority of the chip being in an operational condition. The nopCount initial value is set to make the LED flicker quite quickly. This sequence is repeated continually in case you miss the first serial output (y’know, cos you didn’t have the serial port connected, or whatever).
A GCC preprocessor definitions add quite a lot of value to what you get out;
Over the next few posts I’ll start digging into these debug options, and show just how powerful they really can be with the right processing hanging off the other end.
Now you at least appreciate that there a whole range of options for debug communication with your target, and the more sophisticated ones aren’t really more expensive than the simpler ones, they just need more setting up.