A simple microwave demo

Dependencies:   C12832_lcd LM75B mbed-rtos mbed

rtos_hwk7.cpp

Committer:
joeroop
Date:
2014-04-04
Revision:
4:cd87e2050344
Parent:
3:4fb0721ce794

File content as of revision 4:cd87e2050344:

/*
Assignment: Hwk 7_1
Due:        3/18/14
Objective:
            Finish lab #7. The purpose of this program is to use the RTOS along with
            Interprocess comunication (IPC). In this program the IPC method utilizes
            a MemoryPool and a Queue for a messaging system. Anytime a new message comes
            through the LCD is updated. A union is used to reduce the size of the message
            data structure. The prgram simulates a microwave with a requirement of having
            the system shutdown within 1ms. There are no globals that are written to by the
            threads this reduces the need for mutexes. All communcation is done by signals
            and IPC in order to reduce global variables.
            
            Additions are LED4 light for door open and display the STATE (WAIT, COOK) on the lcd.        
            
Test Condition          Pass/Fail Criteria                          Result 
---------------------   ---------------------------------------     ---------
See attached sheet

*/


#include "mbed.h"
#include "rtos.h"
#include "C12832_lcd.h" //LCD interface
#include "LM75B.h"      //temperature interface

#ifndef DEBUG
    //#define DEBUG
#endif /* DEBUG */


#define DEBOUNCE_TIME_MS    10
#define MAX_COOK_TIME_S     20 //3min
#define MIN_COOK_TIME_S     1
#define INIT_COOK_TIME_S    5 
#define TEMP_INTERVAL_S     5
#define TEMP_INCREASE_F     10


//globals and types
#ifdef DEBUG
    Timer       test_timer; //used to instrument the code
#endif
DigitalOut  led1(LED1);
DigitalOut  led2(LED2);
DigitalOut  led3(LED3);
DigitalOut  led4(LED4);
C12832_LCD  lcd;
PwmOut      spkr(p26);
LM75B       therm(p28,p27);
Mutex       lcd_mutex;      //don't really need this but just to be safe
Timer       debounceTimer;
InterruptIn irptTimeDn(p16);    //cook down
InterruptIn irptTimeUp(p13);    //cook up
InterruptIn irptStart(p15);    //start cooking
InterruptIn irptStop(p12);   //stop cooking
InterruptIn irptDoor(p14);   //door interrupt
Thread      *proxy_lcd;     //pointers to the threads used for signalling
Thread      *proxy_temp;
Thread      *proxy_sound;
Thread      *proxy_state;
Thread      *proxy_led;
Thread      *proxy_timer;

typedef enum { //start at one as we are using this as signals
    //states
    WAITING = 1, //everything is ready for cooking, you can change the request time etc
    COOKING,
    DONE, //signal for threads to buzz, trurn off lights etc
    //interrupts signals
    TIMEUP, 
    TIMEDN, 
    START, 
    STOP,
    DOOR
} state_t; //this is for signaling of threads with proxies

char *stateStr[] = {"NULL","WAIT","COOK","DONE"};


typedef enum {TEMP_VAL, TIME_ELPS, TIME_RQST} data_t; //message data types

typedef struct {
    state_t state;
    data_t type;
    union {
        float temp; 
        int time_elapsed; //seconds of cooking
        int time_request; //seconds
    } data;   
} message_t; //used to pass messages to the lcd thread

MemoryPool<message_t,10> mpool; //used to hold all messages
Queue<message_t,10> queue;

bool debounce(void);
void blink_led(void const *args); //helper function to led thread
void send_time(void const *args); //helper function to timer thread

//Threads
void thread_state(void const *args);
void thread_lcd(void const *args);
void thread_temp(void const *args);
void thread_sound(void const *args);
void thread_led(void const *args);
void thread_timer(void const *args);

//ISRs
void isrTimeUp(void);
void isrTimeDn(void);
void isrStart(void);
void isrStop(void);
void isrDoor(void);

int main(void){
    debounceTimer.start();
    //interrupts debounce everything except for the door
    irptTimeUp.rise(&isrTimeUp);
    irptTimeDn.rise(&isrTimeDn);
    irptStart.rise(&isrStart);
    irptStop.rise(&isrStop);
    irptDoor.rise(&isrDoor); 
    
    //init the threads
    Thread t1(thread_state);    
    Thread t2(thread_temp);
    Thread t3(thread_sound);
    Thread t4(thread_lcd);
    Thread t5(thread_led);
    Thread t6(thread_timer);

    proxy_state     = &t1;
    proxy_temp      = &t2;
    proxy_sound     = &t3;
    proxy_lcd       = &t4;
    proxy_led       = &t5;
    proxy_timer     = &t6;
    
    t1.set_priority(osPriorityRealtime); //state machine most important thread
    proxy_state->signal_set(WAITING); //initialize all the threads
    
    while(1){
        Thread::wait(250);   
    }   
}

