This is an involuntary fork, created because the repository would not update mmSPI. SPI library used to communicate with an altera development board attached to four zigbee-header pins.

Dependents:   Embedded_RTOS_Project

Fork of mmSPI by Mike Moore

mmSPI.cpp

Committer:
gatedClock
Date:
2013-09-17
Revision:
36:32cdc295f859
Parent:
35:6152c9709697

File content as of revision 36:32cdc295f859:

/*----------------------------------------------//------------------------------
    student   : m-moore
    email     : gated.clock@gmail.com
    class     : embedded RTOS
    directory : USB_device_project/mmSPI
    file      : mmSPI.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
----description---------------------------------//------------------------------
    this library provides the low-level SPI data and clock signaling 
    for communication with the altera development board, via four i/o
    pins available on the mbed development board's zigbee header.
    
    this library also provides higher-level calls to send/receive register
    and main-memory data to/from the CPU implemented in the altera.
    
    the SPI clock and the CPU clock are both built by this library.
    
    the SPI clock (*pSCLK) is generated with quarter-phase resolution,
    although at the moment, only half-phase resolution is being used.
    
    within the FPGA, the SPI scan registers are called 'shadow registers'
    because they parallel load-to/store-from the CPU registers.  
    
    character vectors pcSend and pcReceive are used to scan-in and scan-out
    to/from the altera shadow registers.  note that the prefix 'pc' means
    'pointer-of-type-character' and not 'personal-computer'.
    
    the shadow registers in the altera are arranged as follows:
    
 <-- MISO (altera-out to mbed-in)
     ^
     |
   scan_08  U14_spi_control = pcSend/pcReceive[7]
   scan_08  U08_shadowR0    = pcSend/pcReceive[6]
   scan_08  U09_shadowR1    = pcSend/pcReceive[5]
   scan_08  U10_shadowR2    = pcSend/pcReceive[4]
   scan_08  U11_shadowR3    = pcSend/pcReceive[3]
   scan_08  U12_shadowPC    = pcSend/pcReceive[2]
   scan_16  U13_shadowIR    = pcSend/pcReceive[1:0]
     ^
     |
 --> MOSI (altera-in from mbed-out)
 
    when U14_spi_control<1> is high, the mbed takes over CPU operation.
    this means that the CPU instruction decoder decodes U13_shadowIR
    rather than the CPU's actual IR.
    
    when U14_spi_control<2> is high, the mbed writes into the IR.
    this means that the CPU instruction decoder load-enable outputs
    are gated-off so that the mbed may write into the instrucition
    register without immediate consequence in the CPU.  writing to
    the IR this way is more for the ability to show that it can be done,
    rather than any likely useful purpose.
    
    debug/test:
    this library was debugged by using the altera FPGA logic analyzer
    known as 'signal tap'.  the main program uses all of the memory and
    register access calls, which were used as an informal unit test.
-----includes-----------------------------------//----------------------------*/
    #include "mmSPI.h"
//==============================================//==============================
    mmSPI::mmSPI()                              // constructor.
    {
      allocations();                            // object allocations.
      
      *pSCLK        = 0;                        // initialize.
      *pCPUclk      = 0;                        // initialize.
      ulSPIclkCount = 0;                        // initialize.
      ulCPUclkCount = 0;                        // initialize.
    }                                           // constructor.
//----------------------------------------------//------------------------------    
    mmSPI::~mmSPI()                             // destructor.
    {
                                                // deallocations.
      if (pMOSI)   {delete pMOSI;   pMOSI   = NULL;}
      if (pMISO)   {delete pMISO;   pMISO   = NULL;}
      if (pSCLK)   {delete pSCLK;   pSCLK   = NULL;}
      if (pCPUclk) {delete pCPUclk; pCPUclk = NULL;}
    }                                           // destructor.
//----------------------------------------------//------------------------------
    void mmSPI::allocations(void)               // object allocations.
    {
      pMOSI   = new DigitalOut(mmSPI_MOSI);     // SPI MOSI pin object.
      if (!pMOSI) error("\n\r mmSPI::allocations : FATAL malloc error for pMOSI. \n\r"); 
      
      pMISO   = new DigitalOut(mmSPI_MISO);     // SPI MISO pin object.
      if (!pMISO) error("\n\r mmSPI::allocations : FATAL malloc error for pMISO. \n\r"); 
    
      pSCLK   = new DigitalOut(mmSPI_SCLK);     // SPI SCLK pin object.
      if (!pSCLK) error("\n\r mmSPI::allocations : FATAL malloc error for pSCLK. \n\r"); 
      
      pCPUclk = new DigitalOut(mmCPU_CLK);      // SPI SCLK pin object.
      if (!pCPUclk) error("\n\r mmSPI::allocations : FATAL malloc error for pCPUclk. \n\r");
    }                                           // allocations.
