I2C基础介绍及linux驱动框架

I2C基础介绍及linux驱动框架



前言

创造的乐趣常常在于将无聊转为有趣


一、I2C简介

I2C总线是Philips公司在八十年代初推出的一种串行、半双工的总线,主要用于近距离、低速的芯片之间的通信。有两根双向的信号线,SDA用于收发数据,SCL用于双方时钟的同步。下图显示嵌入式系统典型I2C总线应用。
在这里插入图片描述

二、I2C的速率

I2C协议可以工作在以下5种速率模式下,不同的器件可能支持不同的速率。【bps:bit/s,即SCL的频率】
标准模式(Standard):100kbps
快速模式(Fast):400kbps
快速模式+(Fast-Plus):1Mbps
高速模式(High-speed):3.4Mbps
超快模式(Ultra-Fast):5Mbps(单向传输)
其中超快模式是单向数据传输,通常用于LED、LCD等不需要应答的器件,和正常的I2C操作时序类似,但是只进行写数据,不需要考虑ACK应答信号。

三、I2C协议术语

1、起始信号(START)

顾名思义,也就是I2C 通信起始标志,通过这个起始位就可以告诉I2C 从机,“我”要开始进行I2C 通信了。在SCL 为高电平的时候,SDA 出现下降沿就表示为起始位,如图所示:
在这里插入图片描述

2、停止信号(STOP)

停止位就是停止I2C 通信的标志位,和起始位的功能相反。在SCL 位高电平的时候,SDA 出现上升沿就表示为停止位,如图 所示:
在这里插入图片描述

3、数据传输

I2C 总线在数据传输的时候要保证在SCL 高电平期间,SDA 上的数据稳定,因此SDA 上的数据变化只能在SCL 低电平期间发生,如图所示:
在这里插入图片描述

4、应答信号(ACK)

传输过程中的应答信号时序如下图所示,当检测到start信号后,在随后的8个时钟周期,SDA线进行一次8bit的数据传输,若接收到相应的8bit信号,则在第9个时钟周期拉低SDA信号,并视为一次ack应答信号。
在这里插入图片描述

5、非应答信号(NACK)

当在ACK/NACK相关时钟周期期间SDA线保持高电平时,这被解释为NACK。 有几个条件会导致生成NACK:
1、 通信方无法接收或发送,因为它正在执行某些实时功能,并且尚未准备好开始与主站通信。
2、在传输期间,接收方获取它不理解的数据或命令。
3、在传输期间,接收方不能再接收任何数据字节。
4、主接收器完成读取数据并通过NACK向从设备指示
在这里插入图片描述

四、I2C 时序

1、写时序

主机通过I2C 总线与从机之间进行通信不外乎两个操作:写和读,I2C 总线单字节写时序如图所示:
在这里插入图片描述
写时序具体步骤:
(1)开始信号。
(2)发送I2C 设备地址,每个I2C 器件都有一个设备地址,通过发送具体的设备地址来决定访问哪个I2C 器件。这是一个8 位的数据,其中高7 位是设备地址,最后1 位是读写位,为1 的话表示这是一个读操作,为0 的话表示这是一个写操作。
(3)I2C 器件地址后面跟着一个读写位,为0 表示写操作,为1 表示读操作。
(4)从机发送的ACK 应答信号。
(5)发送要写写入数据的寄存器地址。
(6)从机发送的ACK 应答信号。
(7)发送要写入寄存器的数据。
(8)从机发送的ACK 应答信号。
(9)停止信号。

2、读时序

I2C 单字节读时序比写时序要复杂一点,读时序分为4 大步,第一步是发送设备地址,第二步是发送要读取的寄存器地址,第三步重新发送设备地址,最后一步就是I2C 从器件输出要读取的寄存器值,I2C 总线单字节读时序如图所示:
在这里插入图片描述
读时序具体过程:
(1)主机发送起始信号。
(2)主机发送要读取的I2C 从设备地址。
(3)读写控制位,因为是向I2C 从设备发送要读取的寄存器地址,因此是写信号。
(4)从机发送的ACK 应答信号。
(5)主机发送要读取的寄存器地址。
(6)从机发送的ACK 应答信号。
(7)重新发送START 信号。
(8)重新发送要读取的I2C 从设备地址。
(9)读写控制位,这里是读信号,表示接下来是从I2C 从设备里面读取数据。
(10)从机发送的ACK 应答信号。
(11)从I2C 器件里面读取到的数据。
(12)主机发出NACK 信号,表示读取完成,不需要从机再发送ACK 信号了。
(13)主机发出STOP 信号,停止I2C 通信。

