ESP32 ULP program to read ADC when in deep sleep

Hi all,

For a project I want to read analog values from IO32 when the main processor is in deep sleep. So I use ULP for this.

I received this code from someone that is helping me:

#include <WiFi.h> // This is the most basic WiFi implimentation
#include "esp32/ulp.h"
#include "driver/adc.h"         
#include <esp_bt.h>             
#include <esp_wifi.h>           
#include "soc/rtc_cntl_reg.h"
#include "driver/rtc_io.h"
#define uS_TO_S_FACTOR 1000000  
#define TIME_TO_SLEEP  300

void power_off(){
  setCpuFrequencyMhz(80); //Save power by stepping down CPU freq. NOTE: Test this, as it can affect some sensor reading
  btStop();               // Power down BT for best power saving
  esp_wifi_stop();        // Power down the Wifi radios for best power saving
  esp_deep_sleep_start(); 
  }
  
void ulp_adc_wake_up(unsigned int low_adc_treshold, unsigned int high_adc_treshold)
{
   adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_DB_11);
   adc1_config_width(ADC_WIDTH_BIT_10);
   adc1_ulp_enable();

   rtc_gpio_init(GPIO_NUM_32);

   const ulp_insn_t program[] = {
      I_DELAY(32000),              // Wait until ESP32 goes to deep sleep
      M_LABEL(1),                  // LABEL 1
        I_MOVI(R0, 0),             // Set reg. R0 to initial 0
        I_MOVI(R2, 0),             // Set reg. R2 to initial 0
      M_LABEL(2),                  // LABEL 2
        I_ADDI(R0, R0, 1),         // Increment cycle counter (reg. R0)
        I_ADC(R1, 0, 0),           // Read ADC value to reg. R1
        I_ADDR(R2, R2, R1),        // Add ADC value from reg R1 to reg. R2
      M_BL(2, 4),                  // If cycle counter is less than 4, go to LABEL 2
      I_RSHI(R0, R2, 2),           // Divide accumulated ADC value in reg. R2 by 4 and save it to reg. R0
      M_BGE(3, high_adc_treshold), // If average ADC value from reg. R0 is higher or equal than high_adc_treshold, go to LABEL 3
      M_BL(3, low_adc_treshold),   // If average ADC value from reg. R0 is lower than low_adc_treshold, go to LABEL 3
      M_BX(1),                     // Go to LABEL 1
      M_LABEL(3),                  // LABEL 3
      I_WAKE(),                    // Wake up ESP32
      I_END(),                     // Stop ULP program timer
      I_HALT()                     // Halt the coprocessor
   };

   size_t size = sizeof(program)/sizeof(ulp_insn_t);
   ulp_process_macros_and_load(0, program, &size);

   ulp_run(0);
   esp_sleep_enable_ulp_wakeup();
   btStart();
   esp_wifi_start();
}


void setup() {

    
  Serial.begin(115200);
  adc_power_on();    
  power_off();
 
 
}

void loop()
{
  ulp_adc_wake_up(0, 3500);
}

The code compiles perfect, so that is a start :slight_smile:

But is it correct that ulp_adc_wake_up(0, 3500); is in the main-loop? I want to check the ADC as many times as possible, is this the correct way?

I'd put ulp wake in setup() and leave loop empty. Also ulp wakie needs to be done before the esp32 is put to sleep. Once the ESP32 is put to sleep loop() does not run.

Thank you Idahowalker, that is exactly what I was thinking. I paid someone for this code so I was doubting myself. :slight_smile:

I already had this code:

void ulp_adc_wake_up(unsigned int low_adc_treshold, unsigned int high_adc_treshold)
{
   adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_DB_11);  //adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11);
   adc1_config_width(ADC_WIDTH_BIT_10);
   adc1_ulp_enable();

   rtc_gpio_init(GPIO_NUM_32); //36  RTC_GPIO9  //GPIO_NUM_32
   
   gpio_deep_sleep_hold_en();
   

   const ulp_insn_t program[] = {
      I_DELAY(32000),              // Wait until ESP32 goes to deep sleep
      M_LABEL(1),                  // LABEL 1
        I_MOVI(R0, 0),             // Set reg. R0 to initial 0
        I_MOVI(R2, 0),             // Set reg. R2 to initial 0
      M_LABEL(2),                  // LABEL 2
        I_ADDI(R0, R0, 1),         // Increment cycle counter (reg. R0)
        I_ADC(R1, 0, 0),           // Read ADC value to reg. R1
        I_ADDR(R2, R2, R1),        // Add ADC value from reg R1 to reg. R2
      M_BL(2, 4),                  // If cycle counter is less than 4, go to LABEL 2
      I_RSHI(R0, R2, 2),           // Divide accumulated ADC value in reg. R2 by 4 and save it to reg. R0
      M_BGE(3, high_adc_treshold), // If average ADC value from reg. R0 is higher or equal than high_adc_treshold, go to LABEL 3
      M_BL(3, low_adc_treshold),   // If average ADC value from reg. R0 is lower than low_adc_treshold, go to LABEL 3
      M_BX(1),                     // Go to LABEL 1
      M_LABEL(3),                  // LABEL 3
      I_WAKE(),                    // Wake up ESP32
      I_END(),                     // Stop ULP program timer
      I_HALT()                     // Halt the coprocessor
   };

   size_t size = sizeof(program)/sizeof(ulp_insn_t);
   ulp_process_macros_and_load(0, program, &size);

   ulp_run(0);
   
   esp_sleep_enable_ulp_wakeup();
   esp_deep_sleep_start();
}