//function for a global debounce on the isrstick
bool debounce(void){
    if(debounceTimer.read_ms() > DEBOUNCE_TIME_MS){
        debounceTimer.reset();
        return true;
    }else{
        return false;   
    }
}
//ISRs
void isrTimeUp(void){
    if(debounce()){
        proxy_state->signal_set(TIMEUP); //left
    }
}
void isrTimeDn(void){
     if(debounce()){
        proxy_state->signal_set(TIMEDN); //right
    }
}
void isrStart(void){
    if(debounce()){
        proxy_state->signal_set(START); //up
    }
}
void isrStop(void){
    if(debounce()){
        proxy_state->signal_set(STOP); //down
    }
}
void isrDoor(void){
    //no debounce this is most important function!
#ifdef DEBUG
    test_timer.start();
#endif
    proxy_state->signal_set(DOOR);
}

/*
* State thread to do most of the main state machine logic this is where control happens
*/
void thread_state(void const *args){
    osEvent evt;
    int32_t mask;
    bool openDoor = false; //start with door closed
    state_t state = WAITING; //WAITING, COOKING, DONE
    while(1){
        evt = Thread::signal_wait(0);
        mask = evt.value.signals;
        switch(mask){
            case WAITING: //come from DONE to here
                state = WAITING;
                proxy_led->signal_set(state);
                proxy_timer->signal_set(state);
                proxy_temp->signal_set(state);
                break;
            case COOKING:
                if(!openDoor && state == WAITING){ //can't cook if door is open
                    state = COOKING;
                    proxy_led->signal_set(state);
                    proxy_timer->signal_set(state);
                    proxy_temp->signal_set(state);
                }
                break;
            case DONE: //timer can signal this
                state = DONE;
                //tell everyone we are done so they can buzz, dispaly and stop blinking
                proxy_led->signal_set(state);
                proxy_timer->signal_set(state);
                proxy_temp->signal_set(state);
#ifdef DEBUG
                //test_timer.stop(); // ~21 us to here from interrupt
#endif                    
                proxy_state->signal_set(WAITING); //goto WAITING
                break;
            case TIMEUP: //change the timer up
                if(state == WAITING) proxy_timer->signal_set(TIMEUP); 
                break;
            case TIMEDN: //change the timer down
                if(state == WAITING) proxy_timer->signal_set(TIMEDN);
                break;
            case START:
                proxy_state->signal_set(COOKING);
                break;
            case STOP:
                proxy_state->signal_set(DONE);
                break;
            case DOOR: //door changed state
                openDoor = !openDoor;
                state = (openDoor == true) ? DONE : WAITING; //open door then done else state back to waiting 
                led4 = openDoor; //visual for when door is open
                proxy_state->signal_set(state); //signal back to state thread
                break;
        }
    }
}
/*
* Helper function for the timer thread gets called at 1sec intervals
*/
void send_time(void const *args){
    int time = (*((int*)args))--; //have the time in seconds send to lcd now
    state_t state = COOKING;
    if(time == 0){ //time is up signal state machine to done
        state = DONE;
        proxy_state->signal_set(state); //tell threads cooking time up
        proxy_sound->signal_set(state); 
    }
    proxy_temp->signal_set(state); //use this timer to schedule temperature read
    
    message_t *msg = mpool.alloc(); //allocate a new message lcd is recipient
    msg->state = state;
    msg->type = TIME_ELPS;
    msg->data.time_elapsed = time;
    queue.put(msg);
}
/*
* Timer thread to keep track of all the time requirements for temp reading and elapsed time
*/
void thread_timer(void const *args){
    int time_set, time;
    time_set = INIT_COOK_TIME_S;
    int *ptime = &time;
    RtosTimer timer(send_time, osTimerPeriodic,(void*)ptime);
    osEvent evt;
    int32_t sig;
    state_t state;
    while(1){
        evt = Thread::signal_wait(0); //will time out then loop not needed
        sig = evt.value.signals;
        switch(sig){
            case WAITING:
                state = WAITING;
                break;
            case COOKING:
                state = COOKING;
                timer.stop(); //stop the timer
                time = time_set;
                send_time(ptime); //initialize the elpased time
                timer.start(1000); //this is the increments of the timer 1s using RtosTimer
                break;
            case DONE:
                state = DONE;
                time = INIT_COOK_TIME_S;
                //if(time > 0) time_set = time;
                //else time = INIT_COOK_TIME_S;
                timer.stop();
                break;
            case TIMEUP:
                time_set = (++time_set) > MAX_COOK_TIME_S ? MAX_COOK_TIME_S : time_set; //check limits 
                break;
            case TIMEDN:
                time_set = (--time_set) < MIN_COOK_TIME_S ? MIN_COOK_TIME_S : time_set; //check limits  
                break;
        }
        if(sig){
            message_t *msg = mpool.alloc(); //allocate a new message to the lcd
            if(msg != NULL){
                msg->state = state;
                msg->type = TIME_RQST;
                msg->data.time_request = time_set;
                queue.put(msg);
            }
        }
    }
}
/*
* LCD thread used to receive data from several other threads in the form of a message queue
*/
void thread_lcd(void const *args){
    osEvent evt;
    int time_set = 0; //used to calculate the elapsed time
    lcd.cls();
    while(1){
        evt = queue.get(osWaitForever); //wait for data message to post
        if(evt.status == osEventMessage){
            message_t *msg = (message_t*)evt.value.p; //get the message
            lcd_mutex.lock(); //dont really need this as this is the only thread that uses the lcd
            
            if(msg->state){
#ifdef DEBUG
                if(msg->state == DONE){
                    test_timer.stop(); //~75 us, if move print ahead it is 3145 us! printing takes a while
                    lcd.locate(70,20);
                    lcd.printf("%dus",test_timer.read_us());
                    test_timer.reset();
                }
#endif
                lcd.locate(70,0);
                lcd.printf("State: %s ",stateStr[msg->state]);
            }
            switch(msg->type){
                case TIME_RQST:
                    time_set = msg->data.time_request;
                    lcd.locate(0,0);
                    lcd.printf("Request: %3ds ",time_set); //requested time
                    break; 
                case TIME_ELPS:
                    lcd.locate(0,10);
                    lcd.printf("Elapsed: %3ds ",time_set - msg->data.time_elapsed); //elapesed time
                    break; 
                case TEMP_VAL:
                    lcd.locate(0,20);
                    lcd.printf("Temp: %-3.2fF  ",msg->data.temp); //current cooking temp updated at different intervals
                    break;                
            }
            lcd_mutex.unlock(); //free the mutex 
            mpool.free(msg); //free the message
        }
    }
}
/*
* Thread function to send the lcd the temp recieves timing from timer thread
*/
void thread_temp(void const *args){
    osEvent evt;
    int32_t sig;
    int seconds = -1;
    float temp = 0.0;
    while(1){
        evt = Thread::signal_wait(0);
        sig = evt.value.signals;
        switch(sig){
            case COOKING:
                if(!(seconds++ % TEMP_INTERVAL_S)){ //increase the temp if meets the interval
                    temp+= TEMP_INCREASE_F;
                }else{
                    sig = 0x0; //set to zero so don't print since no changes
                }
                break;
            case WAITING:
            case DONE:
                seconds = -1;
                temp = 0.0;
                break;
        }
        if(sig){
            message_t *msg = mpool.alloc(); //allocate a new message
            if(msg != NULL){
                msg->state = (state_t)0; //don't pass a value
                msg->type = TEMP_VAL;
                msg->data.temp = seconds > 0 ? temp+9.0/5.0*therm.read()+32.0 : 0.0;
                queue.put(msg);
            }
        }
    }
}
/*
* Simple sound thread using pwm to buzz when cooking timer is finished
*/
void thread_sound(void const *args){
    osEvent evt;
    int32_t sig;
    while(1){
        evt = Thread::signal_wait(0);
        sig = evt.value.signals;
        switch(sig){
            case DONE:
                spkr.period(1.0/5000);
                spkr=0.5;
                Thread::wait(1000);
                spkr=0.0;
                break;
        }
    }
}
/*
* Helper function to thread_led used to blink led
*/
void blink_led(void const *args){
    led1 = !led1;   
}
/*
* Thread led to blink the light when cooking and stop when done
*/
void thread_led(void const *args){
    RtosTimer timer(blink_led, osTimerPeriodic);
    osEvent evt;
    int32_t sig;
    while(1){
        evt = Thread::signal_wait(0); //will time out then loop not needed
        sig = evt.value.signals;
        switch(sig){
            case COOKING:
                timer.start(250);
                break;
            case DONE:
                timer.stop();
                led1 = 0;
                break;
        }
    }    
}