From 14a2eb4484b796ae2193fb69ce6322ac01412965 Mon Sep 17 00:00:00 2001 From: Dmitry Butyugin Date: Mon, 11 May 2026 22:11:36 +0200 Subject: [PATCH] ads131m0x: Optimized timer scheduling to attain higher sampling rates Signed-off-by: Dmitry Butyugin --- src/sensor_ads131m0x.c | 103 ++++++++++++++++++++++++++++------------- 1 file changed, 72 insertions(+), 31 deletions(-) diff --git a/src/sensor_ads131m0x.c b/src/sensor_ads131m0x.c index c97c84e24d10..fe69f042d49f 100644 --- a/src/sensor_ads131m0x.c +++ b/src/sensor_ads131m0x.c @@ -6,6 +6,7 @@ // This file may be distributed under the terms of the GNU GPLv3 license. #include +#include // memset #include "basecmd.h" // oid_alloc #include "board/gpio.h" // gpio_in_setup #include "board/irq.h" // irq_disable @@ -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, @@ -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; } @@ -92,6 +102,20 @@ 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) @@ -99,13 +123,11 @@ 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; @@ -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]); @@ -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 @@ -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); @@ -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"); @@ -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; @@ -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);