查看: 1624|回复: 0
打印 上一主题 下一主题

使用BBB的I2C

[复制链接]
跳转到指定楼层
沙发
发表于 2015-5-23 14:28:18 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
一、I2C基本操作

1、简介
[size=13.8461542129517px]I2C是一种串行通讯方法,它只需要两根线就能实现通讯,一根时钟线SCL,一根数据线SDA。一般情况下这两根线都使用上拉电阻,同时把芯片的管脚设置成开漏输出(简单理解开漏输出的含义就是:让它输出低电平时,它能输出低电平;而让它输出高电平时,它就断路,什么也不输出,由外接电平决定这个引脚的电平)。如果芯片内部带有上拉电阻(比如BBB的芯片就自带上拉电阻),那不外接上拉也可以。
[size=13.8461542129517px]BBB系统自带了一个Linux下的I2C工具i2c-tools,非常好用,下文以i2c开头的命令都是这个工具包里的,如果你的系统里没有的话,可以搜索并下载i2c-tools工具包。
[size=13.8461542129517px]BBB上有两个可用的I2C,i2c-0和i2c-1,分别对应header上的I2C1和I2C2(总是这么混乱= =)。我们这里使用i2c-1,对应的header是P9_19和P9_20。我怎么知道它有两个可用的I2C呢?使用命令 i2cdetect -l 就可以看到
[size=13.8461542129517px]
  • i2c-0   i2c         OMAP I2C adapter                    I2C adapter
  • i2c-1   i2c         OMAP I2C adapter                    I2C adapter

[color=rgb(51, 102, 153) !important]复制代码

2、检查引脚功能配置是否正确
[size=13.8461542129517px]首先确认一下i2c-1对应的IO口复用功能是否正确([size=13.8461542129517px]BBB默认就是正确的,所以无需进行配置,但严谨起见应当检查一下[size=13.8461542129517px])。查表(见附件)得,P9_19和P9_20分别对应95和94号引脚。然后输入命令:
[size=13.8461542129517px]
  • cat /sys/kernel/debug/pinctrl/44e10800.pinmux/pins | grep 97c

[color=rgb(51, 102, 153) !important]复制代码

[size=13.8461542129517px]上述命令把pins这个文件中包含97c的内容输出(97c是从表中看到的引脚地址),得到95号引脚的功能和复用寄存器值
[size=13.8461542129517px]
  • pin 95 (44e1097c) 00000073 pinctrl-single

[color=rgb(51, 102, 153) !important]复制代码

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

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

[color=rgb(51, 102, 153) !important]复制代码

[size=13.8461542129517px]这里的地址都是16进制表示的。--代表该地址没有设备,UU代表这个地址正忙(也许被内部资源占用了,图中的0x54到0x57这4个地址)。下面我把i2c设备插上以后再执行命令,输出变成了
[size=13.8461542129517px]
  •      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

[color=rgb(51, 102, 153) !important]复制代码


[size=13.8461542129517px]会发现多出了4个地址:0x1e,0x53,0x69和0x77。因为我接入的模块上包含4个芯片,所以这个总线上显示了4个地址。通过读芯片手册得知气压计对应的是0x77这个地址。这一步就完成了。
[size=13.8461542129517px]需要补充说明的是,这里显示的是i2c设备的地址(1110111b=0x77),i2c的设备地址只有7位,最高位当做0。而读/写地址则在最低位增加一个1/0(11101111b / 11101110b),这使得读写地址与设备地址看起来很不相同。

4、查看和修改设备的寄存器值
[size=13.8461542129517px]输入命令# i2cdump -y 1 0x77,我们可以查看设备的寄存器值,其中 -y 还是屏蔽确认环节,1 还是代表查看i2c-1总线,0x77是要查询的设备地址。
[size=13.8461542129517px]
  •      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    ......?.........

[color=rgb(51, 102, 153) !important]复制代码


[size=13.8461542129517px]这里用16进制显示了每个寄存器的值,具体哪个寄存器是干嘛的,就得查阅芯片的数据手册了。
[size=13.8461542129517px]如果想取出某个特定寄存器的值,比如0x80寄存器,可以使用命令 i2cget -y 1 0x77 0x80实现。如果想向某个寄存器写入值,可使用命令 i2cset -y 1 0x69 0x20 0x0f实现。(向i2c-1总线上,设备地址为0x69,寄存器地址为0x20处写入值0x0f)

二、使用BMP085驱动读取气压值

[size=13.8461542129517px]很巧的是,BBB自带了BMP085气压芯片的驱动,所以我们可以更方便地读取气压值。输入命令
[size=13.8461542129517px]
  • echo bmp085 0x77 > /sys/class/i2c-adapter/i2c-1/new_device

[color=rgb(51, 102, 153) !important]复制代码

[size=13.8461542129517px]加载成功后使用如下命令就可以读取气压值了
[size=13.8461542129517px]
  • cat sys/bus/i2c/drivers/bmp085/1-0077/pressure0_input

[color=rgb(51, 102, 153) !important]复制代码

[size=13.8461542129517px]使用完毕以后,我们可以用如下命令从驱动中卸载这个i2c设备
[size=13.8461542129517px]
  • echo 0x77 > /sys/class/i2c-adapter/i2c-1/delete_device

[color=rgb(51, 102, 153) !important]复制代码

