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 和外部收发器配置。该接口:

  1. 申请对应 CAN 实例的 MID 所有权;
  2. 在启用时钟规划时选择父时钟和分频,并要求 HAL 精确回读;
  3. 在启用时序表时填写仲裁域和 FD 数据域 timing;
  4. 初始化并启用 CAN HAL;
  5. 注册 HAL callback、MID DMA request 和 RX DMA;
  6. 配置 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 时:

  1. CAN ISR 读取并释放 HAL RX buffer;
  2. 将原始帧解码为 im_can_frame_t;
  3. 写入调用方提供的环形队列;
  4. 唤醒 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;
}

典型生命周期为:

  1. 完成板级 pinmux 和收发器初始化;
  2. 初始化配置和 RX 存储;
  3. 调用 im_can_init();
  4. 按需调用 im_can_filter_config();
  5. 使用 im_can_transfer() 和 im_can_receive();
  6. 查询事件、状态、错误和统计;
  7. 调用 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 返回的错误码会向 调用方传递。