news 2026/3/23 4:09:13

STM32波形发生器设计:正弦波生成完整示例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32波形发生器设计:正弦波生成完整示例

用一片STM32搞定正弦波信号源:从原理到实战的完整设计

你有没有遇到过这样的场景?做模电实验时需要一个低频正弦信号,手头却只有笨重昂贵的函数发生器;或者开发便携设备时想集成一个可调信号输出功能,但外置DAC方案又太占空间、功耗太高?

其实,这些问题都可以用一片STM32来解决。今天我们就来拆解一个经典但极具实用价值的设计——基于STM32内置DAC的正弦波发生器。它不依赖任何专用芯片,仅靠MCU本身的外设组合,就能输出稳定、频率可调的模拟信号。

这不仅是一个“能跑就行”的Demo,而是一套真正可用于教学、原型验证甚至小型仪器开发的工程级实现方案。更重要的是,整个过程将带你深入理解DAC、定时器与DMA之间如何协同工作,掌握嵌入式系统中“软硬结合”的核心思维。


为什么选STM32来做波形发生器?

在开始动手前,先回答一个问题:我们为什么不用现成的信号源模块,非要自己搞一套?

关键在于三个字:可控性

市面上大多数函数信号发生器虽然功能齐全,但封闭性强,无法灵活定制输出特性。而使用STM32这类高性能MCU构建信号源,优势非常明显:

  • 高度集成:无需额外DAC芯片,节省PCB面积和BOM成本;
  • 完全可编程:频率、幅度、波形类型全由软件控制;
  • 低延迟响应:硬件触发链确保时间精度,避免CPU调度抖动;
  • 易于扩展:支持多通道同步、任意波形生成(AWG)、远程调控等高级功能。

特别是像STM32F4、STM32G4或H7系列,本身就集成了12位DAC、高精度定时器和多通道DMA控制器,天生就是为实时信号处理准备的平台。

比如我们常用的STM32F407VG,就具备:
- 双通道12位DAC
- 多个通用/高级定时器(TIM6/TIM7可作DAC触发源)
- 支持循环模式的DMA传输
- 最高主频168MHz,轻松应对音频范围内的信号合成

这些资源组合起来,完全可以替代传统中低端信号源的功能。


DAC不是简单数模转换,而是模拟输出的核心引擎

很多人对DAC的理解还停留在“把数字变成电压”这个层面,但在实际工程中,它的用法远比想象复杂。

STM32 DAC的关键能力你知道几个?

首先明确一点:STM32的DAC是电压输出型,典型参考电压为3.3V,12位分辨率意味着它可以产生 $ 2^{12} = 4096 $ 个离散电平,最小步进约0.8mV($3.3V / 4095$)。这对于大多数非精密测量应用已经足够。

输出公式如下:

$$
V_{out} = \frac{DIN}{4095} \times V_{REF}
$$

其中DIN是你写入DAC寄存器的值,范围0~4095。

但真正决定性能的,不是分辨率,而是更新机制

软件触发 vs 硬件触发:差别有多大?

如果你只是通过CPU不断写寄存器来更新DAC输出,那结果会怎样?

答案是:波形严重失真,频率不可控

因为每次HAL_DAC_SetValue()都要经历函数调用、中断上下文切换、总线访问等一系列开销,导致采样间隔极不稳定。这种“软件延时+轮询”的方式根本达不到奈奎斯特采样定理的要求。

正确的做法是让硬件自动驱动DAC更新,也就是使用定时器触发模式

具体路径是:

定时器溢出 → 发出TRGO信号 → 触发DAC转换启动

这样一来,每个采样点的时间间隔完全由定时器决定,误差仅取决于晶振稳定性,通常可以做到微秒级精度。


定时器不只是计时工具,更是波形节奏的指挥官

如果说DAC负责“发声”,那么定时器就是掌控节拍的鼓手。

在本设计中,我们选用TIM6作为主控定时器,原因很简单:它是专为DAC服务设计的——支持直接连接至DAC的外部触发输入端。

如何计算采样率?

假设系统时钟来自APB1总线,频率为84MHz。我们希望得到100kHz的采样率,该如何配置?

关键参数有两个:

  • 预分频器 PSC:用于降低计数频率
  • 自动重载值 ARR:决定计数周期

计算公式为:

$$
f_s = \frac{f_{clk}}{(PSC + 1) \times (ARR + 1)}
$$

举个例子:

// 想要 fs = 100kHz // f_clk = 84MHz // 设 PSC = 83 → 分频后为 1MHz // 则 ARR = 9 → 周期为 1μs → fs = 100kHz

配置代码如下:

htim6.Instance = TIM6; htim6.Init.Prescaler = 83; // 84MHz / 84 = 1MHz htim6.Init.Period = 9; // 1MHz / 10 = 100kHz HAL_TIM_Base_Init(&htim6); // 启用主模式,选择更新事件作为触发输出 TIM6->CR2 |= TIM_CR2_MMS_1; // MMS = 010: Update Event as TRGO

一旦使能,TIM6每10μs就会发出一次TRGO脉冲,精准唤醒DAC进行下一次转换。


