Add node.sleep()(#3370)

Supporting wakeup from (non-EXT1) GPIO, UART, ULP, timer and touch
This commit is contained in:
tomsci 2021-01-17 08:00:12 +00:00 committed by GitHub
parent 830366188d
commit d5f0094576
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 244 additions and 14 deletions

View File

@ -259,6 +259,18 @@ static int uart_getconfig(lua_State* L) {
return 4;
}
static int uart_wakeup (lua_State *L)
{
uint32_t id = luaL_checkinteger(L, 1);
MOD_CHECK_ID(uart, id);
int threshold = luaL_checkinteger(L, 2);
esp_err_t err = uart_set_wakeup_threshold(id, threshold);
if (err) {
return luaL_error(L, "Error %d from uart_set_wakeup_threshold()", err);
}
return 0;
}
// Module function map
LROT_BEGIN(uart)
LROT_FUNCENTRY( setup, uart_setup )
@ -268,6 +280,7 @@ LROT_BEGIN(uart)
LROT_FUNCENTRY( on, uart_on )
LROT_FUNCENTRY( setmode, uart_setmode )
LROT_FUNCENTRY( getconfig, uart_getconfig )
LROT_FUNCENTRY( wakeup, uart_wakeup )
LROT_NUMENTRY( STOPBITS_1, PLATFORM_UART_STOPBITS_1 )
LROT_NUMENTRY( STOPBITS_1_5, PLATFORM_UART_STOPBITS_1_5 )
LROT_NUMENTRY( STOPBITS_2, PLATFORM_UART_STOPBITS_2 )

View File

@ -48,11 +48,122 @@ static int node_restart (lua_State *L)
return 0;
}
static void node_sleep_set_uart (lua_State *L, int uart)
{
int err = esp_sleep_enable_uart_wakeup(uart);
if (err) {
luaL_error(L, "Error %d returned from esp_sleep_enable_uart_wakeup(%d)", err, uart);
}
}
static bool node_sleep_get_time_options (lua_State *L, int64_t *usecs)
{
lua_getfield(L, 1, "us");
lua_getfield(L, 1, "secs");
bool option_present = !lua_isnil(L, 2) || !lua_isnil(L, 3);
lua_pop(L, 2);
*usecs = 0;
if (option_present) {
*usecs += opt_checkint(L, "us", 0);
*usecs += (int64_t)opt_checkint(L, "secs", 0) * 1000000;
}
return option_present;
}
static void node_sleep_disable_wakeup_sources (lua_State *L)
{
// Start with known state, to ensure previous sleep calls don't leave any
// settings left over
int err = esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
if (err) {
luaL_error(L, "Error %d returned from esp_sleep_disable_wakeup_source", err);
}
}
static int node_sleep (lua_State *L)
{
lua_settop(L, 1);
luaL_checkanytable(L, 1);
node_sleep_disable_wakeup_sources(L);
// uart options: uart = num|{num, num, ...}
lua_getfield(L, -1, "uart");
int type = lua_type(L, -1);
if (type == LUA_TNUMBER) {
node_sleep_set_uart(L, lua_tointeger(L, -1));
} else if (type == LUA_TTABLE) {
for (int i = 1; ; i++) {
lua_rawgeti(L, -1, i);
if (lua_isnil(L, -1)) {
lua_pop(L, 1); // uart[i]
break;
}
int uart = lua_tointeger(L, -1);
lua_pop(L, 1); // uart[i]
node_sleep_set_uart(L, uart);
}
} else if (type != LUA_TNIL) {
return opt_error(L, "uart", "must be integer or table");
}
lua_pop(L, 1); // uart
// gpio option: boolean (individual pins are configured in advance with gpio.wakeup())
// Make sure to do GPIO before touch, because esp_sleep_enable_gpio_wakeup()
// seems to think touch is not compatible with GPIO wakeup and will error the
// call if you order them the other way round, despite the fact that
// esp_sleep_enable_touchpad_wakeup() does not have a similar check, and I've
// tested using both GPIO and touch wakeups at once and it works fine for me.
// I think this is simply a bug in the Espressif SDK, because sleep_modes.rst
// only mentions compatibility issues with touch and EXT0 wakeup, which is
// not the same as GPIO wakeup.
if (opt_checkbool(L, "gpio", false)) {
int err = esp_sleep_enable_gpio_wakeup();
if (err) {
return luaL_error(L, "Error %d returned from esp_sleep_enable_gpio_wakeup()", err);
}
}
// time options: us, secs
int64_t usecs = 0;
if (node_sleep_get_time_options(L, &usecs)) {
esp_sleep_enable_timer_wakeup(usecs);
}
// touch option: boolean
if (opt_checkbool(L, "touch", false)) {
int err = esp_sleep_enable_touchpad_wakeup();
if (err) {
return luaL_error(L, "Error %d returned from esp_sleep_enable_touchpad_wakeup()", err);
}
}
// ulp option: boolean
if (opt_checkbool(L, "ulp", false)) {
int err = esp_sleep_enable_ulp_wakeup();
if (err) {
return luaL_error(L, "Error %d returned from esp_sleep_enable_ulp_wakeup()", err);
}
}
int err = esp_light_sleep_start();
if (err == ESP_ERR_INVALID_STATE) {
return luaL_error(L, "WiFi and BT must be stopped before sleeping");
} else if (err) {
return luaL_error(L, "Error %d returned from esp_light_sleep_start()", err);
}
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
lua_pushinteger(L, (int)cause);
return 1;
}
// Lua: node.dsleep (microseconds|{opts})
static int node_dsleep (lua_State *L)
{
lua_settop(L, 1);
node_sleep_disable_wakeup_sources(L);
bool enable_timer_wakeup = false;
int64_t usecs = 0;
int type = lua_type(L, 1);
@ -60,15 +171,7 @@ static int node_dsleep (lua_State *L)
enable_timer_wakeup = true;
usecs = lua_tointeger(L, 1);
} else if (type == LUA_TTABLE) {
// time options: us, secs
lua_getfield(L, 1, "us");
lua_getfield(L, 1, "secs");
enable_timer_wakeup = !lua_isnil(L, 2) || !lua_isnil(L, 3);
lua_pop(L, 2);
if (enable_timer_wakeup) {
usecs = opt_checkint(L, "us", 0);
usecs += (int64_t)opt_checkint(L, "secs", 0) * 1000000;
}
enable_timer_wakeup = node_sleep_get_time_options(L, &usecs);
// GPIO wakeup options: gpio = num|{num, num, ...}
uint64_t pin_mask = 0;
@ -524,6 +627,15 @@ LROT_BEGIN(node_task)
LROT_END(node_task, NULL, 0)
// Wakup reasons
LROT_BEGIN(node_wakeup)
LROT_NUMENTRY ( GPIO, ESP_SLEEP_WAKEUP_GPIO )
LROT_NUMENTRY ( TIMER, ESP_SLEEP_WAKEUP_TIMER )
LROT_NUMENTRY ( TOUCHPAD, ESP_SLEEP_WAKEUP_TOUCHPAD )
LROT_NUMENTRY ( UART, ESP_SLEEP_WAKEUP_UART )
LROT_NUMENTRY ( ULP, ESP_SLEEP_WAKEUP_ULP )
LROT_END(node_wakeup, NULL, 0)
LROT_BEGIN(node)
LROT_FUNCENTRY( chipid, node_chipid )
LROT_FUNCENTRY( compile, node_compile )
@ -537,9 +649,11 @@ LROT_BEGIN(node)
LROT_FUNCENTRY( osoutput, node_osoutput )
LROT_FUNCENTRY( osprint, node_osprint )
LROT_FUNCENTRY( restart, node_restart )
LROT_FUNCENTRY( sleep, node_sleep )
LROT_FUNCENTRY( stripdebug, node_stripdebug )
LROT_TABENTRY ( task, node_task )
LROT_FUNCENTRY( uptime, node_uptime )
LROT_TABENTRY ( wakeup, node_wakeup )
LROT_END(node, NULL, 0)

View File

@ -109,8 +109,13 @@ Establish or clear a callback function to run on interrupt for a GPIO.
#### Returns
`nil`
## gpio.wakeup()
Configuring wake-from-sleep-on-GPIO-level.
Configure whether the given pin should trigger wake up from light sleep initiated by [`node.sleep()`](node.md#nodesleep).
Note that the `level` specified here overrides the interrupt type set by `gpio.trig()`, and wakeup only supports the level-triggered options `gpio.INTR_LOW` and `gpio.INTR_HIGH`. Therefore it is not possible to configure an edge-triggered GPIO callback in combination with wake from light sleep, at least not without reconfiguring the pin immediately before and after the call to `node.sleep()`.
The call to `node.sleep()` must additionally include `gpio = true` in the `options` in order for any GPIO to trigger wakeup.
#### Syntax
`gpio.wakeup(pin, level)`
@ -118,13 +123,16 @@ Configuring wake-from-sleep-on-GPIO-level.
#### Parameters
- `pin`, see [GPIO Overview](#gpio-overview)
- `level` wake-up level, one of
- `gpio.INTR_NONE` to disable wake-up
- `gpio.INTR_LOW` for wake-up on low level
- `gpio.INTR_HIGH` for wake-up on high level
- `gpio.INTR_NONE` changes to the level of this pin will not trigger wake from light sleep
- `gpio.INTR_LOW` if this pin is low it should trigger wake from light sleep
- `gpio.INTR_HIGH` if this pin is high it should trigger wake from light sleep
#### Returns
`nil`
#### See also
[`node.sleep()`](node.md#nodesleep)
## gpio.write()
Set digital GPIO pin value.

View File

@ -104,7 +104,7 @@ For compatibility, a number parameter `usecs` can be supplied instead of an `opt
- `us`, a number of microseconds to sleep. If both `secs` and `us` are provided, the values are combined.
- `gpio`, a single GPIO number or a list of GPIOs. These pins must all be RTC-capable otherwise an error is raised.
- `level`. Whether to trigger when *any* of the GPIOs are high (`level=1`, which is the default if not specified), or when *all* the GPIOs are low (`level=0`).
- `isolate`. A list of GPIOs to isolate. Isolating a GPIO disables input, output, pullup, pulldown, and enables hold feature for an RTC IO. Use this function if an RTC IO needs to be disconnected from internal circuits in deep sleep, to minimize leakage current.
- `isolate`. A list of GPIOs to isolate. Isolating a GPIO disables input, output, pullup, pulldown, and enables hold feature for an RTC IO. Use this option if an RTC IO needs to be disconnected from internal circuits in deep sleep, to minimize leakage current.
- `pull`, boolean, whether to keep powering previously-configured internal pullup/pulldown resistors. Default is `false` if not specified.
- `touch`, boolean, whether to trigger wakeup from any previously-configured touchpads. Default is `false` if not specified.
@ -125,6 +125,10 @@ node.dsleep({ secs = 10, gpio = { 13, 15 } })
node.dsleep({ gpio = 13, level = 0, pull = true })
```
#### See also
- [`node.sleep()`](#nodesleep)
## node.flashid()
Returns the flash chip ID.
@ -386,6 +390,73 @@ target CPU frequency (number)
node.setcpufreq(node.CPU80MHZ)
```
## node.sleep()
Enters light sleep mode, which saves power without losing state. The state of the CPU and peripherals is preserved during light sleep and is resumed once the processor wakes up. When the processor wakes back up depends on the supplied `options`. Wake up from light sleep can be triggered by a time period, or when a GPIO (or GPIOs) change level, when a touchpad event occurs, when data is received on a UART, or by the ULP (ultra low power processor, generally not used by NodeMCU). If multiple different wakeup sources are specified, the processor will wake when any of them occur. The return value of the function can be used to determine which source caused the wakeup. The function does not return until a wakeup occurs (and therefore may not return at all if a wakeup trigger never happens).
UART buffers are not flushed on entering light sleep, rather they are suspended and resumed on wakeup, meaning that some data written before entering light sleep may not be output over the UART until after wakeup.
Timers created with `tmr` will not fire during light sleep, and the time spent sleeping is not factored in to their remaining time after wakeup. They are paused, and resumed automatically after wakeup. For example, if a timer has 2 seconds remaining when light sleep starts, it will still have 2 seconds remaining after wakeup, regardless of how much time elapsed during the sleep or what triggered the wakeup. The value returned by [`node.uptime()`](#nodeuptime) however _is_ updated by however long is spent in light sleep, and can therefore be used to calculate how much time was spent asleep.
Although a time period to sleep for can be specified in microseconds, the actual amount of time spent asleep will not be that precise.
Unlike with the [`dsleep()`](#nodedsleep) API, _any_ GPIO (not just the RTC-capable pins) may be used to trigger wakeup from light sleep. To configure which GPIOs should trigger wakeup, and under what circumstances, call [`gpio.wakeup()`](gpio.md#gpiowakeup) prior to calling `node.sleep()`. If a GPIO wakeup occurs, then any callbacks configured with [`gpio.trig()`](gpio.md#gpiotrig) will be called as normal after wakeup. In other words, interrupts do not get 'lost' during light sleep, and all other state such as pullups and drive strength is preserved.
Similarly, if `touch = true` is specified and a touch event triggers wakeup, the touch callback will be called as normal after wakeup.
Wakeup from light sleep can also be triggered by incoming data on UART0 or UART1, (but not UART2) by passing in `uart = 0` or `uart = 1` (or `uart = {0, 1}` to wake on either). Note that the byte(s) which trigger the wakeup are consumed in the process, and will therefore not be seen by the UART after wakeup. Before using uart wakeup for the first time, you must call [`uart.wakeup()`](uart.md#uartwakeup) to configure what data should trigger wakeup.
WiFi and Bluetooth must be switched off before entering light sleep, otherwise an error will be thrown.
#### Syntax
`node.sleep(options)`
#### Parameters
- `options`, a table containing some of:
- `secs`, a number of seconds to sleep. This permits longer sleep periods compared to using the `us` parameter.
- `us`, a number of microseconds to sleep. If both `secs` and `us` are provided, the values are combined.
- `gpio`, a boolean, whether to allow wakeup by GPIOs. Default is `false` if not specified.
- `touch`, boolean, whether to trigger wakeup from any previously-configured touchpads. Default is `false` if not specified.
- `uart`, an integer or list of integers. Which UARTs should trigger wakeup. Default is the empty list if not specified.
- `ulp`, a boolean, whether to allow the ULP to trigger wakeup. Default is `false` if not specified.
If an empty options table is specified, ie no wakeup sources, then the chip will light sleep forever with no way to wake it (except for power cycling or triggering the reset pin/button).
#### Returns
One of the following values, depending on what triggered the wakeup.
- `node.wakeup.GPIO`
- `node.wakeup.TIMER`
- `node.wakeup.TOUCHPAD`
- `node.wakeup.UART`
- `node.wakeup.ULP`
#### Example
```lua
-- sleep 10 seconds then continue
node.sleep({ secs = 10 })
-- sleep until 10 seconds have elapsed or either of GPIO 13 or 15 becomes high
gpio.wakeup(13, gpio.INTR_HIGH)
gpio.wakeup(15, gpio.INTR_HIGH)
node.sleep({ secs = 10, gpio = true })
-- Sleep forever until a previously-configured touchpad is touched, or a byte
-- arrives on UART0.
uart.wakeup(0, 3)
wakereason = node.sleep({ touch = true, uart = 0 })
if wakereason == node.WAKEUP_TOUCHPAD then
print("Woken up by touchpad!")
end
```
#### See also
- [`node.dsleep()`](#nodedsleep)
- [`gpio.wakeup()`](gpio.md#gpiowakeup)
- [`uart.wakeup()`](uart.md#uartwakeup)
## node.stripdebug()
Controls the amount of debug information kept during [`node.compile()`](#nodecompile), and allows removal of debug information from already compiled Lua code.

View File

@ -175,6 +175,30 @@ Set UART controllers communication mode
#### Returns
`nil`
## uart.wakeup()
Configure the light sleep wakeup threshold. This is the number of positive edges that must be seen on the UART RX pin before a light sleep wakeup will be triggered. The minimum value is 3. The default value is undefined, therefore you should always call this function before the first time you call `node.sleep()` with the uart option set.
#### Syntax
`uart.wakeup(id, val)`
#### Parameters
- `id` uart id
- `val` the new value
#### Returns
`nil`
#### Example
```lua
uart.wakeup(0, 5)
```
#### See also
[`node.sleep()`](node/#nodesleep)
## uart.write()
Write string or byte to the UART.