4. CAN总线驱动移植:MCP2515/FlexCAN驱动源码分析、内核配置、驱动编译与加载
这一章,我们直接切入工控网关最核心的环节——CAN总线驱动移植。说白了,就是让Linux内核认识并控制你的CAN控制器硬件。我见过不少项目,硬件设计没问题,却在驱动移植这一步卡了半个月。其实摸清套路后,这事儿没那么玄乎。
咱们主要讲两种主流方案:MCP2515(SPI接口,外挂芯片)和FlexCAN(集成在i.MX、S32K等SoC内部)。前者适合低成本、灵活扩展;后者性能高、延迟低,适合多路CAN场景。
4.1 MCP2515驱动源码分析
MCP2515是Microchip的经典SPI转CAN芯片。Linux内核里它的驱动是mcp251x.c,我最早看这个文件时,第一反应是“怎么才两千多行?”——比起动辄上万行的网卡驱动,这算很精简了。
驱动核心结构其实就三块:
- SPI通信层:通过spi_transfer结构体收发数据,读写寄存器
- CAN核心层:注册net_device,实现open/close/start_xmit/do_set_mode等回调
- 中断处理:处理接收、发送完成、错误状态等中断事件
我个人习惯先看mcp251x_can_probe()函数,它把整个驱动的初始化流程串起来了:
static int mcp251x_can_probe(struct spi_device *spi)
{
struct net_device *net;
struct mcp251x_priv *priv;
int ret;
// 1. 分配net_device结构体
net = alloc_candev(sizeof(*priv), TX_ECHO_SKB_MAX);
if (!net)
return -ENOMEM;
priv = netdev_priv(net);
priv->spi = spi;
priv->net = net;
// 2. 设置SPI参数:最大时钟、模式
spi->mode = SPI_MODE_0;
spi->max_speed_hz = 10 * 1000 * 1000; // 10MHz
// 3. 获取中断号、复位芯片
priv->irq = spi->irq;
mcp251x_reset(priv);
// 4. 注册net_device
ret = register_candev(net);
if (ret)
goto out_free;
return 0;
}
4.2 FlexCAN驱动源码分析
FlexCAN是NXP(原Freescale)芯片内部集成的CAN控制器。驱动文件flexcan.c比MCP2515复杂不少,因为它要处理更多硬件特性:邮箱机制、总线时钟、唤醒、FD模式等。
FlexCAN驱动的核心差异点:
| 特性 | MCP2515 | FlexCAN |
|---|---|---|
| 接口类型 | SPI(外挂) | 内存映射(集成) |
| 收发缓冲区 | 硬件FIFO(3 TX / 2 RX) | 邮箱阵列(最多64个) |
| 时钟源 | 外部晶振(通常8/16MHz) | SoC内部总线时钟 / 外设时钟 |
| 中断处理 | 单中断线 | 多中断(ORed / 独立) |
| 驱动复杂度 | ~2500行 | ~5000行 |
FlexCAN驱动里有个关键函数flexcan_chip_start(),它负责初始化硬件:
static int flexcan_chip_start(struct net_device *dev)
{
struct flexcan_priv *priv = netdev_priv(dev);
struct flexcan_regs __iomem *regs = priv->regs;
// 1. 软复位CAN控制器
flexcan_enter_freeze_mode(priv);
flexcan_exit_freeze_mode(priv);
// 2. 配置位时序:波特率 = 时钟 / (presdiv * (seg1 + seg2 + 3))
flexcan_set_bitrate(priv);
// 3. 配置邮箱:通常使用8个邮箱
flexcan_setup_mailbox(priv);
// 4. 使能中断:接收、发送完成、错误
writel(FLEXCAN_CTRL_ERR_MSK | FLEXCAN_CTRL_BOFF_MSK,
®s->ctrl);
// 5. 退出冻结模式,开始工作
flexcan_enter_freeze_mode(priv);
flexcan_exit_freeze_mode(priv);
return 0;
}
clocks属性,驱动会拿不到时钟频率,导致波特率计算全错。我调试了整整两天才发现是设备树少写了一行。
4.3 内核配置
驱动源码看完了,接下来要让内核把它编译进去。配置内核用make menuconfig,路径如下:
Device Drivers --->
[*] Network device support --->
<*> CAN bus subsystem support --->
CAN Device Drivers --->
<M> Microchip MCP251x/MCP25625
<M> Freescale FlexCAN
这里<M>表示编译为模块(.ko),<*>表示编入内核。我建议前期用模块方式,方便调试时单独替换。
配置完成后,检查.config文件里是否出现:
CONFIG_CAN=y
CONFIG_CAN_DEV=y
CONFIG_CAN_MCP251X=m
CONFIG_CAN_FLEXCAN=m
4.4 设备树配置
驱动和内核都准备好了,但Linux还不知道你的CAN芯片接在哪个SPI总线上、中断脚是哪个。这就需要设备树(Device Tree)来描述硬件。
MCP2515设备树节点示例:
&ecspi2 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi2>;
mcp2515: can@0 {
compatible = "microchip,mcp2515";
reg = <0>; // SPI片选0
clocks = <&clk16m>; // 16MHz晶振
interrupt-parent = <&gpio1>;
interrupts = <19 IRQ_TYPE_EDGE_FALLING>;
spi-max-frequency = <10000000>;
status = "okay";
};
};
FlexCAN设备树节点示例:
&can1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_flexcan1>;
fsl,clk-source = <0>; // 0=IPG_CLK, 1=OSC_CLK
xceiver-supply = <®_can_3v3>; // CAN收发器电源
};
interrupts属性里,我习惯用IRQ_TYPE_EDGE_FALLING。因为MCP2515的中断输出是低电平有效,但有些SoC的GPIO中断控制器对电平触发支持不好,边沿触发更稳定。
4.5 驱动编译与加载
配置和设备树都搞定后,编译驱动:
# 编译内核模块
make modules
# 如果只编译CAN相关模块,可以指定路径
make M=drivers/net/can
# 编译设备树
make dtbs
编译完成后,把.ko文件和.dtb文件拷贝到目标板。加载驱动:
# 加载MCP2515驱动
insmod mcp251x.ko
# 或者用modprobe(会自动处理依赖)
modprobe mcp251x
# 查看是否加载成功
lsmod | grep can
# 查看CAN接口是否出现
ip link show can0
如果一切顺利,你会看到can0接口。设置波特率并启动:
# 设置波特率125kbps
ip link set can0 type can bitrate 125000
# 启动CAN接口
ip link set can0 up
# 查看接口状态
ip -details link show can0
candump can0监听总线,再用cansend can0 123#11223344发送一帧数据。如果能收到自己发的帧,说明驱动移植成功。
4.6 常见问题与避坑指南
驱动移植过程中,我遇到过不少问题,挑几个典型的说说:
- SPI通信失败:检查SPI片选、时钟极性、最大频率。MCP2515最高支持10MHz,但有些板子走线长了,降到5MHz更稳。
- 中断不触发:用
cat /proc/interrupts看中断号有没有注册。我曾经因为设备树里interrupts写成了<19 0>(没指定触发类型),导致中断完全不工作。 - 波特率不准:FlexCAN的时钟源一定要确认。用
clk_get_rate打印一下实际时钟频率,跟数据手册对一下。 - 模块加载报错:
insmod提示Unknown symbol,通常是内核版本不匹配。检查uname -r和编译内核的版本是否一致。
dev_dbg()或netdev_dbg()打印关键寄存器值。比如MCP2515的CANCTRL寄存器、FlexCAN的CTRL1寄存器,看一眼就知道配置有没有生效。
嗯,到这里,CAN总线驱动移植的核心内容就讲完了。从源码分析到内核配置,从设备树到编译加载,每一步都有坑,但每一步也都有迹可循。你只要按这个流程走一遍,基本不会出大问题。