Merge branch 'feature/deep_sleep_wakeup_from_touch' into 'master'

add wakeup from touch sensor, and deep sleep example

- add new deep sleep wakeup mode
- change documentation to explain incompatibilities between different wakeup mode, add error checks
- add new ULP instructions necessary for ULP wakeup scenario
- fix issues with I_WR_REG, I_SLEEP, I_END instructions
- add deep sleep example, illustrating the use of timer, gpio, touch, and ULP wakeup triggers

See merge request !461
This commit is contained in:
Ivan Grokhotkov 2017-03-08 14:27:58 +08:00
commit 66c693eebb
15 changed files with 909 additions and 29 deletions

View file

@ -126,6 +126,9 @@ void IRAM_ATTR esp_deep_sleep_start()
if (s_config.wakeup_triggers & EXT_EVENT1_TRIG_EN) {
ext1_wakeup_prepare();
}
if (s_config.wakeup_triggers & SAR_TRIG_EN) {
SET_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_WAKEUP_FORCE_EN);
}
// TODO: move timer wakeup configuration into a similar function
// once rtc_sleep is opensourced.
@ -163,6 +166,10 @@ void system_deep_sleep(uint64_t) __attribute__((alias("esp_deep_sleep")));
esp_err_t esp_deep_sleep_enable_ulp_wakeup()
{
#ifdef CONFIG_ULP_COPROC_ENABLED
if(s_config.wakeup_triggers & RTC_EXT_EVENT0_TRIG_EN) {
ESP_LOGE(TAG, "Conflicting wake-up trigger: ext0");
return ESP_ERR_INVALID_STATE;
}
s_config.wakeup_triggers |= RTC_SAR_TRIG_EN;
return ESP_OK;
#else
@ -177,6 +184,26 @@ esp_err_t esp_deep_sleep_enable_timer_wakeup(uint64_t time_in_us)
return ESP_OK;
}
esp_err_t esp_deep_sleep_enable_touchpad_wakeup()
{
if (s_config.wakeup_triggers & (RTC_EXT_EVENT0_TRIG_EN)) {
ESP_LOGE(TAG, "Conflicting wake-up trigger: ext0");
return ESP_ERR_INVALID_STATE;
}
s_config.wakeup_triggers |= RTC_TOUCH_TRIG_EN;
return ESP_OK;
}
touch_pad_t esp_deep_sleep_get_touchpad_wakeup_status()
{
if (esp_deep_sleep_get_wakeup_cause() != ESP_DEEP_SLEEP_WAKEUP_TOUCHPAD) {
return TOUCH_PAD_MAX;
}
uint32_t touch_mask = REG_GET_FIELD(SENS_SAR_TOUCH_CTRL2_REG, SENS_TOUCH_MEAS_EN);
assert(touch_mask != 0 && "wakeup reason is RTC_TOUCH_TRIG_EN but SENS_TOUCH_MEAS_EN is zero");
return (touch_pad_t) (__builtin_ffs(touch_mask) - 1);
}
esp_err_t esp_deep_sleep_enable_ext0_wakeup(gpio_num_t gpio_num, int level)
{
if (level < 0 || level > 1) {
@ -185,6 +212,10 @@ esp_err_t esp_deep_sleep_enable_ext0_wakeup(gpio_num_t gpio_num, int level)
if (!RTC_GPIO_IS_VALID_GPIO(gpio_num)) {
return ESP_ERR_INVALID_ARG;
}
if (s_config.wakeup_triggers & (RTC_TOUCH_TRIG_EN | RTC_SAR_TRIG_EN)) {
ESP_LOGE(TAG, "Conflicting wake-up triggers: touch / ULP");
return ESP_ERR_INVALID_STATE;
}
s_config.ext0_rtc_gpio_num = rtc_gpio_desc[gpio_num].rtc_num;
s_config.ext0_trigger_level = level;
s_config.wakeup_triggers |= RTC_EXT_EVENT0_TRIG_EN;
@ -276,8 +307,7 @@ static void ext1_wakeup_prepare()
uint64_t esp_deep_sleep_get_ext1_wakeup_status()
{
int wakeup_reason = REG_GET_FIELD(RTC_CNTL_WAKEUP_STATE_REG, RTC_CNTL_WAKEUP_CAUSE);
if (wakeup_reason != RTC_EXT_EVENT1_TRIG) {
if (esp_deep_sleep_get_wakeup_cause() != ESP_DEEP_SLEEP_WAKEUP_EXT1) {
return 0;
}
uint32_t status = REG_GET_FIELD(RTC_CNTL_EXT_WAKEUP1_STATUS_REG, RTC_CNTL_EXT_WAKEUP1_STATUS);
@ -296,6 +326,28 @@ uint64_t esp_deep_sleep_get_ext1_wakeup_status()
return gpio_mask;
}
esp_deep_sleep_wakeup_cause_t esp_deep_sleep_get_wakeup_cause()
{
if (rtc_get_reset_reason(0) != DEEPSLEEP_RESET) {
return ESP_DEEP_SLEEP_WAKEUP_UNDEFINED;
}
uint32_t wakeup_cause = REG_GET_FIELD(RTC_CNTL_WAKEUP_STATE_REG, RTC_CNTL_WAKEUP_CAUSE);
if (wakeup_cause & RTC_EXT_EVENT0_TRIG) {
return ESP_DEEP_SLEEP_WAKEUP_EXT0;
} else if (wakeup_cause & RTC_EXT_EVENT1_TRIG) {
return ESP_DEEP_SLEEP_WAKEUP_EXT1;
} else if (wakeup_cause & RTC_TIMER_EXPIRE) {
return ESP_DEEP_SLEEP_WAKEUP_TIMER;
} else if (wakeup_cause & RTC_TOUCH_TRIG) {
return ESP_DEEP_SLEEP_WAKEUP_TOUCHPAD;
} else if (wakeup_cause & RTC_SAR_TRIG) {
return ESP_DEEP_SLEEP_WAKEUP_ULP;
} else {
return ESP_DEEP_SLEEP_WAKEUP_UNDEFINED;
}
}
esp_err_t esp_deep_sleep_pd_config(esp_deep_sleep_pd_domain_t domain,
esp_deep_sleep_pd_option_t option)
{
@ -333,13 +385,15 @@ static uint32_t get_power_down_flags()
s_config.pd_options[ESP_PD_DOMAIN_RTC_FAST_MEM] = ESP_PD_OPTION_ON;
}
// RTC_PERIPH is needed for EXT0 wakeup and for ULP.
// If RTC_PERIPH is auto, and both EXT0 and ULP aren't enabled,
// power down RTC_PERIPH.
// RTC_PERIPH is needed for EXT0 wakeup.
// If RTC_PERIPH is auto, and EXT0 isn't enabled, power down RTC_PERIPH.
if (s_config.pd_options[ESP_PD_DOMAIN_RTC_PERIPH] == ESP_PD_OPTION_AUTO) {
if (s_config.wakeup_triggers &
(RTC_SAR_TRIG_EN | RTC_EXT_EVENT0_TRIG_EN)) {
if (s_config.wakeup_triggers & RTC_EXT_EVENT0_TRIG_EN) {
s_config.pd_options[ESP_PD_DOMAIN_RTC_PERIPH] = ESP_PD_OPTION_ON;
} else if (s_config.wakeup_triggers & (RTC_TOUCH_TRIG_EN | RTC_SAR_TRIG_EN)) {
// In both rev. 0 and rev. 1 of ESP32, forcing power up of RTC_PERIPH
// prevents ULP timer and touch FSMs from working correctly.
s_config.pd_options[ESP_PD_DOMAIN_RTC_PERIPH] = ESP_PD_OPTION_OFF;
}
}

View file

@ -17,6 +17,7 @@
#include <stdint.h>
#include "esp_err.h"
#include "driver/gpio.h"
#include "driver/touch_pad.h"
#ifdef __cplusplus
extern "C" {
@ -50,12 +51,28 @@ typedef enum {
ESP_PD_OPTION_AUTO //!< Keep power domain enabled in deep sleep, if it is needed by one of the wakeup options. Otherwise power it down.
} esp_deep_sleep_pd_option_t;
/**
* @brief Deep sleep wakeup cause
*/
typedef enum {
ESP_DEEP_SLEEP_WAKEUP_UNDEFINED, //! Wakeup was not caused by deep sleep
ESP_DEEP_SLEEP_WAKEUP_EXT0, //! Wakeup caused by external signal using RTC_IO
ESP_DEEP_SLEEP_WAKEUP_EXT1, //! Wakeup caused by external signal using RTC_CNTL
ESP_DEEP_SLEEP_WAKEUP_TIMER, //! Wakeup caused by timer
ESP_DEEP_SLEEP_WAKEUP_TOUCHPAD, //! Wakeup caused by touchpad
ESP_DEEP_SLEEP_WAKEUP_ULP, //! Wakeup caused by ULP program
} esp_deep_sleep_wakeup_cause_t;
/**
* @brief Enable wakeup by ULP coprocessor
* @note In revisions 0 and 1 of the ESP32, ULP wakeup source
* can not be used when RTC_PERIPH power domain is forced
* to be powered on (ESP_PD_OPTION_ON) or when ext0 wakeup
* source is used.
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if ULP co-processor is not enabled.
* - ESP_ERR_INVALID_STATE if ULP co-processor is not enabled or if wakeup triggers conflict
*/
esp_err_t esp_deep_sleep_enable_ulp_wakeup();
@ -68,6 +85,29 @@ esp_err_t esp_deep_sleep_enable_ulp_wakeup();
*/
esp_err_t esp_deep_sleep_enable_timer_wakeup(uint64_t time_in_us);
/**
* @brief Enable wakeup by touch sensor
*
* @note In revisions 0 and 1 of the ESP32, touch wakeup source
* can not be used when RTC_PERIPH power domain is forced
* to be powered on (ESP_PD_OPTION_ON) or when ext0 wakeup
* source is used.
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if wakeup triggers conflict
*/
esp_err_t esp_deep_sleep_enable_touchpad_wakeup();
/**
* @brief Get the touch pad which caused wakeup
*
* If wakeup was caused by another source, this function will return TOUCH_PAD_MAX;
*
* @return touch pad which caused wakeup
*/
touch_pad_t esp_deep_sleep_get_touchpad_wakeup_status();
/**
* @brief Enable wakeup using a pin
*
@ -81,6 +121,9 @@ esp_err_t esp_deep_sleep_enable_timer_wakeup(uint64_t time_in_us);
* configured in esp_deep_sleep_start, immediately before
* entering deep sleep.
*
* @note In revisions 0 and 1 of the ESP32, ext0 wakeup source
* can not be used together with touch or ULP wakeup sources.
*
* @param gpio_num GPIO number used as wakeup source. Only GPIOs which are have RTC
* functionality can be used: 0,2,4,12-15,25-27,32-39.
* @param level input level which will trigger wakeup (0=low, 1=high)
@ -88,6 +131,7 @@ esp_err_t esp_deep_sleep_enable_timer_wakeup(uint64_t time_in_us);
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG if the selected GPIO is not an RTC GPIO,
* or the mode is invalid
* - ESP_ERR_INVALID_STATE if wakeup triggers conflict
*/
esp_err_t esp_deep_sleep_enable_ext0_wakeup(gpio_num_t gpio_num, int level);
@ -188,6 +232,15 @@ void esp_deep_sleep(uint64_t time_in_us) __attribute__((noreturn));
*/
void system_deep_sleep(uint64_t time_in_us) __attribute__((noreturn, deprecated));
/**
* @brief Get the source which caused deep sleep wakeup
*
* @return wakeup cause, or ESP_DEEP_SLEEP_WAKEUP_UNDEFINED if reset reason is other than deep sleep reset.
*/
esp_deep_sleep_wakeup_cause_t esp_deep_sleep_get_wakeup_cause();
/**
* @brief Default stub to run on wake from deep sleep.
*

View file

@ -240,7 +240,7 @@
#define RTC_CNTL_TIME_VALID_S 30
/* frequency of RTC slow clock, Hz */
#define RTC_CTNL_SLOWCLK_FREQ 150000
#define RTC_CNTL_SLOWCLK_FREQ 150000
#define RTC_CNTL_TIME0_REG (DR_REG_RTCCNTL_BASE + 0x10)
/* RTC_CNTL_TIME_LO : RO ;bitpos:[31:0] ;default: 32'h0 ; */

View file

@ -0,0 +1,20 @@
#include <stdio.h>
#include "unity.h"
#include "rom/ets_sys.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/sens_reg.h"
TEST_CASE("can control TSENS using registers", "[rtc][ignore]")
{
SET_PERI_REG_BITS(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR, 3, SENS_FORCE_XPD_SAR_S);
SET_PERI_REG_BITS(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_CLK_DIV, 10, SENS_TSENS_CLK_DIV_S);
CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP);
CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_DUMP_OUT);
SET_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP_FORCE);
SET_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP);
ets_delay_us(100);
SET_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_DUMP_OUT);
ets_delay_us(5);
int res = GET_PERI_REG_BITS2(SENS_SAR_SLAVE_ADDR3_REG, SENS_TSENS_OUT, SENS_TSENS_OUT_S);
printf("res=%d\n", res);
}