//----------------------------------------------//------------------------------    
    void mmSPI::setSPIfrequency(float fFreq)    // set SPI clock frequency.
    {
      fSPIfreq = fFreq;                         // promote to object scope.
      if (fSPIfreq < .05)                       // don't get near divide-by-zero.
      error("\n\r mmSPI::setSPIfrequency : FATAL SPI frequency set too low. \n\r"); 
      fSPIquarterP = (1 / fSPIfreq) / 4;        // figure quarter-cycle period.
    }
//----------------------------------------------//------------------------------  
                                                // obtain SPI send buffer pointer.  
    void mmSPI::setSendBuffer(char * pcSendBuffer)
    {
      pcSend = pcSendBuffer;                    // promote to object scope.
    }                                           // setSendBuffer.
//----------------------------------------------//------------------------------    
                                                // obtain SPI receive buffer pointer.  
    void mmSPI::setReceiveBuffer(char * pcReceiveBuffer)
    {
      pcReceive = pcReceiveBuffer;              // promote to object scope.
    }                                           // setReceiveBuffer.
//----------------------------------------------//------------------------------
                                                // obtain number of SPI bytes.
    void mmSPI::setNumberOfBytes(int dNumberOfBytes)
    {
      dNumBytes = dNumberOfBytes;               // promote to object scope.
    }                                           // setNumberOfBytes.
//----------------------------------------------//------------------------------
/*
    this method has four 'components' which may be switched on/off by the caller:
    1. cPreCPU  = 1 means cycle the altera CPU clock once.
    2. cPreSPI  = 1 means cycle the        SPI clock once.
    3. cScan    = 1 means run a full SPI scan-in/scan-out cycle.
    4. cPostCPU = 1 means cycle the alter CPU clock once, then the SPI clock once.
    
    in the altera, upon the falling edge of any CPU clock, a load-enable is
    asserted into the scan registers, such that at the next rising edge of
    the SPI clock, the scan registers perform a parallel-load of the CPU
    registers, so that that data can subsequently be scanned back into the mbed
    via a full SPI scan-in cycle.  (this load-enable is turned-off upon the first
    falling edge of the subsequent SPI clock.)
    therefore, the regular input switches to this method are (1,1,1,0).  
    this means that the altera CPU clock will cycle,
    and the result of that CPU operation will be parallel-loaded into the
    shadow registers for scan into the mbed on the subsequent full-scan cycle.
    
    a finer level of sequencing these 'components' is needed by the 'step' method,
    which is why they are under four-input-parameter control.                                                
 */
    void mmSPI::transceive_vector(char cPreCPU, char cPreSPI, char cScan, char cPostCPU)
    {      
      int  dClear;                              // clear-loop index.
      int  dIndex;                              // total scan index.
      int  dMosiByteIndex;                      // SPI-MOSI byte index.
      int  dMosiBitIndex;                       // SPI-MOSI bit  index.
      int  dMisoByteIndex;                      // SPI-MISO byte index.
      int  dMisoBitIndex;                       // SPI-MISO bit  index.
      
                                                // initializations.  these are needed.
      dIndex         = (dNumBytes * 8) - 1;     // calculate scan count.
      dMosiByteIndex =  dIndex / 8;             // calculate current byte index.
      dMosiBitIndex  =  dIndex % 8;             // calculate current bit  iindex.
      
                                                // clear the SPI receive vector.
      for (dClear = 0; dClear < dNumBytes; dClear++) pcReceive[dClear] = 0;
//---
      if (cPreCPU)                              // if pre-CPU clock.
      {
        *pCPUclk = 1;                           // pulse the CPU clock.
        wait(fSPIquarterP); 
        wait(fSPIquarterP);     
        *pCPUclk = 0;  
        wait(fSPIquarterP); 
        wait(fSPIquarterP); 
        ulCPUclkCount++;
      }                                         // if pre-CPU clock.
 //---     
      if (cPreSPI)                              // if pre-SPI pulse.
      {
        *pSCLK = 1;                             // pulse the SPI clock for parallel load.              
        wait(fSPIquarterP); 
        wait(fSPIquarterP); 
        *pSCLK = 0;
        ulSPIclkCount++;
      }                                         // if pre-SPI pulse.
 //---     
      if (cScan)                                // if cScan.
      {
                                                // pre-assert MOSI.
        *pMOSI = ((pcSend[dMosiByteIndex]) >> dMosiBitIndex) & 1;
        wait(fSPIquarterP); 
        wait(fSPIquarterP); 
      
      
                                                // main SPI scan loop.
        for (dIndex = (dNumBytes * 8) - 1; dIndex >= 0; dIndex--)
        {
          dMisoByteIndex = dIndex / 8;
          dMisoBitIndex  = dIndex % 8;
          pcReceive[dMisoByteIndex] = pcReceive[dMisoByteIndex] | (*pMISO << dMisoBitIndex);      
          *pSCLK = 1;             
          wait(fSPIquarterP); 
          wait(fSPIquarterP); 
          *pSCLK = 0;    
          if (dIndex < 0) dIndex = 0;
          dMosiByteIndex = (dIndex - 1) / 8;
          dMosiBitIndex  = (dIndex - 1) % 8;
          *pMOSI = ((pcSend[dMosiByteIndex]) >> dMosiBitIndex) & 1;
          wait(fSPIquarterP); 
          wait(fSPIquarterP);
          ulSPIclkCount++;
        }                                       // main SPI scan loop.
      }                                         // if cScan.
 //---     
      if (cPostCPU)                             // if post-CPU pulse.
      {
        *pCPUclk = 1;                           // pulse the CPU clock.
        wait(fSPIquarterP); 
        wait(fSPIquarterP);     
        *pCPUclk = 0;  
        wait(fSPIquarterP); 
        wait(fSPIquarterP);   
        ulCPUclkCount++;    
        
        *pSCLK = 1;                             // clear-out the SPI parallel-load enable.        
        wait(fSPIquarterP); 
        wait(fSPIquarterP); 
        *pSCLK = 0;        
        ulSPIclkCount++;
      }                                         // if post-CPU pulse.
    }                                           // transceive_vector.
