From 32ad759409cbaaf53d3f51fc2f6573634eaadbc9 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Tue, 10 Sep 2019 06:47:35 -0400 Subject: [PATCH] Add streaming support for hx711 device (#2793) --- app/modules/hx711.c | 319 ++++++++++++++++++++++++++++++++++++++---- docs/modules/hx711.md | 60 +++++++- 2 files changed, 346 insertions(+), 33 deletions(-) diff --git a/app/modules/hx711.c b/app/modules/hx711.c index 61198e33..cd871504 100644 --- a/app/modules/hx711.c +++ b/app/modules/hx711.c @@ -3,65 +3,313 @@ #include "module.h" #include "lauxlib.h" +#include "lmem.h" #include "platform.h" #include #include #include "user_interface.h" static uint8_t data_pin; static uint8_t clk_pin; +// The fields below are after the pin_num conversion +static uint8_t pin_data_pin; +static uint8_t pin_clk_pin; + +#ifdef GPIO_INTERRUPT_ENABLE +static task_handle_t tasknumber; + +// HX711_STATUS can be defined to enable the hx711.status() function to get debug info +#undef HX711_STATUS +#define BUFFERS 2 + +typedef struct { + char *buf[BUFFERS]; + uint32_t dropped[BUFFERS]; + uint32_t timestamp[BUFFERS]; + uint32_t interrupts; + uint32_t hx711_interrupts; + uint16_t buflen; + uint16_t used; + uint32_t nobuffer; + uint8_t active; // slot of the active buffer + uint8_t freed; // slot of the most recently freed buffer + uint8_t mode; + uint8_t dropping; // is non zero when there is no space + int cb_ref; +} CONTROL; + +static CONTROL *control; +#endif /*Lua: hx711.init(clk_pin,data_pin)*/ static int hx711_init(lua_State* L) { - clk_pin = luaL_checkinteger(L,1); - data_pin = luaL_checkinteger(L,2); + clk_pin = luaL_checkint(L,1); + data_pin = luaL_checkint(L,2); MOD_CHECK_ID( gpio, clk_pin ); MOD_CHECK_ID( gpio, data_pin ); platform_gpio_mode(clk_pin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT); platform_gpio_mode(data_pin, PLATFORM_GPIO_INPUT, PLATFORM_GPIO_FLOAT); platform_gpio_write(clk_pin,1);//put chip to sleep. + + pin_data_pin = pin_num[data_pin]; + pin_clk_pin = pin_num[clk_pin]; return 0; } +static int32_t ICACHE_RAM_ATTR read_sample(char mode) { + int i; + int32_t data = 0; + + for (i = 0; i < 24 ; i++){ //clock in the 24 bits + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 1 << pin_clk_pin); + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, 1 << pin_clk_pin); + data = data << 1; + if (GPIO_REG_READ(GPIO_IN_ADDRESS) & (1 << pin_data_pin)) { + data = i == 0 ? -1 : data | 1; //signextend the first bit + } + } + //add 25th-27th clock pulse to prevent protocol error + for (i = 0; i <= mode; i++) { + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 1 << pin_clk_pin); + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, 1 << pin_clk_pin); + } + + return data; +} + +#ifdef GPIO_INTERRUPT_ENABLE +static void ICACHE_RAM_ATTR hx711_data_available() { + if (!control) { + return; + } + uint32_t bits = GPIO_REG_READ(GPIO_IN_ADDRESS); + if (bits & (1 << pin_data_pin)) { + return; // not ready + } + + // Read a sample + int32_t data = read_sample(control->mode); + + if (control->dropping) { + if (control->active == control->freed) { + // still can't advance + control->nobuffer++; + return; + } + // Advance + control->active = (1 + control->active) % BUFFERS; + control->dropping = 0; + } + + // insert into the active buffer + char *dest = control->buf[control->active] + control->used; + *dest++ = data; + *dest++ = data >> 8; + *dest++ = data >> 16; + + control->used += 3; + if (control->used == control->buflen) { + control->used = 0; + control->timestamp[control->active] = system_get_time(); + control->dropped[control->active] = control->nobuffer; + control->nobuffer = 0; + // post task + task_post_medium(tasknumber, control->active); + + uint8_t next_active = (1 + control->active) % BUFFERS; + + if (control->active == control->freed) { + // We can't advance to the buffer + control->dropping = 1; + } else { + // flip to other buffer + control->active = next_active; + } + } +} + +static uint32_t ICACHE_RAM_ATTR hx711_interrupt(uint32_t ret_gpio_status) +{ + // This function really is running at interrupt level with everything + // else masked off. It should take as little time as necessary. + // + // + + // This gets the set of pins which have changed status + uint32 gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS); + + int pin_mask = 1 << pin_data_pin; + int i; + + control->interrupts++; + + if (gpio_status & pin_mask) { + uint32_t bits = GPIO_REG_READ(GPIO_IN_ADDRESS); + control->hx711_interrupts++; + if (!(bits & pin_mask)) { + // is now ready to read + hx711_data_available(); + } + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & pin_mask); + } + + return gpio_status & ~pin_mask; +} + +// Lua: hx711.start( mode, samples, callback ) +static int hx711_start( lua_State* L ) +{ + uint32_t mode = luaL_checkint( L, 1 ); + uint32_t samples = luaL_checkint( L, 2 ); + + if (mode > 2) { + return luaL_argerror( L, 1, "Mode value out of range" ); + } + + if (!samples || samples > 400) { + return luaL_argerror( L, 2, "Samples value out of range (1-400)" ); + } + + if (control) { + return luaL_error( L, "Already running" ); + } + + int buflen = 3 * samples; + + control = (CONTROL *) luaM_malloc(L, sizeof(CONTROL) + BUFFERS * buflen); + if (!control) { + return luaL_error( L, "Failed to allocate memory" ); + } + + int cb_ref; + + if (lua_type(L, 3) == LUA_TFUNCTION || lua_type(L, 3) == LUA_TLIGHTFUNCTION) { + lua_pushvalue(L, 3); // copy argument (func) to the top of stack + cb_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + luaM_free(L, control); + control = NULL; + return luaL_argerror( L, 3, "Not a callback function" ); + } + + memset(control, 0, sizeof(*control)); + control->buf[0] = (char *) (control + 1); + control->buflen = buflen; + int i; + + for (i = 1; i < BUFFERS; i++) { + control->buf[i] = control->buf[i - 1] + buflen; + } + control->mode = mode; + control->cb_ref = cb_ref; + control->freed = BUFFERS - 1; + + // configure data_pin as interrupt input + platform_gpio_register_intr_hook(1 << pin_data_pin, hx711_interrupt); + platform_gpio_mode(data_pin, PLATFORM_GPIO_INT, PLATFORM_GPIO_FLOAT); + platform_gpio_intr_init(data_pin, GPIO_PIN_INTR_NEGEDGE); + + + // Wake up chip + platform_gpio_write(clk_pin, 0); + + return 0; +} + +// Lua: hx711.stop( ) +static int hx711_stop( lua_State* L ) +{ + if (control) { + platform_gpio_mode(data_pin, PLATFORM_GPIO_INPUT, PLATFORM_GPIO_FLOAT); + CONTROL *to_free = control; + control = NULL; + luaL_unref(L, LUA_REGISTRYINDEX, to_free->cb_ref); + luaM_free(L, to_free); + } + + return 0; +} + +static int hx711_status( lua_State* L ) +{ + if (control) { + lua_pushlstring(L, (char *) control, sizeof(*control)); + return 1; + } + + return 0; +} + +static void hx711_task(os_param_t param, uint8_t prio) +{ + (void) prio; + if (!control) { + return; + } + + lua_State *L = lua_getstate(); + + if (control->cb_ref != LUA_NOREF) { + lua_rawgeti(L, LUA_REGISTRYINDEX, control->cb_ref); + + lua_pushlstring(L, control->buf[param], control->buflen); + lua_pushinteger(L, control->timestamp[param]); + lua_pushinteger(L, control->dropped[param]); + + control->freed = param; + + lua_call(L, 3, 0); + } +} +#endif + #define HX711_MAX_WAIT 1000000 /*will only read chA@128gain*/ /*Lua: result = hx711.read()*/ -static int ICACHE_FLASH_ATTR hx711_read(lua_State* L) { - uint32_t i; - int32_t data = 0; +static int hx711_read(lua_State* L) { + int j; //TODO: double check init has happened first. + // + + uint32_t mode = luaL_optinteger(L, 1, 0); + + if (mode > 2) { + return luaL_argerror( L, 1, "Mode value out of range" ); + } + +#ifdef GPIO_INTERRUPT_ENABLE + if (control) { + hx711_stop(L); + } +#endif //wakeup hx711 - platform_gpio_write(clk_pin,0); + platform_gpio_write(clk_pin, 0); - //wait for data ready. or time out. - //TODO: set pin inturrupt and come back to it. This may take up to 1/10 sec - // or maybe just make an async version too and have both available. - system_soft_wdt_feed(); //clear WDT... this may take a while. - for (i = 0; i=HX711_MAX_WAIT) { - return luaL_error( L, "ADC timeout!", ( unsigned )0 ); - } + // read two samples if mode > 0. We discard the first read and return the + // second value. + for (j = (mode ? 1 : 0); j >= 0; j--) { + uint32_t i; - for (i = 0; i<24 ; i++){ //clock in the 24 bits - platform_gpio_write(clk_pin,1); - platform_gpio_write(clk_pin,0); - data = data<<1; - if (platform_gpio_read(data_pin)==1) { - data = i==0 ? -1 : data|1; //signextend the first bit + //wait for data ready. or time out. + system_soft_wdt_feed(); //clear WDT... this may take a while. + for (i = 0; i= HX711_MAX_WAIT) { + return luaL_error( L, "ADC timeout!"); + } + + data = read_sample(mode); } - //add 25th clock pulse to prevent protocol error (probably not needed - // since we'll go to sleep immediately after and reset on wakeup.) - platform_gpio_write(clk_pin,1); - platform_gpio_write(clk_pin,0); - //sleep - platform_gpio_write(clk_pin,1); - lua_pushinteger( L, data ); + + //sleep -- unfortunately, this resets the mode to 0 + platform_gpio_write(clk_pin, 1); + lua_pushinteger(L, data); return 1; } @@ -69,11 +317,20 @@ static int ICACHE_FLASH_ATTR hx711_read(lua_State* L) { LROT_BEGIN(hx711) LROT_FUNCENTRY( init, hx711_init ) LROT_FUNCENTRY( read, hx711_read ) +#ifdef GPIO_INTERRUPT_ENABLE + LROT_FUNCENTRY( start, hx711_start ) +#ifdef HX711_STATUS + LROT_FUNCENTRY( status, hx711_status ) +#endif + LROT_FUNCENTRY( stop, hx711_stop ) +#endif LROT_END( hx711, NULL, 0 ) int luaopen_hx711(lua_State *L) { - // TODO: Make sure that the GPIO system is initialized +#ifdef GPIO_INTERRUPT_ENABLE + tasknumber = task_get_id(hx711_task); +#endif return 0; } diff --git a/docs/modules/hx711.md b/docs/modules/hx711.md index 0eaa3cff..1095ebae 100644 --- a/docs/modules/hx711.md +++ b/docs/modules/hx711.md @@ -2,8 +2,11 @@ | Since | Origin / Contributor | Maintainer | Source | | :----- | :-------------------- | :---------- | :------ | | 2015-10-09 | [Chris Takahashi](https://github.com/christakahashi) | [Chris Takahashi](https://github.com/christakahashi) | [hx711.c](../../app/modules/hx711.c)| +| 2019-04-20 | [Philip Gladstone](https://github.com/pjsg) | [Philip Gladstone](https://github.com/pjsg) -This module provides access to an [HX711 load cell amplifier/ADC](https://learn.sparkfun.com/tutorials/load-cell-amplifier-hx711-breakout-hookup-guide). The HX711 is an inexpensive 24bit ADC with programmable 128x, 64x, and 32x gain. Currently only channel A at 128x gain is supported. +This module provides access to an [HX711 load cell amplifier/ADC](https://learn.sparkfun.com/tutorials/load-cell-amplifier-hx711-breakout-hookup-guide). The HX711 is an inexpensive 24bit ADC with programmable 128x, 64x, and 32x gain. The standard Chinese sources have [cheap HX711 boards](https://www.aliexpress.com/wholesale?SearchText=hx711+module) for around $1. + +This can be used for single shot reads, or repetitive reads. Note: To save ROM image space, this module is not compiled into the firmware by default. @@ -35,11 +38,13 @@ Read digital loadcell ADC value. `hx711.read(mode)` #### Parameters -`mode` ADC mode. This parameter is currently ignored and reserved to ensure backward compatibility if support for additional modes is added. Currently only channel A @ 128 gain is supported. +- `mode` ADC mode. This parameter specifies which input and the gain to apply to that input. Reading in mode 1 or 2 takes longer than reading in mode 0. |mode | channel | gain | |-----|---------|------| | 0 | A | 128 | +| 1 | B | 32 | +| 2 | A | 64 | #### Returns a number (24 bit signed ADC value extended to the machine int size) @@ -49,3 +54,54 @@ a number (24 bit signed ADC value extended to the machine int size) -- Read ch A with 128 gain. raw_data = hx711.read(0) ``` + +## hx711.start() + +Starts to read multiple samples from the ADC. + +#### Syntax +`hx711.start(mode, samples, callback)` + +#### Parameters +- `mode` ADC mode. This parameter is currently ignored and reserved to ensure backward compatibility if support for additional modes is added. +- `samples` The number of samples before the callback is invoked. The length of time depends on the chip's sampling rate. +- `callback` The callback is invoked with three arguments (see below). + +|mode | channel | gain | +|-----|---------|------| +| 0 | A | 128 | +| 1 | B | 32 | +| 2 | A | 64 | + +#### Returns +nothing + +#### Callback +This is invoked every time `samples` samples are read from the HX711. The arguments are: + +- A string which contains `samples` packed 24 bit values. This can be unpacked with the `struct` module (using the "i3" format). +- The time in microseconds of the reception of the last sample in the buffer. +- The number of samples dropped before the start of this buffer (after the end of the previous buffer). + +#### Notes +This api only is built if GPIO_INTERRUPT_ENABLE and GPIO_INTERRUPT_HOOK_ENABLE are defined in the +`user_config.h`. This is the default. + +Also, do not try and mix calls to `start` and calls to `read`. Any calls to `read` will implicitly call `stop` first. + +#### Example +```lua +-- Read ch A with 128 gain. +hx711.start(0, 2, function(s, t, d) local r1, r2, _ = struct.unpack("i3 i3", s) print(r1, r2) end) +``` + +## hx711.stop() + +Stops a previously started set of reads. Any data in buffers is lost. No more callbacks will be invoked. + +#### Syntax +`hx711.stop()` + +#### Returns +nothing +