diff --git a/src/current_sense/hardware_specific/esp32/esp32_adc_digi_driver.c b/src/current_sense/hardware_specific/esp32/esp32_adc_digi_driver.c new file mode 100644 index 00000000..2afba529 --- /dev/null +++ b/src/current_sense/hardware_specific/esp32/esp32_adc_digi_driver.c @@ -0,0 +1,460 @@ +/* + * MIT License + * + * Copyright (c) 2021 Felipe Neves + * + * ADC digi + DMA for SimpleFOC low-side current sense. + * + * Triggering is selected with esp32_adc_digi_set_trigger(): + * - SOFTWARE: always available (ESP32, S2, S3, …). + * - ETM: ESP32-S3 and newer only; see esp32_adc_etm.c. + */ + +#if defined(ARDUINO_ARCH_ESP32) + +#include "esp32_adc_digi_internal.h" + +#if SIMPLEFOC_ESP32_ADC_DIGI_SUPPORTED + +#include +#include +#include "esp_log.h" +#include "esp_check.h" +#include "esp_heap_caps.h" +#include "esp_memory_utils.h" +#include "esp_clk_tree.h" +#include "esp_private/regi2c_ctrl.h" +#include "esp_private/sar_periph_ctrl.h" +#include "esp_private/adc_share_hw_ctrl.h" +#include "esp_private/gpio.h" +#include "soc/adc_periph.h" +#include "soc/soc_caps.h" +#include "soc/clk_tree_defs.h" +#include "hal/adc_hal.h" +#include "hal/adc_ll.h" +#include "esp32_adc_digi_driver.h" + +#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE +#include "esp_cache.h" +#endif + +#define ESP32_ADC_DMA_DESC_ALIGN 4 + +typedef enum { + ESP32_ADC_STATE_IDLE = 0, + ESP32_ADC_STATE_BUSY, +} esp32_adc_digi_state_t; + +static const char *TAG = "SF_ESP32_ADC_DIGI"; + +typedef struct { + adc_hal_dma_ctx_t hal; + adc_hal_digi_ctrlr_cfg_t hal_cfg; + adc_digi_pattern_config_t patterns[SIMPLEFOC_ESP32_ADC_NUM_CHANNELS]; + uint32_t rx_desc_size; + esp32_adc_digi_dma_ctx_t dma_ctx; + adc_channel_t channels[SIMPLEFOC_ESP32_ADC_NUM_CHANNELS]; + adc_unit_t unit; + adc_atten_t atten; + int *adc_buffer; + int no_adc_channels; + esp32_adc_digi_trigger_t trigger; + esp32_adc_digi_state_t state; + bool started; +#if SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED + esp32_adc_digi_etm_config_t etm_cfg; +#endif +} esp32_adc_digi_ctx_t; + +DRAM_ATTR static esp32_adc_digi_ctx_t s_adc; +static bool s_adc_initialized; + +static adc_bitwidth_t esp32_adc_digi_bitwidth(void) +{ +#if CONFIG_IDF_TARGET_ESP32 + return ADC_BITWIDTH_12; +#else + return SOC_ADC_DIGI_MAX_BITWIDTH; +#endif +} + +static adc_atten_t esp32_adc_default_atten(void) +{ +#if CONFIG_IDF_TARGET_ESP32 + return ADC_ATTEN_DB_11; +#else + return ADC_ATTEN_DB_12; +#endif +} + +static void esp32_adc_digi_apply_convert_limit(void) +{ + adc_ll_digi_set_convert_limit_num(SIMPLEFOC_ESP32_ADC_CONVERT_LIMIT); + adc_ll_digi_convert_limit_enable(true); +} + +static void esp32_adc_apply_max_clock(esp32_adc_digi_ctx_t *adc) +{ +#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)); + adc->hal_cfg.clk_src = ADC_DIGI_CLK_SRC_APLL; + adc->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)); + adc->hal_cfg.clk_src = ADC_DIGI_CLK_SRC_PLL_F240M; + adc->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 +#if defined(ADC_DIGI_CLK_SRC_PLL_F80M) + const soc_module_clk_t fast_src = SOC_MOD_CLK_PLL_F80M; + const soc_periph_adc_digi_clk_src_t adc_clk = ADC_DIGI_CLK_SRC_PLL_F80M; +#elif defined(ADC_DIGI_CLK_SRC_PLL_F96M) + const soc_module_clk_t fast_src = SOC_MOD_CLK_PLL_F96M; + const soc_periph_adc_digi_clk_src_t adc_clk = ADC_DIGI_CLK_SRC_PLL_F96M; +#elif defined(ADC_DIGI_CLK_SRC_APB) + const soc_module_clk_t fast_src = SOC_MOD_CLK_APB; + const soc_periph_adc_digi_clk_src_t adc_clk = ADC_DIGI_CLK_SRC_APB; +#else + const soc_module_clk_t fast_src = (soc_module_clk_t)ADC_DIGI_CLK_SRC_DEFAULT; + const soc_periph_adc_digi_clk_src_t adc_clk = ADC_DIGI_CLK_SRC_DEFAULT; +#endif + 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)); + adc->hal_cfg.clk_src = adc_clk; + adc->hal_cfg.clk_src_freq_hz = clk_hz; + adc_ll_digi_clk_sel(adc_clk); + 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)adc->hal_cfg.clk_src, adc->hal_cfg.clk_src_freq_hz); +} + +static void esp32_adc_rearm(esp32_adc_digi_ctx_t *adc) +{ + esp32_adc_digi_dma_reset(&adc->dma_ctx); + adc_hal_digi_reset(); + adc_hal_digi_dma_link(&adc->hal, (uint8_t *)adc->adc_buffer); +#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE + esp_cache_msync(adc->hal.rx_desc, adc->rx_desc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); +#endif + esp32_adc_digi_dma_start(&adc->dma_ctx, adc->hal.rx_desc); + adc_hal_digi_connect(true); + if (adc->trigger == SIMPLEFOC_ESP32_ADC_TRIG_SOFTWARE) { + adc_hal_digi_enable(true); + } else { + adc_hal_digi_enable(false); + } + adc->state = ESP32_ADC_STATE_BUSY; +} + +static esp_err_t esp32_adc_apply_trigger_mode(esp32_adc_digi_ctx_t *adc) +{ +#if SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED + if (adc->trigger == SIMPLEFOC_ESP32_ADC_TRIG_ETM) { + esp_err_t err = esp32_adc_digi_etm_connect(&adc->etm_cfg); + if (err != ESP_OK) { + return err; + } + err = esp32_adc_digi_etm_enable(true); + if (err != ESP_OK) { + return err; + } + adc_hal_digi_enable(false); + return ESP_OK; + } + esp32_adc_digi_etm_enable(false); + esp32_adc_digi_etm_disconnect(); +#else + if (adc->trigger != SIMPLEFOC_ESP32_ADC_TRIG_SOFTWARE) { + return ESP_ERR_NOT_SUPPORTED; + } +#endif + return ESP_OK; +} + +static esp_err_t esp32_adc_gpio_init(adc_unit_t unit, uint32_t chan_mask) +{ + while (chan_mask) { + int ch = __builtin_ctz(chan_mask); + int8_t io = adc_channel_io_map[unit][ch]; + if (io < 0) { + return ESP_ERR_INVALID_ARG; + } + gpio_config_as_analog(io); + chan_mask &= ~(1U << ch); + } + return ESP_OK; +} + +static void IRAM_ATTR esp32_adc_dma_done(void *arg) +{ + esp32_adc_digi_ctx_t *adc = (esp32_adc_digi_ctx_t *)arg; + +#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE + if (adc->adc_buffer != NULL) { + esp_cache_msync((void *)adc->adc_buffer, ESP32_ADC_DIGI_FRAME_BYTES, ESP_CACHE_MSYNC_FLAG_DIR_M2C); + } + esp_cache_msync(adc->hal.rx_desc, adc->rx_desc_size, + ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); +#endif + +#if SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED + if (adc->trigger == SIMPLEFOC_ESP32_ADC_TRIG_ETM) { + esp32_adc_rearm(adc); + return; + } +#endif + adc_hal_digi_enable(false); + adc_hal_digi_connect(false); + adc->state = ESP32_ADC_STATE_IDLE; +} + +static esp_err_t esp32_adc_hw_start(esp32_adc_digi_ctx_t *adc) +{ + ANALOG_CLOCK_ENABLE(); + + ADC_BUS_CLK_ATOMIC() { + adc_ll_reset_register(); + } + + sar_periph_ctrl_adc_continuous_power_acquire(); + adc_lock_acquire(adc->unit); + +#if SOC_ADC_CALIBRATION_V1_SUPPORTED + adc_hal_calibration_init(adc->unit); + adc_set_hw_calibration_code(adc->unit, adc->atten); +#endif + + adc_hal_set_controller(adc->unit, ADC_HAL_CONTINUOUS_READ_MODE); + +#if !CONFIG_IDF_TARGET_ESP32 + ESP_ERROR_CHECK(esp_clk_tree_enable_src((soc_module_clk_t)adc->hal_cfg.clk_src, true)); +#endif + + adc_hal_digi_init(&adc->hal); + adc_hal_digi_controller_config(&adc->hal, &adc->hal_cfg); + esp32_adc_apply_max_clock(adc); + esp32_adc_digi_apply_convert_limit(); + + esp32_adc_digi_dma_stop(&adc->dma_ctx); + adc->started = true; + esp32_adc_rearm(adc); + return ESP_OK; +} + +static esp_err_t esp32_adc_setup_hal(esp32_adc_digi_ctx_t *adc) +{ + uint32_t clk_hz = 0; + esp_clk_tree_src_get_freq_hz(ADC_DIGI_CLK_SRC_DEFAULT, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &clk_hz); + + adc->hal_cfg.adc_pattern = adc->patterns; + adc->hal_cfg.adc_pattern_len = SIMPLEFOC_ESP32_ADC_NUM_CHANNELS; + adc->hal_cfg.sample_freq_hz = SIMPLEFOC_ESP32_ADC_PATTERN_HZ; + adc->hal_cfg.conv_mode = ADC_CONV_SINGLE_UNIT_1; + adc->hal_cfg.clk_src = ADC_DIGI_CLK_SRC_DEFAULT; + adc->hal_cfg.clk_src_freq_hz = clk_hz; + + uint32_t chan_mask = 0; + for (int i = 0; i < SIMPLEFOC_ESP32_ADC_NUM_CHANNELS; i++) { + adc->patterns[i].atten = adc->atten; + adc->patterns[i].channel = adc->channels[i] & 0x7; + adc->patterns[i].unit = adc->unit; + adc->patterns[i].bit_width = esp32_adc_digi_bitwidth(); + chan_mask |= BIT(adc->patterns[i].channel); + ESP_LOGI(TAG, "pattern[%d] unit=%d ch=%d atten=%d", i, + (int)adc->patterns[i].unit, (int)adc->patterns[i].channel, + (int)adc->patterns[i].atten); + } + + adc_hal_dma_config_t dma_cfg = { + .eof_desc_num = 1, + .eof_step = 1, + .eof_num = SIMPLEFOC_ESP32_ADC_NUM_CHANNELS, + }; + adc_hal_dma_ctx_config(&adc->hal, &dma_cfg); + + adc->hal.rx_desc = heap_caps_aligned_calloc(ESP32_ADC_DMA_DESC_ALIGN, 1, + sizeof(dma_descriptor_t), + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + if (adc->hal.rx_desc == NULL) { + return ESP_ERR_NO_MEM; + } + +#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE + uint32_t line = 4; + adc->rx_desc_size = (sizeof(dma_descriptor_t) + line - 1) & ~(line - 1); +#else + adc->rx_desc_size = sizeof(dma_descriptor_t); +#endif + + ESP_RETURN_ON_ERROR(esp32_adc_gpio_init(adc->unit, chan_mask), TAG, "gpio init failed"); + ESP_RETURN_ON_ERROR(esp32_adc_digi_dma_init(&adc->dma_ctx, esp32_adc_dma_done, adc), + TAG, "dma init failed"); + return ESP_OK; +} + +int esp32_adc_digi_read_raw(const void *adc_buffer, int index) +{ + return esp32_adc_digi_raw_at(adc_buffer, index); +} + +bool esp32_adc_digi_supported(void) +{ + return true; +} + +#if SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED +bool esp32_adc_digi_etm_supported(void) +{ + return true; +} +#endif + +esp_err_t esp32_adc_digi_init(const esp32_adc_digi_config_t *cfg) +{ + if (cfg == NULL || cfg->adc_buffer == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (cfg->unit != ADC_UNIT_1) { + ESP_LOGE(TAG, "only ADC1 supported"); + return ESP_ERR_NOT_SUPPORTED; + } + if (cfg->no_adc_channels < 1 || cfg->no_adc_channels > SIMPLEFOC_ESP32_ADC_NUM_CHANNELS) { + ESP_LOGE(TAG, "need 1..%d ADC channels for hw trigger", SIMPLEFOC_ESP32_ADC_NUM_CHANNELS); + return ESP_ERR_INVALID_ARG; + } + if (!esp_ptr_dma_capable(cfg->adc_buffer) || + !esp_ptr_internal(cfg->adc_buffer)) { + ESP_LOGE(TAG, "adc_buffer must be INTERNAL|DMA capable (heap_caps_calloc)"); + return ESP_ERR_INVALID_ARG; + } + if (s_adc_initialized) { + return ESP_OK; + } + + memset(&s_adc, 0, sizeof(s_adc)); + s_adc.unit = cfg->unit; + s_adc.atten = esp32_adc_default_atten(); + s_adc.channels[0] = cfg->channels[0]; + s_adc.channels[1] = cfg->channels[1]; + s_adc.adc_buffer = cfg->adc_buffer; + s_adc.no_adc_channels = cfg->no_adc_channels; + s_adc.trigger = SIMPLEFOC_ESP32_ADC_TRIG_SOFTWARE; + s_adc.state = ESP32_ADC_STATE_IDLE; +#if SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED + s_adc.etm_cfg.mcpwm_timer = 0; + s_adc.etm_cfg.event = SIMPLEFOC_ESP32_ADC_MCPWM_EVT_TIMER_TEZ; +#endif + + adc_apb_periph_claim(); + + esp_err_t err = esp32_adc_setup_hal(&s_adc); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ADC HAL setup failed: %d", err); + return err; + } + + err = esp32_adc_hw_start(&s_adc); + if (err != ESP_OK) { + return err; + } + + s_adc_initialized = true; + return ESP_OK; +} + +esp_err_t esp32_adc_digi_deinit(void) +{ + if (!s_adc_initialized) { + return ESP_OK; + } + esp32_adc_digi_set_trigger(SIMPLEFOC_ESP32_ADC_TRIG_SOFTWARE); + esp32_adc_digi_dma_stop(&s_adc.dma_ctx); + esp32_adc_digi_dma_deinit(&s_adc.dma_ctx); + adc_hal_digi_deinit(); + adc_lock_release(s_adc.unit); + sar_periph_ctrl_adc_continuous_power_release(); + if (s_adc.hal.rx_desc) { + heap_caps_free(s_adc.hal.rx_desc); + s_adc.hal.rx_desc = NULL; + } + s_adc_initialized = false; + return ESP_OK; +} + +esp_err_t esp32_adc_digi_set_trigger(esp32_adc_digi_trigger_t mode) +{ + if (!s_adc_initialized) { + return ESP_ERR_INVALID_STATE; + } +#if !SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED + if (mode != SIMPLEFOC_ESP32_ADC_TRIG_SOFTWARE) { + return ESP_ERR_NOT_SUPPORTED; + } +#endif + s_adc.trigger = mode; + esp_err_t err = esp32_adc_apply_trigger_mode(&s_adc); + if (err != ESP_OK) { + s_adc.trigger = SIMPLEFOC_ESP32_ADC_TRIG_SOFTWARE; + return err; + } + if (s_adc.started && s_adc.state != ESP32_ADC_STATE_BUSY) { + esp32_adc_rearm(&s_adc); + } + return ESP_OK; +} + +#if SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED +esp_err_t esp32_adc_digi_set_etm_source(const esp32_adc_digi_etm_config_t *cfg) +{ + if (!s_adc_initialized || cfg == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (cfg->mcpwm_timer >= SOC_MCPWM_TIMERS_PER_GROUP) { + return ESP_ERR_INVALID_ARG; + } + s_adc.etm_cfg = *cfg; + if (s_adc.trigger == SIMPLEFOC_ESP32_ADC_TRIG_ETM) { + return esp32_adc_apply_trigger_mode(&s_adc); + } + return ESP_OK; +} +#endif /* SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED */ + +esp_err_t esp32_adc_digi_trigger_software(void) +{ + if (!s_adc_initialized) { + return ESP_ERR_INVALID_STATE; + } + if (s_adc.trigger != SIMPLEFOC_ESP32_ADC_TRIG_SOFTWARE) { + return ESP_ERR_INVALID_STATE; + } + if (s_adc.state == ESP32_ADC_STATE_BUSY) { + return ESP_OK; + } + if (!s_adc.started) { + return esp32_adc_hw_start(&s_adc); + } + esp32_adc_rearm(&s_adc); + return ESP_OK; +} + +#endif /* SIMPLEFOC_ESP32_ADC_DIGI_SUPPORTED */ +#endif /* ARDUINO_ARCH_ESP32 */ diff --git a/src/current_sense/hardware_specific/esp32/esp32_adc_digi_driver.h b/src/current_sense/hardware_specific/esp32/esp32_adc_digi_driver.h new file mode 100644 index 00000000..8abee475 --- /dev/null +++ b/src/current_sense/hardware_specific/esp32/esp32_adc_digi_driver.h @@ -0,0 +1,56 @@ +/* + * MIT License + * + * Copyright (c) 2021 Felipe Neves + * + * Digital ADC + DMA driver for SimpleFOC ESP32 low-side current sense. + * See esp32_adc_digi_internal.h for chip-specific trigger/DMA behaviour. + */ +#pragma once + +#if defined(ARDUINO_ARCH_ESP32) + +#include "esp_err.h" +#include "hal/adc_types.h" +#include "esp32_adc_digi_internal.h" + +#if SIMPLEFOC_ESP32_ADC_DIGI_SUPPORTED + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + adc_channel_t channels[SIMPLEFOC_ESP32_ADC_NUM_CHANNELS]; + adc_unit_t unit; + int *adc_buffer; + int no_adc_channels; +} esp32_adc_digi_config_t; + +typedef enum { + SIMPLEFOC_ESP32_ADC_TRIG_SOFTWARE = 0, +#if SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED + SIMPLEFOC_ESP32_ADC_TRIG_ETM, +#endif +} esp32_adc_digi_trigger_t; + +esp_err_t esp32_adc_digi_init(const esp32_adc_digi_config_t *cfg); +esp_err_t esp32_adc_digi_deinit(void); +esp_err_t esp32_adc_digi_set_trigger(esp32_adc_digi_trigger_t mode); +esp_err_t esp32_adc_digi_trigger_software(void); + +int esp32_adc_digi_read_raw(const void *adc_buffer, int index); + +bool esp32_adc_digi_supported(void); + +#if SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED +esp_err_t esp32_adc_digi_set_etm_source(const esp32_adc_digi_etm_config_t *cfg); +bool esp32_adc_digi_etm_supported(void); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* SIMPLEFOC_ESP32_ADC_DIGI_SUPPORTED */ +#endif /* ARDUINO_ARCH_ESP32 */ diff --git a/src/current_sense/hardware_specific/esp32/esp32_adc_digi_internal.h b/src/current_sense/hardware_specific/esp32/esp32_adc_digi_internal.h new file mode 100644 index 00000000..2d06f1fb --- /dev/null +++ b/src/current_sense/hardware_specific/esp32/esp32_adc_digi_internal.h @@ -0,0 +1,110 @@ +/* + * MIT License + * + * Copyright (c) 2021 Felipe Neves + */ +#pragma once + +#if !defined(ARDUINO_ARCH_ESP32) + +#define SIMPLEFOC_ESP32_ADC_DIGI_SUPPORTED 0 +#define SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED 0 + +#else + +#include +#include +#include "sdkconfig.h" +#include "esp_err.h" +#include "hal/adc_types.h" +#include "hal/dma_types.h" +#include "soc/soc_caps.h" + +/* + * Low-side current sense on ESP32 uses the SAR **digital** controller (pattern + * sequencer) plus a chip-specific DMA shim — same approach as espFoC isensor_adc. + * + * Triggering (when to start one pattern conversion): + * - ESP32 / ESP32-S2: **software** only. MCPWM ISR (comparator or timer event) + * must call esp32_adc_digi_trigger_software() at the desired PWM phase. + * - ESP32-S3 and newer (C3, C6, …): optional **ETM** wires MCPWM timer TEZ/TEP + * to ADC_TASK_START0 with no CPU in the trigger path. + * + * DMA backend (where SOC moves ADC results): + * - ESP32: I2S peripheral (see esp32_adc_dma_esp32.c) + * - ESP32-S2: SPI3 (esp32_adc_dma_esp32s2.c) + * - ESP32-S3+: GDMA (esp32_adc_dma_gdma.c) + */ + +#define SIMPLEFOC_ESP32_ADC_DIGI_SUPPORTED 1 + +#if CONFIG_IDF_TARGET_ESP32 +#define SIMPLEFOC_ESP32_ADC_USE_I2S_DMA 1 +#elif CONFIG_IDF_TARGET_ESP32S2 +#define SIMPLEFOC_ESP32_ADC_USE_SPI3_DMA 1 +#elif SOC_GDMA_SUPPORTED +#define SIMPLEFOC_ESP32_ADC_USE_GDMA_DMA 1 +#endif + +#if SOC_ETM_SUPPORTED && defined(SIMPLEFOC_ESP32_ADC_USE_GDMA_DMA) +#define SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED 1 +#else +#define SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED 0 +#endif + +#define SIMPLEFOC_ESP32_ADC_PATTERN_HZ 80000 +#define SIMPLEFOC_ESP32_ADC_NUM_CHANNELS 2 +#define SIMPLEFOC_ESP32_ADC_CONVERT_LIMIT 2 + +#define ESP32_ADC_DIGI_FRAME_BYTES \ + (SIMPLEFOC_ESP32_ADC_NUM_CHANNELS * SOC_ADC_DIGI_RESULT_BYTES) + +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 +#define ESP32_ADC_DIGI_SAMPLE_RAW(p) ((int32_t)((p)->type1.data)) +#else +#define ESP32_ADC_DIGI_SAMPLE_RAW(p) ((int32_t)((p)->type2.data)) +#endif + +static inline int esp32_adc_digi_raw_at(const void *adc_buffer, int index) +{ + const adc_digi_output_data_t *samples = (const adc_digi_output_data_t *)adc_buffer; + return (int)ESP32_ADC_DIGI_SAMPLE_RAW(&samples[index]); +} + +typedef struct esp32_adc_digi_dma_ctx esp32_adc_digi_dma_ctx_t; + +typedef void (*esp32_adc_digi_dma_done_fn_t)(void *user); + +struct esp32_adc_digi_dma_ctx { + esp32_adc_digi_dma_done_fn_t on_done; + void *on_done_arg; + volatile intptr_t eof_desc_addr; +}; + +esp_err_t esp32_adc_digi_dma_init(esp32_adc_digi_dma_ctx_t *ctx, + esp32_adc_digi_dma_done_fn_t on_done, + void *user); +esp_err_t esp32_adc_digi_dma_deinit(esp32_adc_digi_dma_ctx_t *ctx); +esp_err_t esp32_adc_digi_dma_start(esp32_adc_digi_dma_ctx_t *ctx, dma_descriptor_t *desc); +esp_err_t esp32_adc_digi_dma_stop(esp32_adc_digi_dma_ctx_t *ctx); +esp_err_t esp32_adc_digi_dma_reset(esp32_adc_digi_dma_ctx_t *ctx); + +#if SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED + +typedef enum { + SIMPLEFOC_ESP32_ADC_MCPWM_EVT_TIMER_TEZ = 0, + SIMPLEFOC_ESP32_ADC_MCPWM_EVT_TIMER_TEP, +} esp32_adc_digi_mcpwm_event_t; + +typedef struct { + uint8_t mcpwm_timer; + esp32_adc_digi_mcpwm_event_t event; +} esp32_adc_digi_etm_config_t; + +esp_err_t esp32_adc_digi_etm_connect(const esp32_adc_digi_etm_config_t *cfg); +esp_err_t esp32_adc_digi_etm_enable(bool enable); +void esp32_adc_digi_etm_disconnect(void); + +#endif /* SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED */ + +#endif /* ARDUINO_ARCH_ESP32 */ diff --git a/src/current_sense/hardware_specific/esp32/esp32_adc_digi_lowside.cpp b/src/current_sense/hardware_specific/esp32/esp32_adc_digi_lowside.cpp new file mode 100644 index 00000000..07f9ce2a --- /dev/null +++ b/src/current_sense/hardware_specific/esp32/esp32_adc_digi_lowside.cpp @@ -0,0 +1,209 @@ +#include "esp32_mcu.h" + +#if defined(ESP_H) && defined(ARDUINO_ARCH_ESP32) && defined(SOC_MCPWM_SUPPORTED) \ + && !defined(SIMPLEFOC_ESP32_USELEDC) + +#include "esp32_adc_digi_lowside.h" + +#include "esp32_adc_digi_driver.h" +#include "../../../drivers/hardware_specific/esp32/esp32_driver_mcpwm.h" +#include "../../../drivers/hardware_specific/esp32/mcpwm_private.h" +#include "driver/mcpwm_prelude.h" + +#ifndef SIMPLEFOC_CS_PRETRIGGER_US +#define SIMPLEFOC_CS_PRETRIGGER_US 5 +#endif + +static bool esp32_pin_to_adc1_channel(int pin, adc_channel_t *out_ch) +{ + int8_t ch = digitalPinToAnalogChannel(pin); + if (ch < 0 || ch >= SOC_ADC_MAX_CHANNEL_NUM) { + return false; + } + *out_ch = (adc_channel_t)ch; + return true; +} + +static esp_err_t esp32_adc_digi_bind_params(ESP32CurrentSenseParams *params) +{ + if (params->no_adc_channels < 1 || params->no_adc_channels > SIMPLEFOC_ESP32_ADC_NUM_CHANNELS) { + return ESP_ERR_INVALID_ARG; + } + + adc_channel_t channels[SIMPLEFOC_ESP32_ADC_NUM_CHANNELS] = {}; + for (int i = 0; i < params->no_adc_channels; i++) { + if (!esp32_pin_to_adc1_channel(params->pins[i], &channels[i])) { + return ESP_ERR_INVALID_ARG; + } + } + + esp32_adc_digi_config_t cfg = { + .channels = { channels[0], channels[1] }, + .unit = ADC_UNIT_1, + .adc_buffer = params->adc_buffer, + .no_adc_channels = params->no_adc_channels, + }; + + return esp32_adc_digi_init(&cfg); +} + +ESP32AdcLowsidePath esp32_adc_lowside_configure(ESP32CurrentSenseParams *params) +{ + if (params == NULL || !esp32_adc_digi_supported()) { + return ESP32_ADC_LOWSIDE_ADC_READ; + } + + if (esp32_adc_digi_bind_params(params) != ESP_OK) { + SIMPLEFOC_ESP32_CS_DEBUG("WARN: ADC digi+DMA init failed, using ADC_READ (adcRead) path"); + return ESP32_ADC_LOWSIDE_ADC_READ; + } + + params->adc_lowside_path = ESP32_ADC_LOWSIDE_DIGI_SW; + SIMPLEFOC_ESP32_CS_DEBUG("ADC digi+DMA ready (software trigger via MCPWM ISR on ESP32/S2)"); + return ESP32_ADC_LOWSIDE_DIGI_SW; +} + +bool esp32_adc_lowside_uses_mcpwm_isr(const ESP32CurrentSenseParams *params) +{ + if (params == NULL) { + return false; + } + return params->adc_lowside_path == ESP32_ADC_LOWSIDE_DIGI_SW; +} + +void esp32_adc_lowside_start_conversion(void) +{ + esp32_adc_digi_trigger_software(); +} + +#if SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED +static void *esp32_adc_lowside_sync_etm(void *driver_params, ESP32CurrentSenseParams *cs) +{ + ESP32MCPWMDriverParams *p = (ESP32MCPWMDriverParams *)driver_params; + mcpwm_timer_t *t = (mcpwm_timer_t *)p->timers[0]; + int group_id = p->group_id; + + esp32_adc_digi_etm_config_t etm = { + .mcpwm_timer = (uint8_t)t->timer_id, + .event = SIMPLEFOC_ESP32_ADC_MCPWM_EVT_TIMER_TEZ, + }; + + if (esp32_adc_digi_set_etm_source(&etm) != ESP_OK) { + SIMPLEFOC_ESP32_CS_DEBUG("ERROR: ETM source setup failed"); + return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED; + } + if (esp32_adc_digi_set_trigger(SIMPLEFOC_ESP32_ADC_TRIG_ETM) != ESP_OK) { + SIMPLEFOC_ESP32_CS_DEBUG("ERROR: ETM ADC trigger enable failed"); + return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED; + } + + cs->adc_lowside_path = ESP32_ADC_LOWSIDE_DIGI_ETM; + SIMPLEFOC_ESP32_CS_DEBUG("MCPWM" + String(group_id) + " timer " + String(t->timer_id) + + " -> ETM -> ADC (TEZ), no MCPWM ADC ISR"); + return cs; +} +#endif + +static bool IRAM_ATTR esp32_adc_mcpwm_sw_trigger_cb(mcpwm_cmpr_handle_t cmpr, + const mcpwm_compare_event_data_t *edata, + void *user_data) +{ + (void)cmpr; + (void)user_data; + if (edata->direction != MCPWM_TIMER_DIRECTION_UP) { + return true; + } + esp32_adc_digi_trigger_software(); + return true; +} + +static bool IRAM_ATTR esp32_adc_mcpwm_sw_trigger_timer_cb(mcpwm_timer_handle_t tim, + const mcpwm_timer_event_data_t *edata, + void *user_data) +{ + (void)tim; + (void)edata; + (void)user_data; + esp32_adc_digi_trigger_software(); + return true; +} + +static void *esp32_adc_lowside_sync_mcpwm_sw(void *driver_params, ESP32CurrentSenseParams *cs) +{ + ESP32MCPWMDriverParams *p = (ESP32MCPWMDriverParams *)driver_params; + mcpwm_timer_t *t = (mcpwm_timer_t *)p->timers[0]; + int group_id = p->group_id; + + SIMPLEFOC_ESP32_CS_DEBUG("MCPWM comparator -> software ADC digi trigger (ESP32/S2 style)"); + + mcpwm_comparator_config_t cmp_config = {}; + cmp_config.flags.update_cmp_on_tez = true; + for (int i = 2; i >= 0; i--) { + if (p->oper[i] == nullptr) { + continue; + } + if (mcpwm_new_comparator(p->oper[i], &cmp_config, (mcpwm_cmpr_handle_t *)&cs->pretrig_comparator) == ESP_OK) { + break; + } + } + + if (cs->pretrig_comparator) { + uint32_t pwm_duty_cycle = p->mcpwm_period * (0.75f - ((float)p->pwm_frequency * SIMPLEFOC_CS_PRETRIGGER_US) / 1e6f / 2.0f); + if (mcpwm_comparator_set_compare_value((mcpwm_cmpr_handle_t)cs->pretrig_comparator, pwm_duty_cycle) != ESP_OK) { + SIMPLEFOC_ESP32_CS_DEBUG("ERROR: comparator compare value"); + return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED; + } + mcpwm_comparator_event_callbacks_t cmp_cbs = { + .on_reach = esp32_adc_mcpwm_sw_trigger_cb, + }; + if (mcpwm_comparator_register_event_callbacks((mcpwm_cmpr_handle_t)cs->pretrig_comparator, &cmp_cbs, cs) != ESP_OK) { + SIMPLEFOC_ESP32_CS_DEBUG("ERROR: comparator callback"); + return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED; + } + _notifyLowSideUsingComparator(group_id); + SIMPLEFOC_ESP32_CS_DEBUG("Comparator pre-trigger -> esp32_adc_digi_trigger_software()"); + return cs; + } + + SIMPLEFOC_ESP32_CS_DEBUG("WARN: no comparator; MCPWM on_full -> software ADC digi trigger"); + if (t->on_full != nullptr) { + SIMPLEFOC_ESP32_CS_DEBUG("ERROR: timer on_full already in use"); + return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED; + } + + auto cbs = mcpwm_timer_event_callbacks_t{ + .on_full = esp32_adc_mcpwm_sw_trigger_timer_cb, + }; + t->fsm = MCPWM_TIMER_FSM_INIT; + if (mcpwm_timer_register_event_callbacks(t, &cbs, cs) != ESP_OK) { + SIMPLEFOC_ESP32_CS_DEBUG("ERROR: timer callback"); + return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED; + } + t->fsm = MCPWM_TIMER_FSM_ENABLE; + if (esp_intr_enable(t->intr) != ESP_OK) { + SIMPLEFOC_ESP32_CS_DEBUG("ERROR: enable timer intr"); + return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED; + } + return cs; +} + +void *esp32_adc_lowside_sync_mcpwm(void *driver_params, ESP32CurrentSenseParams *cs) +{ + if (cs == NULL || driver_params == NULL) { + return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED; + } + +#if SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED + if (esp32_adc_digi_etm_supported() && cs->adc_lowside_path != ESP32_ADC_LOWSIDE_ADC_READ) { + return esp32_adc_lowside_sync_etm(driver_params, cs); + } +#endif + + if (cs->adc_lowside_path == ESP32_ADC_LOWSIDE_DIGI_SW) { + return esp32_adc_lowside_sync_mcpwm_sw(driver_params, cs); + } + + return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED; +} + +#endif diff --git a/src/current_sense/hardware_specific/esp32/esp32_adc_digi_lowside.h b/src/current_sense/hardware_specific/esp32/esp32_adc_digi_lowside.h new file mode 100644 index 00000000..1e45a1a5 --- /dev/null +++ b/src/current_sense/hardware_specific/esp32/esp32_adc_digi_lowside.h @@ -0,0 +1,25 @@ +/* + * MIT License + * + * Copyright (c) 2021 Felipe Neves + * + * Glue between SimpleFOC LowsideCurrentSense (ESP32 MCPWM) and esp32_adc_digi_*. + */ +#pragma once + +#if defined(ESP_H) && defined(ARDUINO_ARCH_ESP32) && defined(SOC_MCPWM_SUPPORTED) \ + && !defined(SIMPLEFOC_ESP32_USELEDC) + +#include "esp32_adc_digi_internal.h" + +#if SIMPLEFOC_ESP32_ADC_DIGI_SUPPORTED + +#include "esp32_mcu.h" + +ESP32AdcLowsidePath esp32_adc_lowside_configure(ESP32CurrentSenseParams *params); +bool esp32_adc_lowside_uses_mcpwm_isr(const ESP32CurrentSenseParams *params); +void *esp32_adc_lowside_sync_mcpwm(void *driver_params, ESP32CurrentSenseParams *cs); +void esp32_adc_lowside_start_conversion(void); + +#endif /* SIMPLEFOC_ESP32_ADC_DIGI_SUPPORTED */ +#endif diff --git a/src/current_sense/hardware_specific/esp32/esp32_adc_dma_esp32.c b/src/current_sense/hardware_specific/esp32/esp32_adc_dma_esp32.c new file mode 100644 index 00000000..8f69ae5b --- /dev/null +++ b/src/current_sense/hardware_specific/esp32/esp32_adc_dma_esp32.c @@ -0,0 +1,133 @@ +/* + * MIT License + * + * Copyright (c) 2021 Felipe Neves + * + * I2S DMA backend for ADC digi on ESP32 (i2s_ll only). + */ + +#if defined(ARDUINO_ARCH_ESP32) + +#include "esp32_adc_digi_internal.h" + +#if defined(SIMPLEFOC_ESP32_ADC_USE_I2S_DMA) && SIMPLEFOC_ESP32_ADC_USE_I2S_DMA + +#include "esp_err.h" +#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" + +#define ADC_DMA_I2S_HOST ADC_HAL_DMA_I2S_HOST +#define ADC_DMA_INTR_MASK BIT(9) + +static i2s_dev_t *s_i2s_dev; +static esp32_adc_digi_dma_ctx_t *s_ctx; +static intr_handle_t s_intr; +static bool s_inited; + +static void IRAM_ATTR esp32_adc_i2s_isr(void *arg) +{ + (void)arg; + if ((i2s_ll_get_intr_status(s_i2s_dev) & ADC_DMA_INTR_MASK) == 0) { + return; + } + i2s_ll_clear_intr_status(s_i2s_dev, ADC_DMA_INTR_MASK); + + if (s_ctx != NULL) { + uint32_t desc_addr = 0; + i2s_ll_rx_get_eof_des_addr(s_i2s_dev, &desc_addr); + s_ctx->eof_desc_addr = (intptr_t)desc_addr; + if (s_ctx->on_done != NULL) { + s_ctx->on_done(s_ctx->on_done_arg); + } + } +} + +esp_err_t esp32_adc_digi_dma_init(esp32_adc_digi_dma_ctx_t *ctx, + esp32_adc_digi_dma_done_fn_t on_done, + void *user) +{ + if (ctx == NULL || on_done == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (s_inited) { + ctx->on_done = on_done; + ctx->on_done_arg = user; + s_ctx = ctx; + return ESP_OK; + } + + esp_err_t err = i2s_platform_acquire_occupation(I2S_CTLR_HP, ADC_DMA_I2S_HOST, "simplefoc_adc_digi"); + if (err != ESP_OK) { + return err; + } + s_i2s_dev = I2S_LL_GET_HW(ADC_DMA_I2S_HOST); + + err = esp_intr_alloc(i2s_periph_signal[ADC_DMA_I2S_HOST].irq, ESP_INTR_FLAG_IRAM, + esp32_adc_i2s_isr, NULL, &s_intr); + if (err != ESP_OK) { + i2s_platform_release_occupation(I2S_CTLR_HP, ADC_DMA_I2S_HOST); + return err; + } + + ctx->on_done = on_done; + ctx->on_done_arg = user; + s_ctx = ctx; + s_inited = true; + return ESP_OK; +} + +esp_err_t esp32_adc_digi_dma_deinit(esp32_adc_digi_dma_ctx_t *ctx) +{ + (void)ctx; + if (!s_inited) { + return ESP_OK; + } + esp32_adc_digi_dma_stop(ctx); + if (s_intr != NULL) { + esp_intr_free(s_intr); + s_intr = NULL; + } + i2s_platform_release_occupation(I2S_CTLR_HP, ADC_DMA_I2S_HOST); + s_ctx = NULL; + s_inited = false; + return ESP_OK; +} + +esp_err_t esp32_adc_digi_dma_start(esp32_adc_digi_dma_ctx_t *ctx, dma_descriptor_t *desc) +{ + (void)ctx; + i2s_ll_clear_intr_status(s_i2s_dev, ADC_DMA_INTR_MASK); + i2s_ll_enable_intr(s_i2s_dev, ADC_DMA_INTR_MASK, true); + i2s_ll_enable_dma(s_i2s_dev, true); + i2s_ll_rx_start_link(s_i2s_dev, (uint32_t)desc); + return ESP_OK; +} + +esp_err_t esp32_adc_digi_dma_stop(esp32_adc_digi_dma_ctx_t *ctx) +{ + (void)ctx; + if (!s_inited) { + return ESP_OK; + } + i2s_ll_enable_intr(s_i2s_dev, ADC_DMA_INTR_MASK, false); + i2s_ll_clear_intr_status(s_i2s_dev, ADC_DMA_INTR_MASK); + i2s_ll_rx_stop_link(s_i2s_dev); + return ESP_OK; +} + +esp_err_t esp32_adc_digi_dma_reset(esp32_adc_digi_dma_ctx_t *ctx) +{ + (void)ctx; + if (!s_inited) { + return ESP_OK; + } + i2s_ll_rx_reset_dma(s_i2s_dev); + return ESP_OK; +} + +#endif /* SIMPLEFOC_ESP32_ADC_USE_I2S_DMA */ +#endif /* ARDUINO_ARCH_ESP32 */ diff --git a/src/current_sense/hardware_specific/esp32/esp32_adc_dma_esp32s2.c b/src/current_sense/hardware_specific/esp32/esp32_adc_dma_esp32s2.c new file mode 100644 index 00000000..18e8a020 --- /dev/null +++ b/src/current_sense/hardware_specific/esp32/esp32_adc_dma_esp32s2.c @@ -0,0 +1,138 @@ +/* + * MIT License + * + * Copyright (c) 2021 Felipe Neves + * + * SPI3 DMA backend for ADC digi on ESP32-S2 (spi_ll + spicommon). + */ + +#if defined(ARDUINO_ARCH_ESP32) + +#include "esp32_adc_digi_internal.h" + +#if defined(SIMPLEFOC_ESP32_ADC_USE_SPI3_DMA) && SIMPLEFOC_ESP32_ADC_USE_SPI3_DMA + +#include "esp_err.h" +#include "esp_intr_alloc.h" +#include "esp_attr.h" +#include "hal/spi_ll.h" +#include "esp_private/spi_common_internal.h" + +#define ADC_DMA_SPI_HOST SPI3_HOST +#define ADC_DMA_INTR_MASK SPI_LL_INTR_IN_SUC_EOF + +static spi_dev_t *s_spi_dev; +static spi_dma_ctx_t *s_spi_dma; +static esp32_adc_digi_dma_ctx_t *s_ctx; +static intr_handle_t s_intr; +static bool s_inited; + +static void IRAM_ATTR esp32_adc_spi_isr(void *arg) +{ + (void)arg; + if (!spi_ll_get_intr(s_spi_dev, ADC_DMA_INTR_MASK)) { + return; + } + spi_ll_clear_intr(s_spi_dev, ADC_DMA_INTR_MASK); + + if (s_ctx != NULL) { + s_ctx->eof_desc_addr = spi_dma_ll_get_in_suc_eof_desc_addr(s_spi_dev, + s_spi_dma->rx_dma_chan.chan_id); + if (s_ctx->on_done != NULL) { + s_ctx->on_done(s_ctx->on_done_arg); + } + } +} + +esp_err_t esp32_adc_digi_dma_init(esp32_adc_digi_dma_ctx_t *ctx, + esp32_adc_digi_dma_done_fn_t on_done, + void *user) +{ + if (ctx == NULL || on_done == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (s_inited) { + ctx->on_done = on_done; + ctx->on_done_arg = user; + s_ctx = ctx; + return ESP_OK; + } + + if (!spicommon_periph_claim(ADC_DMA_SPI_HOST, "simplefoc_adc_digi")) { + return ESP_FAIL; + } + + esp_err_t err = spicommon_dma_chan_alloc(ADC_DMA_SPI_HOST, SPI_DMA_CH_AUTO, &s_spi_dma); + if (err != ESP_OK) { + spicommon_periph_free(ADC_DMA_SPI_HOST); + return err; + } + + s_spi_dev = SPI_LL_GET_HW(ADC_DMA_SPI_HOST); + err = esp_intr_alloc(spicommon_irqdma_source_for_host(ADC_DMA_SPI_HOST), ESP_INTR_FLAG_IRAM, + esp32_adc_spi_isr, NULL, &s_intr); + if (err != ESP_OK) { + spicommon_dma_chan_free(s_spi_dma); + spicommon_periph_free(ADC_DMA_SPI_HOST); + return err; + } + + ctx->on_done = on_done; + ctx->on_done_arg = user; + s_ctx = ctx; + s_inited = true; + return ESP_OK; +} + +esp_err_t esp32_adc_digi_dma_deinit(esp32_adc_digi_dma_ctx_t *ctx) +{ + (void)ctx; + if (!s_inited) { + return ESP_OK; + } + esp32_adc_digi_dma_stop(ctx); + if (s_intr != NULL) { + esp_intr_free(s_intr); + s_intr = NULL; + } + spicommon_dma_chan_free(s_spi_dma); + spicommon_periph_free(ADC_DMA_SPI_HOST); + s_spi_dma = NULL; + s_ctx = NULL; + s_inited = false; + return ESP_OK; +} + +esp_err_t esp32_adc_digi_dma_start(esp32_adc_digi_dma_ctx_t *ctx, dma_descriptor_t *desc) +{ + (void)ctx; + spi_ll_clear_intr(s_spi_dev, ADC_DMA_INTR_MASK); + spi_ll_enable_intr(s_spi_dev, ADC_DMA_INTR_MASK); + spi_dma_ll_rx_start(s_spi_dev, s_spi_dma->rx_dma_chan.chan_id, (lldesc_t *)desc); + return ESP_OK; +} + +esp_err_t esp32_adc_digi_dma_stop(esp32_adc_digi_dma_ctx_t *ctx) +{ + (void)ctx; + if (!s_inited) { + return ESP_OK; + } + spi_ll_disable_intr(s_spi_dev, ADC_DMA_INTR_MASK); + spi_ll_clear_intr(s_spi_dev, ADC_DMA_INTR_MASK); + spi_dma_ll_rx_stop(s_spi_dev, s_spi_dma->rx_dma_chan.chan_id); + return ESP_OK; +} + +esp_err_t esp32_adc_digi_dma_reset(esp32_adc_digi_dma_ctx_t *ctx) +{ + (void)ctx; + if (!s_inited) { + return ESP_OK; + } + spi_dma_ll_rx_reset(s_spi_dev, s_spi_dma->rx_dma_chan.chan_id); + return ESP_OK; +} + +#endif /* SIMPLEFOC_ESP32_ADC_USE_SPI3_DMA */ +#endif /* ARDUINO_ARCH_ESP32 */ diff --git a/src/current_sense/hardware_specific/esp32/esp32_adc_dma_gdma.c b/src/current_sense/hardware_specific/esp32/esp32_adc_dma_gdma.c new file mode 100644 index 00000000..f4d94320 --- /dev/null +++ b/src/current_sense/hardware_specific/esp32/esp32_adc_dma_gdma.c @@ -0,0 +1,148 @@ +/* + * MIT License + * + * Copyright (c) 2021 Felipe Neves + * + * GDMA RX backend for ADC digi (ESP32-S3, C3, C6, …) using gdma_hal + gdma_ll only. + */ + +#if defined(ARDUINO_ARCH_ESP32) + +#include "esp32_adc_digi_internal.h" + +#if defined(SIMPLEFOC_ESP32_ADC_USE_GDMA_DMA) && SIMPLEFOC_ESP32_ADC_USE_GDMA_DMA + +#include "esp_err.h" +#include "esp_intr_alloc.h" +#include "esp_attr.h" +#include "hal/gdma_hal.h" +#include "hal/gdma_hal_ahb.h" +#include "hal/gdma_ll.h" +#include "hal/gdma_types.h" +#include "soc/gdma_periph.h" + +#define ESP32_ADC_GDMA_GROUP 0 +#define ESP32_ADC_GDMA_PAIR 2 +#define ESP32_ADC_GDMA_RX_EOF GDMA_LL_EVENT_RX_SUC_EOF + +static gdma_hal_context_t s_gdma_hal; +static esp32_adc_digi_dma_ctx_t *s_ctx; +static intr_handle_t s_intr; +static bool s_inited; + +static void IRAM_ATTR esp32_adc_gdma_isr(void *arg) +{ + (void)arg; + gdma_hal_context_t *hal = &s_gdma_hal; + const int ch = ESP32_ADC_GDMA_PAIR; + + uint32_t st = hal->read_intr_status(hal, ch, GDMA_CHANNEL_DIRECTION_RX, true); + if ((st & ESP32_ADC_GDMA_RX_EOF) == 0) { + return; + } + hal->clear_intr(hal, ch, GDMA_CHANNEL_DIRECTION_RX, ESP32_ADC_GDMA_RX_EOF); + + if (s_ctx != NULL) { + s_ctx->eof_desc_addr = (intptr_t)hal->get_eof_desc_addr(hal, ch, GDMA_CHANNEL_DIRECTION_RX, true); + if (s_ctx->on_done != NULL) { + s_ctx->on_done(s_ctx->on_done_arg); + } + } +} + +esp_err_t esp32_adc_digi_dma_init(esp32_adc_digi_dma_ctx_t *ctx, + esp32_adc_digi_dma_done_fn_t on_done, + void *user) +{ + if (ctx == NULL || on_done == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (s_inited) { + ctx->on_done = on_done; + ctx->on_done_arg = user; + s_ctx = ctx; + return ESP_OK; + } + + int __DECLARE_RCC_ATOMIC_ENV __attribute__((unused)); + + gdma_ll_enable_bus_clock(ESP32_ADC_GDMA_GROUP, true); + gdma_ll_reset_register(ESP32_ADC_GDMA_GROUP); + + gdma_hal_config_t hal_cfg = { + .group_id = ESP32_ADC_GDMA_GROUP, + }; + gdma_ahb_hal_init(&s_gdma_hal, &hal_cfg); + + const int ch = ESP32_ADC_GDMA_PAIR; + s_gdma_hal.reset(&s_gdma_hal, ch, GDMA_CHANNEL_DIRECTION_RX); + s_gdma_hal.connect_peri(&s_gdma_hal, ch, GDMA_CHANNEL_DIRECTION_RX, + GDMA_TRIG_PERIPH_ADC, 0); + s_gdma_hal.set_strategy(&s_gdma_hal, ch, GDMA_CHANNEL_DIRECTION_RX, + true, false, false); + s_gdma_hal.enable_burst(&s_gdma_hal, ch, GDMA_CHANNEL_DIRECTION_RX, false, false); + s_gdma_hal.enable_intr(&s_gdma_hal, ch, GDMA_CHANNEL_DIRECTION_RX, + ESP32_ADC_GDMA_RX_EOF, true); + + int irq = gdma_periph_signals.groups[ESP32_ADC_GDMA_GROUP].pairs[ch].rx_irq_id; + esp_err_t err = esp_intr_alloc(irq, ESP_INTR_FLAG_IRAM, esp32_adc_gdma_isr, NULL, &s_intr); + if (err != ESP_OK) { + return err; + } + + ctx->on_done = on_done; + ctx->on_done_arg = user; + s_ctx = ctx; + s_inited = true; + return ESP_OK; +} + +esp_err_t esp32_adc_digi_dma_deinit(esp32_adc_digi_dma_ctx_t *ctx) +{ + (void)ctx; + if (!s_inited) { + return ESP_OK; + } + esp32_adc_digi_dma_stop(ctx); + if (s_intr != NULL) { + esp_intr_free(s_intr); + s_intr = NULL; + } + s_ctx = NULL; + s_inited = false; + return ESP_OK; +} + +esp_err_t esp32_adc_digi_dma_start(esp32_adc_digi_dma_ctx_t *ctx, dma_descriptor_t *desc) +{ + (void)ctx; + if (desc == NULL) { + return ESP_ERR_INVALID_ARG; + } + s_gdma_hal.start_with_desc(&s_gdma_hal, ESP32_ADC_GDMA_PAIR, + GDMA_CHANNEL_DIRECTION_RX, (intptr_t)desc); + return ESP_OK; +} + +esp_err_t esp32_adc_digi_dma_stop(esp32_adc_digi_dma_ctx_t *ctx) +{ + (void)ctx; + if (!s_inited) { + return ESP_OK; + } + s_gdma_hal.stop(&s_gdma_hal, ESP32_ADC_GDMA_PAIR, GDMA_CHANNEL_DIRECTION_RX); + return ESP_OK; +} + +esp_err_t esp32_adc_digi_dma_reset(esp32_adc_digi_dma_ctx_t *ctx) +{ + (void)ctx; + if (!s_inited) { + return ESP_OK; + } + s_gdma_hal.reset(&s_gdma_hal, ESP32_ADC_GDMA_PAIR, GDMA_CHANNEL_DIRECTION_RX); + return ESP_OK; +} + +#endif /* SIMPLEFOC_ESP32_ADC_USE_GDMA_DMA */ +#endif /* ARDUINO_ARCH_ESP32 */ diff --git a/src/current_sense/hardware_specific/esp32/esp32_adc_etm.c b/src/current_sense/hardware_specific/esp32/esp32_adc_etm.c new file mode 100644 index 00000000..a2605d9b --- /dev/null +++ b/src/current_sense/hardware_specific/esp32/esp32_adc_etm.c @@ -0,0 +1,159 @@ +/* + * MIT License + * + * Copyright (c) 2021 Felipe Neves + * + * ETM wiring: MCPWM timer event -> ADC digi START task. + */ + +#if defined(ARDUINO_ARCH_ESP32) + +#include "esp32_adc_digi_internal.h" + +#if SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED + +#include +#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" +static const char *TAG = "esp32_adc_etm"; + +typedef struct { + esp_etm_event_t base; +} esp32_adc_etm_event_t; + +typedef struct { + esp_etm_task_t base; +} esp32_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; +} esp32_adc_etm_ctx_t; + +static esp32_adc_etm_ctx_t s_etm; + +static esp_err_t esp32_adc_etm_del_event(esp_etm_event_t *event) +{ + esp32_adc_etm_event_t *ev = __containerof(event, esp32_adc_etm_event_t, base); + free(ev); + return ESP_OK; +} + +static esp_err_t esp32_adc_etm_del_task(esp_etm_task_t *task) +{ + esp32_adc_etm_task_t *tk = __containerof(task, esp32_adc_etm_task_t, base); + free(tk); + return ESP_OK; +} + +static uint32_t esp32_adc_mcpwm_event_id(const esp32_adc_digi_etm_config_t *cfg) +{ + switch (cfg->event) { + case SIMPLEFOC_ESP32_ADC_MCPWM_EVT_TIMER_TEZ: + return (uint32_t)(MCPWM_EVT_TIMER0_TEZ + cfg->mcpwm_timer); + case SIMPLEFOC_ESP32_ADC_MCPWM_EVT_TIMER_TEP: + return (uint32_t)(MCPWM_EVT_TIMER0_TEP + cfg->mcpwm_timer); + default: + return 0; + } +} + +static esp_err_t esp32_adc_etm_create_event(uint32_t event_id, esp_etm_event_handle_t *out) +{ + esp32_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 = esp32_adc_etm_del_event; + *out = &ev->base; + return ESP_OK; +} + +static esp_err_t esp32_adc_etm_create_task(uint32_t task_id, esp_etm_task_handle_t *out) +{ + esp32_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 = esp32_adc_etm_del_task; + *out = &tk->base; + return ESP_OK; +} + +static void esp32_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 esp32_adc_digi_etm_connect(const esp32_adc_digi_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 = esp32_adc_mcpwm_event_id(cfg); + ESP_RETURN_ON_FALSE(event_id != 0, ESP_ERR_INVALID_ARG, TAG, "invalid mcpwm event"); + + esp32_adc_etm_teardown(); + + ESP_RETURN_ON_ERROR(esp32_adc_etm_create_event(event_id, &s_etm.event), TAG, "event create failed"); + ESP_RETURN_ON_ERROR(esp32_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 esp32_adc_digi_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 esp32_adc_digi_etm_disconnect(void) +{ + esp32_adc_etm_teardown(); +} + +#endif /* SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED */ +#endif /* ARDUINO_ARCH_ESP32 */ diff --git a/src/current_sense/hardware_specific/esp32/esp32_mcpwm_mcu.cpp b/src/current_sense/hardware_specific/esp32/esp32_mcpwm_mcu.cpp index 41ded6f2..8e4c3bbd 100644 --- a/src/current_sense/hardware_specific/esp32/esp32_mcpwm_mcu.cpp +++ b/src/current_sense/hardware_specific/esp32/esp32_mcpwm_mcu.cpp @@ -2,7 +2,6 @@ #if defined(ESP_H) && defined(ARDUINO_ARCH_ESP32) && defined(SOC_MCPWM_SUPPORTED) && !defined(SIMPLEFOC_ESP32_USELEDC) -// check the version of the ESP-IDF #include "esp_idf_version.h" #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) @@ -11,21 +10,23 @@ #include "../../../drivers/hardware_specific/esp32/esp32_driver_mcpwm.h" #include "../../../drivers/hardware_specific/esp32/mcpwm_private.h" - #include "driver/mcpwm_prelude.h" #include "soc/mcpwm_reg.h" #include "soc/mcpwm_struct.h" +#include "esp32_adc_digi_internal.h" +#if SIMPLEFOC_ESP32_ADC_DIGI_SUPPORTED +#include "esp32_adc_digi_lowside.h" +#include "esp_heap_caps.h" +#endif - -// adding a debug toggle pin to measure the time of the interrupt with oscilloscope // #define SIMPLEFOC_ESP32_INTERRUPT_DEBUG #ifdef SIMPLEFOC_ESP32_INTERRUPT_DEBUG #include "driver/gpio.h" #ifndef DEBUGPIN -#ifdef CONFIG_IDF_TARGET_ESP32S3 +#ifdef CONFIG_IDF_TARGET_ESP32S3 #define DEBUGPIN 16 #else #define DEBUGPIN 19 @@ -35,212 +36,211 @@ #define GPIO_NUM (gpio_num_t)((int)GPIO_NUM_0 + DEBUGPIN) #endif -#ifndef SIMPLEFOC_CS_PRETRIGGER_US -#define SIMPLEFOC_CS_PRETRIGGER_US 5 // 5 us because ADC read takes around 10us -#endif - - - /** - * Low side adc reading implementation -*/ - - -// function reading an ADC value and returning the read voltage -float IRAM_ATTR _readADCVoltageLowSide(const int pin, const void* cs_params){ - ESP32CurrentSenseParams* p = (ESP32CurrentSenseParams*)cs_params; - int no_channel = 0; - for(int i=0; i < 3; i++){ - if(!_isset(p->pins[i])) continue; - if(pin == p->pins[i]) // found in the buffer - return p->adc_buffer[no_channel] * p->adc_voltage_conv; - else no_channel++; - } - SIMPLEFOC_DEBUG("ERROR: ADC pin not found in the buffer!"); - // not found - return 0; + * Low-side current sense on ESP32 MCPWM. + * + * ADC path (see esp32_adc_digi_internal.h): + * - ADC_READ: default; MCPWM ISR + adcRead(), one phase per interrupt (~10 us each). + * - DIGI_SW: ESP32 / S2 — digi+DMA; ISR only calls esp32_adc_digi_trigger_software(). + * - DIGI_ETM: S3 / C6+ — digi+DMA; MCPWM TEZ starts ADC via ETM (no ADC ISR). + */ + +float IRAM_ATTR _readADCVoltageLowSide(const int pin, const void *cs_params) +{ + ESP32CurrentSenseParams *p = (ESP32CurrentSenseParams *)cs_params; + int no_channel = 0; + for (int i = 0; i < 3; i++) { + if (!_isset(p->pins[i])) { + continue; + } + if (pin == p->pins[i]) { + if (p->adc_lowside_path != ESP32_ADC_LOWSIDE_ADC_READ) { + return esp32_adc_digi_raw_at(p->adc_buffer, no_channel) * p->adc_voltage_conv; + } + return p->adc_buffer[no_channel] * p->adc_voltage_conv; + } + no_channel++; + } + SIMPLEFOC_DEBUG("ERROR: ADC pin not found in the buffer!"); + return 0; } +void *IRAM_ATTR _configureADCLowSide(const void *driver_params, const int pinA, const int pinB, const int pinC) +{ + ESP32MCPWMDriverParams *p = (ESP32MCPWMDriverParams *)driver_params; + mcpwm_timer_t *t = (mcpwm_timer_t *)p->timers[0]; -// function configuring low-side current sensing -void* IRAM_ATTR _configureADCLowSide(const void* driver_params, const int pinA,const int pinB,const int pinC){ - // check if driver timer is already running - // fail if it is - // the easiest way that I've found to check if timer is running - // is to start it and stop it - ESP32MCPWMDriverParams *p = (ESP32MCPWMDriverParams*)driver_params; - mcpwm_timer_t* t = (mcpwm_timer_t*) p->timers[0]; - - // check if low side callback is already set - // if it is, return error - if(t->on_full != nullptr){ - SIMPLEFOC_ESP32_CS_DEBUG("ERROR: Low side callback is already set. Cannot set it again for timer: "+String(t->timer_id)+", group: "+String(t->group->group_id)); - return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED; - } - - - ESP32CurrentSenseParams* params = new ESP32CurrentSenseParams{}; - int no_adc_channels = 0; - - // initialize the ADC pins - // fail if the pin is not an ADC pin - int adc_pins[3] = {pinA, pinB, pinC}; - for (int i = 0; i < 3; i++){ - if(_isset(adc_pins[i])){ - if(!adcInit(adc_pins[i])){ - SIMPLEFOC_ESP32_CS_DEBUG("ERROR: Failed to initialise ADC pin: "+String(adc_pins[i]) + String(", maybe not an ADC pin?")); + if (t->on_full != nullptr) { + SIMPLEFOC_ESP32_CS_DEBUG("ERROR: Low side callback is already set. Cannot set it again for timer: " + + String(t->timer_id) + ", group: " + String(t->group->group_id)); return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED; - } - params->pins[no_adc_channels++] = adc_pins[i]; } - } - - t->user_data = params; - params->adc_voltage_conv = (_ADC_VOLTAGE)/(_ADC_RESOLUTION); - params->no_adc_channels = no_adc_channels; - return params; -} -static bool IRAM_ATTR _mcpwmTriggerADCCallback(mcpwm_timer_handle_t tim, const mcpwm_timer_event_data_t* edata, void* user_data){ - ESP32CurrentSenseParams *p = (ESP32CurrentSenseParams*)user_data; -#ifdef SIMPLEFOC_ESP32_INTERRUPT_DEBUG // debugging toggle pin to measure the time of the interrupt with oscilloscope - gpio_set_level(GPIO_NUM,1); //cca 250ns for on+off +#if SIMPLEFOC_ESP32_ADC_DIGI_SUPPORTED + ESP32CurrentSenseParams *params = (ESP32CurrentSenseParams *)heap_caps_calloc( + 1, sizeof(ESP32CurrentSenseParams), MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); + if (params == NULL) { + SIMPLEFOC_ESP32_CS_DEBUG("ERROR: DMA-capable current sense alloc failed"); + return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED; + } +#else + ESP32CurrentSenseParams *params = new ESP32CurrentSenseParams{}; #endif + int no_adc_channels = 0; + + int adc_pins[3] = { pinA, pinB, pinC }; + for (int i = 0; i < 3; i++) { + if (_isset(adc_pins[i])) { + if (!adcInit(adc_pins[i])) { + SIMPLEFOC_ESP32_CS_DEBUG("ERROR: Failed to initialise ADC pin: " + String(adc_pins[i]) + + ", maybe not an ADC pin?"); + return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED; + } + params->pins[no_adc_channels++] = adc_pins[i]; + } + } - // sample the phase currents one at a time - // ESP's adc read takes around 10us which is very long - // so we are sampling one phase per call - p->adc_buffer[p->buffer_index] = adcRead(p->pins[p->buffer_index]); - - // increment buffer index - p->buffer_index++; - if(p->buffer_index >= p->no_adc_channels){ - p->buffer_index = 0; - } + params->adc_voltage_conv = (_ADC_VOLTAGE) / (_ADC_RESOLUTION); + params->no_adc_channels = no_adc_channels; -#ifdef SIMPLEFOC_ESP32_INTERRUPT_DEBUG // debugging toggle pin to measure the time of the interrupt with oscilloscope - gpio_set_level(GPIO_NUM,0); //cca 250ns for on+off +#if SIMPLEFOC_ESP32_ADC_DIGI_SUPPORTED + if (esp32_adc_lowside_configure(params) != ESP32_ADC_LOWSIDE_ADC_READ) { + t->user_data = params; + return params; + } #endif - return true; -} -// Comparator on_reach callback: sample ADC pre-trigger -// In center-aligned mode, comparator fires twice per cycle (up and down) -// Only sample on down-count to get one sample per PWM period -static bool IRAM_ATTR _mcpwmComparatorADCCallback(mcpwm_cmpr_handle_t cmpr, const mcpwm_compare_event_data_t* edata, void* user_data){ - // Only trigger on down-count direction - if(edata->direction != MCPWM_TIMER_DIRECTION_UP){ - return true; - } + t->user_data = params; + return params; +} - ESP32CurrentSenseParams *p = (ESP32CurrentSenseParams*)user_data; +static bool IRAM_ATTR _mcpwm_adc_read_timer_callback(mcpwm_timer_handle_t tim, const mcpwm_timer_event_data_t *edata, + void *user_data) +{ + (void)tim; + (void)edata; + ESP32CurrentSenseParams *p = (ESP32CurrentSenseParams *)user_data; #ifdef SIMPLEFOC_ESP32_INTERRUPT_DEBUG - gpio_set_level(GPIO_NUM,1); + gpio_set_level(GPIO_NUM, 1); #endif - - // sample the phase currents one at a time - // ESP's adc read takes around 10us which is very long - // so we are sampling one phase per call - p->adc_buffer[p->buffer_index] = adcRead(p->pins[p->buffer_index]); - - // increment buffer index - p->buffer_index++; - if(p->buffer_index >= p->no_adc_channels){ - p->buffer_index = 0; - } - + p->adc_buffer[p->buffer_index] = adcRead(p->pins[p->buffer_index]); + p->buffer_index++; + if (p->buffer_index >= p->no_adc_channels) { + p->buffer_index = 0; + } #ifdef SIMPLEFOC_ESP32_INTERRUPT_DEBUG - gpio_set_level(GPIO_NUM,0); + gpio_set_level(GPIO_NUM, 0); #endif - return true; + return true; } -void* IRAM_ATTR _driverSyncLowSide(void* driver_params, void* cs_params){ +static bool IRAM_ATTR _mcpwm_adc_read_comparator_callback(mcpwm_cmpr_handle_t cmpr, + const mcpwm_compare_event_data_t *edata, void *user_data) +{ + if (edata->direction != MCPWM_TIMER_DIRECTION_UP) { + return true; + } + ESP32CurrentSenseParams *p = (ESP32CurrentSenseParams *)user_data; #ifdef SIMPLEFOC_ESP32_INTERRUPT_DEBUG - pinMode(DEBUGPIN, OUTPUT); -#endif - ESP32MCPWMDriverParams *p = (ESP32MCPWMDriverParams*)driver_params; - mcpwm_timer_t* t = (mcpwm_timer_t*) p->timers[0]; - int group_id = p->group_id; - - - ESP32CurrentSenseParams *cs = (ESP32CurrentSenseParams*)cs_params; - if(!cs){ - SIMPLEFOC_ESP32_CS_DEBUG("ERROR: cs_params is null"); - return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED; - } - - SIMPLEFOC_ESP32_CS_DEBUG("Configuring sync with comparator..."); - - // Create a spare comparator on the first operator for ADC pre-trigger - // This comparator will fire ~5 µs before on_reach (peak) - mcpwm_comparator_config_t cmp_config = {0}; - cmp_config.flags.update_cmp_on_tez = true; - for(int i=2; i>=0; i--){ // start from the end as first operators are more likely to be used fully - if(p->oper[i] == nullptr) continue; - if(mcpwm_new_comparator(p->oper[i], &cmp_config, (mcpwm_cmpr_handle_t*)&cs->pretrig_comparator) == ESP_OK){ - break; + gpio_set_level(GPIO_NUM, 1); +#endif + p->adc_buffer[p->buffer_index] = adcRead(p->pins[p->buffer_index]); + p->buffer_index++; + if (p->buffer_index >= p->no_adc_channels) { + p->buffer_index = 0; } - } - - // if comparator creation failed, fall back to on_full callback - if (cs->pretrig_comparator){ - // Calculate pwm duty cycle ticks for pre-trigger channel - // TODO: verify the timing it seems to be correct between 15 and 20kHz (but needs better testing) - uint32_t pwm_duty_cycle = p->mcpwm_period * (0.75f - ((float)p->pwm_frequency*SIMPLEFOC_CS_PRETRIGGER_US)/1e6f/2.0f); - // set up the comparator duty cycle - CHECK_CS_ERR(mcpwm_comparator_set_compare_value((mcpwm_cmpr_handle_t)cs->pretrig_comparator, pwm_duty_cycle), - "Failed to set pretrigger compare value"); - - // Register comparator on_reach callback for ADC sampling - mcpwm_comparator_event_callbacks_t cmp_cbs = { - .on_reach = _mcpwmComparatorADCCallback - }; - // register the callback - CHECK_CS_ERR(mcpwm_comparator_register_event_callbacks((mcpwm_cmpr_handle_t)cs->pretrig_comparator, &cmp_cbs, cs_params), - "Failed to register comparator callback"); +#ifdef SIMPLEFOC_ESP32_INTERRUPT_DEBUG + gpio_set_level(GPIO_NUM, 0); +#endif + return true; +} - SIMPLEFOC_ESP32_CS_DEBUG("MCPWM"+String(group_id)+" Timer "+String(t->timer_id)+" pretrigger comparator configured."); - // notify the driver code that low side is uses one comparator - _notifyLowSideUsingComparator(group_id); +void IRAM_ATTR _startADC3PinConversionLowSide() +{ +#if SIMPLEFOC_ESP32_ADC_DIGI_SUPPORTED + esp32_adc_lowside_start_conversion(); +#endif +} + +static void *esp32_mcpwm_sync_adc_read_lowside(void *driver_params, ESP32CurrentSenseParams *cs) +{ + ESP32MCPWMDriverParams *p = (ESP32MCPWMDriverParams *)driver_params; + mcpwm_timer_t *t = (mcpwm_timer_t *)p->timers[0]; + int group_id = p->group_id; + + SIMPLEFOC_ESP32_CS_DEBUG("ADC_READ: MCPWM ISR + adcRead() (one phase per interrupt)"); + +#ifndef SIMPLEFOC_CS_PRETRIGGER_US +#define SIMPLEFOC_CS_PRETRIGGER_US 5 +#endif - }else{ + mcpwm_comparator_config_t cmp_config = {}; + cmp_config.flags.update_cmp_on_tez = true; + for (int i = 2; i >= 0; i--) { + if (p->oper[i] == nullptr) { + continue; + } + if (mcpwm_new_comparator(p->oper[i], &cmp_config, (mcpwm_cmpr_handle_t *)&cs->pretrig_comparator) == ESP_OK) { + break; + } + } - SIMPLEFOC_ESP32_CS_DEBUG("WARN: Failed to create comparator!"); - SIMPLEFOC_ESP32_CS_DEBUG("Configuring sync with on_full callback (less accurate)..."); + if (cs->pretrig_comparator) { + uint32_t pwm_duty_cycle = + p->mcpwm_period * (0.75f - ((float)p->pwm_frequency * SIMPLEFOC_CS_PRETRIGGER_US) / 1e6f / 2.0f); + CHECK_CS_ERR(mcpwm_comparator_set_compare_value((mcpwm_cmpr_handle_t)cs->pretrig_comparator, pwm_duty_cycle), + "Failed to set pretrigger compare value"); + + mcpwm_comparator_event_callbacks_t cmp_cbs = { + .on_reach = _mcpwm_adc_read_comparator_callback, + }; + CHECK_CS_ERR(mcpwm_comparator_register_event_callbacks((mcpwm_cmpr_handle_t)cs->pretrig_comparator, &cmp_cbs, + cs), + "Failed to register comparator callback"); + _notifyLowSideUsingComparator(group_id); + SIMPLEFOC_ESP32_CS_DEBUG("MCPWM" + String(group_id) + " timer " + String(t->timer_id) + " comparator + adcRead"); + return cs; + } - // check if low side callback is already set - // if it is, return error - if(t->on_full != nullptr){ - SIMPLEFOC_ESP32_CS_DEBUG("ERROR: Low side callback is already set. Cannot set it again for timer: "+String(t->timer_id)+", group: "+String(t->group->group_id)); - return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED; + SIMPLEFOC_ESP32_CS_DEBUG("WARN: comparator unavailable; MCPWM on_full + adcRead"); + if (t->on_full != nullptr) { + SIMPLEFOC_ESP32_CS_DEBUG("ERROR: on_full already set"); + return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED; } - // set the callback for the low side current sensing - // mcpwm_timer_event_callbacks_t can be used to set the callback - // for three timer events - // - on_full - low-side - // - on_empty - high-side - // - on_sync - sync event (not used with simplefoc) + auto cbs = mcpwm_timer_event_callbacks_t{ - .on_full = _mcpwmTriggerADCCallback, + .on_full = _mcpwm_adc_read_timer_callback, }; - SIMPLEFOC_ESP32_CS_DEBUG("Timer "+String(t->timer_id)+" enable interrupt callback."); - // set the timer state to init (so that we can call the `mcpwm_timer_register_event_callbacks` ) - // this is a hack, as this function is not supposed to be called when the timer is running - // the timer does not really go to the init state! t->fsm = MCPWM_TIMER_FSM_INIT; - // set the callback - CHECK_CS_ERR(mcpwm_timer_register_event_callbacks(t, &cbs, cs_params), "Failed to set low side callback"); - // set the timer state to enabled again + CHECK_CS_ERR(mcpwm_timer_register_event_callbacks(t, &cbs, cs), "Failed to set low side callback"); t->fsm = MCPWM_TIMER_FSM_ENABLE; - CHECK_CS_ERR(esp_intr_enable(t->intr), "Failed to enable low-side interrupts!"); + CHECK_CS_ERR(esp_intr_enable(t->intr), "Failed to enable low-side interrupts"); + return cs; +} - SIMPLEFOC_ESP32_CS_DEBUG("MCPWM"+String(group_id)+" Timer "+String(t->timer_id)+" on_full callback configured."); - } +void *IRAM_ATTR _driverSyncLowSide(void *driver_params, void *cs_params) +{ +#ifdef SIMPLEFOC_ESP32_INTERRUPT_DEBUG + pinMode(DEBUGPIN, OUTPUT); +#endif + ESP32CurrentSenseParams *cs = (ESP32CurrentSenseParams *)cs_params; + if (!cs) { + SIMPLEFOC_ESP32_CS_DEBUG("ERROR: cs_params is null"); + return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED; + } - return cs_params; -} +#if SIMPLEFOC_ESP32_ADC_DIGI_SUPPORTED + if (cs->adc_lowside_path != ESP32_ADC_LOWSIDE_ADC_READ) { + void *r = esp32_adc_lowside_sync_mcpwm(driver_params, cs); + if (r == SIMPLEFOC_CURRENT_SENSE_INIT_FAILED) { + return r; + } + return cs; + } +#endif + return esp32_mcpwm_sync_adc_read_lowside(driver_params, cs); +} #endif diff --git a/src/current_sense/hardware_specific/esp32/esp32_mcu.h b/src/current_sense/hardware_specific/esp32/esp32_mcu.h index f6524d5c..7ff7fb1e 100644 --- a/src/current_sense/hardware_specific/esp32/esp32_mcu.h +++ b/src/current_sense/hardware_specific/esp32/esp32_mcu.h @@ -2,6 +2,7 @@ #define ESP32_MCU_CURRENT_SENSING_H #include "../../hardware_api.h" +#include #if defined(ESP_H) && defined(ARDUINO_ARCH_ESP32) @@ -10,14 +11,28 @@ #include "esp32_adc_driver.h" +/* + * Low-side ADC backend (set during LowsideCurrentSense::init): + * ADC_READ — default; MCPWM ISR + adcRead(), one phase per interrupt. + * DIGI_SW — digi controller + DMA; ISR only starts conversion (ESP32, S2, …). + * DIGI_ETM — digi + DMA; MCPWM TEZ starts ADC via ETM (S3, C6, …). + */ +enum ESP32AdcLowsidePath : uint8_t { + ESP32_ADC_LOWSIDE_ADC_READ = 0, + ESP32_ADC_LOWSIDE_DIGI_SW, + ESP32_ADC_LOWSIDE_DIGI_ETM, +}; + // esp32 current sense parameters typedef struct ESP32CurrentSenseParams { int pins[3]; float adc_voltage_conv; + /* ADC_READ: plain counts; DIGI_*: DMA lands adc_digi_output_data_t[0..N] here (zero-copy) */ int adc_buffer[3] = {}; int buffer_index = 0; int no_adc_channels = 0; - void* pretrig_comparator = nullptr; // MCPWM comparator handle for ADC pre-trigger + void* pretrig_comparator = nullptr; + ESP32AdcLowsidePath adc_lowside_path = ESP32_ADC_LOWSIDE_ADC_READ; } ESP32CurrentSenseParams; // macros for debugging wuing the simplefoc debug system