4、内核源码获取与配置:下载Linux内核源码、配置内核(开启CAN支持、配置CAN驱动)、编译内核与设备树
好,到了真正动手的环节了。前面我们把开发环境搭好,交叉工具链就位,现在该请出主角——Linux内核源码。
这一章我会带你走完三个步骤:下载源码 → 配置内核 → 编译内核与设备树。说白了,就是给我们的网关板子“定制”一个能跑CAN总线的操作系统内核。
核心目标:生成一个支持CAN控制器驱动、且设备树正确描述硬件连接的内核镜像(zImage)和设备树文件(.dtb)。
4.1 获取Linux内核源码
我个人习惯从内核官网或芯片厂商的维护分支获取源码。对于嵌入式网关,我建议直接用芯片厂商提供的长期支持(LTS)内核,因为驱动补丁和BSP支持更完整。
以NXP i.MX6ULL平台为例,我常用的是linux-imx分支,基于Linux 4.19或5.10 LTS。
# 方式一:从内核官网下载主线LTS版本
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.120.tar.xz
tar -xf linux-5.10.120.tar.xz
cd linux-5.10.120
# 方式二:从芯片厂商Git仓库克隆(我推荐这个)
git clone -b imx_5.10.120_2.1.0 https://github.com/nxp-imx/linux-imx.git
cd linux-imx
我的经验:下载后先检查 arch/arm/configs/ 目录下有没有你板子的默认配置文件。比如 imx_v7_defconfig 或 imx6ull_defconfig。有这个文件,后面配置能省一半时间。
4.2 配置内核:开启CAN支持
内核配置是门手艺活。你想想看,一个内核有上万个开关选项,我们只需要把跟CAN相关的打开就行。
我一般分三步走:
- 加载默认配置——基于芯片厂商的defconfig做起点
- 开启CAN子系统——网络设备中的CAN协议栈
- 配置具体CAN控制器驱动——比如FlexCAN、M_CAN或SJA1000
4.2.1 加载并开始配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
进入menuconfig界面后,按/键可以搜索关键字。我习惯先搜CAN,看看哪些选项还没开。
4.2.2 必须开启的CAN选项
下面这个表格是我做项目时整理的“CAN配置清单”,照着勾就不会漏。
| 配置路径 | 选项名 | 说明 |
|---|---|---|
| Networking support → CAN bus subsystem support | CONFIG_CAN |
CAN协议栈总开关,必须选 |
| CAN bus subsystem support → CAN Device Drivers | CONFIG_CAN_DEV |
CAN设备驱动框架 |
| CAN Device Drivers → CAN raw protocol | CONFIG_CAN_RAW |
RAW套接字,应用层收发必备 |
| CAN Device Drivers → CAN broadcast manager | CONFIG_CAN_BCM |
广播管理,网关路由时常用 |
| CAN Device Drivers → CAN Gateway | CONFIG_CAN_GW |
CAN网关路由功能,做网关必须开 |
| CAN Device Drivers → Platform CAN drivers with Netlink support | CONFIG_CAN_FLEXCAN |
FlexCAN控制器驱动(i.MX6ULL用这个) |
注意:如果你用的是M_CAN控制器(比如STM32MP1或某些TI平台),要选 CONFIG_CAN_M_CAN 而不是FlexCAN。选错驱动,内核启动时CAN设备根本不会注册。
4.2.3 配置FlexCAN驱动细节
进入 CAN Device Drivers → Platform CAN drivers with Netlink support,找到FlexCAN选项。我建议把它编译进内核(<*>),而不是模块(<M>)。为什么?因为网关设备通常需要CAN在系统启动时就可用,模块加载会有延迟。
# 确认FlexCAN被选中
# 路径: CAN Device Drivers → Platform CAN drivers with Netlink support
# 选项: [*] FlexCAN driver support
另外,别忘了检查时钟频率和中断配置。FlexCAN驱动需要知道CAN控制器的时钟源频率,这个通常在设备树里指定,但内核配置里也有默认值。
4.3 配置设备树:描述CAN硬件
设备树(Device Tree)是嵌入式Linux的“硬件说明书”。内核通过它知道:CAN控制器挂在哪条总线上、中断号是多少、时钟源是什么。
以i.MX6ULL为例,FlexCAN控制器在芯片内部,连接在aips-bus上。设备树节点大概长这样:
&can1 {
pinctrl-names = "default";
pinctrl-0 = &pinctrl_flexcan1;
xceiver-supply = ®_can_3v3;
status = "okay";
};
&can2 {
pinctrl-names = "default";
pinctrl-0 = &pinctrl_flexcan2;
xceiver-supply = ®_can_3v3;
status = "okay";
};
这里有个坑——引脚复用。我曾经在一个项目里忘了配置pinctrl,结果CAN控制器注册成功了,但引脚没被正确复用,示波器一看TX/RX全是高电平,根本发不出数据。
避坑指南:在 imx6ull-pinfunc.h 里找到FlexCAN的引脚定义,确保UART、SPI等外设没有和CAN引脚冲突。我习惯先在芯片参考手册里画一张“引脚分配表”,再写设备树。
4.4 编译内核与设备树
配置完成后,就是编译。命令很简单,但参数别写错。
# 编译内核镜像
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 zImage
# 编译设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 dtbs
# 编译内核模块(如果选了模块)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 modules
编译完成后,在 arch/arm/boot/ 目录下会生成 zImage,在 arch/arm/boot/dts/ 下生成 .dtb 文件(比如 imx6ull-myboard.dtb)。
4.4.1 验证编译产物
我每次编译完都会做两件事:
- 检查内核配置是否包含CAN:
grep CONFIG_CAN .config,确认选项是=y或=m - 反编译设备树验证:
dtc -I dtb -O dts -o output.dts imx6ull-myboard.dtb,看看CAN节点是不是status = "okay"
我的习惯:把 zImage 和 .dtb 复制到一个单独的 deploy/ 目录,用日期命名备份。比如 deploy/20240520/zImage。这样调试时能快速回退到某个已知正常的版本。
4.5 本章知识结构图
下面这张图梳理了从源码到启动镜像的完整流程,以及每个环节的关键产出物。
4.6 常见问题与排查
编译过程中可能会遇到几个典型问题,我列出来供你参考:
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
编译报错 undefined reference to 'can_rx_register' |
CAN核心模块未编译进内核 | 检查 CONFIG_CAN 和 CONFIG_CAN_DEV 是否开启 |
设备树编译报 syntax error |
dts文件中括号或分号不匹配 | 用 dtc -I dts -O dtb 单独检查语法 |
| 内核启动后找不到CAN设备 | 设备树中CAN节点 status = "disabled" |
改为 "okay",并确认引脚复用配置正确 |
FlexCAN驱动报 wrong clock source |
设备树中 clock-frequency 与实际不符 |
查芯片手册确认CAN时钟源频率,修正设备树 |
曾经有个坑:我遇到过编译通过、设备树也正确,但CAN就是无法收发。最后发现是内核配置里把 CONFIG_CAN_RAW 编译成了模块(=m),而根文件系统里没加载这个模块。所以,如果你不是模块化文件系统,建议把CAN相关选项都编进内核。