class CAN -- 控制器區域網路(Controller Area Network)通訊協定

CAN 是一種雙線串列通訊協定,用於在連接至共用匯流排的一個或多個節點之間可靠地即時傳遞訊息。CAN 2.0 已標準化於 ISO-11898 中,現在也稱為 CAN Classic。

另外還有一種較新且向下相容的協定,名為 CAN FD(具彈性資料速率的 CAN)。machine.CAN 驅動程式目前不支援 CAN FD 功能;若您需要 CAN FD,請在 STM32 上使用 pyb.CAN

CAN 支援需要一個控制器(通常是內建的微控制器周邊裝置),以及一個外部收發器,用於將訊號電位轉換到 CAN 匯流排上。

可用於 STM32 OpenMV 相機(M4 / M7 / H7 / H7 Plus / Pure Thermal / N6,以及內建收發器接線的 Arduino 品牌變體)。OpenMV Cam RT1062(mimxrt 移植版)或 OpenMV Cam AE3(alif 移植版)尚未支援。

machine.CAN 介面是一個 低階基本 的 CAN 訊息傳遞介面,它將 CAN 控制器抽象化為一個用於傳送訊息的傳出優先佇列、一個用於接收訊息的傳入佇列,以及用於回報錯誤的機制。

備註

計畫中的 canaiocan micropython-lib 模組將會是在 MicroPython 中使用 CAN 的建議方式。

建構函式

class machine.CAN(id: int, *args, **kwargs)

建構一個具有給定 id 的 CAN 控制器物件:

  • id 用於識別特定的 CAN 控制器物件;它與開發板和移植版有關。

  • 所有其他引數都會傳遞給 CAN.init()。至少必須提供一個引數(bitrate)。

本類別未來的版本可能也會在此接受用於設定硬體的移植版特定關鍵字引數。目前尚未實作任何此類關鍵字引數。

範例

以 500kbps 的位元速率建構並初始化 CAN 控制器 1:

from machine import CAN
can = CAN(1, 500_000)

方法

init(bitrate: int, mode: int = CAN.MODE_NORMAL, sample_point: int = 75, sjw: int = 1, tseg1: int | None = None, tseg2: int | None = None) None

以給定的參數初始化 CAN 匯流排:

  • bitrate 是所需的匯流排位元速率,以每秒位元數為單位。

  • mode模式 下所示值之一,用於指定所需的運作模式。預設為匯流排上的「normal」運作模式。

接下來的參數為選用,與 CAN 位元時序有關。在大多數情況下,您可以讓這些參數維持預設值:

  • sample_point 是資料位元時間的整數百分比。它指定位元取樣相對於整個標稱位元時間的位置。CAN 驅動程式將據此計算參數。若已設定 tseg1tseg2,則此參數會被忽略。

  • sjw 是標稱位元的重新同步跳躍寬度,以時間量子(time quanta)為單位;對於 classic CAN,其值可介於 1 到 4(含)之間。

  • tseg1 以標稱位元的時間量子為單位定義取樣點的位置;對於 classic CAN,其值可介於 1 到 16(含)之間。這是 ISO-11898 標準中所定義的 Prop_SegPhase_Seg1 階段之總和。若已設定此值,則 tseg2 也必須設定,且 sample_point 會被忽略。

  • tseg2 以標稱位元的時間量子為單位定義傳送點的位置;對於 classic CAN,其值可介於 1 到 8(含)之間。這對應於 ISO-11898 標準中的 Phase_Seg2。若已設定此值,則 tseg1 也必須設定。

若指定了這些引數,則 CAN 控制器會針對所需的 bitrate 以及指定的每位元時間量子總數正確設定。若這些值全部提供,則 tseg1tseg2 值會覆寫 sample_point 引數。

備註

個別控制器硬體可能對這些參數的有效值有額外限制,若給定的值不受支援,將會引發 ValueError

備註

特定的控制器硬體可能會接受額外的選用關鍵字參數,以支援過取樣(oversampling)等硬體特定功能。

set_filters(filters: list | tuple | None) None

