From c0095812ff18687ed25ce0f1ed468ebed8f81cfe Mon Sep 17 00:00:00 2001 From: Gareth Farrington Date: Sun, 2 Jun 2024 22:31:28 -0700 Subject: hx71x: Load Cell Skeleton and HX71x bulk ADC * Create the load_cell host module skeleton to create the sensors and start taking samples. * Add support for the HX717 and HX711 ADC sensors. Signed-off-by: Gareth Farrington --- src/Kconfig | 9 +- src/Makefile | 1 + src/sensor_hx71x.c | 245 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 src/sensor_hx71x.c (limited to 'src') diff --git a/src/Kconfig b/src/Kconfig index 7dcea3ba..4fb5268a 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -108,6 +108,10 @@ config WANT_LDC1612 bool depends on HAVE_GPIO_I2C default y +config WANT_HX71X + bool + depends on WANT_GPIO_BITBANGING + default y config WANT_SOFTWARE_I2C bool depends on HAVE_GPIO && HAVE_GPIO_I2C @@ -118,7 +122,7 @@ config WANT_SOFTWARE_SPI default y config NEED_SENSOR_BULK bool - depends on WANT_SENSORS || WANT_LIS2DW || WANT_LDC1612 + depends on WANT_SENSORS || WANT_LIS2DW || WANT_LDC1612 || WANT_HX71X default y menu "Optional features (to reduce code size)" depends on HAVE_LIMITED_CODE_SIZE @@ -137,6 +141,9 @@ config WANT_LIS2DW config WANT_LDC1612 bool "Support ldc1612 eddy current sensor" depends on HAVE_GPIO_I2C +config WANT_HX71X + bool "Support HX711 and HX717 ADC chips" + depends on WANT_GPIO_BITBANGING config WANT_SOFTWARE_I2C bool "Support software based I2C \"bit-banging\"" depends on HAVE_GPIO && HAVE_GPIO_I2C diff --git a/src/Makefile b/src/Makefile index ed98172e..4a1d2436 100644 --- a/src/Makefile +++ b/src/Makefile @@ -20,4 +20,5 @@ sensors-src-$(CONFIG_HAVE_GPIO_I2C) += sensor_mpu9250.c src-$(CONFIG_WANT_SENSORS) += $(sensors-src-y) src-$(CONFIG_WANT_LIS2DW) += sensor_lis2dw.c src-$(CONFIG_WANT_LDC1612) += sensor_ldc1612.c +src-$(CONFIG_WANT_HX71X) += sensor_hx71x.c src-$(CONFIG_NEED_SENSOR_BULK) += sensor_bulk.c diff --git a/src/sensor_hx71x.c b/src/sensor_hx71x.c new file mode 100644 index 00000000..4f0a8c5b --- /dev/null +++ b/src/sensor_hx71x.c @@ -0,0 +1,245 @@ +// Support for bit-banging commands to HX711 and HX717 ADC chips +// +// Copyright (C) 2024 Gareth Farrington +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "autoconf.h" // CONFIG_MACH_AVR +#include "board/gpio.h" // gpio_out_write +#include "board/irq.h" // irq_poll +#include "board/misc.h" // timer_read_time +#include "basecmd.h" // oid_alloc +#include "command.h" // DECL_COMMAND +#include "sched.h" // sched_add_timer +#include "sensor_bulk.h" // sensor_bulk_report +#include +#include + +struct hx71x_adc { + struct timer timer; + uint8_t gain_channel; // the gain+channel selection (1-4) + uint8_t pending_flag; + uint32_t rest_ticks; + uint32_t last_error; + struct gpio_in dout; // pin used to receive data from the hx71x + struct gpio_out sclk; // pin used to generate clock for the hx71x + struct sensor_bulk sb; +}; + +#define BYTES_PER_SAMPLE 4 +#define SAMPLE_ERROR_DESYNC 1 << 31 +#define SAMPLE_ERROR_READ_TOO_LONG 1 << 30 + +static struct task_wake wake_hx71x; + + +/**************************************************************** + * Low-level bit-banging + ****************************************************************/ + +#define MIN_PULSE_TIME nsecs_to_ticks(200) + +static uint32_t +nsecs_to_ticks(uint32_t ns) +{ + return timer_from_us(ns * 1000) / 1000000; +} + +// Pause for 200ns +static void +hx71x_delay_noirq(void) +{ + if (CONFIG_MACH_AVR) { + // Optimize avr, as calculating time takes longer than needed delay + asm("nop\n nop"); + return; + } + uint32_t end = timer_read_time() + MIN_PULSE_TIME; + while (timer_is_before(timer_read_time(), end)) + ; +} + +// Pause for a minimum of 200ns +static void +hx71x_delay(void) +{ + if (CONFIG_MACH_AVR) + // Optimize avr, as calculating time takes longer than needed delay + return; + uint32_t end = timer_read_time() + MIN_PULSE_TIME; + while (timer_is_before(timer_read_time(), end)) + irq_poll(); +} + +// Read 'num_bits' from the sensor +static uint32_t +hx71x_raw_read(struct gpio_in dout, struct gpio_out sclk, int num_bits) +{ + uint32_t bits_read = 0; + while (num_bits--) { + irq_disable(); + gpio_out_toggle_noirq(sclk); + hx71x_delay_noirq(); + gpio_out_toggle_noirq(sclk); + uint_fast8_t bit = gpio_in_read(dout); + irq_enable(); + hx71x_delay(); + bits_read = (bits_read << 1) | bit; + } + return bits_read; +} + + +/**************************************************************** + * HX711 and HX717 Sensor Support + ****************************************************************/ + +// Check if data is ready +static uint_fast8_t +hx71x_is_data_ready(struct hx71x_adc *hx71x) +{ + return !gpio_in_read(hx71x->dout); +} + +// Event handler that wakes wake_hx71x() periodically +static uint_fast8_t +hx71x_event(struct timer *timer) +{ + struct hx71x_adc *hx71x = container_of(timer, struct hx71x_adc, timer); + uint32_t rest_ticks = hx71x->rest_ticks; + if (hx71x->pending_flag) { + hx71x->sb.possible_overflows++; + rest_ticks *= 4; + } else if (hx71x_is_data_ready(hx71x)) { + // New sample pending + hx71x->pending_flag = 1; + sched_wake_task(&wake_hx71x); + rest_ticks *= 8; + } + hx71x->timer.waketime += rest_ticks; + return SF_RESCHEDULE; +} + +static void +add_sample(struct hx71x_adc *hx71x, uint8_t oid, uint32_t counts, + uint8_t force_flush) { + // Add measurement to buffer + hx71x->sb.data[hx71x->sb.data_count] = counts; + hx71x->sb.data[hx71x->sb.data_count + 1] = counts >> 8; + hx71x->sb.data[hx71x->sb.data_count + 2] = counts >> 16; + hx71x->sb.data[hx71x->sb.data_count + 3] = counts >> 24; + hx71x->sb.data_count += BYTES_PER_SAMPLE; + + if (hx71x->sb.data_count + BYTES_PER_SAMPLE > ARRAY_SIZE(hx71x->sb.data) + || force_flush) + sensor_bulk_report(&hx71x->sb, oid); +} + +// hx71x ADC query +static void +hx71x_read_adc(struct hx71x_adc *hx71x, uint8_t oid) +{ + uint32_t start = timer_read_time(); + // Read from sensor + uint_fast8_t gain_channel = hx71x->gain_channel; + uint32_t adc = hx71x_raw_read(hx71x->dout, hx71x->sclk, 24 + gain_channel); + hx71x->pending_flag = 0; + barrier(); + + // Extract report from raw data + uint32_t counts = adc >> gain_channel; + if (counts & 0x800000) + counts |= 0xFF000000; + + // Check for errors + uint_fast8_t extras_mask = (1 << gain_channel) - 1; + if ((adc & extras_mask) != extras_mask) { + // Transfer did not complete correctly + hx71x->last_error = SAMPLE_ERROR_DESYNC; + } else if ((timer_read_time() - start) > (hx71x->rest_ticks * 8)) { + // Transfer took too long + hx71x->last_error = SAMPLE_ERROR_READ_TOO_LONG; + } + + // forever send errors until reset + if (hx71x->last_error != 0) { + counts = hx71x->last_error; + } + + // Add measurement to buffer + add_sample(hx71x, oid, counts, false); +} + +// Create a hx71x sensor +void +command_config_hx71x(uint32_t *args) +{ + struct hx71x_adc *hx71x = oid_alloc(args[0] + , command_config_hx71x, sizeof(*hx71x)); + hx71x->timer.func = hx71x_event; + hx71x->pending_flag = 0; + uint8_t gain_channel = args[1]; + if (gain_channel < 1 || gain_channel > 4) { + shutdown("HX71x gain/channel out of range 1-4"); + } + hx71x->gain_channel = gain_channel; + hx71x->dout = gpio_in_setup(args[2], 1); + hx71x->sclk = gpio_out_setup(args[3], 0); + gpio_out_write(hx71x->sclk, 1); // put chip in power down state +} +DECL_COMMAND(command_config_hx71x, "config_hx71x oid=%c gain_channel=%c" + " dout_pin=%u sclk_pin=%u"); + +// start/stop capturing ADC data +void +command_query_hx71x(uint32_t *args) +{ + uint8_t oid = args[0]; + struct hx71x_adc *hx71x = oid_lookup(oid, command_config_hx71x); + sched_del_timer(&hx71x->timer); + hx71x->pending_flag = 0; + hx71x->last_error = 0; + hx71x->rest_ticks = args[1]; + if (!hx71x->rest_ticks) { + // End measurements + gpio_out_write(hx71x->sclk, 1); // put chip in power down state + return; + } + // Start new measurements + gpio_out_write(hx71x->sclk, 0); // wake chip from power down + sensor_bulk_reset(&hx71x->sb); + irq_disable(); + hx71x->timer.waketime = timer_read_time() + hx71x->rest_ticks; + sched_add_timer(&hx71x->timer); + irq_enable(); +} +DECL_COMMAND(command_query_hx71x, "query_hx71x oid=%c rest_ticks=%u"); + +void +command_query_hx71x_status(const uint32_t *args) +{ + uint8_t oid = args[0]; + struct hx71x_adc *hx71x = oid_lookup(oid, command_config_hx71x); + irq_disable(); + const uint32_t start_t = timer_read_time(); + uint8_t is_data_ready = hx71x_is_data_ready(hx71x); + irq_enable(); + uint8_t pending_bytes = is_data_ready ? BYTES_PER_SAMPLE : 0; + sensor_bulk_status(&hx71x->sb, oid, start_t, 0, pending_bytes); +} +DECL_COMMAND(command_query_hx71x_status, "query_hx71x_status oid=%c"); + +// Background task that performs measurements +void +hx71x_capture_task(void) +{ + if (!sched_check_wake(&wake_hx71x)) + return; + uint8_t oid; + struct hx71x_adc *hx71x; + foreach_oid(oid, hx71x, command_config_hx71x) { + if (hx71x->pending_flag) + hx71x_read_adc(hx71x, oid); + } +} +DECL_TASK(hx71x_capture_task); -- cgit v1.2.3-70-g09d2