It's time to try out the SPI driver that we wrote in the previous tutorial.
This example uses the LIS3DSH accelerometer and temperature sensor that is present on the STM32F407VG discovery board.
You can find an implementation for the accelerometer and the temperature sensor in src/shared/sensors/LIS3DSH/LIS3DSH.h
.
Here we will only consider reading the temperature data.
Suggestion: keep the sensor's datasheet with you while following the tutorial and try to find in it all the things that here are mentioned. This datasheet is pretty simple and it's useful to start dealing with this type of documents.
Header file
We define the methods that we will be implemented in the cpp
file:
- init: configure sensor driver. It returns a boolean indicating if the initialization was successful.
-
checkWhoAmI: every sensor has a special read_only register that contains a number called
who_am_i
. This method checks that thewho_am_i
value is equal to the default one specified in the sensor's datasheet (which in this case is63
). It returns a boolean indicating if thewho_am_i
value is correct. -
getTemperature: read the temperature from the sensor and return it. As described in the datasheet the temperature is an 8-bit 2-complement (
int8_t
) value with a resolution of 1 LSB/deg. It means that is can only output integer temperature values. Finally it is also told that reading zero from the sensor corresponds to 25 celsius degrees (soTEMPERATURE_REF
is set to 25).
We also need two other memebers:
- ODR: it indicates the output data rate of the sensor.
- BDU: it indicates the block data update mode. This can be set to continuous mode (i.e. the sensor continuously produces samples) or we can configure the sensor so that it outputs a new sample only after the previous one has been read.
/* Copyright (c) <YEAR_HERE> Skyward Experimental Rocketry
* Author: <YOUR_NAME_HERE>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <SpiDriver.h>
namespace Boardcore
{
class TempSensor
{
public:
TempSensor(uint8_t odr, uint8_t bdu);
bool init();
bool checkWhoAmI();
int8_t getTemperature();
private:
SpiDriver spi_driver;
uint8_t odr;
uint8_t bdu;
const uint8_t WHO_AM_I_VALUE = 63;
const uint8_t TEMPERATURE_REF = 25;
};
} // namespace Boardcore
In order to configure the ODR and BDU values we can define two enum
that contain all the possible values for those two variables. All these values can be found in the sensor's datasheet.
Since the default value of ODR is ODR_POWER_DOWN
(i.e. the sensor is turned off), we are obliged to configure the ODR value in order for the sensor to work.
enum OutputDataRate : uint8_t
{
ODR_POWER_DOWN = 0, // default value
ODR_3_125_HZ = 1,
ODR_6_25_HZ = 2,
ODR_12_5_HZ = 3,
ODR_25_HZ = 4,
ODR_50_HZ = 5,
ODR_100_HZ = 6,
ODR_400_HZ = 7,
ODR_800_HZ = 8,
ODR_1600_HZ = 9
};
enum BlockDataUpdate : uint8_t
{
CONTINUOUS_UPDATE_MODE = 0,
UPDATE_AFTER_READ_MODE = 1 // values updated only when MSB and LSB are read
};
Finally in order to read the data from the sensor and to configure it we need some variables that indicate the sensor's registers addresses.
The registers that we need are:
- The register that contains the
who_am_i
value. - The register from which we can read the temperature values.
- The
CTRL_REG4
which is needed in order to configure the ODR and BDU values.
The registers addresses, of course, are specified in the sensor's datasheet.
enum Registers : uint8_t
{
WHO_AM_I_REG = 0x0F,
CTRL_REG4 = 0x20, // control register to set ODR and BDU
OUT_T = 0x0C // temperature output register
};
Cpp file
Import the header file and in the constructor simply initialize the odr and bdu members:
/* Copyright (c) <YEAR_HERE> Skyward Experimental Rocketry
* Author: <YOUR_NAME_HERE>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "TempSensor.h"
namespace Boardcore {
TempSensor::TempSensor(uint8_t odr, uint8_t bdu)
: odr(odr), bdu(bdu)
{
}
Check WHO_AM_I value
In order to check the who_am_i
value we can simply ask the SPI driver to read the WHO_AM_I
register. If the returned value is the correct one, than we can return true
(otherwise false
).
bool TempSensor::checkWhoAmI()
{
uint8_t who_am_i = spi_driver.read(WHO_AM_I_REG);
if (who_am_i == WHO_AM_I_VALUE)
{
return true;
}
return false;
}
Configure the sensor
During sensor's initialization, if the checkWhoAmI method returns false
, then the initialization fails.
To configure the ODR and BDU we have to set their values in the CTRL_REG4
register of the sensor. We have to compute the value to be written to the register's address.
The CTRL_REG4
register has the following 8-bits structure (LSB on the right and MSB on the left):
ODR3 |
ODR2 |
ODR1 |
ODR0 |
BDU |
Zen | Yen | Xen |
---|
As we can se we have to shift the BDU value by 3 positions to the left and the ODR value by 4 positions to the left. We can achieve this result through a bitwise OR.
Finally we can write the resulting value to the CTRL_REG4
register's address using the SPI driver.
bool TempSensor::init()
{
if (!checkWhoAmI()) // wrong who_am_i value
{
return false;
}
// set the output data rate and the BDU in CTRL_REG4
uint8_t ctrl_reg4_value = (odr << 4) | (bdu << 3);
spi_driver.write(CTRL_REG4, ctrl_reg4_value);
return true; // correctly initialized
}
Read the temperature
In order to read the temperature sample the only things we need is to read the OUT_T
register through the SPI driver, rembering to add the TEMPERATURE_REF
value to all the samples:
int8_t TempSensor::getTemperature()
{
// the temperature is given as a 8-bits integer (in 2-complement)
// 1 LSB/deg - 8-bit resolution
// also, reading zero means 25 °C, so add the temperature reference
return spi_driver.read(OUT_T) + TEMPERATURE_REF;
}
} // namespace Boardcore
Create your entrypoint
In src/entrypoints
add a file called test-tempsensor.cpp
.
A simple entrypoint creates a TempSensor
object and periodically reads the temperature value:
/* Copyright (c) <YEAR_HERE> Skyward Experimental Rocketry
* Author: <YOUR_NAME_HERE>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <miosix.h>
#include <utils/Debug.h>
#include "drivers/spi/TempSensor.h"
using namespace miosix;
using namespace Boardcore;
int main()
{
TempSensor temp_driver(TempSensor::OutputDataRate::ODR_100_HZ,
TempSensor::BlockDataUpdate::UPDATE_AFTER_READ_MODE);
if (temp_driver.init())
{
while (true)
{
TRACE("Temp : %d °C \n", temp_driver.getTemperature());
Thread::sleep(2000);
}
}
return 0;
}
Compile with SBS
Let's write our dependencies variables; in boardcore.cmake, under Drivers, add:
${SBS_BASE}/src/shared/drivers/spi/TempSensor.cpp
${SBS_BASE}/src/shared/drivers/spi/SpiDriver.cpp
And then specify the entrypoint, including the dependencies variable:
add_executable(test-tempsensor src/entrypoints/test-tempsensor.cpp
)
sbs_target(test-tempsensor stm32f407vg_stm32f4discovery)
Run it!
Same as before: you can use the SBS script to build with debug enabled and flash the entrypoint:
./sbs -d -f test-tempsensor