Display on during deep sleep?

  • @andyt coming From a background of coding 8bit PIC's etc, I have a fare grip on assembler language. So once i get to grips with this ULP coding methods . My next step will be to add a routine to check battery level% change and wake from sleep to update the LCD battery level icon. As at the moment while it is sleeping with the LCD on the M5stack could run out of power and shut down. Or stay displaying the wrong battery level while it sleeps as at the moment it only updates while awake. Just need to get my head around the fundamentals of data handling and storage and setup of ULP. Good that there is more info on this topic these days. Exciting times!!![
    My M5stack waking up and quickly turning on wifi, grabbing my local environment agency river levels .and showing the levels and if they are rising or falling triggering speaker alarms and displaying waring icons when reaching or above flood levels. To conserve battery it is asleep while the backlight remains on at a duty cycle dependant on battery level. Getting dimmer as the battery % lowers for 10 minutes minimum and then display the weather forecast from a weather API. cycling through these screens quicker or slower depending on the level of importance to the viewer. So while the river levels are low the weather will be displayed longer. pressing one of the front buttons wakes the device and scrolls to the next page, so that the user can look through the pages at a glance. It's like doing a painting. Always improving to the code to finished slick working device. next stage is to right a nice routine for a user interface to input their own wifi and postcode location. So the device can select local levels and weather.

  • Hello Andy

    ULP code and ULP variables share the same area of RTC_SLOW_MEM which is about 8 kB. Right now your ULP variable is reset to 0 at every boot since all of that area is reset to 0 by this command: memset(RTC_SLOW_MEM, 0, 8192);. In addition the ULP code and your ULP variable both are located at the same offset 0 in that area. ulp_process_macros_and_load(0, ulp_prog, &size);

    In order to fix this the RTC_SLOW_MEM needs to be orgnanised, e.g. the ULP variables need to be located after (or before) the ULP code and the area used for ULP variables needs to be excluded from being cleared to 0 at every boot.

    There are many ways to solve this. In the example I've linked earlier ULP variables are located shortly after the ULP code (offset 36). The solution I've chosen puts the ULP variables into the last of the eight 1 kB blocks of RTC_SLOW_MEM. That leaves more than enough space (the first 7 kB) for ULP code.

    Note: Next to the boot count ULP variable I've added a second ULP dummy variable to illustrate how multiple ULP variables could be handled.


    #include <M5Stack.h>
    #include "esp32/ulp.h"
    #include "driver/rtc_io.h"
    const int lcdBrightness = 50; // (1-255)
    // RTC slow memory map (vars are in the last 1 kB of total 8 kB)
    // Note: all ULP commands / vars are 4 bytes in size
    const size_t rtc_slow_mem_total_size = 2048; // 8 kB = 4 * 2048
    const size_t rtc_slow_mem_vars_space = 256;  // 1 kB = 4 * 256
    const size_t rtc_slow_mem_vars_start = rtc_slow_mem_total_size - rtc_slow_mem_vars_space;
    const size_t rtc_slow_mem_prog_start = 0;
    const size_t rtc_slow_mem_prog_space = rtc_slow_mem_total_size - rtc_slow_mem_vars_space; // 7 kB
    const size_t ulp_boot_count_var_offset = 0;
    const size_t ulp_dummy_var_offset = 1;
    void ulp_start(void) {
      // RTC slow mem init (except space allocated for vars)
      memset(RTC_SLOW_MEM, 0, rtc_slow_mem_prog_space * sizeof(uint32_t));
      // M5Stack LCD backlight is connected to GPIO32 (specify by +14)
      const gpio_num_t lcdPWMPin = GPIO_NUM_32;
      const int lcdPWMBit = RTCIO_GPIO32_CHANNEL + 14;
      // GPIO32 initialization (set to output and initial value is 0)
      rtc_gpio_set_direction(lcdPWMPin, RTC_GPIO_MODE_OUTPUT_ONLY);
      rtc_gpio_set_level(lcdPWMPin, 0);
      // Define ULP program
      const ulp_insn_t ulp_prog[] = {
        I_WR_REG(RTC_GPIO_OUT_REG, lcdPWMBit, lcdPWMBit, 1), // on
        I_DELAY(lcdBrightness * 100),
        I_WR_REG(RTC_GPIO_OUT_REG, lcdPWMBit, lcdPWMBit, 0), // off
      // Run ULP program
      size_t size = sizeof(ulp_prog) / sizeof(ulp_insn_t);
      ulp_process_macros_and_load(rtc_slow_mem_prog_start, ulp_prog, &size);
    void setup() {
      M5.begin(true, false, true, false);
      M5.Lcd.println("Put something onto the LCD...");
      if(esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TIMER)
        M5.Lcd.println("Woken from timer");
        RTC_SLOW_MEM[rtc_slow_mem_vars_start + ulp_boot_count_var_offset]++;
        M5.Lcd.println("Woken after reset");
        RTC_SLOW_MEM[rtc_slow_mem_vars_start + ulp_boot_count_var_offset] = 0;
        RTC_SLOW_MEM[rtc_slow_mem_vars_start + ulp_dummy_var_offset] = 4711;
      M5.Lcd.println(RTC_SLOW_MEM[rtc_slow_mem_vars_start + ulp_boot_count_var_offset]);
      M5.Lcd.println(RTC_SLOW_MEM[rtc_slow_mem_vars_start + ulp_dummy_var_offset]);
      M5.Lcd.println("Going to deep sleep");
      esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
    void loop() {

  • @felmue Yes!! Thank you. You are a Genius. Saved me hours. I get it !. It's working in my code.

  • Hello Andy

    thank you very much. I am happy to hear it works to your liking.

    Happy Stacking!

  • @felmue Seems my Arduino IDE ulp.h does not have a macro I_i2c_RD. And i can't find a way to assemble the raw assembly instruction set i2C_RD. Am i missing something? As i want to take advantage of the the assembly instructions and also the latest set of Macros. Just spent a day going around the internet and came up with nothing. Trying to write ULP code to wake the M5stack when the battery % changes. Sorry again to trouble you.

  • Hello Andy

    I am afraid you're out of luck. The arduino-esp32 library is based on IDF release 3.3 and the ULP macros for I2C seem to be missing / not implemented yet.

    #define OPCODE_I2C 3 /*!< Instruction: read/write I2C (not implemented yet) */

    But even with the macros in place it would probably still not work. According to this info it seems that the ULP only allows for two predefined sets of GPIOs to be used for SDA and SDL, none of which are GPIO21/22 used in M5Stack.


  • @felmue Thank you again for the prompt replay. Thank you for the info. I drew a blank at the link you provide as well . Also a good find regarding the pin assignments of the hard coded SDA and SDL for channels 0,1. Did find this link for ulp.h using I_i2C_ READ I_I2C_WRITE etc. But could not get the ZIPed Library to install into my Arduino IDE to try it out.
    Maybe start a new thread soon on this as i am getting off the original topic here now that your ULP code above fixed the display going off while sleeping. Just for the record now getting 24hrs off two 700mh add on battery modules at well bright 200 brightness level while the 5Stack is sleeping .

  • @andyt I now have the M5Stack black top and a grey top. The ulp program works well to keep the Black top version backlight running at varying levels of brightness. But if i set the PWM to less than 60 on the grey top version. The back light switches off completely after 15 seconds of sleep. which limits the available brightness levels between 60 and 255. which makes the brightness change hardly noticeable Not sure what is going on with the differences between the two M5stacks?

  • Hello @AndyT

    hmm, strange, if anything I'd expected the M5Stack Gray to work with low brightness level and the M5Stack Black to not behave due to the fact that the M5Stack Gray has additional hardware (e.g. IMU) consuming power to keep the device powered on.

    That said, I suspect the IP5306 switching off due to too light load. Try to add the following statement before going into deep sleep. It should prevent the IP5306 from shutting down under light load.


    Happy Stacking!