From d40ee50a8e0d02253a8504961a27d73858cf9f53 Mon Sep 17 00:00:00 2001 From: philip Date: Sun, 28 Feb 2016 14:48:41 -0500 Subject: [PATCH] Squashed commit of the following: commit 2c7c3fc3985cc32866e8af496abea9971eaee90a Merge: 9179dae 41022c3 Author: philip Date: Sun Feb 28 14:47:47 2016 -0500 Merge remote-tracking branch 'upstream/dev' into rotary_2 commit 9179dae0824e6b35ad09e5113aacc26dc91692c0 Author: philip Date: Fri Feb 26 20:53:27 2016 -0500 Review comments commit 67741170e20ccb2b636e701f0664feff2aafbb4c Author: philip Date: Fri Feb 26 20:59:49 2016 -0500 Squashed commit of the following: commit 8c9a64731c4a8b9aedda18a399b433b173d2199f Merge: 085935f 19d3c1d Author: philip Date: Fri Feb 26 20:58:10 2016 -0500 Merge remote-tracking branch 'upstream/dev' into rotarymod Conflicts: app/platform/platform.c commit 085935fc56986d607ff5e05d1663970331959c34 Author: philip Date: Fri Feb 26 20:53:27 2016 -0500 Review comment commit 7732fd2d1044f28b8fcf5b0aa0f76d76fe80f449 Author: philip Date: Sat Feb 20 12:10:38 2016 -0500 Module to handle rotary decoders Eliminate ROTARY_DEBUG Remove unused file Eliminate a malloc call Cleaned up the register code. Now 0x114 bytes Fix bug with clearing bits in one case Fix the type in the #define name --- app/driver/rotary.c | 320 ++++++++++++++++++++++++++++ app/include/driver/rotary.h | 26 +++ app/include/user_config.h | 1 + app/include/user_modules.h | 1 + app/modules/rotary.c | 405 ++++++++++++++++++++++++++++++++++++ app/platform/platform.c | 115 +++++++++- app/platform/platform.h | 10 +- docs/en/modules/rotary.md | 129 ++++++++++++ mkdocs.yml | 1 + 9 files changed, 1004 insertions(+), 4 deletions(-) create mode 100644 app/driver/rotary.c create mode 100644 app/include/driver/rotary.h create mode 100644 app/modules/rotary.c create mode 100644 docs/en/modules/rotary.md diff --git a/app/driver/rotary.c b/app/driver/rotary.c new file mode 100644 index 00000000..c00f251f --- /dev/null +++ b/app/driver/rotary.c @@ -0,0 +1,320 @@ +/* + * Driver for interfacing to cheap rotary switches that + * have a quadrature output with an optional press button + * + * This sets up the relevant gpio as interrupt and then keeps track of + * the position of the switch in software. Changes are enqueued to task + * level and a task message posted when required. If the queue fills up + * then moves are ignored, but the last press/release will be included. + * + * Philip Gladstone, N1DQ + */ + +#include "platform.h" +#include "c_types.h" +#include "../libc/c_stdlib.h" +#include "../libc/c_stdio.h" +#include "driver/rotary.h" +#include "user_interface.h" +#include "task/task.h" +#include "ets_sys.h" + +// +// Queue is empty if read == write. +// However, we always want to keep the previous value +// so writing is only allowed if write - read < QUEUE_SIZE - 1 + +#define QUEUE_SIZE 8 + +#define GET_LAST_STATUS(d) (d->queue[(d->write_offset-1) & (QUEUE_SIZE - 1)]) +#define GET_PREV_STATUS(d) (d->queue[(d->write_offset-2) & (QUEUE_SIZE - 1)]) +#define HAS_QUEUED_DATA(d) (d->read_offset < d->write_offset) +#define HAS_QUEUE_SPACE(d) (d->read_offset + QUEUE_SIZE - 1 > d->write_offset) + +#define REPLACE_STATUS(d, x) (d->queue[(d->write_offset-1) & (QUEUE_SIZE - 1)] = (rotary_event_t) { (x), system_get_time() }) +#define QUEUE_STATUS(d, x) (d->queue[(d->write_offset++) & (QUEUE_SIZE - 1)] = (rotary_event_t) { (x), system_get_time() }) + +#define GET_READ_STATUS(d) (d->queue[d->read_offset & (QUEUE_SIZE - 1)]) +#define ADVANCE_IF_POSSIBLE(d) if (d->read_offset < d->write_offset) { d->read_offset++; } + +#define STATUS_IS_PRESSED(x) ((x & 0x80000000) != 0) + +typedef struct { + int8_t phase_a_pin; + int8_t phase_b_pin; + int8_t press_pin; + uint32_t read_offset; // Accessed by task + uint32_t write_offset; // Accessed by ISR + uint32_t pin_mask; + uint32_t phase_a; + uint32_t phase_b; + uint32_t press; + uint32_t last_press_change_time; + int tasknumber; + rotary_event_t queue[QUEUE_SIZE]; +} DATA; + +static DATA *data[ROTARY_CHANNEL_COUNT]; + +static uint8_t task_queued; + +static void set_gpio_bits(void); + +static void rotary_clear_pin(int pin) +{ + if (pin >= 0) { + gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[pin]), GPIO_PIN_INTR_DISABLE); + platform_gpio_mode(pin, PLATFORM_GPIO_INPUT, PLATFORM_GPIO_PULLUP); + } +} + +// Just takes the channel number. Cleans up the resources used. +int rotary_close(uint32_t channel) +{ + if (channel >= sizeof(data) / sizeof(data[0])) { + return -1; + } + + DATA *d = data[channel]; + + if (!d) { + return 0; + } + + data[channel] = NULL; + + rotary_clear_pin(d->phase_a_pin); + rotary_clear_pin(d->phase_b_pin); + rotary_clear_pin(d->press_pin); + + c_free(d); + + set_gpio_bits(); + + return 0; +} + +static uint32_t ICACHE_RAM_ATTR rotary_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 i; + for (i = 0; i < sizeof(data) / sizeof(data[0]); i++) { + DATA *d = data[i]; + if (!d || (gpio_status & d->pin_mask) == 0) { + continue; + } + + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & d->pin_mask); + + uint32_t bits = GPIO_REG_READ(GPIO_IN_ADDRESS); + + uint32_t last_status = GET_LAST_STATUS(d).pos; + + uint32_t now = system_get_time(); + + uint32_t new_status; + + new_status = last_status & 0x80000000; + + // This is the debounce logic for the press switch. We ignore changes + // for 10ms after a change. + if (now - d->last_press_change_time > 10 * 1000) { + new_status = (bits & d->press) ? 0 : 0x80000000; + if (STATUS_IS_PRESSED(new_status ^ last_status)) { + d->last_press_change_time = now; + } + } + + // A B + // 1 1 => 0 + // 1 0 => 1 + // 0 0 => 2 + // 0 1 => 3 + + int micropos = 2; + if (bits & d->phase_b) { + micropos = 3; + } + if (bits & d->phase_a) { + micropos ^= 3; + } + + int32_t rotary_pos = last_status; + + switch ((micropos - last_status) & 3) { + case 0: + // No change, nothing to do + break; + case 1: + // Incremented by 1 + rotary_pos++; + break; + case 3: + // Decremented by 1 + rotary_pos--; + break; + default: + // We missed an interrupt + // We will ignore... but mark it. + rotary_pos += 1000000; + break; + } + + new_status |= rotary_pos & 0x7fffffff; + + if (last_status != new_status) { + // Either we overwrite the status or we add a new one + if (!HAS_QUEUED_DATA(d) + || STATUS_IS_PRESSED(last_status ^ new_status) + || STATUS_IS_PRESSED(last_status ^ GET_PREV_STATUS(d).pos)) { + if (HAS_QUEUE_SPACE(d)) { + QUEUE_STATUS(d, new_status); + if (!task_queued) { + if (task_post_medium(d->tasknumber, (os_param_t) &task_queued)) { + task_queued = 1; + } + } + } else { + REPLACE_STATUS(d, new_status); + } + } else { + REPLACE_STATUS(d, new_status); + } + } + ret_gpio_status &= ~(d->pin_mask); + } + + return ret_gpio_status; +} + +// The pin numbers are actual platform GPIO numbers +int rotary_setup(uint32_t channel, int phase_a, int phase_b, int press, task_handle_t tasknumber ) +{ + if (channel >= sizeof(data) / sizeof(data[0])) { + return -1; + } + + if (data[channel]) { + if (rotary_close(channel)) { + return -1; + } + } + + DATA *d = (DATA *) c_zalloc(sizeof(DATA)); + if (!d) { + return -1; + } + + data[channel] = d; + int i; + + d->tasknumber = tasknumber; + + d->phase_a = 1 << pin_num[phase_a]; + platform_gpio_mode(phase_a, PLATFORM_GPIO_INT, PLATFORM_GPIO_PULLUP); + gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[phase_a]), GPIO_PIN_INTR_ANYEDGE); + d->phase_a_pin = phase_a; + + d->phase_b = 1 << pin_num[phase_b]; + platform_gpio_mode(phase_b, PLATFORM_GPIO_INT, PLATFORM_GPIO_PULLUP); + gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[phase_b]), GPIO_PIN_INTR_ANYEDGE); + d->phase_b_pin = phase_b; + + if (press >= 0) { + d->press = 1 << pin_num[press]; + platform_gpio_mode(press, PLATFORM_GPIO_INT, PLATFORM_GPIO_PULLUP); + gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[press]), GPIO_PIN_INTR_ANYEDGE); + } + d->press_pin = press; + + d->pin_mask = d->phase_a | d->phase_b | d->press; + + set_gpio_bits(); + + return 0; +} + +static void set_gpio_bits() +{ + uint32_t bits = 0; + for (int i = 0; i < ROTARY_CHANNEL_COUNT; i++) { + DATA *d = data[i]; + + if (d) { + bits = bits | d->pin_mask; + } + } + + platform_gpio_register_intr_hook(bits, rotary_interrupt); +} + +bool rotary_has_queued_event(uint32_t channel) +{ + if (channel >= sizeof(data) / sizeof(data[0])) { + return FALSE; + } + + DATA *d = data[channel]; + + if (!d) { + return FALSE; + } + + return HAS_QUEUED_DATA(d); +} + +// Get the oldest event in the queue and remove it (if possible) +bool rotary_getevent(uint32_t channel, rotary_event_t *resultp) +{ + rotary_event_t result = { 0 }; + + if (channel >= sizeof(data) / sizeof(data[0])) { + return FALSE; + } + + DATA *d = data[channel]; + + if (!d) { + return FALSE; + } + + ETS_GPIO_INTR_DISABLE(); + + bool status = FALSE; + + if (HAS_QUEUED_DATA(d)) { + result = GET_READ_STATUS(d); + d->read_offset++; + status = TRUE; + } else { + result = GET_LAST_STATUS(d); + } + + ETS_GPIO_INTR_ENABLE(); + + *resultp = result; + + return status; +} + +int rotary_getpos(uint32_t channel) +{ + if (channel >= sizeof(data) / sizeof(data[0])) { + return -1; + } + + DATA *d = data[channel]; + + if (!d) { + return -1; + } + + return GET_LAST_STATUS(d).pos; +} diff --git a/app/include/driver/rotary.h b/app/include/driver/rotary.h new file mode 100644 index 00000000..d4c3ec59 --- /dev/null +++ b/app/include/driver/rotary.h @@ -0,0 +1,26 @@ +/* + * Definitions to access the Rotary driver + */ +#ifndef __ROTARY_H__ +#define __ROTARY_H__ + +#include "c_types.h" + +#define ROTARY_CHANNEL_COUNT 3 + +typedef struct { + uint32_t pos; + uint32_t time_us; +} rotary_event_t; + +int rotary_setup(uint32_t channel, int phaseA, int phaseB, int press, task_handle_t tasknumber); + +bool rotary_getevent(uint32_t channel, rotary_event_t *result); + +bool rotary_has_queued_event(uint32_t channel); + +int rotary_getpos(uint32_t channel); + +int rotary_close(uint32_t channel); + +#endif diff --git a/app/include/user_config.h b/app/include/user_config.h index 80602533..9a07daf6 100644 --- a/app/include/user_config.h +++ b/app/include/user_config.h @@ -42,6 +42,7 @@ #endif /* NODE_ERROR */ #define GPIO_INTERRUPT_ENABLE +#define GPIO_INTERRUPT_HOOK_ENABLE // #define GPIO_SAFE_NO_INTR_ENABLE #define ICACHE_STORE_TYPEDEF_ATTR __attribute__((aligned(4),packed)) diff --git a/app/include/user_modules.h b/app/include/user_modules.h index ae9b413e..60296cbe 100644 --- a/app/include/user_modules.h +++ b/app/include/user_modules.h @@ -38,6 +38,7 @@ //#define LUA_USE_MODULES_PERF #define LUA_USE_MODULES_PWM #define LUA_USE_MODULES_RC +//#define LUA_USE_MODULES_ROTARY #define LUA_USE_MODULES_RTCFIFO #define LUA_USE_MODULES_RTCMEM #define LUA_USE_MODULES_RTCTIME diff --git a/app/modules/rotary.c b/app/modules/rotary.c new file mode 100644 index 00000000..af60a4e3 --- /dev/null +++ b/app/modules/rotary.c @@ -0,0 +1,405 @@ +/* + * Module for interfacing with cheap rotary switches that + * are much used in the automtive industry as the cntrols for + * CD players and the like. + * + * Philip Gladstone, N1DQ + */ + +#include "module.h" +#include "lauxlib.h" +#include "platform.h" +#include "c_types.h" +#include "user_interface.h" +#include "driver/rotary.h" +#include "../libc/c_stdlib.h" + +#define MASK(x) (1 << ROTARY_ ## x ## _INDEX) + +#define ROTARY_PRESS_INDEX 0 +#define ROTARY_LONGPRESS_INDEX 1 +#define ROTARY_RELEASE_INDEX 2 +#define ROTARY_TURN_INDEX 3 +#define ROTARY_CLICK_INDEX 4 +#define ROTARY_DBLCLICK_INDEX 5 + +#define ROTARY_ALL 0x3f + +#define LONGPRESS_DELAY_US 500000 +#define CLICK_DELAY_US 500000 + +#define CALLBACK_COUNT 6 + +#ifdef LUA_USE_MODULES_ROTARY +#if !defined(GPIO_INTERRUPT_ENABLE) || !defined(GPIO_INTERRUPT_HOOK_ENABLE) +#error Must have GPIO_INTERRUPT and GPIO_INTERRUPT_HOOK if using ROTARY module +#endif +#endif + +typedef struct { + int lastpos; + int last_recent_event_was_press : 1; + int last_recent_event_was_release : 1; + int timer_running : 1; + int possible_dbl_click : 1; + uint8_t id; + int click_delay_us; + int longpress_delay_us; + uint32_t last_event_time; + int callback[CALLBACK_COUNT]; + ETSTimer timer; +} DATA; + +static DATA *data[ROTARY_CHANNEL_COUNT]; +static task_handle_t tasknumber; +static void lrotary_timer_done(void *param); +static void lrotary_check_timer(DATA *d, uint32_t time_us, bool dotimer); + +static void callback_free_one(lua_State *L, int *cb_ptr) +{ + if (*cb_ptr != LUA_NOREF) { + luaL_unref(L, LUA_REGISTRYINDEX, *cb_ptr); + *cb_ptr = LUA_NOREF; + } +} + +static void callback_free(lua_State* L, unsigned int id, int mask) +{ + DATA *d = data[id]; + + if (d) { + int i; + for (i = 0; i < CALLBACK_COUNT; i++) { + if (mask & (1 << i)) { + callback_free_one(L, &d->callback[i]); + } + } + } +} + +static int callback_setOne(lua_State* L, int *cb_ptr, int arg_number) +{ + if (lua_type(L, arg_number) == LUA_TFUNCTION || lua_type(L, arg_number) == LUA_TLIGHTFUNCTION) { + lua_pushvalue(L, arg_number); // copy argument (func) to the top of stack + callback_free_one(L, cb_ptr); + *cb_ptr = luaL_ref(L, LUA_REGISTRYINDEX); + return 0; + } + + return -1; +} + +static int callback_set(lua_State* L, int id, int mask, int arg_number) +{ + DATA *d = data[id]; + int result = 0; + + int i; + for (i = 0; i < CALLBACK_COUNT; i++) { + if (mask & (1 << i)) { + result |= callback_setOne(L, &d->callback[i], arg_number); + } + } + + return result; +} + +static void callback_callOne(lua_State* L, int cb, int mask, int arg, uint32_t time) +{ + if (cb != LUA_NOREF) { + lua_rawgeti(L, LUA_REGISTRYINDEX, cb); + + lua_pushinteger(L, mask); + lua_pushinteger(L, arg); + lua_pushinteger(L, time); + + lua_call(L, 3, 0); + } +} + +static void callback_call(lua_State* L, DATA *d, int cbnum, int arg, uint32_t time) +{ + if (d) { + callback_callOne(L, d->callback[cbnum], 1 << cbnum, arg, time); + } +} + +int platform_rotary_exists( unsigned int id ) +{ + return (id < ROTARY_CHANNEL_COUNT); +} + +// Lua: setup(id, phase_a, phase_b [, press]) +static int lrotary_setup( lua_State* L ) +{ + unsigned int id; + + id = luaL_checkinteger( L, 1 ); + MOD_CHECK_ID( rotary, id ); + + if (rotary_close(id)) { + return luaL_error( L, "Unable to close switch." ); + } + callback_free(L, id, ROTARY_ALL); + + if (!data[id]) { + data[id] = (DATA *) c_zalloc(sizeof(DATA)); + if (!data[id]) { + return -1; + } + } + + DATA *d = data[id]; + memset(d, 0, sizeof(*d)); + + os_timer_setfn(&d->timer, lrotary_timer_done, (void *) d); + + int i; + for (i = 0; i < CALLBACK_COUNT; i++) { + d->callback[i] = LUA_NOREF; + } + + d->click_delay_us = CLICK_DELAY_US; + d->longpress_delay_us = LONGPRESS_DELAY_US; + + int phase_a = luaL_checkinteger(L, 2); + luaL_argcheck(L, platform_gpio_exists(phase_a) && phase_a > 0, 2, "Invalid pin"); + int phase_b = luaL_checkinteger(L, 3); + luaL_argcheck(L, platform_gpio_exists(phase_b) && phase_b > 0, 3, "Invalid pin"); + int press; + if (lua_gettop(L) >= 4) { + press = luaL_checkinteger(L, 4); + luaL_argcheck(L, platform_gpio_exists(press) && press > 0, 4, "Invalid pin"); + } else { + press = -1; + } + + if (lua_gettop(L) >= 5) { + d->longpress_delay_us = 1000 * luaL_checkinteger(L, 5); + luaL_argcheck(L, d->longpress_delay_us > 0, 5, "Invalid timeout"); + } + + if (lua_gettop(L) >= 6) { + d->click_delay_us = 1000 * luaL_checkinteger(L, 6); + luaL_argcheck(L, d->click_delay_us > 0, 6, "Invalid timeout"); + } + + if (rotary_setup(id, phase_a, phase_b, press, tasknumber)) { + return luaL_error(L, "Unable to setup rotary switch."); + } + return 0; +} + +// Lua: close( id ) +static int lrotary_close( lua_State* L ) +{ + unsigned int id; + + id = luaL_checkinteger( L, 1 ); + MOD_CHECK_ID( rotary, id ); + callback_free(L, id, ROTARY_ALL); + + DATA *d = data[id]; + if (d) { + data[id] = NULL; + c_free(d); + } + + if (rotary_close( id )) { + return luaL_error( L, "Unable to close switch." ); + } + return 0; +} + +// Lua: on( id, mask[, cb] ) +static int lrotary_on( lua_State* L ) +{ + unsigned int id; + id = luaL_checkinteger( L, 1 ); + MOD_CHECK_ID( rotary, id ); + + int mask = luaL_checkinteger(L, 2); + + if (lua_gettop(L) >= 3) { + if (callback_set(L, id, mask, 3)) { + return luaL_error( L, "Unable to set callback." ); + } + } else { + callback_free(L, id, mask); + } + + return 0; +} + +// Lua: getpos( id ) -> pos, PRESS/RELEASE +static int lrotary_getpos( lua_State* L ) +{ + unsigned int id; + id = luaL_checkinteger( L, 1 ); + MOD_CHECK_ID( rotary, id ); + + int pos = rotary_getpos(id); + + if (pos == -1) { + return 0; + } + + lua_pushnumber(L, (pos << 1) >> 1); + lua_pushnumber(L, (pos & 0x80000000) ? MASK(PRESS) : MASK(RELEASE)); + + return 2; +} + +// Returns TRUE if there maybe/is more stuff to do +static bool lrotary_dequeue_single(lua_State* L, DATA *d) +{ + bool something_pending = FALSE; + + if (d) { + // This chnnel is open + rotary_event_t result; + + if (rotary_getevent(d->id, &result)) { + int pos = result.pos; + + lrotary_check_timer(d, result.time_us, 0); + + if (pos != d->lastpos) { + // We have something to enqueue + if ((pos ^ d->lastpos) & 0x7fffffff) { + // Some turning has happened + callback_call(L, d, ROTARY_TURN_INDEX, (pos << 1) >> 1, result.time_us); + } + if ((pos ^ d->lastpos) & 0x80000000) { + // pressing or releasing has happened + callback_call(L, d, (pos & 0x80000000) ? ROTARY_PRESS_INDEX : ROTARY_RELEASE_INDEX, (pos << 1) >> 1, result.time_us); + if (pos & 0x80000000) { + // Press + if (d->last_recent_event_was_release && result.time_us - d->last_event_time < d->click_delay_us) { + d->possible_dbl_click = 1; + } + d->last_recent_event_was_press = 1; + d->last_recent_event_was_release = 0; + } else { + // Release + d->last_recent_event_was_press = 0; + if (d->possible_dbl_click) { + callback_call(L, d, ROTARY_DBLCLICK_INDEX, (pos << 1) >> 1, result.time_us); + d->possible_dbl_click = 0; + // Do this to suppress the CLICK event + d->last_recent_event_was_release = 0; + } else { + d->last_recent_event_was_release = 1; + } + } + d->last_event_time = result.time_us; + } + + d->lastpos = pos; + } + + something_pending = rotary_has_queued_event(d->id); + } + + lrotary_check_timer(d, system_get_time(), 1); + } + + return something_pending; +} + +static void lrotary_timer_done(void *param) +{ + DATA *d = (DATA *) param; + + d->timer_running = 0; + + lrotary_check_timer(d, system_get_time(), 1); +} + +static void lrotary_check_timer(DATA *d, uint32_t time_us, bool dotimer) +{ + uint32_t delay = time_us - d->last_event_time; + if (d->timer_running) { + os_timer_disarm(&d->timer); + d->timer_running = 0; + } + + int timeout = -1; + + if (d->last_recent_event_was_press) { + if (delay > d->longpress_delay_us) { + callback_call(lua_getstate(), d, ROTARY_LONGPRESS_INDEX, (d->lastpos << 1) >> 1, d->last_event_time + d->longpress_delay_us); + d->last_recent_event_was_press = 0; + } else { + timeout = (d->longpress_delay_us - delay) / 1000; + } + } + if (d->last_recent_event_was_release) { + if (delay > d->click_delay_us) { + callback_call(lua_getstate(), d, ROTARY_CLICK_INDEX, (d->lastpos << 1) >> 1, d->last_event_time + d->click_delay_us); + d->last_recent_event_was_release = 0; + } else { + timeout = (d->click_delay_us - delay) / 1000; + } + } + + if (dotimer && timeout >= 0) { + d->timer_running = 1; + os_timer_arm(&d->timer, timeout + 1, 0); + } +} + +static void lrotary_task(os_param_t param, uint8_t prio) +{ + (void) param; + (void) prio; + + uint8_t *task_queue_ptr = (uint8_t*) param; + if (task_queue_ptr) { + // Signal that new events may need another task post + *task_queue_ptr = 0; + } + + int id; + bool need_to_post = FALSE; + lua_State *L = lua_getstate(); + + for (id = 0; id < ROTARY_CHANNEL_COUNT; id++) { + DATA *d = data[id]; + if (d) { + if (lrotary_dequeue_single(L, d)) { + need_to_post = TRUE; + } + } + } + + if (need_to_post) { + // If there is pending stuff, queue another task + task_post_medium(tasknumber, 0); + } +} + +static int rotary_open(lua_State *L) +{ + tasknumber = task_get_id(lrotary_task); + return 0; +} + +// Module function map +static const LUA_REG_TYPE rotary_map[] = { + { LSTRKEY( "setup" ), LFUNCVAL( lrotary_setup ) }, + { LSTRKEY( "close" ), LFUNCVAL( lrotary_close ) }, + { LSTRKEY( "on" ), LFUNCVAL( lrotary_on ) }, + { LSTRKEY( "getpos" ), LFUNCVAL( lrotary_getpos) }, + { LSTRKEY( "TURN" ), LNUMVAL( MASK(TURN) ) }, + { LSTRKEY( "PRESS" ), LNUMVAL( MASK(PRESS) ) }, + { LSTRKEY( "RELEASE" ), LNUMVAL( MASK(RELEASE) ) }, + { LSTRKEY( "LONGPRESS" ),LNUMVAL( MASK(LONGPRESS) ) }, + { LSTRKEY( "CLICK" ), LNUMVAL( MASK(CLICK) ) }, + { LSTRKEY( "DBLCLICK" ), LNUMVAL( MASK(DBLCLICK)) }, + { LSTRKEY( "ALL" ), LNUMVAL( ROTARY_ALL ) }, + + { LNILKEY, LNILVAL } +}; + +NODEMCU_MODULE(ROTARY, "rotary", rotary_map, rotary_open); diff --git a/app/platform/platform.c b/app/platform/platform.c index 08970e6f..992f53b8 100755 --- a/app/platform/platform.c +++ b/app/platform/platform.c @@ -14,6 +14,24 @@ #include "driver/uart.h" #include "driver/sigma_delta.h" +#ifdef GPIO_INTERRUPT_ENABLE +static task_handle_t gpio_task_handle; + +#ifdef GPIO_INTERRUPT_HOOK_ENABLE +struct gpio_hook_entry { + platform_hook_function func; + uint32_t bits; +}; +struct gpio_hook { + struct gpio_hook_entry *entry; + uint32_t all_bits; + uint32_t count; +}; + +static struct gpio_hook platform_gpio_hook; +#endif +#endif + static void pwms_init(); int platform_init() @@ -152,16 +170,23 @@ int platform_gpio_read( unsigned pin ) } #ifdef GPIO_INTERRUPT_ENABLE -static task_handle_t gpio_task_handle; - static void ICACHE_RAM_ATTR platform_gpio_intr_dispatcher (void *dummy){ uint32 j=0; uint32 gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS); UNUSED(dummy); + +#ifdef GPIO_INTERRUPT_HOOK_ENABLE + if (gpio_status & platform_gpio_hook.all_bits) { + for (j = 0; j < platform_gpio_hook.count; j++) { + if (gpio_status & platform_gpio_hook.entry[j].bits) + gpio_status = (platform_gpio_hook.entry[j].func)(gpio_status); + } + } +#endif /* * gpio_status is a bit map where bit 0 is set if unmapped gpio pin 0 (pin3) has * triggered the ISR. bit 1 if unmapped gpio pin 1 (pin10=U0TXD), etc. Since this - * in the ISR, it makes sense to optimize this by doing a fast scan of the status + * is the ISR, it makes sense to optimize this by doing a fast scan of the status * and reverse mapping any set bits. */ for (j = 0; gpio_status>0; j++, gpio_status >>= 1) { @@ -188,6 +213,90 @@ void platform_gpio_init( task_handle_t gpio_task ) get_pin_map(); ETS_GPIO_INTR_ATTACH(platform_gpio_intr_dispatcher, NULL); } + +#ifdef GPIO_INTERRUPT_HOOK_ENABLE +/* + * Register an ISR hook to be called from the GPIO ISR for a given GPIO bitmask. + * This routine is only called a few times so has been optimised for size and + * the unregister is a special case when the bits are 0. + * + * Each hook function can only be registered once. If it is re-registered + * then the hooked bits are just updated to the new value. + */ +int platform_gpio_register_intr_hook(uint32_t bits, platform_hook_function hook) +{ + struct gpio_hook nh, oh = platform_gpio_hook; + int i, j; + + if (!hook) { + // Cannot register or unregister null hook + return 0; + } + + int delete_slot = -1; + + // If hook already registered, just update the bits + for (i=0; i 0) + nh.entry = c_malloc( nh.count * sizeof(*(nh.entry)) ); + if (nh.count && !(nh.entry)) { + return 0; // Allocation failure + } + + for (i=0, j=0; i