Arduino SPI Master and Slave missing data

Hello,

I'm trying to setup 2 Arduinos communicating using SPI. One is the master and the other the slave unit. I've boiled down the code to about as simple as I can but about 20% of the time the data transmission is missed on the slave. The 2 units are connected directly SPI to SPI. The code just loops 0 to 20 over and over and sends the current value to the other unit. I have to believe that SPI protocol is intended to be much more consistent than the results I'm seeing so I'm hoping someone here can help me determine why some transmissions are missed. The LED attached to pins 5 on each unit is just a visual indicator that makes it easy to spot a missed transmission. When working the LEDs will flash briefly at the same time. When a transfer is missed the slave LED will stay lit until the next transfer is received. It rarely misses more than 1 transfer in a row.

Any suggestions?

The Master code is:

#include <Arduino.h>
#include <SPI.h>

int led=5;
int c = 0;
// Initialize SPI Master Device
void spi_init_master () {
	pinMode(10,OUTPUT);
	pinMode(11,OUTPUT);
	pinMode(12,INPUT);
	pinMode(13,OUTPUT);
	SPCR = (1<<MSTR) | (1<<SPE);
}

// the setup function runs once when you press reset or power the board
void setup() {
	// initialize digital pin (led) as an output.
	pinMode(5,OUTPUT);
	Serial.begin(115200);
	Serial.println("Entering Setup");
	spi_init_master();

}

void sendViaSPI(int intData){
	digitalWrite(10,LOW);		//Pull SS low
	digitalWrite(5,HIGH);		//Turn on indicator LED
	delay(200);					//Pause for 200 ms
	Serial.println("Sent: " + String(c));			//Print current value
	SPDR = c;					//start transfer using SPI
	while(!(SPSR & (1<<SPIF)));	//wait until transfer is complete before continuing
	digitalWrite(10,HIGH);		//Pull SS back HIGH
	digitalWrite(5,LOW);		//Turn off indicator LED
}
// the loop function runs over and over again forever
void loop() {

	sendViaSPI(c);				//go transfer the value
	delay(1000);				//pause for 1 second before repeating
	c++;
	if (c>20){
		c=0;
	}

}

The slave code is:

#include <SPI.h>

int led = 5;

// Initialize SPI Slave Device
void spi_init_slave (void)
{

    pinMode(10,INPUT);
    pinMode(11,INPUT);
    pinMode(12,OUTPUT);
    pinMode(13,INPUT);
    SPCR = (1<<SPE);

}

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin (led) as an output.
  pinMode(led, OUTPUT);
  pinMode(5,OUTPUT);
  Serial.begin(115200);
  spi_init_slave();
  Serial.println("Slave Started");
 }

// the loop function runs over and over again forever
void loop() {
  
  if (!digitalRead(10)){
    digitalWrite(5,HIGH);
    while(!(SPSR & (1<<SPIF)));
    byte rx = SPDR;
    Serial.println("Received: " + String(rx));
    digitalWrite(5,LOW);
    delay(200);
  }

}

tbriley:
Hello,

I'm trying to setup 2 Arduinos communicating using SPI. One is the master and the other the slave unit. I've boiled down the code to about as simple as I can but about 20% of the time the data transmission is missed on the slave. The 2 units are connected directly SPI to SPI. The code just loops 0 to 20 over and over and sends the current value to the other unit. I have to believe that SPI protocol is intended to be much more consistent than the results I'm seeing so I'm hoping someone here can help me determine why some transmissions are missed. The LED attached to pins 5 on each unit is just a visual indicator that makes it easy to spot a missed transmission. When working the LEDs will flash briefly at the same time. When a transfer is missed the slave LED will stay lit until the next transfer is received. It rarely misses more than 1 transfer in a row.

Any suggestions?

The Master code is:

#include <Arduino.h>

#include <SPI.h>

int led=5;
int c = 0;
// Initialize SPI Master Device
void spi_init_master () {
pinMode(10,OUTPUT);
pinMode(11,OUTPUT);
pinMode(12,INPUT);
pinMode(13,OUTPUT);
SPCR = (1<<MSTR) | (1<<SPE);
}

