Page 1 of 1

ESP32 S2 ADC monitor doesn't seem to work

Posted: Fri May 02, 2025 9:38 pm
by kevinevans
I'm trying to get the ADC monitor to work. I'd like to create a monitor when the voltage applied exceeds some threshold. I'm initializing ADC1 CH0 like:

Code: Select all

  // before this:
  // - init calib
  // - call adc_continuous_new_handle
  // - call adc_continuous_config
  // - call adc_continuous_register_event_callbacks
  
  adc_monitor_config_t adc_monitor_cfg = {
      .adc_unit = ADC_UNIT_1,
      .channel = ADC_CHANNEL_0,
      .h_threshold = 100, // I assume this is in raw values, i.e. 0-4095
      .l_threshold = -1,
  };

  ESP_RETURN_ON_ERROR(adc_new_continuous_monitor(s_adc_cont_handle,
                                                 &adc_monitor_cfg,
                                                 &s_monitor_handle),
                      TAG, "failed to init cont monitor");

  adc_monitor_evt_cbs_t adc_monitor_cbs = {
      .on_over_high_thresh = monitor_cb,
      // .on_below_low_thresh = monitor_cb,
  };

  ESP_RETURN_ON_ERROR(adc_continuous_monitor_register_event_callbacks(
                          s_monitor_handle, &adc_monitor_cbs, NULL),
                      TAG, "failed to register monitor cbs");

  ESP_RETURN_ON_ERROR(adc_continuous_monitor_enable(s_monitor_handle), TAG,
                      "failed to enable cont monitor");

  ESP_RETURN_ON_ERROR(adc_continuous_start(s_adc_cont_handle), TAG,
                      "adc cont failed to start :<");
Unfortunately, it looks like my callback function `monitor_cb` is never called. However, if I call `adc_continuous_read` (called by the on_conv_done registered with adc_continuous_register_event_callbacks), it reads the voltage and it clearly exceeds the threshold.

Am I initializing the monitor incorrectly?

Re: ESP32 S2 ADC monitor doesn't seem to work

Posted: Fri May 02, 2025 10:09 pm
by kevinevans
For brevity, here's the full file. I'm just calling sense_init while I work out some issues.

Code: Select all

#include "sense.h"

#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"
#include "esp_adc/adc_continuous.h"
#include "esp_adc/adc_filter.h"
#include "esp_adc/adc_monitor.h"
#include "esp_check.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include "pins.h"

static const char TAG[] = "sense";

#define ADC_UNIT ADC_UNIT_1
#define ADC_CHANNEL ADC_CHANNEL_0
#define ADC_ATTEN ADC_ATTEN_DB_12
#define ADC_WIDTH ADC_BITWIDTH_12
#define ADC_BUFFER_SIZE 1024

/**
 * @brief The scale is wrong when using DB12 attenuation
 * I think this is an ESP IDF bug:
 * https://github.com/espressif/esp-idf/issues/15876
 */
#define ADC_SCALE_CORRECTION_FACTOR 2

static adc_continuous_handle_t s_adc_cont_handle = NULL;
static adc_cali_handle_t s_adc_cali_handle = NULL;
static TaskHandle_t s_task_handle;

static bool adc_calibration_init(adc_unit_t unit, adc_channel_t channel,
                                 adc_atten_t atten,
                                 adc_cali_handle_t *out_handle) {
  esp_err_t ret = ESP_FAIL;
  bool calibrated = false;

  // TODO
  
  adc_cali_scheme_ver_t scheme_ver;
  adc_cali_check_scheme(&scheme_ver);

#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
  if (scheme_ver == ADC_CALI_SCHEME_VER_CURVE_FITTING) {

    if (!calibrated) {
      ESP_LOGI(TAG, "calibration scheme version is %s", "Curve Fitting");
      adc_cali_curve_fitting_config_t cali_config = {
          .unit_id = unit,
          .chan = channel,
          .atten = atten,
          .bitwidth = ADC_WIDTH,
      };
      ret = adc_cali_create_scheme_curve_fitting(&cali_config, out_handle);
      if (ret == ESP_OK) {
        calibrated = true;
      }
    }
  }
#endif

#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
  if (scheme_ver == ADC_CALI_SCHEME_VER_LINE_FITTING) {
    if (!calibrated) {
      ESP_LOGI(TAG, "calibration scheme version is %s", "Line Fitting");
      adc_cali_line_fitting_config_t cali_config = {
          .unit_id = unit,
          .atten = atten,
          .bitwidth = ADC_WIDTH,
      };
      ret = adc_cali_create_scheme_line_fitting(&cali_config, out_handle);
      if (ret == ESP_OK) {
        calibrated = true;
      }
    }
  }

#endif

  if (ret == ESP_OK) {
    ESP_LOGI(TAG, "Calibration Success");
  } else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated) {
    ESP_LOGW(TAG, "eFuse not burnt, skip software calibration");
  } else {
    ESP_LOGE(TAG, "Invalid arg or no memory");
  }

  return calibrated;
}

static esp_err_t sense_adc_calib_init() {

  if (adc_calibration_init(ADC_UNIT, ADC_CHANNEL, ADC_ATTEN,
                           &s_adc_cali_handle)) {
    ESP_LOGI(TAG, "ADC calibration init success");
  } else {
    ESP_LOGE(TAG, "ADC calibration init failed");
  }

  return ESP_OK;
}

static bool IRAM_ATTR handle_conv_done(adc_continuous_handle_t handle,
                                       const adc_continuous_evt_data_t *edata,
                                       void *user_data) {
  BaseType_t mustYield = pdFALSE;
  // Notify that ADC continuous driver has done enough number of conversions
  vTaskNotifyGiveFromISR(s_task_handle, &mustYield);

  return (mustYield == pdTRUE);
}

