ESP32基于espidf DAC输出正/余弦/三角波形
- 🔖ESP-IDF 版本:
v5.4
- ✨带DAC功能外设的ESP32型号:esp32、esp32s2.(ESP32S3 不带DAC)
- ESP32 有 2 个 8-bit DAC 通道,将 2 路数字信号分别转化为 2 个模拟电压信号输出,两个通道可以独立地工作。
- 通道1(GPIO25)和DAC通道2(GPIO26)用于数模转换。
- DAC 输出范围(0-255).
- ESP32 功能框图
数/模转换器 (DAC)介绍
ESP32 有 2 个 8-bit DAC 通道,将 2 路数字信号分别转化为 2 个模拟电压信号输出,两个通道可以独立地工作。
DAC 电路由内置电阻串和 1 个缓冲器组成。这 2 个 DAC 可以作为参考电压使用。
- 可配置 GPIO 25 和 GPIO 26 管脚用于数模转换。
DAC 的主要特性包括:
• 2个8位DAC通道
• 支持双通道的独立/同时转换
• 可从VDD3P3_RTC引脚获得电压参考
• 含有余弦波型发生器
• 支持DMA功能
• 可通过软件或SARADCFSM开始转换。更多信息,请见SARADC章节。
• 可由ULP协处理器通过控制寄存器来实现完全控制。请见ULP协处理器章节。
- 双通道DAC的2个8位通道可实现独立配置,每个通道的输出模拟电压计算方式见下:
- DAC 的功能概况
• PDACn_DAC 拥有多个来源:余弦波形生成器、寄存器RTCIO_PAD_DACn_REG,及DMA。
- 橙色:GPIO25,紫色:GPIO26,波形输出:
- 余弦和三角波输出
📘所需组件包含
所需要使用到的组件esp_driver_dac
、esp_adc esp_timer
。
- 🌿
CMakeLists.txt
:
idf_component_register(SRCS "dac_cosine_example_main.c"
INCLUDE_DIRS "."
PRIV_REQUIRES esp_driver_dac esp_adc esp_timer)
📝DAC输出正/余弦波形驱动代码:
正弦波形,是通过余弦波形相位偏移(90 度)得来。DAC 通道 1:GPIO25(余弦波输出),DAC 通道 2:GPIO26(正弦波输出)
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/dac_cosine.h"
#include "driver/dac_oneshot.h"
#include "esp_timer.h"
#define DAC_COSINE_FREQ 1000 // 余弦波频率 (Hz)
#define PHASE_OFFSET 90 // 正弦波与余弦波的相位差 (90度)
// 正弦波查表法(预计算正弦波值)
#define SINE_TABLE_SIZE 100
static uint8_t sine_table[SINE_TABLE_SIZE];
void generate_sine_table() {
for (int i = 0; i < SINE_TABLE_SIZE; i++) {
float angle = 2 * M_PI * i / SINE_TABLE_SIZE;
sine_table[i] = (uint8_t)((sin(angle) + 1) * 127.5); // 转换为DAC输出值 (0-255)
}
}
void app_main(void)
{
// 预生成正弦波表
generate_sine_table();
// 配置余弦波生成器 (DAC通道1, GPIO25)
dac_cosine_handle_t cos_handle = NULL;
dac_cosine_config_t cos_cfg = {
.chan_id = DAC_CHAN_0,
.freq_hz = DAC_COSINE_FREQ,
.clk_src = DAC_COSINE_CLK_SRC_DEFAULT,
.offset = 0,
.phase = DAC_COSINE_PHASE_0,
.atten = DAC_COSINE_ATTEN_DEFAULT,
.flags.force_set_freq = true,
};
ESP_ERROR_CHECK(dac_cosine_new_channel(&cos_cfg, &cos_handle));
ESP_ERROR_CHECK(dac_cosine_start(cos_handle));
// 配置DAC单次输出 (DAC通道2, GPIO26)
dac_oneshot_handle_t sin_handle = NULL;
dac_oneshot_config_t sin_cfg = {
.chan_id = DAC_CHAN_1,
};
ESP_ERROR_CHECK(dac_oneshot_new_channel(&sin_cfg, &sin_handle));
// 计算每个波形点的时间间隔
uint32_t interval_us = 1000000 / (DAC_COSINE_FREQ * SINE_TABLE_SIZE);
uint32_t last_time = esp_timer_get_time();
int table_index = 0;
while (1) {
// 输出正弦波值到DAC通道2
ESP_ERROR_CHECK(dac_oneshot_output_voltage(sin_handle, sine_table[table_index]));
// 更新查表索引
table_index = (table_index + 1) % SINE_TABLE_SIZE;
// 等待下一个波形点的时间
while (esp_timer_get_time() - last_time < interval_us) {
// 空循环等待
}
last_time = esp_timer_get_time();
}
// 停止余弦波生成器(不会执行到这里)
ESP_ERROR_CHECK(dac_cosine_stop(cos_handle));
ESP_ERROR_CHECK(dac_cosine_del_channel(cos_handle));
ESP_ERROR_CHECK(dac_oneshot_del_channel(sin_handle));
}
📝DAC输出正/余弦波形驱动代码和说明:
DAC 通道 1(GPIO25):使用 dac_cosine.h 硬件生成余弦波。
DAC 通道 2(GPIO26):使用 dac_oneshot.h 逐点输出三角波。
- 余弦波生成:
使用 dac_cosine.h 配置 DAC 通道 1(GPIO25)生成余弦波。
通过 dac_cosine_config_t 设置频率、相位、衰减等参数。
调用 dac_cosine_start 启动余弦波生成。
- 三角波生成:
使用 dac_oneshot.h 配置 DAC 通道 2(GPIO26)生成三角波。
通过查表法预计算三角波的值,并存储在 triangle_table 数组中。
在循环中逐点输出三角波值,并通过 esp_timer_get_time 控制输出速率。
- 查表法:
三角波的上升沿和下降沿分别计算,并存储在 triangle_table 中。
通过查表法避免实时计算,提高代码效率。
时间控制:
- 使用 esp_timer_get_time 获取当前时间,并通过空循环精确控制每个波形点的输出时间。
/*
* SPDX-FileCopyrightText: 2022-2023 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 "driver/dac_oneshot.h"
#include "esp_check.h"
#include "esp_err.h"
#include "esp_timer.h"
#include <math.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_12
#define DAC_COSINE_FREQ 1000 // 余弦波频率 (Hz)
#define DAC_SINE_FREQ 1000 // 正弦波频率 (Hz)
#define PHASE_OFFSET 90 // 正弦波与余弦波的相位差 (90度)
#define TRIANGLE_FREQ 1000 // 三角波频率 (Hz)
#define TRIANGLE_STEPS 100 // 三角波的步数
// 三角波查表法(预计算三角波值)
static uint8_t triangle_table[TRIANGLE_STEPS];
// 正弦波查表法(预计算正弦波值)
#define SINE_TABLE_SIZE 100
static uint8_t sine_table[SINE_TABLE_SIZE];
void generate_sine_table() {
for (int i = 0; i < SINE_TABLE_SIZE; i++) {
float angle = 2 * M_PI * i / SINE_TABLE_SIZE;
sine_table[i] = (uint8_t)((sin(angle) + 1) * 127.5); // 转换为DAC输出值 (0-255)
}
}
void generate_triangle_table() {
for (int i = 0; i < TRIANGLE_STEPS / 2; i++) {
// 上升沿
triangle_table[i] = (uint8_t)(255 * i / (TRIANGLE_STEPS / 2));
}
for (int i = TRIANGLE_STEPS / 2; i < TRIANGLE_STEPS; i++) {
// 下降沿
triangle_table[i] = (uint8_t)(255 * (TRIANGLE_STEPS - i) / (TRIANGLE_STEPS / 2));
}
}
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 ch0-1: %4d,%4d\n", chan0_val, chan1_val);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void app_main(void)
{
// 预生成正弦波表
// generate_sine_table();
// 预生成三角波表
generate_triangle_table();
// 配置余弦波生成器
dac_cosine_handle_t cos_handle = NULL;
dac_cosine_config_t cos_cfg = {
.chan_id = DAC_CHAN_0, // DAC通道1 (GPIO25)
.freq_hz = DAC_COSINE_FREQ,
.clk_src = DAC_COSINE_CLK_SRC_DEFAULT,
.offset = 0, // 直流偏移量
.phase = DAC_COSINE_PHASE_0, // 初始相位
.atten = DAC_COSINE_ATTEN_DEFAULT, // 衰减
.flags.force_set_freq = true, // 强制设置频率
};
ESP_ERROR_CHECK(dac_cosine_new_channel(&cos_cfg, &cos_handle));
// 启动余弦波生成器
ESP_ERROR_CHECK(dac_cosine_start(cos_handle));
// 配置DAC单次输出(用于正弦波)
// dac_oneshot_handle_t sin_handle = NULL;
// dac_oneshot_config_t sin_cfg = {
// .chan_id = DAC_CHAN_1, // DAC通道2 (GPIO26)
// };
// ESP_ERROR_CHECK(dac_oneshot_new_channel(&sin_cfg, &sin_handle));
// 计算正弦波和余弦波的相位差
// float phase_step = 360.0 / (DAC_COSINE_FREQ * 1000.0 / DAC_SINE_FREQ);
// float phase = 0.0;
// 计算每个波形点的时间间隔
// 配置DAC单次输出 (DAC通道2, GPIO26)
dac_oneshot_handle_t tri_handle = NULL;
dac_oneshot_config_t tri_cfg = {
.chan_id = DAC_CHAN_1,
};
ESP_ERROR_CHECK(dac_oneshot_new_channel(&tri_cfg, &tri_handle));
// 计算每个波形点的时间间隔
uint32_t interval_us = 1000000 / (TRIANGLE_FREQ * TRIANGLE_STEPS);
// 计算每个波形点的时间间隔
// uint32_t interval_us = 1000000 / (DAC_COSINE_FREQ * SINE_TABLE_SIZE);
uint32_t last_time = esp_timer_get_time();
int table_index = 0;
while (1) {
// 计算正弦波的值(通过相位偏移模拟)
// float sine_value = cos((phase + PHASE_OFFSET) * M_PI / 180.0); // 余弦函数模拟正弦波
// uint8_t dac_sine = (uint8_t)((sine_value + 1) * 127.5); // 转换为DAC输出值 (0-255)
// 输出正弦波值到DAC通道2
// ESP_ERROR_CHECK(dac_oneshot_output_voltage(sin_handle, sine_table[table_index]));
// 输出正弦波到DAC通道2
// ESP_ERROR_CHECK(dac_oneshot_output_voltage(sin_handle, dac_sine));
// 输出三角波值到DAC通道2
ESP_ERROR_CHECK(dac_oneshot_output_voltage(tri_handle, triangle_table[table_index]));
// 更新查表索引
table_index = (table_index + 1) % TRIANGLE_STEPS;
// 等待下一个波形点的时间
while (esp_timer_get_time() - last_time < interval_us) {
// 空循环等待
}
last_time = esp_timer_get_time();
}
// 停止余弦波生成器(不会执行到这里)
// ESP_ERROR_CHECK(dac_cosine_stop(cos_handle));
// ESP_ERROR_CHECK(dac_cosine_del_channel(cos_handle));
// ESP_ERROR_CHECK(dac_oneshot_del_channel(sin_handle));
//ESP_ERROR_CHECK(dac_oneshot_del_channel(tri_handle));
}