CAN总线协议功能库开发详解
在实时性要求较高的嵌入式系统中,CAN功能组件扮演着至关重要的角色。本文聚焦于一款可复用的CAN功能库实现,涵盖滤波配置、中断处理与多帧传输机制,提升系统通信的可靠性与可维护性。
一、概述
CAN(Controller Area Network)是一种被广泛应用于汽车和工业控制领域的串行通信协议。它支持多主节点通信,具有高可靠性、实时性以及错误检测能力。LuatOS 支持 CAN 2.0A/B 标准,允许最高达 1Mbps 的通信速率。
1.1 CAN 协议简介
支持协议:CAN 2.0A/B 标准,兼容标准帧(11 位 ID)和扩展帧(29 位 ID)
通信速率:最高支持 1Mbps
物理层依赖:需外接 CAN 收发器(如川土微的 CA-IF1051S/VS)
错误检测:具备完善的错误检测和处理机制,包括错误计数器和状态转换机制
1.2 硬件接口特性
GPIO 映射:
Air780 系列(EHV/EGH/EPM 等):
CAN_TXD(发送):默认映射到 GPIO26
CAN_RXD(接收):默认映射到 GPIO25 - CAN_STB(待机模式控制):默认映射到 GPIO28
Air8000 系列:
CAN_TXD(发送):映射到 GPIO36
CAN_RXD(接收):映射到 GPIO37
CAN_STB(待机模式控制):映射到 GPIO35
Air8101 系列:
CAN_TXD(发送):映射到 GPIO44
CAN_RXD(接收):映射到 GPIO45
CAN_STB(待机模式控制):映射到 GPIO46
Air6101 系列:
CAN_TXD(发送):映射到 GPIO44
CAN_RXD(接收):映射到 GPIO45
CAN_STB(待机模式控制):映射到 GPIO46
1.3 错误码说明
CAN 总线错误码是 4 字节小端格式的 uint32 数值,通过 can.CB_ERR 回调报告详细的总线错误信息。错误码的每个字节都有特定含义,帮助开发者精确定位 CAN 通信中的问题。

错误位置编码表:
CAN 错误位置编码对应 CAN 帧中的具体字段,帮助精确定位错误发生的位置。
CAN 帧结构概述
CAN 帧主要包含以下部分:
帧起始(SOF):1 位,标志帧开始
仲裁场:包含标识符和 RTR 位
控制场:包含 IDE 位、保留位和数据长度
数据场:0-8 字节数据
CRC 场:CRC 序列和 CRC 定界符
应答场:ACK 槽和 ACK 定界符
帧结束:7 个隐性位
1.3.1 错误位置编码详解
使用说明
1、 标准帧 vs 扩展帧:标准帧使用 11 位标识符,扩展帧使用 29 位标识符
2、位编号:从帧起始开始,按传输顺序编号(第 1 位=SOF)
3、 错误定位:结合错误类型和位置编码,可以精确判断错误原因
4.、实际应用:通过 parseCanError() 函数可以自动解析错误位置
典型错误组合

1.3.2 实际错误码示例
重要说明:错误码是 4 字节小端格式的 uint32,计算方法是:

示例 1:发送位错误,位置未指定(0x00000000)

示例 2:接收格式错误,位置未指定(0x00010100)

示例 3:发送填充错误,标识符高位(0x00000202)

示例 4:接收位错误,应答槽(0x00010019)

1.3.3 错误码详细解析表
下表列出了常见的 CAN 错误码及其含义,帮助开发者快速诊断总线问题:

注意:表格中的错误码数值是按照标准格式计算的,实际应用中可能会因硬件实现差异而略有不同。
1.3.4 结合 can.CB_ERR 回调的实际应用
can.CB_ERR 是 CAN 总线错误报告回调,当总线检测到错误时触发。下面通过实际例子说明如何使用错误码:
示例:基本错误处理

