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

Gammon Software Solutions forum

See www.mushclient.com/spam for dealing with forum spam. Please read the MUSHclient FAQ!

[Folder]  Entire forum
-> [Folder]  Electronics
. -> [Folder]  Microprocessors
. . -> [Subject]  Debugging using SPI/I2C and a second processor
Home  |  Users  |  Search  |  FAQ
Username:
Register forum user name
Password:
Forgotten password?

Debugging using SPI/I2C and a second processor

Postings by administrators only.

[Refresh] Refresh page


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

Amended on Sun 17 Feb 2013 06:38 AM (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>
#include "pins_arduino.h"

// conditional debugging

#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 void 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


long counter;
unsigned long start;

void setup() {
  start = micros ();

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

}  // end of setup

void loop() 
{

  counter++;
  if (counter == 100000)
  {
    TRACELN ("100000 reached.");
    TRACE ("took ");
    TRACELN (micros () - start);
    counter = 0;
  }  // 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 ("Commencing debugging session ...");
  Serial.println ();
  
  // 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);

}  // 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 = _BV(WGM12) | _BV(CS10);   // CTC, no prescaling
  OCR1A =  1000;       // compare A register value (1000 * clock speed)
  TIMSK1 = _BV (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.

- Nick Gammon

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

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

Amended on Sat 14 Jan 2012 02:09 AM (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 BEGIN_DEBUG   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)
  
  class tI2Cdebug : public Print
  {
  public:
    virtual void write (const byte c)  { 
      Wire.beginTransmission (SLAVE_ADDRESS);
      Wire.send (c);
      Wire.endTransmission ();
    }  // end of tI2Cdebug::write
  }; // end of tI2Cdebug
      
  // an instance of the I2Cdebug object
  tI2Cdebug I2Cdebug;
  
#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


long counter;
unsigned long start;

void setup() {
  start = micros ();

  BEGIN_DEBUG;
  TRACELN ("Commenced device-under-test debugging!");
}  // end of setup

void loop() 
{

  counter++;
  if (counter == 100000)
  {
    TRACELN ("100000 reached.");
    TRACE ("took ");
    TRACELN2 (micros () - start, HEX);
    counter = 0;
  }  // end of if

}  // end of loop





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 ("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.receive ();
    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





Connection photo:



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




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)".

- Nick Gammon

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

Posted by Nick Gammon   Australia  (19,468 posts)  [Biography] bio   Forum Administrator
Date Reply #2 on Sun 04 Dec 2011 06:43 PM (UTC)  quote  ]
Message
As was pointed out in the Arduino forum, long messages like "Commenced device-under-test debugging!" should not be used in a live debugging situation, as they use valuable RAM (as the message is copied from program memory to RAM at program startup).

A better method might be to say nothing at the start, or just something like "Start!".

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


5,029 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.

[Home]

Written by Nick Gammon - 5K

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

[Best viewed with any browser - 2K]    [Web site powered by FutureQuest.Net]