homework 7

Dependencies:   mbed-rtos mbed C12832_lcd LM75B

main.cpp

Committer:
gatedClock
Date:
2013-09-13
Revision:
126:70439be77e75
Parent:
125:6b3a86e3c883

File content as of revision 126:70439be77e75:

/*----------------------------------------------//------------------------------
    student   : m-moore
    class     : rtos
    directory : RTOS_HW_07
    file      : main.cpp
----description---------------------------------//------------------------------
    gotchyas
    1. using pc.printf inside a ticker routine will freeze the routine.
    2. using queues (get, put) will not work within a ticker routine.
    3. Ticker has/had a bug.  http://mbed.org/questions/1563/Mbed-Tickerfunction-hangs-system-from-re/
   
    global variables considerations (mutex)
    the global variables associated with joystick button presses are 
    in the structure 'tButtons' (instance 'giButtons').  ISRs store that a
    button has been pressed in this struct, and since mutex can not be called
    within an interrupt, no mutex is used on these buttons.
    only the ISRs are 'authorized' to set those variables, and the bottom-half
    of the ISRs, 'threadButtonStateManager', is authorized to clear them once
    'threadButtonStateManager' has forwarded the fact that the buttons were
    pressed.  I don't expect there to be any collision of writing the variables.
    
    some mutex's are in use, but I'm running out of time, --
    
    tested use cases.
    
    1.
    * user sets maximum time.
    * user hits  button.
    * LCD shows total time, remaining time, and calculated temperature.
    * LEDs blink for magnetron and carousel.
    * time runs out.
    * beeper sounds for 3 seconds.
    * idle state, total time is reset.
    
    2. 
    * user sets maximum time.
    * user hits start button.
    * user opens door.
    * -> magnetron/carousel off.
    * -> countdown freezes.
    * -> temperature freezes.
    * user hits start button -> nothing happens.
    * user closes door
    * user hits start button
    * -> remaining-time countdown, temperature resume.
    * -> magnetron/carousel on.
    * etc, ending as above.
    
    3. 
    * user sets maximum time.
    * user hits start button.
    * user tries to adjust maximum time -> not allowed.
    * not allowed until 'idle' state.
    
    4. 
    * user sets maximum time.
    * user hits start button.
    * user hits stop button during (running, pause)
    * -> magnetron off, temperature live, remaining time zero
    * -> i.e. jump directly to idle state.
    
    5. 
    * user does not set maximum time.
    * user hits 'start'.
    * nothing happens.
    
    6. 
    * user opens door.
    * user sets time.
    * user hits 'start'.
    * -> set time still there,
    * -> but cook cycle not started.
    * user closes door.
    * user hits 'start'.
    * operation continues as normal.
    
    I just saw some bounce, increased debounce time from 16 to 32 mS.
    
    
    tour of software
    * ISRs register button presses into global structure.
    * debounce mechanism.
    * ISRs share common bottom-half thread, which clears the 
      button-press structure values, and forwards mail to other
      threads of just-pressed button status.
    * total time control thread handles setting total time, and
      will suppress total time changes or clear total time based
      on mail from the FSM thread.
    * FSM thread defines/executes cook states:
      IDLE, COOK, PAUSE, CONTINUE, DONE(beep)
      states are based on current state plus button-press changes
      plus countdown done.
    * temperature thread reads physical temperature, or calculates
      synthetic temperature at 1/6 degree increase per second, 
      upon commands from the FSM.
    * LCD thread, idle priority and moderate wait time.
      it will update display only upon a display value change
      in order to reduce 'blinkieness'.
      
      some globals could be moved into mail, such as data sent
      to the LCD.
    
    magnetron timing
    * LED2 is used for magnetron control (non-blinking).
      - front-end control is used to turn it off:
      - it is turned off in the 'stop' button ISR for safety.
      - it is turned off in the door-toggle ISR for safety.
      - this ensures immediate magnetron off.
      - the FSM will also turn it on/off according to the cook-state,
        and the control is the first statement in all states.
      - the FSM's switch statement default also turns off the
        magnetron for safety.
    
    other timing
    * the FSM thread has (if I counted right) six message get/puts worst
      case in one loop, all with 1mS timeout, and the loop has a 1mS wait,
      so it would be running on the order of once each 10mS.
      this is the highest priority thread.
      
    countdown timer needs to be sync'd to start button press.
    this could not be done, use granularity setting instead.
    I made the countdown timer operate at a high speed, and divided-out the
    output to 1s resolution.  This because I wanted to provide the appearance
    of resetting the phase to zero upon 'start' button press, rather than
    having the countdown change at an arbitrary moment within one second
    of the 'start' press.
    
    I had a half-baked idea of adding some type of background thread to 
    verify consistency of states and turning off the mangetron if it found
    anything suspicious.  Haven't thought this through.
    
    blinkers: I've added greater granularity into magetron enunciator (led0)
    for human appearance of less latency.  I left low granularity in
    the carousel enunciator (led1) for comparision, you can see the difference.
    
    led0 = magnetron numan enunciator.
    led1 = carousel  human enunciator.
    led2 = mangetron control.
    led3 = arousel   control.
    
    
-----includes-----------------------------------//----------------------------*/
    #include "mbed.h"                           // mbed class.
    #include "rtos.h"                           // rtos class.
    #include "LM75B.h"                          // thermometer class.
    #include "C12832_lcd.h"                     // LCD  class.
