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

为什么要读这个?

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

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

然后阅读,如果有什么不清楚,请留下评论.如果您有点匆忙,您也可以在Github上抓住设备树重叠代码并飞走.

第一件事

我在我的BBB上使用ArchLinux ARM,主要是因为Arch Linux很棒,我可能太笨了,不能使用debianoid发行版.
这是系统的screenfetch

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

什么是设备树?

我会做的很快,你可以找到更深入的知识here,here,herehere.
设备树是描述您平台上底层硬件的结构.它在嵌入式设备中大量使用,因为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

由于该命令生成了大量的输出,所以推荐使用传呼机的管道.结果应该看起来像这样..

您的设备树的所有部分也可以在内核源中进行调查,但是由于还有一个包含机制,信息会在几个文件之间分割

<kernel-source>/arch/arm/boot/dts/..

一些相关文件是:

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

Note: The .dtsi files are equivalent to .h files in C or C++
because they get included (therefore the ‘i’ at the end) by .dts
files

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

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

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

因此,您可以使用覆盖机制,允许您在设备树中添加或修改设备AT RUNTIME!惊人.

Note: to be able to compile device tree overlays make sure to install the appropriate package like above (dtc-overlay)

我该如何使用这一切?

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

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

该片段中的重要信息是:

>定义了一个名为’i2c1’的节点
>它被定义为与omap4-i2c驱动程序兼容
>根据处理器reference manual(第181页),设备将分配一个内存映射地址(0x4802a000)和一个适当的地址范围(0x1000)
>设备状态被禁用

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

BBB上的P8和P9接头上的GPIO引脚具有多种功能,它们被复合在一起,因此我们必须调整引脚设置以将其用于I2C通信.从this table可以看到,对于I2C1总线,我们必须在多路复用器模式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 */

OMG刚刚发生什么?

乍一看,重叠语法看起来很奇怪,但它基本上由所谓的片段组成,目标是已经存在的设备节点并修改该节点(而且是子节点).

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

下一部分更复杂一些,如果您有兴趣,请阅读this.每个GPIO引脚都由一个具有几个位的单个寄存器配置,以控制其行为,所有寄存器由pinctrl单个驱动程序控制.要设置一个特定的引脚只是使用它的地址偏移从基地址(你会发现在上面的P9标题表),它的引脚配置作为第二个参数..

我从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,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设备节点的新片段,并告诉它使用我们以前定义的引脚配置.我们设置一个100kHz的I2C时钟频率并激活该设备.

此外,rtc时钟作为一个小孩添加到i2c1节点.内核的重要信息是兼容语句,命名驱动程序(ds1307)和I2C总线上的器件地址(0x68). rtc的I2C地址可以从数据表中获取.

那我该如何把这个代码放到内核中?

起初设备树源必须被编译.使用带有以下调用的dtc编译器

dtc -O dtb -o <filename>-00A0.dtbo -b 0 -@ <filename>.dts

Caution! The filename must be a concatenation of the name you desire plus the version tag as seen above (-00A0) otherwise you’ll have a hard time.

所得到的.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工具并使用..

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中使用,而不是在设备树覆盖中定义的部件号标签.

如果你喜欢,你可以在github将代码放在一个有用的Makefile上.

对不起,长发

解决方法

这是非常有用和有价值的信息.我写了一个i2c内核驱动程序,我可以动态加载,与地址0x77的定制芯片通话.我以前通过手动实例化设备成功地与芯片通信,如下所示:echo act2_chip 0x77> / SYS /总线/ I2C /装置/ I2C-1 / new_device.
在设备实例化之后,我可以看到它使用i2cdetect工具,我的可加载内核驱动程序可以与芯片进行通信.

现在我试图使用设备树方法实例化设备.所以跟随你,我改变了你的dtsi文件中的一些参数,如下所示:

片段@ 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>;
  };

我将芯片连接到引脚17和18,用于scl和sdk.这是echo中得到的dmesg输出>插槽:

但是当将驱动程序插入内核时,我看到被调用的探测功能.这意味着司机能够尽可能地看到设备.

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


