diff --git a/sbs.conf b/sbs.conf index e1c6bca46034ca431f7c12905a67e777d29fa2c0..09a02da7f16e580d7722d5a0e10eca62a060baee 100644 --- a/sbs.conf +++ b/sbs.conf @@ -490,3 +490,11 @@ BinName: test-rls Include: %shared Defines: Main: test-rls + +[test-lis3dsh] +Type: test +BoardId: stm32f407vg_skyward_tortellino +BinName: test-lis3dsh +Include: %shared %spi +Defines: +Main: drivers/test-lis3dsh \ No newline at end of file diff --git a/src/shared/sensors/LIS3DSH/LIS3DSH.h b/src/shared/sensors/LIS3DSH/LIS3DSH.h new file mode 100644 index 0000000000000000000000000000000000000000..78d5094e8c6a89de8023e165675dff96114227e1 --- /dev/null +++ b/src/shared/sensors/LIS3DSH/LIS3DSH.h @@ -0,0 +1,458 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Authors: Luca Conterio + * + * 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 "sensors/Sensor.h" +#include "drivers/spi/SPIDriver.h" + +/** + * Driver for stm32f407vg discovery on-board 3-axis + * accelerometer + temperature sensor. + * + * The sensor is connected to SPI1 using the + * following GPIOs: PA5 : clock + * PA6 : miso + * PA7 : mosi + * PE3 : chip select +*/ +class LIS3DSH: public AccelSensor, public TemperatureSensor +{ + public: + + /** + * @brief Constructor. + * + * @param bus the spi bus. + * @param chip_select the chip_select for the sensor. + * @param _odr output data rate for the accelerometer. + * Default value is 100 Hz. + * @param _bdu BDU value, continuous or non-continuous update mode. + * Default value is to update after data has been read (BDU=1). + * @param _full_scale full scale range (from +/-2g up to +/-16g). + * Default value is +/-2g. + */ + LIS3DSH(SPIBusInterface& bus, + GpioPin chip_select, + uint8_t _odr=ODR_100_HZ, + uint8_t _bdu=UPDATE_AFTER_READ_MODE, + uint8_t _full_scale=FULL_SCALE_2G): + spi_slave(bus, chip_select, {}), odr(_odr), bdu(_bdu), full_scale(_full_scale) + { + spi_slave.config.clock_div = SPIClockDivider::DIV64; // used to set the spi baud rate (maximum is 10 Mhz) + } + + /** + * @brief Constructor. + * + * @param bus the spi bus. + * @param chip_select the chip_select for the sensor. + * @param config the spi bus configurations. + * @param _odr output data rate for the accelerometer. + * Default value is 100 Hz. + * @param _bdu BDU value, continuous or non-continuous update mode. + * Default value is to update after data has been read (BDU=1). + * @param _full_scale full scale range (from +/-2g up to +/-16g). + * Default value is +/-2g. + */ + LIS3DSH(SPIBusInterface& bus, + GpioPin chip_select, + SPIBusConfig config, + uint8_t _odr=ODR_100_HZ, + uint8_t _bdu=UPDATE_AFTER_READ_MODE, + uint8_t _full_scale=FULL_SCALE_2G): + spi_slave(bus, chip_select, config), odr(_odr), bdu(_bdu), full_scale(_full_scale) {} + + /** + * @brief Initialize the sensor. + * + * @return boolean value indicating whether the operation succeded or not + */ + bool init() override { + // check if the sensor is already initialized + if (initialized) { + TRACE("[LIS3DSH] init() : already initialized \n"); + return false; + } + + // check if the sensor is working properly + if (!checkWhoAmI()) { // whoami default value + return false; // sensor correctly initialized + } + + SPITransaction spi(spi_slave); + + // set the full scale value in CTRL_REG5 + uint8_t ctrl_reg5_value = (full_scale << 3); + spi.write(CTRL_REG5, ctrl_reg5_value); + + // select the correct sensitivity + // for the specified full scale range + sensitivity = select_sensitivity(); + + // set the output data rate and the BDU in CTRL_REG4 + // the three least significant bits are enable bits for X, Y and Z axis + uint8_t ctrl_reg4_value = (odr << 4) | (bdu << 3) | (7 << 0); // 7 = 111 -> enable the 3 axis + spi.write(CTRL_REG4, ctrl_reg4_value); + + TRACE("[LIS3DSH] init() : ok \n"); + + initialized = true; + + return true; + } + + /** + * @brief Read new data from the accelerometer. + * Acceleretions are returned in mg. + * + * @return boolean value indicating whether the operation succeded or not + */ + bool onSimpleUpdate() override { + // check if the sensor is initialized + if (!initialized) { + TRACE("[LIS3DSH] onSimpleUpdate() : not initialized, unable to sample data \n"); + return false; + } + return readAccelData(); + } + + /** + * @brief Read temperature data. + * The temperature returned by the sensor is an 8-bits integer. + * + * @return boolean value indicating whether the operation succeded or not + */ + bool updateTemperature() { + // check if the sensor is initialized + if (!initialized) { + TRACE("[LIS3DSH] updateTemperature() : not initialized, unable to sample data \n"); + return false; + } + + SPITransaction spi(spi_slave); + + // NOTE: the temperature is given as a 8-bits integer (in 2-complement) + // while the TemperatureSensor interface returns a float value + // through the method tempDataPtr() + mLastTemp = spi.read(OUT_T) + TEMPERATURE_REF; // add the 'zero' of the temperature sensor + + return true; + } + + /** + * @brief Check if the sensor is working. + * + * @return boolean indicating whether the sensor is correctly working or not + */ + bool selfTest() override { + // check if the sensor is initialized + if (!initialized) { + TRACE("[LIS3DSH] selfTest() : not initialized, unable to self-test \n"); + return false; + } + + SPITransaction spi(spi_slave); + + const uint8_t num_samples = 5; // number of samples to be used + // vectors for storing samples, both + // in self-test and no-self-test modes + float X_ST[] = {0, 0, 0, 0, 0}; + float Y_ST[] = {0, 0, 0, 0, 0}; + float Z_ST[] = {0, 0, 0, 0, 0}; + float X_NO_ST[] = {0, 0, 0, 0, 0}; + float Y_NO_ST[] = {0, 0, 0, 0, 0}; + float Z_NO_ST[] = {0, 0, 0, 0, 0}; + // vectors containing avg values for each axis + float AVG_ST[] = {0, 0, 0}; // one element per axis + float AVG_NO_ST[] = {0, 0, 0}; // one element per axis + + // set full scale to default value +/-2g + // enable the self-test mode with positive sign + uint8_t ctrl_reg5_value = (FULL_SCALE_2G << 3) | (1 << 1); + spi.write(CTRL_REG5, ctrl_reg5_value); + // read samples in self-test positive sign mode + for (int i = 0; i < num_samples; i++) { + readAccelData(); + X_ST[i] = mLastAccel.getX(); + Y_ST[i] = mLastAccel.getY(); + Z_ST[i] = mLastAccel.getZ(); + miosix::Thread::sleep(10); + } + // reset the self-test bits + ctrl_reg5_value &= ~(3 << 1); + // normal mode with full scale range +/-2g + ctrl_reg5_value |= (FULL_SCALE_2G << 3); + spi.write(CTRL_REG5, ctrl_reg5_value); + // read samples in normal mode + for (int i = 0; i < num_samples; i++) { + readAccelData(); + X_NO_ST[i] = mLastAccel.getX(); + Y_NO_ST[i] = mLastAccel.getY(); + Z_NO_ST[i] = mLastAccel.getZ(); + miosix::Thread::sleep(10); + } + // compute averages vectors: + // they contain one element for each axis + // (position 0 for x, 1 for y and 2 for z) + // AVG_ST : for self-test samples + // AVG_NO_ST : for normal mode samples + for (int i = 0; i < num_samples; i++) { + AVG_ST[0] += X_ST[i]; + AVG_ST[1] += Y_ST[i]; + AVG_ST[2] += Z_ST[i]; + AVG_NO_ST[0] += X_NO_ST[i]; + AVG_NO_ST[1] += Y_NO_ST[i]; + AVG_NO_ST[2] += Z_NO_ST[i]; + } + for (int i = 0; i < 3; i++) { + AVG_ST[i] /= num_samples; + AVG_NO_ST[i] /= num_samples; + } + + // Reset registers values with the ones + // specified in the constructor: + // set the full scale value in CTRL_REG5 + ctrl_reg5_value = (full_scale << 3); // normal mode + spi.write(CTRL_REG5, ctrl_reg5_value); + + // check that the averages differences + // do not exceed maximum tolerance + if ((AVG_NO_ST[0] - AVG_ST[0] > SELF_TEST_TOLERANCE_X_Y) || + (AVG_NO_ST[1] - AVG_ST[1] > SELF_TEST_TOLERANCE_X_Y) || + (AVG_NO_ST[2] - AVG_ST[2] > SELF_TEST_TOLERANCE_Z)) { + TRACE("[LIS3DSH] selfTest() : failed \n"); + return false; + } + TRACE("[LIS3DSH] selfTest() : ok \n"); + return true; + } + + /** + * @brief Output data rate allowed values (4 bits). + */ + enum ODR { + ODR_POWER_DOWN = 0, // 0000 + ODR_3_125_HZ = 1, // 0001, 3.125 Hz + ODR_6_25_HZ = 2, // 0010, 6.25 Hz + ODR_12_5_HZ = 3, // 0011, 12.5 Hz + ODR_25_HZ = 4, // 0100 + ODR_50_HZ = 5, // 0101 + ODR_100_HZ = 6, // 0110, default value + ODR_400_HZ = 7, // 0111 + ODR_800_HZ = 8, // 1000 + ODR_1600_HZ = 9 // 1001 + }; + + /** + * @brief Full scale range allowed values (3 bits). + */ + enum FULL_SCALE { + FULL_SCALE_2G = 0, // 000, +/- 2g + FULL_SCALE_4G = 1, // 001, +/- 4g + FULL_SCALE_6G = 2, // 010, +/- 6g + FULL_SCALE_8G = 3, // 011, +/- 8g + FULL_SCALE_16G = 4, // 100 +/- 16g + }; + + /** + * @brief Block data update allowed modes (1 bit). + */ + enum BDU { + CONTINUOUS_UPDATE_MODE = 0, // continuous update of accelerometer data + UPDATE_AFTER_READ_MODE = 1 // values updated only when MSB and LSB are read (recommended) + }; + + private: + + /** + * @brief Read data from the accelerometer. + * + * @return whether the operation succeded or not + */ + bool readAccelData() { + SPITransaction spi(spi_slave); + + // read the sensor's status register + uint8_t status = spi.read(STATUS); + + if (status & 0x08) { // bit 3 of status set to 1 (new data available) + if (status & 0x80) { // bit 7 of status set to 1 (some data overwritten) + + // read acceleration on X + int8_t acc_L = spi.read(OUT_X_L); + int8_t acc_H = spi.read(OUT_X_H); + int16_t acceleration = combine(acc_H, acc_L) * sensitivity; + mLastAccel.setX(acceleration); + + // read acceleration on Y + acc_L = spi.read(OUT_Y_L); + acc_H = spi.read(OUT_Y_H); + acceleration = combine(acc_H, acc_L) * sensitivity; + mLastAccel.setY(acceleration); + + // read acceleration on Z + acc_L = spi.read(OUT_Z_L); + acc_H = spi.read(OUT_Z_H); + acceleration = combine(acc_H, acc_L) * sensitivity; + mLastAccel.setZ(acceleration); + + return true; + } + } + + return false; + } + + /** + * @brief Check that the WHO_AM_I register + * contains the correct value. + * + * @return boolean value indicating whether the value read + * from the WHO_AM_I register is correct or not + */ + bool checkWhoAmI() { + SPITransaction spi(spi_slave); + + // check the WHO_AM_I_REG register + uint8_t who_am_i_value = spi.read(WHO_AM_I_REG); + if (who_am_i_value == WHO_AM_I_DEFAULT_VALUE) { // whoami default value + TRACE("[LIS3DSH] WHO_AM_I : ok \n"); + return true; + } + else { + TRACE("[LIS3DSH] WHO_AM_I : wrong value, %d instead of %d \n", + who_am_i_value, WHO_AM_I_DEFAULT_VALUE); + last_error = ERR_NOT_ME; + } + + return false; + } + + /** + * @brief Combine low and high bits in a single number. + * + * @param msb the most significatn bits + * @param lsb the least significant bits + * @return MSB and LSB combined in one value + */ + int16_t combine(uint8_t msb, uint8_t lsb) { + return (msb << 8) | lsb; + } + + /** + * @brief Given the requested full scale range, select the correct sensitivity value. + * + * @return the sensitivity value corresponding to the requested full scale range + */ + float select_sensitivity() { + float s; + switch (full_scale) { + case FULL_SCALE_2G: + s = sensitivity_values._2G; + break; + case FULL_SCALE_4G: + s = sensitivity_values._4G; + break; + case FULL_SCALE_6G: + s = sensitivity_values._6G; + break; + case FULL_SCALE_8G: + s = sensitivity_values._8G; + break; + case FULL_SCALE_16G: + s = sensitivity_values._16G; + break; + default: + TRACE("[LIS3DSH] Invalid full scale range given, using +/-2g \n"); + this->full_scale = FULL_SCALE_2G; + s = sensitivity_values._2G; + break; + } + return s; + } + + /** + * @brief Registers' addresses definition. + */ + enum REG { + + // whoami register + WHO_AM_I_REG = 0x0F, + + // control registers for the accelerometer + CTRL_REG4 = 0x20, // control register to set accelerometer's ODR and BDU + CTRL_REG1 = 0x21, // state Machine 1 interrupt configuration register + CTRL_REG2 = 0x22, // state Machine 2 interrupt configuration register + CTRL_REG3 = 0x23, + CTRL_REG5 = 0x24, // control register to set the accelerometer full scale range, + // anti-aliansing filter and self-test enable + CTRL_REG6 = 0x25, + + // status register + STATUS = 0x27, + + // accelerometer output registers + // for x, y and z axis + // (low and high bits in separate registers) + OUT_X_L = 0x28, + OUT_X_H = 0x29, + OUT_Y_L = 0x2A, + OUT_Y_H = 0x2B, + OUT_Z_L = 0x2C, + OUT_Z_H = 0x2D, + + // temperature output register + OUT_T = 0x0C, + }; + + /** + * @brief Definition of each sensitivity value, + * corresponding to full scale range allowed values. + */ + struct SENSITIVITY { + const float _2G = 0.06; // +/- 2g + const float _4G = 0.12; // +/- 4g + const float _6G = 0.18; // +/- 6g + const float _8G = 0.24; // +/- 8g + const float _16G = 0.73; // +/- 16g + }; + SENSITIVITY sensitivity_values; + + SPISlave spi_slave; + + bool initialized = false; // whether the sensor has been initialized or not + + uint8_t odr; // output data rate, default 100 Hz + uint8_t bdu; // continuous mode or not, default is 0 (continuous update) + uint8_t full_scale; // full scale range value (default +/-2g) + + float sensitivity = sensitivity_values._2G; // default sensitivity value + + uint8_t WHO_AM_I_DEFAULT_VALUE = 63; // 00111111 + + uint8_t TEMPERATURE_REF = 25; // temperature sensor 'zero'/reference value + // (value 0x00 from the sensor corresponds to 25 degrees celsius) + + uint16_t SELF_TEST_TOLERANCE_X_Y = 140; // 140 mg + uint16_t SELF_TEST_TOLERANCE_Z = 590; // 590 mg +}; diff --git a/src/shared/sensors/LIS3DSH/LIS3DSHData.h b/src/shared/sensors/LIS3DSH/LIS3DSHData.h new file mode 100644 index 0000000000000000000000000000000000000000..b9d834d5ea7a8ddb8fe45ad5ff4d313022060901 --- /dev/null +++ b/src/shared/sensors/LIS3DSH/LIS3DSHData.h @@ -0,0 +1,44 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Authors: Luca Conterio + * + * 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 <ostream> +#include "math/Vec3.h" + +struct LIS3DSHData +{ + long long timestamp; + Vec3 accel; + int8_t temperature; + + static std::string header() + { + return "timestamp,acc_x,acc_y,acc_z,temperature\n"; + } + + void print(std::ostream& os) const + { + os << timestamp << "," << accel.getX() << "," << accel.getY() << "," + << accel.getZ() << temperature << "\n"; + } +}; diff --git a/src/tests/drivers/test-lis3dsh.cpp b/src/tests/drivers/test-lis3dsh.cpp new file mode 100644 index 0000000000000000000000000000000000000000..79fc511fa17bd7a64f22be5a9b48def0fce47180 --- /dev/null +++ b/src/tests/drivers/test-lis3dsh.cpp @@ -0,0 +1,111 @@ +/* Copyright (c) 2020 Skyward Experimental Rocketry + * Authors: Luca Conterio + * + * 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 <drivers/spi/SPIDriver.h> +#include "sensors/LIS3DSH/LIS3DSH.h" +#include "sensors/LIS3DSH/LIS3DSHData.h" + +using namespace std; +using namespace miosix; + +SPIBus bus(SPI1); +GpioPin cs(GPIOE_BASE, 3); + +int main() { + + { + FastInterruptDisableLock dLock; + + RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; // SPI1 ENABLE + + cs.mode(Mode::OUTPUT); + } + cs.high(); + + LIS3DSH sensor( + bus, + cs, + sensor.ODR_100_HZ, + sensor.UPDATE_AFTER_READ_MODE, + sensor.FULL_SCALE_4G + ); + + Vec3 acc; + int8_t temp; + + // sensor not initialized, should return false + if (sensor.onSimpleUpdate() || sensor.updateTemperature()) { + printf("\nTest failed: sensor not initialized \n"); + return -1; + } + + bool initialized = false; + // initialize imu + if (!sensor.init()) { + if (sensor.getLastError() == sensor.ERR_NOT_ME) { + printf("Test failed: invalid WHO_AM_I value, init failed \n"); + } + else { + printf("Test failed: init failed \n"); + } + return -1; + } + initialized = true; + + // check if the sensor is properly working + if (!sensor.selfTest()) { + printf("\nTest failed: self-test failed \n"); + return -1; + } + + // if sensor already inizialized, init() should return false + if (initialized) { + if (sensor.init()) { + printf("\nTest failed: sensor is already initialized \n"); + return -1; + } + } + + Thread::sleep(500); + + // sample some data from the sensor + for(int i = 0; i < 5; i++) { + sensor.updateTemperature(); + // sensor intitialized, should return true (false if no new data exist) + if (!sensor.onSimpleUpdate()) { + printf("\nWarning: no new data to be read \n"); + } + + acc = *(sensor.accelDataPtr()); + temp = (int8_t) *(sensor.tempDataPtr()); + + printf("\nAccel: x: %.2f | y: %.2f | z: %.2f \n", acc.getX(), acc.getY(), acc.getZ()); + printf("Temp: %d C \n", temp); + + Thread::sleep(200); + } + + printf("\nTest ok \n"); + + return 0; +} \ No newline at end of file