/*----------------------------------------------------------------------------*/ | |
/* Copyright (c) FIRST 2008. All Rights Reserved. */ | |
/* Open Source Software - may be modified and shared by FRC teams. The code */ | |
/* must be accompanied by the FIRST BSD license file in $(WIND_BASE)/WPILib. */ | |
/*----------------------------------------------------------------------------*/ | |
#include "SPI.h" | |
#include "ChipObject/tSPI.h" | |
#include "DigitalInput.h" | |
#include "DigitalOutput.h" | |
#include "NetworkCommunication/UsageReporting.h" | |
#include "Synchronized.h" | |
#include "WPIErrors.h" | |
#include <math.h> | |
#include <usrLib.h> | |
SEM_ID SPI::m_semaphore = NULL; | |
/** | |
* Constructor for input and output. | |
* | |
* @param clk The digital output for the clock signal. | |
* @param mosi The digital output for the written data to the slave | |
* (master-out slave-in). | |
* @param miso The digital input for the input data from the slave | |
* (master-in slave-out). | |
*/ | |
SPI::SPI(DigitalOutput &clk, DigitalOutput &mosi, DigitalInput &miso) | |
{ | |
Init(&clk, &mosi, &miso); | |
} | |
/** | |
* Constructor for input and output. | |
* | |
* @param clk The digital output for the clock signal. | |
* @param mosi The digital output for the written data to the slave | |
* (master-out slave-in). | |
* @param miso The digital input for the input data from the slave | |
* (master-in slave-out). | |
*/ | |
SPI::SPI(DigitalOutput *clk, DigitalOutput *mosi, DigitalInput *miso) | |
{ | |
Init(clk, mosi, miso); | |
} | |
/** | |
* Constructor for output only. | |
* | |
* @param clk The digital output for the clock signal. | |
* @param mosi The digital output for the written data to the slave | |
* (master-out slave-in). | |
*/ | |
SPI::SPI(DigitalOutput &clk, DigitalOutput &mosi) | |
{ | |
Init(&clk, &mosi, NULL); | |
} | |
/** | |
* Constructor for output only. | |
* | |
* @param clk The digital output for the clock signal. | |
* @param mosi The digital output for the written data to the slave | |
* (master-out slave-in). | |
*/ | |
SPI::SPI(DigitalOutput *clk, DigitalOutput *mosi) | |
{ | |
Init(clk, mosi, NULL); | |
} | |
/** | |
* Constructor for input only. | |
* | |
* @param clk The digital output for the clock signal. | |
* @param miso The digital input for the input data from the slave | |
* (master-in slave-out). | |
*/ | |
SPI::SPI(DigitalOutput &clk, DigitalInput &miso) | |
{ | |
Init(&clk, NULL, &miso); | |
} | |
/** | |
* Constructor for input only. | |
* | |
* @param clk The digital output for the clock signal. | |
* @param miso The digital input for the input data from the slave | |
* (master-in slave-out). | |
*/ | |
SPI::SPI(DigitalOutput *clk, DigitalInput *miso) | |
{ | |
Init(clk, NULL, miso); | |
} | |
/** | |
* Destructor. | |
*/ | |
SPI::~SPI() | |
{ | |
delete m_spi; | |
} | |
/** | |
* Initialize SPI channel configuration. | |
* | |
* @param clk The digital output for the clock signal. | |
* @param mosi The digital output for the written data to the slave | |
* (master-out slave-in). | |
* @param miso The digital input for the input data from the slave | |
* (master-in slave-out). | |
*/ | |
void SPI::Init(DigitalOutput *clk, DigitalOutput *mosi, DigitalInput *miso) | |
{ | |
if (m_semaphore == NULL) | |
{ | |
m_semaphore = semMCreate(SEM_Q_PRIORITY | SEM_DELETE_SAFE | SEM_INVERSION_SAFE); | |
} | |
tRioStatusCode localStatus = NiFpga_Status_Success; | |
m_spi = tSPI::create(&localStatus); | |
wpi_setError(localStatus); | |
m_config.BusBitWidth = 8; | |
m_config.ClockHalfPeriodDelay = 0; | |
m_config.MSBfirst = 0; | |
m_config.DataOnFalling = 0; | |
m_config.LatchFirst = 0; | |
m_config.LatchLast = 0; | |
m_config.FramePolarity = 0; | |
m_config.WriteOnly = miso ? 0 : 1; | |
m_config.ClockPolarity = 0; | |
m_channels.SCLK_Channel = clk->GetChannelForRouting(); | |
m_channels.SCLK_Module = clk->GetModuleForRouting(); | |
m_channels.SS_Channel = 0; | |
m_channels.SS_Module = 0; | |
if (mosi) | |
{ | |
m_channels.MOSI_Channel = mosi->GetChannelForRouting(); | |
m_channels.MOSI_Module = mosi->GetModuleForRouting(); | |
} | |
else | |
{ | |
m_channels.MOSI_Channel = 0; | |
m_channels.MOSI_Module = 0; | |
} | |
if (miso) | |
{ | |
m_channels.MISO_Channel = miso->GetChannelForRouting(); | |
m_channels.MISO_Module = miso->GetModuleForRouting(); | |
} | |
else | |
{ | |
m_channels.MISO_Channel = 0; | |
m_channels.MISO_Module = 0; | |
} | |
m_ss = NULL; | |
static INT32 instances = 0; | |
instances++; | |
nUsageReporting::report(nUsageReporting::kResourceType_SPI, instances); | |
} | |
/** | |
* Configure the number of bits from each word that the slave transmits | |
* or receives. | |
* | |
* @param bits The number of bits in one frame (1 to 32 bits). | |
*/ | |
void SPI::SetBitsPerWord(UINT32 bits) | |
{ | |
m_config.BusBitWidth = bits; | |
} | |
/** | |
* Get the number of bits from each word that the slave transmits | |
* or receives. | |
* | |
* @return The number of bits in one frame (1 to 32 bits). | |
*/ | |
UINT32 SPI::GetBitsPerWord() | |
{ | |
return m_config.BusBitWidth; | |
} | |
/** | |
* Configure the rate of the generated clock signal. | |
* The default and maximum value is 76,628.4 Hz. | |
* | |
* @param hz The clock rate in Hertz. | |
*/ | |
void SPI::SetClockRate(double hz) | |
{ | |
int delay = 0; | |
// TODO: compute the appropriate values based on digital loop timing | |
if (hz <= 76628.4) | |
{ | |
double v = (1.0/hz)/1.305e-5; | |
int intv = (int)v; | |
if (v-intv > 0.5) | |
delay = intv; | |
else | |
delay = intv-1; | |
} | |
if (delay > 255) | |
{ | |
wpi_setWPIError(SPIClockRateTooLow); | |
delay = 255; | |
} | |
m_config.ClockHalfPeriodDelay = delay; | |
} | |
/** | |
* Configure the order that bits are sent and received on the wire | |
* to be most significant bit first. | |
*/ | |
void SPI::SetMSBFirst() | |
{ | |
m_config.MSBfirst = 1; | |
} | |
/** | |
* Configure the order that bits are sent and received on the wire | |
* to be least significant bit first. | |
*/ | |
void SPI::SetLSBFirst() | |
{ | |
m_config.MSBfirst = 0; | |
} | |
/** | |
* Configure that the data is stable on the falling edge and the data | |
* changes on the rising edge. | |
*/ | |
void SPI::SetSampleDataOnFalling() | |
{ | |
m_config.DataOnFalling = 1; | |
} | |
/** | |
* Configure that the data is stable on the rising edge and the data | |
* changes on the falling edge. | |
*/ | |
void SPI::SetSampleDataOnRising() | |
{ | |
m_config.DataOnFalling = 0; | |
} | |
/** | |
* Configure the slave select line behavior. | |
* | |
* @param ss slave select digital output. | |
* @param mode Frame mode: | |
* kChipSelect: active for the duration of the frame. | |
* kPreLatchPulse: pulses before the transfer of each frame. | |
* kPostLatchPulse: pulses after the transfer of each frame. | |
* kPreAndPostLatchPulse: pulses before and after each frame. | |
* @param activeLow True if slave select line is active low. | |
*/ | |
void SPI::SetSlaveSelect(DigitalOutput *ss, tFrameMode mode, bool activeLow) | |
{ | |
if (ss) | |
{ | |
m_channels.SS_Channel = ss->GetChannelForRouting(); | |
m_channels.SS_Module = ss->GetModuleForRouting(); | |
} | |
else | |
{ | |
m_channels.SS_Channel = 0; | |
m_channels.SS_Module = 0; | |
} | |
m_ss = ss; | |
switch (mode) | |
{ | |
case kChipSelect: | |
m_config.LatchFirst = 0; | |
m_config.LatchLast = 0; | |
break; | |
case kPreLatchPulse: | |
m_config.LatchFirst = 1; | |
m_config.LatchLast = 0; | |
break; | |
case kPostLatchPulse: | |
m_config.LatchFirst = 0; | |
m_config.LatchLast = 1; | |
break; | |
case kPreAndPostLatchPulse: | |
m_config.LatchFirst = 1; | |
m_config.LatchLast = 1; | |
break; | |
} | |
m_config.FramePolarity = activeLow ? 1 : 0; | |
} | |
/** | |
* Configure the slave select line behavior. | |
* | |
* @param ss slave select digital output. | |
* @param mode Frame mode: | |
* kChipSelect: active for the duration of the frame. | |
* kPreLatchPulse: pulses before the transfer of each frame. | |
* kPostLatchPulse: pulses after the transfer of each frame. | |
* kPreAndPostLatchPulse: pulses before and after each frame. | |
* @param activeLow True if slave select line is active low. | |
*/ | |
void SPI::SetSlaveSelect(DigitalOutput &ss, tFrameMode mode, bool activeLow) | |
{ | |
SetSlaveSelect(&ss, mode, activeLow); | |
} | |
/** | |
* Get the slave select line behavior. | |
* | |
* @param mode Frame mode: | |
* kChipSelect: active for the duration of the frame. | |
* kPreLatchPulse: pulses before the transfer of each frame. | |
* kPostLatchPulse: pulses after the transfer of each frame. | |
* kPreAndPostLatchPulse: pulses before and after each frame. | |
* @param activeLow True if slave select line is active low. | |
* @return The slave select digital output. | |
*/ | |
DigitalOutput *SPI::GetSlaveSelect(tFrameMode *mode, bool *activeLow) | |
{ | |
if (mode != NULL) | |
{ | |
*mode = (tFrameMode) (m_config.LatchFirst | (m_config.LatchLast << 1)); | |
} | |
if (activeLow != NULL) | |
{ | |
*activeLow = m_config.FramePolarity != 0; | |
} | |
return m_ss; | |
} | |
/** | |
* Configure the clock output line to be active low. | |
* This is sometimes called clock polarity high. | |
*/ | |
void SPI::SetClockActiveLow() | |
{ | |
m_config.ClockPolarity = 1; | |
} | |
/** | |
* Configure the clock output line to be active high. | |
* This is sometimes called clock polarity low. | |
*/ | |
void SPI::SetClockActiveHigh() | |
{ | |
m_config.ClockPolarity = 0; | |
} | |
/** | |
* Apply configuration settings and reset the SPI logic. | |
*/ | |
void SPI::ApplyConfig() | |
{ | |
Synchronized sync(m_semaphore); | |
tRioStatusCode localStatus = NiFpga_Status_Success; | |
m_spi->writeConfig(m_config, &localStatus); | |
m_spi->writeChannels(m_channels, &localStatus); | |
m_spi->strobeReset(&localStatus); | |
wpi_setError(localStatus); | |
} | |
/** | |
* Get the number of words that can currently be stored before being | |
* transmitted to the device. | |
* | |
* @return The number of words available to be written. | |
*/ | |
UINT16 SPI::GetOutputFIFOAvailable() | |
{ | |
tRioStatusCode localStatus = NiFpga_Status_Success; | |
UINT16 result = m_spi->readAvailableToLoad(&localStatus); | |
wpi_setError(localStatus); | |
return result; | |
} | |
/** | |
* Get the number of words received and currently available to be read from | |
* the receive FIFO. | |
* | |
* @return The number of words available to read. | |
*/ | |
UINT16 SPI::GetNumReceived() | |
{ | |
tRioStatusCode localStatus = NiFpga_Status_Success; | |
UINT16 result = m_spi->readReceivedElements(&localStatus); | |
wpi_setError(localStatus); | |
return result; | |
} | |
/** | |
* Have all pending transfers completed? | |
* | |
* @return True if no transfers are pending. | |
*/ | |
bool SPI::IsDone() | |
{ | |
tRioStatusCode localStatus = NiFpga_Status_Success; | |
bool result = m_spi->readStatus_Idle(&localStatus); | |
wpi_setError(localStatus); | |
return result; | |
} | |
/** | |
* Determine if the receive FIFO was full when attempting to add new data at | |
* end of a transfer. | |
* | |
* @return True if the receive FIFO overflowed. | |
*/ | |
bool SPI::HadReceiveOverflow() | |
{ | |
tRioStatusCode localStatus = NiFpga_Status_Success; | |
bool result = m_spi->readStatus_ReceivedDataOverflow(&localStatus); | |
wpi_setError(localStatus); | |
return result; | |
} | |
/** | |
* Write a word to the slave device. Blocks until there is space in the | |
* output FIFO. | |
* | |
* If not running in output only mode, also saves the data received | |
* on the MISO input during the transfer into the receive FIFO. | |
*/ | |
void SPI::Write(UINT32 data) | |
{ | |
if (m_channels.MOSI_Channel == 0 && m_channels.MOSI_Module == 0) | |
{ | |
wpi_setWPIError(SPIWriteNoMOSI); | |
return; | |
} | |
Synchronized sync(m_semaphore); | |
while (GetOutputFIFOAvailable() == 0) | |
taskDelay(NO_WAIT); | |
tRioStatusCode localStatus = NiFpga_Status_Success; | |
m_spi->writeDataToLoad(data, &localStatus); | |
m_spi->strobeLoad(&localStatus); | |
wpi_setError(localStatus); | |
} | |
/** | |
* Read a word from the receive FIFO. | |
* | |
* Waits for the current transfer to complete if the receive FIFO is empty. | |
* | |
* If the receive FIFO is empty, there is no active transfer, and initiate | |
* is false, errors. | |
* | |
* @param initiate If true, this function pushes "0" into the | |
* transmit buffer and initiates a transfer. | |
* If false, this function assumes that data is | |
* already in the receive FIFO from a previous write. | |
*/ | |
UINT32 SPI::Read(bool initiate) | |
{ | |
if (m_channels.MISO_Channel == 0 && m_channels.MISO_Module == 0) | |
{ | |
wpi_setWPIError(SPIReadNoMISO); | |
return 0; | |
} | |
tRioStatusCode localStatus = NiFpga_Status_Success; | |
UINT32 data; | |
{ | |
Synchronized sync(m_semaphore); | |
if (initiate) | |
{ | |
m_spi->writeDataToLoad(0, &localStatus); | |
m_spi->strobeLoad(&localStatus); | |
} | |
// Do we have anything ready to read? | |
if (GetNumReceived() == 0) | |
{ | |
if (!initiate && IsDone() && GetOutputFIFOAvailable() == kTransmitFIFODepth) | |
{ | |
// Nothing to read: error out | |
wpi_setWPIError(SPIReadNoData); | |
return 0; | |
} | |
// Wait for the transaction to complete | |
while (GetNumReceived() == 0) | |
taskDelay(NO_WAIT); | |
} | |
m_spi->strobeReadReceivedData(&localStatus); | |
data = m_spi->readReceivedData(&localStatus); | |
} | |
wpi_setError(localStatus); | |
return data; | |
} | |
/** | |
* Stop any transfer in progress and empty the transmit FIFO. | |
*/ | |
void SPI::Reset() | |
{ | |
tRioStatusCode localStatus = NiFpga_Status_Success; | |
m_spi->strobeReset(&localStatus); | |
wpi_setError(localStatus); | |
} | |
/** | |
* Empty the receive FIFO. | |
*/ | |
void SPI::ClearReceivedData() | |
{ | |
tRioStatusCode localStatus = NiFpga_Status_Success; | |
m_spi->strobeClearReceivedData(&localStatus); | |
wpi_setError(localStatus); | |
} |