Skip to content

Commit

Permalink
cpu/esp32: add pm_layered support
Browse files Browse the repository at this point in the history
  • Loading branch information
gschorcht committed Feb 20, 2020
1 parent 86fef5a commit 18644ed
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 8 deletions.
6 changes: 5 additions & 1 deletion cpu/esp32/Makefile.dep
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ ifneq (,$(filter ndn-riot,$(USEPKG)))
USEMODULE += cipher_modes
endif

ifneq (,$(filter pm_layered,$(USEMODULE)))
USEMODULE += periph_rtc
endif

ifneq (,$(filter shell,$(USEMODULE)))
USEMODULE += ps
endif
Expand All @@ -98,7 +102,7 @@ endif
# if SPI RAM is enabled, ESP-IDF heap and quot flash mode have to be used
ifneq (,$(filter esp_spi_ram,$(USEMODULE)))
USEMODULE += esp_idf_heap
export FLASH_MODE = qout
FLASH_MODE = qout
CFLAGS += -DFLASH_MODE_QOUT=1
else
ifeq ($(FLASH_MODE), qio)
Expand Down
1 change: 1 addition & 0 deletions cpu/esp32/Makefile.include
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ USEMODULE += periph_adc_ctrl
USEMODULE += periph_hwrng
USEMODULE += periph_flash
USEMODULE += periph_uart
USEMODULE += pm_layered
USEMODULE += riot_freertos
USEMODULE += random
USEMODULE += stdio_uart
Expand Down
111 changes: 107 additions & 4 deletions cpu/esp32/doc.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
8. [RTC Timer](#esp32_rtc_timer)
9. [UART Interfaces](#esp32_uart_interfaces)
10. [CAN Interfaces](#esp32_can_interfaces)
11. [Other Peripherals](#esp32_other_peripherals)
11. [Power Management](#esp32_power_management)
12. [Other Peripherals](#esp32_other_peripherals)
7. [Special On-board Peripherals](#esp32_special_on_board_peripherals)
1. [SPI RAM Modules](#esp32_spi_ram)
2. [SPIFFS Device](#esp32_spiffs_device)
Expand Down Expand Up @@ -143,11 +144,11 @@ MCU | ESP32 | Supported by RIOT
Vendor | Espressif | |
Cores | 1 or 2 x Tensilica Xtensa LX6 | 1 core
FPU | ULP - Ultra low power co-processor | no
RAM | 520 kByte SRAM <br> 16 kByte RTC SRAM | yes
RAM | 520 kByte SRAM <br> 8 kByte slow RTC SRAM <br> 8 kByte fast RTC SRAM | yes <br> yes <br> yes
ROM | 448 kByte | yes
Flash | 512 kByte ... 16 MByte | yes
Frequency | 240 MHz, 160 MHz, 80 MHz | yes
Power Consumption | 68 mA @ 240 MHz <br> 44 mA @ 160 MHz <br> 31 mA @ 80 MHz <br> 5 uA in deep sleep mode | yes <br> yes <br> yes <br> no
Power Consumption | 68 mA @ 240 MHz <br> 44 mA @ 160 MHz (34 mA @ 160 MHz single core) <br> 31 mA @ 80 MHz (25 mA @ 80 MHz single core) <br> 800 uA in light sleep mode <br> 10 uA in deep sleep mode | yes <br> yes <br> yes <br> yes <br> yes
Timers | 4 x 64 bit | yes
ADCs | 2 x SAR-ADC with up to 18 x 12 bit channels total | yes
DACs | 2 x DAC with 8 bit | yes
Expand Down Expand Up @@ -188,6 +189,7 @@ The RIOT-OS for ESP32 SoCs supports the following features at the moment:
- PWM channels
- SPI RAM
- SPI Flash Drive (MTD with SPIFFS and VFS)
- Layered Power Management
- Hardware number generator
- Hardware timer devices
- ESP-NOW netdev interface
Expand Down Expand Up @@ -952,14 +954,115 @@ FEATURES_PROVIDED += periph_can # CAN peripheral interface
Otherwise, the application has to add the `periph_can` module in its makefile
when needed.

\anchor esp32_power_management
## <a name="esp32_power_management"> Power Management </a> &nbsp;[[TOC](#esp32_toc)]

### Power Modes

The RIOT port for the ESP32 implements RIOT's layered power management. It
supports the following operating modes:

- The **Modem-sleep** mode is the default operating mode when the WiFi
interface is disabled.
- The **Active** mode is the default operating mode when the WiFi interface
is used by either the `esp-wifi` or `esp-now` module.
- In **Light-sleep** mode, the CPUs including peripherals are stalled, but
the SRAM is retained. The system can continue when it returns from this mode.
- In **Deep-sleep** the CPU and the SRAM are powered down. The RTC memory can
be retained. The system must be restarted when it returns from this mode.

Since the peripherals are not working during _Light-sleep_/_Deep-sleep_, the
CPU cannot be woken up by internal interrupt sources such as timers. Therefore,
RIOT's layered power management can't select them as idle power mode. They are
therefore blocked for normal operation. The application has to select them
explicitly using the `pm_set` function. RIOT's layered power management
can only select either _Modem-sleep_ or _Active_ as the lowest unblocked mode.

But also in _Modem-sleep_ or _Active_ mode, the lowest possible power level
is used. For this purpose, the Xtensa ISA instruction `waiti` is used,
which saves power by setting the current interrupt level, turning off the
processor logic and waiting for an interrupt.

### Using Power Modes

_Modem-sleep_ mode and _Active_ mode are the default operating modes
dependent on whether the WiFi interface is used. They are selected
automatically by the system.

To enter the _Light-sleep_ or the _Deep-sleep_ mode, function `pm_set` has
to be used with the according mode `ESP_PM_LIGHT_SLEEP` or `ESP_PM_DEEP_SLEEP`
as parameter. To exit from these modes, several wake-up sources can be used:
- RTC timer (set the RTC timer alarm using `rtc_set` before calling `pm_set`)
- RTC GPIOs (configured by `ESP_PM_WUP_PINS` and `ESP_PM_WUP_LEVEL`)

In _Light-sleep_ mode, the RxD signal of UART0 and/or UART1 can be used as an
additional wake-up source (configured by `ESP_PM_WUP_UART0` and
`ESP_PM_WUP_UART1`). This allow to wake-up the system once a key
is entered at the console.

@note RTC GPIOs are the GPIOs that are realized by the RTC unit and can also
be used as ADC channels. See section [GPIO pins](#esp32_gpio_pins) and
[ADC Channels](#esp32_adc_channels) for more information.

### Configuration

Several definitions can be used during compile time to configure the
_Light-sleep_ and the _Deep-sleep_ mode:

- `ESP_PM_WUP_PINS` specifies either a single RTC GPIO or a comma separated
list of RTC GPIOs that are used as wake-up source.
- `ESP_PM_WUP_LEVEL` specifies the level for the wake-up. With
`ESP_PM_WUP_LEVEL=1` (default) the system is woken up when any of the
GPIOs specified in `ESP_PM_WUP_PINS` becomes HIGH. With `ESP_PM_WUP_LEVEL=0`
the system is woken up when all GPIOs specified in `ESP_PM_WUP_PINS`
become LOW.
- `ESP_PM_WUP_UART0` and `ESP_PM_WUP_UART1` define the number of positive
edges of the RxD signal of the respective UART that are necessary to wake
up the system. The value must be greater than 2, otherwise UART is
deactivated as wake-up source.
- If `ESP_PM_GPIO_HOLD` is defined, GPIOs hold their last output level
when entering sleep mode. In _Deep-sleep_ mode, only RTC GPIOs can hold
their output value.

@note The RTC GPIOs specified in `ESP_PM_WUP_PINS` are reconfigured as inputs
when entering the sleep mode. It is therefore recommended that only GPIOs that
are already used as inputs are used as wake-up pins for _Light-sleep_ mode.
Otherwise, the GPIO would have an incorrect configuration after returning
from _Light-sleep_ mode.

In the following example the system shall be woken up if either the pulled-up
pin `GPIO25` (`ESP_PM_WUP_PINS=GPIO25`) goes LOW (`ESP_PM_WUP_LEVEL=0`) or
the RxD signal of UART0 goes HIGH at least 6 times (`ESP_PM_WUP_UART0=6`).
The last GPIO output values are held (`ESP_PM_GPIO_HOLD`).
```
CFLAGS='-DESP_PM_WUP_PINS=GPIO25 -DESP_PM_WUP_LEVEL=0 -DESP_PM_WUP_UART0=6 -DESP_PM_GPIO_HOLD' \
make BOARD=esp32-wroom-32 -C tests/periph_pm
```

### Saving Data in _Deep-sleep_ Mode

In _Deep-sleep_ mode the SRAM is powered down. However, the slow RTC memory
can be retained. Therefore, data that must be retained during _Deep-sleep_ and
the subsequent system restart, have to be stored in the slow RTC memory. For
that purpose, use
- `__attribute__((section(".rtc.bss")))` to place initialized
data in section `.rtc.bss`, and
- `__attribute__((section(".rtc.data")))` to
place uninitialized data in section `.rtc.data`.

For example:
```
static int _i_value __attribute__((section(".rtc.bss"))); /* initialized at power on */
static int _u_value __attribute__((section(".rtc.data"))); /* not initialized at power on */
```

## <a name="esp32_other_peripherals"> Other Peripherals </a> &nbsp;[[TOC](#esp32_toc)]

The ESP32 port of RIOT also supports:

- hardware number generator with 32 bit
- CPU-ID function
- Vref measurement function
- power management functions

# <a name="esp32_special_on_board_peripherals"> Special On-board Peripherals </a> &nbsp;[[TOC](#esp32_toc)]

Expand Down
16 changes: 16 additions & 0 deletions cpu/esp32/include/periph_cpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ extern "C" {
#define PROVIDES_PM_SET_LOWEST
#define PROVIDES_PM_RESTART
#define PROVIDES_PM_OFF
#define PROVIDES_PM_LAYERED_OFF

/**
* @brief Number of usable low power modes
*/
#define PM_NUM_MODES (2U)

/**
* @name Power modes
* @{
*/
#define ESP_PM_MODEM_SLEEP (2U)
#define ESP_PM_LIGHT_SLEEP (1U)
#define ESP_PM_DEEP_SLEEP (0U)
/** @} */

/** @} */

/**
Expand Down
109 changes: 106 additions & 3 deletions cpu/esp32/periph/pm.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@
#include "esp_attr.h"
#include "esp_sleep.h"
#include "syscalls.h"
#include "xtimer.h"

#include "periph/rtc.h"
#include "rom/rtc.h"
#include "rom/uart.h"
#include "soc/rtc.h"
#include "soc/rtc_cntl_reg.h"

void pm_set_lowest(void)
static inline void pm_set_lowest_normal(void)
{
DEBUG ("%s enter to sleep @%u\n", __func__, system_get_time());
DEBUG ("%s enter to normal sleep @%u\n", __func__, system_get_time());

#if !defined(QEMU)
/* passive wait for interrupt to leave lowest power mode */
Expand All @@ -42,7 +44,7 @@ void pm_set_lowest(void)
system_wdt_feed();
#endif

DEBUG ("%s exit from sleep @%u\n", __func__, system_get_time());
DEBUG ("%s exit from normal sleep @%u\n", __func__, system_get_time());
}

void IRAM_ATTR pm_off(void)
Expand All @@ -65,3 +67,104 @@ void pm_reboot(void)

software_reset();
}

#ifndef MODULE_PM_LAYERED

void pm_set_lowest(void)
{
pm_set_lowest_normal();
}

#else /* MODULE_PM_LAYERED */

void pm_set(unsigned mode)
{
if (mode == ESP_PM_MODEM_SLEEP) {
pm_set_lowest_normal();
return;
}

DEBUG ("%s enter to power mode %d @%u\n", __func__, mode, system_get_time());

/* Labels for RTC slow memory that are defined in the linker script */
extern int _rtc_bss_rtc_start;
extern int _rtc_bss_rtc_end;

/*
* Activate the power domain for the slow RTC memory when there are
* data in the .rtc.bss section (RAM backup) that need to be retained.
* Data in .rtc.data section of the RTC slow memory are retained
* automatically.
*/
if (&_rtc_bss_rtc_end > &_rtc_bss_rtc_start) {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON);
}

/*
* Activate the power domain for RTC peripherals either when
* ESP_PM_GPIO_HOLD is defined or when light sleep mode is activated.
* As long as the RTC peripherals are active, the pad state of RTC GPIOs
* is held in deep sleep and the pad state of all GPIOs is held in light
* sleep.
*/
#ifdef ESP_PM_GPIO_HOLD
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
#else
if (mode == ESP_PM_LIGHT_SLEEP) {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
}
#endif

/* Prepare the RTC timer if an RTC alarm is set to wake up. */
struct tm tm_alarm;
struct tm tm_system;

rtc_get_time(&tm_system);
rtc_get_alarm(&tm_alarm);

time_t t_system = mktime(&tm_system);
time_t t_alarm = mktime(&tm_alarm);
int _alarm_set = 0;

if (t_alarm > t_system) {
_alarm_set = 1;
esp_sleep_enable_timer_wakeup((uint64_t)(t_alarm - t_system) * US_PER_SEC);
}

#ifdef ESP_PM_WUP_PINS
/*
* Prepare the wake-up pins if a single pin or a comma-separated list of
* pins is defined for wake-up.
*/
static const gpio_t wup_pins[] = { ESP_PM_WUP_PINS };

uint64_t wup_pin_mask = 0;
for (unsigned i = 0; i < ARRAY_SIZE(wup_pins); i++) {
wup_pin_mask |= 1ULL << wup_pins[i];
}
#ifdef ESP_PM_WUP_LEVEL
esp_sleep_enable_ext1_wakeup(wup_pin_mask, ESP_PM_WUP_LEVEL);
#else /* ESP_PM_WUP_LEVEL */
esp_sleep_enable_ext1_wakeup(wup_pin_mask, ESP_EXT1_WAKEUP_ANY_HIGH);
#endif /* ESP_PM_WUP_LEVEL */
#endif /* ESP_PM_WUP_PINS */

if (mode == ESP_PM_DEEP_SLEEP) {
esp_deep_sleep_start();
/* waking up from deep-sleep leads to a DEEPSLEEP_RESET */
UNREACHABLE();
}
else if (mode == ESP_PM_LIGHT_SLEEP) {
esp_light_sleep_start();
DEBUG ("%s exit from power mode %d @%u\n", __func__, mode,
system_get_time());
if (_alarm_set) {
/* call the RTC alarm handler if an RTC alarm was set */
extern void rtc_handle_pending_alarm(void);
rtc_handle_pending_alarm();
_alarm_set = 0;
}
}
}

#endif /* MODULE_PM_LAYERED */
9 changes: 9 additions & 0 deletions cpu/esp32/periph/rtc.c
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ static void IRAM_ATTR _rtc_timer_handler(void* arg)
/* call back registered function */
if (_rtc_alarm_cb) {
_rtc_alarm_cb(_rtc_alarm_arg);
_rtc_alarm_cb = 0;
}
}
/* clear all interrupts */
Expand Down Expand Up @@ -356,3 +357,11 @@ static void IRAM_ATTR _rtc_timer_handler(void* arg)

irq_isr_exit();
}

void rtc_handle_pending_alarm(void)
{
if (_rtc_alarm_cb) {
_rtc_alarm_cb(_rtc_alarm_arg);
_rtc_alarm_cb = 0;
}
}
8 changes: 8 additions & 0 deletions cpu/esp32/startup.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@

#include "driver/periph_ctrl.h"
#include "esp/common_macros.h"
#include "esp32/esp_sleep.h"
#include "heap/esp_heap_caps_init.h"
#include "log/esp_log.h"
#include "rom/cache.h"
Expand Down Expand Up @@ -153,6 +154,13 @@ NORETURN void IRAM call_start_cpu0 (void)
ets_printf("\n");
#endif

if (reset_reason == DEEPSLEEP_RESET) {
/* the cause has to be read to clear it */
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
(void)cause;
LOG_STARTUP("Restart after deep sleep, wake-up cause: %d\n", cause);
}

LOG_STARTUP("Current clocks in Hz: CPU=%d APB=%d XTAL=%d SLOW=%d\n",
rtc_clk_cpu_freq_value(rtc_clk_cpu_freq_get()),
rtc_clk_apb_freq_get(), rtc_clk_xtal_freq_get()*MHZ,
Expand Down

0 comments on commit 18644ed

Please sign in to comment.