#include "module.h" #include "lauxlib.h" #include "lmem.h" #include "platform.h" #include "user_interface.h" #include "c_types.h" #include "c_string.h" #include "gpio.h" #include "hw_timer.h" #include "pin_map.h" #include "driver/gpio16.h" #define TIMER_OWNER 'P' typedef struct { uint32_t gpio_set; uint32_t gpio_clr; uint32_t delay; uint32_t delay_min; uint32_t delay_max; uint32_t count; uint32_t count_left; uint16_t loop; } pulse_entry_t; typedef struct { uint32_t entry_count; volatile uint32_t steps; volatile uint16_t entry_pos; volatile int16_t stop_pos; // -1 is stop nowhere, -2 is stop everywhere, otherwise is stop point pulse_entry_t *entry; volatile uint32_t expected_end_time; volatile int32_t next_adjust; volatile int cb_ref; } pulse_t; static int active_pulser_ref; static pulse_t *active_pulser; static task_handle_t tasknumber; static int gpio_pulse_push_state(lua_State *L, pulse_t *pulser) { uint32_t now; uint32_t expected_end_time; uint32_t entry_pos; uint32_t steps; do { now = 0x7FFFFFFF & system_get_time(); expected_end_time = pulser->expected_end_time; entry_pos = pulser->entry_pos; steps = pulser->steps; } while (expected_end_time != pulser->expected_end_time || entry_pos != pulser->entry_pos || steps != pulser->steps); if (entry_pos >= pulser->entry_count) { lua_pushnil(L); } else { lua_pushinteger(L, entry_pos + 1); // Lua is 1 offset } lua_pushinteger(L, steps); int32_t diff = (expected_end_time - now) & 0x7fffffff; lua_pushinteger(L, (diff << 1) >> 1); lua_pushinteger(L, now); return 4; } static int gpio_pulse_getstate(lua_State *L) { pulse_t *pulser = luaL_checkudata(L, 1, "gpio.pulse"); if (pulser != active_pulser) { return 0; } return gpio_pulse_push_state(L, pulser); } static int gpio_pulse_stop(lua_State *L) { pulse_t *pulser = luaL_checkudata(L, 1, "gpio.pulse"); if (pulser != active_pulser) { return 0; } int argno = 2; int32_t stop_pos = -2; if (lua_type(L, argno) == LUA_TNUMBER) { stop_pos = luaL_checkinteger(L, 2); if (stop_pos != -2) { if (stop_pos < 1 || stop_pos > pulser->entry_count) { return luaL_error( L, "bad stop position: %d (valid range 1 - %d)", stop_pos, pulser->entry_count ); } stop_pos = stop_pos - 1; } argno++; } if (lua_type(L, argno) == LUA_TFUNCTION || lua_type(L, argno) == LUA_TLIGHTFUNCTION) { lua_pushvalue(L, argno); } else { return luaL_error( L, "missing callback" ); } int new_cb_ref = luaL_ref(L, LUA_REGISTRYINDEX); int cb_ref = pulser->cb_ref; pulser->cb_ref = LUA_NOREF; pulser->stop_pos = -1; pulser->cb_ref = new_cb_ref; pulser->stop_pos = stop_pos; luaL_unref(L, LUA_REGISTRYINDEX, cb_ref); lua_pushboolean(L, 1); return 1; } static int gpio_pulse_delete(lua_State *L) { pulse_t *pulser = luaL_checkudata(L, 1, "gpio.pulse"); if (pulser == active_pulser) { return 0; } luaL_unref(L, LUA_REGISTRYINDEX, pulser->cb_ref); return 0; } static int gpio_pulse_build(lua_State *L) { // Take a table argument luaL_checktype(L, 1, LUA_TTABLE); // First figure out how big we need the block to be size_t size = luaL_getn(L, 1); if (size > 100) { return luaL_error(L, "table is too large: %d entries is more than 100", size); } size_t memsize = sizeof(pulse_t) + size * sizeof(pulse_entry_t); pulse_t *pulser = (pulse_t *) lua_newuserdata(L, memsize); memset(pulser, 0, memsize); // // Associate its metatable luaL_getmetatable(L, "gpio.pulse"); lua_setmetatable(L, -2); pulser->entry = (pulse_entry_t *) (pulser + 1); pulser->entry_count = size; size_t i; for (i = 0; i < size; i++) { pulse_entry_t *entry = pulser->entry + i; entry->delay_min = -1; entry->delay_max = -1; lua_rawgeti(L, 1, i + 1); if (lua_type(L, -1) != LUA_TTABLE) { return luaL_error(L, "All entries must be tables"); } lua_pushnil(L); // key position while (lua_next(L, -2)) { // stack now contains: -1 => value; -2 => key; -3 => table if (lua_type(L, -2) == LUA_TNUMBER) { int pin = lua_tonumber(L, -2); int value = lua_tonumber(L, -1); if (pin < 0 || pin >= GPIO_PIN_NUM) { luaL_error(L, "pin number %d must be in range 0 .. %d", pin, GPIO_PIN_NUM - 1); } if (value) { entry->gpio_set |= BIT(pin_num[pin]); } else { entry->gpio_clr |= BIT(pin_num[pin]); } } else { const char *str = lua_tostring(L, -2); if (strcmp(str, "delay") == 0) { entry->delay = lua_tonumber(L, -1); if (entry->delay < 0 || entry->delay > 1000000) { return luaL_error(L, "delay of %d must be in the range 0 .. 1000000 microseconds", entry->delay); } } else if (strcmp(str, "min") == 0) { entry->delay_min = lua_tonumber(L, -1); if (entry->delay_min < 0 || entry->delay_min > 1000000) { return luaL_error(L, "delay minimum of %d must be in the range 0 .. 1000000 microseconds", entry->delay_min); } } else if (strcmp(str, "max") == 0) { entry->delay_max = lua_tonumber(L, -1); if (entry->delay_max < 0 || entry->delay_max > 1000000) { return luaL_error(L, "delay maximum of %d must be in the range 0 .. 1000000 microseconds", entry->delay_max); } } else if (strcmp(str, "count") == 0) { entry->count = lua_tonumber(L, -1); } else if (strcmp(str, "loop") == 0) { entry->loop = lua_tonumber(L, -1); } else { return luaL_error(L, "Unrecognized key found: %s", str); } } lua_pop(L, 1); } if (entry->delay_min != -1 || entry->delay_max != -1) { if (entry->delay_min == -1) { entry->delay_min = 0; } if (entry->delay_min > entry->delay || entry->delay_max < entry->delay) { return luaL_error(L, "Delay of %d must be between min and max", entry->delay); } } lua_pop(L, 1); } return 1; } static int gpio_pulse_adjust(lua_State *L) { pulse_t *pulser = luaL_checkudata(L, 1, "gpio.pulse"); if (active_pulser != pulser) { return 0; } int offset = luaL_checkinteger(L, 2); // This will alter the next adjustable pulser->next_adjust = offset; int rc = gpio_pulse_push_state(L, active_pulser); return rc; } static int gpio_pulse_cancel(lua_State *L) { pulse_t *pulser = luaL_checkudata(L, 1, "gpio.pulse"); if (active_pulser != pulser) { return 0; } // Shut off the timer platform_hw_timer_close(TIMER_OWNER); int rc = gpio_pulse_push_state(L, active_pulser); active_pulser = NULL; int pulser_ref = active_pulser_ref; active_pulser_ref = LUA_NOREF; luaL_unref(L, LUA_REGISTRYINDEX, pulser_ref); return rc; } static void ICACHE_RAM_ATTR gpio_pulse_timeout(os_param_t p) { (void) p; int delay; do { if (!active_pulser || active_pulser->entry_pos >= active_pulser->entry_count) { if (active_pulser) { active_pulser->steps++; } platform_hw_timer_close(TIMER_OWNER); task_post_low(tasknumber, (task_param_t)0); return; } active_pulser->steps++; pulse_entry_t *entry = active_pulser->entry + active_pulser->entry_pos; // Yes, this means that there is more skew on D0 than on other pins.... if (entry->gpio_set & 0x10000) { gpio16_output_set(1); } GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, entry->gpio_set); GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, entry->gpio_clr); if (entry->gpio_clr & 0x10000) { gpio16_output_set(0); } int16_t stop = active_pulser->stop_pos; if (stop == -2 || stop == active_pulser->entry_pos) { platform_hw_timer_close(TIMER_OWNER); task_post_low(tasknumber, (task_param_t)0); return; } if (entry->loop) { if (entry->count_left == 0) { entry->count_left = entry->count + 1; } if (--entry->count_left >= 1) { active_pulser->entry_pos = entry->loop - 1; // zero offset } else { active_pulser->entry_pos++; } } else { active_pulser->entry_pos++; } delay = entry->delay; if (entry->delay_min != -1) { int offset = active_pulser->next_adjust; active_pulser->next_adjust = 0; int delay_offset = 0x7fffffff & (system_get_time() - (active_pulser->expected_end_time + offset)); delay -= (delay_offset << 1) >> 1; if (delay < entry->delay_min) { active_pulser->next_adjust = entry->delay_min - delay; delay = entry->delay_min; } else if (delay > entry->delay_max) { active_pulser->next_adjust = entry->delay_max - delay; delay = entry->delay_max; } } active_pulser->expected_end_time += delay; } while (delay < 3); platform_hw_timer_arm_us(TIMER_OWNER, delay); } static int gpio_pulse_start(lua_State *L) { pulse_t *pulser = luaL_checkudata(L, 1, "gpio.pulse"); if (active_pulser) { return luaL_error(L, "pulse operation already in progress"); } int argno = 2; int initial_adjust; if (lua_type(L, argno) == LUA_TNUMBER) { initial_adjust = luaL_checkinteger(L, argno); argno++; } if (lua_type(L, argno) == LUA_TFUNCTION || lua_type(L, argno) == LUA_TLIGHTFUNCTION) { lua_pushvalue(L, argno); } else { return luaL_error( L, "missing callback" ); } luaL_unref(L, LUA_REGISTRYINDEX, pulser->cb_ref); pulser->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX); active_pulser = pulser; lua_pushvalue(L, 1); active_pulser_ref = luaL_ref(L, LUA_REGISTRYINDEX); size_t i; for (i = 0; i < pulser->entry_count; i++) { pulser->entry[i].count_left = 0; } pulser->entry_pos = 0; pulser->steps = 0; pulser->stop_pos = -1; pulser->next_adjust = initial_adjust; // Now start things up if (!platform_hw_timer_init(TIMER_OWNER, FRC1_SOURCE, TRUE)) { // Failed to init the timer luaL_error(L, "Unable to initialize timer"); } active_pulser->expected_end_time = 0x7fffffff & system_get_time(); platform_hw_timer_set_func(TIMER_OWNER, gpio_pulse_timeout, 0); gpio_pulse_timeout(0); return 0; } static void gpio_pulse_task(os_param_t param, uint8_t prio) { (void) param; (void) prio; if (active_pulser) { lua_State *L = lua_getstate(); // Invoke the callback lua_rawgeti(L, LUA_REGISTRYINDEX, active_pulser->cb_ref); int rc = gpio_pulse_push_state(L, active_pulser); active_pulser = NULL; int pulser_ref = active_pulser_ref; active_pulser_ref = LUA_NOREF; luaL_unref(L, LUA_REGISTRYINDEX, pulser_ref); lua_call(L, rc, 0); } } static const LUA_REG_TYPE pulse_map[] = { { LSTRKEY( "getstate" ), LFUNCVAL( gpio_pulse_getstate ) }, { LSTRKEY( "stop" ), LFUNCVAL( gpio_pulse_stop ) }, { LSTRKEY( "cancel" ), LFUNCVAL( gpio_pulse_cancel ) }, { LSTRKEY( "start" ), LFUNCVAL( gpio_pulse_start ) }, { LSTRKEY( "adjust" ), LFUNCVAL( gpio_pulse_adjust ) }, { LSTRKEY( "__gc" ), LFUNCVAL( gpio_pulse_delete ) }, { LSTRKEY( "__index" ), LROVAL( pulse_map ) }, { LNILKEY, LNILVAL } }; const LUA_REG_TYPE gpio_pulse_map[] = { { LSTRKEY( "build" ), LFUNCVAL( gpio_pulse_build ) }, { LSTRKEY( "__index" ), LROVAL( gpio_pulse_map ) }, { LNILKEY, LNILVAL } }; int gpio_pulse_init(lua_State *L) { luaL_rometatable(L, "gpio.pulse", (void *)pulse_map); tasknumber = task_get_id(gpio_pulse_task); return 0; } //NODEMCU_MODULE(GPIOPULSE, "gpiopulse", gpio_pulse_map, gpio_pulse_init);