aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorGareth Farrington <gareth@waves.ky>2024-06-02 22:31:28 -0700
committerKevin O'Connor <kevin@koconnor.net>2024-07-31 21:22:06 -0400
commitc0095812ff18687ed25ce0f1ed468ebed8f81cfe (patch)
treecb57572e351128f439789306f2b9b99bb7ccba65 /src
parent0844388d70f225f6458382c1c5d4e7eb37767758 (diff)
downloadkutter-c0095812ff18687ed25ce0f1ed468ebed8f81cfe.tar.gz
kutter-c0095812ff18687ed25ce0f1ed468ebed8f81cfe.tar.xz
kutter-c0095812ff18687ed25ce0f1ed468ebed8f81cfe.zip
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 <gareth@waves.ky>
Diffstat (limited to 'src')
-rw-r--r--src/Kconfig9
-rw-r--r--src/Makefile1
-rw-r--r--src/sensor_hx71x.c245
3 files changed, 254 insertions, 1 deletions
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 <gareth@waves.ky>
+//
+// 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 <stdbool.h>
+#include <stdint.h>
+
+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);