I have an arduino Mega (Master) and a Series of arduino nano (Slaves) communicating via SPI.
Each slave SS pin is wired to a digital pin on the master. I called the pins Ports.
In my project I'll connect 13 Ports but the hardware is set up to allow up to 30 Ports. My idea is that ports can be dynamic, that means that if I connect the slave, the master recognize it and reads and writes data, if I disconnect the slave, the master sends a state value = -1.
Regarding the spi communication, initially I was using Strings to send and receive data, so for example, if the slave was connected and responding, it sent this "<id,state,value>".
On the master i concatenated all ports strings so I got a big String like this:
"<port,id,state,value><port,id,state,value>...<port,id,state,value>"
For example:
"<2,1,1,20><3,1,1,0>...<10,-1,-1,0><11,-1,-1,>"
In that situation ports 2 and 3 where transmitting while ports 10 and 11 did not.
If I needed to send data from the master to a slave, I did the following:
String dataSend = "<value,value...,value>";
SPI.transfer(1); //Send a value indicating transmission started
loop(){SPI.transfer(dataSent.toBytes[index]};
SPI.transfer("-"); // Value that markes the end of the transmission.
That approach was working great and smoothly , I got data from slaves and was able to sent data to them. The problem arose when after a while, slaves stopped transmitting so they got blocked. I read online that using Strings was not a good idea because it generates memory overflow, so I decided to switch to a more robust structure using bytes and packaged structures instead of Strings.
I created two SPI libraries: Master and Slave.
On the master library, I have two functions sendData and readData. in readData, I got data coming from the slave. To achieve that, I have a dummy array that sends two markers: START_TRANSMISSION and END_TRANSMISSION and a dummy payload. The data coming from the slave, structured as START_PAYLOAD PAYLOAD CRC END_PAYLOAD, is stored in the array txBuffer. I put a small delay of 20us between every transmission to give the slave time to receive the data and send it back.
The problem I'm encountering is that I have a lot of wrongs readings from the slave and no mater what I change, I always got the same behavior, whereas when I was using Strings, wrong readings were almost inexistent.
For debug purposes, I'm only testing the port 22, so only one slave is connected. Regarding timing, I'm starting a new transmission every 20ms max 30ms. Speed is important in my project so higher than that will be too slow.
When I was working with Strings I got times of even 8ms per transmission.
This is what I'm getting from slave:
09:14:45.036 -> Port = 22 FF FF C0 1 1 0 0 C1 41 51 C4 C4...
09:14:45.036 -> Port = 22 FF C3 1 1 0 0 10 41 51 C4 C4 C4...
09:14:45.070 -> Port = 22 FF C3 1 1 0 0 10 41 51 C4 C4 C4...
09:14:45.070 -> Port = 22 FF FF FF 1 1 0 0 10 41 51 C4 C4...
09:14:45.070 -> Port = 22 FF C3 1 1 0 0 10 41 51 C4 C4 C4...
09:14:45.070 -> Port = 22 FF C3 1 1 0 0 10 41 51 C4 C4 C4...
09:14:45.108 -> Port = 22 C0 C3 1 1 FF 0 10 FF FF C4 C4 C4...
The correc value must be
FF C3 1 1 0 0 10 41 51 C4 C4 C4...
Another strange thing is that if I connect the slave to the PC usb port, the readings became more stable even though no serial communication is enabled.
like this:
09:14:45.322 -> Port = 22 FF C3 1 1 0 0 10 41 51 C4 C4 C4 ...
09:14:45.354 -> Port = 22 FF C3 1 1 0 0 10 41 51 C4 C4 C4 ...
09:14:45.354 -> Port = 22 FF C3 1 1 0 0 10 41 51 C4 C4 C4 ...
09:14:45.354 -> Port = 22 FF C3 1 1 0 0 10 41 51 C4 C4 C4 ...
09:14:45.354 -> Port = 22 FF C3 1 1 0 0 10 41 51 C4 C4 C4 ...
09:14:45.354 -> Port = 22 FF C3 1 1 0 0 10 41 51 C4 C4 C4 ...
09:14:45.388 -> Port = 22 FF C3 1 1 0 0 10 41 51 C4 C4 C4 ...
09:14:45.388 -> Port = 22 FF C3 1 1 0 0 10 41 51 C4 C4 C4 ...
Here the code of the master and slave's libraries.
Regarding the slave, the function sendReceiveData() is called from the interrupt ISR, from the slave sketch, like this:
ISR(SPI_STC_vect) {
spi_transfer.sendReceiveData();
}
the function getReceivedPacket() process the data coming from master after the transmission has finished
The function setDataToSend() preloads the data to send to the buffer only if nothing is being transmitted.
#ifndef SPITRANSFER_MASTER_H
#define SPITRANSFER_MASTER_H
#include <SPI.h>
#include <D:\OneDrive\Industrial Automation\SOFTWARE\DeviworksApp\Arduino\SPICommunication\DataValidation\DataValidation.h>
class SPITransfer_Master {
private:
static const int bufferSize = 30; //must match the slave's buffer size
static const int8_t START_TRANSMISSION = 0xC0;
static const int8_t END_TRANSMISSION = 0xC1;
static const int8_t START_PAYLOAD = 0xC3;
static const int8_t END_PAYLOAD = 0xC4;
int startPort = 2;
int finalPort = 10;
bool transferParametersSet = false;
public:
void setTransferParameter(int _startPort = 2, int _finalPort = 10) {
if (transferParametersSet) return;
startPort = _startPort;
finalPort = _finalPort;
for (int i = startPort; i <= finalPort; i++) {
pinMode(i, OUTPUT);
digitalWrite(i, HIGH); // Deselect all slaves
}
SPI.begin();
SPI.setClockDivider(SPI_CLOCK_DIV8);
transferParametersSet = true;
}
bool readData(int port, void* rxData,
const void* rxdefaultNotTransmitting, const void* rxdefaultInvalidData, size_t payloadlength) {
int frameBytes = 3;
size_t fullFrameLength = payloadlength + frameBytes;
if (port < startPort || port > finalPort || fullFrameLength > bufferSize) return;
int8_t dummyTx[bufferSize];
int8_t rxBuffer[bufferSize];
for (int i = 0; i < bufferSize; i++) {
if(i<fullFrameLength) dummyTx[i] = -1;
else dummyTx[i] = END_TRANSMISSION;;
}
// Frame the message
dummyTx[1] = START_TRANSMISSION;
dummyTx[fullFrameLength-1] = 0XD1; //Fake CRC value
//start transmission
digitalWrite(port, LOW);
delay(1); // ensure slave ready
// Send and receive bufferSize bytes
for (int i = 0; i < bufferSize; i++) {
rxBuffer[i] = SPI.transfer(dummyTx[i]);
delayMicroseconds(50);
}
digitalWrite(port, HIGH);
delay(1); // ensure slave ready
//end transmission
//Copy just the payload (skip START and END)
//printBuffer(port,rxBuffer);
DataValidation Validator;
DataValidation::DataStatus status = Validator.validateData(START_PAYLOAD,END_PAYLOAD,rxBuffer,bufferSize,payloadlength,true);
switch (status) {
case DataValidation::notTransmitting: memcpy(rxData, rxdefaultNotTransmitting, payloadlength); break;
case DataValidation::dataCorrupted: memcpy(rxData, rxdefaultInvalidData, payloadlength); break;
case DataValidation::dataOk: memcpy(rxData, &rxBuffer[Validator.GetStartIndex() + 1], payloadlength); break;
}
}
bool sendData(int port, const void* txData, size_t payloadlength) {
printBuffer(port,txData,payloadlength);
int frameBytes = 3;
size_t fullFrameLength = payloadlength + frameBytes;
if (port < startPort || port > finalPort || fullFrameLength > bufferSize) return false;
int8_t checkByte;
bool payloadReceived = false;
int8_t txBuffer[bufferSize];
// Frame the message
txBuffer[0] = START_TRANSMISSION;
txBuffer[payloadlength + 1] = DataValidation::computeCRC(reinterpret_cast<const int8_t*>(txData), payloadlength);
txBuffer[payloadlength+2] = END_TRANSMISSION;
//start transmission
digitalWrite(port, LOW);
delay(1); // ensure slave ready
// Send and receive bufferSize bytes
for (size_t i = 0; i < bufferSize; i++) {
checkByte = SPI.transfer(txBuffer);
if (checkByte == END_PAYLOAD) payloadReceived = true;
delayMicroseconds(15);
}
digitalWrite(port, HIGH);
delay(1); // ensure slave ready
//end transmission
return payloadReceived;
}
int getStartPort() const {
return startPort;
}
int getFinalPort() const {
return finalPort;
}
void printBuffer(int port, const int8_t* buffer)
{
Serial.print("Port = " + String(port) + " ");
for(int i=0;i<bufferSize;i++)
{
Serial.print((uint8_t)buffer[i],HEX);
Serial.print(" ");
}
Serial.println();
}
};
#endif
#ifndef SPITRANSFER_SLAVE_H
#define SPITRANSFER_SLAVE_H
#define SS_PIN 10
#include <SPI.h>
#include <D:\OneDrive\Industrial Automation\SOFTWARE\DeviworksApp\Arduino\SPICommunication\DataValidation\DataValidation.h>
class SPITransfer_Slave {
private:
static const int bufferSize = 30;
static const int8_t START_TRANSMISSION = 0xC0;
static const int8_t END_TRANSMISSION = 0xC1;
static const int8_t START_PAYLOAD = 0xC3;
static const int8_t END_PAYLOAD = 0xC4;
volatile int8_t txBuffer[bufferSize];
volatile int8_t rxBuffer[bufferSize];
volatile size_t frameLength = 0;
volatile bool isPackageReady = false;
volatile bool isBusy = false;
volatile int byteIndex = 0;
bool isSSLow() {
return digitalRead(SS_PIN) == LOW;}
public:
void initialize() {
pinMode(MISO, OUTPUT);
SPCR |= _BV(SPE); // Enable SPI
SPCR |= _BV(SPIE); // Enable interrupt
resetBuffer();
}
void sendReceiveData() {
uint8_t received = SPDR;
isBusy = true;
if(byteIndex > bufferSize || received == END_TRANSMISSION)
{
isPackageReady = true;
return;
}
if(byteIndex < bufferSize)
{
rxBuffer[byteIndex] = received;
SPDR = txBuffer[byteIndex];
}
byteIndex++;
}
bool getReceivedPacket(void* dest, size_t length) {
bool result = false;
//if (isSSLow() && isPackageReady) {
if (isPackageReady) {
isPackageReady = false;
DataValidation Validator;
if (Validator.validateData(START_TRANSMISSION, END_TRANSMISSION, rxBuffer, bufferSize, length, true) == DataValidation::dataOk) {
memcpy(dest, &rxBuffer[Validator.GetStartIndex() + 1], length);
result = true;
}
isBusy = false;
resetBuffer();
}
return result;
}
void setDataToSend(const void* src, size_t length) {
if(length > bufferSize-3) return;
//if(!isSSLow() && !isBusy)
if(!isBusy)
{
resetBuffer();
frameLength= length;
txBuffer[0] = START_PAYLOAD;
memcpy(&txBuffer[1], src, length);
txBuffer[length + 1] = DataValidation::computeCRC(reinterpret_cast<const int8_t*>(src), length);
txBuffer[length + 2] = END_PAYLOAD;
// Fill remaining buffer with -1
for (size_t i = length + 2; i < bufferSize; ++i) {
txBuffer[i] = END_PAYLOAD;
}
}
else if(!isPackageReady && !isSSLow())isPackageReady = true;
}
void resetBuffer() {
for (int i = 0; i < bufferSize; ++i) {
rxBuffer[i] = -1;
txBuffer[i] = -1;
}
SPDR = -1; //preload first byte of SPDR
byteIndex = 0;
}
void printBuffer(const int8_t* buffer, size_t length)
{
for(int i=0;i<length;i++)
{
Serial.print((uint8_t)buffer[i],HEX);
Serial.print(" ");
}
Serial.println();
}
};
#endif