第四节:正弦波生成算法——查表法与实时计算法的实战抉择
各位同学,今天我们来聊聊正弦波生成的核心问题。说实话,这个知识点在按摩仪开发里太关键了。你想想看,用户按着舒服不舒服,很大程度上就取决于你生成的波形够不够「丝滑」。
正弦波在按摩仪里干嘛用?说白了就是模拟人手揉捏的力度变化。从轻到重,再从重到轻,这种平滑过渡的感觉,正弦波最合适不过了。我当年做第一款按摩仪时,就因为波形不够平滑,被测试同事吐槽说「像在搓衣板上按摩」……嗯,从那以后我就把波形生成当成了头等大事。
4.1 用NumPy生成正弦波数据点
先上代码,咱们看看怎么用Python生成一组正弦波数据点。我个人习惯用NumPy,因为它快,而且代码写起来很直观。
import numpy as np
# 参数设置
fs = 1000 # 采样率 1000Hz
f = 50 # 正弦波频率 50Hz
amplitude = 255 # 幅值(8位PWM用0-255)
offset = 128 # 直流偏置(让波形在0-255之间摆动)
# 生成一个周期的数据点
t = np.arange(0, 1/f, 1/fs) # 时间轴
sine_wave = amplitude * np.sin(2 * np.pi * f * t) + offset
# 转成整数,方便PWM输出
sine_wave_int = np.round(sine_wave).astype(np.uint8)
print(f"生成了 {len(sine_wave_int)} 个数据点")
print(f"前10个值: {sine_wave_int[:10]}")
这段代码干了什么呢?
- 采样率fs:决定了波形的细腻程度。我建议至少1000Hz,否则波形会有锯齿感。
- 频率f:按摩仪一般用20-80Hz,太低了感觉像「一顿一顿」,太高了马达反应不过来。
- 幅值和偏置:因为PWM通常是0-255的整数范围,所以要把正弦波从-1~1映射到0~255之间。
小技巧: 如果你想让波形更平滑,可以试试提高采样率。但注意,采样点太多会占用MCU内存。我一般取256个点,既够用又不浪费。
4.2 查表法(LUT)—— 经典且高效
查表法,英文叫Look-Up Table,简称LUT。说白了就是提前算好一组正弦波数据,存到数组里。运行时直接从数组里取数,不用再算。
优点很明显:
- 速度快——就一个数组索引操作,几乎没有计算开销
- 适合低性能MCU——比如8位单片机,算个sin函数要几十微秒,查表只要几微秒
- 波形稳定——每次输出都一样,不会因为计算误差抖动
缺点呢?
- 占内存——256个点就要256字节,如果波形多(比如正弦、三角、锯齿各一张表),内存就紧张了
- 频率调整不灵活——想改变频率,要么重新生成表,要么改变输出间隔,比较麻烦
我踩过的坑: 曾经在一个项目里,我用了512点的正弦表,结果MCU的RAM只剩不到100字节了。后来加了个蓝牙功能,直接内存溢出……从那以后,我定了个规矩:LUT表不超过256点,除非MCU内存特别大。
4.3 实时计算法—— 灵活但费CPU
实时计算法,就是每次需要PWM输出时,现场算sin值。代码长这样:
import math
import time
def realtime_sine(t, freq, amplitude, offset):
"""实时计算正弦波值"""
value = amplitude * math.sin(2 * math.pi * freq * t) + offset
return int(round(value))
# 模拟PWM输出循环
t = 0.0
dt = 0.001 # 1ms步进
while True:
pwm_value = realtime_sine(t, 50, 255, 128)
# 这里把pwm_value写入PWM寄存器
print(f"t={t:.3f}s, PWM={pwm_value}")
t += dt
time.sleep(dt)
if t >= 0.02: # 一个周期后退出
break
优点:
- 省内存——不需要存表,一个变量都不多占
- 频率可调——改一下freq参数就行,非常灵活
- 波形可动态变化——比如做「变频按摩」,实时改频率很方便
缺点:
- 计算量大——sin函数是浮点运算,在低端MCU上很慢
- 输出不稳定——如果计算时间不固定,PWM周期会抖动
注意: 实时计算法在STM32这类带FPU的芯片上还好,但在51单片机或PIC上,我建议你直接放弃。我曾经试过在8位MCU上实时算sin,结果PWM输出频率从50Hz掉到了20Hz,按摩仪直接变成「慢动作」了。
4.4 查表法 vs 实时计算法:一张表说清楚
| 对比项 | 查表法(LUT) | 实时计算法 |
|---|---|---|
| 内存占用 | 高(256字节+) | 极低(几乎为0) |
| 计算速度 | 极快(索引操作) | 慢(浮点运算) |
| 频率灵活性 | 差(需重新生成表) | 好(直接改参数) |
| 波形精度 | 取决于表大小 | 取决于浮点精度 |
| 适用MCU | 8位/16位/32位全兼容 | 建议32位带FPU |
| 代码复杂度 | 低 | 中 |
4.5 我的实战建议
说了这么多,到底该用哪个?我个人经验是这样的:
- 量产产品,用查表法。 稳定、可靠、好调试。我做的几款按摩仪,无一例外都用的LUT。用户不会在乎你用了什么算法,只在乎按着舒不舒服。
- 原型验证,用实时计算法。 开发初期需要频繁调频率和波形,实时计算法改起来快。等参数定下来了,再转成查表法。
- 高端产品,两者结合。 比如用查表法做主波形,用实时计算法做微调。我见过一个项目,LUT存了32个点的粗波形,然后实时插值出256个点,既省内存又平滑。
一个实用技巧: 如果你用查表法,但想微调频率,可以这样做——不是改表,而是改输出间隔。比如原来每1ms输出一个点,现在改成每0.9ms输出一个点,频率就变高了。这个方法我在好几个项目里用过,效果不错。
好了,正弦波生成算法就讲到这里。下一节我们会聊PWM的硬件配置和中断处理,到时候你会看到这些波形数据是怎么真正变成马达振动的。嗯,敬请期待。