Skyward boardcore
Loading...
Searching...
No Matches
LIS2MDL.cpp
Go to the documentation of this file.
1/* Copyright (c) 2022 Skyward Experimental Rocketry
2 * Author: Giulia Ghirardini
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#include "LIS2MDL.h"
24
26#include <miosix.h>
28#include <utils/Debug.h>
29
30#include <iostream>
31
32using namespace Eigen;
33
34namespace Boardcore
35{
36
37LIS2MDL::LIS2MDL(SPIBusInterface& bus, miosix::GpioPin pin,
38 SPIBusConfig spiConfig, Config config)
39 : slave(bus, pin, spiConfig), configuration(config)
40{
41}
42
44{
45 SPIBusConfig spiConfig;
47 spiConfig.mode = SPI::Mode::MODE_3;
48 spiConfig.byteOrder = SPI::Order::LSB_FIRST;
49 return spiConfig;
50}
51
53{
54 if (isInitialized)
55 {
56 LOG_ERR(logger, "Attempted to initialized sensor twice but failed");
58 return false;
59 }
60
61 {
62 SPITransaction spi(slave);
63 spi.writeRegister(CFG_REG_C, ENABLE_4WSPI | I2C_DISABLE);
64 }
65
66 {
67 SPITransaction spi(slave);
68 uint8_t res = spi.readRegister(WHO_AM_I);
69
70 if (res != WHO_AM_I_VALUE)
71 {
72 LOG_ERR(logger,
73 "WHO_AM_I value differs from expectation: read 0x{:x} "
74 "but expected 0x{:x}",
75 res, WHO_AM_I_VALUE);
77 return false;
78 }
79 }
80
81 isInitialized = true;
82 return applyConfig(configuration);
83}
84
86{
87 if (!isInitialized)
88 {
89 LOG_ERR(logger, "Invoked selfTest() but sensor was uninitialized");
91 return false;
92 }
93
99 static constexpr int NUM_SAMPLES = 50; // 50 samples suggested by AN5069
100 static constexpr int SLEEP_TIME = 10; // 100Hz -> 10ms between samples
101 Vector3f avgPreTest = Vector3f::Zero();
102 Vector3f avgPostTest = Vector3f::Zero();
103 Vector3f tmp;
104
105 // 1. Set configuration for selfTest procedure. selfTest still not enabled
106 {
107 SPITransaction spi(slave);
108 spi.writeRegister(CFG_REG_A,
109 ENABLE_TEMPERATURE_COMP | ODR_100_HZ | MD_CONTINUOUS);
110 spi.writeRegister(CFG_REG_B,
111 spi.readRegister(CFG_REG_B) | OFFSET_CANCELLATION);
112 spi.writeRegister(CFG_REG_C, spi.readRegister(CFG_REG_C) | ENABLE_BDU);
113 }
114
115 // Wait for power up, ~20ms for a stable output
116 miosix::Thread::sleep(20);
117
118 // 2. Averaging fifty samples before enabling the self-test
119 {
120 for (int i = 0; i < NUM_SAMPLES - 1; i++)
121 {
122 tmp << static_cast<MagnetometerData>(sampleImpl());
123 avgPreTest += tmp;
124
125 miosix::Thread::sleep(SLEEP_TIME);
126 }
127 tmp << static_cast<MagnetometerData>(sampleImpl());
128 avgPreTest += tmp;
129
130 // Compute average
131 avgPreTest /= NUM_SAMPLES;
132 }
133
134 // 3. Enable self-test
135 {
136 SPITransaction spi(slave);
137 spi.writeRegister(CFG_REG_C,
138 spi.readRegister(CFG_REG_C) | ENABLE_SELF_TEST);
139 }
140
141 // Wait 60ms (suggested in AN)
142 miosix::Thread::sleep(60);
143
144 // 4. Averaging fifty samples after enabling the self-test
145 {
146 for (int i = 0; i < NUM_SAMPLES - 1; i++)
147 {
148 tmp << static_cast<MagnetometerData>(sampleImpl());
149 avgPostTest += tmp;
150
151 miosix::Thread::sleep(SLEEP_TIME);
152 }
153 tmp << static_cast<MagnetometerData>(sampleImpl());
154 avgPostTest += tmp;
155
156 // Compute average
157 avgPostTest /= NUM_SAMPLES;
158 }
159
160 // 5. Computing the difference in the module for each axis and verifying
161 // that is falls in the given range: the min and max value are provided
162 // in the datasheet.
163 {
164 Vector3f deltas = (avgPostTest - avgPreTest).cwiseAbs();
165
166 // Range which delta must be between, one for axis and expressed as
167 // {min, max}. The unit is gauss.
168 static constexpr float ST_MIN = 0.015; // [Gauss]
169 static constexpr float ST_MAX = 0.500; // [Gauss]
170
171 bool passed =
172 (ST_MIN < deltas.array()).all() && (deltas.array() < ST_MAX).all();
173
174 // Reset configuration, then return
175 applyConfig(configuration);
176
177 if (!passed)
178 {
179 LOG_ERR(logger, "selfTest() failed");
181 return false;
182 }
183 else
184 {
185 return true;
186 }
187 }
188}
189
191{
192 SPITransaction spi(slave);
193 uint8_t reg = 0;
194
195 // CFG_REG_A: configuration register
196 reg |= config.odr;
197 reg |= config.deviceMode;
198 reg |= (1 << 7);
199 reg |= (spi.readRegister(CFG_REG_A) & 0b01110000);
200 spi.writeRegister(CFG_REG_A, reg);
201
202 return true;
203}
204
206{
207 if (!isInitialized)
208 {
209 LOG_ERR(logger, "Invoked sampleImpl() but sensor was uninitialized");
211 return lastSample;
212 }
213
214 SPITransaction spi(slave);
215 LIS2MDLData newData;
216
217 // Check if the temperature has to be sampled
218 tempCounter++;
219 if (configuration.temperatureDivider != 0 &&
220 tempCounter % configuration.temperatureDivider == 0)
221 {
222 int16_t outTemp = spi.readRegister16(TEMP_OUT_L_REG);
223 newData.temperatureTimestamp = TimestampTimer::getTimestamp();
224 newData.temperature = DEG_PER_LSB * outTemp;
225 newData.temperature += REFERENCE_TEMPERATURE;
226 }
227 else
228 {
229 newData.temperature = lastSample.temperature;
230 }
231
232 uint8_t values[6];
233 spi.readRegisters(OUTX_L_REG, values, sizeof(values));
234
235 int16_t outX = values[1] << 8 | values[0];
236 int16_t outY = values[3] << 8 | values[2];
237 int16_t outZ = values[5] << 8 | values[4];
238
239 newData.magneticFieldTimestamp = TimestampTimer::getTimestamp();
240 newData.magneticFieldX = GAUSS_PER_LSB * outX;
241 newData.magneticFieldY = GAUSS_PER_LSB * outY;
242 newData.magneticFieldZ = GAUSS_PER_LSB * outZ;
243
244 return newData;
245}
246
247} // namespace Boardcore
#define LOG_ERR(logger,...)
SensorErrors lastError
Definition Sensor.h:54
LIS2MDL(SPIBusInterface &bus, miosix::GpioPin pin, SPIBusConfig spiConfig, Config config)
Definition LIS2MDL.cpp:37
bool init() override
Initialize the sensor.
Definition LIS2MDL.cpp:52
bool applyConfig(Config config)
Overwrites the sensor settings.
Definition LIS2MDL.cpp:190
LIS2MDLData sampleImpl() override
Read a data sample from the sensor. In case of errors, the method should return the last available co...
Definition LIS2MDL.cpp:205
@ ODR_100_HZ
100 Hz
Definition LIS2MDL.h:45
bool selfTest() override
Check if the sensor is working.
Definition LIS2MDL.cpp:85
static SPIBusConfig getDefaultSPIConfig()
Definition LIS2MDL.cpp:43
Interface for low level access of a SPI bus as a master.
Provides high-level access to the SPI Bus for a single transaction.
uint8_t readRegister(uint8_t reg)
Reads an 8 bit register.
void readRegisters(uint8_t reg, uint8_t *data, size_t size)
Reads multiple bytes starting from the specified register.
void writeRegister(uint8_t reg, uint8_t data)
Writes an 8 bit register.
uint16_t readRegister16(uint8_t reg)
Reads a 16 bit register.
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
Sensor configuration.
Definition LIS2MDL.h:64
OperativeMode deviceMode
Definition LIS2MDL.h:66
unsigned temperatureDivider
Divide the temperature sampling rate.
Definition LIS2MDL.h:74
SPI Bus configuration for a specific slave.
SPI::ClockDivider clockDivider
< Peripheral clock division