这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【DSPIC33CURIOSITYPLATFMDEVBRD】5-声控节拍灯:让台

共1条 1/1 1 跳转至页

【DSPIC33CURIOSITYPLATFMDEVBRD】5-声控节拍灯:让台灯跟着音乐起舞

菜鸟
2026-10-07 12:30:00     打赏

一、需求:把调音台搬上台灯

第 4 篇的网页调色台解决了"精确选色",但灯是死的——要人戳屏幕才动。这一篇让它自己活过来:

  1. Mac 麦克风实时收音

  2. 板上的 LED0~LED7 排成电平条,像调音台那样随声音起伏

  3. RGB LED 彩虹平滑流转,声音越大流得越快,鼓点一打瞬间飙升

效果目标很明确:放一首歌,电平条跟着节奏跳动,RGB 像夜店灯球一样流转。

【配图 1】板子实拍:放音乐时电平条亮到 5~6 格、RGB 呈彩色

5_1_sound_reactive_board.jpg

二、总体流程:一条从声波到 PWM 的流水线

麦克风 → ffmpeg 采 8kHz PCM → 25ms 分块
   → RMS/dB → 快攻慢释峰值(VU 表) → 低频包络(节拍检测)
   → 色相流速合成(非线性+节拍脉冲) → "M r g b l" 串口指令
   → 主控解析 → PWM 驱动 RGB + LATC 驱动电平条

分层和第 4 篇一样:每一环可独立调试。分析错了看终端 ASCII 电平表;串口断了看回显计数;渲染错了用手敲一条 M 255 0 0 8 命令验证。

三、VU 表力学:灵魂在"快攻慢释"

电平条的"调音台手感",来自两条调度规则:

  • 快攻:声音一来,峰值立刻顶上去——反应速度决定"跟不跟手"

  • 慢释:声音停了,指针缓缓回落,而不是瞬间掉零

就是这两个分支,让电平条有了"惯性"。完整的分析入口长这样——RMS、dB、归一化、快攻慢释、慢包络,一口气走完:

def feed(self, samples):
    n = len(samples)
    rms = math.sqrt(sum(s * s for s in samples) / n)
    db = 20.0 * math.log10(max(rms / 32768.0, 1e-6))
    db = max(FLOOR_DB, min(0.0, db))          # -50dB ~ 0dB 归一化

    if db >= self.peak_db:
        self.peak_db = db                     # 快攻
    else:
        self.peak_db = max(db, self.peak_db - DECAY_DB_S * (n / SR))  # 慢释

    level  = (self.peak_db - FLOOR_DB) / -FLOOR_DB    # 电平条用:峰值
    target = (db - FLOOR_DB) / -FLOOR_DB
    self.smooth += (target - self.smooth) * 0.15      # 颜色用:慢包络
    return level, self.smooth

注意电平条和颜色用的是两个不同的量:条跟峰值(有惯性、好看),颜色跟慢包络(不抖、平滑)。一个 feed 返回两个值,各取所需。快攻慢释的比例(12dB/s)我调到过 20dB/s 和 6dB/s:前者太神经质,后者像慢动作。12 是个不错的起点,喜欢绵软感的可以调小。

RMS 到 dB 的归一化也值得说:-50dB ~ 0dB 映射到 0~1。安静房间的麦克风电平大约 -50dB 上下,这个地板选得合适,平时电平条归零、放音乐才起步。

四、节拍检测:几行代码抓住鼓点

不装 NumPy、不上 FFT——鼓点的本质是低频能量的瞬时突增,用一个一阶低通 + 基线对比就能抓:

bass_in = sum(samples) / n          # 均值天然滤掉高频
self.bass += 0.08 * (bass_in - self.bass)      # 低频包络
self.bass_base += 0.02 * (bass_mag - self.bass_base)  # 慢基线
if bass_mag > 2.0 * (self.bass_base + 200.0):  # 突增 2 倍 = 鼓点
    beat = True

sum(samples)/n 是个讨巧的低通:高频在均值里自相抵消,剩下的是低频包络。再加 180ms 的最小间隔防抖(防止一次鼓点连发三次),拍子就稳了。

五、彩虹流速:非线性映射 + 节拍脉冲

需求拆成两条:

  1. 彩虹永远平滑流转——色相连续推进,不是跳色

  2. 声音只控制流速——安静时 36 秒转一圈,越响越快

第一条用浮点色相累积就行;第二条的关键是非线性映射。线性映射的问题:中等音量就已经转得飞快,大音量反而没区别了。改成 smooth² 之后,安静段几乎不动,响度一上来曲线才抬头:

rate = HUE_RATE_MIN + (HUE_RATE_MAX - HUE_RATE_MIN) * smooth**2
rate *= 1.0 + BEAT_BOOST * beat_env    # 节拍脉冲:鼓点瞬间 ×3.5

