Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 72 additions & 31 deletions src/sensor_ads131m0x.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// This file may be distributed under the terms of the GNU GPLv3 license.

#include <stdint.h>
#include <string.h> // memset
#include "basecmd.h" // oid_alloc
#include "board/gpio.h" // gpio_in_setup
#include "board/irq.h" // irq_disable
Expand All @@ -21,12 +22,22 @@ struct ads131m0x_adc {
uint32_t rest_ticks;
struct gpio_in data_ready;
struct spidev_s *spi;
uint8_t pending_flag, data_count, was_reset;
uint8_t frame_size, data_offset;
uint8_t flags, continuous_reads;
uint8_t channel, num_channels;
struct sensor_bulk sb;
struct trigger_analog *ta;
};

enum {
RESCHEDULE_TIMER = 1
};

enum {
FLAG_PENDING = 1<<0,
FLAG_RESET = 1<<1
};

// Internal errors transmitted to trigger_analog
enum {
SE_ERROR_CRC = 1,
Expand Down Expand Up @@ -71,14 +82,13 @@ ads131m0x_is_data_ready(struct ads131m0x_adc *ads131m0x) {

// CCITT CRC-16 calculation (polynomial 0x1021, init 0xFFFF, optimized version)
static uint16_t
ads131m0x_crc16_ccitt(const uint8_t *data, uint8_t len)
ads131m0x_crc16_ccitt(const uint8_t *data, uint_fast8_t len)
{
uint16_t crc = 0xFFFF;
while (len--) {
uint8_t x = (crc >> 8) ^ *data++;
uint16_t x = (crc >> 8) ^ *data++;
x ^= x >> 4;
crc = (crc << 8) ^ ((uint16_t)x << 12) ^ ((uint16_t)x << 5)
^ (uint16_t)x;
crc = (crc << 8) ^ (x << 12) ^ (x << 5) ^ x;
}
return crc;
}
Comment on lines -74 to 94

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just FYI, modern MCUs are really slow when doing math on uint8_t and uint16_t. The ARM mcus don't have 8bit or 16bit math operations (like add, subtract, shift, multiply). So, any math done on a uint16_t or uint8_t the resulting assembler has to scale the value to a uint32_t, perform the actual math, and then truncate the results back to 8bit (or 16bit).

So, in a nutshell, you almost assuredly want to make all these values uint32_t and change the last line to be return crc & 0xffff. Alternatively, if you care about performance on AVR (it's hard to imagine someone hooking one of these sensors up to an old 8-bit AVR cpu, but whatever) then you'll likely want to use uint_fast16_t and still change the last line to return crc & 0xffff.

Cheers,
-Kevin

Expand All @@ -92,20 +102,32 @@ ads131m0x_has_crc_error(uint8_t *msg, uint8_t frame_size)
return calc_crc != frame_crc;
}

// Helper code to reschedule the ads131m0x_event() timer
static void
ads131m0x_reschedule_timer(struct ads131m0x_adc *ads131m0x)
{
// The event handler will check the data availability
ads131m0x->flags &= ~FLAG_PENDING;
irq_disable();
uint32_t waketime = timer_read_time() + ads131m0x->rest_ticks;
if (timer_is_before(ads131m0x->timer.waketime, waketime))
ads131m0x->timer.waketime = waketime;
sched_add_timer(&ads131m0x->timer);
irq_enable();
}

// Event handler that periodically wakes the capture task
static uint_fast8_t
ads131m0x_event(struct timer *timer)
{
struct ads131m0x_adc *ads131m0x = container_of(
timer, struct ads131m0x_adc, timer);
uint32_t rest_ticks = ads131m0x->rest_ticks;
if (ads131m0x->pending_flag) {
ads131m0x->sb.possible_overflows++;
rest_ticks *= 4;
} else if (ads131m0x_is_data_ready(ads131m0x)) {
ads131m0x->pending_flag = 1;
if (ads131m0x_is_data_ready(ads131m0x)) {
ads131m0x->flags |= FLAG_PENDING;
sched_wake_task(&wake_ads131m0x);
rest_ticks *= 8;
ads131m0x->timer.waketime += rest_ticks << 3;
return SF_DONE;
}
ads131m0x->timer.waketime += rest_ticks;
return SF_RESCHEDULE;
Expand All @@ -131,36 +153,34 @@ ads131m0x_publish_error(struct ads131m0x_adc *ads131m0x, uint8_t oid
}

// Send a NULL command frame and read conversion data from all channels.
// Returns sample from the configured channel.
static void
// Appends a sample from the configured channel to the buffer.
// Returns RESCHEDULE_TIMER if a new timer even task should be rescheduled.
static uint_fast8_t
ads131m0x_read_adc(struct ads131m0x_adc *ads131m0x, uint8_t oid)
{
uint8_t frame_size = (ADS131M0X_STATUS_WORDS + ads131m0x->num_channels
+ ADS131M0X_CRC_WORDS) * ADS131M0X_WORD_SIZE;
uint8_t msg[ADS131M0X_MAX_FRAME_SIZE] = {0};
uint8_t frame_size = ads131m0x->frame_size;
// Frame layout: status(3), channel[0](3), channel[1](3), ..., crc(3)
// Data is 24-bit two's complement, MSB first
uint8_t msg[frame_size];
memset(msg, 0, frame_size);
spidev_transfer(ads131m0x->spi, 1, frame_size, msg);
ads131m0x->pending_flag = 0;
barrier();

// Validate CRC
if (ads131m0x_has_crc_error(msg, frame_size)) {
ads131m0x_publish_error(ads131m0x, oid, SE_ERROR_CRC);
return;
return RESCHEDULE_TIMER;
}

// Check for unexpected sensor reset
if (msg[0] & STATUS_RESET_BIT) {
ads131m0x->was_reset = 1;
ads131m0x->flags |= FLAG_RESET;
}
if (ads131m0x->was_reset) {
if (ads131m0x->flags & FLAG_RESET) {
ads131m0x_publish_error(ads131m0x, oid, SE_ERROR_RESET);
return;
return RESCHEDULE_TIMER;
}

// Frame layout: status(3), channel[0](3), channel[1](3), ..., crc(3)
// Data is 24-bit two's complement, MSB first
uint8_t data_offset = ADS131M0X_STATUS_WORDS * ADS131M0X_WORD_SIZE
+ ads131m0x->channel * ADS131M0X_WORD_SIZE;
uint_fast8_t data_offset = ads131m0x->data_offset;
uint32_t raw = ((uint32_t)msg[data_offset] << 16)
| ((uint32_t)msg[data_offset + 1] << 8)
| ((uint32_t)msg[data_offset + 2]);
Expand All @@ -172,6 +192,21 @@ ads131m0x_read_adc(struct ads131m0x_adc *ads131m0x, uint8_t oid)
trigger_analog_update(ads131m0x->ta, raw);
buffer_append_int32(&ads131m0x->sb, raw);
ads131m0x_flush_buffer(ads131m0x, oid);
if (unlikely(!ads131m0x_is_data_ready(ads131m0x))) {
ads131m0x->continuous_reads = 0;
return RESCHEDULE_TIMER;
}
if (unlikely(ads131m0x->continuous_reads >= 2)) {
// ADS131M0x FIFO can hold two readings
ads131m0x->sb.possible_overflows++;
} else {
ads131m0x->continuous_reads++;
}
// Delay the next timer event even further
ads131m0x->timer.waketime += ads131m0x->rest_ticks << 3;
// More data is available, wake up immediately
sched_wake_task(&wake_ads131m0x);
return 0;
}

// Create an ads131m0x sensor
Expand All @@ -181,7 +216,7 @@ command_config_ads131m0x(uint32_t *args)
struct ads131m0x_adc *ads131m0x = oid_alloc(args[0]
, command_config_ads131m0x, sizeof(*ads131m0x));
ads131m0x->timer.func = ads131m0x_event;
ads131m0x->pending_flag = 0;
ads131m0x->flags = 0;
ads131m0x->spi = spidev_oid_lookup(args[1]);
ads131m0x->channel = (uint8_t)(args[2] & 0xFF);
ads131m0x->num_channels = (uint8_t)(args[3] & 0xFF);
Expand All @@ -190,6 +225,10 @@ command_config_ads131m0x(uint32_t *args)
ads131m0x->channel >= ads131m0x->num_channels)
shutdown("Invalid ads131m0x channels configuration");
ads131m0x->data_ready = gpio_in_setup(args[4], 0);
ads131m0x->frame_size = (ADS131M0X_STATUS_WORDS + ads131m0x->num_channels
+ ADS131M0X_CRC_WORDS) * ADS131M0X_WORD_SIZE;
ads131m0x->data_offset =
(ADS131M0X_STATUS_WORDS + ads131m0x->channel) * ADS131M0X_WORD_SIZE;
}
DECL_COMMAND(command_config_ads131m0x, "config_ads131m0x oid=%c"
" spi_oid=%c channel=%c num_channels=%c data_ready_pin=%u");
Expand All @@ -212,14 +251,14 @@ command_query_ads131m0x(uint32_t *args)
uint8_t oid = args[0];
struct ads131m0x_adc *ads131m0x = oid_lookup(oid, command_config_ads131m0x);
sched_del_timer(&ads131m0x->timer);
ads131m0x->pending_flag = 0;
ads131m0x->flags = 0;
ads131m0x->rest_ticks = args[1];
if (!ads131m0x->rest_ticks) {
// End measurements
return;
}
// Start new measurements
ads131m0x->was_reset = 0;
ads131m0x->continuous_reads = 0;
sensor_bulk_reset(&ads131m0x->sb);
irq_disable();
ads131m0x->timer.waketime = timer_read_time() + ads131m0x->rest_ticks;
Expand Down Expand Up @@ -251,8 +290,10 @@ ads131m0x_capture_task(void)
uint8_t oid;
struct ads131m0x_adc *ads131m0x;
foreach_oid(oid, ads131m0x, command_config_ads131m0x) {
if (ads131m0x->pending_flag)
ads131m0x_read_adc(ads131m0x, oid);
uint_fast8_t flags = ads131m0x->flags;
if (flags & FLAG_PENDING)
if (ads131m0x_read_adc(ads131m0x, oid) == RESCHEDULE_TIMER)
ads131m0x_reschedule_timer(ads131m0x);
}
}
DECL_TASK(ads131m0x_capture_task);
Loading