Hello
I'm working on a serial protocol to send some data back and forth between an ESP8266 and an Arduino or Teensy.
Some context: I'm building a control surface, so the ESP handles the WiFi connection, hosts a webpage, creates UDP connections etc. The Arduino takes care of I/O: it controls motorized faders, keeps track of rotary encoder positions, reads analog values from potentiometers ... (more info here)
First the ESP sends setup information to the Arduino, e.g. "read analog values on pins A1-A8".
If those values change, the Arduino will send back the new value to the ESP.
The ESP can also send new fader positions to the Arduino, and the Arduino should then adjust the position of the motorized faders accordingly.
The messages look like this:
1lll cccc 0ddd dddd ... 0ddd dddd 0ppp pppp
Where lll = length, cccc = command, ddddddd = data and ppppppp = parity (simple XOR)
If the error checking fails in the setup phase, it will resend the message, if it fails for a normal change of value packet, it should be ignored.
I'd like some help to actually implement this protocol. I've made a prototype, but there's a lot of room for improvement, so I am open to any constructive criticism.
I've included the most important parts of my (working) code, and added the rest as attachments.
// ESP8266 (master)
#define UINT unsigned int
struct OneByteMsg {
UINT command : 4;
UINT length : 3;
UINT start : 1;
UINT data1 : 7;
UINT continue1 : 1;
UINT xorCheck : 7;
UINT stop : 1;
OneByteMsg(int cmd, int d1) : command(cmd), data1(d1) {
start = 1;
length = 1;
continue1 = 0;
stop = 0;
xorCheck = 0x7F & ( (length << 4 | command ) ^ (data1) );
}
};
struct TwoByteMsg {
UINT command : 4;
UINT length : 3;
UINT start : 1;
UINT data1 : 7;
UINT continue1 : 1;
UINT data2 : 7;
UINT continue2 : 1;
UINT xorCheck : 7;
UINT stop : 1;
TwoByteMsg(int cmd, int d1, int d2) : command(cmd), data1(d1), data2(d2) {
start = 1;
length = 2;
continue1 = continue2 = 0;
stop = 0;
xorCheck = 0x7F & ( (length << 4 | command ) ^ (data1) ^ (data2) );
}
};
struct faderMsg {
UINT command : 4;
UINT length : 3;
UINT start : 1;
UINT motor1 : 7;
UINT continue1 : 1;
UINT motor2 : 7;
UINT continue2 : 1;
UINT motorPWM : 7;
UINT continue3 : 1;
UINT fader : 7;
UINT continue4 : 1;
UINT touch : 7;
UINT continue5 : 1;
UINT xorCheck : 7;
UINT stop : 1;
faderMsg(int m1, int m2, int mp, int fd, int tch) {
motor1 = 0x7F & m1;
motor2 = 0x7F & m2;
motorPWM = 0x7F & mp;
fader = 0x7F & fd;
touch = 0x7F & tch;
start = 1;
for (int i = 1; i < sizeof(*this); i++) { // all data bytes have MSB = 0
((uint8_t*)(this))[i] &= 0b01111111;
}
command = FADER;
length = sizeof(*this) - 2;
xorCheck = 0;
for (int i = 0; i < sizeof(*this) - 1; i++) { // XOR all bytes together for a parity check
xorCheck ^= ((uint8_t*)(this))[i] & 0x7F;
}
}
};
// ESP8266 (master)
faderMsg fdr(24, 25, 26, 27, 28);
do {
Serial.write((uint8_t*)&fdr, 7);
delay(10);
} while(Serial.read() != ACK);
// Arduino (slave)
uint8_t values[8];
int index = 0;
int length = 0;
int command = 0;
int check = 0;
void loop() {
if (Serial1.available() > 0) {
uint8_t read = Serial1.read();
if (read >> 7 & 1 == 1) { // if it's a start byte (MSB = 1)
index = 0;
length = read >> 4 & 0b111;
command = read & 0b1111;
} else if (index == length) { // Last byte (parity check)
if (read ^ check == 0) { // parity OK
parseMsg(length, command, values);
}
} else { // normal data byte
values[index++] = read;
check ^= read;
}
}
}
// Arduino (slave)
void parseMsg(int length, int command, uint8_t* values) {
Serial.println("Message received:\r\n-----------------");
switch (command) {
case OUT:
if (length == OUT_LEN) {
// ...
sendAck();
}
break;
case IN:
if (length == IN_LEN) {
// ...
sendAck();
}
break;
// ...
}
}
Things I would like to improve:
- Right now there's a separate struct for every different length of packet. The struct for a packet with 2 data bytes is nearly identical to a packet with only one byte of data, apart from the extra byte, obviously.
Also, I explicitly set the MSB of every data byte to zero in the constructor, this is just nasty and doesn't seem right. Is there an easier way? - As mentioned before, in the setup phase, the ESP should resend the message if it doesn't get an acknowledgement back. I'm currently using a do ... while loop for this purpose, but I think there's better ways to implement this.
- Of course, other comments/improvements are very much appreciated as well.
A link with more information about implementing a binary protocol, or maybe an example would be really helpful. I've done some research but I didn't find much useful information.
Thanks a lot,
Pieter
ESP8266_Arduino_protocol_master.ino (2.68 KB)
ESP8266_Arduino_protocol_slave.ino (2.93 KB)