Skyward boardcore
Loading...
Searching...
No Matches
LIS3DSH.h
Go to the documentation of this file.
1/* Copyright (c) 2020 Skyward Experimental Rocketry
2 * Author: Luca Conterio
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#pragma once
24
28#include <math.h>
29#include <sensors/Sensor.h>
30
31#include "LIS3DSHData.h"
32
33namespace Boardcore
34{
35
46class LIS3DSH : public Sensor<LIS3DSHData>
47{
48public:
62 LIS3DSH(SPIBusInterface& bus, miosix::GpioPin chipSelect,
63 uint8_t odr = OutputDataRate::ODR_100_HZ,
65 uint8_t fullScale = FullScale::FULL_SCALE_2G)
66 : spiSlave(bus, chipSelect), odr(odr), bdu(bdu), fullScale(fullScale)
67 {
68 spiSlave.config.clockDivider =
69 SPI::ClockDivider::DIV_64; // used to set the spi baud rate
70 // (maximum is 10 Mhz)
71 }
72
87 LIS3DSH(SPIBusInterface& bus, miosix::GpioPin chipSelect,
88 SPIBusConfig config, uint8_t odr = OutputDataRate::ODR_100_HZ,
90 uint8_t fullScale = FullScale::FULL_SCALE_2G)
91 : spiSlave(bus, chipSelect, config), odr(odr), bdu(bdu),
92 fullScale(fullScale)
93 {
94 }
95
101 bool init() override
102 {
103 // check if the sensor is already initialized
104 if (initialized)
105 {
106 LOG_WARN(logger, "Already initialized");
108 return false;
109 }
110
111 // check if the sensor is working properly
112 if (!checkWhoAmI())
113 { // whoami default value
114 return false; // sensor correctly initialized
115 }
116
117 SPITransaction spi(spiSlave);
118
119 // set the full scale value in CTRL_REG5
120 uint8_t ctrlReg5Value = (fullScale << 3);
121 spi.writeRegister(CTRL_REG5, ctrlReg5Value);
122
123 // select the correct sensitivity
124 // for the specified full scale range
125 sensitivity = selectSensitivity();
126
127 // set the output data rate and the BDU in CTRL_REG4
128 // the three least significant bits are enable bits for X, Y and Z axis
129 uint8_t ctrlReg4Value =
130 (odr << 4) | (bdu << 3) | (7 << 0); // 7 = 111 -> enable the 3 axis
131 spi.writeRegister(CTRL_REG4, ctrlReg4Value);
132
133 initialized = true;
134
135 return true;
136 }
137
143 bool selfTest() override
144 {
145 // check if the sensor is initialized
146 if (!initialized)
147 {
148 LOG_WARN(logger,
149 "Unable to perform selftest, sensor not initialized");
151 return false;
152 }
153
154 const uint8_t numSamples = 5; // number of samples to be used
155 // vectors for storing samples, both
156 // in self-test and no-self-test modes
157 float X_ST[numSamples] = {0};
158 float Y_ST[numSamples] = {0};
159 float Z_ST[numSamples] = {0};
160 float X_NO_ST[numSamples] = {0};
161 float Y_NO_ST[numSamples] = {0};
162 float Z_NO_ST[numSamples] = {0};
163 // vectors containing avg values for each axis
164 float AVG_ST[3] = {0}; // one element per axis
165 float AVG_NO_ST[3] = {0}; // one element per axis
166
167 // set output data rate to 50 hz
168 uint8_t ctrlReg4Value = (OutputDataRate::ODR_100_HZ << 4) |
170 (7 << 0);
171
172 {
173 SPITransaction spi(spiSlave);
174 spi.writeRegister(CTRL_REG4, ctrlReg4Value);
175 }
176
177 // set full scale to default value +/-2g
178 // enable the self-test mode with positive sign
179 uint8_t ctrlReg5Value = (FullScale::FULL_SCALE_2G << 3) | (1 << 1);
180
181 {
182 SPITransaction spi(spiSlave);
183 spi.writeRegister(CTRL_REG5, ctrlReg5Value);
184 }
185
186 // read samples in self-test positive sign mode
187 for (uint8_t i = 0; i < numSamples; i++)
188 {
189 AccelerometerData accelData = readAccelData();
190 X_ST[i] = accelData.accelerationX;
191 Y_ST[i] = accelData.accelerationY;
192 Z_ST[i] = accelData.accelerationZ;
193 miosix::Thread::sleep(10);
194 }
195 // reset the self-test bits
196 ctrlReg5Value &= ~(3 << 1);
197 // normal mode with full scale range +/-2g
198 ctrlReg5Value |= (FULL_SCALE_2G << 3);
199
200 {
201 SPITransaction spi(spiSlave);
202 spi.writeRegister(CTRL_REG5, ctrlReg5Value);
203 }
204
205 // read samples in normal mode
206 for (uint8_t i = 0; i < numSamples; i++)
207 {
208 AccelerometerData accelData = readAccelData();
209 X_NO_ST[i] = accelData.accelerationX;
210 Y_NO_ST[i] = accelData.accelerationY;
211 Z_NO_ST[i] = accelData.accelerationZ;
212 miosix::Thread::sleep(10);
213 }
214 // compute averages vectors:
215 // they contain one element for each axis
216 // (position 0 for x, 1 for y and 2 for z)
217 // AVG_ST : for self-test samples
218 // AVG_NO_ST : for normal mode samples
219 for (uint8_t i = 0; i < numSamples; i++)
220 {
221 AVG_ST[0] += X_ST[i];
222 AVG_ST[1] += Y_ST[i];
223 AVG_ST[2] += Z_ST[i];
224 AVG_NO_ST[0] += X_NO_ST[i];
225 AVG_NO_ST[1] += Y_NO_ST[i];
226 AVG_NO_ST[2] += Z_NO_ST[i];
227 }
228 for (uint8_t i = 0; i < 3; i++)
229 {
230 AVG_ST[i] /= numSamples;
231 AVG_NO_ST[i] /= numSamples;
232 }
233
234 // Reset registers values with the ones
235 // specified in the constructor:
236 // set the output data rate value in CTRL_REG4
237 ctrlReg4Value = (odr << 4) | (bdu << 3) | (7 << 0);
238
239 {
240 SPITransaction spi(spiSlave);
241 spi.writeRegister(CTRL_REG4, ctrlReg4Value);
242 }
243 // set the full scale value in CTRL_REG5
244 ctrlReg5Value = (fullScale << 3); // normal mode
245
246 {
247 SPITransaction spi(spiSlave);
248 spi.writeRegister(CTRL_REG5, ctrlReg5Value);
249 }
250
251 float delta[3] = {0};
252 for (uint8_t i = 0; i < 3; i++)
253 delta[i] = fabs(AVG_NO_ST[i] - AVG_ST[i]);
254
255 LOG_INFO(logger,
256 "Selftest: delta[x] = {}, delta[y] = {}, delta[z] = {}",
257 delta[0], delta[1], delta[2]);
258
259 // check that the averages differences
260 // do not exceed maximum tolerance
261 if ((delta[0] >
262 SELF_TEST_DIFF_X_Y + SELF_TEST_DIFF_X_Y * SELF_TEST_TOLERANCE) ||
263 (delta[1] >
264 SELF_TEST_DIFF_X_Y + SELF_TEST_DIFF_X_Y * SELF_TEST_TOLERANCE) ||
265 (delta[2] >
266 SELF_TEST_DIFF_Z + SELF_TEST_DIFF_Z * SELF_TEST_TOLERANCE))
267 {
268 LOG_ERR(logger, "Selftest failed");
270 return false;
271 }
272
273 return true;
274 }
275
280 {
281 ODR_POWER_DOWN = 0, // 0000
282 ODR_3_125_HZ = 1, // 0001, 3.125 Hz
283 ODR_6_25_HZ = 2, // 0010, 6.25 Hz
284 ODR_12_5_HZ = 3, // 0011, 12.5 Hz
285 ODR_25_HZ = 4, // 0100
286 ODR_50_HZ = 5, // 0101
287 ODR_100_HZ = 6, // 0110, default value
288 ODR_400_HZ = 7, // 0111
289 ODR_800_HZ = 8, // 1000
290 ODR_1600_HZ = 9 // 1001
291 };
292
297 {
298 FULL_SCALE_2G = 0, // 000, +/- 2g
299 FULL_SCALE_4G = 1, // 001, +/- 4g
300 FULL_SCALE_6G = 2, // 010, +/- 6g
301 FULL_SCALE_8G = 3, // 011, +/- 8g
302 FULL_SCALE_16G = 4, // 100 +/- 16g
303 };
304
309 {
310 CONTINUOUS_UPDATE_MODE = 0, // continuous update of accelerometer data
312 1 // values updated only when MSB and LSB are read (recommended)
313 };
314
315private:
323 LIS3DSHData sampleImpl() override
324 {
325 // check if the sensor is initialized
326 if (!initialized)
327 {
328 LOG_WARN(logger, "Unable to sample, sensor not initialized");
330 return lastSample;
331 }
332
333 AccelerometerData accelData = readAccelData();
334 TemperatureData tempData = readTemperature();
335
337 return lastSample;
338 else
339 return LIS3DSHData(accelData, tempData);
340 }
341
347 AccelerometerData readAccelData()
348 {
349 AccelerometerData accelData;
350
351 SPITransaction spi(spiSlave);
352
353 // read the sensor's status register
354 uint8_t status = spi.readRegister(STATUS);
355
356 if (status & 0x08)
357 { // bit 3 of status set to 1 (new data available)
358 if (status & 0x80)
359 { // bit 7 of status set to 1 (some data overwritten)
360
361 accelData.accelerationTimestamp =
363
364 // read acceleration on X
365 int8_t accel_L = spi.readRegister(OUT_X_L);
366 int8_t accel_H = spi.readRegister(OUT_X_H);
367 accelData.accelerationX =
368 static_cast<float>(combine(accel_H, accel_L)) * sensitivity;
369
370 // read acceleration on Y
371 accel_L = spi.readRegister(OUT_Y_L);
372 accel_H = spi.readRegister(OUT_Y_H);
373 accelData.accelerationY =
374 static_cast<float>(combine(accel_H, accel_L)) * sensitivity;
375
376 // read acceleration on Z
377 accel_L = spi.readRegister(OUT_Z_L);
378 accel_H = spi.readRegister(OUT_Z_H);
379 accelData.accelerationZ =
380 static_cast<float>(combine(accel_H, accel_L)) * sensitivity;
381
383 }
384 }
385 else
386 {
388 }
389
390 return accelData;
391 }
392
398 TemperatureData readTemperature()
399 {
400 SPITransaction spi(spiSlave);
401
402 // the temperature is given as a 8-bits integer (in 2-complement)
403 int8_t t = spi.readRegister(OUT_T);
404
405 return TemperatureData{
407 t + TEMPERATURE_REF}; // add the 'zero' of the temperature sensor
408 }
409
417 bool checkWhoAmI()
418 {
419 SPITransaction spi(spiSlave);
420
421 // check the WHO_AM_I_REG register
422 uint8_t whoAmIValue = spi.readRegister(WHO_AM_I_REG);
423 if (whoAmIValue == WHO_AM_I_DEFAULT_VALUE)
424 {
425 LOG_DEBUG(logger, "Correct WHO_AM_I value");
426 return true;
427 }
428 else
429 {
430 LOG_ERR(logger, "Wrong WHO_AM_I value, got {} instead of {}",
431 whoAmIValue, WHO_AM_I_DEFAULT_VALUE);
433 }
434
435 return false;
436 }
437
445 int16_t combine(uint8_t msb, uint8_t lsb) { return (msb << 8) | lsb; }
446
454 float selectSensitivity()
455 {
456 float s;
457 switch (fullScale)
458 {
459 case FULL_SCALE_2G:
460 s = sensitivityValues[FullScale::FULL_SCALE_2G];
461 break;
462 case FULL_SCALE_4G:
463 s = sensitivityValues[FullScale::FULL_SCALE_4G];
464 break;
465 case FULL_SCALE_6G:
466 s = sensitivityValues[FullScale::FULL_SCALE_6G];
467 break;
468 case FULL_SCALE_8G:
469 s = sensitivityValues[FullScale::FULL_SCALE_8G];
470 break;
471 case FULL_SCALE_16G:
472 s = sensitivityValues[FullScale::FULL_SCALE_16G];
473 break;
474 default:
475 LOG_ERR(logger, "Invalid full scale range given, using +/-2g");
476 this->fullScale = FullScale::FULL_SCALE_2G;
477 s = sensitivityValues[FullScale::FULL_SCALE_2G];
478 break;
479 }
480 return s;
481 }
482
486 enum REG
487 {
488
489 // whoami register
490 WHO_AM_I_REG = 0x0F,
491
492 // control registers for the accelerometer
493 CTRL_REG4 =
494 0x20, // control register to set accelerometer's ODR and BDU
495 CTRL_REG1 = 0x21, // state Machine 1 interrupt configuration register
496 CTRL_REG2 = 0x22, // state Machine 2 interrupt configuration register
497 CTRL_REG3 = 0x23,
498 CTRL_REG5 =
499 0x24, // control register to set the accelerometer full scale
500 // range, anti-aliansing filter and self-test enable
501 CTRL_REG6 = 0x25,
502
503 // status register
504 STATUS = 0x27,
505
506 // accelerometer output registers
507 // for x, y and z axis
508 // (low and high bits in separate registers)
509 OUT_X_L = 0x28,
510 OUT_X_H = 0x29,
511 OUT_Y_L = 0x2A,
512 OUT_Y_H = 0x2B,
513 OUT_Z_L = 0x2C,
514 OUT_Z_H = 0x2D,
515
516 // temperature output register
517 OUT_T = 0x0C,
518 };
519
520 SPISlave spiSlave;
521
522 bool initialized = false; // whether the sensor has been initialized or not
523
524 uint8_t odr; // output data rate, default 100 Hz
525 uint8_t bdu; // continuous or block after update
526 uint8_t fullScale; // full scale value, default +/- 2g
527
532 const float sensitivityValues[5] = {0.06, 0.12, 0.18, 0.24, 0.73};
533
534 float sensitivity =
535 sensitivityValues[FullScale::FULL_SCALE_2G]; // default sensitivity
536 // value
537
538 const uint8_t WHO_AM_I_DEFAULT_VALUE = 63; // 00111111
539
540 const float TEMPERATURE_REF = 25.0f; // temperature sensor 'zero'/reference
541 // value (value 0x00 from the sensor
542 // corresponds to 25 degrees celsius)
543
544 const float SELF_TEST_DIFF_X_Y = 140.0f; // 140 mg
545 const float SELF_TEST_DIFF_Z = 590.0f; // 590 mg
546 const float SELF_TEST_TOLERANCE = 0.3f;
547
548 PrintLogger logger = Logging::getLogger("lis3dsh");
549};
550
551} // namespace Boardcore
#define LOG_WARN(logger,...)
#define LOG_INFO(logger,...)
#define LOG_ERR(logger,...)
#define LOG_DEBUG(logger,...)
SensorErrors lastError
Definition Sensor.h:54
BlockDataUpdate
Block data update allowed modes (1 bit).
Definition LIS3DSH.h:309
LIS3DSH(SPIBusInterface &bus, miosix::GpioPin chipSelect, uint8_t odr=OutputDataRate::ODR_100_HZ, uint8_t bdu=BlockDataUpdate::UPDATE_AFTER_READ_MODE, uint8_t fullScale=FullScale::FULL_SCALE_2G)
Constructor.
Definition LIS3DSH.h:62
LIS3DSH(SPIBusInterface &bus, miosix::GpioPin chipSelect, SPIBusConfig config, uint8_t odr=OutputDataRate::ODR_100_HZ, uint8_t bdu=BlockDataUpdate::UPDATE_AFTER_READ_MODE, uint8_t fullScale=FullScale::FULL_SCALE_2G)
Constructor.
Definition LIS3DSH.h:87
FullScale
Full scale range allowed values (3 bits).
Definition LIS3DSH.h:297
bool selfTest() override
Check if the sensor is working.
Definition LIS3DSH.h:143
OutputDataRate
Output data rate allowed values (4 bits).
Definition LIS3DSH.h:280
bool init() override
Initialize the sensor.
Definition LIS3DSH.h:101
static PrintLogger getLogger(const string &name)
Interface for low level access of a SPI bus as a master.
Provides high-level access to the SPI Bus for a single transaction.
void writeRegister(uint8_t reg, uint8_t data)
Writes an 8 bit register.
Base sensor class with has to be extended by any sensor driver.
Definition Sensor.h:91
uint64_t getTimestamp()
Returns the current timer value in microseconds.
This file includes all the types the logdecoder script will decode.
@ SELF_TEST_FAIL
Definition SensorData.h:44
@ INVALID_WHOAMI
Definition SensorData.h:40
Structure to handle accelerometer data.
Definition SensorData.h:121
SPI Bus configuration for a specific slave.
SPI::ClockDivider clockDivider
< Peripheral clock division