embedded RTOS class project.

Dependencies:   C12832_lcd USBDevice mbed-rtos mbed mmSPI_RTOS watchdog_RTOS

main.cpp

Committer:
gatedClock
Date:
2013-09-17
Revision:
3:1b3329242491
Parent:
2:4336e18b60fe

File content as of revision 3:1b3329242491:

/*----------------------------------------------//------------------------------
    student   : m-moore
    email     : gated.clock@gmail.com
    class     : embedded RTOS
    directory : RTOS_project
    file      : main.cpp
    date      : september 19, 2013.
----copyright-----------------------------------//------------------------------   
    licensed for personal and academic use.
    commercial use of original code must be approved by the account-holder of
    gated.clock@gmail.com
----revision------------------------------------//------------------------------
    this is the embedded RTOS class revision.
    changes made since the USB device class release:
    
    1. a 'read all registers' feature has been added, which speeds up
       CPU execution, since the UI obtains all of the register states after
       each CPU clock.  originally, each register was individually read.
       now, all registers are read at once, if so requested by the python code.
       
    2. some 'if' statements were changed to 'switch' statements (neatening).
    
    3. added watchdog timers for the three threads.  this via a meta-watchdog thread.
    
    4. added #defined-based option to either boot on error detection
       (such as malloc fail) or use error(); function.
       
    5. the LCD is updated only if a display value is changed - reduced power
       usage a little & reduces potential 'blinkieness'.
       
    6. BOOT notification on LCD.
----description---------------------------------//------------------------------
    overview:
    program to provide round-trip communication between a python test-control
    program running on a pc host, and a device-under-test CPU implemented on
    an altera board.  the pc-host communication is over USBSerial, and the
    altera communication is over SPI.
    
    features:
    1. multi-threaded design, use of memory-pools to transfer data between threads.
    2. use of USBDevice library for communication with PC host.
    3. use of mmSPI_RTOS custom library for communication with FPGA.
    4. main thread provides USBSerial communication to/from host.
    5. SPI processing thread provides SPI communication to/from DUT.
    6. mmSPI_RTOS library generates non-overlapping SPI and CPU clocks.
    7. background diagnostic thread provides LCD & error updates.
    8. meta watchdog thread monitors itself & the other threads.

    indicators: (led<3:0> = LED<1:4>)
    1. LCD  provides running counts for SPI and CPU clock cycles.
    2. led0 indicates main     thread processing.
    3. led1 indicates SPI      thread processing.
    4. led2 indicates LCD      thread processing.
    5. led3 indicates watchdog thread processing.
    
    implementation:
    1. main.processIncomingSerial(): accept incoming serial data from host,
       and map it into tFromHost structures.
    2. SPIprocessingThread: take the incoming data structures instances, and
       feed their content into mmSPI_RTOS commands.
    3. mmSPI_RTOS object: given commands/data passed from caller,
       map them into SPI outgoing vectors and scan them into the FPGA.
    4. mmSPI_RTOS object: receive incoming SPI vectors from FPGA.
       make FPGA payload data available to caller.
    5. SPIprocessingThread: load tToHost structures with said FPGA payload data.
    6. main.processOutgoingSerial(): transfer tToHost structure data into a
       serial outgoing buffer, and block-transfer it to the host PC.
       
    electrical:
    1. four pins (and ground) attached to the zigbee header,
       programmed as three SPI pins and the CPU clock.
    2. each of the four signals over twisted-pair.
    3. but some ribbon cable is used at the FPGA end.
    4. best if only the mbed application board USB cable is attached
       to the host; if the mbed CPU board USB cable is also attached to
       the host, then the python program may attempt to use the wrong USB
       connection.
    5. no particular power sequence is needed for this system to work.
    
    timing critical path:  serial processing.  the python code needs
    a delay between serial access of 40mS conservatively.
    
    testing:
    the python UI provides the main testing mechanism.
    
        USB connect.
    00. press 'CONNECT' button in UI.  verify connection info in shell.
    
        CPU register w/r
    01. type values into {R0,R1,R2,R3,PC,IR} UI entry-forms.
    02. press 'REG WRITE' UI button.
    03. press 'REG READ'  UI button.
    04. verify that the read data is correct.
    
        CPU main-memory w/r
    05. type an address into 'mmADR' UI entry-form.
    06. type data into 'mmVAL' UI entry-form.
    07. press 'MM WRITE' UI button.
    08. type a different address into 'mmADR' UI entry-form.
    09. type different data into 'mmVAL' UI entry-form.
    10. press 'MM WRITE' UI button.
    11. type address from (05) into 'mmADR' UI entry-form.
    12. press 'MM READ' UI button.
    13. verify that the data from (06) is seen in the 'mmVAL' entry form.
    14. type address from (08) into 'mmADR' UI entry-form.
    15. press 'MM READ' UI button.
    16. verify that the data from (09) is seen in the 'mmVAL' entry form.  
    
        CPU main-memory full load/dump.
    17. press 'PROGRAM' in the UI.  select a program file in the dialog-popup.  
    18. watch the load process in the shell text.
    19. press 'DUMP     in the UI.  select a main-memory dump file in the diaglog-popup.
    20. watch the dump process in the shell text.
    
        CPU step function.
    21. press 'STEP' in the UI repeatedly, watch the UI display the 
        CPU register states as the current program is executed one CPU clock
        at a time.
        
        CPU run function.
    22. press 'RUN'  in the UI.  watch the current program run at high speed.
    23. press 'SLOW' in the UI.  watch the current program run at slow speed.
    24. press 'STOP' in the UI.  the program will stop execution.
    
        CPU test function.
    25. press 'TEST' in the UI.  the program will load,execute,dump,compare.
    26. tail -f testlog.txt to see test status.
    27. the test will repeat until 'STOP TEST' is pressed.
    28. long test performed by allowing this mode to continue.
    
        UI exit function.
    29. press 'EXIT' in the UI.  it will exit.
-----includes-----------------------------------//----------------------------*/
    #include "mbed.h"                           // general.
    #include "USBSerial.h"                      // serial over USB.
    #include "C12832_lcd.h"                     // LCD display.
    #include "rtos.h"                           // RTOS.
    #include "mmSPI_RTOS.h"                     // SPI.
    #include "watchdog_RTOS.h"                  // watchdog.
//---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 LCD3 lcd.locate(0,22);              // LCD line 3.  
    #define WATCHDOG_S     10                   // watchdog timeout, in seconds.
    #define ERROR_BOOT      1                   // 1 means boot rather than error().
    #define SPI_BYTES       8                   // number of SPI bytes. 
    #define SPI_HZ     100000                   // SPI frequency in Hz.
    #define SER_BYTES      18                   // serial in/out # of bytes.
    #define SER_ALIGN       7                   // '$' location in shift-register.
    #define THREAD_0_WAIT   8                   // multitasking wait mS.
    #define THREAD_1_WAIT   2                   // multitasking wait mS. 
    #define THREAD_2_WAIT 128                   // multitasking wait mS. 
    #define THREAD_3_WAIT 128                   // multitasking wait mS.
    #define HB_MODULO      64                   // heartbeat slowdown factor.
    #define POOL_LEN       16                   // memory pool dimension.
    #define HCMD_SETREG     1                   // host command 'set register'.
    #define HCMD_GETREG     2                   // host command 'get register'.
    #define HCMD_SETMM      3                   // host command 'set main-memory.'
    #define HCMD_GETMM      4                   // host command 'get main-memory.'
    #define HCMD_STEP       5                   // host command 'step-CPU'.
    #define HCMD_SETIR      6                   // host command 'set-IR'.
    #define HCMD_GETALLREG  7                   // host command 'get-all-registers'.
    #define CPU_REG_0       0                   // CPU register 0.
    #define CPU_REG_1       1                   // CPU register 1.
    #define CPU_REG_2       2                   // CPU register 2.
    #define CPU_REG_3       3                   // CPU register 3.
    #define CPU_REG_PC      4                   // CPU Program Counter.
    #define CPU_IR_H        5                   // CPU IR high-byte.
    #define CPU_IR_L        6                   // CPU IR low-byte.
//--externals-----------------------------------//------------------------------    
    extern "C" void mbed_reset();               // processor reset.
//--global_definitions--------------------------//------------------------------
    struct tFromHost                            // command from host.
    {
      char cCommand;                            // command from host.
      char cRegisterID;                         // which CPU register.
      char cRegisterValue;                      // write this to CPU register.
      char cIRValueH;                           // write this to IR.
      char cIRValueL;                           // write this to IR.
      char cMMaddress;                          // access this MM address.
      char cMMdataH;                            // MM content high byte.
      char cMMdataL;                            // MM content low  byte.
    };
    MemoryPool<tFromHost, POOL_LEN> mPoolFromHost;
    Queue     <tFromHost, POOL_LEN> qFromHost;
    
//----  
    
    struct tToHost                              // reply to host.
    {
      char cCommand;                            // command executed.
      char cRegisterID;                         // which CPU register read.
      char cRegisterValue;                      // data from CPU register.
      char cMMaddress;                          // which MM address read.
      char cMMdataH;                            // MM content high byte.
      char cMMdataL;                            // MM content low  byte.   
      char cReg0;                               // data from R0.
      char cReg1;                               // data from R1.
      char cReg2;                               // data from R2.
      char cReg3;                               // data from R3.
      char cPC;                                 // data from program counter.
      char cIRH;                                // high byte from instruction register.
      char cIRL;                                // low  byte from instruction register.   
    };
    MemoryPool<tToHost, POOL_LEN> mPoolToHost;
    Queue     <tToHost, POOL_LEN> qToHost;
    
    Queue<int, POOL_LEN> queueWatchdogThread_0; // main thread watchdog notice.
    Queue<int, POOL_LEN> queueWatchdogThread_1; // thread 1    watchdog notice.
    Queue<int, POOL_LEN> queueWatchdogThread_2; // thread 2    watchdog notice.
//--global_variables----------------------------//------------------------------ 
    char          gpcSerialFromHost[SER_BYTES]; // incoming serial buffer.
    char          gpcSerialToHost  [SER_BYTES]; // outgoing serial buffer.
    char          gcNewCommand;                 // new command from host.
    int           gdRoundTrip;                  // +1 from host, -1 to host.
    tToHost     * gpToHost;                     // to-host structure.
    osEvent       gqToHostEvent;                // incoming message event.
    unsigned long gulSPIclkCount;               // SPI clock count.
    unsigned long gulCPUclkCount;               // CPU clock count.
//--global_instances----------------------------//------------------------------ 
    USBSerial  serial;                          // serial over usb.
    C12832_LCD lcd;                             // LCD display.
    DigitalOut led0(LED4);                      // thread heartbeat.
    DigitalOut led1(LED3);                      // thread heartbeat.
    DigitalOut led2(LED2);                      // thread heartbeat.
    DigitalOut led3(LED1);                      // SPI reply underflow warning.