在 CAN 控制器中設定接收篩選器。filters 可以是:

  • None 以接受所有傳入訊息,或

  • []() 以停用所有訊息接收,或

  • 一個包含一個或多個項目、用於定義篩選條件的可迭代物件。每個項目應為包含三個元素的 tuple 或 list:

    • identifier 是一個 CAN 識別碼(int)。

    • bit_mask 是 CAN 識別碼欄位中位元的位元遮罩(int)。

    • flags 是一個整數,其中設定了 訊息旗標 中定義的零個或多個位元。它指定傳入訊息需要符合的屬性。並非所有控制器都支援對所有旗標進行篩選,若請求了不支援的旗標,將會引發 ValueError

若訊息識別碼與篩選器 identifier 值之間,在 bit_mask 所遮罩的位元相符,且篩選器中設定的旗標與傳入訊息相符,則該傳入訊息會被接受。

若 flags 中設定了 CAN.FLAG_EXT_ID 位元,則篩選器僅比對延伸 CAN ID。若未設定 CAN.FLAG_EXT_ID 位元,則篩選器僅比對標準 CAN ID。

所有篩選器在控制器中會以 OR 運算組合在一起。對 filters 引數傳入空的 list 或 tuple 表示不會接收任何訊息。

某些 CAN 控制器要求每個篩選器只能關聯一個接收 FIFO。在這些情況下,引數中的篩選器項目會以循環配置(round-robin)方式分配給可用的 FIFO。本驅動程式在接收 IRQ 中不區分各個 FIFO。

備註

若呼叫者傳入的可迭代物件項目數超過 CAN.FILTERS_MAX,將會引發 ValueError

備註

identifierbit_mask 超出指定 ID 類型的有效範圍,將會引發原因為「invalid id」的 ValueError

範例

接收所有傳入訊息:

can.set_filters(None)

僅接收標準 ID 值為 0x301 和 0x700 的訊息:

can.set_filters(((0x301, 0x7FF, 0),
                 (0x700, 0x7FF, 0)))

僅接收標準 ID 值介於 0x300-0x3FF 範圍內的訊息,以及延伸 ID 值為 0x50700 的訊息:

can.set_filters(((0x300, 0x700, 0),
                 (0x50700, 0x1FFF_FFFF, CAN.FLAG_EXT_ID)))
FILTERS_MAX: int

讀取此硬體控制器所支援的接收篩選器最大數量的常數值。

請注意,某些控制器對於使用中的篩選器數量可能有更複雜的硬體限制(例如,分別計算標準 ID 與延伸 ID 篩選器的數量)。在這些情況下,即使未超出 FILTERS_MAX 限制,CAN.set_filters 仍可能引發 ValueError

send(id: int, data: bytes, flags: int = 0) int | None

將一則新的 CAN 訊息複製到控制器的硬體傳送佇列中,以便傳送至匯流排。傳送佇列是依 CAN 識別碼優先權排序的優先佇列(數值較小的識別碼具有較高的優先權)。

  • id 是一個整數 CAN 識別碼值。

  • data 是一個 bytes 物件(或類似物件),包含 CAN 訊息資料,或描述遠端傳輸請求(見下文)。

  • flags 是一個整數,其中設定了 訊息旗標 中定義的零個或多個位元,用於指定傳出 CAN 訊息的屬性(延伸 ID、遠端傳輸請求等)。

若訊息成功排入佇列以傳送至匯流排,函式會傳回介於 0CAN.TX_QUEUE_LEN(不含)範圍內的整數。此值是訊息排入待送的傳送緩衝區索引,可供 CAN.cancel_send 函式及 CAN.IRQ_TX 事件使用。

若佇列已滿,則傳送會失敗並傳回 None

若提供的 id 值與傳送佇列中現有訊息具有相同優先權,且 CAN 控制器硬體無法保證相同 ID 的訊息會依照加入佇列的順序傳送至匯流排,則傳送也可能失敗並傳回 None。若仍要將訊息排入佇列,請在 flags 引數中傳入 CAN.FLAG_UNORDERED 旗標。此旗標表示可以以任意順序將相同 CAN ID 的訊息傳送至匯流排。

若控制器處於「Bus Off」錯誤狀態或已停用,則呼叫此函式將會引發 OSError

備註

此刻意採用的低階實作設計,是為了讓呼叫者能夠建立傳出訊息的軟體佇列。

