如何实现实时UDP像素显示与舞台可视化器
在上一篇文章中,我们提到为什么运行代码时没有彩色显示,是因为main.py是运行在微控制器上的程序。它的职责是通过以太网/UDP接收LED数据,并将这些数据写入NeoPixel矩阵。然而,微控制器本身并不会自行生成动画帧。
我们将创建一个名为streamer.py的新文件,它运行在计算机上,充当LED帧生成器和发送器。该程序为全部256个LED生成RGB值,将其打包成UDP数据包,并通过IP地址10.0.1.140、端口6454发送给微控制器。
因此,我们需要streamer.py,因为它提供main.py所接收并显示的动画帧。如果没有streamer.py,虽然控制器可能运行正常,但没有新的LED数据显示,LED就会保持关闭或不显示任何内容。
现在,我们直接进入正题!
streamer.py代码解析
导入模块与全局参数
•网络常量:配置目标板IP,并将流量分发到两个独立的UDP端口(6454用于外发视频,6455用于接收遥测数据)。要获取您的板IP,请先运行udp.py,然后复制命令行中显示或OLED上显示的IP地址。
•矩阵尺寸:设置目标网格大小为32x8 = 256个LED。
•共享状态字典:存储模拟变量(速度、亮度)和诊断信息(丢弃帧数)。由于Python以原子方式读取和替换字典条目,该字典在后向通道监听器与帧渲染器之间充当线程安全的桥梁。
遥测监听器:backchannel_worker()
•套接字设置:使用UDP(SOCK_DGRAM)绑定到0.0.0.0:6455,以接收来自任意接口的遥测数据包。
•数据包验证:通过recvfrom(32)最多读取32字节,并验证数据包至少包含5字节长度,且头部魔数为b'CT'。
•二进制解包:
•data[2]提取原始编码器旋钮位置(0-100)。(
•data[3] << 8) | data[4] 重建了 UDP.py 发送的带宽字节序 16 位无符号整数,用于表示帧丢失。
•参数缩放:将旋钮值(0-100)转换为动画速度(0.2x 至 3.0x)和亮度比例(0.1 至 0.8)。
视频引擎:run_streamer()
帧间隔设置:定义目标帧间隔为 1.0 / 60.0 ≈ 16.67 毫秒(60 帧/秒)。frame_id 从 0 开始。
头部生成:生成一个包含 [0xAA, 0x55] 的字节数组,后跟当前 frame_id 的高字节(frame_id >> 8)与 0xFF 的组合,以及低字节 frame_id & 0xFF。
时间累加器:根据遥测线程设定的动态速度,按比例推进相位时间 t。
等离子体算法:在坐标 x 和 y 上计算 R、G、B 的平滑正弦/余弦波,通过 b_factor 进行缩放。我请 Gemini 帮助我创建此图案。
序列化:每像素追加 3 字节到帧中(256×3=768 像素字节 + 4 个头字节 = 总共 772 字节)。
传输与循环:将帧发送至 BOARD_IP:6454,并将 frame_id 在 16 位范围内循环(& 0xFFFF,从 0 到 65535 循环)。
自适应速率限制:使用高分辨率时间测量帧渲染和套接字传输延迟。perf_counter(),仅在60帧每秒周期的剩余时间内进行睡眠。
观察
请注意,当我顺时针旋转旋钮时,动画会加快;而逆时针旋转时则会变慢。我们还可以在OLED屏幕上看到IP、FPS、闪烁次数(DROPS)以及旋钮百分比(KNOB%)。
下一步计划
下一篇文章中,我打算添加一个按钮模块,以便更方便地关闭LED矩阵。目前,我只能通过同时停止Thonny IDE和命令提示符中运行的两个程序,然后在Thonny shell中执行独立命令来关闭LED灯,这种方式非常不便。
不过,我无法直接通过GPIO按钮为LED矩阵供电!一个32×8的矩阵包含256个LED,在最大亮度下,可能需要相当大的电流。因此,LED的电源应来自合适尺寸的外部电源。这时之前我在风扇项目中使用的MOS FET开关模块就派上用场了。
本文编译自hackster.io





