0

I have a Raspberry Pi and an Arduino Micro. The two are connected by a SPI bus, the RPi is the master; the Arduino the slave.

I need to send 5 bytes to the Arduino and get 5 bytes as an answer. The answer is pre-calculated so when the Arduino receives the first byte, it already knows what to answer.

I don't understand why the response I get is mis-ordered. I get the right bytes (value), but they seem shifted in time.

Arduino code:

// Data received
volatile unsigned char rx_buffer[5];

// Data to be sent
volatile unsigned char tx_buffer[5];

// Triggered to true by the interruption when message is complete
volatile bool message_ready(false);

// Useful to keep trace of where we are in the message
unsigned char bytes_received = 0;

// SPI interrupt routine
// Message consists of 5 bytes: never less, never more
ISR (SPI_STC_vect)
{   
  SPDR = tx_buffer[bytes_received];
  rx_buffer[bytes_received] = SPDR;

  if (++bytes_received == 5) // Reached the end of the buffer
  {
    bytes_received = 0;
    message_ready = true;
  }
}

void setup (void)
{
  pinMode(MISO, OUTPUT); // Set Slave Output as output
  SPCR |= _BV(SPE); // Slave mode
  SPCR |= _BV(SPIE); // Turn on interrupts

  // Reponse to be sent
  tx_buffer[0] = 'a';
  tx_buffer[1] = 'b';
  tx_buffer[2] = 'c';
  tx_buffer[3] = 'd';
  tx_buffer[4] = 'e';
}

void loop (void)
{
  // When message is ready, print it on serial port
  if (message_ready)
  {
    message_ready = false;
    Serial.print((char) rx_buffer[0]);
    Serial.print((char) rx_buffer[1]);
    Serial.print((char) rx_buffer[2]);
    Serial.print((char) rx_buffer[3]);
    Serial.println((char) rx_buffer[4]);
  }
}

The RPi uses a C++ program and WiringPi 2.39 to communicate (I also made a similar program with Unix spidev/ioctl calls, same results)

#include <iostream>
#include <thread>
#include <chrono>

#include <wiringPi.h>
#include <wiringPiSPI.h>

using std::cout;
using std::cerr;
using std::endl;

int main(void)
{
  // Initialize wiringPi
  if (!wiringPiSPISetup(0, 150000))
  {
    cerr << "Could not initialize SPI!" << endl;
    return 1;
  }

  // Create message and response arrays
  uint8_t message[5], response[5];
  message[0] = 'z';
  message[1] = 'u';
  message[2] = 't';
  message[3] = ' ';
  message[4] = '!';

  std::chrono::steady_clock::time_point begin, end;
  unsigned count(0);
  while (1)
  {
    // Change the number at the end of the message: "zut 0", "zut 1" etc..
    message[4] = *std::to_string(count++).c_str();
    if (count == 10)
      count = 0;

    // Copy the message into response because reponse is going to be overwritten
    for (unsigned i(0); i < 5; ++i)
      response[i] = message[i];

    // Count time and transfer the message (and get the reponse at the same time)
    begin = std::chrono::steady_clock::now();
    if (!wiringPiSPIDataRW (0, response, 5))
      cerr << "Could not send message!" << endl;
    end = std::chrono::steady_clock::now();

    // Inform user
    cout << "Sent: " << message << endl;
    cout << "Received: " << response << endl;
    cout << "Duration: " << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << " microseconds" << endl;

    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
  }

  return 0;
}

The outputs are the following,

Arduino (read over serial):

zut 0
zut 1
zut 2
zut 3
zut 4
zut 5
zut 6
zut 7
zut 8
zut 9
zut 0
zut 1
zut 2
zut 3

Everything looks good on the Arduino receive side!

RPi output (std::cout/cerr):

Sent: zut 0
Received: eabcd
Duration: 374 microseconds

Sent: zut 1
Received: eabcd
Duration: 359 microseconds

Sent: zut 2
Received: eabcd
Duration: 357 microseconds

Sent: zut 3
Received: eabcd
Duration: 384 microseconds

Sent: zut 4
Received: eabcd
Duration: 389 microseconds

Sent: zut 5
Received: eabcd
Duration: 384 microseconds

Sent: zut 6
Received: eabcd
Duration: 386 microseconds

Sent: zut 7
Received: eabcd
Duration: 391 microseconds

Sent: zut 8
Received: eabcd
Duration: 385 microseconds

Sent: zut 9
Received: eabcd
Duration: 387 microseconds

Sent: zut 0
Received: eabcd
Duration: 387 microseconds

Sent: zut 1
Received: eabcd
Duration: 387 microseconds

Sent: zut 2
Received: eabcd
Duration: 390 microseconds

Sent: zut 3
Received: eabcd
Duration: 386 microseconds

I get the bytes eabcd but I expected abcde.

Why isn't the RPi receiving the bytes in the correct order? Don't hesitate to ask for additional information.

