I2C总线是一种使用起来非常的方便的通信总线,主要由SCL(时钟线),SDA(数据线)组成。本文主要介绍如何在LINUX应用层使用I2C总线与外设芯片通信。实现的方式主要是通过O_RDWR IOTCL实现。
下面的程序代码是基于QT5.12的环境编写的。
写I2C设备的函数实例
int I2C_Dev_Write(int fd, int slave_addr, QByteArray tx_cmd_data)
{
struct i2c_rdwr_ioctl_data work_queue;
int ret;
work_queue.nmsgs = 1;
work_queue.msgs = (struct i2c_msg *)malloc(work_queue.nmsgs * sizeof(struct i2c_msg));
if(!work_queue.msgs)
{
printf("msgs memery alloc error\n");
return -1;
}
if ((work_queue.msgs->buf = (unsigned char *)malloc(tx_cmd_data.length() * sizeof(unsigned char))) == NULL)
{
printf("buf memery alloc error...\n");
free(work_queue.msgs);
return -1;
}
work_queue.msgs->len = tx_cmd_data.length();
work_queue.msgs->flags = 0;
work_queue.msgs->addr = slave_addr;
for(int i=0;i<tx_cmd_data.length();i++)
{
work_queue.msgs->buf[i] = tx_cmd_data.at(i)&0xff;
}
ret=ioctl(fd, I2C_RDWR, (unsigned long) &work_queue);
if(ret < 0)
{
printf("Error during I2C_RDWR ioctl with error code: %d\n", ret);
}
free(work_queue.msgs->buf);
free(work_queue.msgs);
return ret;
}
读I2C设备的函数实例
QByteArray I2C_Dev_Read(int fd,int slave_addr,int reg_addr,int read_data_len)
{
struct i2c_rdwr_ioctl_data work_queue;
QByteArray ret_byte_array;
int ret;
int rx_data_len;
ret_byte_array.clear();
work_queue.nmsgs = 1;
work_queue.msgs = (struct i2c_msg *)malloc(work_queue.nmsgs *sizeof(struct i2c_msg));
if(work_queue.msgs==nullptr)
{
printf("Memery alloc error\n");
return ret_byte_array;
}
rx_data_len = read_data_len;
work_queue.msgs->buf = (unsigned char *)malloc(rx_data_len * sizeof(unsigned char));
if (work_queue.msgs->buf == nullptr)
{
printf("rx buf memery alloc error...\n");
free(work_queue.msgs);
return ret_byte_array;
}
work_queue.msgs->flags = I2C_M_RD;
work_queue.msgs->addr = slave_addr;
work_queue.msgs->len = rx_data_len;
memset(work_queue.msgs->buf,0x00,rx_data_len);
ret = ioctl(fd, I2C_RDWR, (unsigned long) &work_queue);
if(ret < 0)
{
printf("Error during I2C_RDWR ioctl with error code: %d\n", ret);
}
else
{
for(int i=0;i<rx_data_len;i++)
{
ret_byte_array.append(work_queue.msgs->buf[i]&0xff);
}
}
free(work_queue.msgs->buf);
free(work_queue.msgs);
return ret_byte_array;
}
上面I2C设备读写函数主要是通过 “ struct i2c_rdwr_ioctl_data ” 结构和 “ ioctl ” 函数实现的。可以看到上面两个函数的代码十分相似,主要区别在于 “ work_queue.msgs->flags ”和 “work_queue.msgs->buf ” 的赋值,根据I2C总线的规范要求, work_queue.msgs->flags为1时表示读设备数据,I2C从设备按照SCL线的时钟信号,返回内部寄存器的数据,寄存器的起始地址位最近一次写的寄存器地址。因此在对I2C设备读数据之前,应先通过写命令重置I2C设备内部的寄存器指向,使其指向要读的起始地址。
struct i2c_rdwr_ioctl_data 的结构和struct i2c_msg的结构
/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {
struct i2c_msg *msgs; /* pointers to i2c_msgs */
__u32 nmsgs; /* number of i2c_msgs */
};
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RD 0x0001 /* read data, from slave to master */
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
在struct i2c_msg的结构中len成员的值为要读(或者写)的数据长度, 当flags成员的值为1时, *buf成员用于保存读到的数据;当时flags成员为0时,*buf成员中的数据为要写到I2C设备的数据。在写I2C设备时,*buf成员的第0个字节即buf[0]为要写入的寄存器地址,从buf[1]才是要写入的数据;addr成员为I2C设备的通信地址(7bit),“ ioctl ” 函数会对I2C设备地址左移并或上读写标记位。
基于Max44009数字光亮度传感器的读写实例
static unsigned char gy_49_tx_cmd01[8]={0x01,0x00}; //禁用中断
static unsigned char gy_49_tx_cmd02[8]={0x02,0x80}; //初始化芯片为自动模式
void MAX44009::Init(char dev_name[], U8 addr)
{
m_addr=addr; // 设备的I2C通信地址
m_fd=I2C_Dev_Open(dev_name); //打开I2C设备,m_fd为文件描述符,后面读写操作时会用到
if(m_fd==-1)
{
printf("MAX44009 Open I2C Bus Error\n");
}
}
double MAX44009::Read_LUX(void)
{
int value_e,value_m,ret;
double ret_value;
I2CWrite(m_addr,0x02,&gy_49_tx_cmd02[1],1);
for(int i=0;i<8;i++) //依次读取芯片内部8个寄存器的数据
{
I2CWrite(m_addr,(U8)(i&0xff),nullptr,0); //发送写命令,目的在于重置芯片内部的寄存器指针
ret=I2CRead(m_addr,(U8)(i&0xff),&m_reg_map[i],1);//发送读命令,读取一个寄存器的数据
}
value_e=m_reg_map[LUX_HIGH_REG_ADDR]>>4; //计算光亮度
value_m=m_reg_map[LUX_HIGH_REG_ADDR];
value_m<<=4;
value_m|=(m_reg_map[LUX_LOW_REG_ADDR]&0x0f);
ret_value=pow(2,value_e)*value_m+0.045;
return ret_value;
}
void MAX44009::I2CWrite(U8 addr,U8 reg_addr,U8 buf[],U8 len) //类中的写方法
{
int tx_len;
unsigned char tx_buf[128];
tx_buf[0]=reg_addr;
if(len>0)
{
for(int i=0;i<len;i++)
{
tx_buf[i+1]=buf[i];
}
}
tx_len=len+1;
I2C_Dev_Write(m_fd,addr,tx_buf,tx_len);
}
int MAX44009::I2CRead(U8 addr, U8 reg_addr, U8 rbuf[], U8 rlen) //类中的读方法
{
int ret;
ret=I2C_Dev_Read(m_fd,addr,reg_addr,rbuf,rlen);
return ret;
}
运行结果
root@orangepione:~# ./test_i2c_app
Read LUX From MAX44009
LUX=21.045000 //打开手电筒照射Max44009之前
LUX=22.045000
LUX=78.045000
LUX=75.045000
LUX=862.045000
LUX=6060032.045000 //打开手电筒照射Max44009之后
LUX=6057984.045000
LUX=6047744.045000
LUX=6864.045000
LUX=55.045000 //关闭手电筒之后
LUX=46.045000
LUX=28.045000
从上面的运行结果可以看出,MAX44009芯片对于外部环境的光源亮度还是很敏感的。做个简单的环境检测方面的应用是绰绰有余的。