//---defines------------------------------------//------------------------------
    #define LCD1 lcd.locate(0, 0);              // LCD line 1.
    #define LCD2 lcd.locate(0,11);              // LCD line 2.
    #define LCD3 lcd.locate(0,22);              // LCD line 3.
       

    #define DEBOUNCEmS       32                 // debounce wait in mS.
    #define uS_TIMEOUT      100                 // Timer uS timeout.
    #define LBSIG             1                 // left button signal code.
    #define PIPEuS         1000                 // pipeline clock period.
    #define SLOWCLOCKuS  500000                 // slow-clock period.
    #define TEMPCLOCKS        1                 // temperature-measuring period in S.
    #define PIPEDATASIZE      8                 // dimension of tPipeData.
    #define THREAD_1_WAITmS 400                 // thread 1 wait in mS.
    #define THREAD_2_WAITmS  40                 // LCD thread wait.
    #define THREAD_3_WAITmS  80                 // thread 3 wait in mS.
    #define THREAD_4_WAITmS   1                 // thread 4 wait in mS.
    #define THREAD_5_WAITmS   1                 // FSM thread wait.
    #define HB_MODULO      1024                 // heartbeat modulo divisor.
    
    #define MSG_INC_TIME   0x01
    #define MSG_DEC_TIME   0x02
    #define MSG_START      0x04
    #define MSG_STOP       0x08
    #define MSG_OPEN       0x10
    #define MSG_CLOSED     0x20
    
    #define FSM_IDLE       0x01                 // cook-state state-machine.
    #define FSM_COOK       0x02                 // cook-state state-machine.
    #define FSM_PAUSE      0x04                 // cook-state state-machine.
    #define FSM_CONTINUE   0x08                 // cook-state state-machine.
    #define FSM_DONE       0x10                 // cook-state state-machine.
    
    #define RT_PRELOAD     0x01                 // remaining-time preload.
    #define RT_DECREMENT   0x02                 // remaining-time decrement.
    #define RT_PAUSE       0x03                 // remaining-time don't change.
    #define RT_CLEAR       0x04                 // remaining-time set to zero.
    
    #define TEMP_READ      0x01                 // temperature synthesizer control.
    #define TEMP_CALC      0x02                 // temperature synthesizer control.
    #define TEMP_FREEZE    0x03                 // temperature synthesizer control.
    
    #define GRANULARITY    0x400                // 1-second countdown ticker granularity.
    #define MAXSECONDS       180                // maximum microwave-on time.
    #define BEEPTIME           3                // beep duration in S.
    #define BEEPFREQ         300                // beep frequency in Hz.
    
    #define TIMEINC           60                // time increment in seconds.
//--global_definitions--------------------------//------------------------------
    struct tButtons                             // button ISR updates.
    {
      char cLeftButton;                         // cooktime +60S.
      char cRightButton;                        // cooktime -60S.
      char cTopButton;                          // start cook.
      char cBottomButton;                       // stop cook.
      char cCenterButton;                       // center button pressed.
      char cDoorOpen;                           // door open. 
    };
    
    struct tRemainingTime                       // remaining time related.
    {
      char cControl;                            // countdown control.
      char cBeepEnable;                         // beep control.
      int  dTotalTime;                          // initialize to this.
      int  dRemainingTime;                      // the countdown value.
    };
    
    struct tMagnetron                           // magnetron control.
    {
      char cMagnetron;                          // magnetron blink control.
      char cCarousel;                           // carousel  blink control.
    };
    
    struct tLCD                                 // LCD related.
    {
      int   dTotalCookTime;                     // display time in seconds.
      int   dRemainingTime;                     // display time in seconds.
      float fCelsius;                           // temperature in celsius.
    };
    
    Queue<int, 1> queueModTotalTime;            // modify total time.
    Queue<int, 1> queueUpdateFSM;               // tell FSM what button was pressed.
    Queue<int, 1> queueUpdateRemainingTime;     // message to update remaining time.
    Queue<int, 1> queueSetRemainingTime;        // tell countdown it's start time.
    Queue<int, 1> queueTemperatureControl;      // control the temperature synthesizer.
    Queue<int, 1> queueSuppressTimeAdjust;      // total time control.
    Queue<int, 1> queueClearTotalTime;          // total time control.
