如何使用设备树覆盖在Beaglebone Black上添加i2c设备?

Ili*_*psi 18 device-tree beagleboneblack

我为什么要读这个?

如果您有Beaglebone Black(BBB)并且想要将自己的设备连接到它(不是斗篷),您可能已经听说过设备树.在我的情况下,我想将RTC设备连接到BBB上的I2C总线.网络上散布着大量的信息,本文旨在概述我发现的内容以及完成任务的指南.

因此,我将给出一个在BBB上激活I2C总线的完整示例,以及使用内核中包含的设备驱动程序连接DS1308 RTC芯片.听起来不错?

请继续阅读,如果有任何不明确的地方请留下评论.如果您有点匆忙,您也可以在Github上抓取设备树覆盖代码并飞走.

首先要做的事情.

我在我的BBB上使用ArchLinux ARM主要是因为Arch Linux非常棒而且我使用debianoid发行版可能太愚蠢了.这是系统的屏幕截图 ..

BBB上的ArchLinux屏幕截图

你可能会注意到内核版本已经超过了3.x的东西.您在screenfetch中看不到的是内核使用Capemgr实用程序支持设备树覆盖.

什么是设备树?

我会尽快做到,你可以在这里,这里,这里这里找到更深层次的知识.设备树是描述平台上底层硬件的结构.它在嵌入式设备中被大量使用,因为SOC和其他东西没有像PCI这样可以发现设备的总线.它们必须静态定义并附加到"平台总线",以便为内核附带的设备驱动程序提供句柄.

在将设备树引入Linux之前,所有这些工作都必须使用特定的C头文件和自定义实现来完成,然后所有这些都必须合并到主线内核中.因此,这是一个可以想象的详尽的任务,它来到了着名的Linus Torvalds咆哮.在这里,您还可以使用更多设备树背景.

是的很好,但它是如何工作的?

描述我们正在使用的.dts设备树(设备树源)文件,这些文件是人类可读的,并由设备树编译器(dtc)编译成设备树blobs(.dtb),二进制格式.当系统引导引导加载程序(例如u-boot)时,将该blob移交给内核.内核解析它并创建设备树给出的所有设备.

如果您不相信我,请使用设备树编译器进入BBB正在使用的设备树.

如果您尚未安装,请获取相应的包装..

pacman -Sy dtc-overlay
dtc -f -I fs /proc/device-tree | less
Run Code Online (Sandbox Code Playgroud)

less由于该命令生成了大量输出,因此建议使用该寻呼机的管道.结果应该看起来像这样......

在此输入图像描述

设备树的所有部分也可以在内核源代码中进行调查,但由于还有一个包含机制,因此信息在几个文件中分开.

 <kernel-source>/arch/arm/boot/dts/.. 
Run Code Online (Sandbox Code Playgroud)

一些相关文件是:

  • am335x-bone-common.dtsi
  • am335x-boneblack.dts
  • am33xx.dtsi

注:该.dtsi文件是等同于.h在C文件或C++,因为他们得到包括(因此"我"末)的.dts 文件

它们都描述了与处理器相关的设备,Beaglebone平台上的常用设备或仅适用于Beaglebone Black的设备.

你提到叠加层,那是什么?

好问题,我看到你还在和我在一起.正如我之前所说,内核启动时会解析设备树blob.因此,当您的系统启动并运行时,整个魔法已经结束.在像BBB这样的平台上有一大堆扩展板(Capes),这需要你每次去另一个斗篷使用时重新编译设备树.

因此,您具有覆盖机制,允许您在运行时在设备树中添加或修改设备!惊人.

注意:为了能够编译设备树覆盖,请确保安装适当的包,如上所述(dtc-overlay)

我将如何使用这一切?

我给你举个例子.由于BBB没有实时时钟(rtc),这对于生成测量等时间戳很有用,我们将解决这个问题.

我们将使用ds1307实时时钟芯片(实际上我有一个ds1308 rtc,但驱动程序兼容)并通过BBB上的I2C1总线与之通信.默认情况下,从设备树源中可以看到BBB上的总线已禁用.

am33xx.dtsi中的i2c1节点

该代码段中的重要信息是:

  • 定义了名为"i2c1"的节点
  • 它被定义为与omap4-i2c驱动程序兼容
  • 根据处理器参考手册(第181页)为设备分配内存映射地址(0x4802a000)和适当的地址范围(0x1000 )
  • 设备状态已禁用

现在我们将创建一个覆盖层来配置i2c1总线的GPIO引脚,激活该总线,然后我们将添加rtc-device i2c1总线,以便自动加载相应的驱动程序并创建rtc-device /dev.

BBB上的P8和P9接头上的GPIO引脚具有多个功能,这些功能被复用在一起,因此我们必须调整pinmux设置以将其用于I2C通信.正如您在此表中可以看到的I2C1总线,我们必须在多路复用模式2中使用标头引脚17和18.要获得有关BBB上GPIO处理的更多信息,请查看此处.

