3. I2C通讯#

本章简单介绍使用I2C总线与外部设备的通讯,讲解I2C设备的简单使用

3.1. 本章节的代码仓库#

1#如之前有获取则可跳过
2#获取仓库
3git clone https://gitee.com/LubanCat/lubancat_rk_code_storage.git
4
5#代码所在的位置
6lubancat_rk_code_storage/quick_start/i2c

3.2. i2c#

I2C(Inter-Integrated Circuit)是一种通用的总线协议。它是由Philips(飞利浦)公司, 现NXP(恩智浦)半导体开发的一种简单的双向两线制总线协议标准。

LubanCat-RK系列板卡的 I2C 控制器支持下列功能

  • 兼容 I2C 与 SMBus 总线

  • 仅支持主模式下的 I2C 总线

  • 软件可编程时钟频率支持到 400kbps,最高可达 1000kbps

  • 支持 7 位和 10 位寻址模式

  • 一次中断或轮询至多 32 个字节的数据传输

LubanCat-RK3588系列板子的通用i2c设备有两个(如下图所示), 除此之外其他的GPIO也可以复用成I2C设备,需要自行根据引脚复用图去配置, 比如说LubanCat-4的40pin引脚最大可支持5个i2c接口使用

未找到图片
通用i2c引脚#

I2C

引脚

功能

I2C-SCL

5,28

i2c的时钟信号线

I2C-SDA

3,27

i2c的数据线

3.3. IIC通信接口复用说明#

在板卡的引脚复用图中,我们可能会看到

  • I2C5_SDA_M3/I2C5_SCL_M3

  • I2C5_SDA_M2/I2C5_SCL_M2

可以看到他们之间的区别是后缀名的不同,这个后缀名表示I2C设备的将自己的引脚复用到不同的GPIO引脚的编号, 可以理解成下图的样式

未找到图片

复用出来的多组引脚是不能同时使用的, 如果同时设置则会根据设备节点的加载时间所覆盖,最终只有最后配置的复用引脚可以使用。

所以在使能引脚时需要注意是否为重复复用

3.4. IIC通信接口使能#

LubanCat-RK3588系列板子大多数的IIC接口在默认情况是关闭状态的,需要使能才能使用

注意

LubanCat-4的两个通用I2C接口在默认情况下被设置为I2C的功能且默认是使能状态, 所以对于LubanCat-4的板卡两个通用I2C设备是不需用使能的。

3.4.1. fire-config#

1#进入工具配置
2sudo fire-config
3
4#移动光标到下图的位置
5#按确认键进入配置
未找到图片

选择你要打开的I2C接口,这里以打开I2C-3为例

  1. 使用方向键移动光标到 I2C-3

  2. 按 "空格键" 选中I2C-3(出现 "*" ),如下图

  3. 按 "确认键" 进行设置

  4. 按 "Esc键" 退出到终端,运行 sudo reboot 进行重启应用

未找到图片

3.4.2. 配置文件#

可以通过修改 /boot/uEnv/uEnv.txt 进行激活,这里以激活 I2C-3 为例,将带有 I2C-3 的那一行的注释符号去掉 如下图:

未找到图片

然后重启激活设备

备注

如果是直接拔电源的方式重启,会有可能出现文件没能做出修改 (原因:文件未能及时从内存同步到存储设备中,解决方法,在终端上输入 "sync" 再拔电关机)

3.5. 检查I2C设备#

可以通过一下命令查看i2c总线有没有开启

1ls /dev/i2c-*

如下图:

未找到图片

可以看到出现了I2C-xxx

3.6. 连接设备#

将mpu6050接入到i2c-3的总线上,如下图所示

1#板卡与mpu6050连接
2
3板子  ------  mpu6050
43.3V(1)  ------  VCC
5GND(6)   ------  GND
6SCL(5)   ------  SCL
7SDA(3)   ------  SDA
未找到图片

3.7. i2c-tools测试#

使用i2c-tools工具包提供了一些非常方便的工具来对系统的I2C总线进行调试, 在板卡的终端中可直接执行以下命令进行安装:

1sudo apt -y install i2c-tools

安装后可使用的命令有i2cdetect、i2cdump、i2cset以及i2cget,用于扫描I2C总线上的设备、读写指定设备的寄存器等。

然后查看挂载在i2c-3上的器件情况,输出内容如下所示:

 1root@lubancat:~# i2cdetect -a 3
 2WARNING! This program can confuse your I2C bus, cause data loss and worse!
 3I will probe file /dev/i2c-3.
 4I will probe address range 0x00-0x7f.
 5Continue? [Y/n] y
 6        0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
 700: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
 810: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
 920: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