static void sense_read_task(void *params) {
  int i = 0;

  while (1) {
    uint8_t res[ADC_BUFFER_SIZE] = {0};

    ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
    uint32_t size = sizeof(res) / sizeof(uint8_t);
    esp_err_t err = adc_continuous_read(s_adc_cont_handle, res, size, &size, 1);

    if (err != ESP_OK) {
      ESP_LOGE(TAG, "adc_continuous_read failed");
      continue;
    }

    if (i++ % 10 == 0) {
      float sum = 0;
      int voltage;
      float min = 100, max = 0;

      for (int i = 0; i < size; i += SOC_ADC_DIGI_RESULT_BYTES) {
        adc_digi_output_data_t *data = (adc_digi_output_data_t *)&res[i];

        // int raw_value = data->type2.data;
        int raw_value = data->type1.data;
        // ESP_LOGI(TAG, "raw = %d", raw_value);
        if (adc_cali_raw_to_voltage(s_adc_cali_handle, raw_value, &voltage) ==
            ESP_OK) {
          float fvoltage = ADC_SCALE_CORRECTION_FACTOR * voltage / 1000.0;

          if (fvoltage < min) {
            min = fvoltage;
          }
          if (fvoltage > max) {
            max = fvoltage;
          }
          sum += fvoltage;
        }
      }
      ESP_LOGI(TAG, "Voltage: %f", sum / (size / SOC_ADC_DIGI_RESULT_BYTES));
      ESP_LOGI(TAG, "min=%.2f, max=%.2f", min, max);
    }
  }
}

adc_monitor_handle_t s_monitor_handle = NULL;

static bool IRAM_ATTR monitor_cb(adc_monitor_handle_t handle,
                                 const adc_monitor_evt_data_t *event_data,
                                 void *user_data) {
  ESP_LOGI(TAG, "monitor cb called");
  return false;
  BaseType_t mustYield = pdFALSE;
  // Notify that ADC continuous driver has done enough number of conversions
  vTaskNotifyGiveFromISR(s_task_handle, &mustYield);

  return (mustYield == pdTRUE);
}
esp_err_t sense_init() {
  ESP_LOGI(TAG, "sense_init");
  // https://github.com/espressif/esp-idf/blob/v5.4/examples/peripherals/adc/continuous_read/main/continuous_read_main.c
  // https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-reference/peripherals/adc_continuous.html?highlight=adc

  // load the calibration data
  ESP_RETURN_ON_ERROR(sense_adc_calib_init(), TAG,
                      "adc calibration init failed");

  adc_continuous_handle_cfg_t adc_config = {
      .flags.flush_pool = 1,
      .max_store_buf_size = 1024,                                // in bytes
      .conv_frame_size = 256 * SOC_ADC_DIGI_DATA_BYTES_PER_CONV, // in bytes
  };

  ESP_RETURN_ON_ERROR(
      adc_continuous_new_handle(&adc_config, &s_adc_cont_handle), TAG,
      "adc_continuous_new_handle failed");

  adc_continuous_config_t cont_config = {
      .pattern_num = 1,
      .adc_pattern =
          (adc_digi_pattern_config_t[1]){
              (adc_digi_pattern_config_t){
                  .unit = ADC_UNIT,
                  .channel = ADC_CHANNEL,
                  .atten = ADC_ATTEN,
                  .bit_width = ADC_WIDTH,

              },
          },
      .sample_freq_hz = SOC_ADC_SAMPLE_FREQ_THRES_LOW,
      .conv_mode = ADC_CONV_SINGLE_UNIT_1,
      .format = ADC_DIGI_OUTPUT_FORMAT_TYPE1,
  };

  ESP_RETURN_ON_ERROR(adc_continuous_config(s_adc_cont_handle, &cont_config),
                      TAG, "failed to cont config");

  adc_continuous_evt_cbs_t cbs = {
      .on_conv_done = handle_conv_done,
  };

  ESP_RETURN_ON_ERROR(
      adc_continuous_register_event_callbacks(s_adc_cont_handle, &cbs, NULL),
      TAG, "failed to register event callbacks");

  adc_monitor_config_t adc_monitor_cfg = {
      .adc_unit = ADC_UNIT,
      .channel = ADC_CHANNEL,
      .h_threshold = 100,
      .l_threshold = -1,
  };

  ESP_RETURN_ON_ERROR(adc_new_continuous_monitor(s_adc_cont_handle,
                                                 &adc_monitor_cfg,
                                                 &s_monitor_handle),
                      TAG, "failed to init cont monitor");

  adc_monitor_evt_cbs_t adc_monitor_cbs = {
      .on_over_high_thresh = monitor_cb,
      // .on_below_low_thresh = monitor_cb,
  };

  ESP_RETURN_ON_ERROR(adc_continuous_monitor_register_event_callbacks(
                          s_monitor_handle, &adc_monitor_cbs, NULL),
                      TAG, "failed to register monitor cbs");

  ESP_RETURN_ON_ERROR(adc_continuous_monitor_enable(s_monitor_handle), TAG,
                      "failed to enable cont monitor");

  ESP_RETURN_ON_ERROR(adc_continuous_start(s_adc_cont_handle), TAG,
                      "adc cont failed to start :<");

  BaseType_t ret = xTaskCreate(sense_read_task, "sense_read_task", 8192, NULL,
                               1, &s_task_handle);

  if (ret != pdPASS) {
    ESP_LOGE(TAG, "Failed to create sense_read_task");
    return ESP_FAIL;
  }

  ESP_LOGI(TAG, "sense_init done");
  return ESP_OK;
}


(disregard the messiness of it all)