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