一、CAN驱动模块(Can):从底层看通信

各位好,我是老张。做AUTOSAR这些年,CAN驱动模块是我打交道最多的模块之一。说白了,它就是MCU和CAN总线之间的「翻译官」——把你要发的数据变成总线上的电平信号,再把总线上的信号还原成你能用的数据。

今天咱们就聊聊这个模块。我会结合自己踩过的坑,把Can模块的功能、API、配置、中断处理这些核心内容讲清楚。

1.1 Can模块到底干些什么活?

先说说它的职责。Can模块在AUTOSAR架构里属于BSW(基础软件层)的通信服务部分。它的工作可以归纳为三点:

  • 收发报文:把上层要发的CAN报文塞进硬件发送缓冲区,把硬件收到的报文取出来交给上层
  • 硬件抽象:屏蔽不同MCU的CAN控制器差异。你换芯片,我改配置就行,上层代码不用动
  • 错误管理:监控总线状态,处理错误帧、总线关闭等情况

我记得刚入行那会儿,有个项目用的英飞凌TC275,后来换成NXP S32K。要不是AUTOSAR的Can模块做了抽象层,光重写驱动就得花两周。嗯,这就是抽象的价值。

核心要点:Can模块不负责协议解析,它只负责把数据从软件送到硬件,再从硬件取回来。协议解析是上层CanIf和CanTp的事。

1.2 那些你必须知道的API接口

Can模块对外暴露的接口不多,但每个都很关键。我按使用频率给你排个序:

API函数 功能 我常用的场景
Can_Init() 初始化CAN控制器和引脚 上电后第一个调用的函数
Can_Write() 发送一帧CAN报文 周期性发送、事件触发发送
Can_Read() 从接收缓冲区读取一帧 中断处理中调用
Can_SetBaudrate() 动态调整波特率 诊断模式切换时用
Can_GetState() 查询控制器状态 错误恢复流程中检查

这里有个细节我想强调一下:Can_Write() 是异步的。你调用它,它把报文塞进硬件发送邮箱就返回了。至于什么时候真正发出去,那是硬件的事。我曾经有个同事没搞清楚这点,在发送后立刻修改了报文内容,结果发出去的是修改后的数据——坑啊。

个人习惯:我一般在调用Can_Write()之后,通过发送完成回调来确认报文确实发出去了。别依赖延时等待,那玩意儿不靠谱。

1.3 配置与初始化——别小看这一步

Can模块的配置项很多,但核心就几类。我画了个图帮你理清思路:

Can模块配置核心结构 Can模块配置 硬件配置 报文对象配置 中断与回调配置 波特率、时钟源、引脚 ID、DLC、方向、过滤 发送/接收/错误中断 初始化流程: 1. 配置硬件参数 → 2. 注册报文对象 → 3. 绑定中断回调 4. 调用Can_Init() → 5. 检查Can_GetState()确认状态 ⚠️ 我曾经犯过的错:波特率配置时忘了算采样点位置 导致总线通信时好时坏,查了两天才发现是TSEG1/TSEG2配反了

初始化顺序很重要。我个人习惯是:先配硬件参数,再配报文对象,最后配中断。为什么?因为中断一旦开启,硬件随时可能产生中断事件。你报文对象还没配好,中断就来了,那不就乱套了吗?

警告:Can_Init() 必须在任何其他Can模块API之前调用。我见过有人先调了Can_SetBaudrate()再Init,结果配置被Init覆盖了。顺序错了,一切白费。

1.4 中断处理与回调函数——实时性的关键

CAN通信的实时性,说白了就靠中断撑着。Can模块的中断类型主要有三种:

  • 接收中断:硬件收到一帧报文,通知你来取
  • 发送中断:报文成功发出去了,硬件告诉你「我空了,可以发下一帧」
  • 错误中断:总线出错、控制器状态变化

回调函数是AUTOSAR里很巧妙的设计。Can模块不直接调用你的业务代码,而是通过注册的回调函数来通知上层。举个例子:

/* 伪代码示例:接收中断处理流程 */
void Can_IrqHandler(void) {
    /* 1. 读取硬件寄存器,确认中断源 */
    uint32 irq_src = Can_Hw_GetInterruptSource();
    
    if (irq_src & CAN_IRQ_RX) {
        /* 2. 从硬件读取报文 */
        Can_PduType pdu;
        Can_Hw_ReadMailbox(&pdu);
        
        /* 3. 调用上层注册的回调函数 */
        CanIf_RxIndication(&pdu);  /* 注意:这里不是直接调用应用层 */
    }
    
    if (irq_src & CAN_IRQ_TX) {
        /* 发送完成回调 */
        CanIf_TxConfirmation(pdu_id);
    }
    
    if (irq_src & CAN_IRQ_ERROR) {
        /* 错误处理回调 */
        CanIf_ControllerBusOff();
    }
}

为什么要用回调?说白了就是解耦。Can模块不需要知道上层是谁,它只管调用注册好的函数指针。我在一个项目里就吃过亏——当时为了图省事,直接在中断里调了应用层的处理函数。结果应用层处理太慢,导致中断嵌套,系统直接挂了。后来老老实实改成回调方式,问题解决。

避坑指南:我曾经在中断回调里做了打印日志的操作,结果串口打印太慢,导致后续CAN帧丢失。记住:中断回调里只做最必要的事,把数据交给上层就赶紧返回。耗时操作放到任务里去处理。

1.5 小结一下

Can模块是整个CAN通信的基石。它不复杂,但细节很多。我给你的建议是:

  • 配置时先理清硬件参数,再配报文对象
  • 初始化顺序严格遵守规范
  • 中断处理保持轻量,回调函数只做数据传递
  • 多看看硬件手册,不同MCU的CAN控制器寄存器差异不小

嗯,今天就聊到这儿。下一章咱们聊聊CanIf模块——它是怎么把Can模块的原始数据包装成上层能用的服务的。到时候见。


专注资料整理