//--global_variables----------------------------//------------------------------ 
    tButtons       giButtons;                   // ISR button updates.
    tRemainingTime giRemainingTime;             // structure instance.
    tMagnetron     giMagnetron;                 // structure instance.
    tLCD           giLCD;                       // structure instance.
//--global_instances----------------------------//------------------------------ 
    Serial      pc(USBTX, USBRX);               // PuTTY terminal communication.
    LM75B        temperature(p28,p27);          // on-board thermometer.   
    C12832_LCD   lcd;                           // LCD object.
    DigitalOut   led0(LED4);                    // magnetron enunciator (not magnatron control)
    DigitalOut   led1(LED3);                    // carousel  enunciator (not carousel  control)
    DigitalOut   led2(LED2);                    // magnetron CONTROL.
    DigitalOut   led3(LED1);                    // carousel  CONTROL.
    DigitalOut   speaker(p26);                  // speaker device.
    
    InterruptIn  iJoyStickUp    (p15);          // joystick up rising edge.
    InterruptIn  iJoyStickDown  (p12);          // joystick down rising edge.
    InterruptIn  iJoyStickLeft  (p13);          // joystick left rising edge.
    InterruptIn  iJoyStickRight (p16);          // joystick right rising edge.
    InterruptIn  iJoyStickCenter(p14);          // 1 if joystick middle pressed.
    
    Timer        debounceTimer;                 // button debounce timer.
    Timer        beepTimer;                     // beep-duration timer.
    Timer        temperatureTimer;              // how often to raise temperature.

    Ticker       tickerButtonStateManager;      // manage the button states.
    Ticker       tickerCookCountdown;           // remaining cook time.      
    Ticker       tickerBeep;                    // beeper ticker. 
    Ticker       tickerBlinkMagnetron;          // magnetron LED blinker.
    Ticker       tickerBlinkCarousel;           // caroulsel LED blinker.
    
    Mutex        mutexControl;                  // for giRemainingTime.cControl.
//-------prototypes-----------------------------//------------------------------

    void slowClock();                           // 1Hz or thereabouts.

    void initialization();                      // initialize settings.
    
    void ISRleftButtonRising();                 // cook-time increase.
    void ISRleftButtonFalling();                // button-release debounce.
    void ISRrightButtonRising();                // cook-time decrease.
    void ISRrightButtonFalling();               // button-release debounce.
    void ISRtopButtonRising();                  // cook start.
    void ISRtopButtonFalling();                 // button-release debounce.
    void ISRbottomButtonRising();               // cook stop.
    void ISRbottomButtonFalling();              // button-release debounce.
    void ISRcenterButtonRising();               // door state toggle.
    void ISRcenterButtonFalling();              // button-release debounce.
    void disableSignalWaiting();                // break from signal waiting.
    
    void threadButtonStateManager(void const *args);
    void threadTotalTimeControl(void const *args);
    void threadCookStateFSM(void const *args);
    void tickCookRemainingTime();               // remaining time countdown.
    
    void temperatureThread(void const *args);   // temperature measurement.
    void LCDthread        (void const *args);   // LCD display thread.

    void tickerBeeper();                        // beep oscillator.
    void tickerMagnetron();                     // blink magnetron LED.
    void tickerCarousel();                      // blink carousel LED.
//==============================================//==============================
    int main(void) 
    {
    
      initialization();                         // initialize variables.

                                                // ISR setup.
      iJoyStickLeft.rise (&ISRleftButtonRising);
      iJoyStickLeft.fall (&ISRleftButtonFalling);
      
      iJoyStickRight.rise(&ISRrightButtonRising);
      iJoyStickRight.fall(&ISRrightButtonFalling);
   
      iJoyStickUp.rise   (&ISRtopButtonRising);
      iJoyStickUp.fall   (&ISRtopButtonFalling);      
      
      iJoyStickDown.rise (&ISRbottomButtonRising);
      iJoyStickDown.fall (&ISRbottomButtonFalling);
      
      iJoyStickCenter.rise(&ISRcenterButtonRising);
      iJoyStickCenter.fall(&ISRcenterButtonFalling);
      
      debounceTimer.start();                    // kick-off debounce timer.
      
      
                                                // kick-off tickers.
      tickerCookCountdown.attach_us(&tickCookRemainingTime,1000000/GRANULARITY);
      tickerBeep.attach_us         (&tickerBeeper         ,2000);     
      tickerBlinkMagnetron.attach_us(&tickerMagnetron,250000 / 1024);
      tickerBlinkCarousel.attach_us(&tickerCarousel,250000);
      
                                                // kick-off threads.
      Thread thread_1(temperatureThread       ,NULL,osPriorityIdle,DEFAULT_STACK_SIZE,NULL);
      Thread thread_2(LCDthread               ,NULL,osPriorityIdle,DEFAULT_STACK_SIZE,NULL);
      Thread thread_3(threadTotalTimeControl  ,NULL,osPriorityIdle,DEFAULT_STACK_SIZE,NULL);
      Thread thread_4(threadButtonStateManager,NULL,osPriorityNormal,DEFAULT_STACK_SIZE,NULL);
      Thread thread_5(threadCookStateFSM      ,NULL,osPriorityHigh,DEFAULT_STACK_SIZE,NULL);


      while(1) Thread::wait(10000);             // execute microwave controller.
    }     
