Still haven't made that one line change to the slave code I showed you back in post #8, have you?
You have to prime the slave's SPDR with the first byte of the reponse BEFORE the master looks for the number. Otherwise you're always one byte behind. And even with your shifted bytes copy to try and compensate for it, because you're one byte behind, every time the exponent changes in the float, your number is off, because you're using the exponent from the previous number.
But you know what, do what you like, it's your time. I'm done trying to show you how to do it and have you brush it off. I give up.
@van_der_decken Yes, I tried the line. There appeared more ideas here, so I tried one by one, sorry, I am not quick robot. I understand what you ment by the line, unfortunately, even though it comes with ok floats, it did not change the behavior of mistakes on some numbers.
Strange then, don't you think, that I was able to get it working correctly with the sketches I showed you? But never mind. I've taken apart the hardware, and this topic is going on mute.
@van_der_decken I have now left the memcpy solution and gone back to union. I used your sketch for the master side, which I haven't tried yet (just the line on the slave side) and it works, even without the mistakes! Thank you very much.
Please, next time can you be more patient and not attack those who are interested in learning something. No, I don't work randomly, for every solution I look for an explanation to understand it, and that takes a lot of time. And besides playing with the Arduino, I also have to manage my work.
Anyway, thanks again.
@qubits-us Now it works correctly with memcpy and there is no UB of union. Thank you very much for your support!
Master receiving 4 bytes float from Slave:
#include <SPI.h>
uint8_t myData[] = {0x00, 0x00, 0x00, 0x00};
float myFloat;
void setup(){
Serial.begin(19200);
SPI.begin();
delay(100);
SPI.setClockDivider(SPI_CLOCK_DIV16);
}
void loop(){
digitalWrite(SS, LOW);
for(int i = 0; i < 4; i++){
myData[i] = SPI.transfer(myData[i]);
delayMicroseconds(100); // allow Slave to process the received data byte
}
digitalWrite(SS, HIGH);
for (int i = 0; i < 4; i++){
if (myData[i] < 0x10) Serial.print('0');
Serial.print(myData[i], HEX);
Serial.print(" ");
}
memcpy(&myFloat, myData, 4);
Serial.println(myFloat, 2);
delay(3000); //test interval
}
Slave sending 4 bytes float to Master:
#include <SPI.h>
volatile bool flag = false;
volatile int i = 0;
volatile float x = 1.11;
volatile bool flagLoad = false;
volatile byte spiBuff[4];
void setup(){
Serial.begin(19200);
pinMode(SS, INPUT_PULLUP);// ensure SS stays high for now
pinMode(MISO,OUTPUT);
memcpy(spiBuff,(uint8_t*)&x,4);
SPCR |= _BV(SPE);
SPCR |= !(_BV(MSTR)); // defining Slave
SPI.attachInterrupt();
SPDR = spiBuff[i++];
}
void loop(){
if (flag == true){
flag = false;
for (int i = 0; i < 4; i++){
if (spiBuff[i] < 0x10) Serial.print('0');
Serial.print(spiBuff[i], HEX);
Serial.print(" ");
}
Serial.print (" float: ");
Serial.println (x, 2);
if (!flagLoad){
x = x+1;
flagLoad = true;
}
}
}
ISR (SPI_STC_vect){
SPDR = spiBuff[i];
i++;
if (i == 4) {
i = 0; // 4-byte data are sent
flag = true;
if (flagLoad){
memcpy(spiBuff,(uint8_t*)&x,4); //load x into spi buffer..
flagLoad = false; //done loading..
}
}
}
I have answer (s) to this question from my experience.
In the Slave sketch, the SPI.h is included to get the meanings for the symbolic names: MOSI, MISO, SCK, and CS and the codes for attachInterrupt() function.
In Slave side, the SPI.begin() cannot be executed; it is done only at the Master sketch to configue the directions of the MOSI, MISO, SCK, and SS lines and for the codes of SPI.transfer() function.
The Slave should not execute the SPI.transfer() codes as the code generates the necessary SCK pulses to shift-out Master data from its SPDR Register and shifit-in that data into the SPDR-Register of Slave (Fig-1).
Therefore, there is no other alternatives other than using the register level codes at the Slave side to gather arrived data from SPDR Register and then load data into the same SPDR Register intended to be delivered to the Master. It works alright because of back-to-back connection of the SPDR Registers of Master and Slave (Fig-1).
The Slave could gather arrived data by polling the SPIF flag; but, it would be a slower process and the data alignment process (there is always 1-byte lagging) would be very cumbersome. The interrupt process makes the Slave fast responsive to the data arrival event and yet it is needed to insert reasonable time delay at the Master side between successive executions of SPI.transfer() code to keep the Master and Slave synchronized.
You may find worth reading the attached file. Ch-7OnlineLec.pdf (290.6 KB)
In Q8 of Section -7.4 of the attached file, I have successfult transferred float type data (32.67) from Slave to Master over SPI Port using union data structure though people suggest to use memcpy() function to avoid type punning problrm with union.
Setup:
Figure-1:
Master Sketch:
#include<SPI.h>
byte myData[] = {0x00, 0x00, 0x00, 0x00};
union myUData
{
float x;
byte myBytes[4];
};
myUData data; //user defined data type myData is created using union keyword
void setup()
{
Serial.begin(9600);
SPI.begin();
delay(100);
SPI.setClockDivider(SPI_CLOCK_DIV16);//1 MBits/s
//pinMode(SS, OUTPUT);
digitalWrite(SS, LOW); //Slave is selected
}
void loop()
{
for (int i = 0; i < 4; i++)
{
myData[i] = SPI.transfer(myData[i]);
delayMicroseconds(100); //allows Slave to process received byte
Serial.println(myData[i], HEX); //shows: 0xAB, 0x12, 0x34, 0x56
}
data.myBytes[3] = myData[0];
data.myBytes[0] = myData[1];
data.myBytes[1] = myData[2];
data.myBytes[2] = myData[3];
Serial.println(data.x, 2);//shows: 32.67
Serial.println("======================");
delay(1000); //test interval
}
Slave Sketch:
#include<SPI.h>
int i = 0;
byte myData[] = {0x12, 0x34, 0x56, 0xAB};
union myUData
{
float x;
byte myBytes[4];
};
myUData data;
void setup()
{
Serial.begin(9600);
data.x = 32.67;
SPI.setClockDivider(SPI_CLOCK_DIV16);//
pinMode(SS, INPUT_PULLUP); // ensure SS stays high for now
pinMode(MISO, OUTPUT);
SPCR |= _BV(SPE);
SPCR |= !(_BV(MSTR)); //Arduino is Slave
SPI.attachInterrupt(); //interrupt logic is enabled
}
void loop()
{
}
ISR(SPI_STC_vect)
{
SPDR = data.myBytes[i]; //places 0x12, then 0x34, then 0x56, then 0xAB
i++;
if (i == 4) //4-byte data are sent
{
i = 0; //array pointer is reset
}
}
@J-M-L I've been thinking about your question for a while. Do you mean why combine the SPI API and registries when you can work outside the API, only with registres, like in this tutorial? Introduction to SPI
I think it's because there is no need to deal with interrupts to communicate with the sensor, but to communicate between two Arduinos, you already need to use ISR to avoid interrupting the program running on the Slave side.
@J-M-L As the next step, I'd like to try a structure transfer. Since you have experience with this, do you have an example? I think you posted it here, I can't find it, it is deleted?
I had typed it here without testing and upon testing this morning I found out it was total garbage :).
As no-one had reacted to that post I felt safe to remove it rather than keep it around and mislead future readers.
Here is a working (tested) version with a Sender and Receiver where the sender is sending out a structure.
As with any binary data transfer, you need some way to sync the writer and the reader. So there is a header being sent (4 bytes) first before sending the payload. The ISR is waiting to receive a proper header before actually receiving the payload. The likelyhood of a payload containing exactly the sync header has to be low enough to not happen, 4 bytes is usually enough but you could make the syncHeader longer (at the cost of slowing down every data transaction) if you want to minimise that risk.
SENDER:
// SENDER CODE
#include <SPI.h>
const uint8_t sensorPin = A0;
struct __attribute__((packed, aligned(1))) Payload {
uint16_t sensorValue;
unsigned long timeStamp;
};
const uint8_t syncHeader[] = {0xDE, 0xAD, 0xBE, 0xEF}; // Synchronization header
Payload data;
uint8_t *dataPtr = reinterpret_cast<uint8_t*>(&data);
void printPayload(Payload &p) {
Serial.print(p.timeStamp);
Serial.write('\t');
Serial.println(p.sensorValue);
}
void setup() {
Serial.begin(115200);
pinMode(SS, INPUT_PULLUP); // Keep SS HIGH
SPI.begin();
}
void loop() {
// read our sensor
data.sensorValue = analogRead(sensorPin);
data.timeStamp = millis();
Serial.print("Sending: ");
printPayload(data);
digitalWrite(SS, LOW); // enable Slave Select
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); // provide settings 1Mb/s
// Send synchronization header
for (size_t i = 0; i < sizeof syncHeader; i++) {
SPI.transfer(syncHeader[i]);
delayMicroseconds(10); // adjust time against the time the ISR needs on the other side to process the byte
}
// Send payload data
for (size_t i = 0; i < sizeof(data); i++) {
SPI.transfer(dataPtr[i]);
delayMicroseconds(10); // adjust time against the time the ISR needs on the other side to process the byte
}
SPI.endTransaction();
digitalWrite(SS, HIGH); // disable Slave Select
delay(5000);
}
RECEIVER
// RECEIVER CODE
#include <SPI.h>
struct __attribute__((packed, aligned(1))) Payload {
uint16_t sensorValue;
unsigned long timeStamp;
};
Payload data;
volatile uint8_t *dataPtr = reinterpret_cast<uint8_t *>(&data);
volatile bool payloadIsReady = false;
// SPI interrupt routine
ISR(SPI_STC_vect) {
static const uint8_t syncHeader[] = {0xDE, 0xAD, 0xBE, 0xEF}; // Synchronization header
static uint8_t syncPos = 0;
static byte pos = 0;
uint8_t receivedByte = SPDR;
if (syncPos < sizeof syncHeader) {
if (receivedByte == syncHeader[syncPos]) {
syncPos++;
} else {
syncPos = 0;
}
} else {
dataPtr[pos++] = receivedByte;
if (pos >= sizeof(Payload)) {
pos = 0;
syncPos = 0;
payloadIsReady = true;
}
}
}
void printPayload(Payload &p) {
Serial.print(p.timeStamp);
Serial.write('\t');
Serial.println(p.sensorValue);
}
void setup() {
pinMode(SS, INPUT_PULLUP); // EDIT ==> start with SS HIGH (see discussion further)
pinMode(MISO, OUTPUT); // pin on which to send info out
Serial.begin(115200);
SPCR |= bit(SPE); // turn on SPI in slave mode
SPI.attachInterrupt();
Serial.println(F("\nReady to receive data"));
}
void loop() {
if (payloadIsReady) {
// critical section
noInterrupts();
Payload dataCopy = data;
payloadIsReady = false;
interrupts();
// end of critical section
printPayload(dataCopy);
}
}
if you open the Serial monitor on both sides you should see that what is being sent is exactly what's being received.
The Slave Select (SS) line typically needs to be held high when the slave is not selected. This ensures that the slave doesn't respond to unintended communication on the SPI bus.
But, you have put the line in the setup() function of Master; as a result, the line has become an input line (with internal pull-up); whereas, the line should be an output line from Master's side (Fig-1, post #31). The line is totally missing in the setup() of the Slave. Then the question is: why the system is working -- it is working because you have excuted digitalWrite(SS, LOW); at the Master side. To me, this is a flaw though the system is working!
@mandoner 1. It works with a flaw -- see post #38.
2. Are you interested to ask @J-M-L why he has first sent the 4-byte synchronization pattern before sending the data.
3. Also, @J-M-L has not inserted any time delay (at few microseconds level) between successive SPI.transfer() to offer the Slave enough time to process the arrived data in the ISR(). Is it a robust system?
===>
// Send payload data
for (size_t i = 0; i < sizeof(data); i++)
{
SPI.transfer(dataPtr[i]);
delayMicroseconds(10);
}
4. Look below at the size of the ISR(). At 1 Mbits/sec transfer rate of the Master sketch, 1-byte data needs about (1 usec) time to arrive from Master to Slave. Every arrived data byte interrupts the Slave; Slave goes to the ISR() and must execute all those codes within 1 usec time. Apparently, the Slave is executing all those codes within 1 usec time as the system is found to be working. As a design rule, the crane hooks are given 10 to 15 times safety margins of the rated load, and I think the same rule could be applicable here also. Accordingly, a delay of at least 10 usec is to be inserted between successive SPI.transaction() to make the system robust/fail-safe.