View file

@ -52,7 +52,7 @@ static uint64_t get_rtc_time_us()
uint64_t low = READ_PERI_REG(RTC_CNTL_TIME0_REG);
uint64_t high = READ_PERI_REG(RTC_CNTL_TIME1_REG);
uint64_t ticks = (high << 32) | low;
return ticks * 100 / (RTC_CTNL_SLOWCLK_FREQ / 10000); // scale RTC_CTNL_SLOWCLK_FREQ to avoid overflow
return ticks * 100 / (RTC_CNTL_SLOWCLK_FREQ / 10000); // scale RTC_CNTL_SLOWCLK_FREQ to avoid overflow
}
#endif // WITH_RTC

View file

@ -81,7 +81,7 @@ extern "C" {
#define B_CMP_L 0 /*!< Branch if R0 is less than an immediate */
#define B_CMP_GE 1 /*!< Branch if R0 is greater than or equal to an immediate */
#define OPCODE_END 9 /*!< Stop executing the program (not implemented yet) */
#define OPCODE_END 9 /*!< Stop executing the program */
#define SUB_OPCODE_END 0 /*!< Stop executing the program and optionally wake up the chip */
#define SUB_OPCODE_SLEEP 1 /*!< Stop executing the program and run it again after selected interval */
@ -222,7 +222,7 @@ typedef union {
struct {
uint32_t dreg : 2; /*!< Register where to store temperature measurement result */
uint32_t wait_delay: 14; /*!< Cycles to wait after measurement is done */
uint32_t cycles: 12; /*!< Cycles used to perform measurement */
uint32_t reserved: 12; /*!< Reserved, set to 0 */
uint32_t opcode: 4; /*!< Opcode (OPCODE_TSENS) */
} tsens; /*!< Format of TSENS instruction */
@ -271,7 +271,12 @@ _Static_assert(sizeof(ulp_insn_t) == 4, "ULP coprocessor instruction size should
.cycles = cycles_ } }
/**
* Halt the coprocessor
* Halt the coprocessor.
*
* This instruction halts the coprocessor, but keeps ULP timer active.
* As such, ULP program will be restarted again by timer.
* To stop the program and prevent the timer from restarting the program,
* use I_END(0) instruction.
*/
#define I_HALT() { .halt = {\
.unused = 0, \
@ -307,7 +312,7 @@ static inline uint32_t SOC_REG_TO_ULP_PERIPH_SEL(uint32_t reg) {
* This instruction can access RTC_CNTL_, RTC_IO_, and SENS_ peripheral registers.
*/
#define I_WR_REG(reg, low_bit, high_bit, val) {.wr_reg = {\
.addr = reg & 0xff, \
.addr = (reg & 0xff) / sizeof(uint32_t), \
.periph_sel = SOC_REG_TO_ULP_PERIPH_SEL(reg), \
.data = val, \
.low = low_bit, \
@ -320,8 +325,8 @@ static inline uint32_t SOC_REG_TO_ULP_PERIPH_SEL(uint32_t reg) {
* R0 = reg[high_bit : low_bit]
* This instruction can access RTC_CNTL_, RTC_IO_, and SENS_ peripheral registers.
*/
#define I_RD_REG(reg, low_bit, high_bit, val) {.wr_reg = {\
.addr = reg & 0xff, \
#define I_RD_REG(reg, low_bit, high_bit) {.rd_reg = {\
.addr = (reg & 0xff) / sizeof(uint32_t), \
.periph_sel = SOC_REG_TO_ULP_PERIPH_SEL(reg), \
.unused = 0, \
.low = low_bit, \
@ -329,25 +334,105 @@ static inline uint32_t SOC_REG_TO_ULP_PERIPH_SEL(uint32_t reg) {
.opcode = OPCODE_RD_REG } }
/**
* End program.
* Set or clear a bit in the peripheral register.
*
* If wake == 1, wake up main CPU.
* Sets bit (1 << shift) of register reg to value val.
* This instruction can access RTC_CNTL_, RTC_IO_, and SENS_ peripheral registers.
*/
#define I_END(wake) { .end = { \
.wakeup = wake, \
#define I_WR_REG_BIT(reg, shift, val) I_WR_REG(reg, shift, shift, val)
/**
* Wake the SoC from deep sleep.
*
* This instruction initiates wake up from deep sleep.
* Use esp_deep_sleep_enable_ulp_wakeup to enable deep sleep wakeup
* triggered by the ULP before going into deep sleep.
* Note that ULP program will still keep running until the I_HALT
* instruction, and it will still be restarted by timer at regular
* intervals, even when the SoC is woken up.
*
* To stop the ULP program, use I_HALT instruction.
*
* To disable the timer which start ULP program, use I_END()
* instruction. I_END instruction clears the
* RTC_CNTL_ULP_CP_SLP_TIMER_EN_S bit of RTC_CNTL_STATE0_REG
* register, which controls the ULP timer.
*/
#define I_WAKE() { .end = { \
.wakeup = 1, \
.unused = 0, \
.sub_opcode = SUB_OPCODE_END, \
.opcode = OPCODE_END } }
/**
* Stop ULP program timer.
*
* This is a convenience macro which disables the ULP program timer.
* Once this instruction is used, ULP program will not be restarted
* anymore until ulp_run function is called.
*
* ULP program will continue running after this instruction. To stop
* the currently running program, use I_HALT().
*/
#define I_END() \
I_WR_REG_BIT(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN_S, 0)
/**
* Select the time interval used to run ULP program.
*
* This instructions selects which of the SENS_SLEEP_CYCLES_Sx
* registers' value is used by the ULP program timer.
* When the ULP program stops at I_HALT instruction, ULP program
* timer start counting. When the counter reaches the value of
* the selected SENS_SLEEP_CYCLES_Sx register, ULP program
* start running again from the start address (passed to the ulp_run
* function).
* There are 5 SENS_SLEEP_CYCLES_Sx registers, so 0 <= timer_idx < 5.
*
* By default, SENS_SLEEP_CYCLES_S0 register is used by the ULP
* program timer.
*/
#define I_SLEEP_CYCLE_SEL(timer_idx) { .sleep = { \
.cycle_sel = timer_idx, \
.unused = 0, \
.sub_opcode = SUB_OPCODE_SLEEP, \
.opcode = OPCODE_END } }
/**
* Perform temperature sensor measurement and store it into reg_dest.
*
* Delay can be set between 1 and ((1 << 14) - 1). Higher values give
* higher measurement resolution.
*/
#define I_TSENS(reg_dest, delay) { .tsens = { \
.dreg = reg_dest, \
.wait_delay = delay, \
.reserved = 0, \
.opcode = OPCODE_TSENS } }
/**
* Perform ADC measurement and store result in reg_dest.
*
* adc_idx selects ADC (0 or 1).
* pad_idx selects ADC pad (0 - 7).
*/
#define I_ADC(reg_dest, adc_idx, pad_idx) { .adc = {\
.dreg = reg_dest, \
.mux = pad_idx + 1, \
.sar_sel = adc_idx, \
.unused1 = 0, \
.cycles = 0, \
.unused2 = 0, \
.opcode = OPCODE_ADC } }
/**
* Store value from register reg_val into RTC memory.
*
* The value is written to an offset calculated by adding value of
* reg_addr register and offset_ field (this offset is expressed in 32-bit words).
* 32 bits written to RTC memory are built as follows:
* - 5 MSBs are zero
* - next 11 bits hold the PC of current instruction, expressed in 32-bit words
* - next 16 bits hold the actual value to be written
* - bits [31:21] hold the PC of current instruction, expressed in 32-bit words
* - bits [20:16] = 5'b1
* - bits [15:0] are assigned the contents of reg_val
*
* RTC_SLOW_MEM[addr + offset_] = { 5'b0, insn_PC[10:0], val[15:0] }
*/

