6

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;    }  }}
askedMar 28, 2015 at 21:52
George Profenza's user avatar
0

4 Answers4

7

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

answeredMar 29, 2015 at 20:14
Wirewrap's user avatar
3
  • Interesting indeed(+1). I will try to adapt the code without using available() and simply checking ifSerial.read() > -1. Is there a change of missing a byte this way ?CommentedMar 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.CommentedMar 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.CommentedNov 17, 2017 at 12:28
3

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 loop
answeredMar 31, 2016 at 6:38
Jonathan's user avatar
0
2

I 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.

answeredMar 29, 2015 at 19:59
Anonymous Penguin's user avatar
2
  • 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 ofwhile(!Serial.available()) ?CommentedMar 29, 2015 at 21:09
  • @GeorgeProfenza it shouldn't remove any bytes unless it's a major bug.CommentedMar 29, 2015 at 21:12
2

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.

Walk with Me installation image 1

Walk with Me installation image 2

Bluetooth and DMX Shield for the Walk with Me installation

answeredMay 29, 2015 at 21:25
George Profenza's user avatar

Your Answer

Sign up orlog in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

By clicking “Post Your Answer”, you agree to ourterms of service and acknowledge you have read ourprivacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.