Category Archives: Beaglebone Black

在Mac上配置Eclipse交叉编译环境

根据Derek Molloy老师的视频教程,再加上一些mac特别需要进行的设置,终于配置好了Eclipse交叉编译环境,而且还可以通过ftp实时访问和操作BBB上的文件,以后编程序就方便多了,不必专门打开新的Terminal窗口了。下面简要记录一下配置过程。

首先需要在mac上安装交叉编译工具,请见我的这篇日志。这篇日志中是把交叉编译工具安装到一个.dmg文件中的,所以每次启动Eclipse之前一定要先把这个文件挂载。

然后安装Eclipse,到官网上下载最新版本的Eclipse Standard即可(在网页最上面)。mac版Eclipse无需安装,把解压后的文件夹直接拖到“应用程序”目录即可。

然后我们需要安装一下C/C++相关的内容。打开Eclipse软件,点击Help -> Install new software,点击右侧向下箭头,选择一个类似于“Kepler – http//download.eclipse.org/releases/kepler”的选项。下面的框内就会出现一个列表。在Programming Languages选项中勾选“C/C++ Development Tools”和“C/C++ Library API Documentation Hover Help (Incubation)”两项,然后一路下一步进行下载安装。最后会要求重启Eclipse软件,重启之后就会在欢迎界面上发现多出了一个 C/C++ Development 选项。

之后到这里下载一个RSE-runtime,有了它就能在Eclipse中进行ssh,ftp等操作了。下载下来以后是一个压缩包,包中有两个文件夹features和plugins,把这两个文件夹中的内容分别拷贝到Eclipse程序位置的features和plugins文件夹中,然后重启Eclipse生效。

最后配置一下Eclipse的软件窗口,在Window -> Show View -> Others中打开Remote Systems -> Remote Systems面板,这个面板里可以显示本地文件系统,也可以添加远程文件系统。此时面板中应该只有本地文件系统。接下来我们就在其中用SSH连接并添加Beaglebone的文件系统。

在面板中点右键,选择New Connection,选择Linux,点下一步。在Host name中填入Beaglebone的IP地址,比如我的是192.168.7.2,然后修改一下Connection name,点下一步。在左上角勾选ssh.files,点下一步。勾选Processes.shell.linux,点下一步。勾选ssh.shells,点下一步。点finish。此时在Remote System面板中就会显示出Beaglebone的连接,点开就能看到下面有Sftp files,Shell processes,ssh shells,ssh Terminals这4项内容。点开Sftp files,会看到下面有两个目录,My Home和Root。当我们第一次点击它们时,会提示输入用户名密码进行远程登录。输入之后就能顺利在Eclipse中访问BBB的文件了。在这里几乎可以进行任何文件操作,比如直接修改字符型文件,查看文件属性,修改文件权限,比较两个文件,在电脑和BBB之间互传文件等。你可以利用右键和在Window -> Show view菜单中进行一下探索。(有了它,估计Linux的好多命令没几天就要忘了。。。)

下面配置一下交叉编译的各种目录。首先要新建一个Project,然后才能点击菜单中的 Project -> Properties对该project进行设置。新建一个别的project的话还得重新设置(应该有办法能存成默认设置的)。需要设置的东西包括:
1、在C/C++ Build -> Settings的Tool Settings选项卡中修改4个根文件的Command为我们之前安装的交叉编译工具的位置。如把默认的 g++ 改成 /Volumes/arm-x/bin/arm-none-linux-gnueabi-g++ 等。
2、在C/C++ General -> Paths and Symbols的Includes选项卡中添加头文件目录,GNU C的目录是/Volumes/arm-x/arm-none-linux-gnueabi/include,
GNU C++的目录是/Volumes/arm-x/arm-none-linux-gnueabi/include/c++/4.4.1。如果project中还有自己的头文件,也要把相应的目录加进去。
3、在同样面板的Library Paths选项卡中添加库目录/Volumes/arm-x/lib

这下目录也配置完了。写好程序之后,默认必须手动保存之后才能进行编译,我们可以在Eclipse -> Preferences的General -> Workspace中勾选 Save automatically before build 项。

这样就完成了。

但是现在debug不能用,老师的视频中给出了一个解决方法,不过是在Ubuntu中解决的,mac中的解决方法暂时还没找到。

Advertisements

使用BBB的SPI

SPI是可以全双工通信的一种串行总线,两个设备之间双向通信的话一般使用3根线:SCLK,MISO,MOSI,多个设备之间双向通信的话,每个设备还需要再加上一根地址线CSn。相比之下I2C只能半双工,而且一般需要上拉电阻,但无论几个设备,都只需要2根线。更多基础知识请谷歌百度。

Beaglebone Black使用的AM3359芯片上有两个SPI,但SPI1连接到了板子的HDMI芯片上,所以除非禁用HDMI,否则我们只能使用SPI0。本文将利用自带的spidev驱动使能SPI0,并进行一下简单的验证。

1、配置device tree

首先我们用我在《使用BBB的I2C》这篇文章中使用的方法检验一下SPI相关的引脚功能是否配置正确。检查结果是,不正确,也就是说SPI默认是没有启用的,新版arm linux配置硬件的方式是利用device tree,所以我们必须要配置一个device tree来启用它。我们先到 /lib/firmware 目录中看看有没有现成的device tree source (.dts)文件可供使用。我们发现有一个BB-SPI0-00A0.dts。内容如下

/dts-v1/;  
/plugin/;  
  
/ {  
    compatible = "ti,beaglebone", "ti,beaglebone-black";  
  
    /* identification */  
    part-number = "BB-SPI0";  
    version = "00A0";  
  
    /* state the resources this cape uses */  
    exclusive-use =  
        /* the pin header uses */  
        "P9.17",    /* spi0_cs0 */  
        "P9.18",    /* spi0_d1 */  
        "P9.21",    /* spi0_d0 */  
        "P9.22",    /* spi0_sclk */  
        /* the hardware ip uses */  
        "spi0";  
  
    fragment@0 {  
        target = <&am33xx_pinmux>;  
        __overlay__ {  
            /* default state has all gpios released and mode set to uart1 */  
            bb_spi0_pins: pinmux_bb_spi0_pins {  
                pinctrl-single,pins = <  
                    0x150 0x30  /* spi0_sclk.spi0_sclk, INPUT_PULLUP | MODE0 */  
                    0x154 0x30  /* spi0_d0.spi0_d0, INPUT_PULLUP | MODE0 */  
                    0x158 0x10  /* spi0_d1.spi0_d1, OUTPUT_PULLUP | MODE0 */  
                    0x15c 0x10  /* spi0_cs0.spi0_cs0, OUTPUT_PULLUP | MODE0 */  
                >;  
            };  
        };  
    };  
  
    fragment@1 {  
        target = <&spi0>; /* spi0 is numbered correctly */  
        __overlay__ {  
            status = "okay";  
            pinctrl-names = "default";  
            pinctrl-0 = <&bb_spi0_pins>;  
  
            #address-cells = <1>;  
            #size-cells = <0>;  
  
            /* add any spi devices connected here */  
            /* note that you can do direct SPI via spidev now */  
  
            // commented out example of an adafruit 1.8" TFT display  
            // from firmare/capes/cape-bone-adafruit-lcd-00A0.dts  
            // lcd@0 {  
            //  #address-cells = <1>;  
            //  #size-cells = <0>;  
            //  
            //  compatible = "adafruit,tft-lcd-1.8-red", "sitronix,st7735";  
            //  reg = <0>;  
            //  
            //  spi-max-frequency = <8000000>;  
            //  spi-cpol;  
            //  spi-cpha;  
            //  
            //  pinctrl-names = "default";  
            //  pinctrl-0 = <&bone_adafruit_lcd_pins>;  
            //  
            //  st7735-rst = <&gpio4 19 0>;  
            //  st7735-dc = <&gpio4 21 0>;  
            // };  
        };  
    };  
};

从这个文件里我们能得到很多信息(我在此唠叨两句,也算跟大家分享一下我学习的过程),首先我们从exclusive-use这一部分能看出来AM3359芯片对SPI引脚的命名是跟一般不太一样的,它没用MISO和MOSI,而是D0和D1。通过查询4000页手册我们得知,原来是因为这两个引脚的功能是可以通过配置寄存器来互换的。默认的对应方式是d0对应miso,d1对应mosi。

