... | ... | @@ -24,139 +24,159 @@ Contains all the configuration parameters of the bus. Multiple slaves on the sam |
|
|
Aggregates all the objects needed by a driver to access a bus.
|
|
|
|
|
|
## Examples
|
|
|
### High level acccess through SPITransaction
|
|
|
In this example, SPIDriver is used to write and read 1 byte from two sensors. The bus is configured differently for each sensor.
|
|
|
```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
|
|
|
### Simple SPI sensor
|
|
|
In this example, SPIDriver in a sensor driver to configure and sample it.
|
|
|
|
|
|
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
|
|
|
printf("Sensor 1: %d\n", (int)val);
|
|
|
// 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
|
|
|
printf("Sensor 2: %d\n", (int)val);
|
|
|
|
|
|
// End of scope: the SPITransaction is terminated here.
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
|
#### main.cpp
|
|
|
```cpp
|
|
|
#include "drivers/spi/SPIDriver.h"
|
|
|
#include "MySensor.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);
|
|
|
|
|
|
### Low level acccess through SPIBusInterface
|
|
|
In this example, exactly 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_mysensor(GPIOC_BASE, 1); // Chip select pin of the sensor (GPIO C1)
|
|
|
|
|
|
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
|
|
|
SPIBusConfig cfg_mysensor; // Bus configuration for the sensor
|
|
|
|
|
|
int main()
|
|
|
{
|
|
|
cfg_s1.br = SPIBaudRate::DIV_64; // Divide the peripheral clock by 64 (slave 1)
|
|
|
// Slave 1 defaults to cpol = 0
|
|
|
// 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
|
|
|
|
|
|
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)
|
|
|
// Create an instance of the sensor
|
|
|
MySensor sensor{bus, cs_mysensor, cgf_mysensor};
|
|
|
|
|
|
for(;;)
|
|
|
{
|
|
|
// Operations with slave 1
|
|
|
uint8_t reg = 0x25;
|
|
|
uint8_t data = 69;
|
|
|
// Initialize the sensor (configures its registers)
|
|
|
sensor.init();
|
|
|
|
|
|
bus.configure(cfg_s1); // Configure the bus for slave 1
|
|
|
// Sample and display
|
|
|
for(;;)
|
|
|
{
|
|
|
sensor.onSimpleUpdate();
|
|
|
int d = sensor.getData();
|
|
|
|
|
|
// Write one byte
|
|
|
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
|
|
|
printf("Sampled: %d\n", d);
|
|
|
}
|
|
|
|
|
|
// Read one byte
|
|
|
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);
|
|
|
|
|
|
printf("Sensor 1: %d\n", (int)val);
|
|
|
|
|
|
|
|
|
// Operation with slave 2
|
|
|
reg = 0x45;
|
|
|
data = 123;
|
|
|
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
|
|
|
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*.
|
|
|
- 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"
|
|
|
|
|
|
bus.configure(cfg_s2); // Configure the bus for slave 2
|
|
|
class MySensor : public Sensor
|
|
|
{
|
|
|
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)
|
|
|
: spislave(bus, cs, config)
|
|
|
{
|
|
|
// Here, the provided config is used instead of the dafault one
|
|
|
}
|
|
|
|
|
|
bool init() override
|
|
|
{
|
|
|
// Start an SPI transaction
|
|
|
// Upon instantiating an SPITransaction object, the bus is automatically
|
|
|
// configured with the config parameters provided in the constructor and
|
|
|
// stored in the SPISlave struct.
|
|
|
SPITransaction spi{spislave};
|
|
|
// The bus is thus configured and ready to be used to talk to the sensor
|
|
|
|
|
|
uint8_t whoami = spi.read(0x01); // Read WHO_AM_I register (address: 0x01)
|
|
|
if (whoami != 123)
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
spi.write(0x69, 111); // Write 111 into the 0x69 register
|
|
|
|
|
|
// End of scope: The transaction is terminated here.
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
void onSimpleUpdate() override
|
|
|
{
|
|
|
SPITransaction spi{spislave};
|
|
|
|
|
|
uint8_t buf[2];
|
|
|
spi.read(0x42, buf, 2); // Read 4 bytes into buf, starting from address 0x42
|
|
|
|
|
|
// Store into data
|
|
|
data = buf[0] || buf[1] << 8;
|
|
|
}
|
|
|
|
|
|
int getData() { return data; }
|
|
|
private:
|
|
|
SPISlave spislave;
|
|
|
|
|
|
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.
|
|
|
- `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 onSimpleUpdate() (or other methods that access the bus), so the bus must be reconfigured by instantiation a new `SPITransaction` before accessing it.
|
|
|
|
|
|
// Write one byte
|
|
|
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
|
|
|
### 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).
|
|
|
Here, the `onSimpleUpdate()` method from the previous example is modified to do so.
|
|
|
```cpp
|
|
|
// ...
|
|
|
// Same as before, but using directly SPIBusInterface
|
|
|
void onSimpleUpdate() override
|
|
|
{
|
|
|
splislave.bus.configure(spislave.config); // Configure the bus before accessing it
|
|
|
|
|
|
// Read one byte
|
|
|
reg = 0x70 | 0x80; // Specify the register we want to read, set the MSB to 1 to signal a read operation
|
|
|
uint8_t val;
|
|
|
uint9_t reg = 0x42 | 0x80; // Register address. Most significant bit is set to 1 to signal a read operation
|
|
|
|
|
|
bus.select(cs_s2);
|
|
|
bus.write(®, 1);
|
|
|
bus.read(&val, 1);
|
|
|
bus.deselect(cs_s2);
|
|
|
uint8_t buf[2];
|
|
|
bus.select(spislave.cs); // Assert chip select
|
|
|
|
|
|
printf("Sensor 2: %d\n", (int)val);
|
|
|
bus.write(®, 1); // Write register address (1 byte)
|
|
|
bus.read(buf, 2); // Read 2 bytes
|
|
|
|
|
|
}
|
|
|
bus.deselect(spislave.cs); // Clear chip select
|
|
|
|
|
|
return 0;
|
|
|
// Store into data
|
|
|
data = buf[0] || buf[1] << 8;
|
|
|
}
|
|
|
// ...
|
|
|
|
|
|
```
|
|
|
### 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.
|
|
|
- Each slave can use a different `SPIBusConfig`
|
|
|
- SPITransactions must be scoped (much like mutexes). If not, in the first example, the bus may be configured for *slave 2* before the operations with *slave 1* are performed.
|
|
|
- Since the bus is configured in the constructor of `SPITransaction`, `SPITransaction` objects must not be copied or stored. A new transaction must be instantiated every time you need to use the bus (just like mutexes)
|
|
|
- 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.
|
|
|
- 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. |