stm32 dac+dma正弦波输出必须用tim6/tim7的update事件trgo触发,配dma循环模式与word对齐,正弦表需抬升量化至0–4095范围,否则输出失真或静音。

STM32 的 DAC + DMA 正弦波输出,C++ 用法和裸机逻辑完全一致——它不依赖 C++ 运行时,关键在寄存器配置与 DMA 地址对齐,不是语法糖能解决的。
DAC 触发源必须选定时器 TRGO,不能用软件触发
软件触发(DAC_TRIGGER_SOFTWARE)只能打一次,无法维持连续波形;而正弦波需要稳定节拍。必须用 TIM6 或 TIM7(F103 上只有这两个有专用 DAC 触发线)的 TRGO 事件,且触发事件类型得是 UPDATE(不是 CC1/CC2)。否则 DAC 收不到请求,DMA 不会启动搬运。
- 常见错误:CubeMX 里误选
CC1触发 → 波形跳变或无输出 - F103 的
DAC_CH1只认TIM6,DAC_CH2只认TIM7,混用直接失效 - 手动配置时,要置位
DAC_CR寄存器的TEN1/TEN2和对应TSEL1/TSEL2位,不能只开 DMA 使能
DMA 配置必须用 Word 模式 + 循环模式
DAC_DHR12R1 等数据保持寄存器是 32 位宽,虽然只用低 12 位,但总线访问必须按字(Word)对齐。若设成 HAL_DMA_PDATAALIGN_HALFWORD,写入会错位,输出波形严重失真甚至静音。
- 错误现象:示波器看到阶梯状毛刺、幅度骤降、周期性中断
- 正确设置:
PeriphDataAlignment = DMA_PDATAALIGN_WORD,MemDataAlignment = DMA_MDATAALIGN_WORD - 务必启用
Mode = DMA_CIRCULAR,否则 DMA 传完一遍就停,波形只响一声 - 数组地址需 4 字节对齐(
__attribute__((aligned(4)))或alignas(4)),否则某些编译器生成非法地址
正弦表生成要抬升+量化,不能直接用 sin() 原值
DAC 输出电压范围是 0~3.3 V,对应数字值 0~4095(12 位)。而 sin() 返回 [-1, 1],直接截断会烧 DAC 或输出负压(实际为 0 V),必须做线性映射。
- 公式:
sineWave[i] = (uint16_t)(2048 + 2047 * sinf(radian))—— 抬升到中点,保留全动态范围 - 避免浮点运算拖慢初始化:用 MATLAB / Python 预生成数组,转成
const uint16_t sine_table[256]存 ROM - 点数建议 128~512:太少则波形锯齿明显;太多则 DMA 周期长,高频受限(如 512 点 @ 44.1 kHz 需 TIM 更新率 22.6 MHz,F103 实际难达)
- 别用
double计算再强转uint16_t:Keil ARMCC 对 double 支持弱,易链接失败
C++ 封装要注意对象生命周期和中断安全
如果用类封装 DAC 控制(比如 DacWaveGenerator),构造函数里完成 HAL 初始化没问题,但对象不能放在栈上或临时作用域——DMA 会持续读取其内部数组地址。
- 典型 crash:局部对象析构后,DMA 还在往已释放内存读数据 → 野指针访问,系统锁死
- 解决方案:静态对象、全局变量,或用
new分配并确保生命周期覆盖整个运行期 - 禁止在
DAC类成员函数里调用HAL_DAC_Start_DMA()后立刻返回:该函数启动 DMA 传输,但底层未就绪就退出,可能漏触发 - 若需动态切波形,改用双缓冲 +
HAL_DAC_SetValue()软件更新,别反复重配 DMA
真正卡住人的从来不是 C++ 语法,而是 DMA 地址错一位、定时器 TRGO 选错事件、或者正弦表没抬升导致 DAC 输出钳位在 0 V——这些地方一错,示波器上就只有条直线,连调试信息都看不到。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