再接着看,发现有一句注释

/* note that you can do direct SPI via spider now */


这个spidev就是我们要用的spi驱动,然后谷歌一下它的用法就可以了。再下面有一些被注释掉的东西,是要根据不同设备来替换的。

(以下操作都在Beaglebone上进行)

我们把自带的文件复制一份,保存为 BB-SPI0-01-00A0.dts ,然后增加一个节点,内容如下(就是原文件中注释部分要替换的内容)

spidev@0 {  
       spi-max-frequency = <24000000>;  
       reg = <0>;  
       compatible = "linux,spidev";  
};  


保存以后编译这个dts文件

# dtc -O dtb -o BB-SPI0-01-00A0.dtbo -b 0 -@ BB-SPI0-01-00A0.dts

然后把生成的.dtbo文件放到/lib/firmware目录中

# cp BB-SPI0-01-00A0.dtbo /lib/firmware/

然后把它“插”到“插槽”中(请看我的这篇博文

# echo BB-SPI0-01 > /sys/devices/bone_capemgr.*/slots

OK,这时我们进入/dev目录中就会发现比原来多了一个设备 spidev1.0 ,说明device tree配置没有问题,该设备已成功加载。

2、使用SPI

因为我手边没有SPI设备,所以我把D0和D1也就是P9.18和P9.21这两个引脚连接起来进行自发自收,如果收到了发送的数据即成功。时钟线就不必管了,因为自己跟自己的时钟肯定是同步的。

测试程序使用的是linux自带的一个spidev_test.c程序(下载地址在这里,不过还是建议直接把整个kernel下载下来比较方便搜索查询)。这个程序的内容就是发送一串16进制数,然后 printf 接收到的内容(不知道这串数有没有什么别的含义)。

下面把这个文件传到Beaglebone上,用gcc编译一下,生成可执行文件spidev_test。假设现在就在这个文件的目录下,那么我们输入

# ./spidev_test -D /dev/spidev1.0

来进行测试。得到输出

spi mode: 0  
bits per word: 8  
max speed: 500000 Hz (500 KHz)  
  
FF FF FF FF FF FF   
40 00 00 00 00 95   
FF FF FF FF FF FF   
FF FF FF FF FF FF   
FF FF FF FF FF FF   
DE AD BE EF BA AD   
F0 0D  


说明测试成功了。否则会输出一串FF。

3、为什么dts文件要那样改?

刚刚我在自带的BB-SPI0-00A0.dts文件中加了一个节点,然后向其中加了几个属性,SPI0就能用了。增加一个节点还能够理解,但为什么要加这几个属性?这个问题我想了几天也没想得很清楚。不过我知道的是,这3个属性缺一不可。

其中compatible属性是每个节点必须有的,它的作用是将这个设备和某个驱动进行绑定。比如这里就是将这个spi设备与 linux -> spidev 这个驱动绑定。我把逗号换成了箭头,是因为我觉得其实这个逗号表达的是从属关系,用箭头更合适。但是,我在kernel文件中翻遍了也没找到哪里有“linux,spidev”这样的字眼。spidev驱动倒是找到了,而且在这个驱动文件中发现了如下内容

static const struct of_device_id spidev_dt_ids[] = {  
    { .compatible = "rohm,dh2228fv" },  
    {},  
};  


我试着把BB-SPI0-01-00A0.dts里的 compatible 值换成 “rohm,dh2228fv”,结果居然也成功了!这似乎说明以后如果我们知道要用哪个驱动的话,到驱动文件里搜索compatible找到相应内容就可以了。不过,我遗憾地发现大部分驱动文件里都没有这个属性。可能只有一些硬件外设的驱动,或者是别的公司做的驱动里才会有。所以,我又迷惘了……

使用Beaglebone Black的I2C

本文我将使用BBB的I2C1读取气压传感器BMP085的值。

前言

首先说明,我使用的硬件外设是一个九轴加气压传感器模块,包括一个三轴陀螺仪芯片,一个三轴磁场传感器芯片,一个三轴加速度计芯片和一个气压传感器芯片。这4个芯片使用同一个I2C与BBB通信。

I2C是一种串行通讯方法,它只需要两根线就能实现通讯,一根时钟线SCL,一根数据线SDA。一般情况下这两根线都使用上拉电阻,同时把芯片的管脚设置成开漏输出(简单理解开漏输出的含义就是:让它输出低电平时,它能输出低电平;而让它输出高电平时,它就断路,什么也不输出,由外接电平决定这个引脚的电平)。如果芯片内部带有上拉电阻(比如BBB的芯片就自带上拉电阻),那不外接上拉也可以。

BBB系统自带了一个Linux下的I2C工具i2c-tools,非常好用,下文以i2c开头的命令都是这个工具包里的,如果你的系统里没有的话,可以搜索并下载i2c-tools工具包。

BBB上有两个可用的I2C,i2c-0和i2c-1,分别对应header上的I2C1和I2C2(总是这么混乱= =)。我们这里使用i2c-1,对应的header是P9_19和P9_20。我怎么知道它有两个可用的I2C呢?使用命令 i2cdetect -l 就可以看到

i2c-0	i2c       	OMAP I2C adapter                	I2C adapter
i2c-1	i2c       	OMAP I2C adapter                	I2C adapter


OK,下面开始操作。

===========================================================================
Step 1.检查引脚功能配置

首先确认一下i2c-1对应的IO口复用功能是否正确(BBB默认就是正确的,所以无需进行配置)。查表得,P9_19和P9_20分别对应95和94号引脚。

mode

输入命令

# cat /sys/kernel/debug/pinctrl/44e10800.pinmux/pins | grep 97c

上述命令把pins这个文件中包含97c的内容输出(97c是表中看到的引脚地址),得到95号引脚的功能和复用寄存器值

pin 95 (44e1097c) 00000073 pinctrl-single


95号引脚的功能寄存器值是0x00000073,化成2进制是1110011,其含义是:启用功能3(即I2C2_SCL),使能上下拉,开启上拉(所以我们可以不必外接上拉电阻了),使能输入,高速模式。同样可以检查94号引脚,也是0x00000073。

===========================================================================
Step 2.查找i2c设备的地址

(此时我们还没有插入设备)使用命令# i2cdetect -y -r 1 ,可以查看i2c设备地址。其中 -y 选项用来屏蔽讨厌的确认环节,-r 是因为AM3359不支持一种叫做Quick Write的东东,1 代表我们要查看i2c-1总线上的设备(也就是P9_19和P9_20上插着的设备)。输出如下

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- UU UU UU UU -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --


这里的地址都是16进制表示的。–代表该地址没有设备,UU代表这个地址正忙(也许被内部资源占用了,见图中的0x54到0x57这4个地址)。下面我把i2c设备插上以后再执行命令,输出变成了

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- 1e --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- 53 UU UU UU UU -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- 69 -- -- -- -- -- --
70: -- -- -- -- -- -- -- 77


会发现多出了4个地址:0x1e,0x53,0x69和0x77。因为我接入的模块上包含4个芯片,所以这个总线上显示了4个地址。通过读芯片手册得知气压计对应的是0x77这个地址。这一步就完成了。

需要补充说明的是,这里显示的是i2c设备的地址(1110111b=0x77),i2c的设备地址只有7位,最高位当做0。而读/写地址则在最低位增加一个1/0(11101111b / 11101110b),这使得读写地址与设备地址看起来很不相同。

===========================================================================
Step 3.查看和修改设备的寄存器值

输入命令# i2cdump -y 1 0x77,我们可以查看设备的寄存器值,其中 -y 还是屏蔽确认环节,1 还是代表查看i2c-1总线,0x77是要查询的设备地址。

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
80: a5 94 4d 19 b3 27 38 43 8a 2a 1e 05 fb af c7 6e    ??M??'8C?*?????n
90: 84 df 5f b0 56 5a 15 7a 00 3a 80 00 d4 bd 09 80    ??_?VZ?z.:?.????
a0: a5 94 4d 19 b3 27 38 43 8a 2a 1e 05 fb af c7 6e    ??M??'8C?*?????n
b0: 84 df 5f b0 56 5a 15 7a 00 3a 80 00 d4 bd 09 80    ??_?VZ?z.:?.????
c0: 00 00 bc 33 00 00 00 00 00 00 00 10 00 00 00 03    ..?3.......?...?
d0: 55 02 06 00 00 00 00 00 00 00 00 00 00 00 00 00    U??.............
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
f0: 00 00 00 00 00 00 80 00 00 00 00 00 00 00 00 00    ......?.........


这里用16进制显示了每个寄存器的值,具体哪个寄存器是干嘛的,就得查阅芯片的数据手册了。

如果想取出某个特定寄存器的值,比如0x80寄存器,可以使用命令 i2cget -y 1 0x77 0x80 实现。如果想向某个寄存器写入值,可使用命令 i2cset -y 1 0x69 0x20 0x0f 实现(向i2c-1总线上,设备地址为0x69,寄存器地址为0x20处写入值0x0f)。

===========================================================================
Step 4.使用BMP085驱动读取气压值

很巧的是,BBB自带了BMP085气压芯片的驱动,所以我们可以更方便地读取气压值。

# echo bmp085 0x77 > /sys/class/i2c-adapter/i2c-1/new_device

加载成功后使用如下命令就可以读取气压值了

# cat sys/bus/i2c/drivers/bmp085/1-0077/pressure0_input

使用完毕以后,我们可以用如下命令从驱动中卸载这个i2c设备

# echo 0x77 > /sys/class/i2c-adapter/i2c-1/delete_device

想查看加载或卸载是否成功的话,可以输入如下命令

# dmesg | grep bmp

你应该会看到类似下面的输出

[ 6428.602566] i2c i2c-1: new_device: Instantiated device bmp085 at 0x77
[ 6428.633419] bmp085 1-0077: Successfully initialized bmp085!
[ 6436.479407] i2c i2c-1: delete_device: Deleting device bmp085 at 0x77

===========================================================================
但是BBB没有带其他芯片的驱动,我暂时还不知道如何自己写驱动。更奇怪的是我查看其他设备地址的寄存器,得到很诡异的输出。

比如# i2cdump -y 1 0x69 c时:

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4    ????????????????
10: c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4    ????????????????
20: c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4    ????????????????
30: c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4    ????????????????
40: c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4    ????????????????
50: c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4    ????????????????
60: c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4    ????????????????
70: c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4    ????????????????
80: c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4    ????????????????
90: c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4    ????????????????
a0: c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4    ????????????????
b0: c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4    ????????????????
c0: c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4    ????????????????
d0: c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4    ????????????????
e0: c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4    ????????????????
f0: c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4    ????????????????

===========================================================================
更新解答上面的问题。

是输出模式的原因。# i2cdump -y 1 0x69 c命令中的c代表连续字节输出。把它去掉,用默认的字节输出模式就可以了。

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: c4 66 a5 cc 4c d0 89 c1 c2 e0 24 18 3b 87 00 d3    ?f??L?????$?;?.?
10: 44 b5 22 04 0c 80 40 60 1a 11 ee 90 83 01 85 83    D?"???@`????????
20: 07 00 00 00 00 00 00 00 00 f0 fb 06 ce fc 00 20    ?........?????. 
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
80: c4 66 a5 cc 4c d0 89 c1 c2 e0 24 18 3b 87 00 d3    ?f??L?????$?;?.?
90: 44 b5 22 04 0c 80 40 60 1a 11 ee 90 83 01 85 83    D?"???@`????????
a0: 07 00 00 00 00 00 00 00 00 f0 fb 06 ce fc 00 20    ?........?????. 
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................

再多说两句,这个设备是L3G4200D三轴陀螺仪,通过查芯片手册得知,0x20寄存器的第4位是芯片使能,现在0x20寄存器的值是0x07,即00000111b,我要把它变成00001111b,即0x0f,输入命令i2cset -y 1 0x69 0x20 0x0f

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: c4 66 a5 cc 4c d0 89 c1 c2 e0 24 18 3b 87 00 d3    ?f??L?????$?;?.?
10: 44 b5 22 04 0c 80 40 60 1a 11 ee 90 83 01 85 83    D?"???@`????????
20: 0f 00 00 00 00 00 1d ff 33 00 d5 ff fc ff 00 20    ?.....?.3.?.?.. 
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
80: c4 66 a5 cc 4c d0 89 c1 c2 e0 24 18 3b 87 00 d3    ?f??L?????$?;?.?
90: 44 b5 22 04 0c 80 40 60 1a 11 ee 90 83 01 85 83    D?"???@`????????
a0: 0f 00 00 00 00 00 1d ff 52 00 f7 ff 24 00 00 20    ?.....?.R.?.$.. 
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ............….


就成功使能陀螺仪了。根据芯片手册,0x28到0x2d这6个寄存器存放的是三轴角速度的值,所以我们比较上面两次i2cdump的结果会发现,这几个寄存器是有变化的,说明芯片正常工作了。

参考文章

http://elinux.org/Beagleboard:BMP_on_the_Beagle_Bone_Black
和《花点时间让你彻底搞懂I2C总线》这篇文章。

使用Beaglebone Black的串口UART

Beaglebone Black上有UART1-UART5共5个可用的uart串口,UART0连接着BBB新增的串口引脚,供调试使用,我最后再说它。我们进入/lib/firmware/目录中可以看到系统自带了一些与uart有关的device tree

/lib/firmware/BB-UART1-00A0.dts
/lib/firmware/BB-UART2-00A0.dts
/lib/firmware/BB-UART4-00A0.dts
/lib/firmware/BB-UART5-00A0.dts


(不知道为何没有UART3)
以UART1为例,打开BB-UART1-00A0.dts文件我们会看到这部分内容

	exclusive-use =
		/* the pin header uses */
		"P9.24",	/* uart1_txd */
		"P9.26",	/* uart1_rxd */
		/* the hardware ip uses */
		"uart1";


注释写得很清楚,P9.24和P9.26分别用作了txd和rxd引脚。因此我们就可以将这两个引脚连同地线这3根线连上串口转USB模块,稍后把模块插到usb接口上。

下面我们挂载device tree来启动UART1

# echo BB-UART1 > $SLOTS

进入/dev目录会发现比原来多了一个设备ttyO1(注意是大写字母O,不是数字0)。这时beaglebone已经准备好了,下面准备一下电脑端

首先要确认已经装了电脑端的PL2303驱动,mac版的可以在这里找到。win版的一搜便是,网上有很多。

然后需要一个串口助手。mac系统自带了一个在终端里运行的串口助手,名字叫screen,我们就先用它做实验(如果不习惯命令行,你也可以在网上找到各种有图形界面的串口助手)。

此时电脑端也准备好了。下面开始连接。

把usb转串口模块插到电脑上,然后在终端中进入电脑的/dev目录,会发现多了一个cu.usbserial文件。输入

# screen /dev/cu.usbserial 9600

不出错的话会成功打开串口,波特率为9600,终端界面也会清空,等待接收数据。

在beaglebone Black的终端里,我们输入

# echo "What a wonderful day" > /dev/ttyO1

如果在screen程序的终端上显示出同样的内容,串口测试就成功了。

如果你不小心把screen程序终端关闭了,那么再打开一个新的终端的话,会发现连接不上cu.usbserial了,是因为刚刚的串口进程还没关闭,串口还是被占用状态。在电脑端的终端中输入top命令查看进程,找到screen进程,记住PID进程号,然后按q键退出查看进程,然后输入kill 刚刚的PID号杀掉之前的screen进程就可以重新连接了。

如何调波特率?
在beaglebone Black的终端上,输入

# stty -F /dev/ttyO1 38400

就可以把ttyO1的波特率改成38400了。波特率设置不能超过终端的最大速度,可以输入stty speed查看终端的最大速度。输入stty --help可以看到stty命令更详细的参数。

最后说一下UART0,Beaglebone Black比上一代Beaglebone新增了串口调试引脚,我们直接把串口转usb模块的TXD,RXD和地线连到对应的引脚上(如图所示),无需进行任何配置,立刻就能开始使用了。
beaglebone-black-serial
使用方法类似,在电脑端输入(注意这个波特率超过了之前说的最大值)

# screen /dev/cu.usbserial 115200

就能打开串口调试助手。在BBB上输入

# echo "What a wonderful day" > /dev/ttyO0

测试发现没有问题。感谢这篇文章。顺便说一句,这篇文章里还讲了如何让BBB利用usb上网。

使用Beaglebone Black的ADC

Beaglebone Black的处理器芯片AM3359上的8个ADC脚同时也是触摸屏控制器(TSC),我们知道一般的电阻屏分为4线、5线或8线的,除去触摸屏以外的ADC接口可以作为普通的ADC使用。(我们在查询TI公司的4000页AM33xx手册时需要到Touchscreen Controller这一章去找有关ADC的内容。)

特别注意:Beaglebone Black的ADC管脚最大只能输入1.8V!!!

基本使用方法:

因为这个路径太常用,我们先把它存成环境变量

# export SLOTS=/sys/devices/bone_capemgr.8/slots

加载Beaglebone自带的device tree文件

# echo BB-ADC > $SLOTS

这时/sys/bus/iio/devices/目录下会出现一个iio:device0目录(原本是没有的)。里面有如下内容

-r--r--r-- 1 root root 4096 Jan  1 01:25 dev
-rw-r--r-- 1 root root 4096 Jan  1 01:25 in_voltage0_raw
-rw-r--r-- 1 root root 4096 Jan  1 01:25 in_voltage1_raw
-rw-r--r-- 1 root root 4096 Jan  1 01:25 in_voltage2_raw
-rw-r--r-- 1 root root 4096 Jan  1 01:25 in_voltage3_raw
-rw-r--r-- 1 root root 4096 Jan  1 01:25 in_voltage4_raw
-rw-r--r-- 1 root root 4096 Jan  1 01:25 in_voltage5_raw
-rw-r--r-- 1 root root 4096 Jan  1 01:25 in_voltage6_raw
-rw-r--r-- 1 root root 4096 Jan  1 01:25 in_voltage7_raw
-r--r--r-- 1 root root 4096 Jan  1 01:25 name
drwxr-xr-x 2 root root    0 Jan  1 01:25 power
lrwxrwxrwx 1 root root    0 Jan  1 01:25 subsystem -> ../../../../../bus/iio
-rw-r--r-- 1 root root 4096 Jan  1 01:19 uevent


里面的in_voltage*_raw文件便是8个ADC引脚的值。可以用cat命令查看其数值
# cat in_voltage1_raw

2965


beaglebone的ADC是12位的,所以这个数值是0到4095之间的某个数,对应着0到1.8V电压。另外beaglebone black只有0到6这7个ADC是可被外部使用的,第7个可能是连到板子上的某处了。这7个管脚的位置如图所示
adc-pins

如果想在c语言程序中使用ADC值,目前的办法是用read()函数读取文件中的数值。经过不严谨的测试,发现系统刷新in_voltage*_raw文件的速度还是挺快的。我如果每毫秒采一次样,输出数值是在一个小范围内波动的,而且相邻两次的数值很少有相同的。但如果我把采样速度增大10倍,每0.1毫秒采样一次,就会出现连续重复的数值。可见它的刷新速度是毫秒级的,这应当够一般使用了。

源代码

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h> //define O_WRONLY and O_RDONLY

#define SYSFS_ADC_DIR "/sys/bus/iio/devices/iio:device0/in_voltage3_raw"
#define MAX_BUF 64

void main()
{
	int fd, len;
	char buf[MAX_BUF];
	int ch[5];
	int i;

	for(i=0;i<50;i++)
	{
		snprintf(buf,sizeof(buf),SYSFS_ADC_DIR);
		fd = open(buf, O_RDONLY);
		read(fd,ch,4);
		printf("%s\n",ch);
		close(fd);
		usleep(1000);//pause for 1 ms
	}
}


这里只是读取了in_voltage*_raw文件中的字符串并输出,没有转变成整型数字,实际使用时肯定还得做转化。

在Mac OS上实现交叉编译

Mac OS对Linux支持很好,这是我喜欢mac的地方之一。文章转自http://blog.163.com/hy_ice719/blog/static/876628182013229280819/,对我很有帮助,为防止原文章丢失,特搬运过来并对原作者表示感谢!原博客是关于树莓派的,不过都通用的。

一、新建一个磁盘映像

MAC OS的文件系统默认是大小写不敏感的,而交叉编译工具链是基于大小写敏感的文件系统的,所以我们不能直接把工具链安装在原来的文件系统中,而是需要新建一个大小写敏感的磁盘印象用于安装工具链。

实现这一功能并不难,使用系统自带的磁盘工具就可以实现了。打开磁盘工具,然后 文件>新建>空白磁盘映像,然后在弹出窗口中设置名称为arm-x(便于后续操作),大小至少为500M,格式为Mac OS扩展(区分大小写,日志式)的映像。

二、下载工具链

从下面的链接下载已经编译完的arm GNU Linux 工具链:

https://github.com/downloads/UnhandledException/ARMx/ARMx-2009q3-67.tar.bz2

如果你想自己一步步编译,了解工具链中的具体元素的话,可以看一下下面这篇文档:

https://github.com/UnhandledException/ARMx/wiki/Sourcery-G—Lite-for-ARM-GNU-Linux-(2009q3-67)-for-Mac-OS-X

三、解压工具链压缩包到磁盘映像

在终端进入压缩包所在的目录,执行下面的指令即可(新建的磁盘映像默认是挂载的,如果没有挂载的话请先手动挂载):

# tar -zx -C /Volumes/arm-x/ --strip-components 1 -f ARMx-2009q3-67.tar.bz2

四、使用工具链

现在已经能够直接使用工具链了,比如我写了一个hello.c程序,现在可以直接使用工具链对程序进行编译。在终端执行:

/Volumes/arm-x/bin/arm-none-linux-gnueabi-gcc hello.c -o hello

就可以获得一个名为hello的可执行程序。这个程序在mac下是不能运行的,需要在arm处理器的设备上才能运行。

当然,如果你嫌输入一大串路径去调用arm-none-linux-gnueabi-gcc程序麻烦的话,可以把/Volumes/arm-x/bin目录添加到PATH里去,这样就能够像使用系统命令一样使用了。(添加环境变量的方法很多,建议把路径添加到~/.profile文件中去,具体操作百度一下你就知道)

(补充,这个工具链里同样包含了gdb等多个调试工具可以使用,可自行到/Volumes/arm-x/bin目录查看。)

五、把hello程序上传到beaglebone上

(我在之前日志里提到的scp命令是一种方法,原作者又提供了两种方法)

1、使用sftp:这种方法无需另外设置,可以直接把文件上传到树莓派,但每一次更改都需要重新上传。

步骤:

打开一个新的终端,输入# sftp root@192.168.7.2(ip地址根据你的地址进行更改)

然后输入beaglebone的密码(如果你设置过的话。默认无密码)

# put /本地文件路径 /上传到beaglebone上的目录

(补充,用get命令也可以从beaglebone中传文件到电脑上。还有一些其他的有用的命令,连接好sftp以后请输入help命令查看。)

2、使用NFS:这种方法需要先进行配置,但配置好后使用十分方便。可以将beaglebone上的文件直接映射到mac上,这样在mac上修改即可,无需再上传。具体步骤参考这篇文章

分析一个.dts文件

本文的正确性严重有待检验!!!

我先阅读了/lib/firmware/目录中的所有.dts文件,看一下它写了哪些东西,有哪些共性。经过阅读之后我有一些发现,记录一下。(因为这部分跟驱动关联很大,而我还不熟悉驱动相关的东西,所以只能是望文生义,不敢保证理解正确。)

以BB-UART1-00A0.dts文件为例说明

/dts-v1/;
/plugin/;

/ {
	compatible = "ti,beaglebone", "ti,beaglebone-black";

	/* identification */
	part-number = "BB-UART1";
	version = "00A0";

	/* state the resources this cape uses */
	exclusive-use =
		/* the pin header uses */
		"P9.24",	/* uart1_txd */
		"P9.26",	/* uart1_rxd */
		/* the hardware ip uses */
		"uart1";

	fragment@0 {
		target = <&am33xx_pinmux>;
		__overlay__ {
			bb_uart1_pins: pinmux_bb_uart1_pins {
				pinctrl-single,pins = <
					0x184 0x20 /* P9.24 uart1_txd.uart1_txd  OUTPUT  */
					0x180 0x20 /* P9.26 uart1_rxd.uart1_rxd  INPUT  */
				>;
			};
		};
	};

	fragment@1 {
		target = <&uart2>;	/* really uart1 */
		__overlay__ {
			status = "okay";
			pinctrl-names = "default";
			pinctrl-0 = <&bb_uart1_pins>;
		};
	};
};


所有的.dts文件中每个fragment下面紧接着就是 target 声明,然后是 __overlay__ ,这应该是device tree overlay的标准写法,从直观上看就是要重定义这个target的某些属性或添加某些属性。那我们能修改的target有哪些呢?经过查找,发现它们藏在了这里
# ls /proc/device-tree/__symbols__/

aes                         dcdc2_reg  gpmc       mmc2         timer7
am33xx_pinmux               dcdc3_reg  i2c0       mmc3         tps
baseboard_beaglebone        ecap0      i2c0_pins  name	       tscadc
baseboard_beaglebone_black  ecap1      i2c1       cop          uart1
baseboard_eeprom            ecap2      i2c2       pruss        uart2
cape_eeprom0                edma       i2c2_pins  rstctl       uart3
cape_eeprom1                ehrpwm0    intc       rstctl_pins  uart4
cape_eeprom2                ehrpwm1    lcdc       sham	       uart5
cape_eeprom3                ehrpwm2    ldo1_reg   spi0	       uart6
cpsw_emac0                  epwmss0    ldo2_reg   spi1	       usb_otg_hs
cpsw_emac1                  epwmss1    ldo3_reg   timer1       userled_pins
cpu                         epwmss2    ldo4_reg   timer2       vmmcsd_fixed
davinci_mdio                gpio1      mac        timer3       wdt2
dcan0                       gpio2      mcasp0	  timer4
dcan1                       gpio3      mcasp1	  timer5
dcdc1_reg                   gpio4      mmc1       timer6


这些都是文件,如果你用cat命令查看其中的内容,里面写的是相应target的位置。比如
# cat uart2

/ocp/serial@48022000


这个位置不是linux文件系统中的位置,而是device tree中的位置,第一个斜杠/是device tree的根节点。实际上device tree的根节点对应的正是/proc/device-tree/这个目录。所以/ocp/serial@48022000实际上对应的是/proc/device-tree/ocp/serial@48022000这个位置,我们看看这个目录里面有什么
# ls -l /proc/device-tree/ocp/serial@48022000

-r--r--r-- 1 root root  4 Jan  1 00:52 clock-frequency
-r--r--r-- 1 root root 14 Jan  1 00:52 compatible
-r--r--r-- 1 root root  4 Jan  1 00:52 interrupts
-r--r--r-- 1 root root  4 Jan  1 00:52 linux,phandle
-r--r--r-- 1 root root  7 Jan  1 00:52 name
-r--r--r-- 1 root root  4 Jan  1 00:52 phandle
-r--r--r-- 1 root root  8 Jan  1 00:52 reg
-r--r--r-- 1 root root  9 Jan  1 00:52 status
-r--r--r-- 1 root root  6 Jan  1 00:52 ti,hwmods


这些文件应该就是这个节点默认的属性了。在BB-UART1-00A0.dts里,它只修改了其中一个属性,status = “okay”; 代表使能。

	fragment@1 {
		target = <&uart2>;	/* really uart1 */
		__overlay__ {
			status = "okay";
			pinctrl-names = "default";
			pinctrl-0 = <&bb_uart1_pins>;
		};
	};


那下面两行pinctrl-names和pinctrl-0是什么呢?是添加了两个属性,定义了uart2的引脚复用。而具体复用了哪些引脚的哪些功能,是由第一个fragment定义的(可以看到pinctrl-0 = ;这句引用了上一个fragment中定义的节点)。所以接下来我们看下第一个fragment

	fragment@0 {
		target = <&am33xx_pinmux>;
		__overlay__ {
			bb_uart1_pins: pinmux_bb_uart1_pins {
				pinctrl-single,pins = <
					0x184 0x20 /* P9.24 uart1_txd.uart1_txd  OUTPUT  */
					0x180 0x20 /* P9.26 uart1_rxd.uart1_rxd  INPUT  */
				>;
			};
		};
	};


它的target是am33xx_pinmux,跟上面一样,我们先到/proc/device-tree/__symbols__/里找到am33xx_pinmux,然后cat一下内容,确定其位置是在/proc/device-tree/pinmux@44e10800,然后看一下这个目录里有什么(下面的列表里去掉了目录,只保留了文件)
# ls -l /proc/device-tree/pinmux@44e10800

-r--r--r-- 1 root root  4 Jan  1 02:24 #address-cells
-r--r--r-- 1 root root  4 Jan  1 02:24 #size-cells
-r--r--r-- 1 root root 15 Jan  1 02:24 compatible
-r--r--r-- 1 root root  4 Jan  1 02:24 linux,phandle
-r--r--r-- 1 root root  7 Jan  1 02:24 name
-r--r--r-- 1 root root  4 Jan  1 02:24 phandle
-r--r--r-- 1 root root  4 Jan  1 02:24 pinctrl-0
-r--r--r-- 1 root root  8 Jan  1 02:24 pinctrl-names
-r--r--r-- 1 root root  4 Jan  1 02:24 pinctrl-single,function-mask
-r--r--r-- 1 root root  4 Jan  1 02:24 pinctrl-single,register-width
-r--r--r-- 1 root root  8 Jan  1 02:24 reg


看起来fragment里出现的pinctrl-single,pins跟pinctrl-single,function-mask很像。经查询pinctrl-single驱动的官方说明,有很多pinctrl-single,whatever这样的属性。其中pinctrl-single,pins用来定义引脚的复用功能。下面两个16进制数,左边的代表引脚地址的偏移量,右边的代表功能。下面解释一下这两个数字是怎么来的。

首先要分清楚引脚名($PINS)和板子引脚编号(Head_pin)的区别,前者是系统对各个引脚的编号,后者是板子上引脚的位置编号。系统中可以轻松查看每个引脚的信息
root@beaglebone:~# cat /sys/kernel/debug/pinctrl/44e10800.pinmux/pins

registered pins: 142
pin 0 (44e10800) 00000031 pinctrl-single 
pin 1 (44e10804) 00000031 pinctrl-single 
pin 2 (44e10808) 00000031 pinctrl-single 
pin 3 (44e1080c) 00000031 pinctrl-single 
pin 4 (44e10810) 00000031 pinctrl-single  
. . .
pin 137 (44e10a24) 00000028 pinctrl-single 
pin 138 (44e10a28) 00000028 pinctrl-single 
pin 139 (44e10a2c) 00000028 pinctrl-single 
pin 140 (44e10a30) 00000028 pinctrl-single 
pin 141 (44e10a34) 00000020 pinctrl-single 


这里的编号就是$PINS,编号后面括号里的内容就是这个引脚功能的地址,再后面跟着的就是引脚功能的16进制表示(实际上是AM335x芯片内部的CONTROL_MODULE寄存器地址,详见TI公司的那本4000多页的手册,我在之前的日志里提到过)。实际上在.dts文件的pinctrl-single驱动中,并没有使用完整的地址,而是用了偏移地址,基准是pin0的地址,即44e10800。那每个芯片引脚对应到哪个板子的引脚呢?Derekmolloy老师为我们总结了一份PDF文档,里面写得很清楚,大家可以到这里去看一看。

说完地址,再说功能。
第2,1,0位表示引脚的复用功能0到7,从Derekmolloy总结的PDF中能轻松查到。
第3位是引脚上拉/下拉使能,0为使能
第4位是设置上拉/下拉,0为下拉,1为上拉
第5位是输入使能,1为既能输入也能输出,0为只能输出
第6位是slew的速度,0是高速,1是低速(这个不知道是什么东西)
再高位就没用了。

我们用上面的例子解释一下,
0x184 0x20 /* P9.24 uart1_txd.uart1_txd OUTPUT */
其中0x184是偏移地址,加上0x44e10800是0x44e10984,查到是第97个芯片引脚
# pin 97 (44e10984) 00000037 pinctrl-single
查PDF知对应到板子上是P9.24这个引脚。
功能寄存器的值是0x37,化成2进制低7位是0110111,对应着功能7(查PDF知是uart1_txd.uart1_txd),使能了内部上拉,使能了输入。
解释完毕。

那为什么要创建bb_uart1_pins子节点呢?我猜纯粹是为了下面能引用。这个写法bb_uart1_pins: pinmux_bb_uart1_pins中冒号是什么含义?我找遍google也没找到,难道也是为了引用方便起的别名??我能随便起名吗?

还有我为什么能在uart2这个target中随意添加pinctrl-names和pinctrl-0这两个属性?那我还能不能添加别的属性?这个问题也暂时没找到答案。不过据我观察所有涉及引脚的功能节点都是这样写的,暂时就当成一种规则吧。更大的问题是,如果我添加了一些别的属性,系统怎么能知道我的这个属性是在哪里定义的?要用哪个驱动?似乎系统的驱动都放在了/sys/bus/platform/drivers目录下,那这些驱动都定义了哪些变量或属性呢?

还有波特率,奇偶校验啥的能在这里定义吗??

总之,BB-UART1-00A0.dts这个文件中写了两个节点的overlay,第1个节点修改了两个引脚的功能,第2个节点使能了uart1然后引用了第1个节点的引脚修改。

附:因为这两个地址太常用了,我们也可以把它们存成系统环境变量
export SLOTS=/sys/devices/bone_capemgr.8/slots
export PINS=/sys/kernel/debug/pinctrl/44e10800.pinmux/pins
以后用$SLOTS和$PINS就可以代表这两个地址了,但注意仅对本次启动有效。

Q&A2 – 进一步理解和使用device tree

提几个关于device tree overlay的具体使用问题,作为接下来的着力点。

  1. 各种硬件资源的名字和属性都有哪些?芯片引脚地址和功能定义到哪找?(换句话说,.dts文件该怎么修改?)
  2. 加载overlay之后该怎么使用某个外设?(比如uart的模式、波特率怎么设定?输入输出文件怎么设定?)
  3. DT overlay到底能管哪些方面的东西?在硬件接口方面它是万能的吗?
  4. 如何设置系统启动就自动加载某个DT overlay?

================================================================

回答问题前先扯几句。我们知道beagleboard官网上有一些官方的硬件外设,比如lcd显示屏之类的,他们管这些外设叫做cape。其实这里是我理解狭隘了,应该说只要是修改了芯片引脚功能,或占用了空闲的引脚的东西,都可以叫做cape。比如之前我们提到的开启某些引脚的AD转换功能,其实也是给设备添加了一个virtual cape。Beaglebone Black中用一个叫做capemgr的软件管理所有的cape,不论它是实实在在的扩展板,还是虚拟的cape。这个软件的目录是

/sys/devices/bone_capemgr.*/(这里的*是一个每次系统启动可能会不一样的数字(与启动顺序有关))

如果你看过我的前一篇博客,也许还会记得我们加载device tree overlay时打开了一个文件,正是这个目录下的slots文件。slots文件就是capemgr这个软件的对外接口。slot这个单词是“插槽”的意思,看,很形象吧!我要插上一个cape,就向这个“插槽”里echo相应的设备。echo这个命令的含义是“向标准设备输出”嘛。另外,.dtbo文件只有放到/lib/firmware/目录下才能被使用。

还记得我们第一次打开slots文件看到了什么吗?
# cat /sys/devices/bone_capemgr.*/slots

0: 54:PF--- 
1: 55:PF--- 
2: 56:PF--- 
3: 57:PF--- 
4: ff:P-O-L Bone-LT-eMMC-2G,00A0,Texas Instrument,BB-BONE-EMMC-2G
5: ff:P-O-L Bone-Black-HDMI,00A0,Texas Instrument,BB-BONELT-HDMI


这里前4项为什么是空的呢?它们是给那些有EEPROM的实体cape预留的位置。这里有一段解释,我还理解得不太好,就先把原文附上吧

Slots 0-3 get assigned by EEPROM IDs on the capes. There are 4 possible addresses for the EEPROMs (typically determined by switches on the boards) enabling up to 4 boards to be stacked, depending on what functions they use. Additional slots are “virtual”, added incrementally and are triggered in other ways.

4和5这两项则是系统已经加载的两个virtual cape,因为eMMC和HDMI确实需要占用一些引脚,所以根据前面的定义,它们也是cape。

其实编写device tree overlay某种程度上来说就是在开发驱动(前提是驱动或驱动模块已经存在于内核中),只不过相比从前那种重新编译内核的方式来说,这种方式实在太方便了。

================================================================
因为我发现前面提的问题都挺大的,所以决定另开日志进行解答,这篇日志就到此吧。

用Device tree overlay掌控Beaglebone Black的硬件资源

经过一晚上的Google,终于大致明白device tree是怎么用的了,这里简单梳理一下思路。

一、简介
===============================================================
device tree是ARM linux 3.7开始使用的系统控制硬件资源的方式,这里说的硬件资源既包括片上的诸如GPIO、PWM、I2C、ADC等资源,也包括外部拓展的如FLASH、LCD等。ARM使用device tree目前还是很新的东西(2012年低诞生的),自带3.8版本Linux系统的Beaglebone Black是第一批使用device tree的ARM设备之一。难怪目前关于它的中文资料还很少。之前的linux如果要配置硬件,需要重新编译内核,但用device tree就不必重新编译内核,甚至不必重启系统就能实现。如此方便的工具让我们来看一看庐山面目!

从单片机、STM32过渡到Cortex-A8,之前直接操作寄存器来控制硬件的思路已经不好使了,但是device tree提供了一种很类似直接操作寄存器,但是比它更有条理,更容易理解的方式。首先需要编写一个.dts文件(device tree source),在文件中说明我要设置的硬件和它的各种属性,然后使用dtc命令编译这个.dts文件生成对应的二进制文件.dtb(device tree blob),系统启动时就会加载这个device tree并配置各种硬件资源。实际上Beaglebone Black自带系统中/boot/目录下已经包含了一些编译好的.dtb文件,从文件名来看似乎每个.dtb文件都能配置一款beagleboard.org的开发板,其中有一个叫做am335x-boneblack.dtb的文件,没猜错的话应当负责了Beaglebone black的缺省硬件配置。但因为已经编译成了二进制文件,所以我们无法读取其内容。

那么我们如果想要自己修改某些功能改怎么办呢?我们肯定不能重新编译一个am335x-boneblack.dtb代替原来的文件,那样会疯掉的。不过我们可以使用device tree overlay来动态重定义某些功能。device tree overlay与device tree类似,同样是编写一个.dts文件,编译成.dtbo文件(末尾的o应该代表overlay)。不同的是我们不把它放到/boot/目录中去,它也不必在启动时加载,而可以在需要时随时进行动态加载。另外device tree overlay的.dts文件跟device tree的.dts文件格式还是有一点区别的,下面要介绍的是device tree overlay的.dts。接下来我们上机操作一下。

二、编写.dts文件
===============================================================
用ssh连接好Beaglebone black以后,我们先来找找Angstrom系统自带的.dts文件,看看它们长什么样子。用下面的命令搜索一下dts结尾的文件

# find / -name *dts

会得到下面这个列表(部分省略)

/lib/firmware/cape-bone-dvi-00A0.dts
/lib/firmware/bone_pwm_P8_45-00A0.dts
/lib/firmware/BB-SPI1A1-00A0.dts
/lib/firmware/BB-ADC-00A0.dts
/lib/firmware/BB-I2C1A1-00A0.dts
/lib/firmware/BB-BONE-SERL-01-00A1.dts
/lib/firmware/cape-bone-dvi-00A2.dts
/lib/firmware/bone_pwm_P8_13-00A0.dts
/lib/firmware/cape-bone-hexy-00A0.dts
/lib/firmware/BB-BONE-LCD7-01-00A2.dts
...


我们发现它们都在同一个目录内,/lib/firmware/,事实上系统自带的dts文件确实全部都在这个目录中,从文件名上我们会发现这里几乎包含了所有Beaglebone硬件资源的overlay,也包含了一些官方硬件外设(如lcd屏等,它们管自己的外设叫做cape)的overlay,因此以后有需要就可以直接到这里找了。下面随便打开其中一个看看(BB-UART1-00A0.dts)

/*
 * Copyright (C) 2013 CircuitCo
 *
 * Virtual cape for UART1 on connector pins P9.24 P9.26
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
/dts-v1/;
/plugin/;

/ {
   	compatible = "ti,beaglebone", "ti,beaglebone-black";

        /* identification */
        part-number = "BB-UART1";
        version = "00A0";

        /* state the resources this cape uses */
        exclusive-use =
                /* the pin header uses */
                "P9.24",        /* uart1_txd */
                "P9.26",        /* uart1_rxd */
                /* the hardware ip uses */
                "uart1";

        fragment@0 {
                target = <&am33xx_pinmux>;
                __overlay__ {
                        bb_uart1_pins: pinmux_bb_uart1_pins {
                                pinctrl-single,pins = <
                                        0x184 0x20 /* P9.24 uart1_txd.uart1_txd MODE0 OUTPUT (TX) */
                                        0x180 0x20 /* P9.26 uart1_rxd.uart1_rxd MODE0 INPUT (RX) */
                                >;
                        };
                };
        };

        fragment@1 {
                target = <&uart2>;	/* really uart1 */
                __overlay__ {
                        status = "okay";
                        pinctrl-names = "default";
                        pinctrl-0 = <&bb_uart1_pins>;
                };
        };
};


