STM32F103 DAC+DMA输出正弦波需用C语言编写裸机固件,核心是定时器(TIM6/TIM7)触发、DMA1_Channel3循环搬运12位波表至DAC_DHR12R,CPU仅初始化后闲置;关键参数包括:波表用uint16_t静态数组、sineValue = (uint16_t)(2047 + 2047 * sinf(radian))、f_out = f_timer / N,实测稳定上限约200kSPS。

不能直接用 C++ 调用单片机 DAC —— 你写的是裸机固件,不是 Windows/Linux 程序。 STM32 的 DAC+DMA 输出正弦波,本质是 C 语言(或极少数汇编)写的嵌入式固件,编译后烧进 Flash 运行。所谓“C++”在这里只意味着:你可以用 extern "C" 包裹驱动函数,或在 .cpp 文件里写逻辑,但所有外设寄存器操作、中断服务、DMA 配置仍必须遵循 CMSIS 或 HAL 的 C 接口规范,且禁止使用 STL、异常、RTTI 等运行时依赖。
DAC 数据格式必须匹配硬件寄存器宽度
STM32F103 的 DAC 只接受 12 位数据,但有三种对齐方式:DAC_DHR12L(左对齐)、DAC_DHR12R(右对齐)、DAC_DHR8R(8 位)。实际输出电压公式为:DACOutput = VREF * DOR / 4096。如果你用 uint16_t 数组存波表,却配置成 DAC_DHR12R,那高位会被自动截断;若误配 DAC_DHR12L,则数值会整体左移 4 位,导致输出满幅或饱和失真。
常见错误现象:
- 示波器看到波形顶部/底部被削平(数据溢出)
- 幅度只有预期的 1/16(误用左对齐但未左移数据)
- 波形周期正确但幅度随频率升高而下降(DMA 传输速率跟不上定时器触发节奏)
实操建议:
- 统一用
uint16_t波表 +DAC_DHR12R模式,生成时直接填入0x0000–0x0FFF范围值 - 抬升并缩放公式务必为:
sineValue = (uint16_t)(2047 + 2047 * sinf(radian))(对应 0–3.3V 全范围) - 避免在波表生成中用
double或printf,全部用float和查表法预计算
DMA 必须配成循环模式,且触发源只能是 TIM6/TIM7
STM32F103 的 DAC_CH1 只能由 TIM6 或 TIM7 的 TRGO 信号触发 —— 其他定时器(如 TIM2/TIM3)即使能产生更新事件,也无法连到 DAC 触发输入线。DMA 通道也受限:DAC_CH1 固定绑定 DMA1_Channel3(F103 系列),不能随意换。
关键参数差异:
-
DMA_DIR必须为DMA_DIR_PeripheralDST(内存→外设) -
DMA_Mode必须为DMA_Mode_Circular,否则波形只输出一次就停 -
DMA_PeripheralBaseAddr必须是DAC_DHR12R1_ADDRESS(注意是 R1,不是 DOR) -
DMA_MemoryBaseAddr必须指向波表首地址,且该数组不能放在栈上(需static或全局)
容易踩的坑:
- 在
main()函数内定义uint16_t sineTable[256]→ DMA 访问时地址非法,因为栈在函数返回后失效 - 把
DMA_BufferSize设为 256,但波表实际只有 128 点 → DMA 往后越界读取随机内存,输出乱码波形 - 开启 DMA 后没开
DAC_Cmd(DAC_Channel_1, ENABLE)→ 引脚始终为高阻态,测不到电压
定时器触发间隔决定输出频率,但受制于 DMA 带宽
输出频率 f_out = f_timer / N,其中 N 是波表点数。例如:用 100 点波表,想输出 1 kHz 正弦波,则 TIM6 更新事件频率需设为 100 kHz,即 ARR = (72000000 / 100000) - 1 = 719(假设 APB1=72MHz)。
但真实瓶颈常在 DMA 传输延迟:
- 每点传输需占用 AHB 总线周期,若波表点数少、定时器频率高,DMA 可能来不及搬完前一点,就被下一个触发打断 → 出现丢点、跳变或
DMA_FLAG_TC未置位 - 实测发现:F103 在 12 位模式下,稳定输出上限约 200 kHz 波形刷新率(即 200kSPS),再高易失锁
- 若用 8 位音频 WAV 数据(如 8kHz 采样),直接喂给 12 位 DAC 会导致动态范围压缩,应做零扩展:
sample_12bit = (uint16_t)wav_sample
性能提示:
- 优先选 64 或 128 点波表,平衡精度与实时性
- 禁用
DMA_IT_TC中断(除非真需要同步通知),减少 ISR 开销 - PA4 输出引脚后必须加 RC 低通滤波(如 1kΩ + 10nF),否则高频谐波会辐射干扰
最常被忽略的一点:DAC 输出阻抗约 15 kΩ,无法直接驱动扬声器或耳机。哪怕只是蜂鸣器,也得加一级运放缓冲或三极管放大;否则负载一接上,波形立刻塌陷、幅度骤降一半以上。这不是代码问题,是硬件链路缺失 —— 很多人调通代码后拿示波器一看波形完美,一接喇叭就无声,卡在这里两三天。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











