Skip to Content
Lua 自動化錄製 I2C 操作

錄製 I2C 操作

前面幾頁都是自己一行一行寫腳本。但很多時候你手邊只有一個「已經在畫面上手動調對了」的 設定流程——先設位址、寫幾個暫存器、讀一下確認——要再照著寫成腳本很煩,也容易抄錯。 record 就是幫你做這件事:開始錄製之後,你照平常那樣在指令列(或畫面上)操作, 程式把每一個實際完成的 I2C 操作翻成 Lua,一條一條寫進一個檔案。停止錄製後, 這個檔案就是一支可以直接重播的腳本。

三個指令

指令作用
record start [<檔名>]開始錄製
record stop停止錄製,把檔案收尾
record status看現在有沒有在錄、存在哪、已經錄了幾條

檔案存到哪裡

  • 不給檔名:存成 logs/record_日期_時間.lua(例如 logs/record_20260929_141203.lua)。
  • 只給檔名或相對路徑(my_setup.lua、bringup/step1.lua):一律放在目前資料夾(執行程式時的工作目錄)底下的 logs/ 裡(跟寫檔案說的一樣,取決於你從哪裡啟動程式),需要的子資料夾會自動建立。相對路徑裡不能有 ..(會被拒絕), 避免檔案被寫到 logs/ 以外的地方。
  • 完整路徑(C:\lua\my_setup.lua):照你寫的位置存。
  • 檔案已經存在就拒絕:不會覆寫,也不會接在後面續寫。要重錄請換一個檔名或先自己刪掉舊檔。
  • 已經在錄製中再下一次 record start 也會被拒絕;沒在錄製時下 record stop 則會回報錯誤。
record start my_setup.lua addr 58 wr 10 01 rd 10 record stop

record start 成功時會印 REC: recording to <路徑>,record stop 會印 REC: stopped, <N> command(s) saved to <路徑>,同時各記一則 Log。<路徑> 是實際使用的那個, 照你給的樣子印:省略檔名或給相對路徑時是 logs/my_setup.lua 這種相對於目前資料夾的寫法, 給完整路徑就印完整路徑。

視窗版:File → Record I2C…

視窗版也可以不打指令:選單 File → Record I2C… 會開一個存檔對話框(預設在 logs/), 選好檔案就開始錄製,效果等同 record start <路徑>。錄製中這個選項會變成 Stop recording,選它就停止。細節見選單說明:錄製 I2C 操作。

怎麼知道現在有沒有在錄

錄製中,畫面最下面的狀態列會有一個紅色的標示:終端機版是 REC 檔名,視窗版是 ● REC 檔名。 沒有看到它就是沒在錄。直接關閉程式時,如果還在錄製,程式會先自動幫你收尾,已經錄下的部分不會遺失 (每一條都是錄到就立刻寫進檔案)。

什麼會被錄、翻成什麼

只有真的做了事、而且成功了的 I2C 操作會被錄。單純查看的指令(dump、mount、 不帶參數的 watch 之類)和畫面設定(layout、theme…)都不錄。

你下的指令錄成的 Lua
wr 10 01i2c.write(0x10, 0x01)
wr 10 01 02 03(連續多個值)i2c.write(0x10, {0x01, 0x02, 0x03})
set 11 0C 0C(要設的值等於整個遮罩)i2c.set(0x11, 0x0C)
set 11 0C 00(要設的值是 0)i2c.clr(0x11, 0x0C)
set 11 0C 04(其他情形)一行註解寫著原指令,接著一行「讀出來、清掉遮罩位元、換成新值、再寫回」的 i2c.write(...)
clr 11 04i2c.clr(0x11, 0x04)
rd 10local v = i2c.read(0x10) -- recorded: 0x01(註解是當時讀到的值)。只有第一條 rd 會寫 local v = ...,之後的 rd 寫成 v = ...(重新賦值)——Lua 一個函式裡最多只能宣告 200 個 local,錄很長的操作時每條都宣告會撞到上限
rd 10 4v = i2c.read(0x10, 4)(不是第一條 rd 時),註解列出讀到的 4 個位元組
bank 2i2c.bank(2)
addr 58i2c.addr(0x58)
bank reg F0 FFi2c.bank_reg(0xF0, 0xFF)
poll on / poll offpoll.on() / poll.off()(poll toggle 依切換之後的狀態錄成其中一個)
watch 12watch.add(0x12)
unwatch 12watch.remove(0x12)
unwatch allwatch.clear()

