12. I2C通讯¶
本章介绍在应用层中使用I2C总线与外部设备的通讯,讲解Linux系 统总线类型设备驱动架构的应用。
在Linux内核文档的Documentation/i2c目录下有关于I2C驱动非常详细的说明。
12.1. 使能IIC通信接口¶
板子上的MPU6050连接到了STM32MP157的I2C 1接口,开发板上默认开启i2c1和i2c2。 打开/boot/uEnv.txt文件查看是否启用了i2c相关设备设备树插件, 本章重点是使用i2c控制器,MPU6050为挂载在I2C上的一个器件。
如下图:
12.1.1. 检查IIC 设备¶
使能IIC_1通信接口后,我们可以通过系统中的IIC设备文件来检查设备的一些基本信息, 下面简单介绍几个查看命令。
查看系统存在的I2C总线,如下所示,不同版本的Debian系统输出内容可能有差别。
1 2 3 | root@npi:~# ls /sys/bus/i2c/devices
0-005d 1-001a 1-0039 i2c-0 i2c-1
root@npi:~#
|
IIC 1接口对应的驱动设备文件为上图中的i2c-0,这是因为驱动是从0开始编号的。 因此应用程序一般通过操作设备文件i2c-0实现对MPU6050的操作。不止MPU6050, 理论上任何连接到 IIC 1 接口上的设备(触摸、摄像头、oled)都可以通过 i2c-0 设备文件来实现具体的功能。
12.2. IIC 第三方工具- i2c-tools¶
使用i2c-tools工具包提供了一些非常方便的工具来对系统的I2C总线进行调试, 在开发板的终端中可直接执行以下命令进行安装:
1 | sudo apt install i2c-tools -y
|
安装后可使用的命令有i2cdetect、i2cdump、i2cset以及i2cget,用于扫描I2C总线上的设备、读写指定设备的寄存器等。
查看挂载在i2c1上的器件情况,输出内容如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | root@npi:~# i2cdetect -a 0
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-0.
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@npi:~#
|
其中 “68” 是为MPU6050的设备地址,常用的命令还有以下几个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #检测当前系统有几组i2c总线
i2cdetect -l
#查看i2c-0接口上的设备
i2cdetect -a 0
#读取指定设备的全部寄存器的值。
i2cdump -f -y 0 0x68
#读取指定IIC设备的某个寄存器的值,如下读取地址为0x68器件中的0x01寄存器值。
i2cget -f -y 0 0x68 0x01
#写入指定IIC设备的某个寄存器的值,如下设置地址为0x68器件中的0x01寄存器值为0x6f;
i2cset -f -y 0 0x68 0x01 0x6f
|
12.3. 读取陀螺仪传感器数据实验¶
12.3.1. 实验说明¶
本教程将通过IIC接口读取板载陀螺仪(MPU6050)的原始数据, 在测试程序中大约每一秒读取并显示一次MPU6050的原始数据。
查看IIC设备文件,确保IIC 1接口已经使能
1 2 3 | root@npi:~# ls /sys/bus/i2c/devices
i2c-0 i2c-1 i2c-4
root@npi:~#
|
其中“i2c-0”就是MPU6050使用到的 IIC 1接口总线。
12.3.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位 |
12.3.3. 编写应用程序¶
根据ioctl相关参数即可编写与i2c相关的接口函数,读取mpu6050原始数据程序如下
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 156 157 158 159 160 161 162 163 164 | #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;
//打开相对应的i2c设备
fd = open("/dev/i2c-0",O_RDWR );
if(fd < 0)
{
perror("/dev/i2c-0");
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);
i2c_write(fd, addr,SMPLRT_DIV, 0X07);
i2c_write(fd, addr,CONFIG, 0X06);
i2c_write(fd, addr,ACCEL_CONFIG, 0X01);
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;
}
|
12.3.4. i2c设备树插件修改¶
野火STM32MP157提供了很多的设备树插件源码,若想要添加或修改不同的引脚作为i2c总线的引脚, 用户只需要根据相关相对应的设备树插件修改即可。
仓库中提供了i2c1,i2c2的设备树插件, 其中i2c1的设备树插件文件为 stm-fire-i2c1-overlay.dts ,源码如下所示
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 | /dts-v1/;
/plugin/;
//#include "../stm32mp157c.dtsi"
#include <dt-bindings/pinctrl/stm32-pinfunc.h>
#include <dt-bindings/input/input.h>
#include <dt-bindings/mfd/st,stpmic1.h>
#include <dt-bindings/gpio/gpio.h>
/{
fragment@0{
target=<&i2c1>;
__overlay__{
pinctrl-names = "default", "sleep";
pinctrl-0 = <&i2c1_pins_a>;
pinctrl-1 = <&i2c1_pins_sleep_a>;
i2c-scl-rising-time-ns = <100>;
i2c-scl-falling-time-ns = <7>;
status = "okay";
/delete-property/dmas;
/delete-property/dma-names;
};
};
fragment@1{
target=<&pinctrl>;
__overlay__{
i2c1_pins_a: i2c1-0 {
pins {
pinmux = <STM32_PINMUX('F', 14, AF5)>, /* I2C1_SCL */
<STM32_PINMUX('F', 15, AF5)>; /* I2C1_SDA */
bias-disable;
drive-open-drain;
slew-rate = <0>;
};
};
i2c1_pins_sleep_a: i2c1-1 {
pins {
pinmux = <STM32_PINMUX('F', 14, ANALOG)>, /* I2C1_SCL */
<STM32_PINMUX('F', 15, ANALOG)>; /* I2C1_SDA */
};
};
};
};
};
|
i2c设备树插件修改相对简单,若想修改其他具有i2c功能的引脚,只需修改设备树插件的第23-24行即可。