1.4 CAN 错误计数器机制
1.4.1 错误计数器基本原理
CAN 协议通过发送错误计数器(TEC)和接收错误计数器(REC)来监控总线错误状态,实现故障隔离和自动恢复机制:
TEC (Transmit Error Counter):发送错误计数器,记录发送过程中的错误
REC (Receive Error Counter):接收错误计数器,记录接收过程中的错误
错误计数规则:
检测到错误时:TEC 或 REC 增加(通常 +8)
成功发送/接收时:TEC 或 REC 减少(通常-1)
不同的错误类型有不同的计数增减值
1.4.2 错误状态转换
基于错误计数器值,CAN 控制器会在三种状态间转换:
1.4.3 错误帧行为差异
不同状态下的错误帧行为:
主动错误帧:6 个连续的显性位( dominant bits),强烈干扰总线
被动错误帧:6 个连续的隐性位(recessive bits),轻微干扰总线
1.4.4 总线关闭恢复机制
当进入总线关闭状态时:
自动恢复过程:需要检测到 128 次连续的 11 个隐性位
恢复时间:约 2.6ms(标准帧间隔)到 5.2ms(扩展帧间隔)
手动恢复:可通过 can.reset() 函数强制恢复
1.4.5 实际应用意义
错误计数器机制的作用:
故障隔离:防止单个故障节点持续干扰总线
自动恢复:给故障节点恢复通信的机会
状态监控:通过 can.state() 获取当前错误状态
预防性维护:监控错误趋势,提前发现潜在问题
1.4.6 LuatOS 中的错误计数器实现
在 LuatOS 中,错误计数器的具体数值(TEC/REC)由底层硬件自动管理,用户层面主要通过状态常量来感知:
状态获取:使用 can.state() 函数获取当前状态
状态解释:
STATE_ACTIVE:TEC < 128 且 REC < 128
STATE_PASSIVE:TEC ≥ 128 或 REC ≥ 128
STATE_BUSOFF:TEC ≥ 256
手动恢复:使用 can.reset() 从总线关闭状态恢复
错误监控:通过 CB_ERR 和 CB_STATE 回调事件实时监控
注意:LuatOS 目前未提供直接读取 TEC/REC 具体数值的 API,主要通过状态转换来反映错误计数器的变化。
二、核心示例
1、核心示例是指:使用本库文件提供的核心 API,开发的基础业务逻辑的演示代码;
2、核心示例的作用是:帮助开发者快速理解如何使用本库,所以核心示例的逻辑都比较简单;

三、常量详解
核心库常量,顾名思义是由 LuatOS 内核固件中定义的、不可重新赋值或修改的固定值,在脚本代码中不需要声明,可直接调用;
3.1 CAN 工作模式常量
3.1.1 can.MODE_NORMAL(正常工作模式)

3.1.2 can.MODE_LISTEN(监听模式)

3.1.3 can.MODE_TEST(自测模式)

3.1.4 can.MODE_SLEEP(休眠模式)

3.2 CAN 状态常量
3.2.1 can.STATE_STOP(停止状态)

3.2.2 can.STATE_ACTIVE(主动错误状态)

3.2.3 can.STATE_PASSIVE(被动错误状态)

3.2.4 can.STATE_BUSOFF(总线关闭状态)

3.2.5 can.STATE_LISTEN(监听状态)

3.2.6 can.STATE_TEST(自收自发状态)

3.2.7 can.STATE_SLEEP(休眠状态)

3.3 回调消息类型常量
3.3.1 can.CB_MSG(新消息回调)

3.3.2 can.CB_TX(发送完成回调)

3.3.3 can.CB_ERR(错误报告回调)

3.3.4 can.CB_STATE(状态变更回调)

3.4 帧格式常量
3.4.1 can.EXT(扩展帧)

3.4.2 can.STD(标准帧)

四、API 函数详解
4.1 can.init(id, rx_message_cache_max)
功能
CAN 总线初始化
注意事项
1、初始化前确保硬件连接正确
2、接收缓存大小影响性能,建议根据实际需求设置
3、初始化失败时需要检查硬件连接和参数配置
参数
id

rx_message_cache_max

返回值
local result = can.init(id, rx_message_cache_max)
有一个返回值
result

示例

4.2 can.on(id, func)
功能
注册 CAN 事件回调
注意事项
1、回调函数在事件发生时异步执行
2、需要处理所有类型的回调事件
3、回调函数执行时间不宜过长
参数
id

