0

I have recently been trying to send integers via SPI between two arduino Uno's. I am using the first example of Nick Gammons tutorial, and trying to modify it to get a basic understanding of how to communicate via SPI.

http://www.gammon.com.au/spi

MASTER

// Written by Nick Gammon
// February 2011


#include <SPI.h>

void setup (void)
{

  digitalWrite(SS, HIGH);  // ensure SS stays high for now

  // Put SCK, MOSI, SS pins into output mode
  // also put SCK, MOSI into LOW state, and SS into HIGH state.
  // Then put SPI hardware into Master mode and turn SPI on
  SPI.begin ();

  // Slow down the master a bit
  SPI.setClockDivider(SPI_CLOCK_DIV8);

}  // end of setup


void loop (void)
{

  char c;

  // enable Slave Select
  digitalWrite(SS, LOW);    // SS is pin 10

  // send test string
  for (const char * p = "Hello, world!\n" ; c = *p; p++)
    SPI.transfer (c);

  // disable Slave Select
  digitalWrite(SS, HIGH);

  delay (1000);  // 1 seconds delay 
}  // end of loop

SLAVE - WITHOUT INTERRUPTS

// Written by Nick Gammon
// February 2011


#include <SPI.h>

char buf [100];
volatile byte pos;
volatile boolean process_it;

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

  // turn on SPI in slave mode
  SPCR |= bit (SPE);

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);

  // get ready for an interrupt 
  pos = 0;   // buffer empty
  process_it = false;

  // now turn on interrupts
  SPI.attachInterrupt();

}  // end of setup


// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR;  // grab byte from SPI Data Register

  // add to buffer if room
  if (pos < (sizeof (buf) - 1))
    buf [pos++] = c;

  // example: newline means time to process buffer
  if (c == '\n')
    process_it = true;

}  // end of interrupt routine SPI_STC_vect

// main loop - wait for flag set in interrupt routine
void loop (void)
{
  if (process_it)
    {
    buf [pos] = 0;  
    Serial.println (buf);
    pos = 0;
    process_it = false;
    }  // end of flag set

}  // end of loop

SLAVE - WITH INTERRUPTS

// Written by Nick Gammon
// April 2011


// what to do with incoming data
byte command = 0;

// start of transaction, no command yet
void ss_falling ()
{
  command = 0;
}  // end of interrupt service routine (ISR) ss_falling

void setup (void)
{

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);

  // turn on SPI in slave mode
  SPCR |= _BV(SPE);

  // turn on interrupts
  SPCR |= _BV(SPIE);

  // interrupt for SS falling edge
  attachInterrupt (0, ss_falling, FALLING);

}  // end of setup


// SPI interrupt routine
ISR (SPI_STC_vect)
{
  byte c = SPDR;

  switch (command)
  {
  // no command? then this is the command
  case 0:
    command = c;
    SPDR = 0;
    break;

  // add to incoming byte, return result
  case 'a':
    SPDR = c + 15;  // add 15
    break;

  // subtract from incoming byte, return result
  case 's':
    SPDR = c - 8;  // subtract 8
    break;

  } // end of switch

}  // end of interrupt service routine (ISR) SPI_STC_vect


void loop (void)
{
// all done with interrupts
}  // end of loop

ORIGINAL ISSUE - I am able to get this working, so the wiring is correct, however i am unable to get it to send anything other than a string. I can change the string and that works, but i am getting confused with pointers in C. Any code i have tried doesn't compile correctly, or doesn't send at all if it does compile. Could someone give me a clue as to where to start with this. My best effort is below

MASTER

#include <SPI.h>

int x = 10000;
int y = 20000;
int z = 30000;
byte High_x = highByte(x);
byte Low_x = lowByte(x);
byte High_y = highByte(y);
byte Low_y = lowByte(y);
byte High_z = highByte(z);
byte Low_z = lowByte(z);

int X =  word(High_x<<8|Low_x);
int Y =  word(High_y<<8|Low_y);
int Z =  word(High_z<<8|Low_z);

  byte myArray[7];



void setup (void)
{

  Serial.begin(115200);
  digitalWrite(SS, HIGH);  // ensure SS stays high for now

  // Put SCK, MOSI, SS pins into output mode
  // also put SCK, MOSI into LOW state, and SS into HIGH state.
  // Then put SPI hardware into Master mode and turn SPI on
  SPI.begin ();

  // Slow down the master a bit
  SPI.setClockDivider(SPI_CLOCK_DIV8);

 myArray[0] = High_x;
 myArray[1] = Low_x; 
 myArray[2] = High_y;
 myArray[3] = Low_y;
 myArray[4] = High_z;
 myArray[5] = Low_z;  
 myArray[6] = 0;

  Serial.println(X);
  Serial.println(Y);
  Serial.println(Z);
  delay(2000);
}

