《嵌入式驱动(十一):I2C子系统架构》
一、通信协议
I2c,是一种硬件设备常用的通信方式。
1. 几种通信形式的比较
**tips:
串行:硬件结构简单、低速通信、超高速通信
并行:硬件结构复杂、高速通信
同步:通信双方时钟保持一致
异步:双方各自拥有独立的时钟信号
全双工:有收发俩根线
半双工:只有一根线,不能同时收发
1) UART:串行,异步,全双工通信;多台不同终端设备间的通信;波特率为115200时,速率为10k(一位起始信号+8位数据位+1位停止位);
可以通过MAX232和MAX485芯片进行增压,实现远距离传输。
RS232点对点(一对一),主要用于上位机和下位机的通信
RS485总线(一对多)
2) I2C:串行,同步,半双工通信;同一硬件平台,不同芯片间的通信;数据量较少时使用I2C(传感器);100k、400k、1M;
3) SPI:串行,同步,全双工通信;同一硬件平台,不同芯片间的通信;数据量较大时使用SPI(存储、显示屏);1M - 十几M;
4)速率更快的方式;并行传输;USB;网口(千M);
2. I2C与SPI之间的区别
1)通信速率:
SPI > I2C
2. I2C占用的硬件资源较少
1) I2c需要SCL、SDA、GND
2) SPI占用的硬件资源较多SCL、MOSI、MISO、CS、 GND
3) SPI全双工、I2C半双工
3. I2C基础知识
1)I2C有几根线?
SCL、SDA、GND
2)I2C硬件接线有哪些需要注意事项?
I2C SCL和SDA工作在开漏模式,必须加上拉电阻(1k - 10k,常用的有4.7k、10k)
3) 软件I2C还是硬件I2C?
拿GPIO模拟I2C时序,称为软件I2C;使用I2c控制器生成I2C时序,称为硬件I2C (我们使用硬件I2C)
4)IMX6uLL有几个I2c控制器?
4个I2C控制器
5)I2C总线传输?
通信时只支持一主多从,理论上支持多主多从
6) I2C通信时序?
主机提供SCL时钟信号
I2C通信只能由主机发起
一次I2C通信方向不能改变
起始信号:SCL为高,SDA由高到低
结束信号:SCL为高,SDA由低到高
发送数据信号:
SCL为高,SDA采样;SCL为低,SDA变化
I2C通信每传输1个字节,需要发送ACK/NACK
7) I2C总线上面可以挂多少个设备?
每个设备都要有从机地址,从机地址是7位, 2^7 -1 (有一位是广播地址) 127个从机
8) I2C通信过程
写1个字节 
写多个字节
读1个字节![]()
读多个字节![]()
9) 基于I2c UserSpace空间实现传感器读取:
I2c控制器驱动由芯片厂商已经写好了,所以我们编写应用层代码
10)基于I2c子系统编写 设备驱动:
编写I2c子系统驱动框架分为3个大的功能结构:
I2c控制器驱动(i2c_adapter),NXP已经写好,基于platform总线结构编写的
I2c核心层 (i2c_bus_type),Linux系统编写
I2c设备驱动(i2c_client),由我们编写,基于i2c子系统架构编写
二、连接方式

三、函数接口
1.ioctl(fd, I2C_SLAVE, addr)
将指定的 I2C 设备文件描述符(fd)绑定到特定从机地址(addr),后续的读写操作均针对该地址设备,使用前需确保已包含 <linux/i2c-dev.h> 头文件。
2.i2c_add_driver 向I2c子系统注册驱动,并触发设备匹配流程
i2c_add_driver(driver);
3.i2c_del_driver 注销I2c驱动,释放资源并解除设备绑定
void i2c_del_driver(struct i2c_driver *driver);
4.i2c_transfer 执行单次或多次I2C消息传输,适用于复杂时序操作
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
5.i2c_master_send 封装单次写操作,适用于简单数据发送
int i2c_master_send(const struct i2c_client *client, const char *buf, int count);
6.i2c_master_recv 封装单次读操作,适用于简单数据读取
int i2c_master_recv(const struct i2c_client *client, char *buf, int count);
四、步骤

