Merge branch 'refactor/driver_dac_common_v6.1' into 'release/v6.1'

refactor(dac): improve the DAC driver (v6.1)

See merge request espressif/esp-idf!50237
This commit is contained in:
morris
2026-07-03 11:16:57 +08:00
23 changed files with 931 additions and 482 deletions
+1 -1
View File
@@ -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
+52 -51
View File
@@ -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 <stdint.h>
#include <string.h>
#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;
}
}
File diff suppressed because it is too large Load Diff
+3 -4
View File
@@ -5,7 +5,6 @@
*/
#include <string.h>
#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;
+4 -5
View File
@@ -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 <string.h>
#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;
+7 -8
View File
@@ -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);
+24 -15
View File
@@ -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
}
+12 -17
View File
@@ -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);
}
+16 -11
View File
@@ -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);
}
@@ -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
+15
View File
@@ -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)
@@ -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 <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#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));
@@ -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:
*
+10
View File
@@ -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) {
@@ -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
@@ -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)
@@ -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);
+37
View File
@@ -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.
@@ -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`。
@@ -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));
}
@@ -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]);
}
}
@@ -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
@@ -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