实测 504 帧里出现 431 种颜色——每帧的色相都在移动,且相邻帧颜色渐变(第 2 篇的 EMA 哲学在这里复现了一次)。

主循环把整条管线串起来,每 25ms 一块,从声波到串口一气呵成:

while True:
    data = proc.stdout.read(chunk_bytes)          # ffmpeg 实时 PCM
    samples = struct.unpack(f'<{len(data)//2}h', data)
    level, smooth = meter.feed(samples)           # VU + 慢包络
    hue_deg = (hue_deg + rate(smooth) * dt) % 360 # 非线性流速 + 节拍脉冲
    r, g, b = level_to_color(hue_deg, smooth)
    bar = min(8, int(level * 8.999))              # 电平条 0~8 档

    os.write(fd, f"M {r} {g} {b} {bar}\n".encode())  # 一条命令全带走
    read_echo(fd, stats)                          # 收 OK,统计丢包

固件侧对应新增 M 命令的解析和电平条输出,RGB 部分沿用第 4 篇:

/* LED0~LED7 电平条:档位 0~8,从 LED7(左)向右点亮,调音台风格 */
static void setBarLevel(uint8_t level)
{
    if (level > 8u) level = 8u;
    uint16_t pattern = (uint16_t)(((0xFFu << (8u - level)) & 0xFFu) << 3);
    LATC = (uint16_t)((LATC & ~0x07F8u) | pattern);   /* RC3..RC10 */
}

六、踩坑两连:ANSELD 漏配与 UART 停摆

这篇的调试故事值得单开一节,因为两个坑都很经典。

坑 1:MCC 漏配 ANSELD,UART 只能发不能收。 烧完第一版固件,命令发出去石沉大海——TX 通(能收到板子的启动横幅),RX 死。查 MCC 生成的 pins.c,它对 RB5 清过模拟(ANSELB),但 RD3(UART1 RX)的 ANSELD 压根没配。dsPIC33A 的引脚复位默认是模拟态,模拟态的引脚读不到数字电平,RX 就是死的。一行修复:

ANSELD &= ~(1u << 3);   // RD3 = UART1 RX,必须数字输入

(顺带吐槽:这个寄存器在头文件里连 bits 结构体都没有,只能裸位操作。MCC 配了 RPINR9 的 PPS 映射却忘了模拟开关,属于生成器的疏漏。)

坑 2:UART 错误标志不清,RX 永久停摆。 回显统计突然出现 89% 丢失——不是慢,是彻底没响应。定位到 FIFO 溢出错误标志:这版 UART 的错误中断标志是写 1 清零,溢出一次不清,接收就永久停摆。修复是在接收循环里顺手清错:

uint32_t errs = UART1_ErrorGet() & (_U1STAT_FERIF_MASK | _U1STAT_RXFOIF_MASK
                                  | _U1STAT_CERIF_MASK | _U1STAT_ABDOVIF_MASK);
if (errs != 0) { U1STAT = errs; }

这两个坑的共同点:都是"能用但会死"的隐患——第一版能跑,跑快了、跑久了才暴露。串口回显统计(发 N 条收 N 个 OK)是抓住它们的功臣,这个自检机制在第 4 篇埋的线,这篇收了网。

七、自测方法:没有眼睛也能验证

固件加回显(每条有效命令回一个 OK)之后,测试闭环完全自动化:

合成测试音(0.5s 正弦 + 1s 静音 ×3)→ 跑 analyzer
  → 断言1: 响段电平 > 80%,静音段 < 5%        (分析正确)
  → 断言2: 收 OK ≈ 发指令                     (链路完整)
  → 断言3: 504 帧 431 种颜色                   (流转平滑)
麦克风实测 → 说话/拍手 → 电平从 0 跳到 60% 再缓释回零

【配图 2】终端实录:跑测试音时 hue 从红扫到蓝(RGB 数值按自身颜色着色)、VU 峰值 85% 保持、停音后缓释衰减

5_2_terminal_meter.png


【配图 3】10 秒小视频:放歌 → 电平条跳动 + RGB 流转

5_3_demo_video.gif



八、小结

到目前为止,台灯的控制权经历了完整的一圈:

版本控制源灯的反应
M1~M2旋钮 + 按键本地调光、彩虹
M3手机网页远程精确选色
M4声音自己跟着音乐起舞

M4 的工程亮点不在算法(VU 和节拍检测加起来不到 50 行),在于把第 2、3、4 篇的积累全部串了起来:EMA 的平滑切换、文本协议的可调试性、回显的自检闭环、状态机的控制权管理——每一篇的一个点,都是这一篇的一块砖。





关键词: DSPIC33CURIOSITYPLATFMDEV    

共1条 1/1 1 跳转至页

回复

匿名不能发帖!请先 [ 登陆 注册 ]