在嵌入式开发中,数字模拟转换器(DAC)是一个极其实用的外设模块。它能将数字信号转换为模拟电压输出,广泛应用于音频处理、电机控制、传感器校准等场景。今天我们就以GD32F103RCT6这款性价比极高的国产MCU为例,手把手教你实现0-3.3V可调电压输出。
GD32F103RCT6开发板上的DAC模块具有以下特性:
关键引脚分配:
| 引脚 | 功能 | 对应通道 |
|---|---|---|
| PA4 | DAC_OUT0 | DAC0 |
| PA5 | DAC_OUT1 | DAC1 |
注意:使用DAC功能时,必须将对应GPIO配置为模拟输入模式(AIN),即使它实际上是输出模拟电压。
在开始编码前,需要确保开发环境就绪:
工具链安装:
库文件准备:
bash复制# 从官网下载标准外设库
wget https://www.gd32mcu.com/download/down/document_id/185/path_type/1
新建工程:
完整的DAC初始化包含以下几个关键步骤:
c复制// 使能相关时钟
rcu_periph_clock_enable(RCU_GPIOA); // GPIOA时钟
rcu_periph_clock_enable(RCU_DAC); // DAC时钟
rcu_periph_clock_enable(RCU_AF); // 复用功能时钟
// 配置PA4/PA5为模拟输入
gpio_init(GPIOA, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, GPIO_PIN_4 | GPIO_PIN_5);
下面这个函数封装了DAC的核心配置逻辑:
c复制void dac_config(uint32_t dac_periph) {
// 复位DAC外设
dac_deinit();
// 禁用触发功能(使用软件直接控制)
dac_trigger_disable(dac_periph);
// 禁用波形生成功能
dac_wave_mode_config(dac_periph, DAC_WAVE_DISABLE);
// 使能输出缓冲(提高驱动能力)
dac_output_buffer_enable(dac_periph);
// 最后使能DAC模块
dac_enable(dac_periph);
}
提示:输出缓冲虽然能增强驱动能力,但会引入约0.5%的非线性误差。对精度要求极高的场合可以考虑禁用。
DAC输出的核心是理解12位数字量与实际电压的换算关系:
转换公式:
code复制实际电压 = (dat × Vref) / 4096
其中:
dat:12位数字量(0-4095)Vref:参考电压(通常接3.3V)c复制void set_dac_voltage(uint32_t dac_periph, float voltage) {
// 确保电压在有效范围内
if(voltage > 3.3f) voltage = 3.3f;
if(voltage < 0.0f) voltage = 0.0f;
// 计算12位数字量
uint16_t dat = (uint16_t)(voltage * 4096 / 3.3f);
// 设置DAC输出(右对齐12位模式)
dac_data_set(dac_periph, DAC_ALIGN_12B_R, dat);
}
c复制int main(void) {
// 系统时钟配置(108MHz)
systick_config();
// DAC初始化
dac_config(DAC0);
dac_config(DAC1);
// 输出不同电压测试
while(1) {
set_dac_voltage(DAC0, 1.0f); // 输出1.0V
delay_1ms(500);
set_dac_voltage(DAC0, 2.5f); // 输出2.5V
delay_1ms(500);
set_dac_voltage(DAC1, 0.5f); // 另一个通道输出0.5V
delay_1ms(500);
}
}
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 无电压输出 | GPIO模式配置错误 | 检查是否设置为AIN模式 |
| 输出电压不稳定 | 电源噪声大 | 增加电源滤波电容 |
| 最大电压不足3.3V | 输出缓冲未使能 | 调用dac_output_buffer_enable |
| 输出有阶梯状波动 | 未禁用波形生成功能 | 确认dac_wave_mode_config参数 |
参考电压处理:
软件校准:
c复制// 两点校准法示例
void dac_calibrate() {
set_dac_voltage(DAC0, 1.0f);
float actual_1v = measure_voltage(); // 实际测量输出电压
set_dac_voltage(DAC0, 3.0f);
float actual_3v = measure_voltage();
// 根据测量结果计算斜率与截距
// 后续输出时应用这些校准参数
}
输出滤波:
f_c = 1/(2πRC)利用DAC和定时器可以轻松实现可编程电压源:
c复制// 使用TIMER2触发DAC更新
void dac_timer_config(void) {
timer_parameter_struct timer_initpara;
rcu_periph_clock_enable(RCU_TIMER2);
timer_initpara.prescaler = 107; // 108MHz/108=1MHz
timer_initpara.period = 999; // 1kHz更新率
timer_init(TIMER2, &timer_initpara);
// 配置DAC为定时器触发
dac_trigger_enable(DAC0);
dac_trigger_source_config(DAC0, DAC_TRIGGER_T2_TRGO);
timer_enable(TIMER2);
}
// 生成三角波
void generate_triangle_wave() {
static uint16_t count = 0;
static int8_t dir = 1;
count += dir;
if(count >= 4095) dir = -1;
if(count <= 0) dir = 1;
dac_data_set(DAC0, DAC_ALIGN_12B_R, count);
}
在实际项目中,我发现GD32的DAC模块虽然简单易用,但要获得最佳性能还是需要注意几个细节:首先是上电后要等待至少1ms再初始化DAC,其次是输出端不要直接驱动大容性负载。通过合理配置,这个12位DAC完全能够满足大多数工业控制场景的需求。