Source: Modbus Chinese Network(modbus.cn) —— China leadingModbuscommunication protocol technical community
本記事:LLCOM:给シリアルポート装上 Lua 大脑 —— Modbus デバッグ效率翻倍完全に。ガイド · Author:modbus Technical Team · 发布于 2026-07-01
摘要:LLCOM 是 GitHub 开源的 Windows シリアルポートデバッグ工具(chenxuuu/llcom),基于 .NET 和腾讯 xlua 引擎,让シリアルポートデバッグ不再是手动一条条发十六进制フレーム。其核心杀手锏是在標準シリアルポート工具基础上内嵌了完全的 Lua 5.3 运行环境,サポート合宙 Luat Task 协程框架,可以写脚本自动收发、自动計算 Modbus CRC、自动ポーリング、自动解析レスポンス。本記事从インストール到 API 参照到四个 Modbus 実践脚本,カバー你从下载到跑通自動化测试的每一个步骤。关键词:LLCOM、Lua Serial Port Debugging、Modbus CRC 自动計算、シリアルポート自動化测试、xlua、Luat Task。
debug Modbus RTU デバイス最も一般的な的ワークフローワークフロー是:シリアルポートを開く工具 → 手动入力十六进制フレーム → 点送信 → 等回应 → 肉眼对照バイト → 发现 CRC 算错了 → 重新来。十台デバイス调完,手指痛、眼睛酸、心里碎成渣。
LLCOM 解決的就是这个問題。它不只是一个シリアルアシスタント——它是个能跑脚本的シリアルポートデバッグ引擎。
一、LLCOM What is it
LLCOM 是 GitHub オープンソースプロジェクト(chenxuuu/llcom),用 C# 写、基于 .NET 框架,采用腾讯 xlua 作为脚本引擎。ライセンスは是 Apache 2.0。
ポジショニング一句话:可プログラミング的シリアルポートデバッグ工具。普通的シリアルアシスタント只能「键盘打十六进制→手点送信→肉眼看出十六进制」。LLCOM 可以写 Lua 脚本让データ自动処理、自动送信、自动解析、自动チェック。
核心架构三层:
- シリアルポート收发层:標準シリアル通信,サポートボーレート、data bit、チェックビット設定,同时集成 TCP/UDP/MQTT クライアント側は和サーバー·サーバー功能,还能监听其他程序使用的シリアルポート
- Lua 脚本引擎层:Lua 5.3 环境 + 合宙 Luat Task 协程框架(sys.wait、sys.taskInit、sys.timerLoopStart 等すべて移植)
- 送信前処理层:用户在送信框中入力的データ,可以经过一段 Lua 脚本処理后再发出去——自动换行、自动转 Hex、自动组装 JSON,什么都能干
开源アドレス:https://github.com/chenxuuu/llcom
二、インストール与下载
三个官方渠道,选一个:
方式一:微软商店 在 Windows 自带的 Microsoft Store 中検索「LLCOM」即可インストール。优势是自动更新,缺点是需要微软账号。
方式二:便携版(免インストール) 从 底部 Download zip 包,解压到任意目录,双击 llcom.exe run。不上登録表、不写系统目录,放到 U 盘里到处带着跑。
方式三:GitHub Releases 从 https://github.com/chenxuuu/llcom/releases/latest 最新のダウンロード稳定版。适合需要特定版本的デバッグ场景。
系统要求:Windows 10 及以上,64 位。需要 .NET Runtime(Windows 10/11 通常已内置;もし提示缺少 .NET Runtime,从微软官网下载 .NET Desktop Runtime 8.0 即可)。
三、功能清单:比普通シリアルアシスタント多了什么
LLCOM 有普通シリアルアシスタント的すべての功能(シリアルポート選択、Baud rate/チェックビット設定、hexadecimal/ASCII 收发、日志表示),然后加了这些:
| Function | 説明 | Modbus デバッグ场景 |
|---|---|---|
| Lua 送信前処理 | 入力框内容经过 Lua 脚本処理后才送信 | 自动拼接 CRC、自动加駅からの住所 |
| 独立 Lua 脚本区 | 右侧可跑完全的 Lua Script,带タイマータイマー。和协程 | 写一个脚本自动ポーリングすべての駅から。レジスタ |
| 快捷送信栏(10 页×不限条数) | 保存常用命令,一键送信 | 把读不同レジスタ的フレーム放不同页 |
| シリアルポート监听 | モニタリング其他程序正在使用的シリアルポートデータ | 看某トップマシン软件发了什么フレーム |
| TCP/UDP/SSL クライアント側は和サーバー側。 | 可以通信不可过シリアルポート直接调 TCP | 测试 Modbus TCP port |
| MQTT 测试 | 集成 MQTT Published/订阅 | 验证ゲートウェイ的 MQTT 上行 |
| シリアルポート自動再接続。 | 断开后自动恢复连接 | 機器の電源オフと再起動不会断デバッグ |
| 自动日志保存 | 带时间戳的シリアルポート和 Lua 日志 | 调完すべてデバイス,回头看日志复盘 |
| 编码互转/乱码恢复 | GBK/UTF-8/Hex 之间转换 | 中文ストリング·ストリングデバッグ |
四、两大核心功能详解
4.1 送信前 Lua 処理
这是 LLCOM 最直接提效的功能。在「送信設定」→「送信処理脚本」里写一段 Lua,每次点送信时,你入力框里的元の文本先传给这段脚本,脚本処理后戻る的ストリング·ストリング才是真正发到シリアルポート的データ。
三个经典用法:
自动加 rn:很多シリアルデバイス要求命令以回车换行结尾——
return uartData .. "rn"
从此不用每次手打 0D 0A,入力命令直接点送信就完事。
十六进制自动转换:在入力框里写十六进制ストリング·ストリング(如 010300000001),脚本自动转成二进制——
return uartData:fromHex()
这是 Modbus RTU デバッグ的标配——你在入力框写 Modbus フレーム的十六进制表示,脚本转成二进制发给デバイス。
JSON 自动组装:入力 a,b,c,发出去的是 JSON——
json = require("JSON")
t = uartData:split(",")
return json:encode({
key1 = t[1],
key2 = t[2],
key3 = t[3],
})
快捷送信栏的每个按钮也同样走这个脚本,所以你可以建 10 页快捷送信,每页放不同的 Modbus Command,然后每次点按钮自动加 CRC 发出去——后面会给出完全な実装。
4.2 独立 Lua 脚本运行区
右边有个「Lua Script」エリア,你可以直接写完全的 Lua 程序并运行。这里的脚本拥有合宙 Luat Task 框架的すべて能力:
sys.taskInit()作成协程任务sys.wait(ms)延时待機中(不阻塞界面)sys.waitUntil()待機中メッセージ(事件驱动)sys.timerLoopStart()循环タイマータイマー。sys.publish()/sys.subscribe()メッセージ发布订阅
基础サンプル:收到シリアルポートデータ后自动回复——
uartReceive = function (data)
log.info("uartReceive", data)
sys.publish("UART", data)
end
sys.taskInit(function()
while true do
local _, udata = sys.waitUntil("UART")
log.info("task waitUntil", udata)
local sendResult = apiSendUartData("ok!")
log.info("uart send", sendResult)
end
end)
你甚至可以用 xlua 直接调用 C# 的 System.Net 类库发 HTTP Request——这意味着你可以写一个脚本,シリアルポートデータの受信后自动往クラウド·プラットフォーム推一条メッセージ。
五、核心 API 速查
LLCOM 的 Lua API ドキュメント位于 https://github.com/chenxuuu/llcom/blob/master/LuaApi.md ,这里列出 Modbus デバッグ最もよく使われる部分。
5.1 シリアルポート收发 API
-- 送信データ到シリアルポート(おすすめ新接口)
apiSend("uart", data)
-- 送信データ到シリアルポート(旧接口,后续会削除)
apiSendUartData(data)
-- 订阅シリアルポート受信回调
apiSetCb("uart", function(data)
log.info("received", data)
end)
-- キャンセル订阅
apiUnsetCb("uart", callback)
5.2 タイマータイマー。与协程 API
-- 作成任务
sys.taskInit(function()
while true do
-- 你的逻辑
sys.wait(1000) -- 等 1000ms
end
end)
-- 循环タイマータイマー。
sys.timerLoopStart(function()
log.info("tick", os.time())
end, 1000) -- 每 1000ms 触发一次
-- 停止タイマータイマー。
sys.timerStop(timerId)
5.3 ストリング·ストリング処理 API
-- 二进制转十六进制ストリング·ストリング
string.toHex("x01x03") -- 戻る "0103"
-- 十六进制ストリング·ストリング转二进制
("010300000001"):fromHex()
-- 分割ストリング·ストリング
("a,b,c"):split(",") -- 戻る {"a","b","c"}
5.4 日志 API
log.info("tag", "message")
log.warn("tag", "warning")
log.error("tag", "error")
すべての日志自动保存する。软件目录,并附带时间戳。
六、Modbus RTU 実践脚本
以下脚本可以直接コピー到 LLCOM 里运行。每个都是完全可用的。
6.1 智能送信:自动計算 Modbus CRC 并送信
Tired of 手动算 CRC?这是 LLCOM 在 Modbus デバッグ中最经典的应用。
在「送信処理脚本」中貼り付け:
-- Modbus CRC16 查表法(多項式 0xA001)
local crcTable = {}
for i = 0, 255 do
local crc = i
for _ = 1, 8 do
if (crc & 1) ~= 0 then
crc = (crc >> 1) ~ 0xA001
else
crc = crc >> 1
end
end
crcTable[i] = crc
end
local function modbusCrc16(data)
local crc = 0xFFFF
for i = 1, #data do
local b = string.byte(data, i)
crc = (crc >> 8) ~ crcTable[(crc ~ b) & 0xFF]
end
return crc
end
-- 主逻辑:把入力转成二进制,result CRC,拼上去,发给シリアルポート
local raw = uartData:fromHex() -- 入力框写十六进制,如 010300000001
local crc = modbusCrc16(raw)
local lo = crc & 0xFF
local hi = (crc >> 8) & 0xFF
return raw .. string.char(lo, hi)
现在你在入力框写 010300000001(slave address=01,function code=03,starting address=0000,レジスタの数=0001),点送信后实际发出去的是 01 03 00 00 00 01 84 0A——CRC 自动接在后面。
6.2 Modbus RTU 自动ポーリング脚本
跑在「Lua Script」エリア中,每隔 2 秒读一次スレーブ 01 保持レジスタです。 40001(address 0x0000),自动解析戻るデータ:
-- CRC 表(同上,这里省略,需要放在脚本最前面)
-- ...(和 6.1 的 crcTable 和 modbusCrc16 函数一样)
local function buildReadFrame(slave, startAddr, regCount)
local frame = string.char(slave, 0x03,
(startAddr >> 8) & 0xFF, startAddr & 0xFF,
(regCount >> 8) & 0xFF, regCount & 0xFF)
local crc = modbusCrc16(frame)
return frame .. string.char(crc & 0xFF, (crc >> 8) & 0xFF)
end
local function parseResponse(data)
if #data < 5 then
log.warn("modbus", "レスポンス短すぎる: " .. string.toHex(data))
return nil
end
local slave = string.byte(data, 1)
local func = string.byte(data, 2)
if func > 0x80 then
log.error("modbus", "Exception Code: 0x" .. string.format("%02X", string.byte(data, 3)))
return nil
end
local byteCount = string.byte(data, 3)
local values = {}
for i = 0, (byteCount / 2) - 1 do
local hi = string.byte(data, 4 + i * 2)
local lo = string.byte(data, 5 + i * 2)
values[i + 1] = (hi << 8) | lo
end
return {slave = slave, values = values}
end
-- 受信処理
apiSetCb("uart", function(data)
local result = parseResponse(data)
if result then
log.info("modbus", string.format("slave%02X Register value: %s",
result.slave, table.concat(result.values, ", ")))
end
end)
-- ポーリング任务
sys.taskInit(function()
while true do
local frame = buildReadFrame(0x01, 0x0000, 4)
log.info("modbus", "send: " .. string.toHex(frame))
apiSend("uart", frame)
sys.wait(2000)
end
end)
运行后日志出力类似:modbus send: 0103000000044409 → modbus slave01 Register value: 168, 2234, 0, 512。必要なし。人工干预,脚本自动ポーリング、自动解析、自动打日志。
6.3 多スレーブ扫描器
自动扫描バス上有哪些スレーブオンライン。挨个发読み取り命令,応答がある。的就是オンライン:
-- CRC 函数同上,省略
local function scanSlave(slaveId)
local frame = string.char(slaveId, 0x03, 0x00, 0x00, 0x00, 0x01)
local crc = modbusCrc16(frame)
frame = frame .. string.char(crc & 0xFF, (crc >> 8) & 0xFF)
apiSetCb("uart", function(data)
if #data >= 5 and string.byte(data, 1) == slaveId
and string.byte(data, 2) == 0x03 then
log.info("scan", "slave " .. slaveId .. " オンライン✓")
end
end)
apiSend("uart", frame)
log.info("scan", "扫描スレーブ " .. slaveId .. " ...")
end
sys.taskInit(function()
for slaveId = 1, 247 do
scanSlave(slaveId)
sys.wait(150) -- 等 150ms 给スレーブ応答時間
end
log.info("scan", "扫描完成")
end)
脚本跑完,日志里列出すべてのオンライン駅からの住所。大型项目中第一次上电后跑一遍这个,省去挨个看説明书的功夫。
6.4 レスポンスフレーム自动解析 + データ导出
收到 Modbus レスポンス后,自动解析并格式化为 CSV 行,方便コピー到 Excel 做趋势分析:
local results = {}
apiSetCb("uart", function(data)
if #data < 5 then return end
local slave = string.byte(data, 1)
local func = string.byte(data, 2)
if func >= 0x80 then
log.error("modbus", string.format("S=%02X Exception Code0x%02X", slave, string.byte(data, 3)))
return
end
local byteCount = string.byte(data, 3)
local row = {os.date("%H:%M:%S"), slave}
for i = 0, (byteCount / 2) - 1 do
local hi = string.byte(data, 4 + i * 2)
local lo = string.byte(data, 5 + i * 2)
table.insert(row, (hi << 8) | lo)
end
table.insert(results, table.concat(row, ","))
log.info("csv", table.concat(row, ","))
end)
-- ポーリング任务(每 5 秒采集一次,共采集 60 次 = 5 分)
sys.taskInit(function()
for i = 1, 60 do
local frame = string.char(0x01, 0x03, 0x00, 0x00, 0x00, 0x04)
local crc = modbusCrc16(frame)
apiSend("uart", frame .. string.char(crc & 0xFF, (crc >> 8) & 0xFF))
sys.wait(5000)
end
log.info("export", "采集完成,共 " .. #results .. " 行")
end)
日志輸出した。每一行都可以直接コピー貼り付け到 CSV 文件里用 Excel open。
七、TCP / MQTT / 监听 —— 其他 Modbus Scenarios
7.1 调 Modbus TCP
LLCOM 可以当 TCP クライアント側は连接 Modbus TCP device(デフォルトポート 502)。open TCP クライアント側は功能,填入デバイス IP ポートと港。 502,接続成功です。后像シリアルポート一样收发—— Modbus TCP フレーム和 RTU フレーム違いはありません只是少了 CRC、多了 MBAP 头(6 byte)。你可以在送信処理脚本里自动补上 MBAP 头:
-- 入力框写 000000000006010300000001
-- 表示:トランザクションID=0000,ProtocolID=0000,Length=0006,ユニットID=01,function code=03,starting address=0000,レジスタの数=0001
return uartData:fromHex()
或者脚本接到シリアルポート收到的 RTU フレーム,去掉 CRC 换成 MBAP 头后経由 TCP 通道发出去。具体见软件自带的 channel-demo.lua 例子。
7.2 MQTT 测试
もしデバッグ Modbus ゲートウェイ(DTU 把シリアルポートデータ转成 MQTT Published),LLCOM 的 MQTT 功能可以直接订阅ゲートウェイ发布的主题,看到ゲートウェイ发上来的データ是否正确。还能往控制主题发布メッセージ测试下行。
7.3 シリアルポート监听
「シリアルポート监听」功能可以抓取其他软件正在使用的シリアル通信——比如你的トップマシン软件在跟デバイス通信,你不想停掉它但想看它发了什么フレーム。LLCOM 可以静默抓取シリアルポートデータ并表示日志,不影响原来的通信。
八、和同类工具的对比
| Tools | 脚本サポート | Modbus CRC | 开源 | Price |
|---|---|---|---|---|
| LLCOM | Lua 5.3 + xlua + 协程框架 | 需要自己写(本記事已提供) | 是 | free |
| SSCOM | 无 | サポートなし。 | 否 | free |
| COMTool | 有限 | サポートなし。 | 是 | free |
| Modbus Poll | 无(专业 Modbus 固定功能) | 内置 | 否 | $129 |
| ModScan | 无 | 内置 | 否 | $69 |
LLCOM ではない「开箱即用」的 Modbus 专用工具,它是「可以变成 Modbus 专用工具的万能工具」。もし你只需要点一个按钮就发 Modbus フレーム,Modbus Poll 更直接。但もし你需要批量自動化测试、自定义解析、データ导出、和 MQTT/TCP 联调——LLCOM 是目前オープンソースツール里最灵活的。
九、common problems
Q: 收到乱码怎么办? チェックボーレート、チェックビット是否和デバイス一致。然后在 LLCOM 的受信区切换 Hex 表示——很多情况下「乱码」是因为デバイス戻る的是二进制データ但表示区按 ASCII 解析了。
Q: CRC 脚本发出去的フレームデバイス没反应? 最初のステップ:Confirm A/B 线没接反。第二のステップ:在受信区看デバイス有ない戻る任何バイト——もし完全ない,可能是デバイスアドレス正しくない。第三のステップ:もしデバイス戻る了データ但是異常コード。(function code + 0x80),看異常コードとは?——0x02 是「Illegal data address」,レジスター·アドレス可能不存在。
Q: 快捷送信栏的按钮能自动跑 CRC 脚本吗? 能。快捷送信栏的内容也会经过「送信処理脚本」——所以你写好了 CRC 自动計算脚本后,快捷送信栏只需要填裸フレーム(如 010300000001),点按钮自动加 CRC。
Q: Lua 脚本运行时报错了? 在脚本エリア最开头加一行 log.info("boot", "script started") 看脚本是否成功启动。主に。エラー是因为变量名拼错或调用了不存在的 API——日志区会表示 Lua エラー信息。
LLCOM 的核心价值ではない多了多少功能,而是把「debug」从重复体力劳动变成了可プログラミング的任务。你在现场花半小时写了三个脚本,接下来三天点几下鼠标データ自己就来了。这就是「给シリアルポート装上大脑」的真正含义——ではない LLCOM 有 AI,是 LLCOM 让你能把自己的デバッグ逻辑写成代码、然后让软件替你跑。
有問題再聊。
Leave a Reply