3、多字节读写时序

有时候我们需要读写多个字节,多字节读写时序和单字节的基本一致,只是在读写数据的时候可以连续发送多个自己的数据,其他的控制时序都是和单字节一样的。

4、7位和10位从设备地址

大多数I2C器件支持7位地址模式,有一些器件还支持10位地址,而且两种类型的器件可以连接在同一个I2C总线上,目前10位地址的器件还没有被广泛使用。主机发送,从机接收。
(1)使用10位地址进行写时序:
在这里插入图片描述

(2)主机接收,从机发送。使用10位地址进行读时序:

在这里插入图片描述

五、I2C单片机中应用

1 、主设备I2C应用
以下图是嵌入式项目中最经常应用I2C主设备的一种方式,以GD32E230G芯片作为MCU(微控制单元 Microcontroller Unit),MCU通过I2C获取CT75温度传感器的数据,由于需要由mcu端主动发起获取温度的数据,所以,MCU做主设备,CT75做从设备。编写程序具体步骤:
(1)初始化MCU I2C接口
(2)编写获取温度传感器读写接口
(3)进行寄存器配置,读取温度数据
在这里插入图片描述
在这里插入图片描述

2、从设备I2C应用MCU作为从设备,与交换机用I2C进行通信传输,MCU采用I2C从设备中断方式接收交换机的数据。交换机作为主设备,由它发起开始读取或者写入数据到loopback中的eeprom。编写程序步骤:
(1)初始化MCU I2C从设备接口,配置中断
(2)根据光模块spec,MCU模拟出光模块的memory map信息
(3)MCU中I2C中断函数处理交换机发送的信息请求

六、I2Clinux驱动应用

1、I2C驱动整体框架图

在这里插入图片描述
上图是I2C系统的整体框架,介绍如下:
(1)最上层是应用层,在应用层用户可以直接用open read write对设备进行操作,
(2)往下是设备驱动层,这个就是外围的比如一些用I2C总线连接到SOC的传感器或者EEPROM的驱动程序,这个一般由普通驱动工程师负责,
(3)再往下的I2C-Core是核心层,这个是Linux内核源码里面本来就有的,这里面主要是一些驱动和设备的注册函数以及i2c_transfer函数,
(4)再往下就是I2C控制器驱动,这个一般是由芯片原厂的程序员负责编写,当然有些时候比如fpga的驱动的I2C controller部分,我们也是需要写这个I2C控制器驱动。
(5)再往下就是具体的硬件了。
在这里插入图片描述
上图是整体的驱动程序调用过程,左边是i2c控制器驱动部分,中间i2c bus,右边 i2c 设备驱动。

2、I2C控制器

(1)I2C控制器设备
I2C控制器在内核中也被看做一个设备;
在arm架构平台上,只需要完成设备树上的配置,内核初始化会加载设备树,并把I2C节点转换成platform_device,完成i2c设备注册。
举个imx6ull设备的i2c设备,打开内核的./Linux-4.9.88/arch/arm/boot/dts/imx6ull.dtsi设备树文件如下:

 i2c1: i2c@021a0000 {
            #address-cells = <1>;
            #size-cells = <0>;
            compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
            reg = <0x021a0000 0x4000>;
            interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
            clocks = <&clks IMX6UL_CLK_I2C1>;
            status = "disabled"; #实际使用的时候这个地方要改成"okay"。
        };