View file

@ -112,7 +112,9 @@ TEST_CASE("ulp wakeup test", "[ulp][ignore]")
I_MOVI(R2, 42),
I_MOVI(R3, 15),
I_ST(R2, R3, 0),
I_END(1)
I_WAKE(),
I_END(),
I_HALT()
};
size_t size = sizeof(program)/sizeof(ulp_insn_t);
ulp_process_macros_and_load(0, program, &size);
@ -121,6 +123,100 @@ TEST_CASE("ulp wakeup test", "[ulp][ignore]")
esp_deep_sleep_start();
}
TEST_CASE("ulp can write and read peripheral registers", "[ulp]")
{
assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 260 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig");
CLEAR_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN);
memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM);
REG_WRITE(RTC_CNTL_STORE1_REG, 0x89abcdef);
const ulp_insn_t program[] = {
I_MOVI(R1, 64),
I_RD_REG(RTC_CNTL_STORE1_REG, 0, 15),
I_ST(R0, R1, 0),
I_RD_REG(RTC_CNTL_STORE1_REG, 4, 11),
I_ST(R0, R1, 1),
I_RD_REG(RTC_CNTL_STORE1_REG, 16, 31),
I_ST(R0, R1, 2),
I_RD_REG(RTC_CNTL_STORE1_REG, 20, 27),
I_ST(R0, R1, 3),
I_WR_REG(RTC_CNTL_STORE0_REG, 0, 7, 0x89),
I_WR_REG(RTC_CNTL_STORE0_REG, 8, 15, 0xab),
I_WR_REG(RTC_CNTL_STORE0_REG, 16, 23, 0xcd),
I_WR_REG(RTC_CNTL_STORE0_REG, 24, 31, 0xef),
I_LD(R0, R1, 4),
I_ADDI(R0, R0, 1),
I_ST(R0, R1, 4),
I_END(),
I_HALT()
};
size_t size = sizeof(program)/sizeof(ulp_insn_t);
TEST_ESP_OK(ulp_process_macros_and_load(0, program, &size));
TEST_ESP_OK(ulp_run(0));
vTaskDelay(100/portTICK_PERIOD_MS);
TEST_ASSERT_EQUAL_HEX32(0xefcdab89, REG_READ(RTC_CNTL_STORE0_REG));
TEST_ASSERT_EQUAL_HEX16(0xcdef, RTC_SLOW_MEM[64] & 0xffff);
TEST_ASSERT_EQUAL_HEX16(0xde, RTC_SLOW_MEM[65] & 0xffff);
TEST_ASSERT_EQUAL_HEX16(0x89ab, RTC_SLOW_MEM[66] & 0xffff);
TEST_ASSERT_EQUAL_HEX16(0x9a, RTC_SLOW_MEM[67] & 0xffff);
TEST_ASSERT_EQUAL_HEX32(1 | (15 << 21) | (1 << 16), RTC_SLOW_MEM[68]);
}
TEST_CASE("ULP I_WR_REG instruction test", "[ulp]")
{
assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 260 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig");
memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM);
typedef struct {
int low;
int width;
} wr_reg_test_item_t;
const wr_reg_test_item_t test_items[] = {
{0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}, {0, 8},
{3, 1}, {3, 2}, {3, 3}, {3, 4}, {3, 5}, {3, 6}, {3, 7}, {3, 8},
{15, 1}, {15, 2}, {15, 3}, {15, 4}, {15, 5}, {15, 6}, {15, 7}, {15, 8},
{16, 1}, {16, 2}, {16, 3}, {16, 4}, {16, 5}, {16, 6}, {16, 7}, {16, 8},
{18, 1}, {18, 2}, {18, 3}, {18, 4}, {18, 5}, {18, 6}, {18, 7}, {18, 8},
{24, 1}, {24, 2}, {24, 3}, {24, 4}, {24, 5}, {24, 6}, {24, 7}, {24, 8},
};
const size_t test_items_count =
sizeof(test_items)/sizeof(test_items[0]);
for (size_t i = 0; i < test_items_count; ++i) {
const uint32_t mask = (uint32_t) (((1ULL << test_items[i].width) - 1) << test_items[i].low);
const uint32_t not_mask = ~mask;
printf("#%2d: low: %2d width: %2d mask: %08x expected: %08x ", i,
test_items[i].low, test_items[i].width,
mask, not_mask);
REG_WRITE(RTC_CNTL_STORE0_REG, 0xffffffff);
REG_WRITE(RTC_CNTL_STORE1_REG, 0x00000000);
const ulp_insn_t program[] = {
I_WR_REG(RTC_CNTL_STORE0_REG,
test_items[i].low,
test_items[i].low + test_items[i].width - 1,
0),
I_WR_REG(RTC_CNTL_STORE1_REG,
test_items[i].low,
test_items[i].low + test_items[i].width - 1,
0xff & ((1 << test_items[i].width) - 1)),
I_END(),
I_HALT()
};
size_t size = sizeof(program)/sizeof(ulp_insn_t);
ulp_process_macros_and_load(0, program, &size);
ulp_run(0);
vTaskDelay(10/portTICK_PERIOD_MS);
uint32_t clear = REG_READ(RTC_CNTL_STORE0_REG);
uint32_t set = REG_READ(RTC_CNTL_STORE1_REG);
printf("clear: %08x set: %08x\n", clear, set);
TEST_ASSERT_EQUAL_HEX32(not_mask, clear);
TEST_ASSERT_EQUAL_HEX32(mask, set);
}
}
TEST_CASE("ulp controls RTC_IO", "[ulp][ignore]")
{
assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 260 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig");
@ -149,7 +245,9 @@ TEST_CASE("ulp controls RTC_IO", "[ulp][ignore]")
M_LABEL(5),
M_BX(4),
M_LABEL(6),
I_END(1) // wake up the SoC
I_WAKE(), // wake up the SoC
I_END(), // stop ULP program timer
I_HALT()
};
const gpio_num_t led_gpios[] = {
GPIO_NUM_2,
@ -168,3 +266,198 @@ TEST_CASE("ulp controls RTC_IO", "[ulp][ignore]")
esp_deep_sleep_start();
}
TEST_CASE("ulp power consumption in deep sleep", "[ulp]")
{
assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 4 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig");
ulp_insn_t insn = I_HALT();
RTC_SLOW_MEM[0] = *(uint32_t*) &insn;
REG_WRITE(SENS_ULP_CP_SLEEP_CYC0_REG, 0x8000);
ulp_run(0);
esp_deep_sleep_enable_ulp_wakeup();
esp_deep_sleep_enable_timer_wakeup(10 * 1000000);
esp_deep_sleep_start();
}
TEST_CASE("ulp timer setting", "[ulp]")
{
/*
* Run a simple ULP program which increments the counter, for one second.
* Program calls I_HALT each time and gets restarted by the timer.
* Compare the expected number of times the program runs with the actual.
*/
assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 32 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig");
memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM);
const int offset = 6;
const ulp_insn_t program[] = {
I_MOVI(R1, offset), // r1 <- offset
I_LD(R2, R1, 0), // load counter
I_ADDI(R2, R2, 1), // counter += 1
I_ST(R2, R1, 0), // save counter
I_HALT(),
};
size_t size = sizeof(program)/sizeof(ulp_insn_t);
TEST_ESP_OK(ulp_process_macros_and_load(0, program, &size));
assert(offset >= size && "data offset needs to be greater or equal to program size");
TEST_ESP_OK(ulp_run(0));
// disable the ULP program timer — we will enable it later
CLEAR_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN);
const uint32_t cycles_to_test[] = {0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000};
const size_t tests_count = sizeof(cycles_to_test) / sizeof(cycles_to_test[0]);
for (size_t i = 0; i < tests_count; ++i) {
// zero out the counter
RTC_SLOW_MEM[offset] = 0;
// set the number of slow clock cycles
REG_WRITE(SENS_ULP_CP_SLEEP_CYC0_REG, cycles_to_test[i]);
// enable the timer and wait for a second
SET_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN);
vTaskDelay(1000 / portTICK_PERIOD_MS);
// get the counter value and stop the timer
uint32_t counter = RTC_SLOW_MEM[offset] & 0xffff;
CLEAR_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN);
// compare the actual and expected numbers of iterations of ULP program
float expected_period = (cycles_to_test[i] + 16) / (float) RTC_CNTL_SLOWCLK_FREQ + 5 / 8e6f;
float error = 1.0f - counter * expected_period;
printf("%u\t%u\t%.01f\t%.04f\n", cycles_to_test[i], counter, 1.0f / expected_period, error);
// Should be within 15%
TEST_ASSERT_INT_WITHIN(15, 0, (int) error * 100);
// Note: currently RTC_CNTL_SLOWCLK_FREQ is ballpark value — we need to determine it
// Precisely by running calibration similar to the one done in deep sleep.
// This may cause the test to fail on some chips which have the slow clock frequency
// way off.
}
}
TEST_CASE("ulp can use TSENS in deep sleep", "[ulp][ignore]")
{
assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 260 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig");
hexdump(RTC_SLOW_MEM, CONFIG_ULP_COPROC_RESERVE_MEM / 4);
printf("\n\n");
memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM);
// Allow TSENS to be controlled by the ULP
SET_PERI_REG_BITS(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_CLK_DIV, 10, SENS_TSENS_CLK_DIV_S);
SET_PERI_REG_BITS(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR, 3, SENS_FORCE_XPD_SAR_S);
CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP);
CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_DUMP_OUT);
CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP_FORCE);
// data start offset
size_t offset = 20;
// number of samples to collect
RTC_SLOW_MEM[offset] = (CONFIG_ULP_COPROC_RESERVE_MEM) / 4 - offset - 8;
// sample counter
RTC_SLOW_MEM[offset + 1] = 0;
const ulp_insn_t program[] = {
I_MOVI(R1, offset), // r1 <- offset
I_LD(R2, R1, 1), // r2 <- counter
I_LD(R3, R1, 0), // r3 <- length
I_SUBI(R3, R3, 1), // end = length - 1
I_SUBR(R3, R3, R2), // r3 = length - counter
M_BXF(1), // if overflow goto 1:
I_WR_REG(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR_S, SENS_FORCE_XPD_SAR_S + 1, 3),
I_TSENS(R0, 16383), // r0 <- tsens
I_WR_REG(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR_S, SENS_FORCE_XPD_SAR_S + 1, 0),
I_ST(R0, R2, offset + 4),
I_ADDI(R2, R2, 1), // counter += 1
I_ST(R2, R1, 1), // save counter
I_HALT(), // enter sleep
M_LABEL(1), // done with measurements
I_END(), // stop ULP timer
I_WAKE(), // initiate wakeup
I_HALT()
};
size_t size = sizeof(program)/sizeof(ulp_insn_t);
TEST_ESP_OK(ulp_process_macros_and_load(0, program, &size));
assert(offset >= size);
TEST_ESP_OK(ulp_run(0));
esp_deep_sleep_enable_timer_wakeup(4000000);
esp_deep_sleep_enable_ulp_wakeup();
esp_deep_sleep_start();
}
TEST_CASE("can use ADC in deep sleep", "[ulp][ignore]")
{
assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 260 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig");
hexdump(RTC_SLOW_MEM, CONFIG_ULP_COPROC_RESERVE_MEM / 4);
printf("\n\n");
memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM);
SET_PERI_REG_BITS(SENS_SAR_START_FORCE_REG, SENS_SAR1_BIT_WIDTH, 3, SENS_SAR1_BIT_WIDTH_S);
SET_PERI_REG_BITS(SENS_SAR_START_FORCE_REG, SENS_SAR2_BIT_WIDTH, 3, SENS_SAR2_BIT_WIDTH_S);
SET_PERI_REG_BITS(SENS_SAR_READ_CTRL_REG, SENS_SAR1_SAMPLE_BIT, 0x3, SENS_SAR1_SAMPLE_BIT_S);
SET_PERI_REG_BITS(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_SAMPLE_BIT, 0x3, SENS_SAR2_SAMPLE_BIT_S);
CLEAR_PERI_REG_MASK(SENS_SAR_MEAS_START2_REG, SENS_MEAS2_START_FORCE);
CLEAR_PERI_REG_MASK(SENS_SAR_MEAS_START1_REG, SENS_MEAS1_START_FORCE);
SET_PERI_REG_BITS(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR, 0, SENS_FORCE_XPD_SAR_S);
SET_PERI_REG_BITS(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_AMP, 2, SENS_FORCE_XPD_AMP_S);
// SAR1 invert result
SET_PERI_REG_MASK(SENS_SAR_READ_CTRL_REG, SENS_SAR1_DATA_INV);
SET_PERI_REG_MASK(SENS_SAR_READ_CTRL_REG, SENS_SAR2_DATA_INV);
// const int adc = 1;
// const int channel = 1;
// const int atten = 3;
// const int gpio_num = 0;
const int adc = 0;
const int channel = 0;
const int atten = 0;
const int gpio_num = 36;
rtc_gpio_init(gpio_num);
CLEAR_PERI_REG_MASK(SENS_SAR_MEAS_START1_REG, SENS_SAR1_EN_PAD_FORCE_M);
CLEAR_PERI_REG_MASK(SENS_SAR_MEAS_START2_REG, SENS_SAR2_EN_PAD_FORCE_M);
SET_PERI_REG_BITS(SENS_SAR_ATTEN1_REG, 3, atten, 2 * channel); //set SAR1 attenuation
SET_PERI_REG_BITS(SENS_SAR_ATTEN2_REG, 3, atten, 2 * channel); //set SAR2 attenuation
// data start offset
size_t offset = 20;
// number of samples to collect
RTC_SLOW_MEM[offset] = (CONFIG_ULP_COPROC_RESERVE_MEM) / 4 - offset - 8;
// sample counter
RTC_SLOW_MEM[offset + 1] = 0;
const ulp_insn_t program[] = {
I_MOVI(R1, offset), // r1 <- offset
I_LD(R2, R1, 1), // r2 <- counter
I_LD(R3, R1, 0), // r3 <- length
I_SUBI(R3, R3, 1), // end = length - 1
I_SUBR(R3, R3, R2), // r3 = length - counter
M_BXF(1), // if overflow goto 1:
I_ADC(R0, adc, channel), // r0 <- ADC
I_ST(R0, R2, offset + 4),
I_ADDI(R2, R2, 1), // counter += 1
I_ST(R2, R1, 1), // save counter
I_HALT(),
M_LABEL(1), // done with measurements
I_END(), // stop ULP program timer
I_HALT()
};
size_t size = sizeof(program)/sizeof(ulp_insn_t);
TEST_ESP_OK(ulp_process_macros_and_load(0, program, &size));
assert(offset >= size);
TEST_ESP_OK(ulp_run(0));
esp_deep_sleep_enable_timer_wakeup(4000000);
esp_deep_sleep_start();
}

