Commit c505cc02 authored by Johny Mattsson's avatar Johny Mattsson
Browse files

Preliminary gpio module.

The gpio ISR needs to go into the platform layer most likely, but that's
for later.
parent c4911ca3
......@@ -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"
......
// 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 <assert.h>
#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);
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment