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 a sample 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 variables
byte generatedChecksum = 0;
byte checksum = 0;
int payloadLength = 0;
byte payloadData[64] = {
0};
byte poorQuality = 0;
byte attention = 0;
byte meditation = 0;
// system variables
long 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 variables
byte generatedChecksum = 0;
byte checksum = 0;
int payloadLength = 0;
byte payloadData[169] = {0};
byte poorQuality = 0;
byte attention = 0;
byte meditation = 0;
// system variables
long 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 the serialEvent() reference this function would be called only when a new byte is available. I've also added a condition to check if Serial.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 using serialEvent()
instead of the blocking while(!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 a USART_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 used peek() 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 variables
byte generatedChecksum = 0;
byte checksum = 0;
int payloadLength = 0;
int payloadIndex;
byte payloadData[169] = {0};
byte poorQuality = 0;
byte attention = 0;
byte meditation = 0;
// system variables
long 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;
}
}
}