diff --git a/components/esp_driver_dac/CMakeLists.txt b/components/esp_driver_dac/CMakeLists.txt index 49019f6488a..277fd2116bc 100644 --- a/components/esp_driver_dac/CMakeLists.txt +++ b/components/esp_driver_dac/CMakeLists.txt @@ -1,7 +1,7 @@ idf_build_get_property(target IDF_TARGET) set(srcs) -set(priv_req esp_pm esp_driver_gpio esp_hal_clock) +set(priv_req esp_pm esp_driver_gpio esp_hal_clock esp_driver_dma) if(${target} STREQUAL "linux") return() # This component is not supported by the POSIX/Linux simulator diff --git a/components/esp_driver_dac/dac_common.c b/components/esp_driver_dac/dac_common.c index 18b7c716ae5..77108616244 100644 --- a/components/esp_driver_dac/dac_common.c +++ b/components/esp_driver_dac/dac_common.c @@ -1,13 +1,13 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include +#include "stdatomic.h" #include "freertos/FreeRTOS.h" -#include "soc/soc_caps.h" #include "hal/dac_periph.h" #include "hal/dac_types.h" #include "hal/dac_ll.h" @@ -15,79 +15,80 @@ #include "esp_check.h" #include "dac_priv_common.h" -typedef struct { - bool in_use; - bool is_enabled; - const char *mode; -} dac_channel_info_t; +typedef enum { + DAC_CHAN_FSM_IDLE, + DAC_CHAN_FSM_REGISTERED, + DAC_CHAN_FSM_ENABLED, + DAC_CHAN_FSM_WAIT, // transition state +} dac_channel_fsm_t; -static dac_channel_info_t s_dac_chan[SOC_DAC_CHAN_NUM] = { - [0 ... SOC_DAC_CHAN_NUM - 1] = { - .in_use = false, - .is_enabled = false, - .mode = NULL, - } +static _Atomic dac_channel_fsm_t s_dac_chan_fsm[SOC_DAC_CHAN_NUM] = { + [0 ... SOC_DAC_CHAN_NUM - 1] = DAC_CHAN_FSM_IDLE, }; /* Global dac spin lock for the whole DAC driver */ portMUX_TYPE dac_spinlock = portMUX_INITIALIZER_UNLOCKED; static const char *TAG = "dac_common"; -esp_err_t dac_priv_register_channel(dac_channel_t chan_id, const char *mode_name) +esp_err_t dac_priv_register_channel(dac_channel_t chan_id) { - ESP_RETURN_ON_FALSE(chan_id < SOC_DAC_CHAN_NUM, ESP_ERR_INVALID_ARG, TAG, "channel id is invalid"); - DAC_NULL_POINTER_CHECK(mode_name); - esp_err_t ret = ESP_OK; - if (!s_dac_chan[chan_id].in_use) { - s_dac_chan[chan_id].in_use = true; - s_dac_chan[chan_id].mode = mode_name; + ESP_RETURN_ON_FALSE(IS_VALID_DAC_CHANNEL(chan_id), ESP_ERR_INVALID_ARG, TAG, "channel id is invalid"); + + dac_channel_fsm_t expected_fsm = DAC_CHAN_FSM_IDLE; + if (atomic_compare_exchange_strong(&s_dac_chan_fsm[chan_id], &expected_fsm, DAC_CHAN_FSM_REGISTERED)) { + return ESP_OK; } else { - ret = ESP_ERR_INVALID_STATE; + ESP_LOGE(TAG, "dac channel %d has been registered", chan_id); + return ESP_ERR_INVALID_STATE; } - if (ret != ESP_OK) { - ESP_LOGE(TAG, "dac channel %d has been registered by %s", chan_id, s_dac_chan[chan_id].mode); - } - return ret; } esp_err_t dac_priv_deregister_channel(dac_channel_t chan_id) { - ESP_RETURN_ON_FALSE(chan_id < SOC_DAC_CHAN_NUM, ESP_ERR_INVALID_ARG, TAG, "channel id is invalid"); - ESP_RETURN_ON_FALSE(!s_dac_chan[chan_id].is_enabled, ESP_ERR_INVALID_STATE, TAG, "the channel is still enabled"); - esp_err_t ret = ESP_OK; - if (s_dac_chan[chan_id].in_use) { - s_dac_chan[chan_id].in_use = false; - s_dac_chan[chan_id].mode = NULL; + ESP_RETURN_ON_FALSE(IS_VALID_DAC_CHANNEL(chan_id), ESP_ERR_INVALID_ARG, TAG, "channel id is invalid"); + + dac_channel_fsm_t expected_fsm = DAC_CHAN_FSM_REGISTERED; + if (atomic_compare_exchange_strong(&s_dac_chan_fsm[chan_id], &expected_fsm, DAC_CHAN_FSM_IDLE)) { + return ESP_OK; } else { - ret = ESP_ERR_INVALID_STATE; + ESP_LOGE(TAG, "dac channel %d is still enabled or not registered", chan_id); + return ESP_ERR_INVALID_STATE; } - return ret; } esp_err_t dac_priv_enable_channel(dac_channel_t chan_id) { - ESP_RETURN_ON_FALSE(chan_id < SOC_DAC_CHAN_NUM, ESP_ERR_INVALID_ARG, TAG, "channel id is invalid"); - ESP_RETURN_ON_FALSE(s_dac_chan[chan_id].in_use, ESP_ERR_INVALID_STATE, TAG, "the channel is not registered"); + ESP_RETURN_ON_FALSE(IS_VALID_DAC_CHANNEL(chan_id), ESP_ERR_INVALID_ARG, TAG, "channel id is invalid"); - gpio_num_t gpio_num = (gpio_num_t)dac_periph_signal.dac_channel_io_num[chan_id]; - gpio_config_as_analog(gpio_num); - DAC_RTC_ENTER_CRITICAL(); - dac_ll_power_on(chan_id); - dac_ll_rtc_sync_by_adc(false); - DAC_RTC_EXIT_CRITICAL(); - s_dac_chan[chan_id].is_enabled = true; - return ESP_OK; + dac_channel_fsm_t expected_fsm = DAC_CHAN_FSM_REGISTERED; + if (atomic_compare_exchange_strong(&s_dac_chan_fsm[chan_id], &expected_fsm, DAC_CHAN_FSM_WAIT)) { + gpio_num_t gpio_num = (gpio_num_t)dac_periph_signal.dac_channel_io_num[chan_id]; + gpio_config_as_analog(gpio_num); + DAC_RTC_ENTER_CRITICAL(); + dac_ll_power_on(chan_id); + dac_ll_rtc_sync_by_adc(false); + DAC_RTC_EXIT_CRITICAL(); + atomic_store(&s_dac_chan_fsm[chan_id], DAC_CHAN_FSM_ENABLED); + return ESP_OK; + } else { + ESP_LOGE(TAG, "dac channel %d is already enabled or not registered", chan_id); + return ESP_ERR_INVALID_STATE; + } } esp_err_t dac_priv_disable_channel(dac_channel_t chan_id) { - ESP_RETURN_ON_FALSE(chan_id < SOC_DAC_CHAN_NUM, ESP_ERR_INVALID_ARG, TAG, "channel id is invalid"); - ESP_RETURN_ON_FALSE(s_dac_chan[chan_id].in_use, ESP_ERR_INVALID_STATE, TAG, "the channel is not registered"); + ESP_RETURN_ON_FALSE(IS_VALID_DAC_CHANNEL(chan_id), ESP_ERR_INVALID_ARG, TAG, "channel id is invalid"); - DAC_RTC_ENTER_CRITICAL(); - dac_ll_power_down(chan_id); - DAC_RTC_EXIT_CRITICAL(); - s_dac_chan[chan_id].is_enabled = false; - - return ESP_OK; + dac_channel_fsm_t expected_fsm = DAC_CHAN_FSM_ENABLED; + if (atomic_compare_exchange_strong(&s_dac_chan_fsm[chan_id], &expected_fsm, DAC_CHAN_FSM_WAIT)) { + DAC_RTC_ENTER_CRITICAL(); + dac_ll_power_down(chan_id); + DAC_RTC_EXIT_CRITICAL(); + atomic_store(&s_dac_chan_fsm[chan_id], DAC_CHAN_FSM_REGISTERED); + return ESP_OK; + } else { + ESP_LOGE(TAG, "dac channel %d is not enabled", chan_id); + return ESP_ERR_INVALID_STATE; + } } diff --git a/components/esp_driver_dac/dac_continuous.c b/components/esp_driver_dac/dac_continuous.c index b3f8f9c25e0..6c33cd4cfe0 100644 --- a/components/esp_driver_dac/dac_continuous.c +++ b/components/esp_driver_dac/dac_continuous.c @@ -4,33 +4,35 @@ * SPDX-License-Identifier: Apache-2.0 */ +#if CONFIG_DAC_ENABLE_DEBUG_LOG +// The local log level must be defined before including esp_log.h +// Set the maximum log level for this source file +#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG +#endif + +#include #include #include -#include #include "freertos/FreeRTOS.h" #include "freertos/queue.h" #include "freertos/semphr.h" #include "freertos/idf_additions.h" #include "sdkconfig.h" -#include "rom/lldesc.h" #include "soc/soc_caps.h" #include "driver/dac_continuous.h" +#include "esp_private/gdma_link.h" +#include "esp_check.h" #include "dac_priv_common.h" #include "dac_priv_dma.h" -#if CONFIG_DAC_ENABLE_DEBUG_LOG -// The local log level must be defined before including esp_log.h -// Set the maximum log level for this source file -#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG -#endif -#include "esp_check.h" #if CONFIG_PM_ENABLE #include "esp_pm.h" #endif #define DAC_DMA_MAX_BUF_SIZE 4092 // Max DMA buffer size is 4095 but better to align with 4 bytes, so set 4092 here + #if CONFIG_DAC_ISR_IRAM_SAFE || CONFIG_DAC_CTRL_FUNC_IN_IRAM #define DAC_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) #else @@ -45,74 +47,57 @@ #define DAC_DMA_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA) -#define DAC_STAILQ_REMOVE(head, elm, type, field) do { \ - if ((head)->stqh_first == (elm)) { \ - STAILQ_REMOVE_HEAD((head), field); \ - } else { \ - struct type *curelm = (head)->stqh_first; \ - while (curelm->field.stqe_next != (elm) && \ - curelm->field.stqe_next != NULL) \ - curelm = curelm->field.stqe_next; \ - if (curelm->field.stqe_next && (curelm->field.stqe_next = \ - curelm->field.stqe_next->field.stqe_next) == NULL) \ - (head)->stqh_last = &(curelm)->field.stqe_next; \ - } \ -} while (/*CONSTCOND*/0) - struct dac_continuous_s { - uint32_t chan_cnt; dac_continuous_config_t cfg; - atomic_bool is_enabled; - atomic_bool is_cyclic; - atomic_bool is_running; - atomic_bool is_async; intr_handle_t intr_handle; /* Interrupt handle */ #if CONFIG_PM_ENABLE esp_pm_lock_handle_t pm_lock; #endif - SemaphoreHandle_t mutex; - QueueHandle_t desc_pool; /* The pool of available descriptors - * The descriptors in the pool are not linked in to pending chain */ - lldesc_t **desc; - uint8_t **bufs; - STAILQ_HEAD(desc_chain_s, lldesc_s) head; /* Head of the descriptor chain - * The descriptors in the chain are pending to be sent or sending now */ dac_event_callbacks_t cbs; /* Interrupt callbacks */ void *user_data; + + uint32_t cur_index; /* Index of the DMA descriptor that is currently being used by DMA. */ + uint32_t used_desc_num; /* Number of used DMA descriptors. Determines cur_index wrap-around. */ + + QueueHandle_t free_desc_queue; /* Queue of free DMA descriptors indices. Only used in sync writing mode. */ + SemaphoreHandle_t mutex; /* Serializes the public writing APIs (sync / cyclic) */ +#if SOC_IS(ESP32) + portMUX_TYPE dma_lock; /* Serializes the link/restart decision between the sync writing task and the ISR */ + bool dma_running; /* Whether the DMA is running (guarded by 'dma_lock'). Only used in sync writing mode. */ +#endif + + gdma_link_list_handle_t link; /* DMA descriptor link list */ + uint8_t *bufs[]; /* Array of DMA buffers pointers */ }; +typedef enum { + DAC_CONT_FSM_IDLE, + DAC_CONT_FSM_REGISTERED, + DAC_CONT_FSM_ENABLED, // Ready and DMA is NOT running + DAC_CONT_FSM_ASYNC, + DAC_CONT_FSM_CYCLIC, + DAC_CONT_FSM_SYNC, // Sync writing mode. DMA may or may not be running. + DAC_CONT_FSM_SYNC_WAIT, // Transition state for sync writing mode + DAC_CONT_FSM_WAIT, // Transition state +} dac_continuous_fsm_t; + +static _Atomic dac_continuous_fsm_t s_dac_cont_fsm = DAC_CONT_FSM_IDLE; + static const char *TAG = "dac_continuous"; -static bool s_dma_in_use = false; -static portMUX_TYPE desc_spinlock = portMUX_INITIALIZER_UNLOCKED; - -#define DESC_ENTER_CRITICAL() portENTER_CRITICAL(&desc_spinlock) -#define DESC_EXIT_CRITICAL() portEXIT_CRITICAL(&desc_spinlock) - -#define DESC_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&desc_spinlock) -#define DESC_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&desc_spinlock) +static esp_err_t s_dac_continuous_stop_sync(dac_continuous_handle_t handle); static void s_dac_free_dma_desc(dac_continuous_handle_t handle) { - STAILQ_INIT(&handle->head); - if (handle->desc != NULL) { - if (handle->desc[0]) { - free(handle->desc[0]); - } - free(handle->desc); - handle->desc = NULL; + if (handle->link != NULL) { + gdma_del_link_list(handle->link); + handle->link = NULL; } - if (handle->bufs != NULL) { - for (int i = 0; i < handle->cfg.desc_num; i++) { - if (handle->bufs[i]) { - free(handle->bufs[i]); - handle->bufs[i] = NULL; - } - } - free(handle->bufs); - handle->bufs = NULL; + for (uint32_t i = 0; i < handle->cfg.desc_num; i++) { + free(handle->bufs[i]); + handle->bufs[i] = NULL; } } @@ -120,72 +105,103 @@ static esp_err_t s_dac_alloc_dma_desc(dac_continuous_handle_t handle) { esp_err_t ret = ESP_OK; - STAILQ_INIT(&handle->head); - handle->desc = (lldesc_t **) heap_caps_calloc(handle->cfg.desc_num, sizeof(lldesc_t *), DAC_DMA_ALLOC_CAPS); - ESP_RETURN_ON_FALSE(handle->desc, ESP_ERR_NO_MEM, TAG, "failed to allocate dma descriptor array"); - handle->bufs = (uint8_t **) heap_caps_calloc(handle->cfg.desc_num, sizeof(uint8_t *), DAC_DMA_ALLOC_CAPS); - ESP_RETURN_ON_FALSE(handle->bufs, ESP_ERR_NO_MEM, TAG, "failed to allocate dma buffer array"); - lldesc_t *descs = (lldesc_t *)heap_caps_calloc(handle->cfg.desc_num, sizeof(lldesc_t), DAC_DMA_ALLOC_CAPS); - ESP_RETURN_ON_FALSE(descs, ESP_ERR_NO_MEM, TAG, "failed to allocate dma descriptors"); - for (int cnt = 0; cnt < handle->cfg.desc_num; cnt++) { - /* Allocate DMA descriptor */ - handle->desc[cnt] = &descs[cnt]; - ESP_GOTO_ON_FALSE(handle->desc[cnt], ESP_ERR_NO_MEM, err, TAG, "failed to allocate dma descriptor"); - ESP_LOGD(TAG, "desc[%d] %p", cnt, handle->desc[cnt]); - /* Allocate DMA buffer */ - handle->bufs[cnt] = (uint8_t *) heap_caps_calloc(1, handle->cfg.buf_size, DAC_DMA_ALLOC_CAPS); - ESP_GOTO_ON_FALSE(handle->bufs[cnt], ESP_ERR_NO_MEM, err, TAG, "failed to allocate dma buffer"); - /* Assign initial value */ - lldesc_config(handle->desc[cnt], LLDESC_SW_OWNED, 1, 0, handle->cfg.buf_size); - handle->desc[cnt]->size = handle->cfg.buf_size; - handle->desc[cnt]->buf = handle->bufs[cnt]; - handle->desc[cnt]->offset = 0; + const uint32_t desc_num = handle->cfg.desc_num; + const size_t buf_size = handle->cfg.buf_size; + + /* Allocate DMA buffers */ + for (uint32_t i = 0; i < desc_num; i++) { + handle->bufs[i] = heap_caps_calloc(1, buf_size, DAC_DMA_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(handle->bufs[i], ESP_ERR_NO_MEM, err, TAG, "failed to allocate dma buffer"); + } + + /** + * Create the DMA descriptor link list. + * The descriptor format of the link list item is binary-compatible with 'lldesc_t', + * so the link list head address can be fed to the old DMA backend directly. + */ + gdma_link_list_config_t link_cfg = { + .num_items = desc_num, + .item_alignment = 4, + .flags = { + .items_in_ext_mem = false, + .check_owner = false, + }, + }; + ESP_GOTO_ON_ERROR(gdma_new_link_list(&link_cfg, &handle->link), err, TAG, "failed to create dma link list"); + + /** + * Mount each DMA buffer to its own link list item once. + * The buffer<->item binding stays fixed afterwards. + */ + for (uint32_t i = 0; i < desc_num; i++) { + gdma_buffer_mount_config_t mount_cfg = { + .buffer = handle->bufs[i], + .length = buf_size, + .buffer_alignment = 4, + .flags = { + .mark_final = GDMA_FINAL_LINK_TO_DEFAULT, + }, + }; + ESP_GOTO_ON_ERROR(gdma_link_mount_buffers(handle->link, i, &mount_cfg, 1, NULL), + err, TAG, "failed to mount dma buffer"); } return ESP_OK; err: - /* Free DMA buffer if failed to allocate memory */ s_dac_free_dma_desc(handle); return ret; } static void IRAM_ATTR s_dac_default_intr_handler(void *arg) { - dac_continuous_handle_t handle = (dac_continuous_handle_t)arg; - uint32_t dummy; + dac_continuous_handle_t handle = arg; BaseType_t need_awoke = pdFALSE; BaseType_t tmp = pdFALSE; - uint32_t intr_mask = dac_dma_periph_intr_is_triggered(); - if (intr_mask & DAC_DMA_EOF_INTR) { - lldesc_t *fdesc = (lldesc_t *)dac_dma_periph_intr_get_eof_desc(); - if (!atomic_load(&handle->is_cyclic)) { - /* Remove the descriptor in the chain that finished sent */ - DESC_ENTER_CRITICAL_ISR(); - if (STAILQ_FIRST(&handle->head) != NULL) { - DAC_STAILQ_REMOVE(&handle->head, fdesc, lldesc_s, qe); - } - DESC_EXIT_CRITICAL_ISR(); - if (xQueueIsQueueFullFromISR(handle->desc_pool) == pdTRUE) { - xQueueReceiveFromISR(handle->desc_pool, &dummy, &tmp); - need_awoke |= tmp; - } - xQueueSendFromISR(handle->desc_pool, &fdesc, &tmp); + uint32_t intr_mask = dac_dma_periph_intr_get_mask(); + dac_continuous_fsm_t fsm = atomic_load(&s_dac_cont_fsm); + + if (intr_mask & DAC_DMA_DONE_INTR) { + if (fsm == DAC_CONT_FSM_SYNC || fsm == DAC_CONT_FSM_SYNC_WAIT) { + /* Sync writing mode: Recycle the descriptor */ + xQueueSendFromISR(handle->free_desc_queue, &handle->cur_index, &tmp); need_awoke |= tmp; } + if (handle->cbs.on_convert_done) { dac_event_data_t evt_data = { - .buf = (void *)fdesc->buf, + .buf = handle->bufs[handle->cur_index], .buf_size = handle->cfg.buf_size, - .write_bytes = fdesc->length, + .write_bytes = gdma_link_get_length(handle->link, handle->cur_index), }; need_awoke |= handle->cbs.on_convert_done(handle, &evt_data, handle->user_data); } + + handle->cur_index = (handle->cur_index + 1) % handle->used_desc_num; } if (intr_mask & DAC_DMA_TEOF_INTR) { - /* Total end of frame interrupt received, DMA stopped */ - atomic_store(&handle->is_running, false); - if (handle->cbs.on_stop) { + /** + * Total EOF interrupt: DMA has reached the end of a descriptor chain (NULL next pointer). + * This only occurs naturally in sync writing mode when all queued data has been transmitted. + */ + bool dma_restart = false; +#if SOC_IS(ESP32) + if (fsm == DAC_CONT_FSM_SYNC || fsm == DAC_CONT_FSM_SYNC_WAIT) { + /* Check for any remaining descriptors (ignored due to prefetching), and restart the DMA */ + portENTER_CRITICAL_ISR(&handle->dma_lock); + if (!handle->dma_running) { + /* Stop already in progress, do not restart */ + } else if (gdma_link_check_end(handle->link, (int)handle->cur_index - 1) == false) { + dac_dma_periph_trans_start(gdma_link_get_item_addr(handle->link, handle->cur_index)); + dma_restart = true; + } else { + handle->dma_running = false; + } + portEXIT_CRITICAL_ISR(&handle->dma_lock); + } +#endif + + if (!dma_restart && handle->cbs.on_stop) { need_awoke |= handle->cbs.on_stop(handle, NULL, handle->user_data); } } @@ -202,60 +218,71 @@ esp_err_t dac_continuous_new_channels(const dac_continuous_config_t *cont_cfg, d /* Parameters validation */ DAC_NULL_POINTER_CHECK(cont_cfg); DAC_NULL_POINTER_CHECK(ret_handle); - ESP_RETURN_ON_FALSE(cont_cfg->chan_mask <= DAC_CHANNEL_MASK_ALL, ESP_ERR_INVALID_ARG, TAG, "invalid dac channel id"); - ESP_RETURN_ON_FALSE(cont_cfg->desc_num > 1, ESP_ERR_INVALID_STATE, TAG, "at least two DMA descriptor needed"); - ESP_RETURN_ON_FALSE(!s_dma_in_use, ESP_ERR_INVALID_STATE, TAG, "DMA already in use"); + ESP_RETURN_ON_FALSE(IS_VALID_DAC_CHANNEL_MASK(cont_cfg->chan_mask) && cont_cfg->chan_mask, ESP_ERR_INVALID_ARG, TAG, "invalid dac channel mask"); + ESP_RETURN_ON_FALSE(cont_cfg->desc_num > 1, ESP_ERR_INVALID_ARG, TAG, "at least two DMA descriptor needed"); + ESP_RETURN_ON_FALSE(cont_cfg->buf_size > 0 && cont_cfg->buf_size % 2 == 0, ESP_ERR_INVALID_ARG, TAG, "buf_size must be a positive even number"); + ESP_RETURN_ON_FALSE(cont_cfg->buf_size <= DAC_DMA_MAX_BUF_SIZE, ESP_ERR_INVALID_ARG, TAG, "buf_size exceeds the maximum limit"); esp_err_t ret = ESP_OK; + /* FSM: IDLE -> WAIT */ + dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_IDLE; + if (!atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_WAIT)) { + ESP_LOGE(TAG, "DAC continuous is already in use"); + return ESP_ERR_INVALID_STATE; + } + /* Register the channels */ - for (uint32_t i = 0, mask = cont_cfg->chan_mask; mask; mask >>= 1, i++) { - if (mask & 0x01) { - ESP_GOTO_ON_ERROR(dac_priv_register_channel(i, "dac continuous"), - err4, TAG, "register dac channel %"PRIu32" failed", i); - } + dac_channel_mask_t registered_chan_mask = 0; + DAC_CHANNEL_MASK_FOREACH(chan, cont_cfg->chan_mask) { + ESP_GOTO_ON_ERROR(dac_priv_register_channel(chan), + err4, TAG, "register dac channel %"PRIu32" failed", chan); + registered_chan_mask |= BIT(chan); } /* Allocate continuous mode struct */ - dac_continuous_handle_t handle = heap_caps_calloc(1, sizeof(struct dac_continuous_s), DAC_MEM_ALLOC_CAPS); - ESP_RETURN_ON_FALSE(handle, ESP_ERR_NO_MEM, TAG, "no memory for the dac continuous mode structure"); + dac_continuous_handle_t handle = heap_caps_calloc(1, sizeof(struct dac_continuous_s) + cont_cfg->desc_num * sizeof(uint8_t *), DAC_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(handle, ESP_ERR_NO_MEM, err4, TAG, "no memory for the dac continuous mode structure"); - /* Allocate queue and mutex*/ - handle->desc_pool = xQueueCreateWithCaps(cont_cfg->desc_num, sizeof(lldesc_t *), DAC_MEM_ALLOC_CAPS); + handle->cfg = *cont_cfg; + +#if SOC_IS(ESP32) + handle->dma_lock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; +#endif + + handle->free_desc_queue = xQueueCreateWithCaps(cont_cfg->desc_num, sizeof(int), DAC_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(handle->free_desc_queue, ESP_ERR_NO_MEM, err3, TAG, "Failed to create free descriptor queue"); handle->mutex = xSemaphoreCreateMutexWithCaps(DAC_MEM_ALLOC_CAPS); - ESP_GOTO_ON_FALSE(handle->desc_pool, ESP_ERR_NO_MEM, err3, TAG, "no memory for message queue"); - ESP_GOTO_ON_FALSE(handle->mutex, ESP_ERR_NO_MEM, err3, TAG, "no memory for channels mutex"); + ESP_GOTO_ON_FALSE(handle->mutex, ESP_ERR_NO_MEM, err3, TAG, "Failed to create mutex"); /* Create PM lock */ #if CONFIG_PM_ENABLE esp_pm_lock_type_t pm_lock_type = cont_cfg->clk_src == DAC_DIGI_CLK_SRC_APLL ? ESP_PM_NO_LIGHT_SLEEP : ESP_PM_APB_FREQ_MAX; ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_lock_type, 0, "dac_driver", &handle->pm_lock), err3, TAG, "Failed to create DAC pm lock"); #endif - handle->chan_cnt = __builtin_popcount(cont_cfg->chan_mask); - memcpy(&(handle->cfg), cont_cfg, sizeof(dac_continuous_config_t)); - atomic_init(&handle->is_enabled, false); - atomic_init(&handle->is_cyclic, false); - atomic_init(&handle->is_running, false); - atomic_init(&handle->is_async, false); - - /* Allocate DMA buffer */ - ESP_GOTO_ON_ERROR(s_dac_alloc_dma_desc(handle), err2, TAG, "Failed to allocate memory for DMA buffers"); + /* Create DMA descriptors and buffers */ + ESP_GOTO_ON_ERROR(s_dac_alloc_dma_desc(handle), err3, TAG, "Failed to create DMA descriptors and buffers"); /* Initialize DAC DMA peripheral */ ESP_GOTO_ON_ERROR(dac_dma_periph_init(cont_cfg->freq_hz, cont_cfg->chan_mode == DAC_CHANNEL_MODE_ALTER, cont_cfg->clk_src == DAC_DIGI_CLK_SRC_APLL), err2, TAG, "Failed to initialize DAC DMA peripheral"); + /* Register DMA interrupt */ ESP_GOTO_ON_ERROR(esp_intr_alloc(dac_dma_periph_get_intr_signal(), DAC_INTR_ALLOC_FLAGS, s_dac_default_intr_handler, handle, &(handle->intr_handle)), err1, TAG, "Failed to register DAC DMA interrupt"); + /* Connect DAC module to the DMA peripheral */ DAC_RTC_ENTER_CRITICAL(); dac_ll_digi_enable_dma(true); DAC_RTC_EXIT_CRITICAL(); - s_dma_in_use = true; + + /* FSM: WAIT -> REGISTERED */ + atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_REGISTERED); + *ret_handle = handle; return ret; @@ -264,35 +291,48 @@ err1: err2: s_dac_free_dma_desc(handle); err3: - if (handle->desc_pool) { - vQueueDeleteWithCaps(handle->desc_pool); + if (handle->free_desc_queue) { + vQueueDeleteWithCaps(handle->free_desc_queue); } if (handle->mutex) { vSemaphoreDeleteWithCaps(handle->mutex); } +#if CONFIG_PM_ENABLE + if (handle->pm_lock) { + esp_pm_lock_delete(handle->pm_lock); + } +#endif free(handle); err4: - /* Deregister the channels */ - for (uint32_t i = 0, mask = cont_cfg->chan_mask; mask; mask >>= 1, i++) { - if (mask & 0x01) { - dac_priv_deregister_channel(i); - } + /* Deregister registered channels */ + DAC_CHANNEL_MASK_FOREACH(chan, registered_chan_mask) { + dac_priv_deregister_channel(chan); } + /* FSM: WAIT -> IDLE */ + atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_IDLE); return ret; } esp_err_t dac_continuous_del_channels(dac_continuous_handle_t handle) { DAC_NULL_POINTER_CHECK(handle); - ESP_RETURN_ON_FALSE(!atomic_load(&handle->is_enabled), ESP_ERR_INVALID_STATE, TAG, "dac continuous output not disabled yet"); + + /* FSM: REGISTERED -> WAIT */ + dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_REGISTERED; + if (!atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_WAIT)) { + ESP_LOGE(TAG, "DAC continuous is not registered / disabled"); + return ESP_ERR_INVALID_STATE; + } /* Deregister DMA interrupt */ if (handle->intr_handle) { ESP_RETURN_ON_ERROR(esp_intr_free(handle->intr_handle), TAG, "Failed to deregister DMA interrupt"); handle->intr_handle = NULL; } + /* Deinitialize DMA peripheral */ ESP_RETURN_ON_ERROR(dac_dma_periph_deinit(), TAG, "Failed to deinitialize DAC DMA peripheral"); + /* Disconnect DAC module from the DMA peripheral */ DAC_RTC_ENTER_CRITICAL(); dac_ll_digi_enable_dma(false); @@ -300,14 +340,16 @@ esp_err_t dac_continuous_del_channels(dac_continuous_handle_t handle) /* Free allocated resources */ s_dac_free_dma_desc(handle); - if (handle->desc_pool) { - vQueueDeleteWithCaps(handle->desc_pool); - handle->desc_pool = NULL; + + if (handle->free_desc_queue) { + vQueueDeleteWithCaps(handle->free_desc_queue); + handle->free_desc_queue = NULL; } if (handle->mutex) { vSemaphoreDeleteWithCaps(handle->mutex); handle->mutex = NULL; } + #if CONFIG_PM_ENABLE if (handle->pm_lock) { esp_pm_lock_delete(handle->pm_lock); @@ -316,22 +358,24 @@ esp_err_t dac_continuous_del_channels(dac_continuous_handle_t handle) #endif /* Deregister the channels */ - for (uint32_t i = 0, mask = handle->cfg.chan_mask; mask; mask >>= 1, i++) { - if (mask & 0x01) { - dac_priv_deregister_channel(i); - } + DAC_CHANNEL_MASK_FOREACH(chan, handle->cfg.chan_mask) { + dac_priv_deregister_channel(chan); } - free(handle); - s_dma_in_use = false; + free(handle); + + /* FSM: WAIT -> IDLE */ + atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_IDLE); return ESP_OK; } esp_err_t dac_continuous_register_event_callback(dac_continuous_handle_t handle, const dac_event_callbacks_t *callbacks, void *user_data) { DAC_NULL_POINTER_CHECK(handle); - if (!callbacks) { + + if (callbacks == NULL) { memset(&handle->cbs, 0, sizeof(dac_event_callbacks_t)); + handle->user_data = NULL; return ESP_OK; } #if CONFIG_DAC_ISR_IRAM_SAFE @@ -345,7 +389,7 @@ esp_err_t dac_continuous_register_event_callback(dac_continuous_handle_t handle, ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM"); } #endif - memcpy(&handle->cbs, callbacks, sizeof(dac_event_callbacks_t)); + handle->cbs = *callbacks; handle->user_data = user_data; return ESP_OK; @@ -354,79 +398,111 @@ esp_err_t dac_continuous_register_event_callback(dac_continuous_handle_t handle, esp_err_t dac_continuous_enable(dac_continuous_handle_t handle) { DAC_NULL_POINTER_CHECK(handle); - ESP_RETURN_ON_FALSE(!atomic_load(&handle->is_enabled), ESP_ERR_INVALID_STATE, TAG, "dac continuous has already enabled"); - esp_err_t ret = ESP_OK; - /* Reset the descriptor pool */ - xQueueReset(handle->desc_pool); - for (int i = 0; i < handle->cfg.desc_num; i++) { - ESP_GOTO_ON_FALSE(xQueueSend(handle->desc_pool, &handle->desc[i], 0) == pdTRUE, - ESP_ERR_INVALID_STATE, err, TAG, "the descriptor pool is not cleared"); + + /* FSM: REGISTERED -> WAIT */ + dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_REGISTERED; + if (!atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_WAIT)) { + ESP_LOGE(TAG, "DAC continuous is not registered / disabled"); + return ESP_ERR_INVALID_STATE; } + #ifdef CONFIG_PM_ENABLE esp_pm_lock_acquire(handle->pm_lock); #endif - for (uint32_t i = 0, mask = handle->cfg.chan_mask; mask; mask >>= 1, i++) { - if (mask & 0x01) { - dac_priv_enable_channel(i); - } + + DAC_CHANNEL_MASK_FOREACH(chan, handle->cfg.chan_mask) { + dac_priv_enable_channel(chan); } dac_dma_periph_enable(); esp_intr_enable(handle->intr_handle); + DAC_RTC_ENTER_CRITICAL(); dac_ll_digi_enable_dma(true); DAC_RTC_EXIT_CRITICAL(); - atomic_store(&handle->is_enabled, true); -err: - return ret; + + /* FSM: WAIT -> ENABLED */ + atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_ENABLED); + return ESP_OK; } esp_err_t dac_continuous_disable(dac_continuous_handle_t handle) { DAC_NULL_POINTER_CHECK(handle); - ESP_RETURN_ON_FALSE(atomic_load(&handle->is_enabled), ESP_ERR_INVALID_STATE, TAG, "dac continuous has already disabled"); - atomic_store(&handle->is_enabled, false); + + /* For backward compatibility, check if there is any ongoing cyclic conversion and stop it */ + if (atomic_load(&s_dac_cont_fsm) == DAC_CONT_FSM_CYCLIC) { + ESP_LOGW(TAG, "It is recommended to explicitly stop the cyclic conversion by calling dac_continuous_stop_cyclically() before performing other operations."); + ESP_RETURN_ON_ERROR(dac_continuous_stop_cyclically(handle), TAG, "Failed to stop cyclic conversion"); + } + + /* Check if there is any ongoing SYNC writing and wait for it to stop */ + if (atomic_load(&s_dac_cont_fsm) == DAC_CONT_FSM_SYNC) { + ESP_RETURN_ON_ERROR(s_dac_continuous_stop_sync(handle), TAG, "Failed to stop sync writing"); + } + + /* FSM: ENABLED -> WAIT */ + dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_ENABLED; + ESP_RETURN_ON_FALSE(atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_WAIT), + ESP_ERR_INVALID_STATE, TAG, "DAC continuous is running/not enabled"); + dac_dma_periph_disable(); esp_intr_disable(handle->intr_handle); + DAC_RTC_ENTER_CRITICAL(); dac_ll_digi_enable_dma(false); DAC_RTC_EXIT_CRITICAL(); - atomic_store(&handle->is_running, false); - for (uint32_t i = 0, mask = handle->cfg.chan_mask; mask; mask >>= 1, i++) { - if (mask & 0x01) { - dac_priv_disable_channel(i); - } + + DAC_CHANNEL_MASK_FOREACH(chan, handle->cfg.chan_mask) { + dac_priv_disable_channel(chan); } #ifdef CONFIG_PM_ENABLE esp_pm_lock_release(handle->pm_lock); #endif + + /* FSM: WAIT -> REGISTERED */ + atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_REGISTERED); return ESP_OK; } +//////////////////////////////////// Async writing //////////////////////////////////// + esp_err_t dac_continuous_start_async_writing(dac_continuous_handle_t handle) { DAC_NULL_POINTER_CHECK(handle); - ESP_RETURN_ON_FALSE(atomic_load(&handle->is_enabled), ESP_ERR_INVALID_STATE, TAG, "dac continuous has not been enabled"); ESP_RETURN_ON_FALSE(handle->cbs.on_convert_done, ESP_ERR_INVALID_STATE, TAG, "please register 'on_convert_done' callback before starting asynchronous writing"); - atomic_store(&handle->is_async, true); - - if (atomic_load(&handle->is_cyclic)) { - /* Break the DMA descriptor chain to stop the DMA first */ - for (int i = 0; i < handle->cfg.desc_num; i++) { - STAILQ_NEXT(handle->desc[i], qe) = NULL; - } + /* For backward compatibility, check if there is any ongoing cyclic conversion and stop it */ + if (atomic_load(&s_dac_cont_fsm) == DAC_CONT_FSM_CYCLIC) { + ESP_LOGW(TAG, "It is recommended to explicitly stop the cyclic conversion by calling dac_continuous_stop_cyclically() before performing other operations."); + ESP_RETURN_ON_ERROR(dac_continuous_stop_cyclically(handle), TAG, "Failed to stop cyclic conversion"); } - /* Wait for the previous DMA stop */ - while (atomic_load(&handle->is_running)) {} + + /* Check if there is any ongoing SYNC writing and wait for it to stop */ + if (atomic_load(&s_dac_cont_fsm) == DAC_CONT_FSM_SYNC) { + ESP_RETURN_ON_ERROR(s_dac_continuous_stop_sync(handle), TAG, "Failed to stop sync writing"); + } + + /* FSM: ENABLED -> WAIT */ + dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_ENABLED; + ESP_RETURN_ON_FALSE(atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_WAIT), + ESP_ERR_INVALID_STATE, TAG, "DAC continuous is running/not enabled"); /* Link all descriptors as a ring */ for (int i = 0; i < handle->cfg.desc_num; i++) { memset(handle->bufs[i], 0, handle->cfg.buf_size); - STAILQ_NEXT(handle->desc[i], qe) = (i < handle->cfg.desc_num - 1) ? handle->desc[i + 1] : handle->desc[0]; + gdma_link_set_length(handle->link, i, handle->cfg.buf_size); + gdma_link_set_owner(handle->link, i, GDMA_LLI_OWNER_DMA); + gdma_link_concat(handle->link, i, handle->link, (i < handle->cfg.desc_num - 1) ? i + 1 : 0); } - dac_dma_periph_dma_trans_start((uint32_t)handle->desc[0]); - atomic_store(&handle->is_running, true); + + handle->cur_index = 0; + handle->used_desc_num = handle->cfg.desc_num; + /* Start with an all-zero buffer. User will be notified by the 'on_convert_done' callback, then load the data into the buffer. */ + dac_dma_periph_trans_start(gdma_link_get_head_addr(handle->link)); + + /* FSM: WAIT -> ASYNC */ + atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_ASYNC); return ESP_OK; } @@ -434,15 +510,18 @@ esp_err_t dac_continuous_start_async_writing(dac_continuous_handle_t handle) esp_err_t dac_continuous_stop_async_writing(dac_continuous_handle_t handle) { DAC_NULL_POINTER_CHECK(handle); - ESP_RETURN_ON_FALSE(atomic_load(&handle->is_async), ESP_ERR_INVALID_STATE, TAG, "dac asynchronous writing has not been started"); - /* Break the DMA descriptor chain to stop the DMA first */ - for (int i = 0; i < handle->cfg.desc_num; i++) { - STAILQ_NEXT(handle->desc[i], qe) = NULL; + /* FSM: ASYNC -> WAIT */ + dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_ASYNC; + if (!atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_WAIT)) { + ESP_LOGE(TAG, "DAC continuous is not in asynchronous writing mode"); + return ESP_ERR_INVALID_STATE; } - /* Wait for the previous DMA stop */ - while (atomic_load(&handle->is_running)) {} - atomic_store(&handle->is_async, false); + + dac_dma_periph_trans_stop(); + + /* FSM: WAIT -> ENABLED */ + atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_ENABLED); return ESP_OK; } @@ -454,183 +533,319 @@ esp_err_t dac_continuous_stop_async_writing(dac_continuous_handle_t handle) #define DAC_16BIT_ALIGN_COEFF 1 #endif -static size_t s_dac_load_data_into_buf(dac_continuous_handle_t handle, uint8_t *dest, size_t dest_len, const uint8_t *src, size_t src_len) +/** + * @brief Load data into the DMA descriptor + * + * @param auto_balance Whether to balance the data between the last two descriptors. If disabled, we will load as much data as possible. + * @return Loaded data length. The remaining data length is (data_len - return_value) + * + * @note if CONFIG_DAC_DMA_AUTO_16BIT_ALIGN is enabled, data_len can be odd, otherwise it must be even + */ +static size_t s_dac_load_data_into_desc(dac_continuous_handle_t handle, int index, const uint8_t *data, size_t data_len, bool auto_balance) { - size_t load_bytes = 0; + /* Calculate the length of the data to be loaded */ + size_t buf_size = handle->cfg.buf_size; // must be even + size_t need_len = data_len * DAC_16BIT_ALIGN_COEFF; // must be even + size_t load_len; // must be even + if (need_len <= buf_size) { + load_len = need_len; + } else if (auto_balance && need_len < buf_size * 2) { + /** + * The remaining data can fit into two descriptors, so we load half in this round, + * and the next round will naturally fall into the branch above. + */ + load_len = need_len / 2; + load_len += load_len & 1U; // make it even + } else { + load_len = buf_size; + } + + uint8_t *buf = handle->bufs[index]; #if CONFIG_DAC_DMA_AUTO_16BIT_ALIGN /* Load the data to the high 8 bit in the 16-bit width slot */ - load_bytes = (src_len * 2 > dest_len) ? dest_len : src_len * 2; - for (int i = 0; i < load_bytes; i += 2) { - dest[i + 1] = src[i / 2] + handle->cfg.offset; + for (size_t i = 0; i < load_len; i += 2) { + buf[i + 1] = data[i / 2] + handle->cfg.offset; } #else /* Load the data into the DMA buffer */ - load_bytes = (src_len > dest_len) ? dest_len : src_len; - for (int i = 0; i < load_bytes; i++) { - dest[i] = src[i] + handle->cfg.offset; + for (size_t i = 0; i < load_len; i++) { + buf[i] = data[i] + handle->cfg.offset; } #endif - return load_bytes; + + gdma_link_set_length(handle->link, index, load_len); + gdma_link_set_owner(handle->link, index, GDMA_LLI_OWNER_DMA); + + return load_len / DAC_16BIT_ALIGN_COEFF; } -esp_err_t dac_continuous_write_asynchronously(dac_continuous_handle_t handle, uint8_t *dma_buf, - size_t dma_buf_len, const uint8_t *data, - size_t data_len, size_t *bytes_loaded) +esp_err_t dac_continuous_write_asynchronously(dac_continuous_handle_t handle, uint8_t *dma_buf, size_t dma_buf_len, + const uint8_t *data, size_t data_len, size_t *bytes_loaded) { DAC_NULL_POINTER_CHECK_ISR(handle); DAC_NULL_POINTER_CHECK_ISR(dma_buf); DAC_NULL_POINTER_CHECK_ISR(data); - ESP_RETURN_ON_FALSE_ISR(atomic_load(&handle->is_async), ESP_ERR_INVALID_STATE, TAG, "The asynchronous writing has not started"); - int i; - for (i = 0; i < handle->cfg.desc_num; i++) { - if (dma_buf == handle->bufs[i]) { + ESP_RETURN_ON_FALSE_ISR(data_len > 0, ESP_ERR_INVALID_ARG, TAG, "data_len must be > 0"); +#if !CONFIG_DAC_DMA_AUTO_16BIT_ALIGN + ESP_RETURN_ON_FALSE_ISR(data_len % 2 == 0, ESP_ERR_INVALID_ARG, TAG, "data_len must be even when AUTO_16BIT_ALIGN is disabled"); +#endif + + /* FSM: ASYNC -> WAIT */ + dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_ASYNC; + if (!atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_WAIT)) { + ESP_EARLY_LOGE(TAG, "DAC continuous is not in asynchronous writing mode"); + return ESP_ERR_INVALID_STATE; + } + + esp_err_t ret = ESP_OK; + + /** + * Normally, dma_buf_len should always be equal to the buffer size of descriptors + */ + if (dma_buf_len != handle->cfg.buf_size) { + ESP_EARLY_LOGW(TAG, "dma_buf_len != DMA buffer size. This parameter is ignored."); + } + + /* Find the corresponding DMA descriptor index */ + int index = 0; + for (; index < handle->cfg.desc_num; index++) { + if (dma_buf == handle->bufs[index]) { break; } } - /* Fail to find the DMA buffer address */ - ESP_RETURN_ON_FALSE_ISR(i < handle->cfg.desc_num, ESP_ERR_NOT_FOUND, TAG, "Not found the corresponding DMA buffer"); - size_t load_bytes = s_dac_load_data_into_buf(handle, dma_buf, dma_buf_len, data, data_len); - lldesc_config(handle->desc[i], LLDESC_HW_OWNED, 1, 0, load_bytes); + ESP_GOTO_ON_FALSE_ISR(index < handle->cfg.desc_num, ESP_ERR_NOT_FOUND, clean_up, TAG, "Corresponding DMA descriptor not found"); + + /* Load data into DMA buffer. We disable the auto balance here because the total length is actually uncertain. */ + size_t loaded_len = s_dac_load_data_into_desc(handle, index, data, data_len, false); if (bytes_loaded) { - *bytes_loaded = load_bytes / DAC_16BIT_ALIGN_COEFF; + *bytes_loaded = loaded_len; } - return ESP_OK; + +clean_up: + /* FSM: WAIT -> ASYNC */ + atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_ASYNC); + return ret; } +//////////////////////////////////// Cyclic writing //////////////////////////////////// + esp_err_t dac_continuous_write_cyclically(dac_continuous_handle_t handle, uint8_t *buf, size_t buf_size, size_t *bytes_loaded) { DAC_NULL_POINTER_CHECK(handle); - ESP_RETURN_ON_FALSE(atomic_load(&handle->is_enabled), ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels has not been enabled"); - ESP_RETURN_ON_FALSE(!atomic_load(&handle->is_async), ESP_ERR_INVALID_STATE, TAG, "Asynchronous writing is running, can't write cyclically"); - ESP_RETURN_ON_FALSE(buf_size <= handle->cfg.buf_size * handle->cfg.desc_num, ESP_ERR_INVALID_ARG, TAG, - "The cyclic buffer size exceeds the total DMA buffer size: %"PRIu32"(desc_num) * %d(buf_size) = %"PRIu32, - handle->cfg.desc_num, handle->cfg.buf_size, handle->cfg.buf_size * handle->cfg.desc_num); + DAC_NULL_POINTER_CHECK(buf); + ESP_RETURN_ON_FALSE(buf_size > 0, ESP_ERR_INVALID_ARG, TAG, "buf_size must be > 0"); +#if !CONFIG_DAC_DMA_AUTO_16BIT_ALIGN + ESP_RETURN_ON_FALSE(buf_size % 2 == 0, ESP_ERR_INVALID_ARG, TAG, "buf_size must be even when AUTO_16BIT_ALIGN is disabled"); +#endif + ESP_RETURN_ON_FALSE(buf_size * DAC_16BIT_ALIGN_COEFF <= handle->cfg.buf_size * handle->cfg.desc_num, + ESP_ERR_INVALID_ARG, TAG, "Data size exceeds the total DMA buffer size"); esp_err_t ret = ESP_OK; - xSemaphoreTake(handle->mutex, portMAX_DELAY); - if (atomic_load(&handle->is_cyclic)) { - /* Break the DMA descriptor chain to stop the DMA first */ - for (int i = 0; i < handle->cfg.desc_num; i++) { - STAILQ_NEXT(handle->desc[i], qe) = NULL; - } - } - /* Wait for the previous DMA stop */ - while (atomic_load(&handle->is_running)) {} - atomic_store(&handle->is_cyclic, true); - size_t src_buf_size = buf_size; - uint32_t split = 1; - int i; - for (i = 0; i < handle->cfg.desc_num && buf_size > 0; i++) { - /* To spread data more averagely, average the last two descriptors */ - split = (buf_size * DAC_16BIT_ALIGN_COEFF < handle->cfg.buf_size * 2) ? 3 - split : 1; - size_t load_bytes = s_dac_load_data_into_buf(handle, handle->bufs[i], handle->cfg.buf_size, buf, buf_size / split); - lldesc_config(handle->desc[i], LLDESC_HW_OWNED, 1, 0, load_bytes); - /* Link to the next descriptor */ - STAILQ_NEXT(handle->desc[i], qe) = (i < handle->cfg.desc_num - 1) ? handle->desc[i + 1] : NULL; - buf_size -= load_bytes / DAC_16BIT_ALIGN_COEFF; - buf += load_bytes / DAC_16BIT_ALIGN_COEFF; - } - /* Link the tail to the head as a ring */ - STAILQ_NEXT(handle->desc[i - 1], qe) = handle->desc[0]; + /* Serialize with the other writing APIs */ + ESP_RETURN_ON_FALSE(xSemaphoreTake(handle->mutex, portMAX_DELAY) == pdTRUE, + ESP_ERR_TIMEOUT, TAG, "Take mutex timeout"); + + /* For backward compatibility, check if there is any ongoing cyclic conversion and stop it */ + if (atomic_load(&s_dac_cont_fsm) == DAC_CONT_FSM_CYCLIC) { + ESP_GOTO_ON_ERROR(dac_continuous_stop_cyclically(handle), err, TAG, "Failed to stop cyclic conversion"); + } + + /* Check if there is any ongoing SYNC writing and wait for it to stop */ + if (atomic_load(&s_dac_cont_fsm) == DAC_CONT_FSM_SYNC) { + ESP_GOTO_ON_ERROR(s_dac_continuous_stop_sync(handle), err, TAG, "Failed to stop sync writing"); + } + + /* FSM: ENABLED -> WAIT */ + dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_ENABLED; + ESP_GOTO_ON_FALSE(atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_WAIT), + ESP_ERR_INVALID_STATE, err, TAG, "DAC continuous is running/not enabled"); + + size_t remain_size = buf_size; + uint32_t index = 0; + for (; index < handle->cfg.desc_num && remain_size > 0; index++) { + size_t loaded_len = s_dac_load_data_into_desc(handle, index, buf, remain_size, true); + remain_size -= loaded_len; + buf += loaded_len; + } + /* All data should be loaded */ + assert(remain_size == 0); + + /* Link the used descriptors as a ring: 0 -> 1 -> ... -> (index-1) -> 0 */ + for (int k = 0; k < index - 1; k++) { + gdma_link_concat(handle->link, k, handle->link, k + 1); + } + gdma_link_concat(handle->link, index - 1, handle->link, 0); + + handle->cur_index = 0; + handle->used_desc_num = index; + dac_dma_periph_trans_start(gdma_link_get_head_addr(handle->link)); + + /* FSM: WAIT -> CYCLIC */ + atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_CYCLIC); - dac_dma_periph_dma_trans_start((uint32_t)handle->desc[0]); - atomic_store(&handle->is_running, true); if (bytes_loaded) { - *bytes_loaded = src_buf_size - buf_size; + *bytes_loaded = buf_size; } +err: xSemaphoreGive(handle->mutex); return ret; } -static esp_err_t s_dac_wait_to_load_dma_data(dac_continuous_handle_t handle, uint8_t *buf, size_t buf_size, size_t *w_size, TickType_t timeout_tick) +esp_err_t dac_continuous_stop_cyclically(dac_continuous_handle_t handle) { - lldesc_t *desc; - /* Try to get the descriptor from the pool */ - ESP_RETURN_ON_FALSE(xQueueReceive(handle->desc_pool, &desc, timeout_tick) == pdTRUE, - ESP_ERR_TIMEOUT, TAG, "Get available descriptor timeout"); - /* To ensure it is not in the pending desc chain */ - if (STAILQ_FIRST(&handle->head) != NULL) { - DAC_STAILQ_REMOVE(&handle->head, desc, lldesc_s, qe); + DAC_NULL_POINTER_CHECK(handle); + + /* FSM: CYCLIC -> WAIT */ + dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_CYCLIC; + if (!atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_WAIT)) { + ESP_LOGE(TAG, "DAC continuous is not in cyclic writing mode"); + return ESP_ERR_INVALID_STATE; } - static bool split_flag = false; - uint8_t *dma_buf = (uint8_t *)desc->buf; - if (buf_size * DAC_16BIT_ALIGN_COEFF < 2 * handle->cfg.buf_size) { - if (!split_flag) { - buf_size >>= 1; - split_flag = true; - } else { - split_flag = false; - } - } - size_t load_bytes = s_dac_load_data_into_buf(handle, dma_buf, handle->cfg.buf_size, buf, buf_size); - lldesc_config(desc, LLDESC_HW_OWNED, 1, 0, load_bytes); - desc->size = load_bytes; - *w_size = load_bytes / DAC_16BIT_ALIGN_COEFF; - /* Insert the loaded descriptor to the end of the chain, waiting to be sent */ - DESC_ENTER_CRITICAL(); - STAILQ_INSERT_TAIL(&handle->head, desc, qe); - DESC_EXIT_CRITICAL(); + dac_dma_periph_trans_stop(); + + /* FSM: WAIT -> ENABLED */ + atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_ENABLED); return ESP_OK; } +//////////////////////////////////// Synchronous writing //////////////////////////////////// + esp_err_t dac_continuous_write(dac_continuous_handle_t handle, uint8_t *buf, size_t buf_size, size_t *bytes_loaded, int timeout_ms) { DAC_NULL_POINTER_CHECK(handle); DAC_NULL_POINTER_CHECK(buf); - ESP_RETURN_ON_FALSE(atomic_load(&handle->is_enabled), ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels has not been enabled"); - ESP_RETURN_ON_FALSE(!atomic_load(&handle->is_async), ESP_ERR_INVALID_STATE, TAG, "Asynchronous writing is running, can't write synchronously"); + ESP_RETURN_ON_FALSE(buf_size > 0, ESP_ERR_INVALID_ARG, TAG, "buf_size must be > 0"); +#if !CONFIG_DAC_DMA_AUTO_16BIT_ALIGN + ESP_RETURN_ON_FALSE(buf_size % 2 == 0, ESP_ERR_INVALID_ARG, TAG, "buf_size must be even when AUTO_16BIT_ALIGN is disabled"); +#endif esp_err_t ret = ESP_OK; - TickType_t timeout_tick = timeout_ms < 0 ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); - ESP_RETURN_ON_FALSE(xSemaphoreTake(handle->mutex, timeout_tick) == pdTRUE, ESP_ERR_TIMEOUT, TAG, "Take semaphore timeout"); - size_t w_size = 0; - size_t src_buf_size = buf_size; - /* Reset the desc_pool and chain if called cyclic function last time */ - if (atomic_load(&handle->is_cyclic)) { - xQueueReset(handle->desc_pool); - /* Break the chain if DMA still running */ - for (int i = 0; i < handle->cfg.desc_num; i++) { - STAILQ_NEXT(handle->desc[i], qe) = NULL; - xQueueSend(handle->desc_pool, &handle->desc[i], 0); + TickType_t timeout_tick = timeout_ms < 0 ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + size_t remain_size = buf_size; + + /* Serialize with the other writing APIs */ + ESP_RETURN_ON_FALSE(xSemaphoreTake(handle->mutex, timeout_tick) == pdTRUE, + ESP_ERR_TIMEOUT, TAG, "Take mutex timeout"); + + dac_continuous_fsm_t fsm = atomic_load(&s_dac_cont_fsm); + switch (fsm) { + case DAC_CONT_FSM_CYCLIC: + /* For backward compatibility, check if there is any ongoing cyclic conversion and stop it */ + ESP_LOGW(TAG, "It is recommended to explicitly stop the cyclic conversion by calling dac_continuous_stop_cyclically() before performing other operations."); + ESP_GOTO_ON_ERROR(dac_continuous_stop_cyclically(handle), err, TAG, "Failed to stop cyclic conversion"); + [[fallthrough]]; + + case DAC_CONT_FSM_ENABLED: + /* FSM: ENABLED -> SYNC_WAIT */ + dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_ENABLED; + ESP_GOTO_ON_FALSE(atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_SYNC_WAIT), + ESP_ERR_INVALID_STATE, err, TAG, "DAC continuous is running/not enabled"); + + /* Reset the free_desc_queue and the cur_index */ + xQueueReset(handle->free_desc_queue); + for (int i = 1; i < handle->cfg.desc_num; i++) { // skip 0 because we will use it right now + xQueueSend(handle->free_desc_queue, &i, 0); } - STAILQ_INIT(&handle->head); - atomic_store(&handle->is_cyclic, false); - } - /* When there is no descriptor in the chain, DMA has stopped, load data and start the DMA link */ - if (STAILQ_FIRST(&handle->head) == NULL) { - /* Wait for the previous DMA stop */ - while (atomic_load(&handle->is_running)) {} - for (int i = 0; - i < handle->cfg.desc_num && buf_size > 0; - i++, buf += w_size, buf_size -= w_size) { - ESP_GOTO_ON_ERROR(s_dac_wait_to_load_dma_data(handle, buf, buf_size, &w_size, timeout_tick), err, TAG, "Load data failed"); + handle->cur_index = 0; + handle->used_desc_num = handle->cfg.desc_num; + + /* Load one descriptor and start the DMA */ + size_t loaded_len = s_dac_load_data_into_desc(handle, 0, buf, remain_size, true); + remain_size -= loaded_len; + buf += loaded_len; + gdma_link_concat(handle->link, 0, NULL, 0); +#if SOC_IS(ESP32) + /* It is safe to operate without the lock here because the DMA is not running yet. */ + handle->dma_running = true; +#endif + dac_dma_periph_trans_start(gdma_link_get_head_addr(handle->link)); + + goto skip_cas; + + case DAC_CONT_FSM_SYNC: + /* FSM: SYNC -> SYNC_WAIT */ + expected_fsm = DAC_CONT_FSM_SYNC; + ESP_GOTO_ON_FALSE(atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_SYNC_WAIT), + ESP_ERR_INVALID_STATE, err, TAG, "CAS failed: SYNC -> SYNC_WAIT"); + +skip_cas: + while (remain_size > 0) { + int index; + if (xQueueReceive(handle->free_desc_queue, &index, timeout_tick) != pdTRUE) { + ret = ESP_ERR_TIMEOUT; + break; + } + size_t loaded_len = s_dac_load_data_into_desc(handle, index, buf, remain_size, true); + remain_size -= loaded_len; + buf += loaded_len; + /** + * link: (index-1) -> index -> NULL + * NOTE: gdma_link_concat() can normalize the index to be between 0 and desc_num - 1. + */ + gdma_link_concat(handle->link, index, NULL, 0); + +#if SOC_IS(ESP32) + /** + * The ESP32 I2S DMA append() (restart) is buggy, so we re-issue start() when the DMA has stopped. See IDF-15791. + * Synchronize with the TEOF handler via dma_lock to prevent duplicate or missed starts. + */ + portENTER_CRITICAL(&handle->dma_lock); + gdma_link_concat(handle->link, index - 1, handle->link, index); + if (!handle->dma_running) { + handle->dma_running = true; + dac_dma_periph_trans_start(gdma_link_get_item_addr(handle->link, index)); + } + portEXIT_CRITICAL(&handle->dma_lock); +#else + gdma_link_concat(handle->link, index - 1, handle->link, index); + dac_dma_periph_trans_append(); +#endif } - dac_dma_periph_dma_trans_start((uint32_t)(STAILQ_FIRST(&handle->head))); - atomic_store(&handle->is_running, true); - } - /* If the source buffer is not totally loaded, keep loading the rest data */ - while (buf_size > 0) { - ESP_GOTO_ON_ERROR(s_dac_wait_to_load_dma_data(handle, buf, buf_size, &w_size, timeout_tick), err, TAG, "Load data failed"); - /* If the DMA stopped but there are still some descriptors not sent, start the DMA again */ - DESC_ENTER_CRITICAL(); - if (STAILQ_FIRST(&handle->head) && !atomic_load(&handle->is_running)) { - dac_dma_periph_dma_trans_start((uint32_t)(STAILQ_FIRST(&handle->head))); - atomic_store(&handle->is_running, true); - } - DESC_EXIT_CRITICAL(); - buf += w_size; - buf_size -= w_size; + break; + + default: + ESP_LOGE(TAG, "Unexpected FSM state: %u", fsm); + ret = ESP_ERR_INVALID_STATE; + goto err; } + + /* FSM: SYNC_WAIT -> SYNC */ + atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_SYNC); err: - /* The bytes number that has been loaded */ - if (bytes_loaded) { - *bytes_loaded = src_buf_size - buf_size; - } xSemaphoreGive(handle->mutex); + if (bytes_loaded) { + *bytes_loaded = buf_size - remain_size; + } return ret; } + +static esp_err_t s_dac_continuous_stop_sync(dac_continuous_handle_t handle) +{ + /* FSM: SYNC -> WAIT */ + dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_SYNC; + ESP_RETURN_ON_FALSE(atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_WAIT), + ESP_ERR_INVALID_STATE, TAG, "DAC continuous is not in sync writing mode"); + +#if SOC_IS(ESP32) + /** + * Serialize with the TEOF ISR which may also call trans_start() on another core. + * Both must be guarded by dma_lock to prevent concurrent hardware register access. + */ + portENTER_CRITICAL(&handle->dma_lock); + dac_dma_periph_trans_stop(); + handle->dma_running = false; + portEXIT_CRITICAL(&handle->dma_lock); +#else + dac_dma_periph_trans_stop(); +#endif + + /* FSM: WAIT -> ENABLED */ + atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_ENABLED); + + return ESP_OK; +} diff --git a/components/esp_driver_dac/dac_cosine.c b/components/esp_driver_dac/dac_cosine.c index d68edb39e61..6b966e3e2b1 100644 --- a/components/esp_driver_dac/dac_cosine.c +++ b/components/esp_driver_dac/dac_cosine.c @@ -5,7 +5,6 @@ */ #include -#include "soc/soc_caps.h" #include "driver/dac_cosine.h" #include "hal/clk_tree_ll.h" #include "dac_priv_common.h" @@ -43,7 +42,7 @@ esp_err_t dac_cosine_new_channel(const dac_cosine_config_t *cos_cfg, dac_cosine_ /* Parameters validation */ DAC_NULL_POINTER_CHECK(cos_cfg); DAC_NULL_POINTER_CHECK(ret_handle); - ESP_RETURN_ON_FALSE(cos_cfg->chan_id < SOC_DAC_CHAN_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid dac channel id"); + ESP_RETURN_ON_FALSE(IS_VALID_DAC_CHANNEL(cos_cfg->chan_id), ESP_ERR_INVALID_ARG, TAG, "invalid dac channel id"); ESP_RETURN_ON_FALSE(cos_cfg->freq_hz >= (130 / clk_ll_rc_fast_get_divider()), ESP_ERR_NOT_SUPPORTED, TAG, "The cosine wave frequency is too low"); ESP_RETURN_ON_FALSE((!s_cwg_freq) || cos_cfg->flags.force_set_freq || (cos_cfg->freq_hz == s_cwg_freq), ESP_ERR_INVALID_STATE, TAG, "The cosine wave frequency has set already, not allowed to update unless `force_set_freq` is set"); @@ -53,9 +52,9 @@ esp_err_t dac_cosine_new_channel(const dac_cosine_config_t *cos_cfg, dac_cosine_ dac_cosine_handle_t handle = heap_caps_calloc(1, sizeof(struct dac_cosine_s), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); ESP_RETURN_ON_FALSE(handle, ESP_ERR_NO_MEM, TAG, "no memory for the dac cosine handle"); /* Assign configurations */ - memcpy(&handle->cfg, cos_cfg, sizeof(dac_cosine_config_t)); + handle->cfg = *cos_cfg; /* Register the handle */ - ESP_GOTO_ON_ERROR(dac_priv_register_channel(cos_cfg->chan_id, "dac cosine"), err1, TAG, "register dac channel %d failed", cos_cfg->chan_id); + ESP_GOTO_ON_ERROR(dac_priv_register_channel(cos_cfg->chan_id), err1, TAG, "register dac channel %d failed", cos_cfg->chan_id); /* Cosine wave generator uses RTC_FAST clock which is divided from RC_FAST */ uint32_t rtc_clk_freq = 0; diff --git a/components/esp_driver_dac/dac_oneshot.c b/components/esp_driver_dac/dac_oneshot.c index 5e75ea11462..ad92c86dc41 100644 --- a/components/esp_driver_dac/dac_oneshot.c +++ b/components/esp_driver_dac/dac_oneshot.c @@ -1,11 +1,10 @@ /* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include -#include "soc/soc_caps.h" #include "dac_priv_common.h" #include "driver/dac_oneshot.h" @@ -33,16 +32,16 @@ esp_err_t dac_oneshot_new_channel(const dac_oneshot_config_t *oneshot_cfg, dac_o /* Parameters validation */ DAC_NULL_POINTER_CHECK(oneshot_cfg); DAC_NULL_POINTER_CHECK(ret_handle); - ESP_RETURN_ON_FALSE(oneshot_cfg->chan_id < SOC_DAC_CHAN_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid dac channel id"); + ESP_RETURN_ON_FALSE(IS_VALID_DAC_CHANNEL(oneshot_cfg->chan_id), ESP_ERR_INVALID_ARG, TAG, "invalid dac channel id"); esp_err_t ret = ESP_OK; /* Resources allocation */ dac_oneshot_handle_t handle = heap_caps_calloc(1, sizeof(struct dac_oneshot_s), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); ESP_RETURN_ON_FALSE(handle, ESP_ERR_NO_MEM, TAG, "no memory for the dac oneshot handle"); - memcpy(&handle->cfg, oneshot_cfg, sizeof(dac_oneshot_config_t)); + handle->cfg = *oneshot_cfg; /* Register and enable the dac channel */ - ESP_GOTO_ON_ERROR(dac_priv_register_channel(oneshot_cfg->chan_id, "dac oneshot"), err2, TAG, "register dac channel %d failed", oneshot_cfg->chan_id); + ESP_GOTO_ON_ERROR(dac_priv_register_channel(oneshot_cfg->chan_id), err2, TAG, "register dac channel %d failed", oneshot_cfg->chan_id); ESP_GOTO_ON_ERROR(dac_priv_enable_channel(oneshot_cfg->chan_id), err1, TAG, "enable dac channel %d failed", oneshot_cfg->chan_id); *ret_handle = handle; diff --git a/components/esp_driver_dac/dac_priv_common.h b/components/esp_driver_dac/dac_priv_common.h index 70dcf5538aa..43dc1c41cab 100644 --- a/components/esp_driver_dac/dac_priv_common.h +++ b/components/esp_driver_dac/dac_priv_common.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -30,20 +30,19 @@ extern portMUX_TYPE rtc_spinlock; /*!< Extern global rtc spinlock */ * @brief Register dac channel in the driver, in case a same channel is reused by different modes * * @param[in] chan_id DAC channel id - * @param[in] mode_name The const string of mode name * @return * - ESP_ERR_INVALID_STATE The channel has been occupied * - ESP_ERR_INVALID_ARG The channel id is incorrect * - ESP_OK Register the channel success */ -esp_err_t dac_priv_register_channel(dac_channel_t chan_id, const char *mode_name); +esp_err_t dac_priv_register_channel(dac_channel_t chan_id); /** * @brief Deregister dac channel in the driver * * @param[in] chan_id DAC channel id * @return - * - ESP_ERR_INVALID_STATE The channel has been freed + * - ESP_ERR_INVALID_STATE The channel has been freed or not disabled * - ESP_ERR_INVALID_ARG The channel id is incorrect * - ESP_OK Deregister the channel success */ @@ -54,9 +53,9 @@ esp_err_t dac_priv_deregister_channel(dac_channel_t chan_id); * * @param chan_id DAC channel id * @return - * - ESP_ERR_INVALID_STATE The channel has not been registered + * - ESP_ERR_INVALID_STATE The channel has not been registered or already enabled * - ESP_ERR_INVALID_ARG The channel id is incorrect - * - ESP_OK Deregister the channel success + * - ESP_OK Enable the channel success */ esp_err_t dac_priv_enable_channel(dac_channel_t chan_id); @@ -65,9 +64,9 @@ esp_err_t dac_priv_enable_channel(dac_channel_t chan_id); * * @param chan_id DAC channel id * @return - * - ESP_ERR_INVALID_STATE The channel has not been registered + * - ESP_ERR_INVALID_STATE The channel is not enabled * - ESP_ERR_INVALID_ARG The channel id is incorrect - * - ESP_OK Deregister the channel success + * - ESP_OK Disable the channel success */ esp_err_t dac_priv_disable_channel(dac_channel_t chan_id); diff --git a/components/esp_driver_dac/dac_priv_dma.h b/components/esp_driver_dac/dac_priv_dma.h index deae4f55d01..fb03619cfdd 100644 --- a/components/esp_driver_dac/dac_priv_dma.h +++ b/components/esp_driver_dac/dac_priv_dma.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,14 +7,18 @@ #pragma once #include "esp_err.h" +#include "soc/soc_caps.h" +#include "esp_bit_defs.h" #include "esp_intr_alloc.h" #ifdef __cplusplus extern "C" { #endif -#define DAC_DMA_EOF_INTR 0x01 -#define DAC_DMA_TEOF_INTR 0x02 +// one node in the descriptor chain is finished +#define DAC_DMA_DONE_INTR BIT(0) +// all nodes in the descriptor chain are finished +#define DAC_DMA_TEOF_INTR BIT(1) /** * @brief Initialize DAC DMA peripheral @@ -61,20 +65,12 @@ void dac_dma_periph_enable(void); void dac_dma_periph_disable(void); /** - * @brief Whether the TX_EOF interrupt is triggered + * @brief Get the mask of the triggered interrupt * * @return - * - uint32_t Mask of the triggered interrupt: DAC_DMA_EOF_INTR, DAC_DMA_EOF_INTR + * - uint32_t Mask of the triggered interrupt: DAC_DMA_DONE_INTR, DAC_DMA_TEOF_INTR */ -uint32_t dac_dma_periph_intr_is_triggered(void); - -/** - * @brief Get the descriptor that just finished sending data - * - * @return - * - uint32_t The address of the EOF descriptor - */ -uint32_t dac_dma_periph_intr_get_eof_desc(void); +uint32_t dac_dma_periph_intr_get_mask(void); /** * @brief Start a DMA transaction @@ -82,7 +78,20 @@ uint32_t dac_dma_periph_intr_get_eof_desc(void); * * @param[in] desc_addr Descriptor address */ -void dac_dma_periph_dma_trans_start(uint32_t desc_addr); +void dac_dma_periph_trans_start(uintptr_t desc_addr); + +/** + * @brief Stop the current DMA transaction immediately + */ +void dac_dma_periph_trans_stop(void); + +#if !SOC_IS(ESP32) +/** + * @brief Append the newly linked DMA descriptors to the current transaction + * @note The caller should link new descriptors to the current tail before calling this function. + */ +void dac_dma_periph_trans_append(void); +#endif #ifdef __cplusplus } diff --git a/components/esp_driver_dac/esp32/dac_dma.c b/components/esp_driver_dac/esp32/dac_dma.c index 67e500dc27c..fb492c2be73 100644 --- a/components/esp_driver_dac/esp32/dac_dma.c +++ b/components/esp_driver_dac/esp32/dac_dma.c @@ -138,8 +138,6 @@ esp_err_t dac_dma_periph_init(uint32_t freq_hz, bool is_alternate, bool is_apll) /* Should always enable fifo */ i2s_ll_tx_force_enable_fifo_mod(s_ddp->periph_dev, true); i2s_ll_dma_enable_auto_write_back(s_ddp->periph_dev, true); - /* Enable the interrupts */ - i2s_ll_enable_intr(s_ddp->periph_dev, I2S_LL_EVENT_TX_EOF | I2S_LL_EVENT_TX_TEOF, true); return ret; err: @@ -155,7 +153,6 @@ esp_err_t dac_dma_periph_deinit(void) ESP_RETURN_ON_FALSE(s_ddp->intr_handle == NULL, ESP_ERR_INVALID_STATE, TAG, "The interrupt is not deregistered yet"); ESP_RETURN_ON_ERROR(i2s_platform_release_occupation(I2S_CTLR_HP, DAC_DMA_PERIPH_I2S_NUM), TAG, "Failed to release DAC DMA peripheral"); - i2s_ll_enable_intr(s_ddp->periph_dev, I2S_LL_EVENT_TX_EOF | I2S_LL_EVENT_TX_TEOF, false); if (s_ddp->use_apll) { ESP_RETURN_ON_ERROR(esp_clk_tree_enable_src(SOC_MOD_CLK_APLL, false), TAG, "APLL disable failed"); s_ddp->use_apll = false; @@ -181,7 +178,7 @@ static void s_dac_dma_periph_reset(void) static void s_dac_dma_periph_start(void) { i2s_ll_enable_dma(s_ddp->periph_dev, true); - i2s_ll_tx_enable_intr(s_ddp->periph_dev); + i2s_ll_enable_intr(s_ddp->periph_dev, I2S_LL_EVENT_TX_DONE | I2S_LL_EVENT_TX_TEOF, true); i2s_ll_tx_start(s_ddp->periph_dev); i2s_ll_dma_enable_eof_on_fifo_empty(s_ddp->periph_dev, true); i2s_ll_dma_enable_auto_write_back(s_ddp->periph_dev, true); @@ -191,7 +188,7 @@ static void s_dac_dma_periph_stop(void) { i2s_ll_tx_stop(s_ddp->periph_dev); i2s_ll_tx_stop_link(s_ddp->periph_dev); - i2s_ll_tx_disable_intr(s_ddp->periph_dev); + i2s_ll_enable_intr(s_ddp->periph_dev, I2S_LL_EVENT_TX_DONE | I2S_LL_EVENT_TX_TEOF, false); i2s_ll_enable_dma(s_ddp->periph_dev, false); i2s_ll_dma_enable_eof_on_fifo_empty(s_ddp->periph_dev, false); i2s_ll_dma_enable_auto_write_back(s_ddp->periph_dev, false); @@ -213,28 +210,26 @@ void dac_dma_periph_disable(void) s_dac_dma_periph_stop(); } -uint32_t IRAM_ATTR dac_dma_periph_intr_is_triggered(void) +uint32_t IRAM_ATTR dac_dma_periph_intr_get_mask(void) { uint32_t status = i2s_ll_get_intr_status(s_ddp->periph_dev); if (status == 0) { - //Avoid spurious interrupt - return false; + // Avoid spurious interrupt + return 0UL; } i2s_ll_clear_intr_status(s_ddp->periph_dev, status); uint32_t ret = 0; - ret |= (status & I2S_LL_EVENT_TX_EOF) ? DAC_DMA_EOF_INTR : 0; + ret |= (status & I2S_LL_EVENT_TX_DONE) ? DAC_DMA_DONE_INTR : 0; ret |= (status & I2S_LL_EVENT_TX_TEOF) ? DAC_DMA_TEOF_INTR : 0; return ret; } -uint32_t IRAM_ATTR dac_dma_periph_intr_get_eof_desc(void) -{ - uint32_t finish_desc; - i2s_ll_tx_get_eof_des_addr(s_ddp->periph_dev, &finish_desc); - return finish_desc; -} - -void dac_dma_periph_dma_trans_start(uint32_t desc_addr) +void IRAM_ATTR dac_dma_periph_trans_start(uintptr_t desc_addr) { i2s_ll_tx_start_link(s_ddp->periph_dev, desc_addr); } + +void dac_dma_periph_trans_stop(void) +{ + i2s_ll_tx_stop_link(s_ddp->periph_dev); +} diff --git a/components/esp_driver_dac/esp32s2/dac_dma.c b/components/esp_driver_dac/esp32s2/dac_dma.c index 12d9710ac01..b765680c11e 100644 --- a/components/esp_driver_dac/esp32s2/dac_dma.c +++ b/components/esp_driver_dac/esp32s2/dac_dma.c @@ -147,7 +147,7 @@ esp_err_t dac_dma_periph_init(uint32_t freq_hz, bool is_alternate, bool is_apll) ESP_GOTO_ON_ERROR(spicommon_dma_chan_alloc(DAC_DMA_PERIPH_SPI_HOST, SPI_DMA_CH_AUTO, 0), err, TAG, "Failed to allocate dma peripheral channel"); s_ddp->dma_chan = spi_bus_get_dma_ctx(DAC_DMA_PERIPH_SPI_HOST)->rx_dma_chan.chan_id; - spi_ll_enable_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_EOF | SPI_LL_INTR_OUT_TOTAL_EOF); + spi_ll_enable_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_DONE | SPI_LL_INTR_OUT_TOTAL_EOF); dac_ll_digi_set_convert_mode(is_alternate); return ret; err: @@ -163,7 +163,7 @@ esp_err_t dac_dma_periph_deinit(void) ESP_RETURN_ON_ERROR(spicommon_dma_chan_free(DAC_DMA_PERIPH_SPI_HOST), TAG, "Failed to free dma peripheral channel"); } ESP_RETURN_ON_FALSE(spicommon_periph_free(DAC_DMA_PERIPH_SPI_HOST), ESP_FAIL, TAG, "Failed to release DAC DMA peripheral"); - spi_ll_disable_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_EOF | SPI_LL_INTR_OUT_TOTAL_EOF); + spi_ll_disable_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_DONE | SPI_LL_INTR_OUT_TOTAL_EOF); adc_apb_periph_free(); if (s_ddp) { if (s_ddp->use_apll) { @@ -200,24 +200,29 @@ void dac_dma_periph_disable(void) dac_ll_digi_trigger_output(false); } -uint32_t IRAM_ATTR dac_dma_periph_intr_is_triggered(void) +uint32_t IRAM_ATTR dac_dma_periph_intr_get_mask(void) { uint32_t ret = 0; - ret |= spi_ll_get_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_EOF) ? DAC_DMA_EOF_INTR : 0; + ret |= spi_ll_get_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_DONE) ? DAC_DMA_DONE_INTR : 0; ret |= spi_ll_get_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_TOTAL_EOF) ? DAC_DMA_TEOF_INTR : 0; - spi_ll_clear_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_EOF); + spi_ll_clear_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_DONE); spi_ll_clear_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_TOTAL_EOF); return ret; } -uint32_t IRAM_ATTR dac_dma_periph_intr_get_eof_desc(void) -{ - return spi_dma_ll_get_out_eof_desc_addr(s_ddp->periph_dev, s_ddp->dma_chan); -} - -void dac_dma_periph_dma_trans_start(uint32_t desc_addr) +void IRAM_ATTR dac_dma_periph_trans_start(uintptr_t desc_addr) { spi_dma_ll_tx_reset(s_ddp->periph_dev, s_ddp->dma_chan); spi_ll_dma_tx_fifo_reset(s_ddp->periph_dev); spi_dma_ll_tx_start(s_ddp->periph_dev, s_ddp->dma_chan, (lldesc_t *)desc_addr); } + +void dac_dma_periph_trans_stop(void) +{ + spi_dma_ll_tx_stop(s_ddp->periph_dev, s_ddp->dma_chan); +} + +void dac_dma_periph_trans_append(void) +{ + spi_dma_ll_tx_restart(s_ddp->periph_dev, s_ddp->dma_chan); +} diff --git a/components/esp_driver_dac/include/driver/dac_continuous.h b/components/esp_driver_dac/include/driver/dac_continuous.h index ad7668309f8..ada295a2fb7 100644 --- a/components/esp_driver_dac/include/driver/dac_continuous.h +++ b/components/esp_driver_dac/include/driver/dac_continuous.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -15,16 +15,6 @@ extern "C" { #if SOC_DAC_SUPPORTED -/** - * @brief DAC channel mask - * - */ -typedef enum { - DAC_CHANNEL_MASK_CH0 = BIT(0), /*!< DAC channel 0 is GPIO25(ESP32) / GPIO17(ESP32S2) */ - DAC_CHANNEL_MASK_CH1 = BIT(1), /*!< DAC channel 1 is GPIO26(ESP32) / GPIO18(ESP32S2) */ - DAC_CHANNEL_MASK_ALL = BIT(0) | BIT(1), /*!< Both DAC channel 0 and channel 1 */ -} dac_channel_mask_t; - typedef struct dac_continuous_s *dac_continuous_handle_t; /*!< DAC continuous channel handle */ /** @@ -139,7 +129,7 @@ esp_err_t dac_continuous_enable(dac_continuous_handle_t handle); * @param[in] handle The DAC continuous channel handle that obtained from 'dac_continuous_new_channels' * @return * - ESP_ERR_INVALID_ARG The input parameter is invalid - * - ESP_ERR_INVALID_STATE The channels have been enabled already + * - ESP_ERR_INVALID_STATE The channels are not enabled, or a write operation is still ongoing * - ESP_OK Disable the continuous output success */ esp_err_t dac_continuous_disable(dac_continuous_handle_t handle); @@ -185,10 +175,24 @@ esp_err_t dac_continuous_write(dac_continuous_handle_t handle, uint8_t *buf, siz * @return * - ESP_ERR_INVALID_ARG The input parameter is invalid * - ESP_ERR_INVALID_STATE The DAC continuous mode has not been enabled yet - * - ESP_OK Success to output the acyclic DAC data + * - ESP_OK Success to output the cyclic DAC data */ esp_err_t dac_continuous_write_cyclically(dac_continuous_handle_t handle, uint8_t *buf, size_t buf_size, size_t *bytes_loaded); +/** + * @brief Stop the cyclical conversion triggered by 'dac_continuous_write_cyclically' + * @note For backward compatibility, calling this function is optional. That is, after a cyclic write (conversion) has started, + * users can directly call 'dac_continuous_disable', 'dac_continuous_write_cyclically', 'dac_continuous_start_async_writing', + * or 'dac_continuous_write'. These functions will automatically check for and stop any ongoing cyclic conversion. However, + * this behavior is NOT recommended. + * @param[in] handle The DAC continuous channel handle that obtained from 'dac_continuous_new_channels' + * @return + * - ESP_ERR_INVALID_ARG The input parameter is invalid + * - ESP_ERR_INVALID_STATE The DAC continuous is not in cyclic writing mode + * - ESP_OK Success to stop the cyclic conversion + */ +esp_err_t dac_continuous_stop_cyclically(dac_continuous_handle_t handle); + /** * @brief Set event callbacks for DAC continuous mode * @@ -220,7 +224,7 @@ esp_err_t dac_continuous_register_event_callback(dac_continuous_handle_t handle, esp_err_t dac_continuous_start_async_writing(dac_continuous_handle_t handle); /** - * @brief Stop the sync writing + * @brief Stop the async writing * * @param[in] handle The DAC continuous channel handle that obtained from 'dac_continuous_new_channels' * @return @@ -237,7 +241,7 @@ esp_err_t dac_continuous_stop_async_writing(dac_continuous_handle_t handle); * * @param[in] handle The DAC continuous channel handle that obtained from 'dac_continuous_new_channels' * @param[in] dma_buf The DMA buffer address, it can be acquired from 'dac_event_data_t' in the 'on_convert_done' callback - * @param[in] dma_buf_len The DMA buffer length, it can be acquired from 'dac_event_data_t' in the 'on_convert_done' callback + * @param[in] dma_buf_len The DMA buffer length, it can be acquired from 'dac_event_data_t' in the 'on_convert_done' callback. It should always be equal to the buffer size of descriptors. This parameter is kept for compatibility and ignored by the driver. * @param[in] data The data that need to be written * @param[in] data_len The data length the need to be written * @param[out] bytes_loaded The bytes number that has been loaded/written into the DMA buffer diff --git a/components/esp_driver_dac/linker.lf b/components/esp_driver_dac/linker.lf index c58fa57831d..5a8dc0355af 100644 --- a/components/esp_driver_dac/linker.lf +++ b/components/esp_driver_dac/linker.lf @@ -4,3 +4,18 @@ entries: if DAC_CTRL_FUNC_IN_IRAM = y: dac_oneshot: dac_oneshot_output_voltage (noflash) dac_continuous: dac_continuous_write_asynchronously (noflash) + dac_continuous: s_dac_load_data_into_desc (noflash) + +[mapping:dac_driver_gdma_link] +archive: libesp_driver_dma.a +entries: + # Reached from the dac_continuous ISR + if DAC_ISR_IRAM_SAFE = y: + gdma_link: gdma_link_get_length (noflash) + gdma_link: gdma_link_get_item_addr (noflash) + gdma_link: gdma_link_check_end (noflash) + + # Reached from 'dac_continuous_write_asynchronously' (via 's_dac_load_data_into_desc') + if DAC_CTRL_FUNC_IN_IRAM = y: + gdma_link: gdma_link_set_length (noflash) + gdma_link: gdma_link_set_owner (noflash) diff --git a/components/esp_driver_dac/test_apps/dac/main/test_dac.c b/components/esp_driver_dac/test_apps/dac/main/test_dac.c index c36e25b3fea..930c2100a59 100644 --- a/components/esp_driver_dac/test_apps/dac/main/test_dac.c +++ b/components/esp_driver_dac/test_apps/dac/main/test_dac.c @@ -1,11 +1,14 @@ /* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include +#include #include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" #include "unity.h" #include "unity_test_utils.h" #include "driver/dac_oneshot.h" @@ -226,6 +229,50 @@ TEST_CASE("DAC_dma_write_test", "[dac]") TEST_ESP_OK(dac_continuous_del_channels(cont_handle)); } +/* This test targets the synchronous writing "resume after the DMA has fully stopped" path. + * With small DMA buffers and a deliberate long delay between writes, the DMA drains all of + * its descriptors and stops (raises TEOF) before the next write happens. Every write except + * the first one therefore has to re-link a descriptor and resume the transfer through + * dac_dma_periph_trans_append(). If that resume path is broken, the descriptors are never + * recycled, so dac_continuous_write() will return ESP_ERR_TIMEOUT, and dac_continuous_disable() + * (which waits for the ongoing synchronous transfer to stop) will block forever. */ +TEST_CASE("DAC_dma_sync_write_resume_test", "[dac]") +{ + dac_continuous_handle_t cont_handle; + dac_continuous_config_t cont_cfg = { + .chan_mask = DAC_CHANNEL_MASK_ALL, + .desc_num = 4, + .buf_size = 256, + .freq_hz = 48000, + .offset = 0, + .clk_src = DAC_DIGI_CLK_SRC_DEFAULT, + .chan_mode = DAC_CHANNEL_MODE_SIMUL, + }; + + /* These data will be filled into 5 descriptors */ + size_t len = 128 * 5; + uint8_t buf[len]; + for (int i = 0; i < len; i++) { + buf[i] = i % 256; + } + + TEST_ESP_OK(dac_continuous_new_channels(&cont_cfg, &cont_handle)); + TEST_ESP_OK(dac_continuous_enable(cont_handle)); + + /* Waiting 100 ms between writes guarantees the DMA has fully drained and stopped, + * forcing every subsequent write through the resume path. */ + for (int i = 0; i < 10; i++) { + size_t bytes_loaded = 0; + TEST_ESP_OK(dac_continuous_write(cont_handle, buf, len, &bytes_loaded, 1000)); + TEST_ASSERT_EQUAL(len, bytes_loaded); + vTaskDelay(pdMS_TO_TICKS(100)); + } + + /* disable() internally waits for the ongoing synchronous transfer to stop; it must not hang. */ + TEST_ESP_OK(dac_continuous_disable(cont_handle)); + TEST_ESP_OK(dac_continuous_del_channels(cont_handle)); +} + /* Test the conversion frequency by counting the pulse of WS signal * The frequency test is currently only supported on ESP32 * because there is no such signal to monitor on ESP32-S2 */ @@ -355,37 +402,45 @@ TEST_CASE("DAC_cosine_wave_test", "[dac]") TEST_ESP_OK(dac_cosine_del_channel(cos_chan1_handle)); } +typedef struct { + dac_continuous_handle_t handle; + volatile bool stop; + TaskHandle_t notify_task; /* Task to notify before self-deletion */ +} dac_concurrency_test_ctx_t; + static void dac_cyclically_write_task(void *arg) { - dac_continuous_handle_t dac_handle = (dac_continuous_handle_t)arg; + dac_concurrency_test_ctx_t *ctx = arg; size_t len = 1000; uint8_t buf[len]; uint8_t max_val = 50; - while (1) { + while (!ctx->stop) { max_val += 50; for (int i = 0; i < len; i++) { buf[i] = i % max_val; } printf("Write cyclically\n"); - TEST_ESP_OK(dac_continuous_write_cyclically(dac_handle, buf, len, NULL)); + TEST_ESP_OK(dac_continuous_write_cyclically(ctx->handle, buf, len, NULL)); vTaskDelay(pdMS_TO_TICKS(200)); } + xTaskNotifyGive(ctx->notify_task); vTaskDelete(NULL); } static void dac_continuously_write_task(void *arg) { - dac_continuous_handle_t dac_handle = (dac_continuous_handle_t)arg; + dac_concurrency_test_ctx_t *ctx = arg; size_t len = 2048; uint8_t buf[len]; for (int i = 0; i < len; i++) { buf[i] = i % 256; } - while (1) { + while (!ctx->stop) { printf("Write continuously\n"); - TEST_ESP_OK(dac_continuous_write(dac_handle, buf, len, NULL, 100)); + TEST_ESP_OK(dac_continuous_write(ctx->handle, buf, len, NULL, 100)); vTaskDelay(pdMS_TO_TICKS(300)); } + xTaskNotifyGive(ctx->notify_task); vTaskDelete(NULL); } @@ -405,15 +460,27 @@ TEST_CASE("DAC_continuous_mode_concurrency_test", "[dac]") TEST_ESP_OK(dac_continuous_new_channels(&cont_cfg, &cont_handle)); TEST_ESP_OK(dac_continuous_enable(cont_handle)); + dac_concurrency_test_ctx_t ctx = { + .handle = cont_handle, + .stop = false, + .notify_task = xTaskGetCurrentTaskHandle(), + }; + TaskHandle_t cyc_task; TaskHandle_t con_task; - xTaskCreate(dac_cyclically_write_task, "dac_cyclically_write_task", 4096, cont_handle, 5, &cyc_task); - xTaskCreate(dac_continuously_write_task, "dac_continuously_write_task", 4096, cont_handle, 5, &con_task); + xTaskCreate(dac_cyclically_write_task, "dac_cyclically_write_task", 4096, &ctx, 5, &cyc_task); + xTaskCreate(dac_continuously_write_task, "dac_continuously_write_task", 4096, &ctx, 5, &con_task); vTaskDelay(pdMS_TO_TICKS(5000)); - vTaskDelete(cyc_task); - vTaskDelete(con_task); + ctx.stop = true; + + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(2000))); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(2000))); + + /* vTaskDelete(NULL) defers freeing task TCB and stack to the idle task. + * Yield here so idle task(s) can reclaim that memory before tearDown() checks for leaks. */ + vTaskDelay(pdMS_TO_TICKS(10)); TEST_ESP_OK(dac_continuous_disable(cont_handle)); TEST_ESP_OK(dac_continuous_del_channels(cont_handle)); diff --git a/components/esp_driver_dma/include/esp_private/gdma_link.h b/components/esp_driver_dma/include/esp_private/gdma_link.h index e663a7535a9..ced2b811075 100644 --- a/components/esp_driver_dma/include/esp_private/gdma_link.h +++ b/components/esp_driver_dma/include/esp_private/gdma_link.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -117,6 +117,19 @@ esp_err_t gdma_link_mount_buffers(gdma_link_list_handle_t list, int start_item_i */ uintptr_t gdma_link_get_head_addr(gdma_link_list_handle_t list); +/** + * @brief Get the address of a specific link list item by index + * @note The returned address is the cached address used by the DMA hardware (same convention as `gdma_link_get_head_addr`). + * It can be passed directly to the DMA start function to resume transmission from a specific descriptor. + * + * @param[in] list Link list handle, allocated by `gdma_new_link_list` + * @param[in] item_index Index of the link list item (wraps around if out of range) + * @return + * - Address of the specified item + * - 0: Invalid handle + */ +uintptr_t gdma_link_get_item_addr(gdma_link_list_handle_t list, int item_index); + /** * @brief Concatenate two link lists as follows: * diff --git a/components/esp_driver_dma/src/gdma_link.c b/components/esp_driver_dma/src/gdma_link.c index 27e48d7ea71..4bd452e4d22 100644 --- a/components/esp_driver_dma/src/gdma_link.c +++ b/components/esp_driver_dma/src/gdma_link.c @@ -281,6 +281,16 @@ uintptr_t gdma_link_get_head_addr(gdma_link_list_handle_t list) return (uintptr_t)(list->items); } +uintptr_t gdma_link_get_item_addr(gdma_link_list_handle_t list, int item_index) +{ + if (!list) { + return 0; + } + int num_items = list->num_items; + item_index = (item_index % num_items + num_items) % num_items; + return (uintptr_t)(list->items + item_index * list->item_size); +} + esp_err_t gdma_link_concat(gdma_link_list_handle_t first_link, int first_link_item_index, gdma_link_list_handle_t second_link, int second_link_item_index) { if (!first_link) { diff --git a/components/esp_hal_ana_conv/include/hal/dac_types.h b/components/esp_hal_ana_conv/include/hal/dac_types.h index 6236b9498f8..0ddf0346594 100644 --- a/components/esp_hal_ana_conv/include/hal/dac_types.h +++ b/components/esp_hal_ana_conv/include/hal/dac_types.h @@ -1,19 +1,57 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once +#include "esp_assert.h" +#include "esp_bit_defs.h" +#include "soc/soc_caps.h" + #ifdef __cplusplus extern "C" { #endif +#if SOC_DAC_SUPPORTED + +/** + * ESP32: + * - DAC channel 0: GPIO25 + * - DAC channel 1: GPIO26 + * ESP32S2: + * - DAC channel 0: GPIO17 + * - DAC channel 1: GPIO18 + */ + typedef enum { - DAC_CHAN_0 = 0, /*!< DAC channel 0 is GPIO25(ESP32) / GPIO17(ESP32S2) */ - DAC_CHAN_1 = 1, /*!< DAC channel 1 is GPIO26(ESP32) / GPIO18(ESP32S2) */ + DAC_CHAN_0 = 0, + DAC_CHAN_1 = 1, + DAC_CHAN_MAX, /*!< For checking purpose */ } dac_channel_t; +ESP_STATIC_ASSERT(DAC_CHAN_MAX == SOC_DAC_CHAN_NUM, "DAC channel number mismatch"); + +#define IS_VALID_DAC_CHANNEL(channel) ((uint32_t)(channel) < (uint32_t)DAC_CHAN_MAX) + +typedef uint32_t dac_channel_mask_t; + +#define DAC_CHANNEL_MASK_CH0 (BIT(0)) /*!< DAC channel 0 mask */ +#define DAC_CHANNEL_MASK_CH1 (BIT(1)) /*!< DAC channel 1 mask */ +#define DAC_CHANNEL_MASK_ALL ((1ULL << SOC_DAC_CHAN_NUM) - 1) /*!< Bitwise OR of all valid channel masks */ + +/** + * @note 0 is valid + */ +#define IS_VALID_DAC_CHANNEL_MASK(mask) (((mask) & ~DAC_CHANNEL_MASK_ALL) == 0) + +/** + * @brief Use a loop to extract every channel (dac_channel_t) from the mask (dac_channel_mask_t). + */ +#define DAC_CHANNEL_MASK_FOREACH(channel, mask) \ + for (uint32_t __dac_mask = (mask), __dac_chan = DAC_CHAN_0; __dac_chan < DAC_CHAN_MAX; __dac_chan++) \ + for (dac_channel_t channel = (dac_channel_t)__dac_chan; __dac_mask & BIT(__dac_chan); __dac_mask &= ~BIT(__dac_chan)) + /** * @brief The attenuation of the amplitude of the cosine wave generator. The max amplitude is VDD3P3_RTC. */ @@ -35,6 +73,8 @@ typedef enum { DAC_COSINE_PHASE_180 = 0x03, /*!< Phase shift +180° */ } dac_cosine_phase_t; +#endif // SOC_DAC_SUPPORTED + #ifdef __cplusplus } #endif diff --git a/components/esp_hal_gpspi/esp32s2/include/hal/spi_ll.h b/components/esp_hal_gpspi/esp32s2/include/hal/spi_ll.h index 5669114254b..096167ff8e5 100644 --- a/components/esp_hal_gpspi/esp32s2/include/hal/spi_ll.h +++ b/components/esp_hal_gpspi/esp32s2/include/hal/spi_ll.h @@ -86,6 +86,7 @@ typedef enum { SPI_LL_INTR_CMD9 = BIT(12), ///< Has received CMD9 command. Only available in slave HD. SPI_LL_INTR_CMDA = BIT(13), ///< Has received CMDA command. Only available in slave HD. SPI_LL_INTR_SEG_DONE = BIT(14), + SPI_LL_INTR_OUT_DONE = BIT(15), ///< DMA out_done triggered } spi_ll_intr_t; ///< Flags for conditions under which the transaction length should be recorded @@ -356,6 +357,7 @@ static inline void spi_ll_cpu_rx_fifo_reset(spi_dev_t *hw) * * @param hw Beginning address of the peripheral registers. */ +__attribute__((always_inline)) static inline void spi_ll_dma_tx_fifo_reset(spi_dev_t *hw) { hw->dma_conf.val |= SPI_LL_DMA_FIFO_RST_MASK; @@ -369,6 +371,7 @@ static inline void spi_ll_dma_tx_fifo_reset(spi_dev_t *hw) * * @param hw Beginning address of the peripheral registers. */ +__attribute__((always_inline)) static inline void spi_ll_dma_rx_fifo_reset(spi_dev_t *hw) { hw->dma_conf.val |= SPI_LL_DMA_FIFO_RST_MASK; @@ -1126,7 +1129,8 @@ static inline uint32_t spi_ll_slave_get_rcv_bitlen(spi_dev_t *hw) item(SPI_LL_INTR_CMD7, dma_int_ena.cmd7, dma_int_raw.cmd7, dma_int_clr.cmd7=1) \ item(SPI_LL_INTR_CMD8, dma_int_ena.cmd8, dma_int_raw.cmd8, dma_int_clr.cmd8=1) \ item(SPI_LL_INTR_CMD9, dma_int_ena.cmd9, dma_int_raw.cmd9, dma_int_clr.cmd9=1) \ - item(SPI_LL_INTR_CMDA, dma_int_ena.cmda, dma_int_raw.cmda, dma_int_clr.cmda=1) + item(SPI_LL_INTR_CMDA, dma_int_ena.cmda, dma_int_raw.cmda, dma_int_clr.cmda=1) \ + item(SPI_LL_INTR_OUT_DONE, dma_int_ena.out_done, dma_int_raw.out_done, dma_int_clr.out_done=1) __attribute__((always_inline)) static inline void spi_ll_enable_intr(spi_dev_t *hw, spi_ll_intr_t intr_mask) diff --git a/components/esp_hal_i2s/esp32/include/hal/i2s_ll.h b/components/esp_hal_i2s/esp32/include/hal/i2s_ll.h index 26e64ea460c..e30afafc365 100644 --- a/components/esp_hal_i2s/esp32/include/hal/i2s_ll.h +++ b/components/esp_hal_i2s/esp32/include/hal/i2s_ll.h @@ -46,6 +46,7 @@ extern "C" { #define I2S_LL_BCK_MAX_PRESCALE (64) #define I2S_LL_EVENT_RX_EOF BIT(9) +#define I2S_LL_EVENT_TX_DONE BIT(11) #define I2S_LL_EVENT_TX_EOF BIT(12) #define I2S_LL_EVENT_RX_DSCR_ERR BIT(13) #define I2S_LL_EVENT_TX_DSCR_ERR BIT(14) @@ -542,6 +543,7 @@ static inline void i2s_ll_rx_reset_dma(i2s_dev_t *hw) * * @param hw Peripheral I2S hardware instance address. */ +__attribute__((always_inline)) static inline void i2s_ll_start_out_link(i2s_dev_t *hw) { hw->out_link.start = 1; @@ -553,6 +555,7 @@ static inline void i2s_ll_start_out_link(i2s_dev_t *hw) * @param hw Peripheral I2S hardware instance address. * @param val value to set out link address */ +__attribute__((always_inline)) static inline void i2s_ll_set_out_link_addr(i2s_dev_t *hw, uint32_t val) { hw->out_link.addr = val; @@ -584,6 +587,7 @@ static inline void i2s_ll_rx_start(i2s_dev_t *hw) * @param hw Peripheral I2S hardware instance address. * @param link_addr DMA descriptor link address. */ +__attribute__((always_inline)) static inline void i2s_ll_tx_start_link(i2s_dev_t *hw, uint32_t link_addr) { i2s_ll_set_out_link_addr(hw, link_addr); diff --git a/docs/en/api-reference/peripherals/dac.rst b/docs/en/api-reference/peripherals/dac.rst index 7175952e58f..4c76d822cbc 100644 --- a/docs/en/api-reference/peripherals/dac.rst +++ b/docs/en/api-reference/peripherals/dac.rst @@ -44,6 +44,43 @@ DAC channels can convert digital data continuously via the DMA. There are three 2. Cyclical writing: A piece of data can be converted cyclically without blocking, and no more operation is needed after the data are loaded into the DMA buffer. But note that the inputted buffer size is limited by the number of descriptors and the DMA buffer size. It is usually used to transport short signals that need to be repeated, e.g., a sine wave. To achieve cyclical writing, call :cpp:func:`dac_continuous_write_cyclically` after the DAC continuous mode is enabled. Refer to :example:`peripherals/dac/dac_continuous/signal_generator` for examples. 3. Asynchronous writing: Data can be transmitted asynchronously based on the event callback. :cpp:member:`dac_event_callbacks_t::on_convert_done` must be registered to use asynchronous mode. Users can get the :cpp:type:`dac_event_data_t` in the callback which contains the DMA buffer address and length, allowing them to load the data into the buffer directly. To use the asynchronous writing, call :cpp:func:`dac_continuous_register_event_callback` to register the :cpp:member:`dac_event_callbacks_t::on_convert_done` before enabling, and then :cpp:func:`dac_continuous_start_async_writing` to start the asynchronous writing. Note that once the asynchronous writing is started, the callback function will be triggered continuously. Call :cpp:func:`dac_continuous_write_asynchronously` to load the data either in a separate task or in the callback directly. Refer to :example:`peripherals/dac/dac_continuous/dac_audio` for examples. +The following diagram illustrates the life cycle of the DAC continuous driver and the state transitions associated with each API: + +.. mermaid:: + + flowchart TD + NC(["Idle (Initial State)"]) -->|"new_channels()"| REG[Registered] + REG -->|"del_channels()"| NC + REG -->|"enable()"| EN[Enabled] + EN -->|"disable()"| REG + + EN -->|"start_async_writing()"| ASYNC[Async Writing] + ASYNC -->|"stop_async_writing()"| EN + + EN -->|"write_cyclically()"| CYCLIC[Cyclic Writing] + CYCLIC -->|"stop_cyclically()"| EN + + EN -->|"write()"| SYNC[Sync Writing] + SYNC -->|"write()"| SYNC + SYNC -->|"on transmission complete"| EN + + subgraph REG_APIS [Registered State APIs] + REGCB["register_event_callbacks()"] + end + + subgraph ASYNC_APIS [Async Writing State APIs] + AWRITE["write_asynchronously()"] + end + + REG -. can call .-> REGCB + ASYNC -. when receiving a callback .-> AWRITE + +.. note:: + + - For brevity, the prefix ``dac_continuous_`` is omitted from all function names in the diagram. + - For backward compatibility, calling :cpp:func:`dac_continuous_stop_cyclically` to exit cyclic writing is optional — any API that transitions away from the Enabled state will automatically stop an ongoing cyclic conversion. However, explicitly calling :cpp:func:`dac_continuous_stop_cyclically` is recommended. + - Sync writing requires no explicit exit — any API that transitions away from the Enabled state will automatically stop the ongoing sync writing immediately (the data not yet converted is discarded). + .. only:: esp32 On ESP32, the DAC digital controller can be connected internally to the I2S0 and use its DMA for continuous conversion. Although the DAC only needs 8-bit data for conversion, it has to be the left-shifted 8 bits (i.e., the high 8 bits in a 16-bit slot) to satisfy the I2S communication format. By default, the driver helps to expand the data to 16-bit wide automatically. To expand manually, please disable :ref:`CONFIG_DAC_DMA_AUTO_16BIT_ALIGN` in the menuconfig. diff --git a/docs/zh_CN/api-reference/peripherals/dac.rst b/docs/zh_CN/api-reference/peripherals/dac.rst index a2bcf884611..007f3bf62fc 100644 --- a/docs/zh_CN/api-reference/peripherals/dac.rst +++ b/docs/zh_CN/api-reference/peripherals/dac.rst @@ -44,6 +44,43 @@ DAC 通道可以通过 DMA 连续转换数字信号,这种模式下有三种 2. 循环写入:在数据载入 DMA 缓冲区后,缓冲区中的数据将以非阻塞的方式被循环转换。但要注意,输入的缓冲区大小受 DMA 描述符数量和 DMA 缓冲区大小的限制。该模式通常用于传输如正弦波等需要重复的短信号。为了启用循环写入,需要在启用 DAC 连续模式后调用 :cpp:func:`dac_continuous_write_cyclically`。示例可参考 :example:`peripherals/dac/dac_continuous/signal_generator`。 3. 异步写入。可根据事件回调异步传输数据。需要调用 :cpp:member:`dac_event_callbacks_t::on_convert_done` 以启用异步模式。用户在回调中可得到 :cpp:type:`dac_event_data_t`,其中包含 DMA 缓冲区的地址和长度,即允许用户直接将数据载入 DMA 缓冲区。启用异步写入前需要调用 :cpp:func:`dac_continuous_register_event_callback`、 :cpp:member:`dac_event_callbacks_t::on_convert_done` 和 :cpp:func:`dac_continuous_start_async_writing`。注意,异步写入一旦开始,回调函数将被持续触发。调用 :cpp:func:`dac_continuous_write_asynchronously` 可以在某个单独任务中或直接在回调函数中载入数据。示例可参考 :example:`peripherals/dac/dac_continuous/dac_audio`。 +下图展示了连续模式驱动的生命周期,以及各 API 对应的状态转移: + +.. mermaid:: + + flowchart TD + NC([空闲(初始状态)]) -->|"new_channels()"| REG[已注册] + REG -->|"del_channels()"| NC + REG -->|"enable()"| EN[已启用] + EN -->|"disable()"| REG + + EN -->|"start_async_writing()"| ASYNC[异步写入] + ASYNC -->|"stop_async_writing()"| EN + + EN -->|"write_cyclically()"| CYCLIC[循环写入] + CYCLIC -->|"stop_cyclically()"| EN + + EN -->|"write()"| SYNC[同步写入] + SYNC -->|"write()"| SYNC + SYNC -->|"传输完成后"| EN + + subgraph REG_APIS [已注册状态可用 API] + REGCB["register_event_callbacks()"] + end + + subgraph ASYNC_APIS [异步写入可用 API] + AWRITE["write_asynchronously()"] + end + + REG -. 可调用 .-> REGCB + ASYNC -. 收到回调通知后调用 .-> AWRITE + +.. note:: + + - 为了简洁,图中省略了各函数名的前缀 ``dac_continuous_``。 + - 为了向后兼容,循环写入的退出函数 :cpp:func:`dac_continuous_stop_cyclically` 是可选的,所有从已启用状态出发的函数会自动检查并停止任何正在进行的循环写入。推荐显式调用 :cpp:func:`dac_continuous_stop_cyclically` 来停止循环写入。 + - 同步写入无需显式退出,所有从已启用状态出发的函数会自动立即停止任何正在进行的同步写入(尚未转换的数据将被丢弃)。 + .. only:: esp32 在 ESP32 上,DAC 的数字控制器可以在内部连接到 I2S0,并借用其 DMA 进行连续转换。虽然 DAC 转换仅需 8 位数据,但它必须是左移的 8 位(即 16 位中的高 8 位),以满足 I2S 通信格式。默认状态下驱动程序将自动扩充数据至 16 位,如需手动扩充,请在 menuconfig 中禁用 :ref:`CONFIG_DAC_DMA_AUTO_16BIT_ALIGN`。 diff --git a/examples/peripherals/dac/dac_continuous/dac_audio/main/dac_audio_example_main.c b/examples/peripherals/dac/dac_continuous/dac_audio/main/dac_audio_example_main.c index b0ceb64c1b4..ec17382f7d0 100644 --- a/examples/peripherals/dac/dac_continuous/dac_audio/main/dac_audio_example_main.c +++ b/examples/peripherals/dac/dac_continuous/dac_audio/main/dac_audio_example_main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ @@ -49,7 +49,12 @@ static void dac_write_data_asynchronously(dac_continuous_handle_t handle, QueueH /* Clear the legacy data in DMA, clear times equal to the 'dac_continuous_config_t::desc_num' */ for (int i = 0; i < 4; i++) { xQueueReceive(que, &evt_data, portMAX_DELAY); - memset(evt_data.buf, 0, evt_data.buf_size); + /** + * NOTE: Directly manipulating the buffer is allowed, but please be aware of data layout and offset. + * Therefore, it is generally discouraged except in simple clearing cases. + * In this example, the audio data has a global offset of 0x80, so we write 0x80 to clear the buffer. + */ + memset(evt_data.buf, 0x80, evt_data.buf_size); } vTaskDelay(pdMS_TO_TICKS(1000)); } diff --git a/examples/peripherals/dac/dac_continuous/signal_generator/main/dac_continuous_example_dma.c b/examples/peripherals/dac/dac_continuous/signal_generator/main/dac_continuous_example_dma.c index fe3fa3eb650..77aad263f28 100644 --- a/examples/peripherals/dac/dac_continuous/signal_generator/main/dac_continuous_example_dma.c +++ b/examples/peripherals/dac/dac_continuous/signal_generator/main/dac_continuous_example_dma.c @@ -30,6 +30,7 @@ static void dac_dma_write_task(void *args) size_t buf_len = EXAMPLE_ARRAY_LEN; while (1) { + ESP_LOGI(TAG, "%s wave start", wav_name[wav_sel]); /* The wave in the buffer will be converted cyclically */ switch (wav_sel) { case DAC_SINE_WAVE: @@ -49,9 +50,9 @@ static void dac_dma_write_task(void *args) } /* Switch wave every CONFIG_EXAMPLE_WAVE_PERIOD_SEC seconds */ vTaskDelay(pdMS_TO_TICKS(CONFIG_EXAMPLE_WAVE_PERIOD_SEC * 1000)); + ESP_ERROR_CHECK(dac_continuous_stop_cyclically(handle)); wav_sel++; wav_sel %= DAC_WAVE_MAX; - ESP_LOGI(TAG, "%s wave start", wav_name[wav_sel]); } } diff --git a/examples/peripherals/dac/dac_continuous/signal_generator/pytest_dac_continuous.py b/examples/peripherals/dac/dac_continuous/signal_generator/pytest_dac_continuous.py index d64cc10568c..c43400bcbee 100644 --- a/examples/peripherals/dac/dac_continuous/signal_generator/pytest_dac_continuous.py +++ b/examples/peripherals/dac/dac_continuous/signal_generator/pytest_dac_continuous.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut @@ -7,18 +7,18 @@ from pytest_embedded_idf.utils import idf_parametrize def test_dac_continuous_output(dut: Dut, mode: str, chan0_io: str, chan1_io: str) -> None: dut.expect('dac continuous: --------------------------------------------------', timeout=10) - dut.expect('dac continuous: DAC continuous output by {}'.format(mode), timeout=10) - dut.expect('dac continuous: DAC channel 0 io: GPIO_NUM_{}'.format(chan0_io), timeout=10) - dut.expect('dac continuous: DAC channel 1 io: GPIO_NUM_{}'.format(chan1_io), timeout=10) + dut.expect(f'dac continuous: DAC continuous output by {mode}', timeout=10) + dut.expect(f'dac continuous: DAC channel 0 io: GPIO_NUM_{chan0_io}', timeout=10) + dut.expect(f'dac continuous: DAC channel 1 io: GPIO_NUM_{chan1_io}', timeout=10) dut.expect('dac continuous: Waveform: SINE -> TRIANGLE -> SAWTOOTH -> SQUARE', timeout=10) dut.expect('dac continuous: DAC conversion frequency \\(Hz\\): ([0-9]+)', timeout=10) dut.expect('dac continuous: DAC wave frequency \\(Hz\\): ([0-9]+)', timeout=10) dut.expect('dac continuous: --------------------------------------------------', timeout=10) dut.expect(r'DAC channel 0 value:( +)(\d+)(.*)DAC channel 1 value:( +)(\d+)', timeout=10) - dut.expect(r'dac continuous\({}\): triangle wave start'.format(mode), timeout=20) - dut.expect(r'dac continuous\({}\): sawtooth wave start'.format(mode), timeout=20) - dut.expect(r'dac continuous\({}\): square wave start'.format(mode), timeout=20) - dut.expect(r'dac continuous\({}\): sine wave start'.format(mode), timeout=20) + dut.expect(rf'dac continuous\({mode}\): sine wave start', timeout=20) + dut.expect(rf'dac continuous\({mode}\): triangle wave start', timeout=20) + dut.expect(rf'dac continuous\({mode}\): sawtooth wave start', timeout=20) + dut.expect(rf'dac continuous\({mode}\): square wave start', timeout=20) @pytest.mark.generic diff --git a/examples/peripherals/dac/dac_oneshot/pytest_dac_oneshot.py b/examples/peripherals/dac/dac_oneshot/pytest_dac_oneshot.py index 3a44d53282f..952323cd0cc 100644 --- a/examples/peripherals/dac/dac_oneshot/pytest_dac_oneshot.py +++ b/examples/peripherals/dac/dac_oneshot/pytest_dac_oneshot.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut @@ -12,22 +12,12 @@ def test_dac_oneshot_example(dut: Dut) -> None: for _ in range(10): res.append(dut.expect(r'DAC channel 0 value:( +)(\d+)(.*)DAC channel 1 value:( +)(\d+)', timeout=10)) - avg1_ch1 = 0 - avg1_ch2 = 0 - avg2_ch1 = 0 - avg2_ch2 = 0 + avg1_ch0 = sum(int(val.group(2)) for val in res[0:5]) / 5 + avg1_ch1 = sum(int(val.group(5)) for val in res[0:5]) / 5 + avg2_ch0 = sum(int(val.group(2)) for val in res[5:10]) / 5 + avg2_ch1 = sum(int(val.group(5)) for val in res[5:10]) / 5 - for val in res[0:5]: - avg1_ch1 = avg1_ch1 + int(val.group(2)) - avg1_ch2 = avg1_ch2 + int(val.group(5)) - for val in res[5:10]: - avg2_ch1 = avg1_ch1 + int(val.group(2)) - avg2_ch2 = avg1_ch2 + int(val.group(5)) - - avg1_ch1 = int(avg1_ch1 / 5) - avg1_ch2 = int(avg1_ch2 / 5) - avg2_ch1 = int(avg2_ch1 / 5) - avg2_ch2 = int(avg2_ch2 / 5) - - assert avg2_ch1 > avg1_ch1 - assert avg2_ch2 > avg1_ch2 + assert avg2_ch0 > avg1_ch0 + # On ESP32-S2 CI runners, GPIO18 (DAC ch1) has an LED attached. The voltage is clamped. + if dut.target != 'esp32s2': + assert avg2_ch1 > avg1_ch1