SPI module reference#include <spi.h>Makefile :
MODULES+=spi
SPI is a widely-used serial communication bus used to interface other components such as external memories or sensors. Take a look at the Communication buses tutorial for more details on how SPI compares to other protocols such as I2C.
The SPI controller can act as either a Master or a Slave.
In Master mode, the controller allows to perform arbitrary transfers with up to 4 peripherals. The API is pretty straightforward : set up a new peripheral using the addPeripheral() function, then use one of the two transfer() functions to send and receive data.
Note that since SPI is a full-duplex protocol (meaning that there is one electrical line for sending data and one for receiving data), a transfer necessarily implies that a byte is received while another one is sent. However, in case some data is not meaningful (for exemple, you only want to send some command to a slave, and the data received at the same time is not meaningful, such as null padding bytes), the library provides helpers to avoid having to define useless buffers. See the description of the transfer() functions below, especially the next and partial parameters.
While SPI is pretty much standardised, some peripherals might require unusual settings regarding the phase and polarity of the signals, called the SPI mode. Wikipedia gives a great overview of this in the Clock polarity and phase chapter of the SPI article. Each slave can have a dedicated mode, making easy to have peripherals using different modes on the same bus. However, most peripheral are compatible with the default MODE0 value and this setting rarely needs to be changed.
Here is a example of a simple SPI master transfer with a Carbide :
#include <carbide.h>
#include <core.h>
#include <spi.h>
// The CS pin of the peripheral is connected to the NPCS0 line
const SPI::Peripheral peripheral = 0;
int main() {
// Initialize the board
Carbide::init();
// Enable the SPI controller in Master mode and register a peripheral on NPCS0
SPI::enableMaster();
SPI::addPeripheral(peripheral);
// Tx buffer containing some data to send
uint8_t txBuffer[4] = {0xDE, 0xAD, 0xBE, 0xEF};
// Rx buffer where the received data will be stored
uint8_t rxBuffer[4];
while (true) {
// Perform a transfer with the peripheral
SPI::transfer(peripheral, txBuffer, sizeof(txBuffer), rxBuffer, sizeof(txBuffer));
// Blink the blue LED and wait for 1 sec
Carbide::setLedB();
Core::sleep(100);
Carbide::setLedB(false);
Core::sleep(900);
}
}
In Slave mode, since the controller has no control over when the transfers happen (this is the master's role), all transfers are asynchronous. A transfer is set up using slaveTransfer() with a buffer containing the data to send during the next transfer initiated by the master. If the master requests more bytes than the buffer contains, null bytes will be automatically sent as necessary.
The end of the transfer is checked either by polling (using isSlaveTransferFinished()) or by interrupt (by registering a handler with enableSlaveTransferFinishedInterrupt()).
When the transfer is finished, the received data (which was sent by the master) can be read using slaveGetReceivedData() and a new transfer can be set up by calling slaveTransfer() again.
Check the code below for an example of an SPI slave implementation as well as a comparison between polling and interrupt :
Polling :
#include <carbide.h>
#include <core.h>
#include <spi.h>
int main() {
// Initialize the board
Carbide::init();
// Enable the SPI controller in Slave mode
SPI::enableSlave();
// Tx buffer containing some data to send
uint8_t txBuffer[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g'};
// Rx buffer where the received data will be stored
uint8_t rxBuffer[10];
// Set up a transfer
SPI::slaveTransfer(txBuffer, sizeof(txBuffer));
while (true) {
// Check if the transfer is finished
if (SPI::isSlaveTransferFinished()) {
// Get the received data into the rx buffer
int rxSize = SPI::slaveGetReceivedData(rxBuffer, sizeof(rxBuffer));
// Set up the next transfer
SPI::slaveTransfer(txBuffer, sizeof(txBuffer));
// Blink the blue LED to indicate a transfer has been performed,
// as well as the green LED if it matches a known value
Carbide::setLedB();
if (rxSize == 4 && rxBuffer[0] == 0xDE && rxBuffer[1] == 0xAD
&& rxBuffer[2] == 0xBE && rxBuffer[3] == 0xEF) {
Carbide::setLedG();
}
Core::sleep(100);
Carbide::setLedB(false);
Carbide::setLedG(false);
}
}
}
Interrupt :
#include <carbide.h>
#include <core.h>
#include <spi.h>
// Flags to pass information between the handler and the main loop
volatile bool _transferFinished = false;
volatile int _nReceivedBytes = 0;
// Tx buffer containing some data to send
uint8_t txBuffer[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g'};
// Rx buffer where the received data will be stored
uint8_t rxBuffer[10];
// Handler that will be called whenever a transfer is finished
void spiHandler(int nReceivedBytes) {
// Get the received data into the rx buffer
SPI::slaveGetReceivedData(rxBuffer, sizeof(rxBuffer));
// Set up the next transfer
SPI::slaveTransfer(txBuffer, sizeof(rxBuffer));
// Set the flag to tell the main loop that some data has been received
_transferFinished = true;
_nReceivedBytes = nReceivedBytes;
}
int main() {
// Initialize the board
Carbide::init();
// Enable the SPI controller in Slave mode
SPI::enableSlave();
// Register the interrupt handler
SPI::enableSlaveTransferFinishedInterrupt(spiHandler);
// Set up a transfer
SPI::slaveTransfer(txBuffer, sizeof(txBuffer));
while (true) {
// Check if the transfer is finished
if (_transferFinished) {
// Blink the blue LED to indicate a transfer has been performed,
// as well as the green LED if it matches a known value
Carbide::setLedB();
if (_nReceivedBytes == 4 && rxBuffer[0] == 0xDE && rxBuffer[1] == 0xAD
&& rxBuffer[2] == 0xBE && rxBuffer[3] == 0xEF) {
Carbide::setLedG();
}
Core::sleep(100);
Carbide::setLedB(false);
Carbide::setLedG(false);
// Reset the flag
_transferFinished = false;
_nReceivedBytes = 0;
}
}
}

addPeripheral() if necessary so this function does not need to be called manually.Peripheral is the number of the peripheral, from zero to SPI::N_PERIPHERALS_MAX = 4, and corresponds to the number of the CS line connected to this peripheral. mode is either MODE0, MODE1, MODE2 or MODE3 (see the module description above for more details on SPI modes).Perform a 1-byte transfer on the bus with the given peripheral. tx is the byte to send and the received byte is returned.
By default, this function will send and receive at the same time (because SPI is full-duplex, see the description above). However, a common use case is to send a command or register byte to a peripheral and receive the response on the next byte : for this purpose, next can be set to true. In this case, the function will send the tx byte, discard the received value, then send a null byte and return the value received then (making it actually a 2-byte transfer).
Perform an arbitrary-length transfer on the bus with the given peripheral by sending data from txBuffer (up to txBufferSize bytes) and storing received data into rxBuffer (up to rxBufferSize).
If rxBufferSize is lower than txBufferSize, only the first received bytes are stored and the last bytes are discarded. Otherwise, if rxBufferSize is greater than txBufferSize, the function will automatically send as much null bytes as necessary after the end of txBuffer to receive the requested number of bytes.
Both txBuffer and rxBuffer can be set to nullptr if necessary (although not at the same time obviously). In this case, the corresponding size should be set to -1. This indicates that the corresponding side of the transfer is not important and will ask the function to, either, send null bytes or discard received bytes, respectively.
Multiple calls to this function can be chained in order to make the the management of buffers easier. In this case, set partial to true on every function call except the last one, in order to prevent the CS line to be deasserted between calls and make it look like a single transfer from the slave's perspective. A common use case is to perform an SPI transfer with two successive calls to transfer : the first one with rxBuffer=nullptr and partial=true to send an multi-byte command to a peripheral, and the second one with txBuffer=nullptr and partial=false to receive the multi-byte response. See the examples for more information.
txBufferSize, null padding bytes will be sent as necessary. A receive-only transfer can be set up by not giving a buffer, in which case only padding bytes will be sent and the received data is read normally using the functions below.slaveTransfer() has been performed yet. In this case, it is usually necessary to call slaveTransfer() again to set up the next transfer.rxBuffer, up to rxBufferSize bytes. The number of bytes received is returned and should be used to correctly parse the buffer.Set the GPIO pin used for this port. PinFunction can be MOSI, MISO, SCK, CS0, CS1, CS2 or CS3.
Most of the feature set of the hardware peripheral is covered by the driver, although some settings can be tweaked if needed.
The controller has support for more than 4 slaves by using external multiplexing of the four CS lines, in order to support up to 15 peripherals. See the datasheet chapter 26.7.3.6 Peripheral Chip Select Decoding for more information.
In Slave mode, asynchronous data is stored in internal buffers in order to simplify the handling from the user's perspective. The size of these buffers effectively limits the amount of data that can be sent or received in a single transfer, currently to 128 bytes. This can be changed using the SLAVE_BUFFERS_SIZE constant in the driver. Note that it can also be reduced in case only short transfers are necessary and RAM space needs to be saved for other purposes.
#ifndef _SPI_H_
#define _SPI_H_
#include <stdint.h>
#include "gpio.h"
#include "error.h"
// Serial Peripheral Interface
// This module allows the chip to communicate through an SPI interface,
// either as a Master or a Slave.
// SPI is faster than I2C, but uses more wires and can connect to less
// peripherals at the same time.
namespace SPI {
// Peripheral memory space base addresses
const uint32_t SPI_BASE = 0x40008000;
// Register offsets
const uint32_t OFFSET_CR = 0x00; // Control Register
const uint32_t OFFSET_MR = 0x04; // Mode Register
const uint32_t OFFSET_RDR = 0x08; // Receive Data Register
const uint32_t OFFSET_TDR = 0x0C; // Transmit Data Register
const uint32_t OFFSET_SR = 0x10; // Status Register
const uint32_t OFFSET_IER = 0x14; // Interrupt Enable Register
const uint32_t OFFSET_IDR = 0x18; // Interrupt Disable Register
const uint32_t OFFSET_IMR = 0x1C; // Interrupt Mask Register
const uint32_t OFFSET_CSR0 = 0x30; // Chip Select Register 0
const uint32_t OFFSET_CSR1 = 0x34; // Chip Select Register 1
const uint32_t OFFSET_CSR2 = 0x38; // Chip Select Register 2
const uint32_t OFFSET_CSR3 = 0x3C; // Chip Select Register 3
const uint32_t OFFSET_WPCR = 0xE4; // Write Protection Control Register
const uint32_t OFFSET_WPSR = 0xE8; // Write Protection Status Register
// Subregisters
const uint8_t CR_SPIEN = 0;
const uint8_t CR_SPIDIS = 1;
const uint8_t CR_SWRST = 7;
const uint8_t CR_FLUSHFIFO = 8;
const uint8_t CR_LASTXFER = 24;
const uint8_t MR_MSTR = 0;
const uint8_t MR_PS = 1;
const uint8_t MR_PCSDEC = 2;
const uint8_t MR_MODFDIS = 4;
const uint8_t MR_RXFIFOEN = 6;
const uint8_t MR_LLB = 7;
const uint8_t MR_PCS = 16;
const uint8_t MR_DLYBCS = 24;
const uint8_t TDR_TD = 0;
const uint8_t TDR_PCS = 16;
const uint8_t TDR_LASTXFER = 24;
const uint8_t SR_RDRF = 0;
const uint8_t SR_TDRE = 1;
const uint8_t SR_MODF = 2;
const uint8_t SR_OVRES = 3;
const uint8_t SR_NSSR = 8;
const uint8_t SR_TXEMPTY = 9;
const uint8_t SR_UNDES = 10;
const uint8_t SR_SPIENS = 16;
const uint8_t CSR_CPOL = 0;
const uint8_t CSR_NCPHA = 1;
const uint8_t CSR_CSNAAT = 2;
const uint8_t CSR_CSAAT = 3;
const uint8_t CSR_BITS = 4;
const uint8_t CSR_SCBR = 8;
const uint8_t CSR_DLYBS = 16;
const uint8_t CSR_DLYBCT = 24;
const uint8_t WPCR_SPIWPEN = 0;
// Constants
const uint32_t WPCR_KEY = 0x535049 << 8;
const int N_PERIPHERALS_MAX = 4;
// Error codes
const Error::Code ERR_INVALID_PERIPHERAL = 0x0001;
const Error::Code ERR_PERIPHERAL_ALREADY_ENABLED = 0x0002;
const Error::Code ERR_NOT_MASTER_MODE = 0x0003;
const Error::Code ERR_NOT_SLAVE_MODE = 0x0004;
// Static values
enum class Mode {
MODE0 = 0, // CPOL=0, CPHA=0
MODE1 = 1, // CPOL=0, CPHA=1
MODE2 = 2, // CPOL=1, CPHA=0
MODE3 = 3 // CPOL=1, CPHA=1
};
using Peripheral = uint8_t;
enum class PinFunction {
MOSI,
MISO,
SCK,
CS0,
CS1,
CS2,
CS3
};
// Master-mode functions
void enableMaster();
bool addPeripheral(Peripheral peripheral, Mode mode=Mode::MODE0, int delayBetweenBytes=0);
uint8_t transfer(Peripheral peripheral, uint8_t tx=0, bool next=false);
void transfer(Peripheral peripheral, const uint8_t* txBuffer, int txBufferSize, uint8_t* rxBuffer=nullptr, int rxBufferSize=-1, bool partial=false);
// Slave-mode functions
void enableSlave(Mode mode=Mode::MODE0);
void slaveTransfer(const uint8_t* txBuffer=nullptr, int txBufferSize=-1);
bool isSlaveTransferFinished();
int slaveGetReceivedData(uint8_t* rxBuffer, int rxBufferSize);
void enableSlaveTransferFinishedInterrupt(void (*handler)(int nReceivedBytes));
void disableSlaveTransferFinishedInterrupt();
// Common functions
void disable();
void setPin(PinFunction function, GPIO::Pin pin);
}
#endif
#include "spi.h"
#include "pm.h"
#include "dma.h"
#include "gpio.h"
#include "core.h"
#include "error.h"
#include <string.h>
namespace SPI {
// Package-dependant, defined in pins_sam4l_XX.cpp
extern struct GPIO::Pin PIN_MOSI;
extern struct GPIO::Pin PIN_MISO;
extern struct GPIO::Pin PIN_SCK;
extern struct GPIO::Pin PIN_NPCS0;
extern struct GPIO::Pin PIN_NPCS1;
extern struct GPIO::Pin PIN_NPCS2;
extern struct GPIO::Pin PIN_NPCS3;
bool _enabled = false;
bool _modeMaster = false;
int _rxDMAChannel = -1;
int _txDMAChannel = -1;
bool _enabledPeripherals[N_PERIPHERALS_MAX] = {false, false, false, false};
// Used for the DMA channel
const int DUMMY_BYTES_SIZE = 64;
uint8_t DUMMY_BYTES[DUMMY_BYTES_SIZE];
int _dummyBytesCounter = 0;
// Slave mode
const int SLAVE_BUFFERS_SIZE = 128;
uint8_t _slaveTXBuffer[SLAVE_BUFFERS_SIZE];
uint8_t _slaveRXBuffer[SLAVE_BUFFERS_SIZE];
int _slaveTransferSize = 0;
bool _slaveTransferFinished = false;
void (*_slaveTransferFinishedHandler)(int nReceivedBytes) = nullptr;
extern uint8_t INTERRUPT_PRIORITY;
// Internal functions
void txDMAReloadEmptyHandler();
void interruptHandlerWrapper();
void disable() {
_enabled = false;
// Free the pins
GPIO::disablePeripheral(PIN_MISO);
GPIO::disablePeripheral(PIN_MOSI);
GPIO::disablePeripheral(PIN_SCK);
if (_enabledPeripherals[0]) {
GPIO::disablePeripheral(PIN_NPCS0);
_enabledPeripherals[0] = false;
}
if (_enabledPeripherals[1]) {
GPIO::disablePeripheral(PIN_NPCS1);
_enabledPeripherals[1] = false;
}
if (_enabledPeripherals[2]) {
GPIO::disablePeripheral(PIN_NPCS2);
_enabledPeripherals[2] = false;
}
if (_enabledPeripherals[3]) {
GPIO::disablePeripheral(PIN_NPCS3);
_enabledPeripherals[3] = false;
}
// MR (Mode Register) : deconfigure the interface
(*(volatile uint32_t*)(SPI_BASE + OFFSET_MR)) = 0;
// CR (Control Register) : reset the interface
(*(volatile uint32_t*)(SPI_BASE + OFFSET_CR))
= 1 << CR_SWRST; // SWRST : software reset
// CR (Control Register) : disable the interface
(*(volatile uint32_t*)(SPI_BASE + OFFSET_CR))
= 1 << CR_SPIDIS; // SPIDIS : SPI Disable
// Disable the clock
PM::disablePeripheralClock(PM::CLK_SPI);
}
void setPin(PinFunction function, GPIO::Pin pin) {
switch (function) {
case PinFunction::MOSI:
PIN_MOSI = pin;
break;
case PinFunction::MISO:
PIN_MISO = pin;
break;
case PinFunction::SCK:
PIN_SCK = pin;
break;
case PinFunction::CS0:
PIN_NPCS0 = pin;
break;
case PinFunction::CS1:
PIN_NPCS1 = pin;
break;
case PinFunction::CS2:
PIN_NPCS2 = pin;
break;
case PinFunction::CS3:
PIN_NPCS3 = pin;
break;
}
}
void enableMaster() {
// If the controller is already enabled, disable it first
if (_enabled) {
disable();
}
_enabled = true;
_modeMaster = true;
// Initialize the dummy bytes buffer to 0
memset(DUMMY_BYTES, 0, sizeof(DUMMY_BYTES));
// Set the pins in peripheral mode
GPIO::enablePeripheral(PIN_MISO);
GPIO::enablePeripheral(PIN_MOSI);
GPIO::enablePeripheral(PIN_SCK);
// Enable the clock
PM::enablePeripheralClock(PM::CLK_SPI);
// CR (Control Register) : reset the interface
(*(volatile uint32_t*)(SPI_BASE + OFFSET_CR))
= 1 << CR_SWRST; // SWRST : software reset
// MR (Mode Register) : configure the interface in master mode
(*(volatile uint32_t*)(SPI_BASE + OFFSET_MR))
= 1 << MR_MSTR // MSTR : master mode
| 0 << MR_PS // PS : fixed peripheral select
| 0 << MR_PCSDEC // PCSDEC : no CS decoding
| 1 << MR_MODFDIS // MODFDIS : mode fault detection disabled
| 1 << MR_RXFIFOEN // RXFIFOEN : reception fifo enabled
| 0 << MR_LLB // LLB : local loopback disabled
| 0 << MR_PCS // PCS : will be programmed for each transfer
| 6 << MR_DLYBCS; // DLYBCS : delay between chip selects : 6 periods of CLK_SPI
// CR (Control Register) : enable the interface
(*(volatile uint32_t*)(SPI_BASE + OFFSET_CR))
= 1 << CR_SPIEN; // SPIEN : SPI Enable
// Set up the DMA channels and related interrupts
_rxDMAChannel = DMA::setupChannel(_rxDMAChannel, DMA::Device::SPI_RX, DMA::Size::BYTE);
_txDMAChannel = DMA::setupChannel(_txDMAChannel, DMA::Device::SPI_TX, DMA::Size::BYTE);
}
bool addPeripheral(Peripheral peripheral, Mode mode, int delayBetweenBytes) {
// Make sure the controller is enabled in master mode
if (!_enabled) {
enableMaster();
}
if (!_modeMaster) {
Error::happened(Error::Module::SPI, ERR_NOT_MASTER_MODE, Error::Severity::CRITICAL);
return false;
}
if (peripheral >= N_PERIPHERALS_MAX) {
Error::happened(Error::Module::SPI, ERR_INVALID_PERIPHERAL, Error::Severity::CRITICAL);
return false;
} else if (_enabledPeripherals[peripheral]) {
Error::happened(Error::Module::SPI, ERR_PERIPHERAL_ALREADY_ENABLED, Error::Severity::CRITICAL);
return false;
}
_enabledPeripherals[peripheral] = true;
switch (peripheral) {
case 0:
GPIO::enablePeripheral(PIN_NPCS0);
break;
case 1:
GPIO::enablePeripheral(PIN_NPCS1);
break;
case 2:
GPIO::enablePeripheral(PIN_NPCS2);
break;
case 3:
GPIO::enablePeripheral(PIN_NPCS3);
break;
}
// SPI mode
uint8_t cpol = static_cast<int>(mode) & 0b10;
uint8_t ncpha = !(static_cast<int>(mode) & 0b01);
// CSRn (Chip Select Register n) : configure the peripheral-specific settings
(*(volatile uint32_t*)(SPI_BASE + OFFSET_CSR0 + peripheral * 0x04))
= cpol << CSR_CPOL // CPOL : clock polarity
| ncpha << CSR_NCPHA // CPHA : clock phase
| 0 << CSR_CSNAAT // CSNAAT : CS doesn't rise between two consecutive transfers
| 0 << CSR_CSAAT // CSAAT : CS always rises when the last transfer is complete
| 0b0000 << CSR_BITS // BITS : 8 bits per transfer
| 4 << CSR_SCBR // SCBR : SPI clock = CLK_SPI / 4 (not faster, otherwise the DMA/some code won't be able to keep up)
| 0 << CSR_DLYBS // DLYBS : no delay between CS assertion and first clock cycle
| delayBetweenBytes << CSR_DLYBCT; // DLYBCT : no delay between consecutive transfers
return true;
}
uint8_t transfer(Peripheral peripheral, uint8_t tx, bool next) {
// Make sure the controller is enabled in master mode
if (!_enabled || !_modeMaster) {
Error::happened(Error::Module::SPI, ERR_NOT_MASTER_MODE, Error::Severity::CRITICAL);
return 0;
}
// Select the peripheral
uint8_t pcs = ~(1 << peripheral) & 0x0F;
uint32_t mr = (*(volatile uint32_t*)(SPI_BASE + OFFSET_MR));
mr = mr & ~((uint32_t)(0b1111 << MR_PCS)); // Erase the PCS field
(*(volatile uint32_t*)(SPI_BASE + OFFSET_MR)) = mr | (pcs << MR_PCS); // Reprogram MR
// Dummy reads to empty the Rx register
while ((*(volatile uint32_t*)(SPI_BASE + OFFSET_SR)) >> SR_RDRF & 1) {
(*(volatile uint32_t*)(SPI_BASE + OFFSET_RDR));
}
// Write the data to the transfer register
(*(volatile uint32_t*)(SPI_BASE + OFFSET_TDR))
= tx << TDR_TD; // TD : transmit data
// If the user has asked the next read byte, write a second byte immediately
if (next) {
// Wait for the byte to be transfered to the serializer
while (!((*(volatile uint32_t*)(SPI_BASE + OFFSET_SR)) >> SR_TDRE & 1));
// Transmit dummy byte
(*(volatile uint32_t*)(SPI_BASE + OFFSET_TDR))
= 0 << TDR_TD;
// Wait for the end of the transfer
while (!((*(volatile uint32_t*)(SPI_BASE + OFFSET_SR)) >> SR_RDRF & 1));
// Dummy read to received data to prevent overrun condition
(*(volatile uint32_t*)(SPI_BASE + OFFSET_RDR));
}
// Wait for the end of the transfer
while (!((*(volatile uint32_t*)(SPI_BASE + OFFSET_SR)) >> SR_RDRF & 1));
return (*(volatile uint32_t*)(SPI_BASE + OFFSET_RDR));
}
void transfer(Peripheral peripheral, const uint8_t* txBuffer, int txBufferSize, uint8_t* rxBuffer, int rxBufferSize, bool partial) {
// Make sure the controller is enabled in master mode
if (!_enabled || !_modeMaster) {
Error::happened(Error::Module::SPI, ERR_NOT_MASTER_MODE, Error::Severity::CRITICAL);
return;
}
// Select the peripheral
uint8_t pcs = ~(1 << peripheral) & 0x0F;
uint32_t mr = (*(volatile uint32_t*)(SPI_BASE + OFFSET_MR));
mr = mr & ~((uint32_t)(0b1111 << MR_PCS)); // Erase the PCS field
(*(volatile uint32_t*)(SPI_BASE + OFFSET_MR)) = mr | (pcs << MR_PCS); // Reprogram MR
// If this is a partial transfer, do not deselect the device
uint32_t csr = (*(volatile uint32_t*)(SPI_BASE + OFFSET_CSR0 + peripheral * 0x04));
if (partial) {
// Enable CSAAT to prevent CS from rising automatically
(*(volatile uint32_t*)(SPI_BASE + OFFSET_CSR0 + peripheral * 0x04)) = csr | 1 << CSR_CSAAT;
} else {
// Disable CSAAT to make CS rise automatically
(*(volatile uint32_t*)(SPI_BASE + OFFSET_CSR0 + peripheral * 0x04)) = csr & ~(uint32_t)(1 << CSR_CSAAT);
}
// Dummy reads to empty the Rx register
while ((*(volatile uint32_t*)(SPI_BASE + OFFSET_SR)) >> SR_RDRF & 1) {
(*(volatile uint32_t*)(SPI_BASE + OFFSET_RDR));
}
// Enable Rx DMA channel
if (rxBuffer && rxBufferSize > 0) {
DMA::startChannel(_rxDMAChannel, (uint32_t)rxBuffer, rxBufferSize);
} else {
rxBufferSize = 0;
}
if (rxBufferSize == txBufferSize) {
// Enable Tx DMA channel
DMA::startChannel(_txDMAChannel, (uint32_t)txBuffer, txBufferSize);
// Wait for the end of the transfer
while (!(DMA::isFinished(_rxDMAChannel) && DMA::isFinished(_txDMAChannel)));
while (!((*(volatile uint32_t*)(SPI_BASE + OFFSET_SR)) >> SR_TDRE & 1));
// If the user asked to have more bytes received than sent, send dummy bytes
} else if (rxBufferSize > txBufferSize) {
// If there is no bytes to send, directly send dummy bytes instead
if (txBuffer == nullptr || txBufferSize == 0) {
txBuffer = DUMMY_BYTES;
txBufferSize = rxBufferSize;
if (txBufferSize > DUMMY_BYTES_SIZE) {
txBufferSize = DUMMY_BYTES_SIZE;
}
}
// Configure Tx DMA channel without starting it
DMA::setupChannel(_txDMAChannel, (uint32_t)txBuffer, txBufferSize);
// After the channel has finished, reload it with some dummy bytes
int dummySize = DUMMY_BYTES_SIZE;
if (rxBufferSize - txBufferSize < DUMMY_BYTES_SIZE) {
dummySize = rxBufferSize - txBufferSize;
}
DMA::reloadChannel(_txDMAChannel, (uint32_t)DUMMY_BYTES, dummySize);
// Enable Tx DMA interrupt
_dummyBytesCounter = rxBufferSize - txBufferSize - dummySize;
if (_dummyBytesCounter > 0) {
DMA::enableInterrupt(_txDMAChannel, txDMAReloadEmptyHandler, DMA::Interrupt::RELOAD_EMPTY);
}
// Start Tx DMA channel
DMA::startChannel(_txDMAChannel);
// Wait for the end of the Rx channel
while (!(DMA::isFinished(_rxDMAChannel)));
// Disable Tx DMA interrupt
DMA::disableInterrupt(_txDMAChannel);
// If the user asked to have more bytes sent than received, clear the RDR and the Underrun status
} else if (rxBufferSize < txBufferSize) {
// Enable Tx DMA channel
DMA::startChannel(_txDMAChannel, uint32_t(txBuffer), txBufferSize);
// Wait for the end of the transfer
while (!DMA::isFinished(_txDMAChannel));
while (!((*(volatile uint32_t*)(SPI_BASE + OFFSET_SR)) >> SR_TDRE & 1));
// Dummy reads to reset the registers
while ((*(volatile uint32_t*)(SPI_BASE + OFFSET_SR)) >> SR_RDRF & 1) {
(*(volatile uint32_t*)(SPI_BASE + OFFSET_RDR));
}
(*(volatile uint32_t*)(SPI_BASE + OFFSET_SR));
}
}
void txDMAReloadEmptyHandler() {
if (_dummyBytesCounter > 0) {
// Reload the Tx DMA channel with up to 8 dummy bytes
int size = DUMMY_BYTES_SIZE;
if (_dummyBytesCounter < DUMMY_BYTES_SIZE) {
size = _dummyBytesCounter;
}
DMA::reloadChannel(_txDMAChannel, (uint32_t)DUMMY_BYTES, size);
_dummyBytesCounter -= size;
} else {
DMA::disableInterrupt(_txDMAChannel, DMA::Interrupt::RELOAD_EMPTY);
}
}
void enableSlave(Mode mode) {
// If the controller is already enabled, disable it first
if (_enabled) {
disable();
}
_enabled = true;
_modeMaster = false;
// Initialize the dummy bytes buffer to 0
memset(DUMMY_BYTES, 0, sizeof(DUMMY_BYTES));
// Set the pins in peripheral mode
GPIO::enablePeripheral(PIN_MISO);
GPIO::enablePeripheral(PIN_MOSI);
GPIO::enablePeripheral(PIN_SCK);
GPIO::enablePeripheral(PIN_NPCS0); // NSS is the NPCS0 pin
// Enable the clock
PM::enablePeripheralClock(PM::CLK_SPI);
// CR (Control Register) : reset the interface
(*(volatile uint32_t*)(SPI_BASE + OFFSET_CR))
= 1 << CR_SWRST; // SWRST : software reset
// MR (Mode Register) : configure the interface in slave mode
(*(volatile uint32_t*)(SPI_BASE + OFFSET_MR))
= 0 << MR_MSTR // MSTR : slave mode
| 0 << MR_PS // PS : fixed peripheral select
| 0 << MR_PCSDEC // PCSDEC : no CS decoding
| 1 << MR_MODFDIS // MODFDIS : mode fault detection disabled
| 1 << MR_RXFIFOEN // RXFIFOEN : reception fifo enabled
| 0 << MR_LLB // LLB : local loopback disabled
| 0 << MR_PCS // PCS : will be programmed for each transfer
| 6 << MR_DLYBCS; // DLYBCS : delay between chip selects : 6 periods of CLK_SPI
// CR (Control Register) : enable the interface
(*(volatile uint32_t*)(SPI_BASE + OFFSET_CR))
= 1 << CR_SPIEN; // SPIEN : SPI Enable
// Set up the DMA channels and related interrupts
_rxDMAChannel = DMA::setupChannel(_rxDMAChannel, DMA::Device::SPI_RX, DMA::Size::BYTE);
_txDMAChannel = DMA::setupChannel(_txDMAChannel, DMA::Device::SPI_TX, DMA::Size::BYTE);
// SPI mode
uint8_t cpol = static_cast<int>(mode) & 0b10;
uint8_t ncpha = !(static_cast<int>(mode) & 0b01);
// CSR0 (Chip Select Register 0) : configure the slave mode settings
(*(volatile uint32_t*)(SPI_BASE + OFFSET_CSR0))
= cpol << CSR_CPOL // CPOL : clock polarity
| ncpha << CSR_NCPHA // CPHA : clock phase
| 0b0000 << CSR_BITS; // BITS : 8 bits per transfer
}
void slaveTransfer(const uint8_t* txBuffer, int txBufferSize) {
// Make sure the controller is enabled in slave mode
if (!_enabled || _modeMaster) {
Error::happened(Error::Module::SPI, ERR_NOT_SLAVE_MODE, Error::Severity::CRITICAL);
return;
}
// Dummy reads to empty the Rx register
while ((*(volatile uint32_t*)(SPI_BASE + OFFSET_SR)) >> SR_RDRF & 1) {
(*(volatile uint32_t*)(SPI_BASE + OFFSET_RDR));
}
// Reset the transfer finished flag
_slaveTransferFinished = false;
if (txBuffer && txBufferSize > 0) {
// Copy the first byte of data to write directly into TDR to overwrite any previous transfer
// still waiting to be sent
(*(volatile uint32_t*)(SPI_BASE + OFFSET_TDR))
= txBuffer[0] << TDR_TD;
// Copy the rest of the data into the tx buffer
_slaveTransferSize = txBufferSize;
if (_slaveTransferSize > SLAVE_BUFFERS_SIZE) {
_slaveTransferSize = SLAVE_BUFFERS_SIZE;
}
if (txBufferSize >= 2) {
memcpy(_slaveTXBuffer, txBuffer + 1, _slaveTransferSize - 1);
}
// Since the last transfered byte is repeated indefinitely when the master
// keeps requesting data, make sure this is a null byte
_slaveTXBuffer[_slaveTransferSize - 1] = 0x00;
// Enable Tx DMA channel
// '+ 1' because the null byte at the end of the buffer must be sent
// '- 1' because the first byte is already written in TDR
DMA::startChannel(_txDMAChannel, (uint32_t)_slaveTXBuffer, _slaveTransferSize + 1 - 1);
} else {
// Rx-only transfer, send dummy bytes
_slaveTransferSize = 0;
(*(volatile uint32_t*)(SPI_BASE + OFFSET_TDR))
= 0x00 << TDR_TD;
}
// Enable Rx DMA channel
DMA::startChannel(_rxDMAChannel, (uint32_t)_slaveRXBuffer, SLAVE_BUFFERS_SIZE);
}
bool isSlaveTransferFinished() {
// Make sure the controller is enabled in slave mode
if (!_enabled || _modeMaster) {
Error::happened(Error::Module::SPI, ERR_NOT_SLAVE_MODE, Error::Severity::CRITICAL);
return false;
}
// Check in SR.NSSR if a rising edge on NSS has been detected since the last call to slaveTransfer()
_slaveTransferFinished = _slaveTransferFinished || ((*(volatile uint32_t*)(SPI_BASE + OFFSET_SR)) >> SR_NSSR & 1);
return _slaveTransferFinished;
}
int slaveGetReceivedData(uint8_t* rxBuffer, int rxBufferSize) {
// Make sure the controller is enabled in slave mode
if (!_enabled || _modeMaster) {
Error::happened(Error::Module::SPI, ERR_NOT_SLAVE_MODE, Error::Severity::CRITICAL);
return 0;
}
// Get the number of bytes received from the DMA counter and copy the received bytes into the user buffer
int bytesReceived = SLAVE_BUFFERS_SIZE - DMA::getCounter(_rxDMAChannel);
if (rxBufferSize > bytesReceived) {
rxBufferSize = bytesReceived;
}
memcpy(rxBuffer, _slaveRXBuffer, rxBufferSize);
return rxBufferSize;
}
void enableSlaveTransferFinishedInterrupt(void (*handler)(int nReceivedBytes)) {
// Save the user handler
_slaveTransferFinishedHandler = handler;
// IER (Interrupt Enable Register) : enable the interrupt
(*(volatile uint32_t*)(SPI_BASE + OFFSET_IER))
= 1 << SR_NSSR;
// Enable the interrupt in the NVIC
Core::setInterruptHandler(Core::Interrupt::SPI, interruptHandlerWrapper);
Core::enableInterrupt(Core::Interrupt::SPI, INTERRUPT_PRIORITY);
}
void disableSlaveTransferFinishedInterrupt() {
// IDR (Interrupt Disable Register) : disable the interrupt
(*(volatile uint32_t*)(SPI_BASE + OFFSET_IDR))
= 1 << SR_NSSR;
Core::disableInterrupt(Core::Interrupt::SPI);
}
void interruptHandlerWrapper() {
// Call the user handler
if (_slaveTransferFinishedHandler) {
_slaveTransferFinishedHandler(SLAVE_BUFFERS_SIZE - DMA::getCounter(_rxDMAChannel));
}
// SR (Status Register) : dummy read to clear the interrupt
(*(volatile uint32_t*)(SPI_BASE + OFFSET_SR));
}
}