DMA才是真正的“幕后英雄”:零CPU干预的数据流管道

到这里你可能会问:谁来给DAC提供数据?难道还要在中断里一个个塞进去?

当然不是。如果靠中断喂数据,不仅占用CPU,还会引入中断延迟,破坏实时性。

正确姿势是启用DMA(直接内存访问),建立一条从内存到DAC的“高速公路”。

工作流程一目了然:

  1. 在RAM中预存一张正弦查找表(Sine LUT)
  2. 配置DMA通道,源地址指向LUT首址,目标地址为DAC的数据保持寄存器(DHR)
  3. 设置为内存到外设、半字宽度、循环模式
  4. 启动后,DMA自动按节拍搬运数据,全程无需CPU插手

每当DAC完成一次转换并准备好接收新数据时,它会向DMA控制器发起请求(DMA Request),后者立即从LUT中取出下一个值送入DAC。

这就形成了一个闭环流水线:

定时器触发 → DAC请求数据 → DMA传输 → 输出新电压 → 等待下次触发

整个过程流畅无缝,CPU利用率接近于零。


正弦波是怎么“造”出来的?从查表到相位累加

现在所有硬件链路都打通了,最后一步就是生成波形本身。

最基础的方法是查表法(Look-Up Table, LUT)

查表法的本质:用离散点逼近连续曲线

我们预先计算好一个周期内的N个正弦采样点,存储在一个数组中。DAC依次读取这些点,就能还原出近似的正弦波。

例如,创建一个128点的LUT:

#define LUT_SIZE 128 uint16_t sine_lut[LUT_SIZE]; void generate_sine_lut(void) { for (int i = 0; i < LUT_SIZE; i++) { float angle = 2 * M_PI * i / LUT_SIZE; sine_lut[i] = (uint16_t)(2047 + 2047 * sinf(angle)); // 映射到 0~4095 } }

这里做了两个关键处理:
- 使用2047作为偏置,使输出围绕1.65V(中点)振荡
- 幅值也为2047,保证不超出0~4095的有效范围

然后启动DMA传输:

HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)sine_lut, LUT_SIZE, DAC_ALIGN_12B_R);

只要定时器持续触发,DAC就会循环播放这张表,输出稳定的正弦波。

输出频率怎么算?

非常简单:

$$
f_{out} = \frac{f_s}{N}
$$

比如采样率 $ f_s = 100kHz $,LUT长度 $ N = 128 $,则输出频率为:

$$
f_{out} = \frac{100000}{128} \approx 781.25\,\text{Hz}
$$


想要更精细调频?试试DDS思想中的相位累加器

查表法简单有效,但有个问题:频率调节粒度太粗

你想调个800Hz?不好意思,要么改采样率,要么换表,都不方便。

怎么办?引入相位累加器(Phase Accumulator),这是DDS(Direct Digital Synthesis)技术的核心思想。

原理很简单:

传统的查表是以整数步长遍历LUT,比如每次+1。而相位累加器使用一个固定小数增量,累计到整数后再取模访问LUT。

这样即使步长小于1,也能缓慢推进,实现亚赫兹级分辨率。

示例代码:

uint32_t phase_accumulator = 0; uint32_t phase_step = (uint32_t)((100.0 / 100000.0) * (1 << 20)); // 100Hz @ 100kHz fs while (1) { uint16_t index = (phase_accumulator >> 20) % LUT_SIZE; HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, sine_lut[index]); phase_accumulator += phase_step; // 等待下一个定时器触发(可通过中断或事件标志同步) }

虽然这段代码仍需CPU参与,但它展示了如何通过数学方法突破查表法的频率限制。

更进一步的做法是结合双缓冲DMA + 定时器中断动态更新指针,实现完全硬件化的高分辨率DDS引擎。


实际输出什么样?别忘了这几点关键优化

你以为代码跑通就万事大吉了?现实远比理想复杂。

1. 输出是“楼梯状”的,必须滤波!

DAC输出本质上是阶梯波(staircase waveform),含有大量高频谐波成分。直接接负载会导致严重失真。

解决办法:加一级RC低通滤波器,截止频率略高于目标信号频率即可。

例如输出1kHz正弦波,可用:
- R = 1kΩ
- C = 100nF
→ 截止频率 $ f_c = \frac{1}{2\pi RC} \approx 1.6kHz $

简单两级RC滤波就能显著改善波形质量。

2. 参考电压要稳,否则一切白搭

很多开发者忽略VREF引脚,直接用VDD供电。但VDD可能随负载波动,导致DAC输出漂移。

建议:
- 使用独立LDO供电
- 或接入精密基准源(如REF3030、TL431)
- 至少在VREF+引脚加0.1μF陶瓷电容去耦

3. 电源噪声不能忽视

DAC对电源噪声极其敏感。务必在DAC_AVDD和DAC_VSS之间放置0.1μF + 10μF的去耦电容组合,并尽量靠近封装引脚。

同时避免高速数字信号线从DAC附近穿过,防止串扰。


完整系统架构一览

最终系统的信号流如下:

[STM32F407] │ ├─ [System Clock] → [TIM6] → TRGO ─┐ │ ↓ ├─ [Sine LUT in SRAM] → [DMA1_Stream5] → [DAC_DHR1] │ ↓ └──────────────────────────────→ [Analog Out] → [RC LPF] → [Load]

用户可通过按键、ADC旋钮或串口指令动态调整:
- 定时器ARR值 → 改变采样率 → 调整输出频率
- 相位步长 → 实现精细调频
- 替换LUT内容 → 切换为三角波、方波、自定义波形

整个系统模块化清晰,移植性强,适用于多种STM32平台。


还能怎么升级?这些方向值得探索

这套基础架构看似简单,实则潜力巨大。以下是几个可行的拓展方向:

  • 双通道同步输出:利用双DAC通道,生成同频同相或差分信号
  • 幅度调节:在LUT生成时乘以增益系数,或配合外部PGA(可编程增益放大器)
  • 任意波形发生器(AWG):通过上位机下载自定义波形数据到Flash
  • 闭环校准:加入ADC反馈,自动补偿非线性与温漂
  • USB虚拟仪器:搭配USB CDC或Waveform类协议,变身PC外设

甚至可以做成一款迷你版的开源信号发生器,配上OLED屏和编码器旋钮,彻底摆脱对大型仪器的依赖。


写在最后:这不是玩具,是真正的工程实践

这个项目表面上是在“生成一个正弦波”,实际上涵盖了嵌入式开发的多个核心技术点:

  • 外设联动机制(Timer → DAC → DMA)
  • 实时性保障策略
  • 数字信号处理基础(采样定理、DDS)
  • 模拟电路设计意识(滤波、去耦、参考源)

它既适合高校电子类课程作为综合实验项目,也足以支撑工程师快速搭建原型系统。

更重要的是,当你亲手调试出第一段平滑的正弦曲线时,那种“软硬协同”的成就感,远超任何理论讲解。

如果你正在寻找一个既能练手又能落地的STM32实战案例,不妨就从这个波形发生器开始。所需材料不过一块开发板、几个电阻电容,回报却是对嵌入式系统本质更深的理解。

如果你在实现过程中遇到DAC噪声大、频率跳变等问题,欢迎留言交流。也可以分享你的改进方案,比如加入了SPI外置高精度DAC,或是实现了触摸屏交互界面。我们一起把这件小事,做到极致。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/17 1:45:48

Bodymovin插件实战:从零开始掌握AE动画到Web的完美转换

Bodymovin插件实战&#xff1a;从零开始掌握AE动画到Web的完美转换 【免费下载链接】bodymovin-extension Bodymovin UI extension panel 项目地址: https://gitcode.com/gh_mirrors/bod/bodymovin-extension 在数字创意领域&#xff0c;将After Effects中精心设计的动画…

作者头像 李华
网站建设 2026/3/20 9:09:10

利用PWM生成WS2812B协议:一文说清高低电平要求

用PWM硬核驱动WS2812B&#xff1a;揭秘高精度时序背后的工程实践从“灯带闪屏”说起——一个嵌入式开发者的真实困境你有没有遇到过这种情况&#xff1a;精心写好的WS2812B彩灯程序&#xff0c;接上几十颗LED时还能跑得欢快&#xff0c;可一旦扩展到几百颗&#xff0c;灯光就开…

作者头像 李华
网站建设 2026/3/21 14:57:28

Ludusavi游戏存档备份工具:从零开始快速上手终极指南

Ludusavi是一款专为PC游戏玩家设计的开源存档备份神器&#xff0c;采用Rust语言开发&#xff0c;支持Windows、Linux、macOS全平台操作。这款工具能够智能识别并备份超过19,000款游戏的存档数据&#xff0c;帮助玩家轻松管理游戏进度&#xff0c;再也不怕存档丢失的烦恼。 【免…

作者头像 李华
网站建设 2026/3/22 6:49:01

Dify平台在航空公司客服系统升级中的替代成本分析

Dify平台在航空公司客服系统升级中的替代成本分析 在当今航空业竞争日益激烈的环境下&#xff0c;旅客对服务响应速度、准确性和个性化体验的期望不断提升。面对每天数以万计的航班咨询、政策变更和突发状况处理&#xff0c;传统客服模式已显疲态——人工坐席培训周期长、响应不…

作者头像 李华
网站建设 2026/3/13 11:33:59

Android下载管理器:如何实现高效的并行分块下载?

Android下载管理器&#xff1a;如何实现高效的并行分块下载&#xff1f; 【免费下载链接】Android-Download-Manager-Pro Android/Java download manager library help you to download files in parallel mechanism in some chunks. 项目地址: https://gitcode.com/gh_mirro…

作者头像 李华
网站建设 2026/3/22 5:46:52

3D高斯渲染革命:5步掌握gsplat的CUDA加速渲染技术

3D高斯渲染革命&#xff1a;5步掌握gsplat的CUDA加速渲染技术 【免费下载链接】gsplat CUDA accelerated rasterization of gaussian splatting 项目地址: https://gitcode.com/GitHub_Trending/gs/gsplat 在计算机图形学飞速发展的今天&#xff0c;3D高斯渲染技术正以惊…

作者头像 李华