Merge branch 'refactor/driver_dac_common' into 'master'

refactor(dac): improve the DAC driver

See merge request espressif/esp-idf!48212
This commit is contained in:
Hu Rui
2026-07-01 13:36:23 +08:00
23 changed files with 927 additions and 488 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,77 +15,78 @@
#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,
};
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);