/*----------------------------------------------//----------------------------*/

//  this serves as the bottom-half for all of the button-press IRSs.
//  it sends button-status messages to other threads, and it also
//  clears the button-state globals to prepare them for the next
//  button press.


    void threadButtonStateManager(void const *args)         
    {
      int dMessage;                             // message.
      
      while(1)                                  // thread loop.
      {     
//---                                           // TOTAL TIME CONTROL.

                                                // encoded integers will be sent,
                                                // not pointers.
      
      if (giButtons.cLeftButton)                // total time increment button.
      {
        dMessage = MSG_INC_TIME;                // set message.
        queueModTotalTime.put((int *) dMessage,1);
        giButtons.cLeftButton = 0;              // clear the button state.
      }

      if (giButtons.cRightButton)               // total time decrement button.
      {
        dMessage = MSG_DEC_TIME;                // set message.
        queueModTotalTime.put((int *) dMessage,1);
        giButtons.cRightButton = 0;             // clear the button state.
      }
   
//---                                           // COOK-STATE FSM.
    
      if (giButtons.cTopButton)                 // start-cook button.             
      {
        dMessage = MSG_START;                   // set message.
        queueUpdateFSM.put((int *) dMessage,1);
        giButtons.cTopButton = 0;               // clear the button state.
      }
     
      if (giButtons.cBottomButton)              // stop-cook button.
      {
        dMessage = MSG_STOP;                    // set message.
        queueUpdateFSM.put((int *) dMessage,1); 
        giButtons.cBottomButton = 0;            // clear the button state.
      }
  
      if (giButtons.cCenterButton)              // door-state-toggle.
      {
        dMessage = giButtons.cDoorOpen;         // determined in ISR.
        queueUpdateFSM.put((int *) dMessage,1); 
        giButtons.cCenterButton = 0;            // clear the button state.      
      }
//---     
      
        Thread::wait(THREAD_4_WAITmS);          // multitasking.
      }                                         // thread loop.
    }                                           // threadButtonStateManager.
/*----------------------------------------------//----------------------------*/
                                                // total time controller.
    void threadTotalTimeControl(void const *args)           
    {
      static int   dTotalTime = 0;              // total time variable.
             int   dMessage;                    // message.
             int   dSuppress;                   // 1 to suppress operation.
         osEvent   queueEvent;                  // queue event.

      dSuppress = 0;                            // initialize.
      
      while(1)                                  // thread loop.
      {
                                                // if FSM tells this to clear
                                                // the total time.
        queueEvent = queueClearTotalTime.get(1);     
        if (queueEvent.status == osEventMessage)
        {
          dTotalTime = 0;
          queueSetRemainingTime.put((int *) dTotalTime,1);
          giRemainingTime.dTotalTime = dTotalTime;
        }
      
 
                                                // suppression mail from FSM.
        queueEvent = queueSuppressTimeAdjust.get(1);     
        if (queueEvent.status == osEventMessage)
        {
          dSuppress = (int) queueEvent.value.p;
        }
      
     
        queueEvent = queueModTotalTime.get(1);  // get message.
        if (queueEvent.status == osEventMessage)
        {
          dMessage = (int) queueEvent.value.p;  // interpret as integer, not pointer.
          
          if (!dSuppress)                       // increment total time.
          if (dMessage == MSG_INC_TIME) dTotalTime += TIMEINC; 
          
          if (!dSuppress)                       // decrement total time.
          if (dMessage == MSG_DEC_TIME) dTotalTime -= TIMEINC;
          
                                                // saturations.
          if (dTotalTime > 180) dTotalTime = 180; 
          if (dTotalTime <   0) dTotalTime =   0;         
          
          queueSetRemainingTime.put((int *) dTotalTime,1);
          giRemainingTime.dTotalTime = dTotalTime;
        }

             
        Thread::wait(THREAD_3_WAITmS);          // multitasking. 
      }                                         // thread loop.
    }                                           // threadTotalTimeControl.