// the setup function runs once when you press reset or power the board
void setup() {
// initialize digital pin (led) as an output.
pinMode(5,OUTPUT);
Serial.begin(115200);
Serial.println("Entering Setup");
spi_init_master();

}

void sendViaSPI(int intData){
digitalWrite(10,LOW); //Pull SS low
digitalWrite(5,HIGH); //Turn on indicator LED
delay(200); //Pause for 200 ms
Serial.println("Sent: " + String(c)); //Print current value
SPDR = c; //start transfer using SPI
while(!(SPSR & (1<<SPIF))); //wait until transfer is complete before continuing
digitalWrite(10,HIGH); //Pull SS back HIGH
digitalWrite(5,LOW); //Turn off indicator LED
}
// the loop function runs over and over again forever
void loop() {

sendViaSPI(c); //go transfer the value
delay(1000); //pause for 1 second before repeating
c++;
if (c>20){
c=0;
}

}




The slave code is:



#include <SPI.h>

int led = 5;

// Initialize SPI Slave Device
void spi_init_slave (void)
{

pinMode(10,INPUT);
   pinMode(11,INPUT);
   pinMode(12,OUTPUT);
   pinMode(13,INPUT);
   SPCR = (1<<SPE);

}

// the setup function runs once when you press reset or power the board
void setup() {
 // initialize digital pin (led) as an output.
 pinMode(led, OUTPUT);
 pinMode(5,OUTPUT);
 Serial.begin(115200);
 spi_init_slave();
 Serial.println("Slave Started");
}

// the loop function runs over and over again forever
void loop() {
 
 if (!digitalRead(10)){
   digitalWrite(5,HIGH);
   while(!(SPSR & (1<<SPIF)));
   byte rx = SPDR;
   Serial.println("Received: " + String(rx));
   digitalWrite(5,LOW);
   delay(200);
 }

}

A couple of critique's:

  • You are including SPI.h, but never using any of it's function.
    If you are going to access the AVR's hardware directly, skip the Library.
  • The standard SPI hardware can run with a 4mhz clock. You never configured the
    clock divisor, MSB/LSB, SPIMODE?
    If you are going to use bare metal, assign a ISR to the SPI interrupt. Use the hardware instead of trying to replace hardware with software. (if(!pin10)) If you create a ISR, That ISR would be triggered when the 'Slave mode' SPI hardware saw the CS line go LOW.
  • The ISR would have to be a state machine, or just fill/empty global buffers and mark some volatile "dataReady" flag that the main Loop() reads/supports.

Your unreliable transmissions are probably caused by overruns and missed "if(!pin10)" triggers.

You need to move all of the Slave mode responses into a Interrupt Service Routine.

If you go with the default SPI divisor (4mhz clock) each byte send only takes 32 clock cycles. The ISR code has to be very efficient to support this transmission rate. Code in the main loop will never succeed.

Your calls to Serial.print() are guaranteed to create a failure point.

If you are going to use SPI tranfers, you will need to put a lot of effort into creating a transmission model that requires very little logic to support.

You must have the outbound data available instantaneously and a pre-allocated buffer available for the inbound data. The ISR doesn't have time to create data or calculate something. Your slave SPI function would just be a mechanism to access a global memory construct. Your loop() code would fill the output buffer and process the input buffer after the ISR marked the buffer as ready.

Chuck.

Thanks Chuck. I appreciate the critique and suggestions.

Initially I was using the functions from SPI.h but they slowly went away as I tried to strip away more and more overhead to the point where things are now that I could remove the #include as you noted.

After some more troubleshooting I was convinced that the problem was I wasn't getting clean data clocked into the slave. The way the slave code is it is effectively functioning as a continuous ISR. I was certain that I was catching every SS from the Master and could see it using the LEDs alone. I was about the break out the logic analyzer to take a look.

Before doing that I stepped back for a minute and looked at the physical layer. I had 2 cheap 5 V supplies powering the UNOs and the one powering the Master UNO was floating. I jumpered the 2 UNO's grounds and the error rate went to zero.

Your point about using an ISR to service the transfers is well taken though. The data needs to pulled off of SPDR quickly if I want the code to do anything useful in loop().

Thanks.