Debugging The Arduino With The SPI Interface Instead Of Serial.println()

When I first heard of the SPI interface on the Arduino, I thought that would make a great way to debug. As anyone who has used Serial.println() knows, even at speeds of 115,200 baud, data can take quite a few cycles to get out there (as a rough rule of thumb, divide the baud by 10 to get bytes/second, so at this high speed we still only get 10k chars out max per second).

In contrast, SPI has some real advantages over Serial for debug output:

  • It’s fast. Instead of max 10k/sec (or about 100 microseconds, or usec, per byte) SPI can get under 5 usec, so code doesn’t bog down in serial writing.
  • It doesn’t interrupt. Because of this, you can add debug entries inside interrupt routines, something not possible with serial.
  • You can debug tricky serial communication. Send debug on SPI, and your serial line is completely free – handy if you are writing serial code.

With all the positives, I geared up to write some SPI code and get my ‘debug on’ – then I went on the Internet and researched, and found out it had been done already. Nick Gammon had gone into SPI in two tutorials on his site here and here (and an interesting discussion of timing here). In it, he showed how to make a second Arduino a ‘slave’ to the first, receiving the SPI information and then doing something with it. In my case, I wanted to send it to my PC from the second Arduino at the full 115,200 bps. This way, the main program can send ‘light’ messages quick and simple to the second Arduino, whose task is to send them to the PC when it can. As long as I don’t overflow buffers (which we’ll talk about in a moment), I had my debug lines.

To use, first set up your second device. I used a dedicated Pro Mini and TTL-USB device, and uploaded this code:

/*
  Written by Nick Gammon, sep/11
  @: http://www.gammon.com.au/forum/?id=11329
  adapted by moi: https://utopiamechanicus.com/article/spi-debug/
*/
#include "pins_arduino.h"

char buff[600]; // about max avail for atmega168
volatile int inpoint=0, outpoint=0;
volatile int next=0;

void setup (void)
{
  Serial.begin (115200);   // debugging
  Serial.println ("nDebugging Session On...n");
  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);
  // turn on SPI in slave mode
  SPCR |= _BV(SPE);
  // now turn on interrupts
  SPCR |= _BV(SPIE);
}
ISR (SPI_STC_vect) // SPI interrupt routine
{
  // wrap-around at end of buffer
  next = ( inpoint >= sizeof(buff)-1 ? 0 : inpoint+1 );
  if (next != outpoint)  // room to add?
  {
    buff[inpoint] = SPDR; // data in
    inpoint = next;  // advance to next
    return;
  }  
  // no room - can just exit, but instead we'll 
  // replace prev char with '?' to indicate overflow
  next = ( inpoint <=0 ? sizeof(buff)-1 : inpoint-1 );
  buff[next] = '?';
}
void loop (void)
{
  noInterrupts ();  // atomic test of a 16-bit variable
  if (outpoint == inpoint) // empty?
  {
    interrupts ();
    return;
  }
  interrupts ();
  // display anything found in the circular buffer
  Serial.print (buff[outpoint]);
  noInterrupts ();
  if (++outpoint >= sizeof(buff))
    outpoint = 0;  // wrap around
  interrupts ();
}

Note that buff[600] is about as big a buffer as you can get for an ATmega168 – you can tweak it for an ATmega328 chip and increase it a bit since you have more RAM. Basically, if the chip stops working, you’ve gone too far!

Following that, connect pins 10-13 of the second (debug) Arduino to the matching pins on the main Arduino – unless it’s a Mega, where the SPI lines are higher up:

  Arduino Mega
SS Pin 10 Pin 53
SCK Pin 13 Pin 52
MISO Pin 12 Pin 50
MOSI Pin 11 Pin 51

Oh, and by the way, connect the Arduino’s grounds together. If you don’t, you could get random errors like I did where “Hello” comes out “ell” or “Helo” or “Hell” – sometimes. An error like this looked like a code issue to me, and I spent hours trying to figure out if my connection speed was dropping a character or two – till I reread a comment on common grounds. Ouch.

Finally, note the port number your second device connects to the PC with. Once you’ve got this code running, you won’t need to use the Arduino IDE with it, and can display the debug info on a serial terminal monitor instead. As well, resets aren’t an issue; in fact, I don’t use the reset line on my Pro Mini so that the debug Arduino is always ‘live’ and available to display messages – unless I press its physical reset button of course.

On the ‘regular’ side of things, add this code to your programs to allow SPI debugging (clipped from the Master example here):

#define DEBUG true
#include <SPI.h>
#if DEBUG 

  #define BEGIN_DEBUG   do { SPI.begin (); SPI.setClockDivider(SPI_CLOCK_DIV8); } while (0)
  #define TRACE(x)      SPIdebug.print   (x)
  #define TRACE2(x,y)   SPIdebug.print   (x,y)
  #define TRACELN(x)    SPIdebug.println (x)
  #define TRACELN2(x,y) SPIdebug.println (x,y)
  
  class tSPIdebug : public Print
  {
  public:
    virtual size_t write (const byte c)  { 
      digitalWrite(SS, LOW); 
      SPI.transfer (c); 
      digitalWrite(SS, HIGH); 
    }  // end of tSPIdebug::write
  }; // end of tSPIdebug
      
  // an instance of the SPIdebug object
  tSPIdebug SPIdebug;
  
#else
  #define BEGIN_DEBUG   ((void) 0)
  #define TRACE(x)      ((void) 0)
  #define TRACE2(x,y)   ((void) 0)
  #define TRACELN(x)    ((void) 0)
  #define TRACELN2(x,y) ((void) 0)
#endif // DEBUG

You can change the DEBUG setting (at the top) to true or false to enable/disable the display – in fact, I find the program works fine with it always on, even if the second Arduino is not connected – this gives you an opportunity to add diagnostic debugging to a production device, and simply plug in the second Arduino to monitor it in use.

From here, just code as normal, and send debug statements out as needed:

TRACE ("Here's a SPI trace statement w/o a line break");
TRACELN ("Here's one with it");

Be careful of the size of the messages – don’t forget, if 600 characters ‘backs up’ on the other machine before it can be sent to the PC, it will drop characters. So avoid tight loops with large messages, for example (by the way, the second Arduino will indicate the start of a string of dropped characters by outputting a ‘?’)

And that’s about it.

In practice, I can connect the lines between the Arduinos, send debug messages to a second serial display (I like the Open Source RealTerm) and have it running onscreen at all times – like a debug terminal in a regular IDE. And since SPI doesn’t interfere with my project designs, it’s a very handy way to debug – much more useful that Serial() was for me.

Give it a try – SPI isn’t the only way to debug, but it can be convenient in some situations, especially if you are fiddling with the serial port already in your code – so why share, when everyone can ‘call home’ on their own private line?

Comments are closed.