从DMX512到ArtNet:拆解VB6灯光控台与调试助手实现
发布时间:2026/9/13 14:06:18来源:尧图网络
简介这是一套用Visual Basic编写的DMX512舞台灯光控制台程序源码包适合灯光控制爱好者、活动执行人员以及有VB基础的程序员学习参考帮助理解主从式DMX512协议、通道数据发送与可视化控制台的落地方法。压缩包共119个文件、约1.03MB主要包含frm窗体与frx资源、bas业务模块含ArtNet.bas网络扩展、ctl控件、ocx/dll运行组件、chm/hhc联机帮助、htm命令语法说明、shw工程文件以及bmp/ico界面素材目录结构清晰便于按模块研读。已有152人学习下载。从StageConsole主控界面到command_examples实例、select_channels通道选择、default.shw默认布景资源完整呈现了调光通道分配、0-255亮度映射、场景保存及ArtNet扩展通信等关键开发环节源码注释配合帮助文档还能帮助摆脱对特定硬件的依赖快速移植到DMX USB或以太网灯光系统。1. 在旧硬盘里翻出的 deskstage 工程比现成 DMX512 调试助手更值得拆一遍一个名为dmx_deskstage_dmx512_的目录里躺着一堆.bak文件和两个.bas模块。起初我以为只是老式 VB 窗体程序打开Module1.bas和ArtNet.bas才发现这是一个完整的 StageConsole 灯光控台源码。它没有依赖任何商业灯光 SDK而是直接用 Visual Basic 把 DMX512 协议和 ArtNet 网络帧硬怼了一遍。对现在习惯在 Python 里调库的人来说这种“从字节开始造轮子”的思路反而有参考价值。这个项目适合两类人一是要维护十多年前灯光系统的工程师二是在做自定义 dmx512 调试助手、不想被现成软件限制字段细节的开发者。它不负责帮你编排灯光秀只负责把 0 到 255 的通道值按协议放进正确的时序。2. DMX512 与 ArtNet 帧结构deskstage 控制台的数据组织方式deskstage 这个工程里凡是跟硬件发送有关的部分都集中在两个模块Module1.bas管主流程和通道缓冲ArtNet.bas管网络封装。这意味着理解这个项目时需要先把 DMX512 物理帧和 ArtNet 包结构拆开看。2.1 从 0-255 的通道值到 512 个槽位的映射DMX512 的基本单位是一个字节取值 0 到 255对应舞台上调光器的亮度、颜色盘位置或者其他功能参数。一条 DMX 链路最多承载 512 个这样的字节所以“通道号”本质上是数组下标。协议规定在数据之前必须发送 BREAK拉低线路至少 92 微秒、MABMark After Break约 12 微秒和一个起始码 0x00然后才是通道数据。这个时序是由硬件适配器或串口状态机去处理的软件侧只需要把 512 字节按顺序填好。在Module1.bas里常见写法是定义一个 0 到 511 的全局字节数组。我一般在 UI 层加一层映射把用户看到的“第 1 路”转成数组下标 0这样既能保住直觉又能避免到处减一 Module1.bas 中典型的通道值缓冲 Public DMXData(0 To 511) As Byte Public Sub SetChannel(ByVal uiChannel As Integer, ByVal value As Integer) Dim idx As Integer idx uiChannel - 1 If idx 0 And idx 511 Then DMXData(idx) value Mod 256 End If End Sub这段代码的逻辑很直接uiChannel是用户看到的通道号从 1 开始idx是数组下标从 0 开始。value Mod 256是为了防止 UI 上有人输入 255 以外的值比如把调光值从 0-100 映射成 0-255 时因为整数乘法偶尔会产生 256 这个越界值。实际使用时要注意value必须是整数类型VB 里如果直接用浮点数Mod运算符会先做四舍五入容易引入偏差。随后灯光场景的“状态”就全部体现在这 512 个字节里。比如一个 RGB 三色灯占 3 个通道我通常会让 UI 用三个滑块分别控制DMXData(0)、DMXData(1)和DMXData(2)。至于什么通道对应什么颜色这是灯具厂商的说明书决定的deskstage 工程里default.shw.bak这类场景快照本质上就是把这一整块数组序列化保存。2.2 ArtNet.bas 里如何把通道数组封装成 UDP 包如果你只有一条串口线直接发送上面的数组给 USB-DMX 适配器即可。但这个工程里出现ArtNet.bas说明它走了 ArtNet 协议。ArtNet 是 Art-Net 标准定义的在以太网上传输 DMX512 数据的应用协议默认 UDP 端口 6454。它把整帧 DMX512 数据包装成一个 ArtDmx 数据包这样同一局域网里的解码器节点可以收到并转成物理 DMX 信号。ArtDmx 包的格式很固定我从实际抓包里整理了一份字段表字段偏移长度字节值/说明ID08固定为Art-Net\0OpCode820x5000表示 ArtDmx低字节在前ProtVer1020x000E协议版本 14低字节在前Sequence121当前包的序列号用来检测丢包Physical131物理端口号一般填 0Universe142目标宇宙0-32767低字节在前Length162DMX 数据长度高字节在前512 即 0x0200DMX Data18n实际通道数据最多 512 字节注意 Length 字段和其他字段的字节序不一样这是 Art-Net 规范里最容易错的地方。我见过有人把 Length 也按低字节在前写结果节点接收到一个“反向长度”拒绝转发。deskstage 的ArtNet.bas里构造包的时候通常是把 512 字节复制到 offset 18 以后再回填头部Public Sub BuildArtNetPacket(ByVal universe As Integer, _ ByRef data() As Byte, _ ByRef pkt() As Byte) Dim i As Integer 1. 固定 ID pkt(0) Asc(A): pkt(1) Asc(r) pkt(2) Asc(t): pkt(3) Asc(-) pkt(4) Asc(N): pkt(5) Asc(e) pkt(6) Asc(t): pkt(7) 0 2. OpCode 0x5000, 低字节在前 pkt(8) 0: pkt(9) H50 3. ProtVer 14, 低字节在前 pkt(10) 14: pkt(11) 0 4. Sequence / Physical pkt(12) 0 pkt(13) 0 5. Universe, 低字节在前 pkt(14) universe Mod 256 pkt(15) (universe \ 256) Mod 256 6. Length 512, 高字节在前 pkt(16) 2 pkt(17) 0 7. 复制通道数据 For i 0 To 511 pkt(18 i) data(i) Next i End Sub解释一下参数universe是 UI 里看到的宇宙号如果用户选的是“宇宙 1”这里要传 0因为协议从 0 开始计数。pkt(16) 2和pkt(17) 0就是高字节在前地表示十进制的 512如果你以后想发送更少的通道比如只控制 128 通道这里要改成pkt(16) 0: pkt(17) 128并且在复制数据循环里只循环 0 到 127否则多余的数据会被协议忽略。pkt(12)的序列号应该在每次发送前递增并限制在 0 到 255否则接收端检测不到乱序。这个填充函数本身不做网络发送真正的 UDP 发送是在Winsock1.SendData里完成的后面第 4 章我会给出完整的调用方式。这段代码的核心价值在于它把“数据组织”和“传输”分离了。调试 DMX512 设备时如果灯不亮你可以先打印这个pkt数组看头部对不对再谈网络问题。3. 从 Module1.bas 到 StageConsole事件驱动与命令解析StageConsole.hhp是 VB 的帮助项目文件command_syntax.htm.bak里记录了命令语法。这说明这个工程不是简单拉几个按钮而是有完整的人机命令体系。理解这些才能把老的 deskstage 改造为可用的调试助手。3.1 滑块事件里只写缓冲不要在事件里发数据VB 的滑块Slider控件在拖动时会连续触发Change事件频率比想象中高。如果在事件里直接调用 UDP 发送函数UI 线程会被网络和控件刷新拖死。常见做法是事件里只更新DMXData数组并标记一个“脏”标志然后由一个定时器统一发送。这样无论 UI 以多快的速度触发实际发送频率都由定时器控制。Private Sub Slider1_Change() 滑块范围 0-100映射到 0-255 DMXData(0) Slider1.Value * 255 \ 100 DirtyFlag True End Sub这段代码有两个细节值得说。第一\是 VB 的整数除法比/更适合这里因为 DMX 通道值是整数直接用浮点除会出现 99.9 这种到不了 255 的边界问题整数除法可以保证 100 映射到 255。第二DirtyFlag是一个模块级布尔变量定时器事件里看到它为True就发送发送完成后立即置回False。这比定时器每 25 毫秒无脑发一次要高效尤其在做静态灯光场景时能明显减少局域网中的垃圾包。如果要做平滑亮度变化不能依赖滑块事件本身。滑块停止后最后的值会被写入数组但“停在中间”的感觉不对。我一般会再加一个Timer做线性插值把目标值保存到TargetValue每次 Tick 时让当前值朝目标值走一小步再把当前值写入数组。这样从 0 到 255 的过程是视觉上连续调光而不是一下跳变。注意插值步长要根据发送帧率调整例如 40fps 下发 30ms 完成一次全跨度变化步长就是 255/(40*0.03)约等于 212。3.2 command_syntax 文件与内置指令解析的取舍工程里的command_syntax.htm.bak记录了类似“通道 1 到 5 设为 255”的命令语法。在 VB 里解析这种字符串最简单的办法是用Split把命令按空格拆开再根据第一个词分发到不同处理函数。表格是整理命令结构最好的方式命令示例含义解析后的动作ch 1 255通道 1 亮度设为 255调用SetChannel(1,255)ch 1-10 128通道 1 到 10 设为 128循环调用SetChannel(i,128)cls清空所有通道将整个DMXData数组归零store 1保存当前场景到 1 号快照复制DMXData到Scene(1)解析函数可以写成下面这样基本能覆盖 90% 的指令需求Public Sub ParseCommand(ByVal cmd As String) Dim parts() As String Dim i As Integer, ch As Integer parts Split(Trim(cmd), ) Select Case LCase(parts(0)) Case ch If InStr(parts(1), -) 0 Then Dim range() As String range Split(parts(1), -) For i CInt(range(0)) To CInt(range(1)) SetChannel i, CInt(Replace(parts(3), , )) Next i Else SetChannel CInt(parts(1)), CInt(parts(3)) End If Case cls For i 0 To 511 DMXData(i) 0 Next i Case store Call SaveScene(CInt(parts(1))) End Select DirtyFlag True End Sub说明一下解析逻辑Split(cmd, )把ch 1 255拆成四个字符串parts(1)是通道号parts(3)是值。命令字符串里实际上只是装饰词解析时直接忽略。为了兼容ch 1-10 128这种连续通道写法先检查parts(1)里是否包含-包含就拆出起止通道循环赋值。LCase(parts(0))用于统一大小写避免用户输入CH时找不到分支。所有分支结束后统一置DirtyFlag等待定时器把数据发出去。这种解析方式相比“每个命令写一个专用函数”更利于扩展到新的指令比如fade 1-5 255 2000这种带时间的命令。关键点是不在解析函数里直接发送数据而是把解析结果落进DMXData数组。否则一条ch 1-50 128会把 50 个 UDP 包瞬间砸到网络上接收节点会因为这个突发而丢失后续包。我调试时碰到过类似问题灯多个通道跳变原因不是协议错误而是一条命令触发太多次发送。改成统一发送后问题立刻消失。这个逻辑同样适用于下拉框、复选按钮以及 scene 快照的加载。任何 UI 操作都只改数组和标志发送动作留给定时器这是从老式灯光台程序里学到的典型模式。4. 搭建自己的 dmx512 调试助手端口选择与发送节拍把协议和事件模型理顺之后就可以动手写一个自己的 dmx512 调试助手了。这个助手不追求复杂业务只要做到“拖滑块、看灯亮、抓包能对上”。4.1 选 USB-DMX 串口还是 ArtNet 网络口deskstage 工程里只出现ArtNet.bas没有看到串口 API说明原作者默认走网络。但很多 DIY 项目用的是 USB-DMX 设备比如 Enttec Open DMX这类设备在 Windows 上会映射成 COM 口需要用 MSComm 控件或调用 DLL 发送。两者选型差异看表格对比维度USB-DMX串口ArtNet 网络口硬件成本低兼容设备多中需要节点解码器发送方式按 DMX512 时序拉 BREAK 再送字节把字节封装成 UDP 包调试难度需要示波器/逻辑分析仪看时序用 Wireshark 抓包即可定位最大距离几十米内受交换机限制但方便跨房间实时性驱动控制精确到微秒受网络抖动影响现场需注意如果是给剧场做走线ArtNet 是更常见的选择因为一条网线可以带多个 Universe。如果只是桌面上测试一个灯头USB-DMX 更省钱。我一般建议新手用具 ArtNet 节点的方案原因不是它更简单而是抓包工具成熟出了错能立刻看到包格式。如果你用串口最怕的是时序参数BREAK 宽度、MAB 宽度不对那些参数在软件里不可见排查成本高。4.2 用定时器控制发送频率避免阻塞界面选定 ArtNet 后核心是一个 UDP 发送例程加一个定时器。VB6 里没有现成的 UDP Socket通常用 Winsock 控件。Winsock1.Protocol设为sckUDPProtocolRemoteHost填解码器的 IPRemotePort填 6454。然后定时器每隔 25 毫秒检查脏标志并发送Private Sub Timer1_Timer() Dim artnetPkt() As Byte If Not DirtyFlag Then Exit Sub ReDim artnetPkt(0 To 529) Call BuildArtNetPacket(0, DMXData, artnetPkt) Winsock1.SendData artnetPkt DirtyFlag False End Sub Private Sub Form_Load() Winsock1.Protocol sckUDPProtocol Winsock1.RemoteHost 192.168.1.200 Winsock1.RemotePort 6454 Timer1.Interval 25 End Sub这里BuildArtNetPacket对应第 2 章的函数只是把pkt通过参数返回。Timer1.Interval 25表示每秒最多 40 帧。Art-Net 标准推荐数据包发送速率不要超过每秒 40 帧因为流明现场的灯具刷新率通常 25-40Hz超过这个值只是在烧带宽。需要注意Winsock1.RemoteHost在 UDP 模式下可以每次发送前修改所以想控制多个节点时不用建多个控件发送前改RemoteHost再SendData即可。但要注意两点一是SendData是异步的循环改 IP 时不会立即生效二是如果一次要发到多个 Universe推荐用多个 Winsock 控件或者改用底层Winsock1.SendData在每次发包前重新指定地址并确认节点端口的绑定不冲突。发送节拍还要考虑 Timer 的精度。VB6 的 Timer 默认精度只有约 15.6 毫秒把 Interval 设成 25 可能实际触发间隔在 15-30 毫秒之间波动。对舞台灯光的呼吸效果来说够用但如果要做 DMX 波形回放比如把录制的曲线送出去建议把 Interval 设成 16 毫秒以下并在发送前用timeGetTime做微调。另外DirtyFlag方案意味着只有当滑块变化或命令执行时才发包这在静态场景下很省网络但等到你要做自动跑灯效果时就不能依赖滑块事件了需要定时器强制发包并更新数组。我通常会在定时器里加一个ContinuousMode As Boolean为 True 时忽略DirtyFlag每一帧都发送。5. 老工程的兼容性坑与 ArtNet 抓包验证技巧dmx_deskstage_dmx512_里的文件大多是.bak这说明原工程经历过多次覆盖和备份。把这样一套 Visual Basic 6 工程重新跑起来最大的麻烦不在代码而在开发环境兼容性。VB6 的 IDE 在 Windows 10/11 上仍可以安装但安装后要把MSComctlLibSlider 控件、Toolbar和MSWinsockLibWinsock 控件的引用勾选好。如果编译时提示“部件未注册”用管理员身份打开 cmd运行regsvr32 MSCOMCTL.OCX和regsvr32 MSWINSCK.OCX即可。注意 VB6 默认生成 32 位程序在 64 位系统上能运行但如果你的工程引用了 64 位专用 COM 组件就会出现类型不匹配。5.1 验证 ArtDmx 包是否合法跑通后不要直接拿灯光设备试先用 Wireshark 抓包。在过滤栏输入udp.port6454然后拖动滑块你应该能看到目标 IP 端口 6454 的 UDP 包。点开第一个包检查 Data 是否以41 72 74 2d 4e 65 74 00开头这就是Art-Net\0。再往下翻找到偏移 8 的00 50也就是 ArtDmx 的 OpCode。如果抓到的包是50 00说明字节序写反了。另一个实用技巧是把Sequence字段固定为一个常数比如 0x0D发送几帧后再恢复递增。这样在大量相同数据包中能快速区分新旧包。因为 Art-Net 接收端只认 Sequence 变化固定时有些节点会把重复包当成丢包后的重传处理不会触发输出所以平时不要长期固定仅用来调试。5.2 用 Python 做镜像接收端如果你不想反复改 VB 代码可以先用 Python 在另一台机器上监听 UDP 6454把接收到的ArtDmx包解码成 512 个通道值和 VB 界面上的滑块数值比对。下面是一段最小接收程序用于验证通道映射import socket UDP_IP 0.0.0.0 UDP_PORT 6454 sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((UDP_IP, UDP_PORT)) while True: data, addr sock.recvfrom(1024) if data[:8] bArt-Net\x00: universe data[14] (data[15] 8) length (data[16] 8) data[17] dmx data[18:18 length] print(funiverse{universe}, len{length}, ch1{dmx[0]})这段代码直接读取第 14、15 字节合成 Universe第 16、17 字节合成 Length。注意 Python 端不需要再处理字节序因为data[16] 8已经模拟了高字节在前。如果这里打印出来的 universe 和你界面上设置的一致就说明协议封装没问题。如果 ch1 值不对重点检查 VB 端滑块事件里的映射公式。在 Wireshark 里加一条udp.port6454 artnet.opcode0x5000过滤器能直接过滤出 ArtDmx 包再结合上面的 Python 接收端几乎所有的字节序错误都能在两步内暴露出来。如果这条过滤器里始终没有包回头先确认 VB 的Winsock1.RemotePort是不是被某段初始化代码写成了 6453 之类的错误值。本文还有配套的精品资源点击获取
网站建设高端定制企业官网