diff --git a/app/modules/gpio.c b/app/modules/gpio.c index 3ea4f5bc..80d7a8ae 100644 --- a/app/modules/gpio.c +++ b/app/modules/gpio.c @@ -53,17 +53,31 @@ static void gpio_intr_callback_task (task_param_t param, uint8 priority) // GPIO callbacks are run in L0 and include the level as a parameter lua_State *L = lua_getstate(); NODE_DBG("Calling: %08x\n", gpio_cb_ref[pin]); - // - if (!INTERRUPT_TYPE_IS_LEVEL(pin_int_type[pin])) { - // Edge triggered -- re-enable the interrupt - platform_gpio_intr_init(pin, pin_int_type[pin]); - } - // Do the actual callback - lua_rawgeti(L, LUA_REGISTRYINDEX, gpio_cb_ref[pin]); - lua_pushinteger(L, level); - lua_pushinteger(L, then); - lua_call(L, 2, 0); + bool needs_callback = 1; + + while (needs_callback) { + // Note that the interrupt level only modifies 'seen' and + // the base level only modifies 'reported'. + + // Do the actual callback + lua_rawgeti(L, LUA_REGISTRYINDEX, gpio_cb_ref[pin]); + lua_pushinteger(L, level); + lua_pushinteger(L, then); + uint16_t seen = pin_counter[pin].seen; + lua_pushinteger(L, 0x7fff & (seen - pin_counter[pin].reported)); + pin_counter[pin].reported = seen & 0x7fff; // This will cause the next interrupt to trigger a callback + uint16_t diff = (seen ^ pin_counter[pin].seen); + // Needs another callback if seen changed but not if the top bit is set + needs_callback = diff <= 0x7fff && diff > 0; + if (needs_callback) { + // Fake this for next time (this only happens if another interrupt happens since + // we loaded the 'seen' variable. + then = system_get_time() & 0x7fffffff; + } + + lua_call(L, 3, 0); + } if (INTERRUPT_TYPE_IS_LEVEL(pin_int_type[pin])) { // Level triggered -- re-enable the callback @@ -108,6 +122,14 @@ static int lgpio_trig( lua_State* L ) // unreference any overwritten callback if(old_pin_ref != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, old_pin_ref); + uint16_t seen; + + // Make sure that we clear out any queued interrupts + do { + seen = pin_counter[pin].seen; + pin_counter[pin].reported = seen & 0x7fff; + } while (seen != pin_counter[pin].seen); + NODE_DBG("Pin data: %d %d %08x, %d %d %d, %08x\n", pin, type, pin_mux[pin], pin_num[pin], pin_func[pin], pin_int_type[pin], gpio_cb_ref[pin]); platform_gpio_intr_init(pin, type); diff --git a/app/platform/pin_map.c b/app/platform/pin_map.c index 6ad4fe87..79d50ff7 100644 --- a/app/platform/pin_map.c +++ b/app/platform/pin_map.c @@ -9,6 +9,7 @@ uint8_t pin_func[GPIO_PIN_NUM]; #ifdef GPIO_INTERRUPT_ENABLE uint8_t pin_num_inv[GPIO_PIN_NUM_INV]; uint8_t pin_int_type[GPIO_PIN_NUM]; +GPIO_INT_COUNTER pin_counter[GPIO_PIN_NUM]; #endif typedef struct { diff --git a/app/platform/pin_map.h b/app/platform/pin_map.h index 84d5eb41..a4d3fe93 100644 --- a/app/platform/pin_map.h +++ b/app/platform/pin_map.h @@ -15,6 +15,13 @@ extern uint8_t pin_func[GPIO_PIN_NUM]; #ifdef GPIO_INTERRUPT_ENABLE extern uint8_t pin_num_inv[GPIO_PIN_NUM_INV]; extern uint8_t pin_int_type[GPIO_PIN_NUM]; +typedef struct { + // These values have 15 bits of count, and the top bit + // in 'seen' is set if we are missing a task post + volatile uint16_t seen; + volatile uint16_t reported; +} GPIO_INT_COUNTER; +extern GPIO_INT_COUNTER pin_counter[GPIO_PIN_NUM]; #endif void get_pin_map(void); diff --git a/app/platform/platform.c b/app/platform/platform.c index 8327e8b7..af1333f3 100755 --- a/app/platform/platform.c +++ b/app/platform/platform.c @@ -14,6 +14,8 @@ #include "driver/uart.h" #include "driver/sigma_delta.h" +#define INTERRUPT_TYPE_IS_LEVEL(x) ((x) >= GPIO_PIN_INTR_LOLEVEL) + #ifdef GPIO_INTERRUPT_ENABLE static task_handle_t gpio_task_handle; @@ -226,13 +228,25 @@ static void ICACHE_RAM_ATTR platform_gpio_intr_dispatcher (void *dummy){ if (gpio_status&1) { int i = pin_num_inv[j]; if (pin_int_type[i]) { - //disable interrupt - gpio_pin_intr_state_set(GPIO_ID_PIN(j), GPIO_PIN_INTR_DISABLE); + uint16_t diff = pin_counter[i].seen ^ pin_counter[i].reported; + + pin_counter[i].seen = 0x7fff & (pin_counter[i].seen + 1); + + if (INTERRUPT_TYPE_IS_LEVEL(pin_int_type[i])) { + //disable interrupt + gpio_pin_intr_state_set(GPIO_ID_PIN(j), GPIO_PIN_INTR_DISABLE); + } //clear interrupt status GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, BIT(j)); - uint32 level = 0x1 & GPIO_INPUT_GET(GPIO_ID_PIN(j)); - task_post_high (gpio_task_handle, (now << 8) + (i<<1) + level); - // We re-enable the interrupt when we execute the callback + + if (diff == 0 || diff & 0x8000) { + uint32 level = 0x1 & GPIO_INPUT_GET(GPIO_ID_PIN(j)); + if (!task_post_high (gpio_task_handle, (now << 8) + (i<<1) + level)) { + // If we fail to post, then try on the next interrupt + pin_counter[i].seen |= 0x8000; + } + // We re-enable the interrupt when we execute the callback (if level) + } } } } diff --git a/docs/en/modules/gpio.md b/docs/en/modules/gpio.md index ff256040..caf7b0ad 100644 --- a/docs/en/modules/gpio.md +++ b/docs/en/modules/gpio.md @@ -125,10 +125,15 @@ This function is not available if GPIO_INTERRUPT_ENABLE was undefined at compile - `type` "up", "down", "both", "low", "high", which represent *rising edge*, *falling edge*, *both edges*, *low level*, and *high level* trigger modes respectivey. If the type is "none" or omitted then the callback function is removed and the interrupt is disabled. -- `callback_function(level, when)` callback function when trigger occurs. The level of the specified pin +- `callback_function(level, when, eventcount)` callback function when trigger occurs. The level of the specified pin at the interrupt passed as the first parameter to the callback. The timestamp of the event is passed as the second parameter. This is in microseconds and has the same base as for `tmr.now()`. This timestamp is grabbed at interrupt level and is more consistent than getting the time in the callback function. +This timestamp is normally of the first interrupt detected, but, under overload conditions, might be a later one. +The eventcount is the number of interrupts that were elided for this callback. This works best for edge triggered +interrupts and enables counting of edges. However, beware +of switch bounces -- you can get multiple pulses for a single switch closure. Counting +works best when the edges are digitally generated. The previous callback function will be used if the function is omitted. #### Returns