Personal Fitness Wearable Prototype with MBED
ECE 4180 Final Design Project By: Krishna Peri, Kartik Sastry, and Robert Walsh
Introduction
Based on our group's shared interest in wellness, we set out to create a prototype of a fitness wearable. We chose to include an IMU for counting squats, GPS for speed and altitude, a heart rate sensor, a temperature sensor, and an LCD screen. This early prototype is intended to be adapted into a smaller, more portable package and worn on the users chest during workouts.
Schedule of Tasks
- Decided on I/O devices to use
- Tested individual parts
- Devised wiring and prepared code
- Wired, programmed, and tested device
- Preparation for demo and presentation
Wiring
uLCD | mbed |
---|---|
5V | VU |
GND | GND |
TX | p10 |
RX | p9 |
RESET | p30 |
GPS | mbed |
---|---|
VIN | VU |
GND | GND |
TX | p14 |
RX | p13 |
HR Sensor | mbed |
---|---|
Vin | Vout |
GND | GND |
INT | p20 |
SDA | p28 |
SCL | p27 |
IMU (ADXL335) | mbed |
---|---|
Vin | VU |
GND | GND |
X | p15 |
Y | p16 |
Z | p17 |
Pushbutton (PinDetect) to p7
Temperature Sensor to Analog In p19
Code
main.cpp
/* Code for ECE 4180-A Final Design Project */ /* Kartik Sastry, Krishna Peri, Robert Walsh */ /* mbed Based Fitness Wearable Prototype */ /** * Acknowledgements: * * The core of the GPS and Heart Rate code used in this project was supplied * as demonstration code by the manufacturer. The drivers written for the Heart * Rate Monitor were altered by Kartik Sastry in order to allow the device * to function with the mbed LPC1768 specifically. * * The uLCD, Temperature, Accelerometer, Control, and data processing are all * our original work. The Temperature feature uses a class for the TMP36 written * by Prof. James Hamblen, our ECE 4180 professor. */ /******************************************************************************/ /* Devices Used, Wiring, Hardware Info */ /******************************************************************************/ /* - mbed LPC1768 Microcontroller 5V, (2A) External Power Supply or 4.5 V, (3 AA Batteries) Power Debugging and Additional Feedback over USB Virtual COM Port - Heart Rate Sensor / Pulse Oximeter - Maxim Integrated MAXREFDES117# (HR) I2C p27, p28 DigitalOut p20 - SparkFun Triple Axis Accelerometer Breakout - ADXL335 (IMU) AnalogIn p15, p16, p17 (X, Y, Z axes respectively) - Adafruit Ultimate GPS Breakout V3 (GPS) Serial p13, p14 - 4D Systems 4DGL-uLCD LCD Display (LCD) Serial p9, p10 DigitalOut p8 - Pushbutton (Wire one switch pole to p7, the other directly to ground. No need for external pullup resistor.) PinDetect p7 - Adafruit TMP36 Temperature Sensor AnalogIn p19 */ /******************************************************************************/ /* Libraries and Include Files */ /******************************************************************************/ #include "mbed.h" //#include "rtos.h" //#include "SDFileSystem.h" #include "uLCD_4DGL.h" //#include "LSM9DS1.h" #include "MBed_Adafruit_GPS.h" // #include "XNucleo53L0A1.h" #include "math.h" #include <stdio.h> #include "PinDetect.h" #include "algorithm.h" #include "MAX30102.h" #include "TMP36.h" /******************************************************************************/ /* I/O Object Declarations */ /******************************************************************************/ PinDetect myPushbutton(p7); // For Mode Selection Feature Serial pc(USBTX, USBRX); // Interface to PC over virtual COM uLCD_4DGL uLCD(p9, p10, p8); // uLCD AnalogIn Xval(p15); // IMU: Output of X-axis at analog p15 AnalogIn Yval(p16); // IMU: Output of y-axis at analog p16 AnalogIn Zval(p17); // IMU: Output of z-axis at analog p17 TMP36 myTMP36(p19); // Analog in // GPS // #define VL53L0_I2C_SDA p28 // LIDAR // #define VL53L0_I2C_SCL p27 // I2C sensor pins for LIDAR // DigitalOut shdn(p26); // This VL53L0X board test application performs a range measurement in polling mode // // Use 3.3(Vout) for Vin, p9 for SDA, p10 for SCL, P26 for shdn on mbed LPC1768 /******************************************************************************/ /* Global Variables (Carefully Managed) */ /******************************************************************************/ // // Globals For LIDAR // static XNucleo53L0A1 *board = NULL; // int status; // uint32_t distance; // Globals For GPS Serial * gps_Serial; // Globals for Heart Rate Sensor #define MAX_BRIGHTNESS 255 uint32_t aun_ir_buffer[500]; //IR LED sensor data int32_t n_ir_buffer_length; //data length uint32_t aun_red_buffer[500]; //Red LED sensor data int32_t n_sp02; //SPO2 value int8_t ch_spo2_valid; //indicator to show if the SP02 calculation is valid int32_t n_heart_rate; //heart rate value int8_t ch_hr_valid; //indicator to show if the heart rate calculation is valid uint8_t uch_dummy; // Serial pc(USBTX, USBRX); //initializes the serial port // #ifdef TARGET_KL25Z // PwmOut led(PTB18); //initializes the pwm output that connects to the on board LED // DigitalIn myINT(PTD1); //pin PTD1 connects to the interrupt output pin of the MAX30102 // #endif // #ifdef TARGET_K64F // DigitalIn myINT(PTD1); //pin PTD1 connects to the interrupt output pin of the MAX30102 // #endif // #ifdef TARGET_MAX32600MBED PwmOut led(LED1); // initializes the pwm output that connects to the on board LED DigitalIn myINT(p20); // pin p20 connects to the interrupt output pin of the MAX30102 // #endif /******************************************************************************/ /* Device Selection / Thread Control */ /******************************************************************************/ // Add MODE_LIDAR_SELECT if we can get LIDAR to work enum DATA_ACQ_MODE {MODE_IMU_SELECT, MODE_GPS_SELECT, MODE_TEMP_SELECT, MODE_HR_SELECT}; volatile int myMode = MODE_IMU_SELECT; // To be changed by pushbutton presses // Short ISR - serviced when interrupt given by myPushbutton hit void changeMode_ISR(void) { myMode = (myMode + 1) % 4; // mod 4 makes it periodic (0,1,2,3,0) } /******************************************************************************/ /* Function Prototypes of Threads */ /******************************************************************************/ void IMU_THREAD(); void GPS_THREAD(); void TEMP_THREAD(); void HR_THREAD(); // void LIDAR_THREAD(); /******************************************************************************/ /* Main Thread: Initialization */ /******************************************************************************/ int main() { // // Set up LIDAR // pc.printf("\rSetting Up LIDAR...\n"); // DevI2C *device_i2c = new DevI2C(VL53L0_I2C_SDA, VL53L0_I2C_SCL); // LIDAR Objects: // board = XNucleo53L0A1::instance(device_i2c, A2, D8, D2); // creates the 53L0A1 expansion board singleton obj // shdn = 0; // must reset sensor for an mbed reset to work // wait(0.1); // shdn = 1; // wait(0.1); // status = board->init_board(); // init the 53L0A1 board with default values // while (status) { // pc.printf("\r(LIDAR) Failed to init board! \r\n"); // status = board->init_board(); // } // pc.printf("\rSet Up LIDAR.\n"); // Set up uLCD pc.printf("\rSetting Up uLCD...\n"); uLCD.baudrate(31250); pc.printf("\rSet Up uLCD.\n"); uLCD.printf("Welcome!"); // // Set up GPS // pc.printf("\rSetting Up GPS...\n"); // myGPS.begin(9600); // sets baud rate for GPS communication; note this may be changed via Adafruit_GPS::sendCommand(char *) // // a list of GPS commands is available at http://www.adafruit.com/datasheets/PMTK_A08.pdf // myGPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA); // these commands are defined in MBed_Adafruit_GPS.h; a link is provided there for command creation // myGPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ); // myGPS.sendCommand(PGCMD_ANTENNA); // pc.printf("\r(GPS) Connection established at 9600 baud...\n"); // wait(1); // refresh_Timer.start(); // starts the clock on the timer // pc.printf("\rSet Up GPS.\n"); // Set up Mode Selecting Pushbutton (Debounced, Interrupt Based) pc.printf("\rSetting Up Mode Changing PB...\n"); myPushbutton.mode(PullUp); // Use internal pullups for pushbutton wait(.01); // Delay for initial pullup to take effect myPushbutton.attach_deasserted(&changeMode_ISR); // Setup Interrupt Service Routines. PullUp implies 1->0 change means hit myPushbutton.setSampleFrequency(); // Start sampling pushbutton inputs using interruptsUsing default 50 Hz (20 ms period) pc.printf("\rSet Up Mode Changing PB.\n"); /******************************************************************************/ /* Main Thread: Devices */ /******************************************************************************/ while(true) { pc.printf("Entered Main Loop\n"); // Print Current Mode on Top uLCD.text_width(1); // normal size text uLCD.text_height(1); uLCD.background_color(BLACK); uLCD.color(GREEN); uLCD.locate(0, 0); uLCD.printf(" "); uLCD.locate(0, 0); uLCD.printf("Mode: %d", myMode); pc.printf("\rMode: %d", myMode); // Depending on Mode: switch (myMode) { case MODE_IMU_SELECT: // Print Current Mode on Top uLCD.text_width(1); // normal size text uLCD.text_height(1); uLCD.background_color(BLACK); uLCD.color(GREEN); uLCD.locate(0, 0); uLCD.printf(" "); uLCD.locate(0, 0); uLCD.printf("Mode: %d", myMode); pc.printf("\rMode: %d", myMode); // Create a RED Outline of the screen uLCD.background_color(RED); uLCD.cls(); uLCD.filled_rectangle(4, 4, 124, 124, BLACK); while (myMode == MODE_IMU_SELECT) { IMU_THREAD(); } break; case MODE_GPS_SELECT: // Print Current Mode on Top uLCD.text_width(1); // normal size text uLCD.text_height(1); uLCD.background_color(BLACK); uLCD.color(GREEN); uLCD.locate(0, 0); uLCD.printf(" "); uLCD.locate(0, 0); uLCD.printf("Mode: %d", myMode); pc.printf("\rMode: %d", myMode); // Create a BLUE Outline of the screen uLCD.background_color(BLUE); uLCD.cls(); uLCD.filled_rectangle(4, 4, 124, 124, BLACK); while (myMode == MODE_GPS_SELECT) { GPS_THREAD(); } break; case MODE_TEMP_SELECT: // Print Current Mode on Top uLCD.text_width(1); // normal size text uLCD.text_height(1); uLCD.background_color(BLACK); uLCD.color(GREEN); uLCD.locate(0, 0); uLCD.printf(" "); uLCD.locate(0, 0); uLCD.printf("Mode: %d", myMode); pc.printf("\rMode: %d", myMode); // Create a GREEN Outline of the screen uLCD.background_color(GREEN); uLCD.cls(); uLCD.filled_rectangle(4, 4, 124, 124, BLACK); while (myMode == MODE_TEMP_SELECT) { TEMP_THREAD(); } break; case MODE_HR_SELECT: // Print Current Mode on Top uLCD.text_width(1); // normal size text uLCD.text_height(1); uLCD.background_color(BLACK); uLCD.color(GREEN); uLCD.locate(0, 0); uLCD.printf(" "); uLCD.locate(0, 0); uLCD.printf("Mode: %d", myMode); pc.printf("\rMode: %d", myMode); // Create a WHITE Outline of the screen uLCD.background_color(WHITE); uLCD.cls(); uLCD.filled_rectangle(4, 4, 124, 124, BLACK); // Graphics Boilerplate uLCD.text_width(2); // 2X size text uLCD.text_height(2); uLCD.color(WHITE); uLCD.locate(0,0); uLCD.printf("HEARTRATE"); while (myMode == MODE_HR_SELECT) { HR_THREAD(); } break; // case MODE_LIDAR_SELECT: // // Print Current Mode on Top // uLCD.text_width(1); // normal size text // uLCD.text_height(1); // uLCD.background_color(BLACK); // uLCD.color(GREEN); // uLCD.locate(0, 0); // uLCD.printf(" "); // uLCD.locate(0, 0); // uLCD.printf("Mode: %d", myMode); // pc.printf("\rMode: %d", myMode); // // Create a LGREY Outline of the screen // uLCD.background_color(LGREY); // uLCD.cls(); // uLCD.filled_rectangle(4, 4, 124, 124, BLACK); // // Graphics Boilerplate // uLCD.text_width(2); // 2X size text // uLCD.text_height(2); // uLCD.color(LGREY); // uLCD.locate(0,0); // uLCD.printf("PUSH-UPS"); // while (myMode == MODE_LIDAR_SELECT) { // LIDAR_THREAD(); // } // break; default: // Print Current Mode on Top uLCD.text_width(1); // normal size text uLCD.text_height(1); uLCD.background_color(BLACK); uLCD.color(GREEN); uLCD.locate(0, 0); uLCD.printf(" "); uLCD.locate(0, 0); uLCD.printf("Mode: %d", myMode); pc.printf("\rMode: %d", myMode); uLCD.cls(); uLCD.printf("INVALID MODE."); } } } // END OF MAIN!!!! /******************************************************************************/ /* Thread 1: Heart Rate Monitoring */ /******************************************************************************/ void HR_THREAD() { uint32_t un_min, un_max, un_prev_data; //variables to calculate the on-board LED brightness that reflects the heartbeats int i; int32_t n_brightness; float f_temp; int32_t myOldHeartRate; maxim_max30102_reset(); //resets the MAX30102 // // initialize serial communication at 115200 bits per second: // pc.baud(9600); // pc.format(8,SerialBase::None,1); // wait(1); //read and clear status register maxim_max30102_read_reg(0,&uch_dummy); // //wait until the user presses a key // while(pc.readable()==0) // { // pc.printf("\x1B[2J"); //clear terminal program screen // pc.printf("Press any key to start conversion\n\r"); // wait(1); // } // uch_dummy=getchar(); maxim_max30102_init(); //initializes the MAX30102 // pc.printf("\rInitialization Complete - HR\n"); n_brightness=0; un_min=0x3FFFF; un_max=0; n_ir_buffer_length=500; //buffer length of 100 stores 5 seconds of samples running at 100sps //read the first 500 samples, and determine the signal range for(i=0;i<n_ir_buffer_length;i++) { while(myINT.read()==1); //wait until the interrupt pin asserts maxim_max30102_read_fifo((aun_red_buffer+i), (aun_ir_buffer+i)); //read from MAX30102 FIFO if(un_min>aun_red_buffer[i]) un_min=aun_red_buffer[i]; //update signal min if(un_max<aun_red_buffer[i]) un_max=aun_red_buffer[i]; //update signal max // pc.printf("red="); // pc.printf("%i", aun_red_buffer[i]); // pc.printf(", ir="); // pc.printf("%i\n\r", aun_ir_buffer[i]); } un_prev_data=aun_red_buffer[i]; //calculate heart rate and SpO2 after first 500 samples (first 5 seconds of samples) maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid); //Continuously taking samples from MAX30102. Heart rate and SpO2 are calculated every 1 second while (myMode == MODE_HR_SELECT) { i=0; un_min=0x3FFFF; un_max=0; //dumping the first 100 sets of samples in the memory and shift the last 400 sets of samples to the top for(i=100;i<500;i++) { aun_red_buffer[i-100]=aun_red_buffer[i]; aun_ir_buffer[i-100]=aun_ir_buffer[i]; //update the signal min and max if(un_min>aun_red_buffer[i]) un_min=aun_red_buffer[i]; if(un_max<aun_red_buffer[i]) un_max=aun_red_buffer[i]; } //take 100 sets of samples before calculating the heart rate. for(i=400;i<500;i++) { un_prev_data=aun_red_buffer[i-1]; while(myINT.read()==1); maxim_max30102_read_fifo((aun_red_buffer+i), (aun_ir_buffer+i)); if(aun_red_buffer[i]>un_prev_data) { f_temp=aun_red_buffer[i]-un_prev_data; f_temp/=(un_max-un_min); f_temp*=MAX_BRIGHTNESS; n_brightness-=(int)f_temp; if(n_brightness<0) n_brightness=0; } else { f_temp=un_prev_data-aun_red_buffer[i]; f_temp/=(un_max-un_min); f_temp*=MAX_BRIGHTNESS; n_brightness+=(int)f_temp; if(n_brightness>MAX_BRIGHTNESS) n_brightness=MAX_BRIGHTNESS; } //#if defined(TARGET_KL25Z) || defined(TARGET_MAX32600MBED) led.write(1-(float)n_brightness/256); //#endif //send samples and calculation result to terminal program through UART // pc.printf("red="); // pc.printf("%i", aun_red_buffer[i]); // pc.printf(", ir="); // pc.printf("%i", aun_ir_buffer[i]); // pc.printf(", HR=%i, ", n_heart_rate); // pc.printf("HRvalid=%i, ", ch_hr_valid); if (ch_hr_valid == 1) { myOldHeartRate = n_heart_rate; if ((myOldHeartRate >= 50) && (myOldHeartRate <= 200)) { // Print Out on LCD uLCD.text_width(2); // normal size text uLCD.text_height(2); uLCD.locate(1,3); uLCD.printf("HR:\n\n %i", myOldHeartRate); } else { uLCD.text_width(2); // normal size text uLCD.text_height(2); uLCD.locate(1,3); uLCD.printf("HR:\n\n --"); } } // pc.printf("SpO2=%i, ", n_sp02); // pc.printf("SPO2Valid=%i\n\r", ch_spo2_valid); } maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid); } } /******************************************************************************/ /* Thread 2: IMU Measurement */ /******************************************************************************/ void IMU_THREAD() { float x,y,z; // Raw data float xG, yG, zG; // IN G bool start = 0; int count = 0; while (myMode == MODE_IMU_SELECT) { // Graphics Boilerplate uLCD.text_width(3); // 3X size text uLCD.text_height(3); uLCD.color(RED); uLCD.locate(0,0); uLCD.printf("IMU"); // Get Values x = Xval.read(); // Reads X-axis value between 0 and 1 y = Yval.read(); // Reads Y-axis value z = Zval.read(); // Reads Z-axis value xG = (x * 6.6) - 3.3; // Scaling into G's yG = (y * 6.6) - 3.3; zG = (z * 6.6) - 3.3; pc.printf("\r%f, %f, %f\n", xG, yG, zG); if (zG > 0.7){ start = 1; } if (start==1 & zG < 0.5) { count+=1; start = 0; } // Print Out on LCD uLCD.text_width(2); // normal size text uLCD.text_height(2); uLCD.locate(1,3); uLCD.printf("Squats:\n\n %d", count); wait(.25); } } /******************************************************************************/ /* Thread 3: GPS Measurement */ /******************************************************************************/ void GPS_THREAD() { // Graphics Boilerplate uLCD.text_width(3); // 3X size text uLCD.text_height(3); uLCD.color(BLUE); uLCD.locate(0,0); uLCD.printf("GPS"); uLCD.text_width(1); // normal size text uLCD.text_height(1); uLCD.locate(1,3); uLCD.printf("GPS Data:\n"); pc.baud(9600); //sets virtual COM serial communication to high rate; this is to allow more time to be spent on GPS retrieval gps_Serial = new Serial(p13,p14); //serial object for use w/ GPS Adafruit_GPS myGPS(gps_Serial); //object of Adafruit's GPS class char c; //when read via Adafruit_GPS::read(), the class returns single character stored here Timer refresh_Timer; //sets up a timer for use in loop; how often do we print GPS info? const int refresh_Time = 2000; //refresh time in ms myGPS.begin(9600); //sets baud rate for GPS communication; note this may be changed via Adafruit_GPS::sendCommand(char *) //a list of GPS commands is available at http://www.adafruit.com/datasheets/PMTK_A08.pdf myGPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA); //these commands are defined in MBed_Adafruit_GPS.h; a link is provided there for command creation myGPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ); myGPS.sendCommand(PGCMD_ANTENNA); pc.printf("Connection established at 9600 baud...\n"); wait(1); refresh_Timer.start(); //starts the clock on the timer while (myMode == MODE_GPS_SELECT) { c = myGPS.read(); //queries the GPS //if (c) { pc.printf("%c", c); } //this line will echo the GPS data if not paused //check if we recieved a new message from GPS, if so, attempt to parse it, if ( myGPS.newNMEAreceived() ) { if ( !myGPS.parse(myGPS.lastNMEA()) ) { continue; } } //check if enough time has passed to warrant printing GPS info to screen //note if refresh_Time is too low or pc.baud is too low, GPS data may be lost during printing if (refresh_Timer.read_ms() >= refresh_Time) { refresh_Timer.reset(); uLCD.locate(1, 4); pc.printf("\rGPS SAYS:\n\r"); pc.printf("\rTime: %d:%d:%d.%u\n\r", myGPS.hour, myGPS.minute, myGPS.seconds, myGPS.milliseconds); uLCD.printf("\rTime: %d:%d:%d.%u\n\r", myGPS.hour, myGPS.minute, myGPS.seconds, myGPS.milliseconds); pc.printf("\rDate: %d/%d/20%d\n\r", myGPS.day, myGPS.month, myGPS.year); uLCD.printf("\rDate: %d/%d/20%d\n\r", myGPS.day, myGPS.month, myGPS.year); pc.printf("\rFix: %d\n\r", (int) myGPS.fix); uLCD.printf("\rFix: %d\n\r", (int) myGPS.fix); pc.printf("\rQuality: %d\n\r", (int) myGPS.fixquality); if (myGPS.fix) { pc.printf("\rLocation: %5.2f%c, %5.2f%c\n\r", myGPS.latitude, myGPS.lat, myGPS.longitude, myGPS.lon); pc.printf("\rLocation: %5.2f%c, %5.2f%c\n\r", myGPS.latitude, myGPS.lat, myGPS.longitude, myGPS.lon); pc.printf("\rSpeed: %5.2f knots\n\r", myGPS.speed); uLCD.printf("\rSpeed: %5.2f mph\n\r", myGPS.speed * 1.15078); // CONVERT pc.printf("\rAngle: %5.2f\n", myGPS.angle); pc.printf("\rAltitude: %5.2f\n", myGPS.altitude); uLCD.printf("\rAltitude: %5.2f\n", myGPS.altitude); pc.printf("\rSatellites: %d\n\r", myGPS.satellites); } } } } // /******************************************************************************/ // /* Thread 4: LIDAR Measurements */ // /******************************************************************************/ // void LIDAR_THREAD() { // // loop taking and printing distance // while (myMode == MODE_LIDAR_SELECT) { // status = board->sensor_centre->get_distance(&distance); // if (status == VL53L0X_ERROR_NONE) { // pc.printf("\rLIDAR SAYS:\n\r"); // pc.printf("\rD=%ld mm\r\n", distance); // } // } // } /******************************************************************************/ /* Thread 5: Temperature Measurements */ /******************************************************************************/ void TEMP_THREAD() { float myCurrentTemp; while (myMode == MODE_TEMP_SELECT) { // Graphics Boilerplate uLCD.text_width(3); // 3X size text uLCD.text_height(3); uLCD.color(GREEN); uLCD.locate(0,0); uLCD.printf("TEMP"); // Get Temperature myCurrentTemp = myTMP36.read(); // Floating Value // Print Out on LCD uLCD.text_width(2); // normal size text uLCD.text_height(2); uLCD.locate(1,3); uLCD.printf("Temp:\n\n %0.1f C", myCurrentTemp); wait(1); } }
Import library4180Final
Code for ECE 4180 Final Design Project
Photos/Examples
Please log in to post comments.