一.C语言中的static关键字 在C语言中,static可以用来修饰局部变量,全局变量以及函数。在不同的情况下static的作用不尽相同。 (1)修饰局部变量 一般情况下,对于局部变量是存放在栈区的,并且局部变量的生命周期在该语句块执行结束时便结束了。但是如果用static进行修饰的话,该变量便存
浅谈C/C++中的指针和数组(二) 前面已经讨论了指针和数组的一些区别,然而在某些情况下,指针和数组是等同的,下面讨论一下什么时候指针和数组是相同的。C语言标准对此作了说明:规则1:表达式中的数组名被编译器当做一个指向该数组第一个元素的指针; 注:下面几种情况例外 1)数组名作为sizeof的操作数
浅谈C/C++中的指针和数组(一)指针是C/C++的精华,而指针和数组又是一对欢喜冤家,很多时候我们并不能很好的区分指针和数组,对于刚毕业的计算机系的本科生很少有人能够熟练掌握指针以及数组的用法和区别。造成这种原因可能跟现在大学教学以及现在市面上流行的很多C或者C++教程有关,这些教程虽然通俗易懂,
从两个例子分析C语言的声明 在读《C专家编程》一书的第三章时,书中谈到C语言的声明问题,《C专家编程》这本书只有两百多页,却花了一章的内容去阐述这个问题,足以看出这个问题的重要性,要想透彻理解C语言的声明问题仅仅看书是远远不够的,需要平时多实践并大量阅读别人写的代码。下面借鉴《C专家编程》书中的两个
C语言文件操作解析(一)在讨论C语言文件操作之前,先了解一下与文件相关的东西。一.文本文件和二进制文件 文本文件的定义:由若干行字符构成的计算机文件,存在于计算机系统中。文本文件只能存储文件中的有效字符信息,不能存储图像、声音等信息。狭义上的二进制文件则指除开文本文件之外的文件,如图片、DOC文档。
C语言文件操作解析(三) 在前面已经讨论了文件打开操作,下面说一下文件的读写操作。文件的读写操作主要有4种,字符读写、字符串读写、块读写以及格式化读写。一.字符读写 字符读写主要使用两个函数fputc和fgetc,两个函数的原型是: int fputc(int ch,FILE *fp);若写入成功则
浅谈C语言中的位段 位段(bit-field)是以位为单位来定义结构体(或联合体)中的成员变量所占的空间。含有位段的结构体(联合体)称为位段结构。采用位段结构既能够节省空间,又方便于操作。 位段的定义格式为: type [var]:digits 其中type只能为int,unsigned int,s
C语言文件操作解析(五)之EOF解析 在C语言中,有个符号大家都应该很熟悉,那就是EOF(End of File),即文件结束符。但是很多时候对这个理解并不是很清楚,导致在写代码的时候经常出错,特别是在判断文件是否到达文件末尾时,常常出错。1.EOF是什么? 在VC中查看EOF的定义可知: #def
关于VC+ʶ.0中getline函数的一个bug 最近在调试程序时,发现getline函数在VC+ʶ.0和其他编译器上运行结果不一样,比如有如下这段程序:#include &lt;iostream&gt;#include &lt;string&gt;using namespace std;int
C/C++浮点数在内存中的存储方式 任何数据在内存中都是以二进制的形式存储的,例如一个short型数据1156,其二进制表示形式为00000100 10000100。则在Intel CPU架构的系统中,存放方式为 10000100(低地址单元) 00000100(高地址单元),因为Intel CPU
浅析C/C++中的switch/case陷阱 先看下面一段代码: 文件main.cpp#includeusing namespace std;int main(int argc, char *argv[]){ int a =0; switch(a) { case ...
浅谈C/C++中的typedef和#define 在C/C++中,我们平时写程序可能经常会用到typedef关键字和#define宏定义命令,在某些情况下使用它们会达到相同的效果,但是它们是有实质性的区别,一个是C/C++的关键字,一个是C/C++的宏定义命令,typedef用来为一个已有的数据类型
看下面一道面试题:#include&lt;stdio.h&gt;#include&lt;stdlib.h&gt;int main(void) { int a[5]={1,2,3,4,5}; int *ptr=(int *)(&amp;aʱ); printf(&quot;%d,%d&quot;,*(
联合体union 当多个数据需要共享内存或者多个数据每次只取其一时,可以利用联合体(union)。在C Programming Language 一书中对于联合体是这么描述的: 1)联合体是一个结构; 2)它的所有成员相对于基地址的偏移量都为0; 3)此结构空间要大到足够容纳最&quot;宽&quo
从一个程序的Bug解析C语言的类型转换 先看下面一段程序,这段程序摘自《C 专家编程》:#include&lt;stdio.h&gt;int array[]={23,34,12,17,204,99,16};#define TOTAL_ELEMENTS (sizeof(array)/sizeof(ar
大端和小端 嵌入式开发者应该对大端和小端很熟悉。在内存单元中数据是以字节为存储单位的,对于多字节数据,在小端模式中,低字节数据存放在低地址单元,而在大端模式中,低字节数据存放在高地址单元。比如一个定义一个short型的变量a,赋值为1,由于short型数据占2字节。在小端模式中,其存放方式为0X40
位运算和sizeof运算符 C语言中提供了一些运算符可以直接操作整数的位,称为位运算,因此位运算中的操作数都必须是整型的。位运算的效率是比较高的,而且位运算运用好的话会达到意想不到的效果。位运算主要有6种:与(&amp;),或(|),取反(~),异或(^),左移(&gt;)。1.位运算中的类型转换位
C语言文件操作解析(四)在文件操作中除了打开操作以及读写操作,还有几种比较常见的操作。下面介绍一下这些操作中涉及到的函数。一.移动位置指针的函数 rewind函数和fseek函数,这两个函数的原型是:void rewind(FILE *fp); 将位置指针移动到文件首 int fseek(FILE
结构体字节对齐 在用sizeof运算符求算某结构体所占空间时,并不是简单地将结构体中所有元素各自占的空间相加,这里涉及到内存字节对齐的问题。从理论上讲,对于任何变量的访问都可以从任何地址开始访问,但是事实上不是如此,实际上访问特定类型的变量只能在特定的地址访问,这就需要各个变量在空间上按一定的规则排
C语言文件操作解析(二)C语言中对文件进行操作必须首先打开文件,打开文件主要涉及到fopen函数。fopen函数的原型为 FILE* fopen(const char *path,const char *mode) 其中path为文件路径,mode为打开方式 1)对于文件路径,只需注意若未明确给出绝