From 1e5efd7fa7091b20862d6c9c2484af2c6d5feca5 Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Tue, 27 Aug 2024 11:07:17 +0200 Subject: [PATCH] feat(lp_adc): Added support to read LP ADC from the LP core This commit adds APIs to initialize and configure the LP ADC from the HP core and also adds APIs to read the raw and converted ADC values from the LP core. --- components/ulp/CMakeLists.txt | 4 + components/ulp/cmake/IDFULPProject.cmake | 3 +- .../include/ulp_lp_core_lp_adc_shared.h | 113 +++++++++++++ .../shared/ulp_lp_core_lp_adc_shared.c | 157 ++++++++++++++++++ 4 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 components/ulp/lp_core/shared/include/ulp_lp_core_lp_adc_shared.h create mode 100644 components/ulp/lp_core/shared/ulp_lp_core_lp_adc_shared.c diff --git a/components/ulp/CMakeLists.txt b/components/ulp/CMakeLists.txt index 24ea46461d..96b38e2025 100644 --- a/components/ulp/CMakeLists.txt +++ b/components/ulp/CMakeLists.txt @@ -73,6 +73,10 @@ if(CONFIG_ULP_COPROC_TYPE_LP_CORE) if(CONFIG_SOC_LP_CORE_SUPPORT_ETM) list(APPEND srcs "lp_core/lp_core_etm.c") endif() + + if(CONFIG_SOC_LP_ADC_SUPPORTED) + list(APPEND srcs "lp_core/shared/ulp_lp_core_lp_adc_shared.c") + endif() endif() idf_component_register(SRCS ${srcs} diff --git a/components/ulp/cmake/IDFULPProject.cmake b/components/ulp/cmake/IDFULPProject.cmake index f76bb89b7e..6febf12601 100644 --- a/components/ulp/cmake/IDFULPProject.cmake +++ b/components/ulp/cmake/IDFULPProject.cmake @@ -124,7 +124,8 @@ function(ulp_apply_default_sources ulp_app_name) "${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_interrupt.c" "${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_i2c.c" "${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_spi.c" - "${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_ubsan.c") + "${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_ubsan.c" + "${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_lp_adc_shared.c") set(target_folder ${IDF_TARGET}) diff --git a/components/ulp/lp_core/shared/include/ulp_lp_core_lp_adc_shared.h b/components/ulp/lp_core/shared/include/ulp_lp_core_lp_adc_shared.h new file mode 100644 index 0000000000..310caa0b1a --- /dev/null +++ b/components/ulp/lp_core/shared/include/ulp_lp_core_lp_adc_shared.h @@ -0,0 +1,113 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_err.h" +#include "hal/adc_types.h" +#include "esp_adc/adc_oneshot.h" + +/** + * @brief LP ADC channel configurations + */ +typedef adc_oneshot_chan_cfg_t lp_core_lp_adc_chan_cfg_t; + +/** + * @brief Initialize the LP ADC + * + * @note We only support LP ADC1 and not LP ADC2 due to a HW issue. + * + * @param unit_id LP ADC unit to initialize + * + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if the unit_id is invalid + * ESP_ERR_NOT_SUPPORTED if the API is not supported on the LP Core + * ESP_FAIL if the ADC unit failed to initialize + */ +esp_err_t lp_core_lp_adc_init(adc_unit_t unit_id); + +/** + * @brief Deinitialize the LP ADC + * + * @param unit_id LP ADC unit to deinitialize + * + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if the unit_id is invalid + * ESP_ERR_NOT_SUPPORTED if the API is not supported on the LP Core + * ESP_FAIL if the ADC unit failed to deinitialize + */ +esp_err_t lp_core_lp_adc_deinit(adc_unit_t unit_id); + +/** + * @brief Configure an LP ADC channel + * + * @param unit_id ADC unit to configure the channel for + * @param channel ADC channel to configure + * @param chan_config Configuration for the channel + * + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if the unit_id is invalid + * ESP_ERR_NOT_SUPPORTED if the API is not supported on the LP Core + * ESP_FAIL if the channel configuration fails + */ +esp_err_t lp_core_lp_adc_config_channel(adc_unit_t unit_id, adc_channel_t channel, const lp_core_lp_adc_chan_cfg_t *chan_config); + +/** + * @brief Read the raw ADC value from a channel + * + * @note The raw value is the 12-bit value read from the ADC. + * The value is between 0 and 4095. To convert this value + * to a voltage, use the formula: + * voltage = (raw_value * (1.1v / 4095)) * attenuation + * + * Alternatively, use lp_core_lp_adc_read_channel_converted() + * to get the converted value. + * + * @param[in] unit_id ADC unit to configure the channel for + * @param[in] channel ADC channel to configure + * @param[in] adc_raw Pointer to store the raw 12-bit ADC value + * + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if the unit_id is invalid + * ESP_FAIL if the read fails + */ +esp_err_t lp_core_lp_adc_read_channel_raw(adc_unit_t unit_id, adc_channel_t channel, int *adc_raw); + +/** + * @brief Read the converted ADC value in millivolts from a channel + * + * @note The API converts the measured voltage based on the + * internal reference voltage of 1.1v and the the attenuation + * factors. It uses the formula: + * voltage = (raw_value * (1.1v / 4095)) * attenuation + * + * To avoid complex floating-point operations at runtime, + * the API converts the raw data to millivolts. Also, the + * conversion approximates the calculation when scaling + * the voltage by using pre-computed attenuation factors. + * + * @note The conversion approximates the measured voltage based on the + * internal reference voltage of 1.1v and the approximations of + * the attenuation factors. + * + * @param[in] unit_id ADC unit to configure the channel for + * @param[in] channel ADC channel to configure + * @param[out] voltage_mv Pointer to store the converted ADC value in millivolts + * + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if the unit_id is invalid or voltage_mv is NULL + * ESP_FAIL if the read fails + */ +esp_err_t lp_core_lp_adc_read_channel_converted(adc_unit_t unit_id, adc_channel_t channel, int *voltage_mv); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/lp_core/shared/ulp_lp_core_lp_adc_shared.c b/components/ulp/lp_core/shared/ulp_lp_core_lp_adc_shared.c new file mode 100644 index 0000000000..63bc549019 --- /dev/null +++ b/components/ulp/lp_core/shared/ulp_lp_core_lp_adc_shared.c @@ -0,0 +1,157 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" +#if SOC_LP_ADC_SUPPORTED + +#include "ulp_lp_core_lp_adc_shared.h" +#include "hal/adc_types.h" +#include "hal/adc_ll.h" + +#define VREF (1100) /* Internal Reference voltage in millivolts (1.1V) */ +#define INV_ATTEN_0DB (1000) /* Inverse of 10^0 * 1000 */ +#define INV_ATTEN_2_5DB (1335) /* Inverse of 10^(-2.5/20) * 1000 */ +#define INV_ATTEN_6DB (1996) /* Inverse of 10^(-6/20) * 1000 */ +#define INV_ATTEN_12DB (3984) /* Inverse of 10^(-12/20) * 1000 */ + +adc_oneshot_unit_handle_t s_adc1_handle; + +esp_err_t lp_core_lp_adc_init(adc_unit_t unit_id) +{ + if (unit_id != ADC_UNIT_1) { + // TODO: LP ADC2 does not work during sleep (DIG-396) + // For now, we do not allow LP ADC2 usage. + return ESP_ERR_INVALID_ARG; + } + +#if IS_ULP_COCPU + // Not supported + return ESP_ERR_NOT_SUPPORTED; +#else + /* LP ADC is being initialized from the HP core. + * Hence, we use the standard ADC driver APIs here. + */ + + /* Initialize ADC */ + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = unit_id, + .ulp_mode = ADC_ULP_MODE_LP_CORE, // LP Core will use the ADC + }; + + return (adc_oneshot_new_unit(&init_config, &s_adc1_handle)); +#endif /* IS_ULP_COCPU */ +} + +esp_err_t lp_core_lp_adc_deinit(adc_unit_t unit_id) +{ + if (unit_id != ADC_UNIT_1) { + return ESP_ERR_INVALID_ARG; + } + +#if IS_ULP_COCPU + // Not supported + return ESP_ERR_NOT_SUPPORTED; +#else + return (adc_oneshot_del_unit(s_adc1_handle)); +#endif /* IS_ULP_COCPU */ +} + +esp_err_t lp_core_lp_adc_config_channel(adc_unit_t unit_id, adc_channel_t channel, const lp_core_lp_adc_chan_cfg_t *chan_config) +{ + if (unit_id != ADC_UNIT_1) { + // TODO: LP ADC2 does not work during sleep (DIG-396) + // For now, we do not allow LP ADC2 usage. + return ESP_ERR_INVALID_ARG; + } +#if IS_ULP_COCPU + // Not supported + return ESP_ERR_NOT_SUPPORTED; +#else + adc_oneshot_chan_cfg_t config = { + .atten = chan_config->atten, + .bitwidth = chan_config->bitwidth, + }; + + return (adc_oneshot_config_channel(s_adc1_handle, channel, &config)); +#endif /* IS_ULP_COCPU */ +} + +esp_err_t lp_core_lp_adc_read_channel_raw(adc_unit_t unit_id, adc_channel_t channel, int *adc_raw) +{ + if (unit_id != ADC_UNIT_1 || adc_raw == NULL) { + return ESP_ERR_INVALID_ARG; + } + +#if IS_ULP_COCPU + uint32_t event = ADC_LL_EVENT_ADC1_ONESHOT_DONE; + + adc_oneshot_ll_clear_event(event); + adc_oneshot_ll_disable_all_unit(); + adc_oneshot_ll_enable(unit_id); + adc_oneshot_ll_set_channel(unit_id, channel); + + adc_oneshot_ll_start(unit_id); + while (!adc_oneshot_ll_get_event(event)) { + ; + } + *adc_raw = adc_oneshot_ll_get_raw_result(unit_id); + + adc_oneshot_ll_disable_all_unit(); +#else + return (adc_oneshot_read(s_adc1_handle, channel, adc_raw)); +#endif /* IS_ULP_COCPU */ + + return ESP_OK; +} + +esp_err_t lp_core_lp_adc_read_channel_converted(adc_unit_t unit_id, adc_channel_t channel, int *voltage_mv) +{ + esp_err_t ret = ESP_OK; + + if (unit_id != ADC_UNIT_1 || voltage_mv == NULL) { + return ESP_ERR_INVALID_ARG; + } + + /* Read the raw ADC value */ + int adc_raw; + ret = lp_core_lp_adc_read_channel_raw(unit_id, channel, &adc_raw); + if (ret != ESP_OK) { + return ret; + } + + /* On the esp32p4, the ADC raw value can be 12-bit wide. The internal Vref is 1.1V. + * The formula to convert the raw value to voltage is: + * voltage = (((raw_value / (2^12 - 1)) * 1.1V) * attenuation) + * + * To avoid many floating point calculations, we precompute the attenuation factors + * and perform the conversion in millivolts instead of volts. + */ + + int measured_voltage = adc_raw * VREF; // millivolts + measured_voltage /= 4095; + + adc_atten_t atten = adc_ll_get_atten(unit_id, channel); + switch (atten) { + case ADC_ATTEN_DB_0: + *voltage_mv = (measured_voltage * INV_ATTEN_0DB) / 1000; + break; + case ADC_ATTEN_DB_2_5: + *voltage_mv = (measured_voltage * INV_ATTEN_2_5DB) / 1000; + break; + case ADC_ATTEN_DB_6: + *voltage_mv = (measured_voltage * INV_ATTEN_6DB) / 1000; + break; + case ADC_ATTEN_DB_12: + *voltage_mv = (measured_voltage * INV_ATTEN_12DB) / 1000; + break; + default: + ret = ESP_ERR_INVALID_STATE; + } + + return ret; +} + +#endif /* CONFIG_SOC_LP_ADC_SUPPORTED */