它的语法跟c语言有点类似。我先从中抽掉不重要的内容,把它写成下面的伪代码

/ {
        fragment@0 {
                target = <&am33xx_pinmux>;
                __overlay__ {
                        bb_uart1_pins: pinmux_bb_uart1_pins {
                                pinctrl-single,pins = <
                                        0x184 0x20 /* P9.24 uart1_txd.uart1_txd MODE0 OUTPUT (TX) */
                                        0x180 0x20 /* P9.26 uart1_rxd.uart1_rxd MODE0 INPUT (RX) */
                                >;
                        };
                };
        };

        fragment@1 {
                target = <&uart2>;
                __overlay__ {
                        status = "okay";
                        pinctrl-names = "default";
                        pinctrl-0 = <&bb_uart1_pins>;
                };
        };
};


从这里就能看出.dts文件的结构了——是一个树形结构。第一行的/代表根,下面的fragment@0fragment@1是其两个分支节点。每个fragment节点下面又各有一个__overlay__节点(这些节点的名字都是固定的)。每个fragment节点下面相邻的target说明这个节点要修改的对象,在__overlay__节点下面的内容阐明了要修改的属性。

具体来说,am33xx_pinmux可以定义芯片功能复用引脚的具体功能,它使用了pinctrl-single,pins这个驱动,其中第一项0x184代表要修改的引脚,第二项0x20代表要修改成哪个功能(pinctrl-single的具体用法见这里)。这里把P9.24和P9.26两个引脚定义成了uart1的TX和RX。uart2这个target则使能了uart1(这个uart2实际上对应的是硬件的uart1)。

