如果您有Beaglebone Black(BBB)并且想要将自己的设备连接到它(而不是斗篷),您可能已经听说过设备树。在我的情况下,我想将RTC设备连接到BBB上的I2C总线。网络上散布着大量信息,本文旨在概述我发现的内容,以及完成任务的指南。
因此,我将给出一个在BBB上激活I2C总线以及使用内核中包含的设备驱动程序连接DS1308 RTC芯片的完整示例。听起来不错?
然后继续阅读,如果有任何不明确的地方请留下评论。如果您有点匆忙,也可以抓住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.dtsi
am335x-boneblack.dts
am33xx.dtsi
注意:
.dtsi
文件等同于C或C ++中的.h
文件 因为它们被.dts
包括在内(因此最后的#i;#) 文件
它们都描述了与处理器相关的设备,Beaglebone平台上的常用设备或仅适用于Beaglebone Black的设备。
好问题,我看到你还和我在一起。正如我之前所说,内核启动时会解析设备树blob。因此,当您的系统启动并运行时,整个魔法已经结束。在像BBB这样的平台上有一大堆扩展板(Capes),这需要你每次去另一个斗篷使用时重新编译设备树。
因此,您有覆盖机制,允许您在运行时在设备树中添加或修改设备!惊人。
注意:为了能够编译设备树覆盖,请确保安装适当的包,如上所述(
dtc-overlay
)
我给你举个例子。由于BBB没有实时时钟(rtc),这对于生成测量等时间戳很有用,我们将解决这个问题。
我们将使用ds1307实时时钟芯片(实际上我有ds1308 rtc但驱动程序兼容)并通过BBB上的I2C1总线与之通信。默认情况下,从设备树源中可以看到BBB上的总线已禁用..
该摘录中的重要信息是:
现在我们将创建一个覆盖层来配置i2c1总线的GPIO引脚,激活该总线,然后我们将添加rtc-device i2c1总线,以便自动加载相应的驱动程序和rtc -device在/dev
。
BBB上的P8和P9接头上的GPIO引脚具有多个功能,这些功能被复用在一起,因此我们必须调整pinmux设置以将其用于I2C通信。正如您在this table中看到的I2C1总线,我们必须在多路复用模式下使用标头引脚17和18. 2.要获得有关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_pinmux
)中定义的am33xx.dtsi
设备节点。在该节点内,我们添加了一个名为pinmux_i2c1_pins的新子节点,之前不存在(查看am335x-bone-common.dtsi
验证)和标签i2c1_pins。
下一部分有点复杂,如果您有兴趣请阅读this。每个GPIO引脚都由一个寄存器配置,有几个位来控制它的行为,所有寄存器都由pinctrl-single
驱动程序控制。要设置一个特定的引脚,只需使用它从基地址开始的地址偏移(你会在上面的P9标题表中找到它),它的引脚配置作为第二个参数。
我从Derek Molloy借用了这个概述来解释引脚模式。由于0x72
相当于01110010b
,因此我们将两个引脚配置为输入,并在多路复用模式2下启用上拉电阻和有源压摆控制。
这些引脚的多路复用模式2表示接头P9上的引脚17是时钟线SCL,接头P9上的引脚18是数据线SDA。
这绝对是正确的,所以让我们扩展我们的叠加层如下..
/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地址可以从数据表中获得。
首先必须编译设备树源。使用dtc编译器进行以下调用..
dtc -O dtb -o <filename>-00A0.dtbo -b 0 -@ <filename>.dts
小心!文件名必须是您想要的名称和上面显示的版本标签(-00A0)的串联,否则您将会遇到困难。
生成的.dtbo
文件应该复制到/lib/firmware
,我真的不知道&#34; -00A0&#34;命名约定来自但固件目录中还有其他文件也使用它。
从现在开始,您可以使用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旁边。
很抱歉很长的帖子。
答案 0 :(得分:2)
这是非常有用且有价值的信息。我写了一个i2c内核驱动程序,我可以动态加载它与地址0x77的自定义芯片通信。通过如下手动实例化设备,我已经成功地与芯片通信:echo act2_chip 0x77&gt; / SYS /总线/ I2C /装置/ I2C-1 / new_device。 设备实例化后,我可以使用i2cdetect工具看到它,我的可加载内核驱动程序可以与芯片通信。
现在我尝试使用设备树方法实例化设备。因此,在您的指导下,我更改了您的dtsi文件中的一些参数,如下所示:
片段@ 1 { target =&lt;&amp; i2c1&gt ;;
__overlay__ {
pinctrl-0 = <&i2c1_pins>;
clock-frequency = <100000>;
status = "okay";
act2_chip: act2_chip@77 { /* the real time clock defined as child of the i2c1 bus */
compatible = "xx,act2_chip";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x77>;
};
我在针脚17和18连接了芯片,用于scl和sdk。这是我在echo&gt;之后得到的dmesg输出。插槽:
但是在将驱动程序插入内核时,我看到调用了探测函数。这意味着司机能够在我想到的时候看到设备。
当我尝试写入内核驱动程序时,我收到以下消息: omap_i2c 4802a000.i2c:控制器超时