[Home] [Downloads] [Search] [Help/forum]


Register forum user name Search FAQ

Gammon Forum

[Folder]  Entire forum
-> [Folder]  Electronics
. -> [Folder]  Microprocessors
. . -> [Subject]  Debugging using SPI/I2C and a second processor

Debugging using SPI/I2C and a second processor

Postings by administrators only.

[Refresh] Refresh page


Posted by Nick Gammon   Australia  (22,975 posts)  [Biography] bio   Forum Administrator
Date Wed 31 Aug 2011 11:57 PM (UTC)

Amended on Fri 10 Oct 2014 09:44 PM (UTC) by Nick Gammon

Message
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.


  Trace2 (42, HEX);





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.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Nick Gammon   Australia  (22,975 posts)  [Biography] bio   Forum Administrator
Date Reply #1 on Thu 01 Sep 2011 01:35 AM (UTC)

Amended on Fri 10 Oct 2014 09:58 PM (UTC) by Nick Gammon

Message
I2C debugging


Below is the above code adapted to work with I2C.

Note, this isn't suitable for use in an interrupt service routine, because I2C itself uses interrupts.




Master (device being tested):


// Written by Nick Gammon
// September 2011


// make true to debug, false to not
#define DEBUG true

#include <Wire.h>

const byte SLAVE_ADDRESS = 100;  // which address debugging goes to

// conditional debugging

#if DEBUG
  #define beginDebug()  Wire.begin ()
  #define Trace(x)      I2Cdebug.print   (x)
  #define Trace2(x,y)   I2Cdebug.print   (x,y)
  #define Traceln(x)    I2Cdebug.println (x)
  #define Traceln2(x,y) I2Cdebug.println (x,y)
  #define TraceFunc()   do { I2Cdebug.print (F("In function: ")); I2Cdebug.println (__PRETTY_FUNCTION__); } while (0)
  
  class tI2Cdebug : public Print
  {
  public:
    virtual size_t write (const byte c)  
      { 
      Wire.beginTransmission (SLAVE_ADDRESS);
      Wire.write (c);
      Wire.endTransmission ();
      return 1;
      }  // end of tI2Cdebug::write
  }; // end of tI2Cdebug
      
  // an instance of the I2Cdebug object
  tI2Cdebug I2Cdebug;
  
#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 "));
    Traceln2 (micros () - start, HEX);
    counter = 0;
    foo ();
  }  // end of if

}  // end of loop


There is also a macro TraceFunc() which shows the current function name. This is handy for tracking logic flow through various functions.




Slave (which receives debugging information and echoes to serial port):


// Written by Nick Gammon
// September 2011

#include <Wire.h>

const byte MY_ADDRESS = 100;

char buf [1000];
volatile int inpoint, outpoint;

void setup (void)
{
  Serial.begin (115200);   // debugging

  Serial.println ();
  Serial.println (F("Commencing debugging session ..."));
  Serial.println ();

  Wire.begin (MY_ADDRESS);
  Wire.onReceive (receiveEvent);

}  // end of setup


void receiveEvent (int howMany)
{
  while (Wire.available () > 0)
  {
    char c = Wire.read ();
    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?
      continue;  // give up

    // insert at insertion point
    buf [inpoint] = c;
    inpoint = next;  // advance to next
  }  // end of while available
}  // end of receiveEvent

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


Example output:


Commenced device-under-test debugging!
In function: void setup()
100000 reached.
took 3F658
In function: void foo()
100000 reached.
took 7CDC0





Connection photo:



Connect +5V, Gnd, A4 (SDA) and A5 (SCL).


Library code


The above code has been turned into a library.

http://gammon.com.au/Arduino/I2C_Debugging.zip

Download, unzip, and copy the resulting folder (I2C_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 I2C 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
const byte SLAVE_ADDRESS = 100;  // which address debugging goes to

#include <Wire.h>
#include <I2C_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 I2C_Debugging.h file, otherwise that file will not know whether or not to use debugging. You also need to include Wire.h, so that the Wire library is part of the compilation. You also need to have the same SLAVE_ADDRESS in both sketches (the sending and receiving one).




The advantage of using I2C is that you only use 2 wires rather than 3 or 4 that SPI needs. Also you could share the I2C bus with other I2C devices (providing you use a unique address for debugging).

However I2C is a bit slower than SPI, and if you are trying to debug something which uses I2C the clash between the I2C messages and the debug messages might cause confusing behaviour.

[EDIT] Fixed bug where loop tested for "if (outpoint != inpoint)" rather than "if (outpoint == inpoint)".

[EDIT] October 2014. Fixed to work with IDE 1.0 and above. Added F() macro to printing strings. Also added TraceFunc for debugging which functions you are in.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Nick Gammon   Australia  (22,975 posts)  [Biography] bio   Forum Administrator
Date Reply #2 on Sun 04 Dec 2011 06:43 PM (UTC)

Amended on Sat 11 Oct 2014 02:26 AM (UTC) by Nick Gammon

Message
Use the F() macro to save on RAM


To save using valuable RAM on debugging messages they should be put inside the F() macro like this:


  TRACELN (F("Commenced device-under-test debugging!"));


Toggle pins for debugging


A fast and efficient method of debugging is to toggle an output pin by using a single instruction as shown below. By writing to the PINx register the hardware toggles the state of the current pin. The pins below are for the Atmega328 (such as the Uno):


// toggle digital pins (need to be output)
PIND = bit (0); // toggle D0
PIND = bit (1); // toggle D1
PIND = bit (2); // toggle D2
PIND = bit (3); // toggle D3
PIND = bit (4); // toggle D4
PIND = bit (5); // toggle D5
PIND = bit (6); // toggle D6
PIND = bit (7); // toggle D7
PINB = bit (0); // toggle D8
PINB = bit (1); // toggle D9
PINB = bit (2); // toggle D10
PINB = bit (3); // toggle D11
PINB = bit (4); // toggle D12
PINB = bit (5); // toggle D13

// toggle analog pins (need to be output)
PINC = bit (0); // toggle A0
PINC = bit (1); // toggle A1
PINC = bit (2); // toggle A2
PINC = bit (3); // toggle A3
PINC = bit (4); // toggle A4
PINC = bit (5); // toggle A5


You need to make the pin output first, so an example could be:


void setup ()
  {
  pinMode (13, OUTPUT);    
  }  // end of setup

void loop ()
  {
  delay (100);
  PINB = bit (5); // toggle D13 
  }  // end of loop


The toggle line only adds 4 bytes to the program code, and only takes 2 machine cycles to execute (that is, 125 nS on a 16 MHz processor). Thus its impact on your code is minimal. You could use something like that to check that certain parts of your code are executed, and if so, how often.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


35,116 views.

Postings by administrators only.

[Refresh] Refresh page

Go to topic:           Search the forum


[Go to top] top

Quick links: MUSHclient. MUSHclient help. Forum shortcuts. Posting templates. Lua modules. Lua documentation.

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.

[Home]


Written by Nick Gammon - 5K   profile for Nick Gammon on Stack Exchange, a network of free, community-driven Q&A sites   Marriage equality

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( https://gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Hosted at HostDash]