/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 */
Run Code Online (Sandbox Code Playgroud)

OMG刚刚发生了什么?

乍一看,覆盖语法看起来很奇怪,但它基本上由所谓的片段组成,这些片段以现有设备节点为目标并修改该节点(以及它的子节点).

在这种情况下,我们将目标am33xx_pinmux定位在处理器设备树(am33xx.dtsi)中定义的设备节点.在该节点内,我们添加了一个名为pinmux_i2c1_pins的新子节点,该节点之前不存在(请查看am335x-bone-common.dtsi验证)和标签i2c1_pins.

下一部分有点复杂,如果您有兴趣阅读本文.每个GPIO引脚由一个寄存器配置,有几个位来控制它的行为,所有寄存器都由pinctrl-single驱动程序控制.要设置一个特定的引脚,只需使用它与基地址的地址偏移量(你会在上面的P9头表中找到它),并将它的引脚配置作为第二个参数.

BBB gpio设置

我从Derek Molloy借用了这个概述来解释引脚模式.由于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 */
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,我们添加了一个针对i2c1设备节点的新片段,并告诉它使用我们之前定义的引脚配置.我们将I2C时钟频率设置为100kHz并激活器件.

此外,rtc时钟作为子节点添加到i2c1节点.内核的重要信息是兼容语句,命令驱动程序使用(ds1307)和I2C总线上的设备地址(0x68).rtc的I2C地址可以从数据表中获得.

我如何将代码放入内核?

首先必须编译设备树源.使用dtc编译器进行以下调用..

dtc -O dtb -o <filename>-00A0.dtbo -b 0 -@ <filename>.dts
Run Code Online (Sandbox Code Playgroud)

警告!文件名必须是您想要的名称和上面显示的版本标签(-00A0)的串联,否则您将很难.

.dtbo应该复制生成的文件/lib/firmware,我真的不知道"-00A0"命名约定来自哪里,但固件目录中还有其他文件也使用它.

从现在开始,您可以使用Capemgr动态加载叠加层.为此,请进入/sys/devices/platform/bone_capemgr/然后执行..

echo <filename> > slots
Run Code Online (Sandbox Code Playgroud)

然后,Capemgr将.dtbo在固件目录中查找您的文件,并在可能的情况下加载它.通过查看插槽文件,您可以查看该过程是否成功.它应该看起来像这样......

在此输入图像描述

检查Beaglebone使用的设备树.

dtc -f -I fs /proc/device-tree | less
Run Code Online (Sandbox Code Playgroud)

你会发现叠加层中的所有条目..

在此输入图像描述

在此输入图像描述

此外,文件系统中应该有一个新的I2C设备(/dev/i2c-1)和一个新的rtc设备(/dev/rtc1).

要查看您的i2c总线,请安装包i2c-tools并使用..

i2cdetect -r 1
Run Code Online (Sandbox Code Playgroud)

输出应该是这样的..

在此输入图像描述

如您所见,地址0x68被设备占用.

要查询你的rtc使用..

hwclock -r -f /dev/rtc1
Run Code Online (Sandbox Code Playgroud)

但这不是全部,或者是它?

不,还有一个选项,在启动时加载设备树覆盖.真棒!

要这样做,请打开/boot/uEnv.txt并添加bone_capemgr.enable_partno=<filename>optargs语句中.这就是我在BBB上的样子

optargs=coherent_pool=1M bone_capemgr.enable_partno=bbb-i2c1
Run Code Online (Sandbox Code Playgroud)

令人困惑的是,文件名用于optargs而不是part-number设备树覆盖中定义的标记.

如果你愿意的话,你可以把我的示例代码放在github上有用的Makefile旁边.

很抱歉很长的帖子.

小智 2

这是非常有用且有价值的信息。我编写了一个 i2c 内核驱动程序,可以动态加载该驱动程序以与地址 0x77 处的自定义芯片通信。我过去通过手动实例化设备成功地与芯片通信,如下所示:echo act2_chip 0x77 > /sys/bus/i2c/devices/i2c-1/new_device。设备实例化后,我可以使用 i2cdetect 工具查看它,并且我的可加载内核驱动程序可以与芯片通信。

现在我尝试使用设备树方法实例化设备。因此,按照您的指导,我更改了 dtsi 文件中的一些参数,如下所示:

fragment@1 {
    target = <&i2c1>;

    __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>;
      };
Run Code Online (Sandbox Code Playgroud)

我将芯片连接到 scl 和 sda 的引脚 17 和 18。这是 echo > slot 之后得到的 dmesg 输出: 图像

但是,当将驱动程序插入内核时,我看到探针函数被调用。这意味着驱动程序能够按照我的想法看到该设备。

当我尝试写入内核驱动程序时,我收到以下消息: omap_i2c 4802a000.i2c:控制器超时