1. 项目概述:用单片机实现函数信号发生器的核心价值
十年前我刚接触电子设计时,实验室的函数信号发生器要上万元一台。现在用一片不到10元的STM32单片机,配合巧妙的编程和电路设计,就能实现正弦波、方波、三角波等基础波形生成,甚至还能玩出扫频、调幅等进阶功能。这个项目最吸引我的地方在于,它完美融合了硬件设计、数学算法和嵌入式编程三大技能点。
市面上的函数信号发生器通常采用专用DDS芯片(直接数字频率合成),而我们这个方案用普通单片机+ DAC(数模转换器)的方案,虽然性能指标比不上专业设备,但胜在成本极低(BOM成本<50元)、可定制性强(波形算法可自由修改),特别适合电子爱好者练手和教学演示。实测输出频率范围在1Hz-20kHz时波形稳定无畸变,完全能满足音频调试、传感器激励等常见需求。
2. 硬件设计关键解析
2.1 核心器件选型要点
主控首选STM32F103C8T6(蓝色药丸板),原因有三:
- 72MHz主频足够处理波形计算
- 内置12位DAC(虽然精度一般但够用)
- 价格仅8-10元且生态完善
DAC模块如果追求更好性能,可以外接MCP4921(12位SPI接口,约5元),其建立时间仅4.5μs,比STM32内置DAC快3倍。我在对比测试中发现,输出10kHz正弦波时,外接DAC的THD(总谐波失真)能控制在0.8%以内,而内置DAC约为1.5%。
关键提示:DAC基准电压一定要用REF5025等精密基准源(2.5V±0.05%),普通LDO的电压波动会导致波形出现明显台阶。
2.2 输出电路设计精髓
原始DAC输出需要经过两级处理:
- 直流偏置电路:用运放搭建加法器,将单极性信号转为±2.5V双极性输出
- 低通滤波器:截止频率设为50kHz的巴特沃斯滤波器,消除高频量化噪声
实测电路中的一个隐藏技巧:在运放反馈电阻上并联3pF电容(如图1),能有效抑制振铃现象。这个数值需要根据实际波形调整,用示波器观察边沿过冲情况来微调。
3. 波形生成算法实现
3.1 正弦波的三种生成方式对比
-
查表法:预计算256点正弦值存入数组
- 优点:速度最快(仅需内存读取)
- 缺点:占用Flash空间(1KB左右),频率分辨率固定
-
泰勒展开:实时计算 sin(x) ≈ x - x³/6 + x⁵/120
- 优点:内存占用小
- 缺点:计算量大(STM32跑72MHz时最高生成5kHz)
-
CORDIC算法:通过迭代旋转向量逼近正弦值
- 平衡点:10次迭代精度达0.1%,速度比泰勒快3倍
我的选择是查表法+线性插值:建立512点表格,在两个采样点之间进行插值计算。实测在20kHz输出时,THD比纯查表法降低40%。
3.2 方波生成的隐藏坑
看似简单的方波其实最容易出问题:
c复制// 错误示范 - 直接翻转GPIO
while(1) {
GPIO_SetBits(GPIOA, PIN_DAC);
delay_us(period/2);
GPIO_ResetBits(GPIOA, PIN_DAC);
delay_us(period/2);
}
这种写法会导致频率误差>5%,因为delay_us()本身有调用开销。正确做法是用定时器PWM模式,将ARR寄存器值设为 (72MHz/目标频率)-1,精度可达0.1%。
4. 关键性能优化技巧
4.1 中断服务程序(ISR)优化
波形更新的中断处理必须极致高效:
c复制void TIM2_IRQHandler(void) {
static uint16_t phase_acc = 0;
phase_acc += freq_tuning_word; // 相位累加器
DAC->DHR12R1 = sine_table[phase_acc>>8]; // 取高8位作索引
TIM2->SR &= ~TIM_SR_UIF; // 清除中断标志
}
三个优化点:
- 使用静态变量保存相位累加器状态
- 右移代替取模运算(phase_acc % 256 → phase_acc>>8)
- 直接操作寄存器而非HAL库函数
4.2 动态频率调整策略
当用户通过旋钮改变频率时,直接修改freq_tuning_word会导致相位不连续。正确做法是在中断外计算新值,在ISR内通过原子操作更新:
c复制__atomic_store_n(&freq_tuning_word, new_value, __ATOMIC_RELAXED);
实测这样切换频率时,波形相位连续无跳变,适合音频应用场景。
5. 实测问题排查记录
5.1 高频波形畸变问题
现象:输出5kHz以上正弦波时顶部出现平台
排查过程:
- 用示波器检查DAC输出电压 - 正常
- 检查运放供电电压 - 发现±5V电源在波形峰值时跌落0.3V
- 更换电源滤波电容从100uF增至470uF - 问题解决
根本原因:运放输出电流较大时,电源调整率不足导致电压跌落。
5.2 低频方波抖动问题
现象:1Hz方波上升沿时间不一致
解决方案:
- 关闭所有无关中断(如SysTick)
- 将波形生成定时器优先级设为最高
- 在中断入口添加__DSB()指令清空流水线
调整后实测1Hz方波周期抖动<10μs,满足大多数应用需求。
6. 功能扩展方向
6.1 扫频信号实现
在定时器中断中动态调整freq_tuning_word:
c复制if(sweep_enable) {
freq_tuning_word += sweep_step;
if(freq_tuning_word > MAX_FREQ) freq_tuning_word = MIN_FREQ;
}
配合按键设置起止频率和步长,就能实现自动扫频功能。我在做扬声器频响测试时,这个功能非常实用。
6.2 波形叠加功能
通过修改查表内容实现波形混合:
c复制for(int i=0; i<512; i++) {
mixed_wave[i] = 0.7*sine_table[i] + 0.3*triangle_table[i];
}
注意各分量系数之和不能超过1,否则会导致DAC输出饱和削顶。
这个项目最让我惊喜的是,用如此廉价的硬件也能实现相当不错的波形质量。后来我把这个信号发生器集成到了我的桌面工具箱里,调试电路时再也不用搬动笨重的专业设备了。对于想深入理解数字信号处理本质的朋友,亲手实现一遍DAC波形生成绝对是最佳实践之一。