5.16 CAN设备
5.16 CAN设备
📚 本节导读
学习时长: 约 40 分钟
难度级别: ⭐⭐⭐⭐☆
前置知识: C 语言基础、CAN 总线协议基础、5.1 设备驱动框架
🎯 学习目标
- 理解 OneOS CAN 设备驱动框架的分层设计
- 掌握 CAN 消息的发送与接收(阻塞/非阻塞)
- 掌握 CAN 过滤器(Filter)的配置与使用
- 理解 CAN 波特率、工作模式(Normal/Loopback/Listen)的配置
- 了解 CAN 状态监控与总线错误处理
- 掌握 CAN 中断 ISR 处理(
os_hw_can_isr_rxdone/os_hw_can_isr_txdone)
一、概述
CAN(Controller Area Network)是一种广泛应用于汽车电子、工业控制等领域的现场总线协议。OneOS 将 CAN 控制器抽象为设备,提供统一的收发、配置、控制接口。CAN 设备驱动位于 drivers/can/ 目录下,核心头文件为 can.h,实现文件为 can.c。
CAN 设备框架的特点:
- 统一抽象:将不同 MCU 平台的 CAN 控制器统一封装为
os_can_device_t - 消息邮箱机制:内置 RX/TX FIFO 消息队列,支持配置邮箱大小
- 过滤器支持:支持标准帧/扩展帧/远程帧的硬件过滤器配置,可选
OS_CAN_USING_HDR回调模式 - 状态监控:支持总线错误计数、收发统计、状态变化通知
- 多模式支持:Normal(正常)、Loopback(回环)、Listen(监听)、Loopback+Listen 四种模式
1.1 使用场景
一句话核心:CAN 用于多节点、抗干扰、实时性好的现场总线通信。OneOS CAN 框架负责"统一抽象收发 + 过滤 + 状态监控",让应用专注于业务报文,不必关心底层寄存器。
与 UART(串口)总线对比:
| 维度 | CAN | UART |
|---|---|---|
| 多节点 | 支持(总线式,地址在帧里) | 一般点对点 |
| 传输距离 | 远(1Mbps 可达 40m,速率降距离增) | 较短 |
| 抗干扰 | 强(差分信号 + 位填充 + CRC) | 较弱 |
| 多主竞争 | 支持(仲裁机制) | 不支持 |
| 错误处理 | 内置错误检测/重发/离线 | 简单 |
| 典型场景 | 汽车 ECU、工业总线 | 简单设备间通信 |
结论:多节点、远距离、强实时、抗干扰场景用 CAN(汽车/工业);简单点对点短距用 UART。
典型应用场景:
| 场景 | 谁在用 | 实际工作情况 |
|---|---|---|
| 汽车车身网络 | ECU 之间 | 每个 ECU 用过滤只收自己关心的 ID,其余报文硬件过滤丢弃,减轻 CPU 负载 |
| 工业设备控制 | PLC/传感器 | 现场总线多节点,周期交换状态报文,靠滤波/状态监控保证实时性与可靠性 |
| 机器人关节 | 驱动器/主控 | 高频发送控制指令、接收位置反馈,Loopback 模式可先自发自收调试 |
| 车载诊断 | 诊断仪/ECU | 标准帧 + 远程帧请求数据,听模式(Listen)可抓总线报文分析 |
选型边界表(何时不该用 CAN):
| 场景 | 该用什么 | 为什么 |
|---|---|---|
| 点对点短距离低速 | UART/I2C/SPI | CAN 协议开销大,简单场景过重 |
| 高速大数据量(如视频) | Ethernet/USB | CAN 2.0 最高 1Mbps,带宽有限(CAN-FD 才更高) |
| 极低成本 | LIN/单线 | CAN 需要收发器,成本高 |
结合源码的使用注意点:
- 波特率必须一致:总线上所有节点波特率必须相同,否则收不到/误码;用
OS_CAN_CMD_SET_BAUD配置; - 过滤器谨慎配置:过滤不当会漏收关键报文(ID 不匹配)或收太多(掩码太宽);
- 邮箱大小权衡:RX 邮箱(
rx_msgbox_size)太小在突发报文时溢出(dropedrcvpkg增长); - 收发 FIFO 并发安全:框架用
gs_device_lock自旋锁 +can->lock互斥锁保护,应用侧os_can_read/write是线程安全的。
二、核心数据结构
2.1 CAN 消息结构体 struct os_can_msg
来源:drivers/can/can.h
struct os_can_msg
{
uint32_t id : 29; /* CAN ID */
uint32_t ide : 1; /* Extended frame identification bit */
uint32_t rtr : 1; /* Remote frame identification bit */
uint32_t rsv : 1; /* Reserved bit */
uint32_t len : 8; /* data length */
uint32_t priv : 8; /* Message sending priority */
int32_t hdr : 8; /* Hardware filter table number */
uint32_t reserved : 8; /* Reserved bit */
uint8_t data[8]; /* Data segment */
};
typedef struct os_can_msg *os_can_msg_t;字段详解(源码注释为英文原版,下表补充说明,不属于源码):
| 字段 | 位宽 | 含义 |
|---|---|---|
id | 29 bit | CAN 消息 ID:标准帧 11bit(0x000~0x7FF),扩展帧 29bit |
ide | 1 bit | 扩展帧标识:OS_CAN_STDID(0)=标准帧,OS_CAN_EXTID(1)=扩展帧 |
rtr | 1 bit | 远程帧标识:OS_CAN_DTR(0)=数据帧,OS_CAN_RTR(1)=远程帧 |
rsv | 1 bit | 保留位 |
len | 8 bit | 数据长度,CAN 2.0 支持 0~8 字节 |
priv | 8 bit | 消息发送优先级(启用 privmode 时生效) |
hdr | 8 bit(有符号) | 硬件过滤器表编号(OS_CAN_USING_HDR 模式),-1 表示未绑定 |
reserved | 8 bit | 保留位 |
data[8] | —— | 数据段,最多 8 字节 |
位域布局演算 —— os_can_msg 按位打包:
os_can_msg 把多个标志用位域压缩进 32 位内存,紧接 8 字节数据:
32 位(bit31 .. bit0)位域打包:
┌───────────────────────────┬────┬────┬────┬────────────────────────────────────┐
│ id(29 bit) │ide │rtr │rsv │ len(8) │ priv(8) │ hdr(8) │res(8)│
│ bit28..bit0 │bit29│bit30│bit31│ (紧接下一字节起) │
└───────────────────────────┴────┴────┴────┴────────────────────────────────────┘
数据区:data[0..7](8 字节)| 位域 | 位范围 | 最大取值 | 典型含义 |
|---|---|---|---|
id | bit0~bit28 | 0x1FFFFFFF(29bit) | 标准帧只用低 11bit |
ide | bit29 | 0/1 | 0=标准,1=扩展 |
rtr | bit30 | 0/1 | 0=数据,1=远程 |
rsv | bit31 | 0 | 保留 |
关键理解:
os_can_msg是一个位打包紧凑结构,id占 29 bit、ide/rtr/rsv各占 1 bit,正好凑满 4 字节的头部(占 32 bit),后面跟着 8 字节数据。发送/接收时把该结构按内存布局交给硬件,硬件能直接读出 ID、帧类型、长度。标准帧的id只用到低 11 bit(0x7FF),扩展帧用满 29 bit。
2.2 CAN 设备结构体 struct os_can_device
来源:drivers/can/can.h
struct os_can_device
{
struct os_device parent;
const struct os_can_ops *ops;
struct can_configure config;
struct os_can_status status;
struct os_can_status_ind_type status_indicate;
#ifdef OS_CAN_USING_HDR
struct os_can_hdr *hdr;
#endif
#ifdef OS_CAN_USING_BUS_HOOK
os_can_bus_hook bus_hook;
#endif /*OS_CAN_USING_BUS_HOOK*/
os_mutex_id lock;
struct os_can_rx_fifo *can_rx;
struct os_can_tx_fifo *can_tx;
};
typedef struct os_can_device *os_can_t;字段详解(源码无注释,下表补充说明,不属于源码):
| 字段 | 含义 |
|---|---|
parent | 首成员继承 os_device,使 CAN 设备可作普通设备注册/读/写 |
ops | CAN 操作函数表(configure/control/start_send/start_recv 等,见 2.6) |
config | CAN 配置参数(波特率、邮箱大小、模式、优先级等) |
status | 状态统计(收发计数、错误计数等,见 2.5) |
status_indicate | 状态变化回调(ind 函数指针 + args) |
hdr | HDR 过滤器控制块数组(OS_CAN_USING_HDR 时存在) |
bus_hook | 总线错误钩子(OS_CAN_USING_BUS_HOOK 时存在) |
lock | 互斥锁,保护设备并发访问(发送/收发队列) |
can_rx | 接收 FIFO(os_can_rx_fifo,含 freelist/datalist 两条链表) |
can_tx | 发送 FIFO(os_can_tx_fifo) |
关键理解 ——
parent首成员继承:parent是第一个成员,&can->parent与&can地址数值相等。os_hw_can_register里device = &(can->parent),把can_device_ops挂到parent.ops,并os_device_register(device, name)——因此 CAN 设备通过os_device_find找到后,强转回os_can_device*即可访问config/status/can_rx等。
2.3 CAN 配置结构体 struct can_configure
来源:drivers/can/can.h
struct can_configure
{
uint32_t baud_rate;
uint32_t rx_msgbox_size;
uint32_t tx_msgbox_size;
uint32_t mode : 8;
uint32_t privmode : 8;
uint32_t reserved : 16;
uint32_t ticks;
#ifdef OS_CAN_USING_HDR
uint32_t maxhdr;
#endif
};字段详解(源码无注释,下表补充说明,不属于源码):
| 字段 | 含义 |
|---|---|
baud_rate | 波特率(bit/s),用波特率枚举值,如 CAN500kBaud=500000 |
rx_msgbox_size | 接收邮箱数(默认 OS_CAN_RX_MSGBOX_SZ=16) |
tx_msgbox_size | 发送邮箱数(默认 OS_CAN_TX_MSGBOX_SZ=1) |
mode | 工作模式(NORMAL/LISEN/LOOPBACK/LOOPBACKANLISEN) |
privmode | 优先级模式(OS_CAN_MODE_PRIV/NOPRIV) |
reserved | 保留 |
ticks | 超时 tick 数 |
maxhdr | 最大 HDR 过滤器数(OS_CAN_USING_HDR 时存在) |
波特率枚举(drivers/can/can.h,与项目逐字一致,含 CAN800kBaud):
enum CANBAUD
{
CAN1MBaud = 1000UL * 1000, /* 1 MBit/sec */
CAN800kBaud = 1000UL * 800, /* 800 kBit/sec */
CAN500kBaud = 1000UL * 500, /* 500 kBit/sec */
CAN250kBaud = 1000UL * 250, /* 250 kBit/sec */
CAN125kBaud = 1000UL * 125, /* 125 kBit/sec */
CAN100kBaud = 1000UL * 100, /* 100 kBit/sec */
CAN50kBaud = 1000UL * 50, /* 50 kBit/sec */
CAN20kBaud = 1000UL * 20, /* 20 kBit/sec */
CAN10kBaud = 1000UL * 10 /* 10 kBit/sec */
};| 枚举值 | 宏展开值(bit/s) | 实际波特率 |
|---|---|---|
CAN1MBaud | 1000 * 1000 | 1 Mbps |
CAN800kBaud | 1000 * 800 | 800 kbps |
CAN500kBaud | 1000 * 500 | 500 kbps |
CAN250kBaud | 1000 * 250 | 250 kbps |
CAN125kBaud | 1000 * 125 | 125 kbps |
CAN100kBaud | 1000 * 100 | 100 kbps |
CAN50kBaud | 1000 * 50 | 50 kbps |
CAN20kBaud | 1000 * 20 | 20 kbps |
CAN10kBaud | 1000 * 10 | 10 kbps |
关键理解:枚举值直接用实际波特率数值(bit/s),如
CAN500kBaud == 500000。所以配置config.baud_rate = CAN500kBaud就是设置 500kbps,不需要再换算。默认配置宏CANDEFAULTCONFIG把波特率设为CAN1MBaud、RX=16、TX=1、mode=OS_CAN_MODE_NORMAL。
工作模式:
| 宏定义 | 值 | 说明 |
|---|---|---|
OS_CAN_MODE_NORMAL | 0 | 正常收发模式 |
OS_CAN_MODE_LISEN | 1 | 只听模式(不发送 ACK,用于总线监听) |
OS_CAN_MODE_LOOPBACK | 2 | 回环模式(自发自收,用于测试) |
OS_CAN_MODE_LOOPBACKANLISEN | 3 | 回环+监听模式 |
2.4 CAN 过滤器结构体 struct os_can_filter_item
来源:drivers/can/can.h
struct os_can_filter_item
{
uint32_t id : 29; /* can message id */
uint32_t ide : 1; /* Extended frame identification bit */
uint32_t rtr : 1; /* Remote frame identification bit */
uint32_t mode : 1; /* Filter table mode */
uint32_t mask; /* ID mask, 1 means the corresponding bits must matched */
int32_t hdr; /* The corresponding filter table control block will be initialized */
#ifdef OS_CAN_USING_HDR
/* Filter table callback function */
os_err_t (*ind)(os_device_t dev, void *args, int32_t hdr, os_size_t size);
void *args; /* Callback function parameters */
#endif /*OS_CAN_USING_HDR*/
};字段详解(源码注释为英文原版,下表补充说明,不属于源码):
| 字段 | 含义 |
|---|---|
id | 要过滤的 CAN ID |
ide | 帧类型:0=标准帧,1=扩展帧 |
rtr | 帧类型:0=数据帧,1=远程帧 |
mode | 过滤模式:0 = ID 列表模式(精确匹配单个 ID),1 = ID 掩码模式 |
mask | ID 掩码,1 表示对应位必须匹配、0 表示不关心 |
hdr | 对应的过滤器控制块编号(硬件过滤器表槽位) |
ind | 过滤器命中回调(OS_CAN_USING_HDR 时存在) |
args | 回调参数 |
过滤器初始化宏(drivers/can/can.h,与项目逐字一致):
#define OS_CAN_FILTER_STD_INIT(id, ind, args) OS_CAN_FILTER_ITEM_INIT(id, 0, 0, 0, 0xFFFFFFFF, ind, args)
#define OS_CAN_FILTER_EXT_INIT(id, ind, args) OS_CAN_FILTER_ITEM_INIT(id, 1, 0, 0, 0xFFFFFFFF, ind, args)
#define OS_CAN_STD_RMT_FILTER_INIT(id, ind, args) OS_CAN_FILTER_ITEM_INIT(id, 0, 1, 0, 0xFFFFFFFF, ind, args)
#define OS_CAN_EXT_RMT_FILTER_INIT(id, ind, args) OS_CAN_FILTER_ITEM_INIT(id, 1, 1, 0, 0xFFFFFFFF, ind, args)
#define OS_CAN_STD_RMT_DATA_FILTER_INIT(id, ind, args) OS_CAN_FILTER_ITEM_INIT(id, 0, 0, 1, 0xFFFFFFFF, ind, args)
#define OS_CAN_EXT_RMT_DATA_FILTER_INIT(id, ind, args) OS_CAN_FILTER_ITEM_INIT(id, 1, 0, 1, 0xFFFFFFFF, ind, args)说明:以上宏展开为
OS_CAN_FILTER_ITEM_INIT,其参数顺序为(id, ide, rtr, mode, mask, ind, args),mask=0xFFFFFFFF表示 ID 完全匹配(掩码全 1)。不同宏仅ide/rtr/mode组合不同:
| 宏 | ide | rtr | mode | 过滤语义 |
|---|---|---|---|---|
OS_CAN_FILTER_STD_INIT | 0 | 0 | 0 | 标准数据帧,ID 精确匹配 |
OS_CAN_FILTER_EXT_INIT | 1 | 0 | 0 | 扩展数据帧,ID 精确匹配 |
OS_CAN_STD_RMT_FILTER_INIT | 0 | 1 | 0 | 标准远程帧 |
OS_CAN_EXT_RMT_FILTER_INIT | 1 | 1 | 0 | 扩展远程帧 |
OS_CAN_STD_RMT_DATA_FILTER_INIT | 0 | 0 | 1 | 标准数据帧,掩码模式 |
OS_CAN_EXT_RMT_DATA_FILTER_INIT | 1 | 0 | 1 | 扩展数据帧,掩码模式 |
关键理解 —— 过滤如何匹配:
mask中为1的位,要求收到帧的 ID 与id对应位相同;mask中为0的位不关心。mask = 0xFFFFFFFF表示所有位都必须匹配(即 ID 完全相等)。若想过滤"某段 ID 区间",可把mask中不关心的位清 0。
过滤匹配数值演算(标准帧 11bit ID):
| 配置 | 收帧 ID | 是否命中 | 说明 |
|---|---|---|---|
id=0x123、mask=0x7FF | 0x123 | ✅ 命中 | 全 1 掩码,完全相等 |
id=0x123、mask=0x7FF | 0x120 | ❌ 不命中 | bit1 不同,掩码要求匹配 |
id=0x120、mask=0x7E0(bit0~4 置 0) | 0x121 | ✅ 命中 | bit0~4 不关心,高位相同 |
id=0x120、mask=0x7E0 | 0x100 | ❌ 不命中 | bit6 不同,掩码要求匹配 |
2.5 CAN 操作函数表 struct os_can_ops
struct os_can_ops
{
os_err_t (*configure)(struct os_can_device *can, struct can_configure *cfg);
os_err_t (*control)(struct os_can_device *can, int cmd, void *arg);
int (*start_send)(struct os_can_device *can, const struct os_can_msg *msg);
int (*stop_send)(struct os_can_device *can);
int (*start_recv)(struct os_can_device *can, struct os_can_msg *msg);
int (*stop_recv)(struct os_can_device *can);
int (*recv_state)(struct os_can_device *can);
};| 函数指针 | 说明 |
|---|---|
configure | 配置 CAN 控制器(波特率、模式等) |
control | 控制命令(设置过滤器、获取状态等) |
start_send | 启动硬件发送一帧 CAN 消息 |
stop_send | 停止硬件发送 |
start_recv | 启动硬件接收(提供缓冲区) |
stop_recv | 停止硬件接收 |
recv_state | 获取接收状态 |
2.6 CAN 状态结构体 struct os_can_status
来源:drivers/can/can.h
struct os_can_status
{
uint32_t rcverrcnt;
uint32_t snderrcnt;
uint32_t errcode;
uint32_t rcvpkg;
uint32_t dropedrcvpkg;
uint32_t sndpkg;
uint32_t dropedsndpkg;
uint32_t bitpaderrcnt;
uint32_t formaterrcnt;
uint32_t ackerrcnt;
uint32_t biterrcnt;
uint32_t crcerrcnt;
uint32_t rcvchange;
uint32_t sndchange;
uint32_t lasterrtype;
};字段详解(源码无注释,下表补充说明,不属于源码):
| 字段 | 含义 |
|---|---|
rcverrcnt | 接收错误计数 |
snderrcnt | 发送错误计数 |
errcode | 错误码 |
rcvpkg | 接收成功包数 |
dropedrcvpkg | 接收丢弃包数 |
sndpkg | 发送成功包数 |
dropedsndpkg | 发送丢弃包数 |
bitpaderrcnt | 位填充错误计数 |
formaterrcnt | 格式错误计数 |
ackerrcnt | ACK 错误计数 |
biterrcnt | 位错误计数 |
crcerrcnt | CRC 错误计数 |
rcvchange | 接收状态变化标记(收到新帧置 1) |
sndchange | 发送状态变化标记(发出新帧置 1) |
lasterrtype | 最后错误类型 |
总线错误类型(drivers/can/can.h,与项目逐字一致):
enum OS_CAN_BUS_ERR
{
OS_CAN_BUS_NO_ERR = 0,
OS_CAN_BUS_BIT_PAD_ERR = 1,
OS_CAN_BUS_FORMAT_ERR = 2,
OS_CAN_BUS_ACK_ERR = 3,
OS_CAN_BUS_IMPLICIT_BIT_ERR = 4,
OS_CAN_BUS_EXPLICIT_BIT_ERR = 5,
OS_CAN_BUS_CRC_ERR = 6,
};| 枚举值 | 含义 |
|---|---|
OS_CAN_BUS_NO_ERR | 无错误 |
OS_CAN_BUS_BIT_PAD_ERR | 位填充错误(违反填充规则) |
OS_CAN_BUS_FORMAT_ERR | 格式错误(帧字段不符合协议) |
OS_CAN_BUS_ACK_ERR | ACK 错误(无节点应答) |
OS_CAN_BUS_IMPLICIT_BIT_ERR | 隐位错误(应发隐性却读到显性) |
OS_CAN_BUS_EXPLICIT_BIT_ERR | 显位错误(应发显性却读到隐性) |
OS_CAN_BUS_CRC_ERR | CRC 错误(校验不通过) |
关键理解 —— 状态如何产生:
rcvpkg/sndpkg在 ISR 中累加(见 5.4/5.5),rcvchange/sndchange标记"有变化"供应用轮询。这些计数用于监控总线健康:例如dropedrcvpkg持续增长说明 RX 邮箱溢出,ackerrcnt增长说明总线上没有其他节点应答。
三、设备操作接口
CAN 设备继承自 os_device_t,通过设备框架的标准接口进行访问:
| 接口 | 实现函数 | 说明 |
|---|---|---|
init | os_can_init | 初始化 CAN 设备:分配 RX/TX FIFO,配置硬件 |
deinit | os_can_deinit | 去初始化:停止收发,释放 FIFO 内存 |
read | os_can_read → _can_int_rx | 从 RX FIFO 读取 CAN 消息 |
write | os_can_write → _can_int_tx | 向 TX FIFO 写入 CAN 消息 |
control | os_can_control | 控制命令分发(过滤器/波特率/模式/状态) |
四、关键 API
4.1 设备注册
os_err_t os_hw_can_register(struct os_can_device *can,
const char *name,
const struct os_can_ops *ops,
void *data);- can: CAN 设备控制块指针
- name: 设备名称(如
"can0"、"can1") - ops: CAN 操作函数表,由 HAL 层实现
- data: 用户私有数据
- 返回:
OS_SUCCESS注册成功
注册后设备类型为 OS_DEVICE_TYPE_CAN,可通过 os_device_open("can0") 打开使用。
4.2 中断 ISR 处理函数
函数签名(drivers/can/can.c,与项目逐字一致):
void os_hw_can_isr_rxdone(struct os_can_device *can);
void os_hw_can_isr_txdone(struct os_can_device *can, int event);| 函数 | 触发时机 | 职责 |
|---|---|---|
os_hw_can_isr_rxdone(can) | HAL 层在 CAN RX 中断中调用 | 从 freelist 取一个空闲缓冲区加入 datalist,rx_count++,os_device_recv_notify 唤醒读取任务,用下一个空闲缓冲区重启接收 |
os_hw_can_isr_txdone(can, event) | HAL 层在 CAN TX 中断中调用 | 按 event 更新发送结果(OK/ERR),归还缓冲区,os_device_send_notify 唤醒等待任务,若还有排队消息则继续发送 |
这两个函数是框架层回调,由各厂商 CAN 驱动的中断服务函数调用(详见 5.4 与第八章适配)。
事件类型:
| 宏定义 | 值 | 说明 |
|---|---|---|
OS_CAN_EVENT_RX_IND | 0x01 | 接收指示 |
OS_CAN_EVENT_TX_DONE | 0x02 | 发送完成 |
OS_CAN_EVENT_TX_FAIL | 0x03 | 发送失败 |
OS_CAN_EVENT_RX_TIMEOUT | 0x05 | 接收超时 |
OS_CAN_EVENT_RXOF_IND | 0x06 | 接收溢出 |
4.3 控制命令
通过 os_device_control() 发送 CAN 专用控制命令:
| 命令 | 说明 |
|---|---|
OS_CAN_CMD_SET_FILTER | 设置硬件过滤器 |
OS_CAN_CMD_SET_BAUD | 设置波特率 |
OS_CAN_CMD_SET_MODE | 设置工作模式 |
OS_CAN_CMD_SET_PRIV | 设置优先级模式 |
OS_CAN_CMD_GET_STATUS | 获取 CAN 总线状态 |
OS_CAN_CMD_SET_STATUS_IND | 设置状态变化回调 |
OS_CAN_CMD_SET_BUS_HOOK | 设置总线错误钩子 |
五、核心源码详解
说明:以下源码均取自
drivers/can/can.c,与项目源码逐字一致(含原始英文注释)。
5.1 _can_int_rx —— 从 RX FIFO 读取消息
一句话定位:从接收 FIFO 的 datalist 取出一条或多条已接收消息给应用,并把用过的缓冲区归还到 freelist。
完整源码(drivers/can/can.c,与项目一致):
static int _can_int_rx(struct os_can_device *can, struct os_can_msg *data, int msgs)
{
int size;
os_ubase_t level;
struct os_can_rx_fifo *rx_fifo;
struct os_can_msg_list *listmsg;
OS_ASSERT(can != OS_NULL);
rx_fifo = can->can_rx;
OS_ASSERT(rx_fifo != OS_NULL);
for (size = 0; size < msgs; size += sizeof(struct os_can_msg))
{
os_spin_lock_irqsave(&gs_device_lock, &level);
if (os_list_empty(&rx_fifo->datalist))
{
os_spin_unlock_irqrestore(&gs_device_lock, level);
break;
}
listmsg = os_list_first_entry(&rx_fifo->datalist, struct os_can_msg_list, list);
os_list_del(&listmsg->list);
can->parent.rx_count--;
os_spin_unlock_irqrestore(&gs_device_lock, level);
*data++ = listmsg->data;
os_spin_lock_irqsave(&gs_device_lock, &level);
if (os_list_empty(&rx_fifo->freelist))
can->ops->start_recv(can, &listmsg->data);
os_list_add_tail(&rx_fifo->freelist, &listmsg->list);
os_spin_unlock_irqrestore(&gs_device_lock, level);
}
return size;
}分步详解:
第 1 步:循环读取,直到 msgs 条取完或无数据(条件 size < msgs)
for (size = 0; size < msgs; size += sizeof(struct os_can_msg))
{
os_spin_lock_irqsave(&gs_device_lock, &level);
if (os_list_empty(&rx_fifo->datalist))
{
os_spin_unlock_irqrestore(&gs_device_lock, level);
break;
}详细解释:msgs 是应用请求的字节数(应为 sizeof(os_can_msg) 的整数倍)。每次锁住 gs_device_lock(防止与中断竞争),若 datalist 为空(没有已接收消息),解锁并 break。加锁是关键——os_hw_can_isr_rxdone 在中断里也操作 datalist,必须用自旋锁保护。
第 2 步:取队首消息并归还缓冲区(无条件执行)
listmsg = os_list_first_entry(&rx_fifo->datalist, struct os_can_msg_list, list);
os_list_del(&listmsg->list);
can->parent.rx_count--;
os_spin_unlock_irqrestore(&gs_device_lock, level);
*data++ = listmsg->data;
os_spin_lock_irqsave(&gs_device_lock, &level);
if (os_list_empty(&rx_fifo->freelist))
can->ops->start_recv(can, &listmsg->data);
os_list_add_tail(&rx_fifo->freelist, &listmsg->list);
os_spin_unlock_irqrestore(&gs_device_lock, level);
}
return size;
}详细解释:从 datalist 取出队首消息,rx_count--,解锁后把数据拷给应用。然后再次加锁,把这块缓冲区归还到 freelist。关键点:若 freelist 此前为空(说明硬件接收缓冲区耗尽),调用 can->ops->start_recv 重新启动一个接收缓冲区,保证硬件持续能收。
_can_int_rx() 条件走向总结:
_can_int_rx() 条件走向总结:
├── for (size = 0; size < msgs; size += sizeof(msg)):
│ ├── 加锁 gs_device_lock
│ ├── datalist 为空 → 解锁 + break
│ ├── 取 datalist 队首,rx_count--,解锁
│ ├── *data++ = 消息数据(拷给应用)
│ ├── 加锁
│ ├── freelist 为空 → ops->start_recv 重新启动接收
│ ├── 归还缓冲区到 freelist,解锁
└── 返回实际读字节数 size5.2 _can_int_tx —— 向 TX FIFO 写入消息
一句话定位:从 TX freelist 取缓冲区填入待发消息,若发送队列空则立即启动硬件发送。
完整源码(drivers/can/can.c,与项目一致):
static int _can_int_tx(struct os_can_device *can, const struct os_can_msg *data, int msgs)
{
os_ubase_t level;
int ret;
int size = msgs;
struct os_can_tx_fifo *tx_fifo;
struct os_can_sndbxinx_list *tx_tosnd;
OS_ASSERT(can != OS_NULL);
tx_fifo = can->can_tx;
OS_ASSERT(tx_fifo != OS_NULL);
for (size = 0; size < msgs; size += sizeof(struct os_can_msg))
{
os_spin_lock_irqsave(&gs_device_lock, &level);
if (os_list_empty(&tx_fifo->freelist))
{
os_spin_unlock_irqrestore(&gs_device_lock, level);
break;
}
tx_tosnd = os_list_first_entry(&tx_fifo->freelist, struct os_can_sndbxinx_list, list);
OS_ASSERT(tx_tosnd != OS_NULL);
can->parent.tx_count++;
os_list_del(&tx_tosnd->list);
os_spin_unlock_irqrestore(&gs_device_lock, level);
tx_tosnd->data = *data;
os_spin_lock_irqsave(&gs_device_lock, &level);
if (os_list_empty(&tx_fifo->datalist))
{
ret = can->ops->start_send(can, &tx_tosnd->data);
if (ret != 0)
{
can->parent.tx_count--;
os_list_add_tail(&tx_fifo->freelist, &tx_tosnd->list);
os_spin_unlock_irqrestore(&gs_device_lock, level);
os_kprintf("%s send failed %d.\r\n", device_name(&can->parent), ret);
break;
}
}
os_list_add_tail(&tx_fifo->datalist, &tx_tosnd->list);
os_spin_unlock_irqrestore(&gs_device_lock, level);
can->status.sndchange = 1;
}
return size;
}分步详解:
第 1 步:取一个空闲发送缓冲区(条件 freelist 为空则退出)
for (size = 0; size < msgs; size += sizeof(struct os_can_msg))
{
os_spin_lock_irqsave(&gs_device_lock, &level);
if (os_list_empty(&tx_fifo->freelist))
{
os_spin_unlock_irqrestore(&gs_device_lock, level);
break;
}
tx_tosnd = os_list_first_entry(&tx_fifo->freelist, struct os_can_sndbxinx_list, list);
OS_ASSERT(tx_tosnd != OS_NULL);
can->parent.tx_count++;
os_list_del(&tx_tosnd->list);
os_spin_unlock_irqrestore(&gs_device_lock, level);
tx_tosnd->data = *data;详细解释:从 freelist 取一个空闲缓冲区 tx_tosnd(并用 OS_ASSERT 确保非空),tx_count++ 表示进入发送流程,把消息数据填入。freelist 空说明发送缓冲区都占用了(上一次还没发完),直接 break。
第 2 步:发送队列空则立即启动硬件(条件 datalist 为空)
os_spin_lock_irqsave(&gs_device_lock, &level);
if (os_list_empty(&tx_fifo->datalist))
{
ret = can->ops->start_send(can, &tx_tosnd->data);
if (ret != 0)
{
can->parent.tx_count--;
os_list_add_tail(&tx_fifo->freelist, &tx_tosnd->list);
os_spin_unlock_irqrestore(&gs_device_lock, level);
os_kprintf("%s send failed %d.\r\n", device_name(&can->parent), ret);
break;
}
}
os_list_add_tail(&tx_fifo->datalist, &tx_tosnd->list);
os_spin_unlock_irqrestore(&gs_device_lock, level);
can->status.sndchange = 1;
}
return size;
}详细解释:关键点:若 datalist 为空(当前没有正在发送的消息),调用 ops->start_send 立即启动硬件发送;若 start_send 失败(返回非 0),回滚 tx_count 并把缓冲区归还 freelist。然后把该消息加入 datalist(排队)。为什么 datalist 非空就不启动:说明硬件正在发送前一条,等 TX 中断(os_hw_can_isr_txdone)完成后会取下一条继续发。
_can_int_tx() 条件走向总结:
_can_int_tx() 条件走向总结:
├── for (size = 0; size < msgs; size += sizeof(msg)):
│ ├── 加锁;freelist 为空 → 解锁 + break
│ ├── 取 freelist 队首,tx_count++,解锁
│ ├── tx_tosnd->data = *data(填数据)
│ ├── 加锁
│ ├── datalist 为空(无正在发送)→ start_send
│ │ ├── start_send 失败 → tx_count--,归还 freelist,打印 + break
│ │ └── 成功 → 继续
│ ├── 加入 datalist(排队)
│ ├── 解锁;sndchange = 1
└── 返回 size5.3 os_can_init —— 初始化设备
一句话定位:应用 configure 配置硬件,分配并初始化 RX/TX FIFO(缓冲区 + 双链表)。
完整源码(drivers/can/can.c,与项目一致):
static os_err_t os_can_init(struct os_device *dev)
{
int i = 0;
struct os_can_device *can;
struct os_can_rx_fifo *rx_fifo;
struct os_can_tx_fifo *tx_fifo;
OS_ASSERT(dev != OS_NULL);
can = (struct os_can_device *)dev;
/* initialize rx/tx */
OS_ASSERT(can->can_rx == OS_NULL);
OS_ASSERT(can->can_tx == OS_NULL);
/* apply configuration */
if (can->ops->configure)
can->ops->configure(can, &can->config);
CAN_LOCK(can);
dev->rx_count = 0;
dev->rx_size = can->config.rx_msgbox_size;
rx_fifo = os_calloc(1, sizeof(struct os_can_rx_fifo) + can->config.rx_msgbox_size * sizeof(struct os_can_msg_list));
OS_ASSERT(rx_fifo != OS_NULL);
rx_fifo->buffer = (struct os_can_msg_list *)(rx_fifo + 1);
os_list_init(&rx_fifo->freelist);
os_list_init(&rx_fifo->datalist);
for (i = 0; i < can->config.rx_msgbox_size; i++)
{
os_list_add_tail(&rx_fifo->freelist, &rx_fifo->buffer[i].list);
}
can->can_rx = rx_fifo;
can->ops->start_recv(can, &rx_fifo->buffer[0].data);
dev->tx_count = 0;
dev->tx_size = can->config.tx_msgbox_size;
tx_fifo =
os_calloc(1, sizeof(struct os_can_tx_fifo) + can->config.tx_msgbox_size * sizeof(struct os_can_sndbxinx_list));
OS_ASSERT(tx_fifo != OS_NULL);
tx_fifo->buffer = (struct os_can_sndbxinx_list *)(tx_fifo + 1);
os_list_init(&tx_fifo->freelist);
os_list_init(&tx_fifo->datalist);
for (i = 0; i < can->config.tx_msgbox_size; i++)
{
os_list_add_tail(&tx_fifo->freelist, &tx_fifo->buffer[i].list);
tx_fifo->buffer[i].result = OS_CAN_SND_RESULT_OK;
}
can->can_tx = tx_fifo;
CAN_UNLOCK(can);
return OS_SUCCESS;
}分步详解:
第 1 步:应用硬件配置(条件 ops->configure 存在)
/* apply configuration */
if (can->ops->configure)
can->ops->configure(can, &can->config);详细解释:把设备配置 config(波特率/模式/邮箱大小)下发给 HAL 层 configure,配置硬件 CAN 控制器。
第 2 步:分配并初始化 RX FIFO(无条件执行)
dev->rx_count = 0;
dev->rx_size = can->config.rx_msgbox_size;
rx_fifo = os_calloc(1, sizeof(struct os_can_rx_fifo) + can->config.rx_msgbox_size * sizeof(struct os_can_msg_list));
OS_ASSERT(rx_fifo != OS_NULL);
rx_fifo->buffer = (struct os_can_msg_list *)(rx_fifo + 1);
os_list_init(&rx_fifo->freelist);
os_list_init(&rx_fifo->datalist);
for (i = 0; i < can->config.rx_msgbox_size; i++)
{
os_list_add_tail(&rx_fifo->freelist, &rx_fifo->buffer[i].list);
}
can->can_rx = rx_fifo;
can->ops->start_recv(can, &rx_fifo->buffer[0].data);详细解释:os_calloc 一次性申请 rx_msgbox_size 个消息缓冲区(rx_fifo + 1 指向紧跟在结构体后的缓冲区数组首元素),OS_ASSERT 确保分配成功。把所有缓冲区都挂到 freelist(空闲链表),os_list_init 初始化两条链表。can->can_rx = rx_fifo 绑定接收队列,start_recv 用第一个缓冲区启动硬件接收。
第 3 步:分配并初始化 TX FIFO(无条件执行)
dev->tx_count = 0;
dev->tx_size = can->config.tx_msgbox_size;
tx_fifo =
os_calloc(1, sizeof(struct os_can_tx_fifo) + can->config.tx_msgbox_size * sizeof(struct os_can_sndbxinx_list));
OS_ASSERT(tx_fifo != OS_NULL);
tx_fifo->buffer = (struct os_can_sndbxinx_list *)(tx_fifo + 1);
os_list_init(&tx_fifo->freelist);
os_list_init(&tx_fifo->datalist);
for (i = 0; i < can->config.tx_msgbox_size; i++)
{
os_list_add_tail(&tx_fifo->freelist, &tx_fifo->buffer[i].list);
tx_fifo->buffer[i].result = OS_CAN_SND_RESULT_OK;
}
can->can_tx = tx_fifo;
CAN_UNLOCK(can);
return OS_SUCCESS;
}详细解释:TX FIFO 结构与 RX 对称,同样一次性分配 tx_msgbox_size 个缓冲区、初始化 freelist/datalist,且把每个缓冲区的 result 初始化为 OS_CAN_SND_RESULT_OK(发送结果默认成功)。最后 can->can_tx = tx_fifo 绑定发送队列,CAN_UNLOCK 释放互斥锁,返回 OS_SUCCESS。
os_can_init() 条件走向总结:
os_can_init(dev) 条件走向总结:
├── 断言 dev/can_rx/can_tx 非空
├── can->ops->configure 存在
│ └── 调 configure(can, &can->config) 配置硬件
├── CAN_LOCK(can) 加锁
├── 分配并初始化 RX FIFO(rx_msgbox_size 个缓冲区,全入 freelist)
│ └── can->ops->start_recv(can, &buffer[0].data) 启动首个接收
├── 分配并初始化 TX FIFO(tx_msgbox_size 个缓冲区,result 置 OK)
├── CAN_UNLOCK(can) 解锁
└── return OS_SUCCESS5.4 中断 ISR os_hw_can_isr_rxdone / os_hw_can_isr_txdone
一句话定位:由 HAL 层在 CAN 中断中调用,完成消息从硬件到 FIFO 的搬运和唤醒。
完整源码(drivers/can/can.c,与项目一致):
void os_hw_can_isr_rxdone(struct os_can_device *can)
{
struct os_can_rx_fifo *rx_fifo;
struct os_can_msg_list *listmsg = OS_NULL;
rx_fifo = (struct os_can_rx_fifo *)can->can_rx;
OS_ASSERT(rx_fifo != OS_NULL);
can->status.rcvpkg++;
can->status.rcvchange = 1;
listmsg = os_list_first_entry_or_null(&rx_fifo->freelist, struct os_can_msg_list, list);
OS_ASSERT(listmsg != OS_NULL);
os_list_del(&listmsg->list);
os_list_add_tail(&rx_fifo->datalist, &listmsg->list);
can->parent.rx_count++;
os_device_recv_notify(&can->parent);
listmsg = os_list_first_entry_or_null(&rx_fifo->freelist, struct os_can_msg_list, list);
if (listmsg != OS_NULL)
can->ops->start_recv(can, &listmsg->data);
}分步详解:
第 1 步:统计并取一个空闲缓冲区(无条件执行)
can->status.rcvpkg++;
can->status.rcvchange = 1;
listmsg = os_list_first_entry_or_null(&rx_fifo->freelist, struct os_can_msg_list, list);
OS_ASSERT(listmsg != OS_NULL);
os_list_del(&listmsg->list);
os_list_add_tail(&rx_fifo->datalist, &listmsg->list);
can->parent.rx_count++;
os_device_recv_notify(&can->parent);详细解释:接收包数 +1,置接收变化标记。从 freelist 取一个空闲缓冲区(收到一帧),移到 datalist(可读队列),rx_count++,并 os_device_recv_notify 唤醒等待 read 的任务。
第 2 步:用下一个空闲缓冲区重新启动接收(条件 还有空闲缓冲区)
listmsg = os_list_first_entry_or_null(&rx_fifo->freelist, struct os_can_msg_list, list);
if (listmsg != OS_NULL)
can->ops->start_recv(can, &listmsg->data);
}详细解释:关键点:用 freelist 里下一个空闲缓冲区调用 start_recv 重新启动硬件接收,让 CAN 控制器能继续收下一帧(循环利用缓冲区)。
os_hw_can_isr_txdone(发送完成中断,处理对称):
void os_hw_can_isr_txdone(struct os_can_device *can, int event)
{
struct os_can_tx_fifo *tx_fifo;
struct os_can_sndbxinx_list *tx_tosnd;
tx_fifo = (struct os_can_tx_fifo *)can->can_tx;
OS_ASSERT(tx_fifo != OS_NULL);
tx_tosnd = os_list_first_entry(&tx_fifo->datalist, struct os_can_sndbxinx_list, list);
if ((event & 0xff) == OS_CAN_EVENT_TX_DONE)
{
tx_tosnd->result = OS_CAN_SND_RESULT_OK;
can->status.sndpkg++;
}
else
{
tx_tosnd->result = OS_CAN_SND_RESULT_ERR;
can->status.dropedsndpkg++;
}
os_list_del(&tx_tosnd->list);
os_list_add_tail(&tx_fifo->freelist, &tx_tosnd->list);
can->parent.tx_count--;
os_device_send_notify(&can->parent);
tx_tosnd = os_list_first_entry_or_null(&tx_fifo->datalist, struct os_can_sndbxinx_list, list);
if (tx_tosnd != OS_NULL)
can->ops->start_send(can, &tx_tosnd->data);
}分步详解:
第 1 步:按事件记录结果(条件 event == TX_DONE)
if ((event & 0xff) == OS_CAN_EVENT_TX_DONE)
{
tx_tosnd->result = OS_CAN_SND_RESULT_OK;
can->status.sndpkg++;
}
else
{
tx_tosnd->result = OS_CAN_SND_RESULT_ERR;
can->status.dropedsndpkg++;
}详细解释:event 是发送结果。TX_DONE(0x02)表示发送成功,记录成功包数;否则记录失败(dropedsndpkg++)。
第 2 步:归还缓冲区并发送下一条(无条件执行)
os_list_del(&tx_tosnd->list);
os_list_add_tail(&tx_fifo->freelist, &tx_tosnd->list);
can->parent.tx_count--;
os_device_send_notify(&can->parent);
tx_tosnd = os_list_first_entry_or_null(&tx_fifo->datalist, struct os_can_sndbxinx_list, list);
if (tx_tosnd != OS_NULL)
can->ops->start_send(can, &tx_tosnd->data);
}详细解释:把发完的缓冲区归还 freelist,tx_count--,通知等待发送的任务。关键点:若 datalist 还有排队消息(下一条),调用 start_send 继续发送,形成"发送流水线"。
os_hw_can_isr_rxdone() 条件走向总结:
os_hw_can_isr_rxdone(can) 条件走向总结:
├── status.rcvpkg++;status.rcvchange = 1(收包统计)
├── listmsg = freelist 队首(取一个空闲缓冲区)
├── 断言 listmsg 非空
├── 从 freelist 删除,加入 datalist(入可读队列)
├── parent.rx_count++
├── os_device_recv_notify(parent) 唤醒等待读的任务
├── listmsg = freelist 队首(取下一个空闲缓冲区)
│ ├── listmsg != OS_NULL(还有空闲缓冲区)
│ │ └── ops->start_recv(can, &listmsg->data) 重新启动接收
│ └── listmsg == OS_NULL(无空闲,硬件先停止接收等应用读)os_hw_can_isr_txdone() 条件走向总结:
os_hw_can_isr_txdone(can, event) 条件走向总结:
├── tx_tosnd = datalist 队首(取正在发送的消息)
├── (event & 0xff) == OS_CAN_EVENT_TX_DONE(发送成功)
│ ├── tx_tosnd->result = OS_CAN_SND_RESULT_OK
│ └── status.sndpkg++
├── (event & 0xff) != OS_CAN_EVENT_TX_DONE(发送失败)
│ ├── tx_tosnd->result = OS_CAN_SND_RESULT_ERR
│ └── status.dropedsndpkg++
├── 从 datalist 删除,归还 freelist
├── parent.tx_count--
├── os_device_send_notify(parent) 唤醒等待发送完成的任务
├── tx_tosnd = datalist 队首(取下一条排队消息)
│ ├── tx_tosnd != OS_NULL(还有消息要发)
│ │ └── ops->start_send(can, &tx_tosnd->data) 继续发送
│ └── tx_tosnd == OS_NULL(无排队,发送队列空)六、使用示例
6.1 CAN 发送示例
#include <can/can.h>
#include <device.h>
void can_send_example(void)
{
os_device_t *can_dev;
struct os_can_msg msg;
os_size_t written;
/* 1. 打开 CAN 设备 */
can_dev = os_device_open("can0");
if (can_dev == OS_NULL)
{
os_kprintf("open can0 failed!\r\n");
return;
}
/* 2. 构造 CAN 消息:先清零所有位域(rsv/priv/hdr/reserved),避免垃圾值 */
memset(&msg, 0, sizeof(msg));
msg.id = 0x123; /* 标准帧 ID = 0x123 */
msg.ide = OS_CAN_STDID; /* 标准帧 */
msg.rtr = OS_CAN_DTR; /* 数据帧 */
msg.len = 8; /* 8 字节数据 */
msg.data[0] = 0x01;
msg.data[1] = 0x02;
msg.data[2] = 0x03;
msg.data[3] = 0x04;
msg.data[4] = 0x05;
msg.data[5] = 0x06;
msg.data[6] = 0x07;
msg.data[7] = 0x08;
/* 3. 发送消息(应返回 sizeof(msg) 表示写入成功) */
written = os_device_write(can_dev, 0, &msg, sizeof(msg));
if (written != sizeof(msg))
{
os_kprintf("send can msg failed, written=%d\r\n", written);
}
}6.2 CAN 接收示例(阻塞等待)
#include <can/can.h>
#include <device.h>
void can_recv_task(void *arg)
{
os_device_t *can_dev;
struct os_can_msg msg;
os_size_t size;
can_dev = os_device_open("can0");
if (can_dev == OS_NULL)
{
os_kprintf("open can0 failed!\r\n");
return;
}
while (1)
{
/* 阻塞读取 CAN 消息 */
size = os_device_read(can_dev, 0, &msg, sizeof(msg));
if (size == sizeof(msg))
{
os_kprintf("CAN RX: ID=0x%X, len=%d, data=",
msg.id, msg.len);
for (int i = 0; i < msg.len; i++)
{
os_kprintf("%02X ", msg.data[i]);
}
os_kprintf("\r\n");
}
}
}6.3 CAN 过滤器配置示例
#include <can/can.h>
#include <device.h>
void can_filter_example(void)
{
os_device_t *can_dev;
struct os_can_filter_config filter_cfg;
struct os_can_filter_item filter_items[2];
can_dev = os_device_open("can0");
if (can_dev == OS_NULL)
return;
/* 配置过滤器 1:只接收标准帧 ID=0x123 */
filter_items[0].id = 0x123;
filter_items[0].ide = OS_CAN_STDID;
filter_items[0].rtr = OS_CAN_DTR;
filter_items[0].mode = 0; /* ID 列表模式 */
filter_items[0].mask = 0xFFFFFFFF; /* 全部位必须匹配 */
filter_items[0].hdr = -1;
/* 配置过滤器 2:接收标准帧 ID=0x200~0x2FF */
filter_items[1].id = 0x200;
filter_items[1].ide = OS_CAN_STDID;
filter_items[1].rtr = OS_CAN_DTR;
filter_items[1].mode = 1; /* ID 掩码模式 */
filter_items[1].mask = 0x700; /* 高 3 位匹配,低 8 位不关心 */
filter_items[1].hdr = -1;
filter_cfg.count = 2;
filter_cfg.actived = 1; /* 激活过滤器 */
filter_cfg.items = filter_items;
if (os_device_control(can_dev, OS_CAN_CMD_SET_FILTER, &filter_cfg) != OS_SUCCESS)
{
os_kprintf("set filter failed!\r\n");
return;
}
}说明:过滤器
mode=0是列表模式(ID 精确匹配)、mode=1是掩码模式(mask中 1 的位必须匹配)。actived=1表示激活这些过滤项,0表示停用。
6.4 CAN 状态监控示例
void can_status_monitor(os_device_t *can_dev)
{
struct os_can_status status;
if (can_dev == OS_NULL)
return;
if (os_device_control(can_dev, OS_CAN_CMD_GET_STATUS, &status) != OS_SUCCESS)
{
os_kprintf("get can status failed!\r\n");
return;
}
os_kprintf("=== CAN Status ===\r\n");
os_kprintf("RX packets: %d (dropped: %d)\r\n",
status.rcvpkg, status.dropedrcvpkg);
os_kprintf("TX packets: %d (dropped: %d)\r\n",
status.sndpkg, status.dropedsndpkg);
os_kprintf("RX errors: %d, TX errors: %d\r\n",
status.rcverrcnt, status.snderrcnt);
os_kprintf("Bus errors: bit_pad=%d format=%d ack=%d bit=%d crc=%d\r\n",
status.bitpaderrcnt, status.formaterrcnt,
status.ackerrcnt, status.biterrcnt, status.crcerrcnt);
}6.5 HAL 层注册示例
#include <can/can.h>
#include <device.h>
/* HAL 层配置:波特率 500K,Normal 模式 */
struct can_configure can_cfg =
{
.baud_rate = CAN500kBaud,
.rx_msgbox_size = 16,
.tx_msgbox_size = 8,
.mode = OS_CAN_MODE_NORMAL,
};
/* HAL 层需实现 os_can_ops 函数表 */
static const struct os_can_ops stm32_can_ops =
{
.configure = stm32_can_configure,
.control = stm32_can_control,
.start_send = stm32_can_start_send,
.stop_send = stm32_can_stop_send,
.start_recv = stm32_can_start_recv,
.stop_recv = stm32_can_stop_recv,
.recv_state = stm32_can_recv_state,
};
/* 注册 CAN 设备 */
void can_device_init(void)
{
static struct os_can_device can_dev;
/* 设置默认配置 */
can_dev.config = can_cfg;
/* 注册到设备框架,检查返回值 */
if (os_hw_can_register(&can_dev, "can0", &stm32_can_ops, OS_NULL) != OS_SUCCESS)
{
os_kprintf("can0 register failed!\r\n");
return;
}
}七、内部实现机制
7.1 RX FIFO 机制
CAN 框架使用 RX FIFO 管理接收消息,核心是两个链表:
freelist(空闲缓冲区) datalist(已接收数据)
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ buf0 │→│ buf1 │→... │ buf2 │→│ buf3 │→...
└──────┘ └──────┘ └──────┘ └──────┘
↑ ISR 取走 │ os_device_read() 取走
│ ↓
└────────── 归还 ──────────────┘接收流程:
- HAL 层 CAN RX 中断触发 → 调用
os_hw_can_isr_rxdone() - 从
rx_fifo->freelist取一个空闲缓冲区 - 填充数据后加入
rx_fifo->datalist can->parent.rx_count++,调用os_device_recv_notify()唤醒等待读取的任务- 应用层调用
os_device_read()时从datalist取出数据,归还到freelist
7.2 TX FIFO 机制
freelist(空闲缓冲区) datalist(排队发送)
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ buf0 │→│ buf1 │→... │ buf2 │→│ buf3 │→...
└──────┘ └──────┘ └──────┘ └──────┘
↑ os_device_write() 取走 │ ISR 发送完成
│ ↓
└────────── 归还 ──────────────┘发送流程:
- 应用层调用
os_device_write()→_can_int_tx() - 从
tx_fifo->freelist取一个空闲缓冲区,填入数据 - 加入
tx_fifo->datalist,如果 datalist 之前为空,立即调用ops->start_send()启动硬件发送 - HAL 层 CAN TX 中断触发 → 调用
os_hw_can_isr_txdone() - 更新发送结果(OK/ERR),归还缓冲区,检查 datalist 中是否还有排队消息,有则继续发送
7.3 并发保护
- 设备级互斥锁:
can->lock互斥量,在os_can_init/os_can_deinit中使用 - 中断安全锁:
gs_device_lock自旋锁,在 RX/TX FIFO 操作中使用,保护中断上下文与任务上下文之间的并发访问
八、CAN 的适配(接入新 MCU 的 CAN 控制器)
8.1 先认识适配层:CAN 框架是怎么"长"到芯片上的
drivers/can/can.c 是跨平台统一的框架层(FIFO 队列、中断 ISR 搬运、设备注册、并发保护),不碰具体寄存器。真正操作 CAN 控制器的是各厂商 HAL 驱动,例如 ST 的 drivers/hal/st/drivers/drv_can.c。换 MCU 就是换这份 drv_can.c。
适配全景图:
┌──────────────────────────────────────────────────────────┐
│ 应用层 │
│ os_device_open("can0") → read/write/control(过滤/波特率) │
└──────────────────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ CAN 框架层(drivers/can/can.c) │
│ os_hw_can_register / _can_int_rx/_tx / os_can_init │
│ os_hw_can_isr_rxdone/txdone(不改) │
└───────────────┬──────────────────────────┬────────────────┘
│ ①实现 os_can_ops │ ②厂商 ISR 调框架回调
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ 厂商驱动适配层(drv_can.c)★需实现★ │
│ stm32_can_ops(configure/control/start_send/start_recv)│
│ HAL_CAN_* 回调 → os_hw_can_isr_* │
└───────────────┬──────────────────────────┬────────────────┘
│ HAL 库(HAL_CAN_*) │ 中断服务
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ 硬件(MCU 的 bxCAN / FDCAN 外设) │
└──────────────────────────────────────────────────────────┘适配的关键洞察:接入新 CAN 控制器要做两件事:① 实现 struct os_can_ops 全部回调(configure/control/start_send/start_recv 等);② 在厂商中断回调里调用 os_hw_can_isr_rxdone/os_hw_can_isr_txdone,把硬件事件通知给框架。框架层不用改。
8.2 以 ST 为例,看一整套适配代码
以 drivers/hal/st/drivers/drv_can.c 为例(与项目逐字一致,去除无关部分展示关键结构):
① 实现 os_can_ops —— 全部回调:
static const struct os_can_ops stm32_can_ops = {
.configure = stm32_can_config,
.control = stm32_can_control,
.start_send = stm32_can_start_send,
.stop_send = stm32_can_stop_send,
.start_recv = stm32_can_start_recv,
.stop_recv = stm32_can_stop_recv,
.recv_state = stm32_can_recv_state,
};要点:
configure里把框架的can_configure(波特率/模式)换算成 HAL 的Prescaler/TimeSeg并HAL_CAN_Init;control分发过滤/模式/波特率/状态命令;start_send/start_recv使能对应中断。
② 实现厂商中断回调 → 调框架 ISR(收发完成/失败通知框架):
void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan)
{
__HAL_CAN_DISABLE_IT(hcan, CAN_IT_TX_MAILBOX_EMPTY);
os_hw_can_isr_txdone((struct os_can_device *)find_stm32_can(hcan), OS_CAN_EVENT_TX_DONE);
}
void HAL_CAN_TxMailbox0AbortCallback(CAN_HandleTypeDef *hcan)
{
os_hw_can_isr_txdone((struct os_can_device *)find_stm32_can(hcan), OS_CAN_EVENT_TX_FAIL);
}
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
struct stm32_can *st_can = find_stm32_can(hcan);
__HAL_CAN_DISABLE_IT(hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
if (st_can->rx_msg == OS_NULL)
return;
stm32_can_recvmsg(&st_can->can, st_can->rx_msg, CAN_RX_FIFO0);
st_can->rx_msg = OS_NULL;
os_hw_can_isr_rxdone((struct os_can_device *)st_can);
}要点:收到帧时,先
stm32_can_recvmsg把 HAL 的CAN_RxHeaderTypeDef转成框架的os_can_msg(填 id/ide/rtr/len/data),再os_hw_can_isr_rxdone通知框架入 FIFO。发送完成/失败分别传OS_CAN_EVENT_TX_DONE/OS_CAN_EVENT_TX_FAIL。
③ 通过驱动框架注册设备(OS_DRIVER_INFO + probe):
static int stm32_can_probe(const os_driver_info_t *drv, const os_device_info_t *dev)
{
struct can_configure config = CANDEFAULTCONFIG;
struct stm32_can *st_can = os_calloc(1, sizeof(struct stm32_can));
OS_ASSERT(st_can);
st_can->hcan = (CAN_HandleTypeDef *)dev->info;
config.privmode = OS_CAN_MODE_NOPRIV;
config.ticks = 50;
st_can->can.config = config;
os_list_add_tail(&gs_stm32_can_list, &st_can->list);
os_hw_can_register(&st_can->can, dev->name, &stm32_can_ops, st_can);
return OS_SUCCESS;
}
OS_DRIVER_INFO stm32_can_driver = {
.name = "CAN_HandleTypeDef",
.probe = stm32_can_probe,
};
OS_DRIVER_DEFINE(stm32_can_driver, OS_INIT_LEVEL_PRE_DEVICE, OS_INIT_SUBLEVEL_LOW);要点:
OS_DRIVER_INFO用"CAN_HandleTypeDef"匹配板级配置表里的 HAL 句柄,probe里dev->info取到hcan,初始化config后用os_hw_can_register注册。用OS_INIT_LEVEL_PRE_DEVICE在设备层前初始化。
8.3 接口契约:接入 CAN 需要实现什么
| 层 | 接口/回调 | 职责 | 对应 8.2 ST 代码 |
|---|---|---|---|
| 驱动注册 | os_hw_can_register | 把 CAN 设备挂进框架 | stm32_can_probe |
| 操作回调 | os_can_ops.configure | 波特率/模式 → HAL 初始化 | stm32_can_config |
| 操作回调 | os_can_ops.control | 过滤/模式/波特率/状态控制 | stm32_can_control |
| 操作回调 | os_can_ops.start_send/start_recv | 使能收发中断 | stm32_can_start_send/recv |
| 中断通知 | os_hw_can_isr_rxdone | 收帧入 FIFO + 唤醒 | HAL_CAN_RxFifo*Callback |
| 中断通知 | os_hw_can_isr_txdone | 发完成/失败通知框架 | HAL_CAN_TxMailbox*Callback |
| 初始化时机 | OS_DRIVER_DEFINE | 设备层前注册 | OS_INIT_LEVEL_PRE_DEVICE |
8.4 适配新 MCU 的标准流程
第 1 步:确认 CAN 外设能力——查手册确认控制器类型(经典 CAN/CAN-FD)、邮箱数量、滤波器支持情况。
第 2 步:新建厂商 drv_can.c——在厂商 HAL 目录新建,定义厂商私有结构(含 struct os_can_device can 首成员继承,实现 os_can_ops 全部回调)。
第 3 步:实现收发底层——start_send 调 HAL 发送并登记 rx_msg;实现厂商中断回调,把 HAL 句柄转成 os_can_msg 后调 os_hw_can_isr_*。
第 4 步:注册设备——写 probe(dev->info 取 HAL 句柄、初始化 config、os_hw_can_register),配 OS_DRIVER_INFO/OS_DRIVER_DEFINE。
第 5 步:板级配置——在 board/peripherals.c 用 OS_HAL_DEVICE_DEFINE("CAN_HandleTypeDef", "can0", hcan) 之类把 HAL 句柄实例化,probe 才能匹配到。
第 6 步:测试——先用 Loopback 模式自发自收验证收发链路,再上真实总线验证波特率和过滤。
8.5 适配检查清单(快速自查)
| 检查项 | 说明 |
|---|---|
os_can_ops 全部回调都实现? | 漏一个则 start_send/start_recv 等被空调用,收不到/发不出 |
厂商私有结构首成员是 os_can_device? | probe 里 &st_can->can 才能传给框架 |
收到帧后调了 os_hw_can_isr_rxdone? | 否则数据停在 HAL,进不了 FIFO,应用读不到 |
发送完成/失败调了 os_hw_can_isr_txdone? | 否则 tx_count 不减、等待发送的任务永久挂起 |
板级 OS_HAL_DEVICE_DEFINE 名字匹配 OS_DRIVER_INFO.name? | 不匹配则 probe 不触发,设备注册不上 |
| 波特率换算正确? | 波特率算错则总线上节点收不到数据 |
九、注意事项
- 中断上下文:
os_hw_can_isr_rxdone和os_hw_can_isr_txdone在 ISR 上下文中调用,内部操作应尽量简短 - 邮箱大小:
rx_msgbox_size决定了接收缓冲区数量,过小可能导致丢包;tx_msgbox_size决定了发送缓冲区数量,影响发送吞吐量 - 过滤器配置: 过滤器配置通过
OS_CAN_CMD_SET_FILTER控制命令下发到 HAL 层,硬件支持情况因 MCU 而异 - HDR 模式: 启用
OS_CAN_USING_HDR后,每个过滤器条目可绑定回调函数,匹配的消息直接通过回调处理,不经过 FIFO - 总线错误处理: 启用
OS_CAN_USING_BUS_HOOK后,可注册bus_hook回调处理总线错误事件(Bus-Off 恢复等) - 波特率必须一致: 总线上所有节点波特率必须相同,否则收不到/误码;用
OS_CAN_CMD_SET_BAUD配置
十、驱动文件位置
| 文件 | 路径 | 说明 |
|---|---|---|
can.h | drivers/can/can.h | CAN 框架头文件,数据结构定义、API 声明 |
can.c | drivers/can/can.c | CAN 框架实现,设备注册、中断处理、FIFO 管理 |
Kconfig | drivers/can/Kconfig | 编译配置选项 |
drv_can.c(示例) | drivers/hal/st/drivers/drv_can.c | ST 厂商 CAN 适配层,实现 os_can_ops + 中断回调 |
📝 本节小结
核心要点回顾
本节围绕 CAN 设备展开,核心要点如下:
- 统一抽象:将不同 MCU 的 CAN 控制器封装为
os_can_device_t,HAL 层实现os_can_ops,框架层在drivers/can/、厂商适配在drivers/hal/*/drivers/drv_can.c。 - 位打包消息:
os_can_msg用位域把id(29bit)/ide/rtr/rsv打包进 32 位头部,紧跟 8 字节数据,直接按内存布局交给硬件。 - 消息邮箱:内置 RX/TX FIFO(
freelist+datalist双链表),支持配置邮箱大小;_can_int_rx/_can_int_tx负责应用与 FIFO 之间的搬运。 - 中断 ISR:
os_hw_can_isr_rxdone(收帧入队 + 重新启动接收)、os_hw_can_isr_txdone(记结果 + 发送下一条),由厂商中断回调调用。 - 过滤器:支持标准/扩展/远程帧的硬件过滤器(列表/掩码模式),
mask全 1 = 完全匹配,可选 HDR 回调模式。 - 多模式:Normal / Loopback / Listen / Loopback+Listen 四种工作模式。
- 并发保护:
can->lock互斥量(设备级)+gs_device_lock自旋锁(FIFO 操作)。 - 硬件适配:接入新 CAN = 实现
os_can_ops全部回调 + 厂商中断回调调os_hw_can_isr_*+OS_DRIVER_INFO/probe 注册。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
read/write 的 size 不是 sizeof(msg) 的整数倍 | 一次读写按 sizeof(struct os_can_msg) 的倍数进行,否则 _can_int_rx/tx 循环步进错乱 |
| 邮箱过小导致丢包/发送阻塞 | rx_msgbox_size/tx_msgbox_size 决定缓冲区数,按消息速率合理配置 |
| 在 ISR 里做耗时操作 | os_hw_can_isr_rxdone/txdone 在中断上下文执行,应保持简短 |
过滤器 mask 理解错误 | mask=1 表示该位必须匹配,mask=0 不关心;列表模式全部匹配、掩码模式按掩码过滤 |
| 忽略 HDR/总线钩子的条件编译 | HDR(OS_CAN_USING_HDR)和总线钩子(OS_CAN_USING_BUS_HOOK)需对应 Kconfig 开启才生效 |
未调用 start_recv 循环续收 | 收帧后若 freelist 非空会用新缓冲区重启接收;freelist 耗尽时 _can_int_rx 归还时会重启,不要漏 |
| 总线波特率不一致 | 总线上所有节点波特率必须相同,否则收不到/误码;用 OS_CAN_CMD_SET_BAUD 配置 |
把 os_can_msg 当普通结构按字节赋值 | id/ide/rtr 是位域,应按字段赋值;不要用 memcpy 从网络字节序直接填 |
适配漏调 os_hw_can_isr_rxdone/txdone | 收帧/发完不通知框架,数据进不了 FIFO、发送任务永久挂起 |
下一步
掌握了 CAN 总线设备后,接下来学习 FAL Flash 抽象层:
- 5.17 FAL设备:了解 OneOS 的分区表定义、FAL 分区读写擦与 Flash 设备抽象。