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
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;} //----------------------------------------------//------------------------------