//----------------------------------------------//------------------------------
/*
   cRegister  ->  CPU_register  
      0            R0
      1            R1
      2            R2
      3            R3
      4            PC
      5         <use write_IR instead>
      6         <nothing>
      7         <nothing>
      
    in order to write to a CPU register, pcSend[7] is set to 0x02,
    which informs the CPU instruction decoder to decode what we are
    here scanning into the shadow instruction-register, rather than
    the CPU instruction decoder decoding the regular instruction-register.
    here, we set-up pcSend to scan the instruction 
    'load Rx with immediate data yy', and this is executed on the
    subsequent CPU clock.  
    
    said another way, this method writes a value into a CPU register
    by effectively taking over the CPU and doing a write-register-immediate.
*/

                                                // write to a CPU register.
    void mmSPI::write_register(char cRegister, char cValue)
    {     
      clear_transmit_vector();                  // clear transmit vector.
      
      pcSend[7] = 0x02;                         // mbed sends a command.
      
                                                // align into instruction word.
      pcSend[1] = ((cRegister & 0x07) << 2) | 0xA0;
      pcSend[0] = cValue & 0xFF;                // immediate value to i.w.
 
      transceive_vector(1,1,1,0);               // transmit command.
 
      clear_transmit_vector();                  // clear transmit vector.
    }                                           // write_register.
//----------------------------------------------//------------------------------  
/*  
    for writing to the instruction-register, this method is used.
    for reading from the instruction-register, the 'regular' 
    read_register method is used, once for each of the two IR data bytes.
    
    unlike method 'write_register', this method works by gating-off the
    outputs of the CPU instruction decoder, and having the instruction-register
    content loaded in from the shadow instruction-register.
*/
                                                // write instruction register.
    void mmSPI::write_IR(char cValueH, char cValueL)
    {
      clear_transmit_vector();                  // clear transmit vector.
      
      pcSend[7] = 0x06;                         // mbed sends a command.
      
      pcSend[1] = (cValueH & 0xFF);             // load IR shadow with new IR content.
      pcSend[0] = (cValueL & 0xFF);
 
      transceive_vector(1,1,1,0);               // transmit command.
 
      clear_transmit_vector();                  // clear transmit vector.    
    }                                           // write instruction register.
//----------------------------------------------//------------------------------
/*
  cRegister  ->  CPU_register   pcReceive
      0      ->    R0             [6]
      1      ->    R1             [5]
      2      ->    R2             [4]
      3      ->    R3             [3]
      4      ->    PC             [2]
      5      ->    IR-H           [1]
      6      ->    IR-L           [0]
      7      ->  <never-use>
      
    this method works by taking over the instruction decoder as described above,
    but not issuing any instruction, but the scan registers will parallel-load
    all of the CPU register data and scan them into pcReceive, where they are
    picked up below.
*/

    char mmSPI::read_register(char cRegister)   // return content of a CPU register.
    { 
      clear_transmit_vector();                  // clear transmit vector.
      
      pcSend[7] = 0x02;                         // suppress cpu operation.

      transceive_vector(1,1,1,0);               // snap & scan-out reg contents.
      
      return (pcReceive[6 - cRegister]);        // return the particular reg value.
    }                                           // read_register.