void loop (void)
{

  byte data;

  // enable Slave Select
  digitalWrite(SS, LOW);    // SS is pin 10

  // send test string
  for (byte * p =  myArray[0]; data = *p; p++)
    SPI.transfer (data);
    Serial.println("sending data");

  // disable Slave Select
  digitalWrite(SS, HIGH);

  delay (1000);  // 1 seconds delay 
}  // end of loop

SLAVE

#include <SPI.h>

byte buf [100];
volatile byte pos;
volatile boolean process_it;

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

  // turn on SPI in slave mode
  SPCR |= bit (SPE);

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);

  // get ready for an interrupt 
  pos = 0;   // buffer empty
  process_it = false;

  // now turn on interrupts
  SPI.attachInterrupt();
Serial.println("End of Setup");
}  // end of setup


// SPI interrupt routine
ISR (SPI_STC_vect)
{
  Serial.println("Beginning of Interrupt Routine");
byte data = SPDR;  // grab byte from SPI Data Register

  // add to buffer if room
  if (pos < (sizeof (buf) - 1))
    buf [pos++] = data;

  // example: newline means time to process buffer
  if (data == '0')
    process_it = true;
      Serial.println("End of Interrupt Routine");
}  // end of interrupt routine SPI_STC_vect

// main loop - wait for flag set in interrupt routine
void loop (void)
{
    Serial.println("Beggining of Loop");

  for(int i = 0; i < 100; i++)
  if (process_it)
    {
    buf [pos] = 0;  
    Serial.println (buf[i]);
    pos = 0;
    process_it = false;
    }  // end of flag set
    Serial.println("End of Loop");
}  // end of loop

In the future i hope to send 10 16-bit integers over SPI from an arduino to an arduino or maybe arduino to a raspberry Pi.

Thanks

EDIT -I have started using the code that uses interrupts and managed to get the data i need sent across, however, i can only get the correct data by setting both the first and second element of the array to "High_x", otherwise it skips the first part of data?

NEW MASTER

#include <SPI.h>

int x = 10000;
int y = 20000;
int z = 30000;
byte High_x = highByte(x);
byte Low_x = lowByte(x);
byte High_y = highByte(y);
byte Low_y = lowByte(y);
byte High_z = highByte(z);
byte Low_z = lowByte(z);

int X =  word(High_x<<8|Low_x);
int Y =  word(High_y<<8|Low_y);
int Z =  word(High_z<<8|Low_z);

  byte myArray[8];



void setup (void)
{

  digitalWrite(SS, HIGH);  // ensure SS stays high for now

  // Put SCK, MOSI, SS pins into output mode
  // also put SCK, MOSI into LOW state, and SS into HIGH state.
  // Then put SPI hardware into Master mode and turn SPI on
  SPI.begin ();

  // Slow down the master a bit
  SPI.setClockDivider(SPI_CLOCK_DIV8);

 myArray[0] = High_x;
 myArray[1] = High_x;
 myArray[2] = Low_x; 
 myArray[3] = High_y;
 myArray[4] = Low_y;
 myArray[5] = High_z;
 myArray[6] = Low_z;  
 myArray[7] = 0;

  Serial.print("wait for loop");
  //Serial.println(X);
  //Serial.println(Y);
  //Serial.println(Z);
  delay(2000);
}

void loop (void)
{

  byte data;

  // enable Slave Select
  digitalWrite(SS, LOW);    // SS is pin 10

  // send test string
  for (byte * p =  &myArray[0]; data = *p; p++){
    SPI.transfer (data);
  }
  // disable Slave Select
  digitalWrite(SS, HIGH);

  delay (1000);  // 1 seconds delay 
}  // end of loop

NEW SLAVE

byte buf [100];
volatile byte pos;
// what to do with incoming data
byte command = 0;

// start of transaction, no command yet
void ss_falling ()
{
  command = 0;
}  // end of interrupt service routine (ISR) ss_falling

void setup (void)
{

  Serial.begin(9600);

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);

  // turn on SPI in slave mode
  SPCR |= _BV(SPE);

  // turn on interrupts
  SPCR |= _BV(SPIE);

  // interrupt for SS falling edge
  attachInterrupt (0, ss_falling, FALLING);

}  // end of setup