如果把树形结构什么的都忽略掉,就会发现其实它实现了我之前用寄存器干的事:定义引脚功能,然后使能串口。

那么当我想自己写device tree的时候,一上来就会遇到一个问题:我怎么知道我想控制的对象target名字是什么?我怎么知道它有哪些属性?取值范围是什么?答案在linux官网上。(不过有一些在这里似乎找不到,回头解决)

了解了dts文件的基本框架,我们再把之前丢掉的细节拿回来说明一下。(这些细节有些是非常重要的,实际使用中一定不要随意丢掉!)
首先这两行说明了dts的版本号,声明了这个文件的内容是一个plugin

/dts-v1/;
/plugin/;


根节点下面的一行说明了它的适用平台,这个是必须要写的。

compatible = "ti,beaglebone", "ti,beaglebone-black";


接下来的部分说明了这个device tree overlay的名字和版本号(版本号似乎只能是00A0)

/* identification */
part-number = "BB-UART1";
version = "00A0";


再下面的部分说明了要使用的引脚和硬件设备

/* state the resources this cape uses */
        exclusive-use =
                /* the pin header uses */
                "P9.24",        /* uart1_txd */
                "P9.26",        /* uart1_rxd */
                /* the hardware ip uses */
                "uart1";


接下来就是device tree overlay的具体内容,前面已经简单解释过了,但似乎还是看不太明白,也写不出来。实际上我们并不需要自己从头开始写,因为在系统/lib/firmware/目录中已经自带了很多.dts文件,我们只需要在它们的基础上进行修改就行了。需要提示一点,在.dts文件里我们经常会看到target = &ocp,这里的ocp是on chip peripherals的缩写,我猜想可能是用来描述连接到芯片的其他外设的(如按键、lcd等)。后面的日志里我再记录一下详细的操作细节。这篇先简单介绍这么多。