func

返回值
无返回值
示例

4.3 can.timing(id, br, PTS, PBS1, PBS2, SJW)
功能
CAN 总线配置时序;
注意事项
1、波特率计算公式:实际波特率 = 基础时钟 / 分频系数 / (1 + PTS + PBS1 + PBS2);
2、不同平台的基础时钟可能不同,参考 can.capacity()获取; 例如:基础时钟为51.2M,(1 + PTS + PBS1 + PBS2)取值最高为 1+8+8+8 = 25,此时如果要达到10K的波特率所需的分频系数为 51.2M的基础时钟/25(1 + PTS + PBS1 + PBS2) = 204 因为分频系数最大为64所以不支持10K的波特率,最低支持的波特率为 51.2M基础时钟/64最大分频系数/25(1 + PTS + PBS1 + PBS2) = 32K
3、时序参数影响通信的稳定性和抗干扰能力,可使用下方"常用波特率配置参考"中的参数组合;
参数
id

br

PTS

PBS1

PBS2

SJW

返回值
local result = can.timing(id, br, PTS, PBS1, PBS2, SJW)
有一个返回值
result

示例

常用波特率配置参考

配置选择建议

4.4 can.mode(id, mode)
功能
CAN 总线设置工作模式
注意事项
1、工作模式影响 CAN 控制器的行为
2、监听模式可用于总线监控
3、自测模式用于硬件自检
4、休眠模式(降低 can 控制器功耗):主动发送消息,或 can 总线其他设备发送数据时会唤醒,进入正常模式,想要重新进入休眠模式需要 can.mode(0, can.MODE_SLEEP)重新设置。
参数
id

mode

返回值
local result = can.mode(id, mode)
有一个返回值
result

示例

4.5 can.node(id, node_id, id_type)
功能
CAN 总线设置节点 ID,这是一种简易的过滤规则,只接收和 ID 完全匹配的消息
注意事项
1、与 can.filter 函数二选一使用
2、ID 值越小,优先级越高
3、标准帧和扩展帧的 ID 范围不同
参数
id

node_id

id_type

返回值
local result = can.node(id, node_id, id_type)
有一个返回值
result

示例

4.6 can.filter(id, dual_mode, ACR, AMR)
功能
CAN 总线设置接收过滤模式,当 can.node 不满足需求时才使用这个
注意事项
1、与 can.node 函数二选一使用
2、ACR 和 AMR 是逐位配合工作的,其核心规则通常如下:
当 AMR 的某一位设置为 0:表示这一位是“需要关心”的。此时,接收到的报文 ID 中对应的位必须与 ACR 中对应的位完全一致,才算匹配成功
当 AMR 的某一位设置为 1:表示这一位是“不关心”(屏蔽)的。接收到的报文 ID 中对应的位无论是 0 还是 1,都会被接受,而不再与 ACR 的对应位进行比较
我们可以用一个简单的例子来说明
:假设您希望只接收标识符为 00001010(二进制,即 0x0A)的 CAN 帧。
您可以设置 ACR = 0x0A。
为了确保所有位都精确匹配,您需要设置 AMR = 0x00(每一位的屏蔽位都是 0,表示每一位都必须匹配)。
这样,只有 ID 完全等于 0x0A 的报文才能通过滤波。
如果您希望接收一组 ID,例如所有 ID 高 4 位是 1010 的报文,可以这样设置:
设置 ACR = 0xA0(假设高 4 位为 1010)。
设置 AMR = 0x0F(低 4 位屏蔽,不关心;高 4 位必须匹配)。
这样,ID 在 0xA0 到 0xAF 范围内的报文都会被接收。
因为常见的 CAN 接收过滤器 在 ACR/AMR 寄存器里并不是把 ID 从位0开始放的:低 3 位(bit0..2)通常被保留给控制位(如 RTR/IDE 等),29 位的扩展 ID 要从 bit3 开始对齐到寄存器。因此要把扩展帧的 29 位 ID 左移 3 位,才能把 ID 的最低位对齐到寄存器中的 bit3。而标准帧需要左移21位。
简单记法:扩展帧 ID 对齐 = ID << 3;标准帧 ID 对齐 = ID << 21。
can.filter(0, false, 0x12345678 << 3, 0x07)-- 扩展帧
can.filter(0, false, 0x123 << 21, 0x007fffff)-- 标准帧
参数
id