/*----------------------------------------------//----------------------------*/
    void threadCookStateFSM(void const *args)   // cook-cycle FSM.
    {
      int       dFSMstate      = FSM_IDLE;      // state of this FSM.    
      int       dFSMstateLast  = FSM_IDLE;      // previous FSM state.
      int       dButtonState   =  0;            // received button state.
      int       dRemainingTime = 0;             // received remaining time.
      
      int       dButtonStart   = 0;             // received button state.
      int       dButtonStop    = 0;             // received button state.
      int       dDoorOpen      = 0;             // received door state.

      osEvent   queueEvent;                     // from button state manager.
      
      beepTimer.start();                        // run the beep timer.

                                                // constantly read live temperature.
      queueTemperatureControl.put((int *) TEMP_READ,1);
      
      while(1)                                  // thread loop.
      {

        switch (dFSMstate)                      // cook-mode state machine.
        {
//---        
          case  FSM_IDLE :                      // IDLE.
          {
            led2 = 0;                           // magnetron OFF.
            led3 = 0;                           // carousel  OFF.
            giMagnetron.cMagnetron = 0;         // ennunciator.
            giMagnetron.cCarousel  = 0;         // ennunciator.
          
            if (dFSMstate != dFSMstateLast)     // if just entered state.
            {     
                                                // live read of temperature,
                                                // allow total time adjustments,
                                                // clear total time.
             
              queueTemperatureControl.put((int *) TEMP_READ,1);   
              queueSuppressTimeAdjust.put((int*) 0,1);
              queueClearTotalTime.put    ((int*) 1,1);
            }
            

            
                                                // set global LCD data.
            giLCD.dTotalCookTime = giRemainingTime.dTotalTime;
            giLCD.dRemainingTime = 0;           // suppress remaining time display.

                                                // remaining time loaded with total time.
            mutexControl.lock();
            giRemainingTime.cControl = RT_PRELOAD;     
            mutexControl.unlock();                                      
            dFSMstateLast = dFSMstate;          // determine next state.
            
                                                // to FSM_COOK if the start button was
                                                // pressed, the door is closed, and the
                                                // remaining time is more than zero.
            
            if ((dButtonStart == 1) && (dDoorOpen == 0) && (giRemainingTime.dTotalTime > 0)) dFSMstate = FSM_COOK;
            break;
          }
//---   
           case  FSM_COOK :                     // COOK.
          {
            led2 = 1;                           // magnetron ON.
            led3 = 1;                           // carousel  ON.
            giMagnetron.cMagnetron = 1;         // ennunciator.
            giMagnetron.cCarousel  = 1;         // ennunciator.
            
            if (dFSMstate != dFSMstateLast)     // if just entered state.
            { 

              
                                                // start decrementing remaining time.
                                                // temperature synthesizer is now
                                                // calculating the temperature,
                                                // user can no longer adjust total time.
              mutexControl.lock();
              giRemainingTime.cControl = RT_DECREMENT;
              mutexControl.unlock();
              queueTemperatureControl.put((int *) TEMP_CALC,1);
              queueSuppressTimeAdjust.put((int*) 1,1);
            }    
            
                                                // update global LCD data.
            giLCD.dTotalCookTime = giRemainingTime.dTotalTime;
            giLCD.dRemainingTime = giRemainingTime.dRemainingTime;
            
            
            
                                                
            dFSMstateLast = dFSMstate;          // determine next state.
            
                                                // pause on door-open if remaining time.
            
            if ((dDoorOpen == 1) && (dRemainingTime > 0)) dFSMstate = FSM_PAUSE; 
            else
            
                                                // stop button -> idle state.
            if (dButtonStop) dFSMstate = FSM_IDLE;
            else
            
                                                // beeper state once countdown complete.
            if (dRemainingTime <= 0) dFSMstate = FSM_DONE;
            break;
          }
//---   
          case  FSM_PAUSE :                     // PAUSE.
          {
            led2 = 0;                           // magnetron OFF.
            led3 = 0;                           // carousel  OFF.
            giMagnetron.cMagnetron = 0;         // ennunciator.
            giMagnetron.cCarousel  = 0;         // ennunciator.
              
            if (dFSMstate != dFSMstateLast)     // if just entered state.
            { 
                                                // suspend synthesized temperature calculation.
              queueTemperatureControl.put((int *) TEMP_FREEZE,1);
            }
            
                                                // update LCD globals.
            giLCD.dTotalCookTime = giRemainingTime.dTotalTime;
            giLCD.dRemainingTime = giRemainingTime.dRemainingTime;
            
            
            mutexControl.lock();                             
            giRemainingTime.cControl = RT_PAUSE;// pause remaining time countdown.
            mutexControl.unlock();
                                                
            dFSMstateLast = dFSMstate;          // determine next state.
            
            
                                                // continue if user hits start button,
                                                // and the door is closed, and there
                                                // is some remaining time.
            
            if ((dButtonStart == 1) && (dDoorOpen == 0) && (giRemainingTime.dTotalTime > 0)) dFSMstate = FSM_CONTINUE;
            else
            
                                                // to idle if user hits stop button.
            if (dButtonStop) dFSMstate = FSM_IDLE; 
            break;
          }
          
//---             
          case  FSM_CONTINUE :                  // CONTINUE.
          {
            led2 = 1;                           // magnetron ON.
            led3 = 1;                           // carousel  ON.
            giMagnetron.cMagnetron = 1;         // ennunciator.
            giMagnetron.cCarousel  = 1;         // ennunciator.
              
            if (dFSMstate != dFSMstateLast)     // if just entered state.
            { 
                                                // continue calculating synthetic temperature.
              queueTemperatureControl.put((int *) TEMP_CALC,1);
            }
            
                                                // update LCD globals.
            giLCD.dTotalCookTime = giRemainingTime.dTotalTime;
            giLCD.dRemainingTime = giRemainingTime.dRemainingTime;
        
            
                                                // continue decrementing time.
            mutexControl.lock();
            giRemainingTime.cControl = RT_DECREMENT;
            mutexControl.unlock();
                                                
            dFSMstateLast = dFSMstate;          // determine next state.
            
                                                // beep state when no more time.
            
            if (dRemainingTime <= 0) dFSMstate = FSM_DONE;
            else
                                                // user stop causes jump to idle.
            if (dButtonStop) dFSMstate = FSM_IDLE; 
            else
                                                // back to pause if door open,
                                                // and there's remaining time.
            if ((dDoorOpen == 1) && (dRemainingTime > 0)) dFSMstate = FSM_PAUSE;
            break;
          }          
          
          
//---             
          case  FSM_DONE :                      // DONE.
          {
            led2 = 0;                           // magnetron OFF.
            led3 = 0;                           // carousel  OFF.
            giMagnetron.cMagnetron = 0;         // ennunciator.
            giMagnetron.cCarousel  = 0;         // ennunciator.        
          
            if (dFSMstate != dFSMstateLast)     // if just entered state.
            { 
                                                // freeze synthetic temperature.
              queueTemperatureControl.put((int *) TEMP_FREEZE,1);
              giRemainingTime.cBeepEnable = 1;  // initiate beep.
              beepTimer.reset();                // clear the beep timer.
            }
           
                                                // update LCD globals.
            giLCD.dTotalCookTime = giRemainingTime.dTotalTime;
            giLCD.dRemainingTime = giRemainingTime.dRemainingTime;
            
            mutexControl.lock();
            giRemainingTime.cControl = RT_CLEAR;// clear remaining time. (needed?)
            mutexControl.unlock();
                                                
            dFSMstateLast = dFSMstate;          // determine next state.
            
                                                // to idle once beeper is done.
            if (beepTimer.read() >= BEEPTIME) {dFSMstate = FSM_IDLE; giRemainingTime.cBeepEnable = 0;}
            else dFSMstate = FSM_DONE;          // (else stay here).
            break;
          }       
//---           
          default       :                       // SAFETY
          {
            led2 = 0;                           // magnetron OFF.
            led3 = 0;                           // carousel  OFF.
            giMagnetron.cMagnetron = 0;         // ennunciator.
            giMagnetron.cCarousel  = 0;         // ennunciator.
            dFSMstate = FSM_IDLE; 
            break;
          }
        
        }
        
     
    
        queueEvent = queueUpdateFSM.get(1);     // threadButtonStateManager
        if (queueEvent.status == osEventMessage)// update state variable.  
        {
                                                // interpret as integer, not pointer.
          dButtonState = (int) queueEvent.value.p; 
          
          if (dButtonState == MSG_START)
          {
            dButtonStart = 1;                   // copy-in start info.
            dButtonStop  = 0;
            if (dDoorOpen) dButtonStart = 0;    // cancel start if door open.
          }
          if (dButtonState == MSG_STOP)
          {
            dButtonStart = 0;                   // copy-in stop state.
            dButtonStop  = 1;
            queueClearTotalTime.put((int*) 1,1);// clear total time on stop.
          }    
          
          if (dButtonState == MSG_OPEN)         // if the door opens, clear current 'start' state.
          {                       
            dDoorOpen    = 1;
            dButtonStart = 0;
          }       
          
          if (dButtonState == MSG_CLOSED)       // detect door closed.
          {
            dDoorOpen = 0;
          }               
                
        }
        else                                    // no queue update available.
        {
          dButtonStart   = 0;
          dButtonStop    = 0;
        }
 
                                                // fetch from global scope.
        dRemainingTime = giRemainingTime.dRemainingTime;

        Thread::wait(THREAD_5_WAITmS);          // multitasking. 
      }                                         // thread loop.
    }                                           // threadCookStateFSM.
