nodemcu-firmware/app/modules/gpio.c

247 lines
8.2 KiB
C
Raw Normal View History

// Module for interfacing with GPIO
#include "module.h"
#include "lauxlib.h"
#include "lmem.h"
#include "platform.h"
#include "user_interface.h"
#include "c_types.h"
#include <string.h>
2016-02-26 15:07:06 +01:00
#include "gpio.h"
Initial pass at switching to RTOS SDK. This compiles, links, and starts the RTOS without crashing and burning. Lua environment does not yet start due to the different task architecture. Known pain points: - task implementation needs to be rewritten for RTOS (next up on my TODO) - secure espconn does not exist, all secure espconn stuff has been #if 0'd - lwip now built from within the RTOS SDK, but does not appear to include MDNS support. Investigation needed. - there is no access to FRC1 NMI, not sure if we ever actually used that however. Also #if 0'd out for now. - new timing constraints introduced by the RTOS, all use of ets_delay_us() and os_delay_us() needs to be reviewed (the tsl2561 driver in particular). - even more confusion with ets_ vs os_ vs c_ vs non-prefixed versions. In the long run everything should be switched to non-prefixed versions. - system_set_os_print() not available, needs to be reimplemented - all the RTOS rodata is loaded into RAM, as it apparently uses some constants while the flash isn't mapped, so our exception handler can't work its magic. This should be narrowed down to the minimum possible at some point. - with each task having its own stack in RTOS, we probably need change flash-page buffers from the stack to the heap in a bunch of places. A single, shared, page buffer *might* be possible if we limit ourselves to running NodeMCU in a single task. - there's a ton of junk in the sdk-overrides now; over time the core code should be updated to not need those shims
2016-05-24 07:05:01 +02:00
#undef INTERRUPT
#define PULLUP PLATFORM_GPIO_PULLUP
#define FLOAT PLATFORM_GPIO_FLOAT
#define OUTPUT PLATFORM_GPIO_OUTPUT
#define OPENDRAIN PLATFORM_GPIO_OPENDRAIN
#define INPUT PLATFORM_GPIO_INPUT
#define INTERRUPT PLATFORM_GPIO_INT
#define HIGH PLATFORM_GPIO_HIGH
#define LOW PLATFORM_GPIO_LOW
#ifdef GPIO_INTERRUPT_ENABLE
2016-02-26 15:07:06 +01:00
// We also know that the non-level interrupt types are < LOLEVEL, and that
// HILEVEL is > LOLEVEL. Since this is burned into the hardware it is not
// going to change.
#define INTERRUPT_TYPE_IS_LEVEL(x) ((x) >= GPIO_PIN_INTR_LOLEVEL)
static int gpio_cb_ref[GPIO_PIN_NUM];
// This task is scheduled by the ISR and is used
// to initiate the Lua-land gpio.trig() callback function
// It also re-enables the pin interrupt, so that we get another callback queued
static void gpio_intr_callback_task (task_param_t param, task_prio_t priority)
{
unsigned pin = param >> 1;
unsigned level = param & 1;
UNUSED(priority);
NODE_DBG("pin:%d, level:%d \n", pin, level);
if(gpio_cb_ref[pin] != LUA_NOREF) {
// GPIO callbacks are run in L0 and inlcude the level as a parameter
lua_State *L = lua_getstate();
NODE_DBG("Calling: %08x\n", gpio_cb_ref[pin]);
//
2016-02-26 15:07:06 +01:00
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_call(L, 1, 0);
2016-02-26 15:07:06 +01:00
if (INTERRUPT_TYPE_IS_LEVEL(pin_int_type[pin])) {
// Level triggered -- re-enable the callback
platform_gpio_intr_init(pin, pin_int_type[pin]);
}
}
}
// Lua: trig( pin, type, function )
static int lgpio_trig( lua_State* L )
{
unsigned pin = luaL_checkinteger( L, 1 );
2016-03-08 18:38:21 +01:00
static const char * const opts[] = {"none", "up", "down", "both", "low", "high", NULL};
static const int opts_type[] = {
GPIO_PIN_INTR_DISABLE, GPIO_PIN_INTR_POSEDGE, GPIO_PIN_INTR_NEGEDGE,
GPIO_PIN_INTR_ANYEDGE, GPIO_PIN_INTR_LOLEVEL, GPIO_PIN_INTR_HILEVEL
};
luaL_argcheck(L, platform_gpio_exists(pin) && pin>0, 1, "Invalid interrupt pin");
int old_pin_ref = gpio_cb_ref[pin];
2016-03-08 18:38:21 +01:00
int type = opts_type[luaL_checkoption(L, 2, "none", opts)];
if (type == GPIO_PIN_INTR_DISABLE) {
// "none" clears the callback
gpio_cb_ref[pin] = LUA_NOREF;
} else if (lua_gettop(L)==2 && old_pin_ref != LUA_NOREF) {
// keep the old one if no callback
old_pin_ref = LUA_NOREF;
} else if (lua_type(L, 3) == LUA_TFUNCTION || lua_type(L, 3) == LUA_TLIGHTFUNCTION) {
// set up the new callback if present
lua_pushvalue(L, 3);
gpio_cb_ref[pin] = luaL_ref(L, LUA_REGISTRYINDEX);
2016-03-08 18:38:21 +01:00
} else {
// invalid combination, so clear down any old callback and throw an error
if(old_pin_ref != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, old_pin_ref);
luaL_argcheck(L, 0, 3, "invalid callback type");
}
2016-03-08 18:38:21 +01:00
// unreference any overwritten callback
2016-03-08 18:38:21 +01:00
if(old_pin_ref != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, old_pin_ref);
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);
return 0;
}
#endif
// Lua: mode( pin, mode, pullup )
static int lgpio_mode( lua_State* L )
{
unsigned pin = luaL_checkinteger( L, 1 );
unsigned mode = luaL_checkinteger( L, 2 );
unsigned pullup = luaL_optinteger( L, 3, FLOAT );
luaL_argcheck(L, platform_gpio_exists(pin) && (mode!=INTERRUPT || pin>0), 1, "Invalid pin");
luaL_argcheck(L, mode==OUTPUT || mode==OPENDRAIN || mode==INPUT
#ifdef GPIO_INTERRUPT_ENABLE
|| mode==INTERRUPT
#endif
, 2, "wrong arg type" );
if(pullup!=FLOAT) pullup = PULLUP;
NODE_DBG("pin,mode,pullup= %d %d %d\n",pin,mode,pullup);
NODE_DBG("Pin data at mode: %d %08x, %d %d %d, %08x\n",
pin, pin_mux[pin], pin_num[pin], pin_func[pin], pin_int_type[pin], gpio_cb_ref[pin]);
#ifdef GPIO_INTERRUPT_ENABLE
if (mode != INTERRUPT){ // disable interrupt
if(gpio_cb_ref[pin] != LUA_NOREF){
luaL_unref(L, LUA_REGISTRYINDEX, gpio_cb_ref[pin]);
gpio_cb_ref[pin] = LUA_NOREF;
}
}
#endif
if( platform_gpio_mode( pin, mode, pullup ) < 0 )
return luaL_error( L, "wrong pin num." );
return 0;
}
// Lua: read( pin )
static int lgpio_read( lua_State* L )
{
unsigned pin = luaL_checkinteger( L, 1 );
MOD_CHECK_ID( gpio, pin );
lua_pushinteger( L, platform_gpio_read( pin ) );
return 1;
}
// Lua: write( pin, level )
static int lgpio_write( lua_State* L )
{
unsigned pin = luaL_checkinteger( L, 1 );
unsigned level = luaL_checkinteger( L, 2 );
MOD_CHECK_ID( gpio, pin );
luaL_argcheck(L, level==HIGH || level==LOW, 2, "wrong level type" );
platform_gpio_write(pin, level);
return 0;
}
#define DELAY_TABLE_MAX_LEN 256
#define delayMicroseconds os_delay_us
// Lua: serout( pin, firstLevel, delay_table, [repeatNum] )
// -- serout( pin, firstLevel, delay_table, [repeatNum] )
// gpio.mode(1,gpio.OUTPUT,gpio.PULLUP)
// gpio.serout(1,1,{30,30,60,60,30,30}) -- serial one byte, b10110010
// gpio.serout(1,1,{30,70},8) -- serial 30% pwm 10k, lasts 8 cycles
// gpio.serout(1,1,{3,7},8) -- serial 30% pwm 100k, lasts 8 cycles
// gpio.serout(1,1,{0,0},8) -- serial 50% pwm as fast as possible, lasts 8 cycles
// gpio.mode(1,gpio.OUTPUT,gpio.PULLUP)
// gpio.serout(1,0,{20,10,10,20,10,10,10,100}) -- sim uart one byte 0x5A at about 100kbps
// gpio.serout(1,1,{8,18},8) -- serial 30% pwm 38k, lasts 8 cycles
static int lgpio_serout( lua_State* L )
{
unsigned clocks_per_us = system_get_cpu_freq();
unsigned pin = luaL_checkinteger( L, 1 );
unsigned level = luaL_checkinteger( L, 2 );
unsigned repeats = luaL_optint( L, 4, 1 );
unsigned table_len, i, j;
luaL_argcheck(L, platform_gpio_exists(pin), 1, "Invalid pin");
luaL_argcheck(L, level==HIGH || level==LOW, 2, "Wrong arg type" );
luaL_argcheck(L, lua_istable( L, 3 ) &&
((table_len = lua_objlen( L, 3 )<DELAY_TABLE_MAX_LEN)), 3, "Invalid table" );
luaL_argcheck(L, repeats<256, 4, "repeats >= 256" );
uint32 *delay_table = luaM_newvector(L, table_len*repeats, uint32);
for( i = 1; i <= table_len; i++ ) {
lua_rawgeti( L, 3, i + 1 );
unsigned delay = (unsigned) luaL_checkinteger( L, -1 );
if (delay > 1000000) return luaL_error( L, "delay %u must be < 1,000,000 us", i );
delay_table[i-1] = delay;
lua_pop( L, 1 );
}
for( i = 0; i <= repeats; i++ ) {
if (!i) // skip the first loop (presumably this is some form of icache priming??).
continue;
for( j = 0;j < table_len; j++ ){
/* Direct Write is a ROM function which already disables interrupts for the atomic bit */
GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[pin]), level);
delayMicroseconds(delay_table[j]);
level = level==LOW ? HIGH : LOW;
}
}
luaM_freearray(L, delay_table, table_len, uint32);
return 0;
}
#undef DELAY_TABLE_MAX_LEN
// Module function map
static const LUA_REG_TYPE gpio_map[] = {
{ LSTRKEY( "mode" ), LFUNCVAL( lgpio_mode ) },
{ LSTRKEY( "read" ), LFUNCVAL( lgpio_read ) },
{ LSTRKEY( "write" ), LFUNCVAL( lgpio_write ) },
{ LSTRKEY( "serout" ), LFUNCVAL( lgpio_serout ) },
#ifdef GPIO_INTERRUPT_ENABLE
{ LSTRKEY( "trig" ), LFUNCVAL( lgpio_trig ) },
{ LSTRKEY( "INT" ), LNUMVAL( INTERRUPT ) },
#endif
{ LSTRKEY( "OUTPUT" ), LNUMVAL( OUTPUT ) },
{ LSTRKEY( "OPENDRAIN" ), LNUMVAL( OPENDRAIN ) },
{ LSTRKEY( "INPUT" ), LNUMVAL( INPUT ) },
{ LSTRKEY( "HIGH" ), LNUMVAL( HIGH ) },
{ LSTRKEY( "LOW" ), LNUMVAL( LOW ) },
{ LSTRKEY( "FLOAT" ), LNUMVAL( FLOAT ) },
{ LSTRKEY( "PULLUP" ), LNUMVAL( PULLUP ) },
{ LNILKEY, LNILVAL }
};
int luaopen_gpio( lua_State *L ) {
#ifdef GPIO_INTERRUPT_ENABLE
int i;
for(i=0;i<GPIO_PIN_NUM;i++){
gpio_cb_ref[i] = LUA_NOREF;
}
platform_gpio_init(task_get_id(gpio_intr_callback_task));
#endif
return 0;
}
NODEMCU_MODULE(GPIO, "gpio", gpio_map, luaopen_gpio);