Bar-graph visualisation demo of reading data from Neurosky's Mindwave Mobile headset, reading via BlueSMIRF Silver bluetooth modem and displaying via MikroElektronika's TFT Proto 320x240 touchscreen.

Dependencies:   SPI_TFT TFT_fonts mbed

/media/uploads/RorschachUK/_scaled_p6040001.jpg

Uses MikroElektronika's low-cost TFT Proto 2.8" 320x240 touchscreen to display a bar graph representation of data coming in from NeuroSky's Mindwave Mobile brainwave-reading headset. Graphs the two main values coming in - Attention and Meditation, as well as the 8 individual frequency bands the headset processes. Also implements rudimentary blink-detection (top left) and quality of connection indicator (top right).

For a simpler program which doesn't use the screen and sends data to serial-debug, or for instructions on preparing the BlueSMIRF unit to connect to the Mindwave Mobile headset, visit:

Import programMindwave-basic

Basic serial-debug demo of reading and parsing data from Neurosky's Mindwave Mobile EEG headset via BlueSMIRF Silver bluetooth modem.

For info on how to connect the screen, see Peter Drescher's library page.

Revision:
0:b36d13636379
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Tue Jun 04 17:55:00 2013 +0000
@@ -0,0 +1,403 @@
+/* Mindwave Mobile demo - Bob Stone June 2013
+ * Visual demo of reading data packets from Neurosky's Mindwave Mobile headset
+ * via BlueSMIRF Silver bluetooth modem and displaying a simple visualisation on
+ * MikroElektronika's ProtoTFT screen.
+ * Adapted from http://developer.neurosky.com/docs/doku.php?id=mindwave_mobile_and_arduino
+ * Display library from Peter Drescher: http://mbed.org/cookbook/SPI-driven-QVGA-TFT
+ *
+ * Connect pin9 to BlueSMIRF's RX and pin10 to BlueSMIRF's TX,
+ * also hook up GND to GND and VCC to the mbed's 3V3 supply.
+ *
+ * To prepare the BlueSMIRF to auto-connect to the Mindwave Mobile:
+ *
+ * First wire it up and power it using the mbed's power pins.  Once it's powered,
+ * pair your computer to the Mindwave Mobile headset to you can
+ * find out its MAC address.  Write that down as you will need it.
+ *
+ * Next pair your computer to the BlueSMIRF so we can configure it.  It will
+ * appear as RN-42-5922 or similar (it's a Roving Networks RN-42 unit).
+ *
+ * Now we can use a terminal program to connect to the serial modem created on
+ * your computer that connects wirelessly to the BlueSMIRF - by default it's at
+ * 115200 baud, 8 N 1 - when you're connected the light will go green.
+ *
+ * If you've got a successful serial connection, put it into command mode by
+ * typing three dollar signs $$$ - if successful you should see a prompt saying 
+ * 'CMD'.  If not, power it down and try again till you get a CMD prompt.
+ * 
+ * At that prompt we need to change some defaults so that the BlueSMIRF is set to
+ * master mode, 57600 baud and sends a pincode of '0000', and seeks to connect to
+ * the headset's MAC address.  To do this, type:
+ *  SP,0000
+ *  SM,3
+ *  SR,<the 12 digit MAC address of the headset written down earlier>
+ *  SU,57.6
+ *  D
+ * You should see AOK after each line, and after the D it will print out its 
+ * settings so you can check it's now AUTO, 57600, using 0000 and the right MAC
+ * address.  All being well, type three minuses '---' to exit command mode.
+ * 
+ * To check it's working, close the terminal you've been using, reboot the 
+ * BlueSMIRF (flashing red light) and switch on the Mindwave Mobile headset
+ * - the light on the BlueSMIRF should go green when it connects and the 
+ * flashing blue light on the Mindwave Mobile headset should go steady blue.
+ */
+#include "mbed.h"
+#include "SPI_TFT.h"
+#include "Arial12x12.h"
+Serial blueSmirf(p9, p10);  //for bluetooth comms (TX, RX)
+SPI_TFT screen(p11, p12, p13, p14, p15, "TFT");
+int quality=0;
+
+//*****************************
+//User routines to process data
+//*****************************
+
+/** Maps a value from one scale to another
+ * 
+ * @param value Value we're trying to scale
+ * @param min,max The range that value came from
+ * @param newMin,newMax The new range we're scaling value into
+ * @returns value mapped into new scale
+ */
+int map(int value, int min, int max, int newMin, int newMax) {
+    return newMin + (newMax-newMin) * (value-min) / (max-min);
+}
+
+/** Returns a 16-bit RGB565 colour from three 8-bit component values.
+ * 
+ * @param red,green,blue primary colour channel values expressed as 0-255 each
+ * @returns 16-bit RGB565 colour constructed as RRRRRGGGGGGBBBBB
+ */
+int RGBColour(int red, int green, int blue) {
+    //take most-significant parts of red, green and blue and bit-shift into RGB565 positions
+    return ((red & 0xf8) << 8) | ((green & 0xfc) << 3) | ((blue & 0xf8) >> 3);
+}
+
+/** Returns a colour mapped on a gradient from one colour to another.
+ * 
+ * @param value Value we're trying to pick a colour for
+ * @param min,max Scale that value belongs in
+ * @param minColour,maxColour start and end colours of the gradient we're choosing from (16-bit RGB565)
+ * @returns colour that's as far along the gradient from minColour to maxColour as value is between min and max (16-bit RGB565)
+ */
+int getMappedColour(int value, int min, int max, int minColour, int maxColour)
+{
+    // TFT screen colours are 16-bit RGB565 i.e. RRRRRGGGGGGBBBBB 
+    int minRed = (minColour & 0xf800) >> 11; //bitmask for 5 bits red
+    int maxRed = (maxColour & 0xf800) >> 11;
+    int minGreen = (minColour & 0x7e0) >> 5; //bitmask for 6 bits green
+    int maxGreen = (maxColour & 0x7e0) >> 5;
+    int minBlue = minColour & 0x1f; // bitmask for 5 bits blue
+    int maxBlue = maxColour & 0x1f;
+    int valRed = map(value, min, max, minRed, maxRed); 
+    int valGreen = map(value, min, max, minGreen, maxGreen); 
+    int valBlue = map(value, min, max, minBlue, maxBlue); 
+    int valColour = ((valRed & 0x1F) << 11) | ((valGreen & 0x3F) << 5) | (valBlue & 0x1F);
+    return valColour;
+}
+
+/** Displays a bar graph showing 'value' on a scale 'min' to 'max', where coords (x0,y0) are at 'min' and (x1,y1) are at 'max'.
+ *
+ * @param x0,y0 coordinates of the 'min' end of the bargraph
+ * @param x1,y1 coordinates of the 'max' end of the bargraph
+ * @param isHorizontal If true, bar graph will be drawn with horizontal bars
+ * @param value Value of the bar, with bars drawn from min up to value, remaining 'backColour' from there to max
+ * @param min,max Scale of the bar graph that value should be found within
+ * @param minColour,maxColour colours at the min and max ends of the bar, drawn in a gradient between the two (16-bit RGB565)
+ * @param backColour background colour of the bar graph (16-bit RGB565)
+ */
+void displayBarGraph(int x0, int y0, int x1, int y1, bool isHorizontal, int value, int min, int max, int minColour, int maxColour, int backColour)
+{
+    int valColour;    
+    if (isHorizontal) {
+        if (x1>x0)
+        {
+            for (int i = x0; i < x1; i+=5)
+            {
+                if (map(i, x0, x1, min, max) > value) 
+                    valColour = backColour;
+                else
+                    valColour = getMappedColour(i, x0, x1, minColour, maxColour);
+                screen.fillrect(i, y0, i+3, y1, valColour);
+            }
+        } else {
+            for (int i = x1; i < x0; i+=5)
+            {
+                if (map(i, x0, x1, min, max) > value) 
+                    valColour = backColour;
+                else
+                    valColour = getMappedColour(i, x0, x1, minColour, maxColour);
+                screen.fillrect(i-3, y0, i, y1, valColour);
+            }
+        }        
+    } else {
+        if (y1>y0)
+        {
+            for (int i = y0; i < y1; i+=5)
+            {
+                if (map(i, y0, y1, min, max) > value) 
+                    valColour = backColour;
+                else
+                    valColour = getMappedColour(i, y0, y1, minColour, maxColour);
+                screen.fillrect(x0, i, x1, i+3, valColour);
+            }
+        } else {
+            for (int i = y1; i < y0; i+=5)
+            {
+                if (map(i, y0, y1, min, max) > value) 
+                    valColour = backColour;
+                else
+                    valColour = getMappedColour(i, y0, y1, minColour, maxColour);
+                screen.fillrect(x0, i-3, x1, i, valColour);
+            }
+        }
+    }   
+}
+
+/** This will be called if you blink.
+ */
+void blinked(void)
+{
+    if (quality == 0) {
+        screen.locate(0,0);
+        printf("Blink!");
+    }
+}
+
+/** This will be called when processed eSense data comes in, about once a second.
+ *
+ * @param poorQuality will be 0 if connections are good, 200 if connections are useless, and somewhere in between if connection dodgy.
+ * @param attention processed percentage denoting focus and attention.  0 to 100
+ * @param meditation processed percentage denoting calmness and serenity.  0 to 100
+ * @param timeSinceLastPacket time since last packet processed, in milliseconds.
+ */
+void eSenseData(int poorQuality, int attention, int meditation, int timeSinceLastPacket)
+{
+    quality=poorQuality;
+    if (poorQuality == 200) 
+        screen.fillrect(313, 3, 317, 7, Red);
+    else if (poorQuality == 0) 
+        screen.fillrect(313, 3, 317, 7, Green);
+    else 
+        screen.fillrect(313, 3, 317, 7, Yellow);
+    
+    if (attention > 0) {
+        displayBarGraph(50, 210, 75, 30, false, attention, 0, 100, RGBColour(0x10,0x00,0x00), RGBColour(0xFF,0x00,0x00), 0x00);
+        screen.locate(50, 225);
+        screen.foreground(Red);
+        screen.printf("%d ",attention);
+        screen.foreground(White);    // set chars to white
+    }
+    if (meditation > 0) {
+        displayBarGraph(85, 210, 110, 30, false, meditation, 0, 100, RGBColour(0x00,0x10,0x00), RGBColour(0x00,0xFF,0x00), 0x00);
+        screen.locate(85, 225);
+        screen.foreground(Green);
+        screen.printf("%d ",meditation);
+        screen.foreground(White);    // set chars to white
+    }
+}
+
+/** This will be called when processed meter reading data arrives, about once a second.
+ * This is a breakdown of frequencies in the wave data into 8 named bands, these are:
+ *   0: Delta        (0.5-2.75 Hz)
+ *   1: Theta        (3.5-6.75 Hz)
+ *   2: Low-Alpha    (7.5-9.25 Hz)
+ *   3: High-Alpha   (10-11.75 Hz)
+ *   4: Low-Beta     (13-16.75 Hz)
+ *   5: High-Beta    (18-29.75 Hz)
+ *   6: Low-Gamma    (31-39.75 Hz)
+ *   7: High-Gamma   (41-49.75 Hz)
+ * 
+ * @param meter array of meter data for different frequency bands
+ * @param meterMin array of minimum recorded samples of each band
+ * @param meterMax arrat if naximum recorded samples of each band
+ */
+void meterData(int meter[8], int meterMin[8], int meterMax[8])
+{
+    for (int j=0; j<8; j++) {
+        displayBarGraph(160 + j * 15, 210, 170 + j * 15, 100, false, meter[j], meterMin[j], meterMax[j], RGBColour(0, j*2, 0x10), RGBColour(0, j*32, 0xFF), 0x00);
+    }
+    screen.locate(0,0);
+    printf("        ");
+}
+
+/** This will be called when wave data arrives.
+ * There will be a lot of these, 512 a second, so if you're planning to do anything 
+ * here, don't let it take long.  Best not to printf this out as it will just choke.
+ *
+ * param wave Raw wave data point
+ */
+void waveData(int wave)
+{
+}
+
+//*****************
+//End User routines
+//*****************
+
+//System routines to obtain and parse data
+
+/** Simplify serial comms
+ */
+unsigned char ReadOneByte()
+{
+    int ByteRead;
+
+    while(!blueSmirf.readable());
+    ByteRead = blueSmirf.getc();
+
+    return ByteRead;
+}
+
+/** Main loop, sets up and keeps listening for serial
+ */
+int main()
+{
+    //Video setup
+    screen.claim(stdout);        // send stdout to the TFT display
+    screen.background(Black);    // set background to black
+    screen.foreground(White);    // set chars to white
+    screen.cls();                // clear the screen
+    screen.set_font((unsigned char*) Arial12x12);
+    screen.set_orientation(1);
+
+    screen.locate(50, 213);
+    screen.foreground(Red);
+    screen.printf("Att");
+    screen.locate(85, 213);
+    screen.foreground(Green);
+    screen.printf("Med");
+    for (int j = 0; j < 8; j++) 
+    {
+        screen.locate(160 + j * 15, 213);
+        screen.foreground(RGBColour(0, j*32, 0xFF));
+        screen.printf("%d", j);
+    }
+    screen.foreground(White);    // set chars to white
+    
+    Timer t; //packet timer
+    t.start();
+    Timer blinkTimer; //used for detecting blinks
+    int time;
+    int generatedChecksum = 0;
+    int checksum = 0;
+    int payloadLength = 0;
+    int payloadData[64] = {0};
+    int poorQuality = 0;
+    int attention = 0;
+    int meditation = 0;
+    int wave = 0;
+    int meter[8] = {0};
+    int meterMin[8];
+    int meterMax[8];
+    for (int j = 0; j < 8; j++) 
+    {
+        meterMin[j]=99999999;
+        meterMax[j]=-99999999;
+    }
+    bool eSensePacket = false;
+    bool meterPacket = false;
+    bool wavePacket = false;
+
+    blueSmirf.baud(57600);
+    blinkTimer.reset();
+
+    while(1) {
+        // Look for sync bytes
+        if(ReadOneByte() == 170) {
+            if(ReadOneByte() == 170) {
+                //Synchronised to start of packet
+                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 & 0xFF);   //Take one's compliment of generated checksum
+
+                if(checksum == generatedChecksum) {
+                    //Packet seems OK
+                    poorQuality = 200;
+                    attention = 0;
+                    meditation = 0;
+                    wave = 0;
+                    for(int i = 0; i < payloadLength; i++) {    // Parse the payload
+                        switch (payloadData[i]) {
+                            case 2: //quality
+                                i++;
+                                poorQuality = payloadData[i];
+                                eSensePacket = true;
+                                break;
+                            case 4: //attention
+                                i++;
+                                attention = payloadData[i];
+                                eSensePacket = true;
+                                break;
+                            case 5: //meditation
+                                i++;
+                                meditation = payloadData[i];
+                                eSensePacket = true;
+                                break;
+                            case 0x80: //wave
+                                wave = payloadData[i+2] * 256 + payloadData[i+3];
+                                //We also want to try to detect blinks via analysing wave data
+                                time = blinkTimer.read_ms();
+                                if (wave > 32767) wave -= 65535; //cope with negatives
+                                if (wave>200 && time == 0) {
+                                    blinkTimer.start();
+                                } else if (wave<-90 && time > 10 && time < 350) {
+                                    blinkTimer.stop();
+                                    blinkTimer.reset();
+                                    blinked();
+                                } else if (time>500) {
+                                    blinkTimer.stop();
+                                    blinkTimer.reset();
+                                }
+                                i = i + 3;
+                                wavePacket = true;
+                                break;
+                            case 0x83: //meter readings for different frequency bands
+                                for (int j=0; j<8; j++) {
+                                    //documentation is inconsistent about whether these values are big-endian or little-endian,
+                                    //and claims both in different places.  But wave data is big-endian so assuming that here.
+                                    meter[j] = payloadData[i+j*3+2]*65536 + payloadData[i+j*3+3]*256 + payloadData[i+j*3+4];
+                                    if (meter[j]<meterMin[j])
+                                        meterMin[j]=meter[j];
+                                    if (meter[j]>meterMax[j])
+                                        meterMax[j]=meter[j];
+                                }
+                                meterPacket = true;
+                                i = i + 25;
+                                break;
+                            default:
+                                break;
+                        } // switch
+                    } // for loop
+
+                    //Call routines to process data
+                    if(eSensePacket) {
+                        eSenseData(poorQuality, attention, meditation, t.read_ms());
+                        eSensePacket = false;
+                    }
+                    if (meterPacket) {
+                        meterData(meter, meterMin, meterMax);
+                        t.reset();
+                        meterPacket=false;
+                    }
+                    if (wavePacket) {
+                        waveData(wave);
+                        wavePacket=false;
+                    }
+                } else {
+                    // Checksum Error
+                }  // end if else for checksum
+            } // end if read 0xAA byte
+        } // end if read 0xAA byte
+    }
+}
\ No newline at end of file