3.26. 程式碼中的 CAN 匯流排¶
machine.CAN 包裝了一個硬體 CAN 控制器。以匯流排 id 與位元速率將它啟動:
from machine import CAN
can = CAN(1, 500_000)
can.set_filters(None) # accept all incoming IDs
位元速率必須與匯流排上每一個其他節點完全相符——125_000、250_000、500_000 與 1_000_000 是常見的數值。set_filters() 用來設定控制器會放行哪些 ID;None 表示接受所有訊息(在建立連結時很有用,一旦匯流排繁忙就比較沒用了)。
3.26.1. 控制器內部¶
在相機的 Python 程式碼與匯流排之間,有三個硬體部件——TX 信箱(TX mailboxes)、一個接受過濾器(acceptance filter)以及 RX FIFO。其中每一個都對應到下面所使用的 CAN API 的一部分。
控制器位於軟體與匯流排之間的 TX 信箱、接受過濾器與 RX FIFO。¶
TX 信箱。 一小組硬體插槽(通常為三個),用來存放相機已透過
send()交付、但尚未送上匯流排的外送訊框。當匯流排閒置時,控制器會挑選 ID 數值最低(優先權最高)的信箱,並代其在匯流排上進行仲裁。相機並不挑選信箱;控制器會指派一個,並從send()回傳其索引。接受過濾器。 可設定的硬體,會把每個進入訊框的 ID 與一份樣式清單比對,並捨棄任何不相符者。通過的訊框會繼續進入 RX FIFO;被拒絕的訊框則由控制器丟棄,永遠不會到達 Python。
set_filters()用來設定這些樣式。RX FIFO。 一個先進先出(first-in, first-out)的佇列——最先進入的訊框也是最先出去的,就像售票櫃檯前排隊一樣。控制器會在收到訊框時把它們附加到佇列的尾端,而
recv()會以相同的順序從前端把它們取出。這個佇列之所以重要,是因為當 Python 正忙於其他事情時,匯流排會在背景中接住訊框;相機隨後再一次一個地把它們抽出而不會遺失任何一個,只要 FIFO 尚未溢位即可。
3.26.2. 送出一個訊框¶
send() 會把一個訊框排入佇列以待傳輸:
can.send(0x123, b"\x01\x02\x03\x04")
第一個引數是 ID(標準訊框為一個 11 位元整數);第二個是酬載(CAN Classic 為 0 至 8 位元組)。此呼叫會回傳一個小整數索引,標識該訊框所進入的硬體信箱。控制器會與匯流排上任何其他傳輸端進行仲裁,並視需要自動重新傳輸,無需軟體進一步協助。
對於擴充(29 位元)ID,請把 CAN.FLAG_EXT_ID 旗標以 OR 併入第三個引數:
can.send(0x18FF1234, b"hello", CAN.FLAG_EXT_ID)
3.26.3. 接收訊框¶
控制器啟動時並未安裝任何過濾器,並會丟棄每一個進入的訊框。在 recv() 能回傳任何東西之前,請先呼叫一次 set_filters()——最簡單的形式是 None,它會接受每一個 ID:
can.set_filters(None) # accept every frame
recv() 接著會回傳接收 FIFO 中的下一個訊框,若沒有任何訊框在等待則回傳 None:
msg = can.recv()
if msg is not None:
can_id, data, flags, errs = msg
print("got", hex(can_id), bytes(data))
匯流排會在背景中填滿 RX FIFO,因此主迴圈只要以其迭代的速度把它抽空即可。只要 FIFO 比抽取之間最長的間隔還深,就不會遺失任何訊框。
3.26.4. 過濾器¶
真正的匯流排通常充斥著相機並不關心的訊框。硬體過濾器讓控制器能在不想要的 ID 到達 FIFO 之前就把它們丟棄。set_filters() 接受一份 (id, mask, flags) 元組的清單;若某個訊框的 ID 在以 mask 遮罩後與設定的 id 相符,該訊框就會通過過濾器:
# Accept only IDs 0x100 - 0x10F (mask off the bottom 4 bits)
can.set_filters(((0x100, 0x7F0, 0),))
# Accept IDs 0x300 and 0x700 exactly
can.set_filters(((0x300, 0x7FF, 0),
(0x700, 0x7FF, 0)))
不相符的訊框會由控制器捨棄,永遠不會出現在 recv(),這同時節省了緩衝區空間與 CPU 時間。
3.26.5. 錯誤狀態與復原¶
真正的 CAN 匯流排會遭遇傳輸錯誤——對地短路、節點遺失、電氣雜訊損毀位元。控制器會維護兩個追蹤此行為的計數器:傳送錯誤計數器(Transmit Error Counter,TEC)與接收錯誤計數器(Receive Error Counter,REC),各自在控制器偵測到錯誤時遞增,並在成功傳輸後遞減。這些計數器的值會讓控制器進入以下四種狀態之一:
Error Active(TEC 與 REC 皆低於 96)。正常運作。當節點偵測到匯流排錯誤時,它會傳送一個顯性的主動錯誤訊框(active error frame),迫使每個其他節點捨棄進行中的訊框,好讓傳送端能重試。
Error Warning(任一計數器達到 96)。在匯流排上仍完全主動——警告狀態是一個軟體訊號,表示錯誤正在累積,並非行為上的改變。
Error Passive(任一計數器達到 128)。節點仍在匯流排上,但停止送出顯性錯誤訊框;錯誤現在改以被動(隱性)錯誤訊框來告知,如此一來有故障的節點便無法持續為其他所有人擾亂匯流排。
Bus Off(TEC 達到 256)。控制器已判定此節點太不可靠而不宜參與。它會自匯流排斷開、停止傳輸與確認,並保持脫離,直到軟體明確地重新啟動它為止。
前三個轉換完全是自動的——當計數器在成功的訊框之後遞減時,控制器會在無需介入的情況下自行往 Error Active 移回。
Bus Off 是唯一需要軟體動作的狀態。restart() 會重置控制器,並把它回復到 Error Active。一種典型的做法是從主迴圈檢查狀態,並在短暫延遲後重新啟動,以給匯流排時間穩定下來:
import time
from machine import CAN
can = CAN(1, 500_000)
can.set_filters(None)
while True:
if can.state() == CAN.STATE_BUS_OFF:
time.sleep_ms(100)
can.restart()
# ... rest of the loop
目前的計數器值可從 get_counters() 取得以供診斷——在一條原本安靜的匯流排上,TEC 若持續穩定攀升,通常指向布線、終端電阻或設定錯誤的位元速率。