Skip to Content
Lua 自動化位元運算

位元運算

一個暫存器的位元組(8 個位元,也就是 8 個 0 或 1)裡,常常只有其中幾個位元有意義, 其餘位元要維持原樣不能動。這一頁介紹怎麼用 bit.* 只操作你要的那幾個位元,以及 專門為此準備的 i2c.set()/i2c.clr()。

跟上一頁一樣,範例開頭會自己呼叫 i2c.addr(0x58) 連上裝置(細節見 I2C 讀寫),不需要再另外下任何指令:

nexus_tui.exe --dongle mock --batch --run bits.lua

bit.band/bit.bor/bit.bxor:位元的「且」「或」「互斥或」

這三個函式逐一比較兩個數字的每一個位元:

  • bit.band(a, b):兩邊都是 1 的位元才是 1,其餘是 0——常用來「只留下某幾個 位元、其他都清成 0」,例如 bit.band(0xFF, 0x0F) 只留下低 4 位,結果是 0x0F。
  • bit.bor(a, b):任一邊是 1 的位元就是 1——常用來「把某幾個位元設成 1,其他 保持不變」,例如 bit.bor(0x10, 0x01) 結果是 0x11。
  • bit.bxor(a, b):兩邊不一樣的位元才是 1,一樣的是 0——常用來「翻轉某幾個 位元」,例如 bit.bxor(0xFF, 0x0F) 把低 4 位翻轉,結果是 0xF0。

bit.lshift:位元左移

bit.lshift(1, 4) 把 1 的位元往左移動 4 格,結果是 0x10(十進位 16)—— 每左移一格,數值就變成兩倍。

bit.rshift:位元右移,讀出暫存器裡的一個欄位

bit.rshift(v, n) 把 v 的位元往右移動 n 格——跟 bit.lshift 方向相反。 單獨看只是把數字變小,但搭配 bit.band 是「讀出某一個欄位現在的值」最常見的 寫法:

local bit3 = bit.band(bit.rshift(v, 3), 1) -- v 的第 3 個位元(從 0 開始數)是 1,bit3 就是 1;是 0,bit3 就是 0

先用 bit.rshift(v, 3) 把第 3 個位元移到最右邊(第 0 個位元的位置),再用 bit.band(結果, 1) 只留下最右邊那一個位元——不管 v 其他位元是什麼,算出來的 結果都只反映第 3 個位元。

如果要讀的不是一個位元、而是連續好幾個位元組成的欄位,遮罩就不是 1,換成 「涵蓋整個欄位寬度」的值:

-- 讀出第 4~6 個位元(3 個位元寬)組成的欄位 local field = bit.band(bit.rshift(v, 4), 0x07) -- 0x07 是 3 個位元全 1 的遮罩

一樣是「先右移到最低位,再用遮罩只留下要的那幾個位元」,只是遮罩換成 3 個位元 全 1 的 0x07。

bit.bnot:為什麼結果是負數

bit.bnot(x) 會把 x 的每一個位元都反過來(0 變 1、1 變 0)。這裡有一個 容易讓人意外的地方:bit.bnot(0) 算出來的結果是 -1,不是你可能猜的 0xFFFFFFFF 這種很大的正數。

原因是這樣:bit.* 系列函式一律把數字當成剛好 32 個位元在算,就算你的值 只是一個 8 位元的暫存器內容也一樣。0 的 32 個位元全部是 0,反過來就是 32 個 1。 電腦習慣用「最左邊那個位元當作正負號」的方式表示負數(叫做「二補數」):最左邊 的位元是 1,就代表這是一個負數。32 個位元全部是 1,剛好就是這套規則裡代表 -1 的位元組合,所以 bit.bnot(0) == -1。

這不影響實際用法。 你在乎的是「哪幾個位元是 1、哪幾個是 0」,不是這個數字 被印出來是正是負。以一個 8 位元的暫存器值 v 來說,bit.band(v, bit.bnot(m)) (意思是「保留 v 裡不在遮罩 m 內的位元」)算出來的結果一定還是落在 0~255 之間的正常位元組值——因為 v 本身的高位元都是 0,跟任何值做 bit.band 之後高位元還是 0,符號位自然也是 0,不會是負數。

bit.tohex:印成十六進位文字

bit.tohex(0xAB, -2) -- 結果是 "AB"

第二個參數是位數:正數會印出小寫字母,負數會印出大寫字母,數字大小代表要印 幾位。-2 就是「印 2 位、用大寫」。

i2c.set 與 i2c.clr:只改暫存器裡的幾個位元

i2c.set(offset, mask) -- 把 mask 裡是 1 的那些位元,在暫存器裡設成 1 i2c.clr(offset, mask) -- 把 mask 裡是 1 的那些位元,在暫存器裡清成 0

這兩個函式會先讀出目前的值、只改 mask指定的那些位元、再寫回去——不在 mask 裡的位元完全不受影響。跟自己手動 i2c.read() 再算好新值 i2c.write() 回去比起來,這是最不容易出錯的作法。

範例:位元運算與 i2c.set/i2c.clr

-- 先設定這顆 IC 的 slave 位址,之後才能用 i2c.* 存取(見「I2C 讀寫」頁)。
i2c.addr(0x58)

-- bit.* 位元運算,搭配 i2c.set()/i2c.clr() 修改暫存器位元。
check(bit.band(0xFF, 0x0F) == 0x0F, "band masks low nibble")
check(bit.bor(0x10, 0x01) == 0x11, "bor combines bits")
check(bit.bxor(0xFF, 0x0F) == 0xF0, "bxor flips low nibble")
check(bit.bnot(0) == -1, "bnot(0) is -1 (signed 32-bit result)")
check(bit.lshift(1, 4) == 0x10, "lshift(1,4) == 0x10")
check(bit.tohex(0xAB, -2) == "AB", "tohex(-2) gives 2 uppercase digits")

i2c.write(0x30, 0x00)
i2c.set(0x30, 0x0F)
check(i2c.read(0x30) == 0x0F, "i2c.set ORs in the mask")
i2c.clr(0x30, 0x03)
check(i2c.read(0x30) == 0x0C, "i2c.clr ANDs the mask out")

log("info", "bits done")
預期輸出
CHECK OK: band masks low nibble
CHECK OK: bor combines bits
CHECK OK: bxor flips low nibble
CHECK OK: bnot(0) is -1 (signed 32-bit result)
CHECK OK: lshift(1,4) == 0x10
CHECK OK: tohex(-2) gives 2 uppercase digits
CHECK OK: i2c.set ORs in the mask
CHECK OK: i2c.clr ANDs the mask out
bits done

接下來

下一頁介紹 Bank 切換:同一個位移在不同「頁」上代表不同的暫存器時,怎麼用 腳本切頁。