mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-08 04:02:27 +00:00
782 lines
32 KiB
C
782 lines
32 KiB
C
/*
|
|
* 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;
|
|
}
|