Linux驱动学习笔记之I2C通信(观b站讯为电子有感)
1 I2C通信
1.1 I2C通信的介绍
1.1.1 I2C通信的定义
I2C 是很常用的一个串行通信接口,用于连接各种外设、传感器等。I2C 总线仅仅使用时钟线SCL(Serial Clock)、数据线SDA (Serial Data)这两根信号线就实现了设备之间的数据交互,极大地简化了对硬件资源和 PCB 板布线空间的占用。本文主要介绍中在 Linux 内核中如何使用 I2C 的驱动框架。
1.1.2 I2C通信的特点
I2C通信是一种同步、半双工的通信方式,支持总线挂载多设备(一主多从、多主多从)。如下图所示,所有I2C设备的SCL连在一起,SDA连在一起;设备的SCL和SDA均要配置成开漏输出模式;SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右。从设备下拉,总线处于低电平;从设备放开,从设备相当于浮空,但由于上拉电阻作用,总线处于高电平。只要有一个从设备下拉,总线就处于低电平。

1.2 Linux里面的I2C通信
1.2.1 Linux中的I2C节点
不同于单片机使用I2C,使用Linux的I2C通信不需要去利用管脚模拟时序,Linux提供I2C控制器,我们只需要关注如何读写数据就行了。Linux的原则是一切皆文件,所以开发板上的I2C外设也以文件的形式存在,我们打开相应的I2C设备节点。

1.2.2 使用I2C读取触摸芯片FT5X06数据
我们这里使用I2C与触摸芯片FT5X06进行通信,查找原理图FT5X06对应的是I2C2,对应节点i2c-1。我们与FTX06芯片进行通信只需要操作/dev/i2c-1节点就行。我们这里对其状态寄存器TD_STATUS进行读取数据。

应用层操作I2C是以数据包进行交流的,对应的结构体是i2c_rdwr_ioctl_data,其中
struct i2c_rdwr_ioctl_data
{
struct i2c_msg __user *msgs; /*要发送的数据包ic_msg指针 */
__u32 nmsgs; /* 要发送的数据包i2c_msg个数 */
};
struct i2c_msg
{
__u16 addr; /* 从机地址*/
__u16 flags; /* flags 为读写标志位,如果 flags 为 1,则为读,反之为 0,则为写*/
__u16 len; /* buf的大小单位为字节*/
__u8 *buf; /* 发送或接收缓冲区*/
};
以下是应用层实现I2C通信的完整代码及注释:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
int fd;
int ret;
/**
* @description: i2c_read_data i2c 读数据
* @param {unsignedint} slave_addr:从机设备的地址
* @param {unsignedchar} reg_addr:寄存器的地址
* @return {*}
*/
int i2c_read_data(unsigned int slave_addr,unsigned char reg_addr)
{
unsigned char data; //用于存取读到的值
//定义一个要发送的数据包 i2c_read_lcd
struct i2c_rdwr_ioctl_data i2c_read_lcd;
//定义初始化 i2c_msg 结构体
//定义两个数据包,先写是为了找到具体的从机的哪个寄存器,以便从这个寄存器里面读取数据
struct i2c_msg msg[2] = {
[0] = {
.addr = slave_addr, //设置从机额地址
.flags = 0, //设置为写
.buf = ®_addr, //设置寄存器的地址
.len = sizeof(reg_addr)//设置寄存器的地址的长度
},
[1] = {
.addr = slave_addr, //设置从机额地址
.flags = 1, //设置为读
.buf = &data, //设置寄存器的地址
.len = sizeof(data)}, //设置寄存器的地址
};
//初始化数据包的数据
i2c_read_lcd.msgs = msg;
//初始化数据包的个数
i2c_read_lcd.nmsgs = 2;
//操作读写数据包
ret = ioctl(fd, I2C_RDWR, &i2c_read_lcd);
if (ret < 0)
{
perror("ioctl error ");
return ret;
}
return data;
/*
这里data为什么一会char,一会int
C 语言中存在整数提升的规则,即任何小于 int 的整数类型在表达式中会被自动提升为 int 类型。
int 类型的范围通常能够容纳 unsigned char 类型的所有可能取值。
char 和 unsigned char 类型通常用于表示小范围的整数或字符
*/
}
int main(int argc,char const* argv[])
{
int TD_STATUS;
struct input_event test_event;
fd = open("/dev/i2c-1",O_RDWR);//查看原理图触摸芯片FT5X06对应的I2C的i2c-1
if(fd < 0)
{
perror("open error \n");
return fd;
}
while(1){
//i2C 读从机地址为 0x38,寄存器地址为 0x02 的数据
//我们从数据手册中得知 TD_STATUS 的地址为 0x02
TD_STATUS = i2c_read_data(0x38, 0x02);
// 打印 TD_STATUS 的值
printf("TD_STATUS value is %d \n", TD_STATUS);
sleep(1);
}
close(fd);
return 0;
}
2 Linux I2C驱动
Linux中的I2C也是按照平台总线模型设计的,之前的platform总线模型驱动包括device.c和driver.c。但是I2C总线这里的device不叫device而是叫client。
platform是虚拟出的一条总线,而i2c是真实的,直接使用i2c总线即可。
所以,I2C总线模型的驱动编写需要client.c和driver.c。在这里我们去操作触摸芯片FT5X06的驱动程序。
2.1 Linux I2C驱动(非设备树实现)
开始之前注意在设备的文件中注释掉ft5x06的相关节点,还要去出内核里面编译进去的FT5X06的驱动,重新烧录不带有FT5X06驱动模块的内核。完整的client.c直接看代码,大致记一下,具体的