幾件事先講清楚:

  • 數字一律寫成 0x 開頭的十六進位,因為 Lua 裡的數字預設是十進位 (見I2C 讀寫最前面的提醒)。錄下來的檔案不會有「58 變成十進位 58」這種陷阱。
  • rd 只是把讀到的值記在註解裡,不做任何判定。重播時它只是再讀一次、什麼都不比。 想讓它變成「值不對就失敗」,見下面「把 rd 改成 check」。
  • 位址只在變了才寫:每條指令實際送去的位址如果跟前一條不同(包括你用 [<位址>] 臨時指定的),錄製器會先補一行 i2c.addr(...);分頁裡有多個掛載時會帶上第幾個掛載的 slot 參數。你不用自己想「這條要不要重設位址」。
  • 頁(Bank)會盡量還原:如果這個位址設定過頁碼暫存器,錄製器在你用 [<位址>] 臨時改用別的位址、下一條又切回來時,會補回 i2c.bank_reg(...) 和 i2c.bank(<頁碼>) (掛載本身沒換位址,頁碼設定原封不動),讓重播從全新狀態開始時每一條都落在 你當時所在的那一頁(見Bank 切換)。但你用 addr 指令真的換掉掛載位址就不同了: 程式會把該掛載的頁碼設定整個清掉,換回原位址也不會還原,錄製器因此也不會補回舊的頁碼設定。 位移超過 0xFF 的存取(例如 wr 0110 01 代表第 1 頁的 0x10)會被拆成 i2c.bank(1)、以 0x10 存取、再切回原本那一頁。

錄不進去的,會變成註解

錄製器不會悄悄丟掉任何一條它看到、卻翻不出來的指令——它們會以註解留在檔案裡, 你重播時不會執行,但讀檔案時一眼看得出「這裡少做了一件事」。其中開頭是 not recorded 的註解 (下表第二列起,FAILED 與 still pending 不算)第一次出現時,整次錄製會在 Log 裡記一則 WARN 提醒你,之後不再重複。

註解開頭意思
-- FAILED (<原因>): <指令>指令送出了但失敗(例如裝置沒回應)。失敗的操作不會被當成成功錄下來
-- not recorded (no Lua equivalent): trigger ...trigger、password <IC>、rd16/wr16、指定面板的 poll ... @p<N> 沒有對應的 Lua 寫法。password 只會寫 IC 名稱,絕不寫出密碼內容
-- not recorded (other tab): <指令>這條是在別的分頁下的。一支腳本只綁一個分頁(開始錄製時所在的那個),混進別的分頁的操作會讓重播作用在錯的匯流排上。例外:poll on / poll off 是全域的,從任何分頁下都照錄
-- not recorded (no Lua equivalent on 16-bit mount): <指令>掛載使用 16-bit 位址,手動指令與 Lua 的寫法在這種掛載上行為不同,照錄會悄悄改變硬體行為
-- not recorded (bank state unknown for offset above 0xFF): <指令>位移超過 0xFF 但程式不知道你原本在哪一頁,沒辦法保證錄出來的腳本會把頁切回去
-- not recorded (recorder queue full, order may differ): <指令>你連續送出的長時間指令太多,錄製器排不下;為了不寫出順序可能錯亂的腳本而放棄這一條(極少見)
-- still pending at stop: <指令>你按 record stop 的時候,這條指令還在執行、沒有結果,所以不知道成敗

一支腳本裡有這些註解,代表重播出來的結果跟你當時手動做的不完全一樣。 重播前先搜尋一下 FAILED 與 not recorded,決定要不要手動補上。

檔頭與時間註解

錄出來的檔案最前面是三行說明:錄製時間、用哪個 dongle 與哪個分頁、以及重播用的指令樣板。 如果開始錄製的當下這個分頁已經設好位址,接下來會有 i2c.addr(...)(有頁碼暫存器的話還有 i2c.bank_reg(...)、i2c.bank(...)),把當時的狀態「拍一張快照」放在腳本開頭;這樣不論 你後面有沒有再下 addr,重播都是從你開始錄的那個狀態出發。