/*----------------------------------------------//----------------------------*/
    void initialization(void)                   // program initializations.
    {
      led0 = 0;
      led1 = 0;
      led2 = 0;
      led3 = 0;
      giButtons.cDoorOpen = 0;                  // initialize with door closed.
      giRemainingTime.cBeepEnable =0;
      giMagnetron.cMagnetron = 0;
      giMagnetron.cCarousel = 0;
    }
/*----------------------------------------------//----------------------------*/
    void ISRleftButtonRising(void)              // cooktime plus 60s.
    {
      if (debounceTimer.read_ms() > DEBOUNCEmS)
      giButtons.cLeftButton = 1;                // detect left button.
      
      debounceTimer.reset();                    // begin debounce period.
    } 
/*----------------------------------------------//----------------------------*/
    void ISRleftButtonFalling(void)             // button-release debounce.
    {
      debounceTimer.reset();                    // begin debounce period.
    } 
/*----------------------------------------------//----------------------------*/
    void ISRrightButtonRising(void)             // cooktime -60s.
    {
      if (debounceTimer.read_ms() > DEBOUNCEmS)
      giButtons.cRightButton = 1;               // detect right button.
      
      debounceTimer.reset();                    // begin debounce period.
    } 
/*----------------------------------------------//----------------------------*/
    void ISRrightButtonFalling(void)            // button-release debounce.
    {
      debounceTimer.reset();                    // begin debounce period.
    } 