重要

CAN「傳送佇列」並非 FIFO 佇列,而是依優先權排序的,且雖然它最多可容納 CAN.TX_QUEUE_LEN 個項目,但對於可同時排入佇列的訊息可能還有其他硬體限制。

遠端傳輸請求

若在 flags 引數中設定了 CAN.FLAG_RTR 位元,則控制器將傳送遠端傳輸請求而非訊息。在此情況下,data 引數的內容會被忽略。控制器將傳送一個 DLC 長度欄位等於 data 引數長度的請求。

範例

嘗試傳送一則酬載為三個位元組 0a0b0c 且標準 ID 為 0x200 的訊息:

can.send(0x200, b"\x0a\x0b\x0c", 0)

嘗試傳送一則酬載為空且延伸 ID 為 0x180008 的訊息。指出在已有其他相同 ID 的訊息排隊待送的情況下,控制器可以以任意順序傳送此 ID 的訊息:

can.send(0x180008, b"", can.FLAG_EXT_ID | can.FLAG_UNORDERED)

嘗試傳送一則長度為 8 個位元組且標準 ID 為 0x555 的遠端傳輸請求:

can.send(0x555, b" " * 8, can.FLAG_RTR)
recv(arg: list | None = None) list | None

根據由 CAN.set_filters() 設定的篩選器,傳回一則已被控制器接收的 CAN 訊息。

此函式接受單一選用引數,若有提供,則它必須是一個至少包含 4 個元素的 list,其中第二個元素為一個 memoryview 物件,該物件指向一個容量足以容納任何已接收 CAN 訊息的 bytearray 或類似物件(CAN Classic 為 8 個位元組,CAN FD 為 64 個位元組)。所提供的 list 將作為成功結果傳回,並避免在函式內部進行記憶體配置。

若 CAN 控制器尚未接收到任何訊息,則此函式傳回 None

備註

必須先呼叫 CAN.set_filters,控制器才能接收任何訊息。若要接收所有訊息,請呼叫 set_filters(None)

若 CAN 控制器已接收到訊息,則此函式傳回一個包含 4 個元素的 list:

  • 索引 0 是所接收訊息的 CAN ID,為整數。

  • 索引 1 是一個 memoryview,提供對所接收訊息資料的存取。

    • 若未提供 arg,則此為一個 memoryview,存放所接收到的位元組。此 memoryview 由一個足以容納任何已接收 CAN 訊息、新配置的 bytearray 作為後盾。這使得結果可以安全地重複用作未來的 arg,以節省記憶體配置。

    • 若有提供 arg,則所提供的 memoryview 將被調整大小,以恰好容納所接收到的位元組。呼叫者有責任確保 memoryview 的後盾物件能夠容納任何長度的 CAN 訊息。

  • 索引 2 是一個整數,其中設定了 訊息旗標 中定義的零個或多個位元。它指出所接收訊息的相關中繼資料。

  • 索引 3 是一個整數,其中設定了 接收錯誤旗標 中定義的零個或多個位元。任何非零值都表示接收 CAN 訊息時可能存在的問題。每次此函式傳回時,這些旗標都會在控制器內部重設。

遠端傳輸請求

若接收到遠端傳輸請求,則索引 2 中會設定 CAN.FLAG_RTR 位元,且索引 1 的 memoryview 將全部為零,其長度等於所接收請求的 DLC 欄位。

範例
can.set_filters(None)   # receive all
while True:
    res = can.recv()
    if res:
        can_id, data, flags, errs = res
        print("Received", hex(can_id), data.hex(), hex(flags), hex(errs))
    else:
        time.sleep_ms(1)  # not a good pattern, use the irq instead!
irq(handler: Callable[[CAN], None] | None = None, trigger: int = 0, hard: bool = False) None

