Gerrod Ubben / Mbed 2 deprecated ECE4180_Final_Project

Dependencies:   mbed mbed-rtos 4DGL-uLCD-SE RPCInterface

main.cpp

Committer:
Gerrod
Date:
2019-12-05
Revision:
16:d4b686118853
Parent:
15:5120c88a7a87

File content as of revision 16:d4b686118853:

#include "mbed.h"
#include "rtos.h"

#include "mbed_rpc.h"
#include "uLCD_4DGL.h"
#include <time.h>
#include <math.h>
#include "chime.h"

#include "algorithm.h"
#include "MAX30102.h"

/* Example RPC commands that have currently been implemented

/notify/run This_is_a_notification_message_that_should_be_displayed_on_the_lcd
/setTime/run <unix time> <UTC offset (-5 for Atlanta)>
/setTime/run 1256729737 -5

*/

enum displayApp{NOTIFICATION, TIME, HEARTBEAT}; //add to this enum for different things to display to screen

volatile bool display_notification = false;
volatile bool display_time = true;
volatile bool display_heartbeat = false;


volatile int utc_offset; //keeps track of the current timezone of the watch

uLCD_4DGL uLCD(p9,p10,p11); // serial tx, serial rx, reset pin;
//Serial bluetooth(p13,p14);
Serial pc(USBTX, USBRX);

InterruptIn view_button(p12);
DigitalIn INT(p26);  //pin P26 connects to the interrupt output pin of the MAX30102

Mutex stdio_mutex; //mutex used when accessing stdio functions
Mutex lcd_mutex; //mutex used when accessing the lcd object

Thread bluetooth_thread; //thread responsible for receiving rpc commands over bluetooth
Thread time_thread; //thread responsible for updating the lcd with the current time
Thread heartbeat_thread; //thread responsible for updating and displaying heartbeat

//rpc function prototypes
void display_notification_rpc_func(Arguments *in, Reply *out);
void set_time_rpc_func (Arguments *in, Reply *out);
void display_heartbeat_rpc_func(Arguments *in, Reply *out);
RPCFunction rpcHeartbeatLCD(&display_heartbeat_rpc_func, "heartbeat");
RPCFunction rpcWriteLCD(&display_notification_rpc_func, "notify");
RPCFunction rpcset_time_rpc_func(&set_time_rpc_func, "setTime");

#define sample_freq 16000.0
AnalogOut speaker(p18);
Ticker notification_chime_ticker;

//interrupt routine to play next audio sample from array in flash
void audio_sample () {
    static int i=0;
    speaker.write_u16(((uint16_t)data[i]<<7));
    i++;
    if (i>= sizeof(data)) {
        i = 0;
        notification_chime_ticker.detach();
    }
}
//this function is used to turn off displaying everything except the argument passed in
void display_x(enum displayApp i) {
    display_notification = false;
    display_heartbeat = false;
    display_time = false;
    switch(i) {
        case NOTIFICATION:
            display_notification = true;
        break;
        case TIME:
            display_time = true;
        break;
        case HEARTBEAT:
            display_heartbeat = true;
        break;
        default:
            display_time = true;
        break;
    }
    
}

//interrupt routine for when the input button is pressed
//when the view button is pressed, dismiss the currently shown notification and display the current time
void view_button_pressed(void){
    display_x(TIME);
}

//flip the y coordinate around so that standard cartesian coordinates can be used
int flipy(int y_coord){
 return (128-y_coord);
}

#define C_X 64
#define C_Y 64
#define M_PI 3.141592
//create the tick marks for an analog clock on the lcd display
void setup_analog_clock(uint32_t color){
     
     lcd_mutex.lock();
     uLCD.filled_circle(64, 64, 5, color); //centercircle
     double angle;
     //start from 3 oclock and draw all the clock tick marks counter-clockwise
     for(angle = 0; angle < (2*M_PI)-(M_PI/12); angle += M_PI/6){
         uLCD.line(54*cos(angle)+C_X,flipy(54*sin(angle)+C_Y), 64*cos(angle)+C_X,flipy(64*sin(angle)+C_Y), color); //3 oclock tick mark
     }
    lcd_mutex.unlock();
    
}

