3. I2C通讯¶
本章简单介绍使用I2C总线与外部设备的通讯,讲解I2C设备的简单使用
3.1. 本章节的代码仓库¶
1 2 3 4 5 6 | #如之前有获取则可跳过
#获取仓库
git clone https://gitee.com/LubanCat/lubancat_rk_code_storage.git
#代码所在的位置
lubancat_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-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 2 3 4 5 | #进入工具配置
sudo fire-config
#移动光标到下图的位置
#按确认键进入配置
|
选择你要打开的I2C接口,这里以打开I2C-3为例
使用方向键移动光标到
I2C-3
按 “空格键” 选中I2C-3(出现 “*” ),如下图
按 “确认键” 进行设置
按 “Esc键” 退出到终端,运行 sudo reboot 进行重启应用
3.4.2. 配置文件¶
板卡 |
设备树插件配置文件 |
说明 |
---|---|---|
当前你所使用的板子 |
uEnv.txt |
该配置软链接到你所使用的设备,修改该配置相当于修改板子实际的配置 |
LubanCat-4 |
uEnvLubanCat4.txt |
适用于LubanCat-4 |
LubanCat-4-V1 |
uEnvLubanCat4-V1.txt |
适用于LubanCat-4-V1 |
LubanCat-5 |
uEnvLubanCat5.txt |
适用于LubanCat-5 |
LubanCat-5IO |
uEnvLubanCat5IO.txt |
适用于LubanCat-5IO |
可以通过修改 /boot/uEnv/uEnv.txt 或者 /boot/uEnv/uEnvboard.txt (uEnvboard.txt为通过上面的对照表获得的配置文件)
这里以激活 I2C-3 为例,将带有 I2C-3 的那一行的注释符号去掉 如下图:
然后重启激活设备
注解
如果是直接拔电源的方式重启,会有可能出现文件没能做出修改 (原因:文件未能及时从内存同步到存储设备中,解决方法,在终端上输入 “sync” 再拔电关机)
3.6. 连接设备¶
将mpu6050接入到i2c-3的总线上,如下图所示
1 2 3 4 5 6 7 | #板卡与mpu6050连接
板子 ------ mpu6050
3.3V(1) ------ VCC
GND(6) ------ GND
SCL(5) ------ SCL
SDA(3) ------ SDA
|
3.7. i2c-tools测试¶
使用i2c-tools工具包提供了一些非常方便的工具来对系统的I2C总线进行调试, 在板卡的终端中可直接执行以下命令进行安装:
1 | sudo apt -y install i2c-tools
|
安装后可使用的命令有i2cdetect、i2cdump、i2cset以及i2cget,用于扫描I2C总线上的设备、读写指定设备的寄存器等。
然后查看挂载在i2c-3上的器件情况,输出内容如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | root@lubancat:~# i2cdetect -a 3
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-3.
I will probe address range 0x00-0x7f.
Continue? [Y/n] y
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
root@lubancat:~# ^C
|
其中 “68” 是为MPU6050的设备地址,常用的命令还有以下几个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #检测当前系统有几组i2c总线
i2cdetect -l
#查看i2c-0接口上的设备
i2cdetect -a 3
#读取指定设备的全部寄存器的值。
i2cdump -f -y 3 0x68
#读取指定IIC设备的某个寄存器的值,如下读取地址为0x68器件中的0x01寄存器值。
i2cget -f -y 3 0x68 0x01
#写入指定IIC设备的某个寄存器的值,如下设置地址为0x68器件中的0x01寄存器值为0x6f;
i2cset -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接口已经使能
1 2 3 | root@lubancat:~# ls /dev/i2c-*
/dev/i2c-0 /dev/i2c-1 /dev/i2c-3 /dev/i2c-6
root@lubancat:~#
|
其中“i2c-3”就是MPU6050使用到的 IIC 3接口总线。
3.8.2. ioctl函数¶
在编写应用程序时需要使用ioctl函数设置i2c相关配置,其函数原型如下
1 2 3 | #include <sys/ioctl.h>
int 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原始数据程序如下
1 | quick_start/i2c/i2c_mpu6050.c
|
代码较长复制粘贴容易乱序,可以下载我们提供的源码 i2c_mpu6050.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | #include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
/*寄存器地址*/
#define SMPLRT_DIV 0x19
#define PWR_MGMT_1 0x6B
#define CONFIG 0x1A
#define ACCEL_CONFIG 0x1C
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define GYRO_XOUT_H 0x43
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48
//从机地址 MPU6050地址
#define Address 0x68
//MPU6050操作相关函数
static int mpu6050_init(int fd,uint8_t addr);
static int i2c_write(int fd, uint8_t addr,uint8_t reg,uint8_t val);
static int i2c_read(int fd, uint8_t addr,uint8_t reg,uint8_t * val);
static short GetData(int fd,uint8_t addr,unsigned char REG_Address);
int main(int argc,char *argv[] )
{
int fd;
fd = I2C_SLAVE;
if(argc < 2){
printf("Wrong use !!!!\n");
printf("Usage: %s [dev]\n",argv[0]);
return -1;
}
fd = open(argv[1], O_RDWR); // open file and enable read and write
if (fd < 0){
printf("Can't open %s \n",argv[1]); // open i2c dev file fail
exit(1);
}
//初始化MPU6050
mpu6050_init(fd,Address);
while(1){
usleep(1000 * 10);
printf("ACCE_X:%6d\n ", GetData(fd,Address,ACCEL_XOUT_H));
usleep(1000 * 10);
printf("ACCE_Y:%6d\n ", GetData(fd,Address,ACCEL_YOUT_H));
usleep(1000 * 10);
printf("ACCE_Z:%6d\n ", GetData(fd,Address,ACCEL_ZOUT_H));
usleep(1000 * 10);
printf("GYRO_X:%6d\n ", GetData(fd,Address,GYRO_XOUT_H));
usleep(1000 * 10);
printf("GYRO_Y:%6d\n ", GetData(fd,Address,GYRO_YOUT_H));
usleep(1000 * 10);
printf("GYRO_Z:%6d\n\n ", GetData(fd,Address,GYRO_ZOUT_H));
sleep(1);
}
close(fd);
return 0;
}
static int mpu6050_init(int fd,uint8_t addr)
{
i2c_write(fd, addr,PWR_MGMT_1,0x00); //配置电源管理,0x00,正常启动
i2c_write(fd, addr,SMPLRT_DIV,0x07); //设置MPU6050的输出分频既设置采样
i2c_write(fd, addr,CONFIG,0x06); //配置数字低通滤波器和帧同步引脚
i2c_write(fd, addr,ACCEL_CONFIG,0x01); //设置量程和 X、Y、Z 轴加速度自检
return 0;
}
static int i2c_write(int fd, uint8_t addr,uint8_t reg,uint8_t val)
{
int retries;
uint8_t data[2];
data[0] = reg;
data[1] = val;
//设置地址长度:0为7位地址
ioctl(fd,I2C_TENBIT,0);
//设置从机地址
if (ioctl(fd,I2C_SLAVE,addr) < 0){
printf("fail to set i2c device slave address!\n");
close(fd);
return -1;
}
//设置收不到ACK时的重试次数
ioctl(fd,I2C_RETRIES,5);
if (write(fd, data, 2) == 2){
return 0;
}
else{
return -1;
}
}
static int i2c_read(int fd, uint8_t addr,uint8_t reg,uint8_t * val)
{
int retries;
//设置地址长度:0为7位地址
ioctl(fd,I2C_TENBIT,0);
//设置从机地址
if (ioctl(fd,I2C_SLAVE,addr) < 0){
printf("fail to set i2c device slave address!\n");
close(fd);
return -1;
}
//设置收不到ACK时的重试次数
ioctl(fd,I2C_RETRIES,5);
if (write(fd, ®, 1) == 1){
if (read(fd, val, 1) == 1){
return 0;
}
}
else{
return -1;
}
}
static short GetData(int fd,uint8_t addr,unsigned char REG_Address)
{
char H, L;
i2c_read(fd, addr,REG_Address, &H);
usleep(1000);
i2c_read(fd, addr,REG_Address + 1, &L);
return (H << 8) +L;
}
|
保存退出,接下来再进行编译运行
1 2 3 | gcc i2c_mpu6050.c -o mpu6050
sudo ./mpu6050 /dev/i2c-3
|
效果如下图所示
三次数据的采集