本项目基于开发板上的PWM模块和低通滤波(LPF)电路实现信号发生功能,能够生成频率0-100KHz、偏置1.5V、幅度0-1.5V的正弦波、三角波和方波信号。
实现的功能
系统可输出正弦波、方波和三角波,频率调节范围为0至100kHz,步进为1kHz;幅度调节范围为0至1500mV,步进为150mV。所有参数可通过按键交互调整并在屏幕上实时显示。
设计思路
由于开发板未集成数模转换器(DAC),因此选择PWM配合滤波电路来实现模拟电压的输出。受限于低通滤波器的特性,PWM的基础频率不能过低。本设计采用查表法来生成三种波形,并通过DMA循环地将波形数据数组传输给PWM比较寄存器。
频率的调节是通过在查表时“跳跃式”读取数据点来实现的,而幅度的调节则是对整个数据表乘以一个比例系数。
硬件介绍与流程图
硬件部分主要利用了板载的二阶低通滤波电路(附带一个电压跟随器)、几个功能按键以及OLED屏幕。读者可以根据电路参数自行计算该滤波器的截止频率。

整个系统的工作流程如下图所示,从上电初始化开始,通过按键操作循环调整波形参数并刷新显示。

代码
以下是处理按键输入并更新波形参数的核心代码片段(输入捕获逻辑):
HAL_TIM_Base_Start_IT(&htim2);
b_last=HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15);
while(flag_tim2<=20){
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15)!=b_last){
HAL_TIM_Base_Stop_IT(&htim2);
flag_tim2=0;
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15)==1){
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_14);
HAL_TIM_PWM_Stop_DMA(&htim3, TIM_CHANNEL_3);
switch(pat){
case 0:
fv=fv+1;
if(fv==10)fv=0;
break;
case 1:
fre=fre+1;
if(fre==101)fre=1;
break;
case 2:
mode=mode+1;
if(mode==3)mode=0;
break;
}
OLED_Clear();
switch(mode){
case 0:
OLED_ShowString(10, 0, "Square ", 8, 1);
break;
case 1:
OLED_ShowString(10, 0, "sine ", 8, 1);
break;
case 2:
OLED_ShowString(10, 0, "Triangular", 8, 1);
break;
}
sprintf((char*)Vpp,"%d ",1500-fv*150);
OLED_ShowString(42, 10, Vpp, 8, 1);
sprintf((char*)FV,"%d ",fre);
OLED_ShowString(30, 20, FV, 8, 1);
OLED_Menu();
OLED_Refresh();
buff_try();
HAL_TIM_PWM_Start_DMA(&htim3, TIM_CHANNEL_3, (uint32_t*)buff_value, j);
break;
}
else if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15)==0){
break;
}
break;
}
}
HAL_TIM_Base_Stop_IT(&htim2);
flag_tim2=0;
效果展示
下图展示了系统实际运行时的效果,使用STM32G031开发板产生信号,并通过示波器观测生成的波形。

遇到的困难
在项目实现过程中遇到了不少挑战。由于相关知识储备不足,大部分问题都依赖于网络搜索解决。
- 基础环境搭建:第一个难关是点亮LED,花费了两天时间,最终在社区交流中发现是编译器版本兼容性问题。
- 波形生成算法:最初尝试通过定时器动态改变PWM占空比来生成正弦波,但这种方法只能产生极低频率的波形。随后转向了解DDS(直接数字频率合成)原理,但关于用PWM模拟DDS的资料较少。主要思路是查表法(也可实时计算,精度更高,但因不熟悉SPWM公式而放弃),通过改变查表步进来调节频率。然而,当目标频率较高且不能被基准频率整除时,会出现精度问题。后续改进为不改变DMA传输数组大小,通过循环读表来填满缓冲区,结合定时器调整理论上可实现更低频率的输出。
- 幅度调节精度:通过乘以一个系数来调节幅度,将小数转换为整数时精度损失较大。曾考虑实时计算,但受限于公式理解,最终选择用SPWM软件预生成10个不同幅度的波形表。三角波表为手动生成,直接使用了乘系数的方法,牺牲了一定的精度。
- 编码器读数:采用下降沿捕获A相变化并判断B相电平的方式判断方向,但正反转经常误判,最终简化为只支持单向调节。
- 屏幕驱动:OLED屏幕的驱动是耗时最长的部分,相关教学资源相对较少,仅点亮屏幕就花费了一天时间,反复观看教学直播才勉强攻克。
心得体会
这次项目对我个人而言是一个不小的挑战,相当于在相关知识几乎为零的基础上起步。从最初的无从下手,到通过听课、搜索资料、选择性学习,最终拼凑完成了这个设计。非常感谢技术交流群里朋友们的答疑以及平台提供的支持。
经过这次实践,我不仅学习了STM32、PWM、DMA等嵌入式开发的相关知识,更重要的是养成了持续学习和解决问题的习惯。如果你也打算开始动手做项目,很多时候阻碍你的可能不是代码本身,而是手边没有合适的板卡和模块来反复验证想法。
我本次项目使用的硬件平台是“基于STM32的简易信号发生器与示波器”学习套件,这让我少走了很多弯路。如果你想系统地跟随项目实践,打好硬件基础至关重要。同时,在探索信号与系统、电路设计等更深层的原理时,有一个可以随时交流讨论的社区环境会事半功倍。欢迎到云栈社区与更多开发者一起学习成长。