(2)I2C控制器驱动
通过前面 i2c1 节点的 compatible 属性值 可以在 Linux 源码里面找到对应的驱动文件。这里 i2c1节点的compatible 属性值有两个:“fsl,imx6ul-i2c”和“fsl,imx21-i2c”,在 Linux 源码中搜索这两个字符串即可找到对应的驱动文件。
I.MX6U 的 I2C 适配器驱动驱动文件为 drivers/i2c/busses/i2c-imx.c,上面i2c控制器设备最终被转成了platform_device,那么i2c控制器驱动采用的也是platform_driver,挂载在platform_bus_type.
看一个驱动先从入口函数开始看,我们找到drivers/i2c/busses/i2c-imx.c文件中的i2c_adap_imx_init函数,首先调用platform_driver_register(&i2c_imx_driver)注册i2c_imx_driver结构体,具体的函数调用关系如下,然后当match函数发现驱动和设备匹配,就会调用驱动里面的额probe函数,也就是i2c_imx_probe函数

static struct platform_driver i2c_imx_driver = {
        .probe = i2c_imx_probe,
        .remove = i2c_imx_remove,
        .driver = {
                .name = DRIVER_NAME,
                .pm = I2C_IMX_PM_OPS,
                .of_match_table = i2c_imx_dt_ids,
        },
        .id_table = imx_i2c_devtype,
};

这里i2c_imx_probe大致内容如下:
1、申请i2c_adapter,初始化。
2、申请irq
3、初始完成i2c adapter后,使用i2c_add_adapter或者i2c_add_numbered_adapter来向内核注册I2C控制器驱动。
更具体的注册过程可以看内核源码

3、I2C设备

(1)i2c-client(i2c设备)

i2c-client来自设备树文件,一般放在i2c节点里面的子节点,比如下面的ap3216设备.
&i2c1 {
 ap3216c@1e {
 compatible = "lite-on,ap3216c";
 reg = <0x1e>;
 };/i2c里面的子节点,就是用来表示i2c设备的/
};
&i2c1 {
 clock-frequency = <100000>;
 pinctrl-names = "default";
 pinctrl-0 = <&pinctrl_i2c1>;
 status = "okay";
};/这个是用来表示i2c控制器的,不是i2c设备的/

I2c总线节点下的子节点不会被转成platform_device,他们是由I2C总线驱动程序来处理, 把I2C下的设备节点转成client其实是i2c控制器驱动程序里面的probe函数来做的,前面已经分析过probe函数内部的流程,其中中间部分的of_i2c_register_devices函数就是用来增加i2c-client的。

(2)i2c-driver (i2c设备驱动)
i2c­­_driver初始化与注册,IIC驱动程序就是初始化i2c_driver,然后向系统注册。注册使用i2c_register_driver、i2c_add_driver,如果注销i2c_driver使用i2c_del_driver。
当i2c设备驱动跟设备匹配成功后,调用prode函数,通过i2c_transfer函数进行I2C数据传输.
注册的代码流程框架如下:

/* i2c驱动的probe函数 */ 
static int xxx_probe(struct i2c_client *client,const struct i2c_device_id id) 
{ 
 / 函数具体程序 */ 
 return 0;
}
/* i2c驱动的remove函数 */ 
static int xxx_remove(struct i2c_client *client) 
{ 
 /* 函数具体程序 */ 
 return 0;
}
/* 传统匹配方式ID列表 */ 
static const struct i2c_device_id xxx_id[] = { 
 {"xxx", 0},
 {} 
};
/* 设备树匹配列表 / 
static const struct of_device_id xxx_of_match[] = { 
 { .compatible = "xxx" }, 
 { / Sentinel */ } 
};
/* i2c驱动结构体 */ 
static struct i2c_driver xxx_driver = { 
 .probe = xxx_probe, 
 .remove = xxx_remove, 
 .driver = { 
 .owner = THIS_MODULE, 
 .name = "xxx", 
 .of_match_table = xxx_of_match, 
 }, 
 .id_table = xxx_id, 
 };
/* 驱动入口函数 */ 
static int __init xxx_init(void)
{ 
 int ret = 0; 
 ret = i2c_add_driver(&xxx_driver);
 return ret; 
}
/* 驱动出口函数 */ 
static void __exit xxx_exit(void) 
{ 
 i2c_del_driver(&xxx_driver); 
}

总结

以上是i2c的基础知识,后续会写篇i2c注册设备方法,好久没更知识,正如今天看到的一句话:
想,都是问题,做,才有答案!加油!


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值