4、CAN驱动开发:Linux SocketCAN接口、CAN设备初始化、波特率配置

好,咱们进入第四讲。前面聊了CAN协议的基础,也看了TBOX的硬件架构。今天要动真格的了——在Linux系统里把CAN驱动跑起来。

说实话,我刚开始接触嵌入式Linux下的CAN开发时,也踩过不少坑。尤其是SocketCAN这套东西,看着简单,但细节特别多。你想想看,一个波特率配错了,总线上的节点全给你罢工,那场面...嗯,咱们今天就把这些坑一个个填平。

4.1 Linux SocketCAN接口——为什么选它?

先说说SocketCAN。这是Linux内核原生支持的一套CAN协议栈实现。说白了,它把CAN设备抽象成了一个网络接口,你用socket编程那一套就能操作CAN总线。

我个人习惯用SocketCAN而不是那些厂商私有的驱动库。原因有三:

  • 标准化:内核主线支持,不用额外打补丁
  • 接口统一:不管你是MCP2515、SJA1000还是FlexCAN,上层API都一样
  • 工具链成熟:candump、cansend、canconfig这些工具直接拿来用

我在项目中遇到过一家芯片厂商,他们提供的驱动库只支持自家芯片,换平台就得重写。后来我全部切到SocketCAN,移植工作量直接降了80%。

4.2 CAN设备初始化——从硬件到软件

设备初始化这事儿,看着简单,但顺序错了就起不来。我一般按这个步骤来:

  1. 硬件使能:检查原理图,确认CAN收发器的STB/EN引脚是否拉高
  2. 时钟配置:CAN控制器的时钟源,分频系数对不对
  3. GPIO复用:把对应的引脚切换到CAN功能(比如STM32的AF模式)
  4. 设备树配置:在dts里描述CAN节点
  5. 驱动加载:modprobe或者内核编译进镜像

这里有个坑——我曾经在设备树里漏配了时钟,结果CAN一直报"bus-off"。查了两天才发现是时钟源没使能。嗯,这种低级错误,犯过一次就记住了。

4.2.1 设备树配置示例

以i.MX6为例,设备树里这样配:

&can1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_flexcan1>;
    xceiver-supply = <&reg_can_3v3>;
    status = "okay";
};

pinctrl_flexcan1: flexcan1grp {
    fsl,pins = <
        MX6QDL_PAD_KEY_COL2__FLEXCAN1_TX  0x1b0b0
        MX6QDL_PAD_KEY_ROW2__FLEXCAN1_RX  0x1b0b0
    >;
};

注意那个xceiver-supply,很多人会忘。这是给CAN收发器供电的regulator,不配的话收发器不工作,总线电平拉不起来。

4.3 波特率配置——算不对就全完蛋

波特率配置是CAN驱动开发里最容易出问题的地方。为什么?因为CAN的位时序太灵活了,一个参数算错,整个网络就废了。

CAN的位时间由三部分组成:

  • Sync_Seg:同步段,固定1个TQ
  • Prop_Seg + Phase_Seg1:传播段+相位段1
  • Phase_Seg2:相位段2

总位时间 = (1 + TSEG1 + TSEG2) × TQ

其中TQ = 2 × (BRP + 1) / 时钟频率

我建议你直接用公式算,别靠猜。举个例子:

假设CAN时钟是30MHz,目标波特率250kbps:

目标位时间 = 1 / 250000 = 4μs
TQ = 4μs / (1 + TSEG1 + TSEG2)

设TSEG1=13, TSEG2=2, 总TQ数=16
TQ = 4μs / 16 = 250ns

BRP = (TQ × 时钟频率 / 2) - 1
BRP = (250ns × 30MHz / 2) - 1 = 3.75 - 1 ≈ 3

实际配的时候,用ip命令:

ip link set can0 type can bitrate 250000 sample-point 0.875
ip link set can0 up

那个sample-point参数很多人不注意。它决定了采样点在位时间里的位置。我一般设0.875,也就是87.5%的位置采样,这样抗干扰能力最强。

⚠️ 注意: 同一个CAN网络里的所有节点,波特率必须完全一致。包括采样点位置。我曾经在项目里遇到一个节点用75%采样点,其他用87.5%,结果总线偶尔出现错误帧。排查了三天才发现是采样点不匹配。

4.4 完整的初始化流程——手把手来一遍

好,咱们把上面这些串起来,走一遍完整的初始化流程:

# 1. 加载CAN驱动模块
modprobe can
modprobe can_raw
modprobe mttcan  # 具体芯片的驱动

# 2. 查看设备是否识别
ip link show

# 3. 设置波特率并启动
ip link set can0 type can bitrate 500000
ip link set can0 up

# 4. 验证状态
ip -details link show can0

# 5. 测试收发
candump can0 &
cansend can0 123#1122334455667788

如果一切正常,你应该能看到candump打印出发送的那帧数据。如果没反应,先检查硬件连接,再用示波器看CAN_H和CAN_L的差分电平。

💡 小技巧: 我习惯在初始化脚本里加一个循环检测。如果can0起不来,自动重试三次,每次间隔1秒。因为有些CAN收发器上电后需要几十毫秒才能稳定。

4.5 避坑指南——我踩过的那些雷

做CAN驱动开发这几年,我总结了几条血泪教训:

  • 终端电阻:CAN总线两端必须各加一个120Ω电阻。不加的话,信号反射会导致通信不稳定。我见过有人只在一边加,结果总线长度超过2米就丢帧。
  • 波特率容差:CAN标准要求波特率误差在±1.5%以内。但实际项目中,我建议控制在±0.5%以内。尤其是多节点网络,累积误差会放大。
  • bus-off恢复:CAN控制器进入bus-off状态后,需要等待128个11位隐性位才能恢复。这个时间在500kbps下大约是2.8ms。如果你的应用对实时性要求高,得考虑这个延迟。
  • 中断优先级:CAN中断优先级不能太低。我曾经把CAN中断设成最低优先级,结果高优先级的中断频繁打断CAN接收,导致FIFO溢出丢帧。

4.6 总结

嗯,今天的内容就这些。总结一下重点:

  • SocketCAN把CAN设备抽象成网络接口,用socket编程就能操作
  • 设备初始化要按顺序来,设备树里别漏了时钟和供电
  • 波特率配置要算清楚TQ和采样点,全网必须一致
  • 终端电阻、中断优先级这些细节,决定了系统稳不稳定

下一讲咱们聊CAN数据收发——怎么用SocketCAN的API发帧、收帧,还有那些坑爹的CAN ID过滤怎么配。到时候见。