2016-11-26 11:54:05 +01:00
|
|
|
// 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
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2021-07-29 05:47:45 +02:00
|
|
|
case ESP_ERR_INVALID_ARG: return luaL_error (L, "invalid argument");
|
|
|
|
case ESP_ERR_INVALID_STATE: return luaL_error (L, "internal logic error");
|
2016-11-26 11:54:05 +01:00
|
|
|
case ESP_OK: break;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-12-30 09:20:01 +01:00
|
|
|
// TODO: can/should we attempt to guard against task q overflow?
|
|
|
|
_Static_assert(GPIO_PIN_COUNT<256, "task post encoding assumes < 256 gpios");
|
2017-03-17 17:38:17 +01:00
|
|
|
static void single_pin_isr (void *p)
|
2016-12-30 09:20:01 +01:00
|
|
|
{
|
|
|
|
gpio_num_t gpio_num = (gpio_num_t)p;
|
|
|
|
gpio_intr_disable (gpio_num);
|
2021-07-26 07:36:57 +02:00
|
|
|
task_post_isr_low (cb_task, (gpio_num) | (gpio_get_level (gpio_num) << 8));
|
2016-12-30 09:20:01 +01:00
|
|
|
}
|
|
|
|
|
2016-11-26 11:54:05 +01:00
|
|
|
|
|
|
|
/* 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)
|
|
|
|
{
|
2021-08-21 17:39:54 +02:00
|
|
|
luaL_checktable (L, i);
|
2016-11-26 11:54:05 +01:00
|
|
|
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)
|
2017-05-12 03:10:44 +02:00
|
|
|
cfg.pin_bit_mask = 1ULL << lua_tointeger (L, -1);
|
2016-11-26 11:54:05 +01:00
|
|
|
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
|
2017-05-12 03:10:44 +02:00
|
|
|
cfg.pin_bit_mask |= 1ULL << pin;
|
2016-11-26 11:54:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
2020-04-27 14:29:08 +02:00
|
|
|
int intr_type = luaL_optint (L, 2, GPIO_INTR_DISABLE);
|
2016-11-26 11:54:05 +01:00
|
|
|
if (!lua_isnoneornil (L, 3))
|
2021-08-21 17:39:54 +02:00
|
|
|
luaL_checkfunction (L, 3);
|
2016-11-26 11:54:05 +01:00
|
|
|
|
|
|
|
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
|
2020-04-27 14:29:08 +02:00
|
|
|
|
|
|
|
// For compatibility with esp8266 API, passing in a non-zero intr_type and a
|
|
|
|
// nil callback preserves any existing callback that has been set.
|
|
|
|
if (intr_type == GPIO_INTR_DISABLE)
|
|
|
|
{
|
|
|
|
luaL_unref (L, LUA_REGISTRYINDEX, gpio_cb_refs[gpio]);
|
|
|
|
gpio_cb_refs[gpio] = LUA_NOREF;
|
|
|
|
}
|
|
|
|
else if (!lua_isnoneornil (L, 3))
|
2016-11-26 11:54:05 +01:00
|
|
|
{
|
|
|
|
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));
|
|
|
|
|
2020-04-27 14:29:08 +02:00
|
|
|
if (intr_type == GPIO_INTR_DISABLE)
|
2016-12-30 09:20:01 +01:00
|
|
|
{
|
2016-11-26 11:54:05 +01:00
|
|
|
check_err (L, gpio_set_intr_type (gpio, GPIO_INTR_DISABLE));
|
2016-12-30 09:20:01 +01:00
|
|
|
check_err (L, gpio_isr_handler_remove (gpio));
|
|
|
|
}
|
2016-11-26 11:54:05 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
check_err (L, gpio_set_intr_type (gpio, intr_type));
|
2016-12-30 09:20:01 +01:00
|
|
|
check_err (L, gpio_isr_handler_add (gpio, single_pin_isr, (void *)gpio));
|
2016-11-26 11:54:05 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-05-18 19:56:43 +02:00
|
|
|
// Lua: gpio.set_drive(gpio, gpio.DRIVE_x)
|
|
|
|
static int lgpio_set_drive (lua_State *L)
|
|
|
|
{
|
|
|
|
int gpio = luaL_checkint (L, 1);
|
|
|
|
int strength = luaL_checkint (L, 2);
|
|
|
|
luaL_argcheck (L, strength >= GPIO_DRIVE_CAP_0 && strength < GPIO_DRIVE_CAP_MAX,
|
|
|
|
2, "pad strength must be between gpio.DRIVE_0 and gpio.DRIVE_3");
|
|
|
|
check_err (L, gpio_set_drive_capability(gpio, (gpio_drive_cap_t)strength));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-11-26 11:54:05 +01:00
|
|
|
|
|
|
|
static void nodemcu_gpio_callback_task (task_param_t param, task_prio_t prio)
|
|
|
|
{
|
|
|
|
(void)prio;
|
|
|
|
uint32_t gpio = (uint32_t)param & 0xffu;
|
2020-05-01 15:17:41 +02:00
|
|
|
int level = (((int)param) & 0x100u) >> 8;
|
2016-11-26 11:54:05 +01:00
|
|
|
|
|
|
|
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);
|
2021-08-23 16:11:31 +02:00
|
|
|
luaL_pcallx (L, 2, 0);
|
2016-11-26 11:54:05 +01:00
|
|
|
gpio_intr_enable (gpio);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int nodemcu_gpio_init (lua_State *L)
|
|
|
|
{
|
|
|
|
cb_task = task_get_id (nodemcu_gpio_callback_task);
|
|
|
|
check_err (L,
|
2017-03-17 17:38:17 +01:00
|
|
|
gpio_install_isr_service (ESP_INTR_FLAG_LOWMED));
|
2016-11-26 11:54:05 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-08-21 17:39:54 +02:00
|
|
|
LROT_BEGIN(lgpio, NULL, 0)
|
2019-07-22 11:13:43 +02:00
|
|
|
LROT_FUNCENTRY( config, lgpio_config )
|
|
|
|
LROT_FUNCENTRY( read, lgpio_read )
|
|
|
|
LROT_FUNCENTRY( trig, lgpio_trig )
|
|
|
|
LROT_FUNCENTRY( wakeup, lgpio_wakeup )
|
|
|
|
LROT_FUNCENTRY( write, lgpio_write )
|
2020-05-18 19:56:43 +02:00
|
|
|
LROT_FUNCENTRY( set_drive, lgpio_set_drive )
|
2019-07-22 11:13:43 +02:00
|
|
|
|
|
|
|
|
|
|
|
LROT_NUMENTRY ( OUT, GPIO_MODE_OUTPUT )
|
|
|
|
LROT_NUMENTRY ( IN, GPIO_MODE_INPUT )
|
|
|
|
LROT_NUMENTRY ( IN_OUT, GPIO_MODE_INPUT_OUTPUT )
|
|
|
|
|
|
|
|
LROT_NUMENTRY ( FLOATING, 0 )
|
|
|
|
LROT_NUMENTRY ( PULL_UP, PULL_UP )
|
|
|
|
LROT_NUMENTRY ( PULL_DOWN, PULL_DOWN )
|
|
|
|
LROT_NUMENTRY ( PULL_UP_DOWN, PULL_UP | PULL_DOWN )
|
|
|
|
|
|
|
|
LROT_NUMENTRY ( INTR_NONE, GPIO_INTR_DISABLE )
|
|
|
|
LROT_NUMENTRY ( INTR_UP, GPIO_INTR_POSEDGE )
|
|
|
|
LROT_NUMENTRY ( INTR_DOWN, GPIO_INTR_NEGEDGE )
|
|
|
|
LROT_NUMENTRY ( INTR_UP_DOWN, GPIO_INTR_ANYEDGE )
|
|
|
|
LROT_NUMENTRY ( INTR_LOW, GPIO_INTR_LOW_LEVEL )
|
|
|
|
LROT_NUMENTRY ( INTR_HIGH, GPIO_INTR_HIGH_LEVEL )
|
2020-05-18 19:56:43 +02:00
|
|
|
|
|
|
|
LROT_NUMENTRY ( DRIVE_0, GPIO_DRIVE_CAP_0 )
|
|
|
|
LROT_NUMENTRY ( DRIVE_1, GPIO_DRIVE_CAP_1 )
|
|
|
|
LROT_NUMENTRY ( DRIVE_2, GPIO_DRIVE_CAP_2 )
|
|
|
|
LROT_NUMENTRY ( DRIVE_DEFAULT,GPIO_DRIVE_CAP_DEFAULT )
|
|
|
|
LROT_NUMENTRY ( DRIVE_3, GPIO_DRIVE_CAP_3 )
|
2019-07-22 11:13:43 +02:00
|
|
|
LROT_END(lgpio, NULL, 0)
|
|
|
|
|
|
|
|
NODEMCU_MODULE(GPIO, "gpio", lgpio, nodemcu_gpio_init);
|