USB Device Programming class project. This project allows a Python/Tk program running on a PC host to monitor/control a test-CPU programmed into an altera development board.

Dependencies:   C12832_lcd USBDevice mbed-rtos mbed mmSPI

Revision:
2:08655e2bb776
Parent:
0:9a314675a67d
Child:
9:81726c95be74
--- a/main.cpp	Sun Sep 01 02:16:36 2013 +0000
+++ b/main.cpp	Sun Sep 01 02:29:14 2013 +0000
@@ -1,12 +1,664 @@
-#include "mbed.h"
+/*----------------------------------------------//------------------------------
+    student   : m-moore
+    email     : gated.clock@gmail.com
+    class     : usb device drivers
+    directory : USB_device_project
+    file      : main.cpp
+    date      : september 3, 2013.
+----copyright-----------------------------------//------------------------------   
+    licensed for personal and academic use.
+    commercial use must be approved by the account-holder of
+    gated.clock@gmail.com
+----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 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 library generates non-overlapping SPI and CPU clocks.
+    7. background diagnostic thread provides LCD & LED updates.
 
-DigitalOut myled(LED1);
+    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 reply-to-host underflow (should never turn on).
+    
+    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 commands.
+    3. mmSPI object: given commands/data passed from caller,
+       map them into SPI outgoing vectors and scan them into the FPGA.
+    4. mmSPI 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.
+    
+    
+    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.h"                          // SPI.
+//---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 SPI_BYTES       8                   // number of SPI bytes. 
+    #define SPI_HZ     100000                   // SPI frequency in Hz.
+    #define SER_BYTES       8                   // 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 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 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.
+//--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.      
+    };
+    MemoryPool<tToHost, POOL_LEN> mPoolToHost;
+    Queue     <tToHost, POOL_LEN> qToHost;
+//--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.   
+    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.
+      dHeartbeat     =    0;                    // initialize local.
+      dLoop          =    0;                    // initialize local.
+
+                                                // 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);
+ 
+      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;
+        Thread::wait(THREAD_0_WAIT);            // 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'.
+*/
 
-int main() {
-    while(1) {
-        myled = 1;
-        wait(0.2);
-        myled = 0;
-        wait(0.2);
-    }
-}
+    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) 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]);
+                    
+                                                // host requests register access.
+        if (pFromHost->cCommand == HCMD_SETREG || pFromHost->cCommand == HCMD_GETREG)
+        {
+          pFromHost->cRegisterID    =   ascii_nibble_to_binary(gpcSerialFromHost[1]);
+          pFromHost->cRegisterValue = ((ascii_nibble_to_binary(gpcSerialFromHost[2])) << 4) + 
+                                        ascii_nibble_to_binary(gpcSerialFromHost[3]);                
+        }
+        
+                                                
+        if (pFromHost->cCommand == HCMD_SETIR)  // host requests IR write.
+        {
+          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]);         
+        }
+        
+        
+                                                // host requests main-memory access.
+        if (pFromHost->cCommand == HCMD_SETMM || pFromHost->cCommand == HCMD_GETMM)
+        {
+          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]);        
+        }                                       // host requests main-memory access.
+        
+        if (pFromHost->cCommand == HCMD_GETREG || pFromHost->cCommand == HCMD_GETMM)
+        gdRoundTrip++;                          // expected reply to host is pending.           
+        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) error("\n\r processOutgoingSerial : FATAL null gpToHost pointer. \n\r"); 
+          
+                                                // clear outgoing buffer.
+        for (dLoop = 0; dLoop < SER_BYTES; dLoop++) gpcSerialToHost[dLoop] = 0;
+                                                           
+                                                // the commands from the host were
+                                                // looped-back into the to-host struct,
+                                                // make use of them here.
+                                           
+        if (gpToHost->cCommand == HCMD_GETREG)  // reading from a 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.                           
+        }                                  
+                                                         
+        if (gpToHost->cCommand == HCMD_GETMM)  // reading from 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.
+        }           
+        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 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.
+      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     * 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;                         // SPI allocation.
+      if (!pSPI)  error("\n\r SPIprocessingThread : FATAL malloc 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) 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) 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_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) 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;
+        Thread::wait(THREAD_1_WAIT);            // cooperative multitasking.      
+      }                                         // thread loop.
+    }                                           // SPIprocessingThread.
+/*----------------------------------------------//----------------------------*/
+    void diagnosticThread(void const *args)     // LCD and LED notifications.
+    {
+      int dHeartbeat;                           // heartbeat counter.
+    
+      dHeartbeat = 0;                           // initialize.
+      led3       = 0;                           // initialize.
+      
+      while(1)                                  // thread loop.
+      {
+                                                // if message round trip 
+                                                // count not consistent.
+        if (gdRoundTrip > 1025 || gdRoundTrip < 1024) led3 = 1;
+        
+        lcd.cls();                              // clear LCD display.
+        LCD1;                                   // lcd line 1.
+        lcd.printf(" USB DEV CLASS PROJECT");
+        
+        LCD2;                                   // lcd line 2.
+        lcd.printf(" %11lu = SPI clocks",gulSPIclkCount);
+        
+        LCD3;                                   // lcd line 3.
+        lcd.printf(" %11lu = CPU clocks",gulCPUclkCount);
+       
+        dHeartbeat++;                           // thread heartbeat.
+        if (!(dHeartbeat % HB_MODULO)) led2 = !led2;
+        Thread::wait(THREAD_2_WAIT);            // multitasking.
+      }                                         // thread loop.
+    }                                           // diagnosticThread.
+/*----------------------------------------------//----------------------------*/
+    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.
+/*----------------------------------------------//----------------------------*/
\ No newline at end of file