I2C驱动框架介绍(I2C子系统)
i2c_hardware层:具体硬件层(I2C传感器)
i2c_adapter:i2c适配器层,驱动厂家实现
i2c_core:i2c核心层,提供i2c设备和驱动的注册、匹配及通信方法
i2c_client/i2c_driver:遵循platform设备驱动分离思想,匹配成功之后执行probe函数
I2C传感器挂载在对应的I2C总线上,I2C适配器层驱动由芯片厂家实现,I2C核心层用来管理I2C设备和驱动的注册、匹配以及I2C通信方法。设备驱动层通过将i2c总线上的设备和驱动匹配,执行probe函数。应用层调用驱动层接口实现i2c的读取写入
2.I2C子系统中,device和driver匹配的过程
I2c子系统中主要分为三部分:设备I2c_device、总线I2c_bus_type、驱动I2c_driver,总线中有一个match回调函数能够对设备与驱动进行匹配,匹配方法:
(1)根据设备树中的compatible字段与驱动中的of_match_table匹配
(2)ACPI匹配,常用于X86平台
(3)驱动中id_table与设备名匹配
3.流程
I2c控制器驱动(NXP写好),我们要写的是 I2c设备驱动。控制器驱动:基于platform总线架构NXP写的驱动,然后设备树中配置节点,把状态设置为ok,匹配成功后执行probe,最终在I2C核心层(I2C-core)中生成i2c_adapter。接着在设备树中配置自己的设备,和我们自己写的驱动进行匹配,就会生成I2C_client给我们自己的驱动,我们可以通过重写fops来对i2c_client完成一些通信的编写。最后应用层就可以调用他。
五、代码
1.lm75a_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>static struct i2c_client *lm75_client = NULL;static ssize_t lm75_read(struct file *fp, char __user *puser, size_t n, loff_t *off)
{ int ret = 0;unsigned long nret = 0;struct i2c_msg sendmsg;struct i2c_msg recvmsg;unsigned char regaddr = 0x00;unsigned char tempdata[2] = {0};sendmsg.addr = lm75_client->addr; //lm75从机地址sendmsg.flags = 0; //读写标志 0 表示写sendmsg.buf = ®addr; //发送数据(LM75存放温度的寄存器地址)sendmsg.len = 1; //发送数据长度 1ret = i2c_transfer(lm75_client->adapter, &sendmsg, 1); if (nret < 0) {pr_info("i2c_transfer failed\n");return -1;}recvmsg.addr = lm75_client->addr; //lm75从机地址recvmsg.flags |= I2C_M_RD; //读写标志 I2C_M_RD表示读recvmsg.buf = tempdata; //存放读取到的数据空间recvmsg.len = sizeof(tempdata); //读取数据的字节数ret = i2c_transfer(lm75_client->adapter, &recvmsg, 1); if (nret < 0) {pr_info("i2c_transfer failed\n");return -1;}nret = copy_to_user(puser, tempdata, 2);if (nret) {pr_info("copy_to_user failed\n");return -1;}return 2;
}static struct file_operations fops = {.owner = THIS_MODULE,.read = lm75_read,
};static struct miscdevice misc_lm75 = {.minor = MISC_DYNAMIC_MINOR,.name = "misc_lm75",.fops = &fops,
};static int lm75_probe(struct i2c_client *pclient, const struct i2c_device_id *pdevid)
{int ret = 0;lm75_client = pclient;ret = misc_register(&misc_lm75);if (ret) {pr_info("misc_register failed\n");return -1;}pr_info("probe ok\n");return 0;
}static int lm75_remove(struct i2c_client *pclient)
{int ret = 0;ret = misc_deregister(&misc_lm75);if (ret) {pr_info("misc_deregister failed\n");return -1;}pr_info("remove ok\n");return 0;
}static struct i2c_device_id lm75_id_table[] = {{.name = "putelm75"},{},
};static struct of_device_id lm75_of_match_table[] = {{.compatible = "pute,putelm75"},{},
};static struct i2c_driver lm75_drv = {.probe = lm75_probe,.remove = lm75_remove,.driver = {.name = "putelm75",.of_match_table = lm75_of_match_table,},.id_table = lm75_id_table,
};module_i2c_driver(lm75_drv)MODULE_LICENSE("GPL");
MODULE_AUTHOR("pute");2.lm75a_app.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>int main(void)
{int fd = 0;unsigned char data[2] = {0};double temp = 0.0;fd = open("/dev/misc_lm75", O_RDWR);if (-1 == fd){perror("fail to open");return -1;}while (1){read(fd, data, 2);temp = ((((data[0] << 8)) | data[1]) >> 7) * 0.5;printf("temp = %.2lf\n", temp);sleep(1);}close(fd);return 0;
}