... | ... | @@ -2,61 +2,77 @@ This page provides documentation for our own SPI driver implementation, which yo |
|
|
|
|
|
Some basic knowledge about how SPI works is required to read this page. You can take a look at the [Low-Level SPI](Low-Level-SPI) page to see an example.
|
|
|
|
|
|
# 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.
|
|
|
# SPI Driver
|
|
|
|
|
|
Classes found in `shared/drivers/spi/` 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.\
|
|
|
The SPI driver 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
|
|
|
|
|
|
<div align="center"><img src="images/spi/_UML__SPIDriver__1_.png" height="700"/><br/><i></i></div><br>
|
|
|

|
|
|
|
|
|
## Low level SPI
|
|
|
|
|
|
Low level access to the SPI bus is implemented trough the SPI class, which provides access to all the peripheral functionalities and all the methods needed to perform operations on the bus, such as:
|
|
|
|
|
|
### 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
|
|
|
- `setClockDiver(ClockDivider divider)` configures the peripheral clock divider
|
|
|
- `setMode(Mode mode)` sets the clock polarity and phase
|
|
|
- `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
|
|
|
|
|
|
## SPIBus
|
|
|
|
|
|
The lower level SPI driver is enhanced by the SPIBusInterface class. It is a virtual interface which provides all the communication capabilities of SPI and adds a configure function and slave selection through a cs pin.
|
|
|
|
|
|
### 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.
|
|
|
|
|
|
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.
|
|
|
|
|
|
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
|
|
|
|
|
|
### Simple SPI sensor
|
|
|
In this example, SPIDriver in a sensor driver to configure and sample it.
|
|
|
|
|
|
In this example, SPIDriver in a sensor driver to configure and sample it.\
|
|
|
More details about sensors can be found in the [Sensor](Sensor) page.
|
|
|
|
|
|
#### main.cpp
|
|
|
|
|
|
```cpp
|
|
|
#include "drivers/spi/SPIDriver.h"
|
|
|
#include "MySensor.h"
|
|
|
#include <drivers/spi/SPIDriver.h>
|
|
|
#include "MockSensor.h"
|
|
|
|
|
|
// Creates an SPIBus object using the SPI1 peripheral
|
|
|
// The same instance of the SPIBus object must be shared among all slaves on the
|
|
|
// physical SPI bus
|
|
|
SPIBus bus(SPI1);
|
|
|
|
|
|
GpioPin cs_mysensor(GPIOC_BASE, 1); // Chip select pin of the sensor (GPIO C1)
|
|
|
GpioPin csPin(GPIOC_BASE, 1); // Chip select pin of the sensor (GPIO C1)
|
|
|
|
|
|
SPIBusConfig cfg_mysensor; // Bus configuration for the sensor
|
|
|
SPIBusConfig configuration; // Bus configuration for the sensor
|
|
|
|
|
|
int main()
|
|
|
{
|
|
|
// Set bus parameters
|
|
|
cfg_mysensor.br = SPIBaudRate::DIV_64; // Divide the SPI peripheral clock
|
|
|
// by 64
|
|
|
cfg_mysensor.cpol = 1; // Set clock polarity to 1
|
|
|
configuration.clockDivider = SPI::ClockDivider::DIV_256;
|
|
|
configuration.mode = SPI::Mode::MODE_1; // Set clock polarity to 0 and phase to 1
|
|
|
|
|
|
// Create an instance of the sensor
|
|
|
MySensor sensor(bus, cs_mysensor, cgf_mysensor);
|
|
|
MockSensor sensor(bus, cs_mysensor, cgf_mysensor);
|
|
|
|
|
|
// Initialize the sensor (configures its registers)
|
|
|
if (!sensor.init() || !sensor.selfTest())
|
... | ... | @@ -76,44 +92,52 @@ int main() |
|
|
return 0;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
#### Notes
|
|
|
- The SPI peripheral and related GPIOs are not automatically configured by SPIDriver. They are usually configured in the `bsp.cpp` file for your board, in Miosix.
|
|
|
If not, you have to:
|
|
|
- Enable the SPI peripheral clock
|
|
|
|
|
|
- The SPI peripheral and related GPIOs are not automatically configured by SPIDriver. They are usually configured in the `bsp.cpp` file for your board, in Miosix.\
|
|
|
If not, you have to:
|
|
|
- Enable the SPI peripheral clock\
|
|
|
Example: `RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;`
|
|
|
- Set `MISO`, `MOSI`, `CLOCK` GPIOs to the correct alternate mode
|
|
|
- Set `CS` GPIO to output mode, and set its value to _high_.
|
|
|
- Set `MISO`, `MOSI`, `CLOCK` GPIOs to the correct alternate mode
|
|
|
- Set `CS` GPIO to output mode, and set its value to _high_.
|
|
|
- The `SPIBus` object is shared among all slaves on the same physical bus. It must thus be passed **by reference** to each slave.
|
|
|
- Each slave can, however, have a different bus configuration (`SPIBusConfig`).
|
|
|
|
|
|
#### MySensor.h
|
|
|
|
|
|
```cpp
|
|
|
#include "drivers/spi/SPIDriver.h"
|
|
|
#include <drivers/spi/SPIDriver.h>
|
|
|
|
|
|
struct MySensorData : public TimestampData
|
|
|
struct MockSensorData : public TimestampData
|
|
|
{
|
|
|
int data;
|
|
|
}
|
|
|
|
|
|
class MySensor : public Sensor<MySensorData>
|
|
|
class MockSensor : public Sensor<MySensorData>
|
|
|
{
|
|
|
public:
|
|
|
// Constructor with default bus config
|
|
|
MySensor(SPIBusInterface& bus, GpioPin cs)
|
|
|
: MySensor(bus, cs, SPIBusConfig{})
|
|
|
{
|
|
|
// Default parameters:
|
|
|
spislave.config.br = SPIBaudRate::DIV_32; // Divide the SPI peripheral
|
|
|
// clock by 32
|
|
|
spislave.config.cpol = 1; // Set clock polarity to 1
|
|
|
}
|
|
|
|
|
|
// Constructor with custom bus config
|
|
|
MySensor(SPIBusInterface& bus, GpioPin cs, SPIBusConfig config)
|
|
|
MySensor(SPIBusInterface& bus, GpioPin cs, SPIBusConfig config = getDefaultSPIConfig())
|
|
|
: spislave(bus, cs, config)
|
|
|
{
|
|
|
// Here, the provided config is used instead of the dafault one
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Constructs the default config for SPI Bus.
|
|
|
*
|
|
|
* @returns the default SPIBusConfig.
|
|
|
*/
|
|
|
static SPIBusConfig getDefaultSPIConfig()
|
|
|
{
|
|
|
// Default parameters:
|
|
|
SPIBusConfig spiConfig{};
|
|
|
spiConfig.clockDivider = SPI::ClockDivider::DIV_32;
|
|
|
spiConfig.mode = SPI::Mode::MODE_2; // Set clock polarity to 1
|
|
|
return spiConfig;
|
|
|
}
|
|
|
|
|
|
bool init() override
|
|
|
{
|
|
|
// Start an SPI transaction
|
... | ... | @@ -129,7 +153,7 @@ public: |
|
|
return false;
|
|
|
}
|
|
|
|
|
|
spi.write(0x69, 111); // Write 111 into the 0x69 register
|
|
|
spi.writeRegister(0x69, 111); // Write 111 into the 0x69 register
|
|
|
|
|
|
// End of scope: The transaction is terminated here.
|
|
|
return true;
|
... | ... | @@ -137,7 +161,7 @@ public: |
|
|
|
|
|
bool selfTest() { return true; }
|
|
|
|
|
|
MySensorData sampleImpl() override
|
|
|
MockSensorData sampleImpl() override
|
|
|
{
|
|
|
SPITransaction spi{spislave};
|
|
|
|
... | ... | @@ -147,7 +171,7 @@ public: |
|
|
// Store into data
|
|
|
data = buf[0] || buf[1] << 8;
|
|
|
|
|
|
return MySensorData{data};
|
|
|
return MockSensorData{data};
|
|
|
}
|
|
|
|
|
|
private:
|
... | ... | @@ -156,19 +180,23 @@ private: |
|
|
int data;
|
|
|
};
|
|
|
```
|
|
|
|
|
|
#### Notes
|
|
|
|
|
|
- It is good practice to provide 2 constructors: one which initializes the `SPIBusConfig` with the sensor's default parameters, the other that accepts a custom `SPIBusConfig` from outside.
|
|
|
- Constructors must accept a **reference** to `SPIBusInterface`. **Do not** pass it by value (it will not compile) and **do not** use `SPIBus` instead of `SPIBusInterface`, to allow for greater flexibility.
|
|
|
- Constructors must accept a **reference** to `SPIBusInterface`. **Do not** pass it by value (it will not compile) and **do not** use `SPIBus` instead of `SPIBusInterface`, to allow for greater flexibility.
|
|
|
- `SPITransaction` must be instantiated inside a method scope. Do not instantiate and store it at class level (eg, do not store it in a local class variable).
|
|
|
- Reason: other sensor may access the bus (and change its configuration) between calls to `sample()` (or other methods that access the bus), so the bus must be reconfigured by instantiation a new `SPITransaction` before accessing it.
|
|
|
- Reason: other sensor may access the bus (and change its configuration) between calls to `sample()` (or other methods that access the bus), so the bus must be reconfigured by instantiation a new `SPITransaction` before accessing it.
|
|
|
|
|
|
### Low level acccess through SPIBusInterface
|
|
|
In some cases, it may be necessary to access the bus directly via the low level methods provided by `SPIBusInterface` (eg. if methods provided by `SPITransaction` are not suitable to communicate with a particular sensor).
|
|
|
|
|
|
In some cases, it may be necessary to access the bus directly via the low level methods provided by `SPIBusInterface` (eg. if methods provided by `SPITransaction` are not suitable to communicate with a particular sensor).\
|
|
|
Here, the `sampleImpl()` method from the previous example is modified to do so.
|
|
|
|
|
|
```cpp
|
|
|
// ...
|
|
|
// Same as before, but using directly SPIBusInterface
|
|
|
MySensorData sampleImpl() override
|
|
|
MockSensorData sampleImpl() override
|
|
|
{
|
|
|
splislave.bus.configure(spislave.config); // Configure the bus before accessing it
|
|
|
|
... | ... | @@ -185,17 +213,20 @@ MySensorData sampleImpl() override |
|
|
// Store into data
|
|
|
data = buf[0] || buf[1] << 8;
|
|
|
|
|
|
return MySensorData{data};
|
|
|
return MockSensorData{data};
|
|
|
}
|
|
|
// ...
|
|
|
|
|
|
```
|
|
|
|
|
|
### Notes
|
|
|
|
|
|
- Access through `SPIBusInterface` is discouraged due to its complexity and because it is error-prone (easy to forget to configure / select / deselect). Avoid it if possible.
|
|
|
|
|
|
## Further examples
|
|
|
|
|
|
See `tests/drivers/test-l3gd20.cpp` and `shared/Sensors/L3GD20.h` for a real life example of using SPIDriver to sample a gyroscope.
|
|
|
|
|
|
## Final considerations
|
|
|
|
|
|
- SPIDriver is **not thread-safe**. Access to the bus by different threads must be properly synchronized.
|
|
|
- It is strongly suggested to always access the bus from the same thread to avoid synchronization problems. |
|
|
- It is strongly suggested to always access the bus from the same thread to avoid synchronization problems. |
|
|
\ No newline at end of file |