Search code examples
c++carduinospiraspberry-pi3

Unreliable SPI byte array transfer from Arduino to Raspberry Pi


I'm working on a project that collects data from an Arduino Pro Mini and sends it using SPI to a raspberry Pi for storage.

The Pro Mini will be reading analog input and calculating voltage (once I finish), and passing values to the Pi when prompted using an ISR.

I'm using C/C++ for both platforms to keep it uniform. The slave code is snipped together using Arduino IDE and the master code is based off a BCM2835 SPI library example for the Pi.

Arduino code is meant to calculate a float value and pre-process the float value into an array of 4 bytes/chars (I'm shooting for binary because I think it is the best way to go). Once prompted by the Pi, each byte is sent and will be recompiled into a float.

Here is what I have now:

Slave

/*************************************************************
ARDUINO BREAKER READ/SPI PRE-PROC/TRANSMIT CASES
****************************************************************/

/***************************************************************
 Global Variables
***************************************************************/

byte command = 0; //command from PI
byte bytes[4];    //

int sensorVoltage, sensorCurrent; //eventual live reading vars
float Voltage, Current, RealCurrent, RealVoltage, Power;

/***************************************************************
 Set Up
  -designate arudino as slave
  -turn on interrupts
***************************************************************/

void setup (void)
{
  //debugging with serial monitor
  Serial.begin(9600);

  // Set up arduino as slave
  pinMode(MOSI, INPUT);
  pinMode(SCK, INPUT);
  pinMode(SS, INPUT);   
  pinMode(MISO, OUTPUT);

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

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

}  // end of setup

/*************************************************************
 Interrupt Service Routine 
 ************************************************************/

// SPI interrupt routine
ISR (SPI_STC_vect)
{
  delay(500); //for errors

  // Create union of shared memory space
  union 
  {
    float f_var;
    unsigned char bytes[4];
  } u;

  // Overwrite bytes of union with float variable
  u.f_var = RealVoltage;

  // Assign bytes to input array
  memcpy(bytes, u.bytes, 4);

  byte c = SPDR;
  command = c; 

  switch (command)
  {
  // null command zeroes register
  case 0:

    SPDR = 0;
    break;

  // case a - d reserved for voltage
  case 'a':
    SPDR = bytes[3];  
    break;

  // incoming byte, return byte result
  case 'b':

    SPDR = bytes[2];  
    break;

  // incoming byte, return byte result    
  case 'c':

    SPDR =  bytes[1];  
    break;


  // incoming byte, return byte result    
  case 'd':

    SPDR = bytes[0];  
    break;

 /**  // case e -h reserved for current
  case 'e':

    SPDR = amps.b[0];  
    break;

  // incoming byte, return byte result
  case 'f':

    SPDR = amps.b[1];  
    break;

  // incoming byte, return byte result    
  case 'g':

    SPDR = amps.b[2];  
    break;

  // incoming byte, return byte result    
  case 'h':

    SPDR = amps.b[3];  
    break;

   // case i - l reserved for wattage
  case 'i':

    SPDR = watts.b[0];  
    break;

  // incoming byte, return byte result
  case 'j':

    SPDR = watts.b[1];  
    break;

  // incoming byte, return byte result    
  case 'k':

    SPDR = watts.b[2];  
    break;

  // incoming byte, return byte result    
  case 'l':

    SPDR = watts.b[3];  
    break;**/

  } // end of switch

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

/***************************************************************  
 Loop until slave is enabled by Pi.
****************************************************************/
void loop (void)
{
/*************************************************************
Read and Calculate
****************************************************************/

  /**
  sensorVoltage = analogRead(A2);
  sensorCurrent = analogRead(A3);
  Voltage = sensorVoltage*(5.0/1023.0);
  Current = sensorCurrent*(5.0/1023.0);
  RealCurrent = Current/0.204545;
  RealVoltage = (Voltage/0.022005);
  Power = RealVoltage*RealCurrent;
**/
  RealVoltage = 1.234;
/*************************************************************
Loop Check for SS activation
****************************************************************/

  // if SPI not active, clear current command, else preproc floats and pass to SPI
  if (digitalRead (SS) == HIGH){
    command = 0;
  }
/*************************************************************
Debug with serial monitor
****************************************************************/
/*
  Serial.print("Byte 3: ");
  Serial.println(bytes[3],BIN);
  delay(500);
  Serial.print("Byte 2: ");
  Serial.println(bytes[2],BIN);
  delay(500);
  Serial.print("Byte 1: ");
  Serial.println(bytes[1],BIN);
  delay(500);
  Serial.print("Byte 0: ");
  Serial.println(bytes[0],BIN);
  delay(1000);
  Serial.println();*/
}

Master

#include <bcm2835.h>
#include <stdio.h>


void setup()
{
    bcm2835_spi_begin();
    bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_LSBFIRST);      // The default
    bcm2835_spi_setDataMode(BCM2835_SPI_MODE0);                   // The default
    bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_65536); // The default
    bcm2835_spi_setChipSelectPolarity(BCM2835_SPI_CS0, LOW);      // the default
}

char getByte(const char command){

    char read_data = bcm2835_spi_transfer(command);
    delay(100);
    return read_data;
}

int main(int argc, char **argv)
{
//If you call this, it will not actually access the GPIO
//Use for testing
//bcm2835_set_debug(1);

    if (!bcm2835_init())
        return 1;
    setup();

//Start communication       
    bcm2835_spi_chipSelect(BCM2835_SPI_CS0);// Enable 0

    //voltage 1-4  
    char read_data = getByte('a');
    printf("byte is %02d\n", read_data);

    read_data = getByte('b');
    printf("byte is %02d\n", read_data);

    read_data = getByte('c');
    printf("byte is %02d\n", read_data);

    read_data = getByte('d');
    printf("byte is %02d\n", read_data);    

   /** voltage = volts.f;   
    printf("%.6f", voltage);
    printf("\n");
    **/
    delay(1000);   

    bcm2835_spi_setChipSelectPolarity(BCM2835_SPI_CS0, HIGH);
    bcm2835_spi_chipSelect(BCM2835_SPI_CS0);// Disable 0
    bcm2835_spi_end();
    bcm2835_close();

    return 0;
}

I'm using a fixed value to debug the code. Sometimes the output from SPI on the Pi is accurate, but otherwise it changes and outputs partially accurate and/or random bytes.

The issue I have been unable to work out is stability on the side of the Pi, so I am looking for help evaluating whether my code is causing inaccuracies or if it's my hardware.


Solution

  • At a guess I would say that this is a timing issue between the sender and receiver. Try looking at the bits of the data that you are receiving and see if they are shifted forward or backward. This will possibly indicate that the pi is waiting too long to begin receiving, or not waiting long enough for all of the data. I think the issues are probably around the delays:

    delay(500); //for errors
    

    on the Arduino, and

    delay(1000); 
    

    on the receiver. Why are you using these? 500ms is a long time to keep the pi waiting for a response to the SPI data.

    Just a note, there is also now an SPI kernel driver (spidev) for the pi - this is a much more industry standard approach, and is potentially a more robust method.

    There's a good example of this on the raspberry pi github site: https://github.com/raspberrypi/linux/blob/rpi-4.4.y/Documentation/spi/spidev_test.c