mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-06 22:24:33 +02:00
dac: optimize the dma stratege
This commit is contained in:
@@ -103,18 +103,20 @@ if(CONFIG_SOC_TOUCH_SENSOR_SUPPORTED)
|
||||
"${target}/touch_sensor.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_DAC_SUPPORTED)
|
||||
list(APPEND srcs "dac/dac_driver.c"
|
||||
"dac/${target}/dac_dma.c"
|
||||
"deprecated/dac_common_legacy.c"
|
||||
"deprecated/${target}/dac_legacy.c")
|
||||
list(APPEND includes "deprecated/${target}")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_SDIO_SLAVE_SUPPORTED)
|
||||
list(APPEND srcs "sdio_slave.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_DAC_SUPPORTED)
|
||||
list(APPEND srcs "dac/dac_oneshot.c"
|
||||
"dac/dac_cosine.c"
|
||||
"dac/dac_conti.c"
|
||||
"dac/dac_common.c"
|
||||
"dac/${target}/dac_dma.c"
|
||||
"deprecated/dac_common_legacy.c"
|
||||
"deprecated/${target}/dac_legacy.c")
|
||||
endif()
|
||||
|
||||
if(${target} STREQUAL "esp32")
|
||||
list(APPEND srcs "deprecated/adc_i2s_deprecated.c")
|
||||
endif()
|
||||
|
@@ -405,7 +405,7 @@ menu "Driver Configurations"
|
||||
bool "Place DAC control functions into IRAM"
|
||||
default n
|
||||
help
|
||||
Place 'dac_channels_set_voltage' function into IRAM,
|
||||
Place DAC control functions (e.g. 'dac_oneshot_output_voltage') into IRAM,
|
||||
so that this function can be IRAM-safe and able to be called in the other IRAM interrupt context.
|
||||
Enabling this option can improve driver performance as well.
|
||||
|
||||
@@ -420,7 +420,7 @@ menu "Driver Configurations"
|
||||
bool "Suppress legacy driver deprecated warning"
|
||||
default n
|
||||
help
|
||||
Wether to suppress the deprecation warnings when using legacy DAC driver (driver/DAC.h).
|
||||
Wether to suppress the deprecation warnings when using legacy DAC driver (driver/dac.h).
|
||||
If you want to continue using the legacy driver, and don't want to see related deprecation warnings,
|
||||
you can enable this option.
|
||||
|
||||
@@ -433,7 +433,7 @@ menu "Driver Configurations"
|
||||
|
||||
config DAC_DMA_AUTO_16BIT_ALIGN
|
||||
bool "Align the continuous data to 16 bit automatically"
|
||||
depends on IDF_TARGET_ESP32
|
||||
depends on SOC_DAC_DMA_16BIT_ALIGN
|
||||
default y
|
||||
help
|
||||
Whether to left shift the continuous data to align every bytes to 16 bits in the driver.
|
||||
|
98
components/driver/dac/dac_common.c
Normal file
98
components/driver/dac/dac_common.c
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "soc/dac_periph.h"
|
||||
#include "hal/dac_types.h"
|
||||
#include "hal/dac_ll.h"
|
||||
#include "driver/rtc_io.h"
|
||||
#include "esp_check.h"
|
||||
#include "dac_priv_common.h"
|
||||
|
||||
typedef struct {
|
||||
bool in_use;
|
||||
bool is_enabled;
|
||||
const char *mode;
|
||||
} dac_channel_info_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,
|
||||
}
|
||||
};
|
||||
/* 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_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;
|
||||
} else {
|
||||
ret = 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;
|
||||
} else {
|
||||
ret = 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");
|
||||
|
||||
gpio_num_t gpio_num = (gpio_num_t)dac_periph_signal.dac_channel_io_num[chan_id];
|
||||
rtc_gpio_init(gpio_num);
|
||||
rtc_gpio_set_direction(gpio_num, RTC_GPIO_MODE_DISABLED);
|
||||
rtc_gpio_pullup_dis(gpio_num);
|
||||
rtc_gpio_pulldown_dis(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;
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
gpio_num_t gpio_num = (gpio_num_t)dac_periph_signal.dac_channel_io_num[chan_id];
|
||||
rtc_gpio_deinit(gpio_num);
|
||||
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;
|
||||
}
|
652
components/driver/dac/dac_conti.c
Normal file
652
components/driver/dac/dac_conti.c
Normal file
@@ -0,0 +1,652 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
#include <sys/queue.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include "rom/lldesc.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "driver/dac_conti.h"
|
||||
|
||||
#include "dac_priv_common.h"
|
||||
#include "dac_priv_dma.h"
|
||||
|
||||
#include "esp_check.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
|
||||
#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
|
||||
#define DAC_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
|
||||
#endif
|
||||
|
||||
#if CONFIG_DAC_ISR_IRAM_SAFE
|
||||
#define DAC_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED)
|
||||
#else
|
||||
#define DAC_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED)
|
||||
#endif
|
||||
|
||||
#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_conti_s {
|
||||
uint32_t chan_cnt;
|
||||
dac_conti_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;
|
||||
StaticSemaphore_t mutex_struct; /* Static mutex struct */
|
||||
|
||||
QueueHandle_t desc_pool; /* The pool of available descriptors
|
||||
* The descriptors in the pool are not linked in to pending chain */
|
||||
StaticQueue_t desc_pool_struct; /* Static message queue struct */
|
||||
void *desc_pool_storage; /* Static message queue storage */
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
static const char *TAG = "dac_conti";
|
||||
|
||||
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 void s_dac_free_dma_desc(dac_conti_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->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;
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t s_dac_alloc_dma_desc(dac_conti_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\n", 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;
|
||||
}
|
||||
|
||||
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_conti_handle_t handle = (dac_conti_handle_t)arg;
|
||||
uint32_t dummy;
|
||||
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);
|
||||
need_awoke |= tmp;
|
||||
}
|
||||
if (handle->cbs.on_convert_done) {
|
||||
dac_event_data_t evt_data = {
|
||||
.buf = (void *)fdesc->buf,
|
||||
.buf_size = handle->cfg.buf_size,
|
||||
.write_bytes = fdesc->length,
|
||||
};
|
||||
need_awoke |= handle->cbs.on_convert_done(handle, &evt_data, handle->user_data);
|
||||
}
|
||||
}
|
||||
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) {
|
||||
need_awoke |= handle->cbs.on_stop(handle, NULL, handle->user_data);
|
||||
}
|
||||
}
|
||||
if (need_awoke == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
esp_err_t dac_new_conti_channels(const dac_conti_config_t *conti_cfg, dac_conti_handle_t *ret_handle)
|
||||
{
|
||||
#if CONFIG_DAC_ENABLE_DEBUG_LOG
|
||||
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
#endif
|
||||
/* Parameters validation */
|
||||
DAC_NULL_POINTER_CHECK(conti_cfg);
|
||||
DAC_NULL_POINTER_CHECK(ret_handle);
|
||||
ESP_RETURN_ON_FALSE(conti_cfg->chan_mask <= DAC_CHANNEL_MASK_ALL, ESP_ERR_INVALID_ARG, TAG, "invalid dac channel id");
|
||||
ESP_RETURN_ON_FALSE(conti_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_err_t ret = ESP_OK;
|
||||
|
||||
/* Register the channels */
|
||||
for (uint32_t i = 0, mask = conti_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);
|
||||
}
|
||||
}
|
||||
|
||||
/* Allocate continuous mode struct */
|
||||
dac_conti_handle_t handle = heap_caps_calloc(1, sizeof(struct dac_conti_s), DAC_MEM_ALLOC_CAPS);
|
||||
ESP_RETURN_ON_FALSE(handle, ESP_ERR_NO_MEM, TAG, "no memory for the dac continuous mode structure");
|
||||
|
||||
/* Allocate static queue */
|
||||
handle->desc_pool_storage = (uint8_t *)heap_caps_calloc(conti_cfg->desc_num, sizeof(lldesc_t *), DAC_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(handle->desc_pool_storage, ESP_ERR_NO_MEM, err3, TAG, "no memory for message queue storage");
|
||||
handle->desc_pool = xQueueCreateStatic(conti_cfg->desc_num, sizeof(lldesc_t *), handle->desc_pool_storage, &handle->desc_pool_struct);
|
||||
ESP_GOTO_ON_FALSE(handle->desc_pool, ESP_ERR_NO_MEM, err3, TAG, "no memory for message queue");
|
||||
|
||||
/* Allocate static mutex */
|
||||
handle->mutex = xSemaphoreCreateMutexStatic(&handle->mutex_struct);
|
||||
ESP_GOTO_ON_FALSE(handle->mutex, ESP_ERR_NO_MEM, err3, TAG, "no memory for channels mutex");
|
||||
|
||||
/* Create PM lock */
|
||||
#if CONFIG_PM_ENABLE
|
||||
esp_pm_lock_type_t pm_lock_type = conti_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(conti_cfg->chan_mask);
|
||||
memcpy(&(handle->cfg), conti_cfg, sizeof(dac_conti_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");
|
||||
|
||||
/* Initialize DAC DMA peripheral */
|
||||
ESP_GOTO_ON_ERROR(dac_dma_periph_init(conti_cfg->freq_hz,
|
||||
conti_cfg->chan_mode == DAC_CHANNEL_MODE_ALTER,
|
||||
conti_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;
|
||||
*ret_handle = handle;
|
||||
return ret;
|
||||
|
||||
err1:
|
||||
dac_dma_periph_deinit();
|
||||
err2:
|
||||
s_dac_free_dma_desc(handle);
|
||||
err3:
|
||||
if (handle->desc_pool) {
|
||||
vQueueDelete(handle->desc_pool);
|
||||
}
|
||||
if (handle->desc_pool_storage) {
|
||||
free(handle->desc_pool_storage);
|
||||
}
|
||||
if (handle->mutex) {
|
||||
vSemaphoreDelete(handle->mutex);
|
||||
}
|
||||
free(handle);
|
||||
err4:
|
||||
/* Deregister the channels */
|
||||
for (uint32_t i = 0, mask = conti_cfg->chan_mask; mask; mask >>= 1, i++) {
|
||||
if (mask & 0x01) {
|
||||
dac_priv_deregister_channel(i);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t dac_del_conti_channels(dac_conti_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");
|
||||
|
||||
/* 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);
|
||||
DAC_RTC_EXIT_CRITICAL();
|
||||
|
||||
/* Free allocated resources */
|
||||
s_dac_free_dma_desc(handle);
|
||||
if (handle->desc_pool) {
|
||||
vQueueDelete(handle->desc_pool);
|
||||
handle->desc_pool = NULL;
|
||||
}
|
||||
if (handle->desc_pool_storage) {
|
||||
free(handle->desc_pool_storage);
|
||||
handle->desc_pool_storage = NULL;
|
||||
}
|
||||
if (handle->mutex) {
|
||||
vSemaphoreDelete(handle->mutex);
|
||||
handle->mutex = NULL;
|
||||
}
|
||||
#if CONFIG_PM_ENABLE
|
||||
if (handle->pm_lock) {
|
||||
esp_pm_lock_delete(handle->pm_lock);
|
||||
handle->pm_lock = NULL;
|
||||
}
|
||||
#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);
|
||||
}
|
||||
}
|
||||
free(handle);
|
||||
s_dma_in_use = false;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_conti_register_event_callback(dac_conti_handle_t handle, const dac_event_callbacks_t *callbacks, void *user_data)
|
||||
{
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
if (!callbacks) {
|
||||
memset(&handle->cbs, 0, sizeof(dac_event_callbacks_t));
|
||||
return ESP_OK;
|
||||
}
|
||||
#if CONFIG_DAC_ISR_IRAM_SAFE
|
||||
if (callbacks->on_convert_done) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_convert_done), ESP_ERR_INVALID_ARG, TAG, "on_convert_done callback not in IRAM");
|
||||
}
|
||||
if (callbacks->on_stop) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_stop), ESP_ERR_INVALID_ARG, TAG, "on_stop callback not in IRAM");
|
||||
}
|
||||
if (user_data) {
|
||||
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->user_data = user_data;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_conti_enable(dac_conti_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");
|
||||
}
|
||||
#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_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;
|
||||
}
|
||||
|
||||
esp_err_t dac_conti_disable(dac_conti_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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
#ifdef CONFIG_PM_ENABLE
|
||||
esp_pm_lock_release(handle->pm_lock);
|
||||
#endif
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_conti_start_async_writing(dac_conti_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++) {
|
||||
handle->desc[i]->empty = 0;
|
||||
}
|
||||
}
|
||||
/* Wait for the previous DMA stop */
|
||||
while (atomic_load(&handle->is_running)) {}
|
||||
|
||||
/* 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);
|
||||
handle->desc[i]->empty = (uint32_t)(i < handle->cfg.desc_num - 1 ? handle->desc[i + 1] : handle->desc[0]);
|
||||
}
|
||||
dac_dma_periph_dma_trans_start((uint32_t)handle->desc[0]);
|
||||
atomic_store(&handle->is_running, true);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_conti_stop_async_writing(dac_conti_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++) {
|
||||
handle->desc[i]->empty = 0;
|
||||
}
|
||||
/* Wait for the previous DMA stop */
|
||||
while (atomic_load(&handle->is_running)) {}
|
||||
atomic_store(&handle->is_async, false);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Buffer expanding coefficient, the input buffer will expand to twice length while enabled AUTO_16_BIT */
|
||||
#if CONFIG_DAC_DMA_AUTO_16BIT_ALIGN
|
||||
#define DAC_16BIT_ALIGN_COEFF 2
|
||||
#else
|
||||
#define DAC_16BIT_ALIGN_COEFF 1
|
||||
#endif
|
||||
|
||||
static size_t s_dac_load_data_into_buf(dac_conti_handle_t handle, uint8_t *dest, size_t dest_len, const uint8_t *src, size_t src_len)
|
||||
{
|
||||
size_t load_bytes = 0;
|
||||
#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;
|
||||
}
|
||||
#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;
|
||||
}
|
||||
#endif
|
||||
return load_bytes;
|
||||
}
|
||||
|
||||
esp_err_t dac_conti_write_asynchronously(dac_conti_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]) {
|
||||
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);
|
||||
if (bytes_loaded) {
|
||||
*bytes_loaded = load_bytes / DAC_16BIT_ALIGN_COEFF;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_conti_write_cyclically(dac_conti_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);
|
||||
|
||||
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++) {
|
||||
handle->desc[i]->empty = 0;
|
||||
}
|
||||
}
|
||||
/* 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 */
|
||||
handle->desc[i]->empty = (uint32_t)(i < handle->cfg.desc_num - 1 ? handle->desc[i + 1] :0);
|
||||
buf_size -= load_bytes / DAC_16BIT_ALIGN_COEFF;
|
||||
buf += load_bytes / DAC_16BIT_ALIGN_COEFF;
|
||||
}
|
||||
/* Link the tail to the head as a ring */
|
||||
handle->desc[i-1]->empty = (uint32_t)(handle->desc[0]);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
xSemaphoreGive(handle->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t s_dac_wait_to_load_dma_data(dac_conti_handle_t handle, uint8_t *buf, size_t buf_size, size_t *w_size, TickType_t timeout_tick)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_conti_write(dac_conti_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_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++) {
|
||||
handle->desc[i]->empty = 0;
|
||||
xQueueSend(handle->desc_pool, &handle->desc[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");
|
||||
}
|
||||
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;
|
||||
}
|
||||
err:
|
||||
/* The bytes number that has been loaded */
|
||||
if (bytes_loaded) {
|
||||
*bytes_loaded = src_buf_size - buf_size;
|
||||
}
|
||||
xSemaphoreGive(handle->mutex);
|
||||
return ret;
|
||||
}
|
154
components/driver/dac/dac_cosine.c
Normal file
154
components/driver/dac/dac_cosine.c
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "soc/soc_caps.h"
|
||||
#include "driver/dac_cosine.h"
|
||||
#include "hal/clk_tree_ll.h"
|
||||
#include "dac_priv_common.h"
|
||||
#include "clk_ctrl_os.h"
|
||||
#include "esp_check.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
|
||||
#if CONFIG_PM_ENABLE
|
||||
#include "esp_pm.h"
|
||||
#endif
|
||||
|
||||
struct dac_cosine_s {
|
||||
dac_cosine_config_t cfg; /*!< Cosine mode configurations */
|
||||
bool is_started; /*!< Flag: is the channel started(not cosine wave generator) */
|
||||
};
|
||||
|
||||
static const char *TAG = "dac_cosine";
|
||||
|
||||
/* Cosine wave generator reference count
|
||||
* The cosine wave generator is shared by dac channels */
|
||||
static uint32_t s_cwg_refer_cnt = 0;
|
||||
|
||||
/* The frequency of cosine wave generator */
|
||||
static uint32_t s_cwg_freq = 0;
|
||||
|
||||
esp_err_t dac_new_cosine_channel(const dac_cosine_config_t *cos_cfg, dac_cosine_handle_t *ret_handle)
|
||||
{
|
||||
#if CONFIG_DAC_ENABLE_DEBUG_LOG
|
||||
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
#endif
|
||||
/* 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(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");
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
/* Allocate cosine handle */
|
||||
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));
|
||||
/* 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);
|
||||
|
||||
/* Only enabled for getting the correct rtc clock frequency */
|
||||
periph_rtc_dig_clk8m_enable();
|
||||
/* Cosine wave generator uses RTC_FAST clock which is divided from RC_FAST */
|
||||
// [clk_tree] TODO: replace the following calculation with the RTC_FAST frequency getter
|
||||
uint32_t rtc_clk_freq = periph_rtc_dig_clk8m_get_freq() / clk_ll_rc_fast_get_divider();
|
||||
/* Disabled after getting the frequency, will re-enabled again when start outputting cosine wave */
|
||||
periph_rtc_dig_clk8m_disable();
|
||||
if (rtc_clk_freq == 0) {
|
||||
ESP_LOGW(TAG, "RTC clock calibration failed, using the approximate value as default");
|
||||
rtc_clk_freq = SOC_CLK_RC_FAST_FREQ_APPROX;
|
||||
}
|
||||
DAC_RTC_ENTER_CRITICAL();
|
||||
/* Set coefficients for cosine wave generator */
|
||||
if ((!s_cwg_freq) || cos_cfg->flags.force_set_freq) {
|
||||
dac_ll_cw_set_freq(cos_cfg->freq_hz, rtc_clk_freq);
|
||||
s_cwg_freq = cos_cfg->freq_hz;
|
||||
}
|
||||
dac_ll_cw_set_atten(cos_cfg->chan_id, cos_cfg->atten);
|
||||
dac_ll_cw_set_phase(cos_cfg->chan_id, cos_cfg->phase);
|
||||
dac_ll_cw_set_dc_offset(cos_cfg->chan_id, cos_cfg->offset);
|
||||
DAC_RTC_EXIT_CRITICAL();
|
||||
|
||||
*ret_handle = handle;
|
||||
return ret;
|
||||
|
||||
err1:
|
||||
free(handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t dac_del_cosine_channel(dac_cosine_handle_t handle)
|
||||
{
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
ESP_RETURN_ON_FALSE(!handle->is_started, ESP_ERR_INVALID_STATE, TAG,
|
||||
"the dac cosine generator is not stopped yet");
|
||||
|
||||
ESP_RETURN_ON_ERROR(dac_priv_deregister_channel(handle->cfg.chan_id), TAG,
|
||||
"deregister dac channel %d failed", handle->cfg.chan_id);
|
||||
/* Clear the frequency if no channel using it */
|
||||
if (!s_cwg_refer_cnt) {
|
||||
s_cwg_freq = 0;
|
||||
}
|
||||
free(handle);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_cosine_start(dac_cosine_handle_t handle)
|
||||
{
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
ESP_RETURN_ON_FALSE(!handle->is_started, ESP_ERR_INVALID_STATE, TAG,
|
||||
"the dac channel has already started");
|
||||
/* Acquire the RTC clock */
|
||||
periph_rtc_dig_clk8m_enable();
|
||||
/* Enabled DAC channel */
|
||||
ESP_RETURN_ON_ERROR(dac_priv_enable_channel(handle->cfg.chan_id), TAG,
|
||||
"enable dac channel %d failed", handle->cfg.chan_id);
|
||||
/* Enabled the cosine wave generator if no channel using it before */
|
||||
DAC_RTC_ENTER_CRITICAL();
|
||||
if (s_cwg_refer_cnt == 0) {
|
||||
dac_ll_cw_generator_enable();
|
||||
}
|
||||
/* Connect the DAC channel to the cosine wave generator */
|
||||
dac_ll_cw_enable_channel(handle->cfg.chan_id, true);
|
||||
s_cwg_refer_cnt++;
|
||||
handle->is_started = true;
|
||||
DAC_RTC_EXIT_CRITICAL();
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_cosine_stop(dac_cosine_handle_t handle)
|
||||
{
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
ESP_RETURN_ON_FALSE(handle->is_started, ESP_ERR_INVALID_STATE, TAG,
|
||||
"the dac channel has already stopped");
|
||||
|
||||
/* Enabled DAC channel */
|
||||
ESP_RETURN_ON_ERROR(dac_priv_disable_channel(handle->cfg.chan_id), TAG,
|
||||
"disable dac channel %d failed", handle->cfg.chan_id);
|
||||
DAC_RTC_ENTER_CRITICAL();
|
||||
/* Disconnect the DAC channel from the cosine wave generator */
|
||||
dac_ll_cw_enable_channel(handle->cfg.chan_id, false);
|
||||
s_cwg_refer_cnt--;
|
||||
/* Disable the cosine wave generator if no channel using it */
|
||||
if (s_cwg_refer_cnt == 0) {
|
||||
dac_ll_cw_generator_disable();
|
||||
}
|
||||
handle->is_started = false;
|
||||
DAC_RTC_EXIT_CRITICAL();
|
||||
/* Release the RTC clock */
|
||||
periph_rtc_dig_clk8m_disable();
|
||||
|
||||
return ESP_OK;
|
||||
}
|
@@ -1,781 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/queue.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include "rom/lldesc.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "soc/dac_periph.h"
|
||||
#include "hal/dac_types.h"
|
||||
#include "hal/dac_ll.h"
|
||||
#include "driver/rtc_io.h"
|
||||
#include "driver/dac_driver.h"
|
||||
#include "dac_dma.h"
|
||||
#include "esp_memory_utils.h"
|
||||
#include "clk_ctrl_os.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
|
||||
#define DAC_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
|
||||
#endif
|
||||
|
||||
#if CONFIG_DAC_ISR_IRAM_SAFE
|
||||
#define DAC_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED)
|
||||
#else
|
||||
#define DAC_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED)
|
||||
#endif
|
||||
|
||||
#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)
|
||||
|
||||
typedef enum {
|
||||
DAC_STATE_INITIAL, /* Initial state, dac has been registered */
|
||||
DAC_STATE_OUTPUT_READY, /* DAC channels enabled and ready to output constant voltage */
|
||||
DAC_STATE_DMA_READY, /* DMA mode initialized, but not started */
|
||||
DAC_STATE_CW_READY, /* Cosine wave mode is initialized, but not started */
|
||||
DAC_STATE_DMA_ENABLED, /* DAC DMA dma is enabled, can't switch mode in this stage */
|
||||
DAC_STATE_CW_RUNNING, /* DAC cosine wave generator is running, can't switch mode in this stage */
|
||||
} dac_state_t;
|
||||
|
||||
typedef struct {
|
||||
QueueHandle_t desc_pool; /* The pool of available descriptors
|
||||
* The descriptors in the pool are not linked in to pending chain */
|
||||
#if CONFIG_DAC_ISR_IRAM_SAFE
|
||||
StaticQueue_t *desc_pool_struct; /* Static message queue struct */
|
||||
void *desc_pool_storage; /* Static message queue storage */
|
||||
#endif
|
||||
dac_conti_config_t cfg;
|
||||
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 */
|
||||
lldesc_t **desc;
|
||||
uint8_t **bufs;
|
||||
volatile bool is_running; /* Is DMA running or stopped */
|
||||
volatile bool is_cyclic; /* Is transport data cyclicly */
|
||||
} dac_dma_t;
|
||||
|
||||
typedef struct dac_channel_chain_node_s {
|
||||
dac_channel_t id;
|
||||
SLIST_ENTRY(dac_channel_chain_node_s) next;
|
||||
} dac_channel_info_t;
|
||||
|
||||
typedef SLIST_HEAD(dac_channel_chain_s, dac_channel_chain_node_s) dac_channel_chain_t;
|
||||
|
||||
struct dac_channels_s {
|
||||
uint32_t chan_num;
|
||||
dac_state_t state;
|
||||
SemaphoreHandle_t mutex;
|
||||
StaticSemaphore_t *mutex_struct; /*!< Static mutex struct */
|
||||
#if CONFIG_PM_ENABLE
|
||||
esp_pm_lock_handle_t pm_lock;
|
||||
#endif
|
||||
bool is_enabled;
|
||||
dac_dma_t dma;
|
||||
dac_cosine_config_t sw_cfg;
|
||||
dac_channel_chain_t head;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
dac_channel_info_t *chan[SOC_DAC_PERIPH_NUM];
|
||||
bool dma_in_use;
|
||||
} dac_platform_t;
|
||||
|
||||
static const char *TAG = "DAC";
|
||||
|
||||
static dac_platform_t s_dac = {
|
||||
.chan = {
|
||||
[0 ... SOC_DAC_PERIPH_NUM - 1] = NULL,
|
||||
},
|
||||
.dma_in_use = false,
|
||||
};
|
||||
|
||||
/* Global dac spin lock for the whole DAC driver */
|
||||
portMUX_TYPE dac_spinlock = portMUX_INITIALIZER_UNLOCKED; // TODO: check rtc_spinlock
|
||||
|
||||
#define DAC_NULL_POINTER_CHECK(p) ESP_RETURN_ON_FALSE((p), ESP_ERR_INVALID_ARG, TAG, "input parameter '"#p"' is NULL")
|
||||
|
||||
static void dac_free_dma_desc(dac_channels_handle_t handle)
|
||||
{
|
||||
STAILQ_EMPTY(&handle->dma.head);
|
||||
if (handle->dma.desc != NULL) {
|
||||
for (int i = 0; i < handle->dma.cfg.desc_num; i++) {
|
||||
if (handle->dma.desc[i]) {
|
||||
free(handle->dma.desc[i]);
|
||||
handle->dma.desc[i] = NULL;
|
||||
}
|
||||
}
|
||||
free(handle->dma.desc);
|
||||
handle->dma.desc = NULL;
|
||||
}
|
||||
|
||||
if (handle->dma.bufs != NULL) {
|
||||
for (int i = 0; i < handle->dma.cfg.desc_num; i++) {
|
||||
if (handle->dma.bufs[i]) {
|
||||
free(handle->dma.bufs[i]);
|
||||
handle->dma.bufs[i] = NULL;
|
||||
}
|
||||
}
|
||||
free(handle->dma.bufs);
|
||||
handle->dma.bufs = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t dac_alloc_dma_desc(dac_channels_handle_t handle)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
STAILQ_INIT(&handle->dma.head);
|
||||
handle->dma.desc = (lldesc_t **) heap_caps_calloc(handle->dma.cfg.desc_num, sizeof(lldesc_t *), DAC_MEM_ALLOC_CAPS);
|
||||
ESP_RETURN_ON_FALSE(handle->dma.desc, ESP_ERR_NO_MEM, TAG, "Faild to allocate dma descriptor array");
|
||||
handle->dma.bufs = (uint8_t **) heap_caps_calloc(handle->dma.cfg.desc_num, sizeof(uint8_t *), DAC_MEM_ALLOC_CAPS);
|
||||
ESP_RETURN_ON_FALSE(handle->dma.bufs, ESP_ERR_NO_MEM, TAG, "Faild to allocate dma buffer array");
|
||||
for (int cnt = 0; cnt < handle->dma.cfg.desc_num; cnt++) {
|
||||
/* Allocate DMA descpriptor */
|
||||
handle->dma.desc[cnt] = (lldesc_t *) heap_caps_calloc(1, sizeof(lldesc_t), DAC_DMA_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(handle->dma.desc[cnt], ESP_ERR_NO_MEM, err, TAG, "failed to allocate dma descriptor");
|
||||
/* Allocate DMA buffer */
|
||||
handle->dma.bufs[cnt] = (uint8_t *) heap_caps_calloc(1, handle->dma.cfg.buf_size, DAC_DMA_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(handle->dma.desc[cnt], ESP_ERR_NO_MEM, err, TAG, "failed to allocate dma buffer");
|
||||
/* Assign initial value */
|
||||
lldesc_config(handle->dma.desc[cnt], LLDESC_HW_OWNED, 1, 0, handle->dma.cfg.buf_size);
|
||||
handle->dma.desc[cnt]->size = handle->dma.cfg.buf_size;
|
||||
handle->dma.desc[cnt]->buf = handle->dma.bufs[cnt];
|
||||
handle->dma.desc[cnt]->offset = 0;
|
||||
xQueueSend(handle->dma.desc_pool, &handle->dma.desc[cnt], portMAX_DELAY);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
err:
|
||||
/* Free DMA buffer if failed to allocate memory */
|
||||
dac_free_dma_desc(handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR dac_default_intr_handler(void *arg)
|
||||
{
|
||||
dac_channels_handle_t handle = (dac_channels_handle_t)arg;
|
||||
uint32_t dummy;
|
||||
BaseType_t need_awoke = pdFALSE;
|
||||
BaseType_t tmp;
|
||||
uint32_t intr_mask = dac_dma_periph_intr_is_triggered();
|
||||
if ((intr_mask & DAC_DMA_EOF_INTR) && (!handle->dma.is_cyclic)) {
|
||||
lldesc_t *fdesc = (lldesc_t *)dac_dma_periph_intr_get_eof_desc();
|
||||
/* Remove the descriptor in the chain that finished sent */
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
if (STAILQ_FIRST(&handle->dma.head) != NULL) {
|
||||
DAC_STAILQ_REMOVE(&handle->dma.head, fdesc, lldesc_s, qe);
|
||||
}
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
if (xQueueIsQueueFullFromISR(handle->dma.desc_pool) == pdTRUE) {
|
||||
xQueueReceiveFromISR(handle->dma.desc_pool, &dummy, &tmp);
|
||||
need_awoke |= tmp;
|
||||
}
|
||||
xQueueSendFromISR(handle->dma.desc_pool, &fdesc, &tmp);
|
||||
need_awoke |= tmp;
|
||||
}
|
||||
if (intr_mask & DAC_DMA_TEOF_INTR) {
|
||||
/* Total end of frame interrupt received, DMA stopped */
|
||||
handle->dma.is_running = false;
|
||||
}
|
||||
if (need_awoke == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------
|
||||
DAC common APIs
|
||||
---------------------------------------------------------------------------*/
|
||||
esp_err_t dac_new_channels(const dac_channels_config_t *dac_cfg, dac_channels_handle_t *handle)
|
||||
{
|
||||
#if CONFIG_DAC_ENABLE_DEBUG_LOG
|
||||
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
#endif
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
DAC_NULL_POINTER_CHECK(dac_cfg);
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
|
||||
ESP_RETURN_ON_FALSE((uint32_t)dac_cfg->chan_sel > 0, ESP_ERR_INVALID_ARG, TAG, "invalid DAC channel");
|
||||
ESP_RETURN_ON_FALSE((32 - __builtin_clz((uint32_t)dac_cfg->chan_sel)) <= SOC_DAC_PERIPH_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid DAC channel");
|
||||
|
||||
dac_channels_handle_t channels = (dac_channels_handle_t)heap_caps_calloc(1, sizeof(struct dac_channels_s), DAC_MEM_ALLOC_CAPS);
|
||||
ESP_RETURN_ON_FALSE(channels, ESP_ERR_NO_MEM, TAG, "No memory for DAC channels' structure");
|
||||
|
||||
channels->mutex_struct = (StaticSemaphore_t *)heap_caps_calloc(1, sizeof(StaticSemaphore_t), DAC_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(channels->mutex_struct, ESP_ERR_NO_MEM, err, TAG, "No memory for channels mutex struct");
|
||||
channels->mutex = xSemaphoreCreateMutexStatic(channels->mutex_struct);
|
||||
ESP_GOTO_ON_FALSE(channels->mutex, ESP_ERR_NO_MEM, err, TAG, "No memory for channels mutex");
|
||||
channels->chan_num = 0;
|
||||
channels->is_enabled = false;
|
||||
channels->state = DAC_STATE_INITIAL; // Set static output as default
|
||||
/* Register selected channels and link into a chain*/
|
||||
SLIST_INIT(&channels->head);
|
||||
for (uint32_t msk = (uint32_t)dac_cfg->chan_sel, i = 0; msk != 0; msk >>= 1, i++) {
|
||||
if (msk & 0x01) {
|
||||
/* Allocate memory for the channel when it is enabled */
|
||||
ESP_GOTO_ON_FALSE(!s_dac.chan[i], ESP_ERR_INVALID_STATE, err, TAG, "DAC channel %d has been registered already", i + 1);
|
||||
dac_channel_info_t *node = (dac_channel_info_t *)calloc(1, sizeof(dac_channel_info_t));
|
||||
ESP_GOTO_ON_FALSE(node, ESP_ERR_NO_MEM, err, TAG, "No memory for DAC channel object");
|
||||
node->id = i;
|
||||
s_dac.chan[i] = node;
|
||||
channels->chan_num++;
|
||||
/* Add the channel node into a chain */
|
||||
SLIST_INSERT_HEAD(&channels->head, node, next);
|
||||
}
|
||||
}
|
||||
|
||||
*handle = channels;
|
||||
|
||||
return ret;
|
||||
|
||||
err:
|
||||
/* Free the resource when error occurs */
|
||||
dac_del_channels(channels);
|
||||
channels = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t dac_del_channels(dac_channels_handle_t handle)
|
||||
{
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
ESP_RETURN_ON_FALSE(handle->state == DAC_STATE_INITIAL, ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels is not deinitialized");
|
||||
ESP_RETURN_ON_FALSE(!handle->is_enabled, ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels is not disabled");
|
||||
|
||||
if (handle->mutex) {
|
||||
vSemaphoreDelete(handle->mutex);
|
||||
}
|
||||
if (handle->mutex_struct) {
|
||||
free(handle->mutex_struct);
|
||||
}
|
||||
while (SLIST_FIRST(&handle->head)) {
|
||||
dac_channel_info_t *node = SLIST_FIRST(&handle->head);
|
||||
SLIST_REMOVE_HEAD(&handle->head, next);
|
||||
s_dac.chan[node->id] = NULL;
|
||||
free(node);
|
||||
}
|
||||
free(handle);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_channels_enable(dac_channels_handle_t handle)
|
||||
{
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
ESP_RETURN_ON_FALSE(!handle->is_enabled, ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels has been enabled already");
|
||||
|
||||
xSemaphoreTake(handle->mutex, portMAX_DELAY);
|
||||
|
||||
dac_channel_info_t *p;
|
||||
SLIST_FOREACH(p, &handle->head, next) {
|
||||
gpio_num_t gpio_num = (gpio_num_t)dac_periph_signal.dac_channel_io_num[p->id];
|
||||
rtc_gpio_init(gpio_num);
|
||||
rtc_gpio_set_direction(gpio_num, RTC_GPIO_MODE_DISABLED);
|
||||
rtc_gpio_pullup_dis(gpio_num);
|
||||
rtc_gpio_pulldown_dis(gpio_num);
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
dac_ll_power_on(p->id);
|
||||
dac_ll_rtc_sync_by_adc(false);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
}
|
||||
handle->is_enabled = true;
|
||||
/* If the channels has not been intialized to other mode, set it `DAC_STATE_OUTPUT_READY` as default */
|
||||
if (handle->state == DAC_STATE_INITIAL) {
|
||||
handle->state = DAC_STATE_OUTPUT_READY;
|
||||
}
|
||||
xSemaphoreGive(handle->mutex);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_channels_disable(dac_channels_handle_t handle)
|
||||
{
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
ESP_RETURN_ON_FALSE(handle->is_enabled, ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels has not been enabled yet");
|
||||
ESP_RETURN_ON_FALSE(handle->state < DAC_STATE_DMA_ENABLED, ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels is still running");
|
||||
|
||||
xSemaphoreTake(handle->mutex, portMAX_DELAY);
|
||||
dac_channel_info_t *p;
|
||||
SLIST_FOREACH(p, &handle->head, next) {
|
||||
gpio_num_t gpio_num = (gpio_num_t)dac_periph_signal.dac_channel_io_num[p->id];
|
||||
rtc_gpio_deinit(gpio_num);
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
dac_ll_power_down(p->id);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
}
|
||||
handle->is_enabled = false;
|
||||
/* If the channels has not been intialized to other mode, set it `DAC_STATE_INITIAL` as default */
|
||||
if (handle->state == DAC_STATE_OUTPUT_READY) {
|
||||
handle->state = DAC_STATE_INITIAL;
|
||||
}
|
||||
xSemaphoreGive(handle->mutex);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------
|
||||
DAC direct voltage outputting APIs
|
||||
---------------------------------------------------------------------------*/
|
||||
#if CONFIG_DAC_ISR_IRAM_SAFE
|
||||
IRAM_ATTR
|
||||
#endif
|
||||
esp_err_t dac_channels_set_voltage(dac_channels_handle_t handle, uint8_t value)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE_ISR(handle, ESP_ERR_INVALID_ARG, TAG, "NULL pointer");
|
||||
ESP_RETURN_ON_FALSE_ISR(handle->is_enabled, ESP_ERR_INVALID_STATE, TAG, "Not enabled");
|
||||
ESP_RETURN_ON_FALSE_ISR(handle->state == DAC_STATE_OUTPUT_READY, ESP_ERR_INVALID_STATE, TAG, "Incorrect mode");
|
||||
|
||||
/* Set the voltage for each channel in the channels */
|
||||
dac_channel_info_t *p;
|
||||
SLIST_FOREACH(p, &handle->head, next) {
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
dac_ll_update_output_value(p->id, value);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------
|
||||
DAC continuous mode APIs
|
||||
---------------------------------------------------------------------------*/
|
||||
esp_err_t dac_channels_init_continuous_mode(dac_channels_handle_t handle, const dac_conti_config_t *conti_cfg)
|
||||
{
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
ESP_RETURN_ON_FALSE(handle->state < DAC_STATE_DMA_READY, ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels has been initialized already");
|
||||
ESP_RETURN_ON_FALSE(conti_cfg->desc_num > 1, ESP_ERR_INVALID_STATE, TAG, "At least two DMA descriptor needed");
|
||||
ESP_RETURN_ON_FALSE(!s_dac.dma_in_use, ESP_ERR_INVALID_STATE, TAG, "Only one set of channels can use DMA output");
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
xSemaphoreTake(handle->mutex, portMAX_DELAY);
|
||||
|
||||
#if CONFIG_DAC_ISR_IRAM_SAFE
|
||||
handle->dma.desc_pool_storage = (uint8_t *)heap_caps_calloc(conti_cfg->desc_num, sizeof(lldesc_t *), DAC_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(handle->dma.desc_pool_storage, ESP_ERR_NO_MEM, err3, TAG, "No memory for message queue storage");
|
||||
handle->dma.desc_pool_struct = (StaticQueue_t *)heap_caps_calloc(1, sizeof(StaticQueue_t), DAC_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(handle->dma.desc_pool_struct , ESP_ERR_NO_MEM, err3, TAG, "No memory for message queue struct");
|
||||
handle->dma.desc_pool = xQueueCreateStatic(conti_cfg->desc_num, sizeof(lldesc_t *), handle->dma.desc_pool_storage, handle->dma.desc_pool_struct);
|
||||
#else
|
||||
handle->dma.desc_pool = xQueueCreate(conti_cfg->desc_num, sizeof(lldesc_t *));
|
||||
#endif
|
||||
ESP_GOTO_ON_FALSE(handle->dma.desc_pool, ESP_ERR_NO_MEM, err3, TAG, "No memory for message queue");
|
||||
#if CONFIG_PM_ENABLE
|
||||
esp_pm_lock_type_t pm_lock_type = conti_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->dma.is_running = false;
|
||||
/* Allocate DMA buffer */
|
||||
memcpy(&(handle->dma.cfg), conti_cfg, sizeof(dac_conti_config_t));
|
||||
ESP_GOTO_ON_ERROR(dac_alloc_dma_desc(handle), err2, TAG, "Failed to allocate memory for DMA buffers");
|
||||
|
||||
/* Initialize DAC DMA peripheral */
|
||||
ESP_GOTO_ON_ERROR(dac_dma_periph_init(handle->chan_num ,conti_cfg->freq_hz, conti_cfg->chan_mode == DAC_CHANNEL_MODE_ALTER, conti_cfg->clk_src == DAC_DIGI_CLK_SRC_APLL),
|
||||
err2, TAG, "Failed to initialize DAC DMA peripheral");
|
||||
/* Register DMA interrupt */
|
||||
ESP_GOTO_ON_ERROR(dac_dma_periph_register_intr(dac_default_intr_handler, handle), err1, TAG, "Failed to register DMA interrupt");
|
||||
/* Connect DAC module to the DMA peripheral */
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
dac_ll_digi_enable_dma(true);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
|
||||
handle->state = DAC_STATE_DMA_READY;
|
||||
s_dac.dma_in_use = true;
|
||||
|
||||
xSemaphoreGive(handle->mutex);
|
||||
|
||||
return ret;
|
||||
err1:
|
||||
dac_dma_periph_deinit();
|
||||
err2:
|
||||
dac_free_dma_desc(handle);
|
||||
err3:
|
||||
if (handle->dma.desc_pool) {
|
||||
vQueueDelete(handle->dma.desc_pool);
|
||||
}
|
||||
#if CONFIG_DAC_ISR_IRAM_SAFE
|
||||
if (handle->dma.desc_pool_struct) {
|
||||
free(handle->dma.desc_pool_struct);
|
||||
}
|
||||
if (handle->dma.desc_pool_storage) {
|
||||
free(handle->dma.desc_pool_storage);
|
||||
}
|
||||
#endif
|
||||
xSemaphoreGive(handle->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t dac_channels_deinit_continuous_mode(dac_channels_handle_t handle)
|
||||
{
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
ESP_RETURN_ON_FALSE(handle->state == DAC_STATE_DMA_READY, ESP_ERR_INVALID_STATE, TAG,
|
||||
"This set of DAC channels is still running or has been configured to other mode");
|
||||
|
||||
xSemaphoreTake(handle->mutex, portMAX_DELAY);
|
||||
|
||||
#if CONFIG_DAC_ISR_IRAM_SAFE
|
||||
free(handle->dma.desc_pool_storage);
|
||||
handle->dma.desc_pool_storage = NULL;
|
||||
free(handle->dma.desc_pool_struct);
|
||||
handle->dma.desc_pool_struct = NULL;
|
||||
#endif
|
||||
#if CONFIG_PM_ENABLE
|
||||
if (handle->pm_lock) {
|
||||
esp_pm_lock_delete(handle->pm_lock);
|
||||
handle->pm_lock = NULL;
|
||||
}
|
||||
#endif
|
||||
vQueueDelete(handle->dma.desc_pool);
|
||||
|
||||
/* Free DMA buffer */
|
||||
dac_free_dma_desc(handle);
|
||||
|
||||
/* Deregister DMA interrupt */
|
||||
ESP_RETURN_ON_ERROR(dac_dma_periph_deregister_intr(), TAG, "Failed to deregister DMA interrupt");
|
||||
/* Deinitialize DMA peripheral */
|
||||
ESP_RETURN_ON_ERROR(dac_dma_periph_deinit(), TAG, "Failed to deinitialize DAC DMA peripheral");
|
||||
/* Disconnect DAC module to the DMA peripheral */
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
dac_ll_digi_enable_dma(false);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
|
||||
if (handle->is_enabled) {
|
||||
handle->state = DAC_STATE_OUTPUT_READY;
|
||||
} else {
|
||||
handle->state = DAC_STATE_INITIAL;
|
||||
}
|
||||
s_dac.dma_in_use = false;
|
||||
xSemaphoreGive(handle->mutex);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_channels_enable_continuous_mode(dac_channels_handle_t handle)
|
||||
{
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
ESP_RETURN_ON_FALSE(handle->is_enabled, ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels has not been enabled");
|
||||
ESP_RETURN_ON_FALSE(handle->state == DAC_STATE_DMA_READY, ESP_ERR_INVALID_STATE, TAG,
|
||||
"This set of DAC channels has started already or not working at DMA mode");
|
||||
|
||||
xSemaphoreTake(handle->mutex, portMAX_DELAY);
|
||||
#ifdef CONFIG_PM_ENABLE
|
||||
esp_pm_lock_acquire(handle->pm_lock);
|
||||
#endif
|
||||
dac_dma_periph_enable();
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
dac_ll_digi_enable_dma(true);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
handle->state = DAC_STATE_DMA_ENABLED;
|
||||
xSemaphoreGive(handle->mutex);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_channels_disable_continuous_mode(dac_channels_handle_t handle)
|
||||
{
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
ESP_RETURN_ON_FALSE(handle->is_enabled, ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels has not been enabled");
|
||||
ESP_RETURN_ON_FALSE(handle->state == DAC_STATE_DMA_ENABLED, ESP_ERR_INVALID_STATE, TAG,
|
||||
"This set of DAC channels has stopped already or not working at DMA mode");
|
||||
|
||||
xSemaphoreTake(handle->mutex, portMAX_DELAY);
|
||||
dac_dma_periph_disable();
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
dac_ll_digi_enable_dma(false);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
#ifdef CONFIG_PM_ENABLE
|
||||
esp_pm_lock_release(handle->pm_lock);
|
||||
#endif
|
||||
handle->state = DAC_STATE_DMA_READY;
|
||||
xSemaphoreGive(handle->mutex);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t s_dac_load_dma_data(dac_channels_handle_t handle, uint8_t *buf, size_t buf_size, size_t *w_size, uint32_t timeout_ms)
|
||||
{
|
||||
lldesc_t *desc = NULL;
|
||||
|
||||
/* Try to get the descriptor from the pool */
|
||||
ESP_RETURN_ON_FALSE(xQueueReceive(handle->dma.desc_pool, &desc, pdMS_TO_TICKS(timeout_ms)) == pdTRUE,
|
||||
ESP_ERR_TIMEOUT, TAG, "Get available descriptor timeout");
|
||||
if (STAILQ_FIRST(&handle->dma.head) != NULL) {
|
||||
DAC_STAILQ_REMOVE(&handle->dma.head, desc, lldesc_s, qe);
|
||||
}
|
||||
|
||||
/* Get the DMA buffer address (use 'memcpy' to avoid the 'volatile' warning) */
|
||||
uint8_t *dma_buf;
|
||||
memcpy(&dma_buf, &desc->buf, sizeof(desc->buf));
|
||||
#if SOC_DAC_DMA_16BIT_ALIGN && CONFIG_DAC_DMA_AUTO_16BIT_ALIGN
|
||||
/* Load the data to the high 8 bit in the 16-bit width slot */
|
||||
size_t _w_size = (buf_size > handle->dma.cfg.buf_size / 2) ? handle->dma.cfg.buf_size / 2 : buf_size;
|
||||
for (int i = 0; i < _w_size; i++) {
|
||||
dma_buf[2 * i + 1] = buf[i];
|
||||
}
|
||||
lldesc_config(desc, LLDESC_HW_OWNED, 1, 0, _w_size * 2);
|
||||
desc->size = _w_size * 2;
|
||||
#else
|
||||
/* Load the data into the DMA buffer */
|
||||
size_t _w_size = (buf_size > handle->dma.cfg.buf_size) ? handle->dma.cfg.buf_size : buf_size;
|
||||
memcpy(dma_buf, buf, _w_size);
|
||||
lldesc_config(desc, LLDESC_HW_OWNED, 1, 0, _w_size);
|
||||
desc->size = _w_size;
|
||||
#endif
|
||||
*w_size = _w_size;
|
||||
/* Insert the loaded descriptor to the end of the chain, waiting to be sent */
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
STAILQ_INSERT_TAIL(&handle->dma.head, desc, qe);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_channels_write_continuously(dac_channels_handle_t handle, uint8_t *buf, size_t buf_size, size_t *bytes_loaded, uint32_t timeout_ms)
|
||||
{
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
DAC_NULL_POINTER_CHECK(buf);
|
||||
ESP_RETURN_ON_FALSE(handle->is_enabled, ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels has not been enabled");
|
||||
ESP_RETURN_ON_FALSE(handle->state == DAC_STATE_DMA_ENABLED, ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels is not started");
|
||||
#if CONFIG_DAC_ISR_IRAM_SAFE
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_internal(buf), ESP_ERR_INVALID_ARG, TAG, "the buffer is not in internal RAM");
|
||||
#endif
|
||||
esp_err_t ret = ESP_OK;
|
||||
ESP_RETURN_ON_FALSE(xSemaphoreTake(handle->mutex, pdMS_TO_TICKS(timeout_ms) == pdTRUE), ESP_ERR_TIMEOUT, TAG, "Take semaphore timeout");
|
||||
|
||||
size_t w_size = 0;
|
||||
size_t src_buf_size = buf_size;
|
||||
/* When there is no descriptor in the chain, DMA has stopped, load data and start the DMA link */
|
||||
if (STAILQ_FIRST(&handle->dma.head) == NULL) {
|
||||
/* Break the chain if DMA still running */
|
||||
for (int i = 0; handle->dma.is_running && i < handle->dma.cfg.desc_num; i++) {
|
||||
handle->dma.desc[i]->empty = 0;
|
||||
}
|
||||
for (int i = 0;
|
||||
i < handle->dma.cfg.desc_num && buf_size > 0;
|
||||
i++, buf += w_size, buf_size -= w_size) {
|
||||
ESP_GOTO_ON_ERROR(s_dac_load_dma_data(handle, buf, buf_size, &w_size, timeout_ms), err, TAG, "Load data failed");
|
||||
}
|
||||
/* Wait for the previous DMA stop */
|
||||
while (handle->dma.is_running) {}
|
||||
handle->dma.is_cyclic = false;
|
||||
dac_dma_periph_dma_trans_start((uint32_t)(STAILQ_FIRST(&handle->dma.head)));
|
||||
handle->dma.is_running = true;
|
||||
}
|
||||
/* If the source buffer is not totally loaded, keep loading the rest data */
|
||||
while (buf_size > 0) {
|
||||
/* If the DMA stopped but there are still some decriptors not sent, start the DMA again */
|
||||
if ((!handle->dma.is_running) && STAILQ_FIRST(&handle->dma.head)) {
|
||||
dac_dma_periph_dma_trans_start((uint32_t)(STAILQ_FIRST(&handle->dma.head)));
|
||||
}
|
||||
ESP_GOTO_ON_ERROR(s_dac_load_dma_data(handle, buf, buf_size, &w_size, timeout_ms), err, TAG, "Load data failed");
|
||||
buf += w_size;
|
||||
buf_size -= w_size;
|
||||
}
|
||||
err:
|
||||
/* The bytes number that has been loaded */
|
||||
if (bytes_loaded) {
|
||||
*bytes_loaded = src_buf_size - buf_size;
|
||||
}
|
||||
xSemaphoreGive(handle->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t dac_channels_write_cyclically(dac_channels_handle_t handle, uint8_t *buf, size_t buf_size, size_t *bytes_loaded, uint32_t timeout_ms)
|
||||
{
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
ESP_RETURN_ON_FALSE(handle->is_enabled, ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels has not been enabled");
|
||||
ESP_RETURN_ON_FALSE(handle->state == DAC_STATE_DMA_ENABLED, ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels is not started");
|
||||
#if CONFIG_DAC_ISR_IRAM_SAFE
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_internal(buf), ESP_ERR_INVALID_ARG, TAG, "the buffer is not in internal RAM");
|
||||
#endif
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
ESP_RETURN_ON_FALSE(xSemaphoreTake(handle->mutex, pdMS_TO_TICKS(timeout_ms) == pdTRUE), ESP_ERR_TIMEOUT, TAG, "Take semaphore timeout");
|
||||
|
||||
/* Break the DMA descriptor chain to stop the DMA first */
|
||||
for (int i = 0; i < handle->dma.cfg.desc_num; i++) {
|
||||
handle->dma.desc[i]->empty = 0;
|
||||
}
|
||||
size_t src_buf_size = buf_size;
|
||||
|
||||
/* If the source buffer size is smaller than the DMA buffer size, load the source buffer into two descriptors */
|
||||
if (buf_size < handle->dma.cfg.buf_size) {
|
||||
/* Load data */
|
||||
size_t half_size = buf_size / 2;
|
||||
memcpy(handle->dma.bufs[0], buf, half_size);
|
||||
memcpy(handle->dma.bufs[1], buf, buf_size - half_size);
|
||||
lldesc_config(handle->dma.desc[0], LLDESC_HW_OWNED, 1, 0, half_size);
|
||||
lldesc_config(handle->dma.desc[1], LLDESC_HW_OWNED, 1, 0, buf_size - half_size);
|
||||
buf_size = 0;
|
||||
/* Link as a circle */
|
||||
handle->dma.desc[0]->empty = (uint32_t)handle->dma.desc[1];
|
||||
handle->dma.desc[1]->empty = (uint32_t)handle->dma.desc[0];
|
||||
} else {
|
||||
int i;
|
||||
for (i = 0; i < handle->dma.cfg.desc_num && buf_size > 0; i++) {
|
||||
size_t w_size = buf_size > handle->dma.cfg.buf_size ? handle->dma.cfg.buf_size : buf_size;
|
||||
memcpy(handle->dma.bufs[i], buf, w_size);
|
||||
lldesc_config(handle->dma.desc[i], LLDESC_HW_OWNED, 1, 0, w_size);
|
||||
handle->dma.desc[i]->empty = (uint32_t)(i < handle->dma.cfg.desc_num - 1 ? handle->dma.desc[i+1] :0);
|
||||
buf_size -= w_size;
|
||||
buf += w_size;
|
||||
}
|
||||
handle->dma.desc[i-1]->empty = (uint32_t)(handle->dma.desc[0]);
|
||||
}
|
||||
/* Wait for the previous DMA stop */
|
||||
while (handle->dma.is_running) {}
|
||||
handle->dma.is_cyclic = true;
|
||||
dac_dma_periph_dma_trans_start((uint32_t)handle->dma.desc[0]);
|
||||
handle->dma.is_running = true;
|
||||
if (bytes_loaded) {
|
||||
*bytes_loaded = src_buf_size - buf_size;
|
||||
}
|
||||
if (buf_size > 0) {
|
||||
ESP_LOGW(TAG, "The cyclic buffer size exceeds the total DMA buffer size: %d(desc_num) * %d(buf_size) = %d",
|
||||
handle->dma.cfg.desc_num, handle->dma.cfg.buf_size, handle->dma.cfg.buf_size * handle->dma.cfg.desc_num);
|
||||
}
|
||||
|
||||
xSemaphoreGive(handle->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------
|
||||
DAC cosine wave outputting APIs
|
||||
---------------------------------------------------------------------------*/
|
||||
esp_err_t dac_channels_init_cosine_mode(dac_channels_handle_t handle, const dac_cosine_config_t *cw_cfg)
|
||||
{
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
DAC_NULL_POINTER_CHECK(cw_cfg);
|
||||
ESP_RETURN_ON_FALSE((handle->state == DAC_STATE_INITIAL) | (handle->state == DAC_STATE_OUTPUT_READY),
|
||||
ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels has been initialized already");
|
||||
ESP_RETURN_ON_FALSE(cw_cfg->freq_hz >= 130, ESP_ERR_NOT_SUPPORTED, TAG, "The cosine wave generator doesn't support frequency below 130 Hz");
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
xSemaphoreTake(handle->mutex, portMAX_DELAY);
|
||||
#if CONFIG_PM_ENABLE
|
||||
ESP_GOTO_ON_ERROR(esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "dac_driver", &handle->pm_lock), err, TAG, "Failed to create DAC pm lock");
|
||||
#endif
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
dac_channel_info_t *p;
|
||||
SLIST_FOREACH(p, &handle->head, next) {
|
||||
/* Connect DAC module to cosine wave generator */
|
||||
dac_ll_cw_set_channel(p->id, true);
|
||||
/* Set coefficients for cosine wave generator */
|
||||
dac_ll_cw_set_freq(cw_cfg->freq_hz, periph_rtc_dig_clk8m_get_freq());
|
||||
dac_ll_cw_set_scale(p->id, cw_cfg->scale);
|
||||
dac_ll_cw_set_phase(p->id, cw_cfg->phase == DAC_COSINE_PHASE_180 ? DAC_LL_CW_PHASE_180 : DAC_LL_CW_PHASE_0);
|
||||
dac_ll_cw_set_dc_offset(p->id, cw_cfg->offset);
|
||||
}
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
handle->state = DAC_STATE_CW_READY;
|
||||
#if CONFIG_PM_ENABLE
|
||||
err:
|
||||
#endif
|
||||
xSemaphoreGive(handle->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t dac_channels_deinit_cosine_mode(dac_channels_handle_t handle)
|
||||
{
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
ESP_RETURN_ON_FALSE(handle->state == DAC_STATE_CW_READY, ESP_ERR_INVALID_STATE, TAG,
|
||||
"This set of DAC channels is still running or not working at cosine wave mode");
|
||||
|
||||
xSemaphoreTake(handle->mutex, portMAX_DELAY);
|
||||
#if CONFIG_PM_ENABLE
|
||||
if (handle->pm_lock) {
|
||||
esp_pm_lock_delete(handle->pm_lock);
|
||||
handle->pm_lock = NULL;
|
||||
}
|
||||
#endif
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
dac_channel_info_t *p;
|
||||
SLIST_FOREACH(p, &handle->head, next) {
|
||||
/* Disonnect DAC module to cosine wave generator */
|
||||
dac_ll_cw_set_channel(p->id, false);
|
||||
}
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
if (handle->is_enabled) {
|
||||
handle->state = DAC_STATE_OUTPUT_READY;
|
||||
} else {
|
||||
handle->state = DAC_STATE_INITIAL;
|
||||
}
|
||||
xSemaphoreGive(handle->mutex);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_channels_start_cosine_output(dac_channels_handle_t handle)
|
||||
{
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
ESP_RETURN_ON_FALSE(handle->is_enabled, ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels has not been enabled");
|
||||
ESP_RETURN_ON_FALSE(handle->state == DAC_STATE_CW_READY, ESP_ERR_INVALID_STATE, TAG,
|
||||
"This set of DAC channels has started already or not working at cosine wave mode");
|
||||
|
||||
xSemaphoreTake(handle->mutex, portMAX_DELAY);
|
||||
#ifdef CONFIG_PM_ENABLE
|
||||
esp_pm_lock_acquire(handle->pm_lock);
|
||||
#endif
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
dac_ll_cw_generator_enable();
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
handle->state = DAC_STATE_CW_RUNNING;
|
||||
xSemaphoreGive(handle->mutex);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_channels_stop_cosine_output(dac_channels_handle_t handle)
|
||||
{
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
ESP_RETURN_ON_FALSE(handle->is_enabled, ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels has not been enabled");
|
||||
ESP_RETURN_ON_FALSE(handle->state == DAC_STATE_CW_RUNNING, ESP_ERR_INVALID_STATE, TAG,
|
||||
"This set of DAC channels has stopped already or not working at cosine wave mode");
|
||||
|
||||
xSemaphoreTake(handle->mutex, portMAX_DELAY);
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
dac_ll_cw_generator_disable();
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
#ifdef CONFIG_PM_ENABLE
|
||||
esp_pm_lock_release(handle->pm_lock);
|
||||
#endif
|
||||
handle->state = DAC_STATE_CW_READY;
|
||||
xSemaphoreGive(handle->mutex);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
84
components/driver/dac/dac_oneshot.c
Normal file
84
components/driver/dac/dac_oneshot.c
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 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"
|
||||
#include "esp_check.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
|
||||
#if CONFIG_PM_ENABLE
|
||||
#include "esp_pm.h"
|
||||
#endif
|
||||
|
||||
struct dac_oneshot_s {
|
||||
dac_oneshot_config_t cfg; /*!< Oneshot mode configurations */
|
||||
};
|
||||
|
||||
static const char *TAG = "dac_oneshot";
|
||||
|
||||
esp_err_t dac_new_oneshot_channel(const dac_oneshot_config_t *oneshot_cfg, dac_oneshot_handle_t *ret_handle)
|
||||
{
|
||||
#if CONFIG_DAC_ENABLE_DEBUG_LOG
|
||||
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
#endif
|
||||
/* 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_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));
|
||||
|
||||
/* 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_enable_channel(oneshot_cfg->chan_id), err1, TAG, "enable dac channel %d failed", oneshot_cfg->chan_id);
|
||||
|
||||
*ret_handle = handle;
|
||||
return ret;
|
||||
|
||||
err1:
|
||||
dac_priv_deregister_channel(oneshot_cfg->chan_id);
|
||||
err2:
|
||||
free(handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t dac_del_oneshot_channel(dac_oneshot_handle_t handle)
|
||||
{
|
||||
DAC_NULL_POINTER_CHECK(handle);
|
||||
|
||||
/* Disable and deregister the channel */
|
||||
ESP_RETURN_ON_ERROR(dac_priv_disable_channel(handle->cfg.chan_id), TAG, "disable dac channel %d failed", handle->cfg.chan_id);
|
||||
ESP_RETURN_ON_ERROR(dac_priv_deregister_channel(handle->cfg.chan_id), TAG, "deregister dac channel %d failed", handle->cfg.chan_id);
|
||||
|
||||
/* Free resources */
|
||||
free(handle);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_oneshot_output_voltage(dac_oneshot_handle_t handle, uint8_t digi_value)
|
||||
{
|
||||
if (!handle) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Set the voltage by the digital value */
|
||||
DAC_RTC_ENTER_CRITICAL_SAFE();
|
||||
dac_ll_update_output_value(handle->cfg.chan_id, digi_value);
|
||||
DAC_RTC_EXIT_CRITICAL_SAFE();
|
||||
|
||||
return ESP_OK;
|
||||
}
|
76
components/driver/dac/dac_priv_common.h
Normal file
76
components/driver/dac/dac_priv_common.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "hal/dac_types.h"
|
||||
#include "hal/dac_ll.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern portMUX_TYPE rtc_spinlock; /*!< Extern global rtc spinlock */
|
||||
|
||||
#define DAC_RTC_ENTER_CRITICAL() portENTER_CRITICAL(&rtc_spinlock)
|
||||
#define DAC_RTC_EXIT_CRITICAL() portEXIT_CRITICAL(&rtc_spinlock)
|
||||
|
||||
#define DAC_RTC_ENTER_CRITICAL_SAFE() portENTER_CRITICAL_SAFE(&rtc_spinlock)
|
||||
#define DAC_RTC_EXIT_CRITICAL_SAFE() portEXIT_CRITICAL_SAFE(&rtc_spinlock)
|
||||
|
||||
#define DAC_NULL_POINTER_CHECK(p) ESP_RETURN_ON_FALSE((p), ESP_ERR_INVALID_ARG, TAG, "input parameter '"#p"' is NULL")
|
||||
#define DAC_NULL_POINTER_CHECK_ISR(p) ESP_RETURN_ON_FALSE_ISR((p), ESP_ERR_INVALID_ARG, TAG, "input parameter '"#p"' is NULL")
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @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_ARG The channel id is incorrect
|
||||
* - ESP_OK Deregister the channel success
|
||||
*/
|
||||
esp_err_t dac_priv_deregister_channel(dac_channel_t chan_id);
|
||||
|
||||
/**
|
||||
* @brief Enable the DAC channel and turn on its power
|
||||
*
|
||||
* @param chan_id DAC channel id
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_STATE The channel has not been registered
|
||||
* - ESP_ERR_INVALID_ARG The channel id is incorrect
|
||||
* - ESP_OK Deregister the channel success
|
||||
*/
|
||||
esp_err_t dac_priv_enable_channel(dac_channel_t chan_id);
|
||||
|
||||
/**
|
||||
* @brief Disable the DAC channel and turn off its power
|
||||
*
|
||||
* @param chan_id DAC channel id
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_STATE The channel has not been registered
|
||||
* - ESP_ERR_INVALID_ARG The channel id is incorrect
|
||||
* - ESP_OK Deregister the channel success
|
||||
*/
|
||||
esp_err_t dac_priv_disable_channel(dac_channel_t chan_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@@ -26,39 +26,28 @@ extern "C" {
|
||||
* @return
|
||||
* - ESP_ERR_NOT_FOUND The DMA peripheral has been occupied
|
||||
* - ESP_ERR_NO_MEM No memory for the DMA peripheral struct
|
||||
* - ESP_ERR_INVALID_ARG The frequency is outof range
|
||||
* - ESP_ERR_INVALID_ARG The frequency is out of range
|
||||
* - ESP_OK Initialize DAC DMA peripheral success
|
||||
*/
|
||||
esp_err_t dac_dma_periph_init(uint8_t chan_num, uint32_t freq_hz, bool is_alternate, bool is_apll);
|
||||
esp_err_t dac_dma_periph_init(uint32_t freq_hz, bool is_alternate, bool is_apll);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize DAC DMA peripheral
|
||||
*
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_STATE The DAC DMA has deinitialized already
|
||||
* - ESP_ERR_INVALID_STATE The DAC DMA has been de-initialized already
|
||||
* or the interrupt has not been de-registered
|
||||
* - ESP_OK Deinitialize DAC DMA peripheral success
|
||||
*/
|
||||
esp_err_t dac_dma_periph_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief Register the DMA interrupt
|
||||
*
|
||||
* @param[in] intr_handler_func DMA interrupt handler function
|
||||
* @param[in] user_ctx User contex that pass to the interrupt handler
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_STATE DAC DMA peripheral is not initialized
|
||||
* - ESP_OK Register DMA interrupt success
|
||||
*/
|
||||
esp_err_t dac_dma_periph_register_intr(intr_handler_t intr_handler_func, void *user_ctx);
|
||||
|
||||
/**
|
||||
* @brief Deregister the DMA interrupt
|
||||
* @brief Get the DMA interrupt signal id
|
||||
*
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_STATE DAC DMA peripheral is not initialized
|
||||
* - ESP_OK Deregister DMA interrupt success
|
||||
* - int DMA interrupt signal id
|
||||
*/
|
||||
esp_err_t dac_dma_periph_deregister_intr(void);
|
||||
int dac_dma_periph_get_intr_signal(void);
|
||||
|
||||
/**
|
||||
* @brief Enable the DMA and interrupt of the DAC DMA peripheral
|
||||
@@ -76,8 +65,7 @@ void dac_dma_periph_disable(void);
|
||||
* @brief Whether the TX_EOF interrupt is triggered
|
||||
*
|
||||
* @return
|
||||
* - true TX_EOF interrupt is triggered
|
||||
* - false TX_EOF interrupt is not triggered
|
||||
* - uint32_t Mask of the triggered interrupt: DAC_DMA_EOF_INTR, DAC_DMA_EOF_INTR
|
||||
*/
|
||||
uint32_t dac_dma_periph_intr_is_triggered(void);
|
||||
|
@@ -4,15 +4,23 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file is a target specific for DAC DMA peripheral
|
||||
* Target: ESP32
|
||||
* DAC DMA peripheral (data source): I2S0 (i.e. use I2S DMA to transmit data)
|
||||
* DAC DMA interrupt source: I2S0
|
||||
* DAC digital controller clock source: I2S ws signal (root clock: D2PLL or APLL)
|
||||
*/
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "hal/adc_ll.h"
|
||||
#include "hal/i2s_ll.h"
|
||||
#include "hal/i2s_types.h"
|
||||
#include "soc/i2s_periph.h"
|
||||
#include "../dac_dma.h"
|
||||
#include "../dac_priv_dma.h"
|
||||
#include "esp_private/i2s_platform.h"
|
||||
#include "esp_private/esp_clk.h"
|
||||
#include "clk_ctrl_os.h"
|
||||
#if CONFIG_DAC_ENABLE_DEBUG_LOG
|
||||
// The local log level must be defined before including esp_log.h
|
||||
@@ -35,9 +43,7 @@ static dac_dma_periph_i2s_t *s_ddp = NULL; // Static DAC DMA peripheral structur
|
||||
|
||||
static const char *TAG = "DAC_DMA";
|
||||
|
||||
extern portMUX_TYPE dac_spinlock; /* Global DAC spinlock */
|
||||
|
||||
static uint32_t dac_set_apll(uint32_t mclk)
|
||||
static uint32_t s_dac_set_apll_freq(uint32_t mclk)
|
||||
{
|
||||
/* Calculate the expected APLL */
|
||||
int div = (int)((SOC_APLL_MIN_HZ / mclk) + 1);
|
||||
@@ -54,42 +60,50 @@ static uint32_t dac_set_apll(uint32_t mclk)
|
||||
return 0;
|
||||
}
|
||||
if (ret == ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGW(TAG, "APLL is occupied already, it is working at %d Hz", real_freq);
|
||||
ESP_LOGW(TAG, "APLL is occupied already, it is working at %"PRIu32" Hz", real_freq);
|
||||
}
|
||||
ESP_LOGD(TAG, "APLL expected frequency is %d Hz, real frequency is %d Hz", expt_freq, real_freq);
|
||||
ESP_LOGD(TAG, "APLL expected frequency is %"PRIu32" Hz, real frequency is %"PRIu32" Hz", expt_freq, real_freq);
|
||||
return real_freq;
|
||||
}
|
||||
|
||||
static esp_err_t dac_dma_periph_set_clock(uint32_t freq_hz, bool is_apll)
|
||||
/**
|
||||
* @brief Calculate and set DAC data frequency
|
||||
* @note DAC frequency is decided by I2S WS frequency, the clock source of I2S is D2PLL or APLL on ESP32
|
||||
* freq_hz = ws = bclk / I2S_LL_AD_BCK_FACTOR
|
||||
* @param freq_hz DAC byte transmit frequency
|
||||
* @return
|
||||
* - ESP_OK config success
|
||||
* - ESP_ERR_INVALID_ARG invalid frequency
|
||||
*/
|
||||
static esp_err_t s_dac_dma_periph_set_clock(uint32_t freq_hz, bool is_apll)
|
||||
{
|
||||
/* Calculate clock coefficients */
|
||||
uint32_t bclk = freq_hz * I2S_LL_AD_BCK_FACTOR;
|
||||
uint32_t bclk_div = DAC_DMA_PERIPH_I2S_BIT_WIDTH;
|
||||
uint32_t mclk = bclk * bclk_div;
|
||||
uint32_t sclk; // use 160M PLL clock as default, minimun support freq: 19.6 KHz maximun support freq: 2.5 MHz
|
||||
uint32_t sclk; // use 160M PLL clock as default, minimum support freq: 19.6 KHz maximum support freq: 2.5 MHz
|
||||
if (is_apll) {
|
||||
sclk = dac_set_apll(mclk);
|
||||
sclk = s_dac_set_apll_freq(mclk);
|
||||
ESP_RETURN_ON_FALSE(sclk, ESP_ERR_INVALID_ARG, TAG, "set APLL coefficients failed");
|
||||
} else {
|
||||
sclk = I2S_LL_BASE_CLK;
|
||||
// [clk_tree] TODO: replace the following clock by clk_tree API
|
||||
sclk = esp_clk_apb_freq() * 2; // D2PLL
|
||||
}
|
||||
uint32_t mclk_div = sclk / mclk;
|
||||
|
||||
/* Check if the configuration is correct */
|
||||
ESP_RETURN_ON_FALSE(sclk / (float)mclk > 1.99, ESP_ERR_INVALID_ARG, TAG, "Frequency is too large, the mclk division is below minimum value 2");
|
||||
ESP_RETURN_ON_FALSE(mclk_div < 256, ESP_ERR_INVALID_ARG, TAG, "Frequency is too small, the mclk division exceed the maximum value 255");
|
||||
ESP_LOGD(TAG, "[sclk] %d [mclk] %d [mclk_div] %d [bclk] %d [bclk_div] %d", sclk, mclk, mclk_div, bclk, bclk_div);
|
||||
ESP_LOGD(TAG, "[sclk] %"PRIu32" [mclk] %"PRIu32" [mclk_div] %"PRIu32" [bclk] %"PRIu32" [bclk_div] %"PRIu32, sclk, mclk, mclk_div, bclk, bclk_div);
|
||||
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
i2s_ll_tx_clk_set_src(s_ddp->periph_dev, is_apll ? I2S_CLK_SRC_APLL : I2S_CLK_SRC_DEFAULT);
|
||||
i2s_ll_tx_set_mclk(s_ddp->periph_dev, sclk, mclk, mclk_div);
|
||||
i2s_ll_tx_set_bck_div_num(s_ddp->periph_dev, bclk_div);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_dma_periph_init(uint8_t chan_num, uint32_t freq_hz, bool is_alternate, bool is_apll)
|
||||
esp_err_t dac_dma_periph_init(uint32_t freq_hz, bool is_alternate, bool is_apll)
|
||||
{
|
||||
#if CONFIG_DAC_ENABLE_DEBUG_LOG
|
||||
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
@@ -98,7 +112,7 @@ esp_err_t dac_dma_periph_init(uint8_t chan_num, uint32_t freq_hz, bool is_altern
|
||||
/* Acquire DMA peripheral */
|
||||
ESP_RETURN_ON_ERROR(i2s_platform_acquire_occupation(DAC_DMA_PERIPH_I2S_NUM, "dac_dma"), TAG, "Failed to acquire DAC DMA peripheral");
|
||||
/* Allocate DAC DMA peripheral object */
|
||||
s_ddp = (dac_dma_periph_i2s_t *)calloc(1, sizeof(dac_dma_periph_i2s_t));
|
||||
s_ddp = (dac_dma_periph_i2s_t *)heap_caps_calloc(1, sizeof(dac_dma_periph_i2s_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
ESP_GOTO_ON_FALSE(s_ddp, ESP_ERR_NO_MEM, err, TAG, "No memory for DAC DMA object");
|
||||
s_ddp->periph_dev = (void *)I2S_LL_GET_HW(DAC_DMA_PERIPH_I2S_NUM);
|
||||
|
||||
@@ -106,22 +120,23 @@ esp_err_t dac_dma_periph_init(uint8_t chan_num, uint32_t freq_hz, bool is_altern
|
||||
periph_rtc_apll_acquire();
|
||||
s_ddp->use_apll = true;
|
||||
}
|
||||
ESP_GOTO_ON_ERROR(dac_dma_periph_set_clock(freq_hz, is_apll), err, TAG, "Failed to set clock of DMA peripheral");
|
||||
ESP_GOTO_ON_ERROR(s_dac_dma_periph_set_clock(freq_hz, is_apll), err, TAG, "Failed to set clock of DMA peripheral");
|
||||
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
i2s_ll_enable_builtin_dac(s_ddp->periph_dev, true);
|
||||
i2s_ll_tx_reset(s_ddp->periph_dev);
|
||||
i2s_ll_tx_set_slave_mod(s_ddp->periph_dev, false);
|
||||
i2s_ll_tx_set_sample_bit(s_ddp->periph_dev, DAC_DMA_PERIPH_I2S_BIT_WIDTH, DAC_DMA_PERIPH_I2S_BIT_WIDTH);
|
||||
i2s_ll_tx_enable_mono_mode(s_ddp->periph_dev, !is_alternate);
|
||||
i2s_ll_tx_select_std_slot(s_ddp->periph_dev, 0x03, !is_alternate);
|
||||
i2s_ll_tx_select_std_slot(s_ddp->periph_dev, I2S_STD_SLOT_BOTH, !is_alternate);
|
||||
i2s_ll_tx_enable_msb_shift(s_ddp->periph_dev, false);
|
||||
i2s_ll_tx_set_ws_width(s_ddp->periph_dev, DAC_DMA_PERIPH_I2S_BIT_WIDTH);
|
||||
i2s_ll_tx_enable_msb_right(s_ddp->periph_dev, false);
|
||||
i2s_ll_tx_enable_right_first(s_ddp->periph_dev, true);
|
||||
/* Should always enable fifo */
|
||||
i2s_ll_tx_force_enable_fifo_mod(s_ddp->periph_dev, true);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
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:
|
||||
@@ -131,11 +146,10 @@ err:
|
||||
|
||||
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(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) {
|
||||
if (s_ddp->intr_handle) {
|
||||
dac_dma_periph_deregister_intr();
|
||||
}
|
||||
if (s_ddp->use_apll) {
|
||||
periph_rtc_apll_release();
|
||||
s_ddp->use_apll = false;
|
||||
@@ -147,67 +161,51 @@ esp_err_t dac_dma_periph_deinit(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_dma_periph_register_intr(intr_handler_t intr_handler_func, void *user_ctx)
|
||||
int dac_dma_periph_get_intr_signal(void)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(s_ddp, ESP_ERR_INVALID_STATE, TAG, "DAC DMA peripheral has not initialized yet");
|
||||
/* Regigster interrupt */
|
||||
ESP_RETURN_ON_ERROR(esp_intr_alloc(i2s_periph_signal[DAC_DMA_PERIPH_I2S_NUM].irq, ESP_INTR_FLAG_LEVEL1,
|
||||
intr_handler_func, user_ctx, &(s_ddp->intr_handle)),
|
||||
TAG, "Failed to register DAC DMA interrupt");
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
i2s_ll_enable_intr(s_ddp->periph_dev, I2S_LL_EVENT_TX_EOF | I2S_LL_EVENT_TX_TEOF, true);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
return ESP_OK;
|
||||
return i2s_periph_signal[DAC_DMA_PERIPH_I2S_NUM].irq;
|
||||
}
|
||||
|
||||
esp_err_t dac_dma_periph_deregister_intr(void)
|
||||
static void s_dac_dma_periph_reset(void)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(s_ddp, ESP_ERR_INVALID_STATE, TAG, "DAC DMA peripheral has not initialized yet");
|
||||
if (s_ddp->intr_handle) {
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
i2s_ll_enable_intr(s_ddp->periph_dev, I2S_LL_EVENT_TX_EOF | I2S_LL_EVENT_TX_TEOF, false);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
esp_intr_free(s_ddp->intr_handle);
|
||||
s_ddp->intr_handle = NULL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void dac_dma_periph_enable(void)
|
||||
{
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
/* Reset */
|
||||
i2s_ll_tx_reset(s_ddp->periph_dev);
|
||||
i2s_ll_tx_reset_dma(s_ddp->periph_dev);
|
||||
i2s_ll_tx_reset_fifo(s_ddp->periph_dev);
|
||||
/* Start */
|
||||
}
|
||||
|
||||
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_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);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
/* Enable interrupt */
|
||||
esp_intr_enable(s_ddp->intr_handle);
|
||||
}
|
||||
|
||||
void dac_dma_periph_disable(void)
|
||||
static void s_dac_dma_periph_stop(void)
|
||||
{
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
/* Reset */
|
||||
i2s_ll_tx_reset(s_ddp->periph_dev);
|
||||
i2s_ll_tx_reset_dma(s_ddp->periph_dev);
|
||||
i2s_ll_tx_reset_fifo(s_ddp->periph_dev);
|
||||
/* Stop */
|
||||
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_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);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
/* Disable interrupt */
|
||||
esp_intr_disable(s_ddp->intr_handle);
|
||||
}
|
||||
|
||||
void dac_dma_periph_enable(void)
|
||||
{
|
||||
/* Reset */
|
||||
s_dac_dma_periph_reset();
|
||||
/* Start */
|
||||
s_dac_dma_periph_start();
|
||||
}
|
||||
|
||||
void dac_dma_periph_disable(void)
|
||||
{
|
||||
/* Reset */
|
||||
s_dac_dma_periph_reset();
|
||||
/* Stop */
|
||||
s_dac_dma_periph_stop();
|
||||
}
|
||||
|
||||
uint32_t IRAM_ATTR dac_dma_periph_intr_is_triggered(void)
|
||||
@@ -231,9 +229,7 @@ uint32_t IRAM_ATTR dac_dma_periph_intr_get_eof_desc(void)
|
||||
return finish_desc;
|
||||
}
|
||||
|
||||
void inline dac_dma_periph_dma_trans_start(uint32_t desc_addr)
|
||||
void dac_dma_periph_dma_trans_start(uint32_t desc_addr)
|
||||
{
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
i2s_ll_tx_start_link(s_ddp->periph_dev, desc_addr);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
}
|
||||
|
@@ -4,6 +4,15 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file is a target specific for DAC DMA peripheral
|
||||
* Target: ESP32-S2
|
||||
* DAC DMA peripheral (data source): SPI3 (i.e. use SPI DMA to transmit data)
|
||||
* DAC DMA interrupt source: SPI3
|
||||
* DAC digital controller clock source: DIG_SARADC_CLK (root clock: APB or APLL)
|
||||
*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_private/spi_common_internal.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "hal/spi_ll.h"
|
||||
@@ -12,7 +21,7 @@
|
||||
#include "soc/lldesc.h"
|
||||
#include "soc/soc.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "../dac_dma.h"
|
||||
#include "../dac_priv_dma.h"
|
||||
#include "clk_ctrl_os.h"
|
||||
#if CONFIG_DAC_ENABLE_DEBUG_LOG
|
||||
// The local log level must be defined before including esp_log.h
|
||||
@@ -35,9 +44,7 @@ static dac_dma_periph_spi_t *s_ddp = NULL; // Static DAC DMA peripheral structur
|
||||
|
||||
static const char *TAG = "DAC_DMA";
|
||||
|
||||
extern portMUX_TYPE dac_spinlock; /* Global DAC spinlock */
|
||||
|
||||
static uint32_t dac_set_apll_freq(uint32_t expt_freq)
|
||||
static uint32_t s_dac_set_apll_freq(uint32_t expt_freq)
|
||||
{
|
||||
/* Set APLL coefficients to the given frequency */
|
||||
uint32_t real_freq = 0;
|
||||
@@ -46,30 +53,30 @@ static uint32_t dac_set_apll_freq(uint32_t expt_freq)
|
||||
return 0;
|
||||
}
|
||||
if (ret == ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGW(TAG, "APLL is occupied already, it is working at %d Hz", real_freq);
|
||||
ESP_LOGW(TAG, "APLL is occupied already, it is working at %"PRIu32" Hz", real_freq);
|
||||
}
|
||||
ESP_LOGD(TAG, "APLL expected frequency is %d Hz, real frequency is %d Hz", expt_freq, real_freq);
|
||||
ESP_LOGD(TAG, "APLL expected frequency is %"PRIu32" Hz, real frequency is %"PRIu32" Hz", expt_freq, real_freq);
|
||||
return real_freq;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate and set DAC data frequency
|
||||
* @note DAC clcok shares clock devider with ADC, the clock source is APB or APLL on ESP32-S2
|
||||
* @note DAC clock shares clock divider with ADC, the clock source is APB or APLL on ESP32-S2
|
||||
* freq_hz = (source_clk / (clk_div + (b / a) + 1)) / interval
|
||||
* interval range: 1~4095, to avoid decimal as possible, all calculations will base on interval = 4000
|
||||
* interval range: 1~4095
|
||||
* @param freq_hz DAC byte transmit frequency
|
||||
* @return
|
||||
* - ESP_OK config success
|
||||
* - ESP_ERR_INVALID_ARG invalid frequency
|
||||
*/
|
||||
static esp_err_t dac_dma_periph_set_clock(uint32_t freq_hz, bool is_apll){
|
||||
static esp_err_t s_dac_dma_periph_set_clock(uint32_t freq_hz, bool is_apll){
|
||||
/* Step 1: Determine the digital clock source frequency */
|
||||
uint32_t digi_ctrl_freq; // Digital controller clock
|
||||
if (is_apll) {
|
||||
/* Theoretical frequency range (due to the limitation of DAC, the maximum frequency may not reach):
|
||||
* SOC_APLL_MAX_HZ: 119.24 Hz ~ 67.5 MHz
|
||||
* SOC_APLL_MIN_HZ: 5.06 Hz ~ 2.65 MHz */
|
||||
digi_ctrl_freq = dac_set_apll_freq(freq_hz < 120 ? SOC_APLL_MIN_HZ :SOC_APLL_MAX_HZ);
|
||||
digi_ctrl_freq = s_dac_set_apll_freq(freq_hz < 120 ? SOC_APLL_MIN_HZ :SOC_APLL_MAX_HZ);
|
||||
ESP_RETURN_ON_FALSE(digi_ctrl_freq, ESP_ERR_INVALID_ARG, TAG, "set APLL coefficients failed");
|
||||
} else {
|
||||
digi_ctrl_freq = APB_CLK_FREQ;
|
||||
@@ -83,7 +90,7 @@ static esp_err_t dac_dma_periph_set_clock(uint32_t freq_hz, bool is_apll){
|
||||
if (total_div < 256) { // For the case that smaller than the maximum ADC controller division
|
||||
/* Fix the interval to 1, the division is fully realized by the ADC controller clock divider */
|
||||
interval = 1;
|
||||
} else if (total_div < 8192) { // for the case that smaller than the maximum inverval
|
||||
} else if (total_div < 8192) { // for the case that smaller than the maximum interval
|
||||
/* Set the interval to 'total_div / 2', fix the integer part of ADC controller clock division to 2 */
|
||||
interval = total_div / 2;
|
||||
} else {
|
||||
@@ -119,16 +126,14 @@ static esp_err_t dac_dma_periph_set_clock(uint32_t freq_hz, bool is_apll){
|
||||
|
||||
finish:
|
||||
/* Step 4: Set the clock coefficients */
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
dac_ll_digi_clk_inv(true);
|
||||
dac_ll_digi_set_trigger_interval(interval); // secondary clock division
|
||||
adc_ll_digi_controller_clk_div(clk_div - 1, b, a);
|
||||
adc_ll_digi_clk_sel(is_apll);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_dma_periph_init(uint8_t chan_num, uint32_t freq_hz, bool is_alternate, bool is_apll)
|
||||
esp_err_t dac_dma_periph_init(uint32_t freq_hz, bool is_alternate, bool is_apll)
|
||||
{
|
||||
#if CONFIG_DAC_ENABLE_DEBUG_LOG
|
||||
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
@@ -138,7 +143,7 @@ esp_err_t dac_dma_periph_init(uint8_t chan_num, uint32_t freq_hz, bool is_altern
|
||||
ESP_RETURN_ON_FALSE(spicommon_periph_claim(DAC_DMA_PERIPH_SPI_HOST, "dac_dma"), ESP_ERR_NOT_FOUND, TAG, "Failed to acquire DAC DMA peripheral");
|
||||
periph_module_enable(PERIPH_SARADC_MODULE);
|
||||
/* Allocate DAC DMA peripheral object */
|
||||
s_ddp = (dac_dma_periph_spi_t *)calloc(1, sizeof(dac_dma_periph_spi_t));
|
||||
s_ddp = (dac_dma_periph_spi_t *)heap_caps_calloc(1, sizeof(dac_dma_periph_spi_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
ESP_GOTO_ON_FALSE(s_ddp, ESP_ERR_NO_MEM, err, TAG, "No memory for DAC DMA object");
|
||||
s_ddp->periph_dev = (void *)SPI_LL_GET_HW(DAC_DMA_PERIPH_SPI_HOST);
|
||||
|
||||
@@ -148,11 +153,12 @@ esp_err_t dac_dma_periph_init(uint8_t chan_num, uint32_t freq_hz, bool is_altern
|
||||
}
|
||||
/* When transmit alternately, twice frequency is needed to guarantee the convert frequency in one channel */
|
||||
uint32_t trans_freq_hz = freq_hz * (is_alternate ? 2 : 1);
|
||||
ESP_GOTO_ON_ERROR(dac_dma_periph_set_clock(trans_freq_hz, is_apll), err, TAG, "Failed to set clock of DMA peripheral");
|
||||
ESP_GOTO_ON_ERROR(s_dac_dma_periph_set_clock(trans_freq_hz, is_apll), err, TAG, "Failed to set clock of DMA peripheral");
|
||||
ESP_GOTO_ON_ERROR(spicommon_dma_chan_alloc(DAC_DMA_PERIPH_SPI_HOST, SPI_DMA_CH_AUTO, &s_ddp->dma_chan, &s_ddp->dma_chan),
|
||||
err, TAG, "Failed to allocate dma peripheral channel");
|
||||
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
spi_ll_enable_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_EOF | SPI_LL_INTR_OUT_TOTAL_EOF);
|
||||
dac_ll_digi_set_convert_mode(is_alternate);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
return ret;
|
||||
err:
|
||||
dac_dma_periph_deinit();
|
||||
@@ -161,12 +167,14 @@ err:
|
||||
|
||||
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");
|
||||
if (s_ddp->dma_chan) {
|
||||
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);
|
||||
periph_module_disable(PERIPH_SARADC_MODULE);
|
||||
if (s_ddp) {
|
||||
if (s_ddp->intr_handle) {
|
||||
dac_dma_periph_deregister_intr();
|
||||
}
|
||||
if (s_ddp->use_apll) {
|
||||
periph_rtc_apll_release();
|
||||
s_ddp->use_apll = false;
|
||||
@@ -177,59 +185,28 @@ esp_err_t dac_dma_periph_deinit(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dac_dma_periph_register_intr(intr_handler_t intr_handler_func, void *user_ctx)
|
||||
int dac_dma_periph_get_intr_signal(void)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(s_ddp, ESP_ERR_INVALID_STATE, TAG, "DAC DMA peripheral has not initialized yet");
|
||||
ESP_RETURN_ON_ERROR(spicommon_dma_chan_alloc(DAC_DMA_PERIPH_SPI_HOST, SPI_DMA_CH_AUTO, &s_ddp->dma_chan, &s_ddp->dma_chan),
|
||||
TAG, "Failed to allocate dma peripheral channel");
|
||||
esp_err_t ret = ESP_OK;
|
||||
/* Regigster interrupt */
|
||||
ESP_GOTO_ON_ERROR(esp_intr_alloc(spicommon_irqdma_source_for_host(DAC_DMA_PERIPH_SPI_HOST),
|
||||
0, intr_handler_func, user_ctx, &(s_ddp->intr_handle)), err, TAG, "Failed to register DAC DMA interrupt");
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
spi_ll_enable_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_EOF | SPI_LL_INTR_OUT_TOTAL_EOF);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
return ret;
|
||||
err:
|
||||
spicommon_dma_chan_free(DAC_DMA_PERIPH_SPI_HOST);
|
||||
return ret;
|
||||
return spicommon_irqdma_source_for_host(DAC_DMA_PERIPH_SPI_HOST);
|
||||
}
|
||||
|
||||
esp_err_t dac_dma_periph_deregister_intr(void)
|
||||
static void s_dac_dma_periph_reset(void)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(s_ddp, ESP_ERR_INVALID_STATE, TAG, "DAC DMA peripheral has not initialized yet");
|
||||
ESP_RETURN_ON_ERROR(spicommon_dma_chan_free(DAC_DMA_PERIPH_SPI_HOST), TAG, "Failed to free dma peripheral channel");
|
||||
if (s_ddp->intr_handle) {
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
spi_ll_disable_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_EOF | SPI_LL_INTR_OUT_TOTAL_EOF);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
esp_intr_free(s_ddp->intr_handle);
|
||||
s_ddp->intr_handle = NULL;
|
||||
}
|
||||
return ESP_OK;
|
||||
spi_dma_ll_tx_reset(s_ddp->periph_dev, s_ddp->dma_chan);
|
||||
spi_ll_dma_tx_fifo_reset(s_ddp->periph_dev);
|
||||
}
|
||||
|
||||
void dac_dma_periph_enable(void)
|
||||
{
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
spi_dma_ll_tx_reset(s_ddp->periph_dev, s_ddp->dma_chan);
|
||||
spi_ll_dma_tx_fifo_reset(s_ddp->periph_dev);
|
||||
s_dac_dma_periph_reset();
|
||||
dac_ll_digi_trigger_output(true);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
/* Enable interrupt */
|
||||
esp_intr_enable(s_ddp->intr_handle);
|
||||
}
|
||||
|
||||
void dac_dma_periph_disable(void)
|
||||
{
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
spi_dma_ll_tx_reset(s_ddp->periph_dev, s_ddp->dma_chan);
|
||||
spi_ll_dma_tx_fifo_reset(s_ddp->periph_dev);
|
||||
s_dac_dma_periph_reset();
|
||||
spi_dma_ll_tx_stop(s_ddp->periph_dev, s_ddp->dma_chan);
|
||||
dac_ll_digi_trigger_output(false);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
/* Disable interrupt */
|
||||
esp_intr_disable(s_ddp->intr_handle);
|
||||
}
|
||||
|
||||
uint32_t IRAM_ATTR dac_dma_periph_intr_is_triggered(void)
|
||||
@@ -249,9 +226,7 @@ uint32_t IRAM_ATTR dac_dma_periph_intr_get_eof_desc(void)
|
||||
|
||||
void dac_dma_periph_dma_trans_start(uint32_t desc_addr)
|
||||
{
|
||||
portENTER_CRITICAL(&dac_spinlock);
|
||||
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);
|
||||
portEXIT_CRITICAL(&dac_spinlock);
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ static __attribute__((unused)) const char *TAG = "DAC";
|
||||
---------------------------------------------------------------*/
|
||||
esp_err_t dac_pad_get_io_num(dac_channel_t channel, gpio_num_t *gpio_num)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(channel < SOC_DAC_PERIPH_NUM, ESP_ERR_INVALID_ARG, TAG, "DAC channel error");
|
||||
ESP_RETURN_ON_FALSE(channel < SOC_DAC_CHAN_NUM, ESP_ERR_INVALID_ARG, TAG, "DAC channel error");
|
||||
|
||||
*gpio_num = (gpio_num_t)dac_periph_signal.dac_channel_io_num[channel];
|
||||
|
||||
@@ -33,7 +33,7 @@ esp_err_t dac_pad_get_io_num(dac_channel_t channel, gpio_num_t *gpio_num)
|
||||
|
||||
static esp_err_t dac_rtc_pad_init(dac_channel_t channel)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(channel < SOC_DAC_PERIPH_NUM, ESP_ERR_INVALID_ARG, TAG, "DAC channel error");
|
||||
ESP_RETURN_ON_FALSE(channel < SOC_DAC_CHAN_NUM, ESP_ERR_INVALID_ARG, TAG, "DAC channel error");
|
||||
|
||||
gpio_num_t gpio_num = 0;
|
||||
dac_pad_get_io_num(channel, &gpio_num);
|
||||
@@ -47,7 +47,7 @@ static esp_err_t dac_rtc_pad_init(dac_channel_t channel)
|
||||
|
||||
esp_err_t dac_output_enable(dac_channel_t channel)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(channel < SOC_DAC_PERIPH_NUM, ESP_ERR_INVALID_ARG, TAG, "DAC channel error");
|
||||
ESP_RETURN_ON_FALSE(channel < SOC_DAC_CHAN_NUM, ESP_ERR_INVALID_ARG, TAG, "DAC channel error");
|
||||
|
||||
dac_rtc_pad_init(channel);
|
||||
portENTER_CRITICAL(&rtc_spinlock);
|
||||
@@ -60,7 +60,7 @@ esp_err_t dac_output_enable(dac_channel_t channel)
|
||||
|
||||
esp_err_t dac_output_disable(dac_channel_t channel)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(channel < SOC_DAC_PERIPH_NUM, ESP_ERR_INVALID_ARG, TAG, "DAC channel error");
|
||||
ESP_RETURN_ON_FALSE(channel < SOC_DAC_CHAN_NUM, ESP_ERR_INVALID_ARG, TAG, "DAC channel error");
|
||||
|
||||
portENTER_CRITICAL(&rtc_spinlock);
|
||||
dac_ll_power_down(channel);
|
||||
@@ -71,7 +71,7 @@ esp_err_t dac_output_disable(dac_channel_t channel)
|
||||
|
||||
esp_err_t dac_output_voltage(dac_channel_t channel, uint8_t dac_value)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(channel < SOC_DAC_PERIPH_NUM, ESP_ERR_INVALID_ARG, TAG, "DAC channel error");
|
||||
ESP_RETURN_ON_FALSE(channel < SOC_DAC_CHAN_NUM, ESP_ERR_INVALID_ARG, TAG, "DAC channel error");
|
||||
|
||||
portENTER_CRITICAL(&rtc_spinlock);
|
||||
dac_ll_update_output_value(channel, dac_value);
|
||||
@@ -82,7 +82,7 @@ esp_err_t dac_output_voltage(dac_channel_t channel, uint8_t dac_value)
|
||||
|
||||
esp_err_t dac_out_voltage(dac_channel_t channel, uint8_t dac_value)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(channel < SOC_DAC_PERIPH_NUM, ESP_ERR_INVALID_ARG, TAG, "DAC channel error");
|
||||
ESP_RETURN_ON_FALSE(channel < SOC_DAC_CHAN_NUM, ESP_ERR_INVALID_ARG, TAG, "DAC channel error");
|
||||
|
||||
portENTER_CRITICAL(&rtc_spinlock);
|
||||
dac_ll_update_output_value(channel, dac_value);
|
||||
@@ -94,6 +94,7 @@ esp_err_t dac_out_voltage(dac_channel_t channel, uint8_t dac_value)
|
||||
esp_err_t dac_cw_generator_enable(void)
|
||||
{
|
||||
portENTER_CRITICAL(&rtc_spinlock);
|
||||
periph_rtc_dig_clk8m_enable();
|
||||
dac_ll_cw_generator_enable();
|
||||
portEXIT_CRITICAL(&rtc_spinlock);
|
||||
|
||||
@@ -104,6 +105,7 @@ esp_err_t dac_cw_generator_disable(void)
|
||||
{
|
||||
portENTER_CRITICAL(&rtc_spinlock);
|
||||
dac_ll_cw_generator_disable();
|
||||
periph_rtc_dig_clk8m_disable();
|
||||
portEXIT_CRITICAL(&rtc_spinlock);
|
||||
|
||||
return ESP_OK;
|
||||
@@ -112,13 +114,16 @@ esp_err_t dac_cw_generator_disable(void)
|
||||
esp_err_t dac_cw_generator_config(dac_cw_config_t *cw)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(cw, ESP_ERR_INVALID_ARG, TAG, "invalid clock configuration");
|
||||
|
||||
portENTER_CRITICAL(&rtc_spinlock);
|
||||
dac_ll_cw_set_freq(cw->freq, periph_rtc_dig_clk8m_get_freq());
|
||||
dac_ll_cw_set_scale(cw->en_ch, cw->scale);
|
||||
dac_ll_cw_set_phase(cw->en_ch, cw->phase);
|
||||
/* Enable the rtc8m clock temporary to get the correct frequecy */
|
||||
periph_rtc_dig_clk8m_enable();
|
||||
uint32_t rtc_freq = periph_rtc_dig_clk8m_get_freq();
|
||||
periph_rtc_dig_clk8m_disable();
|
||||
dac_ll_cw_set_freq(cw->freq, rtc_freq);
|
||||
dac_ll_cw_set_atten(cw->en_ch, (dac_cosine_atten_t)cw->scale);
|
||||
dac_ll_cw_set_phase(cw->en_ch, (dac_cosine_phase_t)cw->phase);
|
||||
dac_ll_cw_set_dc_offset(cw->en_ch, cw->offset);
|
||||
dac_ll_cw_set_channel(cw->en_ch, true);
|
||||
dac_ll_cw_enable_channel(cw->en_ch, true);
|
||||
portEXIT_CRITICAL(&rtc_spinlock);
|
||||
|
||||
return ESP_OK;
|
||||
@@ -131,11 +136,11 @@ __attribute__((constructor))
|
||||
static void check_dac_legacy_driver_conflict(void)
|
||||
{
|
||||
// This function was declared as weak here. The new DAC driver has one implementation.
|
||||
// So if the new DAC driver is not linked in, then `dac_new_channels()` should be NULL at runtime.
|
||||
extern __attribute__((weak)) esp_err_t dac_new_channels(const void *dac_cfg, void **handle);
|
||||
if ((void *)dac_new_channels != NULL) {
|
||||
// So if the new DAC driver is not linked in, then `dac_priv_register_channel()` should be NULL at runtime.
|
||||
extern __attribute__((weak)) esp_err_t dac_priv_register_channel(dac_channel_t chan_id, const char *mode_name);
|
||||
if ((void *)dac_priv_register_channel != NULL) {
|
||||
ESP_EARLY_LOGE(TAG, "CONFLICT! The new DAC driver is not allowed to be used together with the legacy driver");
|
||||
abort();
|
||||
}
|
||||
ESP_EARLY_LOGW(TAG, "legacy driver is deprecated, please migrate to `driver/dac_driver.h` instead");
|
||||
ESP_EARLY_LOGW(TAG, "legacy driver is deprecated, please migrate to `driver/dac_oneshot.h`, `driver/dac_cosine.h` or `driver/dac_conti.h` instead");
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@
|
||||
#include "driver/dac_types_legacy.h"
|
||||
|
||||
#if !CONFIG_DAC_SUPPRESS_DEPRECATE_WARN
|
||||
#warning "The legacy DAC driver is deprecated, please use driver/dac_driver.h instead"
|
||||
#warning "The legacy DAC driver is deprecated, please use `driver/dac_oneshot.h`, `driver/dac_cosine.h` or `driver/dac_conti.h` instead"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -109,7 +109,7 @@ esp_err_t dac_i2s_enable(void);
|
||||
* - ESP_OK success
|
||||
*/
|
||||
esp_err_t dac_i2s_disable(void);
|
||||
#endif
|
||||
#endif // CONFIG_IDF_TARGET_ESP32
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32S2
|
||||
/**
|
||||
@@ -164,7 +164,7 @@ esp_err_t dac_digi_fifo_reset(void);
|
||||
* - ESP_OK success
|
||||
*/
|
||||
esp_err_t dac_digi_reset(void);
|
||||
#endif
|
||||
#endif // CONFIG_IDF_TARGET_ESP32S2
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
263
components/driver/include/driver/dac_conti.h
Normal file
263
components/driver/include/driver/dac_conti.h
Normal file
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "driver/dac_types.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#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_conti_s *dac_conti_handle_t; /*!< DAC continuous channel handle */
|
||||
|
||||
/**
|
||||
* @brief DAC continuous channels' configurations
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
dac_channel_mask_t chan_mask; /*!< DAC channels' mask for selecting which channels are used */
|
||||
uint32_t desc_num; /*!< The number of DMA descriptor, at least 2 descriptors are required
|
||||
* The number of descriptors is directly proportional to the max data buffer size while converting in cyclic output
|
||||
* but only need to ensure it is greater than '1' in acyclic output
|
||||
* Typically, suggest to set the number bigger than 5, in case the DMA stopped while sending a short buffer
|
||||
*/
|
||||
size_t buf_size; /*!< The DMA buffer size, should be within 32~4092 bytes. Each DMA buffer will be attached to a DMA descriptor,
|
||||
* i.e. the number of DMA buffer will be equal to the DMA descriptor number
|
||||
* The DMA buffer size is not allowed to be greater than 4092 bytes
|
||||
* The total DMA buffer size equal to `desc_num * buf_size`
|
||||
* Typically, suggest to set the size to the multiple of 4
|
||||
*/
|
||||
uint32_t freq_hz; /*!< The frequency of DAC conversion in continuous mode, unit: Hz
|
||||
* The supported range is related to the target and the clock source.
|
||||
* For the clock `DAC_DIGI_CLK_SRC_DEFAULT`: the range is 19.6 KHz to several MHz on ESP32
|
||||
* and 77 Hz to several MHz on ESP32-S2.
|
||||
* For the clock `DAC_DIGI_CLK_SRC_APLL`: the range is 648 Hz to several MHz on ESP32
|
||||
* and 6 Hz to several MHz on ESP32-S2.
|
||||
* Typically not suggest to set the frequency higher than 2 MHz, otherwise the severe distortion will appear
|
||||
*/
|
||||
int8_t offset; /*!< The offset of the DAC digital data. Range -128~127 */
|
||||
dac_conti_digi_clk_src_t clk_src; /*!< The clock source of digital controller, which can affect the range of supported frequency
|
||||
* Currently `DAC_DIGI_CLK_SRC_DEFAULT` and `DAC_DIGI_CLK_SRC_APLL` are available
|
||||
*/
|
||||
dac_conti_channel_mode_t chan_mode; /*!< The channel mode of continuous mode, only take effect when multiple channels enabled, depends converting the buffer alternately or simultaneously */
|
||||
} dac_conti_config_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Event structure used in DAC event queue
|
||||
*/
|
||||
typedef struct {
|
||||
void *buf; /*!< The pointer of DMA buffer that just finished sending */
|
||||
size_t buf_size; /*!< The writable buffer size of the DMA buffer, equal to 'dac_conti_config_t::buf_size' */
|
||||
size_t write_bytes; /*!< The number of bytes that be written successfully */
|
||||
} dac_event_data_t;
|
||||
|
||||
/**
|
||||
* @brief DAC event callback
|
||||
* @param[in] handle DAC channel handle, created from `dac_new_conti_channels()`
|
||||
* @param[in] event DAC event data
|
||||
* @param[in] user_data User registered context, passed from `dac_conti_register_event_callback()`
|
||||
*
|
||||
* @return Whether a high priority task has been waken up by this callback function
|
||||
*/
|
||||
typedef bool (*dac_isr_callback_t)(dac_conti_handle_t handle, const dac_event_data_t *event, void *user_data);
|
||||
|
||||
/**
|
||||
* @brief Group of DAC callbacks
|
||||
* @note The callbacks are all running under ISR environment
|
||||
* @note When CONFIG_DAC_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM.
|
||||
* The variables used in the function should be in the SRAM as well.
|
||||
*/
|
||||
typedef struct {
|
||||
dac_isr_callback_t on_convert_done; /**< Callback of data conversion done event
|
||||
* An event data buffer previously loaded to the driver has been output and converted.
|
||||
* The event data includes DMA buffer address and size that just finished converting.
|
||||
*/
|
||||
dac_isr_callback_t on_stop; /**< Callback of finished sending all the data.
|
||||
* All loaded event data buffers are converted. Driver is pending for new data buffers to be loaded.
|
||||
* The event data will be NULL in this callback.
|
||||
*/
|
||||
} dac_event_callbacks_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Allocate new DAC channels in continuous mode
|
||||
* @note The DAC channels can't be registered to continuous mode separately
|
||||
*
|
||||
* @param[in] conti_cfg Continuous mode configuration
|
||||
* @param[out] ret_handle The returned continuous mode handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The DAC channel has been registered already
|
||||
* - ESP_ERR_NOT_FOUND Not found the available dma peripheral, might be occupied
|
||||
* - ESP_ERR_NO_MEM No memory for the DAC continuous mode resources
|
||||
* - ESP_OK Allocate the new DAC continuous mode success
|
||||
*/
|
||||
esp_err_t dac_new_conti_channels(const dac_conti_config_t *conti_cfg, dac_conti_handle_t *ret_handle);
|
||||
|
||||
/**
|
||||
* @brief Delete the DAC continuous handle
|
||||
*
|
||||
* @param[in] handle The DAC continuous channel handle that obtained from 'dac_new_conti_channels'
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The channels have already been deregistered or not disabled
|
||||
* - ESP_OK Delete the continuous channels success
|
||||
*/
|
||||
esp_err_t dac_del_conti_channels(dac_conti_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Enabled the DAC continuous mode
|
||||
* @note Must enable the channels before
|
||||
*
|
||||
* @param[in] handle The DAC continuous channel handle that obtained from 'dac_new_conti_channels'
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The channels have been enabled already
|
||||
* - ESP_OK Enable the continuous output success
|
||||
*/
|
||||
esp_err_t dac_conti_enable(dac_conti_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Disable the DAC continuous mode
|
||||
*
|
||||
* @param[in] handle The DAC continuous channel handle that obtained from 'dac_new_conti_channels'
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The channels have been enabled already
|
||||
* - ESP_OK Disable the continuous output success
|
||||
*/
|
||||
esp_err_t dac_conti_disable(dac_conti_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Write DAC continuous data cyclically
|
||||
* @note The data in buffer will be converted cyclically using DMA once this function is called,
|
||||
* This function will return once the data loaded into DMA buffers.
|
||||
* @note The buffer size of cyclically output is limited by the descriptor number and
|
||||
* dma buffer size while initializing the continuous mode.
|
||||
* Concretely, in order to load all the data into descriptors,
|
||||
* the cyclic buffer size is not supposed to be greater than `desc_num * buf_size`
|
||||
* @note Specially, on ESP32, the data bit width of DAC continuous data is fixed to 16 bits while only the high 8 bits are available,
|
||||
* The driver will help to expand the inputted buffer automatically by default,
|
||||
* you can also align the data to 16 bits manually by clearing `CONFIG_DAC_DMA_AUTO_16BIT_ALIGN` in menuconfig.
|
||||
*
|
||||
* @param[in] handle The DAC continuous channel handle that obtained from 'dac_new_conti_channels'
|
||||
* @param[in] buf The digital data buffer to convert
|
||||
* @param[in] buf_size The buffer size of digital data buffer
|
||||
* @param[out] bytes_loaded The bytes that has been loaded into DMA buffer, can be NULL if don't need it
|
||||
* @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_err_t dac_conti_write_cyclically(dac_conti_handle_t handle, uint8_t *buf, size_t buf_size, size_t *bytes_loaded);
|
||||
|
||||
/**
|
||||
* @brief Write DAC data continuously
|
||||
* @note The data in buffer will only be converted one time,
|
||||
* This function will be blocked until all data loaded or timeout
|
||||
* then the DAC output will keep outputting the voltage of the last data in the buffer
|
||||
* @note Specially, on ESP32, the data bit width of DAC continuous data is fixed to 16 bits while only the high 8 bits are available,
|
||||
* The driver will help to expand the inputted buffer automatically by default,
|
||||
* you can also align the data to 16 bits manually by clearing `CONFIG_DAC_DMA_AUTO_16BIT_ALIGN` in menuconfig.
|
||||
*
|
||||
* @param[in] handle The DAC continuous channel handle that obtained from 'dac_new_conti_channels'
|
||||
* @param[in] buf The digital data buffer to convert
|
||||
* @param[in] buf_size The buffer size of digital data buffer
|
||||
* @param[out] bytes_loaded The bytes that has been loaded into DMA buffer, can be NULL if don't need it
|
||||
* @param[in] timeout_ms The timeout time in millisecond, set a minus value means will block forever
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The DAC continuous mode has not been enabled yet
|
||||
* - ESP_ERR_TIMEOUT Waiting for semaphore or message queue timeout
|
||||
* - ESP_OK Success to output the acyclic DAC data
|
||||
*/
|
||||
esp_err_t dac_conti_write(dac_conti_handle_t handle, uint8_t *buf, size_t buf_size, size_t *bytes_loaded, int timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Set event callbacks for DAC continuous mode
|
||||
*
|
||||
* @note User can deregister a previously registered callback by calling this function and setting the callback member in the `callbacks` structure to NULL.
|
||||
* @note When CONFIG_DAC_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM.
|
||||
* The variables used in this function, including the `user_data`, should be in the internal RAM as well.
|
||||
*
|
||||
* @param[in] handle The DAC continuous channel handle that obtained from 'dac_new_conti_channels'
|
||||
* @param[in] callbacks Group of callback functions, input NULL to clear the former callbacks
|
||||
* @param[in] user_data User data, which will be passed to callback functions directly
|
||||
* @return
|
||||
* - ESP_OK Set event callbacks successfully
|
||||
* - ESP_ERR_INVALID_ARG Set event callbacks failed because of invalid argument
|
||||
*/
|
||||
esp_err_t dac_conti_register_event_callback(dac_conti_handle_t handle, const dac_event_callbacks_t *callbacks, void *user_data);
|
||||
|
||||
/**
|
||||
* @brief Start the async writing
|
||||
* @note When the asynchronous writing start, the DAC will keep outputting '0' until the data are loaded into the DMA buffer.
|
||||
* To loaded the data into DMA buffer, 'on_convert_done' callback is required,
|
||||
* which can be registered by 'dac_conti_register_event_callback' before enabling
|
||||
*
|
||||
* @param[in] handle The DAC continuous channel handle that obtained from 'dac_new_conti_channels'
|
||||
* @return
|
||||
* - ESP_OK Start asynchronous writing successfully
|
||||
* - ESP_ERR_INVALID_ARG The handle is NULL
|
||||
* - ESP_ERR_INVALID_STATE The channel is not enabled or the 'on_convert_done' callback is not registered
|
||||
*/
|
||||
esp_err_t dac_conti_start_async_writing(dac_conti_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Stop the sync writing
|
||||
*
|
||||
* @param[in] handle The DAC continuous channel handle that obtained from 'dac_new_conti_channels'
|
||||
* @return
|
||||
* - ESP_OK Stop asynchronous writing successfully
|
||||
* - ESP_ERR_INVALID_ARG The handle is NULL
|
||||
* - ESP_ERR_INVALID_STATE Asynchronous writing has not started
|
||||
*/
|
||||
esp_err_t dac_conti_stop_async_writing(dac_conti_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Write DAC data asynchronously
|
||||
* @note This function can be called when the asynchronous writing started, and it can be called in the callback directly
|
||||
* but recommend to writing data in a task, referring to :example:`peripherals/dac/dac_continuous/dac_audio`
|
||||
*
|
||||
* @param[in] handle The DAC continuous channel handle that obtained from 'dac_new_conti_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] 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
|
||||
* @return
|
||||
* - ESP_OK Write the data into DMA buffer successfully
|
||||
* - ESP_ERR_INVALID_ARG NULL pointer
|
||||
* - ESP_ERR_INVALID_STATE The channels haven't start the asynchronous writing
|
||||
* - ESP_ERR_NOT_FOUND The param 'dam_buf' not match any existed DMA buffer
|
||||
*/
|
||||
esp_err_t dac_conti_write_asynchronously(dac_conti_handle_t handle,
|
||||
uint8_t *dma_buf,
|
||||
size_t dma_buf_len,
|
||||
const uint8_t *data,
|
||||
size_t data_len,
|
||||
size_t *bytes_loaded);
|
||||
|
||||
#endif // SOC_DAC_SUPPORTED
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
97
components/driver/include/driver/dac_cosine.h
Normal file
97
components/driver/include/driver/dac_cosine.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "driver/dac_types.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if SOC_DAC_SUPPORTED
|
||||
|
||||
typedef struct dac_cosine_s *dac_cosine_handle_t; /*!< DAC cosine wave channel handle */
|
||||
|
||||
/**
|
||||
* @brief DAC cosine channel configurations
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
dac_channel_t chan_id; /*!< The cosine wave channel id */
|
||||
uint32_t freq_hz; /*!< The frequency of cosine wave, unit: Hz.
|
||||
* The cosine wave generator is driven by RTC_FAST clock which is divide from RC_FAST,
|
||||
* With the default RTC clock, the minimum frequency of cosine wave is about 130 Hz,
|
||||
* Although it can support up to several MHz frequency theoretically,
|
||||
* the waveform will distort at high frequency due to the hardware limitation.
|
||||
* Typically not suggest to set the frequency higher than 200 KHz
|
||||
*/
|
||||
dac_cosine_clk_src_t clk_src; /*!< The clock source of the cosine wave generator, currently only support `DAC_COSINE_CLK_SRC_DEFAULT` */
|
||||
dac_cosine_atten_t atten; /*!< The attenuation of cosine wave amplitude */
|
||||
dac_cosine_phase_t phase; /*!< The phase of cosine wave, can only support DAC_COSINE_PHASE_0 or DAC_COSINE_PHASE_180, default as 0 while setting an unsupported phase */
|
||||
int8_t offset; /*!< The DC offset of cosine wave */
|
||||
struct {
|
||||
bool force_set_freq: 1; /*!< Force to set the cosine wave frequency */
|
||||
} flags; /*!< Flags of cosine mode */
|
||||
} dac_cosine_config_t;
|
||||
|
||||
/**
|
||||
* @brief Allocate a new DAC cosine wave channel
|
||||
* @note Since there is only one cosine wave generator,
|
||||
* only the first channel can set the frequency of the cosine wave.
|
||||
* Normally, the latter one is not allowed to set a different frequency,
|
||||
* but the it can be forced to set by setting the bit `force_set_freq` in the configuration,
|
||||
* notice that another channel will be affected as well when the frequency is updated.
|
||||
*
|
||||
* @param[in] cos_cfg The configuration of cosine wave channel
|
||||
* @param[out] ret_handle The returned cosine wave channel handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The DAC channel has been registered already
|
||||
* - ESP_ERR_NO_MEM No memory for the DAC cosine wave channel resources
|
||||
* - ESP_OK Allocate the new DAC cosine wave channel success
|
||||
*/
|
||||
esp_err_t dac_new_cosine_channel(const dac_cosine_config_t *cos_cfg, dac_cosine_handle_t *ret_handle);
|
||||
|
||||
/**
|
||||
* @brief Delete the DAC cosine wave channel
|
||||
*
|
||||
* @param[in] handle The DAC cosine wave channel handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The channel has already been deregistered
|
||||
* - ESP_OK Delete the cosine wave channel success
|
||||
*/
|
||||
esp_err_t dac_del_cosine_channel(dac_cosine_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Start outputting the cosine wave on the channel
|
||||
*
|
||||
* @param[in] handle The DAC cosine wave channel handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The channel has been started already
|
||||
* - ESP_OK Start the cosine wave success
|
||||
*/
|
||||
esp_err_t dac_cosine_start(dac_cosine_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Stop outputting the cosine wave on the channel
|
||||
*
|
||||
* @param[in] handle The DAC cosine wave channel handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The channel has been stopped already
|
||||
* - ESP_OK Stop the cosine wave success
|
||||
*/
|
||||
esp_err_t dac_cosine_stop(dac_cosine_handle_t handle);
|
||||
|
||||
#endif // SOC_DAC_SUPPORTED
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@@ -1,298 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "driver/dac_types.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if SOC_DAC_SUPPORTED
|
||||
|
||||
/**
|
||||
* @brief DAC channel configuration
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
dac_channel_mask_t chan_sel; /*!< Using DAC channel mask to select the channels */
|
||||
} dac_channels_config_t;
|
||||
|
||||
/**
|
||||
* @brief DAC continuous mode configration
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t freq_hz; /*!< The frequency of DAC conversion in continuous mode, unit: Hz
|
||||
* The supported range is related to the target and the clock source.
|
||||
* For the clock `DAC_DIGI_CLK_SRC_DEFAULT`: the range is 19.6 KHz to several MHz on ESP32
|
||||
* and 77 Hz to several MHz on ESP32-S2.
|
||||
* For the clock `DAC_DIGI_CLK_SRC_APLL`: the range is 648 Hz to several MHz on ESP32
|
||||
* and 6 Hz to several MHz on ESP32-S2.
|
||||
* Typically not suggest to set the frequency higher than 2 MHz, otherwise the severe distortion will appear
|
||||
*/
|
||||
dac_conti_clk_src_t clk_src; /*!< The clock source of digital controller, which can affect the range of supported frequency
|
||||
* Currently `DAC_DIGI_CLK_SRC_DEFAULT` and `DAC_DIGI_CLK_SRC_APLL` are available
|
||||
*/
|
||||
uint32_t desc_num; /*!< The number of DMA descriptor, at least 2 descriptors are required
|
||||
* The number of descriptors is directly proportional to the max data buffer size while converting in cyclic output
|
||||
* but only need to ensure it is greater than '1' in acyclic output
|
||||
* Typically, suggest to set the number bigger than 5, in case the DMA stopped while sending a short buffer
|
||||
*/
|
||||
uint32_t buf_size; /*!< The DMA buffer size, should be within 4092 bytes. Each DMA buffer will be attached to a DMA descriptor,
|
||||
* i.e. the number of DMA buffer will be equal to the DMA descriptor number
|
||||
* The DMA buffer size is not allowed to be greater than 4092 bytes
|
||||
* The total DMA buffer size equal to `desc_num * buf_size`
|
||||
* Typically, suggest to set the size to the multiple of 4
|
||||
*/
|
||||
dac_conti_channel_mode_t chan_mode; /*!< The channel mode of continuous mode, only take effect when multiple channels enabled, depends converting the buffer alternately or simultaneously */
|
||||
} dac_conti_config_t;
|
||||
|
||||
/**
|
||||
* @brief DAC cosine wave gnerator configuration
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t freq_hz; /*!< The frequency of cosine wave, unit: Hz.
|
||||
* The cosine wave generator is driven by RTC clock which is about SOC_CLK_RC_FAST_FREQ_APPROX Hz by default,
|
||||
* With the default RTC clock, the minimun frequency of cosine wave is about 130 Hz,
|
||||
* Although it can support up to serveral MHz frequency theoretically,
|
||||
* the waveform will distort at high frequency due to the hardware limitation.
|
||||
* Typically not suggest to set the frequency higher than 200 KHz
|
||||
*/
|
||||
dac_cosine_clk_src_t clk_src; /*!< The clock source of the cosine wave generator, currently only support `DAC_COSINE_CLK_SRC_DEFAULT` which comes from RTC FAST clock */
|
||||
dac_cosine_scale_t scale; /*!< The scale of cosine wave amplitude */
|
||||
dac_cosine_phase_t phase; /*!< The phase of cosine wave */
|
||||
int8_t offset; /*!< The DC offset of cosine wave */
|
||||
} dac_cosine_config_t;
|
||||
|
||||
typedef struct dac_channels_s *dac_channels_handle_t; /*!< DAC channels' handle of DAC peripheral, one or multiple DAC channels can be controlled by this handle */
|
||||
|
||||
/*--------------------------------------------------------------------------
|
||||
DAC common APIs
|
||||
---------------------------------------------------------------------------*/
|
||||
/**
|
||||
* @brief Allocate a new DAC channels' handle
|
||||
* @note The driver supports to manage one single channel by enabling only one channel in the channel mask,
|
||||
* or multiple channels together as a whole by enabling multiple channels in the channel mask.
|
||||
* Moreover, the channels can also be managed separately if they are allocated separately.
|
||||
*
|
||||
* @param[in] dac_cfg DAC basic configuration
|
||||
* @param[out] handle DAC channels handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_NO_MEM No memory for a new DAC handle
|
||||
* - ESP_ERR_INVALID_STATE The specified DAC channel is occupied already
|
||||
* - ESP_OK Success to allocate DAC channels
|
||||
*/
|
||||
esp_err_t dac_new_channels(const dac_channels_config_t *dac_cfg, dac_channels_handle_t *handle);
|
||||
|
||||
/**
|
||||
* @brief Delete and free the DAC channels
|
||||
*
|
||||
* @param[in] handle DAC channels handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The channels are not disabled
|
||||
* - ESP_OK Success to delete the channels
|
||||
*/
|
||||
esp_err_t dac_del_channels(dac_channels_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Enabled the DAC channels in the channels
|
||||
* @note GPIOs of DAC channles will be enabled in this step
|
||||
*
|
||||
* @param[in] handle DAC channels handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The channels has enabled already or the channels are running
|
||||
* - ESP_OK Success to enable the channels
|
||||
*/
|
||||
esp_err_t dac_channels_enable(dac_channels_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Disable the DAC channels in the channels
|
||||
*
|
||||
* @param[in] handle DAC channels handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The channels has disabled already or the channels are running
|
||||
* - ESP_OK Success to enable the channels
|
||||
*/
|
||||
esp_err_t dac_channels_disable(dac_channels_handle_t handle);
|
||||
|
||||
/*--------------------------------------------------------------------------
|
||||
DAC direct voltage outputting APIs
|
||||
---------------------------------------------------------------------------*/
|
||||
/**
|
||||
* @brief DAC channels output a voltage directly
|
||||
* @note This function is available when DAC chennels is enbled
|
||||
* @note Please enable 'DAC ISR IRAM-Safe' in memuconfig when it is called in an IRAM safe ISR
|
||||
*
|
||||
* @param[in] handle DAC channels handle
|
||||
* @param[in] value The digital value of the voltage
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The channels are not enabled
|
||||
* - ESP_OK Success to enable the channels
|
||||
*/
|
||||
esp_err_t dac_channels_set_voltage(dac_channels_handle_t handle, uint8_t value);
|
||||
|
||||
/*--------------------------------------------------------------------------
|
||||
DAC continuous outputting APIs
|
||||
---------------------------------------------------------------------------*/
|
||||
/**
|
||||
* @brief Initialize the DAC channels to continuous mode
|
||||
* @note DAC can convert digital data continuously in continuous mode
|
||||
*
|
||||
* @param[in] handle DAC channels handle
|
||||
* @param[in] conti_cfg DAC continuous mode configuration
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The DAC channels has been initialized already
|
||||
* - ESP_ERR_NO_MEM No memory for DAC continuous mode
|
||||
* - ESP_OK Success to initializing the DAC channels to continuous mode
|
||||
*/
|
||||
esp_err_t dac_channels_init_continuous_mode(dac_channels_handle_t handle, const dac_conti_config_t *conti_cfg);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize the continuous mode of the DAC channels
|
||||
* @note It can only be deinitialized when the continuous output is disabled
|
||||
*
|
||||
* @param[in] handle DAC channels handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The DAC continuous mode is not disabled yet
|
||||
* - ESP_OK Success to deinitialize the DAC continuous mode
|
||||
*/
|
||||
esp_err_t dac_channels_deinit_continuous_mode(dac_channels_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Enable the DAC continuous output
|
||||
*
|
||||
* @param[in] handle DAC channels handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The DAC continuous mode has been enabled already
|
||||
* - ESP_OK Success to start the continuous output
|
||||
*/
|
||||
esp_err_t dac_channels_enable_continuous_mode(dac_channels_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Disable the DAC continuous output
|
||||
*
|
||||
* @param[in] handle DAC channels handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The DAC continuous mode is disabled already
|
||||
* - ESP_OK Success to stop the continuous output
|
||||
*/
|
||||
esp_err_t dac_channels_disable_continuous_mode(dac_channels_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Write DAC continuous data continuously
|
||||
* @note The data in buffer will only be converted one time,
|
||||
* This function will be blocked until all data loaded or timeout
|
||||
* then the DAC output will keep outputting the voltage of the last data in the buffer
|
||||
* @note On ESP32, the data bit width of DAC continuous data is fixed to 16 bits while only the high 8 bits are available,
|
||||
* you can align the DAC data to 16 bits manually or set `CONFIG_DAC_DMA_AUTO_16BIT_ALIGN` to get the correct wave.
|
||||
* But the data bit width is already 8 bits on ESP32-S2, each byte stands for an vailable voltage,
|
||||
* no need to do any alignment.
|
||||
*
|
||||
* @param[in] handle DAC channels handle
|
||||
* @param[in] buf The digital data buffer to convert
|
||||
* @param[in] buf_size The buffer size of digital data buffer
|
||||
* @param[out] bytes_loaded The bytes that has been loaded into DMA buffer, can be NULL if don't need it
|
||||
* @param[in] timeout_ms The timeout time in mili-second
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The DAC continuous mode has not been enabled yet
|
||||
* - ESP_ERR_TIMEOUT Waiting for semaphore or message queue timeout
|
||||
* - ESP_OK Success to output the acyclic DAC data
|
||||
*/
|
||||
esp_err_t dac_channels_write_continuously(dac_channels_handle_t handle, uint8_t *buf, size_t buf_size, size_t *bytes_loaded, uint32_t timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Write DAC continuous data cyclically
|
||||
* @note The data in buffer will be converted cyclically once this function is called,
|
||||
* This function won't be blocked, it will return once the data loaded into DMA buffers
|
||||
* @note The buffer size of cyclically output is limited by the descriptor number while initializing the continuous mode,
|
||||
* Concretely, in order to load all the data into descriptors,
|
||||
* the cyclic buffer size is not supposed to be greater than `desc_num * 4092`
|
||||
* @note On ESP32, the data bit width of DAC continuous data is fixed to 16 bits while only the high 8 bits are available,
|
||||
* you can align the DAC data to 16 bits manually or set `CONFIG_DAC_DMA_AUTO_16BIT_ALIGN` to get the correct wave.
|
||||
* But the data bit width is already 8 bits on ESP32-S2, each byte stands for an vailable voltage,
|
||||
* no need to do any alignment.
|
||||
*
|
||||
* @param[in] handle DAC channels handle
|
||||
* @param[in] buf The digital data buffer to convert
|
||||
* @param[in] buf_size The buffer size of digital data buffer
|
||||
* @param[out] bytes_loaded The bytes that has been loaded into DMA buffer, can be NULL if don't need it
|
||||
* @param[in] timeout_ms The timeout time in mili-second
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The DAC continuous mode has not been enabled yet
|
||||
* - ESP_ERR_TIMEOUT Waiting for semaphore or message queue timeout
|
||||
* - ESP_OK Success to output the acyclic DAC data
|
||||
*/
|
||||
esp_err_t dac_channels_write_cyclically(dac_channels_handle_t handle, uint8_t *buf, size_t buf_size, size_t *bytes_loaded, uint32_t timeout_ms);
|
||||
|
||||
/*--------------------------------------------------------------------------
|
||||
DAC cosine wave outputting APIs
|
||||
---------------------------------------------------------------------------*/
|
||||
/**
|
||||
* @brief Initialize the DAC channels to cosine wave mode
|
||||
*
|
||||
* @param[in] handle DAC channels handle
|
||||
* @param[in] cw_cfg DAC cosine wave generater configuration
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The DAC channels has been initialized already
|
||||
* - ESP_OK Success to initialize the DAC channels into cosine wave mode
|
||||
*/
|
||||
esp_err_t dac_channels_init_cosine_mode(dac_channels_handle_t handle, const dac_cosine_config_t *cw_cfg);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize the DAC channels to cosine wave mode
|
||||
*
|
||||
* @param[in] handle DAC channels handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The DAC cosine wave generator is not stopped yet
|
||||
* - ESP_OK Success to deinitialize the DAC cosine mode
|
||||
*/
|
||||
esp_err_t dac_channels_deinit_cosine_mode(dac_channels_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Start the DAC cosine wave generator output
|
||||
*
|
||||
* @param[in] handle DAC channels handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The DAC channels has not been enabled yet or started already
|
||||
* - ESP_OK Success to start cosine wave generator
|
||||
*/
|
||||
esp_err_t dac_channels_start_cosine_output(dac_channels_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Stop the DAC cosine wave generator output
|
||||
*
|
||||
* @param[in] handle DAC channels handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The DAC channels has not been enabled yet or stoppped already
|
||||
* - ESP_OK Success to stop cosine wave generator
|
||||
*/
|
||||
esp_err_t dac_channels_stop_cosine_output(dac_channels_handle_t handle);
|
||||
|
||||
#endif // SOC_DAC_SUPPORTED
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
70
components/driver/include/driver/dac_oneshot.h
Normal file
70
components/driver/include/driver/dac_oneshot.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "driver/dac_types.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if SOC_DAC_SUPPORTED
|
||||
|
||||
typedef struct dac_oneshot_s *dac_oneshot_handle_t; /*!< DAC oneshot channel handle */
|
||||
|
||||
/**
|
||||
* @brief DAC oneshot channel configuration
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
dac_channel_t chan_id; /*!< DAC channel id */
|
||||
} dac_oneshot_config_t;
|
||||
|
||||
/**
|
||||
* @brief Allocate a new DAC oneshot channel
|
||||
* @note The channel will be enabled as well when the channel allocated
|
||||
*
|
||||
* @param[in] oneshot_cfg The configuration for the oneshot channel
|
||||
* @param[out] ret_handle The returned oneshot channel handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The DAC channel has been registered already
|
||||
* - ESP_ERR_NO_MEM No memory for the DAC oneshot channel resources
|
||||
* - ESP_OK Allocate the new DAC oneshot channel success
|
||||
*/
|
||||
esp_err_t dac_new_oneshot_channel(const dac_oneshot_config_t *oneshot_cfg, dac_oneshot_handle_t *ret_handle);
|
||||
|
||||
/**
|
||||
* @brief Delete the DAC oneshot channel
|
||||
* @note The channel will be disabled as well when the channel deleted
|
||||
*
|
||||
* @param[in] handle The DAC oneshot channel handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_ERR_INVALID_STATE The channel has already been de-registered
|
||||
* - ESP_OK Delete the oneshot channel success
|
||||
*/
|
||||
esp_err_t dac_del_oneshot_channel(dac_oneshot_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Output the voltage
|
||||
* @note Generally it'll take 7~11 us on ESP32 and 10~21 us on ESP32-S2
|
||||
*
|
||||
* @param[in] handle The DAC oneshot channel handle
|
||||
* @param[in] digi_value The digital value that need to be converted
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG The input parameter is invalid
|
||||
* - ESP_OK Convert the digital value success
|
||||
*/
|
||||
esp_err_t dac_oneshot_output_voltage(dac_oneshot_handle_t handle, uint8_t digi_value);
|
||||
|
||||
#endif // SOC_DAC_SUPPORTED
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@@ -12,6 +12,7 @@
|
||||
#include "soc/soc_caps.h"
|
||||
#include "soc/clk_tree_defs.h"
|
||||
#include "hal/adc_types.h"
|
||||
#include "hal/dac_types.h"
|
||||
#include "esp_bit_defs.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
@@ -20,17 +21,6 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
#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_BOTH = BIT(0) | BIT(1), /*!< Both DAC channel 0 and channel 1 */
|
||||
} dac_channel_mask_t;
|
||||
|
||||
/**
|
||||
* @brief DAC channel work mode in dma mode
|
||||
* @note Only take effect when multiple channels enabled.
|
||||
@@ -51,7 +41,7 @@ typedef enum {
|
||||
* @brief DAC DMA (digitial controller) clock source
|
||||
*
|
||||
*/
|
||||
typedef soc_periph_dac_digi_clk_src_t dac_conti_clk_src_t;
|
||||
typedef soc_periph_dac_digi_clk_src_t dac_conti_digi_clk_src_t;
|
||||
|
||||
/**
|
||||
* @brief DAC cosine wave generator clock source
|
||||
@@ -59,26 +49,6 @@ typedef soc_periph_dac_digi_clk_src_t dac_conti_clk_src_t;
|
||||
*/
|
||||
typedef soc_periph_dac_cosine_clk_src_t dac_cosine_clk_src_t;
|
||||
|
||||
/**
|
||||
* @brief The attenuation of the amplitude of the cosine wave generator. The max amplitude is VDD3P3_RTC.
|
||||
*/
|
||||
typedef enum {
|
||||
DAC_COSINE_NO_ATTEN = 0x0, /*!< No attenuation to the DAC cosine wave amplitude. Default. */
|
||||
DAC_COSINE_ATTEN_2 = 0x1, /*!< 1/2 amplitude of the DAC cosine wave */
|
||||
DAC_COSINE_ATTEN_4 = 0x2, /*!< 1/4 amplitude of the DAC cosine wave */
|
||||
DAC_COSINE_ATTEN_8 = 0x3, /*!< 1/8 amplitude of the DAC cosine wave */
|
||||
} dac_cosine_scale_t;
|
||||
|
||||
/**
|
||||
* @brief Set the phase of the cosine wave generator output.
|
||||
* @note Only 0 or 180 are supported,
|
||||
* it will be set to 0 as default if configured to an unsupported phase.
|
||||
*/
|
||||
typedef enum {
|
||||
DAC_COSINE_PHASE_0 = 0, /*!< Phase shift +0° */
|
||||
DAC_COSINE_PHASE_180 = 180, /*!< Phase shift +180° */
|
||||
} dac_cosine_phase_t;
|
||||
|
||||
#endif // SOC_DAC_SUPPORTED
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@@ -18,6 +18,7 @@ entries:
|
||||
if SDM_CTRL_FUNC_IN_IRAM = y:
|
||||
sdm: sdm_channel_set_duty (noflash)
|
||||
if DAC_CTRL_FUNC_IN_IRAM = y:
|
||||
dac_driver: dac_channels_set_voltage (noflash)
|
||||
dac_oneshot: dac_oneshot_output_voltage (noflash)
|
||||
dac_conti: dac_conti_write_asynchronously (noflash)
|
||||
if MCPWM_CTRL_FUNC_IN_IRAM = y:
|
||||
mcpwm_cmpr: mcpwm_comparator_set_compare_value (noflash)
|
||||
|
@@ -4,11 +4,15 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include "unity.h"
|
||||
#include "unity_test_utils.h"
|
||||
#include "driver/dac_driver.h"
|
||||
#include "driver/adc.h"
|
||||
#include "driver/dac_oneshot.h"
|
||||
#include "driver/dac_cosine.h"
|
||||
#include "driver/dac_conti.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
#include "esp_err.h"
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#include "esp_private/i2s_platform.h"
|
||||
@@ -22,109 +26,129 @@
|
||||
#endif
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#define ADC_TEST_CHANNEL_NUM ADC2_CHANNEL_8 // GPIO25, same as DAC channel 0
|
||||
#define ADC_TEST_WIDTH ADC_WIDTH_BIT_12
|
||||
#define ADC_TEST_CHANNEL_NUM ADC_CHANNEL_8 // GPIO25, same as DAC channel 0
|
||||
#define ADC_TEST_WIDTH ADC_BITWIDTH_12
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
#define ADC_TEST_CHANNEL_NUM ADC2_CHANNEL_6 // GPIO17, same as DAC channel 0
|
||||
#define ADC_TEST_WIDTH ADC_WIDTH_BIT_13
|
||||
#define ADC_TEST_CHANNEL_NUM ADC_CHANNEL_6 // GPIO17, same as DAC channel 0
|
||||
#define ADC_TEST_WIDTH ADC_BITWIDTH_13
|
||||
#endif
|
||||
|
||||
#define ADC_TEST_ATTEN ADC_ATTEN_DB_11
|
||||
|
||||
TEST_CASE("DAC_API_basic_logic_test", "[dac]")
|
||||
{
|
||||
dac_channels_handle_t handle;
|
||||
dac_channels_config_t cfg = {.chan_sel = DAC_CHANNEL_MASK_BOTH};
|
||||
dac_conti_config_t dma_cfg = {
|
||||
.freq_hz = 20000,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT,
|
||||
.desc_num = 10,
|
||||
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
|
||||
};
|
||||
dac_cosine_config_t cos_cfg = {
|
||||
.freq_hz = 1000,
|
||||
/* Constant API test */
|
||||
dac_oneshot_handle_t oneshot_chan0_handle;
|
||||
TEST_ESP_OK(dac_new_oneshot_channel(&(dac_oneshot_config_t){.chan_id = DAC_CHAN_0}, &oneshot_chan0_handle));
|
||||
TEST_ESP_OK(dac_oneshot_output_voltage(oneshot_chan0_handle, 128));
|
||||
TEST_ESP_OK(dac_del_oneshot_channel(oneshot_chan0_handle));
|
||||
dac_oneshot_handle_t oneshot_chan1_handle;
|
||||
TEST_ESP_OK(dac_new_oneshot_channel(&(dac_oneshot_config_t){.chan_id = DAC_CHAN_1}, &oneshot_chan1_handle));
|
||||
TEST_ESP_OK(dac_oneshot_output_voltage(oneshot_chan1_handle, 100));
|
||||
TEST_ESP_OK(dac_del_oneshot_channel(oneshot_chan1_handle));
|
||||
|
||||
/* Cosine wave API test */
|
||||
dac_cosine_handle_t cos_chan0_handle;
|
||||
dac_cosine_handle_t cos_chan1_handle;
|
||||
dac_cosine_config_t cos0_cfg = {
|
||||
.chan_id = DAC_CHAN_0,
|
||||
.freq_hz = 1000, // It will be covered by 8000 in the latter configuration
|
||||
.clk_src = DAC_COSINE_CLK_SRC_DEFAULT,
|
||||
.offset = 0,
|
||||
.phase = DAC_COSINE_PHASE_0,
|
||||
.scale = DAC_COSINE_NO_ATTEN,
|
||||
.atten = DAC_COSINE_ATTEN_DEFAULT,
|
||||
.flags.force_set_freq = false,
|
||||
};
|
||||
/* Constant API test */
|
||||
TEST_ESP_OK(dac_new_channels(&cfg, &handle));
|
||||
TEST_ESP_OK(dac_channels_enable(handle));
|
||||
TEST_ESP_OK(dac_channels_set_voltage(handle, 100));
|
||||
TEST_ESP_OK(dac_channels_disable(handle));
|
||||
dac_cosine_config_t cos1_cfg = {
|
||||
.chan_id = DAC_CHAN_1,
|
||||
.freq_hz = 8000,
|
||||
.clk_src = DAC_COSINE_CLK_SRC_DEFAULT,
|
||||
.offset = 0,
|
||||
.phase = DAC_COSINE_PHASE_180,
|
||||
.atten = DAC_COSINE_ATTEN_DB_6,
|
||||
.flags.force_set_freq = false,
|
||||
};
|
||||
TEST_ESP_OK(dac_new_cosine_channel(&cos0_cfg, &cos_chan0_handle));
|
||||
/* Try to update the frequency without force set */
|
||||
TEST_ASSERT(dac_new_cosine_channel(&cos1_cfg, &cos_chan1_handle) == ESP_ERR_INVALID_STATE);
|
||||
/* Force update the frequnecy */
|
||||
cos1_cfg.flags.force_set_freq = true;
|
||||
TEST_ESP_OK(dac_new_cosine_channel(&cos1_cfg, &cos_chan1_handle));
|
||||
TEST_ASSERT(dac_cosine_stop(cos_chan0_handle) == ESP_ERR_INVALID_STATE);
|
||||
TEST_ESP_OK(dac_cosine_start(cos_chan0_handle));
|
||||
TEST_ESP_OK(dac_cosine_start(cos_chan1_handle));
|
||||
TEST_ASSERT(dac_del_cosine_channel(cos_chan0_handle) == ESP_ERR_INVALID_STATE);
|
||||
TEST_ESP_OK(dac_cosine_stop(cos_chan0_handle));
|
||||
TEST_ESP_OK(dac_cosine_stop(cos_chan1_handle));
|
||||
TEST_ESP_OK(dac_del_cosine_channel(cos_chan0_handle));
|
||||
TEST_ESP_OK(dac_del_cosine_channel(cos_chan1_handle));
|
||||
|
||||
/* DMA API test */
|
||||
TEST_ESP_OK(dac_channels_init_continuous_mode(handle, &dma_cfg));
|
||||
TEST_ASSERT(dac_channels_enable_continuous_mode(handle) == ESP_ERR_INVALID_STATE);
|
||||
TEST_ESP_OK(dac_channels_enable(handle));
|
||||
TEST_ESP_OK(dac_channels_enable_continuous_mode(handle));
|
||||
TEST_ASSERT(dac_channels_disable(handle) == ESP_ERR_INVALID_STATE);
|
||||
TEST_ASSERT(dac_channels_deinit_continuous_mode(handle) == ESP_ERR_INVALID_STATE);
|
||||
TEST_ESP_OK(dac_channels_disable_continuous_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_deinit_continuous_mode(handle));
|
||||
|
||||
/* Cosine wave API test */
|
||||
TEST_ESP_OK(dac_channels_init_cosine_mode(handle, &cos_cfg));
|
||||
TEST_ASSERT(dac_del_channels(handle) == ESP_ERR_INVALID_STATE);
|
||||
TEST_ESP_OK(dac_channels_start_cosine_output(handle));
|
||||
TEST_ASSERT(dac_channels_disable(handle) == ESP_ERR_INVALID_STATE);
|
||||
TEST_ESP_OK(dac_channels_stop_cosine_output(handle));
|
||||
TEST_ESP_OK(dac_channels_deinit_cosine_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_disable(handle));
|
||||
TEST_ESP_OK(dac_del_channels(handle));
|
||||
|
||||
dac_conti_handle_t conti_handle;
|
||||
dac_conti_config_t conti_cfg = {
|
||||
.chan_mask = DAC_CHANNEL_MASK_ALL,
|
||||
.desc_num = 8,
|
||||
.buf_size = 2048,
|
||||
.freq_hz = 48000,
|
||||
.offset = 0,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT,
|
||||
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
|
||||
};
|
||||
/* DMA peripheral availability test */
|
||||
TEST_ESP_OK(dac_new_channels(&cfg, &handle));
|
||||
TEST_ESP_OK(dac_channels_enable(handle));
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
TEST_ESP_OK(i2s_platform_acquire_occupation(0, "dac_test"));
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
TEST_ASSERT(spicommon_periph_claim(SPI3_HOST, "dac_test"));
|
||||
#endif
|
||||
TEST_ASSERT(dac_channels_init_continuous_mode(handle, &dma_cfg) == ESP_ERR_NOT_FOUND);
|
||||
TEST_ASSERT(dac_new_conti_channels(&conti_cfg, &conti_handle) == ESP_ERR_NOT_FOUND);
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
TEST_ESP_OK(i2s_platform_release_occupation(0));
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
TEST_ASSERT(spicommon_periph_free(SPI3_HOST));
|
||||
#endif
|
||||
TEST_ESP_OK(dac_channels_disable(handle));
|
||||
TEST_ESP_OK(dac_del_channels(handle));
|
||||
|
||||
TEST_ESP_OK(dac_new_conti_channels(&conti_cfg, &conti_handle));
|
||||
TEST_ASSERT(dac_conti_disable(conti_handle) == ESP_ERR_INVALID_STATE);
|
||||
TEST_ESP_OK(dac_conti_enable(conti_handle));
|
||||
TEST_ASSERT(dac_del_conti_channels(conti_handle) == ESP_ERR_INVALID_STATE);
|
||||
TEST_ESP_OK(dac_conti_disable(conti_handle));
|
||||
TEST_ESP_OK(dac_del_conti_channels(conti_handle));
|
||||
}
|
||||
|
||||
TEST_CASE("DAC_memory_leak_test", "[dac]")
|
||||
{
|
||||
dac_channels_handle_t handle;
|
||||
dac_channels_config_t cfg = {.chan_sel = DAC_CHANNEL_MASK_BOTH};
|
||||
dac_conti_config_t dma_cfg = {
|
||||
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
|
||||
dac_conti_handle_t conti_handle;
|
||||
dac_conti_config_t conti_cfg = {
|
||||
.chan_mask = DAC_CHANNEL_MASK_ALL,
|
||||
.desc_num = 8,
|
||||
.buf_size = 2048,
|
||||
.freq_hz = 48000,
|
||||
.offset = 0,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT,
|
||||
.desc_num = 10,
|
||||
.freq_hz = 20000,
|
||||
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
|
||||
};
|
||||
/* Some resources will be lazy installed, ignore the first around */
|
||||
TEST_ESP_OK(dac_new_channels(&cfg, &handle));
|
||||
TEST_ESP_OK(dac_channels_enable(handle));
|
||||
TEST_ESP_OK(dac_channels_init_continuous_mode(handle, &dma_cfg));
|
||||
TEST_ESP_OK(dac_channels_enable_continuous_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_disable_continuous_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_deinit_continuous_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_disable(handle));
|
||||
TEST_ESP_OK(dac_del_channels(handle));
|
||||
size_t len = 1024;
|
||||
uint8_t buf[len];
|
||||
for (int i = 0; i < len; i++) {
|
||||
buf[i] = i % 256;
|
||||
}
|
||||
|
||||
int initial_size = esp_get_free_heap_size();
|
||||
printf("Initial free heap size: %d\n", initial_size);
|
||||
TEST_ESP_OK(dac_new_conti_channels(&conti_cfg, &conti_handle));
|
||||
TEST_ESP_OK(dac_conti_enable(conti_handle));
|
||||
TEST_ESP_OK(dac_conti_write_cyclically(conti_handle, buf, len, NULL));
|
||||
TEST_ESP_OK(dac_conti_disable(conti_handle));
|
||||
TEST_ESP_OK(dac_del_conti_channels(conti_handle));
|
||||
|
||||
uint32_t initial_size = esp_get_free_heap_size();
|
||||
printf("Initial free heap size: %"PRIu32"\n", initial_size);
|
||||
for (int i = 0; i < 20; i++) {
|
||||
printf("# %d: ---------------------------------\n", i + 1);
|
||||
TEST_ESP_OK(dac_new_channels(&cfg, &handle));
|
||||
TEST_ESP_OK(dac_channels_enable(handle));
|
||||
TEST_ESP_OK(dac_channels_init_continuous_mode(handle, &dma_cfg));
|
||||
TEST_ESP_OK(dac_channels_enable_continuous_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_disable_continuous_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_deinit_continuous_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_disable(handle));
|
||||
TEST_ESP_OK(dac_del_channels(handle));
|
||||
printf("current heap size: %d\n", esp_get_free_heap_size());
|
||||
TEST_ESP_OK(dac_new_conti_channels(&conti_cfg, &conti_handle));
|
||||
TEST_ESP_OK(dac_conti_enable(conti_handle));
|
||||
TEST_ESP_OK(dac_conti_write_cyclically(conti_handle, buf, len, NULL));
|
||||
TEST_ESP_OK(dac_conti_disable(conti_handle));
|
||||
TEST_ESP_OK(dac_del_conti_channels(conti_handle));
|
||||
printf("current heap size: %"PRIu32"\n", esp_get_free_heap_size());
|
||||
TEST_ASSERT(initial_size == esp_get_free_heap_size());
|
||||
}
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
@@ -133,60 +157,70 @@ TEST_CASE("DAC_memory_leak_test", "[dac]")
|
||||
|
||||
TEST_CASE("DAC_set_voltage_test", "[dac]")
|
||||
{
|
||||
dac_channels_handle_t handle;
|
||||
dac_channels_config_t cfg = {.chan_sel = DAC_CHANNEL_MASK_BOTH};
|
||||
TEST_ESP_OK(dac_new_channels(&cfg, &handle));
|
||||
TEST_ESP_OK(dac_channels_enable(handle));
|
||||
dac_oneshot_handle_t oneshot_chan0_handle;
|
||||
dac_oneshot_config_t onshot_cfg = {
|
||||
.chan_id = DAC_CHAN_0,
|
||||
};
|
||||
TEST_ESP_OK(dac_new_oneshot_channel(&onshot_cfg, &oneshot_chan0_handle));
|
||||
|
||||
/* Prepare ADC2 */
|
||||
TEST_ESP_OK(adc2_config_channel_atten(ADC_TEST_CHANNEL_NUM, ADC_TEST_ATTEN));
|
||||
adc_oneshot_unit_handle_t adc2_handle;
|
||||
adc_oneshot_unit_init_cfg_t unit_cfg = {
|
||||
.unit_id = ADC_UNIT_2,
|
||||
.ulp_mode = false,
|
||||
};
|
||||
TEST_ESP_OK(adc_oneshot_new_unit(&unit_cfg, &adc2_handle));
|
||||
adc_oneshot_chan_cfg_t chan_cfg = {
|
||||
.atten = ADC_TEST_ATTEN,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
};
|
||||
TEST_ESP_OK(adc_oneshot_config_channel(adc2_handle, ADC_TEST_CHANNEL_NUM, &chan_cfg));
|
||||
|
||||
int curr_adc = 0;
|
||||
int last_adc = 0;
|
||||
for (uint8_t i = 0; i <= 200; i += 20) {
|
||||
TEST_ESP_OK(dac_channels_set_voltage(handle, i));
|
||||
TEST_ESP_OK(dac_oneshot_output_voltage(oneshot_chan0_handle, i));
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
TEST_ESP_OK(adc2_get_raw(ADC_TEST_CHANNEL_NUM, ADC_TEST_WIDTH, &curr_adc));
|
||||
TEST_ESP_OK(adc_oneshot_read(adc2_handle, ADC_TEST_CHANNEL_NUM, &curr_adc));
|
||||
printf("DAC: %d - ADC: %d\n", i, curr_adc);
|
||||
if (last_adc != 0) {
|
||||
TEST_ASSERT_GREATER_THAN(last_adc, curr_adc);
|
||||
}
|
||||
last_adc = curr_adc;
|
||||
}
|
||||
TEST_ESP_OK(dac_channels_disable(handle));
|
||||
TEST_ESP_OK(dac_del_channels(handle));
|
||||
TEST_ESP_OK(dac_del_oneshot_channel(oneshot_chan0_handle));
|
||||
TEST_ESP_OK(adc_oneshot_del_unit(adc2_handle));
|
||||
}
|
||||
|
||||
TEST_CASE("DAC_dma_write_test", "[dac]")
|
||||
{
|
||||
dac_channels_handle_t handle;
|
||||
dac_channels_config_t cfg = {.chan_sel = DAC_CHANNEL_MASK_BOTH};
|
||||
dac_conti_config_t dma_cfg = {
|
||||
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
|
||||
dac_conti_handle_t conti_handle;
|
||||
dac_conti_config_t conti_cfg = {
|
||||
.chan_mask = DAC_CHANNEL_MASK_ALL,
|
||||
.desc_num = 8,
|
||||
.buf_size = 1024,
|
||||
.freq_hz = 48000,
|
||||
.offset = 0,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT,
|
||||
.desc_num = 10,
|
||||
.freq_hz = 20000,
|
||||
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
|
||||
};
|
||||
uint8_t *data = (uint8_t *)calloc(1, 2000);
|
||||
TEST_ASSERT(data);
|
||||
for (int i = 0; i < 2000; i++) {
|
||||
data[i] = i % 256;
|
||||
size_t len = 520; // To test if the driver can work correctly with uncommon length
|
||||
uint8_t buf[len];
|
||||
for (int i = 0; i < len; i++) {
|
||||
buf[i] = i % 104;
|
||||
}
|
||||
|
||||
TEST_ESP_OK(dac_new_channels(&cfg, &handle));
|
||||
TEST_ESP_OK(dac_channels_enable(handle));
|
||||
TEST_ESP_OK(dac_channels_init_continuous_mode(handle, &dma_cfg));
|
||||
TEST_ESP_OK(dac_channels_enable_continuous_mode(handle));
|
||||
|
||||
TEST_ESP_OK(dac_channels_write_continuously(handle, data, 2000, NULL, 1000));
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
TEST_ESP_OK(dac_channels_write_cyclically(handle, data, 2000, NULL, 1000));
|
||||
|
||||
TEST_ESP_OK(dac_channels_disable_continuous_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_deinit_continuous_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_disable(handle));
|
||||
TEST_ESP_OK(dac_del_channels(handle));
|
||||
free(data);
|
||||
TEST_ESP_OK(dac_new_conti_channels(&conti_cfg, &conti_handle));
|
||||
TEST_ESP_OK(dac_conti_enable(conti_handle));
|
||||
for (int i = 0; i < 4; i++) {
|
||||
TEST_ESP_OK(dac_conti_write_cyclically(conti_handle, buf, len, NULL));
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
for (int j = 0; j < 10; j++) {
|
||||
TEST_ESP_OK(dac_conti_write(conti_handle, buf, len, NULL, 1000));
|
||||
}
|
||||
}
|
||||
TEST_ESP_OK(dac_conti_disable(conti_handle));
|
||||
TEST_ESP_OK(dac_del_conti_channels(conti_handle));
|
||||
}
|
||||
|
||||
/* Test the conversion frequency by counting the pulse of WS signal
|
||||
@@ -221,26 +255,25 @@ TEST_CASE("DAC_dma_conver_frequency_test", "[dac]")
|
||||
esp_rom_gpio_connect_out_signal(GPIO_NUM_4, i2s_periph_signal[0].m_tx_ws_sig, 0, 0);
|
||||
esp_rom_gpio_connect_in_signal(GPIO_NUM_4, pcnt_periph_signals.groups[0].units[0].channels[0].pulse_sig, 0);
|
||||
|
||||
uint8_t *data = (uint8_t *)calloc(1, 2000);
|
||||
TEST_ASSERT(data);
|
||||
size_t len = 800;
|
||||
uint8_t data[len];
|
||||
for (int i = 0; i < len; i++) {
|
||||
data[i] = i % 256;
|
||||
}
|
||||
|
||||
/* Register DAC DMA using PLL */
|
||||
dac_channels_handle_t handle;
|
||||
dac_channels_config_t cfg = {.chan_sel = DAC_CHANNEL_MASK_BOTH};
|
||||
dac_conti_config_t dma_cfg = {
|
||||
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT,
|
||||
.desc_num = 10,
|
||||
dac_conti_handle_t conti_handle;
|
||||
dac_conti_config_t conti_cfg = {
|
||||
.chan_mask = DAC_CHANNEL_MASK_ALL,
|
||||
.desc_num = 8,
|
||||
.buf_size = 2048,
|
||||
.freq_hz = 20000,
|
||||
.offset = 0,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT,
|
||||
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
|
||||
};
|
||||
|
||||
/* Initialize DAC to test default PLL clock */
|
||||
TEST_ESP_OK(dac_new_channels(&cfg, &handle));
|
||||
TEST_ESP_OK(dac_channels_enable(handle));
|
||||
TEST_ESP_OK(dac_channels_init_continuous_mode(handle, &dma_cfg));
|
||||
TEST_ESP_OK(dac_channels_enable_continuous_mode(handle));
|
||||
/* Start transmitting data on line */
|
||||
TEST_ESP_OK(dac_channels_write_cyclically(handle, data, 2000, NULL, 1000));
|
||||
TEST_ESP_OK(dac_new_conti_channels(&conti_cfg, &conti_handle));
|
||||
TEST_ESP_OK(dac_conti_enable(conti_handle));
|
||||
TEST_ESP_OK(dac_conti_write_cyclically(conti_handle, data, len, NULL));
|
||||
|
||||
int expt_pulse = 2000;
|
||||
int real_pulse;
|
||||
@@ -250,23 +283,19 @@ TEST_CASE("DAC_dma_conver_frequency_test", "[dac]")
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
TEST_ESP_OK(pcnt_unit_stop(pcnt_unit));
|
||||
TEST_ESP_OK(pcnt_unit_get_count(pcnt_unit, &real_pulse));
|
||||
/* Delete DAC handle */
|
||||
TEST_ESP_OK(dac_channels_disable_continuous_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_deinit_continuous_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_disable(handle));
|
||||
TEST_ESP_OK(dac_del_channels(handle));
|
||||
/* Delete DAC continuous handle */
|
||||
TEST_ESP_OK(dac_conti_disable(conti_handle));
|
||||
TEST_ESP_OK(dac_del_conti_channels(conti_handle));
|
||||
|
||||
printf("[PLL | 20000 Hz] %d pulses, expected %d, err %d\n", real_pulse, expt_pulse, real_pulse - expt_pulse);
|
||||
TEST_ASSERT_INT_WITHIN(expt_pulse * 0.01, expt_pulse, real_pulse);
|
||||
|
||||
dma_cfg.clk_src = DAC_DIGI_CLK_SRC_APLL;
|
||||
conti_cfg.clk_src = DAC_DIGI_CLK_SRC_APLL;
|
||||
/* Initialize DAC to test APLL clock */
|
||||
TEST_ESP_OK(dac_new_channels(&cfg, &handle));
|
||||
TEST_ESP_OK(dac_channels_enable(handle));
|
||||
TEST_ESP_OK(dac_channels_init_continuous_mode(handle, &dma_cfg));
|
||||
TEST_ESP_OK(dac_channels_enable_continuous_mode(handle));
|
||||
TEST_ESP_OK(dac_new_conti_channels(&conti_cfg, &conti_handle));
|
||||
TEST_ESP_OK(dac_conti_enable(conti_handle));
|
||||
/* Start transmitting data on line */
|
||||
TEST_ESP_OK(dac_channels_write_cyclically(handle, data, 2000, NULL, 1000));
|
||||
TEST_ESP_OK(dac_conti_write_cyclically(conti_handle, data, len, NULL));
|
||||
|
||||
/* Count pulse by PCNT */
|
||||
TEST_ESP_OK(pcnt_unit_clear_count(pcnt_unit));
|
||||
@@ -275,14 +304,11 @@ TEST_CASE("DAC_dma_conver_frequency_test", "[dac]")
|
||||
TEST_ESP_OK(pcnt_unit_stop(pcnt_unit));
|
||||
TEST_ESP_OK(pcnt_unit_get_count(pcnt_unit, &real_pulse));
|
||||
/* Delete DAC handle */
|
||||
TEST_ESP_OK(dac_channels_disable_continuous_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_deinit_continuous_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_disable(handle));
|
||||
TEST_ESP_OK(dac_del_channels(handle));
|
||||
TEST_ESP_OK(dac_conti_disable(conti_handle));
|
||||
TEST_ESP_OK(dac_del_conti_channels(conti_handle));
|
||||
|
||||
printf("[APLL | 20000 Hz] %d pulses, expected %d, err %d\n", real_pulse, expt_pulse, real_pulse - expt_pulse);
|
||||
TEST_ASSERT_INT_WITHIN(expt_pulse * 0.01, expt_pulse, real_pulse);
|
||||
free(data);
|
||||
/* Free PCNT */
|
||||
TEST_ESP_OK(pcnt_del_channel(pcnt_chan));
|
||||
TEST_ESP_OK(pcnt_unit_stop(pcnt_unit));
|
||||
@@ -293,76 +319,100 @@ TEST_CASE("DAC_dma_conver_frequency_test", "[dac]")
|
||||
|
||||
TEST_CASE("DAC_cosine_wave_test", "[dac]")
|
||||
{
|
||||
dac_channels_handle_t handle;
|
||||
dac_channels_config_t cfg = {.chan_sel = DAC_CHANNEL_MASK_BOTH};
|
||||
dac_cosine_config_t cos_cfg = {
|
||||
.freq_hz = 1000,
|
||||
dac_cosine_handle_t cos_chan0_handle;
|
||||
dac_cosine_handle_t cos_chan1_handle;
|
||||
dac_cosine_config_t cos0_cfg = {
|
||||
.chan_id = DAC_CHAN_0,
|
||||
.freq_hz = 1000, // It will be covered by 8000 in the latter configuration
|
||||
.clk_src = DAC_COSINE_CLK_SRC_DEFAULT,
|
||||
.offset = 0,
|
||||
.phase = DAC_COSINE_PHASE_0,
|
||||
.scale = DAC_COSINE_NO_ATTEN,
|
||||
.atten = DAC_COSINE_ATTEN_DEFAULT,
|
||||
.flags.force_set_freq = false,
|
||||
};
|
||||
TEST_ESP_OK(dac_new_channels(&cfg, &handle));
|
||||
TEST_ESP_OK(dac_channels_enable(handle));
|
||||
TEST_ESP_OK(dac_channels_init_cosine_mode(handle, &cos_cfg));
|
||||
TEST_ESP_OK(dac_channels_start_cosine_output(handle));
|
||||
dac_cosine_config_t cos1_cfg = {
|
||||
.chan_id = DAC_CHAN_1,
|
||||
.freq_hz = 1000,
|
||||
.clk_src = DAC_COSINE_CLK_SRC_DEFAULT,
|
||||
.offset = 0,
|
||||
.phase = DAC_COSINE_PHASE_180,
|
||||
.atten = DAC_COSINE_ATTEN_DB_6,
|
||||
.flags.force_set_freq = false,
|
||||
};
|
||||
TEST_ESP_OK(dac_new_cosine_channel(&cos0_cfg, &cos_chan0_handle));
|
||||
TEST_ESP_OK(dac_new_cosine_channel(&cos1_cfg, &cos_chan1_handle));
|
||||
TEST_ESP_OK(dac_cosine_start(cos_chan0_handle));
|
||||
TEST_ESP_OK(dac_cosine_start(cos_chan1_handle));
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
// TODO: find some more meaningful way to test cosine wave
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
|
||||
TEST_ESP_OK(dac_channels_stop_cosine_output(handle));
|
||||
TEST_ESP_OK(dac_channels_deinit_cosine_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_disable(handle));
|
||||
TEST_ESP_OK(dac_del_channels(handle));
|
||||
TEST_ESP_OK(dac_cosine_stop(cos_chan0_handle));
|
||||
TEST_ESP_OK(dac_cosine_stop(cos_chan1_handle));
|
||||
TEST_ESP_OK(dac_del_cosine_channel(cos_chan0_handle));
|
||||
TEST_ESP_OK(dac_del_cosine_channel(cos_chan1_handle));
|
||||
}
|
||||
|
||||
static volatile bool task_run_flag;
|
||||
|
||||
static void dac_acyclicly_write_task(void *arg)
|
||||
static void dac_cyclically_write_task(void *arg)
|
||||
{
|
||||
dac_channels_handle_t dac_handle = (dac_channels_handle_t)arg;
|
||||
uint8_t buf[1000];
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
buf[i] = i % 256;
|
||||
}
|
||||
while (task_run_flag) {
|
||||
if (dac_channels_write_continuously(dac_handle, buf, 100, NULL, 1000) == ESP_OK) {
|
||||
printf("DAC write data success\n");
|
||||
dac_conti_handle_t dac_handle = (dac_conti_handle_t)arg;
|
||||
size_t len = 1000;
|
||||
uint8_t buf[len];
|
||||
uint8_t max_val = 50;
|
||||
while (1) {
|
||||
max_val += 50;
|
||||
for (int i = 0; i < len; i++) {
|
||||
buf[i] = i % max_val;
|
||||
}
|
||||
vTaskDelay(20);
|
||||
printf("Write cyclically\n");
|
||||
TEST_ESP_OK(dac_conti_write_cyclically(dac_handle, buf, len, NULL));
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("DAC_DMA_thread_safe", "[dac]")
|
||||
static void dac_continuously_write_task(void *arg)
|
||||
{
|
||||
dac_channels_handle_t handle;
|
||||
dac_channels_config_t cfg = {.chan_sel = DAC_CHANNEL_MASK_CH0};
|
||||
dac_conti_config_t dma_cfg = {
|
||||
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT,
|
||||
.desc_num = 10,
|
||||
.freq_hz = 20000,
|
||||
};
|
||||
TEST_ESP_OK(dac_new_channels(&cfg, &handle));
|
||||
TEST_ESP_OK(dac_channels_enable(handle));
|
||||
TEST_ESP_OK(dac_channels_init_continuous_mode(handle, &dma_cfg));
|
||||
TEST_ESP_OK(dac_channels_enable_continuous_mode(handle));
|
||||
task_run_flag = true;
|
||||
xTaskCreate(dac_acyclicly_write_task, "dac_acyclicly_write_task", 4096, handle, 5, NULL);
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
TEST_ESP_OK(dac_channels_disable_continuous_mode(handle));
|
||||
printf("DAC stopped\n");
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
TEST_ESP_OK(dac_channels_enable_continuous_mode(handle));
|
||||
printf("DAC started\n");
|
||||
dac_conti_handle_t dac_handle = (dac_conti_handle_t)arg;
|
||||
size_t len = 2048;
|
||||
uint8_t buf[len];
|
||||
for (int i = 0; i < len; i++) {
|
||||
buf[i] = i % 256;
|
||||
}
|
||||
while (1) {
|
||||
printf("Write continuously\n");
|
||||
TEST_ESP_OK(dac_conti_write(dac_handle, buf, len, NULL, 100));
|
||||
vTaskDelay(pdMS_TO_TICKS(300));
|
||||
}
|
||||
task_run_flag = false;
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
TEST_ESP_OK(dac_channels_disable_continuous_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_deinit_continuous_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_disable(handle));
|
||||
TEST_ESP_OK(dac_del_channels(handle));
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("DAC_continuous_mode_concurrency_test", "[dac]")
|
||||
{
|
||||
dac_conti_handle_t conti_handle;
|
||||
dac_conti_config_t conti_cfg = {
|
||||
.chan_mask = DAC_CHANNEL_MASK_ALL,
|
||||
.desc_num = 8,
|
||||
.buf_size = 1024,
|
||||
.freq_hz = 48000,
|
||||
.offset = 0,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT,
|
||||
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
|
||||
};
|
||||
|
||||
TEST_ESP_OK(dac_new_conti_channels(&conti_cfg, &conti_handle));
|
||||
TEST_ESP_OK(dac_conti_enable(conti_handle));
|
||||
|
||||
TaskHandle_t cyc_task;
|
||||
TaskHandle_t con_task;
|
||||
xTaskCreate(dac_cyclically_write_task, "dac_cyclically_write_task", 4096, conti_handle, 5, &cyc_task);
|
||||
xTaskCreate(dac_continuously_write_task, "dac_continuously_write_task", 4096, conti_handle, 5, &con_task);
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
|
||||
vTaskDelete(cyc_task);
|
||||
vTaskDelete(con_task);
|
||||
|
||||
TEST_ESP_OK(dac_conti_disable(conti_handle));
|
||||
TEST_ESP_OK(dac_del_conti_channels(conti_handle));
|
||||
}
|
||||
|
@@ -7,40 +7,91 @@
|
||||
#include <stdio.h>
|
||||
#include "unity.h"
|
||||
#include "unity_test_utils.h"
|
||||
#include "driver/dac_driver.h"
|
||||
#include "esp_private/spi_flash_os.h"
|
||||
#include "unity_test_utils_cache.h"
|
||||
#include "driver/dac_oneshot.h"
|
||||
#include "driver/dac_conti.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#define BUF_SIZE 2000
|
||||
|
||||
static void IRAM_ATTR test_dac_direct_set_safety(dac_channels_handle_t handle)
|
||||
typedef struct {
|
||||
int cnt;
|
||||
bool result;
|
||||
} test_dac_intr_data_t;
|
||||
|
||||
static void IRAM_ATTR test_dac_direct_set_safety(void *usr_ctx)
|
||||
{
|
||||
spi_flash_guard_get()->start();
|
||||
dac_channels_set_voltage(handle, 128);
|
||||
spi_flash_guard_get()->end();
|
||||
dac_oneshot_handle_t handle = (dac_oneshot_handle_t)usr_ctx;
|
||||
dac_oneshot_output_voltage(handle, 128);
|
||||
}
|
||||
|
||||
static void IRAM_ATTR test_dac_dma_iram_safety(dac_channels_handle_t handle, uint8_t *data, uint32_t len)
|
||||
static void IRAM_ATTR test_dac_dma_iram_safety(void *usr_ctx)
|
||||
{
|
||||
spi_flash_guard_get()->start();
|
||||
// Change the data of DMA
|
||||
for (int i = 0; i < len; i++) {
|
||||
data[i] = i % 100;
|
||||
uint8_t *data = (uint8_t *)usr_ctx;
|
||||
for (int i = 0; i < BUF_SIZE; i++) {
|
||||
data[i] = i % 128 + 1;
|
||||
}
|
||||
spi_flash_guard_get()->end();
|
||||
}
|
||||
|
||||
static void IRAM_ATTR test_dac_dma_intr_iram_safety(void *usr_ctx)
|
||||
{
|
||||
test_dac_intr_data_t *data = (test_dac_intr_data_t *)usr_ctx;
|
||||
data->cnt = 0;
|
||||
esp_rom_delay_us(100 * 1000);
|
||||
data->result = data->cnt > 0;
|
||||
}
|
||||
|
||||
static bool IRAM_ATTR test_dac_on_convert_done_cb(dac_conti_handle_t handle, const dac_event_data_t *event, void *user_data)
|
||||
{
|
||||
test_dac_intr_data_t *data = (test_dac_intr_data_t *)user_data;
|
||||
data->cnt++;
|
||||
return false;
|
||||
}
|
||||
|
||||
TEST_CASE("DAC_IRAM_safe_test", "[dac]")
|
||||
{
|
||||
dac_channels_handle_t handle;
|
||||
dac_channels_config_t cfg = {.chan_sel = DAC_CHANNEL_MASK_BOTH};
|
||||
dac_conti_config_t dma_cfg = {
|
||||
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT,
|
||||
.desc_num = 10,
|
||||
dac_oneshot_handle_t oneshot_handle;
|
||||
TEST_ESP_OK(dac_new_oneshot_channel(&(dac_oneshot_config_t){.chan_id = DAC_CHAN_0}, &oneshot_handle));
|
||||
|
||||
/* Test direct voltage setting safety */
|
||||
unity_utils_run_cache_disable_stub(test_dac_direct_set_safety, oneshot_handle);
|
||||
|
||||
TEST_ESP_OK(dac_del_oneshot_channel(oneshot_handle));
|
||||
|
||||
dac_conti_handle_t conti_handle;
|
||||
dac_conti_config_t conti_cfg = {
|
||||
.chan_mask = DAC_CHANNEL_MASK_ALL,
|
||||
.desc_num = 8,
|
||||
.buf_size = 2048,
|
||||
.freq_hz = 40000,
|
||||
.offset = 0,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT, // If the frequency is out of range, try 'DAC_DIGI_CLK_SRC_APLL'
|
||||
/* Assume the data in buffer is 'A B C D E F'
|
||||
* DAC_CHANNEL_MODE_SIMUL:
|
||||
* - channel 0: A B C D E F
|
||||
* - channel 1: A B C D E F
|
||||
* DAC_CHANNEL_MODE_ALTER:
|
||||
* - channel 0: A C E
|
||||
* - channel 1: B D F
|
||||
*/
|
||||
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
|
||||
};
|
||||
/* Allocate continuous channel */
|
||||
TEST_ESP_OK(dac_new_conti_channels(&conti_cfg, &conti_handle));
|
||||
/* Register a callback to check if the interrupt can still triggered when cache is disabled */
|
||||
dac_event_callbacks_t cbs = {
|
||||
.on_convert_done = test_dac_on_convert_done_cb,
|
||||
.on_stop = NULL,
|
||||
};
|
||||
test_dac_intr_data_t intr_data = {
|
||||
.cnt = 0,
|
||||
.result = false,
|
||||
};
|
||||
TEST_ESP_OK(dac_conti_register_event_callback(conti_handle, &cbs, &intr_data));
|
||||
/* Enable the channels in the group */
|
||||
TEST_ESP_OK(dac_conti_enable(conti_handle));
|
||||
|
||||
/* Real data in internal memory */
|
||||
uint8_t *data = (uint8_t *)heap_caps_calloc(1, BUF_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
@@ -48,23 +99,12 @@ TEST_CASE("DAC_IRAM_safe_test", "[dac]")
|
||||
for (int i = 0; i < BUF_SIZE; i++) {
|
||||
data[i] = i % 256;
|
||||
}
|
||||
/* Get ready for dma transmition */
|
||||
TEST_ESP_OK(dac_new_channels(&cfg, &handle));
|
||||
TEST_ESP_OK(dac_channels_enable(handle));
|
||||
/* Test direct voltage setting safety */
|
||||
test_dac_direct_set_safety(handle);
|
||||
dac_conti_write_cyclically(conti_handle, data, BUF_SIZE, NULL);
|
||||
unity_utils_run_cache_disable_stub(test_dac_dma_iram_safety, data);
|
||||
unity_utils_run_cache_disable_stub(test_dac_dma_intr_iram_safety, &intr_data);
|
||||
TEST_ASSERT(intr_data.result);
|
||||
|
||||
TEST_ESP_OK(dac_channels_init_continuous_mode(handle, &dma_cfg));
|
||||
TEST_ESP_OK(dac_channels_enable_continuous_mode(handle));
|
||||
|
||||
/* Simulate cache off */
|
||||
dac_channels_write_cyclically(handle, data, BUF_SIZE, NULL, 1000);
|
||||
test_dac_dma_iram_safety(handle, data, BUF_SIZE);
|
||||
|
||||
/* Deregister DAC DMA channel group */
|
||||
TEST_ESP_OK(dac_channels_disable_continuous_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_deinit_continuous_mode(handle));
|
||||
TEST_ESP_OK(dac_channels_disable(handle));
|
||||
TEST_ESP_OK(dac_del_channels(handle));
|
||||
TEST_ESP_OK(dac_conti_disable(conti_handle));
|
||||
TEST_ESP_OK(dac_del_conti_channels(conti_handle));
|
||||
free(data);
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
CONFIG_COMPILER_DUMP_RTL_FILES=y
|
||||
CONFIG_DAC_ISR_IRAM_SAFE=y
|
||||
CONFIG_DAC_CTRL_FUNC_IN_IRAM=y
|
||||
# silent the error check, as the error string are stored in rodata, causing RTL check failure
|
||||
CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_NONE=y
|
||||
|
@@ -1,4 +1,4 @@
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
CONFIG_ESP_TASK_WDT=n
|
||||
# Disable this config, otherwise DAC will be disabled when ADC initialized
|
||||
CONFIG_ADC_DISABLE_DAC=n
|
||||
CONFIG_ADC_DISABLE_DAC_OUTPUT=n
|
||||
|
@@ -6,6 +6,7 @@
|
||||
/*
|
||||
Tests for the dac device driver
|
||||
*/
|
||||
#include <inttypes.h>
|
||||
#include "esp_system.h"
|
||||
|
||||
#include "unity.h"
|
||||
@@ -158,26 +159,26 @@ TEST_CASE("esp32s2_adc2-dac_with_adc2_calibration", "[dac_legacy]")
|
||||
printf("Test 0dB atten...\n");
|
||||
adc2_config_channel_atten((adc2_channel_t)ADC_TEST_CHANNEL_NUM, ADC_ATTEN_DB_0);
|
||||
esp_adc_cal_characterize(ADC_UNIT_2, ADC_ATTEN_DB_0, ADC_WIDTH_BIT_13, 0, &chars);
|
||||
printf("a %d, b %d\n", chars.coeff_a, chars.coeff_b);
|
||||
printf("a %"PRIu32", b %"PRIu32"\n", chars.coeff_a, chars.coeff_b);
|
||||
subtest_adc_dac(750, &chars);
|
||||
|
||||
printf("Test 2.5dB atten...\n");
|
||||
adc2_config_channel_atten((adc2_channel_t)ADC_TEST_CHANNEL_NUM, ADC_ATTEN_DB_2_5);
|
||||
esp_adc_cal_characterize(ADC_UNIT_2, ADC_ATTEN_DB_2_5, ADC_WIDTH_BIT_13, 0, &chars);
|
||||
printf("a %d, b %d\n", chars.coeff_a, chars.coeff_b);
|
||||
printf("a %"PRIu32", b %"PRIu32"\n", chars.coeff_a, chars.coeff_b);
|
||||
subtest_adc_dac(1100, &chars);
|
||||
|
||||
printf("Test 6dB atten...\n");
|
||||
adc2_config_channel_atten((adc2_channel_t)ADC_TEST_CHANNEL_NUM, ADC_ATTEN_DB_6);
|
||||
esp_adc_cal_characterize(ADC_UNIT_2, ADC_ATTEN_DB_6, ADC_WIDTH_BIT_13, 0, &chars);
|
||||
printf("a %d, b %d\n", chars.coeff_a, chars.coeff_b);
|
||||
printf("a %"PRIu32", b %"PRIu32"\n", chars.coeff_a, chars.coeff_b);
|
||||
subtest_adc_dac(800, &chars);
|
||||
subtest_adc_dac(1250, &chars);
|
||||
|
||||
printf("Test 11dB atten...\n");
|
||||
adc2_config_channel_atten((adc2_channel_t)ADC_TEST_CHANNEL_NUM, ADC_ATTEN_DB_11);
|
||||
esp_adc_cal_characterize(ADC_UNIT_2, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_13, 0, &chars);
|
||||
printf("a %d, b %d\n", chars.coeff_a, chars.coeff_b);
|
||||
printf("a %"PRIu32", b %"PRIu32"\n", chars.coeff_a, chars.coeff_b);
|
||||
subtest_adc_dac(1500, &chars);
|
||||
subtest_adc_dac(2500, &chars);
|
||||
}
|
||||
|
@@ -158,7 +158,7 @@ static void s_disable_dac(adc_oneshot_hal_ctx_t *hal, adc_channel_t channel)
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
if (hal->unit == ADC_UNIT_2) {
|
||||
if (channel == ADC_CHANNEL_8) {
|
||||
dac_ll_power_down(DAC_CHAN_0); // the same as DAC channel 1
|
||||
dac_ll_power_down(DAC_CHAN_0); // the same as DAC channel 0
|
||||
}
|
||||
if (channel == ADC_CHANNEL_9) {
|
||||
dac_ll_power_down(DAC_CHAN_1);
|
||||
@@ -167,7 +167,7 @@ static void s_disable_dac(adc_oneshot_hal_ctx_t *hal, adc_channel_t channel)
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
if (hal->unit == ADC_UNIT_2) {
|
||||
if (channel == ADC_CHANNEL_6) {
|
||||
dac_ll_power_down(DAC_CHAN_0); // the same as DAC channel 1
|
||||
dac_ll_power_down(DAC_CHAN_0); // the same as DAC channel 0
|
||||
}
|
||||
if (channel == ADC_CHANNEL_7) {
|
||||
dac_ll_power_down(DAC_CHAN_1);
|
||||
|
@@ -56,6 +56,7 @@ static inline void dac_ll_power_down(dac_channel_t channel)
|
||||
* @param value Output value. Value range: 0 ~ 255.
|
||||
* The corresponding range of voltage is 0v ~ VDD3P3_RTC.
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void dac_ll_update_output_value(dac_channel_t channel, uint8_t value)
|
||||
{
|
||||
if (channel == DAC_CHAN_0) {
|
||||
@@ -104,7 +105,7 @@ static inline void dac_ll_cw_generator_disable(void)
|
||||
* @param channel DAC channel num.
|
||||
* @param enable
|
||||
*/
|
||||
static inline void dac_ll_cw_set_channel(dac_channel_t channel, bool enable)
|
||||
static inline void dac_ll_cw_enable_channel(dac_channel_t channel, bool enable)
|
||||
{
|
||||
if (channel == DAC_CHAN_0) {
|
||||
SENS.sar_dac_ctrl2.dac_cw_en1 = enable;
|
||||
@@ -117,12 +118,12 @@ static inline void dac_ll_cw_set_channel(dac_channel_t channel, bool enable)
|
||||
* Set frequency of cosine wave generator output.
|
||||
*
|
||||
* @note We know that CLK8M is about 8M, but don't know the actual value. so this freq have limited error.
|
||||
* @param freq_hz CW generator frequency. Range: 130(130Hz)
|
||||
* @param freq_hz CW generator frequency. Range: >= 130Hz, no exact ceiling limitation, but will distort when reach several MHz
|
||||
* @param rtc8m_freq the calibrated RTC 8M clock frequency
|
||||
*/
|
||||
static inline void dac_ll_cw_set_freq(uint32_t freq, uint32_t rtc8m_freq)
|
||||
{
|
||||
uint32_t sw_freq = (uint32_t)(((float)freq / (float)rtc8m_freq) * 65536);
|
||||
uint32_t sw_freq = (uint32_t)(((uint64_t)freq << 16) / rtc8m_freq);
|
||||
HAL_FORCE_MODIFY_U32_REG_FIELD(SENS.sar_dac_ctrl1, sw_fstep, (sw_freq > 0xFFFF) ? 0xFFFF : sw_freq);
|
||||
}
|
||||
|
||||
@@ -130,14 +131,18 @@ static inline void dac_ll_cw_set_freq(uint32_t freq, uint32_t rtc8m_freq)
|
||||
* Set the amplitude of the cosine wave generator output.
|
||||
*
|
||||
* @param channel DAC channel num.
|
||||
* @param scale The multiple of the amplitude. The max amplitude is VDD3P3_RTC.
|
||||
* @param atten The attenuation of the amplitude. The max amplitude is VDD3P3_RTC.
|
||||
* 0: attenuation = 1, amplitude = VDD3P3_RTC / attenuation,
|
||||
* 1: attenuation = 2, amplitude = VDD3P3_RTC / attenuation,
|
||||
* 2: attenuation = 4, amplitude = VDD3P3_RTC / attenuation,
|
||||
* 3: attenuation = 8, amplitude = VDD3P3_RTC / attenuation
|
||||
*/
|
||||
static inline void dac_ll_cw_set_scale(dac_channel_t channel, uint32_t scale)
|
||||
static inline void dac_ll_cw_set_atten(dac_channel_t channel, dac_cosine_atten_t atten)
|
||||
{
|
||||
if (channel == DAC_CHAN_0) {
|
||||
SENS.sar_dac_ctrl2.dac_scale1 = scale;
|
||||
SENS.sar_dac_ctrl2.dac_scale1 = atten;
|
||||
} else if (channel == DAC_CHAN_1) {
|
||||
SENS.sar_dac_ctrl2.dac_scale2 = scale;
|
||||
SENS.sar_dac_ctrl2.dac_scale2 = atten;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +152,7 @@ static inline void dac_ll_cw_set_scale(dac_channel_t channel, uint32_t scale)
|
||||
* @param channel DAC channel num.
|
||||
* @param phase Phase value. 0: 0x02 180: 0x03.
|
||||
*/
|
||||
static inline void dac_ll_cw_set_phase(dac_channel_t channel, uint32_t phase)
|
||||
static inline void dac_ll_cw_set_phase(dac_channel_t channel, dac_cosine_phase_t phase)
|
||||
{
|
||||
if (channel == DAC_CHAN_0) {
|
||||
SENS.sar_dac_ctrl2.dac_inv1 = phase;
|
||||
|
@@ -68,6 +68,7 @@ static inline void dac_ll_power_down(dac_channel_t channel)
|
||||
* @param value Output value. Value range: 0 ~ 255.
|
||||
* The corresponding range of voltage is 0v ~ VDD3P3_RTC.
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void dac_ll_update_output_value(dac_channel_t channel, uint8_t value)
|
||||
{
|
||||
if (channel == DAC_CHAN_0) {
|
||||
@@ -125,7 +126,7 @@ static inline void dac_ll_cw_generator_disable(void)
|
||||
* @param channel DAC channel num.
|
||||
* @param enable
|
||||
*/
|
||||
static inline void dac_ll_cw_set_channel(dac_channel_t channel, bool enable)
|
||||
static inline void dac_ll_cw_enable_channel(dac_channel_t channel, bool enable)
|
||||
{
|
||||
if (channel == DAC_CHAN_0) {
|
||||
SENS.sar_dac_ctrl2.dac_cw_en1 = enable;
|
||||
@@ -138,12 +139,12 @@ static inline void dac_ll_cw_set_channel(dac_channel_t channel, bool enable)
|
||||
* Set frequency of cosine wave generator output.
|
||||
*
|
||||
* @note We know that CLK8M is about 8M, but don't know the actual value. so this freq have limited error.
|
||||
* @param freq_hz CW generator frequency. Range: >= 130(130Hz)
|
||||
* @param freq_hz CW generator frequency. Range: >= 130Hz, no exact ceiling limitation, but will distort when reach several MHz
|
||||
* @param rtc8m_freq the calibrated RTC 8M clock frequency
|
||||
*/
|
||||
static inline void dac_ll_cw_set_freq(uint32_t freq, uint32_t rtc8m_freq)
|
||||
{
|
||||
uint32_t sw_freq = (uint32_t)(((float)freq / (float)rtc8m_freq) * 65536);
|
||||
uint32_t sw_freq = (uint32_t)(((uint64_t)freq << 16) / rtc8m_freq);
|
||||
HAL_FORCE_MODIFY_U32_REG_FIELD(SENS.sar_dac_ctrl1, sw_fstep, (sw_freq > 0xFFFF) ? 0xFFFF : sw_freq);
|
||||
}
|
||||
|
||||
@@ -151,14 +152,18 @@ static inline void dac_ll_cw_set_freq(uint32_t freq, uint32_t rtc8m_freq)
|
||||
* Set the amplitude of the cosine wave generator output.
|
||||
*
|
||||
* @param channel DAC channel num.
|
||||
* @param scale The multiple of the amplitude. The max amplitude is VDD3P3_RTC.
|
||||
* @param atten The attenuation of the amplitude. The max amplitude is VDD3P3_RTC.
|
||||
* 0: attenuation = 1, amplitude = VDD3P3_RTC / attenuation,
|
||||
* 1: attenuation = 2, amplitude = VDD3P3_RTC / attenuation,
|
||||
* 2: attenuation = 4, amplitude = VDD3P3_RTC / attenuation,
|
||||
* 3: attenuation = 8, amplitude = VDD3P3_RTC / attenuation
|
||||
*/
|
||||
static inline void dac_ll_cw_set_scale(dac_channel_t channel, uint32_t scale)
|
||||
static inline void dac_ll_cw_set_atten(dac_channel_t channel, dac_cosine_atten_t atten)
|
||||
{
|
||||
if (channel == DAC_CHAN_0) {
|
||||
SENS.sar_dac_ctrl2.dac_scale1 = scale;
|
||||
SENS.sar_dac_ctrl2.dac_scale1 = atten;
|
||||
} else if (channel == DAC_CHAN_1) {
|
||||
SENS.sar_dac_ctrl2.dac_scale2 = scale;
|
||||
SENS.sar_dac_ctrl2.dac_scale2 = atten;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +173,7 @@ static inline void dac_ll_cw_set_scale(dac_channel_t channel, uint32_t scale)
|
||||
* @param channel DAC channel num.
|
||||
* @param phase Phase value. 0: 0x02 180: 0x03.
|
||||
*/
|
||||
static inline void dac_ll_cw_set_phase(dac_channel_t channel, uint32_t phase)
|
||||
static inline void dac_ll_cw_set_phase(dac_channel_t channel, dac_cosine_phase_t phase)
|
||||
{
|
||||
if (channel == DAC_CHAN_0) {
|
||||
SENS.sar_dac_ctrl2.dac_inv1 = phase;
|
||||
|
@@ -1260,6 +1260,7 @@ static inline void spi_dma_ll_enable_out_auto_wrback(spi_dma_dev_t *dma_out, uin
|
||||
* @param channel DMA channel, for chip version compatibility, not used.
|
||||
* @return The address
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline uint32_t spi_dma_ll_get_out_eof_desc_addr(spi_dma_dev_t *dma_out, uint32_t channel)
|
||||
{
|
||||
return dma_out->dma_out_eof_des_addr;
|
||||
|
@@ -11,6 +11,27 @@ typedef enum {
|
||||
DAC_CHANNEL_2 __attribute__((deprecated("please use 'DAC_CHAN_1' instead"))) = 1, /*!< Alias of 'DAC_CHAN_1', now the channel index start from '0' */
|
||||
} dac_channel_t;
|
||||
|
||||
/**
|
||||
* @brief The attenuation of the amplitude of the cosine wave generator. The max amplitude is VDD3P3_RTC.
|
||||
*/
|
||||
typedef enum {
|
||||
DAC_COSINE_ATTEN_DEFAULT = 0x0, /*!< No attenuation to the DAC cosine wave amplitude. Default. */
|
||||
DAC_COSINE_ATTEN_DB_0 = 0x0, /*!< Original amplitude of the DAC cosine wave, equals to DAC_COSINE_ATTEN_DEFAULT */
|
||||
DAC_COSINE_ATTEN_DB_6 = 0x1, /*!< 1/2 amplitude of the DAC cosine wave */
|
||||
DAC_COSINE_ATTEN_DB_12 = 0x2, /*!< 1/4 amplitude of the DAC cosine wave */
|
||||
DAC_COSINE_ATTEN_DB_18 = 0x3, /*!< 1/8 amplitude of the DAC cosine wave */
|
||||
} dac_cosine_atten_t;
|
||||
|
||||
/**
|
||||
* @brief Set the phase of the cosine wave generator output.
|
||||
* @note Only 0 or 180 are supported,
|
||||
* it will be set to 0 as default if configured to an unsupported phase.
|
||||
*/
|
||||
typedef enum {
|
||||
DAC_COSINE_PHASE_0 = 0x02, /*!< Phase shift +0° */
|
||||
DAC_COSINE_PHASE_180 = 0x03, /*!< Phase shift +180° */
|
||||
} dac_cosine_phase_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@@ -10,6 +10,6 @@
|
||||
Bunch of constants for DAC peripheral: GPIO number
|
||||
*/
|
||||
const dac_signal_conn_t dac_periph_signal = {
|
||||
.dac_channel_io_num[0] = DAC_CHAN_0_GPIO_NUM,
|
||||
.dac_channel_io_num[1] = DAC_CHAN_1_GPIO_NUM,
|
||||
.dac_channel_io_num[0] = 25,
|
||||
.dac_channel_io_num[1] = 26,
|
||||
};
|
||||
|
@@ -243,7 +243,7 @@ config SOC_CPU_WATCHPOINT_SIZE
|
||||
int
|
||||
default 64
|
||||
|
||||
config SOC_DAC_PERIPH_NUM
|
||||
config SOC_DAC_CHAN_NUM
|
||||
int
|
||||
default 2
|
||||
|
||||
|
@@ -289,23 +289,23 @@ typedef enum {
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
DAC_CLK_SRC_PLLD2 = SOC_MOD_CLK_PLL_D2,
|
||||
DAC_DIGI_CLK_SRC_APLL = SOC_MOD_CLK_APLL,
|
||||
DAC_DIGI_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_D2,
|
||||
DAC_DIGI_CLK_SRC_PLLD2 = SOC_MOD_CLK_PLL_D2, /*!< Select PLL_D2 as the source clock */
|
||||
DAC_DIGI_CLK_SRC_APLL = SOC_MOD_CLK_APLL, /*!< Select APLL as the source clock */
|
||||
DAC_DIGI_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_D2, /*!< Select PLL_D2 as the default source clock */
|
||||
} soc_periph_dac_digi_clk_src_t;
|
||||
|
||||
/**
|
||||
* @brief Array initializer for all supported clock sources of DAC cosine wave generator
|
||||
*/
|
||||
#define SOC_DAC_COSINE_CLKS {DAC_COSINE_CLK_SRC_RTC}
|
||||
#define SOC_DAC_COSINE_CLKS {SOC_MOD_CLK_RTC_FAST}
|
||||
|
||||
/**
|
||||
* @brief DAC cosine wave generator clock source
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
DAC_COSINE_CLK_SRC_RTC = SOC_MOD_CLK_RTC_FAST,
|
||||
DAC_COSINE_CLK_SRC_DEFAULT = SOC_MOD_CLK_RTC_FAST,
|
||||
DAC_COSINE_CLK_SRC_RTC_FAST = SOC_MOD_CLK_RTC_FAST, /*!< Select RTC FAST as the source clock */
|
||||
DAC_COSINE_CLK_SRC_DEFAULT = SOC_MOD_CLK_RTC_FAST, /*!< Select RTC FAST as the default source clock */
|
||||
} soc_periph_dac_cosine_clk_src_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@@ -4,13 +4,13 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _SOC_DAC_CHANNEL_H
|
||||
#define _SOC_DAC_CHANNEL_H
|
||||
#pragma once
|
||||
|
||||
|
||||
#define DAC_GPIO25_CHANNEL DAC_CHAN_0
|
||||
#define DAC_CHAN_0_GPIO_NUM 25
|
||||
#define DAC_CHAN0_GPIO_NUM 25
|
||||
#define DAC_CHANNEL_1_GPIO_NUM DAC_CHAN0_GPIO_NUM //`DAC_CHANNEL_1_GPIO_NUM` is defined for DAC legacy driver, indicating the first DAC channel.
|
||||
|
||||
#define DAC_GPIO26_CHANNEL DAC_CHAN_1
|
||||
#define DAC_CHAN_1_GPIO_NUM 26
|
||||
|
||||
#endif
|
||||
#define DAC_CHAN1_GPIO_NUM 26
|
||||
#define DAC_CHANNEL_2_GPIO_NUM DAC_CHAN1_GPIO_NUM //`DAC_CHANNEL_2_GPIO_NUM` is defined for DAC legacy driver, indicating the second DAC channel.
|
||||
|
@@ -148,7 +148,7 @@
|
||||
#define SOC_CPU_WATCHPOINT_SIZE 64 // bytes
|
||||
|
||||
/*-------------------------- DAC CAPS ----------------------------------------*/
|
||||
#define SOC_DAC_PERIPH_NUM 2
|
||||
#define SOC_DAC_CHAN_NUM 2
|
||||
#define SOC_DAC_RESOLUTION 8 // DAC resolution ratio 8 bit
|
||||
#define SOC_DAC_DMA_16BIT_ALIGN 1 // The DMA data should left shift 8 bit to be aligned with 16 bit
|
||||
|
||||
|
@@ -10,6 +10,6 @@
|
||||
Bunch of constants for DAC peripheral: GPIO number
|
||||
*/
|
||||
const dac_signal_conn_t dac_periph_signal = {
|
||||
.dac_channel_io_num[0] = DAC_CHAN_0_GPIO_NUM,
|
||||
.dac_channel_io_num[1] = DAC_CHAN_1_GPIO_NUM,
|
||||
.dac_channel_io_num[0] = 17,
|
||||
.dac_channel_io_num[1] = 18,
|
||||
};
|
||||
|
@@ -271,7 +271,7 @@ config SOC_CPU_WATCHPOINT_SIZE
|
||||
int
|
||||
default 64
|
||||
|
||||
config SOC_DAC_PERIPH_NUM
|
||||
config SOC_DAC_CHAN_NUM
|
||||
int
|
||||
default 2
|
||||
|
||||
|
@@ -277,23 +277,23 @@ typedef enum {
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
DAC_DIGI_CLK_SRC_APB = SOC_MOD_CLK_APB,
|
||||
DAC_DIGI_CLK_SRC_APLL = SOC_MOD_CLK_APLL,
|
||||
DAC_DIGI_CLK_SRC_DEFAULT = SOC_MOD_CLK_APB,
|
||||
DAC_DIGI_CLK_SRC_APB = SOC_MOD_CLK_APB, /*!< Select APB as the source clock */
|
||||
DAC_DIGI_CLK_SRC_APLL = SOC_MOD_CLK_APLL, /*!< Select APLL as the source clock */
|
||||
DAC_DIGI_CLK_SRC_DEFAULT = SOC_MOD_CLK_APB, /*!< Select APB as the default source clock */
|
||||
} soc_periph_dac_digi_clk_src_t;
|
||||
|
||||
/**
|
||||
* @brief Array initializer for all supported clock sources of DAC cosine wave generator
|
||||
*/
|
||||
#define SOC_DAC_COSINE_CLKS {DAC_COSINE_CLK_SRC_RTC}
|
||||
#define SOC_DAC_COSINE_CLKS {SOC_MOD_CLK_RTC_FAST}
|
||||
|
||||
/**
|
||||
* @brief DAC cosine wave generator clock source
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
DAC_COSINE_CLK_SRC_RTC = SOC_MOD_CLK_RTC_FAST,
|
||||
DAC_COSINE_CLK_SRC_DEFAULT = SOC_MOD_CLK_RTC_FAST,
|
||||
DAC_COSINE_CLK_SRC_RTC_FAST = SOC_MOD_CLK_RTC_FAST, /*!< Select RTC FAST as the source clock */
|
||||
DAC_COSINE_CLK_SRC_DEFAULT = SOC_MOD_CLK_RTC_FAST, /*!< Select RTC FAST as the default source clock */
|
||||
} soc_periph_dac_cosine_clk_src_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@@ -4,13 +4,12 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _SOC_DAC_CHANNEL_H
|
||||
#define _SOC_DAC_CHANNEL_H
|
||||
#pragma once
|
||||
|
||||
#define DAC_GPIO17_CHANNEL DAC_CHAN_0
|
||||
#define DAC_CHAN_0_GPIO_NUM 17
|
||||
#define DAC_CHAN0_GPIO_NUM 17
|
||||
#define DAC_CHANNEL_1_GPIO_NUM DAC_CHAN0_GPIO_NUM //`DAC_CHANNEL_1_GPIO_NUM` is defined for DAC legacy driver, indicating the first DAC channel.
|
||||
|
||||
#define DAC_GPIO18_CHANNEL DAC_CHAN_1
|
||||
#define DAC_CHAN_1_GPIO_NUM 18
|
||||
|
||||
#endif
|
||||
#define DAC_CHAN1_GPIO_NUM 18
|
||||
#define DAC_CHANNEL_2_GPIO_NUM DAC_CHAN1_GPIO_NUM //`DAC_CHANNEL_2_GPIO_NUM` is defined for DAC legacy driver, indicating the second DAC channel.
|
||||
|
@@ -130,7 +130,7 @@
|
||||
#define SOC_CPU_WATCHPOINT_SIZE 64 // bytes
|
||||
|
||||
/*-------------------------- DAC CAPS ----------------------------------------*/
|
||||
#define SOC_DAC_PERIPH_NUM 2
|
||||
#define SOC_DAC_CHAN_NUM 2
|
||||
#define SOC_DAC_RESOLUTION 8 // DAC resolution ratio 8 bit
|
||||
|
||||
/*-------------------------- GPIO CAPS ---------------------------------------*/
|
||||
|
@@ -1,16 +1,8 @@
|
||||
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -19,7 +11,6 @@
|
||||
#include "soc/rtc_io_reg.h"
|
||||
#include "soc/rtc_io_struct.h"
|
||||
#include "soc/rtc.h"
|
||||
#include "soc/dac_channel.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -28,7 +19,7 @@ extern "C"
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
const uint8_t dac_channel_io_num[SOC_DAC_PERIPH_NUM];
|
||||
const uint8_t dac_channel_io_num[SOC_DAC_CHAN_NUM];
|
||||
} dac_signal_conn_t;
|
||||
|
||||
extern const dac_signal_conn_t dac_periph_signal;
|
||||
|
BIN
docs/_static/diagrams/dac/dac_file_structure.png
vendored
BIN
docs/_static/diagrams/dac/dac_file_structure.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 79 KiB |
BIN
docs/_static/diagrams/dac/dac_state_machine.png
vendored
BIN
docs/_static/diagrams/dac/dac_state_machine.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 110 KiB |
@@ -61,6 +61,10 @@ INPUT = \
|
||||
$(PROJECT_PATH)/components/bt/host/nimble/esp-hci/include/esp_nimble_hci.h \
|
||||
$(PROJECT_PATH)/components/bt/include/esp32/include/esp_bt.h \
|
||||
$(PROJECT_PATH)/components/console/esp_console.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/dac_conti.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/dac_cosine.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/dac_oneshot.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/dac_types.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/dedic_gpio.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/gpio.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/gptimer.h \
|
||||
@@ -177,6 +181,7 @@ INPUT = \
|
||||
$(PROJECT_PATH)/components/freertos/FreeRTOS-Kernel/include/freertos/task.h \
|
||||
$(PROJECT_PATH)/components/freertos/FreeRTOS-Kernel/include/freertos/timers.h \
|
||||
$(PROJECT_PATH)/components/hal/include/hal/adc_types.h \
|
||||
$(PROJECT_PATH)/components/hal/include/hal/dac_types.h \
|
||||
$(PROJECT_PATH)/components/hal/include/hal/esp_flash_err.h \
|
||||
$(PROJECT_PATH)/components/hal/include/hal/gpio_types.h \
|
||||
$(PROJECT_PATH)/components/hal/include/hal/i2c_types.h \
|
||||
|
@@ -15,9 +15,9 @@ The DAC peripheral supports outputting analog signal in following ways:
|
||||
|
||||
1. Outputting a voltage directly. The DAC channel will keep outputting a specified voltage.
|
||||
2. Outputting continuous analog signal by DMA. The DAC will convert the data in a buffer at the specified frequency.
|
||||
3. Outputting a cosine wave by the cosine wave generateor. The DAC channel can output a cosing wave with specified frequency and amplitude.
|
||||
3. Outputting a cosine wave by the cosine wave generator. The DAC channel can output a cosine wave with specified frequency and amplitude.
|
||||
|
||||
For other analog output options, see the :doc:`Sigma-delta Modulation module <sigmadelta>` and the :doc:`LED Control module <ledc>`. Both these modules produce high-frequency PWM/PDM output, which can be hardware low-pass filtered in order to generate a lower frequency analog output.
|
||||
For other analog output options, see the :doc:`Sigma-delta Modulation module <sdm>` and the :doc:`LED Control module <ledc>`. Both modules produce high-frequency PWM/PDM output, which can be hardware low-pass filtered in order to generate a lower frequency analog output.
|
||||
|
||||
DAC File Structure
|
||||
------------------
|
||||
@@ -29,12 +29,14 @@ DAC File Structure
|
||||
|
||||
**Public headers that need to be included in the DAC application**
|
||||
|
||||
- ``dac.h``: The header file of legacy DAC driver (for apps using legacy driver)
|
||||
- ``dac_driver.h``: The header file of new DAC driver (for apps using new DAC driver)
|
||||
- ``dac.h``: The top header file of legacy DAC driver, only included in the apps which use legacy driver API
|
||||
- ``dac_oneshot.h``: The top header file of new DAC driver, should be included in the apps which use the new driver API with oneshot mode.
|
||||
- ``dac_cosine.h``: The top header file of new DAC driver, should be included in the apps which use the new driver API with cosine mode.
|
||||
- ``dac_conti.h``: The top header file of new DAC driver, should be included in the apps which use the new driver API with continuous mode.
|
||||
|
||||
.. note::
|
||||
|
||||
The legacy driver can't coexist with the new driver. Including ``dac.h`` to use the legacy driver or ``dac_driver.h`` to use the new driver. The legacy driver might be removed in future.
|
||||
The legacy driver can't coexist with the new driver. Including ``dac.h`` to use the legacy driver or ``dac_oneshot.h``, ``dac_cosine.h`` and ``dac_conti.h`` to use the new driver. The legacy driver might be removed in future.
|
||||
|
||||
Functional Overview
|
||||
-------------------
|
||||
@@ -42,56 +44,54 @@ Functional Overview
|
||||
Resources Management
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The DAC on {IDF_TARGET_NAME} has two channels, which can be managed by the :cpp:type:`dac_channels_handle_t`. One or both two channels can be registered to a group by calling :cpp:func:`dac_new_channels`, it will return a channels handle so that the channels in a same group can be operated at the same time with this handle. While the channels in a group are not used any more, please call :cpp:func:`dac_del_channels` to free the resources and reset the hardware.
|
||||
The DAC on {IDF_TARGET_NAME} has two channels, due to the software resources are separate, they could be managed by the :cpp:type:`dac_oneshot_handle_t`, :cpp:type:`dac_cosine_handle_t`:cpp:type:`dac_conti_handle_t` according to the usage. Of cause, registering different modes on a same DAC channel is not allowed.
|
||||
|
||||
Direct Voltage Output (Direct Mode)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Direct Voltage Output (One-shot/Direct Mode)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The DAC channels in the group can convert a 8-bit digital value into the analog every time calling :cpp:func:`dac_channels_set_voltage` (it can be called in ISR), and then the analog voltage will keep on corresponding pins until next convert start. But before starting to convert the voltage, the DAC channels and pins should be enabled by calling :cpp:func:`dac_channels_enable`.
|
||||
The DAC channels in the group can convert a 8-bit digital value into the analog every time calling :cpp:func:`dac_oneshot_output_voltage` (it can be called in ISR), and then the analog voltage will be kept on the DAC channel until next conversion start. To start to convert the voltage, the DAC channels need to be registered by :cpp:func:`dac_new_oneshot_channel` first, and the channel will be enabled after it is registered.
|
||||
|
||||
Continuous Wave Output (DMA Mode)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Continuous Wave Output (Continuous/DMA Mode)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
DAC channels in the group can also convert digital data continuously via the DMA.
|
||||
DAC channels can convert digital data continuously via the DMA. There are three ways to writing DAC data:
|
||||
|
||||
To convert data continuously, the channels need to be initialized into DMA mode by calling :cpp:func:`dac_channels_init_continuous_mode` and then enable DMA conversion by :cpp:func:`dac_channels_enable_continuous_mode`.
|
||||
|
||||
After start up the DMA, there are two methods to transmit the rerial digital data. One method is transmitting only one time by :cpp:func:`dac_channels_write_continuously`, it will be blocked until all data transmitted, and the voltage will be kept according to the last conversion value. Another method is transmitting repeatly by :cpp:func:`dac_channels_write_cyclically`, the data in the buffer will be converted cyclicly without block, but note that the buffer size is limited by the descriptor number, and it has to stay available during the whole conversion period, otherwise invalid data might be transmitted.
|
||||
1. Normal writing (synchronous): It can transmit the data one time and keep blocking until all data has been loaded into the DMA buffer, and the voltage will be kept according to the last conversion value while no more data inputted, usually it is used to transport a long signal like an audio. To convert data continuously, the continuous channel handle need to be allocated by calling :cpp:func:`dac_new_conti_channels` and the DMA conversion should be enabled by :cpp:func:`dac_conti_enable`, then data can be written by :cpp:func:`dac_conti_write` synchronously. Referring to :example:`peripherals/dac/dac_continuous/dac_audio` for example.
|
||||
2. Cyclical writing: It can convert a piece of data cyclically without blocking, no more operation needed after the data are loaded into the DMA buffer,but note that the inputted buffer size is limited by the descriptor number and the DMA buffer size, usually it is used to transport some short signal that need to be repeated, for example, a sine wave. To achieve cyclical writing, :cpp:func:`dac_conti_write_cyclically` can be called after the DAC continuous mode is enabled. For the cyclical writing example, please refer to :example:`peripherals/dac/dac_continuous/signal_generator`
|
||||
3. Asynchronous writing: It can transmit the data asynchronously based on the event callback. Thus :cpp:member:`dac_event_callbacks_t::on_convert_done` must be registered to use asynchronous mode, and then users can get the :cpp:type:`dac_event_data_t` in the callback which contains the DMA buffer address and length, allowing user to load the data into it directly. As mentioned, to use the asynchronous writing, :cpp:func:`dac_conti_register_event_callback` need to be called to register the :cpp:member:`dac_event_callbacks_t::on_convert_done` before enabling, and then calling :cpp:func:`dac_conti_start_async_writing` to start the asynchronous writing, note that once the asynchronous writing started, the callback function will be triggered continuously, :cpp:func:`dac_conti_write_asynchronously` can help to load the data either in a separate task or the callback directly. For the asynchronous example, please refer to :example:`peripherals/dac/dac_continuous/dac_audio` as well.
|
||||
|
||||
.. only:: esp32
|
||||
|
||||
On ESP32, 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 left shifted 8 bits (i.e. the high 8 bits in 16-bit slot) to satisfy the I2S communication format. So the data buffer need to be expanded before passing it into the write function. Besides, if the both two channels are enabled, the data will be transmitted with a fixed phase diferrence between these channels.
|
||||
On ESP32, 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 left shifted 8 bits (i.e. the high 8 bits in 16-bit slot) to satisfy the I2S communication format. But the driver can help to expand automatically, if you want to expand manually, please disable :ref:`CONFIG_DAC_DMA_AUTO_16BIT_ALIGN` in the menuconfig.
|
||||
|
||||
The clock of DAC digital controller comes from I2S0 as well, so there are two kinds of clock source can be selected, they are :cpp:enumerator:`dac_conti_clk_src_t::DAC_DIGI_CLK_SRC_DEFAULT` which comes from ``CPU_D2_PLL`` and :cpp:enumerator:`dac_conti_clk_src_t::DAC_DIGI_CLK_SRC_APLL`. Theoretically, :cpp:enumerator:`dac_conti_clk_src_t::DAC_DIGI_CLK_SRC_DEFAULT` can support frequency between 19.6 KHz to several MHz and :cpp:enumerator:`dac_conti_clk_src_t::DAC_DIGI_CLK_SRC_DEFAULT` can support frequency between 648 Hz to several MHz, however, the latter clock source might be occupied by other peripherals, then it may not provide the required frequency. But it doesn't mean APLL is not available in this case, it can still work as long as it can be divided to the target DAC DMA frequency.
|
||||
The clock of DAC digital controller comes from I2S0 as well, so there are two kinds of clock source can be selected:
|
||||
|
||||
- :cpp:enumerator:`dac_conti_digi_clk_src_t::DAC_DIGI_CLK_SRC_PLL_D2` can support frequency between 19.6 KHz to several MHz. It is the default clock which can also be selected by :cpp:enumerator:`dac_conti_digi_clk_src_t::DAC_DIGI_CLK_SRC_DEFAULT`.
|
||||
- :cpp:enumerator:`dac_conti_digi_clk_src_t::DAC_DIGI_CLK_SRC_APLL` can support frequency between 648 Hz to several MHz, however, it might be occupied by other peripherals, then it may not provide the required frequency. But it doesn't mean APLL is not available in this case, it can still work as long as it can be divided to the target DAC DMA frequency correctly.
|
||||
|
||||
.. only:: esp32s2
|
||||
|
||||
On ESP32-S2, DAC digital controller can be connected internally to the SPI3 and use its DMA for continuous conversion.
|
||||
|
||||
The clock source of DAC difital controller can be choosen to :cpp:enumerator:`dac_conti_clk_src_t::DAC_DIGI_CLK_SRC_DEFAULT` (which comes from ``APB``) or :cpp:enumerator:`dac_conti_clk_src_t::DAC_DIGI_CLK_SRC_APLL`. Theoretically, :cpp:enumerator:`dac_conti_clk_src_t::DAC_DIGI_CLK_SRC_DEFAULT` can support frequency between 77 Hz to several MHz and :cpp:enumerator:`dac_conti_clk_src_t::DAC_DIGI_CLK_SRC_DEFAULT` can support frequency between 6 Hz to several MHz,however, the latter clock source might be occupied by other peripherals, then it may not provide the required frequency. But it doesn't mean APLL is not available in this case, it can still work as long as it can be divided to the target DAC DMA frequency.
|
||||
The clock source of DAC digital controller are:
|
||||
|
||||
- :cpp:enumerator:`dac_conti_digi_clk_src_t::DAC_DIGI_CLK_SRC_APB` can support frequency between 77 Hz to several MHz. It is the default clock which can also be selected by :cpp:enumerator:`dac_conti_digi_clk_src_t::DAC_DIGI_CLK_SRC_DEFAULT`.
|
||||
- :cpp:enumerator:`dac_conti_digi_clk_src_t::DAC_DIGI_CLK_SRC_APLL` can support frequency between 6 Hz to several MHz, however, it might be occupied by other peripherals, then it may not provide the required frequency. But it doesn't mean APLL is not available in this case, it can still work as long as it can be divided to the target DAC DMA frequency correctly.
|
||||
|
||||
|
||||
Cosine Wave Output (Cosine Mode)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The DAC peripheral has a cosine wave generateor on each channel, it can generate cosine wave on these channels, users can specify the frequency, amplitude and phase of the cosine wave. To output the cosine wave, please initialize the DAC to cosine mode by :cpp:func:`dac_channels_init_cosine_mode` first, and then start the cosine wave generator by :cpp:func:`dac_channels_start_cosine_output`.
|
||||
The DAC peripheral has one cosine wave generator, it can generate cosine wave on the channels, users can specify the frequency, amplitude and phase of the cosine wave. To output the cosine wave, please acquire the DAC to cosine mode handle by :cpp:func:`dac_new_cosine_channel` first, and then start the cosine wave generator by :cpp:func:`dac_cosine_start`.
|
||||
|
||||
Currently, the source clock of the consine wave generator only comes from ``RTC FAST`` which can be choosen by :cpp:enumerator:`dac_cosine_clk_src_t::DAC_COSINE_CLK_SRC_DEFAULT`.
|
||||
|
||||
Finite-State Machine
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The DAC driver adopts a finite-state machine, the following diagram shows the relationship of the public APIs and the driver internal states.
|
||||
|
||||
.. figure:: ../../../_static/diagrams/dac/dac_state_machine.png
|
||||
:align: center
|
||||
:alt: DAC Finite-State Machine
|
||||
Currently, the source clock of the cosine wave generator only comes from ``RTC_FAST`` which can be chosen by :cpp:enumerator:`dac_cosine_clk_src_t::DAC_COSINE_CLK_SRC_RTC_FAST`, it is also the default clock source which is same as :cpp:enumerator:`dac_cosine_clk_src_t::DAC_COSINE_CLK_SRC_RTC_DEFAULT`.
|
||||
|
||||
Power Management
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
When the power management is enabled (i.e. :ref:`CONFIG_PM_ENABLE` is on), the system will adjust or stop the source clock of DAC before going into light sleep, thus potentially influence to the DAC signals may lead the data conversion goes wrong.
|
||||
|
||||
DAC driver can prevent the system from changing or stopping the source clock in DMA or cosine wave mode by acquiring a power management lock. When the source clock is generated from APB, the lock type will be set to :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_APB_FREQ_MAX` and when the source clock is APLL (only in DMA mode), it will be set to :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_NO_LIGHT_SLEEP`. Whenever the DAC is converting (i.e. DMA or cosing wave generator is working), the driver will guarantee that the power management lock is acquired. Likewise, the driver releases the lock after conversion stopped.
|
||||
When using DAC driver in continuous mode, it can prevent the system from changing or stopping the source clock in DMA or cosine wave mode by acquiring a power management lock. When the source clock is generated from APB, the lock type will be set to :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_APB_FREQ_MAX` and when the source clock is APLL (only in DMA mode), it will be set to :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_NO_LIGHT_SLEEP`. Whenever the DAC is converting (i.e. DMA or cosine wave generator is working), the driver will guarantee that the power management lock is acquired after calling :cpp:func:`dac_conti_enable`. Likewise, the driver will release the lock when :cpp:func:`dac_conti_disable` is called.
|
||||
|
||||
IRAM Safe
|
||||
^^^^^^^^^
|
||||
@@ -104,14 +104,12 @@ There's a Kconfig option :ref:`CONFIG_DAC_ISR_IRAM_SAFE` that will:
|
||||
|
||||
2. Place driver object into DRAM (in case it's linked to PSRAM by accident)
|
||||
|
||||
This will allow the interrupt to run while the cache is disabled but will come at the cost of increased IRAM consumption. However, before enable :ref:`CONFIG_DAC_ISR_IRAM_SAFE` please make sure that the data buffer is allocated in the internal RAM by :cpp:func:`heap_caps_calloc` and set the last parameter to ``MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT``.
|
||||
This will allow the interrupt to run while the cache is disabled but will come at the cost of increased IRAM consumption.
|
||||
|
||||
Thread Safety
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
All the public DAC APIs are guaranteed to be thread safe by the driver, which means, users can call them from different RTOS tasks without protection by extra locks. Notice that DAC driver uses mutex lock to ensure the thread safety, thus these APIs are not allowed to be used in ISR.
|
||||
|
||||
For other analog output options, see the :doc:`Sigma-delta Modulation module <sdm>` and the :doc:`LED Control module <ledc>`. Both these modules produce high frequency PDM/PWM output, which can be hardware low-pass filtered in order to generate a lower frequency analog output.
|
||||
All the public DAC APIs are guaranteed to be thread safe by the driver, which means, users can call them from different RTOS tasks without protection by extra locks. Notice that DAC driver uses mutex lock to ensure the thread safety, thus the APIs except :cpp:func:`dac_oneshot_output_voltage` are not allowed to be used in ISR.
|
||||
|
||||
Kconfig Options
|
||||
^^^^^^^^^^^^^^^
|
||||
@@ -120,198 +118,24 @@ Kconfig Options
|
||||
- :ref:`CONFIG_DAC_SUPPRESS_DEPRECATE_WARN` controls whether to suppress the compiling warning message while using the legacy DAC driver.
|
||||
- :ref:`CONFIG_DAC_ENABLE_DEBUG_LOG` is used to enabled the debug log output. Enable this option will increase the firmware binary size.
|
||||
|
||||
.. only:: esp32
|
||||
|
||||
- :ref:`CONFIG_DAC_DMA_AUTO_16BIT_ALIGN` will auto expand the 8-bit data to 16-bit data in the driver to satisfy the I2S DMA format.
|
||||
|
||||
Application Example
|
||||
-------------------
|
||||
|
||||
The basic examples for the ``Direct Mode``, ``DMA Mode`` and ``Consine Mode`` can be found in :example:`peripherals/dac/dac_basic`, :example:`peripherals/dac/dac_continuous` and :example:`peripherals/dac/dac_cosine_wave`. Here is a general overview of how to use these modes:
|
||||
|
||||
Direct Output Example
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code:: c
|
||||
|
||||
#include <driver/dac_driver.h>
|
||||
|
||||
...
|
||||
|
||||
dac_channels_handle_t handle;
|
||||
dac_channels_config_t cfg = {.chan_sel = DAC_CHANNEL_MASK_BOTH};
|
||||
|
||||
/* Allocate a channels handle for the choosen channels */
|
||||
ESP_ERROR_CHECK(dac_new_channels(&cfg, &handle));
|
||||
/* Must enable the channels before any outputting */
|
||||
ESP_ERROR_CHECK(dac_channels_enable(handle));
|
||||
|
||||
/* Output '100', the DAC pin will output about (100 / 255) * 3.3 = 1.29 V */
|
||||
ESP_ERROR_CHECK(dac_channels_set_voltage(handle, 100));
|
||||
|
||||
/* Disable the channels to stop outputting */
|
||||
ESP_ERROR_CHECK(dac_channels_disable(handle));
|
||||
/* Delete and free the channels */
|
||||
ESP_ERROR_CHECK(dac_del_channels(handle));
|
||||
|
||||
|
||||
Continuous Output Example
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. only:: esp32
|
||||
|
||||
.. code:: c
|
||||
|
||||
#include "driver/dac_driver.h"
|
||||
#if CONFIG_DAC_ISR_IRAM_SAFE
|
||||
/* If DAC IRAM safe enabled in Kconfig, include "esp_heap_caps.h" to allocate memory on internal RAM */
|
||||
#include "esp_heap_caps.h"
|
||||
#endif
|
||||
|
||||
...
|
||||
|
||||
dac_channels_handle_t handle;
|
||||
dac_channels_config_t cfg = {.chan_sel = DAC_CHANNEL_MASK_BOTH};
|
||||
dac_conti_config_t dma_cfg = {
|
||||
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT,
|
||||
.desc_num = 10,
|
||||
.freq_hz = 20000,
|
||||
};
|
||||
|
||||
/* Allocate a channels handle for the choosen channels */
|
||||
ESP_ERROR_CHECK(dac_new_channels(&cfg, &handle));
|
||||
/* Must enable the channels before any outputting */
|
||||
ESP_ERROR_CHECK(dac_channels_enable(handle));
|
||||
/* Initialize the channels to DMA mode */
|
||||
ESP_ERROR_CHECK(dac_channels_init_continuous_mode(handle, &dma_cfg));
|
||||
/* Must enable DMA before writing data */
|
||||
ESP_ERROR_CHECK(dac_channels_enable_continuous_mode(handle));
|
||||
|
||||
/* Allocate the memory for the buffer to write */
|
||||
uint32_t buf_size = 2000;
|
||||
uint32_t timeout_ms = 1000;
|
||||
#if CONFIG_DAC_ISR_IRAM_SAFE
|
||||
/* If DAC IRAM SAFE is enabled, allocate the buffer on the internal RAM */
|
||||
uint8_t *data = (uint8_t *)heap_caps_calloc(1, buf_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
#else
|
||||
uint8_t *data = (uint8_t *)calloc(1, buf_size);
|
||||
#endif
|
||||
assert(data);
|
||||
/* Load the 16-bit aligned data */
|
||||
for (int i = 0; i < buf_size; i += 2) {
|
||||
data[i] = 0; // Fill 0 to the low 8 bits
|
||||
data[i+1] = (i / 2) % 255; // Only the high 8 bits will be conveted
|
||||
}
|
||||
/* Write the data acyclicly, it will be blocked untill finishing sending all the data */
|
||||
ESP_ERROR_CHECK(dac_channels_write_continuously(handle, data, buf_size, NULL, timeout_ms));
|
||||
/* Write the data acyclicly, it will start output the buffer cyclicly without block, it can only be timeout when failed to aquire the lock */
|
||||
ESP_ERROR_CHECK(dac_channels_write_continuously(handle, data, buf_size, NULL, timeout_ms));
|
||||
|
||||
/* Disable the DMA before deinitializing DMA mode */
|
||||
ESP_ERROR_CHECK(dac_channels_disable_continuous_mode(handle));
|
||||
/* Deinitialize DMA mode before disabling the channels */
|
||||
ESP_ERROR_CHECK(dac_channels_deinit_continuous_mode(handle));
|
||||
/* Disable the channels before deleting it */
|
||||
ESP_ERROR_CHECK(dac_channels_disable(handle));
|
||||
/* Delete the channels */
|
||||
ESP_ERROR_CHECK(dac_del_channels(handle));
|
||||
|
||||
|
||||
.. only:: esp32s2
|
||||
|
||||
.. code:: c
|
||||
|
||||
#include "driver/dac_driver.h"
|
||||
#if CONFIG_DAC_ISR_IRAM_SAFE
|
||||
/* If DAC IRAM safe enabled in Kconfig, include "esp_heap_caps.h" to allocate memory on internal RAM */
|
||||
#include "esp_heap_caps.h"
|
||||
#endif
|
||||
|
||||
...
|
||||
|
||||
dac_channels_handle_t handle;
|
||||
dac_channels_config_t cfg = {.chan_sel = DAC_CHANNEL_MASK_BOTH};
|
||||
dac_conti_config_t dma_cfg = {
|
||||
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT,
|
||||
.desc_num = 10,
|
||||
.freq_hz = 20000,
|
||||
};
|
||||
|
||||
/* Allocate a channels handle for the choosen channels */
|
||||
ESP_ERROR_CHECK(dac_new_channels(&cfg, &handle));
|
||||
/* Must enable the channels before any outputting */
|
||||
ESP_ERROR_CHECK(dac_channels_enable(handle));
|
||||
/* Initialize the channels to DMA mode */
|
||||
ESP_ERROR_CHECK(dac_channels_init_continuous_mode(handle, &dma_cfg));
|
||||
/* Must enable DMA before writing data */
|
||||
ESP_ERROR_CHECK(dac_channels_enable_continuous_mode(handle));
|
||||
|
||||
/* Allocate the memory for the buffer to write */
|
||||
uint32_t buf_size = 2000;
|
||||
uint32_t timeout_ms = 1000;
|
||||
#if CONFIG_DAC_ISR_IRAM_SAFE
|
||||
/* If DAC IRAM SAFE is enabled, allocate the buffer on the internal RAM */
|
||||
uint8_t *data = (uint8_t *)heap_caps_calloc(1, buf_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
#else
|
||||
uint8_t *data = (uint8_t *)calloc(1, buf_size);
|
||||
#endif
|
||||
assert(data);
|
||||
for (int i = 0; i < buf_size; i++) {
|
||||
data[i] = i % 255;
|
||||
}
|
||||
/* Write the data acyclicly, it will be blocked untill finishing sending all the data */
|
||||
ESP_ERROR_CHECK(dac_channels_write_continuously(handle, data, buf_size, NULL, timeout_ms));
|
||||
/* Write the data acyclicly, it will start output the buffer cyclicly without block, it can only be timeout when failed to aquire the lock */
|
||||
ESP_ERROR_CHECK(dac_channels_write_continuously(handle, data, buf_size, NULL, timeout_ms));
|
||||
|
||||
/* Disable the DMA before deinitializing DMA mode */
|
||||
ESP_ERROR_CHECK(dac_channels_disable_continuous_mode(handle));
|
||||
/* Deinitialize DMA mode before disabling the channels */
|
||||
ESP_ERROR_CHECK(dac_channels_deinit_continuous_mode(handle));
|
||||
/* Disable the channels before deleting it */
|
||||
ESP_ERROR_CHECK(dac_channels_disable(handle));
|
||||
/* Delete the channels */
|
||||
ESP_ERROR_CHECK(dac_del_channels(handle));
|
||||
|
||||
|
||||
Cosine Wave Output Example
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code:: c
|
||||
|
||||
#include "driver/dac_driver.h"
|
||||
|
||||
...
|
||||
|
||||
dac_channels_handle_t handle;
|
||||
dac_channels_config_t cfg = {.chan_sel = DAC_CHANNEL_MASK_BOTH};
|
||||
dac_cosine_config_t cos_cfg = {
|
||||
.freq_hz = 1000,
|
||||
.clk_src = DAC_COSINE_CLK_SRC_DEFAULT,
|
||||
.offset = 0,
|
||||
.phase = DAC_COSINE_PHASE_0,
|
||||
.scale = DAC_COSINE_NO_ATTEN,
|
||||
};
|
||||
/* Allocate a channels handle for the choosen channels */
|
||||
ESP_ERROR_CHECK(dac_new_channels(&cfg, &handle));
|
||||
/* Must enable the channels before any outputting */
|
||||
ESP_ERROR_CHECK(dac_channels_enable(handle));
|
||||
/* Initialize the channels to cosine wave mode */
|
||||
ESP_ERROR_CHECK(dac_channels_init_cosine_mode(handle, &cos_cfg));
|
||||
/* Start outputting the cosine wave */
|
||||
ESP_ERROR_CHECK(dac_channels_start_cosine_output(handle));
|
||||
|
||||
/* Stop the cosine wave generator before deinitializing cosine mode */
|
||||
ESP_ERROR_CHECK(dac_channels_stop_cosine_output(handle));
|
||||
/* Deinitialize consine mode before disabling the channels */
|
||||
ESP_ERROR_CHECK(dac_channels_deinit_cosine_mode(handle));
|
||||
/* Disable the channels before deleting it */
|
||||
ESP_ERROR_CHECK(dac_channels_disable(handle));
|
||||
/* Delete the channels */
|
||||
ESP_ERROR_CHECK(dac_del_channels(handle));
|
||||
The basic examples for the ``One-shot Mode``, ``Continuous Mode`` and ``Cosine Mode`` can be found in:
|
||||
|
||||
- :example:`peripherals/dac/dac_oneshot`
|
||||
- :example:`peripherals/dac/dac_continuous`
|
||||
- :example:`peripherals/dac/dac_cosine_wave`
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/dac_driver.inc
|
||||
.. include-build-file:: inc/dac_channel.inc
|
||||
.. include-build-file:: inc/dac_types.inc
|
||||
.. include-build-file:: inc/dac_oneshot.inc
|
||||
.. include-build-file:: inc/dac_cosine.inc
|
||||
.. include-build-file:: inc/dac_conti.inc
|
||||
.. include-build-file:: inc/components/driver/include/driver/dac_types.inc
|
||||
.. include-build-file:: inc/components/hal/include/hal/dac_types.inc
|
||||
|
@@ -527,65 +527,6 @@ LCD
|
||||
|
||||
The deprecated ``CAN`` peripheral driver is removed. Please use ``TWAI`` driver instead (i.e. include ``driver/twai.h`` in your application).
|
||||
|
||||
.. only:: SOC_DAC_SUPPORTED
|
||||
|
||||
DAC
|
||||
---
|
||||
|
||||
DAC driver has been redesigned (see :doc:`DAC API Reference <../api-reference/peripherals/dac>`), which aims to unify and extend the usage of DAC peripheral. Although it's recommended to use the new driver APIs, the legacy driver is still available in the previous include path ``driver/dac.h``. However, by default, including ``driver/dac.h`` will bring a build warning like `The legacy DAC driver is deprecated, please use driver/dac_driver.h instead`. The warning can be suppressed by the Kconfig option :ref:`CONFIG_DAC_SUPPRESS_DEPRECATE_WARN`.
|
||||
|
||||
The major breaking changes in concept and usage are listed as follows:
|
||||
|
||||
.. only:: esp32
|
||||
|
||||
Breaking Changes in Concepts
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``dac_channel_t`` which used to identify the hardware channel are removed from user space, the channel index now starts from ``0``, please use `DAC_CHAN_0` and `DAC_CHAN_1` instead. And in the new driver, DAC channels can be seleted by :cpp:type:`dac_channel_mask_t`. And these channels can be allocated in a same channel group which is represented by :cpp:type:`dac_channels_handle_t`.
|
||||
- ``dac_cw_scale_t`` is replaced by :cpp:type:`dac_cosine_scale_t`, to decupling the legacy driver and the new driver.
|
||||
- ``dac_cw_phase_t`` is replaced by :cpp:type:`dac_cosine_phase_t`, the enum value is now the phase angle directly.
|
||||
- ``dac_cw_config_t`` is replaced by :cpp:type:`dac_cosine_config_t`, but the ``en_ch`` field is removed because it should be specify while allocate the channel group.
|
||||
|
||||
Breaking Changes in Usage
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``dac_pad_get_io_num`` is removed.
|
||||
- ``dac_output_voltage`` is replaced by :cpp:func:`dac_channels_set_voltage`.
|
||||
- ``dac_output_enable`` is replaced by :cpp:func:`dac_channels_enable`.
|
||||
- ``dac_output_disable`` is replaced by :cpp:func:`dac_channels_disable`.
|
||||
- ``dac_cw_generator_enable`` is replaced by :cpp:func:`dac_channels_start_cosine_output`, but it need to initialize the DAC channel group to cosine mode first by :cpp:func:`dac_channels_start_cosine_output`, and :cpp:func:`dac_channels_enable` should be called as well.
|
||||
- ``dac_cw_generator_disable`` is replaced by :cpp:func:`dac_channels_stop_cosine_output`, it is also only allowed to be called under cosine mode.
|
||||
- ``dac_cw_generator_config`` is replaced by :cpp:func:`dac_channels_init_cosine_mode`, when it is called, the driver will work at cosine mode.
|
||||
- ``dac_i2s_enable`` is replaced by :cpp:func:`dac_channels_enable_continuous_mode`, but it need to initialize the DAC channel group to DMA mode first by :cpp:func:`dac_channels_init_continuous_mode`, and :cpp:func:`dac_channels_enable` should be called as well.
|
||||
- ``dac_i2s_disable`` is replaced by :cpp:func:`dac_channels_disable_continuous_mode`, it is also only allowed to be called under DMA mode.
|
||||
|
||||
.. only:: esp32s2
|
||||
|
||||
Breaking Changes in Concepts
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``dac_channel_t`` which used to identify the hardware channel are removed from user space, the channel index now starts from ``0``, please use `DAC_CHAN_0` and `DAC_CHAN_1` instead. And in the new driver, DAC channels can be seleted by :cpp:type:`dac_channel_mask_t`. And these channels can be allocated in a same channel group which is represented by :cpp:type:`dac_channels_handle_t`.
|
||||
- ``dac_cw_scale_t`` is replaced by :cpp:type:`dac_cosine_scale_t`, to decupling the legacy driver and the new driver.
|
||||
- ``dac_cw_phase_t`` is replaced by :cpp:type:`dac_cosine_phase_t`, the enum value is now the phase angle directly.
|
||||
- ``dac_digi_convert_mode_t`` is removed. The driver now can transmit DMA data in different ways by calling :cpp:func:`dac_channels_write_continuously` or :cpp:func:`dac_channels_write_cyclically`.
|
||||
- ``dac_cw_config_t`` is replaced by :cpp:type:`dac_cosine_config_t`, but the ``en_ch`` field is removed because it should be specify while allocate the channel group.
|
||||
- ``dac_digi_config_t`` is replaced by :cpp:type:`dac_conti_config_t`.
|
||||
|
||||
Breaking Changes in Usage
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``dac_pad_get_io_num`` is removed.
|
||||
- ``dac_output_voltage`` is replaced by :cpp:func:`dac_channels_set_voltage`.
|
||||
- ``dac_output_enable`` is replaced by :cpp:func:`dac_channels_enable`.
|
||||
- ``dac_output_disable`` is replaced by :cpp:func:`dac_channels_disable`.
|
||||
- ``dac_cw_generator_enable`` is replaced by :cpp:func:`dac_channels_start_cosine_output`, but it need to initialize the DAC channel group to cosine mode first by :cpp:func:`dac_channels_start_cosine_output`, and :cpp:func:`dac_channels_enable` should be called as well.
|
||||
- ``dac_cw_generator_disable`` is replaced by :cpp:func:`dac_channels_stop_cosine_output`, it is also only allowed to be called under cosine mode.
|
||||
- ``dac_cw_generator_config`` is replaced by :cpp:func:`dac_channels_init_cosine_mode`, when it is called, the driver will work at cosine mode.
|
||||
- ``dac_digi_init`` and ``dac_digi_controller_config`` is merged into :cpp:func:`dac_channels_init_continuous_mode`.
|
||||
- ``dac_digi_deinit`` is replaced by :cpp:func:`dac_channels_deinit_continuous_mode`.
|
||||
- ``dac_digi_start``, ``dac_digi_fifo_reset`` and ``dac_digi_reset`` are merged into :cpp:func:`dac_channels_enable_continuous_mode`.
|
||||
- ``dac_digi_stop`` is replaced by :cpp:func:`dac_channels_disable_continuous_mode`.
|
||||
|
||||
Register Access Macros
|
||||
----------------------
|
||||
|
||||
|
@@ -3,3 +3,47 @@ Peripherals
|
||||
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
|
||||
.. only:: SOC_DAC_SUPPORTED
|
||||
|
||||
DAC
|
||||
---
|
||||
|
||||
DAC driver has been redesigned (see :doc:`DAC API Reference <../../../api-reference/peripherals/dac>`), which aims to unify and extend the usage of DAC peripheral. Although it's recommended to use the new driver APIs, the legacy driver is still available in the previous include path ``driver/dac.h``. However, by default, including ``driver/dac.h`` will bring a build warning like `The legacy DAC driver is deprecated, please use 'driver/dac_oneshot.h', 'driver/dac_cosine.h' or 'driver/dac_conti.h' instead`. The warning can be suppressed by the Kconfig option :ref:`CONFIG_DAC_SUPPRESS_DEPRECATE_WARN`.
|
||||
|
||||
The major breaking changes in concept and usage are listed as follows:
|
||||
|
||||
Breaking Changes in Concepts
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``dac_channel_t`` which used to identify the hardware channel are removed from user space, the channel index now starts from ``0``, please use `DAC_CHAN_0` and `DAC_CHAN_1` instead. And in the new driver, DAC channels can be seleted by :cpp:type:`dac_channel_mask_t`. And these channels can be allocated in a same channel group which is represented by :cpp:type:`dac_channels_handle_t`.
|
||||
- ``dac_cw_scale_t`` is replaced by :cpp:type:`dac_cosine_atten_t`, to decupling the legacy driver and the new driver.
|
||||
- ``dac_cw_phase_t`` is replaced by :cpp:type:`dac_cosine_phase_t`, the enum value is now the phase angle directly.
|
||||
- ``dac_cw_config_t`` is replaced by :cpp:type:`dac_cosine_config_t`, but the ``en_ch`` field is removed because it should be specify while allocate the channel group.
|
||||
|
||||
.. only:: esp32s2
|
||||
|
||||
- ``dac_digi_convert_mode_t`` is removed. The driver now can transmit DMA data in different ways by calling :cpp:func:`dac_channels_write_continuously` or :cpp:func:`dac_channels_write_cyclically`.
|
||||
- ``dac_digi_config_t`` is replaced by :cpp:type:`dac_conti_config_t`.
|
||||
|
||||
Breaking Changes in Usage
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``dac_pad_get_io_num`` is removed.
|
||||
- ``dac_output_voltage`` is replaced by :cpp:func:`dac_oneshot_output_voltage`.
|
||||
- ``dac_output_enable`` is removed, for oneshot mode, it will be enabled after the channel allocated.
|
||||
- ``dac_output_disable`` is removed, for oneshot mode, it will be disabled before the channel deleted.
|
||||
- ``dac_cw_generator_enable`` is replaced by :cpp:func:`dac_cosine_start`.
|
||||
- ``dac_cw_generator_disable`` is replaced by :cpp:func:`dac_cosine_stop`.
|
||||
- ``dac_cw_generator_config`` is replaced by :cpp:func:`dac_new_cosine_channel`.
|
||||
|
||||
.. only:: esp32
|
||||
|
||||
- ``dac_i2s_enable`` is replaced by :cpp:func:`dac_conti_enable`, but it needs to allocate the continuous DAC channel first by :cpp:func:`dac_new_conti_channels`.
|
||||
- ``dac_i2s_disable`` is replaced by :cpp:func:`dac_conti_disable`.
|
||||
|
||||
.. only:: esp32s2
|
||||
|
||||
- ``dac_digi_init`` and ``dac_digi_controller_config`` is merged into :cpp:func:`dac_new_conti_channels`.
|
||||
- ``dac_digi_deinit`` is replaced by :cpp:func:`dac_del_conti_channels`.
|
||||
- ``dac_digi_start``, ``dac_digi_fifo_reset`` and ``dac_digi_reset`` are merged into :cpp:func:`dac_conti_enable`.
|
||||
- ``dac_digi_stop`` is replaced by :cpp:func:`dac_conti_disable`.
|
||||
|
@@ -9,7 +9,9 @@ menu "A2DP Example Configuration"
|
||||
config EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
bool "Internal DAC"
|
||||
help
|
||||
Select this to use Internal DAC sink output
|
||||
Select this to use Internal DAC sink output,
|
||||
note that DAC_DMA_AUTO_16BIT_ALIGN should be turned off
|
||||
because the audio data are already 16-bit width
|
||||
|
||||
config EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S
|
||||
bool "External I2S Codec"
|
||||
|
@@ -21,8 +21,7 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
// DAC DMA mode is only supported by the legacy I2S driver, it will be replaced once DAC has its own DMA dirver
|
||||
#include "driver/dac_driver.h"
|
||||
#include "driver/dac_conti.h"
|
||||
#else
|
||||
#include "driver/i2s_std.h"
|
||||
#endif
|
||||
@@ -90,7 +89,7 @@ static bool s_volume_notify; /* notify volume change or not */
|
||||
#ifndef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
i2s_chan_handle_t tx_chan = NULL;
|
||||
#else
|
||||
dac_channels_handle_t tx_chan;
|
||||
dac_conti_handle_t tx_chan;
|
||||
#endif
|
||||
|
||||
/********************************
|
||||
@@ -171,24 +170,19 @@ static void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *even
|
||||
void bt_i2s_driver_install(void)
|
||||
{
|
||||
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
dac_channels_config_t cfg = {
|
||||
.chan_sel = DAC_CHANNEL_MASK_BOTH,
|
||||
};
|
||||
dac_conti_config_t conti_cfg = {
|
||||
.freq_hz = 44100,
|
||||
.chan_mode = DAC_CHANNEL_MODE_ALTER,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT, // If the frequency is out of range, try 'DAC_DIGI_CLK_SRC_APLL'
|
||||
.desc_num = 6,
|
||||
.chan_mask = DAC_CHANNEL_MASK_ALL,
|
||||
.desc_num = 8,
|
||||
.buf_size = 2048,
|
||||
.freq_hz = 44100,
|
||||
.offset = 127,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT, // Using APLL as clock source to get a wider frequency range
|
||||
.chan_mode = DAC_CHANNEL_MODE_ALTER,
|
||||
};
|
||||
/* Allocate the channel group */
|
||||
ESP_ERROR_CHECK(dac_new_channels(&cfg, &tx_chan));
|
||||
/* Enable the channels in the group */
|
||||
ESP_ERROR_CHECK(dac_channels_enable(tx_chan));
|
||||
/* Initialize DAC DMA peripheral */
|
||||
ESP_ERROR_CHECK(dac_channels_init_continuous_mode(tx_chan, &conti_cfg));
|
||||
/* Start the DAC DMA peripheral */
|
||||
ESP_ERROR_CHECK(dac_channels_enable_continuous_mode(tx_chan));
|
||||
/* Allocate continuous channels */
|
||||
ESP_ERROR_CHECK(dac_new_conti_channels(&conti_cfg, &tx_chan));
|
||||
/* Enable the continuous channels */
|
||||
ESP_ERROR_CHECK(dac_conti_enable(tx_chan));
|
||||
#else
|
||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
|
||||
chan_cfg.auto_clear = true;
|
||||
@@ -218,10 +212,8 @@ void bt_i2s_driver_install(void)
|
||||
void bt_i2s_driver_uninstall(void)
|
||||
{
|
||||
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
ESP_ERROR_CHECK(dac_channels_disable_continuous_mode(tx_chan));
|
||||
ESP_ERROR_CHECK(dac_channels_deinit_continuous_mode(tx_chan));
|
||||
ESP_ERROR_CHECK(dac_channels_disable(tx_chan));
|
||||
ESP_ERROR_CHECK(dac_del_channels(tx_chan));
|
||||
ESP_ERROR_CHECK(dac_conti_disable(tx_chan));
|
||||
ESP_ERROR_CHECK(dac_del_conti_channels(tx_chan));
|
||||
#else
|
||||
ESP_ERROR_CHECK(i2s_channel_disable(tx_chan));
|
||||
ESP_ERROR_CHECK(i2s_del_channel(tx_chan));
|
||||
@@ -322,17 +314,21 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
|
||||
ch_count = 1;
|
||||
}
|
||||
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
dac_conti_disable(tx_chan);
|
||||
dac_del_conti_channels(tx_chan);
|
||||
dac_conti_config_t conti_cfg = {
|
||||
.freq_hz = sample_rate,
|
||||
.chan_mode = ch_count == 1 ? DAC_CHANNEL_MODE_SIMUL : DAC_CHANNEL_MODE_ALTER,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT, // If the frequency is out of range, try 'DAC_DIGI_CLK_SRC_APLL'
|
||||
.desc_num = 6,
|
||||
.chan_mask = DAC_CHANNEL_MASK_ALL,
|
||||
.desc_num = 8,
|
||||
.buf_size = 2048,
|
||||
.freq_hz = sample_rate,
|
||||
.offset = 127,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT, // Using APLL as clock source to get a wider frequency range
|
||||
.chan_mode = (ch_count == 1) ? DAC_CHANNEL_MODE_SIMUL : DAC_CHANNEL_MODE_ALTER,
|
||||
};
|
||||
dac_channels_disable_continuous_mode(tx_chan);
|
||||
dac_channels_deinit_continuous_mode(tx_chan);
|
||||
dac_channels_init_continuous_mode(tx_chan, &conti_cfg);
|
||||
dac_channels_enable_continuous_mode(tx_chan);
|
||||
/* Allocate continuous channels */
|
||||
dac_new_conti_channels(&conti_cfg, &tx_chan);
|
||||
/* Enable the continuous channels */
|
||||
dac_conti_enable(tx_chan);
|
||||
#else
|
||||
i2s_channel_disable(tx_chan);
|
||||
i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(sample_rate);
|
||||
|
@@ -16,7 +16,7 @@
|
||||
#include "esp_log.h"
|
||||
#include "bt_app_core.h"
|
||||
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
#include "driver/dac_driver.h"
|
||||
#include "driver/dac_conti.h"
|
||||
#else
|
||||
#include "driver/i2s_std.h"
|
||||
#endif
|
||||
@@ -62,7 +62,7 @@ static uint16_t ringbuffer_mode = RINGBUFFER_MODE_PROCESSING;
|
||||
#ifndef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
extern i2s_chan_handle_t tx_chan;
|
||||
#else
|
||||
extern dac_channels_handle_t tx_chan;
|
||||
extern dac_conti_handle_t tx_chan;
|
||||
#endif
|
||||
|
||||
/*******************************
|
||||
@@ -140,7 +140,7 @@ static void bt_i2s_task_handler(void *arg)
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
dac_channels_write_continuously(tx_chan, data, item_size, &bytes_written, portMAX_DELAY);
|
||||
dac_conti_write(tx_chan, data, item_size, &bytes_written, -1);
|
||||
#else
|
||||
i2s_channel_write(tx_chan, data, item_size, &bytes_written, portMAX_DELAY);
|
||||
#endif
|
||||
|
@@ -7,4 +7,4 @@ CONFIG_BTDM_CTRL_MODE_BTDM=n
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
CONFIG_BT_CLASSIC_ENABLED=y
|
||||
CONFIG_BT_A2DP_ENABLE=y
|
||||
CONFIG_I2S_SUPPRESS_DEPRECATE_WARN=y
|
||||
CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=n
|
||||
|
@@ -9,7 +9,9 @@ menu "A2DP Example Configuration"
|
||||
config EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
bool "Internal DAC"
|
||||
help
|
||||
Select this to use Internal DAC sink output
|
||||
Select this to use Internal DAC sink output,
|
||||
note that DAC_DMA_AUTO_16BIT_ALIGN should be turned off
|
||||
because the audio data are already 16-bit width
|
||||
|
||||
config EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S
|
||||
bool "External I2S Codec"
|
||||
|
@@ -22,7 +22,7 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
#include "driver/dac_driver.h"
|
||||
#include "driver/dac_conti.h"
|
||||
#else
|
||||
#include "driver/i2s_std.h"
|
||||
#endif
|
||||
@@ -55,7 +55,7 @@ static bool s_volume_notify;
|
||||
#ifndef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
extern i2s_chan_handle_t tx_chan;
|
||||
#else
|
||||
extern dac_channels_handle_t tx_chan;
|
||||
extern dac_conti_handle_t tx_chan;
|
||||
#endif
|
||||
|
||||
/* callback for A2DP sink */
|
||||
@@ -172,17 +172,21 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
|
||||
sample_rate = 48000;
|
||||
}
|
||||
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
dac_conti_disable(tx_chan);
|
||||
dac_del_conti_channels(tx_chan);
|
||||
dac_conti_config_t conti_cfg = {
|
||||
.freq_hz = sample_rate,
|
||||
.chan_mode = DAC_CHANNEL_MODE_ALTER,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT, // If the frequency is out of range, try 'DAC_DIGI_CLK_SRC_APLL'
|
||||
.desc_num = 6,
|
||||
.chan_mask = DAC_CHANNEL_MASK_ALL,
|
||||
.desc_num = 8,
|
||||
.buf_size = 2048,
|
||||
.freq_hz = sample_rate,
|
||||
.offset = 127,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT, // Using APLL as clock source to get a wider frequency range
|
||||
.chan_mode = DAC_CHANNEL_MODE_ALTER,
|
||||
};
|
||||
dac_channels_disable_continuous_mode(tx_chan);
|
||||
dac_channels_deinit_continuous_mode(tx_chan);
|
||||
dac_channels_init_continuous_mode(tx_chan, &conti_cfg);
|
||||
dac_channels_enable_continuous_mode(tx_chan);
|
||||
/* Allocate continuous channels */
|
||||
dac_new_conti_channels(&conti_cfg, &tx_chan);
|
||||
/* Enable the continuous channels */
|
||||
dac_conti_enable(tx_chan);
|
||||
#else
|
||||
i2s_channel_disable(tx_chan);
|
||||
i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(sample_rate);
|
||||
|
@@ -16,7 +16,7 @@
|
||||
#include "bt_app_core.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
#include "driver/dac_driver.h"
|
||||
#include "driver/dac_conti.h"
|
||||
#else
|
||||
#include "driver/i2s_std.h"
|
||||
#endif
|
||||
@@ -32,7 +32,7 @@ static RingbufHandle_t s_ringbuf_i2s = NULL;
|
||||
#ifndef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
extern i2s_chan_handle_t tx_chan;
|
||||
#else
|
||||
extern dac_channels_handle_t tx_chan;
|
||||
extern dac_conti_handle_t tx_chan;
|
||||
#endif
|
||||
|
||||
bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback)
|
||||
@@ -133,7 +133,7 @@ static void bt_i2s_task_handler(void *arg)
|
||||
data = (uint8_t *)xRingbufferReceive(s_ringbuf_i2s, &item_size, (TickType_t)portMAX_DELAY);
|
||||
if (item_size != 0){
|
||||
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
dac_channels_write_continuously(tx_chan, data, item_size, &bytes_written, portMAX_DELAY);
|
||||
dac_conti_write(tx_chan, data, item_size, &bytes_written, -1);
|
||||
#else
|
||||
i2s_channel_write(tx_chan, data, item_size, &bytes_written, portMAX_DELAY);
|
||||
#endif
|
||||
|
@@ -36,8 +36,7 @@
|
||||
#include "esp_a2dp_api.h"
|
||||
#include "esp_avrc_api.h"
|
||||
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
// DAC DMA mode is only supported by the legacy I2S driver, it will be replaced once DAC has its own DMA dirver
|
||||
#include "driver/dac_driver.h"
|
||||
#include "driver/dac_conti.h"
|
||||
#else
|
||||
#include "driver/i2s_std.h"
|
||||
#endif
|
||||
@@ -78,7 +77,7 @@ static prepare_type_env_t b_prepare_write_env;
|
||||
#ifndef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
i2s_chan_handle_t tx_chan;
|
||||
#else
|
||||
dac_channels_handle_t tx_chan;
|
||||
dac_conti_handle_t tx_chan;
|
||||
#endif
|
||||
|
||||
//Declare the static function
|
||||
@@ -693,24 +692,19 @@ void app_main(void)
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
dac_channels_config_t cfg = {
|
||||
.chan_sel = DAC_CHANNEL_MASK_BOTH,
|
||||
};
|
||||
dac_conti_config_t conti_cfg = {
|
||||
.freq_hz = 44100,
|
||||
.chan_mode = DAC_CHANNEL_MODE_ALTER,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT, // If the frequency is out of range, try 'DAC_DIGI_CLK_SRC_APLL'
|
||||
.desc_num = 6,
|
||||
.chan_mask = DAC_CHANNEL_MASK_ALL,
|
||||
.desc_num = 8,
|
||||
.buf_size = 2048,
|
||||
.freq_hz = 44100,
|
||||
.offset = 127,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT, // Using APLL as clock source to get a wider frequency range
|
||||
.chan_mode = DAC_CHANNEL_MODE_ALTER,
|
||||
};
|
||||
/* Allocate the channel group */
|
||||
ESP_ERROR_CHECK(dac_new_channels(&cfg, &tx_chan));
|
||||
/* Enable the channels in the group */
|
||||
ESP_ERROR_CHECK(dac_channels_enable(tx_chan));
|
||||
/* Initialize DAC DMA peripheral */
|
||||
ESP_ERROR_CHECK(dac_channels_init_continuous_mode(tx_chan, conti_cfg));
|
||||
/* Start the DAC DMA peripheral */
|
||||
ESP_ERROR_CHECK(dac_channels_enable_continuous_mode(tx_chan));
|
||||
/* Allocate continuous channels */
|
||||
ESP_ERROR_CHECK(dac_new_conti_channels(&conti_cfg, &tx_chan));
|
||||
/* Enable the continuous channels */
|
||||
ESP_ERROR_CHECK(dac_conti_enable(tx_chan));
|
||||
#else
|
||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
|
||||
chan_cfg.auto_clear = true;
|
||||
|
@@ -29,4 +29,4 @@ CONFIG_BT_ACL_CONNECTIONS=4
|
||||
CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST=n
|
||||
CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY=n
|
||||
CONFIG_BT_SMP_ENABLE=y
|
||||
CONFIG_I2S_SUPPRESS_DEPRECATE_WARN=y
|
||||
CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=n
|
||||
|
@@ -6,19 +6,7 @@ examples/peripherals/adc/continuous_read:
|
||||
temporary: true
|
||||
reason: adc dma mode isn't supported on these targets
|
||||
|
||||
examples/peripherals/dac/dac_audio:
|
||||
disable:
|
||||
- if: SOC_DAC_SUPPORTED != 1
|
||||
|
||||
examples/peripherals/dac/dac_basic:
|
||||
disable:
|
||||
- if: SOC_DAC_SUPPORTED != 1
|
||||
|
||||
examples/peripherals/dac/dac_continuous:
|
||||
disable:
|
||||
- if: SOC_DAC_SUPPORTED != 1
|
||||
|
||||
examples/peripherals/dac/dac_cosine_wave:
|
||||
examples/peripherals/dac:
|
||||
disable:
|
||||
- if: SOC_DAC_SUPPORTED != 1
|
||||
|
||||
|
@@ -1,2 +0,0 @@
|
||||
idf_component_register(SRCS "dac_audio.c"
|
||||
INCLUDE_DIRS ".")
|
File diff suppressed because it is too large
Load Diff
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/dac_driver.h"
|
||||
#include "esp_check.h"
|
||||
#include "audio_example_file.h"
|
||||
|
||||
#define EXAMPLE_CONVERT_FREQ_HZ 16000 // DAC conversion frequency, it determines how fast to play the audio
|
||||
|
||||
static const char *TAG = "dac audio";
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "DAC audio example start");
|
||||
ESP_LOGI(TAG, "--------------------------------------");
|
||||
|
||||
dac_channels_handle_t dac_handle;
|
||||
|
||||
dac_channels_config_t cfg = {
|
||||
.chan_sel = DAC_CHANNEL_MASK_BOTH,
|
||||
};
|
||||
dac_conti_config_t dma_cfg = {
|
||||
.freq_hz = EXAMPLE_CONVERT_FREQ_HZ,
|
||||
/* Assume the data in buffer is 'A B C D E F'
|
||||
* DAC_CHANNEL_MODE_SIMUL:
|
||||
* - channel 0: A B C D E F
|
||||
* - channel 1: A B C D E F
|
||||
* DAC_CHANNEL_MODE_ALTER:
|
||||
* - channel 0: A C E
|
||||
* - channel 1: B D F
|
||||
*/
|
||||
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_APLL, // Using APLL as clock source to get a wider frequency range
|
||||
.desc_num = 5, // At least 2 descriptions
|
||||
};
|
||||
/* Allocate the channel group */
|
||||
ESP_ERROR_CHECK(dac_new_channels(&cfg, &dac_handle));
|
||||
/* Enable the channels in the group */
|
||||
ESP_ERROR_CHECK(dac_channels_enable(dac_handle));
|
||||
/* Initialize DAC DMA peripheral */
|
||||
ESP_ERROR_CHECK(dac_channels_init_continuous_mode(dac_handle, &dma_cfg));
|
||||
/* Start the DAC DMA peripheral */
|
||||
ESP_ERROR_CHECK(dac_channels_enable_continuous_mode(dac_handle));
|
||||
ESP_LOGI(TAG, "DAC initialized success, DAC DMA is ready");
|
||||
|
||||
uint8_t *audio = NULL;
|
||||
size_t audio_size = sizeof(audio_table);
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
audio = (uint8_t *)calloc(1, audio_size * 2);
|
||||
assert(audio);
|
||||
/* On ESP32, the data have to align with 16 bits, and only the high 8 bit will be converted by DAC */
|
||||
for (int i = 0; i < audio_size; i++) {
|
||||
audio[2 * i + 1] = audio_table[i];
|
||||
}
|
||||
#else
|
||||
audio = (uint8_t *)calloc(1, audio_size);
|
||||
assert(audio);
|
||||
/* 'audio_table' is a const buffer which can't be sent by DMA directly, copy it into a new buffer */
|
||||
memcpy(audio, audio_table, audio_size);
|
||||
#endif
|
||||
|
||||
while (1) {
|
||||
ESP_LOGI(TAG, "Audio size %d bytes, played at frequency %d Hz", audio_size, EXAMPLE_CONVERT_FREQ_HZ);
|
||||
ESP_ERROR_CHECK(dac_channels_write_continuously(dac_handle, audio, audio_size, NULL, portMAX_DELAY));
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.esp32s2
|
||||
@pytest.mark.generic
|
||||
def test_dac_audio_exampl(dut: Dut) -> None:
|
||||
|
||||
dut.expect('I \\(([0-9]+)\\) dac audio: DAC audio example start', timeout=10)
|
||||
dut.expect('I \\(([0-9]+)\\) dac audio: --------------------------------------', timeout=5)
|
||||
dut.expect('I \\(([0-9]+)\\) dac audio: DAC initialized success, DAC DMA is ready', timeout=5)
|
||||
dut.expect('I \\(([0-9]+)\\) dac audio: Audio size ([0-9]+) bytes, played at frequency ([0-9]+) Hz', timeout=5)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,71 +0,0 @@
|
||||
| Supported Targets | ESP32 | ESP32-S2 |
|
||||
| ----------------- | ----- | -------- |
|
||||
|
||||
# DAC Basic Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
## Overview
|
||||
|
||||
This example shows the basic usage of outputting a voltage directly by the DAC driver.
|
||||
|
||||
The output voltage will increase a step every 500 ms, and it will reset to 0 periodically.
|
||||
|
||||
## How to use the Example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* A development board with ESP32 or ESP32-S2 SoC
|
||||
- Note that some ESP32-S2 DevKits have LED on it which is connected to GPIO18 (same pin as DAC channel2), so the output voltage of DAC channel 1 can't go down due the this LED.
|
||||
* (Optional) An oscilloscope to monitor the output wave
|
||||
|
||||
### Configure the Project
|
||||
|
||||
There is a macro `EXAMPLE_DAC_USE_SEPARATE_CHANNEL` in the example to choose whether put the two DAC channels into a same group
|
||||
|
||||
- If `EXAMPLE_DAC_USE_SEPARATE_CHANNEL` is 1, DAC channel 0 and channel 2 can be set to different voltage separately by their own group handle.
|
||||
- If `EXAMPLE_DAC_USE_SEPARATE_CHANNEL` is 0, DAC channel 0 and channel 2 will be set to a same voltage by the same group handle.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
The DAC channels can be read by ADC channels internally. The ADC read period is 100 ms, the following log is the raw ADC value read from the DAC channels, it shows the output voltage is increasing every 500 ms.
|
||||
|
||||
```
|
||||
DAC channel 0 vaule: 37 DAC channel 1 vaule: 0
|
||||
DAC channel 0 vaule: 37 DAC channel 1 vaule: 0
|
||||
DAC channel 0 vaule: 38 DAC channel 1 vaule: 0
|
||||
DAC channel 0 vaule: 38 DAC channel 1 vaule: 0
|
||||
DAC channel 0 vaule: 34 DAC channel 1 vaule: 0
|
||||
DAC channel 0 vaule: 179 DAC channel 1 vaule: 117
|
||||
DAC channel 0 vaule: 176 DAC channel 1 vaule: 117
|
||||
DAC channel 0 vaule: 178 DAC channel 1 vaule: 122
|
||||
DAC channel 0 vaule: 179 DAC channel 1 vaule: 118
|
||||
DAC channel 0 vaule: 177 DAC channel 1 vaule: 115
|
||||
DAC channel 0 vaule: 316 DAC channel 1 vaule: 261
|
||||
DAC channel 0 vaule: 317 DAC channel 1 vaule: 263
|
||||
DAC channel 0 vaule: 311 DAC channel 1 vaule: 261
|
||||
DAC channel 0 vaule: 317 DAC channel 1 vaule: 260
|
||||
DAC channel 0 vaule: 317 DAC channel 1 vaule: 262
|
||||
DAC channel 0 vaule: 458 DAC channel 1 vaule: 406
|
||||
DAC channel 0 vaule: 456 DAC channel 1 vaule: 406
|
||||
DAC channel 0 vaule: 454 DAC channel 1 vaule: 403
|
||||
DAC channel 0 vaule: 457 DAC channel 1 vaule: 406
|
||||
DAC channel 0 vaule: 459 DAC channel 1 vaule: 407
|
||||
...
|
||||
```
|
||||
|
||||
If monitoring the DAC channels with an oscilloscope, there will be a direct voltage on the screen and it will be updated every 500 ms.
|
@@ -1,2 +0,0 @@
|
||||
idf_component_register(SRCS "dac_basic.c"
|
||||
INCLUDE_DIRS ".")
|
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/dac_driver.h"
|
||||
#include "driver/adc.h"
|
||||
#include "esp_check.h"
|
||||
|
||||
#define EXAMPLE_DAC_USE_SEPARATE_CHANNEL 0 // Whether to register two DAC channels in separate control group
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#define EXAMPLE_DAC_CHAN1_ADC_CHAN ADC2_CHANNEL_8 // GPIO25, same as DAC channel 0
|
||||
#define EXAMPLE_DAC_CHAN2_ADC_CHAN ADC2_CHANNEL_9 // GPIO26, same as DAC channel 1
|
||||
#define EXAMPLE_ADC_WIDTH ADC_WIDTH_BIT_12
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
#define EXAMPLE_DAC_CHAN1_ADC_CHAN ADC2_CHANNEL_6 // GPIO17, same as DAC channel 0
|
||||
#define EXAMPLE_DAC_CHAN2_ADC_CHAN ADC2_CHANNEL_7 // GPIO18, same as DAC channel 1
|
||||
#define EXAMPLE_ADC_WIDTH ADC_WIDTH_BIT_13
|
||||
#endif
|
||||
#define EXAMPLE_ADC_ATTEN ADC_ATTEN_DB_11
|
||||
|
||||
static void adc_monitor_task(void *args)
|
||||
{
|
||||
/* Set the ADC channels, these channels are connected to the DAC channels internally */
|
||||
ESP_ERROR_CHECK(adc2_config_channel_atten(EXAMPLE_DAC_CHAN1_ADC_CHAN, EXAMPLE_ADC_ATTEN));
|
||||
ESP_ERROR_CHECK(adc2_config_channel_atten(EXAMPLE_DAC_CHAN2_ADC_CHAN, EXAMPLE_ADC_ATTEN));
|
||||
|
||||
int chan1_val = 0;
|
||||
int chan2_val = 0;
|
||||
while (1) {
|
||||
/* Read the DAC output voltage */
|
||||
ESP_ERROR_CHECK(adc2_get_raw(EXAMPLE_DAC_CHAN1_ADC_CHAN, EXAMPLE_ADC_WIDTH, &chan1_val));
|
||||
ESP_ERROR_CHECK(adc2_get_raw(EXAMPLE_DAC_CHAN2_ADC_CHAN, EXAMPLE_ADC_WIDTH, &chan2_val));
|
||||
printf("DAC channel 0 vaule: %4d\tDAC channel 1 vaule: %4d\n", chan1_val, chan2_val);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
|
||||
static void dac_output_task(void *args)
|
||||
{
|
||||
dac_channels_handle_t handle = (dac_channels_handle_t)args;
|
||||
uint32_t val = 0;
|
||||
while (1) {
|
||||
/* Set the voltage every 100 ms */
|
||||
ESP_ERROR_CHECK(dac_channels_set_voltage(handle, val));
|
||||
val += 10;
|
||||
val %= 250;
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
}
|
||||
}
|
||||
|
||||
static void dac_init_channel(dac_channel_mask_t mask, dac_channels_handle_t *dac_handle)
|
||||
{
|
||||
dac_channels_handle_t handle = NULL;
|
||||
dac_channels_config_t cfg = {
|
||||
.chan_sel = mask,
|
||||
};
|
||||
/* Allocate the channel group */
|
||||
ESP_ERROR_CHECK(dac_new_channels(&cfg, &handle));
|
||||
/* Enable the channels in the group */
|
||||
ESP_ERROR_CHECK(dac_channels_enable(handle));
|
||||
*dac_handle = handle;
|
||||
}
|
||||
|
||||
static void __attribute__((unused)) dac_deinit_channel(dac_channels_handle_t dac_handle)
|
||||
{
|
||||
/* Disable the DAC channels */
|
||||
ESP_ERROR_CHECK(dac_channels_disable(dac_handle));
|
||||
/* Delete the channel group */
|
||||
ESP_ERROR_CHECK(dac_del_channels(dac_handle));
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
#if EXAMPLE_DAC_USE_SEPARATE_CHANNEL
|
||||
dac_channels_handle_t chan1_handle;
|
||||
dac_channels_handle_t chan2_handle;
|
||||
/* Initialize the two channels separately */
|
||||
dac_init_channel(DAC_CHANNEL_MASK_CH0, &chan1_handle);
|
||||
dac_init_channel(DAC_CHANNEL_MASK_CH1, &chan2_handle);
|
||||
xTaskCreate(dac_output_task, "dac_chan1_output_task", 4096, chan1_handle, 5, NULL);
|
||||
vTaskDelay(pdMS_TO_TICKS(500)); // To differential the output of two channels
|
||||
xTaskCreate(dac_output_task, "dac_chan2_output_task", 4096, chan2_handle, 5, NULL);
|
||||
xTaskCreate(adc_monitor_task, "adc_monitor_task", 4096, NULL, 5, NULL);
|
||||
#else
|
||||
dac_channels_handle_t chan12_handle;
|
||||
/* Initialize the two channels in a same group */
|
||||
dac_init_channel(DAC_CHANNEL_MASK_BOTH, &chan12_handle);
|
||||
xTaskCreate(dac_output_task, "dac_output_task", 4096, chan12_handle, 5, NULL);
|
||||
xTaskCreate(adc_monitor_task, "adc_monitor_task", 4096, NULL, 5, NULL);
|
||||
#endif
|
||||
}
|
@@ -1 +0,0 @@
|
||||
CONFIG_ADC_DISABLE_DAC=n
|
@@ -1,153 +0,0 @@
|
||||
| Supported Targets | ESP32 | ESP32-S2 |
|
||||
| ----------------- | ----- | -------- |
|
||||
|
||||
# DAC Constant Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
## Overview
|
||||
|
||||
This example shows the basic usage of outputting continuous voltage by the DAC driver. There are two ways to realize continuous output, one is outputting by DMA transmission and another is by timer interrupt.
|
||||
|
||||
### Timer Interrupt
|
||||
|
||||
While using timer interrupt to output the waves, the digital value will be set in every timer interrupt callback, it means the conversion frequency is equal to the timer interrupt frequency. Obviously, the conversion frequency is limited by the interrupt, which relies on the CPU scheduling, thus it can't reach a high frequency in this mode. But it can be used as a supplementary way while the conversion frequency is too low to use DMA mode.
|
||||
|
||||
### DMA transmission
|
||||
|
||||
While using DMA to transmit the wave buffers, the digital values are put into a DMA buffer wait for transmitting and converting, it means the conversion frequency is equal to the frequency that DMA transmitting the data. We can set the DMA frequency directly, and the digital data int the buffer will be sent automatically when the buffer has been loaded onto the DMA. So the conversion frequency can reach even several MHz while using DMA mode. But the wave can be distorted if the frequency is too high.
|
||||
|
||||
## How to use the Example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* A development board with ESP32 or ESP32-S2 SoC
|
||||
- Note that some ESP32-S2 DevKits have LED on it which is connected to GPIO18 (same pin as DAC channel2), so the output voltage of DAC channel 1 can't go down due the this LED.
|
||||
* (Optional) An oscilloscope to monitor the output wave
|
||||
|
||||
### Configure the Project
|
||||
|
||||
You can switch the output method by setting the macro `EXAMPLE_DAC_CONTINUOUS_MODE` to `EXAMPLE_DAC_CONTINUOUS_BY_TIMER` or `EXAMPLE_DAC_CONTINUOUS_BY_DMA`.
|
||||
|
||||
There are four waves: sine, triangle, saw tooth and square. These waves are stored in corresponding buffers, and each wave has 400 points as default, which can be modified by `EXAMPLE_ARRAY_LEN`, reduce the point number can increase the wave frequency.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
This example can output sine wave, triangle wave, saw tooth wave and square wave periodically, each wave will last for 3 seconds.
|
||||
|
||||
The DAC channels can be read by ADC channels internally. The ADC read period is 500 ms, the following log is the raw ADC value read from the DAC channels. But since the ADC sample-rate is lower than the DAC output-rate, the sampling value can only indicate that the voltage is changing.
|
||||
|
||||
### Timer Triggered Output
|
||||
|
||||
You can see sine wave, triangle wave, saw tooth wave and square wave at 50 Hz on the oscilloscope.
|
||||
|
||||
```
|
||||
I (333) dac continuous: --------------------------------------------------
|
||||
I (343) dac continuous: DAC continuous output by Timer
|
||||
I (343) dac continuous: DAC channel 0 io: GPIO_NUM_25
|
||||
I (353) dac continuous: DAC channel 1 io: GPIO_NUM_26
|
||||
I (353) dac continuous: Waveform: SINE -> TRIANGLE -> SAWTOOTH -> SQUARE
|
||||
I (363) dac continuous: DAC conversion frequency (Hz): 20000
|
||||
I (373) dac continuous: DAC wave frequency (Hz): 50
|
||||
I (373) dac continuous: --------------------------------------------------
|
||||
DAC channel 0 vaule: 2291 DAC channel 1 vaule: 2331
|
||||
DAC channel 0 vaule: 43 DAC channel 1 vaule: 3
|
||||
DAC channel 0 vaule: 55 DAC channel 1 vaule: 32
|
||||
DAC channel 0 vaule: 57 DAC channel 1 vaule: 33
|
||||
DAC channel 0 vaule: 56 DAC channel 1 vaule: 34
|
||||
DAC channel 0 vaule: 59 DAC channel 1 vaule: 34
|
||||
DAC channel 0 vaule: 56 DAC channel 1 vaule: 33
|
||||
I (3393) dac continuous: triangle wave start
|
||||
DAC channel 0 vaule: 2258 DAC channel 1 vaule: 2243
|
||||
DAC channel 0 vaule: 2257 DAC channel 1 vaule: 2242
|
||||
DAC channel 0 vaule: 2259 DAC channel 1 vaule: 2242
|
||||
DAC channel 0 vaule: 2257 DAC channel 1 vaule: 2245
|
||||
DAC channel 0 vaule: 2257 DAC channel 1 vaule: 2243
|
||||
DAC channel 0 vaule: 2258 DAC channel 1 vaule: 2240
|
||||
I (6393) dac continuous: sawtooth wave start
|
||||
DAC channel 0 vaule: 2704 DAC channel 1 vaule: 2735
|
||||
DAC channel 0 vaule: 2704 DAC channel 1 vaule: 2735
|
||||
DAC channel 0 vaule: 2704 DAC channel 1 vaule: 2736
|
||||
DAC channel 0 vaule: 2704 DAC channel 1 vaule: 2717
|
||||
DAC channel 0 vaule: 2704 DAC channel 1 vaule: 2734
|
||||
DAC channel 0 vaule: 2704 DAC channel 1 vaule: 2736
|
||||
I (9393) dac continuous: square wave start
|
||||
DAC channel 0 vaule: 0 DAC channel 1 vaule: 0
|
||||
DAC channel 0 vaule: 0 DAC channel 1 vaule: 0
|
||||
DAC channel 0 vaule: 0 DAC channel 1 vaule: 0
|
||||
DAC channel 0 vaule: 0 DAC channel 1 vaule: 0
|
||||
DAC channel 0 vaule: 0 DAC channel 1 vaule: 0
|
||||
DAC channel 0 vaule: 0 DAC channel 1 vaule: 0
|
||||
I (12393) dac continuous: sine wave start
|
||||
DAC channel 0 vaule: 82 DAC channel 1 vaule: 62
|
||||
DAC channel 0 vaule: 83 DAC channel 1 vaule: 62
|
||||
DAC channel 0 vaule: 82 DAC channel 1 vaule: 62
|
||||
DAC channel 0 vaule: 87 DAC channel 1 vaule: 62
|
||||
DAC channel 0 vaule: 84 DAC channel 1 vaule: 63
|
||||
DAC channel 0 vaule: 83 DAC channel 1 vaule: 64
|
||||
...
|
||||
```
|
||||
|
||||
### DMA Output
|
||||
|
||||
You can see sine wave, triangle wave, saw tooth wave and square wave at 2 KHz on the oscilloscope.
|
||||
|
||||
```
|
||||
I (335) dac continuous: --------------------------------------------------
|
||||
I (345) dac continuous: DAC continuous output by DMA
|
||||
I (345) dac continuous: DAC channel 0 io: GPIO_NUM_25
|
||||
I (355) dac continuous: DAC channel 1 io: GPIO_NUM_26
|
||||
I (355) dac continuous: Waveform: SINE -> TRIANGLE -> SAWTOOTH -> SQUARE
|
||||
I (365) dac continuous: DAC conversion frequency (Hz): 800000
|
||||
I (375) dac continuous: DAC wave frequency (Hz): 2000
|
||||
I (375) dac continuous: --------------------------------------------------
|
||||
DAC channel 0 vaule: 3131 DAC channel 1 vaule: 1634
|
||||
DAC channel 0 vaule: 1712 DAC channel 1 vaule: 2531
|
||||
DAC channel 0 vaule: 1716 DAC channel 1 vaule: 2535
|
||||
DAC channel 0 vaule: 1715 DAC channel 1 vaule: 2544
|
||||
DAC channel 0 vaule: 1715 DAC channel 1 vaule: 2533
|
||||
DAC channel 0 vaule: 1712 DAC channel 1 vaule: 2539
|
||||
I (3395) dac continuous(DMA): triangle wave start
|
||||
DAC channel 0 vaule: 592 DAC channel 1 vaule: 1190
|
||||
DAC channel 0 vaule: 4095 DAC channel 1 vaule: 3518
|
||||
DAC channel 0 vaule: 4095 DAC channel 1 vaule: 3515
|
||||
DAC channel 0 vaule: 4095 DAC channel 1 vaule: 3516
|
||||
DAC channel 0 vaule: 4095 DAC channel 1 vaule: 3514
|
||||
DAC channel 0 vaule: 4095 DAC channel 1 vaule: 3515
|
||||
I (6395) dac continuous(DMA): sawtooth wave start
|
||||
DAC channel 0 vaule: 294 DAC channel 1 vaule: 560
|
||||
DAC channel 0 vaule: 2861 DAC channel 1 vaule: 3227
|
||||
DAC channel 0 vaule: 2860 DAC channel 1 vaule: 3216
|
||||
DAC channel 0 vaule: 2861 DAC channel 1 vaule: 3227
|
||||
DAC channel 0 vaule: 2861 DAC channel 1 vaule: 3216
|
||||
DAC channel 0 vaule: 2859 DAC channel 1 vaule: 3183
|
||||
I (9395) dac continuous(DMA): square wave start
|
||||
DAC channel 0 vaule: 4095 DAC channel 1 vaule: 4095
|
||||
DAC channel 0 vaule: 0 DAC channel 1 vaule: 0
|
||||
DAC channel 0 vaule: 0 DAC channel 1 vaule: 0
|
||||
DAC channel 0 vaule: 0 DAC channel 1 vaule: 0
|
||||
DAC channel 0 vaule: 0 DAC channel 1 vaule: 0
|
||||
DAC channel 0 vaule: 0 DAC channel 1 vaule: 0
|
||||
I (12395) dac continuous(DMA): sine wave start
|
||||
DAC channel 0 vaule: 2864 DAC channel 1 vaule: 3691
|
||||
DAC channel 0 vaule: 0 DAC channel 1 vaule: 204
|
||||
DAC channel 0 vaule: 0 DAC channel 1 vaule: 202
|
||||
DAC channel 0 vaule: 0 DAC channel 1 vaule: 193
|
||||
DAC channel 0 vaule: 0 DAC channel 1 vaule: 181
|
||||
DAC channel 0 vaule: 0 DAC channel 1 vaule: 194
|
||||
...
|
||||
```
|
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "dac_audio_example_main.c"
|
||||
INCLUDE_DIRS ".")
|
@@ -0,0 +1,21 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
choice EXAMPLE_DAC_WRITE_MODE
|
||||
prompt "Select DAC writing mode"
|
||||
default EXAMPLE_DAC_WRITE_SYNC
|
||||
help
|
||||
DAC writes data synchronously or asynchronously
|
||||
|
||||
config EXAMPLE_DAC_WRITE_SYNC
|
||||
bool "Synchronous transmitting"
|
||||
config EXAMPLE_DAC_WRITE_ASYNC
|
||||
bool "Asynchronous transmitting"
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_AUDIO_SAMPLE_RATE
|
||||
int "The audio sample rate (Unit: Hz)"
|
||||
default 48000
|
||||
help
|
||||
The audio sample rate
|
||||
|
||||
endmenu
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "driver/dac_conti.h"
|
||||
#include "esp_check.h"
|
||||
#include "audio_example_file.h"
|
||||
|
||||
static const char *TAG = "dac_audio";
|
||||
|
||||
#if CONFIG_EXAMPLE_DAC_WRITE_ASYNC
|
||||
static bool IRAM_ATTR dac_on_convert_done_callback(dac_conti_handle_t handle, const dac_event_data_t *event, void *user_data)
|
||||
{
|
||||
QueueHandle_t que = (QueueHandle_t)user_data;
|
||||
BaseType_t need_awoke;
|
||||
/* When the queue is full, drop the oldest item */
|
||||
if (xQueueIsQueueFullFromISR(que)) {
|
||||
dac_event_data_t dummy;
|
||||
xQueueReceiveFromISR(que, &dummy, &need_awoke);
|
||||
}
|
||||
/* Send the event from callback */
|
||||
xQueueSendFromISR(que, event, &need_awoke);
|
||||
return need_awoke;
|
||||
}
|
||||
|
||||
static void dac_write_data_asynchronously(dac_conti_handle_t handle, QueueHandle_t que, uint8_t *data, size_t data_size)
|
||||
{
|
||||
ESP_LOGI(TAG, "Audio size %d bytes, played at frequency %d Hz asynchronously", data_size, CONFIG_EXAMPLE_AUDIO_SAMPLE_RATE);
|
||||
uint32_t cnt = 1;
|
||||
while (1) {
|
||||
printf("Play count: %"PRIu32"\n", cnt++);
|
||||
dac_event_data_t evt_data;
|
||||
size_t byte_written = 0;
|
||||
/* Receive the event from callback and load the data into the DMA buffer until the whole audio loaded */
|
||||
while (byte_written < data_size) {
|
||||
xQueueReceive(que, &evt_data, portMAX_DELAY);
|
||||
size_t loaded_bytes = 0;
|
||||
ESP_ERROR_CHECK(dac_conti_write_asynchronously(handle, evt_data.buf, evt_data.buf_size,
|
||||
data + byte_written, data_size - byte_written, &loaded_bytes));
|
||||
byte_written += loaded_bytes;
|
||||
}
|
||||
/* Clear the legacy data in DMA, clear times equal to the 'dac_conti_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);
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void dac_write_data_synchronously(dac_conti_handle_t handle, uint8_t *data, size_t data_size)
|
||||
{
|
||||
ESP_LOGI(TAG, "Audio size %d bytes, played at frequency %d Hz synchronously", data_size, CONFIG_EXAMPLE_AUDIO_SAMPLE_RATE);
|
||||
uint32_t cnt = 1;
|
||||
while (1) {
|
||||
printf("Play count: %"PRIu32"\n", cnt++);
|
||||
ESP_ERROR_CHECK(dac_conti_write(handle, data, data_size, NULL, -1));
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "DAC audio example start");
|
||||
ESP_LOGI(TAG, "--------------------------------------");
|
||||
|
||||
dac_conti_handle_t dac_handle;
|
||||
dac_conti_config_t conti_cfg = {
|
||||
.chan_mask = DAC_CHANNEL_MASK_ALL,
|
||||
.desc_num = 4,
|
||||
.buf_size = 2048,
|
||||
.freq_hz = CONFIG_EXAMPLE_AUDIO_SAMPLE_RATE,
|
||||
.offset = 0,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_APLL, // Using APLL as clock source to get a wider frequency range
|
||||
/* Assume the data in buffer is 'A B C D E F'
|
||||
* DAC_CHANNEL_MODE_SIMUL:
|
||||
* - channel 0: A B C D E F
|
||||
* - channel 1: A B C D E F
|
||||
* DAC_CHANNEL_MODE_ALTER:
|
||||
* - channel 0: A C E
|
||||
* - channel 1: B D F
|
||||
*/
|
||||
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
|
||||
};
|
||||
/* Allocate continuous channels */
|
||||
ESP_ERROR_CHECK(dac_new_conti_channels(&conti_cfg, &dac_handle));
|
||||
#if CONFIG_EXAMPLE_DAC_WRITE_ASYNC
|
||||
/* Create a queue to transport the interrupt event data */
|
||||
QueueHandle_t que = xQueueCreate(10, sizeof(dac_event_data_t));
|
||||
assert(que);
|
||||
dac_event_callbacks_t cbs = {
|
||||
.on_convert_done = dac_on_convert_done_callback,
|
||||
.on_stop = NULL,
|
||||
};
|
||||
/* Must register the callback if using asynchronous writing */
|
||||
ESP_ERROR_CHECK(dac_conti_register_event_callback(dac_handle, &cbs, que));
|
||||
#endif
|
||||
/* Enable the continuous channels */
|
||||
ESP_ERROR_CHECK(dac_conti_enable(dac_handle));
|
||||
ESP_LOGI(TAG, "DAC initialized success, DAC DMA is ready");
|
||||
|
||||
size_t audio_size = sizeof(audio_table);
|
||||
#if CONFIG_EXAMPLE_DAC_WRITE_ASYNC
|
||||
ESP_ERROR_CHECK(dac_conti_start_async_writing(dac_handle));
|
||||
dac_write_data_asynchronously(dac_handle, que, (uint8_t *)audio_table, audio_size);
|
||||
#else
|
||||
dac_write_data_synchronously(dac_handle, (uint8_t *)audio_table, audio_size);
|
||||
#endif
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
def dac_example_expect(dut: Dut, mode: str) -> None:
|
||||
dut.expect('dac_audio: DAC audio example start', timeout=10)
|
||||
dut.expect('dac_audio: --------------------------------------', timeout=5)
|
||||
dut.expect('dac_audio: DAC initialized success, DAC DMA is ready', timeout=5)
|
||||
dut.expect('dac_audio: Audio size 95824 bytes, played at frequency 48000 Hz ' + mode, timeout=5)
|
||||
dut.expect('Play count: 2', timeout=10)
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.esp32s2
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'sync',
|
||||
'async',
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_dac_audio_example(dut: Dut) -> None:
|
||||
sdkconfig = dut.app.sdkconfig
|
||||
if sdkconfig['EXAMPLE_DAC_WRITE_SYNC']:
|
||||
dac_example_expect(dut, 'synchronously')
|
||||
else:
|
||||
dac_example_expect(dut, 'asynchronously')
|
@@ -0,0 +1,2 @@
|
||||
CONFIG_EXAMPLE_DAC_WRITE_SYNC=n
|
||||
CONFIG_EXAMPLE_DAC_WRITE_ASYNC=y
|
@@ -0,0 +1,2 @@
|
||||
CONFIG_EXAMPLE_DAC_WRITE_SYNC=y
|
||||
CONFIG_EXAMPLE_DAC_WRITE_ASYNC=n
|
Binary file not shown.
@@ -1,6 +0,0 @@
|
||||
set(srcs "dac_continuous_main.c"
|
||||
"dac_continuous_dma.c"
|
||||
"dac_continuous_timer.c")
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS ".")
|
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_check.h"
|
||||
#include "dac_continuous.h"
|
||||
|
||||
#define EXAMPLE_WAVE_FREQ_HZ 2000 // Default wave frequency 50 Hz, it can't be too high
|
||||
#define EXAMPLE_CONVERT_FREQ_HZ (EXAMPLE_ARRAY_LEN * EXAMPLE_WAVE_FREQ_HZ) // The frequency that DAC convert every data in the wave array
|
||||
|
||||
static uint8_t sin_wav[EXAMPLE_ARRAY_LEN]; // Used to store sine wave values
|
||||
static uint8_t tri_wav[EXAMPLE_ARRAY_LEN]; // Used to store triangle wave values
|
||||
static uint8_t saw_wav[EXAMPLE_ARRAY_LEN]; // Used to store sawtooth wave values
|
||||
static uint8_t squ_wav[EXAMPLE_ARRAY_LEN]; // Used to store square wave values
|
||||
|
||||
static const char *TAG = "dac continuous(DMA)";
|
||||
static const char wav_name[DAC_WAVE_MAX][15] = {"sine", "triangle", "sawtooth", "square"};
|
||||
|
||||
static void dac_dma_write_task(void *args)
|
||||
{
|
||||
dac_channels_handle_t handle = (dac_channels_handle_t)args;
|
||||
dac_example_wave_type_t wav_sel = DAC_SINE_WAVE; // Start from sine wave
|
||||
|
||||
size_t buf_len = EXAMPLE_ARRAY_LEN;
|
||||
|
||||
while (1) {
|
||||
/* The wave in the buffer will be converted cyclicly
|
||||
* but take care the data buffer need to be available during the conversion */
|
||||
switch (wav_sel) {
|
||||
case DAC_SINE_WAVE:
|
||||
ESP_ERROR_CHECK(dac_channels_write_cyclically(handle, (uint8_t *)sin_wav, buf_len, NULL, 1000));
|
||||
break;
|
||||
case DAC_TRIANGLE_WAVE:
|
||||
ESP_ERROR_CHECK(dac_channels_write_cyclically(handle, (uint8_t *)tri_wav, buf_len, NULL, 1000));
|
||||
break;
|
||||
case DAC_SAWTOOTH_WAVE:
|
||||
ESP_ERROR_CHECK(dac_channels_write_cyclically(handle, (uint8_t *)saw_wav, buf_len, NULL, 1000));
|
||||
break;
|
||||
case DAC_SQUARE_WAVE:
|
||||
ESP_ERROR_CHECK(dac_channels_write_cyclically(handle, (uint8_t *)squ_wav, buf_len, NULL, 1000));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
/* Switch wave every EXAMPLE_WAVE_PERIOD_SEC seconds */
|
||||
vTaskDelay(pdMS_TO_TICKS(EXAMPLE_WAVE_PERIOD_SEC * 1000));
|
||||
wav_sel++;
|
||||
wav_sel %= DAC_WAVE_MAX;
|
||||
ESP_LOGI(TAG, "%s wave start", wav_name[wav_sel]);
|
||||
}
|
||||
}
|
||||
|
||||
static void dac_init_channel(dac_channel_mask_t mask, dac_conti_config_t *conti_cfg, dac_channels_handle_t *dac_handle)
|
||||
{
|
||||
dac_channels_handle_t handle = NULL;
|
||||
dac_channels_config_t cfg = {
|
||||
.chan_sel = mask,
|
||||
};
|
||||
/* Allocate the channel group */
|
||||
ESP_ERROR_CHECK(dac_new_channels(&cfg, &handle));
|
||||
/* Enable the channels in the group */
|
||||
ESP_ERROR_CHECK(dac_channels_enable(handle));
|
||||
/* Initialize DAC DMA peripheral */
|
||||
ESP_ERROR_CHECK(dac_channels_init_continuous_mode(handle, conti_cfg));
|
||||
/* Start the DAC DMA peripheral */
|
||||
ESP_ERROR_CHECK(dac_channels_enable_continuous_mode(handle));
|
||||
|
||||
*dac_handle = handle;
|
||||
}
|
||||
|
||||
/* Unused DAC de-initialize example, to show how to delete the DAC resources */
|
||||
static void __attribute__((unused)) dac_deinit_channel(dac_channels_handle_t dac_handle)
|
||||
{
|
||||
/* Stop the DAC DMA peripheral */
|
||||
ESP_ERROR_CHECK(dac_channels_disable_continuous_mode(dac_handle));
|
||||
/* Deinitialize the DAC DMA peripheral */
|
||||
ESP_ERROR_CHECK(dac_channels_deinit_continuous_mode(dac_handle));
|
||||
/* Disable the DAC channels */
|
||||
ESP_ERROR_CHECK(dac_channels_disable(dac_handle));
|
||||
/* Delete the channel group */
|
||||
ESP_ERROR_CHECK(dac_del_channels(dac_handle));
|
||||
}
|
||||
|
||||
static void example_generate_wave(void)
|
||||
{
|
||||
uint32_t pnt_num = EXAMPLE_ARRAY_LEN;
|
||||
|
||||
for (int i = 0; i < pnt_num; i ++) {
|
||||
sin_wav[i] = (uint8_t)((sin( i * CONST_PERIOD_2_PI / pnt_num) + 1) * (double)(EXAMPLE_DAC_AMPLITUDE) / 2 + 0.5);
|
||||
tri_wav[i] = (i > (pnt_num / 2)) ? (2 * EXAMPLE_DAC_AMPLITUDE * (pnt_num - i) / pnt_num) : (2 * EXAMPLE_DAC_AMPLITUDE * i / pnt_num);
|
||||
saw_wav[i] = (i == pnt_num) ? 0 : (i * EXAMPLE_DAC_AMPLITUDE / pnt_num);
|
||||
squ_wav[i] = (i < (pnt_num / 2)) ? EXAMPLE_DAC_AMPLITUDE : 0;
|
||||
}
|
||||
}
|
||||
|
||||
void dac_continuous_by_dma(void)
|
||||
{
|
||||
dac_channels_handle_t chan12_handle;
|
||||
dac_conti_config_t conti_cfg = {
|
||||
.freq_hz = EXAMPLE_CONVERT_FREQ_HZ,
|
||||
/* Assume the data in buffer is 'A B C D E F'
|
||||
* DAC_CHANNEL_MODE_SIMUL:
|
||||
* - channel 0: A B C D E F
|
||||
* - channel 1: A B C D E F
|
||||
* DAC_CHANNEL_MODE_ALTER:
|
||||
* - channel 0: A C E
|
||||
* - channel 1: B D F
|
||||
*/
|
||||
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT, // If the frequency is out of range, try 'DAC_DIGI_CLK_SRC_APLL'
|
||||
.desc_num = 10,
|
||||
};
|
||||
/* For Continuous(DMA) Mode, only one group can access the DMA periphral,
|
||||
* which means the two channels can't be initialized to DMA mode separately */
|
||||
dac_init_channel(DAC_CHANNEL_MASK_BOTH, &conti_cfg, &chan12_handle);
|
||||
|
||||
example_log_info(EXAMPLE_CONVERT_FREQ_HZ, EXAMPLE_WAVE_FREQ_HZ);
|
||||
|
||||
/* Generate the data buffer, the data is a sawtooth wave every 256 point,
|
||||
* With the the data frequency at 20 KHz, the sawtooth wave frequency is about 20 KHz / 256 = 78.125 Hz */
|
||||
example_generate_wave();
|
||||
|
||||
/* Start to convert wave */
|
||||
xTaskCreate(dac_dma_write_task, "dac_dma_write_task", 4096, chan12_handle, 5, NULL);
|
||||
}
|
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/adc.h"
|
||||
#include "esp_check.h"
|
||||
#include "dac_continuous.h"
|
||||
|
||||
/**
|
||||
* There are two ways to convert digital data to analog signal continuously:
|
||||
* - Using a timer: setting DAC voltage periodically in the timer interrupt
|
||||
* in this way, DAC can achieve a relatively low conversion frequency
|
||||
* but it is not a efficient way comparing to using the DMA
|
||||
* - Using DMA: tansmitting the data buffer via DMA,
|
||||
* the conversion frequency is controlled by how fast it is transmitted by DMA
|
||||
* in this way, the conversion frequency can reach sevral MHz,
|
||||
* but it can't achieve a very low conversion frequency because it is limited by the DMA clock source
|
||||
* Generally, recommand to use DMA, if the DMA peripheral is occupied or the required conversion frequency is very low,
|
||||
* then use timer instead
|
||||
*/
|
||||
#define EXAMPLE_DAC_CONTINUOUS_BY_TIMER 0
|
||||
#define EXAMPLE_DAC_CONTINUOUS_BY_DMA 1
|
||||
#define EXAMPLE_DAC_CONTINUOUS_MODE EXAMPLE_DAC_CONTINUOUS_BY_DMA
|
||||
|
||||
/* ADC configuration */
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#define EXAMPLE_DAC_CHAN0_ADC_CHAN ADC2_CHANNEL_8 // GPIO25, same as DAC channel 0
|
||||
#define EXAMPLE_DAC_CHAN1_ADC_CHAN ADC2_CHANNEL_9 // GPIO26, same as DAC channel 1
|
||||
#define EXAMPLE_DAC_CHAN0_IO (25) // DAC channel 0 io number
|
||||
#define EXAMPLE_DAC_CHAN1_IO (26) // DAC channel 1 io number
|
||||
#define EXAMPLE_ADC_WIDTH ADC_WIDTH_BIT_12
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
#define EXAMPLE_DAC_CHAN0_ADC_CHAN ADC2_CHANNEL_6 // GPIO17, same as DAC channel 0
|
||||
#define EXAMPLE_DAC_CHAN1_ADC_CHAN ADC2_CHANNEL_7 // GPIO18, same as DAC channel 1
|
||||
#define EXAMPLE_DAC_CHAN0_IO (17) // DAC channel 0 io number
|
||||
#define EXAMPLE_DAC_CHAN1_IO (18) // DAC channel 1 io number
|
||||
#define EXAMPLE_ADC_WIDTH ADC_WIDTH_BIT_13
|
||||
#endif
|
||||
#define EXAMPLE_ADC_ATTEN ADC_ATTEN_DB_11
|
||||
|
||||
_Static_assert(EXAMPLE_DAC_AMPLITUDE < 256, "The DAC accuracy is 8 bit-width, doesn't support the amplitude beyond 255");
|
||||
|
||||
static const char *TAG = "dac continuous";
|
||||
|
||||
void example_log_info(uint32_t conv_freq, uint32_t wave_freq)
|
||||
{
|
||||
ESP_LOGI(TAG, "--------------------------------------------------");
|
||||
ESP_LOGI(TAG, "DAC continuous output by %s", EXAMPLE_DAC_CONTINUOUS_MODE ? "DMA" : "Timer");
|
||||
ESP_LOGI(TAG, "DAC channel 0 io: GPIO_NUM_%d", EXAMPLE_DAC_CHAN0_IO);
|
||||
ESP_LOGI(TAG, "DAC channel 1 io: GPIO_NUM_%d", EXAMPLE_DAC_CHAN1_IO);
|
||||
ESP_LOGI(TAG, "Waveform: SINE -> TRIANGLE -> SAWTOOTH -> SQUARE");
|
||||
ESP_LOGI(TAG, "DAC conversion frequency (Hz): %d", conv_freq);
|
||||
ESP_LOGI(TAG, "DAC wave frequency (Hz): %d", wave_freq);
|
||||
ESP_LOGI(TAG, "--------------------------------------------------");
|
||||
}
|
||||
|
||||
static void adc_monitor_task(void *args)
|
||||
{
|
||||
/* Set the ADC channels, these channels are connected to the DAC channels internally */
|
||||
ESP_ERROR_CHECK(adc2_config_channel_atten(EXAMPLE_DAC_CHAN0_ADC_CHAN, EXAMPLE_ADC_ATTEN));
|
||||
ESP_ERROR_CHECK(adc2_config_channel_atten(EXAMPLE_DAC_CHAN1_ADC_CHAN, EXAMPLE_ADC_ATTEN));
|
||||
|
||||
int chan1_val = 0;
|
||||
int chan2_val = 0;
|
||||
while (1) {
|
||||
/* Read the DAC output voltage */
|
||||
ESP_ERROR_CHECK(adc2_get_raw(EXAMPLE_DAC_CHAN0_ADC_CHAN, EXAMPLE_ADC_WIDTH, &chan1_val));
|
||||
ESP_ERROR_CHECK(adc2_get_raw(EXAMPLE_DAC_CHAN1_ADC_CHAN, EXAMPLE_ADC_WIDTH, &chan2_val));
|
||||
printf("DAC channel 0 vaule: %4d\tDAC channel 1 vaule: %4d\n", chan1_val, chan2_val);
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
#if EXAMPLE_DAC_CONTINUOUS_MODE == EXAMPLE_DAC_CONTINUOUS_BY_DMA
|
||||
/* Output 2 kHz waves using DMA */
|
||||
dac_continuous_by_dma();
|
||||
#else
|
||||
/* Output 50 Hz waves using timer interrupt */
|
||||
dac_continuous_by_timer();
|
||||
#endif
|
||||
/* Create ADC monitor task to detect the voltage on DAC pin every 500 ms */
|
||||
xTaskCreate(adc_monitor_task, "adc_monitor_task", 4096, NULL, 5, NULL);
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.generic
|
||||
def test_dac_continuous_example_with_12bit_adc(dut: Dut) -> None:
|
||||
dut.expect('I \\(([0-9]+)\\) dac continuous: --------------------------------------------------', timeout=10)
|
||||
dut.expect('I \\(([0-9]+)\\) dac continuous: DAC continuous output by DMA', timeout=10)
|
||||
dut.expect('I \\(([0-9]+)\\) dac continuous: DAC channel 0 io: GPIO_NUM_25', timeout=10)
|
||||
dut.expect('I \\(([0-9]+)\\) dac continuous: DAC channel 1 io: GPIO_NUM_26', timeout=10)
|
||||
dut.expect('I \\(([0-9]+)\\) dac continuous: Waveform: SINE -> TRIANGLE -> SAWTOOTH -> SQUARE', timeout=10)
|
||||
dut.expect('I \\(([0-9]+)\\) dac continuous: DAC conversion frequency \\(Hz\\): ([0-9]+)', timeout=10)
|
||||
dut.expect('I \\(([0-9]+)\\) dac continuous: DAC wave frequency \\(Hz\\): ([0-9]+)', timeout=10)
|
||||
dut.expect('I \\(([0-9]+)\\) dac continuous: --------------------------------------------------', timeout=10)
|
||||
dut.expect(r'DAC channel 0 vaule:( +)(\d+)(.*)DAC channel 1 vaule:( +)(\d+)', timeout=10)
|
||||
|
||||
|
||||
@pytest.mark.esp32s2
|
||||
@pytest.mark.generic
|
||||
def test_dac_continuous_example_with_13bit_adc(dut: Dut) -> None:
|
||||
dut.expect('I \\(([0-9]+)\\) dac continuous: --------------------------------------------------', timeout=10)
|
||||
dut.expect('I \\(([0-9]+)\\) dac continuous: DAC continuous output by DMA', timeout=10)
|
||||
dut.expect('I \\(([0-9]+)\\) dac continuous: DAC channel 0 io: GPIO_NUM_17', timeout=10)
|
||||
dut.expect('I \\(([0-9]+)\\) dac continuous: DAC channel 1 io: GPIO_NUM_18', timeout=10)
|
||||
dut.expect('I \\(([0-9]+)\\) dac continuous: Waveform: SINE -> TRIANGLE -> SAWTOOTH -> SQUARE', timeout=10)
|
||||
dut.expect('I \\(([0-9]+)\\) dac continuous: DAC conversion frequency \\(Hz\\): ([0-9]+)', timeout=10)
|
||||
dut.expect('I \\(([0-9]+)\\) dac continuous: DAC wave frequency \\(Hz\\): ([0-9]+)', timeout=10)
|
||||
dut.expect('I \\(([0-9]+)\\) dac continuous: --------------------------------------------------', timeout=10)
|
||||
dut.expect(r'DAC channel 0 vaule:( +)(\d+)(.*)DAC channel 1 vaule:( +)(\d+)', timeout=10)
|
@@ -1 +0,0 @@
|
||||
CONFIG_ADC_DISABLE_DAC=n
|
@@ -3,4 +3,4 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(dac_continuous)
|
||||
project(signal_generator)
|
@@ -0,0 +1,155 @@
|
||||
| Supported Targets | ESP32 | ESP32-S2 |
|
||||
| ----------------- | ----- | -------- |
|
||||
|
||||
# DAC Constant Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
## Overview
|
||||
|
||||
This example shows the basic usage of outputting continuous voltage by the DAC driver. There are two ways to realize continuous output, one is outputting by DMA transmission and another is by timer interrupt.
|
||||
|
||||
### Timer Interrupt
|
||||
|
||||
While using timer interrupt to output the waves, it actrually sets the voltage by `oneshot` API in every timer interrupt callback. Which means the conversion frequency is equal to the timer interrupt frequency. Obviously, the conversion frequency is limited by the interrupt, which relies on the CPU scheduling, thus it can't reach a high frequency in this mode. But it can be used as a supplementary way while the conversion frequency is too low to use DMA mode.
|
||||
|
||||
### DMA transmission
|
||||
|
||||
While using DMA to transmit the wave buffers, the digital values are put into a DMA buffer waiting for transmition and conversion, that means the conversion frequency is equal to the frequency that DMA transmitting the data. We can set the DMA frequency directly, and the digital data in the buffer will be sent automatically when the buffer has been loaded into the DMA. So the conversion frequency can reach even several MHz while using DMA mode. But the wave can be distorted if the frequency is too high.
|
||||
|
||||
## How to use the Example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* A development board with ESP32 or ESP32-S2 SoC
|
||||
- Note that some ESP32-S2 DevKits have LED on it which is connected to GPIO18 (same pin as DAC channel1), so the output voltage of DAC channel 1 can't go down due the this LED.
|
||||
* (Optional) An oscilloscope to monitor the output wave
|
||||
|
||||
### Configure the Project
|
||||
|
||||
You can switch the output method by setting the macro `EXAMPLE_DAC_CONTINUOUS_MODE` to `EXAMPLE_DAC_CONTINUOUS_BY_TIMER` or `EXAMPLE_DAC_CONTINUOUS_BY_DMA`.
|
||||
|
||||
There are four waves: sine, triangle, saw tooth and square. These waves are stored in corresponding buffers, and each wave has 400 points as default, which can be modified by `EXAMPLE_ARRAY_LEN`, reduce the point number can increase the wave frequency.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Note that as we use the ADC to monitor the output data, we need to set false to `CONFIG_ADC_DISABLE_DAC_OUTPUT` in the menuconfig, otherwise the ADC will shutdown the DAC power to guarantee it won't be affect by DAC.
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
This example can output sine wave, triangle wave, saw tooth wave and square wave periodically, each wave will last for 3 seconds.
|
||||
|
||||
The DAC channels can be read by ADC channels internally. The ADC read period is 500 ms, the following log is the raw ADC value read from the DAC channels. But since the ADC sample-rate is lower than the DAC output-rate, the sampling value can only indicate that the voltage is changing.
|
||||
|
||||
### Timer Triggered Output
|
||||
|
||||
You can see sine wave, triangle wave, saw tooth wave and square wave at 50 Hz on the oscilloscope.
|
||||
|
||||
```
|
||||
I (333) dac continuous: --------------------------------------------------
|
||||
I (343) dac continuous: DAC continuous output by Timer
|
||||
I (343) dac continuous: DAC channel 0 io: GPIO_NUM_25
|
||||
I (353) dac continuous: DAC channel 1 io: GPIO_NUM_26
|
||||
I (353) dac continuous: Waveform: SINE -> TRIANGLE -> SAWTOOTH -> SQUARE
|
||||
I (363) dac continuous: DAC conversion frequency (Hz): 20000
|
||||
I (373) dac continuous: DAC wave frequency (Hz): 50
|
||||
I (373) dac continuous: --------------------------------------------------
|
||||
DAC channel 0 value: 2291 DAC channel 1 value: 2331
|
||||
DAC channel 0 value: 43 DAC channel 1 value: 3
|
||||
DAC channel 0 value: 55 DAC channel 1 value: 32
|
||||
DAC channel 0 value: 57 DAC channel 1 value: 33
|
||||
DAC channel 0 value: 56 DAC channel 1 value: 34
|
||||
DAC channel 0 value: 59 DAC channel 1 value: 34
|
||||
DAC channel 0 value: 56 DAC channel 1 value: 33
|
||||
I (3393) dac continuous(timer): triangle wave start
|
||||
DAC channel 0 value: 2258 DAC channel 1 value: 2243
|
||||
DAC channel 0 value: 2257 DAC channel 1 value: 2242
|
||||
DAC channel 0 value: 2259 DAC channel 1 value: 2242
|
||||
DAC channel 0 value: 2257 DAC channel 1 value: 2245
|
||||
DAC channel 0 value: 2257 DAC channel 1 value: 2243
|
||||
DAC channel 0 value: 2258 DAC channel 1 value: 2240
|
||||
I (6393) dac continuous(timer): sawtooth wave start
|
||||
DAC channel 0 value: 2704 DAC channel 1 value: 2735
|
||||
DAC channel 0 value: 2704 DAC channel 1 value: 2735
|
||||
DAC channel 0 value: 2704 DAC channel 1 value: 2736
|
||||
DAC channel 0 value: 2704 DAC channel 1 value: 2717
|
||||
DAC channel 0 value: 2704 DAC channel 1 value: 2734
|
||||
DAC channel 0 value: 2704 DAC channel 1 value: 2736
|
||||
I (9393) dac continuous(timer): square wave start
|
||||
DAC channel 0 value: 0 DAC channel 1 value: 0
|
||||
DAC channel 0 value: 0 DAC channel 1 value: 0
|
||||
DAC channel 0 value: 0 DAC channel 1 value: 0
|
||||
DAC channel 0 value: 0 DAC channel 1 value: 0
|
||||
DAC channel 0 value: 0 DAC channel 1 value: 0
|
||||
DAC channel 0 value: 0 DAC channel 1 value: 0
|
||||
I (12393) dac continuous(timer): sine wave start
|
||||
DAC channel 0 value: 82 DAC channel 1 value: 62
|
||||
DAC channel 0 value: 83 DAC channel 1 value: 62
|
||||
DAC channel 0 value: 82 DAC channel 1 value: 62
|
||||
DAC channel 0 value: 87 DAC channel 1 value: 62
|
||||
DAC channel 0 value: 84 DAC channel 1 value: 63
|
||||
DAC channel 0 value: 83 DAC channel 1 value: 64
|
||||
...
|
||||
```
|
||||
|
||||
### DMA Output
|
||||
|
||||
You can see sine wave, triangle wave, saw tooth wave and square wave at 2 KHz on the oscilloscope.
|
||||
|
||||
```
|
||||
I (335) dac continuous: --------------------------------------------------
|
||||
I (345) dac continuous: DAC continuous output by DMA
|
||||
I (345) dac continuous: DAC channel 0 io: GPIO_NUM_25
|
||||
I (355) dac continuous: DAC channel 1 io: GPIO_NUM_26
|
||||
I (355) dac continuous: Waveform: SINE -> TRIANGLE -> SAWTOOTH -> SQUARE
|
||||
I (365) dac continuous: DAC conversion frequency (Hz): 800000
|
||||
I (375) dac continuous: DAC wave frequency (Hz): 2000
|
||||
I (375) dac continuous: --------------------------------------------------
|
||||
DAC channel 0 value: 3131 DAC channel 1 value: 1634
|
||||
DAC channel 0 value: 1712 DAC channel 1 value: 2531
|
||||
DAC channel 0 value: 1716 DAC channel 1 value: 2535
|
||||
DAC channel 0 value: 1715 DAC channel 1 value: 2544
|
||||
DAC channel 0 value: 1715 DAC channel 1 value: 2533
|
||||
DAC channel 0 value: 1712 DAC channel 1 value: 2539
|
||||
I (3395) dac continuous(DMA): triangle wave start
|
||||
DAC channel 0 value: 592 DAC channel 1 value: 1190
|
||||
DAC channel 0 value: 4095 DAC channel 1 value: 3518
|
||||
DAC channel 0 value: 4095 DAC channel 1 value: 3515
|
||||
DAC channel 0 value: 4095 DAC channel 1 value: 3516
|
||||
DAC channel 0 value: 4095 DAC channel 1 value: 3514
|
||||
DAC channel 0 value: 4095 DAC channel 1 value: 3515
|
||||
I (6395) dac continuous(DMA): sawtooth wave start
|
||||
DAC channel 0 value: 294 DAC channel 1 value: 560
|
||||
DAC channel 0 value: 2861 DAC channel 1 value: 3227
|
||||
DAC channel 0 value: 2860 DAC channel 1 value: 3216
|
||||
DAC channel 0 value: 2861 DAC channel 1 value: 3227
|
||||
DAC channel 0 value: 2861 DAC channel 1 value: 3216
|
||||
DAC channel 0 value: 2859 DAC channel 1 value: 3183
|
||||
I (9395) dac continuous(DMA): square wave start
|
||||
DAC channel 0 value: 4095 DAC channel 1 value: 4095
|
||||
DAC channel 0 value: 0 DAC channel 1 value: 0
|
||||
DAC channel 0 value: 0 DAC channel 1 value: 0
|
||||
DAC channel 0 value: 0 DAC channel 1 value: 0
|
||||
DAC channel 0 value: 0 DAC channel 1 value: 0
|
||||
DAC channel 0 value: 0 DAC channel 1 value: 0
|
||||
I (12395) dac continuous(DMA): sine wave start
|
||||
DAC channel 0 value: 2864 DAC channel 1 value: 3691
|
||||
DAC channel 0 value: 0 DAC channel 1 value: 204
|
||||
DAC channel 0 value: 0 DAC channel 1 value: 202
|
||||
DAC channel 0 value: 0 DAC channel 1 value: 193
|
||||
DAC channel 0 value: 0 DAC channel 1 value: 181
|
||||
DAC channel 0 value: 0 DAC channel 1 value: 194
|
||||
...
|
||||
```
|
@@ -0,0 +1,6 @@
|
||||
set(srcs "dac_conti_example_main.c"
|
||||
"dac_conti_example_dma.c"
|
||||
"dac_conti_example_timer.c")
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS ".")
|
@@ -0,0 +1,22 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
choice EXAMPLE_DAC_CONTINUOUS_MODE
|
||||
prompt "Select DAC continuous example mode"
|
||||
default EXAMPLE_DAC_CONTINUOUS_BY_DMA
|
||||
help
|
||||
DAC output continuous data by DMA or timer
|
||||
|
||||
config EXAMPLE_DAC_CONTINUOUS_BY_DMA
|
||||
bool "By DMA"
|
||||
config EXAMPLE_DAC_CONTINUOUS_BY_TIMER
|
||||
bool "By Timer"
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_WAVE_PERIOD_SEC
|
||||
int "Period of switching the waveform (Unit: Sec)"
|
||||
default 2
|
||||
help
|
||||
The waveforms will switch every several seconds,
|
||||
this configuration is to specify the switching period
|
||||
|
||||
endmenu
|
@@ -3,14 +3,10 @@
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
#include "driver/dac_driver.h"
|
||||
|
||||
#define CONST_PERIOD_2_PI 6.2832 // 2 * PI
|
||||
|
||||
#define EXAMPLE_ARRAY_LEN 400 // Length of wave array
|
||||
#define EXAMPLE_DAC_AMPLITUDE 255 // Amplitude of DAC voltage. If it's more than 256 will causes dac_output_voltage() output 0.
|
||||
#define EXAMPLE_WAVE_PERIOD_SEC 3 // Switch wave every 3 senconds
|
||||
#define EXAMPLE_DAC_CHANNEL DAC_CHANNEL_MASK_BOTH // DAC_CHANNEL_MASK_CH0 & DAC_CHANNEL_MASK_CH1
|
||||
|
||||
typedef enum {
|
||||
DAC_SINE_WAVE,
|
||||
@@ -24,13 +20,13 @@ typedef enum {
|
||||
* @brief Use DMA to convert continuously
|
||||
*
|
||||
*/
|
||||
void dac_continuous_by_dma(void);
|
||||
void example_dac_continuous_by_dma(void);
|
||||
|
||||
/**
|
||||
* @brief Use timer to convert continuously
|
||||
*
|
||||
*/
|
||||
void dac_continuous_by_timer(void);
|
||||
void example_dac_continuous_by_timer(void);
|
||||
|
||||
/**
|
||||
* @brief Print the example log information
|
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/dac_conti.h"
|
||||
#include "esp_check.h"
|
||||
#include "dac_conti_example.h"
|
||||
|
||||
#define EXAMPLE_WAVE_FREQ_HZ 2000 // Default wave frequency 2000 Hz, it can't be too low
|
||||
#define EXAMPLE_CONVERT_FREQ_HZ (EXAMPLE_ARRAY_LEN * EXAMPLE_WAVE_FREQ_HZ) // The frequency that DAC convert every data in the wave array
|
||||
|
||||
extern uint8_t sin_wav[EXAMPLE_ARRAY_LEN]; // Used to store sine wave values
|
||||
extern uint8_t tri_wav[EXAMPLE_ARRAY_LEN]; // Used to store triangle wave values
|
||||
extern uint8_t saw_wav[EXAMPLE_ARRAY_LEN]; // Used to store sawtooth wave values
|
||||
extern uint8_t squ_wav[EXAMPLE_ARRAY_LEN]; // Used to store square wave values
|
||||
|
||||
static const char *TAG = "dac continuous(DMA)";
|
||||
static const char wav_name[DAC_WAVE_MAX][15] = {"sine", "triangle", "sawtooth", "square"};
|
||||
|
||||
static void dac_dma_write_task(void *args)
|
||||
{
|
||||
dac_conti_handle_t handle = (dac_conti_handle_t)args;
|
||||
dac_example_wave_type_t wav_sel = DAC_SINE_WAVE; // Start from sine wave
|
||||
|
||||
size_t buf_len = EXAMPLE_ARRAY_LEN;
|
||||
|
||||
while (1) {
|
||||
/* The wave in the buffer will be converted cyclically */
|
||||
switch (wav_sel) {
|
||||
case DAC_SINE_WAVE:
|
||||
ESP_ERROR_CHECK(dac_conti_write_cyclically(handle, (uint8_t *)sin_wav, buf_len, NULL));
|
||||
break;
|
||||
case DAC_TRIANGLE_WAVE:
|
||||
ESP_ERROR_CHECK(dac_conti_write_cyclically(handle, (uint8_t *)tri_wav, buf_len, NULL));
|
||||
break;
|
||||
case DAC_SAWTOOTH_WAVE:
|
||||
ESP_ERROR_CHECK(dac_conti_write_cyclically(handle, (uint8_t *)saw_wav, buf_len, NULL));
|
||||
break;
|
||||
case DAC_SQUARE_WAVE:
|
||||
ESP_ERROR_CHECK(dac_conti_write_cyclically(handle, (uint8_t *)squ_wav, buf_len, NULL));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
/* Switch wave every CONFIG_EXAMPLE_WAVE_PERIOD_SEC seconds */
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_EXAMPLE_WAVE_PERIOD_SEC * 1000));
|
||||
wav_sel++;
|
||||
wav_sel %= DAC_WAVE_MAX;
|
||||
ESP_LOGI(TAG, "%s wave start", wav_name[wav_sel]);
|
||||
}
|
||||
}
|
||||
|
||||
void example_dac_continuous_by_dma(void)
|
||||
{
|
||||
dac_conti_handle_t conti_handle;
|
||||
dac_conti_config_t conti_cfg = {
|
||||
.chan_mask = DAC_CHANNEL_MASK_ALL,
|
||||
.desc_num = 8,
|
||||
.buf_size = 2048,
|
||||
.freq_hz = EXAMPLE_CONVERT_FREQ_HZ,
|
||||
.offset = 0,
|
||||
.clk_src = DAC_DIGI_CLK_SRC_DEFAULT, // If the frequency is out of range, try 'DAC_DIGI_CLK_SRC_APLL'
|
||||
/* Assume the data in buffer is 'A B C D E F'
|
||||
* DAC_CHANNEL_MODE_SIMUL:
|
||||
* - channel 0: A B C D E F
|
||||
* - channel 1: A B C D E F
|
||||
* DAC_CHANNEL_MODE_ALTER:
|
||||
* - channel 0: A C E
|
||||
* - channel 1: B D F
|
||||
*/
|
||||
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
|
||||
};
|
||||
/* Allocate continuous channel */
|
||||
ESP_ERROR_CHECK(dac_new_conti_channels(&conti_cfg, &conti_handle));
|
||||
/* Enable the channels in the group */
|
||||
ESP_ERROR_CHECK(dac_conti_enable(conti_handle));
|
||||
|
||||
example_log_info(EXAMPLE_CONVERT_FREQ_HZ, EXAMPLE_WAVE_FREQ_HZ);
|
||||
|
||||
/* Start to convert wave */
|
||||
xTaskCreate(dac_dma_write_task, "dac_dma_write_task", 4096, conti_handle, 5, NULL);
|
||||
}
|
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "soc/dac_channel.h"
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
#include "esp_check.h"
|
||||
#include "dac_conti_example.h"
|
||||
|
||||
/**
|
||||
* There are two ways to convert digital data to analog signal continuously:
|
||||
* - Using a timer: setting DAC voltage periodically in the timer interrupt
|
||||
* in this way, DAC can achieve a relatively low conversion frequency
|
||||
* but it is not a efficient way comparing to using the DMA
|
||||
* - Using DMA: tansmitting the data buffer via DMA,
|
||||
* the conversion frequency is controlled by how fast it is transmitted by DMA
|
||||
* in this way, the conversion frequency can reach several MHz,
|
||||
* but it can't achieve a very low conversion frequency because it is limited by the DMA clock source
|
||||
* Generally, recommand to use DMA, if the DMA peripheral is occupied or the required conversion frequency is very low,
|
||||
* then use timer instead
|
||||
*/
|
||||
|
||||
/* ADC configuration */
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#define EXAMPLE_DAC_CHAN0_ADC_CHAN ADC_CHANNEL_8 // GPIO25, same as DAC channel 0
|
||||
#define EXAMPLE_DAC_CHAN1_ADC_CHAN ADC_CHANNEL_9 // GPIO26, same as DAC channel 1
|
||||
#define EXAMPLE_ADC_WIDTH ADC_WIDTH_BIT_12
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
#define EXAMPLE_DAC_CHAN0_ADC_CHAN ADC_CHANNEL_6 // GPIO17, same as DAC channel 0
|
||||
#define EXAMPLE_DAC_CHAN1_ADC_CHAN ADC_CHANNEL_7 // GPIO18, same as DAC channel 1
|
||||
#define EXAMPLE_ADC_WIDTH ADC_WIDTH_BIT_13
|
||||
#endif
|
||||
#define EXAMPLE_DAC_CHAN0_IO DAC_CHAN0_GPIO_NUM // DAC channel 0 io number
|
||||
#define EXAMPLE_DAC_CHAN1_IO DAC_CHAN1_GPIO_NUM // DAC channel 1 io number
|
||||
#define EXAMPLE_ADC_ATTEN ADC_ATTEN_DB_11
|
||||
|
||||
_Static_assert(EXAMPLE_DAC_AMPLITUDE < 256, "The DAC accuracy is 8 bit-width, doesn't support the amplitude beyond 255");
|
||||
|
||||
static const char *TAG = "dac continuous";
|
||||
|
||||
uint8_t sin_wav[EXAMPLE_ARRAY_LEN]; // Used to store sine wave values
|
||||
uint8_t tri_wav[EXAMPLE_ARRAY_LEN]; // Used to store triangle wave values
|
||||
uint8_t saw_wav[EXAMPLE_ARRAY_LEN]; // Used to store sawtooth wave values
|
||||
uint8_t squ_wav[EXAMPLE_ARRAY_LEN]; // Used to store square wave values
|
||||
|
||||
static void example_generate_wave(void)
|
||||
{
|
||||
uint32_t pnt_num = EXAMPLE_ARRAY_LEN;
|
||||
|
||||
for (int i = 0; i < pnt_num; i ++) {
|
||||
sin_wav[i] = (uint8_t)((sin( i * CONST_PERIOD_2_PI / pnt_num) + 1) * (double)(EXAMPLE_DAC_AMPLITUDE) / 2 + 0.5);
|
||||
tri_wav[i] = (i > (pnt_num / 2)) ? (2 * EXAMPLE_DAC_AMPLITUDE * (pnt_num - i) / pnt_num) : (2 * EXAMPLE_DAC_AMPLITUDE * i / pnt_num);
|
||||
saw_wav[i] = (i == pnt_num) ? 0 : (i * EXAMPLE_DAC_AMPLITUDE / pnt_num);
|
||||
squ_wav[i] = (i < (pnt_num / 2)) ? EXAMPLE_DAC_AMPLITUDE : 0;
|
||||
}
|
||||
}
|
||||
|
||||
void example_log_info(uint32_t conv_freq, uint32_t wave_freq)
|
||||
{
|
||||
ESP_LOGI(TAG, "--------------------------------------------------");
|
||||
#if CONFIG_EXAMPLE_DAC_CONTINUOUS_BY_DMA
|
||||
ESP_LOGI(TAG, "DAC continuous output by DMA");
|
||||
#else
|
||||
ESP_LOGI(TAG, "DAC continuous output by timer");
|
||||
#endif
|
||||
ESP_LOGI(TAG, "DAC channel 0 io: GPIO_NUM_%d", EXAMPLE_DAC_CHAN0_IO);
|
||||
ESP_LOGI(TAG, "DAC channel 1 io: GPIO_NUM_%d", EXAMPLE_DAC_CHAN1_IO);
|
||||
ESP_LOGI(TAG, "Waveform: SINE -> TRIANGLE -> SAWTOOTH -> SQUARE");
|
||||
ESP_LOGI(TAG, "DAC conversion frequency (Hz): %"PRIu32, conv_freq);
|
||||
ESP_LOGI(TAG, "DAC wave frequency (Hz): %"PRIu32, wave_freq);
|
||||
ESP_LOGI(TAG, "--------------------------------------------------");
|
||||
}
|
||||
|
||||
static void adc_monitor_task(void *args)
|
||||
{
|
||||
adc_oneshot_unit_handle_t adc2_handle = (adc_oneshot_unit_handle_t)args;
|
||||
|
||||
int chan0_val = 0;
|
||||
int chan1_val = 0;
|
||||
while (1) {
|
||||
/* Read the DAC output voltage */
|
||||
ESP_ERROR_CHECK(adc_oneshot_read(adc2_handle, EXAMPLE_DAC_CHAN0_ADC_CHAN, &chan0_val));
|
||||
ESP_ERROR_CHECK(adc_oneshot_read(adc2_handle, EXAMPLE_DAC_CHAN1_ADC_CHAN, &chan1_val));
|
||||
printf("DAC channel 0 value: %4d\tDAC channel 1 value: %4d\n", chan0_val, chan1_val);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
example_generate_wave();
|
||||
#if CONFIG_EXAMPLE_DAC_CONTINUOUS_BY_DMA
|
||||
/* Output 2 kHz waves using DMA */
|
||||
example_dac_continuous_by_dma();
|
||||
#else
|
||||
/* Output 50 Hz waves using timer interrupt */
|
||||
example_dac_continuous_by_timer();
|
||||
#endif
|
||||
|
||||
/* Set the ADC2 channels, these channels are connected to the DAC channels internally */
|
||||
adc_oneshot_unit_handle_t adc2_handle;
|
||||
adc_oneshot_unit_init_cfg_t adc_cfg = {
|
||||
.unit_id = ADC_UNIT_2,
|
||||
.ulp_mode = false,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_new_unit(&adc_cfg, &adc2_handle));
|
||||
adc_oneshot_chan_cfg_t chan_cfg = {
|
||||
.atten = EXAMPLE_ADC_ATTEN,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc2_handle, EXAMPLE_DAC_CHAN0_ADC_CHAN, &chan_cfg));
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc2_handle, EXAMPLE_DAC_CHAN1_ADC_CHAN, &chan_cfg));
|
||||
/* Create ADC monitor task to detect the voltage on DAC pin */
|
||||
xTaskCreate(adc_monitor_task, "adc_monitor_task", 4096, adc2_handle, 5, NULL);
|
||||
}
|
@@ -12,24 +12,25 @@
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/adc.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "driver/dac_oneshot.h"
|
||||
#include "esp_log.h"
|
||||
#include "dac_continuous.h"
|
||||
#include "dac_conti_example.h"
|
||||
|
||||
#define EXAMPLE_TIEMR_RESOLUTION 1000000 // 1MHz, 1 tick = 1us
|
||||
#define EXAMPLE_TIMER_RESOLUTION 1000000 // 1MHz, 1 tick = 1us
|
||||
#define EXAMPLE_WAVE_FREQ_HZ 50 // Default wave frequency 50 Hz, it can't be too high
|
||||
#define EXAMPLE_CONVERT_FREQ_HZ (EXAMPLE_ARRAY_LEN * EXAMPLE_WAVE_FREQ_HZ) // The frequency that DAC convert every data in the wave array
|
||||
#define EXAMPLE_TIMER_ALARM_COUNT (EXAMPLE_TIEMR_RESOLUTION / EXAMPLE_CONVERT_FREQ_HZ) // The count value that trigger the timer alarm callback
|
||||
#define EXAMPLE_TIMER_ALARM_COUNT (EXAMPLE_TIMER_RESOLUTION / EXAMPLE_CONVERT_FREQ_HZ) // The count value that trigger the timer alarm callback
|
||||
|
||||
static const char *TAG = "dac continuous(timer)";
|
||||
static const char wav_name[DAC_WAVE_MAX][15] = {"sine", "triangle", "sawtooth", "square"};
|
||||
static dac_channels_handle_t dac_handle;
|
||||
static dac_oneshot_handle_t chan0_handle;
|
||||
static dac_oneshot_handle_t chan1_handle;
|
||||
|
||||
static uint8_t sin_wav[EXAMPLE_ARRAY_LEN]; // sine wave values
|
||||
static uint8_t tri_wav[EXAMPLE_ARRAY_LEN]; // triangle wave values
|
||||
static uint8_t saw_wav[EXAMPLE_ARRAY_LEN]; // sawtooth wave values
|
||||
static uint8_t squ_wav[EXAMPLE_ARRAY_LEN]; // square wave values
|
||||
extern uint8_t sin_wav[EXAMPLE_ARRAY_LEN]; // Used to store sine wave values
|
||||
extern uint8_t tri_wav[EXAMPLE_ARRAY_LEN]; // Used to store triangle wave values
|
||||
extern uint8_t saw_wav[EXAMPLE_ARRAY_LEN]; // Used to store sawtooth wave values
|
||||
extern uint8_t squ_wav[EXAMPLE_ARRAY_LEN]; // Used to store square wave values
|
||||
|
||||
/* Timer interrupt service routine */
|
||||
static bool IRAM_ATTR on_timer_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
|
||||
@@ -38,20 +39,24 @@ static bool IRAM_ATTR on_timer_alarm_cb(gptimer_handle_t timer, const gptimer_al
|
||||
static uint32_t index = 0; // The current index of the wave buffer
|
||||
static dac_example_wave_type_t wav_sel = DAC_SINE_WAVE; // Start from sine wave
|
||||
|
||||
// Switch wave every EXAMPLE_WAVE_PERIOD_SEC sencond
|
||||
if (point_cnt < EXAMPLE_CONVERT_FREQ_HZ * EXAMPLE_WAVE_PERIOD_SEC) {
|
||||
// Switch wave every CONFIG_EXAMPLE_WAVE_PERIOD_SEC second
|
||||
if (point_cnt < EXAMPLE_CONVERT_FREQ_HZ * CONFIG_EXAMPLE_WAVE_PERIOD_SEC) {
|
||||
switch (wav_sel) {
|
||||
case DAC_SINE_WAVE:
|
||||
ESP_ERROR_CHECK(dac_channels_set_voltage(dac_handle, sin_wav[index]));
|
||||
ESP_ERROR_CHECK(dac_oneshot_output_voltage(chan0_handle, sin_wav[index]));
|
||||
ESP_ERROR_CHECK(dac_oneshot_output_voltage(chan1_handle, sin_wav[index]));
|
||||
break;
|
||||
case DAC_TRIANGLE_WAVE:
|
||||
ESP_ERROR_CHECK(dac_channels_set_voltage(dac_handle, tri_wav[index]));
|
||||
ESP_ERROR_CHECK(dac_oneshot_output_voltage(chan0_handle, tri_wav[index]));
|
||||
ESP_ERROR_CHECK(dac_oneshot_output_voltage(chan1_handle, tri_wav[index]));
|
||||
break;
|
||||
case DAC_SAWTOOTH_WAVE:
|
||||
ESP_ERROR_CHECK(dac_channels_set_voltage(dac_handle, saw_wav[index]));
|
||||
ESP_ERROR_CHECK(dac_oneshot_output_voltage(chan0_handle, saw_wav[index]));
|
||||
ESP_ERROR_CHECK(dac_oneshot_output_voltage(chan1_handle, saw_wav[index]));
|
||||
break;
|
||||
case DAC_SQUARE_WAVE:
|
||||
ESP_ERROR_CHECK(dac_channels_set_voltage(dac_handle, squ_wav[index]));
|
||||
ESP_ERROR_CHECK(dac_oneshot_output_voltage(chan0_handle, squ_wav[index]));
|
||||
ESP_ERROR_CHECK(dac_oneshot_output_voltage(chan1_handle, squ_wav[index]));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -69,34 +74,25 @@ static bool IRAM_ATTR on_timer_alarm_cb(gptimer_handle_t timer, const gptimer_al
|
||||
return false;
|
||||
}
|
||||
|
||||
static void example_generate_wave(void)
|
||||
{
|
||||
uint32_t pnt_num = EXAMPLE_ARRAY_LEN;
|
||||
for (int i = 0; i < pnt_num; i ++) {
|
||||
sin_wav[i] = (uint8_t)((sin( i * CONST_PERIOD_2_PI / pnt_num) + 1) * (double)(EXAMPLE_DAC_AMPLITUDE) / 2 + 0.5);
|
||||
tri_wav[i] = (i > (pnt_num / 2)) ? (2 * EXAMPLE_DAC_AMPLITUDE * (pnt_num - i) / pnt_num) : (2 * EXAMPLE_DAC_AMPLITUDE * i / pnt_num);
|
||||
saw_wav[i] = (i == pnt_num) ? 0 : (i * EXAMPLE_DAC_AMPLITUDE / pnt_num);
|
||||
squ_wav[i] = (i < (pnt_num / 2)) ? EXAMPLE_DAC_AMPLITUDE : 0;
|
||||
}
|
||||
}
|
||||
|
||||
void dac_continuous_by_timer(void)
|
||||
void example_dac_continuous_by_timer(void)
|
||||
{
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = EXAMPLE_TIEMR_RESOLUTION, // 1MHz, 1 tick = 1us
|
||||
.resolution_hz = EXAMPLE_TIMER_RESOLUTION, // 1MHz, 1 tick = 1us
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
dac_channels_config_t dac_cfg = {
|
||||
.chan_sel = EXAMPLE_DAC_CHANNEL,
|
||||
dac_oneshot_config_t dac0_cfg = {
|
||||
.chan_id = DAC_CHAN_0,
|
||||
};
|
||||
ESP_ERROR_CHECK(dac_new_channels(&dac_cfg, &dac_handle));
|
||||
ESP_ERROR_CHECK(dac_channels_enable(dac_handle));
|
||||
ESP_ERROR_CHECK(dac_new_oneshot_channel(&dac0_cfg, &chan0_handle));
|
||||
dac_oneshot_config_t dac1_cfg = {
|
||||
.chan_id = DAC_CHAN_1,
|
||||
};
|
||||
ESP_ERROR_CHECK(dac_new_oneshot_channel(&dac1_cfg, &chan1_handle));
|
||||
|
||||
example_log_info(EXAMPLE_CONVERT_FREQ_HZ, EXAMPLE_WAVE_FREQ_HZ);
|
||||
example_generate_wave();
|
||||
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
@@ -0,0 +1,46 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
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('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)
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.esp32s2
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'dma',
|
||||
'timer',
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_dac_continuous_example_with_dma(dut: Dut) -> None:
|
||||
sdkconfig = dut.app.sdkconfig
|
||||
if dut.target == 'esp32':
|
||||
if sdkconfig['EXAMPLE_DAC_CONTINUOUS_BY_DMA']:
|
||||
test_dac_continuous_output(dut, 'DMA', '25', '26')
|
||||
else:
|
||||
test_dac_continuous_output(dut, 'timer', '25', '26')
|
||||
elif dut.target == 'esp32s2':
|
||||
if sdkconfig['EXAMPLE_DAC_CONTINUOUS_BY_DMA']:
|
||||
test_dac_continuous_output(dut, 'DMA', '17', '18')
|
||||
else:
|
||||
test_dac_continuous_output(dut, 'timer', '17', '18')
|
@@ -0,0 +1,2 @@
|
||||
CONFIG_EXAMPLE_DAC_CONTINUOUS_BY_DMA=y
|
||||
CONFIG_EXAMPLE_DAC_CONTINUOUS_BY_TIMER=n
|
@@ -0,0 +1,2 @@
|
||||
CONFIG_EXAMPLE_DAC_CONTINUOUS_BY_DMA=n
|
||||
CONFIG_EXAMPLE_DAC_CONTINUOUS_BY_TIMER=y
|
@@ -0,0 +1 @@
|
||||
CONFIG_ADC_DISABLE_DAC_OUTPUT=n
|
@@ -16,18 +16,13 @@ This example will output cosine wave on both channels.
|
||||
### Hardware Required
|
||||
|
||||
* A development board with ESP32 or ESP32-S2 SoC
|
||||
- Note that some ESP32-S2 DevKits have LED on it which is connected to GPIO18 (same pin as DAC channel2), so the output voltage of DAC channel 1 can't go down due the this LED.
|
||||
- Note that some ESP32-S2 DevKits have LED on it which is connected to GPIO18 (same pin as DAC channel1), so the output voltage of DAC channel 1 can't go down due the this LED.
|
||||
* (Optional) An oscilloscope to monitor the output wave
|
||||
|
||||
### Configure the Project
|
||||
|
||||
There is a macro `EXAMPLE_DAC_USE_SEPARATE_CHANNEL` in the example to choose whether put the two DAC channels into a same group
|
||||
|
||||
- If `EXAMPLE_DAC_USE_SEPARATE_CHANNEL` is 1, DAC channel 0 and channel 2 can be set to different cosine wave (but the frequency are same) by their own group handle.
|
||||
- If `EXAMPLE_DAC_USE_SEPARATE_CHANNEL` is 0, DAC channel 0 and channel 2 will be set to a same cosine wave by the same group handle.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Note that as we use the ADC to monitor the output data, we need to set false to `CONFIG_ADC_DISABLE_DAC_OUTPUT` in the menuconfig, otherwise the ADC will shutdown the DAC power to guarantee it won't be affect by DAC.
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
@@ -45,26 +40,26 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui
|
||||
The DAC channels can be read by ADC channels internally. The ADC read period is 100 ms, the following log is the raw ADC value read from the DAC channels. But since the ADC sample-rate might be lower than the DAC cosine period, the sampling value can only indicate that the voltage is changing.
|
||||
|
||||
```
|
||||
DAC channel 0 vaule: 647 DAC channel 1 vaule: 1728
|
||||
DAC channel 0 vaule: 2112 DAC channel 1 vaule: 2166
|
||||
DAC channel 0 vaule: 778 DAC channel 1 vaule: 2483
|
||||
DAC channel 0 vaule: 4095 DAC channel 1 vaule: 1922
|
||||
DAC channel 0 vaule: 238 DAC channel 1 vaule: 1282
|
||||
DAC channel 0 vaule: 3187 DAC channel 1 vaule: 2609
|
||||
DAC channel 0 vaule: 627 DAC channel 1 vaule: 1068
|
||||
DAC channel 0 vaule: 3168 DAC channel 1 vaule: 2624
|
||||
DAC channel 0 vaule: 225 DAC channel 1 vaule: 1286
|
||||
DAC channel 0 vaule: 4095 DAC channel 1 vaule: 2083
|
||||
DAC channel 0 vaule: 89 DAC channel 1 vaule: 1934
|
||||
DAC channel 0 vaule: 3603 DAC channel 1 vaule: 1434
|
||||
DAC channel 0 vaule: 725 DAC channel 1 vaule: 2469
|
||||
DAC channel 0 vaule: 2277 DAC channel 1 vaule: 960
|
||||
DAC channel 0 vaule: 1306 DAC channel 1 vaule: 2670
|
||||
DAC channel 0 vaule: 1670 DAC channel 1 vaule: 899
|
||||
DAC channel 0 vaule: 3189 DAC channel 1 vaule: 2609
|
||||
DAC channel 0 vaule: 86 DAC channel 1 vaule: 1459
|
||||
DAC channel 0 vaule: 4095 DAC channel 1 vaule: 2258
|
||||
DAC channel 0 value: 647 DAC channel 1 value: 1728
|
||||
DAC channel 0 value: 2112 DAC channel 1 value: 2166
|
||||
DAC channel 0 value: 778 DAC channel 1 value: 2483
|
||||
DAC channel 0 value: 4095 DAC channel 1 value: 1922
|
||||
DAC channel 0 value: 238 DAC channel 1 value: 1282
|
||||
DAC channel 0 value: 3187 DAC channel 1 value: 2609
|
||||
DAC channel 0 value: 627 DAC channel 1 value: 1068
|
||||
DAC channel 0 value: 3168 DAC channel 1 value: 2624
|
||||
DAC channel 0 value: 225 DAC channel 1 value: 1286
|
||||
DAC channel 0 value: 4095 DAC channel 1 value: 2083
|
||||
DAC channel 0 value: 89 DAC channel 1 value: 1934
|
||||
DAC channel 0 value: 3603 DAC channel 1 value: 1434
|
||||
DAC channel 0 value: 725 DAC channel 1 value: 2469
|
||||
DAC channel 0 value: 2277 DAC channel 1 value: 960
|
||||
DAC channel 0 value: 1306 DAC channel 1 value: 2670
|
||||
DAC channel 0 value: 1670 DAC channel 1 value: 899
|
||||
DAC channel 0 value: 3189 DAC channel 1 value: 2609
|
||||
DAC channel 0 value: 86 DAC channel 1 value: 1459
|
||||
DAC channel 0 value: 4095 DAC channel 1 value: 2258
|
||||
...
|
||||
```
|
||||
|
||||
If monitoring the DAC channels with an oscilloscope, there will be two same cosine waves at 1000 Hz if `EXAMPLE_DAC_USE_SEPARATE_CHANNEL` is 0, and two cosine waves with opposite phase and different amplitude at 8000 Hz if `EXAMPLE_DAC_USE_SEPARATE_CHANNEL` is 1.
|
||||
If monitoring the DAC channels with an oscilloscope, there will be two cosine waves with opposite phase and different amplitude at 8000 Hz on the two DAC channels.
|
||||
|
@@ -1,2 +1,2 @@
|
||||
idf_component_register(SRCS "dac_cosine_wave.c"
|
||||
idf_component_register(SRCS "dac_cosine_example_main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
|
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/dac_cosine.h"
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
#include "esp_check.h"
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#define EXAMPLE_DAC_CHAN0_ADC_CHAN ADC_CHANNEL_8 // GPIO25, same as DAC channel 0
|
||||
#define EXAMPLE_DAC_CHAN1_ADC_CHAN ADC_CHANNEL_9 // GPIO26, same as DAC channel 1
|
||||
#define EXAMPLE_ADC_WIDTH ADC_WIDTH_BIT_12
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
#define EXAMPLE_DAC_CHAN0_ADC_CHAN ADC_CHANNEL_6 // GPIO17, same as DAC channel 0
|
||||
#define EXAMPLE_DAC_CHAN1_ADC_CHAN ADC_CHANNEL_7 // GPIO18, same as DAC channel 1
|
||||
#define EXAMPLE_ADC_WIDTH ADC_WIDTH_BIT_13
|
||||
#endif
|
||||
#define EXAMPLE_ADC_ATTEN ADC_ATTEN_DB_11
|
||||
|
||||
static void adc_monitor_task(void *args)
|
||||
{
|
||||
/* Set the ADC2 channels, these channels are connected to the DAC channels internally */
|
||||
adc_oneshot_unit_handle_t adc2_handle = (adc_oneshot_unit_handle_t)args;
|
||||
int chan0_val = 0;
|
||||
int chan1_val = 0;
|
||||
while (1) {
|
||||
/* Read the DAC output voltage */
|
||||
ESP_ERROR_CHECK(adc_oneshot_read(adc2_handle, EXAMPLE_DAC_CHAN0_ADC_CHAN, &chan0_val));
|
||||
ESP_ERROR_CHECK(adc_oneshot_read(adc2_handle, EXAMPLE_DAC_CHAN1_ADC_CHAN, &chan1_val));
|
||||
printf("DAC channel 0 value: %4d\tDAC channel 1 value: %4d\n", chan0_val, chan1_val);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
dac_cosine_handle_t chan0_handle;
|
||||
dac_cosine_handle_t chan1_handle;
|
||||
/* Normally two channels can only be configured to one frequency
|
||||
* But we can set force_set_freq bit to force update the frequency
|
||||
* The example here will produce cosine wave at 8 KHz on both channels */
|
||||
dac_cosine_config_t cos0_cfg = {
|
||||
.chan_id = DAC_CHAN_0,
|
||||
.freq_hz = 1000, // It will be covered by 8000 in the latter configuration
|
||||
.clk_src = DAC_COSINE_CLK_SRC_DEFAULT,
|
||||
.offset = 0,
|
||||
.phase = DAC_COSINE_PHASE_0,
|
||||
.atten = DAC_COSINE_ATTEN_DEFAULT,
|
||||
.flags.force_set_freq = false,
|
||||
};
|
||||
dac_cosine_config_t cos1_cfg = {
|
||||
.chan_id = DAC_CHAN_1,
|
||||
.freq_hz = 8000,
|
||||
.clk_src = DAC_COSINE_CLK_SRC_DEFAULT,
|
||||
.offset = 0,
|
||||
.phase = DAC_COSINE_PHASE_180,
|
||||
.atten = DAC_COSINE_ATTEN_DB_6,
|
||||
.flags.force_set_freq = true, // set true will allow to overwrite the frequency that set before
|
||||
};
|
||||
ESP_ERROR_CHECK(dac_new_cosine_channel(&cos0_cfg, &chan0_handle));
|
||||
ESP_ERROR_CHECK(dac_new_cosine_channel(&cos1_cfg, &chan1_handle));
|
||||
ESP_ERROR_CHECK(dac_cosine_start(chan0_handle));
|
||||
ESP_ERROR_CHECK(dac_cosine_start(chan1_handle));
|
||||
|
||||
/* Set the ADC2 channels, these channels are connected to the DAC channels internally */
|
||||
adc_oneshot_unit_handle_t adc2_handle;
|
||||
adc_oneshot_unit_init_cfg_t adc_cfg = {
|
||||
.unit_id = ADC_UNIT_2,
|
||||
.ulp_mode = false,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_new_unit(&adc_cfg, &adc2_handle));
|
||||
adc_oneshot_chan_cfg_t chan_cfg = {
|
||||
.atten = EXAMPLE_ADC_ATTEN,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc2_handle, EXAMPLE_DAC_CHAN0_ADC_CHAN, &chan_cfg));
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc2_handle, EXAMPLE_DAC_CHAN1_ADC_CHAN, &chan_cfg));
|
||||
xTaskCreate(adc_monitor_task, "adc_monitor_task", 4096, adc2_handle, 5, NULL);
|
||||
}
|
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/dac_driver.h"
|
||||
#include "driver/adc.h"
|
||||
#include "esp_check.h"
|
||||
|
||||
#define EXAMPLE_DAC_USE_SEPARATE_CHANNEL 1 // Whether to register two DAC channels in separate control group
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#define EXAMPLE_DAC_CHAN1_ADC_CHAN ADC2_CHANNEL_8 // GPIO25, same as DAC channel 0
|
||||
#define EXAMPLE_DAC_CHAN2_ADC_CHAN ADC2_CHANNEL_9 // GPIO26, same as DAC channel 1
|
||||
#define EXAMPLE_ADC_WIDTH ADC_WIDTH_BIT_12
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
#define EXAMPLE_DAC_CHAN1_ADC_CHAN ADC2_CHANNEL_6 // GPIO17, same as DAC channel 0
|
||||
#define EXAMPLE_DAC_CHAN2_ADC_CHAN ADC2_CHANNEL_7 // GPIO18, same as DAC channel 1
|
||||
#define EXAMPLE_ADC_WIDTH ADC_WIDTH_BIT_13
|
||||
#endif
|
||||
#define EXAMPLE_ADC_ATTEN ADC_ATTEN_DB_11
|
||||
|
||||
static void adc_monitor_task(void *args)
|
||||
{
|
||||
/* Set the ADC channels, these channels are connected to the DAC channels internally */
|
||||
ESP_ERROR_CHECK(adc2_config_channel_atten(EXAMPLE_DAC_CHAN1_ADC_CHAN, EXAMPLE_ADC_ATTEN));
|
||||
ESP_ERROR_CHECK(adc2_config_channel_atten(EXAMPLE_DAC_CHAN2_ADC_CHAN, EXAMPLE_ADC_ATTEN));
|
||||
|
||||
int chan1_val = 0;
|
||||
int chan2_val = 0;
|
||||
while (1) {
|
||||
/* Read the DAC output voltage */
|
||||
ESP_ERROR_CHECK(adc2_get_raw(EXAMPLE_DAC_CHAN1_ADC_CHAN, EXAMPLE_ADC_WIDTH, &chan1_val));
|
||||
ESP_ERROR_CHECK(adc2_get_raw(EXAMPLE_DAC_CHAN2_ADC_CHAN, EXAMPLE_ADC_WIDTH, &chan2_val));
|
||||
printf("DAC channel 0 vaule: %4d\tDAC channel 1 vaule: %4d\n", chan1_val, chan2_val);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
|
||||
static void dac_init_channel(dac_channel_mask_t mask, dac_cosine_config_t *cos_cfg, dac_channels_handle_t *dac_handle)
|
||||
{
|
||||
dac_channels_handle_t handle = NULL;
|
||||
dac_channels_config_t cfg = {
|
||||
.chan_sel = mask,
|
||||
};
|
||||
/* Allocate the channel group */
|
||||
ESP_ERROR_CHECK(dac_new_channels(&cfg, &handle));
|
||||
/* Enable the channels in the group */
|
||||
ESP_ERROR_CHECK(dac_channels_enable(handle));
|
||||
/* Initialize DAC cosine wave genarator */
|
||||
ESP_ERROR_CHECK(dac_channels_init_cosine_mode(handle, cos_cfg));
|
||||
/* Start to output the cosine wave */
|
||||
ESP_ERROR_CHECK(dac_channels_start_cosine_output(handle));
|
||||
|
||||
*dac_handle = handle;
|
||||
}
|
||||
|
||||
static void __attribute__((unused)) dac_deinit_channel(dac_channels_handle_t dac_handle)
|
||||
{
|
||||
/* Stop outputting the cosine wave */
|
||||
ESP_ERROR_CHECK(dac_channels_stop_cosine_output(dac_handle));
|
||||
/* Deinitialize the cosine wave genarator */
|
||||
ESP_ERROR_CHECK(dac_channels_deinit_cosine_mode(dac_handle));
|
||||
/* Disable the DAC channels */
|
||||
ESP_ERROR_CHECK(dac_channels_disable(dac_handle));
|
||||
/* Delete the channel group */
|
||||
ESP_ERROR_CHECK(dac_del_channels(dac_handle));
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
#if EXAMPLE_DAC_USE_SEPARATE_CHANNEL
|
||||
dac_channels_handle_t chan1_handle;
|
||||
dac_channels_handle_t chan2_handle;
|
||||
/* Adopt different cosine wave configuration to the two DAC channel
|
||||
* But take care that the two channels can only be configured to one frequence
|
||||
* If they are differt, the latter frequency will cover the former one
|
||||
* The example here will produce cosine wave at 8 KHz on both channels */
|
||||
dac_cosine_config_t cos1_cfg = {
|
||||
.freq_hz = 1000, // It will be covered by 8000 in the latter configuration
|
||||
.clk_src = DAC_COSINE_CLK_SRC_DEFAULT,
|
||||
.offset = 0,
|
||||
.phase = DAC_COSINE_PHASE_0,
|
||||
.scale = DAC_COSINE_NO_ATTEN,
|
||||
};
|
||||
dac_cosine_config_t cos2_cfg = {
|
||||
.freq_hz = 8000,
|
||||
.clk_src = DAC_COSINE_CLK_SRC_DEFAULT,
|
||||
.offset = 0,
|
||||
.phase = DAC_COSINE_PHASE_180,
|
||||
.scale = DAC_COSINE_ATTEN_2,
|
||||
};
|
||||
dac_init_channel(DAC_CHANNEL_MASK_CH0, &cos1_cfg, &chan1_handle);
|
||||
dac_init_channel(DAC_CHANNEL_MASK_CH1, &cos2_cfg, &chan2_handle);
|
||||
xTaskCreate(adc_monitor_task, "adc_monitor_task", 4096, NULL, 5, NULL);
|
||||
#else
|
||||
dac_channels_handle_t chan12_handle;
|
||||
dac_cosine_config_t cos_cfg = {
|
||||
.freq_hz = 1000, // 130 Hz ~ 200 KHz
|
||||
.clk_src = DAC_COSINE_CLK_SRC_DEFAULT,
|
||||
.offset = 0,
|
||||
.phase = DAC_COSINE_PHASE_0,
|
||||
.scale = DAC_COSINE_NO_ATTEN,
|
||||
};
|
||||
dac_init_channel(DAC_CHANNEL_MASK_BOTH, &cos_cfg, &chan12_handle);
|
||||
xTaskCreate(adc_monitor_task, "adc_monitor_task", 4096, NULL, 5, NULL);
|
||||
#endif
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user