|
|
# SPIDriver
|
|
|
Classes in the file `shared/drivers/spi/SPIDriver.h` provide both low and high level access to an SPI Bus, in order to perform *read*, *write* and *full duplex transactions* with slaves on the bus.
|
|
|
Formerly, these function were provided by the infamous `BusTemplate.h`.
|
|
|
SPIDriver allow for sensors working with different bus configurations (baud rate, polarity, phase etc) to work togheter on the same bus, by appropriately reconfiguring the bus before each transaction.
|
|
|
|
|
|
## General architecture
|
|
|
SPIDriver.h
|
|
|
### SPIBusInterface
|
|
|
Low level access to the SPI bus is provided trough the interface SPIBusInterfaces, a virtual class the provides all the methods needed to perform operations on the bus:
|
|
|
- `configure(...)` Configures the bus with the provided parameters
|
|
|
- `select(...)` / `deselect(...)` asserts and clears the Chip Select (CS) pin
|
|
|
- `write(...)` writes bytes on the bus
|
|
|
- `read(...)` reads bytes from the bus
|
|
|
- `transfer(...)` performs a full duplex transaction, writing the provided bytes and returning the received bytes
|
|
|
### Interface implementations (SPIBus)
|
|
|
This interface is implemented primarly by the `SPIBus` class, which performs the *real* operations on a SPI bus, using the SPI peripheral of the STM32F4 micro.
|
|
|
The interface may be implemented by other classes, for example `MockSpiBus`, that implements a "fake" SPI bus used for unit testing.
|
|
|
The various **drivers must access the an SPI bus exclusively through the interface**. By doing this, the implementation of the driver is completely transparent to the use of a real SPI bus (`SPIBus`), a mock one (`MockSPIBus`) or other implementations: all can be used interchangeably without needing to change a single line of code in the driver.
|
|
|
### SPITransaction
|
|
|
While `SPIBusInterface` provides low level access to the bus (the user must manually set the CS pin and manually configure the bus at each transaction), the class `SPITransaction` does all of this automatically, thus providing an high level interface that hides some of the implementation details of the SPI protocol. Because of this, `SPITransaction` is much simpler (and less verbose) to use and is in fact the suggested way of operating on the bus.
|
|
|
### SPIBusConfig struct
|
|
|
Contains all the configuration parameters of the bus. Multiple slaves on the same bus may use different bus parameters.
|
|
|
### SPISlave struct
|
|
|
Aggregates all the objects needed by a driver to access a bus.
|
|
|
|
|
|
## Examples
|
|
|
### High level acccess through SPITransaction
|
|
|
```cpp
|
|
|
// Creates an SPIBus object using the SPI5 peripheral
|
|
|
// The SPIBus object is shared among all slaves on the bus
|
|
|
SPIBus bus(SPI5);
|
|
|
|
|
|
GpioPin cs_s1(GPIOC_BASE, 1); // Chip select pin for slave 1 (GPIO C1)
|
|
|
GpioPin cs_s2(GPIOC_BASE, 1); // Chip select pin for slave 2 (GPIO C2)
|
|
|
|
|
|
SPIBusConfig cfg_s1; // Bus configuration for slave 1
|
|
|
SPIBusConfig cfg_s2; // Bus configuration for slave 1
|
|
|
|
|
|
int main()
|
|
|
{
|
|
|
cfg_s1.br = SPIBaudRate::DIV_64; // Divide the peripheral clock by 64 (slave 1)
|
|
|
// Slave 1 defaults to cpol = 0
|
|
|
|
|
|
cfg_s2.br = SPIBaudRate::DIV_128; // Divide the peripheral clock by 128 (slave 2)
|
|
|
cfg_s2.cpol = 1; // Set clock polarity to 1 (slave 2)
|
|
|
|
|
|
for(;;)
|
|
|
{
|
|
|
// Operations with slave 1
|
|
|
{
|
|
|
SPITransaction t1{bus, cs_s1, cfg_s1}; // Start a transaction for slave 1
|
|
|
// The bus is immediately configured here
|
|
|
t1.write(0x25, 69); // Writes 69 in the 0x25 register
|
|
|
uint8_t val = t1.read(0x26); // Reads the 0x26 register
|
|
|
|
|
|
// End of scope: the SPITransaction is terminated here.
|
|
|
}
|
|
|
// Operations with slave 2
|
|
|
{
|
|
|
SPITransaction t2{bus, cs_s2, cfg_s2}; // Start a transaction for slave 2
|
|
|
// The bus is immediately reconfigured here using the parameters for the second slave
|
|
|
|
|
|
t2.write(0x45, 123); // Writes in the 0x45 register
|
|
|
uint8_t val = t2.read(0x70); // Reads the 0x70 register
|
|
|
|
|
|
// End of scope: the SPITransaction is terminated here.
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
### Low level acccess through SPIBusInterface
|
|
|
In this example, the same operations are performed, but directly using `SPIBusInterface` instead of `SPITransaction`
|
|
|
```cpp
|
|
|
// Creates an SPIBus object using the SPI5 peripheral
|
|
|
// The SPIBus object is shared among all slaves on the bus
|
|
|
SPIBus bus(SPI5);
|
|
|
|
|
|
GpioPin cs_s1(GPIOC_BASE, 1); // Chip select pin for slave 1 (GPIO C1)
|
|
|
GpioPin cs_s2(GPIOC_BASE, 1); // Chip select pin for slave 2 (GPIO C2)
|
|
|
|
|
|
SPIBusConfig cfg_s1; // Bus configuration for slave 1
|
|
|
SPIBusConfig cfg_s2; // Bus configuration for slave 1
|
|
|
|
|
|
int main()
|
|
|
{
|
|
|
cfg_s1.br = SPIBaudRate::DIV_64; // Divide the peripheral clock by 64 (slave 1)
|
|
|
// Slave 1 defaults to cpol = 0
|
|
|
|
|
|
cfg_s2.br = SPIBaudRate::DIV_128; // Divide the peripheral clock by 128 (slave 2)
|
|
|
cfg_s2.cpol = 1; // Set clock polarity to 1 (slave 2)
|
|
|
|
|
|
for(;;)
|
|
|
{
|
|
|
// Operations with slave 1
|
|
|
uint8_t reg = 0x25;
|
|
|
uint8_t data = 69;
|
|
|
|
|
|
bus.configure(cfg_s1); // Configure the bus for slave 1
|
|
|
bus.select(cs_s1); // Assert CS
|
|
|
|
|
|
bus.write(®, 1); // Write the register address
|
|
|
bus.write(&data, 1); // Write the data to be put in the register
|
|
|
|
|
|
bus.deselect(cs_s1); // Clear CS
|
|
|
|
|
|
reg = 0x26 | 0x80; // Specify the register we want to read, set the MSB to 1 to signal a read operation
|
|
|
uint8_t val;
|
|
|
bus.select(cs_s1);
|
|
|
bus.write(®, 1); // Write register address
|
|
|
bus.read(&val, 1); // Read 1 byte into val
|
|
|
bus.deselect(cs_s1);
|
|
|
|
|
|
|
|
|
// Operation with slave 2
|
|
|
reg = 0x45;
|
|
|
data = 123;
|
|
|
|
|
|
bus.configure(cfg_s2); // Configure the bus for slave 2
|
|
|
bus.select(cs_s2); // Assert CS
|
|
|
|
|
|
bus.write(®, 1); // Write the register address
|
|
|
bus.write(&data, 1); // Write the data to be put in the register
|
|
|
|
|
|
bus.deselect(cs_s2); // Clear CS
|
|
|
|
|
|
reg = 0x70 | 0x80; // Specify the register we want to read, set the MSB to 1 to signal a read operation
|
|
|
uint8_t val;
|
|
|
|
|
|
bus.select(cs_s2);
|
|
|
bus.write(®, 1);
|
|
|
bus.read(&val, 1);
|
|
|
bus.deselect(cs_s2);
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
```
|
|
|
### Notes
|
|
|
- The `SPIBus` object must be shared by all slaves on the same bus. Do not create multiple instances of the class `SPIBus` for each sensor.
|
|
|
- But each slave can have different configurations
|
|
|
- SPITransactions must be scoped (if not, in this example, the bus may be configured for slave 2 before the operations with slave 1 are performed)
|
|
|
- Access through `SPIBusInterface` (second example) is discouraged due to its complexity and because it is error-prone (easy to forget to configure / select / deselect). But it may be necessary in some cases (sensors with non-standard SPI implementation)
|
|
|
- Operations on the same bus must be synchronized between multiple threads. It is strongly recommended to perform operations from a single thread to avoid synchronization problems.
|
|
|
|