But unfortunately with this the ESP32 won't wake up anymore...

I run this code every time when I push a button like this:

if (digitalRead(BUTTON1) == LOW){ulp_adc_wake_up(225,1023);}

Can you see what I do wrong here?

Thanks again!

post all the code, please.

The code is very, very long (1.38MB when compiled). What exactly do you want to see?

I use these references:

#include "esp32/ulp.h"
#include "soc/rtc_cntl_reg.h"
#include "driver/rtc_io.h"
#include "driver/adc.h"

And in the main loop I do nothing else with this ULP code.

Please let me know if you need to see more specific code :slight_smile:

Thanks again!

I can't find any loop in the ULP (Assembly) code. Isn't that needed?

I mean, the ADC-reading has to be done VERY often, but then you would expect a loop somewhere, right?

I don't understand this exactly, can someone help me with this please?

The docs, API Reference - ESP32 - β€” ESP-IDF Programming Guide latest documentation

Thanks.

Unfortunately this assembly (and ULP) code is too difficult for me.
I'm just happy/proud that everything else works perfectly. I hope that I can get this last thing also working, but I definitely need help here :slight_smile:

I also don't understand why the example that I posted earlies just don't work. On that forum they say it has to work so that's very strange isn't it? :frowning:

The code above will not work for many reasons.

When the ESP32 is sleeping the GPIO pins are shut down. When the ESP32 is sleeping the RTC_GPIO pins become active but become inactive when then main CPU is awake, except when coded not to do that behavior.

The ULP passes info into the RTC_RAM FAST and SLOW. The Main cpu cores can read the RTC ram if coded to do so.

Here is a link to the docs, API Reference - ESP32 - β€” ESP-IDF Programming Guide latest documentation

Wow, okay.... It is not the intention that this piece of code (reading the button) is executed during sleep. I just put this piece in the main loop (of the main processor). With this I THOUGHT to start the ULP/assembly code (when I push that button), and thereby put the main-processor into sleep...

But that's not right you say? Where should I call "ulp_adc_wake_up(225,1023);" then? And how often? Does it have to be REALLY OFTEN (in a loop) because I actually want to read the ADC as often as possible, or just once "per sleep cycle"?

Thanks again for your input. It is really helpful :slight_smile:

Okay, I got it working now :slight_smile:
So I can read ADC in ULP and wake the processor up when the threshold is reached.

The code I use is:

if (EnableSleep){
  esp_sleep_wakeup_cause_t WakkerMaakReden = esp_sleep_get_wakeup_cause();
  if (WakkerMaakReden != ESP_SLEEP_WAKEUP_ULP){
    SendUDP("Reden van wakker maken was niet ULP"); 
    delay(1000);  
    init_ulp_program();
  } else {
    ulp_ADC_reading &= UINT16_MAX;
    SendUDP("ulp_ADC_reading: " + String(ulp_ADC_reading));  
    delay(1000);
    init_ulp_program(); //Dit moet hier staan anders werkt het niet
  }
  
  SendUDP("Slaapstand starten"); 
  delay(1000);
  start_ulp_program();
  ESP_ERROR_CHECK(esp_sleep_enable_ulp_wakeup());

  esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK,ESP_EXT1_WAKEUP_ALL_LOW);

  esp_sleep_enable_timer_wakeup(rtc_timer_in_seconds * convert_to_microseconds);  //Iedere 20 sec. even wakker maken (werkt i.c.m. ADC uitlezen)
  esp_deep_sleep_start();

}

But now a new problem pops up when I also want to wake from deep-sleep with a push-button :frowning:
This line of code:

esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK,ESP_EXT1_WAKEUP_ALL_LOW);

works only half: it wakes the processor nicely, but once pushed I can't wake up the processor with the ADC reading anymore.

So (wake up with) ADC reading works perfectly until I push that button for waking up...

Very strange behavior, isn't it?

Hopefully someone here can help me with this? :+1:

I can't understand why the push of a button does change the behavior in this case :frowning:

Normally I write code so that the button does exactly what I want, but in this case I didn't program anything. I just want it to wake the main processor. That works, but why does the ADC stops working then?

Is there something else that I can test/check in order to fix this?

Any help is highly appreciated :slight_smile:

Best regards,

Atmoz

Bump ^^

Is there someone with a good idea that I can try? :innocent: :slight_smile:

Nobody with an idea what this strange problem can be or how to solve it? :pensive:

Did not read all but, :slight_smile:
look at this
esp_sleep_enable_ulp_wakeup();
ulp_set_wakeup_period(0, ULPSLEEP);

and more then init alone rtc_gpio_init(GPIO_NUM_X);
rtc_gpio_set_direction();
rtc_gpio_set_level();
etc

I will look at your code this week, I'm using the ULP also for the first time,
there not a lot of info.
Try to use only English in your code, it's better to understand for everybody,
snap je...