1030: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
1140: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
1250: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
1360: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
1470: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
15root@lubancat:~# ^C

其中 “68” 是为MPU6050的设备地址,常用的命令还有以下几个。

 1#检测当前系统有几组i2c总线
 2i2cdetect -l
 3
 4#查看i2c-0接口上的设备
 5i2cdetect -a 3
 6
 7#读取指定设备的全部寄存器的值。
 8i2cdump  -f -y 3 0x68
 9
10#读取指定IIC设备的某个寄存器的值,如下读取地址为0x68器件中的0x01寄存器值。
11i2cget -f -y 3 0x68 0x01
12
13#写入指定IIC设备的某个寄存器的值,如下设置地址为0x68器件中的0x01寄存器值为0x6f;
14i2cset -f -y 3 0x68 0x01 0x6f

3.8. 读取陀螺仪传感器数据实验#

3.8.1. 实验说明#

本教程将通过IIC接口读取陀螺仪(MPU6050)的原始数据。 本次实验会以i2c-3做为示例,i2c-5的操作和i2c-3的一样, 当然,如果您没有mpu6050模块,可以通过学习操作mpu6050的方式操作您想要操作的i2c设备 在测试程序中大约每一秒读取并显示一次MPU6050的原始数据

查看IIC设备文件,确保IIC 3接口已经使能

1root@lubancat:~# ls /dev/i2c-*
2/dev/i2c-0  /dev/i2c-1  /dev/i2c-3  /dev/i2c-6
3root@lubancat:~#

其中“i2c-3”就是MPU6050使用到的 IIC 3接口总线。

3.8.2. ioctl函数#

在编写应用程序时需要使用ioctl函数设置i2c相关配置,其函数原型如下

1#include <sys/ioctl.h>
2
3int ioctl(int fd, unsigned long request, ...);

其中对于终端request的值常用的有以下几种

I2C_RETRIES

设置收不到ACK时的重试次数,默认为1

I2C_TIMEOUT

设置超时时限的jiffies

I2C_SLAVE

设置从机地址

I2C_SLAVE_FORCE

强制设置从机地址

I2C_TENBIT

选择地址长度0为7位地址,非0为10位

3.8.3. 编写应用程序#

根据ioctl相关参数即可编写与i2c相关的接口函数,读取mpu6050原始数据程序如下

代码位置#
1quick_start/i2c/i2c_mpu6050.c

代码较长复制粘贴容易乱序,可以下载我们提供的源码 i2c_mpu6050.c