/*----------------------------------------------//----------------------------*/
    void ISRtopButtonRising(void)               // cook start.
    {
      if (debounceTimer.read_ms() > DEBOUNCEmS)
      giButtons.cTopButton = 1;                 // detect top button.
      
      debounceTimer.reset();                    // begin debounce period.
    } 
/*----------------------------------------------//----------------------------*/
    void ISRtopButtonFalling(void)              // button-release debounce.
    {
      debounceTimer.reset();                    // begin debounce period.
    } 
/*----------------------------------------------//----------------------------*/
//  front-end control of magnetron off.
//  due to physical danger, the magnetron is turned off immediately
//  upon off-button press, in the interrupt that it generates.

    void ISRbottomButtonRising(void)            // cook stop.
    {
      if (debounceTimer.read_ms() > DEBOUNCEmS)
      {
        led2 = 0;                               // magnetron off.
        giButtons.cBottomButton = 1;            // detect bottom button.
      }
      debounceTimer.reset();                    // begin debounce period.
    }
/*----------------------------------------------//----------------------------*/
    void ISRbottomButtonFalling(void)           // button-release debounce.
    {
      debounceTimer.reset();                    // begin debounce period.
    } 
/*----------------------------------------------//----------------------------*/
//  front-end control of magnetron off.
//  due to physical danger, the magnetron is turned off immediately
//  upon detection of an open door.

    void ISRcenterButtonRising(void)            // toggle door state.
    {
      if (debounceTimer.read_ms() > DEBOUNCEmS)
      {
        if (giButtons.cDoorOpen == MSG_OPEN)    // calculate door state.
        giButtons.cDoorOpen = MSG_CLOSED;
        else
        giButtons.cDoorOpen = MSG_OPEN;
      
                                                // magnetron off.
        if (giButtons.cDoorOpen == MSG_OPEN) led2 = 0;        
      
        giButtons.cCenterButton = 1;              
      }
      debounceTimer.reset();                    // begin debounce period.
    } 
/*----------------------------------------------//----------------------------*/
    void ISRcenterButtonFalling(void)           // button-release debounce.
    {
      debounceTimer.reset();                    // begin debounce period.
    } 
/*----------------------------------------------//----------------------------*/

//  this will measure live temperature,  or calculate it to be
//  increasing at 1/6 degree per second, or freeze it.


    void temperatureThread(void const *args)    // temperature measurement.
    {
      int       dTempControl = 0;               // temperature control.
      float     fLastMeasuredTemperature = 0.0; // last physically-measured temperature.
      float     fCelsius;                       // synthetic temperature.
      osEvent   queueEvent;                     // from button state manager.
      
      while(1)                                  // thread loop.
      {
                                                // obtain temperature command.
        queueEvent = queueTemperatureControl.get(1);  
        if (queueEvent.status == osEventMessage)
        {
          dTempControl = (int) queueEvent.value.p;  
        }
      
        if (dTempControl == TEMP_READ)
        {
          fCelsius = temperature.read();         // physical measurement.
          fLastMeasuredTemperature = fCelsius;
        }
        
        if (dTempControl == TEMP_CALC)          // calculate synthetic temperature.
        fCelsius = ((float) (giRemainingTime.dTotalTime - giRemainingTime.dRemainingTime) / 
                            (float) 6.0) + fLastMeasuredTemperature;
            
        giLCD.fCelsius = fCelsius;              // to global scope for LCD.
        
        Thread::wait(THREAD_1_WAITmS);          // multitasking. 
      }                                         // thread loop.
    }                                           // temperatureThread.
