应用层访问I2C设备的步骤
在应用层中可以通过 i2c_dev 设备访问 I2C 适配器,从而达到在应用层直接访问 I2C 设备的目的,在应用层可以按如下步骤访问 I2C 设备:
- 使用 open(“/dev/i2c*”, O_RDWR) 打开对应的 I2C 适配器
- 通过 ioctl(fd, I2C_FUNCS, unsigned long *funcs) 获取 I2C 适配器所支持的功能,功能掩码通过参数funcs返回,常用功能对应的宏有:
I2C_FUNC_10BIT_ADDR 支持10位地址
//下列宏对应的 smbus 协议支持
I2C_FUNC_SMBUS_READ_BYTE 单字节随机读
I2C_FUNC_SMBUS_WRITE_BYTE 单字节随机写
I2C_FUNC_SMBUS_READ_WORD_DATA 双字节随机读
I2C_FUNC_SMBUS_WRITE_WORD_DATA 双字节随机写
I2C_FUNC_SMBUS_READ_BYTE_DATA 多字节水机读
I2C_FUNC_SMBUS_WRITE_BYTE_DATA 多字节随机写
I2C_FUNC_SMBUS_WRITE_BLOCK_DATA 多个block随机写
I2C_FUNC_SMBUS_WRITE_I2C_BLOCK 单block随机写
- 通过 ioctl(fd, I2C_SLAVE_FORCE, long address) 或 ioctl(fd, I2C_SLAVE,long address) 设置要访问的从机地址,其中 I2C_SLAVE_FORCE 表示强制设置,即内核中有对应设备的驱动也能执行成功。
- 通过 ioctl(fd, I2C_RDWR, struct i2c_rdwr_ioctl_data *msg) 或 ioctl(fd, I2C_SMBUS, struct i2c_smbus_ioctl_data *args) 传输数据, struct i2c_rdwr_ioctl_data 和 struct i2c_smbus_ioctl_data 的数据结构如下:
struct i2c_rdwr_ioctl_data {
//msg地址
struct i2c_msg *msgs;
//msg个数
int nmsgs;
};
struct i2c_msg {
//从机地址
__u16 addr;
//传输标志,有如下标志:
// I2C_M_RD :从从机读数据
// I2C_M_TEN :地址长度为10位
// I2C_M_DMA_SAFE :缓冲区支持 DMA
// I2C_M_RECV_LEN :接收的第一个 byte 表示长度
// I2C_M_NO_RD_ACK :不产生 ACK
// I2C_M_IGNORE_NAK :忽略 NAK
// I2C_M_NOSTART :不产生开始信号
// I2C_M_STOP :产生停止信号
_u16 flags;
//消息长度
__u16 len;
//消息缓冲区
__u8 *buf;
};
struct i2c_smbus_ioctl_data {
//读写标志
// I2C_SMBUS_READ smbus读
// I2C_SMBUS_WRITE smbus写
char read_write;
//读数据前发送给从机的命令,一般表示芯片内部的寄存器地址
__u8 command;
//SMbus传输类型
// I2C_SMBUS_QUICK 只发送一位数据,通过R/W位实现
// I2C_SMBUS_BYTE 传输1个字节(读操作时直接读取一个字节,写操作时只写命令字段)
// I2C_SMBUS_BYTE_DATA 发送命令后再传输1个字节
// I2C_SMBUS_WORD_DATA 发送命令后再传输2个字节
// I2C_SMBUS_PROC_CALL 发送命令后写2个字节,然后再读2字节
// I2C_SMBUS_BLOCK_DATA 发送命令后再传输1个block,写操作时写的长度为I2C_SMBUS_BLOCK_MAX,读操作时block的第一个字节是要读取的长度
// I2C_SMBUS_BLOCK_PROC_CALL 先写一个block,再读一个block,block的第一个字节是要读取的数据的长度,写入的长度为I2C_SMBUS_BLOCK_MAX
// I2C_SMBUS_I2C_BLOCK_DATA 发送命令后再传输1个block,block第一个字节是要读写的长度
int size;
//消息缓存
union i2c_smbus_data *data;
};
union i2c_smbus_data {
//单字节读写使用
__u8 byte;
//双字节读写使用
__u16 word;
//block读写使用
__u8 block[I2C_SMBUS_BLOCK_MAX + 2];
};
- 使用完成后通过 close(fd) 关闭对应的 I2C 适配器
在应用层编写AP3216C驱动
AP3216C集成了光强(Ambilent Light Sensor,ALS)、距离(Proximity Sensor,PS)和红外传感器(Infrared Radiation LED,IR),该芯片通过IIC接口与主控芯片交互。
电路原理图
从原理图中可以看出AP3216C接在控制器的I2C5 I2C接口上,所复用的GPIO分别是PA11和PA12
编写设备树
虽然是在应用层访问I2C适配器,驱动AP3216C设备,但是依然要用到I2C适配器设备,所以需要在设备树中描述I2C适配器的硬件,按如下步骤在设备树中添加I2C5适配器的描述:
- 在顶层设备树文件中引用i2c5 节点,并进行如下修改
&i2c5 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&i2c5_pins_a>;
pinctrl-1 = <&i2c5_pins_sleep_a>;
status = "okay";
};
- 在 stm32mp15-pinctrl.dtsi 的 &pinctrl 节点中增加 I2C5 的引脚配置,内容如下:
i2c5_pins_a: i2c5-0 {
pins {
pinmux = <STM32_PINMUX('A', 11, AF4)>, /* I2C5_SCL */
<STM32_PINMUX('A', 12, AF4)>; /* I2C5_SDA */
bias-disable;
drive-open-drain;
slew-rate = <0>;
};
};
i2c5_pins_sleep_a: i2c5-1 {
pins {
pinmux = <STM32_PINMUX('A', 11, ANALOG)>, /* I2C5_SCL */
<STM32_PINMUX('A', 12, ANALOG)>; /* I2C5_SDA */
};
};
编写驱动代码
应用层AP3216C驱动代码的流程如下:
- 打开 i2c_dev 设备
- 检查适配器是否支持所需的功能
- 通过ioctl设置从机地址
- 通过ioctl配置AP3216C寄存器,以初始化AP3216C
- 通过ioctl读取AP3216C的寄存器,得到传感器采集的数据
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#define AP3216C_ADDR 0X1E /* AP3216C 器件地址 */
/* AP3316C 寄存器 */
#define AP3216C_SYSTEMCONG 0x00 /* 配置寄存器 */
#define AP3216C_INTSTATUS 0X01 /* 中断状态寄存器 */
#define AP3216C_INTCLEAR 0X02 /* 中断清除寄存器 */
#define AP3216C_IRDATALOW 0x0A /* IR 数据低字节 */
#define AP3216C_IRDATAHIGH 0x0B /* IR 数据高字节 */
#define AP3216C_ALSDATALOW 0x0C /* ALS 数据低字节 */
#define AP3216C_ALSDATAHIGH 0X0D /* ALS 数据高字节 */
#define AP3216C_PSDATALOW 0X0E /* PS 数据低字节 */
#define AP3216C_PSDATAHIGH 0X0F /* PS 数据高字节 */
static int check_funcs(int file)
{
unsigned long funcs;
/* check adapter functionality */
if (ioctl(file, I2C_FUNCS, &funcs) < 0)
return -1;
if (!(funcs & I2C_FUNC_I2C))
return -1;
return 0;
}
static int ap3216c_read_regs(int fd, uint8_t reg, uint8_t *data, uint8_t lenght)
{
int nmsgs_sent;
struct i2c_msg msg[2];
struct i2c_rdwr_ioctl_data rdwr;
//从机地址
msg[0].addr = AP3216C_ADDR;
//表示写
msg[0].flags = 0;
//buf是一个指针,指向了要发送的数据
msg[0].buf = ®
//msg[0].buf的数据长度
msg[0].len = 1;
msg[1].addr = AP3216C_ADDR;
//表示读
msg[1].flags = I2C_M_RD;
msg[1].buf = data;
msg[1].len = lenght;
rdwr.msgs = msg;
rdwr.nmsgs = 2;
nmsgs_sent = ioctl(fd, I2C_RDWR, &rdwr);
if (nmsgs_sent < 2)
return -1;
return 0;
}
static int ap3216c_write_regs(int fd, uint8_t reg, uint8_t *data, uint8_t lenght)
{
int nmsgs_sent;
uint8_t buffer[256];
struct i2c_msg msg[1];
struct i2c_rdwr_ioctl_data rdwr;
//只能用一个msg发送,分多个msg时msg衔接的时候不会等待设备的ACK信号,可能会导致失败
buffer[0] = reg;
memcpy(&buffer[1], data, lenght);
//从机地址
msg[0].addr = AP3216C_ADDR;
//表示写
msg[0].flags = 0;
//buf是一个指针,指向了要发送的数据
msg[0].buf = buffer;
//msg[0].buf的数据长度
msg[0].len = 1 + lenght;
rdwr.msgs = msg;
rdwr.nmsgs = 1;
nmsgs_sent = ioctl(fd, I2C_RDWR, &rdwr);
if (nmsgs_sent < 1)
return -1;
return 0;
}
static int ap3216c_read_reg(int fd, uint8_t reg, uint8_t *data)
{
return ap3216c_read_regs(fd, reg, data, 1);
}
static int ap3216c_write_reg(int fd, uint8_t reg, uint8_t data)
{
return ap3216c_write_regs(fd, reg, &data, 1);
}
static int ap3216c_init(int fd)
{
int result;
//初始化AP3216C,然后开启ALS、PS+IR
result = ap3216c_write_reg(fd, AP3216C_SYSTEMCONG, 0x04);
if(result != 0)
return result;
usleep(50*1000);
result = ap3216c_write_reg(fd, AP3216C_SYSTEMCONG, 0X03);
if(result != 0)
return result;
return 0;
}
static int ap3216c_read(int fd, uint16_t data[3])
{
int i;
int result;
uint8_t buffer[6];
/* 循环读取所有传感器数据 */
for(i=0; i<6; i++)
{
result = ap3216c_read_reg(fd, AP3216C_IRDATALOW+i, &buffer[i]);
if(result != 0)
return result;
}
if(buffer[0] & 0X80)
{
/* IR_OF位为1,则数据无效 */
data[0] = 0;
}
else
{
/* 读取IR传感器的数据*/
data[0] = ((uint16_t)buffer[1] << 2) | (buffer[0] & 0X03);
}
data[1] = ((uint16_t)buffer[3] << 8) | buffer[2]; /* 读取ALS传感器的数据 */
if(buffer[4] & 0x40)
{
/* IR_OF位为1,则数据无效 */
data[2] = 0;
}
else
{
/* 读取PS传感器的数据 */
data[2] = ((uint16_t)(buffer[5] & 0X3F) << 4) | (buffer[4] & 0X0F);
}
return 0;
}
static int open_i2c_dev(uint32_t i2cbus)
{
int file;
char filename[32];
snprintf(filename, sizeof(filename), "/dev/i2c/%d", i2cbus);
file = open(filename, O_RDWR);
if(file < 0 && (errno == ENOENT || errno == ENOTDIR))
{
snprintf(filename, sizeof(filename), "/dev/i2c-%d", i2cbus);
file = open(filename, O_RDWR);
}
return file;
}
static int set_slave_addr(int file, int address, int force)
{
/* With force, let the user read from/write to the registers
even when a driver is also running */
if (ioctl(file, force ? I2C_SLAVE_FORCE : I2C_SLAVE, address) < 0)
return -1;
return 0;
}
int main(int argc, char *argv[])
{
int file;
int result;
uint16_t ir, als, ps;
uint32_t i2cbus;
uint16_t databuf[3];
i2cbus = 0;
if(argc > 1)
i2cbus = strtoul(argv[1], NULL, 0);
//打开I2C控制器
file = open_i2c_dev(i2cbus);
if (file < 0)
{
fprintf(stderr, "Error: open i2c bus failed\r\n");
return -1;
}
//检查适配器是否支持
result = check_funcs(file);
if(result < 0)
{
fprintf(stderr, "Error: Adapter does not have capability\r\n");
return -1;
}
//设置从机地址
result = set_slave_addr(file, AP3216C_ADDR, 1);
if(result < 0)
{
fprintf(stderr,
"Error: Could not set address to 0x%02x: %s\r\n",
AP3216C_ADDR, strerror(errno));
return -1;
}
//初始化AP3216C
result = ap3216c_init(file);
if(result < 0)
{
fprintf(stderr, "Error: initial ap3216 failed\r\n");
return -1;
}
while(1)
{
/*200ms */
usleep(200000);
result = ap3216c_read(file, databuf);
if(result == 0)
{
/* 数据读取成功 */
ir = databuf[0]; /* ir传感器数据 */
als = databuf[1]; /* als传感器数据 */
ps = databuf[2]; /* ps传感器数据 */
printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);
}
else
fprintf(stderr, "Error: read data failed\r\n");
}
/* 关闭文件 */
close(file);
return 0;
}
上机测试
- 修改设备树(设备树需要结合硬件进行修改,主要是使能AP3216C所在的I2C总线),然后编译设备树,并用新的设备树启动
- 从这里下载代码,并进行编译,然后拷贝到目标板根文件系统的root目录
- 通过AP3216C得知其I2C地址为7位地址模式,从机地址是0X1E
- 通过i2cdetect -ya 0命令探测到0x1E的/dev/i2c-1所对应的适配器下
- 执行命令./ap3216.out 1即可完成对AP3216C的初始化,并不断读取其IR、ALS、PS寄存器的值。