設定一個中斷 handler 函式,當 trigger 中標示的一個或多個事件發生時呼叫它。

  • handler 是當中斷事件觸發時要呼叫的函式。該處理常式必須恰好接受一個引數,即 CAN 實例。

  • trigger 設定可產生中斷的事件。可能的值是下列一個或多個項目的遮罩:

    • CAN.IRQ_RX 事件會在 CAN 控制器已將至少一則訊息接收至其 RX FIFO 後發生(這表示 CAN.recv() 將會成功傳回)。

    • CAN.IRQ_TX 事件會在 CAN 控制器成功將一則訊息傳送至 CAN 匯流排或傳送訊息失敗後發生。此觸發對處理常式有額外要求,詳見 IRQ 旗標

    • CAN.IRQ_STATE 事件會在 CAN 控制器轉換至更嚴重的錯誤狀態時發生。請呼叫 CAN.state() 以取得更新後的狀態。

  • hard 若為 True,則使用硬體中斷。這可減少 CAN 控制器事件與處理常式被呼叫之間的延遲。硬體中斷處理常式不可配置記憶體;請見 撰寫中斷處理常式

傳回一個 irq 物件。若呼叫時不帶引數,則會傳回先前設定的 irq 物件。

範例請見 IRQ 旗標

cancel_send(index: int) bool

請求 CAN 控制器取消將某則訊息傳送至匯流排。

引數 index 用於識別單一傳送緩衝區。它應為介於 0CAN.TX_QUEUE_LEN(不含)範圍內的整數。一般來說,這會是先前由 CAN.send() 傳回的值。

若此緩衝區中有訊息正待傳送且傳送已被取消,則結果為 True

否則結果為 False(表示此緩衝區中沒有待傳送的訊息,或傳送已經成功)。

應使用 IRQ 事件 CAN.IRQ_TX 來判定訊息是否確實已傳送,但請注意,若取消傳送後又使用同一個緩衝區傳送另一則訊息(尤其是當 CAN 控制器 IRQ 並非「hard」時),可能存在競態條件。

state() int

傳回一個整數值,指出控制器目前的狀態。該值將為 狀態 中定義的值之一。

若匯流排恢復,較低嚴重程度的錯誤狀態可能會自動清除,但 CAN.STATE_BUS_OFF 狀態只能透過呼叫 CAN.restart() 來復原。

get_counters(list: list | None = None, /) list

傳回控制器的錯誤計數器值。結果是一個包含八個值的 list。若指定了選用的 list 參數,則所提供的 list 物件會被更新並作為結果傳回,以避免配置。

list 項目為:

  • TEC(傳送錯誤計數器,Transmit Error Counter)值

  • REC(接收錯誤計數器,Receive Error Counter)值

  • 控制器從 Active 狀態進入 Warning 狀態的次數。

  • 控制器從 Warning 狀態進入 Error Passive 狀態的次數。

  • 控制器從 Error Passive 狀態進入 Bus Off 狀態的次數。

  • 硬體佇列中待傳送 TX 訊息的總數。

  • 硬體佇列中待接收 RX 訊息的總數。

  • 發生 RX 溢位(overrun)的次數。

備註

視控制器而定,這些值在達到某個數值後可能會溢位回到 0。

備註

若控制器不支援某個特定計數器,則該 list 元素會傳回 None

get_timings(list: list | None = None, /) list

傳回一個 list,其元素指出 CAN 控制器中目前設定的時序。這可用於為除錯目的驗證時序。結果是一個包含六個值的 list。若指定了選用的 list 參數,則所提供的 list 物件會被更新並作為結果傳回,以避免配置。

list 項目為:

  • 控制器所使用的確切位元速率。由於需量子化以符合硬體限制,此值可能與傳遞給 CAN.init()bitrate 引數不同。

  • 標稱位元的重新同步跳躍寬度(SJW),以時間量子為單位。其意義與 CAN.init()sjw 參數相同。

  • 標稱位元的取樣點位置,以時間量子為單位。其意義與 CAN.init()tseg1 參數相同。

  • 標稱位元的傳送點位置,以時間量子為單位。其意義與 CAN.init()tseg2 參數相同。

  • CAN FD 時序資訊。對於不支援 CAN FD 的控制器,或若 CAN FD 未初始化,則為 None。否則為一個包含四個元素的巢狀 list,對應於上述項目,但適用於 CAN FD 的 BRS 功能。

  • 選用的控制器特定時序資訊。視控制器而定,若控制器未回報任何資訊則為 None,否則為一個長度固定的 list,其元素為特定硬體控制器所專有。

備註

