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

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers main.cpp Source File

main.cpp

00001 /* Mindwave Mobile demo - Bob Stone June 2013
00002  * Visual demo of reading data packets from Neurosky's Mindwave Mobile headset
00003  * via BlueSMIRF Silver bluetooth modem and displaying a simple visualisation on
00004  * MikroElektronika's ProtoTFT screen.
00005  * Adapted from http://developer.neurosky.com/docs/doku.php?id=mindwave_mobile_and_arduino
00006  * Display library from Peter Drescher: http://mbed.org/cookbook/SPI-driven-QVGA-TFT
00007  *
00008  * Connect pin9 to BlueSMIRF's RX and pin10 to BlueSMIRF's TX,
00009  * also hook up GND to GND and VCC to the mbed's 3V3 supply.
00010  *
00011  * To prepare the BlueSMIRF to auto-connect to the Mindwave Mobile:
00012  *
00013  * First wire it up and power it using the mbed's power pins.  Once it's powered,
00014  * pair your computer to the Mindwave Mobile headset to you can
00015  * find out its MAC address.  Write that down as you will need it.
00016  *
00017  * Next pair your computer to the BlueSMIRF so we can configure it.  It will
00018  * appear as RN-42-5922 or similar (it's a Roving Networks RN-42 unit).
00019  *
00020  * Now we can use a terminal program to connect to the serial modem created on
00021  * your computer that connects wirelessly to the BlueSMIRF - by default it's at
00022  * 115200 baud, 8 N 1 - when you're connected the light will go green.
00023  *
00024  * If you've got a successful serial connection, put it into command mode by
00025  * typing three dollar signs $$$ - if successful you should see a prompt saying 
00026  * 'CMD'.  If not, power it down and try again till you get a CMD prompt.
00027  * 
00028  * At that prompt we need to change some defaults so that the BlueSMIRF is set to
00029  * master mode, 57600 baud and sends a pincode of '0000', and seeks to connect to
00030  * the headset's MAC address.  To do this, type:
00031  *  SP,0000
00032  *  SM,3
00033  *  SR,<the 12 digit MAC address of the headset written down earlier>
00034  *  SU,57.6
00035  *  D
00036  * You should see AOK after each line, and after the D it will print out its 
00037  * settings so you can check it's now AUTO, 57600, using 0000 and the right MAC
00038  * address.  All being well, type three minuses '---' to exit command mode.
00039  * 
00040  * To check it's working, close the terminal you've been using, reboot the 
00041  * BlueSMIRF (flashing red light) and switch on the Mindwave Mobile headset
00042  * - the light on the BlueSMIRF should go green when it connects and the 
00043  * flashing blue light on the Mindwave Mobile headset should go steady blue.
00044  */
00045 #include "mbed.h"
00046 #include "SPI_TFT.h"
00047 #include "Arial12x12.h"
00048 Serial blueSmirf(p9, p10);  //for bluetooth comms (TX, RX)
00049 SPI_TFT screen(p11, p12, p13, p14, p15, "TFT");
00050 int quality=0;
00051 
00052 //*****************************
00053 //User routines to process data
00054 //*****************************
00055 
00056 /** Maps a value from one scale to another
00057  * 
00058  * @param value Value we're trying to scale
00059  * @param min,max The range that value came from
00060  * @param newMin,newMax The new range we're scaling value into
00061  * @returns value mapped into new scale
00062  */
00063 int map(int value, int min, int max, int newMin, int newMax) {
00064     return newMin + (newMax-newMin) * (value-min) / (max-min);
00065 }
00066 
00067 /** Returns a 16-bit RGB565 colour from three 8-bit component values.
00068  * 
00069  * @param red,green,blue primary colour channel values expressed as 0-255 each
00070  * @returns 16-bit RGB565 colour constructed as RRRRRGGGGGGBBBBB
00071  */
00072 int RGBColour(int red, int green, int blue) {
00073     //take most-significant parts of red, green and blue and bit-shift into RGB565 positions
00074     return ((red & 0xf8) << 8) | ((green & 0xfc) << 3) | ((blue & 0xf8) >> 3);
00075 }
00076 
00077 /** Returns a colour mapped on a gradient from one colour to another.
00078  * 
00079  * @param value Value we're trying to pick a colour for
00080  * @param min,max Scale that value belongs in
00081  * @param minColour,maxColour start and end colours of the gradient we're choosing from (16-bit RGB565)
00082  * @returns colour that's as far along the gradient from minColour to maxColour as value is between min and max (16-bit RGB565)
00083  */
00084 int getMappedColour(int value, int min, int max, int minColour, int maxColour)
00085 {
00086     // TFT screen colours are 16-bit RGB565 i.e. RRRRRGGGGGGBBBBB 
00087     int minRed = (minColour & 0xf800) >> 11; //bitmask for 5 bits red
00088     int maxRed = (maxColour & 0xf800) >> 11;
00089     int minGreen = (minColour & 0x7e0) >> 5; //bitmask for 6 bits green
00090     int maxGreen = (maxColour & 0x7e0) >> 5;
00091     int minBlue = minColour & 0x1f; // bitmask for 5 bits blue
00092     int maxBlue = maxColour & 0x1f;
00093     int valRed = map(value, min, max, minRed, maxRed); 
00094     int valGreen = map(value, min, max, minGreen, maxGreen); 
00095     int valBlue = map(value, min, max, minBlue, maxBlue); 
00096     int valColour = ((valRed & 0x1F) << 11) | ((valGreen & 0x3F) << 5) | (valBlue & 0x1F);
00097     return valColour;
00098 }
00099 
00100 /** 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'.
00101  *
00102  * @param x0,y0 coordinates of the 'min' end of the bargraph
00103  * @param x1,y1 coordinates of the 'max' end of the bargraph
00104  * @param isHorizontal If true, bar graph will be drawn with horizontal bars
00105  * @param value Value of the bar, with bars drawn from min up to value, remaining 'backColour' from there to max
00106  * @param min,max Scale of the bar graph that value should be found within
00107  * @param minColour,maxColour colours at the min and max ends of the bar, drawn in a gradient between the two (16-bit RGB565)
00108  * @param backColour background colour of the bar graph (16-bit RGB565)
00109  */
00110 void displayBarGraph(int x0, int y0, int x1, int y1, bool isHorizontal, int value, int min, int max, int minColour, int maxColour, int backColour)
00111 {
00112     int valColour;    
00113     if (isHorizontal) {
00114         if (x1>x0)
00115         {
00116             for (int i = x0; i < x1; i+=5)
00117             {
00118                 if (map(i, x0, x1, min, max) > value) 
00119                     valColour = backColour;
00120                 else
00121                     valColour = getMappedColour(i, x0, x1, minColour, maxColour);
00122                 screen.fillrect(i, y0, i+3, y1, valColour);
00123             }
00124         } else {
00125             for (int i = x1; i < x0; i+=5)
00126             {
00127                 if (map(i, x0, x1, min, max) > value) 
00128                     valColour = backColour;
00129                 else
00130                     valColour = getMappedColour(i, x0, x1, minColour, maxColour);
00131                 screen.fillrect(i-3, y0, i, y1, valColour);
00132             }
00133         }        
00134     } else {
00135         if (y1>y0)
00136         {
00137             for (int i = y0; i < y1; i+=5)
00138             {
00139                 if (map(i, y0, y1, min, max) > value) 
00140                     valColour = backColour;
00141                 else
00142                     valColour = getMappedColour(i, y0, y1, minColour, maxColour);
00143                 screen.fillrect(x0, i, x1, i+3, valColour);
00144             }
00145         } else {
00146             for (int i = y1; i < y0; i+=5)
00147             {
00148                 if (map(i, y0, y1, min, max) > value) 
00149                     valColour = backColour;
00150                 else
00151                     valColour = getMappedColour(i, y0, y1, minColour, maxColour);
00152                 screen.fillrect(x0, i-3, x1, i, valColour);
00153             }
00154         }
00155     }   
00156 }
00157 
00158 /** This will be called if you blink.
00159  */
00160 void blinked(void)
00161 {
00162     if (quality == 0) {
00163         screen.locate(0,0);
00164         printf("Blink!");
00165     }
00166 }
00167 
00168 /** This will be called when processed eSense data comes in, about once a second.
00169  *
00170  * @param poorQuality will be 0 if connections are good, 200 if connections are useless, and somewhere in between if connection dodgy.
00171  * @param attention processed percentage denoting focus and attention.  0 to 100
00172  * @param meditation processed percentage denoting calmness and serenity.  0 to 100
00173  * @param timeSinceLastPacket time since last packet processed, in milliseconds.
00174  */
00175 void eSenseData(int poorQuality, int attention, int meditation, int timeSinceLastPacket)
00176 {
00177     quality=poorQuality;
00178     if (poorQuality == 200) 
00179         screen.fillrect(313, 3, 317, 7, Red);
00180     else if (poorQuality == 0) 
00181         screen.fillrect(313, 3, 317, 7, Green);
00182     else 
00183         screen.fillrect(313, 3, 317, 7, Yellow);
00184     
00185     if (attention > 0) {
00186         displayBarGraph(50, 210, 75, 30, false, attention, 0, 100, RGBColour(0x10,0x00,0x00), RGBColour(0xFF,0x00,0x00), 0x00);
00187         screen.locate(50, 225);
00188         screen.foreground(Red);
00189         screen.printf("%d ",attention);
00190         screen.foreground(White);    // set chars to white
00191     }
00192     if (meditation > 0) {
00193         displayBarGraph(85, 210, 110, 30, false, meditation, 0, 100, RGBColour(0x00,0x10,0x00), RGBColour(0x00,0xFF,0x00), 0x00);
00194         screen.locate(85, 225);
00195         screen.foreground(Green);
00196         screen.printf("%d ",meditation);
00197         screen.foreground(White);    // set chars to white
00198     }
00199 }
00200 
00201 /** This will be called when processed meter reading data arrives, about once a second.
00202  * This is a breakdown of frequencies in the wave data into 8 named bands, these are:
00203  *   0: Delta        (0.5-2.75 Hz)
00204  *   1: Theta        (3.5-6.75 Hz)
00205  *   2: Low-Alpha    (7.5-9.25 Hz)
00206  *   3: High-Alpha   (10-11.75 Hz)
00207  *   4: Low-Beta     (13-16.75 Hz)
00208  *   5: High-Beta    (18-29.75 Hz)
00209  *   6: Low-Gamma    (31-39.75 Hz)
00210  *   7: High-Gamma   (41-49.75 Hz)
00211  * 
00212  * @param meter array of meter data for different frequency bands
00213  * @param meterMin array of minimum recorded samples of each band
00214  * @param meterMax arrat if naximum recorded samples of each band
00215  */
00216 void meterData(int meter[8], int meterMin[8], int meterMax[8])
00217 {
00218     for (int j=0; j<8; j++) {
00219         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);
00220     }
00221     screen.locate(0,0);
00222     printf("        ");
00223 }
00224 
00225 /** This will be called when wave data arrives.
00226  * There will be a lot of these, 512 a second, so if you're planning to do anything 
00227  * here, don't let it take long.  Best not to printf this out as it will just choke.
00228  *
00229  * param wave Raw wave data point
00230  */
00231 void waveData(int wave)
00232 {
00233 }
00234 
00235 //*****************
00236 //End User routines
00237 //*****************
00238 
00239 //System routines to obtain and parse data
00240 
00241 /** Simplify serial comms
00242  */
00243 unsigned char ReadOneByte()
00244 {
00245     int ByteRead;
00246 
00247     while(!blueSmirf.readable());
00248     ByteRead = blueSmirf.getc();
00249 
00250     return ByteRead;
00251 }
00252 
00253 /** Main loop, sets up and keeps listening for serial
00254  */
00255 int main()
00256 {
00257     //Video setup
00258     screen.claim(stdout);        // send stdout to the TFT display
00259     screen.background(Black);    // set background to black
00260     screen.foreground(White);    // set chars to white
00261     screen.cls();                // clear the screen
00262     screen.set_font((unsigned char*) Arial12x12);
00263     screen.set_orientation(1);
00264 
00265     screen.locate(50, 213);
00266     screen.foreground(Red);
00267     screen.printf("Att");
00268     screen.locate(85, 213);
00269     screen.foreground(Green);
00270     screen.printf("Med");
00271     for (int j = 0; j < 8; j++) 
00272     {
00273         screen.locate(160 + j * 15, 213);
00274         screen.foreground(RGBColour(0, j*32, 0xFF));
00275         screen.printf("%d", j);
00276     }
00277     screen.foreground(White);    // set chars to white
00278     
00279     Timer t; //packet timer
00280     t.start();
00281     Timer blinkTimer; //used for detecting blinks
00282     int time;
00283     int generatedChecksum = 0;
00284     int checksum = 0;
00285     int payloadLength = 0;
00286     int payloadData[64] = {0};
00287     int poorQuality = 0;
00288     int attention = 0;
00289     int meditation = 0;
00290     int wave = 0;
00291     int meter[8] = {0};
00292     int meterMin[8];
00293     int meterMax[8];
00294     for (int j = 0; j < 8; j++) 
00295     {
00296         meterMin[j]=99999999;
00297         meterMax[j]=-99999999;
00298     }
00299     bool eSensePacket = false;
00300     bool meterPacket = false;
00301     bool wavePacket = false;
00302 
00303     blueSmirf.baud(57600);
00304     blinkTimer.reset();
00305 
00306     while(1) {
00307         // Look for sync bytes
00308         if(ReadOneByte() == 170) {
00309             if(ReadOneByte() == 170) {
00310                 //Synchronised to start of packet
00311                 payloadLength = ReadOneByte();
00312                 if(payloadLength > 169) //Payload length can not be greater than 169
00313                     return;
00314 
00315                 generatedChecksum = 0;
00316                 for(int i = 0; i < payloadLength; i++) {
00317                     payloadData[i] = ReadOneByte();            //Read payload into memory
00318                     generatedChecksum += payloadData[i];
00319                 }
00320 
00321                 checksum = ReadOneByte();                      //Read checksum byte from stream
00322                 generatedChecksum = 255 - (generatedChecksum & 0xFF);   //Take one's compliment of generated checksum
00323 
00324                 if(checksum == generatedChecksum) {
00325                     //Packet seems OK
00326                     poorQuality = 200;
00327                     attention = 0;
00328                     meditation = 0;
00329                     wave = 0;
00330                     for(int i = 0; i < payloadLength; i++) {    // Parse the payload
00331                         switch (payloadData[i]) {
00332                             case 2: //quality
00333                                 i++;
00334                                 poorQuality = payloadData[i];
00335                                 eSensePacket = true;
00336                                 break;
00337                             case 4: //attention
00338                                 i++;
00339                                 attention = payloadData[i];
00340                                 eSensePacket = true;
00341                                 break;
00342                             case 5: //meditation
00343                                 i++;
00344                                 meditation = payloadData[i];
00345                                 eSensePacket = true;
00346                                 break;
00347                             case 0x80: //wave
00348                                 wave = payloadData[i+2] * 256 + payloadData[i+3];
00349                                 //We also want to try to detect blinks via analysing wave data
00350                                 time = blinkTimer.read_ms();
00351                                 if (wave > 32767) wave -= 65535; //cope with negatives
00352                                 if (wave>200 && time == 0) {
00353                                     blinkTimer.start();
00354                                 } else if (wave<-90 && time > 10 && time < 350) {
00355                                     blinkTimer.stop();
00356                                     blinkTimer.reset();
00357                                     blinked();
00358                                 } else if (time>500) {
00359                                     blinkTimer.stop();
00360                                     blinkTimer.reset();
00361                                 }
00362                                 i = i + 3;
00363                                 wavePacket = true;
00364                                 break;
00365                             case 0x83: //meter readings for different frequency bands
00366                                 for (int j=0; j<8; j++) {
00367                                     //documentation is inconsistent about whether these values are big-endian or little-endian,
00368                                     //and claims both in different places.  But wave data is big-endian so assuming that here.
00369                                     meter[j] = payloadData[i+j*3+2]*65536 + payloadData[i+j*3+3]*256 + payloadData[i+j*3+4];
00370                                     if (meter[j]<meterMin[j])
00371                                         meterMin[j]=meter[j];
00372                                     if (meter[j]>meterMax[j])
00373                                         meterMax[j]=meter[j];
00374                                 }
00375                                 meterPacket = true;
00376                                 i = i + 25;
00377                                 break;
00378                             default:
00379                                 break;
00380                         } // switch
00381                     } // for loop
00382 
00383                     //Call routines to process data
00384                     if(eSensePacket) {
00385                         eSenseData(poorQuality, attention, meditation, t.read_ms());
00386                         eSensePacket = false;
00387                     }
00388                     if (meterPacket) {
00389                         meterData(meter, meterMin, meterMax);
00390                         t.reset();
00391                         meterPacket=false;
00392                     }
00393                     if (wavePacket) {
00394                         waveData(wave);
00395                         wavePacket=false;
00396                     }
00397                 } else {
00398                     // Checksum Error
00399                 }  // end if else for checksum
00400             } // end if read 0xAA byte
00401         } // end if read 0xAA byte
00402     }
00403 }