一.简介:
1.为I2C从设备驱动设计的统一驱动开发模型;
2.为I2C从设备提供统一的编程方法;
二.I2C传输数据的格式
1>写操作:
白色背景表示:“主==>从”
灰色背景表示:“从==>主”
流程:
确定与哪个从设备通信:
- 主芯片发出一个start信号;
- 然后发出一个设备地址+方向
(设备地址:用来确定是往哪个芯片写数据;方向:读/写,0表示写,1表示读) - 从设备回应(用来确定这个设备是否存在)
确定从设备,开始数据传输
- 主设备发送一个字节数据给从设备,等待从设备的回应;
- 每传输一个字节数据,接收方要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据.
- 数据发送完之后,主芯片就会发送一个停止信号.
1>读操作:
流程:
确定与哪个从设备通信:
- 主芯片要发出一个start信号;
- 然后发出一个设备地址+方向;
(设备地址:用来确定是网哪个芯片写数据; 方向:读/写,0表示写,1表示读) - 从设备回应(用来确定这个设备是否0存在),然后就可以传输数据;
确定从设备,开始数据传输
- 从设备发送一个字节数据给主机,并等待回应;
- 每传输一个字节数据,接收方要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据.
- 数据发送完之后,主芯片就会发送一个停止信号.
三.I2C信号
I2C协议中数据传输的单位是字节,也就是8位.但是要用到9个时钟:前8个始终是用来传输8位数据的,第9个时钟用来传输回应信号.注意:传输时,先传输的是最高位(MSB).
开始信号(start): SCL为高电平时,SDA由高电平向低电平开始跳变,开始传输数据.
结束信号( P ): SCL为高电平时:SDA由低电平向高电平开始跳变,结束传输数据.
响应信号(ACK): 接收器在接受到8位数据后,在第9个时钟周期,拉低SDA.
SDA上传输的数据在SCL高电平期间保持稳定,SDA上的数据只能在SCL为低电平期间变化.
如何在SDA上实现双向传输?
试想:主芯片通过一根SDA线既可以把数据发给从设备,也可以从SDA上读取数据,连接SDA线的引脚里面必然有两个引脚(发送引脚/接收引脚).
那么主从设备都可以通过SDA发送数据,肯定不能同时发送数据,应如何错开时间?
在9个时钟里:
前8个始终由主设备发送数据的话,第9个始终就由从设备发送数据;
前8个始终由从设备发送数据的话,第9个始终就由主设备发送数据;
举例:
双方设备中,某个设备发送数据的话,另一方怎样才能不影响SDA上的数据?
设备的SDA中有个三极管,使用开极/开漏电路(三极管是开极,CMOS管是开漏,作用是一样的)
如下图:
从真值表和电路图我们可以知道:
当某一个芯片不想影响SDA线时,那就不驱动这个三极管
想让SDA输出高电平,双方都不驱动三极管(SDA通过上拉电阻变为高电平)
想让SDA变成低电平,就驱动三极管
具体举例说明数据是怎么实现双向传输的:
前8个CLK
从设备不要影响SDA,从设备不驱动三极管
主设备决定数据,主设备要发送1时不驱动三极管,要发送0时驱动三极管第9个CLK,由从设备决定数据
主设备不驱动三极管
从设备决定数据,要发出回应信号的话,就驱动三极管让SDA变为0
从这里也可以知道ACK信号是低电平
从上述的例子,怎么在一条SDA线上实现双向传输,这就是SDA线上要使用上拉电阻的原因.
那么为什么SCL也需要使用上拉电阻?
在第9个时钟之后,如果某一方需要更多的时间来处理数据,它可以一直驱动三极管把SCL拉低.
当SCL为低电平时,大家都不应该使用IIC总线,只有当SCL从低电平变为高电平的时候,IIC总线才能被使用.
当它就绪后,就可以不再驱动三极管,这时上拉电阻把SCL变为高电平,其他设备就可以继续使用IIC总线.
对于IIC总线协议它只能规定怎么传输数据,数据是什么含义由从设备决定.
I2C子系统框架
分为三层:
一.i2c-driver层-------i2c从设备驱动层:由我们自己编写(一般原厂BSP工程师都已编写好,可能需要我们适当修改)
作用:
1>>完成和从设备的初始化
2>>和用户进行数据交互:file_operation
2>>知道如何和用户传递数据,但是不知道如何与从设备进行通信
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>二.i2c-core层--------i2c核心层(总线层)内核实现:位置在drivers/i2c/i2c-core.c
作用:
1>>维护i2c子系统
2>>给上下两层提供编程接口,并进行匹配
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> 三.i2c-adpater层(BSP工程师实现)--------i2c适配器层或i2c控制器层:位置在:drivers/i2c/busses/i2c-s3c2410.c
作用:
1>>初始化i2c适配器,让从设备正常工作
2>>提供与硬件通信的方法
3>>解析从设备节点—i2c_client,实现i2c发送和接受数据的方法
如果要利用i2c子系统编写从设备驱动,必须要确保下面两层代码被编译进内核中去
应用层调用驱动传输数据的过程
重要结构体:
struct i2c_driver {
int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
int (*remove)(struct i2c_client *client);
struct device_driver driver;
const struct i2c_device_id *id_table;
};
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)
大家不要误解i2c_driver从设备驱动,不是运行在从设备上的,而是运行在主芯片上,主要作用是:驱动从设备工作(给从机发送数据,让从机正常工作也就是初始化从设备)
下述内容请结合上图理解
i2c_adapter
层里面的传输结构:
BSP工程师实现了一个重要的结构体i2c_adapter,有几个控制器,就会有几个这样的结构体,每个i2c_adapter都可以挂在很多个从设备,这些从设备的描述信息都在设备树中,在I2C控制器下,定义子节点{ compatible:名字; address:地址 }
,这个控制器的驱动就是i2c_stm32f7.c
,这个控制器,就会把上述的子节点解析成一个个i2c_client
,里面就会有地址+名称,每一个子节点都会解析成一个i2c_client,这些i2c_client都是挂载在i2c_adapter下,i2c_client里面是有一个指针指向i2c_adapter.
i2c_adapter
里面要实现alogo
这样一个结构体,这个结构体里面实现了master_xfer()
方法,这个方法是收发数据的.
再将i2c_adapter
注册到虚拟总线i2c_core
中,这个虚拟总线是为了管理上下层,进行上下层匹配的作用,将i2c_client
注册到i2c_core
中.
i2c_driver
从设备层里面的传输结构:
创建一个i2c_driver
结构体,里面有{ probe();remove();id_table;i2c_register_driver();i2c_del_driver() }
创建完成后,也注册进i2c_core
中去,注册进去后,i2c_core
虚拟总线会根据i2c_driver
中的id_table
和i2c_client
中的compatible
进行匹配,如果相同,则匹配成功,成功后,i2c_driver
会通过函数指针调用i2c_driver
中的probe()
函数,probe()
函数被调用,同时会将成功匹配后的i2c_client
当成参数传递给probe()
函数(probe(i2c_client)
),此时从设备层就拿到了i2c_adapter
层解析出来的i2c_client
,由于i2c_client
里面有指针指向i2c_adapter
,也就是说也可以拿到i2c_adapter==>alogo==>master_xfer()
方法,此时就可以收发数据了.
在实际的驱动开发过程中
应先申请设备号+创建类+创建设备节点,进而实现fops
,fops
下面有{ read();write(); }
,当应用程序通过read()/write()
调用的时候,就会调用到驱动里面的read()/write()
,在驱动中的read()/write()
里面使用copy_to_user()/copy_from_user()
实现用户空间与驱动进行数据传递.
eg.
在==用户空间(应用层)==使用write()
,有数据需要传递给内核,write()(应)=>wirte()(驱)=>copy_from_user()
,从而拿到应用层的数据;
同时probe()拿到了i2c_client
,将i2c_client
变成全局变量,在write()
(驱)里面通过i2c_client
指针调用i2c_adapter=>alogo=>master_xfer()
;
这就是应用程调用驱动的全过程.
其中注册方法是i2c_core
层提供的方法.
如何找到这两层代码,并且编译进内核中去
顺便说一下目录结构及用途:
i2c-core层在哪里:
i2c-adpater层代码在哪里:
详细讲解请转到这篇文章:
Linux内核目录
如何找到这两层代码:
i2c-core层怎么找:
这个名字是固定的,记住就好.
i2c-adpater层怎么找:
用stm32mp157的芯片举例:
先在设备树里面找I2C的定义:
随后通过回到最上层,再通过grep查找:compatible里面的名字,此时就能找到对应的控制器.C文件
如何将这两层代码编译进内核
i2c-adpater层:
先进busses目录下打开Makefile查看编译宏
随后找i2c-s3c2410.o,由于包含在i2c-stm32f7-drv下面,所以在make menuconfig下面找I2C_STM32F7添加进去就可以了
i2c-core层:
进到i2c目录下:
随后找i2c-core-base.o,由于包含在i2c-core.o下面,所以在make menuconfig下面找I2C添加进去就可以了
此时就需要进行内核裁剪了:make menuconfig
进make menuconfig到这个里面之后,按/键进行查找
随后根据路径一个一个目录进:
最后到那个下面按空格,前面括号显示*好的话就是代表编译进内核;
此时了解一下内核中的menuconfig:
详细请查看该文章:
配置内核必不可少的工具–menuconfig
随后,保存,退出,编译内核,就可以编写最上层i2c-driver层的代码了.
此文章后续还会继续更新…