View file

@ -265,12 +265,16 @@ esp_err_t ulp_run(uint32_t entry_point)
{
// disable ULP timer
CLEAR_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN);
// wait for at least 1 RTC_SLOW_CLK cycle
ets_delay_us(10);
// set entry point
SET_PERI_REG_BITS(SENS_SAR_START_FORCE_REG, SENS_PC_INIT_V, entry_point, SENS_PC_INIT_S);
// disable force start
CLEAR_PERI_REG_MASK(SENS_SAR_START_FORCE_REG, SENS_ULP_CP_FORCE_START_TOP_M);
// make sure voltage is raised when RTC 8MCLK is enabled
SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_BIAS_I2C_FOLW_8M);
SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_BIAS_CORE_FOLW_8M);
SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_BIAS_SLEEP_FOLW_8M);
// enable ULP timer
SET_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN);
return ESP_OK;

View file

@ -26,6 +26,15 @@ The following function can be used to enable deep sleep wakeup using a timer.
.. doxygenfunction:: esp_deep_sleep_enable_timer_wakeup
Touch pad
^^^^^^^^^
RTC IO module contains logic to trigger wakeup when a touch sensor interrupt occurs. You need to configure the touch pad interrupt before the chip starts deep sleep.
Revisions 0 and 1 of the ESP32 only support this wakeup mode when RTC peripherals are not forced to be powered on (i.e. ESP_PD_DOMAIN_RTC_PERIPH should be set to ESP_PD_OPTION_AUTO).
.. doxygenfunction:: esp_deep_sleep_enable_touchpad_wakeup
External wakeup (ext0)
^^^^^^^^^^^^^^^^^^^^^^
@ -34,6 +43,8 @@ RTC IO module contains logic to trigger wakeup when one of RTC GPIOs is set to a
Because RTC IO module is enabled in this mode, internal pullup or pulldown resistors can also be used. They need to be configured by the application using ``rtc_gpio_pullup_en`` and ``rtc_gpio_pulldown_en`` functions, before calling ``esp_deep_sleep_start``.
In revisions 0 and 1 of the ESP32, this wakeup source is incompatible with ULP and touch wakeup sources.
.. doxygenfunction:: esp_deep_sleep_enable_ext0_wakeup
External wakeup (ext1)
@ -41,8 +52,8 @@ External wakeup (ext1)
RTC controller contains logic to trigger wakeup using multiple RTC GPIOs. One of the two logic functions can be used to trigger wakeup:
- wake up if any of the selected pins is low
- wake up if all the selected pins are high
- wake up if any of the selected pins is high (``ESP_EXT1_WAKEUP_ANY_HIGH``)
- wake up if all the selected pins are low (``ESP_EXT1_WAKEUP_ALL_LOW``)
This wakeup source is implemented by the RTC controller. As such, RTC peripherals and RTC memories can be powered off in this mode. However, if RTC peripherals are powered down, internal pullup and pulldown resistors will be disabled. To use internal pullup or pulldown resistors, request RTC peripherals power domain to be kept on during deep sleep, and configure pullup/pulldown resistors using ``rtc_gpio_`` functions, before entering deep sleep::
@ -60,7 +71,9 @@ The following function can be used to enable this wakeup mode:
ULP coprocessor wakeup
^^^^^^^^^^^^^^^^^^^^^^
ULP coprocessor can run while the chip is in deep sleep, and may be used to poll sensors, monitor ADC or touch sensor values, and wake up the chip when a specific event is detected. ULP coprocessor is part of RTC peripherals power domain, and it runs the program stored in RTC slow memeory. Therefore RTC peripherals and RTC slow memory will be powered on during deep sleep if this wakeup mode is requested.
ULP coprocessor can run while the chip is in deep sleep, and may be used to poll sensors, monitor ADC or touch sensor values, and wake up the chip when a specific event is detected. ULP coprocessor is part of RTC peripherals power domain, and it runs the program stored in RTC slow memeory. RTC slow memory will be powered on during deep sleep if this wakeup mode is requested. RTC peripherals will be automatically powered on before ULP coprocessor starts running the program; once the program stops running, RTC peripherals are automatically powered down again.
Revisions 0 and 1 of the ESP32 only support this wakeup mode when RTC peripherals are not forced to be powered on (i.e. ESP_PD_DOMAIN_RTC_PERIPH should be set to ESP_PD_OPTION_AUTO).
The following function can be used to enable this wakeup mode:
@ -71,7 +84,7 @@ Power-down of RTC peripherals and memories
By default, ``esp_deep_sleep_start`` function will power down all RTC power domains which are not needed by the enabled wakeup sources. To override this behaviour, the following function is provided:
Note: on the first revision of the ESP32, RTC fast memory will always be kept enabled in deep sleep, so that the deep sleep stub can run after reset. This can be overriden, if the application doesn't need clean reset behaviour after deep sleep.
Note: in revision 0 of the ESP32, RTC fast memory will always be kept enabled in deep sleep, so that the deep sleep stub can run after reset. This can be overriden, if the application doesn't need clean reset behaviour after deep sleep.
If some variables in the program are placed into RTC slow memory (for example, using ``RTC_DATA_ATTR`` attribute), RTC slow memory will be kept powered on by default. This can be overriden using ``esp_deep_sleep_pd_config`` function, if desired.
@ -87,8 +100,20 @@ The following function can be used to enter deep sleep once wakeup sources are c
.. doxygenfunction:: esp_deep_sleep_start
Checking deep sleep wakeup cause
--------------------------------
The following function can be used to check which wakeup source has triggered wakeup from deep sleep mode. For touch pad and ext1 wakeup sources, it is possible to identify pin or touch pad which has caused wakeup.
.. doxygenfunction:: esp_deep_sleep_get_wakeup_cause
.. doxygenenum:: esp_deep_sleep_wakeup_cause_t
.. doxygenfunction:: esp_deep_sleep_get_touchpad_wakeup_status
.. doxygenfunction:: esp_deep_sleep_get_ext1_wakeup_status
Application Example
-------------------
Implementation of basic functionality of deep sleep is shown in :example:`protocols/sntp` example, where ESP module is periodically waken up to retrive time from NTP server.
More extensive example in :example:`system/deep_sleep` illustrates usage of various deep sleep wakeup triggers and ULP coprocessor programming.