若尚未呼叫 CAN.init(),此函式仍會傳回結果,但結果取決於控制器內部狀態,可能不準確。

restart() None

使控制器離開 STATE_BUS_OFF,但不清除任何其他內部狀態。同時也會清除部分錯誤計數器(一律包含各錯誤狀態的進入次數,並可能依控制器而定包含 TEC 與 REC)。

呼叫此函式也會取消任何等待傳送的訊息。不會為這些訊息傳遞 IRQ_TX 中斷。

請注意,視控制器硬體是否將 TEC 與 REC 歸零而定,此函式可能會也可能不會使控制器離開「Error Passive」狀態。

deinit() None

解除初始化先前處於活躍狀態的 CAN 實例。所有待處理的訊息(傳送與接收)都會被丟棄,且控制器會停止在匯流排上互動。若要再次使用此實例,請呼叫 CAN.init()

呼叫此函式不會觸發任何 IRQ_TXIRQ_RX 中斷。

另請參閱 CAN.restart()

常數

TX_QUEUE_LEN: int

可在控制器傳出硬體訊息佇列中排隊的 CAN 訊息最大數量。CAN.send()CAN.cancel_send()IRQ 旗標 所使用的「傳送緩衝區索引」將位於此範圍內。

模式

這些值代表控制器的運作模式,會傳遞給 CAN.init()。並非所有控制器都支援所有模式。

變更執行中控制器的模式需要呼叫 CAN.deinit(),然後以新模式再次呼叫 CAN.init()

MODE_NORMAL: int

控制器作為標準 CAN 網路節點處於活躍狀態(會確認有效訊息,並可能視其目前的 State 而傳送錯誤)。

MODE_SLEEP: int

CAN 控制器在低功耗模式下處於睡眠狀態。視控制器而定,這可能支援在接收到 CAN 流量時喚醒控制器並轉換至 CAN.MODE_NORMAL

MODE_LOOPBACK: int

一種測試模式。CAN 控制器仍連接至外部匯流排,但也會接收自己傳送的訊息並忽略任何 ACK 錯誤。

MODE_SILENT: int

CAN 控制器接收訊息但不與 CAN 匯流排互動(包括傳送 ACK、錯誤等)。

MODE_SILENT_LOOPBACK: int

一種完全不需要連接 CAN 收發器的測試模式。CAN 控制器接收自己傳送的訊息,完全不與 CAN 匯流排互動。CAN 的 TX 與 RX 接腳保持閒置。

狀態

這些值由 CAN.state() 傳回,反映 CAN 控制器的錯誤狀態:

STATE_STOPPED: int

控制器尚未初始化。

STATE_ACTIVE: int

控制器處於活躍狀態,且 TECREC 錯誤計數器皆低於 96 的警告閾值。請見 CAN.get_counters()

STATE_WARNING: int

控制器處於活躍狀態,但 TECREC 錯誤計數器中至少有一個介於 96 與 127 之間。請見 CAN.get_counters()

STATE_PASSIVE: int

控制器處於「Error Passive」狀態,表示它不再向匯流排傳送主動錯誤,但在其他方面仍可運作。當 TECREC 錯誤計數器中至少有一個達到 128 或以上、但 TEC 小於 255 時,會進入此狀態。請見 CAN.get_counters()

STATE_BUS_OFF: int

控制器處於 Bus-Off 狀態,表示 TEC 錯誤計數器大於 255。在此狀態下,CAN 控制器不會與匯流排互動,需透過 CAN.restart() 重新啟動才能繼續。

訊息旗標

這些值代表 CAN 訊息的相關中繼資料。CAN.send()CAN.recv()CAN.set_filters() 函式會接受或傳回一個由零個或多個這些旗標以位元 OR 運算組合而成的整數值。

FLAG_RTR: int

表示訊息為遠端傳輸請求。

FLAG_EXT_ID: int

若設定,表示訊息識別碼為延伸式(29 位元)。若未設定,表示訊息識別碼為標準式(11 位元)。

FLAG_UNORDERED: int

若在 CAN.send()flags 引數中設定,表示可以以任意順序將相同 CAN ID 的訊息傳送至匯流排。

