ok, I feel like this should be simple, but I am at a loss
Simple code mock up to send 4 bytes(mimicking a float) over I2C from Arduino to rpi. I am trying to create a state machine so that I can request certain "registers" containing 4 bytes each. I send a byte from pi to arduino indicating what "register" I want, then read back 4 bytes. I was attemping to reset the state back to NO_STATE after the end of the switch statement, but it effects the bytes received by the pi, and I dont see how.
#include <Wire.h>
#define SLAVE_ADDRESS 0x04
//States
#define NO_STATE 0x00
#define GET_BAT_TEMP 0x03
byte state = NO_STATE;
void setup() {
Serial.begin(9600);
pinMode(13, OUTPUT);
Wire.begin(SLAVE_ADDRESS);
Wire.onReceive(receiveData);
Wire.onRequest(sendData);
}
void loop() {
delay(100);
}
// callback for received data
void receiveData(int byteCount){
while(Wire.available()) {
state = Wire.read();
}
Serial.print("Got:");
Serial.println(state);
}
// callback for sending data
void sendData(){
Serial.println("Sending...");
byte b[4]={1,1,1,1};
switch (state){
case NO_STATE:
//bad state not sure if can return only byte, may need to return 4 zeros or somthing
Wire.write(0);
Wire.write(0);
Wire.write(0);
Wire.write(0);
break;
case GET_BAT_TEMP:
//send battery temp
Wire.write(3);
Wire.write(3);
Wire.write(3);
Wire.write(3);
break;
default:
Wire.write(9);
Wire.write(9);
Wire.write(9);
Wire.write(9);
//send back error code
}
//reset state to none
state=NO_STATE;
}
with this code I receive [3,0,0,0]
if I comment out the line
//state=NO_STATE;
I then receive back [3,3,3,3]
which is what I would expect. But I do not see how reseting the state "after" all the bytes are sent would cause an issue
output from arduino shows entering that function only once, and sending the 4 bytes
Got:3
Sending...
Sending...
Sending...
Sending...
-
I give you +1 for a well written code, your debugging work and a well formed question.user31481– user314812018年02月13日 09:44:28 +00:00Commented Feb 13, 2018 at 9:44
1 Answer 1
I'm not an expert, but I think I found why it is not working.
The main problem is that you are using always the same byte (0, 3 or 9) for debugging, so you are not noticing that it is not sending four bytes, but only one, and the function gets called four times. This is obvious however from the reply on the serial interface (+1 for adding it in the question, good work).
What you can do is to track how many bytes you sent, and send the successive one. After the bytes are all sent you can reset back the state. The code can be something like
//States
#define NO_STATE 0x00
#define GET_BAT_TEMP 0x03
byte sentData;
byte batteryLevel[4]={3,4,5,6};
byte state = NO_STATE;
void setup() {
Serial.begin(9600);
pinMode(13, OUTPUT);
Wire.begin(SLAVE_ADDRESS);
Wire.onReceive(receiveData);
Wire.onRequest(sendData);
}
void loop() {
// delay(100); // Useless, let it cycle at its max speed
}
// callback for received data
void receiveData(int byteCount){
while(Wire.available()) {
state = Wire.read();
sentData = 0;
}
Serial.print("Got:");
Serial.println(state);
}
// callback for sending data
void sendData(){
Serial.println("Sending...");
switch (state){
case NO_STATE:
//bad state not sure if can return only byte, may need to return 4 zeros or somthing
Wire.write(0);
break;
case GET_BAT_TEMP:
//send battery temp
Wire.write(batteryLevel[sentData]);
if (sentData < 3)
sentData++;
else
//reset state to none
state=NO_STATE;
break;
default:
Wire.write(9);
//send back error code
}
}
I haven't tested it, so forgive any bugs. But I'm sure you understood the main point.
Just some remarks: the delay is useless (you are not saving energy or other with it, so just remove it); the serial is ok for debugging, but I think you should remove it from the code you will use (not ok to stop the interrupts for such a long time).
And most important: I hope you considered the 5V vs 3.3V voltage problem on the I2C ;)
In any case, I second Look Alterno: good way to ask a question, with all the information needed.
EDIT: as pointed out in the comments, there exist a function to send all the data together. I'm not sure how it behaves in different conditions (e.g. when less bytes are requested), but if you trust the communication the function can be simplified in
// callback for sending data
void sendData(){
Serial.println("Sending...");
switch (state){
case NO_STATE:
//bad state not sure if can return only byte, may need to return 4 zeros or somthing
Wire.write(0);
break;
case GET_BAT_TEMP:
//send battery temp
Wire.write(batteryLevel, 4);
state=NO_STATE;
break;
default:
Wire.write(9);
//send back error code
}
}
(and the sentData
variable becomes useless)
-
1You can use
Wire.write(data, len)
to send all 4 bytes at once.user31481– user314812018年02月13日 13:05:18 +00:00Commented Feb 13, 2018 at 13:05 -
@LookAlterno I never used it, so I didn't know it existed ;) in any case the OP was using the single byte version, so I used it too... Thank you for the info, I'll update the answer with thisfrarugi87– frarugi872018年02月13日 15:17:12 +00:00Commented Feb 13, 2018 at 15:17
-
1It was not a criticism, just a collaboration.user31481– user314812018年02月13日 15:50:37 +00:00Commented Feb 13, 2018 at 15:50
-
If the internal pullups on the arduino side are disabled, there is no reason to worry about the voltage mismatch correct? Arduino only pulls the SDA line low, letting the pi pull it up to rail voltage at 3.3V.Chad G– Chad G2018年02月13日 16:18:18 +00:00Commented Feb 13, 2018 at 16:18
-
I must have been really tired last night. Didn't put it together in my head that the interrupt was being triggered again before the remaining data was sent. I have not tired any of this yet, but will mark it correct as it seems right. Will edit it with working code once I am done.Chad G– Chad G2018年02月13日 16:20:17 +00:00Commented Feb 13, 2018 at 16:20