4、CAN设备树配置:设备树语法、CAN控制器节点配置、时钟与引脚配置
设备树,说白了就是一块「硬件描述板」。它告诉内核:你有哪些外设、它们挂在哪条总线上、中断号是多少、时钟怎么配。我刚开始接触设备树时,总觉得这东西玄乎,后来调了三个月的驱动才明白——设备树配错了,内核根本认不出你的CAN控制器。
这一节,我们就来啃下设备树配置这块硬骨头。我会结合项目经验,把语法、节点、时钟、引脚这些点一一拆开。
核心要点:设备树是驱动与硬件的「翻译官」。CAN控制器节点配置的核心在于 compatible、reg、interrupts、clocks、pinctrl 这几个属性。
4.1 设备树语法速览
设备树的语法其实不复杂。它用树形结构描述硬件,每个节点就是一个设备。我习惯把设备树看作一个巨大的 JSON 对象,只不过它的写法更「C语言」一些。
/dts-v1/;
/ {
model = "My CAN Board";
compatible = "vendor,my-board";
soc {
#address-cells = <1>;
#size-cells = <1>;
ranges;
can0: can@1c00000 {
compatible = "vendor,can-controller";
reg = <0x01c00000 0x1000>;
interrupts = <0 25 4>;
...
};
};
};
这里有几个关键点:
- 节点名:格式是
name@address,比如can@1c00000。address 是基地址,用来区分同名节点。 - 属性:键值对。值可以是字符串、数字、数组、二进制数据。
- 标签:比如
can0:,方便在其他地方引用。
我的习惯:每个节点都加标签,哪怕暂时用不到。后续要覆盖或引用时,省去翻地址的麻烦。
4.2 CAN控制器节点配置
CAN控制器的设备树节点,核心属性就那么几个。我列个表,你一看就明白:
| 属性 | 说明 | 示例值 |
|---|---|---|
compatible |
驱动匹配字符串,内核靠它找驱动 | "vendor,can-controller" |
reg |
寄存器基地址和范围 | <0x01c00000 0x1000> |
interrupts |
中断号、触发类型 | <0 25 4> |
clocks |
时钟源引用 | <&clkc CAN_CLK> |
pinctrl-names |
引脚复用状态名称 | "default" |
pinctrl-0 |
引脚复用配置引用 | <&can_pins> |
bus-off |
总线关闭恢复时间(可选) | <100> |
来看一个完整的CAN控制器节点示例:
can0: can@1c00000 {
compatible = "allwinner,sun8i-can";
reg = <0x01c00000 0x1000>;
interrupts = <GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_CAN>;
resets = <&ccu RST_BUS_CAN>;
pinctrl-names = "default";
pinctrl-0 = <&can_pins>;
status = "okay";
};
这里我用了 GIC_SPI 和 IRQ_TYPE_LEVEL_HIGH 宏,它们定义在 include/dt-bindings/interrupt-controller/arm-gic.h 里。我个人建议直接引用这些宏,别写裸数字——可读性差,还容易出错。
注意:status = "okay" 必须显式写。设备树默认是 "disabled",不写的话内核不会使能这个节点。我曾经因为这个坑,浪费了半天排查。
4.3 时钟配置
CAN控制器需要时钟才能工作。时钟配置不对,要么无法初始化,要么波特率跑偏。
时钟通常来自系统时钟控制器(CCU)。设备树里通过 clocks 属性引用时钟源:
clocks = <&ccu CLK_BUS_CAN>;
clock-names = "can_clk";
clock-names 是可选的,但建议加上。驱动里可以通过 devm_clk_get(&pdev->dev, "can_clk") 按名字获取,比按索引更安全。
时钟频率怎么确定?我一般这么算:
- 查芯片手册,找到CAN控制器的时钟源(通常是AHB或APB总线时钟)。
- 确认时钟树里该时钟的默认频率。
- 在设备树里用
assigned-clocks和assigned-clock-rates强制设定频率。
&can0 {
assigned-clocks = <&ccu CLK_BUS_CAN>;
assigned-clock-rates = <24000000>; // 强制设为24MHz
};
经验之谈:CAN控制器的时钟频率直接影响波特率精度。24MHz 是常见值,能整除 125K、250K、500K、1M 这些标准波特率。如果时钟频率是 26MHz 这种「奇怪」的值,波特率误差可能会超标。
4.4 引脚配置(pinctrl)
引脚配置是设备树里最容易出问题的部分。CAN控制器的 TX 和 RX 引脚必须正确复用,否则数据发不出去,也收不进来。
引脚配置通常在 pinctrl 节点里定义:
can_pins: can-pins {
pins = "PA8", "PA9";
function = "can";
bias-pull-up;
};
然后在CAN控制器节点里引用:
pinctrl-names = "default";
pinctrl-0 = <&can_pins>;
这里有几个要点:
- pins:指定哪些引脚。不同芯片的引脚名规则不同,查数据手册。
- function:复用功能。必须是芯片支持的复用选项。
- bias-pull-up:CAN总线建议加上拉,防止浮空。
我曾经踩过的坑:有一次配置CAN引脚,忘了加 bias-pull-up,结果总线在空闲时电平不稳定,导致误帧率飙升。加了上拉后,问题立刻消失。
4.5 知识结构总览
下面这张图,把CAN设备树配置的核心脉络画出来了。你可以把它当作配置时的检查清单:
4.6 实战:完整配置示例
最后,我放一个完整的设备树配置片段。这是我在某款全志芯片上调试CAN时用的,你可以直接参考:
/ {
model = "My CAN Board";
compatible = "vendor,my-board";
soc {
#address-cells = <1>;
#size-cells = <1>;
ranges;
can0: can@1c00000 {
compatible = "allwinner,sun8i-can";
reg = <0x01c00000 0x1000>;
interrupts = <GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_CAN>;
resets = <&ccu RST_BUS_CAN>;
pinctrl-names = "default";
pinctrl-0 = <&can_pins>;
status = "okay";
};
};
pinctrl@1c20800 {
can_pins: can-pins {
pins = "PA8", "PA9";
function = "can";
bias-pull-up;
};
};
};
&can0 {
assigned-clocks = <&ccu CLK_BUS_CAN>;
assigned-clock-rates = <24000000>;
};
调试小技巧:配置完设备树后,在 /sys/firmware/devicetree/base/ 下可以查看实际生效的设备树内容。用 dtc -I fs -O dts /sys/firmware/devicetree/base 可以反编译成可读的DTS,方便对比检查。
嗯,设备树配置这块就讲到这里。记住:compatible 是钥匙,reg 是门牌,interrupts 是门铃,clocks 是电源,pinctrl 是门锁。五样都对了,门才能打开。