can - can操作库
以下为关键点梳理
一. 常量定义
(1)CAN 工作模式常量
can.MODE_NORMAL
:正常工作模式。can.MODE_LISTEN
:监听模式(仅接收,不发送)。can.MODE_TEST
:自测模式(自收自发)。can.MODE_SLEEP
:休眠模式。
(2)CAN 状态常量
can.STATE_STOP
:停止工作状态。can.STATE_ACTIVE
:主动错误状态(一般情况下为该状态)。can.STATE_PASSIVE
:被动错误状态(总线上错误较多时进入,但仍可正常收发)。can.STATE_BUSOFF
:离线状态(总线错误非常多,不能收发,需手动退出)。can.STATE_LISTEN
:监听状态(选择监听模式时进入)。can.STATE_TEST
:自收自发状态(选择自测模式时进入)。can.STATE_SLEEP
:休眠状态(选择休眠模式时进入)。
(3)回调消息类型常量
can.CB_MSG
:有新数据写入缓存。can.CB_TX
:数据发送完成(需通过param
判断成功/失败).can.CB_ERR
:错误报告(param
为错误码)can.CB_STATE
:总线状态变更,后续param
参数是新的状态,也可以用can.state
读出。
(4)帧格式常量
can.EXT
:扩展帧。can.STD
:标准帧。
二. API 函数详解
(1) 初始化与配置
can.init(id, rx_message_cache_max)
- 功能:初始化 CAN 总线。
-
参数:
id
:总线 ID(0
表示can0
,1
表示can1
,通常默认为0
)。rx_message_cache_max
:接收缓存消息数最大值128-2048 单位:条。- 返回值:
boolean
(成功返回true
,失败返回false
)。 - 示例:
can.init(0,128) -- 默认初始化 can0,使用默认缓存大小
can.timing(id, br, PTS, PBS1, PBS2, SJW)
- 功能:配置 CAN 总线的波特率及时序参数。
-
参数:
id
:总线 ID。br
:波特率(默认1Mbps
)。PTS
:传播时间段(范围1~8
,默认5
)。PBS1
:相位缓冲段 1(范围1~8
,默认4
)。PBS2
:相位缓冲段 2(范围2~8
,默认3
)。SJW
:同步补偿宽度(范围1~4
,默认2
)。- 返回值:
boolean
。 - 示例:
can.timing(0, 500000, 5, 4, 3, 2) -- 配置 500kbps 波特率
can.mode(id, mode)
- 功能:设置 CAN 总线工作模式。
-
参数:
id
:总线 ID。mode
:模式常量(如can.MODE_NORMAL
)。- 返回值:
boolean
。 - 示例:
can.mode(0, can.MODE_NORMAL) -- 设置为正常模式
can.filter(id, dual_mode, ACR, AMR)
- 功能:设置消息过滤规则。
-
参数:
id
:总线 ID。dual_mode
:是否启用双滤波模式。ACR
:接受代码寄存器(过滤 ID 值)。AMR
:接受掩码寄存器(掩码值)。- 返回值:
boolean
。 - 注意:具体实现依赖硬件特性。
can.node(id, node_id, id_type)
- 功能:设置 CAN 节点 ID 及类型。
-
参数:
id
:总线 ID。node_id
:节点 ID。id_type
:ID 类型(can.EXT
或can.STD
)。- 返回值:
boolean
。
(2) 数据收发
can.tx(id, msg_id, id_type, RTR, need_ack, data)
- 功能:发送 CAN 消息。
-
参数:
id
:总线 ID。msg_id
:消息 ID。id_type
:帧类型(can.EXT
或can.STD
)。RTR
:是否为远程传输请求(true/false
)。need_ack
:是否需要应答(true/false
)。data
:字符串或者zbuff对象。- 返回值:无。
- 示例:
can.tx(0, 0x123, can.STD, false, true, string.char(0x01, 0x02))
can.rx(id)
- 功能:接收 CAN 消息。
- 参数:
id
(总线 ID)。 - 返回值:消息数据(如
{id=0x123, data={0x01, 0x02}}
)。 - 示例:
local msg = can.rx(0)
log.info("Received Message", msg)
(3) 状态与事件
can.on(id, func)
- 功能:注册事件回调函数。
-
参数:
id
:总线 ID。func
:回调函数(参数:id, type, param
)。- 示例:
can.on(0, function(id, type, param)
if type == can.CB_MSG then
log.info("New Message", param)
elseif type == can.CB_ERR then
log.error("Error", param)
end
end)
can.state(id)
- 功能:获取当前总线状态。
- 参数:
id
。 - 返回值:状态常量(如
can.STATE_ACTIVE
)。
can.stop(id)
- 功能:停止指定总线。
- 参数:
id
。
can.reset(id)
- 功能:重置总线(恢复默认配置)。
- 参数:
id
。
can.deinit(id)
- 功能:反初始化总线。
- 参数:
id
。
can.debug(on_off)
- 功能:启用/禁用调试信息输出。
- 参数:
on_off
(布尔值)。
三. 使用建议与注意事项
1. 初始化顺序:
can.init(0,128) -- 初始化
can.timing(...) -- 配置波特率
can.mode(...) -- 设置模式
can.filter(...) -- 配置过滤器
can.on(...) -- 注册回调
2. 消息发送与接收:
-
使用
can.tx
发送消息,确保参数正确。 -
通过
can.rx
拉取消息,或在回调中处理can.CB_MSG
。
3. 状态监控:
-
定期检查
can.state
,处理离线状态(can.STATE_BUSOFF
)。 -
错误回调
can.CB_ERR
需记录param
错误码。 4. 调试与性能: -
启用
can.debug(true)
输出底层日志。 -
接收缓存
rx_message_cache_max
需根据需求调整,避免消息丢失。
四. 示例代码
PROJECT = "candemo"
VERSION = "1.0.0"
sys = require("sys")
log.style(1)
local SELF_TEST_FLAG = false --自测模式标识,写true就进行自收自发模式,写false就进行正常收发模式
local node_a = true -- A节点写true, B节点写false
local can_id = 0
local rx_id
local tx_id
local stb_pin = 28 -- stb引脚根据实际情况写,不用的话,也可以不写
if node_a then -- A/B节点区分,互相传输测试
rx_id = 0x12345678
tx_id = 0x12345677
else
rx_id = 0x12345677
tx_id = 0x12345678
end
local test_cnt = 0
local tx_buf = zbuff.create(8) --创建zbuff
local function can_cb(id, cb_type, param)
if cb_type == can.CB_MSG then
log.info("有新的消息")
local succ, id, id_type, rtr, data = can.rx(id)
while succ do
log.info(mcu.x32(id), #data, data:toHex())
succ, id, id_type, rtr, data = can.rx(id)
end
end
if cb_type == can.CB_TX then
if param then
log.info("发送成功")
else
log.info("发送失败")
end
end
if cb_type == can.CB_ERR then
log.info("CAN错误码", mcu.x32(param))
end
if cb_type == can.CB_STATE then
log.info("CAN新状态", param)
end
end
local function can_tx_test(data)
if node_a then
log.info("node a tx")
else
log.info("node b tx")
end
test_cnt = test_cnt + 1
if test_cnt > 8 then
test_cnt = 1
end
tx_buf:set(0,test_cnt) --zbuff的类似于memset操作,类似于memset(&buff[start], num, len)
tx_buf:seek(test_cnt) --zbuff设置光标位置(可能与当前指针位置有关;执行后指针会被设置到指定位置)
can.tx(can_id, tx_id, can.EXT, false, true, tx_buf)
end
-- can.debug(true)
--gpio.setup(stb_pin,0) -- 配置STB引脚为输出低电平
gpio.setup(stb_pin,1) -- 如果开发板上STB信号有逻辑取反,则要配置成输出高电平
can.init(can_id, 128) -- 初始化CAN,参数为CAN ID,接收缓存消息数的最大值
can.on(can_id, can_cb) -- 注册CAN的回调函数
can.timing(can_id, 1000000, 5, 4, 3, 2) --CAN总线配置时序
if SELF_TEST_FLAG then
can.node(can_id, tx_id, can.EXT) -- 测试模式下,允许接收的ID和发送ID一致才会有新数据提醒
can.mode(can_id, can.MODE_TEST) -- 如果只是自身测试硬件好坏,可以用测试模式来验证,如果发送成功就OK
else
can.node(can_id, rx_id, can.EXT)
can.mode(can_id, can.MODE_NORMAL) -- 一旦设置mode就开始正常工作了,此时不能再设置node,timing,filter等
end
sys.timerLoopStart(can_tx_test, 2000)
sys.run()
五. 常见问题
-
Q:如何解决
can.STATE_BUSOFF
离线状态?- 需先调用
can.reset(id)
恢复总线,再重新初始化配置。 -
Q:如何确保消息发送成功?
-
通过
can.CB_TX
回调判断发送结果,或检查返回值(若 API 支持)。 -
Q:如何处理高频率消息接收?
-
增大
rx_message_cache_max
,或在回调中及时处理消息。
- 需先调用
以下为详细说明
常量
常量 | 类型 | 解释 |
---|---|---|
can.MODE_NORMAL | number | 正常工作模式 |
can.MODE_LISTEN | number | 监听模式 |
can.MODE_TEST | number | 自测模式 |
can.MODE_SLEEP | number | 休眠模式 |
can.STATE_STOP | number | 停止工作状态 |
can.STATE_ACTIVE | number | 主动错误状态,一般都是这个状态 |
can.STATE_PASSIVE | number | 被动错误状态,总线上错误多会进入这个状态,但是还能正常收发 |
can.STATE_BUSOFF | number | 离线状态,总线上错误非常多会进入这个状态,不能收发,需要手动退出 |
can.STATE_LISTEN | number | 监听状态,选择监听模式时进入这个状态 |
can.STATE_TEST | number | 自收自发状态,选择自测模式时进入这个状态 |
can.STATE_SLEEP | number | 休眠状态,选择休眠模式时进入这个状态 |
can.CB_MSG | number | 回调消息类型,有新数据写入缓存 |
can.CB_TX | number | 回调消息类型,数据发送结束,需要根据后续param确定发送成功还是失败 |
can.CB_ERR | number | 回调消息类型,有错误报告,后续param是错误码 |
can.CB_STATE | number | 回调消息类型,总线状态变更,后续param是新的状态,也可以用can.state读出 |
can.STATE_TEST | number | 自收自发状态,选择自测模式时进入这个状态 |
can.STATE_SLEEP | number | 休眠状态,选择休眠模式时进入这个状态 |
can.init(id, rx_message_cache_max)
CAN总线初始化
参数
传入值类型 | 解释 |
---|---|
int | id, 如果只有一条总线写0或者留空, 有多条的,can0写0,can1写1, 如此类推, 一般情况只有1条 |
int | rx_message_cache_max,接收缓存消息数最大值128-2048 单位:条 |
返回值
返回值类型 | 解释 |
---|---|
boolean | 成功返回true,失败返回false |
例子
can.init(0,128)
can.on(id, func)
注册CAN事件回调
参数
传入值类型 | 解释 |
---|---|
int | id, 如果只有一条总线写0或者留空, 有多条的,can0写0,can1写1, 如此类推, 一般情况只有1条 |
function | 回调方法 |
返回值
返回值类型 | 解释 |
---|---|
nil | 无返回值 |
例子
can.on(1, function(id, type, param)
log.info("can", id, type, param)
end)
can.timing(id, br, PTS, PBS1, PBS2, SJW)
CAN总线配置时序
参数
传入值类型 | 解释 |
---|---|
int | id, 如果只有一条总线写0或者留空, 有多条的,can0写0,can1写1, 如此类推, 一般情况只有1条 |
int | br, 波特率, 默认1Mbps |
int | PTS, 传播时间段, 范围1~8,默认5 |
int | PBS1, 相位缓冲段1,范围1~8,默认4 |
int | PBS2, 相位缓冲段2,范围2~8,默认3 |
int | SJW, 同步补偿宽度值,范围1~4,默认2 |
返回值
返回值类型 | 解释 |
---|---|
boolean | 成功返回true,失败返回false |
例子
can.timing(0, 1000000, 5, 4, 3, 2)
can.timing(0, 650000, 9, 6, 4, 2)
can.timing(0, 500000, 5, 4, 3, 2)
can.timing(0, 250000, 5, 4, 3, 2)
can.timing(0, 125000, 5, 4, 3, 2)
can.timing(0, 100000, 5, 4, 3, 2)
can.timing(0, 50000, 9, 6, 4, 2)
can.timing(0, 25000, 9, 6, 4, 2)
can.mode(id, mode)
CAN总线设置工作模式
参数
传入值类型 | 解释 |
---|---|
int | id, 如果只有一条总线写0或者留空, 有多条的,can0写0,can1写1, 如此类推, 一般情况只有1条 |
int | mode, 见MODE_XXX,默认是MODE_NORMAL |
返回值
返回值类型 | 解释 |
---|---|
boolean | 成功返回true,失败返回false |
例子
can.mode(0, can.MODE_NORMAL)
can.node(id, node_id, id_type)
CAN总线设置节点ID,这是一种简易的过滤规则,只接收和ID完全匹配的消息,和can.filter选择一个使用
参数
传入值类型 | 解释 |
---|---|
int | id, 如果只有一条总线写0或者留空, 有多条的,can0写0,can1写1, 如此类推, 一般情况只有1条 |
int | node_id, 节点ID, 标准格式11位或者扩展格式29位,根据is_extend_id决定,默认值是0x1fffffff,id值越小,优先级越高 |
int | id_type,ID类型,填1或者can.EXT为扩展格式,填0或者can.STD为标准格式 |
返回值
返回值类型 | 解释 |
---|---|
boolean | 成功返回true,失败返回false |
例子
can.node(0, 0x12345678, can.EXT)
can.node(0, 0x123, can.STD)
can.filter(id, dual_mode, ACR, AMR)
CAN总线设置接收过滤模式,当can.node不满足需求时才使用这个,和can.node选择一个使用,过滤模式比较复杂,请参考SJA1000的Pelican模式下滤波
参数
传入值类型 | 解释 |
---|---|
int | id, 如果只有一条总线写0或者留空, 有多条的,can0写0,can1写1, 如此类推, 一般情况只有1条 |
boolean | dual_mode, 是否是双过滤模式,true是,false不是,默认是false |
int | ACR, 接收代码寄存器值,必须写0xnnnnnnnn这样的格式,大端格式赋值到4个ACR寄存器上,默认值是0 |
int | AMR, 接收屏蔽寄存器值,必须写0xnnnnnnnn这样的格式,大端格式赋值到4个AMR寄存器上,对应bit写0表示需要检测,写1表示不检测,默认是0xffffffff,不过滤全接收 |
返回值
返回值类型 | 解释 |
---|---|
boolean | 成功返回true,失败返回false |
例子
can.filter(0, false, 0x12345678, 0x07) --效果等同于can.node(0, 0x12345678, can.EXT)
can.filter(0, false, 0x123, 0x0001fffff) --效果等同于can.node(0, 0x123, can.STD)
can.state(id)
CAN工作状态
参数
传入值类型 | 解释 |
---|---|
int | id, 如果只有一条总线写0或者留空, 有多条的,can0写0,can1写1, 如此类推, 一般情况只有1条 |
返回值
返回值类型 | 解释 |
---|---|
int | 返回值见STATE_XXX |
例子
can.state(0)
can.tx(id, msg_id, id_type, RTR, need_ack, data)
CAN发送一条消息
参数
传入值类型 | 解释 |
---|---|
int | id, 如果只有一条总线写0或者留空, 有多条的,can0写0,can1写1, 如此类推, 一般情况只有1条 |
int | msg_id, 节点ID, 标准格式11位或者扩展格式29位,根据is_extend_id决定,默认值是0x1fffffff,id值越小,优先级越高 |
int | id_type, ID类型,填1或者can.EXT为扩展格式,填0或者can.STD为标准格式 |
boolean | RTR, 是否是双过滤模式,true是,false不是,默认是false |
boolean | need_ack,是否需要应答,true是,false不需要,默认是true |
string/zbuff | data, 需要发送的数据, 如果是zbuff会从指针起始位置开始发送,最多发送8字节 |
返回值
返回值类型 | 解释 |
---|---|
boolean | 成功返回true,失败返回false |
例子
local data="\x00\x01\x02\x03\x04\x05\x06\x07"
can.tx(id, 0x12345678, can.EXT, false, true,data) --发送01 02 03 04 05 06 07 08
--为什么会这么写呢,为什么string类型的值可以发16进制呢?这个是lua字符串的转义字符控制的 \xhh 1到2位十六进制所代表的任意字符 二位十六进制
--第二种发送string.fromHex()的方式可以这样写发送的内容也是为01 02 03 04 05 06 07 08
local data=string.fromHex("0102030405060708")
can.tx(can_id, tx_id, can.EXT, false, true, data)
--用zbuff这样操作
local tx_buf = zbuff.create(8) --创建zbuff
local len = tx_buf:write("\x00\x01\x02\x03\x04\x05\x06\x07")
can.tx(can_id, tx_id, can.EXT, false, true, tx_buf)
--也可以这样写
local tx_buf = zbuff.create(8) --创建zbuff
local len = tx_buf:write(0x1a,0x30,0x31,0x32,0x00,0x01,0x02,0x03)
can.tx(can_id, tx_id, can.EXT, false, true, tx_buf)
--关于DLC的解释:
-- CAN协议中,数据帧的长度(Data Length Code,DLC)是指数据字段的字节数。CAN帧的总长度为8位,其中前6位为数据字段的字节数,后2位为控制字段。
-- DLC的取值范围为0~8,其中0表示空帧,1~8表示数据字段的字节数。
-- 空帧的DLC为0,表示不含数据字段。
-- 标准帧的DLC为8,表示8字节数据字段。
--DLC为底层自动处理,所以不需要用户自己处理。
--关于need_ack的解释:
-- CAN协议中的应答机制是其核心可靠性保障措施之一,主要通过以下方式实现:
-- 一、应答机制的核心组成
-- ACK应答位
-- 数据帧中的应答场包含2位ACK应答位。接收节点若正确接收到数据帧,会在应答间隙(ACK SLOT)期间发送一个显性位(通常为1)进行响应;若未收到有效应答,则发送错误帧触发重传。
-- 应答间隙(ACK SLOT)
-- 位于应答场中间,接收节点在此期间发送ACK信号。若发送节点未收到预期应答,则认为传输失败并触发重传机制。
-- 应答界定符(ACK DELIMITER)
-- 应答场的第一个位为隐性位,第二个位为显性位,用于标识应答开始。若在应答间隙检测到显性位,则不视为有效应答。
-- 二、应答机制的工作流程
-- 数据传输
-- 发送节点发送数据帧后进入等待应答状态,总线进入仲裁阶段。
-- 仲裁与应答
-- 多个节点同时发送时,通过CAN-ID进行仲裁,优先级高的节点获胜。
-- 接收节点验证CRC校验,若正确则发送ACK信号,否则丢弃数据帧。
-- 错误处理
-- 若发送节点未收到ACK,会启动自动重传机制,最多重传3次。
-- 若连续3次重传失败,发送节点进入Busoff状态(停止发送),等待1秒后重新初始化。
-- 通俗点来讲,就是发送完数据之后,对方会有应答内容,如果没有有效应答,设置成重发就会重新发送数据,不重发就不会重发数据
can.rx(id)
从缓存里读出一条消息
参数
传入值类型 | 解释 |
---|---|
int | id, 如果只有一条总线写0或者留空, 有多条的,can0写0,can1写1, 如此类推, 一般情况只有1条 |
返回值
返回值类型 | 解释 |
---|---|
boolean | 是否读出数据,true读出,false没有读出,缓存已经空了,或者id不对 |
int | 消息ID |
int | ID类型,返回1为can.EXT扩展格式,返回0为can.STD标准格式 |
boolean | 是否是遥控帧,true是,false不是 |
string | 数据 |
例子
local succ, id, type, rtr, data = can.rx(0)
--数据长度可以用#data获取
can.stop(id)
立刻停止当前的发送
参数
传入值类型 | 解释 |
---|---|
int | id, 如果只有一条总线写0或者留空, 有多条的,can0写0,can1写1, 如此类推, 一般情况只有1条 |
返回值
返回值类型 | 解释 |
---|---|
boolean | 成功返回true,失败返回false |
例子
can.stop(0)
can.reset(id)
CAN总线复位,一般用于从总线关闭状态恢复成主动错误
参数
传入值类型 | 解释 |
---|---|
int | id, 如果只有一条总线写0或者留空, 有多条的,can0写0,can1写1, 如此类推, 一般情况只有1条 |
返回值
返回值类型 | 解释 |
---|---|
boolean | 成功返回true,失败返回false |
例子
can.reset(0)
can.deinit(id)
CAN完全关闭
参数
传入值类型 | 解释 |
---|---|
int | id, 如果只有一条总线写0或者留空, 有多条的,can0写0,can1写1, 如此类推, 一般情况只有1条 |
返回值
返回值类型 | 解释 |
---|---|
boolean | 成功返回true,失败返回false |
例子
can.state(0)
can.debug(on_off)
CAN debug开关,打开后有更详细的打印
参数
传入值类型 | 解释 |
---|---|
boolean | true打开,false关闭 |
return | nil |
返回值
无
例子
can.debug(true)