Skyward boardcore
Loading...
Searching...
No Matches
ADS131M08.cpp
Go to the documentation of this file.
1/* Copyright (c) 2023 Skyward Experimental Rocketry
2 * Author: Alberto Nidasio
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 "ADS131M08.h"
24
26#include <util/crc16.h>
27#include <utils/CRC.h>
28
29using namespace miosix;
30using namespace Boardcore::ADS131M08Defs;
31
32namespace Boardcore
33{
34
35ADS131M08::ADS131M08(SPIBusInterface& bus, miosix::GpioPin cs,
36 SPIBusConfig spiConfig, const Config& config)
37 : spiSlave(bus, cs, spiConfig), config(config)
38{
39 // Impose the correct spi mode
40 spiSlave.config.mode = SPI::Mode::MODE_1;
41}
42
44{
45 bool resetSuccess = reset();
46 applyConfig(config);
47 return resetSuccess;
48}
49
51{
52 SPITransaction transaction(spiSlave);
53
54 uint8_t data[FULL_FRAME_SIZE] = {0};
55 sendCommand(transaction, Command::RESET, data);
56
57 // The reset command takes typically 5us to reload the register contents.
58 // We wait 10us to be sure
59 miosix::delayUs(10);
60
61 // To read the response we need to read the full 3 bytes word. The response
62 // code is contained in the first 2 bytes, the last byte is padding
63 uint16_t response = transaction.read24() >> 8;
64
65 // Check for the correct response
66 if (response != RESET_CMD_RESPONSE)
67 {
69 LOG_ERR(logger, "Reset command failed, response was {:X}", response);
70 return false;
71 }
72
73 return true;
74}
75
77{
78 for (int i = 0; i < CHANNELS_NUM; i++)
79 applyChannelConfig(static_cast<Channel>(i), config.channelsConfig[i]);
80
81 setOversamplingRatio(config.oversamplingRatio);
82 if (config.globalChopModeEnabled)
83 enableGlobalChopMode();
84 else
85 disableGlobalChopMode();
86
87 // Save the newly applied configuration
88 this->config = config;
89}
90
92{
93 // The device internal data chain firsts subtracts the offset and then
94 // multiplies for the gain. To calibrate the offset we first reset it, then
95 // take some samples and apply the average as the new offset.
96 // So we need to reset the offset and gain
97 Config::ChannelConfig calibrationConfig{
98 .enabled = true,
99 .pga = PGA::PGA_1,
100 .offset = 0,
101 .gain = 1.0,
102 };
103 applyChannelConfig(channel, calibrationConfig);
104
105 // Sample the channel and average the samples
106 int32_t averageValue = 0;
107 int realSampleCount = 0;
108 for (int i = 0; i < CALIBRATION_SAMPLES; i++)
109 {
110 // Wait for a sample to be ready
111 Thread::sleep(4);
112
113 // Sample the channels
114 int32_t rawValues[CHANNELS_NUM];
115 if (!readSamples(rawValues))
116 {
117 // If the CRC failed we skip this sample
118 continue;
119 }
120
121 averageValue += rawValues[static_cast<int>(channel)];
122
123 realSampleCount++;
124 }
125
126 if (realSampleCount == 0)
127 {
128 LOG_ERR(logger, "Calibration failed, no valid samples");
129 }
130 else
131 {
132 // Compute the average
133 averageValue /= realSampleCount;
134 LOG_INFO(logger, "Channel {} average offset: {}",
135 static_cast<int>(channel),
136 averageValue * getLSBSizeFromGain(calibrationConfig.pga));
137
138 // Set the new offset only if valid, otherwise keep the old one
139 if (averageValue != 0)
140 {
141 config.channelsConfig[static_cast<int>(channel)].offset =
142 averageValue;
143 }
144 }
145
146 // Reset the original configuration with the new offset value
147 applyChannelConfig(channel,
148 config.channelsConfig[static_cast<int>(channel)]);
149}
150
152{
153 bool success = true;
154
155 // Reset configuration and connect the positive test signal
156 Config::ChannelConfig selfTestConfig{
157 .enabled = true,
158 .pga = PGA::PGA_1,
159 .offset = 0,
160 .gain = 1.0,
161 };
162 for (int i = 0; i < CHANNELS_NUM; i++)
163 {
164 applyChannelConfig(static_cast<Channel>(i), selfTestConfig);
165 setChannelInput(static_cast<Channel>(i), Input::POSITIVE_DC_TEST);
166 }
167 setOversamplingRatio(OversamplingRatio::OSR_16256);
168 disableGlobalChopMode();
169
170 // Take some samples
171 int32_t averageValues[CHANNELS_NUM] = {0};
172 int realSampleCount = 0;
173 for (int i = 0; i < SELF_TEST_SAMPLES; i++)
174 {
175 // Wait for a sample to be ready
176 Thread::sleep(4);
177
178 // Sample the channels
179 int32_t rawValues[CHANNELS_NUM];
180 if (!readSamples(rawValues))
181 {
182 // If the CRC failed we skip this sample
183 continue;
184 }
185
186 for (int j = 0; j < CHANNELS_NUM; j++)
187 averageValues[j] += rawValues[j];
188
189 realSampleCount++;
190 }
191
192 if (realSampleCount == 0)
193 {
194 LOG_ERR(logger,
195 "Failed self test with positive DC signal, no valid samples");
196 success = false;
197 }
198 else
199 {
200 // Check the values
201 for (int i = 0; i < CHANNELS_NUM; i++)
202 {
203 // Compute the average
204 averageValues[i] /= realSampleCount;
205
206 // Convert the value to Volts
207 float volts =
208 averageValues[i] * getLSBSizeFromGain(selfTestConfig.pga);
209
210 // Check if the value is in the acceptable range
211 if (volts < V_REF * TEST_SIGNAL_FACTOR - TEST_SIGNAL_SLACK)
212 {
213 LOG_ERR(
214 logger,
215 "Self test failed on channel {} on positive test signal, "
216 "value was {}",
217 i, volts);
218 success = false;
219 }
220
221 // Reset the raw value
222 averageValues[i] = 0;
223 }
224 }
225
226 // Connect all channels to the negative DC test signal
227 for (int i = 0; i < CHANNELS_NUM; i++)
228 setChannelInput(static_cast<Channel>(i), Input::NEGATIVE_DC_TEST);
229
230 // Take some samples
231 realSampleCount = 0;
232 for (int i = 0; i < SELF_TEST_SAMPLES; i++)
233 {
234 // Wait for a sample to be ready
235 Thread::sleep(4);
236
237 // Sample the channels
238 int32_t rawValues[CHANNELS_NUM];
239 if (!readSamples(rawValues))
240 {
241 // If the CRC failed we skip this sample
242 continue;
243 }
244
245 for (int j = 0; j < CHANNELS_NUM; j++)
246 averageValues[j] += rawValues[j];
247
248 realSampleCount++;
249 }
250
251 if (realSampleCount == 0)
252 {
253 LOG_ERR(logger,
254 "Failed self test with positive DC signal, no valid samples");
255 success = false;
256 }
257 else
258 {
259 // Check the values
260 for (int i = 0; i < CHANNELS_NUM; i++)
261 {
262 // Compute the average
263 averageValues[i] /= realSampleCount;
264
265 // Convert the value to Volts
266 float volts =
267 averageValues[i] * getLSBSizeFromGain(selfTestConfig.pga);
268
269 // Check if the value is in the acceptable range
270 if (volts > -V_REF * TEST_SIGNAL_FACTOR + TEST_SIGNAL_SLACK)
271 {
272 LOG_ERR(
273 logger,
274 "Self test failed on channel {} on negative test signal, "
275 "value was {}",
276 i, volts);
277 success = false;
278 }
279 }
280 }
281
282 // Reset channels input to default
283 for (int i = 0; i < CHANNELS_NUM; i++)
284 setChannelInput(static_cast<Channel>(i), Input::DEFAULT);
285
286 // Reset to previous configuration
287 applyConfig(config);
288
289 // We fail even if one channel didn't pass the test
290 return success;
291}
292
294{
295 // Read the samples and check if the CRC is correct
296 int32_t rawValues[CHANNELS_NUM];
297 if (!readSamples(rawValues))
298 {
299 // The CRC failed, return the last sample
300 return lastSample;
301 }
302
303 // Convert the raw values to voltages
304 ADS131M08Data adcData;
306 for (int i = 0; i < CHANNELS_NUM; i++)
307 {
308 adcData.voltage[i] =
309 rawValues[i] * getLSBSizeFromGain(config.channelsConfig[i].pga);
310 }
311
312 return adcData;
313}
314
315void ADS131M08::applyChannelConfig(Channel channel,
316 Config::ChannelConfig config)
317{
318 if (config.enabled)
319 {
320 setChannelPGA(channel, config.pga);
321 setChannelOffset(channel, config.offset);
322 setChannelGain(channel, config.gain);
323 enableChannel(channel);
324 }
325 else
326 {
327 disableChannel(channel);
328 }
329}
330
331void ADS131M08::setOversamplingRatio(OversamplingRatio ratio)
332{
333 changeRegister(Register::REG_CLOCK, static_cast<uint16_t>(ratio),
335}
336
337void ADS131M08::setChannelPGA(Channel channel, PGA gain)
338{
339 if (channel <= Channel::CHANNEL_3)
340 {
341 int shift = static_cast<int>(channel) * 4;
342 changeRegister(Register::REG_GAIN_1,
343 static_cast<uint16_t>(gain) << shift,
344 RegGainMasks::PGA_GAIN_0 << shift);
345 }
346 else
347 {
348 int shift = (static_cast<int>(channel) - 4) * 4;
349 changeRegister(Register::REG_GAIN_2,
350 static_cast<uint16_t>(gain) << shift,
351 RegGainMasks::PGA_GAIN_0 << shift);
352 }
353}
354
355void ADS131M08::setChannelOffset(Channel channel, int32_t offset)
356{
357 // The offset is a 24 bit value. Its two most significant bytes are stored
358 // in the MSB register, and the least significant in the LSB register, like
359 // this:
360 // +----------+-------+
361 // | | 23-16 |
362 // | OCAL_MSB +-------+
363 // | | 15-8 |
364 // +----------+-------+
365 // | | 7-0 |
366 // | OCAL_LSB +-------+
367 // | | |
368 // +----------+-------+
369 writeRegister(getChannelOffsetRegisterMSB(channel), offset >> 8);
370 writeRegister(getChannelOffsetRegisterLSB(channel), offset << 8);
371}
372
373void ADS131M08::setChannelGain(Channel channel, double gain)
374{
375 // If the user passes a value outside the range [0, 2] we cap it.
376 if (gain < 0)
377 gain = 0;
378 else if (gain > 2)
379 gain = 2;
380
381 // The ADS131M08 corrects for gain errors by multiplying the ADC conversion
382 // result using the gain calibration registers.
383 // The contents of the gain calibration registers are interpreted by the
384 // dives as 24-bit unsigned values corresponding to linear steps from 0 to
385 // 2-(1/2^23).
386 // So effectively the register value is a fixed point number with 1bit for
387 // the integer part and 23 bits for the fractional part.
388 // So to convert the gain value to the register value we multiply it by 2^23
389 uint32_t rawGain = gain * (1 << 23);
390
391 // The gain is a 24 bit value. Its two most significant bytes are stored
392 // in the MSB register, and the least significant in the LSB register, like
393 // this:
394 // +----------+-------+
395 // | | 23-16 |
396 // | GCAL_MSB +-------+
397 // | | 15-8 |
398 // +----------+-------+
399 // | | 7-0 |
400 // | GCAL_LSB +-------+
401 // | | |
402 // +----------+-------+
403 writeRegister(getChannelGainRegisterMSB(channel), rawGain >> 8);
404 writeRegister(getChannelGainRegisterLSB(channel), rawGain << 8);
405}
406
407void ADS131M08::enableChannel(Channel channel)
408{
409 changeRegister(Register::REG_CLOCK, 1 << (static_cast<int>(channel) + 8),
410 1 << (static_cast<int>(channel) + 8));
411}
412
413void ADS131M08::disableChannel(Channel channel)
414{
415 changeRegister(Register::REG_CLOCK, 0,
416 1 << (static_cast<int>(channel) + 8));
417}
418
419void ADS131M08::enableGlobalChopMode()
420{
421 changeRegister(Register::REG_CFG, RegConfigurationMasks::GC_EN,
423}
424
425void ADS131M08::disableGlobalChopMode()
426{
427 changeRegister(Register::REG_CFG, 0, RegConfigurationMasks::GC_EN);
428}
429
430void ADS131M08::setChannelInput(Channel channel, Input input)
431{
432 Register reg = getChannelConfigRegister(channel);
433 changeRegister(reg, static_cast<uint16_t>(input), RegChannelMasks::CFG_MUX);
434}
435
436Register ADS131M08::getChannelConfigRegister(Channel channel)
437{
438 switch (static_cast<int>(channel))
439 {
440 case 0:
441 return Register::REG_CH0_CFG;
442 case 1:
443 return Register::REG_CH1_CFG;
444 case 2:
445 return Register::REG_CH2_CFG;
446 case 3:
447 return Register::REG_CH3_CFG;
448 case 4:
449 return Register::REG_CH4_CFG;
450 case 5:
451 return Register::REG_CH5_CFG;
452 case 6:
453 return Register::REG_CH6_CFG;
454 default:
455 return Register::REG_CH7_CFG;
456 }
457}
458
459Register ADS131M08::getChannelOffsetRegisterMSB(Channel channel)
460{
461 switch (static_cast<int>(channel))
462 {
463 case 0:
464 return Register::REG_CH0_OCAL_MSB;
465 case 1:
466 return Register::REG_CH1_OCAL_MSB;
467 case 2:
468 return Register::REG_CH2_OCAL_MSB;
469 case 3:
470 return Register::REG_CH3_OCAL_MSB;
471 case 4:
472 return Register::REG_CH4_OCAL_MSB;
473 case 5:
474 return Register::REG_CH5_OCAL_MSB;
475 case 6:
476 return Register::REG_CH6_OCAL_MSB;
477 default:
478 return Register::REG_CH7_OCAL_MSB;
479 }
480}
481
482Register ADS131M08::getChannelOffsetRegisterLSB(Channel channel)
483{
484 switch (static_cast<int>(channel))
485 {
486 case 0:
487 return Register::REG_CH0_OCAL_LSB;
488 case 1:
489 return Register::REG_CH1_OCAL_LSB;
490 case 2:
491 return Register::REG_CH2_OCAL_LSB;
492 case 3:
493 return Register::REG_CH3_OCAL_LSB;
494 case 4:
495 return Register::REG_CH4_OCAL_LSB;
496 case 5:
497 return Register::REG_CH5_OCAL_LSB;
498 case 6:
499 return Register::REG_CH6_OCAL_LSB;
500 default:
501 return Register::REG_CH7_OCAL_LSB;
502 }
503}
504
505Register ADS131M08::getChannelGainRegisterMSB(Channel channel)
506{
507 switch (static_cast<int>(channel))
508 {
509 case 0:
510 return Register::REG_CH0_GCAL_MSB;
511 case 1:
512 return Register::REG_CH1_GCAL_MSB;
513 case 2:
514 return Register::REG_CH2_GCAL_MSB;
515 case 3:
516 return Register::REG_CH3_GCAL_MSB;
517 case 4:
518 return Register::REG_CH4_GCAL_MSB;
519 case 5:
520 return Register::REG_CH5_GCAL_MSB;
521 case 6:
522 return Register::REG_CH6_GCAL_MSB;
523 default:
524 return Register::REG_CH7_GCAL_MSB;
525 }
526}
527
528Register ADS131M08::getChannelGainRegisterLSB(Channel channel)
529{
530 switch (static_cast<int>(channel))
531 {
532 case 0:
533 return Register::REG_CH0_GCAL_LSB;
534 case 1:
535 return Register::REG_CH1_GCAL_LSB;
536 case 2:
537 return Register::REG_CH2_GCAL_LSB;
538 case 3:
539 return Register::REG_CH3_GCAL_LSB;
540 case 4:
541 return Register::REG_CH4_GCAL_LSB;
542 case 5:
543 return Register::REG_CH5_GCAL_LSB;
544 case 6:
545 return Register::REG_CH6_GCAL_LSB;
546 default:
547 return Register::REG_CH7_GCAL_LSB;
548 }
549}
550
551bool ADS131M08::readSamples(int32_t rawValues[CHANNELS_NUM])
552{
553 // Send the NULL command and read response
554 uint8_t data[FULL_FRAME_SIZE] = {0};
555
556 data[0] = (static_cast<uint16_t>(Command::NULL_CMD) & 0xff00) >> 8;
557 data[1] = (static_cast<uint16_t>(Command::NULL_CMD) & 0x00ff);
558
559 SPITransaction transaction(spiSlave);
560 transaction.transfer(data, sizeof(data));
561
562 // Extract and verify the CRC
563 uint16_t dataCrc =
564 static_cast<uint16_t>(data[27]) << 8 | static_cast<uint16_t>(data[28]);
565 uint16_t calculatedCrc = CRCUtils::crc16(data, sizeof(data) - 3);
566
567 if (dataCrc != calculatedCrc)
568 {
570 LOG_ERR(logger, "Failed CRC check during sensor sampling");
571
572 // Return and don't convert the corrupted data
573 return false;
574 }
575
576 // Extract each channel value
577 for (int i = 0; i < CHANNELS_NUM; i++)
578 {
579 rawValues[i] = static_cast<uint32_t>(data[i * 3 + 3]) << 16 |
580 static_cast<uint32_t>(data[i * 3 + 4]) << 8 |
581 static_cast<uint32_t>(data[i * 3 + 5]);
582
583 // Extend the sign bit with a double shift
584 rawValues[i] <<= 8;
585 rawValues[i] >>= 8;
586 }
587
588 return true;
589}
590
591uint16_t ADS131M08::readRegister(Register reg)
592{
593 uint8_t data[3] = {0};
594
595 // Prepare the command
596 data[0] = static_cast<uint16_t>(Command::RREG) >> 8 |
597 static_cast<uint16_t>(reg) >> 1;
598 data[1] = static_cast<uint16_t>(reg) << 7;
599
600 SPITransaction transaction(spiSlave);
601 transaction.write(data, sizeof(data));
602 transaction.read(data, sizeof(data));
603
604 return data[0] << 8 | data[1];
605}
606
607void ADS131M08::writeRegister(Register reg, uint16_t data)
608{
609 // The write command uses two communication words (3 bytes each), one for
610 // the register address and one for the data to write
611 uint8_t writeCommand[6] = {0};
612
613 // Prepare the command
614 writeCommand[0] = static_cast<uint16_t>(Command::WREG) >> 8 |
615 static_cast<uint16_t>(reg) >> 1;
616 writeCommand[1] = static_cast<uint16_t>(reg) << 7;
617 writeCommand[3] = data >> 8;
618 writeCommand[4] = data;
619
620 SPITransaction transaction(spiSlave);
621 transaction.write(writeCommand, sizeof(writeCommand));
622
623 // The response contains a fixed part and the register address.
624 uint16_t response = transaction.read24() >> 8;
625 if (response != (WRITE_CMD_RESPONSE | (static_cast<uint16_t>(reg) << 7)))
626 {
628 LOG_ERR(logger, "Write command failed, response was {:X}", response);
629 }
630}
631
632void ADS131M08::changeRegister(Register reg, uint16_t newValue, uint16_t mask)
633{
634 // Read the clock register
635 uint16_t regValue = readRegister(reg);
636
637 // Remove the target configuration
638 regValue &= ~mask;
639
640 // Set the new value
641 regValue |= newValue;
642
643 // Write the new value
644 writeRegister(reg, regValue);
645}
646
647void ADS131M08::sendCommand(SPITransaction& transaction, Command command,
648 uint8_t data[FULL_FRAME_SIZE])
649{
650 // All commands (a part from read and write) needs the full 10 words
651 // communication frame. Each word is (by default) 3 bytes long
652
653 // The command is 16 bits long and goes in the most significant bytes of the
654 // first communication word
655 data[0] = (static_cast<uint16_t>(command) & 0xff00) >> 8;
656 data[1] = (static_cast<uint16_t>(command) & 0x00ff);
657
658 transaction.write(data, FULL_FRAME_SIZE);
659}
660
661float ADS131M08::getLSBSizeFromGain(PGA gain)
662{
663 return PGA_LSB_SIZE[static_cast<uint16_t>(gain)];
664}
665
666} // namespace Boardcore
#define LOG_INFO(logger,...)
#define LOG_ERR(logger,...)
bool selfTest() override
The self test samples internally connects each channel to known test signals and verifies if the samp...
ADS131M08(SPIBusInterface &bus, miosix::GpioPin cs, SPIBusConfig spiConfig, const Config &config)
Definition ADS131M08.cpp:35
void applyConfig(Config config)
Overwrites the sensor settings.
Definition ADS131M08.cpp:76
bool init() override
Initialize the sensor.
Definition ADS131M08.cpp:43
void calibrateOffset(ADS131M08Defs::Channel channel)
Samples each channel, averages the samples and applies offset compensation in the device.
Definition ADS131M08.cpp:91
ADS131M08Data sampleImpl() override
Read a data sample from the sensor. In case of errors, the method should return the last available co...
SensorErrors lastError
Definition Sensor.h:54
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 write(uint8_t data)
Writes a single byte to the bus.
virtual uint32_t read24()
Reads 24 bits from the bus.
OversamplingRatio
ADC's oversampling ratio configurations.
constexpr float PGA_LSB_SIZE[8]
const auto crc16
CRC-16/CCITT-FALSE.
Definition CRC.h:67
@ MODE_1
CPOL = 1, CPHA = 0 -> Clock high when idle, sample on first edge.
uint64_t getTimestamp()
Returns the current timer value in microseconds.
This file includes all the types the logdecoder script will decode.
@ COMMAND_FAILED
Definition SensorData.h:49
ChannelConfig channelsConfig[ADS131M08Defs::CHANNELS_NUM]
Definition ADS131M08.h:88
ADS131M08Defs::OversamplingRatio oversamplingRatio
Definition ADS131M08.h:90
SPI Bus configuration for a specific slave.
SPI::Mode mode
MSBit or LSBit first.