9.3在应用层使用I2C总线

应用层访问I2C设备的步骤

在应用层中可以通过 i2c_dev 设备访问 I2C 适配器,从而达到在应用层直接访问 I2C 设备的目的,在应用层可以按如下步骤访问 I2C 设备:

  1. 使用 open(“/dev/i2c*”, O_RDWR) 打开对应的 I2C 适配器
  2. 通过 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随机写
  1. 通过 ioctl(fd, I2C_SLAVE_FORCE, long address) 或 ioctl(fd, I2C_SLAVE,long address) 设置要访问的从机地址,其中 I2C_SLAVE_FORCE 表示强制设置,即内核中有对应设备的驱动也能执行成功。
  2. 通过 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];
};
  1. 使用完成后通过 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适配器的描述:

  1. 在顶层设备树文件中引用i2c5 节点,并进行如下修改
&i2c5 {
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&i2c5_pins_a>;
	pinctrl-1 = <&i2c5_pins_sleep_a>;
	status = "okay";
};
  1. 在 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驱动代码的流程如下:

  1. 打开 i2c_dev 设备
  2. 检查适配器是否支持所需的功能
  3. 通过ioctl设置从机地址
  4. 通过ioctl配置AP3216C寄存器,以初始化AP3216C
  5. 通过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 = &reg;
	//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;
}

上机测试

  1. 修改设备树(设备树需要结合硬件进行修改,主要是使能AP3216C所在的I2C总线),然后编译设备树,并用新的设备树启动
  2. 这里下载代码,并进行编译,然后拷贝到目标板根文件系统的root目录
  3. 通过AP3216C得知其I2C地址为7位地址模式,从机地址是0X1E在这里插入图片描述
  4. 通过i2cdetect -ya 0命令探测到0x1E的/dev/i2c-1所对应的适配器下
    在这里插入图片描述
  5. 执行命令./ap3216.out 1即可完成对AP3216C的初始化,并不断读取其IR、ALS、PS寄存器的值。
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值