#define RAD_PER_SEC (2*M_PI)/60
#define RAD_PER_MIN (2*M_PI)/60
#define RAD_PER_HOUR (2*M_PI)/12
//function to handle the display of the time on the lcd screen
void show_time_analog(int sec, int minute, int hour, int day, int month, int year, uint32_t sec_color, uint32_t min_color, uint32_t hour_color, uint32_t back_color) {
    static double angle;
    static int prev_sec;
    static int prev_minute;
    static int prev_hour;
    
    lcd_mutex.lock();
    
    //tear down the previous hands that were drawn
    angle = -(RAD_PER_SEC*prev_sec) + M_PI/2;
    uLCD.line(C_X,C_Y,64*cos(angle)+C_X, flipy(64*sin(angle)+C_Y),back_color);
    
    angle = -(RAD_PER_MIN*prev_minute) + M_PI/2;
    uLCD.line(C_X,C_Y,52*cos(angle)+C_X, flipy(52*sin(angle)+C_Y),back_color);
    
    angle = -(RAD_PER_HOUR*prev_hour) + M_PI/2;
    uLCD.line(C_X,C_Y,40*cos(angle)+C_X, flipy(40*sin(angle)+C_Y),back_color);
    
    //draw the new hands
    angle = -(RAD_PER_SEC*sec) + M_PI/2;
    uLCD.line(C_X,C_Y,64*cos(angle)+C_X, flipy(64*sin(angle)+C_Y),sec_color);
    
    angle = -(RAD_PER_MIN*minute) + M_PI/2;
    uLCD.line(C_X,C_Y,52*cos(angle)+C_X, flipy(52*sin(angle)+C_Y),min_color);
    
    angle = -(RAD_PER_HOUR*hour) + M_PI/2;
    uLCD.line(C_X,C_Y,40*cos(angle)+C_X, flipy(40*sin(angle)+C_Y),hour_color);
    
    stdio_mutex.lock();
    
    //print the current date in a month/day/year format
    uLCD.locate(4,10);
    uLCD.printf("%2d/%2d/%4d",month, day, year);
    uLCD.locate(8,11);
    if (hour < 12) {uLCD.printf("AM");}
    else {uLCD.printf("PM");}
    
    stdio_mutex.unlock();
    lcd_mutex.unlock();
    
    //store the location of the current hands
    prev_sec = sec;
    prev_minute = minute;
    prev_hour = hour;
}

//function to update the time displayed on the lcd approximately every second
void time_thread_func() {
    struct tm * t; //time struct defined in time.h
    static time_t unix_time; //the time in unix time
    static bool prev_display_time; //indicates whether time was being displayed the last time the thread ran
    
    while (true) {
        if (display_time == true) {
            if (prev_display_time == false){ //clear whatever was previously on the screen
                lcd_mutex.lock();
                uLCD.cls();
                lcd_mutex.unlock(); 
            }
            unix_time = time(NULL);
            t = localtime(&unix_time);

            setup_analog_clock(WHITE);
            int hour = (t->tm_hour + utc_offset);
            if (hour < 0){ hour += 24;}
            else if (hour >= 24) {hour -= 24;}
            show_time_analog(t->tm_sec,t->tm_min,hour,t->tm_mday,t->tm_mon+1,t->tm_year+1900,RED+BLUE,WHITE,BLUE,BLACK);
        }
        prev_display_time = display_time;
        
        Thread::wait(1000); //only update every second
    }
    
}

//function to continuously take in characters over bluetooth serial and parse them as RPC commands
void bluetooth_thread_func() {
    char buf[256], outbuf[256];
    uint16_t buf_pos = 0;
    
    while(true) {
        
        if (pc.readable() == true) { //comment out when using bluetooth to receive rpc commands
        //if (bluetooth.readable() == true) {
            
            stdio_mutex.lock();
            
            buf[buf_pos] = pc.getc(); //comment out when using bluetooth to receive rpc commands
            //buf[buf_pos] = bluetooth.getc();
            
            stdio_mutex.unlock();
            
            if (buf[buf_pos] == '\n') { //the end of the RPC command has been received
                buf[buf_pos] = '\0'; //replace the newline character with a null character
                buf_pos = 0;
                RPC::call(buf, outbuf);  //make an RPC call  
                
                stdio_mutex.lock();
                pc.printf("%s\n", outbuf); //send the response
                stdio_mutex.unlock();
            }
            else {
                buf_pos++;
            }
            
        } else { 
            Thread::yield();
        }
        
    }
}

//RPC function to receive the current time
//the first argument is unix time and the second argument is the offset from UTC time
void set_time_rpc_func (Arguments *in, Reply *out)   {
    static const char * unix_time_str;
    uint32_t unix_time;
    int offset;
    unix_time_str = in->getArg<const char*>(); //get a pointer to the location where the argument string is stored
    offset = in->getArg<int>(); //get the second argument which indicates the offeset (in hours) from UTC time
    unix_time = atoll(unix_time_str);
    utc_offset = offset;
    
    set_time(unix_time);  // Set RTC time to Wed, 28 Oct 2009 11:35:37
    
}


