[I posted this to the Arduino developer's mailing list, but figured others might find it useful too]
When I first started with Arduino, I thought Serial.available() was a very loose wrapping of the RXC bit in the USCRA register, i.e. if I didn’t get data out of there fast, it’d be gone. That led to convoluted code like:
if( Serial.available() ) {
val1 = Serial.read();
while( !Serial.available() );
val2 = Serial.read();
// and so on
}
Yuck. So you end up designing protocols that are too terse. Or maybe you think you need to buffer so you don’t lose it:
while( Serial.available() ) {
commandbuffer[i++] = Serial.read();
}
Then parsing becomes a two step process: read serial, parse buffer. Confusing to newbies perhaps, but at least it allows for a better protocol down the line. (And descend into madness as you gaze into the maw of strtok())
Because Serial contains these big comfy buffers, we often don’t need a second buffer to let us easily implement good protocols. My current favorite is to do something like this:
// protocol is "CCaaaa", two bytes of command, four bytes of args
if( Serial.available() >= 6 ) { // command length is 6 bytes
cmd0 = Serial.read();
cmd1 = Serial.read();
arg0 = Serial.read();
arg1 = Serial.read();
// ...etc...
}
I don’t think I’ve seen any Serial examples that check for a specific number of bytes available. It’s really handy.
Implementing a human-friendly protocol like “command arg0 arg1 arg2″, where command and args are space-separated strings like “servo 12 0xff”, is currently hard with Serial. I do this right now with a 2nd buffer and lots of C hackery:
char* cmdbuf; char c; int i;
while( Serial.available() && c!= '\n' ) { // buffer up a line
c = Serial.read();
cmdbuf[i++] = c;
}
int i = 0;
while( cmdbuf[++i] != ' ' ) ; // find first space
cmdbuf[i] = 0; // null terminate command
char* cmd = cmdbuf; //
int cmdlen = i; // length of cmd
int args[5], a; // five args max, 'a' is arg counter
char* s; char* argbuf = cmdbuf+cmdlen+1;
while( (s = strtok(argbuf, " ")) != NULL && a < 5 ) {
argbuf = NULL;
args[a++] = (byte)strtol(s,NULL,0); // parse hex or decimal arg
}
int argcnt = a; // number of args read
This sort of functionality would be great in a library I think. Maybe not in Serial, but a core class.
Any other protocols people like to use and the Arduino code they use to do it?
Hi todbot & Rob -
*Loved* the code for serial communication- many many thanks…
In using it I noticed it was getting so far and it was freezing.. going through the code (Rob re-wrote) I noticed that the malloc was been run and using up all the memory.. so put
free(cmdbuf);
somewhere when you are clearing variables and you shouldn’t have the same problem :D
Yes, it’s easy to run into many traps when using malloc(). Since the memory space of the chip in Arduino is so small and the tasks being performed are usually fairly simple, I much prefer to use statically-allocated buffers. You never run the risk of memory leaks then and you can know at compile-time exactly how much RAM you have left. (Arduino doesn’t yet expose this however, but the “avr-size” command can give you static RAM usage)
but
while( Serial.available() && c!= ‘\n’ ) {
requires that Arduino while is waiting for the endline, cannot do anything other.
I think it is a big problem to solve.
r.
no, sorry… I mistaked!!! :)
Hi ryuujin,
There is the possibility for the lockup you fear when doing something like:
while( Serial.available() < 5 ) { // wait for 5 bytes // do nothing }If the host never sends 5 characters, it'll just hang. One wait to get around this is to have a timeout:
long start_time = millis(); while( Serial.available() < 5 ) { // wait for 5 bytes if( (millis() - start_time) < 3000 ) // wait at most 3 seconds break; }[...] The best way (I think) is to structure everything as fixed-length messages along the lines of this example. Before you try to talk with EMC, write a Python program that sends and receives messages from the [...]
Hi, very intresting post. I making Rc plane controle by Arduino and Xbee i try to make a light protocol. How could i do to send over serial floar data like lator lng gps data. Could you help me. Protocol seems like this
Start frame 1 byte
Command 1 byte
Data 10 bytes
Checksum 1 byte
End frame 1 byte
But 10 bytes to pass latitudelike -2.153627728 is a good idea?
Thanks
Hi, very good post. I try to make an RC plane where serial communication is very important.
I wrote this
long start_time = millis(); while(Serial.available()){ // Permet de rendre la main au reste du programme if((millis() - start_time) > 10 ) { // wait at most 10 ms break; } // Lecture du byte arrivé byte theByte = Serial.read(); //Nouvelle trame et que nous sommes pas dans la construction d'une trame ou la trame met trop longtemps a arrivé if((theByte == START_TRAME && newTrame == false) || (millis() - DEB_START_TRAME>OUT_OF_TIME_TRAME)) { newTrame = true; nbBuf = -1; DEB_START_TRAME = millis(); } else { // Cas ou nous sommes en construction d'une trame if(nbBuf>=BUFFER_SIZE) { // Cas ou le buffer et plein mais nous il ne s'agit pas de la fin de la trame // alors on sait que la trame est corrompu pas besoin de la traiter if(theByte != END_TRAME) { newTrame = true; nbBuf = -1; for(int i = 0;i<BUFFER_SIZE;i++) { Serial.print(cmdbuf[i]); } Serial.println(" Erreur de trame - "); } else { // Verification du checksum car la trame est valide // On pointe sur le pointeur cmdbuf if(cmdbuf[11] == checkSum()) { // Checksum ok Serial.println("Checksum ok"); // Ici la trame est ok donc on peux executer une commande en call back Serial.print("Commande : "); Serial.print(cmdbuf[0]); long val = ((long )cmdbuf[9]) << 8; val |= cmdbuf[10]; Serial.print("Val : "); Serial.print(cmdbuf[9],DEC); } else { // Checksum error $A0000000080I% Serial.print("Erreur dans le check sum : "); Serial.println(cmdbuf[11],DEC); Serial.print("Compute checksum : "); Serial.println(checkSum(),DEC); } } break; } else { //Decodage d'une trame en cours cmdbuf[++nbBuf] = theByte; Serial.print(nbBuf,DEC); Serial.print(" - "); Serial.println(theByte); } } }In human readable way, the packet is : $A0000000080I%
where $ start trame
A command
0000000080 Data
I Checksum
% end trame
How to pass float data ?
“How to pass float data ?”
The arduino converts the value according to what it thinks it is, so declaring it a float arduino-side should work just fine..
NICE post! far the most usable I have seen the past few days on the subject, tnx.. Btw making me a wifi robot :)
hi guys,
I’ve read kasper and todbot’s posts about the leds. I am trying to store 4 values in the arduino as well. I am using the code below, in which i expect to see the value for xStep printed. Instead of that I always get 32 returned by the serialmonitor, does anyone know why?
cheers, tim
#define stepPin 26
#define dirPin 28
#define enablePin 24
byte cmd;
byte xStep;
byte yStep;
byte zStep;
int i = 0;
void setup()
{
// We set the enable pin to be an output
pinMode(enablePin, OUTPUT);
pinMode(stepPin, OUTPUT);
pinMode(dirPin, OUTPUT);
digitalWrite(enablePin, LOW);
digitalWrite(dirPin, LOW);
digitalWrite(stepPin, LOW);
Serial.begin(9600);
Serial.print("Program Initiated\n");
}
void loop() {
if( Serial.available() == 4 ) {
cmd = Serial.read();
xStep = Serial.read();
yStep = Serial.read();
zStep = Serial.read();
Serial.println(xStep);
}
for(i=0; i<xStep; i++){
stepx_p();
delay(10000);
}
}
void stepx_p(){
digitalWrite(stepPin, HIGH);
delayMicroseconds(2);
digitalWrite(stepPin, LOW);
delayMicroseconds(2);
}
Hi Tim,
What does your code look like that is sending data to your Arduino?
Nanopb (Google protocol buffers for microcontrollers) are an effective solution for a wide range of these problems:
http://koti.kapsi.fi/~jpa/nanopb/docs/
Note that when sending multiple messages to the Serial stream, there is nothing about a PB message that delimits it from the next one. Google has some great advice on how to deal with that here:
https://developers.google.com/protocol-buffers/docs/techniques