//-------prototypes-----------------------------//------------------------------
    int  main();                                // main.
    void processIncomingSerial();               // process incoming host data.    
    void processOutgoingSerial();               // process outgoing data to host.   
    void SPIprocessingThread(void const *args); // SPI-side processing.   
    void diagnosticThread   (void const *args); // LCD and LED notifications.  
    void watchdogThread     (void const *args); // overall watchdog. 
    char ascii_nibble_to_binary(char cAscii);   // ascii nibble -> binary.
    char binary_to_ascii_nibble(char cBinary);  // binary -> ascii nibble.                
    void clear_tFromHost(tFromHost *ptFromHost);// initialize structure.
    void clear_tToHost  (tToHost   *ptToHost);  // initialize structure.  
//==============================================//==============================
    int main(void)                              // USBSerial processing thread.
    {
      int dHeartbeat;                           // heartbeat counter.
      int dLoop;                                // loop index.

      gpToHost       = NULL;                    // initialize global.
      gcNewCommand   =    0;                    // initialize global.
      gdRoundTrip    = 1024;                    // initialize global.
      gulSPIclkCount =    0;                    // initialize global.
      gulCPUclkCount =    0;                    // initialize global.
      led0           =    0;                    // initialize global.
      led1           =    0;                    // initialize global.
      led2           =    0;                    // initialize global.
      led3           =    0;                    // initialize global.
      dHeartbeat     =    0;                    // initialize local.
      dLoop          =    0;                    // initialize local.
      
                                                // BOOT notification.
      lcd.cls(); LCD2; lcd.printf("           BOOT"); wait(1.0);

                                                // initialize serial-in shift-register.
      for (dLoop = 0; dLoop < SER_BYTES; dLoop++) gpcSerialFromHost[dLoop] = 0;
      
                                                // thread-out SPI-side processing.
      Thread thread_1(SPIprocessingThread,NULL,osPriorityHigh,DEFAULT_STACK_SIZE,NULL);
      
                                                // thread-out diagnostics.
      Thread thread_2(diagnosticThread,   NULL,osPriorityIdle,DEFAULT_STACK_SIZE,NULL);
      
                                                // thread-out universal watchdog.
      Thread thread_3(watchdogThread,     NULL,osPriorityIdle,DEFAULT_STACK_SIZE,NULL);
      
      while(1)                                  // main loop.
      {   
        processIncomingSerial();                // process data in from host.
        processOutgoingSerial();                // process data out to host.
                        
        dHeartbeat++;                           // thread heartbeat.
        if (!(dHeartbeat % HB_MODULO)) led0 = !led0;
        queueWatchdogThread_0.put((int *) 0,1); // adds 1mS to wait.
        Thread::wait(THREAD_0_WAIT - 1);        // multitasking.   
      }                                         // main loop.
    }                                           // main.
