class CAN -- 控制器局域网(CAN)协议

CAN 是一种两线串行协议,用于在连接到公共总线的一个或多个节点之间可靠地实时传递消息。CAN 2.0 在 ISO-11898 中实现了标准化,现在也被称为 CAN Classic(经典 CAN)。

还有一种较新的、向后兼容的协议,名为 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)。

本类的未来版本也可能在此接受用于配置硬件的移植专用关键字参数。目前尚未实现任何此类关键字参数。

示例

构造并初始化 CAN 控制器 1,波特率为 500kbps:

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 是标称位的重新同步跳转宽度,以时间量子为单位;对于经典 CAN,它可以是 1 到 4(含)之间的值。

  • tseg1 以标称位的时间量子为单位定义采样点的位置;对于经典 CAN,它可以是 1 到 16(含)之间的值。这是 ISO-11898 标准中定义的 Prop_SegPhase_Seg1 阶段之和。如果设置了此值,则也必须设置 tseg2,并且忽略 sample_point

  • tseg2 以标称位的时间量子为单位定义发送点的位置;对于经典 CAN,它可以是 1 到 8(含)之间的值。这对应于 ISO-11898 标准中的 Phase_Seg2。如果设置了此值,则也必须设置 tseg1

如果指定了这些参数,则 CAN 控制器会针对期望的 bitrate 和指定的每位时间量子总数进行正确配置。如果同时提供了所有这些参数,则 tseg1tseg2 的值会覆盖 sample_point 参数。

备注

各个控制器硬件对这些参数的有效值可能有额外限制,如果某个给定值不受支持,将引发 ValueError

备注

特定的控制器硬件可能会接受额外的可选关键字参数,用于过采样等硬件专用特性。

set_filters(filters: list | tuple | None) None

在 CAN 控制器中设置接收过滤器。filters 可以是:

  • None 表示接受所有传入消息,或

  • []() 表示禁用所有消息接收,或

  • 一个包含一个或多个项的可迭代对象,用于定义过滤条件。每一项应是一个具有三个元素的元组或列表:

    • identifier 是一个 CAN 标识符(int)。

    • bit_mask 是 CAN 标识符字段中各位的位掩码(int)。

    • flags 是一个整数,由 消息标志 中定义的零个或多个位组成。它指定传入消息需要匹配的属性。并非所有控制器都支持对所有标志进行过滤,如果请求了不支持的标志,将引发 ValueError

如果 bit_mask 中掩码的各位在消息标识符与过滤器 identifier 值之间相匹配,且过滤器中设置的标志与传入消息相匹配,则传入消息被接受。

如果标志中设置了 CAN.FLAG_EXT_ID 位,则过滤器仅匹配扩展 CAN ID。如果未设置 CAN.FLAG_EXT_ID 位,则过滤器仅匹配标准 CAN ID。

控制器中的所有过滤器之间是“或”关系。为 filters 参数传入空列表或空元组意味着不会接收任何消息。

某些 CAN 控制器要求每个过滤器仅关联一个接收 FIFO。在这些情况下,参数中的过滤器项会以轮询方式分配到可用的 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 是一个包含 CAN 消息数据的 bytes 对象(或类似对象),或用于描述远程发送请求(见下文)。

  • 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 个元素的列表,其中第二个元素是一个 memoryview 对象,引用一个 bytearray 或类似对象,且该对象具有足够的容量来容纳任何接收到的 CAN 消息(经典 CAN 为 8 字节,CAN FD 为 64 字节)。所提供的列表将作为成功结果返回,从而避免在函数内部分配内存。

如果 CAN 控制器尚未接收到任何消息,则此函数返回 None

备注

在控制器能够接收任何消息之前,必须先调用 CAN.set_filters。要接收所有消息,请调用 set_filters(None)

如果 CAN 控制器已接收到一条消息,则此函数返回一个包含 4 个元素的列表:

  • 索引 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 是中断事件触发时要调用的函数。该 handler 必须恰好接受一个参数,即 CAN 实例。

  • trigger 配置可以生成中断的事件。可能的值是以下一个或多个值的掩码:

    • CAN.IRQ_RX 事件在 CAN 控制器至少已向其 RX FIFO 接收一条消息后发生(意味着 CAN.recv() 将成功返回)。

    • CAN.IRQ_TX 事件在 CAN 控制器成功将一条消息发送到 CAN 总线上或发送消息失败后发生。此触发器对 handler 有额外要求,详见 IRQ 标志

    • CAN.IRQ_STATE 事件在 CAN 控制器转入更严重的错误状态时发生。调用 CAN.state() 以获取更新后的状态。

  • hard 如果为 True,则使用硬中断。这会减少 CAN 控制器事件与调用 handler 之间的延迟。硬中断 handler 不得分配内存;参见 编写中断处理程序

返回一个 irq 对象。如果在调用时不带参数,则返回先前配置的 irq 对象。

示例参见 IRQ 标志

cancel_send(index: int) bool

请求 CAN 控制器取消将一条消息发送到总线上。

参数 index 标识单个发送缓冲区。它应是一个范围在 0CAN.TX_QUEUE_LEN(不含)之间的整数。通常这将是先前由 CAN.send() 返回的值。

如果此缓冲区中有一条消息正在等待发送且发送已被取消,则结果为 True

否则结果为 False(要么此缓冲区中没有消息正在等待发送,要么发送已经成功)。