每條指令前面都有一行 -- +12.4s,是距離開始錄製過了幾秒。錄製器不會幫你插 delay_ms():你思考、打字的時間不是硬體需要的等待,把它照抄進去只會讓腳本白白慢下來。 如果某一步之間硬體真的需要等(例如寫了電源設定要等它穩定),自己在那裡加一行 delay_ms(...)(見等待與中止)。

檔尾是 -- End of recording: N commands;N 包含 FAILED、not recorded 這些註解條目。

所有註解都是英文 ASCII,不管你的系統語言為何。

範例:錄出來的樣子

下面是一次實際錄製的結果。當時下的指令是:

nexus_tui.exe --dongle mock --batch --cmd "record start C:\lua\record_demo.lua" --cmd "addr 58" --cmd "wr 10 01" --cmd "set 11 0C 0C" --cmd "clr 11 04" --cmd "rd 10" --cmd "record stop"

(先說一件事:下面範例的 +2.1s 之類的時間是為了好讀手動改過的——用 --cmd 批次下指令時, 每條之間幾乎沒有間隔,時間幾乎都是 +0.0s。你手動輸入時看到的就是像這樣有間隔的數字。)

record_demo.lua下載 record_demo.lua
-- Recorded by Nexus 'record' on 2026-09-29 12:13:56
-- Dongle: mock   Tab: 1
-- Run: nexus_tui.exe --dongle mock --batch --run <this file>
-- +2.1s
i2c.addr(0x58)
-- +5.8s
i2c.write(0x10, 0x01)
-- +9.4s
i2c.set(0x11, 0x0C)
-- +14.0s
i2c.clr(0x11, 0x04)
-- +17.6s
local v = i2c.read(0x10)   -- recorded: 0x01
-- End of recording: 5 commands

(這支腳本不印任何東西)

重播它:

nexus_tui.exe --dongle mock --batch --run C:\lua\record_demo.lua

路徑一樣要用完整路徑,理由見「第一次跑腳本」。重播沒有任何輸出——錄下來的腳本只做動作, 不印東西,所以上面會註明「這支腳本不印任何東西」,只要 Exit Code 是 0 就代表整支腳本跑完、沒有出錯。 (腳本檔頭那行 -- Run: ... 是提示你怎麼重播;接真實硬體時,記得把 --dongle mock 換成真正的 dongle 名稱或整個拿掉,理由見整合進批次流程。)

把 rd 改成 check

rd 被錄成「讀一次、把當時的值寫在註解裡」,並沒有判定。錄完之後,你很可能想把其中幾個讀取 變成驗證:重播時值不對就標成失敗。這是一個手動的小改動:把註解裡的值抄成 check()(見判定與結束摘要):

local v = i2c.read(0x10) -- recorded: 0x01

改成

local v = i2c.read(0x10) check(v == 0x01, "register 0x10 reads back 0x01")

改完的整支腳本:

record_demo_check.lua下載 record_demo_check.lua
-- Edited from record_demo.lua: the recorded 'rd' became a check().
i2c.addr(0x58)
i2c.write(0x10, 0x01)
i2c.set(0x11, 0x0C)
i2c.clr(0x11, 0x04)
local v = i2c.read(0x10)
check(v == 0x01, "register 0x10 reads back 0x01")
預期輸出
CHECK OK: register 0x10 reads back 0x01

這樣重播時會多出一行 CHECK OK:,數值不對則是 CHECK FAIL:,批次模式的 Exit Code 也會跟著變 (對照表見判定與結束摘要)。

小提醒

  • 錄製只記你下的指令。監看(watch)觸發時自動執行的動作、Plugin 管理器自己的安裝/移除, 都不會被錄進去;run 執行腳本也不錄(腳本裡做的事你已經有腳本了)。
  • 每一條指令要等完成才寫入檔案,所以檔案裡的順序就是你送出指令的順序; 一起送出多條耗時的指令時,寫檔仍然照你送出的先後。
  • 錄好的檔案是普通的 Lua 檔,可以用任何編輯器打開、刪掉不要的行、加上 check() 與 delay_ms(), 再把它當成正式的測試腳本使用。

接下來

下一頁介紹 gpio.* 與 uart.*。