I am currently playing with a MindWave Mobile headset. I am parsing bluetooth data received from a connected BlueSMIRF Silver.The code I started from is asample from the MindWave wiki and it works using hardware Serial.For reference here is the original code:
////////////////////////////////////////////////////////////////////////// Arduino Bluetooth Interface with Mindwave// // This is example code provided by NeuroSky, Inc. and is provided// license free.////////////////////////////////////////////////////////////////////////#define LED 13#define BAUDRATE 57600#define DEBUGOUTPUT 1#define GREENLED1 3#define GREENLED2 4#define GREENLED3 5#define YELLOWLED1 6#define YELLOWLED2 7#define YELLOWLED3 8#define YELLOWLED4 9#define REDLED1 10#define REDLED2 11#define REDLED3 12#define powercontrol 10// checksum variablesbyte generatedChecksum = 0;byte checksum = 0; int payloadLength = 0;byte payloadData[64] = { 0};byte poorQuality = 0;byte attention = 0;byte meditation = 0;// system variableslong lastReceivedPacket = 0;boolean bigPacket = false;//////////////////////////// Microprocessor Setup ////////////////////////////void setup() { pinMode(GREENLED1, OUTPUT); pinMode(GREENLED2, OUTPUT); pinMode(GREENLED3, OUTPUT); pinMode(YELLOWLED1, OUTPUT); pinMode(YELLOWLED2, OUTPUT); pinMode(YELLOWLED3, OUTPUT); pinMode(YELLOWLED4, OUTPUT); pinMode(REDLED1, OUTPUT); pinMode(REDLED2, OUTPUT); pinMode(REDLED3, OUTPUT); pinMode(LED, OUTPUT); Serial.begin(BAUDRATE); // USB}////////////////////////////////// Read data from Serial UART //////////////////////////////////byte ReadOneByte() { int ByteRead; while(!Serial.available()); ByteRead = Serial.read();#if DEBUGOUTPUT Serial.print((char)ByteRead); // echo the same byte out the USB serial (for debug purposes)#endif return ByteRead;}///////////////MAIN LOOP///////////////void loop() { // Look for sync bytes if(ReadOneByte() == 170) { if(ReadOneByte() == 170) { payloadLength = ReadOneByte(); if(payloadLength > 169) //Payload length can not be greater than 169 return; generatedChecksum = 0; for(int i = 0; i < payloadLength; i++) { payloadData[i] = ReadOneByte(); //Read payload into memory generatedChecksum += payloadData[i]; } checksum = ReadOneByte(); //Read checksum byte from stream generatedChecksum = 255 - generatedChecksum; //Take one's compliment of generated checksum if(checksum == generatedChecksum) { poorQuality = 200; attention = 0; meditation = 0; for(int i = 0; i < payloadLength; i++) { // Parse the payload switch (payloadData[i]) { case 2: i++; poorQuality = payloadData[i]; bigPacket = true; break; case 4: i++; attention = payloadData[i]; break; case 5: i++; meditation = payloadData[i]; break; case 0x80: i = i + 3; break; case 0x83: i = i + 25; break; default: break; } // switch } // for loop#if !DEBUGOUTPUT // *** Add your code here *** if(bigPacket) { if(poorQuality == 0) digitalWrite(LED, HIGH); else digitalWrite(LED, LOW); Serial.print("PoorQuality: "); Serial.print(poorQuality, DEC); Serial.print(" Attention: "); Serial.print(attention, DEC); Serial.print(" Time since last packet: "); Serial.print(millis() - lastReceivedPacket, DEC); lastReceivedPacket = millis(); Serial.print("\n"); }#endif bigPacket = false; } else { // Checksum Error } // end if else for checksum } // end if read 0xAA byte } // end if read 0xAA byte}One problem I have is the blocking while loop in the ReadOneByte function:
byte ReadOneByte() { int ByteRead; while(!Serial.available()); ByteRead = Serial.read();#if DEBUGOUTPUT Serial.print((char)ByteRead); // echo the same byte out the USB serial (for debug purposes)#endif return ByteRead;}I am trying to avoid this so I started drafting a basic state machine approach:
#define BAUDRATE 57600// checksum variablesbyte generatedChecksum = 0;byte checksum = 0; int payloadLength = 0;byte payloadData[169] = {0};byte poorQuality = 0;byte attention = 0;byte meditation = 0;// system variableslong lastReceivedPacket = 0;boolean bigPacket = false;int payloadIndex;int state = 0;const int STATE_WAIT_FOR_FIRST_A = 0;const int STATE_WAIT_FOR_SECOND_A = 1;const int STATE_WAIT_FOR_PAYLOAD_LENGTH = 2;const int STATE_WAIT_FOR_PAYLOAD = 3;const int STATE_WAIT_FOR_CHECKSUM = 4;const String stateNames[5] = {"waiting for first A","waiting for second A","waiting for payload length","accumulating payload","waiting for checksum"};void setup() { Serial.begin(BAUDRATE); // USB}void loop() {}void parsePayload(){ poorQuality = 200; attention = 0; meditation = 0; for(int i = 0; i < payloadLength; i++) { // Parse the payload switch (payloadData[i]) { case 2: i++; poorQuality = payloadData[i]; bigPacket = true; break; case 4: i++; attention = payloadData[i]; break; case 5: i++; meditation = payloadData[i]; break; case 0x80: i = i + 3; break; case 0x83: i = i + 25; break; default: break; } // switch } // for loop Serial.print("bigPacket:"); Serial.println(bigPacket); if(bigPacket) { Serial.print("PoorQuality: "); Serial.print(poorQuality, DEC); Serial.print(" Attention: "); Serial.print(attention, DEC); Serial.print(" Time since last packet: "); Serial.print(millis() - lastReceivedPacket, DEC); lastReceivedPacket = millis(); Serial.print("\n"); } bigPacket = false; }void printState(){ Serial.print("state:"); Serial.println(stateNames[state]);}void serialEvent(){ if(Serial.available() > 0){ switch(state){ case STATE_WAIT_FOR_FIRST_A: printState(); if(Serial.read() == 170) state = STATE_WAIT_FOR_SECOND_A; break; case STATE_WAIT_FOR_SECOND_A: printState(); if(Serial.read() == 170) state = STATE_WAIT_FOR_PAYLOAD_LENGTH; break; case STATE_WAIT_FOR_PAYLOAD_LENGTH: printState(); payloadLength = Serial.read(); Serial.print("payloadLength:");Serial.println(payloadLength); if(payloadLength > 169){ Serial.println(payloadLength > 169); state = STATE_WAIT_FOR_FIRST_A; return; } generatedChecksum = payloadIndex = 0; state = STATE_WAIT_FOR_PAYLOAD; break; case STATE_WAIT_FOR_PAYLOAD: printState(); if(payloadIndex < payloadLength){ payloadData[payloadIndex] = Serial.read(); generatedChecksum += payloadData[payloadIndex]; Serial.print("payloadData[");Serial.print(payloadIndex);Serial.print(" of ");Serial.print(payloadLength);Serial.print("]: "); Serial.println(payloadData[payloadIndex]); }else{ state = STATE_WAIT_FOR_CHECKSUM; } break; case STATE_WAIT_FOR_CHECKSUM: printState(); checksum = Serial.read(); generatedChecksum = 255 - generatedChecksum; if(checksum == generatedChecksum) { Serial.println("checksum MATCH! parsing payload"); parsePayload(); state = STATE_WAIT_FOR_FIRST_A; }else{ Serial.println("checksum FAIL!"); state = STATE_WAIT_FOR_FIRST_A; } break; } }}As far as I can understand from theserialEvent() reference this function would be called only when a new byte is available. I've also added a condition to check ifSerial.available() > 0.
I can see the messages I expect when parsing the data, but only small packets(usually 4 bytes long) end up having a correct checksum and never receive a payload with the useful EEG data I'm looking for.
How can I check that my approach is correct or not/ I'm not loosing bytes usingserialEvent() instead of the blockingwhile(!Serial.available()) ? If so, how can I rewrite the while loop in a non blocking way ?
I've not super experienced with Arduino, but I started reading on interrupts. Would aUSART_RX interrupt help at all ? (or would it do the same as serialEvent -> trigger when a new byte is available?)
Update!Using Wirewrap's suggestion to use read() which returns -1 if there is no data, I've usedpeek() which does almost the same, except it doesn't remove the character peeked at from the buffer.For reference here is the code used:
#define BAUDRATE 57600#define DEBUGOUTPUT 1// checksum variablesbyte generatedChecksum = 0;byte checksum = 0; int payloadLength = 0;int payloadIndex;byte payloadData[169] = {0};byte poorQuality = 0;byte attention = 0;byte meditation = 0;// system variableslong lastReceivedPacket = 0;boolean bigPacket = false;int state = 0;const int STATE_WAIT_FOR_FIRST_A = 0;const int STATE_WAIT_FOR_SECOND_A = 1;const int STATE_WAIT_FOR_PAYLOAD_LENGTH = 2;const int STATE_WAIT_FOR_PAYLOAD = 3;const int STATE_WAIT_FOR_CHECKSUM = 4;void setup() { Serial.begin(BAUDRATE); // USB }void loop() {}void parsePayload(){ poorQuality = 200; attention = 0; meditation = 0; for(int i = 0; i < payloadLength; i++) { // Parse the payload switch (payloadData[i]) { case 2: i++; poorQuality = payloadData[i]; bigPacket = true; break; case 4: i++; attention = payloadData[i]; break; case 5: i++; meditation = payloadData[i]; break; case 0x80: i = i + 3; break; case 0x83: i = i + 25; break; default: break; } // switch } // for loop if(bigPacket) { Serial.print("PoorQuality: "); Serial.print(poorQuality, DEC); Serial.print(" Attention: "); Serial.print(attention, DEC); Serial.print(" Time since last packet: "); Serial.print(millis() - lastReceivedPacket, DEC); lastReceivedPacket = millis(); Serial.print("\n"); } bigPacket = false; }void serialEvent(){ if(Serial.peek() >= 0){ switch(state){ case STATE_WAIT_FOR_FIRST_A: if(Serial.read() == 170) state = STATE_WAIT_FOR_SECOND_A; break; case STATE_WAIT_FOR_SECOND_A: if(Serial.read() == 170) state = STATE_WAIT_FOR_PAYLOAD_LENGTH; break; case STATE_WAIT_FOR_PAYLOAD_LENGTH: payloadLength = Serial.read(); if(payloadLength > 169){ state = STATE_WAIT_FOR_FIRST_A; return; } generatedChecksum = payloadIndex = 0; state = STATE_WAIT_FOR_PAYLOAD; break; case STATE_WAIT_FOR_PAYLOAD: if(payloadIndex < payloadLength){ payloadData[payloadIndex] = Serial.read(); generatedChecksum += payloadData[payloadIndex]; payloadIndex++; }else{ state = STATE_WAIT_FOR_CHECKSUM; } break; case STATE_WAIT_FOR_CHECKSUM: checksum = Serial.read(); generatedChecksum = 255 - generatedChecksum; if(checksum == generatedChecksum) { parsePayload(); state = STATE_WAIT_FOR_FIRST_A; }else{ state = STATE_WAIT_FOR_FIRST_A; } break; } }}4 Answers4
Interestingly enough,Serial.read() does not work like most of us think.
Serial.read() is not blocking, it always returns immediately with a byte 0-255 or -1 if there is no character to read.
From Arduino Reference
Description
Reads incoming serial data. read() inherits from the Stream utility class.
Syntax
Serial.read()
Parameters
None
Returns
the first byte of incoming serial data available (or -1 if no data is available) - int
- Interesting indeed(+1). I will try to adapt the code without using available() and simply checking if
Serial.read() > -1. Is there a change of missing a byte this way ?George Profenza– George Profenza2015-03-29 21:11:16 +00:00CommentedMar 29, 2015 at 21:11 - Serial.read() reads from a 64 bytes buffer. If you can avoid filling up the buffer you will be okay.Wirewrap– Wirewrap2015-03-30 14:38:45 +00:00CommentedMar 30, 2015 at 14:38
- "Serial.read() is not blocking, it always returns immediately with a byte 0-255 or -1 if there is no character to read." unless it returns a multi-byte data type, it couldn't return 0-255 or -1 -> as -1 is 128. it likely returns 0-127 and -1 instead.dannyf– dannyf2017-11-17 12:28:41 +00:00CommentedNov 17, 2017 at 12:28
Nick Gammon, a moderator on the official Arduino site and a very active member of the Arduino community on Stackoverflow, has done a very nice post onreading serial without blocking. I've posted the relevant code. /* Example of processing incoming serial data without blocking.
Author: Nick GammonDate: 13 November 2011. Modified: 31 August 2013.Released for public use.*/// how much serial data we expect before a newlineconst unsigned int MAX_INPUT = 50;void setup (){Serial.begin (115200);} // end of setup// here to process incoming serial data after a terminator receivedvoid process_data (const char * data) { // for now just display it // (but you could compare it to some value, convert to an integer, etc.) Serial.println (data); } // end of process_datavoid processIncomingByte (const byte inByte){ static char input_line [MAX_INPUT]; static unsigned int input_pos = 0;switch (inByte) { case '\n': // end of text input_line [input_pos] = 0; // terminating null byte // terminator reached! process input_line here ... process_data (input_line); // reset buffer for next time input_pos = 0; break; case '\r': // discard carriage return break; default: // keep adding if not full ... allow for terminating null byte if (input_pos < (MAX_INPUT - 1)) input_line [input_pos++] = inByte; break; } // end of switch} // end of processIncomingByte void loop(){// if serial data available, process itwhile (Serial.available () > 0) processIncomingByte (Serial.read ());// do other stuff here like testing digital input (button presses) ...} // end of loopI don't likeserialEvent() becauseit only checks to see if it should run that function after the whole loop is executed. With your new example, it's basically the same as the original (except you moved that code to the very bottom of the loop).
I'm not sure exactly how fast the data is sent, but you should first ask yourself if the data is sent fast enough that you don't even notice that it's blocking. Blocking code isn't bad itself (except maybe from a standpoint of the other device freezing up and stalling your program), but blocking codethat takes a long time is bad.
Since you need five bytes, the best thing would be to wrap the blocking code in an if statement like this:
if(Serial.available >= 5) { //Blocking code here}It's simple and readable, yet effective.
- Thanks for the tips (+1). I didn't know about serialEvent being called once at the end of loop. Reading all the bytes in a full packet with a big payload takes between 900ms and 2500ms from what I can observe. Is there a chance of missing bytes using serialEvent instead of
while(!Serial.available())?George Profenza– George Profenza2015-03-29 21:09:56 +00:00CommentedMar 29, 2015 at 21:09 - @GeorgeProfenza it shouldn't remove any bytes unless it's a major bug.Anonymous Penguin– Anonymous Penguin2015-03-29 21:12:36 +00:00CommentedMar 29, 2015 at 21:12
Since then I've figured out how to rewrite the code to be non-blocking, by replacing the while loop with states, accumulating one byte at a time.
I've written my first Arduino library with this occasion:Mindwave.Here's a basic example:
#include <Mindwave.h> //import the libraryMindwave mindwave; //start using itvoid setup() { Serial.begin(MINDWAVE_BAUDRATE); //setup serial communication (MindWave mobile is set to 57600 baud rate)}//create a function to received new values as soon as they're avaialblevoid onMindwaveData(){ Serial.print("attention: "); Serial.println(mindwave.attention()); //access attention value}void loop() { mindwave.update(Serial,onMindwaveData);//update using the data input(Serial in this case) and the function to call when data is ready}It's non blocking, you can see the implementationhere
This was used for theWalk with Me art installation.



Explore related questions
See similar questions with these tags.


