Linux驱动:I2C子系统

一.简介:

1.为I2C从设备驱动设计的统一驱动开发模型;
2.为I2C从设备提供统一的编程方法;

二.I2C传输数据的格式

1>操作:

白色背景表示:“主==>从”
灰色背景表示:“从==>主”
在这里插入图片描述

流程:

确定与哪个从设备通信:
  1. 主芯片发出一个start信号;
  2. 然后发出一个设备地址+方向
    (设备地址:用来确定是往哪个芯片写数据;方向:读/写,0表示写,1表示读)
  3. 从设备回应(用来确定这个设备是否存在)
确定从设备,开始数据传输
  1. 主设备发送一个字节数据给从设备,等待从设备的回应;
  2. 每传输一个字节数据,接收方要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据.
  3. 数据发送完之后,主芯片就会发送一个停止信号.

1>操作:

在这里插入图片描述

流程:

确定与哪个从设备通信:
  1. 主芯片要发出一个start信号;
  2. 然后发出一个设备地址+方向;
    (设备地址:用来确定是网哪个芯片写数据; 方向:读/写,0表示写,1表示读)
  3. 从设备回应(用来确定这个设备是否0存在),然后就可以传输数据;
确定从设备,开始数据传输
  1. 从设备发送一个字节数据给主机,并等待回应;
  2. 每传输一个字节数据,接收方要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据.
  3. 数据发送完之后,主芯片就会发送一个停止信号.

三.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_tablei2c_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-s3c2410.c

i2c-core层:

进到i2c目录下:
在这里插入图片描述

在这里插入图片描述
随后找i2c-core-base.o,由于包含在i2c-core.o下面,所以在make menuconfig下面找I2C添加进去就可以了

此时就需要进行内核裁剪了:make menuconfig
进make menuconfig到这个里面之后,按/键进行查找

在这里插入图片描述

在这里插入图片描述
随后根据路径一个一个目录进:
在这里插入图片描述
最后到那个下面按空格,前面括号显示*好的话就是代表编译进内核;

此时了解一下内核中的menuconfig:

在这里插入图片描述
在这里插入图片描述
详细请查看该文章:
配置内核必不可少的工具–menuconfig

随后,保存,退出,编译内核,就可以编写最上层i2c-driver层的代码了.

此文章后续还会继续更新…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值