4
  • 2
    Have you tried to send two different strings? It might show you why it's "shifted". Anyway to not be shifted, you'll have to fill the data register in slave device BEFORE master even starts the transfer.
    – KIIV
    May 14, 2017 at 22:33
  • 1
    Did you try changing the order of writing and reading the SPI register (SPDR) in the ISR? May 14, 2017 at 22:49
  • Switching the write/read order (before/afer) in the SPDR does not change the behavior. @KIIV you are right, this solved my problem, I'm going to write the answer now! May 15, 2017 at 8:02
  • @RobertoLoGiacco I can't until 13 hours, thanks for the reminder! May 16, 2017 at 7:45

1 Answer 1

1

@KIIV gave me the answer:

Anyway to not be shifted, you'll have to fill the data register in slave device BEFORE master even starts the transfer.

What happens is that when an SPI transfer is complete, we enter the ISR (SPI_STC_vect), this means that 1 byte (8 bits) has been received. This also means that the response has been sent, because SPI is full duplex!

So the SPDR must be filled with the next byte to be sent before entering the ISR. If you modify the SPDR inside the ISR, this will set the next byte to be sent; it will not be sent until a byte is received.


Fixed Arduino code

// Data received
volatile unsigned char rx_buffer[5];

// Data to be sent
volatile unsigned char tx_buffer[5];

// Triggered to true by the interruption when message is complete
volatile bool message_ready(false);

// Useful to keep trace of where we are in the message
unsigned char bytes_received = 0;

// SPI interrupt routine
// Message consists of 5 bytes: never less, never more
ISR (SPI_STC_vect)
{
  // Store incoming byte
  rx_buffer[bytes_received] = SPDR;

  if (++bytes_received == 5) // Reached the end of the message/buffer
  {
    bytes_received = 0;
    message_ready = true;

    // Let's send another message next time!
    changeTxMessage();
  }

  // When next byte is received, this will be the response
  SPDR = tx_buffer[bytes_received];
}

// This changes the response message sent by the Arduino to the RPi
unsigned count(0);
void changeTxMessage()
{
  // Update message
  switch (count++)
  {
    case 0:
      tx_buffer[0] = 'c';
      tx_buffer[1] = 'r';
      tx_buffer[2] = 'a';
      tx_buffer[3] = 'z';
      tx_buffer[4] = 'y';
      break;
    case 1:
      tx_buffer[0] = 'k';
      tx_buffer[1] = 'a';
      tx_buffer[2] = 'n';
      tx_buffer[3] = 'j';
      tx_buffer[4] = 'i';
      break;
    case 2:
      tx_buffer[0] = 'b';
      tx_buffer[1] = 'u';
      tx_buffer[2] = 'x';
      tx_buffer[3] = 'o';
      tx_buffer[4] = 'm';
      break;
  }

  if (count > 2)
    count = 0;
}

void setup (void)
{
  pinMode(MISO, OUTPUT); // Set Slave Output as output
  SPCR |= _BV(SPE); // Slave mode
  SPCR |= _BV(SPIE); // Turn on interrupts

  // Reponse to be sent
  tx_buffer[0] = 'a';
  tx_buffer[1] = 'b';
  tx_buffer[2] = 'c';
  tx_buffer[3] = 'd';
  tx_buffer[4] = 'e';

  // Prepare first byte to be sent as a response
  SPDR = tx_buffer[0];
}

void loop (void)
{
  // When message is ready, print it on serial port
  if (message_ready)
  {
    message_ready = false;
    Serial.print((char) rx_buffer[0]);
    Serial.print((char) rx_buffer[1]);
    Serial.print((char) rx_buffer[2]);
    Serial.print((char) rx_buffer[3]);
    Serial.println((char) rx_buffer[4]);
  }
}

Changes done:

  • On setup(), SPDR is filled with the first response byte
  • Added a new function void changeTxMessage() that changes the response message sent by the Arduino for demo purposes.

Arduino output:

zut 0
zut 1
zut 2
zut 3
zut 4
zut 5

RPi output:

Sent: zut 0
Received: abcde
Duration: 241 microseconds
Sent: zut 1
Received: crazy
Duration: 231 microseconds
Sent: zut 2
Received: kanji
Duration: 232 microseconds
Sent: zut 3
Received: buxom
Duration: 249 microseconds
Sent: zut 4
Received: crazy
Duration: 254 microseconds
Sent: zut 5
Received: kanji
Duration: 254 microseconds

Everything looks good!


Further thoughts

One thing I don't understand with the old code: why did I receive eabcd the first time I get an answer, the first byte should be random because I did not set the SPDR?! Maybe it's kept in memory during Arduino upload.


Performance

I cannot achieve high bit rates with this code. 250 000 Hz is the maximum I can reach (wiringPiSPISetup(0, 250000)) before getting problems in the communication. Example at 0.5 Mhz:

Arduino log:

zut 0
zut 1
zut 2
zut 3
zut 4
zut 5
zut 6
zut 7

RPi log:

Sent: zut 0
Received: bzut 
Duration: 143 microseconds
Sent: zut 1
Received: czut 
Duration: 136 microseconds
Sent: zut 2
Received: kzut 
Duration: 135 microseconds

I expected to reach at least 2 Mhz, I think I'll open a new question for that.

1
  • 1
    To reach 2 MHz on Arduino Micro you will have to replace the ISR with a tight communications loop. The ISR overhead is just too large on a 16 MHz MCU. You might even have to turn off interrupts (Timer) to achieve reliable communication. May 15, 2017 at 9:02

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.