From c505cc0270192d0dcbc8919ab79831af3129e441 Mon Sep 17 00:00:00 2001 From: Johny Mattsson Date: Sat, 26 Nov 2016 21:54:05 +1100 Subject: [PATCH] Preliminary gpio module. The gpio ISR needs to go into the platform layer most likely, but that's for later. --- components/modules/Kconfig | 6 + components/modules/gpio.c | 281 +++++++++++++++++++++++++++++++++++++ 2 files changed, 287 insertions(+) create mode 100644 components/modules/gpio.c diff --git a/components/modules/Kconfig b/components/modules/Kconfig index 2bf6a166..4ec367be 100644 --- a/components/modules/Kconfig +++ b/components/modules/Kconfig @@ -73,6 +73,12 @@ config LUA_MODULE_FILE help Includes the file module (recommended). +config LUA_MODULE_GPIO + bool "GPIO module" + default "y" + help + Includes the GPIO module (recommended). + config LUA_MODULE_NET bool "Net module" default "y" diff --git a/components/modules/gpio.c b/components/modules/gpio.c new file mode 100644 index 00000000..55c32bf6 --- /dev/null +++ b/components/modules/gpio.c @@ -0,0 +1,281 @@ +// vim: ts=2 sw=2 et ai +/* + * Copyright (c) 2016 Johny Mattsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * - Neither the name of the copyright holders nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "module.h" +#include "lauxlib.h" +#include "lmem.h" + +#include "driver/gpio.h" +#include "task/task.h" + +#include + +#define PULL_UP 1 +#define PULL_DOWN 2 + +// TODO: change this to dynamically allocated once that framework is available! +#define MAP_GPIO_PRO_INT_NO 12 + +static int *gpio_cb_refs = NULL; // Lazy init +static task_handle_t cb_task; + +static int check_err (lua_State *L, esp_err_t err) +{ + switch (err) + { + case ESP_ERR_INVALID_ARG: luaL_error (L, "invalid argument"); + case ESP_OK: break; + } + return 0; +} + + +/* Lua: gpio.config({ + * gpio= x || { x, y ... } + * dir= IN || OUT || IN_OUT + * opendrain= 0 || 1 (output only) + * pull= UP || DOWN || UP_DOWN || FLOATING + * }, ... + */ +static int lgpio_config (lua_State *L) +{ + const int n = lua_gettop (L); + luaL_checkstack (L, 5, "out of mem"); + for (int i = 1; i <= n; ++i) + { + luaL_checkanytable (L, i); + gpio_config_t cfg; + cfg.intr_type = GPIO_INTR_DISABLE; + + lua_getfield (L, i, "dir"); + cfg.mode = luaL_checkinteger (L, -1); + lua_getfield(L, i, "opendrain"); + if (luaL_optint (L, -1, 0) && (cfg.mode & GPIO_MODE_DEF_OUTPUT)) + cfg.mode |= GPIO_MODE_DEF_OD; + + lua_getfield(L, i, "pull"); + int pulls = luaL_optint (L, -1, 0); + cfg.pull_down_en = + (pulls & PULL_DOWN) ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE; + cfg.pull_up_en = + (pulls & PULL_UP) ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE; + + lua_pop (L, 3); + + cfg.pin_bit_mask = 0; + lua_getfield(L, i, "gpio"); + int type = lua_type (L, -1); + if (type == LUA_TNUMBER) + cfg.pin_bit_mask = 1 << lua_tointeger (L, -1); + else if (type == LUA_TTABLE) + { + lua_pushnil (L); + while (lua_next (L, -2) != 0) + { + lua_pushvalue (L, -1); // copy, so lua_tonumber() doesn't break iter + int pin = lua_tointeger (L, -1); + lua_pop (L, 2); // leave key + cfg.pin_bit_mask |= 1 << pin; + } + } + else + return luaL_error (L, "missing/bad 'gpio' field"); + lua_settop (L, n); // discard remaining temporaries + check_err (L, gpio_config (&cfg)); + } + return 0; +} + + +// Lua: gpio.read(gpio) => 0 || 1 +static int lgpio_read (lua_State *L) +{ + int gpio = luaL_checkint (L, 1); + if (!GPIO_IS_VALID_GPIO(gpio)) + return check_err (L, ESP_ERR_INVALID_ARG); + lua_pushinteger (L, gpio_get_level (gpio)); + return 1; +} + + +// Lua: gpio.trig(gpio, UP/DOWN/etc, function(gpio, level) ... end || nil) +// up, down, both, low, high +static int lgpio_trig (lua_State *L) +{ + int gpio = luaL_checkint (L, 1); + int intr_type = luaL_checkint (L, 2); + if (!lua_isnoneornil (L, 3)) + luaL_checkanyfunction (L, 3); + + lua_settop (L, 3); + + if (gpio < 0 || gpio >= GPIO_PIN_COUNT) + return luaL_error (L, "invalid gpio"); + + if (!gpio_cb_refs) + { + gpio_cb_refs = luaM_newvector (L, GPIO_PIN_COUNT, int); + for (unsigned i = 0; i < GPIO_PIN_COUNT; ++i) + gpio_cb_refs[i] = LUA_NOREF; + } + + // Set/update interrupt callback + if (!lua_isnoneornil (L, 3)) + { + luaL_unref (L, LUA_REGISTRYINDEX, gpio_cb_refs[gpio]); + gpio_cb_refs[gpio] = luaL_ref (L, LUA_REGISTRYINDEX); + } + + // Disable interrupt while reconfiguring + check_err (L, gpio_intr_disable (gpio)); + + if (gpio_cb_refs[gpio] == LUA_NOREF) + check_err (L, gpio_set_intr_type (gpio, GPIO_INTR_DISABLE)); + else + { + check_err (L, gpio_set_intr_type (gpio, intr_type)); + check_err (L, gpio_intr_enable (gpio)); + } + return 0; +} + + +// Lua: gpio.wakeup(gpio, INTR_NONE | INTR_LOW | INTR_HIGH) +static int lgpio_wakeup (lua_State *L) +{ + int gpio = luaL_checkint (L, 1); + int intr_type = luaL_optint (L, 2, GPIO_INTR_DISABLE); + if (intr_type == GPIO_INTR_DISABLE) + check_err (L, gpio_wakeup_disable (gpio)); + else + check_err (L, gpio_wakeup_enable (gpio, intr_type)); + return 0; +} + + +// Lua: gpio.write(gpio, 0 || 1) +static int lgpio_write (lua_State *L) +{ + int gpio = luaL_checkint (L, 1); + int level = luaL_checkint (L, 2); + check_err (L, gpio_set_level (gpio, level)); + return 0; +} + + +// TODO: move this to the platform layer so it can be shared +// TODO: can/should we attempt to guard against task q overflow? +_Static_assert(GPIO_PIN_COUNT<256, "task post encoding assumes < 256 gpios"); +static void IRAM_ATTR nodemcu_gpio_isr (void *p) +{ + uint32_t intrs = READ_PERI_REG(GPIO_STATUS_REG); + uint32_t intrs_rtc = READ_PERI_REG(GPIO_STATUS1_REG); + + #define handle_gpio_intr(gpio_num) do { \ + gpio_intr_disable (gpio_num); \ + task_post_low (cb_task, (gpio_num) | (gpio_get_level (gpio_num) << 8)); \ + } while (0) + + // Regular gpios + for (uint32_t gpio = 0; intrs && gpio < 32; ++gpio) + { + if (intrs & BIT(gpio)) + handle_gpio_intr (gpio); + } + // RTC gpios + for (uint32_t gpio = 0; intrs_rtc && gpio < (GPIO_PIN_COUNT - 32); ++gpio) + { + if (intrs_rtc & BIT(gpio)) + handle_gpio_intr (gpio + 32); + } + + SET_PERI_REG_MASK(GPIO_STATUS_W1TC_REG, intrs); + SET_PERI_REG_MASK(GPIO_STATUS1_W1TC_REG, intrs_rtc); +} + + +static void nodemcu_gpio_callback_task (task_param_t param, task_prio_t prio) +{ + (void)prio; + uint32_t gpio = (uint32_t)param & 0xffu; + int level = ((int)param) & 0x100u; + + lua_State *L = lua_getstate (); + if (gpio_cb_refs[gpio] != LUA_NOREF) + { + lua_rawgeti (L, LUA_REGISTRYINDEX, gpio_cb_refs[gpio]); + lua_pushinteger (L, gpio); + lua_pushinteger (L, level); + lua_call (L, 2, 0); + gpio_intr_enable (gpio); + } +} + + +static int nodemcu_gpio_init (lua_State *L) +{ + cb_task = task_get_id (nodemcu_gpio_callback_task); + check_err (L, + gpio_isr_register (MAP_GPIO_PRO_INT_NO, nodemcu_gpio_isr, NULL)); + return 0; +} + + +static const LUA_REG_TYPE lgpio_map[] = +{ + { LSTRKEY( "config" ), LFUNCVAL( lgpio_config ) }, + { LSTRKEY( "read" ), LFUNCVAL( lgpio_read ) }, + { LSTRKEY( "trig" ), LFUNCVAL( lgpio_trig ) }, + { LSTRKEY( "wakeup" ), LFUNCVAL( lgpio_wakeup ) }, + { LSTRKEY( "write" ), LFUNCVAL( lgpio_write ) }, + + + { LSTRKEY( "OUT" ), LNUMVAL( GPIO_MODE_OUTPUT ) }, + { LSTRKEY( "IN" ), LNUMVAL( GPIO_MODE_INPUT ) }, + { LSTRKEY( "IN_OUT" ), LNUMVAL( GPIO_MODE_INPUT_OUTPUT ) }, + + { LSTRKEY( "FLOATING"), LNUMVAL( 0 ) }, + { LSTRKEY( "PULL_UP" ), LNUMVAL( PULL_UP ) }, + { LSTRKEY( "PULL_DOWN" ), LNUMVAL( PULL_DOWN ) }, + { LSTRKEY( "PULL_UP_DOWN"), LNUMVAL( PULL_UP | PULL_DOWN ) }, + + { LSTRKEY( "INTR_NONE" ), LNUMVAL( GPIO_INTR_DISABLE ) }, + { LSTRKEY( "INTR_UP" ), LNUMVAL( GPIO_INTR_POSEDGE ) }, + { LSTRKEY( "INTR_DOWN" ), LNUMVAL( GPIO_INTR_NEGEDGE ) }, + { LSTRKEY( "INTR_UP_DOWN" ), LNUMVAL( GPIO_INTR_ANYEDGE ) }, + { LSTRKEY( "INTR_LOW" ), LNUMVAL( GPIO_INTR_LOW_LEVEL ) }, + { LSTRKEY( "INTR_HIGH" ), LNUMVAL( GPIO_INTR_HIGH_LEVEL ) }, + + { LNILKEY, LNILVAL } +}; + +NODEMCU_MODULE(GPIO, "gpio", lgpio_map, nodemcu_gpio_init);