/*----------------------------------------------//----------------------------*/
/*
   the python program running on the host is sending/receiving ascii characters
   which represent command/data binary nibbles.  the python program will send
   the '$' character for command-string alignment.  this function reads-in the
   incoming serial stream when any serial data is available, into a shift-register,
   and breaks upon detection of the '$' alignment character for python
   command-processing. at that point the shift-register will look something like
   [0] [1] [2] [3] [4] [5] [6] [7]
   '1' '2' '3' '4'  x   x   x  '$'  (means write 0x34 to CPU R2).


    command-host register-number interpretation:
    0 = CPU R0.
    1 = CPU R1.
    2 = CPU R2.
    3 = CPU R3.
    4 = CPU program-counter.
    5 = CPU instruction-register high-byte.
    6 = CPU instruction-register low-byte.
    
    instruction-register write is specially implemented,
    instruction-register read  is implemented as two standard register-reads.

    host-command shift-register interpretation:
    
    gpcSerialFromHost[0] = command.  
    subsequent interpretation depends on the command.
    
----   
    if command = HCMD_SETREG (write-CPU-register) or HCMD_GETREG (read-CPU-register):
    
      gpcSerialFromHost[1] = register number (see above).
      gpcSerialFromHost[2] = register content, high nibble.
      gpcSerialFromHost[3] = register content, low  nibble.
      gpcSerialFromHost[4] = not used.
      gpcSerialFromHost[5] = not used.
      gpcSerialFromHost[6] = not used.

----
    if command = HCMD_SETIR (write-CPU-instruction-register):
    
      gpcSerialFromHost[1] = IR register number, implied anyway.
      gpcSerialFromHost[2] = IR write value high byte high nibble.
      gpcSerialFromHost[3] = IR write value high byte low  nibble.
      gpcSerialFromHost[4] = IR write value low  byte high nibble.
      gpcSerialFromHost[5] = IR write value low  byte low  nibble.
      gpcSerialFromHost[6] = not used.

----    
    if command = HCMD_SETMM (write to CPU main-memory) or HCMD_GETMM (read from CPU main-memory):
    
      gpcSerialFromHost[1] = MM address high nibble.
      gpcSerialFromHost[2] = MM address low  nibble.
      gpcSerialFromHost[3] = MM content high byte high nibble.
      gpcSerialFromHost[4] = MM content high byte low  nibble.
      gpcSerialFromHost[5] = MM content low  byte high nibble.
      gpcSerialFromHost[6] = MM content low  byte low  nibble.
      
      the above also applies to function 'processOutgoingSerial'.
*/

    void processIncomingSerial(void)            // process incoming serial data.
    {
      int         dLoop;                        // loop index.
      tFromHost * pFromHost;                    // from-host structure.
      
      while(serial.available())                 // while data from host is available.
      {
                                                // shift-in the serial stream.
        for (dLoop = 0; dLoop < (SER_BYTES - 1); dLoop++)
        gpcSerialFromHost[dLoop] = gpcSerialFromHost[dLoop + 1];
        gpcSerialFromHost[SER_BYTES - 1] = serial._getc();
        
        if (gpcSerialFromHost[SER_ALIGN] == '$')// data from host is aligned.
        {
          gcNewCommand = 1;                     // new host command just recognised.
          break;                                // need to process aligned data.
        }                                       // data from host is aligned. 
      }                                         // while data from host is available.
      
                                                // even if more data awaits from the
                                                // incoming serial stream, we now need
                                                // to process the aligned data recognised
                                                // as a command from the host.  
                                                
      if (gcNewCommand)                         // execute once per new command.
      {
        pFromHost = mPoolFromHost.alloc();      // allocate next pool entry.
        if (!pFromHost)                         // failure detection.
        {
          if (ERROR_BOOT) mbed_reset(); else
          error("\n\r processIncomingSerial : FATAL malloc error for pFromHost. \n\r"); 
        }
        
        clear_tFromHost(pFromHost);             // initialize structure.
        
                                                // copy-in host message.
        pFromHost->cCommand = ascii_nibble_to_binary(gpcSerialFromHost[0]);
        
//----

        switch(pFromHost->cCommand)             // command dependency.
        {
          case HCMD_SETREG :                    // host command 'set register'.
          {
            pFromHost->cRegisterID    =   ascii_nibble_to_binary(gpcSerialFromHost[1]);
            pFromHost->cRegisterValue = ((ascii_nibble_to_binary(gpcSerialFromHost[2])) << 4) + 
                                          ascii_nibble_to_binary(gpcSerialFromHost[3]);          
            break;
          }                                     // host command 'set register'.
        
          case HCMD_GETREG :                    // host command 'get register'.
          {
            pFromHost->cRegisterID    =   ascii_nibble_to_binary(gpcSerialFromHost[1]);
            pFromHost->cRegisterValue = ((ascii_nibble_to_binary(gpcSerialFromHost[2])) << 4) + 
                                          ascii_nibble_to_binary(gpcSerialFromHost[3]);   
            gdRoundTrip++;                      // expected reply to host is pending.       
            break;
          }                                     // host command 'get register'.
                 
          case HCMD_SETMM :                     // host command 'set main-memory.'
          {
            pFromHost->cMMaddress = ((ascii_nibble_to_binary(gpcSerialFromHost[1])) << 4) + 
                                      ascii_nibble_to_binary(gpcSerialFromHost[2]);  
            pFromHost->cMMdataH   = ((ascii_nibble_to_binary(gpcSerialFromHost[3])) << 4) + 
                                      ascii_nibble_to_binary(gpcSerialFromHost[4]);         
            pFromHost->cMMdataL   = ((ascii_nibble_to_binary(gpcSerialFromHost[5])) << 4) + 
                                      ascii_nibble_to_binary(gpcSerialFromHost[6]);           
            break;
          }                                     // host command 'set main-memory.'
               
          case HCMD_GETMM :                     // host command 'get main-memory.'
          {
            pFromHost->cMMaddress = ((ascii_nibble_to_binary(gpcSerialFromHost[1])) << 4) + 
                                      ascii_nibble_to_binary(gpcSerialFromHost[2]);  
            pFromHost->cMMdataH   = ((ascii_nibble_to_binary(gpcSerialFromHost[3])) << 4) + 
                                      ascii_nibble_to_binary(gpcSerialFromHost[4]);         
            pFromHost->cMMdataL   = ((ascii_nibble_to_binary(gpcSerialFromHost[5])) << 4) + 
                                      ascii_nibble_to_binary(gpcSerialFromHost[6]);    
                                      
            gdRoundTrip++;                      // expected reply to host is pending.        
            break;
          }                                     // host command 'get main-memory.'
                
          case HCMD_STEP :                      // host command 'step-CPU'.
          {
            break;
          }                                     // host command 'step-CPU'.
            
          case HCMD_SETIR :                     // host command 'set-IR'.
          {
            pFromHost->cIRValueH = ((ascii_nibble_to_binary(gpcSerialFromHost[2])) << 4) + 
                                     ascii_nibble_to_binary(gpcSerialFromHost[3]);     
            pFromHost->cIRValueL = ((ascii_nibble_to_binary(gpcSerialFromHost[4])) << 4) + 
                                     ascii_nibble_to_binary(gpcSerialFromHost[5]);            
            break;
          }                                     // host command 'set-IR'.
            
          case HCMD_GETALLREG :                 // host command 'get-all-registers'.
          {
            gdRoundTrip++;                      // expected reply to host is pending.
            break;
          }                                     // host command 'get-all-registers'.
        
          default :                             // default.
          {
            if (ERROR_BOOT) mbed_reset(); else
            error("\n\r processIncomingSerial : ERROR unrecognised command from host. \n\r");
            break;
          }                                     // default.
        }                                       // command dependency.
        
//----
                   
        qFromHost.put(pFromHost);               // send out for processing.    
        gcNewCommand = 0;                       // don't execute until next new command.
      }                                         // execute once per new command.
      Thread::wait(THREAD_0_WAIT);              // multitasking.  
    }                                           // processIncomingSerial