否則,若控制器硬體無法強制排序,嘗試將多則相同 ID 的訊息排入佇列可能會導致 CAN.send() 失敗。

此旗標永遠不會在接收的訊息上設定,且會被 CAN.set_filters() 忽略。

接收錯誤旗標

CAN.recv() 的結果包含一個由零個或多個這些旗標以位元 OR 運算組合而成的整數值。若設定,這些旗標表示接收 CAN 訊息時可能存在的一般性問題。

RECV_ERR_FULL: int

接收此訊息的硬體 FIFO 已滿,額外的傳入訊息可能會遺失。

RECV_ERR_OVERRUN: int

接收此訊息的硬體 FIFO 已滿,且已有一個或多個傳入訊息遺失。

IRQ 值

IRQ_RX: int

傳遞給 irq()trigger 引數,使處理常式在每次 CAN 控制器將一則完整訊息接收至 RX FIFO 時觸發。在處理常式內部,請以 recv() 讀取訊息。

IRQ_TX: int

傳遞給 irq()trigger 引數,使處理常式在每次 CAN 控制器完成一次傳送嘗試(成功或失敗)時觸發。在處理常式內部,請使用下列額外位元來得知是哪個信箱(mailbox)完成以及是否失敗——請見 IRQ 旗標

IRQ_STATE: int

傳遞給 irq()trigger 引數,使處理常式在每次控制器於各 STATE_* 值之間轉換(active / warning / passive / bus-off)時觸發。請在處理常式內部使用 state() 來讀取新狀態。

IRQ_TX_FAILED: int

IRQ_TX 事件觸發時,可能會在 irq().flags() 中設定的狀態旗標。表示傳送嘗試失敗(通常是因為呼叫了 cancel_send(),或控制器進入了錯誤狀態)。

IRQ_TX_IDX_SHIFT: int

IRQ_TX 事件期間,傳送信箱索引欄位在 irq().flags() 值中的位元位置。信箱索引以 (flags >> IRQ_TX_IDX_SHIFT) & IRQ_TX_IDX_MASK 提取。

IRQ_TX_IDX_MASK: int

IRQ_TX 事件期間,傳送信箱索引欄位在 irq().flags() 值中的位元遮罩。所提取的索引與對應 send() 呼叫所傳回的整數相符(一個介於 0TX_QUEUE_LEN 範圍內的 int)。

IRQ 旗標

呼叫 CAN.irq() 會註冊一個中斷處理常式,其觸發條件為 CAN.IRQ_RXCAN.IRQ_TXCAN.IRQ_STATE 中的一個或多個。

此函式傳回一個 IRQ 物件,對此物件呼叫 flags() 函式會傳回一個整數,指出是哪個或哪些觸發事件引發了中斷。CAN IRQ 處理常式應重複呼叫 flags() 函式,直到它傳回 0 為止。

flags() 函式傳回時設定了 CAN.IRQ_TX 位元,處理常式還可以檢查結果中的下列旗標位元,以取得關於 TX 事件的額外資訊:

  • 若傳送失敗,則會設定 CAN.IRQ_TX_FAILED 位元。通常只有在呼叫了 CAN.cancel_send() 時才會發生,不過控制器進入錯誤狀態時也可能發生。

  • CAN.IRQ_TX_IDX_MASK << CAN.IRQ_TX_IDX_SHIFT 是 flags 值中以位元遮罩標示的區域,存放產生該事件的傳送緩衝區索引。這會是一個介於 0CAN.TX_QUEUE_LEN(不含)範圍內的整數,並與先前呼叫 CAN.send() 的結果相符。

IRQ_TX 範例

from machine import CAN

def irq_send(can):
    while flags := can.irq().flags():
        if flags & can.IRQ_TX:
            idx = (flags >> can.IRQ_TX_IDX_SHIFT) & can.IRQ_TX_IDX_MASK
            success = not (flags & can.IRQ_TX_FAILED)
            print("irq_send", idx, success)

can = CAN(1, 500_000)
can.irq(irq_send, trigger=can.IRQ_TX, hard=True)

重要

若設定了 CAN.IRQ_TX 觸發,則處理常式 必須 重複呼叫 flags() 直到它傳回 0,如本範例所示。否則,CAN 中斷可能無法正確地重新啟用。