[size=13.8461542129517px]想查看加载或卸载是否成功的话,可以输入如下命令
[size=13.8461542129517px]
  • dmesg | grep bmp

[color=rgb(51, 102, 153) !important]复制代码

[size=13.8461542129517px]你应该会看到类似下面的输出
[size=13.8461542129517px]
  • [ 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

[color=rgb(51, 102, 153) !important]复制代码

[size=13.8461542129517px]上面给出了使用某个芯片的现成驱动来操作的方法。要是我用的I2C设备没有现成的驱动怎么办?下面就介绍如何在c语言程序中使用通用的i2c-dev驱动来操作i2c设备。[size=13.8461542129517px]
三、用C语言操作I2C设备
[size=13.8461542129517px]下面以使用L3G4200D三轴陀螺仪为例。
[size=13.8461542129517px]BBB自带了i2c-dev驱动,它使用ioctl方法对i2c设备进行配置,然后利用read()、write()函数就可以操作i2c设备了。
[size=13.8461542129517px]要操作某个i2c设备,首先要确定它的地址。按照我在前文中的说法,通常i2cdetect显示的器件地址是由“7位”二进制数换算成的,而器件的读写地址则需要将这7位左移,然后在末尾添加一个1或0。但是对于i2c-dev驱动来说,我们不必费此周折,只要告诉他i2cdetect显示的那个地址就可以了,驱动会自动根据你的读操作或写操作来相应地在末尾添加1或者0。

[size=13.8461542129517px]先把完整程序摆出来:
[size=13.8461542129517px]
  • #include <stdio.h>
  • #include <stdlib.h>   //exit()
  • #include <fcntl.h>    //define O_RDWR
  • #include <linux/i2c-dev.h>
  • #include <sys/ioctl.h>
  • void main()
  • {
  •         int file,i;
  •         int addr = 0b01101001; //i2c device address of gyro
  •         char *filename = "/dev/i2c-1";
  •         char wbuf[] = {0x20, 0x0f}; //first byte is address to write. others are bytes to be written
  •         char read_start_buf[1];
  •         char rbuf[1000] = {0};
  •         if((file = open(filename, O_RDWR)) < 0)
  •         {
  •                 perror("Failed to open i2c device.\n");
  •                 exit(1);
  •         }
  •         if(ioctl(file, I2C_SLAVE, addr) < 0)
  •         {
  •                 printf("Failed to access bus.\n");
  •                 exit(1);
  •         }
  •         write(file, wbuf, 2); //write 0x0f to register 0x20 to enable gyro.
  •         for(i=0;i<5;i++)
  •         {
  •                 read_start_buf[0] = 0x29;
  •                 write(file, read_start_buf, 1); //reposition file pointer to register 0x29
  •                 read(file, rbuf, 1); //read register 0x29 and save to rbuf
  •                 printf("%x", rbuf[0]);
  •                 read_start_buf[0] = 0x28;
  •                 write(file, read_start_buf, 1); //reposition file pointer to register 0x28
  •                 read(file, rbuf, 1); //read register 0x28 and save to rbuf
  •                 printf("%x\n", rbuf[0]);
  •                 sleep(1);
  •         }
  •         close(file);
  • }

[color=rgb(51, 102, 153) !important]复制代码

[size=13.8461542129517px]上面的程序首先打开/dev/i2c-1这个设备,然后用ioctl配置成slave模式。然后通过将第0x20这个寄存器写成0x0f来使能陀螺仪。然后在for循环中依次读取0x29和0x28两个寄存器的值并输出,这两个寄存器一起组成了X轴的角速度值。

[size=13.8461542129517px]程序输出如下:
[size=13.8461542129517px]
  • root@beaglebone:~/ioctl_test# ./i2c
  • 03c
  • 04a
  • 035
  • 05f
  • 03c
  • root@beaglebone:~/ioctl_test# ./i2c
  • 1f7f
  • f2c3
  • f567
  • 3979
  • 3aca

[color=rgb(51, 102, 153) !important]复制代码

[size=13.8461542129517px]这里运行了两次,第一次陀螺仪静止(但输出仍有一点小波动),第二次陀螺仪在晃动。可以看到输出值的不同。


[size=13.8461542129517px]需要注意的一点,读i2c设备时如何定位读取的位置?
[size=13.8461542129517px]使用lseek()的话会返回-1,这个是不可行的。答案就是程序中,用write()写一个字节。write函数参数中的buf数组里的第一项代表了write的位置,从第二项开始是写入的内容,因此只写一个字节就是把指针移动过去了但是不写入任何内容。

[size=13.8461542129517px]用这个方法就无需其他驱动也可以操作任意i2c外设了。

[size=13.8461542129517px]最后补充一点。按理说执行 read(file, rbuf, 10); 的话,可以读出当前指针位置开始的10个字节的内容。我在其他设备上也验证了这点。但是在L3G4200D陀螺仪上,一次读取10个的话,读出的就全是一样的数值,都是第一个数值。所以只能一位一位地读取。同样的,执行 i2cdump -y 1 0x69 c 会导致输出全部是一样的,都是第一个字节的内容。把c参数(该参数表示连续字节读取)去掉才可以正常输出。

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 加入因仑

本版积分规则

快速回复 返回顶部 返回列表