Sometimes it can be tricky to debug code on microprocessors, particularly if you are using the serial port for whatever-it-is you are doing (for example, connecting to a serial device like a barcode scanner, GPS, RFID reader, and so on).
The code below demonstrates how you can achieve this by sending debugging "prints" out from the device you are testing, via SPI, to a second processor. The second processor simply sits there awaiting incoming bytes, and then dumps them out its serial port. Thus you effectively get a second serial port, via SPI.
SPI is quite fast (that is, around 3 microseconds per byte) so the debugging shouldn't slow it down too much.
This is suitable for debugging inside an interrupt service routine (ISR) because doing the SPI.transfer does not use interrupts.
SPI debugging
The example code below uses a #define to let you turn the debugging on or off. When off, you will save memory (and free up the SPI pins for other uses).
Of course, this technique won't work if you need the SPI pins for something else. The SPI pins on an Arduino Uno are 10, 11, 12 and 13. In this case, since pin 12 (MISO) is not actually used, you could conceivably still use that for some other inputting purpose.
This photo shows how I connected the two Unos together:
Specifically, I connected together:
- Gnd
- +5V
- D10 (SS)
- D11 (MOSI)
- D13 (SCK)
You could connect pins D12 (MISO) together too, it wouldn't hurt, and then it is easier to remember: just connect pins 10 to 13.
On the Arduino Mega, the pins are 50 (MISO), 51 (MOSI), 52 (SCK), and 53 (SS).
This is the example code on the "master" side. That is, the device you are testing. You would copy and paste the code in bold into your code (near the start).
// Written by Nick Gammon
// September 2011
// make true to debug, false to not
#define DEBUG true
#include <SPI.h>
// conditional debugging
#if DEBUG
#define beginDebug() 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)
#define TraceFunc() do { SPIdebug.print (F("In function: ")); SPIdebug.println (__PRETTY_FUNCTION__); } while (0)
class tSPIdebug : public Print
{
public:
virtual size_t write (const byte c)
{
digitalWrite(SS, LOW);
SPI.transfer (c);
digitalWrite(SS, HIGH);
return 1;
} // end of tSPIdebug::write
}; // end of tSPIdebug
// an instance of the SPIdebug object
tSPIdebug SPIdebug;
#else
#define beginDebug() ((void) 0)
#define Trace(x) ((void) 0)
#define Trace2(x,y) ((void) 0)
#define Traceln(x) ((void) 0)
#define Traceln2(x,y) ((void) 0)
#define TraceFunc() ((void) 0)
#endif // DEBUG
long counter;
unsigned long start;
void setup() {
start = micros ();
beginDebug ();
Traceln (F("Commenced device-under-test debugging!"));
TraceFunc (); // show current function name
} // end of setup
void foo ()
{
TraceFunc (); // show current function name
}
void loop()
{
counter++;
if (counter == 100000)
{
Traceln (F("100000 reached."));
Trace (F("took "));
Traceln (micros () - start);
counter = 0;
foo ();
} // end of if
} // end of loop
Then, as in the example, you use Trace or Traceln to output data via SPI. Since the class tSPIdebug is derived from the Print class you can send a character string, a number, and so on. You can also use Trace2 to specify a second argument, eg.
The code for the "slave" device (the one receiving the debug information) is below:
// Written by Nick Gammon
// September 2011
#include "pins_arduino.h"
char buf [1000];
volatile int inpoint, outpoint;
void setup (void)
{
Serial.begin (115200); // debugging
Serial.println ();
Serial.println (F("Commencing debugging session ..."));
Serial.println ();
// have to send on master in, *slave out*
pinMode(MISO, OUTPUT);
// turn on SPI in slave mode
SPCR |= bit (SPE);
// now turn on interrupts
SPCR |= bit (SPIE);
} // end of setup
// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR; // grab byte from SPI Data Register
int next = inpoint + 1; // next insert point
// wrap-around at end of buffer
if (next >= sizeof buf)
next = 0;
if (next == outpoint) // caught up with removal point?
return; // give up
// insert at insertion point
buf [inpoint] = c;
inpoint = next; // advance to next
} // end of interrupt routine SPI_STC_vect
void loop (void)
{
// insertion and removal point the same, nothing there
noInterrupts (); // atomic test of a 16-bit variable
if (outpoint == inpoint)
{
interrupts ();
return;
}
interrupts ();
// display anything found in the circular buffer
Serial.print (buf [outpoint]);
noInterrupts ();
if (++outpoint >= sizeof buf)
outpoint = 0; // wrap around
interrupts ();
} // end of loop
This shouldn't need changing. It simply echoes what it receives via SPI to the serial port at 115200 baud. It uses a 1000-byte circular buffer, so it can cope with high-speed bursts of debugging, provided there are gaps in-between the bursts to give it time to output the messages at 115200 baud. SPI can transfer at around 333,000 bytes per second, whereas serial transmission at 115200 baud is limited to 11,520 bytes per second.
Once both sketches are uploaded you can connect the master and slave together, and hook up the slave to some serial monitor, and watch the debugging messages fly by.
Alternative: if you need the SPI pins you could modify this slightly to use I2C instead (see post below).
[EDIT] Fixed inpoint/outpoint to be int rather than byte, as the buffer is over 256 bytes long.
[EDIT] Modified SPI to run at clock/8 so that the receiving end doesn't miss things. This will make the speed a bit slower than 333,000 bytes per second - in fact measured at about 60,600 bytes per second.
[EDIT] Fixed bug where loop tested for "if (outpoint != inpoint)" rather than "if (outpoint == inpoint)".
Example of debugging an ISR using this method
#define DEBUG
#include <SPI.h>
#include "pins_arduino.h"
const byte LED = 9;
ISR (TIMER1_COMPA_vect)
{
static boolean state = false;
state = !state; // toggle
digitalWrite (LED, state ? HIGH : LOW);
#ifdef DEBUG
digitalWrite(SS, LOW);
SPI.transfer ('*');
digitalWrite(SS, HIGH);
#endif
}
void setup() {
#ifdef DEBUG
SPI.begin ();
SPI.setClockDivider(SPI_CLOCK_DIV8);
#endif
pinMode (LED, OUTPUT);
// set up Timer 1
TCCR1A = 0; // normal operation
TCCR1B = bit (WGM12) | bit (CS10); // CTC, no prescaling
OCR1A = 1000; // compare A register value (1000 * clock speed)
TIMSK1 = bit (OCIE1A); // interrupt on Compare A Match
} // end of setup
void loop() { }
This example uses Timer 1 to call an ISR at intervals of 62.5 uS (1000 times the clock speed). A logic analyzer capture shows:
This shows that the debugging "print" (in this case an asterisk) only takes 3.75 uS to be sent, which is not much overhead. Of course you could send more information than an asterisk, for example an 'a' for one ISR and a 'b' for a different one.
Library code
The above code has been turned into a library.
http://gammon.com.au/Arduino/SPI_Debugging.zip
Download, unzip, and copy the resulting folder (SPI_Debugging) inside the "libraries" folder which is found in your Arduino "sketchbook" folder. The location of that can be found from your Arduino "Preferences", where it says "Sketchbook location".
You will find a copy of the SPI debugging slave (above) which you can program onto one Arduino, to report the debugging information. You will find an example of using the debugging calls (in the sketch to be debugged) as well. This example is now simplified to:
// make true to debug, false to not
#define DEBUG true
#include <SPI.h>
#include <SPI_Debugging.h>
long counter;
unsigned long start;
void setup() {
start = micros ();
beginDebug ();
Traceln (F("Commenced device-under-test debugging!"));
TraceFunc (); // show current function name
} // end of setup
void foo ()
{
TraceFunc (); // show current function name
}
void loop()
{
counter++;
if (counter == 100000)
{
Traceln (F("100000 reached."));
Trace (F("took "));
Traceln (micros () - start);
counter = 0;
foo ();
} // end of if
} // end of loop
Important! You have to put the line with "#define DEBUG true" before including the SPI_Debugging.h file, otherwise that file will not know whether or not to use debugging. You also need to include SPI.h, so that the SPI library is part of the compilation. |