第四节:正弦波生成算法——查表法与实时计算法的实战抉择

各位同学,今天我们来聊聊正弦波生成的核心问题。说实话,这个知识点在按摩仪开发里太关键了。你想想看,用户按着舒服不舒服,很大程度上就取决于你生成的波形够不够「丝滑」。

正弦波在按摩仪里干嘛用?说白了就是模拟人手揉捏的力度变化。从轻到重,再从重到轻,这种平滑过渡的感觉,正弦波最合适不过了。我当年做第一款按摩仪时,就因为波形不够平滑,被测试同事吐槽说「像在搓衣板上按摩」……嗯,从那以后我就把波形生成当成了头等大事。

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 我的实战建议

说了这么多,到底该用哪个?我个人经验是这样的:

  1. 量产产品,用查表法。 稳定、可靠、好调试。我做的几款按摩仪,无一例外都用的LUT。用户不会在乎你用了什么算法,只在乎按着舒不舒服。
  2. 原型验证,用实时计算法。 开发初期需要频繁调频率和波形,实时计算法改起来快。等参数定下来了,再转成查表法。
  3. 高端产品,两者结合。 比如用查表法做主波形,用实时计算法做微调。我见过一个项目,LUT存了32个点的粗波形,然后实时插值出256个点,既省内存又平滑。

一个实用技巧: 如果你用查表法,但想微调频率,可以这样做——不是改表,而是改输出间隔。比如原来每1ms输出一个点,现在改成每0.9ms输出一个点,频率就变高了。这个方法我在好几个项目里用过,效果不错。

好了,正弦波生成算法就讲到这里。下一节我们会聊PWM的硬件配置和中断处理,到时候你会看到这些波形数据是怎么真正变成马达振动的。嗯,敬请期待。