三、编译.dts文件
===============================================================
写好.dts文件以后需要用dtc编译器编译一下,生成.dtbo文件才能使用。
假设我们写好了一个名为ADAFRUIT-SPI0-00A0.dts的文件,编译指令如下

# dtc -O dtb -o ADAFRUIT-SPI0-00A0.dtbo -b 0 -@ ADAFRUIT-SPI0-00A0.dts

然后就会生成ADAFRUIT-SPI0-00A0.dtbo文件。下面解释一下各个参数
-O dtb 声明输出格式为dtb文件
-o 输出文件名
-b 设置启动CPU
-@ (我不太清楚这项是干嘛的,似乎是overlay专有的一项)
注意文件的命名,一定是“程序名-版本号.dtbo(.dts)”的形式。
编译完成以后,一定要把.dtbo文件放到/lib/firmware/目录下才能使用

# cp ADAFRUIT-SPI0-00A0.dtbo /lib/firmware

四、overlay的使用 (Exporting and Unexporting an Overlay,加载和卸载)
===============================================================
所有已经加载的overlay列表都在/sys/devices/bone_capemgr.*/slots这个文件中。(bone_capemgr.*中的*号实际是一个数字,但是每次系统启动时这个数字可能会变化,所以我们用通配符*代替。)我们打开这个文件看一看
# cat /sys/devices/bone_capemgr.*/slots

