... | @@ -3,60 +3,76 @@ This page provides documentation for our own SPI driver implementation, which yo |
... | @@ -3,60 +3,76 @@ 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.
|
|
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.
|
|
|
|
|
|
# SPI Driver
|
|
# SPI Driver
|
|
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`.
|
|
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.\
|
|
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.
|
|
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
|
|
## 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
|
|
- `setClockDiver(ClockDivider divider)` configures the peripheral clock divider
|
|
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:
|
|
- `setMode(Mode mode)` sets the clock polarity and phase
|
|
- `configure(...)` Configures the bus with the provided parameters
|
|
|
|
- `select(...)` / `deselect(...)` asserts and clears the Chip Select (CS) pin
|
|
|
|
- `write(...)` writes bytes on the bus
|
|
- `write(...)` writes bytes on the bus
|
|
- `read(...)` reads bytes from the bus
|
|
- `read(...)` reads bytes from the bus
|
|
- `transfer(...)` performs a full duplex transaction, writing the provided bytes and returning the received bytes
|
|
- `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)
|
|
### 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.
|
|
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.
|
|
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
|
|
### 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.
|
|
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
|
|
### 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
|
|
### SPISlave struct
|
|
|
|
|
|
Aggregates all the objects needed by a driver to access a bus.
|
|
Aggregates all the objects needed by a driver to access a bus.
|
|
|
|
|
|
## Examples
|
|
## Examples
|
|
|
|
|
|
### Simple SPI sensor
|
|
### 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.
|
|
More details about sensors can be found in the [Sensor](Sensor) page.
|
|
|
|
|
|
#### main.cpp
|
|
#### main.cpp
|
|
|
|
|
|
```cpp
|
|
```cpp
|
|
#include "drivers/spi/SPIDriver.h"
|
|
#include <drivers/spi/SPIDriver.h>
|
|
#include "MySensor.h"
|
|
#include "MockSensor.h"
|
|
|
|
|
|
// Creates an SPIBus object using the SPI1 peripheral
|
|
// Creates an SPIBus object using the SPI1 peripheral
|
|
// The same instance of the SPIBus object must be shared among all slaves on the
|
|
// The same instance of the SPIBus object must be shared among all slaves on the
|
|
// physical SPI bus
|
|
// physical SPI bus
|
|
SPIBus bus(SPI1);
|
|
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()
|
|
int main()
|
|
{
|
|
{
|
|
// Set bus parameters
|
|
// Set bus parameters
|
|
cfg_mysensor.br = SPIBaudRate::DIV_64; // Divide the SPI peripheral clock
|
|
configuration.clockDivider = SPI::ClockDivider::DIV_256;
|
|
// by 64
|
|
configuration.mode = SPI::Mode::MODE_1; // Set clock polarity to 0 and phase to 1
|
|
cfg_mysensor.cpol = 1; // Set clock polarity to 1
|
|
|
|
|
|
|
|
// Create an instance of the sensor
|
|
// 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)
|
|
// Initialize the sensor (configures its registers)
|
|
if (!sensor.init() || !sensor.selfTest())
|
|
if (!sensor.init() || !sensor.selfTest())
|
... | @@ -76,44 +92,52 @@ int main() |
... | @@ -76,44 +92,52 @@ int main() |
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
```
|
|
```
|
|
|
|
|
|
#### Notes
|
|
#### 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.
|
|
|
|
|
|
- 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:
|
|
If not, you have to:
|
|
- Enable the SPI peripheral clock
|
|
- Enable the SPI peripheral clock\
|
|
Example: `RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;`
|
|
Example: `RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;`
|
|
- Set `MISO`, `MOSI`, `CLOCK` GPIOs to the correct alternate mode
|
|
- Set `MISO`, `MOSI`, `CLOCK` GPIOs to the correct alternate mode
|
|
- Set `CS` GPIO to output mode, and set its value to _high_.
|
|
- 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.
|
|
- 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`).
|
|
- Each slave can, however, have a different bus configuration (`SPIBusConfig`).
|
|
|
|
|
|
#### MySensor.h
|
|
#### MySensor.h
|
|
|
|
|
|
```cpp
|
|
```cpp
|
|
#include "drivers/spi/SPIDriver.h"
|
|
#include <drivers/spi/SPIDriver.h>
|
|
|
|
|
|
struct MySensorData : public TimestampData
|
|
struct MockSensorData : public TimestampData
|
|
{
|
|
{
|
|
int data;
|
|
int data;
|
|
}
|
|
}
|
|
|
|
|
|
class MySensor : public Sensor<MySensorData>
|
|
class MockSensor : public Sensor<MySensorData>
|
|
{
|
|
{
|
|
public:
|
|
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
|
|
// Constructor with custom bus config
|
|
MySensor(SPIBusInterface& bus, GpioPin cs, SPIBusConfig config)
|
|
MySensor(SPIBusInterface& bus, GpioPin cs, SPIBusConfig config = getDefaultSPIConfig())
|
|
: spislave(bus, cs, config)
|
|
: spislave(bus, cs, config)
|
|
{
|
|
{
|
|
// Here, the provided config is used instead of the dafault one
|
|
// 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
|
|
bool init() override
|
|
{
|
|
{
|
|
// Start an SPI transaction
|
|
// Start an SPI transaction
|
... | @@ -129,7 +153,7 @@ public: |
... | @@ -129,7 +153,7 @@ public: |
|
return false;
|
|
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.
|
|
// End of scope: The transaction is terminated here.
|
|
return true;
|
|
return true;
|
... | @@ -137,7 +161,7 @@ public: |
... | @@ -137,7 +161,7 @@ public: |
|
|
|
|
|
bool selfTest() { return true; }
|
|
bool selfTest() { return true; }
|
|
|
|
|
|
MySensorData sampleImpl() override
|
|
MockSensorData sampleImpl() override
|
|
{
|
|
{
|
|
SPITransaction spi{spislave};
|
|
SPITransaction spi{spislave};
|
|
|
|
|
... | @@ -147,7 +171,7 @@ public: |
... | @@ -147,7 +171,7 @@ public: |
|
// Store into data
|
|
// Store into data
|
|
data = buf[0] || buf[1] << 8;
|
|
data = buf[0] || buf[1] << 8;
|
|
|
|
|
|
return MySensorData{data};
|
|
return MockSensorData{data};
|
|
}
|
|
}
|
|
|
|
|
|
private:
|
|
private:
|
... | @@ -156,19 +180,23 @@ private: |
... | @@ -156,19 +180,23 @@ private: |
|
int data;
|
|
int data;
|
|
};
|
|
};
|
|
```
|
|
```
|
|
|
|
|
|
#### Notes
|
|
#### 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.
|
|
- 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).
|
|
- `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
|
|
### 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.
|
|
Here, the `sampleImpl()` method from the previous example is modified to do so.
|
|
|
|
|
|
```cpp
|
|
```cpp
|
|
// ...
|
|
// ...
|
|
// Same as before, but using directly SPIBusInterface
|
|
// Same as before, but using directly SPIBusInterface
|
|
MySensorData sampleImpl() override
|
|
MockSensorData sampleImpl() override
|
|
{
|
|
{
|
|
splislave.bus.configure(spislave.config); // Configure the bus before accessing it
|
|
splislave.bus.configure(spislave.config); // Configure the bus before accessing it
|
|
|
|
|
... | @@ -185,17 +213,20 @@ MySensorData sampleImpl() override |
... | @@ -185,17 +213,20 @@ MySensorData sampleImpl() override |
|
// Store into data
|
|
// Store into data
|
|
data = buf[0] || buf[1] << 8;
|
|
data = buf[0] || buf[1] << 8;
|
|
|
|
|
|
return MySensorData{data};
|
|
return MockSensorData{data};
|
|
}
|
|
}
|
|
// ...
|
|
// ...
|
|
|
|
|
|
```
|
|
```
|
|
|
|
|
|
### Notes
|
|
### 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.
|
|
- 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
|
|
## 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.
|
|
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
|
|
## Final considerations
|
|
|
|
|
|
- SPIDriver is **not thread-safe**. Access to the bus by different threads must be properly synchronized.
|
|
- 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 |