dual_mode

ACR

AMR

返回值
local result = can.filter(id, dual_mode, ACR, AMR)
有一个返回值
result

示例

4.7 can.state(id)
功能
获取 CAN 总线当前状态
注意事项
1、状态反映总线的健康状况
2、需要根据状态进行相应的处理:STATE_BUSOFF 总线离线状态时需要 can.reset(0)手动复位,其他状态为正常状态或自行恢复。
3、离线状态需要手动恢复
参数
id

返回值
local state = can.state(id)
state

示例

4.8 can.tx(id, msg_id, id_type, RTR, need_ack, data)
功能
发送 CAN 消息
注意事项
1、数据长度最大 8 字节(CAN 协议限制)
2、标准帧 ID 范围 0-0x7FF,扩展帧 ID 范围 0-0x1FFFFFFF
3、发送结果通过回调函数通知
参数
id

msg_id

id_type

RTR

need_ack

data

返回值
local result = can.tx(id, msg_id, id_type, RTR, need_ack, data)
result

示例

4.9 can.rx(id)
功能
接收 CAN 消息
注意事项
1、需要在回调函数中调用;
2、缓存为空时返回失败;
3、数据为 string 类型;
4、需要循环读取并处理数据,直到没有数据可读(类似 UART 读取逻辑);
5、 如果只读取一次,可能会遗漏后续到达的消息;
参数
id

返回值
local succ, msg_id, msg_type, rtr, data = can.rx(id)
有 5 个返回值
succ

msg_id

msg_type

rtr

data

示例

4.10 can.reset(id)
功能
CAN 总线复位,一般用于从总线关闭状态恢复成主动错误
注意事项
1、用于从离线状态恢复
2、会重置错误计数器
3、保持当前配置不变
参数
id

返回值
local result = can.reset(id)
有一个返回值
result

示例

4.11 can.busOff(id)
功能
CAN 总线关闭,此时可以重新进行 timing、filter、node 等配置
注意事项
1、主动进入离线状态
2、用于重新配置参数
3、需要重新设置工作模式或复位才能恢复
can.reset(0) --复位
can.mode(0, can.MODE_NORMAL) --重新设置正常模式,设置后恢复正常状态
参数
id

返回值
local result = can.busOff(id)
有一个返回值
result

示例

4.12 can.debug(on_off)
功能
CAN 调试开关,打开后有更详细的打印信息
注意事项
1、用于调试和问题排查
2、会增加日志输出量
3、建议在开发阶段使用
参数
on_off

返回值
无
示例

4.13 can.capacity(id)
功能
获取 CAN 时钟特性,包括基础时钟、分频系数范围。CAN 的实际波特率=基础时钟/分频系数/(1+PTS+PBS1+PBS2),从时钟特性里能看出对应平台是否能配置出需要的波特率
注意事项
1、用于确定平台支持的波特率范围
2、不同平台的基础时钟可能不同
3、是配置时序的重要参考
参数
id

返回值
local result, clk, div_min, div_max, div_step = can.capacity(id)
有五个返回值 result, clk, div_min, div_max 和 div_step
result

clk

div_min

div_max

div_step

示例

4.14 can.deinit(id)
功能
完全关闭 CAN 总线
注意事项
1、关闭后需要重新初始化才能使用
2、会释放所有相关资源
3、建议在程序退出前调用
参数
id

返回值
local result = can.deinit(id)
result

示例

4.15 can.stop(id)
功能
立即停止当前的发送操作
注意事项
1、用于紧急停止发送
2、不会影响接收操作
参数
id

返回值
local result = can.stop(id)
result

示例

五、模组支持说明
支持 LuatOS 开发的所有模组中只有 Air780EPM,Air780EHM,Air780EGH,Air780EHV,Air8000 系列,Air8101 系列,Air6101 系列模组支持 can 核心库。
今天的内容就分享到这里了~

浙公网安备 33010602011771号