View file

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := deep_sleep
include $(IDF_PATH)/make/project.mk

View file

@ -0,0 +1,11 @@
# Example: deep sleep
This example illustrates usage of deep sleep mode and various wakeup sources.
The following wake up sources are configured:
- Timer: wake up the chip in 20 seconds
- EXT1: wake up the chip if any of the two buttons are pressed (GPIO25, GPIO26)
- Touch: wake up the chip if any of the touch pads are pressed (GPIO32, GPIO33)
- ULP: wake up when the chip temperature changes by more than ~5 degrees Celsius (this value hasn't been characterized exactly yet).

View file

@ -0,0 +1,20 @@
menu "Example Configuration"
config ENABLE_TOUCH_WAKEUP
bool "Enable touch wake up"
default y
help
This option enables wake up from deep sleep using touch pads
TOUCH8 and TOUCH9, which correspond to GPIO33 and GPIO32.
config ENABLE_ULP_TEMPERATURE_WAKEUP
bool "Enable temperature monitoring by ULP"
default y
help
This option enables wake up from deep sleep using ULP.
ULP program monitors the on-chip temperature sensor and
wakes up the chip when the temperature goes outside of
the window defined by the initial temperature and a threshold
around it.
endmenu

View file

@ -0,0 +1,3 @@
#
# Main Makefile. This is basically the same as a component makefile.
#

View file

@ -0,0 +1,296 @@
/* Deep sleep wake up example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_deep_sleep.h"
#include "esp_log.h"
#include "esp32/ulp.h"
#include "driver/touch_pad.h"
#include "driver/adc.h"
#include "driver/rtc_io.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/sens_reg.h"
static RTC_DATA_ATTR struct timeval sleep_enter_time;
#ifdef CONFIG_ENABLE_ULP_TEMPERATURE_WAKEUP
/*
* Offset (in 32-bit words) in RTC Slow memory where the data is placed
* by the ULP coprocessor. It can be chosen to be any value greater or equal
* to ULP program size, and less than the CONFIG_ULP_COPROC_RESERVE_MEM/4 - 6,
* where 6 is the number of words used by the ULP coprocessor.
*/
#define ULP_DATA_OFFSET 36
_Static_assert(ULP_DATA_OFFSET < CONFIG_ULP_COPROC_RESERVE_MEM/4 - 6,
"ULP_DATA_OFFSET is set too high, or CONFIG_ULP_COPROC_RESERVE_MEM is not sufficient");
/**
* @brief Start ULP temperature monitoring program
*
* This function loads a program into the RTC Slow memory and starts up the ULP.
* The program monitors on-chip temperature sensor and wakes up the SoC when
* the temperature goes lower or higher than certain thresholds.
*/
static void start_ulp_temperature_monitoring();
/**
* @brief Utility function which reads data written by ULP program
*
* @param offset offset from ULP_DATA_OFFSET in RTC Slow memory, in words
* @return lower 16-bit part of the word writable by the ULP
*/
static inline uint16_t ulp_data_read(size_t offset)
{
return RTC_SLOW_MEM[ULP_DATA_OFFSET + offset] & 0xffff;
}
/**
* @brief Utility function which writes data to be ready by ULP program
*
* @param offset offset from ULP_DATA_OFFSET in RTC Slow memory, in words
* @param value lower 16-bit part of the word to be stored
*/
static inline void ulp_data_write(size_t offset, uint16_t value)
{
RTC_SLOW_MEM[ULP_DATA_OFFSET + offset] = value;
}
#endif // CONFIG_ENABLE_ULP_TEMPERATURE_WAKEUP
#ifdef CONFIG_ENABLE_TOUCH_WAKEUP
static void calibrate_touch_pad(touch_pad_t pad);
#endif
void app_main()
{
struct timeval now;
gettimeofday(&now, NULL);
int sleep_time_ms = (now.tv_sec - sleep_enter_time.tv_sec) * 1000 + (now.tv_usec - sleep_enter_time.tv_usec) / 1000;
switch (esp_deep_sleep_get_wakeup_cause()) {
case ESP_DEEP_SLEEP_WAKEUP_EXT1: {
uint64_t wakeup_pin_mask = esp_deep_sleep_get_ext1_wakeup_status();
if (wakeup_pin_mask != 0) {
int pin = __builtin_ffsll(wakeup_pin_mask) - 1;
printf("Wake up from GPIO %d\n", pin);
} else {
printf("Wake up from GPIO\n");
}
break;
}
case ESP_DEEP_SLEEP_WAKEUP_TIMER: {
printf("Wake up from timer. Time spent in deep sleep: %dms\n", sleep_time_ms);
break;
}
#ifdef CONFIG_ENABLE_TOUCH_WAKEUP
case ESP_DEEP_SLEEP_WAKEUP_TOUCHPAD: {
printf("Wake up from touch on pad %d\n", esp_deep_sleep_get_touchpad_wakeup_status());
break;
}
#endif // CONFIG_ENABLE_TOUCH_WAKEUP
#ifdef CONFIG_ENABLE_ULP_TEMPERATURE_WAKEUP
case ESP_DEEP_SLEEP_WAKEUP_ULP: {
printf("Wake up from ULP\n");
int16_t diff_high = (int16_t) ulp_data_read(3);
int16_t diff_low = (int16_t) ulp_data_read(4);
if (diff_high < 0) {
printf("High temperature alarm was triggered\n");
} else if (diff_low < 0) {
printf("Low temperature alarm was triggered\n");
} else {
assert(false && "temperature has stayed within limits, but got ULP wakeup\n");
}
break;
}
#endif // CONFIG_ENABLE_ULP_TEMPERATURE_WAKEUP
case ESP_DEEP_SLEEP_WAKEUP_UNDEFINED:
default:
printf("Not a deep sleep reset\n");
}
#ifdef CONFIG_ENABLE_ULP_TEMPERATURE_WAKEUP
if (esp_deep_sleep_get_wakeup_cause() != ESP_DEEP_SLEEP_WAKEUP_UNDEFINED) {
printf("ULP did %d temperature measurements in %d ms\n", ulp_data_read(1), sleep_time_ms);
printf("Initial T=%d, latest T=%d\n", ulp_data_read(0), ulp_data_read(2));
}
#endif // CONFIG_ENABLE_ULP_TEMPERATURE_WAKEUP
vTaskDelay(1000 / portTICK_PERIOD_MS);
const int wakeup_time_sec = 20;
printf("Enabling timer wakeup, %ds\n", wakeup_time_sec);
esp_deep_sleep_enable_timer_wakeup(wakeup_time_sec * 1000000);
const int ext_wakeup_pin_1 = 25;
const uint64_t ext_wakeup_pin_1_mask = 1ULL << ext_wakeup_pin_1;
const int ext_wakeup_pin_2 = 26;
const uint64_t ext_wakeup_pin_2_mask = 1ULL << ext_wakeup_pin_2;
printf("Enabling EXT1 wakeup on pins GPIO%d, GPIO%d\n", ext_wakeup_pin_1, ext_wakeup_pin_2);
esp_deep_sleep_enable_ext1_wakeup(ext_wakeup_pin_1_mask | ext_wakeup_pin_2_mask, ESP_EXT1_WAKEUP_ANY_HIGH);
#ifdef CONFIG_ENABLE_TOUCH_WAKEUP
touch_pad_init();
calibrate_touch_pad(TOUCH_PAD_NUM8);
calibrate_touch_pad(TOUCH_PAD_NUM9);
printf("Enabling touch pad wakeup\n");
esp_deep_sleep_enable_touchpad_wakeup();
#endif // CONFIG_ENABLE_TOUCH_WAKEUP
#ifdef CONFIG_ENABLE_ULP_TEMPERATURE_WAKEUP
printf("Enabling ULP wakeup\n");
esp_deep_sleep_enable_ulp_wakeup();
#endif
printf("Entering deep sleep\n");
gettimeofday(&sleep_enter_time, NULL);
#ifdef CONFIG_ENABLE_ULP_TEMPERATURE_WAKEUP
start_ulp_temperature_monitoring();
#endif
esp_deep_sleep_start();
}
#ifdef CONFIG_ENABLE_TOUCH_WAKEUP
static void calibrate_touch_pad(touch_pad_t pad)
{
touch_pad_config(pad, 1000);
int avg = 0;
const size_t calibration_count = 128;
for (int i = 0; i < calibration_count; ++i) {
uint16_t val;
touch_pad_read(pad, &val);
avg += val;
}
avg /= calibration_count;
const int min_reading = 300;
if (avg < min_reading) {
printf("Touch pad #%d average reading is too low: %d (expecting at least %d). "
"Not using for deep sleep wakeup.\n", pad, avg, min_reading);
touch_pad_config(pad, 0);
} else {
int threshold = avg - 100;
printf("Touch pad #%d average: %d, wakeup threshold set to %d.\n", pad, avg, threshold);
touch_pad_config(pad, threshold);
}
}
#endif // CONFIG_ENABLE_TOUCH_WAKEUP
#ifdef CONFIG_ENABLE_ULP_TEMPERATURE_WAKEUP
static void start_ulp_temperature_monitoring()
{
/*
* This ULP program monitors the on-chip temperature sensor and wakes the chip up when
* the temperature goes outside of certain window.
* When the program runs for the first time, it saves the temperature measurement,
* it is considered initial temperature (T0).
*
* On each subsequent run, temperature measured and compared to T0.
* If the measured value is higher than T0 + max_temp_diff or lower than T0 - max_temp_diff,
* the chip is woken up from deep sleep.
*/
/* Temperature difference threshold which causes wakeup
* With settings here (TSENS_CLK_DIV=2, 8000 cycles),
* TSENS measurement is done in units of 0.73 degrees Celsius.
* Therefore, max_temp_diff below is equivalent to ~2.2 degrees Celsius.
*/
const int16_t max_temp_diff = 3;
// Number of measurements ULP should do per second
const uint32_t measurements_per_sec = 5;
// Allow TSENS to be controlled by the ULP
SET_PERI_REG_BITS(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_CLK_DIV, 2, SENS_TSENS_CLK_DIV_S);
SET_PERI_REG_BITS(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR, 3, SENS_FORCE_XPD_SAR_S);
CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP);
CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_DUMP_OUT);
CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP_FORCE);
// Clear the part of RTC_SLOW_MEM reserved for the ULP. Makes debugging easier.
memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM);
// The first word of memory (at data offset) is used to store the initial temperature (T0)
// Zero it out here, then ULP will update it on the first run.
ulp_data_write(0, 0);
// The second word is used to store measurement count, zero it out as well.
ulp_data_write(1, 0);
const ulp_insn_t program[] = {
// load data offset into R2
I_MOVI(R2, ULP_DATA_OFFSET),
// load/increment/store measurement counter using R1
I_LD(R1, R2, 1),
I_ADDI(R1, R1, 1),
I_ST(R1, R2, 1),
// enable temperature sensor
I_WR_REG(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR_S, SENS_FORCE_XPD_SAR_S + 1, 3),
// do temperature measurement and store result in R3
I_TSENS(R3, 8000),
// disable temperature sensor
I_WR_REG(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR_S, SENS_FORCE_XPD_SAR_S + 1, 0),
// Save current measurement at offset+2
I_ST(R3, R2, 2),
// load initial value into R0
I_LD(R0, R2, 0),
// if threshold value >=1 (i.e. initialized), goto 1
M_BGE(1, 1),
// otherwise, save the current value as initial (T0)
I_MOVR(R0, R3),
I_ST(R0, R2, 0),
M_LABEL(1),
// check if the temperature is greater or equal (T0 + max_temp_diff)
// uses R1 as scratch register, difference is saved at offset + 3
I_ADDI(R1, R0, max_temp_diff - 1),
I_SUBR(R1, R1, R3),
I_ST(R1, R2, 3),
M_BXF(2),
// check if the temperature is less or equal (T0 - max_temp_diff)
// uses R1 as scratch register, difference is saved at offset + 4
I_SUBI(R1, R0, max_temp_diff - 1),
I_SUBR(R1, R3, R1),
I_ST(R1, R2, 4),
M_BXF(2),
// temperature is within (T0 - max_temp_diff; T0 + max_temp_diff)
// stop ULP until the program timer starts it again
I_HALT(),
M_LABEL(2),
// temperature is out of bounds
// disable ULP program timer
I_WR_REG_BIT(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN_S, 0),
// initiate wakeup of the SoC
I_WAKE(),
// stop the ULP program
I_HALT()
};
// Load ULP program into RTC_SLOW_MEM, at offset 0
size_t size = sizeof(program)/sizeof(ulp_insn_t);
ESP_ERROR_CHECK( ulp_process_macros_and_load(0, program, &size) );
assert(size < ULP_DATA_OFFSET && "ULP_DATA_OFFSET needs to be greater or equal to the program size");
// Set ULP wakeup period
const uint32_t sleep_cycles = RTC_CNTL_SLOWCLK_FREQ / measurements_per_sec;
REG_WRITE(SENS_ULP_CP_SLEEP_CYC0_REG, sleep_cycles);
// Start ULP
ESP_ERROR_CHECK( ulp_run(0) );
}
#endif // CONFIG_ENABLE_ULP_TEMPERATURE_WAKEUP

View file

@ -0,0 +1,7 @@
CONFIG_ESP32_DEFAULT_CPU_FREQ_80=y
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=80
CONFIG_ULP_COPROC_ENABLED=y
CONFIG_ULP_COPROC_RESERVE_MEM=512
CONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1=y
CONFIG_ESP32_RTC_CLOCK_SOURCE_INTERNAL_RC=y
CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY=500