API GUIDE / CAN / MIDDLEWARE
CAN Middleware 接口
can mid api
1. 文档范围
本文档定义 G32S10 SDK CAN middleware(MID)层的公开接口、配置约束、资源所有权 和运行语义,面向使用 Classic CAN 或 CAN FD 完成设备通信的应用代码。
本文档以当前 SDK 源码为准,主要核对文件如下:
| 类型 | 路径 |
|---|---|
| MID 公开接口 | code/middleware/can/include/im_can.h |
| MID 生命周期与传输实现 | code/middleware/can/src/im_can.c |
| MID 协议与帧格式实现 | code/middleware/can/src/im_can_protocol.c |
| MID 私有 DMA 路由 | code/middleware/can/src/im_can_dma.c |
| 构建配置 | code/middleware/can/Kconfig |
| CMake 集成 | code/middleware/can/CMakeLists.txt |
| MID 示例 | code/middleware/can/example/ |
补充说明:
- MID 通过 CAN HAL 公共接口访问控制器,不直接操作 CAN 寄存器。HAL/LL 的控制器
配置、时序字段、帧窗口和中断限制见
G32S10 CAN HAL/LL 正式接口文档。 - 应用必须在使用 MID 前完成板级 CAN pinmux、时钟树基础和外部收发器配置。MID 管理 CAN 控制器、MID 私有 DMA 请求、SoC IRQ、RX 队列和收发等待资源,不配置 板级 pinmux 或收发器。
- MID 的公开收发接口均为同步接口。IRQ 和 DMA 是内部完成路径,应用不需要调用 单独的 start/wait 接口。
- 本文描述的返回值、状态变化、事件和资源释放行为均以当前源码为准。
目录
2. 分层关系与依赖
Application / BSP / Example
|
v
MID CAN
|
+-------> CPM CAN clock and gate
+-------> IM DMA (optional TX/RX DMA)
+-------> IRQ framework (CAN0/CAN1 IRQ)
|
v
HAL CAN
|
v
LL CAN
|
v
CAN Registers
MID 层职责:
- 校验应用帧,完成 payload 长度、DLC 和 CAN FD 帧格式转换。
- 根据 CANCLK 和 bitrate 执行时序表查询,或把应用的原始 timing 字段交给 HAL。
- 管理 CAN 控制器生命周期、Acceptance filter、SoC IRQ 和 MID 私有 DMA 请求。
- 提供 polling、IRQ 和 DMA 三种 TX/RX 执行路径,并把它们封装为同步公开调用。
- 管理 IRQ RX 环形队列、DMA byte ring 的解码和超时等待。
- 记录 sticky 错误历史、累计统计,并通过 callback 报告 CAN 事件。
MID 不负责:
- 建立 CAN 物理总线、连接外部收发器或配置板级 pinmux。
- 修改系统 PLL 或其他不属于 CAN 时钟树的 CPM 配置。
- 为应用创建 TX 或 RX 任务。应用应按业务模型安排发送和接收调用。
- 暴露独立的 TX start/wait 公共调用。
im_can_transfer()完成整个 TX 生命周期。 - 释放调用方提供的 RX 队列和 RX DMA ring。
3. 构建配置与文件组织
3.1 Kconfig
CAN MID 的 Kconfig 位于 code/middleware/can/Kconfig。启用方式如下:
CONFIG_DRIVER_CAN=y
CONFIG_MIDDLEWARE_CAN=y
CONFIG_MIDDLEWARE_CAN 的依赖和选择关系如下:
| 选项 | 作用 |
|---|---|
DRIVER_CAN |
编译 G32S10 CAN HAL/LL 驱动。 |
OS_API |
提供 MID 使用的线程 waiter、临界区和调度接口。 |
DRIVER_IRQ |
提供 SoC IRQ 注册、使能和释放能力。 |
IRQ_SIMPLE_DISPATCH |
必须关闭;CAN MID 依赖普通 PLIC IRQ 注册路径。 |
MIDDLEWARE_DMA |
由 CAN MID 自动选择,提供 TX/RX DMA 请求和等待服务。 |
MIDDLEWARE_DMA 继续选择底层 DMA 驱动。即使应用只使用 polling 模式,构建仍会
包含 CAN MID 的 DMA 依赖;运行期只申请当前模式实际使用的 DMA 资源。
3.2 文件职责
| 文件 | 说明 |
|---|---|
code/middleware/can/include/im_can.h |
公开宏、帧/类型、配置结构、回调和 API 声明。 |
code/middleware/can/src/im_can.c |
单实例所有权、生命周期、时钟规划、IRQ、队列、收发、恢复和统计。 |
code/middleware/can/src/im_can_dma.c |
把 CAN 实例映射到平台 RX DMA 请求,不向 HAL 公共接口暴露 DMA source。 |
code/middleware/can/src/im_can_dma.h |
CAN MID 私有 DMA 路由声明。 |
code/middleware/can/src/im_can_protocol.c |
帧校验、DLC 转换和非标准 FD 长度补齐。 |
code/middleware/can/src/im_can_protocol.h |
CAN MID 内部协议 helper 声明。 |
code/middleware/can/Kconfig |
MIDDLEWARE_CAN 构建选项、依赖和 DMA 选择。 |
code/middleware/can/CMakeLists.txt |
在 CONFIG_MIDDLEWARE_CAN=y 时加入 MID 源文件和头文件目录。 |
code/middleware/can/example/main/main.c |
CAN0 到 CAN1 的 IRQ 模式 Classic CAN 示例。 |
4. 资源模型与公开类型
4.1 控制器实例与所有权
| 实例 | 说明 |
|---|---|
CAN0 |
独立的 CAN 控制器实例。 |
CAN1 |
独立的 CAN 控制器实例。 |
每个控制器同一时刻只能绑定一个 im_can_t。重复绑定同一实例返回 IM_ERR_BUSY。
MID 从控制器实例推导平台 IRQ 和 DMA 路由;应用只填写 CAN 实例和可选 IRQ 名称,
不需要选择底层中断号或 DMA source。
im_can_t 采用静态分配模型。对象地址必须在 im_can_init() 前保持到
im_can_deinit() 返回后,绑定期间不得移动、覆盖或复用对象存储。
im_can.h 为便于静态分配而公开 struct im_can 的完整布局,但其中字段均由 MID
管理。应用不得直接读写、复制或持久化活动对象的内部字段,应使用公开查询接口。
4.2 公开宏
| 宏 | 值 | 说明 |
|---|---|---|
IM_CAN_RX_DMA_MIN_BUFFER_SIZE |
256U |
RX DMA ring 最小字节数。 |
IM_CAN_RX_DMA_BUFFER_ALIGN |
HAL_DMA_DESCRIPTOR_ALIGN |
RX DMA ring 起始地址对齐要求。 |
IM_CAN_RX_DMA_FRAME_BUDGET_SIZE |
128U |
RX DMA 每帧预留字节数,也是 buffer 大小的对齐单位。 |
IM_CAN_RX_DMA_SEGMENT_COUNT |
1U |
RX cyclic DMA descriptor 的 segment 数。 |
IM_CAN_TX_DMA_SEGMENT_COUNT |
1U |
TX linked DMA descriptor 的 segment 数。 |
IM_CAN_DEFAULT_NOMINAL_BITRATE |
1000000U |
默认仲裁域速率,单位 bit/s。 |
IM_CAN_DEFAULT_DATA_BITRATE |
1000000U |
默认 FD 数据域速率,单位 bit/s。 |
IM_CAN_CLASSIC_MAX_DATA_LEN |
8U |
Classic CAN 最大 payload 长度。 |
IM_CAN_MAX_DATA_LEN |
64U |
CAN FD 最大 payload 长度。 |
IM_CAN_STD_ID_MAX |
0x000007FF |
标准标识符最大值。 |
IM_CAN_EXT_ID_MAX |
0x1FFFFFFF |
扩展标识符最大值。 |
IM_CAN_FILTER_ACCEPT_ALL |
IM_CAN_EXT_ID_MAX |
filter mask 全 1,忽略所有标识符 bit。 |
4.3 应用层 CAN 帧
typedef struct {
uint32_t id;
uint32_t len;
uint32_t dlc;
bool is_extended;
bool is_remote;
bool is_fd;
bool brs;
bool esi;
uint32_t filter_match;
uint8_t data[IM_CAN_MAX_DATA_LEN];
} im_can_frame_t;
| 字段 | TX | RX |
|---|---|---|
id |
标准 ID 不超过 0x7FF,扩展 ID 不超过 0x1FFFFFFF |
HAL 解码后的有效标识符 |
len |
应用提供的数据长度;数据帧 DLC 由 MID 推导 | 线上帧解码后的 payload 长度 |
dlc |
仅 Classic remote 帧使用,范围 0..8 |
接收帧的原始 DLC 编码 |
is_extended |
true 表示 29-bit 扩展 ID | 接收帧 ID 格式 |
is_remote |
true 表示 remote 帧;remote 帧不能使用 FD 或 BRS | 是否收到 remote 帧 |
is_fd |
true 表示 CAN FD 帧,要求配置已启用 FD | 是否收到 CAN FD 帧 |
brs |
仅 FD 数据帧可置位 | 接收帧是否使用 bit-rate switching |
esi |
TX 时忽略 | 接收帧的 error-state indicator |
filter_match |
TX 时忽略 | 接收匹配的硬件 filter index |
data |
只使用前 len byte |
只使用前 len byte |
调用 im_can_frame_struct_init() 可将整个结构清零。
4.4 Acceptance filter
typedef struct {
uint32_t id;
uint32_t mask;
} im_can_filter_t;
mask 的 bit 为 0 时比较对应 ID bit,为 1 时忽略该 bit:
mask = 0:精确匹配id;mask = IM_CAN_FILTER_ACCEPT_ALL:接受全部标识符;id和mask均不得超过0x1FFFFFFF。
每个控制器有 HAL_CAN_FILTER_COUNT 个硬件 filter,当前为 4 个。
im_can_filter_struct_init() 返回 id = 0、mask 全 1 的接受全部配置。
4.5 传输模式
typedef enum {
IM_CAN_TRANSFER_POLLING = 0,
IM_CAN_TRANSFER_IRQ,
IM_CAN_TRANSFER_DMA,
} im_can_transfer_mode_t;
| 模式 | TX | RX | 调用方额外存储 |
|---|---|---|---|
POLLING |
调用线程写入并轮询发送完成 | 调用线程轮询硬件 RX buffer | 无 |
IRQ |
TX complete IRQ 唤醒同步调用 | CAN IRQ 将帧写入 rx_queue |
im_can_frame_t 环形队列 |
DMA |
memory DMA 搬运内部帧镜像到 TX 窗口 | cyclic DMA 写入 RX byte ring | 对齐的 RX DMA buffer |
tx_mode 和 rx_mode 可以独立选择。只要任一方向不是 polling,MID 就会注册并使能
对应 CAN 实例的 SoC IRQ。
4.6 运行状态
typedef enum {
IM_CAN_STATE_RESET = 0,
IM_CAN_STATE_READY,
IM_CAN_STATE_BUSY,
IM_CAN_STATE_ERROR,
IM_CAN_STATE_TIMEOUT,
IM_CAN_STATE_BUS_OFF,
} im_can_state_t;
| 状态 | 含义 |
|---|---|
RESET |
未初始化、正在初始化或已经反初始化 |
READY |
可以发起 TX 或 RX |
BUSY |
当前存在活动 TX |
ERROR |
最近一次 TX 或控制操作失败 |
TIMEOUT |
最近一次 TX 因有限超时被终止 |
BUS_OFF |
控制器处于 bus-off,收发返回 IM_ERR_NOT_READY,等待自动或显式恢复 |
4.7 应用事件与 callback
typedef enum {
IM_CAN_EVENT_RX_READY = 0,
IM_CAN_EVENT_TX_DONE,
IM_CAN_EVENT_RX_OVERFLOW,
IM_CAN_EVENT_ERROR,
IM_CAN_EVENT_TIMEOUT,
IM_CAN_EVENT_BUS_OFF,
IM_CAN_EVENT_RECOVERED,
IM_CAN_EVENT_ERROR_WARNING,
IM_CAN_EVENT_ERROR_PASSIVE,
IM_CAN_EVENT_ARBITRATION_LOST,
IM_CAN_EVENT_BUS_ERROR,
} im_can_event_t;
typedef void (*im_can_callback_t)(im_can_t *dev,
im_can_event_t event,
void *user_data);
事件通过 im_can_config_t::callback 报告。IRQ 和 DMA 路径中的事件通常在中断
上下文产生;callback 必须非阻塞,不能调用会阻塞的 CAN API。
| 事件 | 含义 |
|---|---|
RX_READY |
帧已放入 IRQ 队列或 DMA ring。 |
TX_DONE |
TX 请求成功完成。 |
RX_OVERFLOW |
RX 队列、DMA ring 或硬件 RX overrun 导致丢帧。 |
ERROR |
middleware、DMA 或控制器操作失败。 |
TIMEOUT |
有限等待到期,或发送因超时被终止。 |
BUS_OFF |
控制器进入 bus-off。 |
RECOVERED |
已从 bus-off 恢复。 |
ERROR_WARNING |
达到 error-warning 阈值。 |
ERROR_PASSIVE |
进入 error-passive 状态。 |
ARBITRATION_LOST |
检测到仲裁丢失。 |
BUS_ERROR |
检测到 CAN bus error。 |
4.8 统计信息
typedef struct {
uint32_t rx_frames;
uint32_t tx_frames;
uint32_t rx_overflows;
uint32_t dma_overflows;
uint32_t dma_errors;
uint32_t error_warnings;
uint32_t error_passive;
uint32_t arbitration_lost;
uint32_t bus_errors;
uint32_t bus_off;
uint32_t recoveries;
} im_can_stats_t;
| 字段 | 含义 |
|---|---|
rx_frames |
通过 im_can_receive() 成功交付的帧数 |
tx_frames |
成功发送完成的帧数 |
rx_overflows |
IRQ 队列满或硬件 RX overrun 导致的丢帧次数 |
dma_overflows |
RX DMA ring 中尚未读取的帧超过一个 descriptor cycle 的次数 |
dma_errors |
RX DMA 回调、状态异常或帧解析失败次数 |
error_warnings |
error-warning 事件数 |
error_passive |
error-passive 事件数 |
arbitration_lost |
仲裁丢失事件数 |
bus_errors |
CAN bus-error 事件数 |
bus_off |
进入 bus-off 的次数 |
recoveries |
成功从 bus-off 恢复的次数 |
统计值累计保存。im_can_clear_error_flags() 只清除错误历史,不会清除统计值。
4.9 时钟规划
typedef struct {
hal_cpm_id_t parent;
uint32_t parent_rate_hz;
uint32_t module_clock_hz;
uint32_t divider;
} im_can_clock_plan_t;
时钟链为:
parent clock -> CAN clock divider (1..256) -> CANCLK -> CAN bit-timing prescaler
CANCLK = parent_rate_hz / divider。im_can_clock_plan_get() 只计算结果,不修改
寄存器;im_can_init() 在 use_clock_plan = true 时使用该结果配置 CAN HAL。
4.10 初始化配置
typedef struct {
hal_can_config_t hal_config;
bool use_clock_plan;
bool use_timing_table;
uint32_t nominal_bitrate;
uint32_t data_bitrate;
im_can_transfer_mode_t tx_mode;
im_can_transfer_mode_t rx_mode;
const char *irq_name;
im_can_frame_t *rx_queue;
uint32_t rx_queue_count;
uint8_t *rx_dma_buffer;
uint32_t rx_dma_buffer_size;
bool manage_dma_cache;
im_can_callback_t callback;
void *user_data;
} im_can_config_t;
im_can_config_struct_init() 的主要默认值如下:
| 字段 | 默认值 | 约束 |
|---|---|---|
hal_config |
HAL 默认配置 | hal_config.instance 必须为 CAN0 或 CAN1 |
hal_config.module_clock_hz |
24000000U |
必须非零 |
hal_config.fd_enable |
true | 发送 FD 帧时必须保持 true |
use_clock_plan |
硬件构建为 true;CONFIG_CPM_ENABLE_STUB=y 时为 false |
true 时 MID 精确选择父时钟和分频 |
use_timing_table |
true | true 时根据有效 CANCLK 和 bitrate 查表 |
nominal_bitrate |
1 Mbit/s | use_timing_table = true 时必须非零 |
data_bitrate |
1 Mbit/s | FD 且查表时必须非零;Classic 忽略 |
tx_mode |
IM_CAN_TRANSFER_POLLING |
三种模式之一 |
rx_mode |
IM_CAN_TRANSFER_POLLING |
三种模式之一 |
irq_name |
"im_can" |
可为 NULL;只作为 IRQ 注册名称 |
rx_queue |
NULL | rx_mode = IRQ 时必填 |
rx_queue_count |
0 | rx_mode = IRQ 时必须非零 |
rx_dma_buffer |
NULL | rx_mode = DMA 时必填 |
rx_dma_buffer_size |
0 | 至少 256 byte,且为 128 byte 的整数倍 |
manage_dma_cache |
true | 由 MID 执行必要的 cache clean/invalidate |
callback |
NULL | 可选事件 callback |
user_data |
NULL | 原样传给 callback |
当 use_timing_table = false 时,应用必须在 hal_config.timing 中提供合法的原始
HAL timing 字段。
当 use_clock_plan = true 时,MID 覆盖 hal_config.module_clock_parent,要求 HAL
精确回读请求的 CANCLK,并使用精确的 CANCLK 查询时序表。父时钟或分频无法精确
产生请求频率时,初始化失败,不回退到较低频率。
当 use_clock_plan = false 时,MID 使用调用方设置的
hal_config.module_clock_parent,遵循 HAL 的时钟设置和就近分频规则。HAL 建立
控制器后,MID 使用 HAL 回读的实际 module_clock_hz 查询时序表。
im_can_init() 复制 im_can_config_t 中的配置值,但不会复制或释放
rx_queue、rx_dma_buffer 指向的存储。
4.11 设备对象与生命周期
typedef struct im_can im_can_t;
| 生命周期 | 行为 |
|---|---|
| 未绑定 | im_can_init() 可申请控制器所有权。 |
| 已初始化、READY | 可执行 filter 配置、TX、RX、状态查询和恢复条件判断。 |
| 活动 TX | 新 TX 返回 IM_ERR_BUSY;filter 配置和恢复也返回 IM_ERR_BUSY。 |
| 已反初始化 | HAL 和 MID 资源已释放,控制器所有权已归还;再次使用前必须重新初始化。 |
初始化失败后,MID 会释放已经取得的资源和控制器所有权。应用不应再对初始化失败
的 dev 调用 im_can_deinit()。
5. 公开接口规范
5.1 im_can_config_struct_init
void im_can_config_struct_init(im_can_config_t *config);
将配置初始化为 MID 默认值,不访问硬件。传入 NULL 不做任何操作。
应用必须先调用该函数,再覆盖实例、时钟、bitrate、传输模式和 RX 存储等字段。
配置中的 hal_config 由 hal_can_config_struct_init() 初始化,随后再应用 MID
默认值。
5.2 帧与 filter 结构初始化
5.2.1 im_can_frame_struct_init
void im_can_frame_struct_init(im_can_frame_t *frame);
清零帧结构。传入 NULL 不做任何操作。
5.2.2 im_can_filter_struct_init
void im_can_filter_struct_init(im_can_filter_t *filter);
将 filter 初始化为接受全部标识符。传入 NULL 不做任何操作。需要精确 ID 匹配时,
在初始化后把 mask 设置为 0。
5.3 im_can_clock_plan_get
error_code_t im_can_clock_plan_get(uint32_t module_clock_hz,
im_can_clock_plan_t *plan_out);
为指定 CANCLK 查找父时钟和 1..256 分频比。候选顺序为 EXCLK、MPLL、SCLK_A;
只接受满足 module_clock_hz <= parent_rate_hz 且能够整除的组合。SCLK_A 只是
EXCLK 副本或已经停止时会被跳过。
| 返回值 | 条件 |
|---|---|
IM_OK |
找到精确匹配 |
IM_ERR_INVALID_PARAM |
时钟为 0 或输出指针为 NULL |
IM_ERR_NOT_READY |
CPM 时钟树不可读,例如 stub 构建 |
IM_ERR_UNSUPPORTED |
没有父时钟能精确产生请求频率 |
该接口不会修改 PLL,也不会就近降频。参数有效时,输出结构会在查找开始前清零。
5.4 im_can_init
error_code_t im_can_init(im_can_t *dev, const im_can_config_t *config);
初始化一个 CAN MID 设备。调用前应完成 board pinmux 和外部收发器配置。该接口:
- 申请对应 CAN 实例的 MID 所有权;
- 在启用时钟规划时选择父时钟和分频,并要求 HAL 精确回读;
- 在启用时序表时填写仲裁域和 FD 数据域 timing;
- 初始化并启用 CAN HAL;
- 注册 HAL callback、MID DMA request 和 RX DMA;
- 配置 CAN IRQ source,使能控制器并注册 SoC IRQ。
常见返回值:
| 返回值 | 条件 |
|---|---|
IM_OK |
初始化和控制器启用成功 |
IM_ERR_INVALID_PARAM |
参数、实例、模式、timing、queue 或 DMA buffer 配置非法 |
IM_ERR_BUSY |
同一 CAN 实例已被其他 MID 对象占用 |
IM_ERR_NOT_READY |
时钟树不可读或 HAL 未就绪 |
IM_ERR_UNSUPPORTED |
时钟或 bitrate 组合无法精确支持 |
IM_ERR_NO_MEMORY |
DMA middleware 无法分配私有 context 或 descriptor |
IM_ERR_ALREADY_REGISTERED |
对应 SoC IRQ 已被其他模块注册 |
| 其他错误码 | CPM、HAL、DMA 或 IRQ 初始化失败 |
初始化失败时会进入清理路径;接口返回触发失败的初始化步骤错误。应用不应再对
初始化失败的 dev 调用 im_can_deinit()。
5.5 im_can_deinit
error_code_t im_can_deinit(im_can_t *dev);
停止活动 TX,关闭 IRQ 和 DMA,停止控制器,释放 MID 所有权,并反初始化 HAL。
调用方拥有的 rx_queue 和 rx_dma_buffer 不会被释放,但必须保持有效直到该
函数返回。
| 返回值 | 条件 |
|---|---|
IM_OK |
所有已申请资源释放成功 |
IM_ERR_INVALID_PARAM |
dev 为空或 HAL 对象未初始化 |
| 其他错误码 | 关闭过程中首先遇到的 HAL、IRQ 或 DMA 错误 |
5.6 im_can_recover
error_code_t im_can_recover(im_can_t *dev);
手动恢复处于 ERROR、TIMEOUT 或 BUS_OFF 的设备。
| 场景 | 行为 |
|---|---|
| 当前存在活动 TX | 返回 IM_ERR_BUSY |
| 状态不是 ERROR/TIMEOUT/BUS_OFF | 返回 IM_ERR_NOT_READY |
| 设备未初始化 | 返回 IM_ERR_INVALID_PARAM |
| 恢复成功 | 状态置为 READY,last_status 置为 IM_OK |
| 从 bus-off 恢复成功 | 增加 stats.recoveries 并产生 IM_CAN_EVENT_RECOVERED |
恢复流程会暂时关闭控制器和中断源;若 RX DMA 正在运行,会停止并重启它。随后复位
硬件错误状态、恢复 DMA request 和 IRQ mask,并重新使能控制器。恢复失败时状态
置为 ERROR。
5.7 im_can_filter_config
error_code_t im_can_filter_config(im_can_t *dev,
uint32_t index,
const im_can_filter_t *filter,
bool enable);
配置并选择是否使能一个 acceptance filter。
| 参数 | 说明 |
|---|---|
index |
0..HAL_CAN_FILTER_COUNT - 1,当前为 0..3 |
filter |
标识符和 mask,均不得超过 IM_CAN_EXT_ID_MAX |
enable |
true 使能;false 关闭并保留已写入的 ID/mask |
该操作会暂时关闭控制器、停止并重启正在运行的 RX DMA,再恢复 IRQ 和控制器。
活动 TX 期间返回 IM_ERR_BUSY。filter 重配置必须由应用与发送、接收、恢复和
反初始化操作在外部串行化。
im_can_init() 使用 HAL 默认 filter 状态:四组 filter 均为接受全部,但只有
filter 0 使能。应用需要精确过滤时应显式调用 im_can_filter_config()。
5.8 im_can_transfer
error_code_t im_can_transfer(im_can_t *dev,
const im_can_frame_t *frame,
uint32_t timeout_ms);
校验并同步发送一帧。函数返回时,TX 已经完成、失败或被终止,调用方可以立即
释放 frame。一个 im_can_t 同一时间只允许一个活动 TX。
timeout_ms 的语义如下:
IM_TIMEOUT_NO_WAIT_MS:只检查一次;尚无完成结果时返回IM_ERR_NOT_READY;IM_TIMEOUT_FOREVER_MS:一直等待完成;- 其他值:等待指定毫秒数,超时返回
IM_ERR_TIMEOUT。
no-wait 未完成或有限超时发生时,MID 会终止当前 TX。设备未初始化、处于
RESET 或 BUS_OFF 时返回 IM_ERR_NOT_READY。帧格式非法时返回
IM_ERR_INVALID_PARAM;配置未启用 FD 时发送 FD 帧返回
IM_ERR_UNSUPPORTED;并发 TX 返回 IM_ERR_BUSY。
5.9 im_can_receive
error_code_t im_can_receive(im_can_t *dev,
im_can_frame_t *frame_out,
uint32_t timeout_ms);
同步接收一帧。成功时 frame_out 被完整覆盖,并增加 stats.rx_frames。
| RX 模式 | 行为 |
|---|---|
POLLING |
轮询 CAN 状态并直接读取硬件 RX buffer |
IRQ |
从 rx_queue 取出 ISR 已收集的帧 |
DMA |
等待 DMA frame event,从 cyclic byte ring 解码一帧 |
IM_TIMEOUT_NO_WAIT_MS 无可用帧时返回 IM_ERR_NOT_READY,有限等待超时返回
IM_ERR_TIMEOUT。设备处于 RESET 或 BUS_OFF 时返回 IM_ERR_NOT_READY。
DMA 解析、DMA 状态或 RX overrun 失败可能返回 IM_ERR_IO。任一参数为 NULL 时
返回 IM_ERR_INVALID_PARAM。
5.10 状态、错误和统计查询
5.10.1 im_can_get_state
im_can_state_t im_can_get_state(const im_can_t *dev);
返回当前运行状态。dev 为 NULL 时返回 IM_CAN_STATE_RESET。
5.10.2 im_can_get_last_status
error_code_t im_can_get_last_status(const im_can_t *dev);
返回最近一次 MID 操作记录的状态。dev 为 NULL 时返回
IM_ERR_INVALID_PARAM。
5.10.3 im_can_get_rx_drop_count
uint32_t im_can_get_rx_drop_count(const im_can_t *dev);
返回 IRQ 队列、DMA ring 和硬件 RX overrun 的累计丢帧数。dev 为 NULL 时
返回 0。
5.10.4 im_can_get_error_flags
uint32_t im_can_get_error_flags(const im_can_t *dev);
返回 MID 记录的 sticky HAL_CAN_ERROR_* 历史。可包含 warning、overrun、
bus-off、error-passive、arbitration-lost 和 bus-error。dev 为 NULL 时返回 0。
5.10.5 im_can_clear_error_flags
error_code_t im_can_clear_error_flags(im_can_t *dev, uint32_t error_mask);
清除 error_mask 中选择的错误历史。mask 只能包含
HAL_CAN_ERROR_VALID_MASK 中的 bit。该接口不修改累计统计,也不直接复位控制器
硬件错误计数器。dev 为 NULL 或 mask 含无效 bit 时返回
IM_ERR_INVALID_PARAM。
5.10.6 im_can_get_stats
error_code_t im_can_get_stats(const im_can_t *dev, im_can_stats_t *stats_out);
将累计统计复制到调用方结构。任一参数为 NULL 时返回
IM_ERR_INVALID_PARAM。
5.11 公开接口清单
| 分类 | 接口 |
|---|---|
| 配置与结构 | im_can_config_struct_init、im_can_frame_struct_init、im_can_filter_struct_init |
| 时钟规划 | im_can_clock_plan_get |
| 生命周期 | im_can_init、im_can_deinit、im_can_recover |
| 控制 | im_can_filter_config |
| 收发 | im_can_transfer、im_can_receive |
| 查询 | im_can_get_state、im_can_get_last_status、im_can_get_rx_drop_count、im_can_get_error_flags、im_can_clear_error_flags、im_can_get_stats |
当前公开接口不提供 im_can_start() 或 im_can_wait_tx()。
6. 行为约束与资源管理
6.1 帧、payload 与 DLC
发送前 MID 检查:
- 标准或扩展标识符范围;
is_remote、is_fd和brs的组合;- Classic/FD payload 长度;
- 当前配置是否启用 CAN FD。
规则摘要:
| 帧类型 | 合法长度与编码 |
|---|---|
| Classic 数据帧 | len = 0..8 |
| CAN FD 数据帧 | len = 0..64 |
| Classic remote 帧 | len = 0,dlc = 0..8 |
| FD remote 帧 | 不支持 |
CAN FD 标准线上长度为 0..8, 12, 16, 20, 24, 32, 48, 64。MID 接受 0..64
范围内其他长度,并在发送前补零到下一个合法长度。例如 len = 9 会按 12 byte
发送,接收端 len 为 12。该补零只影响线上帧,不修改调用方传入的 frame。
对于数据帧,TX DLC 始终由 len 推导,调用方设置的 dlc 不参与编码。Classic
remote 帧使用调用方提供的 dlc。
6.2 CANCLK 与时序表
CAN 模块时钟链路为:
parent clock -> 1..256 divider -> CANCLK -> CAN bit timing
use_clock_plan = true 时,MID 按 EXCLK、MPLL、SCLK_A 顺序选择第一个能够精确
产生请求 CANCLK 的父时钟。候选父时钟必须满足:
CANCLK <= parent_rate_hz
parent_rate_hz % CANCLK == 0
1 <= divider <= 256
MID 不会修改 PLL。无法精确产生请求频率时返回 IM_ERR_UNSUPPORTED。
use_timing_table = true 时,MID 使用固定时序表将有效 CANCLK 和 bitrate 映射为
HAL 原始 timing 字段。当前支持以下组合:
| CANCLK | Classic 仲裁域速率 | FD 数据域速率 |
|---|---|---|
| 8 MHz | 50k, 100k, 125k, 250k, 500k, 1M | 50k, 100k, 125k, 250k, 500k, 1M |
| 12 MHz | 100k, 125k, 250k, 500k, 1M | 100k, 125k, 250k, 500k, 1M |
| 16 MHz | 500k, 1M | 500k, 1M |
| 24 MHz | 125k, 250k, 500k, 1M | 125k, 250k, 500k, 1M, 4M |
| 50 MHz | 500k, 1M | 500k, 1M, 2M, 5M |
| 80 MHz | 500k, 1M | 500k, 1M, 2M, 4M, 5M, 8M |
| 100 MHz | 500k, 1M | 500k, 1M, 2M, 4M, 5M |
| 125 MHz | 500k, 1M | 500k, 1M, 5M |
Classic CAN 忽略 data_bitrate。CAN FD 要求仲裁域和数据域速率都存在于有效
CANCLK 对应的表中,否则初始化返回 IM_ERR_UNSUPPORTED。
6.3 IRQ 资源
- TX 或 RX 任一方向使用 IRQ/DMA 模式时,MID 注册对应 CAN 实例的 SoC IRQ。
irq_name只用于 IRQ 注册和调试显示,不能用于选择其他 IRQ。- CAN 实例到 SoC IRQ 的映射封装在 HAL/平台层内部,MID 公共配置不要求调用方 填写 IRQ 编号。
CONFIG_IRQ_SIMPLE_DISPATCH=y不满足 CAN MID 的构建依赖。- 对应 IRQ 已被其他模块注册时,
im_can_init()返回IM_ERR_ALREADY_REGISTERED。 im_can_deinit()、初始化失败清理和恢复流程会按当前资源状态关闭或恢复 IRQ。
6.4 TX DMA
TX DMA 使用 MID 内部按 HAL_DMA_DESCRIPTOR_ALIGN 对齐的 72-byte 帧镜像和普通
memory DMA,将镜像搬运到 CAN TX 寄存器窗口,再由 MID 发起发送。应用不需要提供
TX DMA buffer。
TX DMA 请求、descriptor 和完成等待资源由 MID 管理。DMA 搬运或状态异常时,
im_can_transfer() 返回相应错误并产生错误事件;wait/no-wait 超时则终止活动 TX。
6.5 RX DMA
RX DMA 使用调用方提供的 cyclic byte ring,要求如下:
rx_dma_buffer按IM_CAN_RX_DMA_BUFFER_ALIGN对齐;- 大小至少为
IM_CAN_RX_DMA_MIN_BUFFER_SIZE; - 大小为
IM_CAN_RX_DMA_FRAME_BUDGET_SIZE的整数倍; - 每帧预留 128 byte,可容纳最大 72-byte CAN FD 帧镜像;
manage_dma_cache = true时由 MID 执行 cache clean/invalidate;- 存储必须在
im_can_deinit()返回前持续有效。
每轮可接收的帧数为
rx_dma_buffer_size / IM_CAN_RX_DMA_FRAME_BUDGET_SIZE。达到该帧数后,硬件和 MID
都回到 buffer 起始位置。
RX DMA ring 溢出、DMA 状态错误或帧解析失败时,接收接口可能返回 IM_ERR_IO,
同时更新 stats.dma_overflows / stats.dma_errors 并产生 error 或 overflow 事件。
6.6 IRQ RX 队列
rx_mode = IM_CAN_TRANSFER_IRQ 时:
- CAN ISR 读取并释放 HAL RX buffer;
- 将原始帧解码为
im_can_frame_t; - 写入调用方提供的环形队列;
- 唤醒
im_can_receive()并产生IM_CAN_EVENT_RX_READY。
队列满时 MID 丢弃最早且尚未读取的帧,增加 rx_drop_count、
stats.rx_overflows,并产生 IM_CAN_EVENT_RX_OVERFLOW。
6.7 错误和恢复策略
| 条件 | MID 行为 |
|---|---|
| Arbitration lost | 计数并产生 IM_CAN_EVENT_ARBITRATION_LOST |
| Error warning | 计数并产生 IM_CAN_EVENT_ERROR_WARNING |
| Error passive | 计数并产生 IM_CAN_EVENT_ERROR_PASSIVE |
| Bus error | 计数并产生 IM_CAN_EVENT_BUS_ERROR |
| RX overrun | 记录 drop/overflow 并产生 IM_CAN_EVENT_RX_OVERFLOW |
| Bus-off | 进入 BUS_OFF,终止活动 TX,唤醒 RX waiter 并产生 IM_CAN_EVENT_BUS_OFF |
| TX timeout | 终止活动 TX,进入 TIMEOUT 并产生 IM_CAN_EVENT_TIMEOUT |
启用 hal_config.auto_recovery 时,后续发送或接收入口会检测硬件是否已经退出
bus-off;若已恢复,MID 将状态改回 READY,增加恢复统计并产生
IM_CAN_EVENT_RECOVERED。关闭自动恢复时需要调用 im_can_recover()。
6.8 并发和 callback 约束
推荐每个 im_can_t 使用一个 TX 调用者和一个 RX 消费者。MID 会阻止并发 TX,
但不会替应用串行化以下操作:
im_can_filter_config();im_can_recover();im_can_deinit();- 与上述控制操作并发的发送、接收或其他控制操作。
callback 可能运行在中断上下文,必须短小且非阻塞。建议在 callback 中只记录事件、 更新原子标志或唤醒工作任务。callback 不得调用会阻塞的 CAN API。
6.9 重新配置与时钟变化
- 正在运行的控制器不能在
im_can_init()中重新配置;需要先调用im_can_deinit()。 - CAN 时钟 mux 不支持无毛刺切换,控制器运行期间不得改变 CAN 父时钟。
- 修改 CANCLK 或父时钟后,应反初始化并按目标配置重新初始化控制器。
im_can_filter_config()和im_can_recover()会按需要暂时关闭控制器,应用 不应与收发调用并发执行这些操作。
7. 典型用法
7.1 IRQ 模式打开两个控制器
以下配置使用 IRQ TX/RX、1 Mbit/s Classic CAN 和精确标识符过滤。两个控制器必须 连接到具备有效 ACK 路径的 CAN 总线。
#include "im_can.h"
#define APP_RX_QUEUE_COUNT 8U
#define APP_TIMEOUT_MS 1000U
static im_can_t can0;
static im_can_t can1;
static im_can_frame_t can0_rx_queue[APP_RX_QUEUE_COUNT];
static im_can_frame_t can1_rx_queue[APP_RX_QUEUE_COUNT];
static error_code_t can_open(im_can_t *dev,
can_regs_t *instance,
im_can_frame_t *rx_queue,
const char *irq_name)
{
im_can_config_t config;
im_can_filter_t filter;
error_code_t ret;
im_can_config_struct_init(&config);
config.hal_config.instance = instance;
config.hal_config.module_clock_hz = 24000000U;
config.hal_config.fd_enable = false;
config.nominal_bitrate = 1000000U;
config.tx_mode = IM_CAN_TRANSFER_IRQ;
config.rx_mode = IM_CAN_TRANSFER_IRQ;
config.irq_name = irq_name;
config.rx_queue = rx_queue;
config.rx_queue_count = APP_RX_QUEUE_COUNT;
ret = im_can_init(dev, &config);
if (ret != IM_OK) {
return ret;
}
im_can_filter_struct_init(&filter);
filter.id = 0x123U;
filter.mask = 0U;
ret = im_can_filter_config(dev, 0U, &filter, true);
if (ret != IM_OK) {
(void)im_can_deinit(dev);
}
return ret;
}
7.2 同步发送和接收
static error_code_t can_roundtrip(void)
{
im_can_frame_t tx_frame;
im_can_frame_t rx_frame;
error_code_t ret;
ret = can_open(&can0, CAN0, can0_rx_queue, "app_can0");
if (ret != IM_OK) {
return ret;
}
ret = can_open(&can1, CAN1, can1_rx_queue, "app_can1");
if (ret != IM_OK) {
(void)im_can_deinit(&can0);
return ret;
}
im_can_frame_struct_init(&tx_frame);
tx_frame.id = 0x123U;
tx_frame.len = 8U;
tx_frame.data[0] = 0x11U;
tx_frame.data[7] = 0x88U;
ret = im_can_transfer(&can0, &tx_frame, APP_TIMEOUT_MS);
if (ret == IM_OK) {
ret = im_can_receive(&can1, &rx_frame, APP_TIMEOUT_MS);
}
(void)im_can_deinit(&can1);
(void)im_can_deinit(&can0);
return ret;
}
典型生命周期为:
- 完成板级 pinmux 和收发器初始化;
- 初始化配置和 RX 存储;
- 调用
im_can_init(); - 按需调用
im_can_filter_config(); - 使用
im_can_transfer()和im_can_receive(); - 查询事件、状态、错误和统计;
- 调用
im_can_deinit()释放 MID 和 HAL 资源。
7.3 使用指定父时钟
需要由调用方指定 CAN 父时钟并采用 HAL 就近分频时,在配置初始化后设置
use_clock_plan = false:
im_can_config_struct_init(&config);
config.hal_config.module_clock_parent = HAL_CPM_ID_CLK_EXTAL;
config.hal_config.module_clock_hz = 24000000U;
config.use_clock_plan = false;
设置为 true 时,MID 自动选择父时钟并覆盖 hal_config.module_clock_parent,同时
要求 CANCLK 能被父时钟精确整除;无法精确生成时 im_can_init() 返回
IM_ERR_UNSUPPORTED。
7.4 手动恢复
关闭 hal_config.auto_recovery 后,可在设备处于 ERROR、TIMEOUT 或
BUS_OFF 且没有活动 TX 时调用:
error_code_t ret = im_can_recover(&can0);
if (ret == IM_OK) {
/* 控制器已重新使能,可以继续收发。 */
}
8. 示例程序与构建
示例目录为 code/middleware/can/example。示例使用 CAN0 发送、CAN1 接收,
两个控制器均采用 IRQ 模式,以 1 Mbit/s 传输一个标准 8-byte Classic CAN 帧,
最后反初始化两个控制器。
构建步骤:
tools/imde/bin/imde use-defconfig code/middleware/can/example/can_example_defconfig
tools/imde/bin/imde menuconfig
tools/imde/bin/imde -D SDK_APP_DIR=code/middleware/can/example configure
tools/imde/bin/imde build
说明:
can_example_defconfig启用 CAN HAL 和 CAN MID,但目标板配置由menuconfig选择,必须与待测硬件匹配。- 必须通过
SDK_APP_DIR选中示例的main/main.c;只切换 defconfig 不会替换 固件入口。 - 构建目录会缓存
SDK_APP_DIR。恢复默认应用时使用imde -D SDK_APP_DIR= configure后重新构建。 - 所选板级配置必须复用两组 CAN 控制器引脚,并为 CAN0 和 CAN1 提供外部收发器 或等效物理层。两个控制器应接入同一条带终端电阻的总线。
示例成功传输的关键日志包括:
CAN example: CAN0 ready, opening CAN1
CAN example: CAN1 ready, transmitting CAN0 -> CAN1
CAN example: transmit completed, receiving on CAN1
CAN example: CAN0 -> CAN1 IRQ transfer passed
更完整的构建、硬件连接、DMA ring 和错误恢复说明见
code/middleware/can/example/README.md。
9. 常见错误码
error_code_t |
常见场景 |
|---|---|
IM_OK |
操作成功。 |
IM_ERR_INVALID_PARAM |
空指针、非法实例/模式、timing 非法、bitrate 为 0、RX queue 或 DMA buffer 不符合要求、filter 超出范围。 |
IM_ERR_NOT_READY |
设备未初始化、处于 RESET/BUS_OFF、no-wait 没有立即可用结果,或时钟树不可读。 |
IM_ERR_BUSY |
同一控制器已有 MID 对象、已有活动 TX,或控制操作与活动 TX 冲突。 |
IM_ERR_TIMEOUT |
有限发送或接收等待到期。 |
IM_ERR_UNSUPPORTED |
时钟无法精确生成、时序表中没有对应 bitrate,或控制器配置不支持请求的帧类型。 |
IM_ERR_NO_MEMORY |
DMA middleware 无法分配私有 context 或 descriptor。 |
IM_ERR_ALREADY_REGISTERED |
对应 SoC IRQ 已被其他模块注册。 |
IM_ERR_IO |
HAL、DMA、RX overrun、帧解析或控制器访问失败。 |
CAN MID 通过 im_can_get_error_flags() 提供 sticky 错误历史,通过
im_can_get_stats() 提供累计统计。底层 CPM、IRQ、DMA 和 HAL 返回的错误码会向
调用方传递。