/*----------------------------------------------//----------------------------*/
    void processOutgoingSerial(void)            // prepare/transmit data to host.
    {
      int dLoop;                                // loop index.

      gqToHostEvent = qToHost.get(1);           // check for reply back to host.
          
                                                // if new reply to host:
      if (gqToHostEvent.status == osEventMessage) 
      {
                                                // bring it in from the queue.
        gpToHost = (tToHost *) gqToHostEvent.value.p;
        if (!gpToHost)                         // failure detection.
        {
          if (ERROR_BOOT) mbed_reset(); else
          error("\n\r processOutgoingSerial : FATAL NULL gpToHost pointer. \n\r"); 
        }
          
                                                // clear outgoing buffer.
        for (dLoop = 0; dLoop < SER_BYTES; dLoop++) gpcSerialToHost[dLoop] = 0;
                                                                                                           
        switch(gpToHost->cCommand)              // the from-host command was looped to here.
        {
          case HCMD_SETREG :                    // host command 'set register'.
          {
            break;
          }                                     // host command 'set register'.
        
          case HCMD_GETREG :                    // host command 'get register'.
          {
            gpcSerialToHost[0] = binary_to_ascii_nibble(  gpToHost->cCommand);
            gpcSerialToHost[1] = binary_to_ascii_nibble(  gpToHost->cRegisterID);
            gpcSerialToHost[2] = binary_to_ascii_nibble(((gpToHost->cRegisterValue) >> 4) & 0x0F);
            gpcSerialToHost[3] = binary_to_ascii_nibble(((gpToHost->cRegisterValue) >> 0) & 0x0F);
            gpcSerialToHost[4] = '\n';          // signals end of transfer.
             
                                                // transmit to the host.
            serial.writeBlock((uint8_t *) gpcSerialToHost, (uint16_t) SER_BYTES);        
            gdRoundTrip--;                      // expected reply sent to host.       
            break;
          }                                     // host command 'get register'.
                 
          case HCMD_SETMM :                     // host command 'set main-memory.'
          {
            break;
          }                                     // host command 'set main-memory.'
               
          case HCMD_GETMM :                     // host command 'get main-memory.'
          {
            gpcSerialToHost[0] = binary_to_ascii_nibble(  gpToHost->cCommand);
            gpcSerialToHost[1] = binary_to_ascii_nibble(((gpToHost->cMMaddress) >> 4) & 0x0F);
            gpcSerialToHost[2] = binary_to_ascii_nibble(((gpToHost->cMMaddress) >> 0) & 0x0F);
            gpcSerialToHost[3] = binary_to_ascii_nibble(((gpToHost->cMMdataH  ) >> 4) & 0x0F);
            gpcSerialToHost[4] = binary_to_ascii_nibble(((gpToHost->cMMdataH  ) >> 0) & 0x0F);
            gpcSerialToHost[5] = binary_to_ascii_nibble(((gpToHost->cMMdataL  ) >> 4) & 0x0F);
            gpcSerialToHost[6] = binary_to_ascii_nibble(((gpToHost->cMMdataL  ) >> 0) & 0x0F);   
            gpcSerialToHost[7] = '\n';          // signals end of transfer.    
             
                                                // transmit to the host.
            serial.writeBlock((uint8_t *) gpcSerialToHost, (uint16_t) SER_BYTES);    
            gdRoundTrip--;                      // expected reply sent to host.        
            break;
          }                                     // host command 'get main-memory.'
                
          case HCMD_STEP :                      // host command 'step-CPU'.
          {
            break;
          }                                     // host command 'step-CPU'.
            
          case HCMD_SETIR :                     // host command 'set-IR'.
          {
            break;
          }                                     // host command 'set-IR'.
            
          case HCMD_GETALLREG :                 // host command 'get-all-registers'.
          {
            gpcSerialToHost[ 0] = binary_to_ascii_nibble(  gpToHost->cCommand);
            gpcSerialToHost[ 1] = binary_to_ascii_nibble(((gpToHost->cReg0) >> 4) & 0x0F);
            gpcSerialToHost[ 2] = binary_to_ascii_nibble(((gpToHost->cReg0) >> 0) & 0x0F);
            gpcSerialToHost[ 3] = binary_to_ascii_nibble(((gpToHost->cReg1) >> 4) & 0x0F);
            gpcSerialToHost[ 4] = binary_to_ascii_nibble(((gpToHost->cReg1) >> 0) & 0x0F);
            gpcSerialToHost[ 5] = binary_to_ascii_nibble(((gpToHost->cReg2) >> 4) & 0x0F);
            gpcSerialToHost[ 6] = binary_to_ascii_nibble(((gpToHost->cReg2) >> 0) & 0x0F);
            gpcSerialToHost[ 7] = binary_to_ascii_nibble(((gpToHost->cReg3) >> 4) & 0x0F);
            gpcSerialToHost[ 8] = binary_to_ascii_nibble(((gpToHost->cReg3) >> 0) & 0x0F);
            gpcSerialToHost[ 9] = binary_to_ascii_nibble(((gpToHost->cPC)   >> 4) & 0x0F);
            gpcSerialToHost[10] = binary_to_ascii_nibble(((gpToHost->cPC)   >> 0) & 0x0F);
            gpcSerialToHost[11] = binary_to_ascii_nibble(((gpToHost->cIRH)  >> 4) & 0x0F);
            gpcSerialToHost[12] = binary_to_ascii_nibble(((gpToHost->cIRH)  >> 0) & 0x0F);          
            gpcSerialToHost[13] = binary_to_ascii_nibble(((gpToHost->cIRL)  >> 4) & 0x0F);
            gpcSerialToHost[14] = binary_to_ascii_nibble(((gpToHost->cIRL)  >> 0) & 0x0F);                 
            gpcSerialToHost[15] = '\n';         // signals end of transfer.         
          
                                                // transmit to the host.
            serial.writeBlock((uint8_t *) gpcSerialToHost, (uint16_t) SER_BYTES);        
            gdRoundTrip--;                      // expected reply sent to host.  
            break;
          }                                     // host command 'get-all-registers'.
        
          default :
          {
            if (ERROR_BOOT) mbed_reset(); else
            error("\n\r processOutgoingSerial : ERROR unrecognised command from host. \n\r");
            break;
          }                                     // default.
        }                                       // switch(gpToHost->cCommand)                                         
                                                
        mPoolToHost.free(gpToHost);             // done with this queue entry.
        gpToHost = NULL;                        // clear pointer.
      }                                         // if new reply to host.     
    }                                           // processOutgoingSerial.