应使用 IRQ 事件 CAN.IRQ_TX 来确定一条消息是否确实已发送,但请注意,如果取消了某次发送,然后用同一个缓冲区发送另一条消息,则可能存在竞态条件(尤其是在 CAN 控制器 IRQ 不是“硬”中断的情况下)。

state() int

返回一个整数值,指示控制器的当前状态。该值将是 状态 中定义的值之一。

较低严重程度的错误状态在总线恢复时可能会自动清除,但 CAN.STATE_BUS_OFF 状态只能通过调用 CAN.restart() 来恢复。

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

返回控制器的错误计数器值。结果是一个包含八个值的列表。如果指定了可选的 list 参数,则会更新所提供的列表对象并将其作为结果返回,以避免分配。

列表项为:

  • TEC(发送错误计数器)值

  • REC(接收错误计数器)值

  • 控制器从 Active(活动)状态进入 Warning(警告)状态的次数。

  • 控制器从 Warning(警告)状态进入 Error Passive(错误被动)状态的次数。

  • 控制器从 Error Passive(错误被动)状态进入 Bus Off(总线关闭)状态的次数。

  • 硬件队列中待发送 TX 消息的总数。

  • 硬件队列中待接收 RX 消息的总数。

  • 发生 RX 溢出的次数。

备注

取决于控制器,这些值在达到某个值后可能会溢出并回绕到 0。

备注

如果某个控制器不支持特定的计数器,则它将为该列表元素返回 None

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

返回一个元素列表,指示 CAN 控制器中当前配置的定时。这可用于出于调试目的验证定时。结果是一个包含六个值的列表。如果指定了可选的 list 参数,则会更新所提供的列表对象并将其作为结果返回,以避免分配。

列表项为:

  • 控制器使用的确切位速率。由于为满足硬件约束而进行量化,它可能与传递给 CAN.init()bitrate 参数有所不同。

  • 标称位的重新同步跳转宽度(SJW),以时间量子为单位。其含义与 CAN.init()sjw 参数相同。

  • 标称位采样点的位置,以时间量子为单位。其含义与 CAN.init()tseg1 参数相同。

  • 标称位发送点的位置,以时间量子为单位。其含义与 CAN.init()tseg2 参数相同。

  • CAN FD 定时信息。对于不支持 CAN FD 的控制器,或者在 CAN FD 未初始化时,为 None。否则,为一个包含四个元素的嵌套列表,对应于上述各项但适用于 CAN FD BRS 特性。

  • 可选的控制器专用定时信息。取决于控制器,如果控制器不报告任何此类信息,则该值为 None,否则它将是一个固定长度的列表,其元素特定于某个特定的硬件控制器。

备注

如果尚未调用 CAN.init(),则此函数仍会返回一个结果,但该结果取决于控制器内部状态,可能并不准确。

restart() None

使控制器退出 STATE_BUS_OFF 状态,而不清除任何其他内部状态。同时也会清除一部分错误计数器(始终包括进入每个错误状态的次数,可能还包括 TEC 和 REC,取决于控制器)。

调用此函数还会取消任何等待发送的消息。不会为这些消息传递 IRQ_TX 中断。

请注意,此函数可能会也可能不会使控制器退出“Error Passive”状态,取决于控制器硬件是否将 TEC 和 REC 清零。

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 网络节点处于活动状态(将确认有效消息,并可能根据其当前 状态 发送错误)。

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() 接受或返回一个由这些标志按位“或”组合而成的整数值(零个或多个)。

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() 的结果包含一个由这些标志按位“或”组合而成的整数值(零个或多个)。如果设置,这些标志表示在接收 CAN 消息时可能存在的一般性问题。

RECV_ERR_FULL: int

接收此消息的硬件 FIFO 已满,后续传入的消息可能会丢失。

RECV_ERR_OVERRUN: int

接收此消息的硬件 FIFO 已满,且已有一条或多条传入消息丢失。

IRQ 值

IRQ_RX: int

传递给 irq()trigger 参数,以便在 CAN 控制器每次向 RX FIFO 接收一条完整消息时触发 handler。在 handler 内部,使用 recv() 读取消息。

IRQ_TX: int

传递给 irq()trigger 参数,以便在 CAN 控制器每次完成一次发送尝试(成功或失败)时触发 handler。在 handler 内部,使用下面的附加位来确定是哪个邮箱完成以及它是否失败——参见 IRQ 标志

IRQ_STATE: int

传递给 irq()trigger 参数,以便在控制器每次在 STATE_* 值(active / warning / passive / bus-off)之间转换时触发 handler。在 handler 内部使用 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 之间的整数)。

IRQ 标志

调用 CAN.irq() 会注册一个中断 handler,其触发器为 CAN.IRQ_RXCAN.IRQ_TXCAN.IRQ_STATE 中的一个或多个。

该函数返回一个 IRQ 对象,对该对象调用 flags() 函数会返回一个整数,指示是哪个(哪些)触发事件触发了中断。CAN IRQ handler 应反复调用 flags() 函数,直到它返回 0

flags() 函数返回时设置了 CAN.IRQ_TX 位,handler 还可以检查结果中的以下标志位,以获取有关 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 触发器,则 handler 必须反复调用 flags(),直到它返回 0,如本示例所示。否则,CAN 中断可能无法正确地重新启用。