/*----------------------------------------------//----------------------------*/
    void LCDthread(void const *args)            // LCD display thread.
    {
    
      static int   dLCDtotalCookTimeSec  = 0;   // sample current values.
      static int   dCookTimeRemainingSec = 0;
      static float fLCDcelsius           = 0.0;
    
                                                // remember previous values.
      static int   dLCDtotalCookTimeSecLast  = 0;
      static int   dCookTimeRemainingSecLast = 0;
      static float fLCDcelsiusLast           = 0.0;
      
      while(1)                                  // thread loop.
      {
                                                // don't allow the values to
                                                // change in the middle of the 
                                                // below else the anti-blink
                                                // code won't work.
        dLCDtotalCookTimeSec  = giLCD.dTotalCookTime;
        dCookTimeRemainingSec = giLCD.dRemainingTime;
        fLCDcelsius           = giLCD.fCelsius;
       
                                                // clear display only when
                                                // necessary, in order to avoid
                                                // 'blinkieness'. 
        if (dLCDtotalCookTimeSec  != dLCDtotalCookTimeSecLast  ||
            dCookTimeRemainingSec != dCookTimeRemainingSecLast || 
            fLCDcelsius           != fLCDcelsiusLast)
        lcd.cls();
      
        LCD1;                                   // line 1.
        lcd.printf(" total   cook time: %d",dLCDtotalCookTimeSec);
      
        LCD2;                                   // line 2.
        lcd.printf(" remaing cook time: %d",dCookTimeRemainingSec);
    
        LCD3;                                   // line 3.
        lcd.printf(" temperature      : %5.3f",fLCDcelsius);
        
                                                // pipeline variables.
        dLCDtotalCookTimeSecLast  = dLCDtotalCookTimeSec;
        dCookTimeRemainingSecLast = dCookTimeRemainingSec;
        fLCDcelsiusLast           = fLCDcelsius;
      
        Thread::wait(THREAD_2_WAITmS);          // multitasking.
      }                                         // thread loop.
    }                                           // LCDthread.
/*----------------------------------------------//----------------------------*/

//  cook remaining time countdown counter.
//  possibly due to a bug in Ticker
//  http://mbed.org/questions/1563/Mbed-Tickerfunction-hangs-system-from-re/
//  I've been unable to detach/attach this routine in order to reset its phase
//  when I tried it at a 1s resolution.  In order to provide the human perception
//  of an immediate restart, I've increased the ticker frequency by the factor
//  'GRANULARITY' and likewise divide that factor out when this routine
//  promotes the remaining time to the global variable.

//  communication is via a global instance of struct tRemainingTime.

    void tickCookRemainingTime(void)            // cook-cycle countdown.
    {
      static int dRemainingTime = 0;            // remaining time in seconds.
             int dMaximum;                      // MAXSECONDS * GRANULARITY.
            char cControl;                      // local copy.
             
      dMaximum = MAXSECONDS * GRANULARITY;      // precalculate.
      
      mutexControl.lock();
      cControl = giRemainingTime.cControl;
      mutexControl.unlock();
      
      switch (cControl)                         // control processing.
      {
      
        case RT_PRELOAD   :                     // preload with total time.
        {
                                                // the 'GRANULARITY - 1' factor
                                                // compensates for integer division
                                                // dropping the right-of-decimal result,
                                                // that occuring at the bottom of this
                                                // routine.
          dRemainingTime = (giRemainingTime.dTotalTime * GRANULARITY) + (GRANULARITY - 1);
          break;
        }
        
        case RT_DECREMENT :                     // count-down.
        {
          dRemainingTime--;
          break;
        }
      
        case RT_PAUSE    :                      // suspend countdown.
        {
          dRemainingTime = dRemainingTime;
          break;
        }
        
        case RT_CLEAR    :                      // clear countdown.
        {
          dRemainingTime = 0;        
          break;
        }
      
        default          :                      // saturate, just in case.
        {
        }
      }                                         // control processing.
      
                                                // saturate value.
      if (dRemainingTime >  dMaximum) dRemainingTime = dMaximum;
      if (dRemainingTime <         0) dRemainingTime = 0;
      
                                                // promote to global scope.
      giRemainingTime.dRemainingTime = dRemainingTime/GRANULARITY;
      
    }                                           // cookRemainingTime.
/*----------------------------------------------//----------------------------*/
    void tickerBeeper(void)                     // beep when giRemainingTime.
    {
      static char cState = 0;
      if (giRemainingTime.cBeepEnable)
      {
        if (cState) {speaker = 1; cState = 0;}
        else        {speaker = 0; cState = 1;}
      }
    }
/*----------------------------------------------//----------------------------*/
                                                // this is not magnetron control.
    void tickerMagnetron(void)                  // magnetron enunciator.
    {
      static int dCounter = 0;                  // latency reduction.
      
      if (giMagnetron.cMagnetron)               // turn off humanly quickly.
      {
        if (!(dCounter % 1024)) led0 = !led0;
      } else led0 = 0;
      
      dCounter++;
    }

/*----------------------------------------------//----------------------------*/
                                                // this is not carousel control.
    void tickerCarousel(void)                   // carousel enunciator.
    {
      if (giMagnetron.cCarousel) led1 = !led1;
      else led1 = 0;
    }
/*----------------------------------------------//----------------------------*/