/*----------------------------------------------//----------------------------*/      
//  the pcSendBuffer and pcReceiveBuffer arrays are not used by this function,
//  but they are declared by this function, and their pointers are passed
//  down to the mmSPI_RTOS library for its use of them.
//  note- the prefix 'pc' means 'pointer of type character', not 'personal computer'.

    void SPIprocessingThread(void const *args)  // process host-commands via SPI.
    {
      char         pcSendBuffer   [SPI_BYTES];  // SPI send buffer.
      char         pcReceiveBuffer[SPI_BYTES];  // SPI receive buffer.
      char         pcRegisters    [SPI_BYTES];  // fetch-all-registers vector.
      int          dHeartbeat;                  // heartbeat counter.
      int          dMemoryRead;                 // read main-memory into this.
      int          dLoop;                       // loop index.
      osEvent      qFromHostEvent;              // incoming message event.
      tFromHost  * pFromHost;                   // message structure.
      tToHost    * gpToHost;                    // message structure.
      mmSPI_RTOS * pSPI;                        // SPI.
      
      pFromHost   = NULL;                       // NULL pointers.
      gpToHost    = NULL;
      pSPI        = NULL;
      dHeartbeat  = 0;                          // clear variables.
      dMemoryRead = 0;
      dLoop       = 0;
                                                // clear SPI vectors.
      for (dLoop = 0; dLoop < SPI_BYTES; dLoop++) pcSendBuffer   [dLoop] = 0;
      for (dLoop = 0; dLoop < SPI_BYTES; dLoop++) pcReceiveBuffer[dLoop] = 0;
 
      pSPI = new mmSPI_RTOS;                    // SPI allocation.      
      if (!pSPI)                                // failure detection.
      {
        if (ERROR_BOOT) mbed_reset(); else
        error("\n\r SPIprocessingThread : FATAL new error for pSPI. \n\r"); 
      }      
      
      pSPI->setSendBuffer   (pcSendBuffer);     // set SPI send buffer.
      pSPI->setReceiveBuffer(pcReceiveBuffer);  // set SPI receive buffer.
      pSPI->setNumberOfBytes(SPI_BYTES);        // set SPI number of bytes.
      pSPI->setSPIfrequency (SPI_HZ);           // set SPI clock frequency.
      
      while(1)                                  // thread loop.
      {
        qFromHostEvent = qFromHost.get(1);      // check for incoming host command.
        
                                                // if new host command:
        if (qFromHostEvent.status == osEventMessage) 
        {
                                                // bring it in from the queue.
          pFromHost = (tFromHost *) qFromHostEvent.value.p;
          if (!pFromHost)                       // failure detection.
          {
            if (ERROR_BOOT) mbed_reset(); else
            error("\n\r SPIprocessingThread : FATAL NULL pFromHost pointer. \n\r");  
          }
          
          switch(pFromHost->cCommand)           // host command decode.
          {
            case HCMD_SETREG :                  // set CPU register.
            {
              switch(pFromHost->cRegisterID)    // which register to write to.
              {
                case CPU_REG_0  : {pSPI->write_register(CPU_REG_0 , pFromHost->cRegisterValue); break;}
                case CPU_REG_1  : {pSPI->write_register(CPU_REG_1 , pFromHost->cRegisterValue); break;}
                case CPU_REG_2  : {pSPI->write_register(CPU_REG_2 , pFromHost->cRegisterValue); break;}
                case CPU_REG_3  : {pSPI->write_register(CPU_REG_3 , pFromHost->cRegisterValue); break;}
                case CPU_REG_PC : {pSPI->write_register(CPU_REG_PC, pFromHost->cRegisterValue); break;}
                default         : {break;}
              }                                 // which register to write to.
              break;
            }                                   // set CPU register.
            
            case HCMD_SETIR:                    // set instruction register.
            {
              pSPI->write_IR(pFromHost->cIRValueH, pFromHost->cIRValueL);
              break;
            }                                   // set instruction register.
            
            case HCMD_GETREG :                  // get CPU register.
            {
              gpToHost = mPoolToHost.alloc();   // allocate next pool entry.
              if (!gpToHost)                    // failure detection.
              {
                if (ERROR_BOOT) mbed_reset(); else
                error("\n\r SPIprocessingThread : FATAL malloc error for gpToHost. \n\r"); 
              }              
              
              clear_tToHost(gpToHost);          // initialize structure.
            
              switch(pFromHost->cRegisterID)    // which register to read from.
              {
                case CPU_REG_0  : {gpToHost->cRegisterValue = pSPI->read_register(CPU_REG_0 ); break;}
                case CPU_REG_1  : {gpToHost->cRegisterValue = pSPI->read_register(CPU_REG_1 ); break;}
                case CPU_REG_2  : {gpToHost->cRegisterValue = pSPI->read_register(CPU_REG_2 ); break;}
                case CPU_REG_3  : {gpToHost->cRegisterValue = pSPI->read_register(CPU_REG_3 ); break;}
                case CPU_REG_PC : {gpToHost->cRegisterValue = pSPI->read_register(CPU_REG_PC); break;}
                case CPU_IR_H   : {gpToHost->cRegisterValue = pSPI->read_register(CPU_IR_H  ); break;}
                case CPU_IR_L   : {gpToHost->cRegisterValue = pSPI->read_register(CPU_IR_L  ); break;}
                default         : {break;}    
              }                                 // which register to read from.
              
                                                // loop-back to host.
              gpToHost->cCommand    = pFromHost->cCommand;
              gpToHost->cRegisterID = pFromHost->cRegisterID;  
                          
              qToHost.put(gpToHost);            // send up for processing.    
              break; 
            }                                   // get CPU register.     
            
            case HCMD_GETALLREG :               // get all CPU registers.
            {
              gpToHost = mPoolToHost.alloc();   // allocate next pool entry.              
              if (!gpToHost)                    // failure detection.
              {
                if (ERROR_BOOT) mbed_reset(); else
                error("\n\r SPIprocessingThread : FATAL malloc error for gpToHost. \n\r");
              }              
              
              clear_tToHost(gpToHost);          // initialize structure.
              
                                                // tell SPI to return all reg content.
              pSPI->read_all_registers(pcRegisters);
              
              gpToHost->cReg0 = pcRegisters[6]; // transfer to outgoing structure.
              gpToHost->cReg1 = pcRegisters[5];
              gpToHost->cReg2 = pcRegisters[4];
              gpToHost->cReg3 = pcRegisters[3];
              gpToHost->cPC   = pcRegisters[2];
              gpToHost->cIRH  = pcRegisters[1];
              gpToHost->cIRL  = pcRegisters[0];
              
                                                // loop-back to host.
              gpToHost->cCommand = pFromHost->cCommand;
              qToHost.put(gpToHost);            // send up for processing. 
              
              break;
            }                                   // get all CPU registers.
            
            case HCMD_SETMM  :                  // do main-memory write.
            {
              pSPI->write_memory(pFromHost->cMMdataH, pFromHost->cMMdataL, pFromHost->cMMaddress);     
              break;
            }                                   // do main-memory write.                   
            
            case HCMD_GETMM  :                  // do main-memory read.
            {
              gpToHost = mPoolToHost.alloc();   // allocate next pool entry.   
              if (!gpToHost)                    // failure detection.
              {
                if (ERROR_BOOT) mbed_reset(); else
                error("\n\r SPIprocessingThread : FATAL malloc error for gpToHost. \n\r"); 
              }              
              
              clear_tToHost(gpToHost);          // initialize structure.
            
                                                // read from CPU memory.
              dMemoryRead = pSPI->read_memory(pFromHost->cMMaddress); 
              gpToHost->cMMdataH = (dMemoryRead >> 8) & 0xFF;
              gpToHost->cMMdataL = (dMemoryRead >> 0) & 0xFF;
              
                                                // loop-back to host.
              gpToHost->cCommand   = pFromHost->cCommand;
              gpToHost->cMMaddress = pFromHost->cMMaddress;
                      
              qToHost.put(gpToHost);            // send up for processing.
              break;
            }                                   // do main-memory read.
            
            case HCMD_STEP   :                  // step the CPU.
            {
              pSPI->step();
              break;
            }                                   // step the CPU.
            default          : break;
          }                                     // host command decode.            
          mPoolFromHost.free(pFromHost);        // done with this queue entry.
          pFromHost = NULL;                     // clear pointer.
        }                                       // if new host command.
        
        gulSPIclkCount = pSPI->SPIClockCount(); // propagate to global variable.
        gulCPUclkCount = pSPI->CPUClockCount(); // propagate to global variable.
 
                                                // thread heartbeat.
        dHeartbeat++; if (!(dHeartbeat % HB_MODULO)) led1 = !led1;
        queueWatchdogThread_1.put((int *) 0,1); // adds 1mS to wait.
        Thread::wait(THREAD_1_WAIT - 1);        // cooperative multitasking.      
      }                                         // thread loop.
    }                                           // SPIprocessingThread.