0: 54:PF--- 
1: 55:PF--- 
2: 56:PF--- 
3: 57:PF--- 
4: ff:P-O-L Bone-LT-eMMC-2G,00A0,Texas Instrument,BB-BONE-EMMC-2G
5: ff:P-O-L Bone-Black-HDMI,00A0,Texas Instrument,BB-BONELT-HDMI


我们看到系统已经自动加载了两个overlay,eMMC和HDMI。下面我们把之前讲解的BB-UART1-00A0.dtbo加载一下,方法是

# echo BB-UART1 > /sys/devices/bone_capemgr.*/slots

然后我们再打开slots文件看看有什么变化

0: 54:PF--- 
1: 55:PF--- 
2: 56:PF--- 
3: 57:PF--- 
4: ff:P-O-L Bone-LT-eMMC-2G,00A0,Texas Instrument,BB-BONE-EMMC-2G
5: ff:P-O-L Bone-Black-HDMI,00A0,Texas Instrument,BB-BONELT-HDMI
6: ff:P-O-L Override Board Name,00A0,Override Manuf,BB-UART1


会发现多了一项,说明加载成功了,下面就可以使用外设了。

外设使用完毕以后,如何卸载呢?一种方法是重启系统,另一种是

# echo -6 > /sys/devices/bone_capemgr.*/slots

