STM32 DAC实战:从原理到波形生成与播放
👁 11 阅读 · 2026-08-14 · STM32 DAC
STM32内置DAC模块可输出真实模拟电压,常用于波形生成、音频播放和信号激励。本文从DAC工作原理出发,详细讲解基于HAL库的配置步骤,并给出通过定时器触发DMA循环输出正弦波的完整代码,涵盖原理、配置、代码与注意事项,助力开发者快速掌握STM32 DAC应用。
## 一、STM32 DAC概述
STM32的DAC(数模转换器)可将数字量转换为模拟电压信号,相比PWM+滤波方案,DAC输出波形更平滑、无需外部滤波电路。STM32绝大多数系列(如F1、F4、L4、H7)都集成至少1个DAC,通常包含两个独立通道(如PA4、PA5),支持三角波生成、噪声生成以及DMA请求,非常适合波形合成和音频播放。
## 二、DAC工作原理与关键参数
DAC模块将12位数字值映射为模拟电压,输出关系为:
- **Vout = VREF+ × (DAC_DOR / 4096)**
其中DAC_DOR为输出数据寄存器值,VREF+为参考电压(通常接3.3V)。若开启输出缓冲,则驱动能力增强,但最小输出电压可能受限;关闭缓冲则输出阻抗较高,适合外接高阻负载或运放。
核心寄存器:
- **DAC_CR**:控制使能、缓冲、DMA请求等;
- **DAC_DHRx**:数据保持寄存器(8/12位,左对齐/右对齐);
- **DAC_DORx**:输出数据寄存器,转换结果由此直接驱动输出。
实际开发多使用HAL库,无需直接操作寄存器,但理解原理有助于定位问题。
## 三、硬件连接与CubeMX配置
以STM32F407为例,使用DAC通道1输出至PA4引脚。硬件连接:PA4接到示波器探头或ADC采样端,若驱动扬声器需通过功放/运放。
### CubeMX配置步骤
1. 设置PA4为DAC_OUT1模式;
2. 使能DAC通道1,开启输出缓冲;
3. 添加定时器(如TIM6)作为DMA触发源,配置更新事件产生TRGO信号;
4. 配置DAC DMA通道,选择循环模式,数据宽度为半字(16位),内存地址递增;
5. 生成项目代码。
若希望精确控制采样率,定时器触发周期决定每个波形点的时间间隔。比如正弦波表256点,采样率48kHz,则定时器更新频率应为48kHz,即每个点间隔约20.8μs。
## 四、波形生成:查表法输出正弦波
正弦波是基础波形,通过查表法生成查表函数:
```c
#define SINE_POINTS 256
uint16_t sine_wave[SINE_POINTS];
void Generate_Sine_Wave(void)
{
for (int i = 0; i < SINE_POINTS; i++)
{
// 以3.3V为参考电压,满量程12位
float v = 3.3f * (1.0f + sinf(2 * 3.14159265f * i / SINE_POINTS)) / 2.0f;
sine_wave[i] = (uint16_t)(v * 4095.0f / 3.3f);
}
}
```
此表产生0~3.3V偏置为1.65V的正弦波,峰值1.65V。若需不同幅值,修改v的计算即可。
## 五、完整代码示例:定时器触发DMA循环输出
### 1. DAC与DMA初始化(HAL库)
```c
DAC_HandleTypeDef hdac;
DMA_HandleTypeDef hdma_dac;
TIM_HandleTypeDef htim6;
static void MX_DAC_Init(void)
{
hdac.Instance = DAC;
HAL_DAC_Init(&hdac);
DAC_ChannelConfTypeDef sConfig = {0};
sConfig.DAC_SampleAndHold = DAC_SAMPLEANDHOLD_DISABLE;
sConfig.DAC_Trigger = DAC_TRIGGER_T6_TRGO;
sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1);
}
static void MX_DMA_Init(void)
{
__HAL_RCC_DMA1_CLK_ENABLE();
hdma_dac.Instance = DMA1_Stream5;
hdma_dac.Init.Channel = DMA_CHANNEL_7;
hdma_dac.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_dac.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_dac.Init.MemInc = DMA_MINC_ENABLE;
hdma_dac.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_dac.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_dac.Init.Mode = DMA_CIRCULAR;
hdma_dac.Init.Priority = DMA_PRIORITY_HIGH;
HAL_DMA_Init(&hdma_dac);
__HAL_LINKDMA(&hdac, DMA_Handle1, hdma_dac);
}
static void MX_TIM6_Init(void)
{
htim6.Instance = TIM6;
htim6.Init.Prescaler = 0;
htim6.Init.Period = 420; // 84MHz / (0+1) / (420+1) ≈ 200kHz? 调整为实际采样率
HAL_TIM_Base_Init(&htim6);
TIM_MasterConfigTypeDef sMasterConfig = {0};
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig);
}
```
### 2. 主函数启动DAC输出
```c
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_TIM6_Init();
MX_DAC_Init();
Generate_Sine_Wave();
// 启动定时器更新中断,并开启DAC通道触发
HAL_TIM_Base_Start(&htim6);
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)sine_wave, SINE_POINTS, DAC_ALIGN_12B_R);
while (1)
{
// 可在此修改波形或采样率
}
}
```
上述代码配置TIM6周期为420,假设DAC时钟84MHz,则更新频率为200kHz,实际采样率200kHz。若需48kHz,设置Period = (84MHz / 48kHz) - 1 ≈ 1750。注意DMA循环模式无需中断即可持续输出。
## 六、播放音频的扩展思路
若要播放WAV音频,可将音频数据转为12位无符号格式,用DMA循环输出,配合定时器触发实现固定采样率。建议使用缓冲区,双缓冲(Half Transfer/Transfer Complete中断)动态填充数据,避免音频中断。
## 七、注意事项
- **参考电压**:VREF+必须稳定,否则输出波形幅度漂移,建议测量实际电压并修正计算公式。
- **输出缓冲**:开启缓冲时,DAC输出接近轨到轨但最小电压可能为0.2V左右;关闭缓冲可趋近0V,但负载能力弱,需外接高阻或运放。
- **DMA与定时器配置**:确保DMA通道与DAC请求匹配(不同芯片映射不同,如F4使用DMA1 Stream5 Channel7),且定时器TRGO选择正确。
- **波形表长度与采样率**:表长度和采样率决定输出频率,公式为:**f_out = fs / N**(如fs=200kHz, N=256 → 781Hz)。调整两者可改变频率。
- **数据对齐格式**:DMA传输数据为16位,对应DAC的12位右对齐(DAC_ALIGN_12B_R),务必保证内存和DMA宽度一致。
- **功耗与发热**:高采样率持续输出会增加CPU空闲但DMA占用总线,注意电源稳定性。
## 八、总结
本文从STM32 DAC原理出发,讲解了通过定时器触发DMA查表输出正弦波的方法。该方案可扩展至任意周期波形和音频播放,核心在于理解DMA+定时器+DAC的协同工作。建议读者用示波器观察实际输出,调整缓冲开关和波形表,深入体会DAC的特性,为后续物联网语音播报、信号激励等应用打下基础。