做linux 嵌入式驱动,离不开调试i2c 外设,这里对i2c做一下3点总结:
1.先要知道i2c的4个信号;
a) 起始信号:当 SCL 线是高电平时 SDA 线从高电平向低电平切换。
b) 停止信号:当 SCL 线是高电平时 SDA 线由低电平向高电平切换。
c) ACk信号: 当scl线由低变高时候,SDA线保持低电平;
d) NACK信号:当scl线由低变高时候,SDA线保持高电平;
2、通信接口
i2c发送或者接收一次数据都以数据包 struct i2c_msg 封装
[cpp]
struct i2c_msg {
__u16 addr; // 从机地址
__u16 flags; // 标志
#define I2C_M_TEN 0x0010 // 十位地址标志
#define I2C_M_RD 0x0001 // 接收数据标志
__u16 len; // 数据长度
__u8 *buf; // 数据指针
};
其中addr为从机地址;flags则是这次通信的标志,发送数据为0,接收数据则为 I2C_M_RD;len为此次通信的数据字节数;buf 为发送或接收数据的指针。在设备驱动中我们通常调用 i2c-core 定义的接口 i2c_master_send 和 i2c_master_recv 来发送或接收一次数据。
[cpp]
int i2c_master_send(struct i2c_client *client,const char *buf ,int count)
{
int ret;
struct i2c_adapter *adap=client->adapter; // 获取adapter信息
struct i2c_msg msg; // 定义一个临时的数据包
msg.addr = client->addr; // 将从机地址写入数据包
msg.flags = client->flags & I2C_M_TEN; // 将从机标志并入数据包
msg.len = count; // 将此次发送的数据字节数写入数据包
msg.buf = (char *)buf; // 将发送数据指针写入数据包
ret = i2c_transfer(adap, &msg, 1); // 调用平台接口发送数据
return (ret == 1) ? count : ret; // 如果发送成功就返回字节数
}
EXPORT_SYMBOL(i2c_master_send);
i2c_master_send 接口的三个参数:client 为此次与主机通信的从机,buf 为发送的数据指针,count 为发送数据的字节数。
[cpp]
int i2c_master_recv(struct i2c_client *client, char *buf ,int count)
{
struct i2c_adapter *adap=client->adapter; // 获取adapter信息
struct i2c_msg msg; // 定义一个临时的数据包
int ret;
msg.addr = client->addr; // 将从机地址写入数据包
msg.flags = client->flags & I2C_M_TEN; // 将从机标志并入数据包
msg.flags |= I2C_M_RD; // 将此次通信的标志并入数据包
msg.len = count; // 将此次接收的数据字节数写入数据包
msg.buf = buf;
ret = i2c_transfer(adap, &msg, 1); // 调用平台接口接收数据
return (ret == 1) ? count : ret; // 如果接收成功就返回字节数
}
EXPORT_SYMBOL(i2c_master_recv);
i2c_master_recv 接口的三个参数:client 为此次与主机通信的从机,buf 为接收的数据指针,count 为接收数据的字节数。我们看一下 i2c_transfer 接口的参数说明:
[cpp
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
其中 adap 为此次主机与从机通信的适配器;msgs 为通信的数据包,这里可以是单个或多个数据包;num 用于指定数据包的个数,如果大于1则表明将进行不止一次的通信。通信一次就需要寻址一次,如果需要多次通信就需要多次寻址,前面2个接口都是进行一次通 信,所以 num 为1;有的情况下我们要读一个寄存器的值,就需要先向从机发送一个寄存器地址然后再接收数据,这样如果想自己封装一个接口就需要将 num 设置为2。接口的返回值如果失败则为负数,如果成功则返回传输的数据包个数。
首先我们先来看一下写数据的时序图:进行了1次寻址操作
结合I2C总线协议的知识,我们可以知道OZ9350的I2C写数据由一下10个步骤组成。
第一步,发送一个起始信号。
第二步,发送7bit从机地址,即OZ9350的地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+r/w。最低位为1表示读,为0表示写。
第三步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
第四步,发送寄存器地址,8bit数据。
第五步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
第六步,发送一个数据,8bit数据。
第七步,产生一个ACK应答信号,此应答信号为从机器件产生的应答信号。
第八步,发送一个CRC校验码,此CRC校验值为2、4、6步数据产生的校验码。
第九步,既可以发送一个应答信号,也可以发送一个无应答信号,均有从机器件产生。
第十步,发送一个停止信号。
读数据的时序图如下图所示:进行了2次寻址操作
通过分解后的时序图,可以看到OZ9350的读数据由以下13个步骤组成。
第一步,发送一个起始信号。
第二步,发送7bit从机地址,即OZ9350的地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+r/w。最低位为1表示读,为0表示写。
第三步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
第四步,发送寄存器地址。
第五步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
第六步,再次发送一个骑士信号。
第七步,发送7bit从机地址,即OZ9350的地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+r/w。最低位为1表示读,为0表示写。
第八步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
第九步,读取一个字节(8bit)的数据。
第十步,产生一个ACK应答信号,此应答信号为CPU产生。
第十一步,读取一个CRC校验码。
第十二步,产生一个NACK信号。此无应答信号由CPU产生。
第十三步,产生一个停止信号。
3.I2C读写设备寄存器封装例子(短包:向8bit地址,读写8bit位的数据,读写函数原型返回值都是int):
rt5651_i2c_write(0x02, 0x08);
int cydat1[1] = {0};
cydat1[0] = rt5651_i2c_read(0xFF);
static int rt5651_i2c_write(u8 reg, u8 writedata) //由上知识可知,要进行1次寻址操作
{4.I2C读写设备寄存器封装例子(长包:向8bit地址,读写16bit位的数据,读写函数原型返回值都是指针型*int):
//向0xff寄存器里面写0x1f2c
u8 Wdata[3];
Wdata[0]=0x1f;
Wdata[1]=0x2c;
ALC5623_WriteReg(0xff, Wdata);
u8 * rt5651id;
rt5651id=ALC5623_ReadReg(0xFF);
printk("rt5651_i2c_probe rt5651id[0]=%x,rt5651id[1]=%x\n",rt5651id[0],rt5651id[1]); //这里把指针里面的值打印出来了。
下面一共给了2组例子,一个用了指针,一个用数组,很是经典:首先下面是别人的例子:
static void ALC5623_WriteReg( uint8 Reg, uint8 *data)
{
uint16 Ret=0;
uint8 *Wdata = NULL; //1.申请一个指针
Wdata=(uint8 *)PanicUnlessMalloc(4); //2.给指针开辟一个4个u8的空间,其实这里相当于申请一个有4个u8数据的数组
Wdata[0]=Reg;
memcpy(&Wdata[1], data, 2); //这里非常巧妙,很有意思,指针这么好用,跟数据一样;
Ret = I2cTransfer( ALC5623_addr |Codec_Write, Wdata,3, NULL,0);
free(Wdata); //3.之前有申请了空间,所以这里要释放
}
static uint8* ALC5623_ReadReg( uint8 Reg)
{
uint8 * ReadREG= malloc(16);
uint8 REGADDR[1];
memset(ReadREG,0,16);
REGADDR[0]=Reg;
I2cTransfer( ALC5623_addr |Codec_Write, REGADDR,1, NULL,0);
I2cTransfer( ALC5623_addr |Codec_Read, NULL,0, ReadREG,2);
return ReadREG;
}
它山之石可以攻玉:本人参照上面的例子写的,已在mt6737 andorid_m0上验证通过:
static int ALC5623_WriteReg( u8 Reg, u8 *data)
{
int ret = 0;
u8 databuf[4] = {0};
databuf[0] = Reg;
memcpy(&databuf[1], data, 2);
ret = i2c_master_send(rt5651_i2c_client, databuf, 3);