【发布时间】:2015-11-05 15:57:44
【问题描述】:
我为什么要读这篇文章?
如果您有 Beaglebone Black (BBB),并且想要将自己的设备连接到它(而不是 cape),您可能已经听说过设备树。就我而言,我想将 RTC 设备连接到 BBB 上的 I2C 总线。网络上散布着大量信息,本文旨在总结我发现的内容,同时也是完成它的指南。
所以我将给出一个完整的例子来激活 BBB 上的 I2C 总线,以及使用内核中包含的设备驱动程序连接 DS1308 RTC 芯片。听起来不错?
然后继续阅读,如果有任何不清楚的地方,请离开 cmets。如果您有点着急,您也可以在Github 上获取设备树覆盖代码并飞走。
首要任务。
我在我的 BBB 上使用 ArchLinux ARM 主要是因为 Arch Linux 非常棒,而且我可能太愚蠢而无法使用 debianoid 发行版。 这是系统的screenfetch..
您可能会注意到内核版本已经高于 3.x 版本。您在 screenfetch 中看不到的是内核使用 Capemgr 实用程序支持设备树覆盖。
什么是设备树?
我就快点,你可以找到更深入的知识here、here、here和here。 设备树是描述平台上底层硬件的结构。它在嵌入式设备中大量使用,因为 SOC 和其他东西没有像 PCI 这样可以发现设备的总线。它们必须静态定义并连接到“平台总线”,以便为内核附带的设备驱动程序提供句柄。
在将设备树引入 Linux 之前,所有这些工作都必须使用特定的 C 头文件和自定义实现来完成,然后所有这些都必须合并到主线内核中。因此,这是一项可以想象的详尽任务,它来到了著名的Linus Torvalds rant。这里还有更多device tree background。
是的,很好,但它是如何工作的?
为了描述设备树,我们使用.dts(设备树源)文件,这些文件是人类可读的,并由设备树编译器 (dtc) 编译成设备树 blob (.dtb),二进制格式。当系统启动时,引导加载程序(例如u-boot)将该 blob 移交给内核。内核对其进行解析并创建设备树给定的所有设备。
如果您不相信我,请使用设备树编译器来查看您的 BBB 现在正在使用的设备树。
如果您还没有安装,请获取相应的软件包..
pacman -Sy dtc-overlay
dtc -f -I fs /proc/device-tree | less
推荐使用到寻呼机less 的管道,因为该命令会生成大量输出。结果应该是这样的..
您的设备树的所有部分也可以在内核源代码中进行调查,但由于还有一个包含机制,因此信息会在
中的多个文件中拆分 <kernel-source>/arch/arm/boot/dts/..
一些相关文件是:
am335x-bone-common.dtsiam335x-boneblack.dtsam33xx.dtsi
注意:
.dtsi文件等价于 C 或 C++ 中的.h文件 因为它们被.dts包括在内(因此最后是'i') 文件
它们都描述了与处理器相关的设备、Beaglebone 平台上的常见设备或仅适用于 Beaglebone Black 的设备。
你提到了叠加层,那是什么?
问得好,我看你还在我身边。正如我之前所说,设备树 blob 在内核启动时被解析。因此,当您的系统启动并运行时,整个魔法已经结束。在像 BBB 这样带有一大堆扩展板 (Capes) 的平台上,这将需要您在每次使用另一个 cape 时重新编译设备树。
因此,您拥有允许您在运行时在设备树中添加或修改设备的覆盖机制!惊人的。
注意:为了能够编译设备树覆盖,请确保安装适当的包,如上述 (
dtc-overlay)
我将如何使用这一切?
我给你举个例子。由于 BBB 没有实时时钟 (rtc),这对于生成测量时间戳等很有用,我们将解决这个问题。
我们将使用ds1307 实时时钟芯片(实际上我有一个ds1308 rtc,但驱动程序是兼容的)并通过BBB 上的I2C1 总线与其通信。默认情况下,BBB 上的总线是禁用的,正如您从设备树源中看到的那样。
那个sn-p里面的重要信息是:
- 定义了一个名为“i2c1”的节点
- 定义为兼容omap4-i2c驱动
- 根据处理器reference manual(第 181 页)为设备分配一个内存映射地址 (0x4802a000) 和适当的地址范围 (0x1000)
- 设备状态为禁用
现在我们将创建一个覆盖来配置 i2c1 总线的 GPIO 引脚,激活该总线,然后我们将添加 rtc-device i2c1 总线,以便自动加载适当的驱动程序并创建 rtc-device在/dev。
BBB 上 P8 和 P9 接头上的 GPIO 引脚具有多种功能,它们混合在一起,因此我们必须调整 pinmux 设置以将它们用于 I2C 通信。正如您在 I2C1 总线的 this table 中看到的那样,我们必须在多路复用器模式 2 中使用接头引脚 17 和 18。要获取有关 BBB 上 GPIO 处理的更多信息,请查看 here。
/dts-v1/;
/plugin/;
/{ /* this is our device tree overlay root node */
compatible = "ti,beaglebone", "ti,beaglebone-black";
part-number = "BBB-I2C1"; // you can choose any name here but it should be memorable
version = "00A0";
fragment@0 {
target = <&am33xx_pinmux>; // this is a link to an already defined node in the device tree, so that node is overlayed with our modification
__overlay__ {
i2c1_pins: pinmux_i2c1_pins {
pinctrl-single,pins = <
0x158 0x72 /* spi0_d1.i2c1_sda */
0x15C 0x72 /* spi0_cs0.i2c1_sdl */
>;
};
};
};
}; /* root node end */
天哪,刚刚发生了什么?
乍一看,覆盖语法看起来很奇怪,但它基本上由所谓的片段组成,这些片段针对已经存在的设备节点并修改该节点(以及它的子节点)。
在这种情况下,我们以处理器设备树 (am33xx.dtsi) 中定义的am33xx_pinmux 设备节点为目标。在该节点中,我们添加了一个名为 pinmux_i2c1_pins 的新子节点,该子节点之前不存在(查看am335x-bone-common.dtsi 进行验证)和标签 i2c1_pins。
下一部分有点复杂,如果您有兴趣阅读this。每个 GPIO 引脚都由一个具有多个位的寄存器配置以控制其行为,并且所有寄存器都由 pinctrl-single 驱动程序控制。要设置特定引脚,只需使用它与基地址的地址偏移量(您将在上面的 P9 头表中找到)并将其引脚配置作为第二个参数..
我从Derek Molloy借用了这个概述来解释pin模式。由于0x72 等效于01110010b,我们将两个引脚配置为输入,启用上拉电阻和多路复用模式 2 中的主动转换控制。
这些管脚的多路复用模式 2 意味着接头 P9 上的管脚 17 是时钟线 SCL,接头 P9 上的管脚 18 是数据线 SDA。
但我们还是要启用 I2C1?
这是绝对正确的,所以让我们如下扩展我们的叠加层..
/dts-v1/;
/plugin/;
/{ /* this is our device tree overlay root node */
compatible = "ti,beaglebone", "ti,beaglebone-black";
part-number = "BBB-I2C1"; // you can choose any name here but it should be memorable
version = "00A0";
fragment@0 {
target = <&am33xx_pinmux>; // this is a link to an already defined node in the device tree, so that node is overlayed with our modification
__overlay__ {
i2c1_pins: pinmux_i2c1_pins {
pinctrl-single,pins = <
0x158 0x72 /* spi0_d1.i2c1_sda */
0x15C 0x72 /* spi0_cs0.i2c1_sdl */
>;
};
};
};
fragment@1 {
target = <&i2c1>;
__overlay__ {
pinctrl-0 = <&i2c1_pins>;
clock-frequency = <100000>;
status = "okay";
rtc: rtc@68 { /* the real time clock defined as child of the i2c1 bus */
compatible = "dallas,ds1307";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x68>;
};
};
};
}; /* root node end */
在上面的代码中,我们添加了一个针对 i2c1 设备节点的新片段,并告诉它使用我们之前定义的引脚配置。我们将 I2C 时钟频率设置为 100kHz 并激活设备。
此外,rtc 时钟作为子节点添加到 i2c1 节点。内核的重要信息是兼容声明,命名要使用的驱动程序 (ds1307) 和 I2C 总线上的设备地址 (0x68)。 rtc的I2C地址可以从datasheet中获取。
我如何将该代码放入内核?
首先必须编译设备树源。通过以下调用使用 dtc 编译器..
dtc -O dtb -o <filename>-00A0.dtbo -b 0 -@ <filename>.dts
小心!文件名必须是你想要的名字加上上面看到的版本标签 (-00A0) 的串联,否则你会很难。
应将生成的 .dtbo 文件复制到 /lib/firmware 中,我真的不知道“-00A0”命名约定的来源,但固件目录中的其他文件也使用它。
从现在开始,您可以使用 Capemgr 动态加载叠加层。为此,请移至/sys/devices/platform/bone_capemgr/,然后执行..
echo <filename> > slots
Capemgr 然后会在固件目录中查找您的.dtbo 文件并在可能的情况下加载它。通过查看插槽文件,您可以查看该过程是否成功。它应该看起来像这样..
检查 Beaglebone 使用的设备树。
dtc -f -I fs /proc/device-tree | less
您会从叠加层中找到所有条目..
此外,您的文件系统中应该有一个新的 I2C 设备 (/dev/i2c-1) 和一个新的 rtc 设备 (/dev/rtc1)。
要查看您的 i2c 总线,请安装包 i2c-tools 并使用..
i2cdetect -r 1
输出应该是这样的..
如您所见,地址 0x68 已被设备占用。
查询您的 rtc 使用..
hwclock -r -f /dev/rtc1
但这还不是全部,不是吗?
不,您还有一个选择,即在启动时加载设备树覆盖。 太棒了!
为此打开/boot/uEnv.txt 并将bone_capemgr.enable_partno=<filename> 添加到optargs 语句中。这就是它在我的 BBB 上的样子
optargs=coherent_pool=1M bone_capemgr.enable_partno=bbb-i2c1
令人困惑的是,optargs 中使用的文件名不是设备树覆盖中定义的part-number 标记。
如果您愿意,可以在 github 上将我的示例代码放在有用的 Makefile 旁边。
抱歉,帖子太长了。
【问题讨论】:
-
这是一个超级棒的解释!您能否发布更多有关如何连接 GPIO 设备的详细信息。假设需要 5 个 gpios?此外,如果您必须为您的设备编写绑定,比如 rtc,它会是什么样子?以及如何以及在何处插入并构建/测试它?我知道你也很清楚这个问题的答案!!非常期待您的回复!
-
简而言之,如果您必须使用设备树而不是覆盖来运行驱动程序
-
如果您不想使用覆盖机制,您需要从内核源代码中获取设备树源代码(形成内核源代码目录,您可以在
./arch/arm/boot/dts中找到它)并添加您的自定义配置。那是容易的部分。之后,您需要为您的板重新编译设备树并替换旧的设备树二进制 blob,该二进制 blob 通常附加到内核 uimage 或 zimage 文件中。可能最好的方法是为您的主板重新编译内核。这样做时,您也可以将驱动程序嵌入内核中。问候 -
@Raulp 关于你的 GPIO 请求看看这个post,也许你可以提取一些有用的信息。
-
感谢它对我有用!我在 am335x-boneblack.dts 中添加了一个节点,其中包含我正在使用的所需 gpios:my-gpio = ;其中 gpio1 是 gpio 控制器 1 , 16 是 gpio1 bank 上的引脚 => 实际 gpio 引脚是 32*1+16=> 48 ,“1”是 ACTIVE_LOW 配置。我也可以使用宏来表示低电平有效/高的。我使用以下方法提取了 gpio: ret = of_get_named_gpio(child, "my-gpio", 0);
标签: beagleboneblack device-tree