但是在最近的Angstrom系统中,用这种方法会导致kernel panic,然后ssh会断开,所以现在还是用重启系统的方法吧。相信今后这个问题应该很快会解决的。

注:adafruit的这篇Introduction to the BeagleBone Black Device Tree令我受益匪浅,在此表示一下感谢。

Q&A1

连接好BBB以后,先需要熟悉一下可能会用到的硬件外设,解决一些实用的调试问题。为了不一下子面对太多问题,暂时先使用片上自带的的gcc编译器而非交叉编译,先使用纯命令行和vim写程序而非IDE。虽然慢点,但很简单。

Qs:

  1. 怎样在片上编写、编译、执行、修改c语言程序?怎样debug?
  2. terminal命令和用c语言编写程序有什么相同和不同?能互相转化吗?
  3. 怎样操作IO口(both in and out)?
  4. 怎样使用AD?
  5. 怎样保存变量数据?
  6. 怎样向电脑传输数据?是否有必要用串口?怎么用?

等我解决了这几个问题再来更新博客。

================================================================

A1:Linux编程的基本操作。只列出命令名,具体功能请自行Google。
首先是Linux基本功如

  1. 随意跑到任意目录下(cd),查看目录下有什么(ls -l),显示目录(pwd)
  2. 新建(mkdir、vi、vim)、移动(cp、mv)、删除(rm -rf)文件和目录
  3. 根据(部分)文件名查找文件(find -name),根据文件内容查找文件(grep),在某个文件中查找某些内容并定位(grep -n)
  4. 查看文件内容(cat、more、less),编辑文件(vi、vim、echo)(echo的功能其实是向标准设备输出)
  5. 结束正在执行的任务(control+C)
  6. 自动补全文件名(tab键)
  7. 注意文件或目录名中出现任何符号时要在前面加上\转义字符