quick_start/i2c/i2c_mpu6050.c#
  1#include <stdio.h>
  2#include <stdlib.h>
  3#include <stdint.h>
  4#include <unistd.h>
  5#include <sys/types.h>
  6#include <sys/stat.h>
  7#include <fcntl.h>
  8#include <linux/i2c.h>
  9#include <linux/i2c-dev.h>
 10#include <sys/ioctl.h>
 11
 12/*寄存器地址*/
 13#define SMPLRT_DIV      0x19
 14#define PWR_MGMT_1      0x6B
 15#define CONFIG          0x1A
 16#define ACCEL_CONFIG    0x1C
 17
 18#define ACCEL_XOUT_H    0x3B
 19#define ACCEL_XOUT_L    0x3C
 20#define ACCEL_YOUT_H    0x3D
 21#define ACCEL_YOUT_L    0x3E
 22#define ACCEL_ZOUT_H    0x3F
 23#define ACCEL_ZOUT_L    0x40
 24#define GYRO_XOUT_H     0x43
 25#define GYRO_XOUT_L     0x44
 26#define GYRO_YOUT_H     0x45
 27#define GYRO_YOUT_L     0x46
 28#define GYRO_ZOUT_H     0x47
 29#define GYRO_ZOUT_L     0x48
 30
 31//从机地址 MPU6050地址
 32#define Address         0x68
 33
 34//MPU6050操作相关函数
 35static int mpu6050_init(int fd,uint8_t addr);
 36static int i2c_write(int fd, uint8_t addr,uint8_t reg,uint8_t val);
 37static int i2c_read(int fd, uint8_t addr,uint8_t reg,uint8_t * val);
 38static short GetData(int fd,uint8_t addr,unsigned char REG_Address);
 39
 40int main(int argc,char *argv[] )
 41{
 42    int  fd;
 43    fd = I2C_SLAVE;
 44
 45    if(argc < 2){
 46        printf("Wrong use !!!!\n");
 47        printf("Usage: %s [dev]\n",argv[0]);
 48        return -1;
 49    }
 50
 51    fd = open(argv[1], O_RDWR); // open file and enable read and  write
 52    if (fd < 0){
 53        printf("Can't open %s \n",argv[1]); // open i2c dev file fail
 54        exit(1);
 55    }
 56
 57    //初始化MPU6050
 58    mpu6050_init(fd,Address);
 59    while(1){
 60        usleep(1000 * 10);
 61        printf("ACCE_X:%6d\n ", GetData(fd,Address,ACCEL_XOUT_H));
 62        usleep(1000 * 10);
 63        printf("ACCE_Y:%6d\n ", GetData(fd,Address,ACCEL_YOUT_H));
 64        usleep(1000 * 10);
 65        printf("ACCE_Z:%6d\n ", GetData(fd,Address,ACCEL_ZOUT_H));
 66        usleep(1000 * 10);
 67        printf("GYRO_X:%6d\n ", GetData(fd,Address,GYRO_XOUT_H));
 68        usleep(1000 * 10);
 69        printf("GYRO_Y:%6d\n ", GetData(fd,Address,GYRO_YOUT_H));
 70        usleep(1000 * 10);
 71        printf("GYRO_Z:%6d\n\n ", GetData(fd,Address,GYRO_ZOUT_H));
 72        sleep(1);
 73    }
 74
 75    close(fd);
 76
 77    return 0;
 78}
 79
 80static int mpu6050_init(int fd,uint8_t addr)
 81{
 82    i2c_write(fd, addr,PWR_MGMT_1,0x00);  //配置电源管理,0x00,正常启动
 83    i2c_write(fd, addr,SMPLRT_DIV,0x07);  //设置MPU6050的输出分频既设置采样
 84    i2c_write(fd, addr,CONFIG,0x06);  //配置数字低通滤波器和帧同步引脚
 85    i2c_write(fd, addr,ACCEL_CONFIG,0x01);  //设置量程和 X、Y、Z 轴加速度自检
 86
 87    return 0;
 88}
 89
 90static int i2c_write(int fd, uint8_t addr,uint8_t reg,uint8_t val)
 91{
 92    int retries;
 93    uint8_t data[2];
 94
 95    data[0] = reg;
 96    data[1] = val;
 97
 98    //设置地址长度:0为7位地址
 99    ioctl(fd,I2C_TENBIT,0);
100
101    //设置从机地址
102    if (ioctl(fd,I2C_SLAVE,addr) < 0){
103        printf("fail to set i2c device slave address!\n");
104        close(fd);
105        return -1;
106    }
107
108    //设置收不到ACK时的重试次数
109    ioctl(fd,I2C_RETRIES,5);
110
111    if (write(fd, data, 2) == 2){
112        return 0;
113    }
114    else{
115        return -1;
116    }
117
118}
119
120static int i2c_read(int fd, uint8_t addr,uint8_t reg,uint8_t * val)
121{
122    int retries;
123
124    //设置地址长度:0为7位地址
125    ioctl(fd,I2C_TENBIT,0);
126
127    //设置从机地址
128    if (ioctl(fd,I2C_SLAVE,addr) < 0){
129        printf("fail to set i2c device slave address!\n");
130        close(fd);
131        return -1;
132    }
133
134    //设置收不到ACK时的重试次数
135    ioctl(fd,I2C_RETRIES,5);
136
137    if (write(fd, &reg, 1) == 1){
138        if (read(fd, val, 1) == 1){
139                return 0;
140        }
141    }
142    else{
143        return -1;
144    }
145}
146
147static short GetData(int fd,uint8_t addr,unsigned char REG_Address)
148{
149    char H, L;
150
151    i2c_read(fd, addr,REG_Address, &H);
152    usleep(1000);
153    i2c_read(fd, addr,REG_Address + 1, &L);
154    return (H << 8) +L;
155}

保存退出,接下来再进行编译运行

1gcc i2c_mpu6050.c -o mpu6050
2
3sudo ./mpu6050 /dev/i2c-3

效果如下图所示

未找到图片show

三次数据的采集

3.9. 更多#

更多关于rk-i2c的驱动以及GPIO模拟I2C等可以查看: rk开源手册 /Common/I2C

3.10. 参考资料#

本文档主要提供有一定经验的使用者快速入门使用,对于初学者可查看以下文档:

《I2C通讯》

rk开源资料(github): 《开源手册》