|
|
|
|
|
This tutorial has been highly inspired by : [passion-in-action SPI example](https://github.com/skyward-er/passion_in_action/blob/master/src/4.bus_spi/main.cpp#L119).
|
|
|
This tutorial has been highly inspired by : [passion-in-action SPI example](https://github.com/skyward-er/passion_in_action/blob/master/src/4.bus_spi/main.cpp#L119).
|
|
|
|
|
|
## Introduction
|
|
|
The [Serial Peripheral Interface](https://en.wikipedia.org/wiki/Serial_Peripheral_Interface) is a pretty simple and widely used communication protocol.
|
|
|
A prerequisite for this tutorial is to know the basics of how the SPI protocol works.
|
|
|
|
|
|
|
|
|
The [Serial Peripheral Interface](https://en.wikipedia.org/wiki/Serial_Peripheral_Interface) is a pretty simple and widely used communication protocol.
|
|
|
A prerequisite for this tutorial is to know the basics of how the SPI protocol works.
|
|
|
|
|
|
In this tutorial you will write a basic driver that can be used to control the communication over an SPI bus.
|
|
|
|
|
|
## Header file
|
|
|
First of all we have to define the GPIOs that are needed in order for the SPI bus to work.
|
|
|
|
|
|
First of all we have to define the GPIOs that are needed in order for the SPI bus to work.
|
|
|
Those GPIOs are:
|
|
|
|
|
|
- **SCK**: bus clock signal
|
|
|
- **MISO**: communication from slave to master (Master Input Slave Output)
|
|
|
- **MOSI**: communication master to slave (Master Output Slave Input)
|
|
|
|
|
|
> :warning: **The SPI is a *syncronous bus*: while the master is writing to `MOSI`, the slave is writing to `MISO`.**
|
|
|
> :warning: **The SPI is a _syncronous bus_: while the master is writing to `MOSI`, the slave is writing to `MISO`.**
|
|
|
|
|
|
Consider that the GPIOs associated to the SPI bus (`SPI1` in this example) can vary according to the microcontroller you are using. For the STM32F407VG, the needed GPIOs are
|
|
|
`PA5`, `PA6` and `PA7`.
|
|
|
|
|
|
```cpp
|
|
|
using namespace miosix;
|
|
|
|
... | ... | @@ -26,41 +29,46 @@ typedef Gpio<GPIOA_BASE, 6> miso; |
|
|
typedef Gpio<GPIOA_BASE, 7> mosi;
|
|
|
```
|
|
|
|
|
|
Moreover we need to specify a **chip-select** pin: when set to the low logic level it indicates the beginning of the communication. If you have multiple slaves you will need one chip-select pin for each of them.
|
|
|
Moreover we need to specify a **chip-select** pin: when set to the low logic level it indicates the beginning of the communication. If you have multiple slaves you will need one chip-select pin for each of them.
|
|
|
In this example we consider the scenario in which we have a single slave, so a single chip-select pin is required:
|
|
|
|
|
|
```cpp
|
|
|
typedef Gpio<GPIOE_BASE, 3> cs;
|
|
|
```
|
|
|
|
|
|
Then we define the methods that we will be implemented in the `cpp` file:
|
|
|
- *config*: configure the SPI peripheral
|
|
|
- *read*: read a specific slave address/register, given by `addr`
|
|
|
- *write*: write the paramter `value` to a specific slave address/register, given by `addr`
|
|
|
- *sendRecv*: private method that actually handles the communication
|
|
|
- *waitBusy*: wait until the SPI bus is busy
|
|
|
|
|
|
- _config_: configure the SPI peripheral
|
|
|
- _read_: read a specific slave address/register, given by `addr`
|
|
|
- _write_: write the paramter `value` to a specific slave address/register, given by `addr`
|
|
|
- _sendRecv_: private method that actually handles the communication
|
|
|
- _waitBusy_: wait until the SPI bus is busy
|
|
|
|
|
|
```cpp
|
|
|
class SpiDriver
|
|
|
{
|
|
|
public:
|
|
|
|
|
|
|
|
|
SpiDriver();
|
|
|
|
|
|
|
|
|
void config();
|
|
|
|
|
|
|
|
|
uint8_t read(uint8_t addr);
|
|
|
|
|
|
|
|
|
void write(uint8_t addr, uint8_t value);
|
|
|
|
|
|
private:
|
|
|
|
|
|
uint8_t sendRecv(uint8_t data);
|
|
|
|
|
|
|
|
|
void waitBusy();
|
|
|
};
|
|
|
```
|
|
|
|
|
|
## Cpp file
|
|
|
|
|
|
Import the header file and specify that we are using the `miosix` namespace:
|
|
|
|
|
|
```cpp
|
|
|
#include "SpiDriver.h"
|
|
|
|
... | ... | @@ -68,6 +76,7 @@ using namespace miosix; |
|
|
```
|
|
|
|
|
|
#### SPI Configuration
|
|
|
|
|
|
```cpp
|
|
|
void SpiDriver::config()
|
|
|
{
|
... | ... | @@ -78,7 +87,7 @@ void SpiDriver::config() |
|
|
cs::high();
|
|
|
|
|
|
// SCK, MISO and MOSI GPIOs have to be configured in alternate mode. In this
|
|
|
// mode they are controlled by the internal SPI peripheral and not by the
|
|
|
// mode they are controlled by the internal SPI peripheral and not by the
|
|
|
// GPIO control registers
|
|
|
sck::mode(Mode::ALTERNATE);
|
|
|
miso::mode(Mode::ALTERNATE);
|
... | ... | @@ -132,7 +141,9 @@ void SpiDriver::config() |
|
|
```
|
|
|
|
|
|
#### Send and receive data
|
|
|
|
|
|
In order to send over the bus we have to put the data we want to send into peripheral's data register (`SPI1->DR`). Then we have to wait until the data transfer is completed. Finally we can read the `SPI1->DR` register in order to read the received data, which is then returned by the method.
|
|
|
|
|
|
```cpp
|
|
|
uint8_t SpiDriver::sendRecv(uint8_t data)
|
|
|
{
|
... | ... | @@ -141,26 +152,32 @@ uint8_t SpiDriver::sendRecv(uint8_t data) |
|
|
return SPI1->DR;
|
|
|
}
|
|
|
```
|
|
|
where the *waitBusy()* method is implemented as:
|
|
|
|
|
|
where the _waitBusy()_ method is implemented as:
|
|
|
|
|
|
```cpp
|
|
|
void SpiDriver::waitBusy()
|
|
|
{
|
|
|
// Wait until the RX Not Empty flag in peripheral's status
|
|
|
// register (SPI1->SR) is set. This signals that the data transfer
|
|
|
// is completed and the data received from the slave device is
|
|
|
// Wait until the RX Not Empty flag in peripheral's status
|
|
|
// register (SPI1->SR) is set. This signals that the data transfer
|
|
|
// is completed and the data received from the slave device is
|
|
|
// into data register (SPI1->DR)
|
|
|
while((SPI1->SR & SPI_SR_RXNE) == 0)
|
|
|
{
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
#### Write to address
|
|
|
As said previously when we want to communicate with the slave we have to pull the chip-select to the low state.
|
|
|
|
|
|
As said previously when we want to communicate with the slave we have to pull the chip-select to the low state.
|
|
|
Then, in order to write a value to a specific address:
|
|
|
|
|
|
- First send the address of the register we want to write
|
|
|
- Then send the value we want to write
|
|
|
|
|
|
When the transfer is done, we can bring the chip-select back to the high state to signal that the communication is finished.
|
|
|
|
|
|
```cpp
|
|
|
void SpiDriver::write(uint8_t addr, uint8_t value)
|
|
|
{
|
... | ... | @@ -173,15 +190,19 @@ void SpiDriver::write(uint8_t addr, uint8_t value) |
|
|
usleep(10); // wait 10 microseconds
|
|
|
}
|
|
|
```
|
|
|
When writing we can ignore the value that the *sendRecv()* method reads from the data register (the return value).
|
|
|
|
|
|
When writing we can ignore the value that the _sendRecv()_ method reads from the data register (the return value).
|
|
|
|
|
|
#### Read from address
|
|
|
|
|
|
If we want to read a specific address, these are the steps to be followed:
|
|
|
- First send the register address we want to read with the 8th bit set to 1 in order to signal that we want to read the register. This can be done through a *bitwise OR* operation between `0x80` and the register address.
|
|
|
|
|
|
- First send the register address we want to read with the 8th bit set to 1 in order to signal that we want to read the register. This can be done through a _bitwise OR_ operation between `0x80` and the register address.
|
|
|
- Then send a dummy value (e.g. `0x00`) to the slave to make it write back the register
|
|
|
content (that we can then read). The written back value is read by the *sendRecv()* method in this case and returned to the caller function.
|
|
|
content (that we can then read). The written back value is read by the _sendRecv()_ method in this case and returned to the caller function.
|
|
|
|
|
|
When the transfer is done, we can bring the chip-select back to the high state to signal that the communication is finished.
|
|
|
|
|
|
```cpp
|
|
|
uint8_t SpiDriver::read(uint8_t addr)
|
|
|
{
|
... | ... | @@ -198,4 +219,5 @@ uint8_t SpiDriver::read(uint8_t addr) |
|
|
```
|
|
|
|
|
|
## What's next?
|
|
|
In order to test the SPI driver we need a sensor from which we can read some data, so you can move on to the next tutorial: [Temperature Sensor](). |
|
|
\ No newline at end of file |
|
|
|
|
|
In order to test the SPI driver we need a sensor from which we can read some data, so you can move on to the next tutorial: [Temperature Sensor](Temperature-Sensor). |