From 70bf52062cc73f7411d9862a8c5de7c2e2388955 Mon Sep 17 00:00:00 2001 From: Thorsten von Eicken Date: Fri, 17 Nov 2023 18:41:12 -0800 Subject: [PATCH] Add new driver for HX711 load cell ADC --- examples/drivers/sensors/hx711/main.js | 23 ++++++ examples/drivers/sensors/hx711/manifest.json | 11 +++ modules/drivers/sensors/hx711/README.md | 14 ++++ modules/drivers/sensors/hx711/hx711.ts | 76 ++++++++++++++++++++ modules/drivers/sensors/hx711/hx711c.d.ts | 7 ++ modules/drivers/sensors/hx711/hx711c.js | 6 ++ modules/drivers/sensors/hx711/manifest.json | 8 +++ modules/drivers/sensors/hx711/modHX711c.c | 76 ++++++++++++++++++++ 8 files changed, 221 insertions(+) create mode 100644 examples/drivers/sensors/hx711/main.js create mode 100644 examples/drivers/sensors/hx711/manifest.json create mode 100644 modules/drivers/sensors/hx711/README.md create mode 100644 modules/drivers/sensors/hx711/hx711.ts create mode 100644 modules/drivers/sensors/hx711/hx711c.d.ts create mode 100644 modules/drivers/sensors/hx711/hx711c.js create mode 100644 modules/drivers/sensors/hx711/manifest.json create mode 100644 modules/drivers/sensors/hx711/modHX711c.c diff --git a/examples/drivers/sensors/hx711/main.js b/examples/drivers/sensors/hx711/main.js new file mode 100644 index 0000000000..5aa677da56 --- /dev/null +++ b/examples/drivers/sensors/hx711/main.js @@ -0,0 +1,23 @@ +// Copyright © 2023 by Thorsten von Eicken. +import HX711 from "embedded:sensor/ADC/HX711" +import Timer from "timer" +import Time from "time" + +trace("===== HX711 TEST =====\n") + +let hx711 = new HX711({ + sensor: { + io: device.io, + din: 7, + clk: 6, + }, + gain: 1, // 128x +}) + +let v = 0 +Timer.repeat(() => { + const t0 = Time.ticks + const raw = hx711.read() + const dt = Time.ticks - t0 + trace(`Raw: ${raw} in ${dt}ms\n`) +}, 1000) diff --git a/examples/drivers/sensors/hx711/manifest.json b/examples/drivers/sensors/hx711/manifest.json new file mode 100644 index 0000000000..dfdf8ac113 --- /dev/null +++ b/examples/drivers/sensors/hx711/manifest.json @@ -0,0 +1,11 @@ +{ + "include": [ + "$(MODDABLE)/examples/manifest_base.json", + "$(MODDABLE)/examples/manifest_typings.json", + "$(MODDABLE)/modules/io/manifest.json", + "$(MODDABLE)/modules/drivers/sensors/hx711/manifest.json" + ], + "modules": { + "*": ["./main"] + } +} diff --git a/modules/drivers/sensors/hx711/README.md b/modules/drivers/sensors/hx711/README.md new file mode 100644 index 0000000000..463f162621 --- /dev/null +++ b/modules/drivers/sensors/hx711/README.md @@ -0,0 +1,14 @@ +Avia HX711 24-bit ADC for weight scales +======================================= + +The HX711 chip is an ADC designed for weight scales or load cells. +It has a 24-bit ADC and a programmable gain amplifier (PGA) with gain up to 128. +The interface requires a clock wire and a data wire: the clock is pulsed 25-27 times and +24 bits of data are read from the data line. A number of extra clock pulses are required and +tell the chip what gain to use. + +This driver is very simple and basically provides a single function to read the ADC. This +function is implemented in C using modGPIO in order to perform the read as quickly as possible +and meet the timing requirements of the chip. A read takes a couple of milliseconds. + +This driver is intended to conform to ECMA-419. An example is provided in the examples directory. diff --git a/modules/drivers/sensors/hx711/hx711.ts b/modules/drivers/sensors/hx711/hx711.ts new file mode 100644 index 0000000000..3ec45d114c --- /dev/null +++ b/modules/drivers/sensors/hx711/hx711.ts @@ -0,0 +1,76 @@ +// Avia HX711 24-bit ADC for weight scales +// Copyright © 2023 by Thorsten von Eicken. + +import Time from "time" +import HX711c from "embedded:sensor/ADC/HX711c" + +export interface Options { + sensor: { + clk: number + din: number + } + onError?: (error: string) => void + + // gain selects the gain of the ADC as well as the analog input channel + gain?: number // 1:128chA, 2:32chB, 3:64chA (default: 1) +} + +export default class HX711 { + #hx711c: HX711c + + constructor(options) { + this.configure(options) + } + + close() { + this.#hx711c = undefined + } + + // To "configure" the HX711 we have to perform a read, which ends up setting up the gain + // for the next read + configure(options: Options) { + this.#hx711c = new HX711c(options.sensor.clk, options.sensor.din, options.gain || 1) + this.#hx711c.read() + } + + get format() { + return "number" + } + + readable() { + //return this.#din.read() == 0 + } + + read() { + return this.#hx711c.read() + } + + /* read() has to be implemented in C to meet the timing requirements (clk high < 50us) + + // read the 24-bit signed value from the sensor and perform additional pulses to set-up the gain + read() { + const clk_w = this.#clk.write.bind(this.#clk) + const din_r = this.#din.read.bind(this.#din) + if (din_r() != 0) return undefined + // read 24 bits: din is stable 100ns after clk rising edge until next rising edge, so we read + // after producing the falling edge + // ugh: clk high must be less than 50us or the chip enters power-down mode + let val = 0 + clk_w(0) // preload caches... + for (let i = 24; i > 0; i--) { + clk_w(1) + clk_w(0) + val = (val << 1) | (din_r() & 1) + } + // sign extend + val = (val << 8) >> 8 + if (val == -1) return undefined // chip entered power-down mode + // pulse the clock to set the gain + for (let i = this.#gain; i > 0; i--) { + clk_w(1) + clk_w(0) + } + return val + } +*/ +} diff --git a/modules/drivers/sensors/hx711/hx711c.d.ts b/modules/drivers/sensors/hx711/hx711c.d.ts new file mode 100644 index 0000000000..6b013cd6da --- /dev/null +++ b/modules/drivers/sensors/hx711/hx711c.d.ts @@ -0,0 +1,7 @@ +// Copyright © 2023 by Thorsten von Eicken. +export default class { + constructor(clk: number, din: number, gain: number) + read(): number + readable(): boolean + close(): void +} diff --git a/modules/drivers/sensors/hx711/hx711c.js b/modules/drivers/sensors/hx711/hx711c.js new file mode 100644 index 0000000000..57e1804d20 --- /dev/null +++ b/modules/drivers/sensors/hx711/hx711c.js @@ -0,0 +1,6 @@ +// Copyright © 2023 by Thorsten von Eicken. +export default class @ "xs_HX711_destructor" { + constructor(a, b, c) @ "xs_HX711_init"; + read() @ "xs_HX711_read"; + readable() @ "xs_HX711_readable"; +} diff --git a/modules/drivers/sensors/hx711/manifest.json b/modules/drivers/sensors/hx711/manifest.json new file mode 100644 index 0000000000..6a1e951d58 --- /dev/null +++ b/modules/drivers/sensors/hx711/manifest.json @@ -0,0 +1,8 @@ +{ + "include": [], + "modules": { + "embedded:sensor/ADC/HX711": "./hx711", + "embedded:sensor/ADC/HX711c": ["./hx711c", "./modHX711c.c"] + }, + "preload": ["embedded:sensor/ADC/HX711"] +} diff --git a/modules/drivers/sensors/hx711/modHX711c.c b/modules/drivers/sensors/hx711/modHX711c.c new file mode 100644 index 0000000000..09f42fb651 --- /dev/null +++ b/modules/drivers/sensors/hx711/modHX711c.c @@ -0,0 +1,76 @@ +// Copyright © 2023 by Thorsten von Eicken. + +#include "xsPlatform.h" +#include "xsmc.h" +#include "modGPIO.h" +#include "mc.xs.h" // for xsID_* constants + +#define xsmcVar(x) xsVar(x) + +typedef struct { + modGPIOConfigurationRecord clk; + modGPIOConfigurationRecord din; + int gain; +} hx711_data; + +void xs_HX711_init(xsMachine *the) { + if (xsmcArgc != 3) xsUnknownError("invalid arguments"); + int clk_pin = xsmcToInteger(xsArg(0)); + int din_pin = xsmcToInteger(xsArg(1)); + + hx711_data *data = c_malloc(sizeof(hx711_data)); + if (data == NULL) xsUnknownError("can't allocate data"); + data->gain = xsmcToInteger(xsArg(2)); + if (modGPIOInit(&data->clk, NULL, clk_pin, kModGPIOOutput)) + xsUnknownError("can't init clk pin"); + modGPIOWrite(&data->clk, 0); + if (modGPIOInit(&data->din, NULL, din_pin, kModGPIOInput)) + xsUnknownError("can't init dat pin"); + + xsmcSetHostData(xsThis, data); +} + +void xs_HX711_destructor(void *hostData) { + hx711_data *data = hostData; + modGPIOUninit(&data->clk); + modGPIOUninit(&data->din); + c_free(data); +} + +void xs_HX711_readable(xsMachine *the) { + hx711_data *data = xsmcGetHostData(xsThis); + xsmcSetBoolean(xsResult, modGPIORead(&data->din) == 0); +} + +void xs_HX711_read(xsMachine *the) { + hx711_data *data = xsmcGetHostData(xsThis); + + // check data is ready + if (modGPIORead(&data->din) != 0) { + xsmcSetUndefined(xsResult); + return; + } + modCriticalSectionBegin(); + + // read 24 bits + int32_t value = 0; + for (int i = 0; i < 24; i++) { + modGPIOWrite(&data->clk, 1); + modDelayMicroseconds(1); + modGPIOWrite(&data->clk, 0); + value = (value<<1) | (modGPIORead(&data->din) & 1); + } + + // sign-extend 24->32 bits + value = (value << 8) >> 8; + + // signal gain + for (int i = 0; i < data->gain; i++) { + modGPIOWrite(&data->clk, 1); + modGPIOWrite(&data->clk, 0); + } + modCriticalSectionEnd(); + + // return value + xsmcSetInteger(xsResult, value); +}