// SPI interrupt routine
ISR (SPI_STC_vect)
{
  byte data = SPDR;

  // add to buffer if room
  if (pos < (sizeof (buf) - 1))
    buf [pos++] = data;

}  // end of interrupt service routine (ISR) SPI_STC_vect


void loop (void)
{

  for(int i = 0; i < 100; i++){


    Serial.println (buf[i]);
    pos = 0;
    delay(1000);
  }
}  // end of loop

Any suggestions on why this is necessary? Is there some basic SPI idiosyncrasy I'm missing?

Thanks all

Ross

1

You have a couple of issues going on here.

First of all, Nick Gammon's example is transmitting strings. Strings have the property that a particular value, '\0' (null), cannot normally appear in the string itself, and it can therefore be used as an "end flag" to mark the end of the string. This is why the for loop on the master side works correctly.

Note that the null itself is NOT transmitted over the SPI link. Instead, Nick shows an example of using another character ('\n' - newline) to trigger processing on the slave side.

If you want to transmit binary data, which could easily contain bytes of 0x00 (null), you need to find a different way to delimit the data on both the sending side and the receiving side. Your example data (16-bit integers of 10000, 20000 and 30000) doesn't contain any null bytes, but you can't rely on that in general. Also, because the null doesn't actually get transmitted, you can't use it on the receiving side to trigger processing.

There are a couple of approaches that are frequently used to send binary data:

  • Send the number of bytes explicitly at the beginning of the transaction, and trigger receive-side processing once that number of bytes has been received.

  • Use a "special" value (e.g., null) to mark the end of the data, but then you have to "escape" that value in some way if it ever appears in the actual data.


In addition to all of that, you have a bug on your master side code. The initialization of your for loop, byte * p = myArray[0] does not set the pointer p to the address of myArray as intended; instead, it sets it to the value of the first byte in the array, which is 0x27 (High_x). You need to say either byte * p = &myArray[0] or simply byte * p = myArray.

  • Thank you for a detailed answer. So first, sending 10 x 16 bit integers (output of mpu9250) which would you say is the best method? A "\n" isn't going to appear in the output as I would handle that once sent. If not, could you elaborate on "receive side processing" even if just a link please. I will change the for loop and report back. Thank you – Ross Hanna Sep 19 '18 at 12:54
  • If it were me, I would probably use SS to delimit the data explicitly. Nick shows examples of this using either polling or interrupts. – Dave Tweed Sep 19 '18 at 13:07
  • OK, i'm looking at the interrupts now, however his example doesn't seem to collect the data on the slave side? Can you shed any light on a method of accomplishing this? I'm trying not to just use "if(digitalRead(2) = etc){ stop code} – Ross Hanna Sep 19 '18 at 13:49
  • No, he doesn't collect it on the receive side in that example; he just processes it directly in the ISR and sends it out again. But there's no reason you can't put the data into a buffer just like in the earlier slave-side examples. – Dave Tweed Sep 19 '18 at 13:57
  • Well I have transmission! using interrupts and placing the data in a buffer. However out of the 6 bytes sent, the first one is missing, the next 5 are correct, and there's a strange one that matches nothing at the end. posting the code here will go over the character limit, do i put it as an answer to my question? or change the original post? – Ross Hanna Sep 19 '18 at 14:28
2

I can see two big problems at your code (there could be more, but i didnt look that hard).

First is using Serial.println in interrupt routine. This prevent receiving of more than one byte as Serial.println will take much more time to execute than receiving 6 bytes (even on slow SPI clock) on SPI. In most cases Interrupt routines should finish fast.

Next problem is that you are testing for char 0 ('0') which is not equal to byte 0 you are sending as end of data, but has value of 48 in decimal.

Edit for how to test for end of data:

You can test for end of that few different ways, having fixed number of byte transfers, setting interrupt on slave select pin or encode data binary data in base64 and use control char. I would recommend interrupt.

  • OK, i will refrain from randomly putting in Serial.prints. Seemed the easiest way to visualise what the code was doing and were to be removed when the code worked. I didn't realise it could STOP it working! I now have added and since removed a serial.print in the Master code in the FOR loop to see if the data that was being transferred was correct and it does work. So the problem is receiving? What could i use to test for the end of the transfer? I'm looking into interrupts but is there other, not as elegant tests. just to test if the principle works then tidy afterwards? Thanks – Ross Hanna Sep 19 '18 at 13:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.