//----------------------------------------------//------------------------------
//----------------------------------------------//------------------------------
/*
  since the Tk UI often wants to obtain the content of all of the CPU registers,
  this method may be used to obtain all of them at once, speading up the UI 
  read cycle.
      
  pRegisters[6] <- pcReceive[6] = R0    
  pRegisters[5] <- pcReceive[5] = R1
  pRegisters[4] <- pcReceive[4] = R2
  pRegisters[3] <- pcReceive[3] = R3
  pRegisters[2] <- pcReceive[2] = PC
  pRegisters[1] <- pcReceive[1] = IR-H
  pRegisters[0] <- pcReceive[0] = IR-L  
*/

    void mmSPI::read_all_registers(char * pcRegisters) 
    { 
      int dLoop;                                // loop index.
      
      clear_transmit_vector();                  // clear transmit vector.
      
      pcSend[7] = 0x02;                         // suppress cpu operation.

      transceive_vector(1,1,1,0);               // snap & scan-out reg contents.
      
                                                // propagate register data.
      for (dLoop = 0; dLoop < 7; dLoop++) pcRegisters[dLoop] = pcReceive[dLoop];
      
    }                                           // read_all_registers.
//----------------------------------------------//------------------------------
/*
    this method works by taking over the instruction decoder, and issuing
    the CPU commands which load the MM address/data into {R3,R2,R1}
    followed by issuing a write-pulse.
*/
                                                // write a word to main-memory.
    void mmSPI::write_memory(char cHData, char cLdata, char cAddress)
    {
      clear_transmit_vector();                  // clear transmit vector.
                    
      write_register(0x03,cAddress);            // R3 <- address.
      write_register(0x02,cHData);              // R2 <- high-data.
      write_register(0x01,cLdata);              // R1 <- low-data.
  
      pcSend[7] = 0x02;                         // mbed sends command.
      pcSend[1] = 0x02;                         // write-enable high.
      pcSend[0] = 0x00;                         // remainder of instruction.
      transceive_vector(1,1,1,0);
      
      pcSend[7] = 0x02;                         // mbed sends command.
      pcSend[1] = 0x00;                         // write-enable low.
      pcSend[0] = 0x00;                         // remainder of instruction.
      transceive_vector(1,1,1,0);   
                           
      clear_transmit_vector();                  // clear transmit vector. 
    }                                           // write_memory.
//----------------------------------------------//------------------------------
/*
    this command works by taking over the instruction decoder, loading
    R3 with the MM address, then issuing load-from-MM commands so that
    R2,R1 are loaded with MM[R3], and then reading the result from
    R1,R2.
*/
                                                // fetch a word from main memory.
    unsigned int mmSPI::read_memory(char cAddress)
    { 
      unsigned int udMemoryContent;             // return variable.
      char         cHData;                      // returned data-high.
      char         cLData;                      // returned data-low.
                               
      clear_transmit_vector();                  // clear transmit vector.
            
      write_register(0x03,cAddress);            // R3 <= address.
      
      pcSend[7] = 0x02;                         // mbed sends command.
      pcSend[1] = 0xC8;                         // R2 <- MM[R3]
      pcSend[0] = 0x00;
      transceive_vector(1,1,1,0);               // send command. 
      
      pcSend[7] = 0x02;                         // mbed sends command.
      pcSend[1] = 0xC4;                         // R1 <- MM[R3]
      pcSend[0] = 0x00;
      transceive_vector(1,1,1,0);               // send command.      
           
      cHData = read_register(0x02);             // obtain MM high-data-byte.
      cLData = read_register(0x01);             // obtain MM low-data-byte.
                
      udMemoryContent = (cHData << 8) + cLData; // build the memory word.
                         
      clear_transmit_vector();                  // clear transmit vector.
    
      return udMemoryContent;                   // return the memory word.
    }                                           // read_memory.
//----------------------------------------------//------------------------------
    void mmSPI::step(void)                      // step the CPU.
    {      
      clear_transmit_vector();                  // clear transmit vector.
      transceive_vector(0,0,1,0);               // enable CPU mode.
      transceive_vector(0,0,0,1);               // advance CPU, clear shadow-load.
      pcSend[7] = 0x02;                         // ready to disable CPU mode.
      transceive_vector(0,0,1,0);               // disable CPU mode.
    }                                           // step.
//----------------------------------------------//------------------------------
    void mmSPI::clear_transmit_vector(void)     // fill transmit buffer with 0.
    {
      int dLoop;
      for (dLoop = 0; dLoop < dNumBytes; dLoop++) pcSend[dLoop] = 0x00;
    }                                           // clear_transmit_vector.
//----------------------------------------------//------------------------------
                                                // getters.
    unsigned long mmSPI::SPIClockCount() {return ulSPIclkCount;}
    unsigned long mmSPI::CPUClockCount() {return ulCPUclkCount;}
//----------------------------------------------//------------------------------