Use ADC in main program and ULP

campbellh
Posts: 2
Joined: Wed Jun 07, 2023 9:25 pm

Use ADC in main program and ULP

Postby campbellh » Wed Jun 07, 2023 9:35 pm

I have run into an odd problem and I cannot seem to identify a solution reading the documentation. I need to use the ADC in my main program (using adc1_get_raw) and I also need to use it in ULP. The documentation of adc1_ulp_enable seems to suggest this is possible as long as I call adc1_ulp_enable before activating my ULP program. Below you will find a minimal-ish example, if the line Serial.println(adc1_get_raw(ADC1_CHANNEL_6)); is commented out, the ULP runs and wakes the device up as expected if the ADC reading exceeds a set threshold. If the line is present (the main program uses the ADC) there is a bit of a bizarre behavior. The first time I put the device to sleep the ULP runs as normal (ADC works), it will wake on a timer interrupt and show 11 ULP runs... after that first run though, the ULP seems to hang (I suspect on the ADC instruction). I have tried various configurations and sequences of instructions but it seems that accessing the ADC in the main program causes problems on the ULP side, even though I call adc1_ulp_enable before each sleep.

Appreciate any help!

Code: Select all

#include <Arduino.h>

#include "esp32/ulp.h"

#include "ulp_main.h"
#include "ulptool.h"
#include "driver/adc.h"

// Unlike the esp-idf always use these binary blob names
extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start");
extern const uint8_t ulp_main_bin_end[] asm("_binary_ulp_main_bin_end");

RTC_DATA_ATTR bool first_init = false;
static void init_ulp();
static void run_ulp();

void setup()
{
 
  Serial.begin(115200);

  // get wakeup cause
  auto wake_cause = esp_sleep_get_wakeup_cause();
  switch (wake_cause)
  {
  case ESP_SLEEP_WAKEUP_ULP:
    Serial.println("ULP WAKEUP!");
    Serial.println(ulp_count & 0xFFFF);
    break;
  case ESP_SLEEP_WAKEUP_TIMER:
    Serial.println("TIMER WAKEUP");
    break;
  default:
    Serial.println("OTHER WAKEUP");
     init_ulp();
    
  }
  Serial.print("COUNT: ");
  Serial.println(ulp_count & 0xFFFF);
  Serial.println(ulp_adc_value & 0xFFFF);
  Serial.println(ulp_trigger_threshold & 0xFFFF);
  adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);
  adc1_config_width(ADC_WIDTH_BIT_12);
  Serial.println(adc1_get_raw(ADC1_CHANNEL_6));
  Serial.println();
  Serial.flush();
  delay(2000);
  run_ulp();
}

static void run_ulp(){
  adc1_ulp_enable();
  ulp_run((&ulp_entry - RTC_SLOW_MEM) / sizeof(uint32_t));
  esp_sleep_enable_ulp_wakeup();
  esp_sleep_enable_timer_wakeup(5000000);
  esp_deep_sleep_start();
}
static void init_ulp(){
  ulp_set_wakeup_period(0, 500000);
  long hold;
  if(!first_init){
    hold = 0;
    first_init = true;
  } else {
    hold = ulp_count;
  }
  esp_err_t err = ulptool_load_binary(0, ulp_main_bin_start, (ulp_main_bin_end - ulp_main_bin_start) / sizeof(uint32_t));
  ulp_count = hold;
  ulp_trigger_threshold = (uint32_t) 3000;
  
}


void loop()
{

}

Code: Select all

#include "soc/rtc_cntl_reg.h"
#include "soc/rtc_io_reg.h"
#include "soc/soc_ulp.h"

.set adc_oversampling_factor_log, 1
.set adc_oversampling_factor, (1 << adc_oversampling_factor_log)

.data

.global count
count:
    .long 0

.global adc_value
adc_value:
    .long 0

.global trigger_threshold
trigger_threshold:
    .long 0

.text
.global entry
entry:
    move r3, count
    ld  r0, r3, 0
    add r0, r0, 1
    st r0, r3, 0

    move r0, 0 // clear r0
    stage_rst // clear stage counter
measure:
    adc r1, 0, 7 // Read from ADC1_6 into R1
    add r0, r0, r1 // Add r1 to r0
    stage_inc 1 // increment counter
    jumps measure, adc_oversampling_factor, LT // return to measure if the right number of samples haven't been taken
calculate:
    /* average the value */
    rsh r0, r0, adc_oversampling_factor_log
    stage_rst
    move r3, adc_value
    st r0, r3, 0 // write the averaged value to adc_value

    move r3, trigger_threshold
    ld r1, r3, 0 // load the threshold
    sub r1, r1, r0
    jump wakeup, ov
    halt // exit if it is not the right value
wakeup:
    READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP) // Check if it is ready for a wakeup
    and r0, r0, 1 
    jump wakeup, eq
    wake
    REG_WR 0x006, 24, 24, 0 // this stops the ULP from running again
    halt

campbellh
Posts: 2
Joined: Wed Jun 07, 2023 9:25 pm

Re: Use ADC in main program and ULP

Postby campbellh » Fri Jun 09, 2023 12:59 pm

As some additional information, it seems that it is also linked to the deep sleep timer wakeup source. If I do not use the timer as a wakeup source then the ULP runs as expected, even with ADC reads in the main program. There must be some kind of interaction that I am not understanding. As a short term work around, I can implement a routine in the ULP that handles both the ADC reads and replaces the timer but it is still odd that I cannot use both.

Who is online

Users browsing this forum: No registered users and 63 guests