Master-Slave SPI communication between two Arduino Uno

I'm trying to send 2 byte size int values between two Arduino Unos using SPI.
I made two sketches, one for Master and one for Slave
but it works in a weird way.
Master receives two bytes but one from previous count and one from current count.
The first and second bytes from Slave are in reverse order.
There may be some synchronization problem but I cannot figure out.

I added some Serial.print stuff to check the behavior.
Even when I removed the Serial.print stuff, it did not work.

Here is the sketch for Master

#include <SPI.h>

void setup (void) {
  SPI.begin ();
  Serial.begin(9600);
}

void loop (void) {
  uint8_t count1, count2;
  int count;
  
  SPI.beginTransaction( SPISettings(1000000, MSBFIRST, SPI_MODE0) );

  digitalWrite(SS, LOW); // select slave

  count1 = SPI.transfer(0); // MSB
  delayMicroseconds (20);
  count2 = SPI.transfer(0); // LSB
  count = (count1 << 8) | count2;
  
  Serial.print("1st byte : ");
  Serial.println(count1);
  Serial.print("2nd byte : ");
  Serial.println(count2);
  Serial.print("* slave count : ");
  Serial.println(count);

  digitalWrite(SS, HIGH); // de-select slave
  SPI.endTransaction();
    
  delay(1000);
}

Here is the sketch for Slave.

#include <SPI.h>

int count = 250;

const int TRANSMIT_BYTES = 2;
volatile boolean transmit_start;
int transmit_data;
volatile int transmit_count = 0;

void setup () {
  pinMode(MISO, OUTPUT);
  pinMode(MOSI, INPUT);
  pinMode(SCK, INPUT);
  pinMode(SS, INPUT);

  SPI.setClockDivider(SPI_CLOCK_DIV16);

  SPCR |= _BV(SPE);       // SPI enable
  SPCR &= ~_BV(MSTR);     // Slave mode enable
  SPCR |= _BV(SPIE);      // enable interrupt

  Serial.begin(9600);
  transmit_start = false;
}

ISR (SPI_STC_vect) {
  if(!transmit_start){
    transmit_start = true;
    transmit_data = count;

    uint8_t d = (transmit_data >> 8) & 0xFF;
    SPDR = d;
    transmit_count++;

    Serial.print("1st byte : ");
    Serial.println(d);
  }
  else{
    uint8_t d = transmit_data & 0xFF;
    SPDR = d;
    transmit_count++;

    if(transmit_count == TRANSMIT_BYTES){
      transmit_start = false;
      transmit_count = 0;
    }

    Serial.print("2nd byte : ");
    Serial.println(d);
  }
}

void loop (){
  count++;
  Serial.println(String("* slave count : ") + count);

  delay(1000);
}

Thanks in advance.

whenever a SPI.transfer called it is the current register value that is returned.

so on the master side to received the bytes correctly you will need to add an extra SPI.transfer.

something like this:

count1 = SPI.transfer(0); // returns any value
delayMicroseconds (20);
count1 = SPI.transfer(0); // return MSB
delayMicroseconds (20);
count2 = SPI.transfer(0); // LSB
count = ((uint16_t)count1 << 8 ) | count2;

you may also need to adjust the slave code as I suspect that after the first time the code is executed the bytes will be out of order again.... but let see if you get the first read correctly first! :wink:

Thank you for you comment.
But I don't understand the 'current' issue.

sherzaad:
whenever a SPI.transfer called it is the current register value that is returned.

so on the master side to received the bytes correctly you will need to add an extra SPI.transfer.

Whenever Master send 1 byte, Master receive 1 byte from Slave, doesn't it?
You mean 3 bytes should be sent to receive correct 2 bytes data ?
It does not make sense to me.

This is from SPI library.