//RPC function to receive notification strings
//note notification strings should not contain the character ' ', in lieu
//they should contain '_' to indicate spaces
void display_notification_rpc_func (Arguments *in, Reply *out)   {
    static char display_str[18];
    static const char * msg_str;
    int i,j;
    bool break_out = false;
    
    display_x(NOTIFICATION);
    
    msg_str = in->getArg<const char*>(); //get a pointer to the location where the argument string is stored
    
    stdio_mutex.lock();
    lcd_mutex.lock();
    
    uLCD.cls();
    uLCD.locate(0,0);
    i = 0;
    while(true){
        for(j=0; j<18; j++){
            if (msg_str[i+j] == '_'){
                display_str[j] = ' ';
            } else {
                display_str[j] = msg_str[i+j];
            }
            if (msg_str[i+j] == '\0') {break_out = true; break;}
        }
        i+= 18;
        uLCD.printf("%s\r\n",display_str);
        if (break_out){break;}
    }
    
    stdio_mutex.unlock();
    lcd_mutex.unlock();
    
    notification_chime_ticker.attach(&audio_sample, 1.0/sample_freq);
    
}

void display_heartbeat_rpc_func(Arguments *in, Reply *out){
    
    //check if heartbeat thread is sleeping
    Thread::State hbs = heartbeat_thread.get_state();
    if (hbs != Thread::Running && hbs != Thread::Ready && hbs != Thread::Inactive && hbs != Thread::Deleted){ //don't know which one wait state thread gets set to when it sleeps
        display_x(HEARTBEAT);
        heartbeat_thread.signal_set(0x1); //wake up heartbeat thread
    }//there is a funky scenario where you can hit the back button, but also send a heartbeat rpc command leading to undefined behavior
     //don't know which should take priority
     
}
void draw_heartbeat(int32_t hr, int8_t hr_valid, int32_t sp02, int8_t sp02_valid, int8_t cls) {
    //first time draw when heartbeat is called
    //text_string(char *s, char col, char row, char font, int color)
    stdio_mutex.lock(); //maybe i need this
    lcd_mutex.lock();
    if (cls) {
        uLCD.cls(); 
        uLCD.text_string("BPM:", 0, 1, 0, WHITE);
        uLCD.text_string("SPO2:", 0, 3, 0, WHITE);
    }
    int hrcolor = hr_valid ? GREEN : RED;
    int spcolor = sp02_valid ? GREEN : RED;
    char bpm[] = "   ";
    char sp[] = "   "; 
    sprintf(bpm, "%d", hr);
    sprintf(sp, "%d", sp);
    
    uLCD.text_string(bpm, 3, 1, 0, hrcolor);
    uLCD.text_string(sp, 3, 3, 0, spcolor);
    
    lcd_mutex.unlock();
    stdio_mutex.unlock();
}

void heartbeat_thread_func() {
    uint32_t aun_ir_buffer[200]; //IR LED sensor data
    int32_t n_ir_buffer_length = 0;    //data length
    uint32_t aun_red_buffer[200];    //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
    int i = 0;
    uint8_t dummy;
    maxim_max30102_reset(); //resets the MAX30102
    wait(1);
    maxim_max30102_read_reg(0,&dummy);
    maxim_max30102_init();  //initializes the MAX30102
    if(!display_heartbeat)
        Thread::signal_wait(0x1); //sleep until called on
    draw_heartbeat(0, 0, 0, 0, 1); //sets up the screen with default values
    while (true) {
        //Take 50 samples each half second, then check if we should relinquish control
        if (n_ir_buffer_length == 200) {
            //dump first 50 sets of samples, shift the last 150 to the top
            for (i = 50; i < 200; ++i) {
                aun_red_buffer[i-50]=aun_red_buffer[i];
                aun_ir_buffer[i-50]=aun_ir_buffer[i];   
            } 
            //take 50 sets of sample before calculating heart rate
            for (i = 150; i < 200; ++i) {
                while(INT.read()==1);
                maxim_max30102_read_fifo((aun_red_buffer+i), (aun_ir_buffer+i));   
            }
            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);   
        } else {
            for (i = n_ir_buffer_length; i < n_ir_buffer_length+50; ++i) {
                while(INT.read()==1);   //wait until the interrupt pin asserts
                maxim_max30102_read_fifo((aun_red_buffer+i), (aun_ir_buffer+i));  //read from MAX30102 FIFO   
            }  
            n_ir_buffer_length += 50;
            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);
        } 
        if (display_heartbeat) {
            draw_heartbeat(n_heart_rate, ch_hr_valid, n_sp02, ch_spo2_valid, 0); //draw heartbeat
        } else {
            Thread::signal_wait(0x1); //sleep till woken by rpc command
            draw_heartbeat(n_heart_rate, ch_hr_valid, n_sp02, ch_spo2_valid, 1);  //clear and setup screen upon return
        }     
    }       
}

int main() {
    
    uLCD.baudrate(3000000); //increase the lcd baud rate
    
    //configure the input button and attach an interrupt routine to it
    view_button.mode(PullUp);
    view_button.fall(&view_button_pressed);
    
    
    bluetooth_thread.start(bluetooth_thread_func); //start the thread that takes in characters to construct RPC commands
    time_thread.start(time_thread_func); //start the thread that updates the displayed time
    heartbeat_thread.start(heartbeat_thread_func); //start the thread that only runs when a heartbeat RPC command is called
    
}