上面提到的vi或vim编辑器的使用

  1. i进入编辑模式,esc退出编辑模式,进入命令模式
  2. 命令模式下输入 :q 退出,输入 :wq 保存并退出,输入 :q! 强制退出

然后是关于编译执行调试c程序

  1. gcc test.c -o test 编译test.c并生成可执行文件test
  2. ./test 执行test文件(别忘了加./)
  3. 先执行gcc -g test.c -o testdebug 生成包含标准调试信息的文件testdebug,然后可以用gdb testdebug命令来调试程序

A2:(见A3的例子就明白了)

A3:怎样操作io口?(学习自这里 )
首先是直接用shell控制io口。

root@beaglebone:~# cd /sys/class/gpio
root@beaglebone:/sys/class/gpio# ls -l
total 0
--w------- 1 root root 4096 Jan  1 00:00 export
lrwxrwxrwx 1 root root    0 Jan  1 00:00 gpiochip0 -> ../../devices/virtual/gpio/gpiochip0
lrwxrwxrwx 1 root root    0 Jan  1 00:00 gpiochip32 -> ../../devices/virtual/gpio/gpiochip32
lrwxrwxrwx 1 root root    0 Jan  1 00:00 gpiochip64 -> ../../devices/virtual/gpio/gpiochip64
lrwxrwxrwx 1 root root    0 Jan  1 00:00 gpiochip96 -> ../../devices/virtual/gpio/gpiochip96
--w------- 1 root root 4096 Jan  1 00:00 unexport
root@beaglebone:/sys/class/gpio# echo 44 > export
root@beaglebone:/sys/class/gpio# ls -l
total 0
--w------- 1 root root 4096 Jan  1 00:03 export
lrwxrwxrwx 1 root root    0 Jan  1 00:03 gpio44 -> ../../devices/virtual/gpio/gpio44
lrwxrwxrwx 1 root root    0 Jan  1 00:00 gpiochip0 -> ../../devices/virtual/gpio/gpiochip0
lrwxrwxrwx 1 root root    0 Jan  1 00:00 gpiochip32 -> ../../devices/virtual/gpio/gpiochip32
lrwxrwxrwx 1 root root    0 Jan  1 00:00 gpiochip64 -> ../../devices/virtual/gpio/gpiochip64
lrwxrwxrwx 1 root root    0 Jan  1 00:00 gpiochip96 -> ../../devices/virtual/gpio/gpiochip96
--w------- 1 root root 4096 Jan  1 00:00 unexport
root@beaglebone:/sys/class/gpio# cd gpio44
root@beaglebone:/sys/class/gpio/gpio44# ls -l
total 0
-rw-r--r-- 1 root root 4096 Jan  1 00:03 active_low
-rw-r--r-- 1 root root 4096 Jan  1 00:03 direction
-rw-r--r-- 1 root root 4096 Jan  1 00:03 edge
drwxr-xr-x 2 root root    0 Jan  1 00:03 power
lrwxrwxrwx 1 root root    0 Jan  1 00:03 subsystem -> ../../../../class/gpio
-rw-r--r-- 1 root root 4096 Jan  1 00:03 uevent
-rw-r--r-- 1 root root 4096 Jan  1 00:03 value
root@beaglebone:/sys/class/gpio/gpio44# cat direction
in
root@beaglebone:/sys/class/gpio/gpio44# echo out > direction
root@beaglebone:/sys/class/gpio/gpio44# cat direction
out
root@beaglebone:/sys/class/gpio/gpio44# cat value
0
root@beaglebone:/sys/class/gpio/gpio44# echo 1 > value
root@beaglebone:/sys/class/gpio/gpio44# cat value
1


解释一下,首先要把某个(本例中是第44个)gpio export一下,变成用户可用的状态,然后目录里就会多出来一个gpio44文件夹,进入它对相应文件进行读写就可以操作io口了。输入输出是一样的道理。读的话Linux会自动实时更新value文件里的数据,但更新速度有多快暂时还不清楚,高速io操作的话用这种方法感觉不靠谱。不过速度不敏感的话是没问题的。

用c程序控制io口,我们当然可以完全照搬上面对文件操作的过程,只不过写成c语言的形式。具体如下

#include
#include
#include
#include  //define O_WRONLY and O_RDONLY

#define SYSFS_GPIO_DIR "/sys/class/gpio"
#define MAX_BUF 64

void main()
{
	int fd, len;
	char buf[MAX_BUF];
	char ch;
	int i;

	//export gpio44
	fd = open(SYSFS_GPIO_DIR "/export", O_WRONLY);
	len = snprintf(buf,sizeof(buf),"44");
	write(fd,buf,len);
	close(fd);

	//set direction
	snprintf(buf,sizeof(buf),SYSFS_GPIO_DIR"/gpio44/direction");
	fd = open(buf, O_WRONLY);
	write(fd, "in", 3);
	close(fd);

	//read and print value 10 times
	for(i=0;i<10;i++)
	{
		snprintf(buf,sizeof(buf),SYSFS_GPIO_DIR"/gpio44/value");
		fd = open(buf, O_RDONLY);
		read(fd,&ch,1);
		printf("%c\n",ch);
		close(fd);
		sleep(1);
	}
}


如上所述,用文件操作的话,速度受制于linux设定的文件更新速度。实际上也可以用直接读取寄存器的方法。详见这里

A4:暂且引用一下这里的方法。

root@beaglebone:~# echo cape-bone-iio > /sys/devices/bone_capemgr.8/slots
root@beaglebone:~# find /sys/ -name '*AIN*'
/sys/devices/ocp.2/helper.14/AIN0
/sys/devices/ocp.2/helper.14/AIN1
/sys/devices/ocp.2/helper.14/AIN2
/sys/devices/ocp.2/helper.14/AIN3
/sys/devices/ocp.2/helper.14/AIN4
/sys/devices/ocp.2/helper.14/AIN5
/sys/devices/ocp.2/helper.14/AIN6
/sys/devices/ocp.2/helper.14/AIN7
root@beaglebone:~# cat /sys/devices/ocp.2/helper.14/AIN1
1365
root@beaglebone:~# cat /sys/devices/ocp.2/helper.14/AIN1
1297
root@beaglebone:~# cat /sys/devices/ocp.2/helper.14/AIN1
1295


在这里我开启了8个AIN中的AIN1,并读取了3次数值,可以看到值在一定范围内波动。关于这么做的原理呢,涉及到一个很复杂但很重要的东西,叫做Device tree。这是新版的嵌入式linux用于统一管理硬件资源的方式。这部分内容比较多,我还没来得及消化,今后会用专文来写这部分。

A5:写入文件就可以啦,像前面的用c语言操作io口例程那样。

A6:基于ssh连接的话,用scp [OPTIONS] file_source file_target命令就可以了。例如:
从电脑复制到BBB:

# scp /Users/myname/full.tar.gz root@192.168.7.2:/home/root

然后会提示你输入另外那台192.168.7.2主机的root用户的登录密码,接着就开始copy了,复制目录加参数-r即可
从BBB复制到电脑:

# scp root@192.168.7.2:/home/root/full.tar.gz /Users/myname/full.tar.gz

暂时用这种方法足够调试程序了,串口只有在实时查看数据时才有必要用,等需要用时再说。
会互传文件以后,编写程序就更方便了,我甚至可以在Mac上装上gcc编译器(这是很容易的事),某些与硬件无关的程序可以在电脑上书写并编译通过以后再拷贝到BBB中去,修改内容和编译速度都会快很多。