From c14ad956a812e9b569b376165a244e415b6e7b3e Mon Sep 17 00:00:00 2001 From: Felipe Neves Date: Mon, 1 Jun 2026 17:02:21 -0300 Subject: [PATCH 1/2] Unify isensor ADC with ETM HW trigger and drop oneshot driver. ETM+MCPWM trigger on supported targets, max ADC clock, encoder char example on link layer, and guard efuse calib include for esp32 CI. --- CMakeLists.txt | 5 +- examples/axis_tuning/main/main.c | 27 +- examples/kconfig/hw_test_encoder.kconfig | 52 +- .../test_drivers/test_encoder/CMakeLists.txt | 4 +- .../test_encoder/main/CMakeLists.txt | 6 +- .../test_encoder/main/test_encoder.c | 182 ++++-- .../test_encoder/sdkconfig.defaults | 18 +- .../test_isensor_characterize/main/main.c | 9 + include/espFoC/gui_link/esp_foc_tuner.h | 5 + source/drivers/cali/esp_foc_adc_cali_curve.c | 2 +- source/drivers/current_sensor_adc.c | 174 +++++- source/drivers/current_sensor_adc_one_shot.c | 539 ------------------ source/drivers/espFoC/current_sensor_adc.h | 16 + .../espFoC/current_sensor_adc_one_shot.h | 42 -- source/drivers/isensor_adc_dma_esp32.c | 1 + source/drivers/isensor_adc_etm.c | 157 +++++ source/drivers/isensor_adc_internal.h | 7 + source/gui_link/esp_foc_tuner.c | 16 + test/test_isensor_adc.c | 28 +- 19 files changed, 609 insertions(+), 681 deletions(-) delete mode 100644 source/drivers/current_sensor_adc_one_shot.c delete mode 100644 source/drivers/espFoC/current_sensor_adc_one_shot.h create mode 100644 source/drivers/isensor_adc_etm.c diff --git a/CMakeLists.txt b/CMakeLists.txt index b1bf84c2..3a4b394c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,9 +37,12 @@ list(APPEND srcs "source/drivers/inverter_3pwm_mcpwm.c" "source/drivers/cali/esp_foc_adc_cali_curve.c" "source/drivers/cali/esp_foc_adc_cali_line.c" "source/drivers/cali/esp_foc_cali_curve_coeff.c" - "source/drivers/current_sensor_adc_one_shot.c" "source/osal/os_interface_idf.c") +if(CONFIG_SOC_ETM_SUPPORTED) + list(APPEND srcs "source/drivers/isensor_adc_etm.c") +endif() + if(target STREQUAL "esp32") list(APPEND srcs "source/drivers/isensor_adc_dma_esp32.c") list(APPEND requires esp_driver_i2s) diff --git a/examples/axis_tuning/main/main.c b/examples/axis_tuning/main/main.c index ff4eeb8c..78371c98 100644 --- a/examples/axis_tuning/main/main.c +++ b/examples/axis_tuning/main/main.c @@ -22,12 +22,9 @@ #elif defined(CONFIG_AXIS_TUNING_ROTOR_SIMU) #include "espFoC/rotor_sensor_simu.h" #endif -#if defined(CONFIG_IDF_TARGET_ESP32P4) -#include "espFoC/current_sensor_adc_one_shot.h" -#else #include "espFoC/current_sensor_adc.h" -#endif #include "espFoC/utils/esp_foc_q16.h" +#include "soc/soc_caps.h" static const char *TAG = "axis_tuning"; @@ -137,19 +134,6 @@ void app_main(void) return; } -#if defined(CONFIG_IDF_TARGET_ESP32P4) - esp_foc_isensor_adc_oneshot_config_t shunt_cfg = { - .axis_channels = {(adc_channel_t)CONFIG_AXIS_TUNING_ISENSE_CH_U, - (adc_channel_t)CONFIG_AXIS_TUNING_ISENSE_CH_V}, - .units = {(adc_unit_t)(CONFIG_AXIS_TUNING_ISENSE_ADC_UNIT - 1), - (adc_unit_t)(CONFIG_AXIS_TUNING_ISENSE_ADC_UNIT - 1)}, - .amp_gain = (float)CONFIG_AXIS_TUNING_ISENSE_AMP_GAIN_X100 / 100.0f, - .shunt_resistance = (float)CONFIG_AXIS_TUNING_ISENSE_SHUNT_MOHM / 1000.0f, - .number_of_channels = 2, - .enable_analog_encoder = false, - }; - s_shunts = isensor_adc_oneshot_new(&shunt_cfg, NULL); -#else esp_foc_isensor_adc_config_t shunt_cfg = { .channels = {(adc_channel_t)CONFIG_AXIS_TUNING_ISENSE_CH_U, (adc_channel_t)CONFIG_AXIS_TUNING_ISENSE_CH_V}, @@ -158,7 +142,16 @@ void app_main(void) .shunt_resistance = (float)CONFIG_AXIS_TUNING_ISENSE_SHUNT_MOHM / 1000.0f, }; s_shunts = isensor_adc_new(&shunt_cfg); + if (s_shunts != NULL) { +#if SOC_ETM_SUPPORTED + esp_foc_isensor_adc_etm_config_t etm_cfg = { + .mcpwm_timer = 0, + .event = ESP_FOC_ISENSOR_ADC_MCPWM_EVT_TIMER_TEZ, + }; + esp_foc_isensor_adc_set_etm_source(s_shunts, &etm_cfg); + esp_foc_isensor_adc_set_trigger(s_shunts, ESP_FOC_ISENSOR_ADC_TRIG_ETM); #endif + } if (s_shunts == NULL) { ESP_LOGE(TAG, "current sensor init failed"); return; diff --git a/examples/kconfig/hw_test_encoder.kconfig b/examples/kconfig/hw_test_encoder.kconfig index 28323b68..415ab03c 100644 --- a/examples/kconfig/hw_test_encoder.kconfig +++ b/examples/kconfig/hw_test_encoder.kconfig @@ -1,33 +1,55 @@ -menu "test_encoder — hardware (SPI AS5048)" +menu "test_encoder — hardware" - config FOC_ENCODER_SDA_PIN - int "I2C SDA (if used; often unused in SPI test)" + choice TEST_ENCODER_SENSOR + prompt "Rotor sensor" + default TEST_ENCODER_AS5600 + + config TEST_ENCODER_AS5600 + bool "AS5600 (I2C)" + + config TEST_ENCODER_AS5048 + bool "AS5048 (SPI)" + endchoice + + config TEST_ENCODER_POLE_PAIRS + int "Motor pole pairs (for electrical angle scale only)" + range 1 64 + default 7 + + config TEST_ENCODER_ENC_SDA + int "AS5600 I2C SDA" range 0 54 - default 8 + default 25 + depends on TEST_ENCODER_AS5600 - config FOC_ENCODER_SCL_PIN - int "I2C SCL (if used)" + config TEST_ENCODER_ENC_SCL + int "AS5600 I2C SCL" range 0 54 - default 9 + default 24 + depends on TEST_ENCODER_AS5600 - config FOC_ENCODER_MISO_PIN - int "SPI MISO" + config TEST_ENCODER_MISO_PIN + int "AS5048 SPI MISO" range 0 54 default 4 + depends on TEST_ENCODER_AS5048 - config FOC_ENCODER_MOSI_PIN - int "SPI MOSI" + config TEST_ENCODER_MOSI_PIN + int "AS5048 SPI MOSI" range 0 54 default 5 + depends on TEST_ENCODER_AS5048 - config FOC_ENCODER_SCK_PIN - int "SPI SCK" + config TEST_ENCODER_SCK_PIN + int "AS5048 SPI SCK" range 0 54 default 6 + depends on TEST_ENCODER_AS5048 - config FOC_ENCODER_CS_PIN - int "SPI CS" + config TEST_ENCODER_CS_PIN + int "AS5048 SPI CS" range 0 54 default 7 + depends on TEST_ENCODER_AS5048 endmenu diff --git a/examples/test_drivers/test_encoder/CMakeLists.txt b/examples/test_drivers/test_encoder/CMakeLists.txt index c91d3102..c58e961d 100644 --- a/examples/test_drivers/test_encoder/CMakeLists.txt +++ b/examples/test_drivers/test_encoder/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.5) -set (EXTRA_COMPONENT_DIRS "./../../../.") +set(EXTRA_COMPONENT_DIRS "./../../../.") include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(freerunning_foc) \ No newline at end of file +project(espfoc_encoder_char) diff --git a/examples/test_drivers/test_encoder/main/CMakeLists.txt b/examples/test_drivers/test_encoder/main/CMakeLists.txt index bbd088d1..7d5b9e0a 100644 --- a/examples/test_drivers/test_encoder/main/CMakeLists.txt +++ b/examples/test_drivers/test_encoder/main/CMakeLists.txt @@ -1,2 +1,4 @@ -idf_component_register(SRCS "test_encoder.c" - INCLUDE_DIRS ".") \ No newline at end of file +set(MOCK_DRIVER_SRC "${CMAKE_CURRENT_LIST_DIR}/../../../../test/mock_drivers.c") + +idf_component_register(SRCS "test_encoder.c" "${MOCK_DRIVER_SRC}" + INCLUDE_DIRS "." "${CMAKE_CURRENT_LIST_DIR}/../../../../test") diff --git a/examples/test_drivers/test_encoder/main/test_encoder.c b/examples/test_drivers/test_encoder/main/test_encoder.c index d22fa1fe..42b50e08 100644 --- a/examples/test_drivers/test_encoder/main/test_encoder.c +++ b/examples/test_drivers/test_encoder/main/test_encoder.c @@ -2,56 +2,174 @@ * MIT License * * Copyright (c) 2021 Felipe Neves + * + * Encoder characterization: real rotor sensor on a dummy axis (mock inverter + + * mock isensor) exposed to the link layer for counts/degrees verification. + * + * Workflow: + * 1. Flash and connect espFoC Studio / tunerctl on UART. + * 2. CONNECT → SCOPE_START, rotate the shaft by hand. + * 3. Scope channels: raw counts, mech turns, degrees, PLL omega, elec angle. + * 4. READ ENC_COUNTS / ENC_DEG / ENC_TURNS for spot checks. + * 5. EXEC ENC_SET_ZERO to re-zero the encoder. */ + #include "esp_log.h" #include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" -#include "espFoC/rotor_sensor_as5600.h" -#include "espFoC/rotor_sensor_as5048.h" #include "espFoC/esp_foc.h" +#include "espFoC/gui_link/esp_foc_tuner.h" +#include "espFoC/gui_link/esp_foc_link_session.h" +#include "espFoC/esp_foc_estimator_q16.h" #include "espFoC/utils/esp_foc_q16.h" +#include "mock_drivers.h" -static const char *TAG = "esp-foc-example"; +#if defined(CONFIG_TEST_ENCODER_AS5600) +#include "espFoC/rotor_sensor_as5600.h" +#elif defined(CONFIG_TEST_ENCODER_AS5048) +#include "espFoC/rotor_sensor_as5048.h" +#endif + +static const char *TAG = "encoder_char"; -static esp_foc_rotor_sensor_t *sensor; -static esp_foc_rotor_sensor_t *sensor2; +static esp_foc_axis_t s_axis; +static mock_inverter_t s_mock_inv; +static mock_isensor_t s_mock_isen; +static esp_foc_rotor_sensor_t *s_rotor; +static q16_t s_enc_deg_q16; +static q16_t s_enc_elec_deg_q16; -static void initialize_foc_drivers(void) +uint32_t esp_foc_tuner_firmware_type(void) { - sensor = rotor_sensor_as5600_new( - CONFIG_FOC_ENCODER_SDA_PIN, - CONFIG_FOC_ENCODER_SCL_PIN, - 0 - ); - - if(sensor == NULL) { - ESP_LOGE(TAG, "failed to create as5600 encoder driver"); - } + return ESP_FOC_TUNER_FIRMWARE_TYPE_ENCHAR; +} - sensor2 = rotor_sensor_as5048_new( - CONFIG_FOC_ENCODER_MOSI_PIN, - CONFIG_FOC_ENCODER_MISO_PIN, - CONFIG_FOC_ENCODER_SCK_PIN, - CONFIG_FOC_ENCODER_CS_PIN, +static esp_foc_rotor_sensor_t *encoder_new(void) +{ +#if defined(CONFIG_TEST_ENCODER_AS5600) + return rotor_sensor_as5600_new( + CONFIG_TEST_ENCODER_ENC_SDA, + CONFIG_TEST_ENCODER_ENC_SCL, + 0); +#elif defined(CONFIG_TEST_ENCODER_AS5048) + return rotor_sensor_as5048_new( + CONFIG_TEST_ENCODER_MOSI_PIN, + CONFIG_TEST_ENCODER_MISO_PIN, + CONFIG_TEST_ENCODER_SCK_PIN, + CONFIG_TEST_ENCODER_CS_PIN, 0, - 0 - ); + 0); +#else + return NULL; +#endif +} - if(sensor2 == NULL) { - ESP_LOGE(TAG, "failed to create as5048 encoder driver"); +static void encoder_axis_poll(esp_foc_axis_t *axis) +{ + if (axis->rotor_sensor_driver == NULL || + axis->rotor_sensor_driver->read_counts == NULL) { + return; + } + + q16_t ticks = axis->rotor_sensor_driver->read_counts(axis->rotor_sensor_driver); + axis->rotor_shaft_ticks = ticks; + axis->rotor_position = ticks; + + q16_t theta_mech = q16_normalize_angle(q16_mul(ticks, axis->encoder_inv_cpr_q16)); + esp_foc_estimator_q16_set_meas(&axis->rotor_estimator, theta_mech); + esp_foc_estimator_q16_step(&axis->rotor_estimator); + + s_enc_deg_q16 = q16_mul(theta_mech, q16_from_float(360.0f)); + q16_t theta_elec = esp_foc_estimator_q16_theta_elec(&axis->rotor_estimator); + s_enc_elec_deg_q16 = q16_mul(theta_elec, q16_from_float(360.0f)); +} + +static void wire_scope_channels(void) +{ +#if defined(CONFIG_ESP_FOC_SCOPE) + esp_foc_scope_add_channel(&s_axis.rotor_shaft_ticks, 0); + esp_foc_scope_add_channel(&s_axis.rotor_estimator.theta_meas_mech, 1); + esp_foc_scope_add_channel(&s_enc_deg_q16, 2); + esp_foc_scope_add_channel(&s_axis.rotor_estimator.omega_est_mech, 3); + esp_foc_scope_add_channel(&s_enc_elec_deg_q16, 4); + esp_foc_scope_add_channel( + (const q16_t *)(const volatile void *)&s_axis.rotor_estimator.pll_err, 5); + esp_foc_scope_initalize(); +#endif +} + +#if defined(CONFIG_ESP_FOC_SCOPE) +static void pump_scope_idle(void) +{ + for (int n = 0; n < CONFIG_ESP_FOC_SCOPE_BUFFER_SIZE; n++) { + esp_foc_scope_data_push(); + } +} +#endif + +static void encoder_task(void *arg) +{ + (void)arg; + const TickType_t period = pdMS_TO_TICKS(1); + while (1) { + encoder_axis_poll(&s_axis); + vTaskDelay(period); } } void app_main(void) { - initialize_foc_drivers(); - - while(1) { - esp_foc_sleep_ms(100); - q16_t r1 = (sensor != NULL) ? sensor->read_counts(sensor) : 0; - q16_t r2 = (sensor2 != NULL) ? sensor2->read_counts(sensor2) : 0; - ESP_LOGI(TAG, "Turn the motor axis by Hand! Encoder reading raw: %f", (double)q16_to_float(r1)); - ESP_LOGI(TAG, "Turn the motor axis by Hand! Encoder SPI reading raw: %f", (double)q16_to_float(r2)); + ESP_LOGI(TAG, "encoder characterization — connect host, SCOPE, rotate shaft"); + + mock_inverter_init(&s_mock_inv, 12.0f, (float)CONFIG_ESP_FOC_PWM_RATE_HZ); + mock_isensor_init(&s_mock_isen); + + s_rotor = encoder_new(); + if (s_rotor == NULL) { + ESP_LOGE(TAG, "encoder driver init failed"); + return; + } + + esp_foc_motor_control_settings_t settings = { + .motor_pole_pairs = CONFIG_TEST_ENCODER_POLE_PAIRS, + .natural_direction = ESP_FOC_MOTOR_NATURAL_DIRECTION_CW, + .motor_unit = 0, + }; + + esp_foc_err_t err = esp_foc_initialize_axis( + &s_axis, + mock_inverter_interface(&s_mock_inv), + s_rotor, + mock_isensor_interface(&s_mock_isen), + settings); + if (err != ESP_FOC_OK) { + ESP_LOGE(TAG, "dummy axis init failed: %d", err); + return; + } + + wire_scope_channels(); +#if defined(CONFIG_ESP_FOC_SCOPE) + pump_scope_idle(); +#endif + + ESP_ERROR_CHECK(esp_foc_link_attach_axis(0, &s_axis)); + + if (esp_foc_task_spawn(encoder_task, NULL, 4096, 5, NULL) != 0) { + ESP_LOGE(TAG, "encoder poll task spawn failed"); + return; + } + + ESP_LOGW(TAG, + ">>> Rotate the shaft. CONNECT + SCOPE_START on UART. " + "READ 0x0057=counts 0x0058=deg 0x0059=turns. EXEC 0x00A6=zero."); + + while (1) { +#if defined(CONFIG_ESP_FOC_SCOPE) + pump_scope_idle(); +#endif + vTaskDelay(pdMS_TO_TICKS(200)); } } diff --git a/examples/test_drivers/test_encoder/sdkconfig.defaults b/examples/test_drivers/test_encoder/sdkconfig.defaults index 15dfe613..763c2fb2 100644 --- a/examples/test_drivers/test_encoder/sdkconfig.defaults +++ b/examples/test_drivers/test_encoder/sdkconfig.defaults @@ -1,4 +1,16 @@ -CONFIG_ESP_FOC_SCOPE=n +CONFIG_ESP_TASK_WDT_EN=n + +CONFIG_ESP_FOC_TUNER_ENABLE=y +CONFIG_ESP_FOC_BRIDGE_UART=y +CONFIG_ESP_FOC_BRIDGE_UART_BAUD=921600 +CONFIG_ESP_FOC_BRIDGE_UART_TX_PIN=17 +CONFIG_ESP_FOC_BRIDGE_UART_RX_PIN=18 + +CONFIG_ESP_FOC_SCOPE=y +CONFIG_ESP_FOC_SCOPE_NUM_CHANNELS=8 +CONFIG_ESP_FOC_SCOPE_BUFFER_SIZE=512 + +CONFIG_ESP_FOC_PWM_RATE_HZ=20000 +CONFIG_ESP_FOC_CALIBRATION_NVS=n + CONFIG_COMPILER_OPTIMIZATION_PERF=y -CONFIG_ESP_FOC_DEBUG_CORE_TIMING=n -CONFIG_FREERTOS_ISR_STACKSIZE=4096 \ No newline at end of file diff --git a/examples/test_drivers/test_isensor_characterize/main/main.c b/examples/test_drivers/test_isensor_characterize/main/main.c index fdc947de..f210a1dc 100644 --- a/examples/test_drivers/test_isensor_characterize/main/main.c +++ b/examples/test_drivers/test_isensor_characterize/main/main.c @@ -27,6 +27,7 @@ #include "espFoC/gui_link/esp_foc_link_session.h" #include "espFoC/inverter_6pwm_mcpwm.h" #include "espFoC/current_sensor_adc.h" +#include "soc/soc_caps.h" #include "espFoC/utils/esp_foc_q16.h" static const char *TAG = "isensor_char"; @@ -120,6 +121,14 @@ void app_main(void) ESP_LOGE(TAG, "current sensor init failed"); return; } +#if SOC_ETM_SUPPORTED + esp_foc_isensor_adc_etm_config_t etm_cfg = { + .mcpwm_timer = 0, + .event = ESP_FOC_ISENSOR_ADC_MCPWM_EVT_TIMER_TEZ, + }; + esp_foc_isensor_adc_set_etm_source(s_shunts, &etm_cfg); + esp_foc_isensor_adc_set_trigger(s_shunts, ESP_FOC_ISENSOR_ADC_TRIG_ETM); +#endif esp_foc_axis_bench_config_t bench_cfg = { .motor = { diff --git a/include/espFoC/gui_link/esp_foc_tuner.h b/include/espFoC/gui_link/esp_foc_tuner.h index b761e1aa..02f134c7 100644 --- a/include/espFoC/gui_link/esp_foc_tuner.h +++ b/include/espFoC/gui_link/esp_foc_tuner.h @@ -64,6 +64,9 @@ typedef enum { ESP_FOC_TUNER_PARAM_IV_Q16 = 0x0054, ESP_FOC_TUNER_PARAM_IQ_MEAS_Q16 = 0x0055, ESP_FOC_TUNER_PARAM_BENCH_THETA_Q16 = 0x0056, + ESP_FOC_TUNER_PARAM_ENC_COUNTS_Q16 = 0x0057, + ESP_FOC_TUNER_PARAM_ENC_DEG_Q16 = 0x0058, + ESP_FOC_TUNER_PARAM_ENC_TURNS_Q16 = 0x0059, /* Write: gain swap (atomic) */ ESP_FOC_TUNER_WRITE_KP_Q16 = 0x0020, @@ -87,6 +90,7 @@ typedef enum { ESP_FOC_TUNER_CMD_STOP_AXIS = 0x00A3, ESP_FOC_TUNER_CMD_RUN_AXIS = 0x00A4, ESP_FOC_TUNER_CMD_CALISENSOR = 0x00A5, + ESP_FOC_TUNER_CMD_ENC_SET_ZERO = 0x00A6, ESP_FOC_TUNER_CMD_STORE_NVS = 0x00B0, ESP_FOC_TUNER_CMD_ERASE_NVS = 0x00B2, @@ -192,6 +196,7 @@ uint32_t esp_foc_tuner_firmware_type(void); #define ESP_FOC_TUNER_FIRMWARE_TYPE_GENERIC 0u #define ESP_FOC_TUNER_FIRMWARE_TYPE_TSGX 0x58475354u /* 'TSGX' LE */ #define ESP_FOC_TUNER_FIRMWARE_TYPE_ISCHAR 0x52484349u /* 'ICHR' LE — isensor characterization */ +#define ESP_FOC_TUNER_FIRMWARE_TYPE_ENCHAR 0x5248454Eu /* 'ENHR' LE — encoder characterization */ #ifdef __cplusplus } diff --git a/source/drivers/cali/esp_foc_adc_cali_curve.c b/source/drivers/cali/esp_foc_adc_cali_curve.c index 39f2ff0b..e1ddb714 100644 --- a/source/drivers/cali/esp_foc_adc_cali_curve.c +++ b/source/drivers/cali/esp_foc_adc_cali_curve.c @@ -11,11 +11,11 @@ #include #include "esp_err.h" #include "sdkconfig.h" -#include "esp_efuse_rtc_calib.h" #include "esp_private/adc_share_hw_ctrl.h" #include "esp_foc_adc_cali.h" #if SOC_ADC_CALIB_SCHEME_CURVE_FITTING_SUPPORTED +#include "esp_efuse_rtc_calib.h" static const int coeff_a_scaling = 65536; diff --git a/source/drivers/current_sensor_adc.c b/source/drivers/current_sensor_adc.c index 11e71f48..1ac70a81 100644 --- a/source/drivers/current_sensor_adc.c +++ b/source/drivers/current_sensor_adc.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "sdkconfig.h" #include "esp_log.h" #include "esp_check.h" @@ -78,6 +79,9 @@ typedef struct { isensor_callback_t callback; void *user_data; bool started; +#if SOC_ETM_SUPPORTED + esp_foc_isensor_adc_etm_config_t etm_cfg; +#endif } isensor_adc_t; DRAM_ATTR static isensor_adc_t s_isensor; @@ -109,6 +113,88 @@ static void isensor_adc_digi_apply_convert_limit(void) adc_ll_digi_convert_limit_enable(true); } +static void isensor_adc_apply_max_clock(isensor_adc_t *isensor) +{ +#if CONFIG_IDF_TARGET_ESP32 + adc_ll_set_sample_cycle(ADC_LL_SAMPLE_CYCLE_DEFAULT); + adc_ll_digi_set_clk_div(2); +#elif CONFIG_IDF_TARGET_ESP32S2 + ESP_ERROR_CHECK(esp_clk_tree_enable_src(SOC_MOD_CLK_APLL, true)); + uint32_t clk_hz = 0; + ESP_ERROR_CHECK(esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_APLL, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &clk_hz)); + isensor->hal_cfg.clk_src = ADC_DIGI_CLK_SRC_APLL; + isensor->hal_cfg.clk_src_freq_hz = clk_hz; + adc_ll_digi_clk_sel(ADC_DIGI_CLK_SRC_APLL); + adc_ll_digi_controller_clk_div(0, 1, 0); + adc_ll_digi_set_clk_div(1); + adc_ll_set_sample_cycle(ADC_LL_SAMPLE_CYCLE_DEFAULT); +#elif CONFIG_IDF_TARGET_ESP32S3 + ESP_ERROR_CHECK(esp_clk_tree_enable_src(SOC_MOD_CLK_PLL_D2, true)); + uint32_t clk_hz = 0; + ESP_ERROR_CHECK(esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_PLL_D2, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &clk_hz)); + isensor->hal_cfg.clk_src = ADC_DIGI_CLK_SRC_PLL_F240M; + isensor->hal_cfg.clk_src_freq_hz = clk_hz; + adc_ll_digi_clk_sel(ADC_DIGI_CLK_SRC_PLL_F240M); + adc_ll_digi_controller_clk_div(0, 1, 0); + adc_ll_digi_set_clk_div(1); + adc_ll_set_sample_cycle(ADC_LL_SAMPLE_CYCLE_DEFAULT); +#else + const soc_module_clk_t fast_src = SOC_MOD_CLK_PLL_F80M; + ESP_ERROR_CHECK(esp_clk_tree_enable_src(fast_src, true)); + uint32_t clk_hz = 0; + ESP_ERROR_CHECK(esp_clk_tree_src_get_freq_hz(fast_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &clk_hz)); + isensor->hal_cfg.clk_src = ADC_DIGI_CLK_SRC_PLL_F80M; + isensor->hal_cfg.clk_src_freq_hz = clk_hz; + adc_ll_digi_clk_sel(ADC_DIGI_CLK_SRC_PLL_F80M); + adc_ll_digi_controller_clk_div(0, 1, 0); + adc_ll_digi_set_clk_div(1); + adc_ll_set_sample_cycle(ADC_LL_SAMPLE_CYCLE_DEFAULT); +#endif + ESP_LOGI(TAG, "ADC fast clock: src=%d freq=%" PRIu32 " Hz", + (int)isensor->hal_cfg.clk_src, isensor->hal_cfg.clk_src_freq_hz); +} + +static void isensor_adc_rearm(isensor_adc_t *isensor) +{ + isensor_adc_dma_reset(&isensor->dma_ctx); + adc_hal_digi_reset(); + adc_hal_digi_dma_link(&isensor->hal, isensor->rx_buf); +#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE + esp_cache_msync(isensor->hal.rx_desc, isensor->rx_desc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); +#endif + isensor_adc_dma_start(&isensor->dma_ctx, isensor->hal.rx_desc); + adc_hal_digi_connect(true); + if (isensor->trigger == ESP_FOC_ISENSOR_ADC_TRIG_SOFTWARE) { + adc_hal_digi_enable(true); + } else { + adc_hal_digi_enable(false); + } + isensor->state = ESP_FOC_ISENSOR_ADC_STATE_BUSY; +} + +#if SOC_ETM_SUPPORTED +static esp_foc_err_t isensor_adc_apply_trigger_mode(isensor_adc_t *obj) +{ + if (obj->trigger == ESP_FOC_ISENSOR_ADC_TRIG_ETM) { + esp_err_t err = isensor_adc_etm_connect(&obj->etm_cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ETM connect failed: %d", err); + return ESP_FOC_ERR_UNKNOWN; + } + err = isensor_adc_etm_enable(true); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ETM enable failed: %d", err); + return ESP_FOC_ERR_UNKNOWN; + } + adc_hal_digi_enable(false); + } else { + isensor_adc_etm_enable(false); + isensor_adc_etm_disconnect(); + } + return ESP_FOC_OK; +} +#endif + static esp_err_t isensor_adc_gpio_init(adc_unit_t unit, uint32_t chan_mask) { while (chan_mask) { @@ -196,9 +282,13 @@ static void IRAM_ATTR isensor_adc_dma_done(void *arg) ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); #endif - adc_hal_digi_enable(false); - adc_hal_digi_connect(false); - isensor->state = ESP_FOC_ISENSOR_ADC_STATE_IDLE; + if (isensor->trigger == ESP_FOC_ISENSOR_ADC_TRIG_ETM) { + isensor_adc_rearm(isensor); + } else { + adc_hal_digi_enable(false); + adc_hal_digi_connect(false); + isensor->state = ESP_FOC_ISENSOR_ADC_STATE_IDLE; + } } static esp_err_t isensor_adc_hw_start(isensor_adc_t *isensor) @@ -225,23 +315,12 @@ static esp_err_t isensor_adc_hw_start(isensor_adc_t *isensor) adc_hal_digi_init(&isensor->hal); adc_hal_digi_controller_config(&isensor->hal, &isensor->hal_cfg); + isensor_adc_apply_max_clock(isensor); isensor_adc_digi_apply_convert_limit(); - adc_hal_digi_enable(false); - adc_hal_digi_connect(false); isensor_adc_dma_stop(&isensor->dma_ctx); - adc_hal_digi_reset(); - adc_hal_digi_dma_link(&isensor->hal, isensor->rx_buf); - -#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE - esp_cache_msync(isensor->hal.rx_desc, isensor->rx_desc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); -#endif - - isensor_adc_dma_start(&isensor->dma_ctx, isensor->hal.rx_desc); - adc_hal_digi_connect(true); - adc_hal_digi_enable(true); - isensor->state = ESP_FOC_ISENSOR_ADC_STATE_BUSY; isensor->started = true; + isensor_adc_rearm(isensor); return ESP_OK; } @@ -341,25 +420,24 @@ static void sample_isensors(esp_foc_isensor_t *self) return; } - isensor_adc_dma_reset(&obj->dma_ctx); - adc_hal_digi_reset(); - adc_hal_digi_dma_link(&obj->hal, obj->rx_buf); -#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE - esp_cache_msync(obj->hal.rx_desc, obj->rx_desc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); -#endif - isensor_adc_dma_start(&obj->dma_ctx, obj->hal.rx_desc); - adc_hal_digi_connect(true); - adc_hal_digi_enable(true); - obj->state = ESP_FOC_ISENSOR_ADC_STATE_BUSY; + isensor_adc_rearm(obj); } static void calibrate_isensors(esp_foc_isensor_t *self, int calibration_rounds) { isensor_adc_t *obj = __containerof(self, isensor_adc_t, interface); +#if SOC_ETM_SUPPORTED + const bool was_etm = (obj->trigger == ESP_FOC_ISENSOR_ADC_TRIG_ETM); + if (was_etm) { + isensor_adc_etm_enable(false); + obj->trigger = ESP_FOC_ISENSOR_ADC_TRIG_SOFTWARE; + } +#else if (obj->trigger != ESP_FOC_ISENSOR_ADC_TRIG_SOFTWARE) { return; } +#endif isensor_values_t val; esp_foc_sleep_ms(100); @@ -392,6 +470,15 @@ static void calibrate_isensors(esp_foc_isensor_t *self, int calibration_rounds) self->fetch_isensors(self, &val); ESP_LOGI(TAG, "No-current test: iu=%f iv=%f iw=%f", q16_to_float(val.iu_axis_0), q16_to_float(val.iv_axis_0), q16_to_float(val.iw_axis_0)); + +#if SOC_ETM_SUPPORTED + if (was_etm) { + obj->trigger = ESP_FOC_ISENSOR_ADC_TRIG_ETM; + if (isensor_adc_apply_trigger_mode(obj) == ESP_FOC_OK) { + isensor_adc_rearm(obj); + } + } +#endif } static void set_callback(esp_foc_isensor_t *self, isensor_callback_t cb, void *arg) @@ -434,14 +521,45 @@ esp_foc_err_t esp_foc_isensor_adc_set_trigger(esp_foc_isensor_t *isensor, if (isensor == NULL) { return ESP_FOC_ERR_INVALID_ARG; } +#if !SOC_ETM_SUPPORTED if (mode == ESP_FOC_ISENSOR_ADC_TRIG_ETM) { return ESP_FOC_ERR_NOT_SUPPORTED; } +#endif isensor_adc_t *obj = __containerof(isensor, isensor_adc_t, interface); obj->trigger = mode; +#if SOC_ETM_SUPPORTED + esp_foc_err_t err = isensor_adc_apply_trigger_mode(obj); + if (err != ESP_FOC_OK) { + obj->trigger = ESP_FOC_ISENSOR_ADC_TRIG_SOFTWARE; + return err; + } + if (obj->started && obj->state != ESP_FOC_ISENSOR_ADC_STATE_BUSY) { + isensor_adc_rearm(obj); + } +#endif return ESP_FOC_OK; } +#if SOC_ETM_SUPPORTED +esp_foc_err_t esp_foc_isensor_adc_set_etm_source(esp_foc_isensor_t *isensor, + const esp_foc_isensor_adc_etm_config_t *cfg) +{ + if (isensor == NULL || cfg == NULL) { + return ESP_FOC_ERR_INVALID_ARG; + } + if (cfg->mcpwm_timer >= SOC_MCPWM_TIMERS_PER_GROUP) { + return ESP_FOC_ERR_INVALID_ARG; + } + isensor_adc_t *obj = __containerof(isensor, isensor_adc_t, interface); + obj->etm_cfg = *cfg; + if (obj->trigger == ESP_FOC_ISENSOR_ADC_TRIG_ETM) { + return isensor_adc_apply_trigger_mode(obj); + } + return ESP_FOC_OK; +} +#endif + esp_foc_isensor_t *isensor_adc_new(esp_foc_isensor_adc_config_t *config) { if (config == NULL) { @@ -467,6 +585,10 @@ esp_foc_isensor_t *isensor_adc_new(esp_foc_isensor_adc_config_t *config) s_isensor.channels[1] = config->channels[1]; s_isensor.trigger = ESP_FOC_ISENSOR_ADC_TRIG_SOFTWARE; s_isensor.state = ESP_FOC_ISENSOR_ADC_STATE_IDLE; +#if SOC_ETM_SUPPORTED + s_isensor.etm_cfg.mcpwm_timer = 0; + s_isensor.etm_cfg.event = ESP_FOC_ISENSOR_ADC_MCPWM_EVT_TIMER_TEZ; +#endif s_isensor.adc_to_current_scale = adc_to_volts * (1.0f / (config->amp_gain * config->shunt_resistance)); s_isensor.adc_to_current_scale_q16 = q16_from_float(2048.0f * s_isensor.adc_to_current_scale); diff --git a/source/drivers/current_sensor_adc_one_shot.c b/source/drivers/current_sensor_adc_one_shot.c deleted file mode 100644 index d63b985f..00000000 --- a/source/drivers/current_sensor_adc_one_shot.c +++ /dev/null @@ -1,539 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Felipe Neves - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include -#include -#include -#include -#include "espFoC/utils/esp_foc_q16.h" -#include "espFoC/utils/biquad_q16.h" -#include "espFoC/utils/foc_math_q16.h" -#include "espFoC/driver_q16_local.h" -#include "espFoC/current_sensor_adc_one_shot.h" -#include "hal/adc_hal.h" -#include "hal/adc_oneshot_hal.h" -#include "driver/gpio.h" -#include "esp_intr_alloc.h" -#include "esp_log.h" -#include "esp_clk_tree.h" - -#ifdef CONFIG_IDF_TARGET_ESP32P4 -static const char *TAG = "ESP_FOC_ISENSOR_ONE_SHOT"; - -typedef struct { - adc_channel_t channels[4]; - adc_unit_t units[4]; - adc_oneshot_hal_ctx_t hal; - float adc_to_current_scale; - q16_t adc_to_current_scale_q16; - int32_t encoder_prev_iq; - int64_t encoder_accum_i64; - uint32_t encoder_ppr_u32; - /* latest_raw is the unfiltered ADC sample per channel — used by - * calibration AND by the optional analog encoder, which must not - * see filtered position. */ - int32_t latest_raw[4]; - /* filtered_count is bq[i] output mapped back to count units. */ - int32_t filtered_count[4]; - esp_foc_biquad_q16_t bq[4]; - float offsets[4]; - int32_t offset_counts[4]; - /* Optional Clarke publish targets — see current_sensor_adc.c for - * the same plumbing on the continuous driver. */ - volatile q16_t *publish_alpha; - volatile q16_t *publish_beta; - volatile q16_t *publish_iu; - volatile q16_t *publish_iv; - esp_foc_isensor_t interface; - - int number_of_channels; - int number_of_conversions; - isensor_callback_t callback; - void *user_data; - - bool enable_analog_encoder; - float analog_encoder_ppr; - float encoder_accumulated; - float encoder_previous; - uint16_t encoder_zero_offset; - esp_foc_rotor_sensor_t encoder_interface; - -}isensor_adc_t; - -#define ISENSOR_HWREG(x) (*((volatile uint32_t *)(x))) -#define ISENSOR_LP_ADC 0x50127000 -#define ISENSOR_LP_ADC_INT_CLR_OFFSET 0x0054 -#define ISENSOR_LP_ADC_INT_ENA_SET_OFFSET 0x0058 -#define ISENSOR_LP_ADC_READER1_CTRL_OFFSET 0x0000 - -DRAM_ATTR static isensor_adc_t isensor_adc; -static bool adc_initialized = false; -static const float adc_to_volts = ((3.1f)/ 4096.0f); - -static adc_oneshot_hal_chan_cfg_t chan_cfg = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = SOC_ADC_DIGI_MAX_BITWIDTH, -}; - -static adc_oneshot_hal_cfg_t hal_cfg = { - .work_mode = ADC_HAL_LP_MODE, - .clk_src = ADC_RTC_CLK_SRC_DEFAULT, - .clk_src_freq_hz = 0, -}; - -static void oneshot_adc_start_sample(isensor_adc_t *isensor, adc_channel_t channel, adc_unit_t unit); - -static inline void adc_publish_alpha_beta(isensor_adc_t *obj) -{ - const int32_t adc_rng = 2048; - int32_t d0 = obj->filtered_count[0] - obj->offset_counts[0]; - int32_t d1 = obj->filtered_count[1] - obj->offset_counts[1]; - d0 = esp_foc_clamp_int32(d0, -adc_rng, adc_rng); - d1 = esp_foc_clamp_int32(d1, -adc_rng, adc_rng); - q16_t iu = q16_mul(esp_foc_q16_from_adc_diff_clamped(d0, adc_rng), - obj->adc_to_current_scale_q16); - q16_t iv = q16_mul(esp_foc_q16_from_adc_diff_clamped(d1, adc_rng), - obj->adc_to_current_scale_q16); - q16_t iw = q16_sub(0, q16_add(iu, iv)); - q16_t alpha, beta; - q16_clarke(iu, iv, iw, &alpha, &beta); - *obj->publish_alpha = alpha; - *obj->publish_beta = beta; - if (obj->publish_iu != NULL) { - *obj->publish_iu = iu; - } - if (obj->publish_iv != NULL) { - *obj->publish_iv = iv; - } -} - -static void isensor_adc_isr(void *arg) -{ - isensor_adc_t * isensor = (isensor_adc_t *)arg; - uint32_t event = (isensor->units[isensor->number_of_conversions] == ADC_UNIT_1) ? - ADC_LL_EVENT_ADC1_ONESHOT_DONE : ADC_LL_EVENT_ADC2_ONESHOT_DONE; - - /* Clear the interrupts flags */ - ISENSOR_HWREG(ISENSOR_LP_ADC + ISENSOR_LP_ADC_INT_CLR_OFFSET) = 0x7F; - - if(adc_oneshot_ll_get_event(event)) { - - int32_t raw = (int32_t)adc_oneshot_ll_get_raw_result( - isensor->units[isensor->number_of_conversions]); - int ch = isensor->number_of_conversions; - isensor->latest_raw[ch] = raw; - /* Only filter the channels that carry currents. When the - * analog encoder is enabled it lives on channel 2 and must - * stay raw — otherwise the position read would lag the - * actual rotor angle. */ - if (isensor->enable_analog_encoder && ch == 2) { - isensor->filtered_count[ch] = raw; - } else { - q16_t y = esp_foc_biquad_q16_update(&isensor->bq[ch], - (q16_t)(raw << 16)); - isensor->filtered_count[ch] = y >> 16; - } - - isensor->number_of_conversions++; - if(isensor->number_of_conversions == isensor->number_of_channels) { - isensor->number_of_conversions = 0; - adc_oneshot_ll_clear_event(event); - adc_oneshot_ll_disable_all_unit(); - if(isensor->publish_alpha != NULL && isensor->publish_beta != NULL) { - adc_publish_alpha_beta(isensor); - } - if(isensor->callback) { - isensor->callback(isensor->user_data); - } - } else { - adc_oneshot_ll_clear_event(event); - oneshot_adc_start_sample(isensor, - isensor->channels[isensor->number_of_conversions], - isensor->units[isensor->number_of_conversions]); - } - } else { - adc_oneshot_ll_clear_event(event); - } -} - -static int8_t adc_get_io_num(adc_unit_t adc_unit, uint8_t adc_channel) -{ - assert(adc_unit < SOC_ADC_PERIPH_NUM); - uint8_t adc_n = (adc_unit == ADC_UNIT_1) ? 0 : 1; - return adc_channel_io_map[adc_n][adc_channel]; -} - -static esp_err_t adc_gpio_init(adc_unit_t adc_unit, uint16_t channel_mask) -{ - esp_err_t ret = ESP_OK; - uint64_t gpio_mask = 0; - uint32_t n = 0; - int8_t io = 0; - - while (channel_mask) { - if (channel_mask & 0x1) { - io = adc_get_io_num(adc_unit, n); - if (io < 0) { - return ESP_ERR_INVALID_ARG; - } - gpio_mask |= BIT64(io); - } - channel_mask = channel_mask >> 1; - n++; - } - - gpio_config_t cfg = { - .pin_bit_mask = gpio_mask, - .mode = GPIO_MODE_DISABLE, - }; - ret = gpio_config(&cfg); - - return ret; -} - - -static void oneshot_adc_init(isensor_adc_t *isensor) -{ - for(int i = 0; i < isensor->number_of_channels; i++) { - uint16_t adc_mask = (1 << isensor->channels[i]); - adc_gpio_init(isensor->units[i], adc_mask); - } - - ESP_LOGI(TAG, "interrupt source is :%"PRIx8, ETS_LP_ADC_INTR_SOURCE); - /* And allocate the LP_ADC interrupt vector */ - esp_err_t sts = esp_intr_alloc(ETS_LP_ADC_INTR_SOURCE, ESP_INTR_FLAG_IRAM, - (intr_handler_t)isensor_adc_isr, (void *)isensor,NULL); - - ESP_ERROR_CHECK(sts); - - esp_foc_sleep_ms(10); -} - -static void oneshot_adc_start_sample(isensor_adc_t *isensor, adc_channel_t channel, adc_unit_t unit) -{ - hal_cfg.unit = unit; - adc_oneshot_hal_init(&isensor->hal, &hal_cfg); - adc_oneshot_hal_channel_config(&isensor->hal, &chan_cfg, channel); - adc_oneshot_hal_setup(&isensor->hal, channel); - adc_oneshot_ll_enable(unit); - - /* Tweak LP ADC registers to enable their both interrupts */ - ISENSOR_HWREG(ISENSOR_LP_ADC + ISENSOR_LP_ADC_INT_ENA_SET_OFFSET) = 0x03; - - adc_oneshot_ll_start(unit); - -} - -static void fetch_isensors(esp_foc_isensor_t *self, isensor_values_t *values) -{ - isensor_adc_t *obj = __containerof(self, isensor_adc_t, interface); - const int32_t adc_rng = 2048; - - int32_t f0 = obj->filtered_count[0]; - int32_t f1 = obj->filtered_count[1]; - int32_t f2 = obj->filtered_count[2]; - int32_t f3 = obj->filtered_count[3]; - - int32_t d0 = f0 - obj->offset_counts[0]; - int32_t d1 = f1 - obj->offset_counts[1]; - d0 = esp_foc_clamp_int32(d0, -adc_rng, adc_rng); - d1 = esp_foc_clamp_int32(d1, -adc_rng, adc_rng); - - q16_t iu0 = q16_mul(esp_foc_q16_from_adc_diff_clamped(d0, adc_rng), obj->adc_to_current_scale_q16); - q16_t iv0 = q16_mul(esp_foc_q16_from_adc_diff_clamped(d1, adc_rng), obj->adc_to_current_scale_q16); - q16_t zero = 0; - q16_t iw0 = q16_sub(zero, q16_add(iu0, iv0)); - - values->iu_axis_0 = iu0; - values->iv_axis_0 = iv0; - values->iw_axis_0 = iw0; - - if (!obj->enable_analog_encoder) { - int32_t d2 = f2 - obj->offset_counts[2]; - int32_t d3 = f3 - obj->offset_counts[3]; - d2 = esp_foc_clamp_int32(d2, -adc_rng, adc_rng); - d3 = esp_foc_clamp_int32(d3, -adc_rng, adc_rng); - q16_t iu1 = q16_mul(esp_foc_q16_from_adc_diff_clamped(d2, adc_rng), obj->adc_to_current_scale_q16); - q16_t iv1 = q16_mul(esp_foc_q16_from_adc_diff_clamped(d3, adc_rng), obj->adc_to_current_scale_q16); - q16_t iw1 = q16_sub(zero, q16_add(iu1, iv1)); - values->iu_axis_1 = iu1; - values->iv_axis_1 = iv1; - values->iw_axis_1 = iw1; - } else { - values->iu_axis_1 = 0; - values->iv_axis_1 = 0; - values->iw_axis_1 = 0; - } -} - -static void sample_isensors(esp_foc_isensor_t *self) -{ - isensor_adc_t *obj = - __containerof(self, isensor_adc_t, interface); - oneshot_adc_start_sample(obj, obj->channels[0], obj->units[0]); -} - -static void calibrate_isensors (esp_foc_isensor_t *self, int calibration_rounds) -{ - isensor_values_t val; - isensor_adc_t *obj = - __containerof(self, isensor_adc_t, interface); - - esp_foc_sleep_ms(100); - - obj->offsets[0] = 0.0f; - obj->offsets[1] = 0.0f; - obj->offsets[2] = 0.0f; - obj->offsets[3] = 0.0f; - for (int oi = 0; oi < 4; oi++) { - obj->offset_counts[oi] = 0; - } - - for(int i = 0; i < calibration_rounds; i++) { - self->sample_isensors(self); - esp_foc_sleep_ms(10); - - /* Use the unfiltered latest_raw value here on purpose: see the - * matching note in current_sensor_adc.c. */ - obj->offsets[0] += ((float)obj->latest_raw[0]); - obj->offsets[1] += ((float)obj->latest_raw[1]); - obj->offsets[2] += ((float)obj->latest_raw[2]); - obj->offsets[3] += ((float)obj->latest_raw[3]); - } - - obj->offsets[0] /= calibration_rounds; - obj->offsets[1] /= calibration_rounds; - obj->offsets[2] /= calibration_rounds; - obj->offsets[3] /= calibration_rounds; - for (int oi = 0; oi < 4; oi++) { - obj->offset_counts[oi] = (int32_t)lroundf(obj->offsets[oi]); - } - - for (int i = 0; i < 4; ++i) { - esp_foc_biquad_q16_reset(&obj->bq[i]); - obj->filtered_count[i] = obj->latest_raw[i]; - } - - ESP_LOGI(TAG, "ADC calibrated, phase current offsets are: %f, %f, %f, %f", - obj->offsets[0], obj->offsets[1], obj->offsets[2], obj->offsets[3]); - esp_foc_sleep_ms(100); - - /* Dummy read to check reading when no current is flowing*/ - self->sample_isensors(self); - esp_foc_sleep_ms(10); - self->fetch_isensors(self, &val); - - ESP_LOGI(TAG, "No current flow isensor test read: %f, %f, %f, %f, %f, %f", - q16_to_float(val.iu_axis_0), q16_to_float(val.iv_axis_0), q16_to_float(val.iw_axis_0), - q16_to_float(val.iu_axis_1), q16_to_float(val.iv_axis_1), q16_to_float(val.iw_axis_1)); - esp_foc_sleep_ms(100); -} - -static void set_callback(esp_foc_isensor_t *self, isensor_callback_t cb, void *arg) -{ - isensor_adc_t *obj = - __containerof(self, isensor_adc_t, interface); - - esp_foc_critical_enter(); - obj->callback = cb; - obj->user_data = arg; - esp_foc_critical_leave(); -} - -static void set_filter_cutoff(esp_foc_isensor_t *self, float fc_hz, float fs_hz) -{ - isensor_adc_t *obj = - __containerof(self, isensor_adc_t, interface); - - esp_foc_critical_enter(); - for (int i = 0; i < 4; ++i) { - if (obj->enable_analog_encoder && i == 2) { - esp_foc_biquad_q16_set_bypass(&obj->bq[i]); - continue; - } - esp_foc_biquad_butterworth_lpf_design_q16(&obj->bq[i], fc_hz, fs_hz); - } - esp_foc_critical_leave(); -} - -static void set_publish_targets(esp_foc_isensor_t *self, - q16_t *i_alpha_target, - q16_t *i_beta_target, - q16_t *i_u_target, - q16_t *i_v_target) -{ - isensor_adc_t *obj = - __containerof(self, isensor_adc_t, interface); - - esp_foc_critical_enter(); - obj->publish_alpha = (volatile q16_t *)i_alpha_target; - obj->publish_beta = (volatile q16_t *)i_beta_target; - obj->publish_iu = (volatile q16_t *)i_u_target; - obj->publish_iv = (volatile q16_t *)i_v_target; - esp_foc_critical_leave(); -} - -static void set_to_zero(esp_foc_rotor_sensor_t *self) -{ - isensor_adc_t *obj = __containerof(self, isensor_adc_t, encoder_interface); - - sample_isensors(&obj->interface); - esp_foc_sleep_ms(10); - obj->encoder_zero_offset = (uint16_t)obj->latest_raw[2]; - obj->encoder_prev_iq = (int32_t)obj->encoder_zero_offset; - - ESP_LOGI(TAG, "Setting %d [ticks] as offset.", obj->encoder_zero_offset); -} - -static uint32_t get_counts_per_revolution(esp_foc_rotor_sensor_t *self) -{ - isensor_adc_t *obj = __containerof(self, isensor_adc_t, encoder_interface); - return obj->encoder_ppr_u32 ? obj->encoder_ppr_u32 : 1u; -} - -static q16_t read_counts_encoder(esp_foc_rotor_sensor_t *self) -{ - isensor_adc_t *obj = __containerof(self, isensor_adc_t, encoder_interface); - - uint16_t raw = (uint16_t)obj->latest_raw[2]; - uint32_t ppr = obj->encoder_ppr_u32 ? obj->encoder_ppr_u32 : 1u; - uint32_t wrap = ppr * 95u / 100u; - - int32_t delta = (int32_t)raw - obj->encoder_prev_iq; - int32_t ad = delta < 0 ? -delta : delta; - - if ((uint32_t)ad >= wrap) { - if (delta < 0) { - obj->encoder_accum_i64 += (int64_t)ppr; - } else { - obj->encoder_accum_i64 -= (int64_t)ppr; - } - } - - obj->encoder_prev_iq = (int32_t)raw; - - uint32_t cm = (uint32_t)((raw - obj->encoder_zero_offset) & (ppr - 1u)); - if (ppr == 0u) { - return 0; - } - if ((ppr & (ppr - 1u)) == 0u) { - return q16_from_int((int32_t)(cm & (ppr - 1u))); - } - return q16_from_int((int32_t)(cm % ppr)); -} - -static int64_t read_accumulated_counts_i64_enc(esp_foc_rotor_sensor_t *self) -{ - isensor_adc_t *obj = __containerof(self, isensor_adc_t, encoder_interface); - return obj->encoder_accum_i64 + (int64_t)obj->encoder_prev_iq; -} - -static void encoder_set_simulation_count(esp_foc_rotor_sensor_t *self, q16_t increment_normalized) -{ - isensor_adc_t *obj = __containerof(self, isensor_adc_t, encoder_interface); - uint32_t ppr = obj->encoder_ppr_u32; - int64_t dt = ((int64_t)increment_normalized * (int64_t)ppr) >> 31; - obj->encoder_accum_i64 += dt; -} - -esp_foc_isensor_t *isensor_adc_oneshot_new(esp_foc_isensor_adc_oneshot_config_t *config, - esp_foc_rotor_sensor_t **optional_encoder) -{ - if(config->enable_analog_encoder && config->number_of_channels != 3) { - ESP_LOGE(TAG, "share analog chgannel for encoder requires exact three channels!"); - return NULL; - } - - if (config->enable_analog_encoder && optional_encoder == NULL) { - ESP_LOGE(TAG, "Optional analog channel for encoder activated but no storage provided, aborting"); - return NULL; - } - - if(adc_initialized == true) { - return &isensor_adc.interface; - } - - isensor_adc.adc_to_current_scale = adc_to_volts * (1.0f / (config->amp_gain * config->shunt_resistance)); - isensor_adc.adc_to_current_scale_q16 = q16_from_float(2048.0f * isensor_adc.adc_to_current_scale); - isensor_adc.encoder_prev_iq = 0; - isensor_adc.encoder_accum_i64 = 0; - isensor_adc.encoder_ppr_u32 = (uint32_t)(int32_t)config->analog_encoder_ppr; - - isensor_adc.interface.fetch_isensors = fetch_isensors; - isensor_adc.interface.sample_isensors = sample_isensors; - isensor_adc.interface.calibrate_isensors = calibrate_isensors; - isensor_adc.interface.set_isensor_callback = set_callback; - isensor_adc.interface.set_filter_cutoff = set_filter_cutoff; - isensor_adc.interface.set_publish_targets = set_publish_targets; - isensor_adc.publish_alpha = NULL; - isensor_adc.publish_beta = NULL; - isensor_adc.publish_iu = NULL; - isensor_adc.publish_iv = NULL; - isensor_adc.number_of_channels = config->number_of_channels; - /* Default to bypass on every channel; axis init dials a real - * cutoff in via set_filter_cutoff. */ - for (int i = 0; i < 4; ++i) { - esp_foc_biquad_q16_set_bypass(&isensor_adc.bq[i]); - } - - isensor_adc.encoder_interface.get_counts_per_revolution = get_counts_per_revolution; - isensor_adc.encoder_interface.read_counts = read_counts_encoder; - isensor_adc.encoder_interface.set_to_zero = set_to_zero; - isensor_adc.encoder_interface.read_accumulated_counts_i64 = read_accumulated_counts_i64_enc; - isensor_adc.encoder_interface.set_simulation_count = encoder_set_simulation_count; - isensor_adc.encoder_interface.set_zero_offset_raw_12b = NULL; - isensor_adc.encoder_interface.get_zero_offset_12b = NULL; - isensor_adc.encoder_accumulated = 0.0f; - isensor_adc.encoder_previous = 0.0f; - isensor_adc.encoder_zero_offset = 0; - isensor_adc.enable_analog_encoder = config->enable_analog_encoder; - isensor_adc.analog_encoder_ppr = config->analog_encoder_ppr; - isensor_adc.channels[0] = config->axis_channels[0]; - isensor_adc.channels[1] = config->axis_channels[1]; - isensor_adc.channels[2] = config->axis_channels[2]; - isensor_adc.channels[3] = config->axis_channels[3]; - isensor_adc.units[0] = config->units[0]; - isensor_adc.units[1] = config->units[1]; - isensor_adc.units[2] = config->units[2]; - isensor_adc.units[3] = config->units[3]; - isensor_adc.offsets[0] = 0.0f; - isensor_adc.offsets[1] = 0.0f; - isensor_adc.offsets[2] = 0.0f; - isensor_adc.offsets[3] = 0.0f; - isensor_adc.offset_counts[0] = 0; - isensor_adc.offset_counts[1] = 0; - isensor_adc.offset_counts[2] = 0; - isensor_adc.offset_counts[3] = 0; - isensor_adc.callback = NULL; - - oneshot_adc_init(&isensor_adc); - adc_initialized = true; - - if(config->enable_analog_encoder && optional_encoder) { - *optional_encoder = &isensor_adc.encoder_interface; - } - - return &isensor_adc.interface; -} -#endif diff --git a/source/drivers/espFoC/current_sensor_adc.h b/source/drivers/espFoC/current_sensor_adc.h index 7a40172d..d7b5f2ad 100644 --- a/source/drivers/espFoC/current_sensor_adc.h +++ b/source/drivers/espFoC/current_sensor_adc.h @@ -9,6 +9,7 @@ #include "espFoC/esp_foc.h" #include "hal/adc_types.h" #include "espFoC/esp_foc_err.h" +#include "soc/soc_caps.h" typedef struct { adc_channel_t channels[2]; @@ -22,7 +23,22 @@ typedef enum { ESP_FOC_ISENSOR_ADC_TRIG_ETM, } esp_foc_isensor_adc_trigger_t; +typedef enum { + ESP_FOC_ISENSOR_ADC_MCPWM_EVT_TIMER_TEZ = 0, + ESP_FOC_ISENSOR_ADC_MCPWM_EVT_TIMER_TEP, +} esp_foc_isensor_adc_mcpwm_event_t; + +typedef struct { + uint8_t mcpwm_timer; + esp_foc_isensor_adc_mcpwm_event_t event; +} esp_foc_isensor_adc_etm_config_t; + esp_foc_isensor_t *isensor_adc_new(esp_foc_isensor_adc_config_t *config); esp_foc_err_t esp_foc_isensor_adc_set_trigger(esp_foc_isensor_t *isensor, esp_foc_isensor_adc_trigger_t mode); + +#if SOC_ETM_SUPPORTED +esp_foc_err_t esp_foc_isensor_adc_set_etm_source(esp_foc_isensor_t *isensor, + const esp_foc_isensor_adc_etm_config_t *cfg); +#endif diff --git a/source/drivers/espFoC/current_sensor_adc_one_shot.h b/source/drivers/espFoC/current_sensor_adc_one_shot.h deleted file mode 100644 index 9a217a5d..00000000 --- a/source/drivers/espFoC/current_sensor_adc_one_shot.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Felipe Neves - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#pragma once - -#include "espFoC/esp_foc.h" -#include "hal/adc_hal.h" -#include "esp_err.h" - -typedef struct { - adc_channel_t axis_channels[4]; - adc_unit_t units[4]; - float amp_gain; - float shunt_resistance; - int number_of_channels; - bool enable_analog_encoder; - int analog_encoder_ppr; -}esp_foc_isensor_adc_oneshot_config_t; - -esp_foc_isensor_t *isensor_adc_oneshot_new(esp_foc_isensor_adc_oneshot_config_t *config, - esp_foc_rotor_sensor_t **optional_encoder); diff --git a/source/drivers/isensor_adc_dma_esp32.c b/source/drivers/isensor_adc_dma_esp32.c index f0e9f0ea..37f6badf 100644 --- a/source/drivers/isensor_adc_dma_esp32.c +++ b/source/drivers/isensor_adc_dma_esp32.c @@ -11,6 +11,7 @@ #include "esp_intr_alloc.h" #include "esp_attr.h" #include "hal/i2s_ll.h" +#include "hal/adc_hal.h" #include "soc/i2s_periph.h" #include "esp_private/i2s_platform.h" #include "isensor_adc_internal.h" diff --git a/source/drivers/isensor_adc_etm.c b/source/drivers/isensor_adc_etm.c new file mode 100644 index 00000000..7ca7629d --- /dev/null +++ b/source/drivers/isensor_adc_etm.c @@ -0,0 +1,157 @@ +/* + * MIT License + * + * Copyright (c) 2021 Felipe Neves + * + * ETM wiring: MCPWM timer event -> ADC digi START task. + */ + +#include +#include "sdkconfig.h" +#include "esp_log.h" +#include "esp_check.h" +#include "esp_etm.h" +#include "esp_heap_caps.h" +#include "esp_private/etm_interface.h" +#include "soc/soc_caps.h" +#include "soc/soc_etm_source.h" +#include "isensor_adc_internal.h" + +#if SOC_ETM_SUPPORTED + +static const char *TAG = "isensor_adc_etm"; + +typedef struct { + esp_etm_event_t base; +} isensor_adc_etm_event_t; + +typedef struct { + esp_etm_task_t base; +} isensor_adc_etm_task_t; + +typedef struct { + esp_etm_channel_handle_t chan; + esp_etm_event_handle_t event; + esp_etm_task_handle_t task; + bool connected; + bool enabled; +} isensor_adc_etm_ctx_t; + +static isensor_adc_etm_ctx_t s_etm; + +static esp_err_t isensor_adc_etm_del_event(esp_etm_event_t *event) +{ + isensor_adc_etm_event_t *ev = __containerof(event, isensor_adc_etm_event_t, base); + free(ev); + return ESP_OK; +} + +static esp_err_t isensor_adc_etm_del_task(esp_etm_task_t *task) +{ + isensor_adc_etm_task_t *tk = __containerof(task, isensor_adc_etm_task_t, base); + free(tk); + return ESP_OK; +} + +static uint32_t isensor_adc_mcpwm_event_id(const esp_foc_isensor_adc_etm_config_t *cfg) +{ + switch (cfg->event) { + case ESP_FOC_ISENSOR_ADC_MCPWM_EVT_TIMER_TEZ: + return (uint32_t)(MCPWM_EVT_TIMER0_TEZ + cfg->mcpwm_timer); + case ESP_FOC_ISENSOR_ADC_MCPWM_EVT_TIMER_TEP: + return (uint32_t)(MCPWM_EVT_TIMER0_TEP + cfg->mcpwm_timer); + default: + return 0; + } +} + +static esp_err_t isensor_adc_etm_create_event(uint32_t event_id, esp_etm_event_handle_t *out) +{ + isensor_adc_etm_event_t *ev = heap_caps_calloc(1, sizeof(*ev), MALLOC_CAP_DEFAULT); + ESP_RETURN_ON_FALSE(ev, ESP_ERR_NO_MEM, TAG, "no mem for etm event"); + ev->base.event_id = event_id; + ev->base.trig_periph = ETM_TRIG_PERIPH_MCPWM; + ev->base.del = isensor_adc_etm_del_event; + *out = &ev->base; + return ESP_OK; +} + +static esp_err_t isensor_adc_etm_create_task(uint32_t task_id, esp_etm_task_handle_t *out) +{ + isensor_adc_etm_task_t *tk = heap_caps_calloc(1, sizeof(*tk), MALLOC_CAP_DEFAULT); + ESP_RETURN_ON_FALSE(tk, ESP_ERR_NO_MEM, TAG, "no mem for etm task"); + tk->base.task_id = task_id; + tk->base.trig_periph = ETM_TRIG_PERIPH_MCPWM; + tk->base.del = isensor_adc_etm_del_task; + *out = &tk->base; + return ESP_OK; +} + +static void isensor_adc_etm_teardown(void) +{ + if (s_etm.enabled) { + esp_etm_channel_disable(s_etm.chan); + s_etm.enabled = false; + } + if (s_etm.connected) { + esp_etm_channel_connect(s_etm.chan, NULL, NULL); + s_etm.connected = false; + } + if (s_etm.event) { + esp_etm_del_event(s_etm.event); + s_etm.event = NULL; + } + if (s_etm.task) { + esp_etm_del_task(s_etm.task); + s_etm.task = NULL; + } + if (s_etm.chan) { + esp_etm_del_channel(s_etm.chan); + s_etm.chan = NULL; + } +} + +esp_err_t isensor_adc_etm_connect(const esp_foc_isensor_adc_etm_config_t *cfg) +{ + ESP_RETURN_ON_FALSE(cfg, ESP_ERR_INVALID_ARG, TAG, "cfg is NULL"); + ESP_RETURN_ON_FALSE(cfg->mcpwm_timer < SOC_MCPWM_TIMERS_PER_GROUP, ESP_ERR_INVALID_ARG, TAG, + "invalid mcpwm timer %u", cfg->mcpwm_timer); + + uint32_t event_id = isensor_adc_mcpwm_event_id(cfg); + ESP_RETURN_ON_FALSE(event_id != 0, ESP_ERR_INVALID_ARG, TAG, "invalid mcpwm event"); + + isensor_adc_etm_teardown(); + + ESP_RETURN_ON_ERROR(isensor_adc_etm_create_event(event_id, &s_etm.event), TAG, "event create failed"); + ESP_RETURN_ON_ERROR(isensor_adc_etm_create_task(ADC_TASK_START0, &s_etm.task), TAG, "task create failed"); + + esp_etm_channel_config_t chan_cfg = {}; + ESP_RETURN_ON_ERROR(esp_etm_new_channel(&chan_cfg, &s_etm.chan), TAG, "channel alloc failed"); + ESP_RETURN_ON_ERROR(esp_etm_channel_connect(s_etm.chan, s_etm.event, s_etm.task), TAG, "connect failed"); + s_etm.connected = true; + + ESP_LOGI(TAG, "ETM MCPWM timer%u evt=%u -> ADC_TASK_START0", cfg->mcpwm_timer, (unsigned)cfg->event); + return ESP_OK; +} + +esp_err_t isensor_adc_etm_enable(bool enable) +{ + if (!s_etm.chan || !s_etm.connected) { + return ESP_ERR_INVALID_STATE; + } + if (enable == s_etm.enabled) { + return ESP_OK; + } + esp_err_t err = enable ? esp_etm_channel_enable(s_etm.chan) : esp_etm_channel_disable(s_etm.chan); + if (err == ESP_OK) { + s_etm.enabled = enable; + } + return err; +} + +void isensor_adc_etm_disconnect(void) +{ + isensor_adc_etm_teardown(); +} + +#endif /* SOC_ETM_SUPPORTED */ diff --git a/source/drivers/isensor_adc_internal.h b/source/drivers/isensor_adc_internal.h index 273950cc..5d1b58ac 100644 --- a/source/drivers/isensor_adc_internal.h +++ b/source/drivers/isensor_adc_internal.h @@ -11,6 +11,7 @@ #include "esp_err.h" #include "hal/dma_types.h" #include "soc/soc_caps.h" +#include "espFoC/current_sensor_adc.h" #define ESP_FOC_ISENSOR_ADC_PATTERN_HZ 80000 #define ESP_FOC_ISENSOR_ADC_NUM_CHANNELS 2 @@ -37,3 +38,9 @@ typedef enum { ESP_FOC_ISENSOR_ADC_STATE_IDLE = 0, ESP_FOC_ISENSOR_ADC_STATE_BUSY, } esp_foc_isensor_adc_state_t; + +#if SOC_ETM_SUPPORTED +esp_err_t isensor_adc_etm_connect(const esp_foc_isensor_adc_etm_config_t *cfg); +esp_err_t isensor_adc_etm_enable(bool enable); +void isensor_adc_etm_disconnect(void); +#endif diff --git a/source/gui_link/esp_foc_tuner.c b/source/gui_link/esp_foc_tuner.c index b8ce242a..1fda1367 100644 --- a/source/gui_link/esp_foc_tuner.c +++ b/source/gui_link/esp_foc_tuner.c @@ -159,6 +159,13 @@ static esp_foc_err_t handle_read(esp_foc_axis_t *axis, case ESP_FOC_TUNER_PARAM_IV_Q16: value = axis->i_v; break; case ESP_FOC_TUNER_PARAM_IQ_MEAS_Q16: value = axis->i_q.raw; break; case ESP_FOC_TUNER_PARAM_BENCH_THETA_Q16: value = axis->bench_theta_e; break; + case ESP_FOC_TUNER_PARAM_ENC_COUNTS_Q16: value = axis->rotor_shaft_ticks; break; + case ESP_FOC_TUNER_PARAM_ENC_TURNS_Q16: + value = axis->rotor_estimator.theta_meas_mech; + break; + case ESP_FOC_TUNER_PARAM_ENC_DEG_Q16: + value = q16_mul(axis->rotor_estimator.theta_meas_mech, q16_from_float(360.0f)); + break; default: return ESP_FOC_ERR_INVALID_ARG; } @@ -378,6 +385,15 @@ static esp_foc_err_t handle_exec(esp_foc_axis_t *axis, return ESP_FOC_OK; } + if (id == ESP_FOC_TUNER_CMD_ENC_SET_ZERO) { + if (axis->rotor_sensor_driver == NULL || + axis->rotor_sensor_driver->set_to_zero == NULL) { + return ESP_FOC_ERR_AXIS_INVALID_STATE; + } + axis->rotor_sensor_driver->set_to_zero(axis->rotor_sensor_driver); + return ESP_FOC_OK; + } + if (id == ESP_FOC_TUNER_CMD_STORE_NVS) { return esp_foc_calibration_axis_tuner_store(axis); } diff --git a/test/test_isensor_adc.c b/test/test_isensor_adc.c index 4dfa0129..1895cee5 100644 --- a/test/test_isensor_adc.c +++ b/test/test_isensor_adc.c @@ -6,6 +6,7 @@ #include "espFoC/current_sensor_adc.h" #include "espFoC/esp_foc_err.h" #include "hal/adc_types.h" +#include "soc/soc_caps.h" TEST_CASE("adc cali LUT builds 4096 entries", "[espFoC][isensor_adc]") { @@ -28,8 +29,33 @@ TEST_CASE("adc cali LUT apply clamps out of range", "[espFoC][isensor_adc]") TEST_ASSERT_EQUAL(100, esp_foc_adc_cali_lut_apply(lut, 100)); } -TEST_CASE("isensor trigger ETM returns not supported", "[espFoC][isensor_adc]") +TEST_CASE("isensor trigger NULL returns invalid arg", "[espFoC][isensor_adc]") { esp_foc_err_t err = esp_foc_isensor_adc_set_trigger(NULL, ESP_FOC_ISENSOR_ADC_TRIG_ETM); TEST_ASSERT_EQUAL(ESP_FOC_ERR_INVALID_ARG, err); } + +#if !SOC_ETM_SUPPORTED +TEST_CASE("isensor trigger ETM not supported on this target", "[espFoC][isensor_adc]") +{ + esp_foc_isensor_adc_config_t cfg = { + .channels = {ADC_CHANNEL_0, ADC_CHANNEL_1}, + .unit = ADC_UNIT_1, + .amp_gain = 20.0f, + .shunt_resistance = 0.001f, + }; + esp_foc_isensor_t *isensor = isensor_adc_new(&cfg); + if (isensor != NULL) { + esp_foc_err_t err = esp_foc_isensor_adc_set_trigger(isensor, ESP_FOC_ISENSOR_ADC_TRIG_ETM); + TEST_ASSERT_EQUAL(ESP_FOC_ERR_NOT_SUPPORTED, err); + } +} +#endif + +#if SOC_ETM_SUPPORTED +TEST_CASE("isensor etm source NULL returns invalid arg", "[espFoC][isensor_adc]") +{ + esp_foc_err_t err = esp_foc_isensor_adc_set_etm_source(NULL, NULL); + TEST_ASSERT_EQUAL(ESP_FOC_ERR_INVALID_ARG, err); +} +#endif From 2f7a354ddb0da48b2243bd67fe6ca24fe53133e0 Mon Sep 17 00:00:00 2001 From: Felipe Neves Date: Mon, 1 Jun 2026 18:00:12 -0300 Subject: [PATCH 2/2] Port per-target ADC calibration and align isensor to 3.3 V full scale. Curve/line fitting coefficients now match ESP-IDF per chip, S2/S3 range extension feeds the LUT build, and QEMU tests no longer hang on stack overflow. --- CMakeLists.txt | 15 +- .../unit_test_runner/run_unit_tests_qemu.py | 6 +- .../cali/coeff/esp_foc_curve_coeff_esp32c3.c | 67 +++++ .../cali/coeff/esp_foc_curve_coeff_esp32c5.c | 76 ++++++ .../esp_foc_curve_coeff_esp32c6.c} | 23 +- .../cali/coeff/esp_foc_curve_coeff_esp32c61.c | 76 ++++++ .../cali/coeff/esp_foc_curve_coeff_esp32h2.c | 77 ++++++ .../cali/coeff/esp_foc_curve_coeff_esp32p4.c | 104 ++++++++ .../cali/coeff/esp_foc_curve_coeff_esp32s3.c | 80 ++++++ source/drivers/cali/esp_foc_adc_cali_curve.c | 14 +- source/drivers/cali/esp_foc_adc_cali_line.c | 85 ------ .../cali/esp_foc_adc_cali_line_esp32.c | 246 ++++++++++++++++++ .../cali/esp_foc_adc_cali_line_esp32c2.c | 77 ++++++ .../cali/esp_foc_adc_cali_line_esp32s2.c | 149 +++++++++++ .../drivers/cali/esp_foc_adc_range_extend.c | 86 ++++++ .../drivers/cali/esp_foc_adc_range_extend.h | 11 + source/drivers/current_sensor_adc.c | 3 +- source/drivers/esp_foc_adc_cali_lut.c | 14 +- source/drivers/isensor_adc_dma_gdma.c | 2 + test/test_isensor_adc.c | 4 +- 20 files changed, 1100 insertions(+), 115 deletions(-) create mode 100644 source/drivers/cali/coeff/esp_foc_curve_coeff_esp32c3.c create mode 100644 source/drivers/cali/coeff/esp_foc_curve_coeff_esp32c5.c rename source/drivers/cali/{esp_foc_cali_curve_coeff.c => coeff/esp_foc_curve_coeff_esp32c6.c} (86%) create mode 100644 source/drivers/cali/coeff/esp_foc_curve_coeff_esp32c61.c create mode 100644 source/drivers/cali/coeff/esp_foc_curve_coeff_esp32h2.c create mode 100644 source/drivers/cali/coeff/esp_foc_curve_coeff_esp32p4.c create mode 100644 source/drivers/cali/coeff/esp_foc_curve_coeff_esp32s3.c delete mode 100644 source/drivers/cali/esp_foc_adc_cali_line.c create mode 100644 source/drivers/cali/esp_foc_adc_cali_line_esp32.c create mode 100644 source/drivers/cali/esp_foc_adc_cali_line_esp32c2.c create mode 100644 source/drivers/cali/esp_foc_adc_cali_line_esp32s2.c create mode 100644 source/drivers/cali/esp_foc_adc_range_extend.c create mode 100644 source/drivers/cali/esp_foc_adc_range_extend.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a4b394c..d156d7db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ set(requires esp_driver_uart esp_driver_pcnt esp_driver_gpio esp_driver_mcpwm dr if(target STREQUAL "esp32s2" OR target STREQUAL "esp32s3" OR target STREQUAL "esp32p4") list(APPEND requires espressif__esp_tinyusb) endif() -list(APPEND includes "source/drivers") +list(APPEND includes "source/drivers" "source/drivers/cali") list(APPEND srcs "source/drivers/inverter_3pwm_mcpwm.c" "source/drivers/inverter_6pwm_mcpwm.c" "source/drivers/rotor_sensor_as5600.c" @@ -35,10 +35,19 @@ list(APPEND srcs "source/drivers/inverter_3pwm_mcpwm.c" "source/drivers/current_sensor_adc.c" "source/drivers/esp_foc_adc_cali_lut.c" "source/drivers/cali/esp_foc_adc_cali_curve.c" - "source/drivers/cali/esp_foc_adc_cali_line.c" - "source/drivers/cali/esp_foc_cali_curve_coeff.c" + "source/drivers/cali/esp_foc_adc_range_extend.c" "source/osal/os_interface_idf.c") +if(target STREQUAL "esp32") + list(APPEND srcs "source/drivers/cali/esp_foc_adc_cali_line_esp32.c") +elseif(target STREQUAL "esp32s2") + list(APPEND srcs "source/drivers/cali/esp_foc_adc_cali_line_esp32s2.c") +elseif(target STREQUAL "esp32c2") + list(APPEND srcs "source/drivers/cali/esp_foc_adc_cali_line_esp32c2.c") +elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/source/drivers/cali/coeff/esp_foc_curve_coeff_${target}.c") + list(APPEND srcs "source/drivers/cali/coeff/esp_foc_curve_coeff_${target}.c") +endif() + if(CONFIG_SOC_ETM_SUPPORTED) list(APPEND srcs "source/drivers/isensor_adc_etm.c") endif() diff --git a/examples/unit_test_runner/run_unit_tests_qemu.py b/examples/unit_test_runner/run_unit_tests_qemu.py index 71899a97..0a1e6e38 100644 --- a/examples/unit_test_runner/run_unit_tests_qemu.py +++ b/examples/unit_test_runner/run_unit_tests_qemu.py @@ -27,6 +27,8 @@ RESULT_RE = re.compile(r"(\d+) Tests (\d+) Failures") MENU_PROMPT = "Enter test for running" BOOT_PROMPT = "Press ENTER to see the list of tests." +# Run only espFoC-tagged cases (same as typing [espFoC] in the Unity menu). +TEST_FILTER = "[espFoC]" def _spawn_idf_qemu(cwd: str) -> pexpect.spawn: @@ -111,7 +113,7 @@ def main() -> int: print("Unity menu prompt never appeared") return 1 - child.sendline("*") + child.sendline(TEST_FILTER) try: child.expect(RESULT_RE, timeout=600) @@ -124,6 +126,8 @@ def main() -> int: total = int(child.match.group(1)) failures = int(child.match.group(2)) + if total == 0: + print("Warning: Unity ran 0 tests — rebuild with: idf.py -D TEST_COMPONENTS=espFoC build") if failures == 0: print(f"All unit tests passed ({total} tests).") exit_code = 0 diff --git a/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32c3.c b/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32c3.c new file mode 100644 index 00000000..a2a3846c --- /dev/null +++ b/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32c3.c @@ -0,0 +1,67 @@ +/* + * SPDX-FileCopyrightText: Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + * + * Curve-fitting coefficients ported from ESP-IDF esp_adc/esp32c3/curve_fitting_coefficients.c + */ + +#include +#include +#include "sdkconfig.h" +#include "esp_foc_adc_cali.h" + +#if SOC_ADC_CALIB_SCHEME_CURVE_FITTING_SUPPORTED +#include "esp_efuse_rtc_calib.h" + + +#define COEFF_GROUP_NUM 4 +#define TERM_MAX 5 + +/** + * @note Error Calculation + * Coefficients for calculating the reading voltage error. + * Four sets of coefficients for atten0 ~ atten3 respectively. + * + * For each item, first element is the Coefficient, second element is the Multiple. (Coefficient / Multiple) is the real coefficient. + * + * @note {0,0} stands for unused item + * @note In case of the overflow, these coefficients are recorded as Absolute Value + * @note For atten0 ~ 2, error = (K0 * X^0) + (K1 * X^1) + (K2 * X^2); For atten3, error = (K0 * X^0) + (K1 * X^1) + (K2 * X^2) + (K3 * X^3) + (K4 * X^4); + * @note Above formula is rewritten from the original documentation, please note that the coefficients are re-ordered. + * @note ADC1 and ADC2 use same coefficients + */ +const static uint64_t adc1_error_coef_atten[COEFF_GROUP_NUM][TERM_MAX][2] = { + {{225966470500043, 1e15}, {7265418501948, 1e16}, {109410402681, 1e16}, {0, 0}, {0, 0}}, //atten0 + {{4229623392600516, 1e16}, {731527490903, 1e16}, {88166562521, 1e16}, {0, 0}, {0, 0}}, //atten1 + {{1017859239236435, 1e15}, {97159265299153, 1e16}, {149794028038, 1e16}, {0, 0}, {0, 0}}, //atten2 + {{14912262772850453, 1e16}, {228549975564099, 1e16}, {356391935717, 1e16}, {179964582, 1e16}, {42046, 1e16}} //atten3 +}; +/** + * Term sign + */ +const static int32_t adc1_error_sign[COEFF_GROUP_NUM][TERM_MAX] = { + {-1, -1, 1, 0, 0}, //atten0 + { 1, -1, 1, 0, 0}, //atten1 + {-1, -1, 1, 0, 0}, //atten2 + {-1, -1, 1, -1, 1} //atten3 +}; + +void esp_foc_curve_fitting_get_second_step_coeff(adc_unit_t unit, adc_atten_t atten, esp_foc_cali_chars_second_step_t *ctx) +{ + ctx->term_num = (atten == 3) ? 5 : 3; + // On esp32c3, ADC1 and ADC2 share the second step coefficients + // And if the target only has 1 ADC peripheral, just use the ADC1 directly + ctx->coeff = adc1_error_coef_atten[atten]; + ctx->sign = adc1_error_sign[atten]; +} + +#else + +void esp_foc_curve_fitting_get_second_step_coeff(adc_unit_t unit, adc_atten_t atten, + esp_foc_cali_chars_second_step_t *ctx) +{ + (void)unit; (void)atten; + ctx->term_num = 0; ctx->coeff = NULL; ctx->sign = NULL; +} + +#endif diff --git a/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32c5.c b/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32c5.c new file mode 100644 index 00000000..7fbfc8df --- /dev/null +++ b/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32c5.c @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + * + * Curve-fitting coefficients ported from ESP-IDF esp_adc/esp32c5/curve_fitting_coefficients.c + */ + +#include +#include +#include "sdkconfig.h" +#include "esp_foc_adc_cali.h" + +#if SOC_ADC_CALIB_SCHEME_CURVE_FITTING_SUPPORTED +#include "esp_efuse_rtc_calib.h" + + +#define COEFF_VERSION_NUM 1 // Currently C5 has one versions of curve calibration schemes +#define COEFF_GROUP_NUM 4 +#define TERM_MAX 3 + +/** + * @note Error Calculation + * Coefficients for calculating the reading voltage error. + * Four sets of coefficients for atten0 ~ atten3 respectively. + * + * For each item, first element is the Coefficient, second element is the Multiple. (Coefficient / Multiple) is the real coefficient. + * + * @note {0,0} stands for unused item + * @note In case of the overflow, these coefficients are recorded as Absolute Value + * @note For atten0 ~ 3, error = (K0 * X^0) + (K1 * X^1) + * @note Above formula is rewritten from the original documentation, please note that the coefficients are re-ordered. + */ +const static uint64_t adc1_error_coef_atten[COEFF_VERSION_NUM][COEFF_GROUP_NUM][TERM_MAX][2] = { + /* Coefficients of calibration version 1 */ + { + {{2941017829027464, 1e16}, {7368674918527, 1e16}, {0, 1}}, //atten0 + {{3224276125615327, 1e16}, {5325658467636, 1e16}, {0, 1}}, //atten1 + {{3307554632960901, 1e16}, {409244304226, 1e15}, {0, 1}}, //atten2 + {{1463642578413965, 1e15}, {3349642363147, 1e15}, {11676836451, 1e16}}, //atten3 + }, +}; + +/** + * Term sign ADC1 + */ +const static int32_t adc1_error_sign[COEFF_VERSION_NUM][COEFF_GROUP_NUM][TERM_MAX] = { + /* Coefficient sign of calibration version 1 */ + { + {-1, 1, 1}, //atten0 + {-1, 1, 1}, //atten1 + {-1, 1, 1}, //atten2 + {1, -1, 1}, //atten3 + }, +}; + +void esp_foc_curve_fitting_get_second_step_coeff(adc_unit_t unit, adc_atten_t atten, esp_foc_cali_chars_second_step_t *ctx) +{ + uint32_t adc_calib_ver = esp_efuse_rtc_calib_get_ver(); + assert((adc_calib_ver >= ESP_EFUSE_ADC_CALIB_VER_MIN) && + (adc_calib_ver <= ESP_EFUSE_ADC_CALIB_VER_MAX)); + + ctx->term_num = 3; + ctx->coeff = adc1_error_coef_atten[VER2IDX(adc_calib_ver)][atten]; + ctx->sign = adc1_error_sign[VER2IDX(adc_calib_ver)][atten]; +} + +#else + +void esp_foc_curve_fitting_get_second_step_coeff(adc_unit_t unit, adc_atten_t atten, + esp_foc_cali_chars_second_step_t *ctx) +{ + (void)unit; (void)atten; + ctx->term_num = 0; ctx->coeff = NULL; ctx->sign = NULL; +} + +#endif diff --git a/source/drivers/cali/esp_foc_cali_curve_coeff.c b/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32c6.c similarity index 86% rename from source/drivers/cali/esp_foc_cali_curve_coeff.c rename to source/drivers/cali/coeff/esp_foc_curve_coeff_esp32c6.c index a60c43c1..27b87b5b 100644 --- a/source/drivers/cali/esp_foc_cali_curve_coeff.c +++ b/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32c6.c @@ -1,9 +1,10 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: Espressif Systems (Shanghai) CO LTD * SPDX-License-Identifier: Apache-2.0 + * + * Curve-fitting coefficients ported from ESP-IDF esp_adc/esp32c6/curve_fitting_coefficients.c */ -#include #include #include #include "sdkconfig.h" @@ -12,6 +13,7 @@ #if SOC_ADC_CALIB_SCHEME_CURVE_FITTING_SUPPORTED #include "esp_efuse_rtc_calib.h" + #define COEFF_VERSION_NUM 2 // Currently C6 has two versions of curve calibration schemes #define COEFF_GROUP_NUM 4 #define TERM_MAX 3 @@ -65,11 +67,8 @@ const static int32_t adc1_error_sign[COEFF_VERSION_NUM][COEFF_GROUP_NUM][TERM_MA }, }; -void esp_foc_curve_fitting_get_second_step_coeff(adc_unit_t unit, - adc_atten_t atten, - esp_foc_cali_chars_second_step_t *ctx) +void esp_foc_curve_fitting_get_second_step_coeff(adc_unit_t unit, adc_atten_t atten, esp_foc_cali_chars_second_step_t *ctx) { - (void)unit; uint32_t adc_calib_ver = esp_efuse_rtc_calib_get_ver(); assert((adc_calib_ver >= ESP_EFUSE_ADC_CALIB_VER_MIN) && (adc_calib_ver <= ESP_EFUSE_ADC_CALIB_VER_MAX)); @@ -86,15 +85,11 @@ void esp_foc_curve_fitting_get_second_step_coeff(adc_unit_t unit, #else -void esp_foc_curve_fitting_get_second_step_coeff(adc_unit_t unit, - adc_atten_t atten, +void esp_foc_curve_fitting_get_second_step_coeff(adc_unit_t unit, adc_atten_t atten, esp_foc_cali_chars_second_step_t *ctx) { - (void)unit; - (void)atten; - ctx->term_num = 0; - ctx->coeff = NULL; - ctx->sign = NULL; + (void)unit; (void)atten; + ctx->term_num = 0; ctx->coeff = NULL; ctx->sign = NULL; } -#endif /* SOC_ADC_CALIB_SCHEME_CURVE_FITTING_SUPPORTED */ +#endif diff --git a/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32c61.c b/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32c61.c new file mode 100644 index 00000000..adb0c5b8 --- /dev/null +++ b/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32c61.c @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + * + * Curve-fitting coefficients ported from ESP-IDF esp_adc/esp32c61/curve_fitting_coefficients.c + */ + +#include +#include +#include "sdkconfig.h" +#include "esp_foc_adc_cali.h" + +#if SOC_ADC_CALIB_SCHEME_CURVE_FITTING_SUPPORTED +#include "esp_efuse_rtc_calib.h" + + +#define COEFF_VERSION_NUM 1 // Currently C5 has one versions of curve calibration schemes +#define COEFF_GROUP_NUM 4 +#define TERM_MAX 3 + +/** + * @note Error Calculation + * Coefficients for calculating the reading voltage error. + * Four sets of coefficients for atten0 ~ atten3 respectively. + * + * For each item, first element is the Coefficient, second element is the Multiple. (Coefficient / Multiple) is the real coefficient. + * + * @note {0,0} stands for unused item + * @note In case of the overflow, these coefficients are recorded as Absolute Value + * @note For atten0 ~ 3, error = (K0 * X^0) + (K1 * X^1) + * @note Above formula is rewritten from the original documentation, please note that the coefficients are re-ordered. + */ +const static uint64_t adc1_error_coef_atten[COEFF_VERSION_NUM][COEFF_GROUP_NUM][TERM_MAX][2] = { + /* Coefficients of calibration version 1 */ + { + {{8668885650149671, 1e16}, {15630376830615, 1e16}, {0, 1}}, //atten0 + {{1090569589734153, 1e15}, {13859487941542, 1e16}, {0, 1}}, //atten1 + {{14231790752153335, 1e16}, {122016745867, 1e14}, {0, 1}}, //atten2 + {{13204544579940347, 1e16}, {11762579610906, 1e16}, {7639928529, 1e16}}, //atten3 + }, +}; + +/** + * Term sign ADC1 + */ +const static int32_t adc1_error_sign[COEFF_VERSION_NUM][COEFF_GROUP_NUM][TERM_MAX] = { + /* Coefficient sign of calibration version 1 */ + { + {-1, 1, 1}, //atten0 + {-1, 1, 1}, //atten1 + {-1, 1, 1}, //atten2 + {-1, -1, 1}, //atten3 + }, +}; + +void esp_foc_curve_fitting_get_second_step_coeff(adc_unit_t unit, adc_atten_t atten, esp_foc_cali_chars_second_step_t *ctx) +{ + uint32_t adc_calib_ver = esp_efuse_rtc_calib_get_ver(); + assert((adc_calib_ver >= ESP_EFUSE_ADC_CALIB_VER_MIN) && + (adc_calib_ver <= ESP_EFUSE_ADC_CALIB_VER_MAX)); + + ctx->term_num = 3; + ctx->coeff = adc1_error_coef_atten[VER2IDX(adc_calib_ver)][atten]; + ctx->sign = adc1_error_sign[VER2IDX(adc_calib_ver)][atten]; +} + +#else + +void esp_foc_curve_fitting_get_second_step_coeff(adc_unit_t unit, adc_atten_t atten, + esp_foc_cali_chars_second_step_t *ctx) +{ + (void)unit; (void)atten; + ctx->term_num = 0; ctx->coeff = NULL; ctx->sign = NULL; +} + +#endif diff --git a/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32h2.c b/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32h2.c new file mode 100644 index 00000000..b16eb143 --- /dev/null +++ b/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32h2.c @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + * + * Curve-fitting coefficients ported from ESP-IDF esp_adc/esp32h2/curve_fitting_coefficients.c + */ + +#include +#include +#include "sdkconfig.h" +#include "esp_foc_adc_cali.h" + +#if SOC_ADC_CALIB_SCHEME_CURVE_FITTING_SUPPORTED +#include "esp_efuse_rtc_calib.h" + + +#define COEFF_VERSION_NUM 1 // Currently H2 has one versions of curve calibration schemes +#define COEFF_GROUP_NUM 4 +#define TERM_MAX 3 + +/** + * @note Error Calculation + * Coefficients for calculating the reading voltage error. + * Four sets of coefficients for atten0 ~ atten3 respectively. + * + * For each item, first element is the Coefficient, second element is the Multiple. (Coefficient / Multiple) is the real coefficient. + * + * @note {0,0} stands for unused item + * @note In case of the overflow, these coefficients are recorded as Absolute Value + * @note For atten0 ~ 2, error = (K0 * X^0) + (K1 * X^1) + * @note For atten3, error = (K0 * X^0) + (K1 * X^1) + (K2 * X^2) + * @note Above formula is rewritten from the original documentation, please note that the coefficients are re-ordered. + */ +const static uint64_t adc1_error_coef_atten[COEFF_VERSION_NUM][COEFF_GROUP_NUM][TERM_MAX][2] = { + /* Coefficients of calibration version 1 */ + { + {{5081991760658888, 1e16}, {7858995318513, 1e19}, {0, 1}}, //atten0 + {{8359230818901277, 1e16}, {9025419089179, 1e19}, {0, 1}}, //atten1 + {{1165668771581976, 1e15}, {8294679249061, 1e19}, {0, 1}}, //atten2 + {{3637329628677273, 1e16}, {19607259738935, 1e18}, {7871689227, 1e16}}, //atten3 + }, +}; + +/** + * Term sign + */ +const static int32_t adc1_error_sign[COEFF_VERSION_NUM][COEFF_GROUP_NUM][TERM_MAX] = { + /* Coefficient sign of calibration version 1 */ + { + {-1, 1, 1}, //atten0 + {-1, 1, 1}, //atten1 + {-1, 1, 1}, //atten2 + {-1, -1, 1}, //atten3 + }, +}; + +void esp_foc_curve_fitting_get_second_step_coeff(adc_unit_t unit, adc_atten_t atten, esp_foc_cali_chars_second_step_t *ctx) +{ + uint32_t adc_calib_ver = esp_efuse_rtc_calib_get_ver(); + assert((adc_calib_ver >= ESP_EFUSE_ADC_CALIB_VER_MIN) && + (adc_calib_ver <= ESP_EFUSE_ADC_CALIB_VER_MAX)); + + ctx->term_num = 3; + ctx->coeff = adc1_error_coef_atten[VER2IDX(adc_calib_ver)][atten]; + ctx->sign = adc1_error_sign[VER2IDX(adc_calib_ver)][atten]; +} + +#else + +void esp_foc_curve_fitting_get_second_step_coeff(adc_unit_t unit, adc_atten_t atten, + esp_foc_cali_chars_second_step_t *ctx) +{ + (void)unit; (void)atten; + ctx->term_num = 0; ctx->coeff = NULL; ctx->sign = NULL; +} + +#endif diff --git a/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32p4.c b/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32p4.c new file mode 100644 index 00000000..d6af8ecf --- /dev/null +++ b/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32p4.c @@ -0,0 +1,104 @@ +/* + * SPDX-FileCopyrightText: Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + * + * Curve-fitting coefficients ported from ESP-IDF esp_adc/esp32p4/curve_fitting_coefficients.c + */ + +#include +#include +#include "sdkconfig.h" +#include "esp_foc_adc_cali.h" + +#if SOC_ADC_CALIB_SCHEME_CURVE_FITTING_SUPPORTED +#include "esp_efuse_rtc_calib.h" + + +#define COEFF_VERSION_NUM 1 // Currently P4 has one versions of curve calibration schemes +#define COEFF_GROUP_NUM 4 +#define TERM_MAX 2 + +/** + * @note Error Calculation + * Coefficients for calculating the reading voltage error. + * Four sets of coefficients for atten0 ~ atten3 respectively. + * + * For each item, first element is the Coefficient, second element is the Multiple. (Coefficient / Multiple) is the real coefficient. + * + * @note {0,0} stands for unused item + * @note In case of the overflow, these coefficients are recorded as Absolute Value + * @note For atten0 ~ 3, error = (K0 * X^0) + (K1 * X^1) + * @note Above formula is rewritten from the original documentation, please note that the coefficients are re-ordered. + */ +const static uint64_t adc1_error_coef_atten[COEFF_VERSION_NUM][COEFF_GROUP_NUM][TERM_MAX][2] = { + /* Coefficients of calibration version 1 */ + { + {{7170501832480995, 1e16}, {10598497992115, 1e16}}, //atten0 + {{9960085535084866, 1e16}, {15840076608145, 1e16}}, //atten1 + {{14711053224678996, 1e16}, {1594266424857, 1e17}}, //atten2 + {{28811493455181565, 1e16}, {10082311568625, 1e16}}, //atten3 + }, +}; + +const static uint64_t adc2_error_coef_atten[COEFF_VERSION_NUM][COEFF_GROUP_NUM][TERM_MAX][2] = { + /* Coefficients of calibration version 1 */ + { + {{4900967548489932, 1e16}, {5037402667913, 1e16}}, //atten0 + {{7296214814536025, 1e16}, {11021577596635, 1e16}}, //atten1 + {{10991620450220592, 1e16}, {11623930881896, 1e16}}, //atten2 + {{24421401024626730, 1e16}, {9458501263393, 1e16}}, //atten3 + }, +}; + +/** + * Term sign ADC1 + */ +const static int32_t adc1_error_sign[COEFF_VERSION_NUM][COEFF_GROUP_NUM][TERM_MAX] = { + /* Coefficient sign of calibration version 1 */ + { + {-1, 1}, //atten0 + {-1, 1}, //atten1 + {-1, 1}, //atten2 + {-1, 1}, //atten3 + }, +}; + +/** + * Term sign ADC2 + */ +const static int32_t adc2_error_sign[COEFF_VERSION_NUM][COEFF_GROUP_NUM][TERM_MAX] = { + /* Coefficient sign of calibration version 1 */ + { + {-1, 1}, //atten0 + {-1, 1}, //atten1 + {-1, 1}, //atten2 + {-1, 1}, //atten3 + }, +}; + +void esp_foc_curve_fitting_get_second_step_coeff(adc_unit_t unit, adc_atten_t atten, esp_foc_cali_chars_second_step_t *ctx) +{ + uint32_t adc_calib_ver = esp_efuse_rtc_calib_get_ver(); + assert((adc_calib_ver >= ESP_EFUSE_ADC_CALIB_VER_MIN) && + (adc_calib_ver <= ESP_EFUSE_ADC_CALIB_VER_MAX)); + + ctx->term_num = 2; + + ctx->coeff = unit == ADC_UNIT_1 ? + adc1_error_coef_atten[VER2IDX(adc_calib_ver)][atten] : + adc2_error_coef_atten[VER2IDX(adc_calib_ver)][atten]; + ctx->sign = unit == ADC_UNIT_1 ? + adc1_error_sign[VER2IDX(adc_calib_ver)][atten] : + adc2_error_sign[VER2IDX(adc_calib_ver)][atten]; +} + +#else + +void esp_foc_curve_fitting_get_second_step_coeff(adc_unit_t unit, adc_atten_t atten, + esp_foc_cali_chars_second_step_t *ctx) +{ + (void)unit; (void)atten; + ctx->term_num = 0; ctx->coeff = NULL; ctx->sign = NULL; +} + +#endif diff --git a/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32s3.c b/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32s3.c new file mode 100644 index 00000000..bae905ca --- /dev/null +++ b/source/drivers/cali/coeff/esp_foc_curve_coeff_esp32s3.c @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + * + * Curve-fitting coefficients ported from ESP-IDF esp_adc/esp32s3/curve_fitting_coefficients.c + */ + +#include +#include +#include "sdkconfig.h" +#include "esp_foc_adc_cali.h" + +#if SOC_ADC_CALIB_SCHEME_CURVE_FITTING_SUPPORTED +#include "esp_efuse_rtc_calib.h" + + +#define COEFF_GROUP_NUM 4 +#define TERM_MAX 5 + +/** + * @note Error Calculation + * Coefficients for calculating the reading voltage error. + * Four sets of coefficients for atten0 ~ atten3 respectively. + * + * For each item, first element is the Coefficient, second element is the Multiple. (Coefficient / Multiple) is the real coefficient. + * + * @note {0,0} stands for unused item + * @note In case of the overflow, these coefficients are recorded as Absolute Value + * @note For atten0 ~ 2, error = (K0 * X^0) + (K1 * X^1) + (K2 * X^2); For atten3, error = (K0 * X^0) + (K1 * X^1) + (K2 * X^2) + (K3 * X^3) + (K4 * X^4); + * @note Above formula is rewritten from the original documentation, please note that the coefficients are re-ordered. + */ +const static uint64_t adc1_error_coef_atten[COEFF_GROUP_NUM][TERM_MAX][2] = { + {{27856531419538344, 1e16}, {50871540569528, 1e16}, {9798249589, 1e15}, {0, 0}, {0, 0}}, //ADC1 atten0 + {{29831022915028695, 1e16}, {49393185868806, 1e16}, {101379430548, 1e16}, {0, 0}, {0, 0}}, //ADC1 atten1 + {{23285545746296417, 1e16}, {147640181047414, 1e16}, {208385525314, 1e16}, {0, 0}, {0, 0}}, //ADC1 atten2 + {{644403418269478, 1e15}, {644334888647536, 1e16}, {1297891447611, 1e16}, {70769718, 1e15}, {13515, 1e15}} //ADC1 atten3 +}; +const static uint64_t adc2_error_coef_atten[COEFF_GROUP_NUM][TERM_MAX][2] = { + {{25668651654328927, 1e16}, {1353548869615, 1e16}, {36615265189, 1e16}, {0, 0}, {0, 0}}, //ADC2 atten0 + {{23690184690298404, 1e16}, {66319894226185, 1e16}, {118964995959, 1e16}, {0, 0}, {0, 0}}, //ADC2 atten1 + {{9452499397020617, 1e16}, {200996773954387, 1e16}, {259011467956, 1e16}, {0, 0}, {0, 0}}, //ADC2 atten2 + {{12247719764336924, 1e16}, {755717904943462, 1e16}, {1478791187119, 1e16}, {79672528, 1e15}, {15038, 1e15}} //ADC2 atten3 +}; +/** + * Term sign + */ +const static int32_t adc1_error_sign[COEFF_GROUP_NUM][TERM_MAX] = { + {-1, -1, 1, 0, 0}, //ADC1 atten0 + {-1, -1, 1, 0, 0}, //ADC1 atten1 + {-1, -1, 1, 0, 0}, //ADC1 atten2 + {-1, -1, 1, -1, 1} //ADC1 atten3 +}; +const static int32_t adc2_error_sign[COEFF_GROUP_NUM][TERM_MAX] = { + {-1, 1, 1, 0, 0}, //ADC2 atten0 + {-1, -1, 1, 0, 0}, //ADC2 atten1 + {-1, -1, 1, 0, 0}, //ADC2 atten2 + { 1, -1, 1, -1, 1} //ADC2 atten3 +}; + +void esp_foc_curve_fitting_get_second_step_coeff(adc_unit_t unit, adc_atten_t atten, esp_foc_cali_chars_second_step_t *ctx) +{ + ctx->term_num = (atten == 3) ? 5 : 3; + ctx->coeff = unit == ADC_UNIT_1 ? + adc1_error_coef_atten[atten] : + adc2_error_coef_atten[atten]; + ctx->sign = unit == ADC_UNIT_1 ? + adc1_error_sign[atten] : + adc2_error_sign[atten]; +} + +#else + +void esp_foc_curve_fitting_get_second_step_coeff(adc_unit_t unit, adc_atten_t atten, + esp_foc_cali_chars_second_step_t *ctx) +{ + (void)unit; (void)atten; + ctx->term_num = 0; ctx->coeff = NULL; ctx->sign = NULL; +} + +#endif diff --git a/source/drivers/cali/esp_foc_adc_cali_curve.c b/source/drivers/cali/esp_foc_adc_cali_curve.c index e1ddb714..c9d6a49a 100644 --- a/source/drivers/cali/esp_foc_adc_cali_curve.c +++ b/source/drivers/cali/esp_foc_adc_cali_curve.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "esp_err.h" #include "sdkconfig.h" #include "esp_private/adc_share_hw_ctrl.h" @@ -48,15 +49,19 @@ static esp_foc_cali_curve_ctx_t s_curve_ctx; static int32_t get_reading_error(uint64_t v_cali_1, const esp_foc_cali_chars_second_step_t *param) { - if (v_cali_1 == 0 || param->term_num == 0) { + if (v_cali_1 == 0 || param->term_num == 0 || param->coeff == NULL || param->sign == NULL) { return 0; } uint8_t term_num = param->term_num; int32_t error = 0; uint64_t coeff = 0; - uint64_t variable[3]; - uint64_t term[3]; + uint64_t variable[5]; + uint64_t term[5]; + + if (term_num > (uint8_t)(sizeof(variable) / sizeof(variable[0]))) { + term_num = (uint8_t)(sizeof(variable) / sizeof(variable[0])); + } variable[0] = 1; coeff = param->coeff[0][0]; @@ -66,8 +71,7 @@ static int32_t get_reading_error(uint64_t v_cali_1, const esp_foc_cali_chars_sec for (int i = 1; i < term_num; i++) { variable[i] = variable[i - 1] * v_cali_1; coeff = param->coeff[i][0]; - term[i] = variable[i] * coeff; - term[i] = term[i] / param->coeff[i][1]; + term[i] = variable[i] * coeff / param->coeff[i][1]; error += (int32_t)term[i] * param->sign[i]; } diff --git a/source/drivers/cali/esp_foc_adc_cali_line.c b/source/drivers/cali/esp_foc_adc_cali_line.c deleted file mode 100644 index c98c52be..00000000 --- a/source/drivers/cali/esp_foc_adc_cali_line.c +++ /dev/null @@ -1,85 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Felipe Neves - * - * Line-fitting ADC calibration for ESP32 / ESP32-S2 (init-time LUT only). - */ - -#include -#include "sdkconfig.h" -#include "hal/adc_types.h" -#include "hal/efuse_ll.h" -#include "esp_foc_adc_cali.h" - -#if (CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2) && SOC_ADC_CALIB_SCHEME_LINE_FITTING_SUPPORTED - -#define LIN_COEFF_A_SCALE 65536 -#define LIN_COEFF_A_ROUND (LIN_COEFF_A_SCALE / 2) - -static bool line_coeffs_ready; -static uint32_t s_coeff_a; -static int32_t s_coeff_b; - -static bool line_coeffs_init(adc_unit_t unit, adc_atten_t atten) -{ - if (line_coeffs_ready) { - return true; - } - - uint32_t coeff_a = 0; - int32_t coeff_b = 0; - if (efuse_ll_get_adc_calib_ver() == 0) { - return false; - } - - if (unit == ADC_UNIT_1) { - coeff_a = efuse_ll_get_adc_calib_cali_val(atten); - coeff_b = efuse_ll_get_adc_calib_offset(atten); - } else { - coeff_a = efuse_ll_get_adc2_calib_cali_val(atten); - coeff_b = efuse_ll_get_adc2_calib_offset(atten); - } - - if (coeff_a == 0) { - return false; - } - - s_coeff_a = coeff_a; - s_coeff_b = coeff_b; - line_coeffs_ready = true; - return true; -} - -bool esp_foc_adc_cali_line_raw_to_mv(adc_unit_t unit, - adc_channel_t channel, - adc_atten_t atten, - int raw, - int *mv_out) -{ - (void)channel; - if (mv_out == NULL || !line_coeffs_init(unit, atten)) { - return false; - } - - *mv_out = (int)(((int64_t)raw * s_coeff_a + LIN_COEFF_A_ROUND) / LIN_COEFF_A_SCALE) + s_coeff_b; - return true; -} - -#else - -bool esp_foc_adc_cali_line_raw_to_mv(adc_unit_t unit, - adc_channel_t channel, - adc_atten_t atten, - int raw, - int *mv_out) -{ - (void)unit; - (void)channel; - (void)atten; - (void)raw; - (void)mv_out; - return false; -} - -#endif diff --git a/source/drivers/cali/esp_foc_adc_cali_line_esp32.c b/source/drivers/cali/esp_foc_adc_cali_line_esp32.c new file mode 100644 index 00000000..a976d78d --- /dev/null +++ b/source/drivers/cali/esp_foc_adc_cali_line_esp32.c @@ -0,0 +1,246 @@ +/* + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + * + * Line-fitting ADC calibration for ESP32 (init-time LUT, LUT high-range enabled). + */ + +#include +#include +#include "sdkconfig.h" +#include "hal/adc_types.h" +#include "hal/efuse_ll.h" +#include "esp_foc_adc_cali.h" + +#if CONFIG_IDF_TARGET_ESP32 && SOC_ADC_CALIB_SCHEME_LINE_FITTING_SUPPORTED + +#define LIN_COEFF_A_SCALE 65536 +#define LIN_COEFF_A_ROUND (LIN_COEFF_A_SCALE / 2) +#define LUT_VREF_LOW 1000 +#define LUT_VREF_HIGH 1200 +#define LUT_ADC_STEP_SIZE 64 +#define LUT_POINTS 20 +#define LUT_LOW_THRESH 2880 +#define LUT_HIGH_THRESH (LUT_LOW_THRESH + LUT_ADC_STEP_SIZE) +#define ADC_12_BIT_RES 4096 + +#define VREF_MASK 0x1F +#define VREF_STEP_SIZE 7 +#define VREF_OFFSET 1100 +#define TP_LOW1_OFFSET 278 +#define TP_LOW2_OFFSET 421 +#define TP_LOW_MASK 0x7F +#define TP_LOW_VOLTAGE 150 +#define TP_HIGH1_OFFSET 3265 +#define TP_HIGH2_OFFSET 3406 +#define TP_HIGH_MASK 0x1FF +#define TP_HIGH_VOLTAGE 850 +#define TP_STEP_SIZE 4 +#define CHECK_BLK3_FLAG 1 +#define VREF_FORMAT 0 + +static const uint32_t adc1_tp_atten_scale[4] = {65504, 86975, 120389, 224310}; +static const uint32_t adc2_tp_atten_scale[4] = {65467, 86861, 120416, 224708}; +static const uint32_t adc1_tp_atten_offset[4] = {0, 1, 27, 54}; +static const uint32_t adc2_tp_atten_offset[4] = {0, 9, 26, 66}; +static const uint32_t adc1_vref_atten_scale[4] = {57431, 76236, 105481, 196602}; +static const uint32_t adc2_vref_atten_scale[4] = {57236, 76175, 105678, 197170}; +static const uint32_t adc1_vref_atten_offset[4] = {75, 78, 107, 142}; +static const uint32_t adc2_vref_atten_offset[4] = {63, 66, 89, 128}; + +static const uint32_t lut_adc1_low[LUT_POINTS] = { + 2240, 2297, 2352, 2405, 2457, 2512, 2564, 2616, 2664, 2709, + 2754, 2795, 2832, 2868, 2903, 2937, 2969, 3000, 3030, 3060 +}; +static const uint32_t lut_adc1_high[LUT_POINTS] = { + 2667, 2706, 2745, 2780, 2813, 2844, 2873, 2901, 2928, 2956, + 2982, 3006, 3032, 3059, 3084, 3110, 3135, 3160, 3184, 3209 +}; +static const uint32_t lut_adc2_low[LUT_POINTS] = { + 2238, 2293, 2347, 2399, 2451, 2507, 2561, 2613, 2662, 2710, + 2754, 2792, 2831, 2869, 2904, 2937, 2968, 2999, 3029, 3059 +}; +static const uint32_t lut_adc2_high[LUT_POINTS] = { + 2657, 2698, 2738, 2774, 2807, 2838, 2867, 2894, 2921, 2946, + 2971, 2996, 3020, 3043, 3067, 3092, 3116, 3139, 3162, 3185 +}; + +typedef struct { + adc_unit_t unit; + adc_atten_t atten; + bool ready; + uint32_t coeff_a; + uint32_t coeff_b; + uint32_t vref; + const uint32_t *low_curve; + const uint32_t *high_curve; +} esp_foc_line_ctx_t; + +static esp_foc_line_ctx_t s_line; + +static inline int decode_bits(uint32_t bits, uint32_t mask, bool is_twos_compl) +{ + if (bits & (~(mask >> 1) & mask)) { + if (is_twos_compl) { + return -(((~bits) + 1) & (mask >> 1)); + } + return -(bits & (mask >> 1)); + } + return (int)(bits & (mask >> 1)); +} + +static bool check_efuse_tp(void) +{ + if (CHECK_BLK3_FLAG && (efuse_ll_get_blk3_part_reserve() == 0)) { + return false; + } + return efuse_ll_get_adc1_tp_low() && efuse_ll_get_adc2_tp_low() + && efuse_ll_get_adc1_tp_high() && efuse_ll_get_adc2_tp_high(); +} + +static void characterize_using_two_point(adc_unit_t unit, adc_atten_t atten, + uint32_t high, uint32_t low, + uint32_t *coeff_a, uint32_t *coeff_b) +{ + const uint32_t *scales = (unit == ADC_UNIT_1) ? adc1_tp_atten_scale : adc2_tp_atten_scale; + const uint32_t *offsets = (unit == ADC_UNIT_1) ? adc1_tp_atten_offset : adc2_tp_atten_offset; + uint32_t delta_x = high - low; + if (delta_x == 0) { + return; + } + uint32_t delta_v = TP_HIGH_VOLTAGE - TP_LOW_VOLTAGE; + *coeff_a = (delta_v * scales[atten] + (delta_x / 2)) / delta_x; + *coeff_b = TP_HIGH_VOLTAGE - ((delta_v * high + (delta_x / 2)) / delta_x) + offsets[atten]; +} + +static void characterize_using_vref(adc_unit_t unit, adc_atten_t atten, + uint32_t vref, uint32_t *coeff_a, uint32_t *coeff_b) +{ + const uint32_t *scales = (unit == ADC_UNIT_1) ? adc1_vref_atten_scale : adc2_vref_atten_scale; + const uint32_t *offsets = (unit == ADC_UNIT_1) ? adc1_vref_atten_offset : adc2_vref_atten_offset; + *coeff_a = (vref * scales[atten]) / ADC_12_BIT_RES; + *coeff_b = offsets[atten]; +} + +static uint32_t calc_voltage_linear(uint32_t raw, uint32_t coeff_a, uint32_t coeff_b) +{ + return (((coeff_a * raw) + LIN_COEFF_A_ROUND) / LIN_COEFF_A_SCALE) + coeff_b; +} + +static inline uint32_t interpolate_two_points(uint32_t y1, uint32_t y2, uint32_t x_step, uint32_t x) +{ + return ((y1 * x_step) + (y2 * x) - (y1 * x) + (x_step / 2)) / x_step; +} + +static uint32_t calc_voltage_lut(uint32_t adc, uint32_t vref, + const uint32_t *low_curve, const uint32_t *high_curve) +{ + uint32_t i = (adc - LUT_LOW_THRESH) / LUT_ADC_STEP_SIZE; + if (i + 1 >= LUT_POINTS) { + i = LUT_POINTS - 2; + } + int x2dist = LUT_VREF_HIGH - (int)vref; + int x1dist = (int)vref - LUT_VREF_LOW; + int y2dist = (int)(((i + 1) * LUT_ADC_STEP_SIZE) + LUT_LOW_THRESH - adc); + int y1dist = (int)(adc - ((i * LUT_ADC_STEP_SIZE) + LUT_LOW_THRESH)); + int q11 = (int)low_curve[i]; + int q12 = (int)low_curve[i + 1]; + int q21 = (int)high_curve[i]; + int q22 = (int)high_curve[i + 1]; + int voltage = (q11 * x2dist * y2dist) + (q21 * x1dist * y2dist) + + (q12 * x2dist * y1dist) + (q22 * x1dist * y1dist); + voltage += ((LUT_VREF_HIGH - LUT_VREF_LOW) * LUT_ADC_STEP_SIZE) / 2; + voltage /= ((LUT_VREF_HIGH - LUT_VREF_LOW) * LUT_ADC_STEP_SIZE); + return (uint32_t)voltage; +} + +static bool line_ctx_prepare(adc_unit_t unit, adc_atten_t atten) +{ + if (s_line.ready && s_line.unit == unit && s_line.atten == atten) { + return true; + } + if (efuse_ll_get_adc_calib_ver() == 0) { + return false; + } + + uint32_t coeff_a = 0; + uint32_t coeff_b = 0; + uint32_t vref = 1100; + + if (check_efuse_tp()) { + uint32_t high = (unit == ADC_UNIT_1) ? + (TP_HIGH1_OFFSET + decode_bits(efuse_ll_get_adc1_tp_high(), TP_HIGH_MASK, true) * TP_STEP_SIZE) : + (TP_HIGH2_OFFSET + decode_bits(efuse_ll_get_adc2_tp_high(), TP_HIGH_MASK, true) * TP_STEP_SIZE); + uint32_t low = (unit == ADC_UNIT_1) ? + (TP_LOW1_OFFSET + decode_bits(efuse_ll_get_adc1_tp_low(), TP_LOW_MASK, true) * TP_STEP_SIZE) : + (TP_LOW2_OFFSET + decode_bits(efuse_ll_get_adc2_tp_low(), TP_LOW_MASK, true) * TP_STEP_SIZE); + characterize_using_two_point(unit, atten, high, low, &coeff_a, &coeff_b); + if (coeff_a == 0) { + return false; + } + vref = VREF_OFFSET + decode_bits(efuse_ll_get_adc_vref(), VREF_MASK, VREF_FORMAT) * VREF_STEP_SIZE; + } else if (efuse_ll_get_adc_vref() != 0) { + vref = VREF_OFFSET + decode_bits(efuse_ll_get_adc_vref(), VREF_MASK, VREF_FORMAT) * VREF_STEP_SIZE; + characterize_using_vref(unit, atten, vref, &coeff_a, &coeff_b); + } else { + characterize_using_vref(unit, atten, vref, &coeff_a, &coeff_b); + } + + s_line.unit = unit; + s_line.atten = atten; + s_line.coeff_a = coeff_a; + s_line.coeff_b = coeff_b; + s_line.vref = vref; + if (atten == ADC_ATTEN_DB_11 || atten == ADC_ATTEN_DB_12) { + s_line.low_curve = (unit == ADC_UNIT_1) ? lut_adc1_low : lut_adc2_low; + s_line.high_curve = (unit == ADC_UNIT_1) ? lut_adc1_high : lut_adc2_high; + } else { + s_line.low_curve = NULL; + s_line.high_curve = NULL; + } + s_line.ready = true; + return true; +} + +bool esp_foc_adc_cali_line_raw_to_mv(adc_unit_t unit, adc_channel_t channel, + adc_atten_t atten, int raw, int *mv_out) +{ + (void)channel; + if (mv_out == NULL || !line_ctx_prepare(unit, atten)) { + return false; + } + + if (raw < 0) { + raw = 0; + } + if (raw > ADC_12_BIT_RES - 1) { + raw = ADC_12_BIT_RES - 1; + } + + uint32_t voltage; + if (s_line.low_curve != NULL && raw >= (int)LUT_LOW_THRESH) { + uint32_t lut_v = calc_voltage_lut((uint32_t)raw, s_line.vref, s_line.low_curve, s_line.high_curve); + if (raw <= (int)LUT_HIGH_THRESH) { + uint32_t lin_v = calc_voltage_linear((uint32_t)raw, s_line.coeff_a, s_line.coeff_b); + voltage = interpolate_two_points(lin_v, lut_v, LUT_ADC_STEP_SIZE, (uint32_t)(raw - LUT_LOW_THRESH)); + } else { + voltage = lut_v; + } + } else { + voltage = calc_voltage_linear((uint32_t)raw, s_line.coeff_a, s_line.coeff_b); + } + + *mv_out = (int)voltage; + return true; +} + +#else + +bool esp_foc_adc_cali_line_raw_to_mv(adc_unit_t unit, adc_channel_t channel, + adc_atten_t atten, int raw, int *mv_out) +{ + (void)unit; (void)channel; (void)atten; (void)raw; (void)mv_out; + return false; +} + +#endif diff --git a/source/drivers/cali/esp_foc_adc_cali_line_esp32c2.c b/source/drivers/cali/esp_foc_adc_cali_line_esp32c2.c new file mode 100644 index 00000000..5a9bc633 --- /dev/null +++ b/source/drivers/cali/esp_foc_adc_cali_line_esp32c2.c @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + * + * Line-fitting ADC calibration for ESP32-C2 (init-time LUT). + */ + +#include +#include +#include "sdkconfig.h" +#include "hal/adc_types.h" +#include "esp_err.h" +#include "esp_efuse_rtc_calib.h" +#include "esp_foc_adc_cali.h" + +#if CONFIG_IDF_TARGET_ESP32C2 && SOC_ADC_CALIB_SCHEME_LINE_FITTING_SUPPORTED + +static const int coeff_a_scaling = 65536; + +typedef struct { + adc_unit_t unit; + adc_atten_t atten; + bool ready; + uint32_t coeff_a; + uint32_t coeff_b; +} esp_foc_line_ctx_t; + +static esp_foc_line_ctx_t s_line; + +static bool line_ctx_prepare(adc_unit_t unit, adc_atten_t atten) +{ + if (s_line.ready && s_line.unit == unit && s_line.atten == atten) { + return true; + } + + uint32_t ver = esp_efuse_rtc_calib_get_ver(); + if (ver < ESP_EFUSE_ADC_CALIB_VER_MIN || ver > ESP_EFUSE_ADC_CALIB_VER_MAX) { + return false; + } + + uint32_t voltage_mv = 0; + uint32_t digi_val = 0; + if (esp_efuse_rtc_calib_get_cal_voltage(ver, unit, (int)atten, &digi_val, &voltage_mv) != ESP_OK + || digi_val == 0) { + return false; + } + + s_line.unit = unit; + s_line.atten = atten; + s_line.coeff_a = (uint32_t)(coeff_a_scaling * voltage_mv / digi_val); + s_line.coeff_b = 0; + s_line.ready = true; + return true; +} + +bool esp_foc_adc_cali_line_raw_to_mv(adc_unit_t unit, adc_channel_t channel, + adc_atten_t atten, int raw, int *mv_out) +{ + (void)channel; + if (mv_out == NULL || !line_ctx_prepare(unit, atten)) { + return false; + } + + *mv_out = (int)(raw * s_line.coeff_a / coeff_a_scaling + s_line.coeff_b); + return true; +} + +#else + +bool esp_foc_adc_cali_line_raw_to_mv(adc_unit_t unit, adc_channel_t channel, + adc_atten_t atten, int raw, int *mv_out) +{ + (void)unit; (void)channel; (void)atten; (void)raw; (void)mv_out; + return false; +} + +#endif diff --git a/source/drivers/cali/esp_foc_adc_cali_line_esp32s2.c b/source/drivers/cali/esp_foc_adc_cali_line_esp32s2.c new file mode 100644 index 00000000..351a46c5 --- /dev/null +++ b/source/drivers/cali/esp_foc_adc_cali_line_esp32s2.c @@ -0,0 +1,149 @@ +/* + * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + * + * Line-fitting ADC calibration for ESP32-S2 (init-time LUT). + */ + +#include +#include +#include "sdkconfig.h" +#include "hal/adc_types.h" +#include "esp_efuse_rtc_table.h" +#include "esp_foc_adc_cali.h" + +#if CONFIG_IDF_TARGET_ESP32S2 && SOC_ADC_CALIB_SCHEME_LINE_FITTING_SUPPORTED + +static const int coeff_a_scaling = 65536; +static const int coeff_b_scaling = 1024; + +typedef struct { + int adc_calib_high; + int adc_calib_low; +} esp_foc_s2_calib_ver1_t; + +typedef struct { + int adc_calib_high; + int adc_calib_high_voltage; +} esp_foc_s2_calib_ver2_t; + +typedef struct { + char version_num; + adc_unit_t unit_id; + adc_atten_t atten_level; + union { + esp_foc_s2_calib_ver1_t ver1; + esp_foc_s2_calib_ver2_t ver2; + } efuse_data; +} esp_foc_s2_calib_info_t; + +typedef struct { + adc_unit_t unit; + adc_atten_t atten; + bool ready; + uint32_t coeff_a; + uint32_t coeff_b; +} esp_foc_line_ctx_t; + +static esp_foc_line_ctx_t s_line; + +static void characterize_using_two_point(adc_atten_t atten, uint32_t high, uint32_t low, + uint32_t *coeff_a, uint32_t *coeff_b) +{ + static const uint32_t v_high[] = {600, 800, 1000, 2000}; + static const uint32_t v_low = 250; + *coeff_a = (uint32_t)(coeff_a_scaling * (v_high[atten] - v_low) / (high - low)); + *coeff_b = (uint32_t)(coeff_b_scaling * (v_low * high - v_high[atten] * low) / (high - low)); +} + +static bool prepare_calib_data(adc_unit_t unit_id, adc_atten_t atten, esp_foc_s2_calib_info_t *out) +{ + int version_num = esp_efuse_rtc_table_read_calib_version(); + if (version_num != 1 && version_num != 2) { + return false; + } + + out->version_num = (char)version_num; + out->unit_id = unit_id; + out->atten_level = atten; + + int tag; + switch (version_num) { + case 1: + tag = esp_efuse_rtc_table_get_tag(version_num, unit_id, atten, RTCCALIB_V1_PARAM_VLOW); + out->efuse_data.ver1.adc_calib_low = esp_efuse_rtc_table_get_parsed_efuse_value(tag, false); + tag = esp_efuse_rtc_table_get_tag(version_num, unit_id, atten, RTCCALIB_V1_PARAM_VHIGH); + out->efuse_data.ver1.adc_calib_high = esp_efuse_rtc_table_get_parsed_efuse_value(tag, false); + break; + case 2: + tag = esp_efuse_rtc_table_get_tag(version_num, unit_id, atten, RTCCALIB_V2_PARAM_VHIGH); + out->efuse_data.ver2.adc_calib_high = esp_efuse_rtc_table_get_parsed_efuse_value(tag, false); + switch (atten) { + case ADC_ATTEN_DB_0: out->efuse_data.ver2.adc_calib_high_voltage = 600; break; + case ADC_ATTEN_DB_2_5: out->efuse_data.ver2.adc_calib_high_voltage = 800; break; + case ADC_ATTEN_DB_6: out->efuse_data.ver2.adc_calib_high_voltage = 1000; break; + case ADC_ATTEN_DB_12: out->efuse_data.ver2.adc_calib_high_voltage = 2000; break; + default: return false; + } + break; + default: + return false; + } + return true; +} + +static bool line_ctx_prepare(adc_unit_t unit, adc_atten_t atten) +{ + if (s_line.ready && s_line.unit == unit && s_line.atten == atten) { + return true; + } + + esp_foc_s2_calib_info_t info = {0}; + if (!prepare_calib_data(unit, atten, &info)) { + return false; + } + + switch (info.version_num) { + case 1: + characterize_using_two_point(atten, + (uint32_t)info.efuse_data.ver1.adc_calib_high, + (uint32_t)info.efuse_data.ver1.adc_calib_low, + &s_line.coeff_a, &s_line.coeff_b); + break; + case 2: + s_line.coeff_a = (uint32_t)(coeff_a_scaling * info.efuse_data.ver2.adc_calib_high_voltage + / info.efuse_data.ver2.adc_calib_high); + s_line.coeff_b = 0; + break; + default: + return false; + } + + s_line.unit = unit; + s_line.atten = atten; + s_line.ready = true; + return true; +} + +bool esp_foc_adc_cali_line_raw_to_mv(adc_unit_t unit, adc_channel_t channel, + adc_atten_t atten, int raw, int *mv_out) +{ + (void)channel; + if (mv_out == NULL || !line_ctx_prepare(unit, atten)) { + return false; + } + + *mv_out = (int)(raw * s_line.coeff_a / (coeff_a_scaling / coeff_b_scaling) + s_line.coeff_b) / coeff_b_scaling; + return true; +} + +#else + +bool esp_foc_adc_cali_line_raw_to_mv(adc_unit_t unit, adc_channel_t channel, + adc_atten_t atten, int raw, int *mv_out) +{ + (void)unit; (void)channel; (void)atten; (void)raw; (void)mv_out; + return false; +} + +#endif diff --git a/source/drivers/cali/esp_foc_adc_range_extend.c b/source/drivers/cali/esp_foc_adc_range_extend.c new file mode 100644 index 00000000..65bb0341 --- /dev/null +++ b/source/drivers/cali/esp_foc_adc_range_extend.c @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + * + * ADC range extension for ESP32-S2/S3 (esp-iot-solution patch logic, LUT build path). + * Dual-read HW path is not available in digi-DMA mode; polynomial extrapolation applies + * when the first-pass calibrated voltage exceeds the chip threshold. + */ + +#include +#include "sdkconfig.h" +#include "hal/adc_types.h" +#include "esp_foc_adc_cali.h" + +#define ESP_FOC_ADC_RANGE_EXTEND_REF_MV 3300 + +#if CONFIG_IDF_TARGET_ESP32S2 + +#define ESP_FOC_ADC_RANGE_THRESHOLD_MV 2600 + +static int s2_range_extend_mv(int mv) +{ + const float a = -0.0000050800531f; + const float b = 0.02334678273232382f; + const float c = -26.699083271336267f; + float v = (float)mv; + float e = a * v * v + b * v + c; + v = v * (1.0f + e / 100.0f); + if (v < 0.0f) { + v = 0.0f; + } + if (v > ESP_FOC_ADC_RANGE_EXTEND_REF_MV) { + v = (float)ESP_FOC_ADC_RANGE_EXTEND_REF_MV; + } + return (int)(v + 0.5f); +} + +#elif CONFIG_IDF_TARGET_ESP32S3 + +#define ESP_FOC_ADC_RANGE_THRESHOLD_MV 2900 + +static int s3_range_extend_mv(int mv) +{ + int v = mv + 1000; + if (v > 2700) { + const float a = -0.0000016625088686597596f; + const float b = 0.0012152697844402401f; + const float c = 7.660092154791914f; + float vf = (float)v; + float e = a * vf * vf + b * vf + c; + vf = vf * (1.0f + e / 100.0f); + v = (int)(vf + 0.5f); + } + if (v < 0) { + v = 0; + } + if (v > ESP_FOC_ADC_RANGE_EXTEND_REF_MV) { + v = ESP_FOC_ADC_RANGE_EXTEND_REF_MV; + } + return v; +} + +#endif + +bool esp_foc_adc_range_extend_supported(void) +{ +#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + return true; +#else + return false; +#endif +} + +int esp_foc_adc_range_extend_mv(adc_atten_t atten, int mv) +{ +#if CONFIG_IDF_TARGET_ESP32S2 + if (atten == ADC_ATTEN_DB_12 && mv > ESP_FOC_ADC_RANGE_THRESHOLD_MV) { + return s2_range_extend_mv(mv); + } +#elif CONFIG_IDF_TARGET_ESP32S3 + if (atten == ADC_ATTEN_DB_12 && mv > ESP_FOC_ADC_RANGE_THRESHOLD_MV) { + return s3_range_extend_mv(mv); + } +#endif + return mv; +} diff --git a/source/drivers/cali/esp_foc_adc_range_extend.h b/source/drivers/cali/esp_foc_adc_range_extend.h new file mode 100644 index 00000000..992b5abd --- /dev/null +++ b/source/drivers/cali/esp_foc_adc_range_extend.h @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "hal/adc_types.h" + +bool esp_foc_adc_range_extend_supported(void); +int esp_foc_adc_range_extend_mv(adc_atten_t atten, int mv); diff --git a/source/drivers/current_sensor_adc.c b/source/drivers/current_sensor_adc.c index 1ac70a81..5ca9ae23 100644 --- a/source/drivers/current_sensor_adc.c +++ b/source/drivers/current_sensor_adc.c @@ -87,7 +87,8 @@ typedef struct { DRAM_ATTR static isensor_adc_t s_isensor; static bool s_adc_initialized; -static const float adc_to_volts = 3.1f / 4096.0f; +#define ESP_FOC_ADC_FULL_SCALE_MV 3300 +static const float adc_to_volts = (float)ESP_FOC_ADC_FULL_SCALE_MV / 4096.0f; static adc_bitwidth_t isensor_adc_digi_bitwidth(void) { diff --git a/source/drivers/esp_foc_adc_cali_lut.c b/source/drivers/esp_foc_adc_cali_lut.c index 2946cc1f..57953ea0 100644 --- a/source/drivers/esp_foc_adc_cali_lut.c +++ b/source/drivers/esp_foc_adc_cali_lut.c @@ -6,8 +6,10 @@ #include #include "esp_log.h" +#include "sdkconfig.h" #include "espFoC/esp_foc_adc_cali_lut.h" #include "cali/esp_foc_adc_cali.h" +#include "cali/esp_foc_adc_range_extend.h" static const char *TAG = "esp_foc_adc_cali_lut"; @@ -19,12 +21,16 @@ static bool raw_to_mv(adc_unit_t unit, int raw, int *mv_out) { -#if (CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2) - return esp_foc_adc_cali_line_raw_to_mv(unit, channel, atten, raw, mv_out); + bool ok = false; +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32C2 + ok = esp_foc_adc_cali_line_raw_to_mv(unit, channel, atten, raw, mv_out); #else - (void)unit; - return esp_foc_adc_cali_curve_raw_to_mv(unit, channel, atten, raw, mv_out); + ok = esp_foc_adc_cali_curve_raw_to_mv(unit, channel, atten, raw, mv_out); #endif + if (ok && mv_out != NULL && esp_foc_adc_range_extend_supported()) { + *mv_out = esp_foc_adc_range_extend_mv(atten, *mv_out); + } + return ok; } bool esp_foc_adc_cali_lut_build(adc_unit_t unit, diff --git a/source/drivers/isensor_adc_dma_gdma.c b/source/drivers/isensor_adc_dma_gdma.c index b667f447..9af38166 100644 --- a/source/drivers/isensor_adc_dma_gdma.c +++ b/source/drivers/isensor_adc_dma_gdma.c @@ -62,6 +62,8 @@ esp_err_t isensor_adc_dma_init(isensor_adc_dma_ctx_t *ctx, return ESP_OK; } + int __DECLARE_RCC_ATOMIC_ENV __attribute__((unused)); + gdma_ll_enable_bus_clock(ISENSOR_ADC_GDMA_GROUP, true); gdma_ll_reset_register(ISENSOR_ADC_GDMA_GROUP); diff --git a/test/test_isensor_adc.c b/test/test_isensor_adc.c index 1895cee5..5ae22ecf 100644 --- a/test/test_isensor_adc.c +++ b/test/test_isensor_adc.c @@ -10,7 +10,7 @@ TEST_CASE("adc cali LUT builds 4096 entries", "[espFoC][isensor_adc]") { - int16_t lut[ESP_FOC_ISENSOR_ADC_LUT_SIZE]; + static int16_t lut[ESP_FOC_ISENSOR_ADC_LUT_SIZE]; bool ok = esp_foc_adc_cali_lut_build(ADC_UNIT_1, ADC_CHANNEL_0, ADC_ATTEN_DB_12, lut, ESP_FOC_ISENSOR_ADC_LUT_SIZE); (void)ok; @@ -20,7 +20,7 @@ TEST_CASE("adc cali LUT builds 4096 entries", "[espFoC][isensor_adc]") TEST_CASE("adc cali LUT apply clamps out of range", "[espFoC][isensor_adc]") { - int16_t lut[ESP_FOC_ISENSOR_ADC_LUT_SIZE]; + static int16_t lut[ESP_FOC_ISENSOR_ADC_LUT_SIZE]; for (int i = 0; i < ESP_FOC_ISENSOR_ADC_LUT_SIZE; i++) { lut[i] = (int16_t)i; }