/*----------------------------------------------//----------------------------*/
    void diagnosticThread(void const *args)     // LCD notification & error check.
    {
                                                // track previous values.
      static unsigned long ulLastSPIclkCount = 1;      
      static unsigned long ulLastCPUclkCount = 1;
      int                  dHeartbeat;          // heartbeat counter.
      
      dHeartbeat = 0;                           // initialize.
            
      while(1)                                  // thread loop.
      {
                                                // if message round trip 
                                                // count not consistent.
        if (gdRoundTrip > 1025 || gdRoundTrip < 1024)
        {
          if (ERROR_BOOT) mbed_reset(); else
          error("\n\r diagnosticThread : ERROR serial reply underflow. \n\r");         
        }
        
                                                // update LCD only if a display
                                                // value has changed.  anti-blink,
                                                // save a bit of power.
        if (gulSPIclkCount != ulLastSPIclkCount ||
            gulCPUclkCount != ulLastCPUclkCount   )
        {
          lcd.cls();                            // clear LCD display.
          LCD1;                                 // lcd line 1.
          lcd.printf(" RTOS CLASS PROJECT");
        
          LCD2;                                 // lcd line 2.
          lcd.printf(" %11lu = SPI clocks",gulSPIclkCount);
        
          LCD3;                                 // lcd line 3.
          lcd.printf(" %11lu = CPU clocks",gulCPUclkCount);
        }
        
        ulLastSPIclkCount = gulSPIclkCount;     // pipeline.
        ulLastCPUclkCount = gulCPUclkCount;     // pipeline.
       
        dHeartbeat++;                           // thread heartbeat.
        if (!(dHeartbeat % HB_MODULO)) led2 = !led2;
        queueWatchdogThread_2.put((int *) 0,1); // adds 1mS to wait.
        Thread::wait(THREAD_2_WAIT - 1);        // multitasking.
      }                                         // thread loop.
    }                                           // diagnosticThread.