// Write to the SPI bus (MOSI pin) and also receive (MISO pin)
inline static uint8_t transfer(uint8_t data) {
	SPDR = data;
	/*
	* The following NOP introduces a small delay that can prevent the wait
	* loop form iterating when running at the maximum speed. This gives
	* about 10% more speed, even if it seems counter-intuitive. At lower
	* speeds it is unnoticed.
	*/
	asm volatile("nop");
	while (!(SPSR & _BV(SPIF))) ; // wait
	return SPDR;
}

transfer function returns the data he received from Slave during the actual transfer, doesn't it?

Is there any mis-understanding I made?

Thank you :slight_smile:

I've modded your master and slave code to what I think should give you the desired result. hopefully going through it you may understand what I was trying to explain in post #1

both codes compile (not tested!)
Here is the sketch for Master

#include <SPI.h>

void setup (void) {
  SPI.begin ();
  SPI.beginTransaction( SPISettings(1000000, MSBFIRST, SPI_MODE0) );
  Serial.begin(9600);
}

void loop (void) {
  uint8_t count1, count2;
  int count;

  digitalWrite(SS, LOW); // select slave
  count1 = SPI.transfer('s'); // 's' command here to request to send byte (undefined value received!)
  delayMicroseconds (20);
  count1 = SPI.transfer(0); // MSB value was put into slave's SPDR when  "'s'" was transmitted
  delayMicroseconds (20);
  count2 = SPI.transfer(0); // LSB value was put into slave's SPDR when previous "0" was transmitted
  count = ((uint16_t)count1 << 8) | count2;

  Serial.print("1st byte : ");
  Serial.println(count1);
  Serial.print("2nd byte : ");
  Serial.println(count2);
  Serial.print("* slave count : ");
  Serial.println(count);

  digitalWrite(SS, HIGH); // de-select slave

  delay(1000);
}

Here is the sketch for Slave.

#include <SPI.h>

int count = 250;

const int TRANSMIT_BYTES = 2;
volatile boolean transmit_start;
int transmit_data;
volatile int transmit_count = 0;

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

void setup () {
  pinMode(MISO, OUTPUT);
  pinMode(MOSI, INPUT);
  pinMode(SCK, INPUT);
  pinMode(SS, INPUT);

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

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

  Serial.begin(9600);
  transmit_start = false;
}

ISR (SPI_STC_vect) {
  byte c = SPDR;

  if (command == 0) command = c;

  if (command == 's') { //command to send byte data
    if (!transmit_start) {
      transmit_start = true;
      transmit_data = count;

      uint8_t d = (transmit_data >> 8) & 0xFF;
      SPDR = d;
      transmit_count++;

      Serial.print("1st byte : ");
      Serial.println(d);
    }
    else {
      uint8_t d = transmit_data & 0xFF;
      SPDR = d;
      transmit_count++;

      if (transmit_count == TRANSMIT_BYTES) {
        transmit_start = false;
        transmit_count = 0;
      }

      Serial.print("2nd byte : ");
      Serial.println(d);
    }
  }
}

void loop () {
  // if SPI not active, clear current command
  if (digitalRead (SS) == HIGH) command = 0;

  count++;
  Serial.println(String("* slave count : ") + count);

  delay(1000);
}

hope that helps

Yes, I got the idea you tried to explain.

Your modified code compiles successfully but does not work.
I am currently trying to modify the sketches to get the result I expected.

I'll post again when I make it work.

Thank you again. :slight_smile: :wink:

First of all, thank you for the valuable comment.
I finally figured out what I mis-understand and fixed it.
Here is the fixed sketch which is different from previous ones.
In the fixed sketch I used fixed-size string to include some start and end mark later.
Although some optimization might be needed, it works. :slight_smile: :wink:

But I still have one thing that I cannot clearly figured out.
In the sketch for Master, there should be some delay
between two consecutive SPI transfer function.
If the delay is too short, data exchange fails.
100 us was long enough according to the experiments I conducted
which is 5 times longer than I googled.

Is this normal? or I made some mistakes in my sketch?

Here is the sketch for Master.

#include <SPI.h>

#define DELAY_BETWEEN_TRANFER_US 100
void setup (void) {
  SPI.begin ();
  Serial.begin(9600);
}

