diff --git a/app/modules/gpio.c b/app/modules/gpio.c index b1e04b1b..75634429 100644 --- a/app/modules/gpio.c +++ b/app/modules/gpio.c @@ -1,4 +1,5 @@ // Module for interfacing with GPIO +//#define NODE_DEBUG #include "module.h" #include "lauxlib.h" @@ -8,6 +9,7 @@ #include "c_types.h" #include "c_string.h" #include "gpio.h" +#include "hw_timer.h" #define PULLUP PLATFORM_GPIO_PULLUP #define FLOAT PLATFORM_GPIO_FLOAT @@ -160,8 +162,7 @@ static int lgpio_write( lua_State* L ) #define DELAY_TABLE_MAX_LEN 256 #define delayMicroseconds os_delay_us -// Lua: serout( pin, firstLevel, delay_table, [repeatNum] ) -// -- serout( pin, firstLevel, delay_table, [repeatNum] ) +// Lua: serout( pin, firstLevel, delay_table[, repeat_num[, callback]]) // 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 @@ -170,42 +171,98 @@ static int lgpio_write( lua_State* L ) // 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 + +typedef struct +{ + unsigned pin; + unsigned level; + uint32 index; + uint32 repeats; + uint32 *delay_table; + uint32 tablelen; + task_handle_t done_taskid; + int lua_done_ref; // callback when transmission is done +} serout_t; +static serout_t serout; +static const os_param_t TIMER_OWNER = 0x6770696f; // "gpio" + +static void seroutasync_done (task_param_t arg) +{ + lua_State *L = lua_getstate(); + luaM_freearray(L, serout.delay_table, serout.tablelen, uint32); + if (serout.lua_done_ref != LUA_REFNIL) { // we're here so serout.lua_done_ref != LUA_NOREF + lua_rawgeti (L, LUA_REGISTRYINDEX, serout.lua_done_ref); + if (lua_pcall(L, 0, 0, 0)) { + // Uncaught Error. Print instead of sudden reset + luaL_error(L, "error: %s", lua_tostring(L, -1)); + } + luaL_unref (L, LUA_REGISTRYINDEX, serout.lua_done_ref); + } +} + +static void ICACHE_RAM_ATTR seroutasync_cb(os_param_t p) { + (void) p; + NODE_DBG("%d\t%d\t%d\t%d\t%d\t%d\t%d\n", serout.repeats, serout.index, serout.level, serout.pin, serout.tablelen, serout.delay_table[serout.index], system_get_time()); // timing is delayed for short timings when debug output is enabled + if (serout.index < serout.tablelen) { + GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[serout.pin]), serout.level); + serout.level = serout.level==LOW ? HIGH : LOW; + platform_hw_timer_arm_us(TIMER_OWNER, serout.delay_table[serout.index]); + serout.index++; + if (serout.repeats && serout.index>=serout.tablelen) {serout.index=0; serout.repeats--;} + } else { + platform_hw_timer_close(TIMER_OWNER); + task_post_low (serout.done_taskid, (task_param_t)0); + } +} + 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; + serout.pin = luaL_checkinteger( L, 1 ); + serout.level = luaL_checkinteger( L, 2 ); + serout.repeats = luaL_optint( L, 4, 1 )-1; + if (!lua_isnoneornil(L, 5)) { + if (lua_isnumber(L, 5)) { + serout.lua_done_ref = LUA_REFNIL; + } else { + lua_pushvalue(L, 5); + serout.lua_done_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } + } else { + serout.lua_done_ref = LUA_NOREF; + } - luaL_argcheck(L, platform_gpio_exists(pin), 1, "Invalid pin"); - luaL_argcheck(L, level==HIGH || level==LOW, 2, "Wrong arg type" ); + luaL_argcheck(L, platform_gpio_exists(serout.pin), 1, "Invalid pin"); + luaL_argcheck(L, serout.level==HIGH || serout.level==LOW, 2, "Wrong level type" ); luaL_argcheck(L, lua_istable( L, 3 ) && - ((table_len = lua_objlen( L, 3 )= 256" ); + ((serout.tablelen = lua_objlen( L, 3 )) < DELAY_TABLE_MAX_LEN), 3, "Invalid delay_times" ); - uint32 *delay_table = luaM_newvector(L, table_len*repeats, uint32); - for( i = 1; i <= table_len; i++ ) { + serout.delay_table = luaM_newvector(L, serout.tablelen, uint32); + for(unsigned i = 0; i < serout.tablelen; 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; + serout.delay_table[i] = 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; + if (serout.lua_done_ref != LUA_NOREF) { // async version for duration above 15 mSec + if (!platform_hw_timer_init(TIMER_OWNER, FRC1_SOURCE, TRUE)) { + // Failed to init the timer + luaL_error(L, "Unable to initialize timer"); } + platform_hw_timer_set_func(TIMER_OWNER, seroutasync_cb, 0); + serout.index = 0; + seroutasync_cb(0); + } else { // sync version for sub-50 µs resolution & total duration < 15 mSec + do { + for( serout.index = 0;serout.index < serout.tablelen; serout.index++ ){ + NODE_DBG("%d\t%d\t%d\t%d\t%d\t%d\t%d\n", serout.repeats, serout.index, serout.level, serout.pin, serout.tablelen, serout.delay_table[serout.index], system_get_time()); // timings is delayed for short timings when debug output is enabled + GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[serout.pin]), serout.level); + serout.level = serout.level==LOW ? HIGH : LOW; + delayMicroseconds(serout.delay_table[serout.index]); + } + } while (serout.repeats--); + luaM_freearray(L, serout.delay_table, serout.tablelen, uint32); } - - luaM_freearray(L, delay_table, table_len, uint32); return 0; } #undef DELAY_TABLE_MAX_LEN @@ -238,6 +295,7 @@ int luaopen_gpio( lua_State *L ) { } platform_gpio_init(task_get_id(gpio_intr_callback_task)); #endif + serout.done_taskid = task_get_id((task_callback_t) seroutasync_done); return 0; } diff --git a/docs/en/modules/gpio.md b/docs/en/modules/gpio.md index 2e1696a7..2d7cbc1b 100644 --- a/docs/en/modules/gpio.md +++ b/docs/en/modules/gpio.md @@ -69,18 +69,29 @@ gpio.read(0) ## gpio.serout() -Serialize output based on a sequence of delay-times. After each delay, the pin is toggled. +Serialize output based on a sequence of delay-times in µs. After each delay, the pin is toggled. After the last repeat and last delay the pin is not toggled. + +The function works in two modes: +* synchronous - for sub-50 µs resolution, restricted to max. overall duration, +* asynchrounous - synchronous operation with less granularity but virtually unrestricted duration. + +Whether the asynchronous mode is chosen is defined by presence of the `callback` parameter. If present and is of function type the function goes asynchronous the callback function is invoked when sequence finishes. If the parameter is numeric the function still goes asynchronous but no callback is invoked when done. + +For asynchronous version minimum delay time should not be shorter than 50 μs and maximum delay time is 0x7fffff μs (~8.3 seconds). +In this mode the function does not block the stack and returns immediately before the output sequence is finalized. HW timer inf `FRC1_SOURCE` mode is used to change the states. + +Note that the synchronous variant (no or nil `callback` parameter) function blocks the stach and as such any use of it must adhere to the SDK guidelines (also explained [here](https://nodemcu.readthedocs.io/en/dev/en/extn-developer-faq/#extension-developer-faq)). Failure to do so may lead to WiFi issues or outright to crashes/reboots. Shortly it means that sum of all delay times multiplied by the number of repeats should not exceed 15 ms. #### Syntax -`gpio.serout(pin, start_level, delay_times [, repeat_num])` +`gpio.serout(pin, start_level, delay_times [, repeat_num[, callback]])` #### Parameters - `pin` pin to use, IO index - `start_level` level to start on, either `gpio.HIGH` or `gpio.LOW` -- `delay_times` an array of delay times between each toggle of the gpio pin. +- `delay_times` an array of delay times in µs between each toggle of the gpio pin. - `repeat_num` an optional number of times to run through the sequence. +- `callback` an optional callback function or number, if present the function ruturns immediately and goes asynchronous. -Note that this function blocks, and as such any use of it must adhere to the SDK guidelines of time spent blocking the stack (10-100ms). Failure to do so may lead to WiFi issues or outright crashes/reboots. #### Returns `nil` @@ -94,6 +105,9 @@ 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.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 + +gpio.serout(1,1,{5000,995000},100, function() print("done") end) -- asynchronous 100 flashes 5 ms long every second with a callback function when done +gpio.serout(1,1,{5000,995000},100, 1) -- asynchronous 100 flashes 5 ms long, no callback ``` ## gpio.trig()