/*----------------------------------------------//----------------------------*/
                                                // this is its own watchdog.

    void watchdogThread(void const *args)       // overall watchdog.
    {
      int           dHeartbeat;                 // heartbeat counter.
      Watchdog_RTOS watchdog;                   // the one and only watchdog.
      
      dHeartbeat = 0;                           // initialize.

      watchdog.kick(WATCHDOG_S);                // initialize watchdog.
    
      while (1)                                 // thread loop.
      {
                                                // all other threads report-in.
                                                // blocking wait on all of them.
        queueWatchdogThread_0.get(osWaitForever); 
        queueWatchdogThread_1.get(osWaitForever);
        queueWatchdogThread_2.get(osWaitForever);  

        watchdog.kick();                        // reset watchdog timer.
        
        dHeartbeat++;                           // thread heartbeat.
        if (!(dHeartbeat % HB_MODULO)) led3 = !led3;
        Thread::wait(THREAD_3_WAIT);            // multitasking.
      }                                         // thread loop.
    }
/*----------------------------------------------//----------------------------*/
    char ascii_nibble_to_binary(char cAscii)    // ascii nibble -> binary.
    {
      char cBinary;                             // converted value.
      
      switch(cAscii)
      {
        case 'F' : {cBinary = 15; break;}
        case 'E' : {cBinary = 14; break;}
        case 'D' : {cBinary = 13; break;}
        case 'C' : {cBinary = 12; break;}
        case 'B' : {cBinary = 11; break;}
        case 'A' : {cBinary = 10; break;}
        case 'f' : {cBinary = 15; break;}
        case 'e' : {cBinary = 14; break;}
        case 'd' : {cBinary = 13; break;}
        case 'c' : {cBinary = 12; break;}
        case 'b' : {cBinary = 11; break;}
        case 'a' : {cBinary = 10; break;}
        case '9' : {cBinary =  9; break;}
        case '8' : {cBinary =  8; break;}
        case '7' : {cBinary =  7; break;}
        case '6' : {cBinary =  6; break;}
        case '5' : {cBinary =  5; break;}
        case '4' : {cBinary =  4; break;}
        case '3' : {cBinary =  3; break;}
        case '2' : {cBinary =  2; break;}
        case '1' : {cBinary =  1; break;}
        case '0' : {cBinary =  0; break;}
        default  : {cBinary =  0; break;}
      }                                         // switch(cAscii).
      return(cBinary);                          // return the binary.
    }                                           // ascii_nibble_to_binary.
/*----------------------------------------------//----------------------------*/
    char binary_to_ascii_nibble(char cBinary)   // binary -> ascii nibble.
    {
      char cAscii;                              // converted value.
      
      switch(cBinary)
      {
        case 15 : {cAscii = 'F'; break;}
        case 14 : {cAscii = 'E'; break;}
        case 13 : {cAscii = 'D'; break;}
        case 12 : {cAscii = 'C'; break;}
        case 11 : {cAscii = 'B'; break;}
        case 10 : {cAscii = 'A'; break;}
        case  9 : {cAscii = '9'; break;}
        case  8 : {cAscii = '8'; break;}
        case  7 : {cAscii = '7'; break;}
        case  6 : {cAscii = '6'; break;}
        case  5 : {cAscii = '5'; break;}
        case  4 : {cAscii = '4'; break;}
        case  3 : {cAscii = '3'; break;}
        case  2 : {cAscii = '2'; break;}
        case  1 : {cAscii = '1'; break;}
        case  0 : {cAscii = '0'; break;}
        default : {cAscii = '0'; break;}
      }                                         // switch(cBinary).
      return(cAscii);                           // return the binary.
    }                                           // binary_to_ascii_nibble.
/*----------------------------------------------//----------------------------*/
    void clear_tFromHost(tFromHost * ptFromHost)// clear structure.
    {
      ptFromHost->cCommand       = 0x00;
      ptFromHost->cRegisterID    = 0x00;
      ptFromHost->cRegisterValue = 0x00;
      ptFromHost->cIRValueH      = 0x00;
      ptFromHost->cIRValueL      = 0x00;
      ptFromHost->cMMaddress     = 0x00;
      ptFromHost->cMMdataH       = 0x00;
      ptFromHost->cMMdataL       = 0x00;
    }                                           // clear_tFromHost.
/*----------------------------------------------//----------------------------*/
    void clear_tToHost(tToHost * ptToHost)      // clear structure.
    {
      ptToHost->cCommand       = 0x00;
      ptToHost->cRegisterID    = 0x00;
      ptToHost->cRegisterValue = 0x00;
      ptToHost->cMMaddress     = 0x00;
      ptToHost->cMMdataH       = 0x00;
      ptToHost->cMMdataL       = 0x00;
    }                                           // clear_tToHost.
/*----------------------------------------------//----------------------------*/