void loop (void) {
  uint8_t from_slave;
  char receive_data[7];

  SPI.beginTransaction( SPISettings(1000000, MSBFIRST, SPI_MODE0) );

  digitalWrite(SS, LOW); // select slave

  from_slave = SPI.transfer('

Here is the sketch for Slave.

#include <SPI.h>

unsigned int count = 99;

char transmit_data[6];
volatile int transmit_count = 0;
volatile boolean transmit_start = false;

void setup () {
  pinMode(MISO, OUTPUT);
  pinMode(MOSI, INPUT);
  pinMode(SCK, INPUT);
  pinMode(SS, INPUT);

  SPI.setClockDivider(SPI_CLOCK_DIV16);

  SPCR |= _BV(SPE);       // SPI enable
  SPCR &= ~_BV(MSTR);     // Slave mode enable
  SPCR |= _BV(SPIE);      // enable interrupt

  Serial.begin(9600);

  transmit_start = false;
}

ISR (SPI_STC_vect) {
  uint8_t received = SPDR;
  if (received == '

); // start sign
  delayMicroseconds(DELAY_BETWEEN_TRANFER_US);
  receive_data[0] = from_slave;

for (int pos = 0; pos < 5; pos++) { // 5 digit
    from_slave = SPI.transfer(0);
    delayMicroseconds(DELAY_BETWEEN_TRANFER_US);
    receive_data[pos + 1] = from_slave;
  }
  receive_data[6] = 0;

digitalWrite(SS, HIGH); // de-select slave
  SPI.endTransaction();

Serial.print("슬레이브 카운터 : [");
  Serial.print(String(receive_data) + "] ");
  String buf = String(receive_data);
  Serial.println(buf.substring(1).toInt());

delay(2000);
}


Here is the sketch for Slave.

§DISCOURSE_HOISTED_CODE_1§


) {
    transmit_start = true;
    transmit_count = 0;

    sprintf(transmit_data, "%05d", count); // prepare fixed size string
  }

  if (transmit_start) {
    if (transmit_count < 5) {
      SPDR = transmit_data[transmit_count];
      transmit_count++;
    }
    else if (transmit_count == 5) {
      SPDR = '#';
      transmit_start = false;
    }
  }
}

void loop () {
  count++;
  Serial.println(String("슬레이브 카운터 : ") + count);

  delay(1000);
}

); // start sign
  delayMicroseconds(DELAY_BETWEEN_TRANFER_US);
  receive_data[0] = from_slave;

for (int pos = 0; pos < 5; pos++) { // 5 digit
    from_slave = SPI.transfer(0);
    delayMicroseconds(DELAY_BETWEEN_TRANFER_US);
    receive_data[pos + 1] = from_slave;
  }
  receive_data[6] = 0;

digitalWrite(SS, HIGH); // de-select slave
  SPI.endTransaction();

Serial.print("슬레이브 카운터 : [");
  Serial.print(String(receive_data) + "] ");
  String buf = String(receive_data);
  Serial.println(buf.substring(1).toInt());

delay(2000);
}


Here is the sketch for Slave.

§DISCOURSE_HOISTED_CODE_1§

hgycap:
First of all, thank you for the valuable comment.
I finally figured out what I mis-understand and fixed it.
Here is the fixed sketch which is different from previous ones.
In the fixed sketch I used fixed-size string to include some start and end mark later.
Although some optimization might be needed, it works. :slight_smile: :wink:

But I still have one thing that I cannot clearly figured out.
In the sketch for Master, there should be some delay
between two consecutive SPI transfer function.
If the delay is too short, data exchange fails.
100 us was long enough according to the experiments I conducted
which is 5 times longer than I googled.

Is this normal? or I made some mistakes in my sketch?

congrats for fixing your code! :smiley:

with regards to the delay you mentioned it probably related to your code and if you manage some further optimisation as your guessed, you should be able to reduce the delay time between transmissions.