i2c驱动

I2C驱动

1、硬件

  • 物理接线

I2C 总线在物理连接上非常简单,分别由 SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对 SCL 和 SDA 线高低电平时序的控制,来产生 I2C 总线协议所需要的信号进行数据的传递。在总线空闲状态时,一般被所接的上拉电阻拉高,保持着高电平。

  • I2C总线特征

I2C 总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址,主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,我们把 CPU 带 I2C 总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。I2C 总线上可挂接的设备数量受总线的最大电容 400pF 限制,如果所挂接的是相同型号的器件,则还受器件地址的限制。I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达 400kbit/s,高速模式下可达 3.4Mbit/s。一般通过 I2C 总线接口可编程时钟来实现传输速率的调整。I2C 总线上的主设备与从设备之间以 8bit 为单位进行双向的数据传输。

  • I2C器件地址

不同的I2C器件有不同的地址设置,可分为 7bit 地址与 10bit 地址
此处以7bit 地址 EEPROM 为例(查看datasheet、硬件原理图):
在这里插入图片描述在这里插入图片描述
前 4bit (1010)为固定位,由厂家决定;之后 3bit (A2、A1、A0)由器件连接方式决定,由上面的硬件原理图可知为 001;第 8bit 为读写位,0为写,1为读。
7bit 地址为:1010 001
读地址:0xA3;写地址:0xA2

2、时序

  • 基本概念

①总线空闲状态:SCL 和 SDA 都保持着高电平;
②起始条件:总线在空闲状态时,当 SCL 为高电平而 SDA 由高到低跳变,表示产生一个起始条件;
③停止条件:当 SCL 为高电平而 SDA 由低到高的跳变,表示产生一个停止条件;
④应答信号:每个字节传输完成后的下一个时钟信号,在 SCL 高电平期间,SDA 为低,则表示一个应答信号;
⑤非应答信号:每个字节传输完成后的下一个时钟信号,在 SCL 高电平期间,SDA 为高,则表示一个非应答信号;

  • 传输过程

不同器件可能存在些许差异,具体看datasheet
此处以 写 EEPROM为例:
在这里插入图片描述产生起始信号–>发送设备地址0xA2–>等待EEPROM应答ACK–>发送寄存器高 8bit 地址–>等待EEPROM应答ACK–>发送寄存器低 8bit 地址–>等待EEPROM应答ACK–>发送要写入的 8bit 数据–>等待EEPROM应答ACK–>发送停止条件

3、I2C整体架构

  • I2C架构概述

下面的框图完整的描述了Linux下 I2C 驱动架构
在这里插入图片描述简化图:
在这里插入图片描述I2C子系统由上到下可大致分为3层:
① I2C 设备驱动层:
由设备层代码 + 驱动层代码构成,可类比平台设备驱动模型
真正实现具体设备的时序的代码
② I2C 核心层:
linux内核对i2c架构抽象了一个叫核心层core的中间件,它分离了设备驱动和硬件控制的实现细节(如操作i2c的寄存器);提供了 I2C 总线驱动和设备驱动的注册、注销方法、 I2C通信方法等;提供了设备驱动层和适配器驱动层需要的API接口,起到了承上启下的作用
主要包含文件:i2c-core.c
③ I2C 适配器驱动层:
一般由芯片厂商提供,对 I2C硬件体系结构中适配器端的实现
主要包含文件:busses文件夹下

  • 相关的重要结构体

①i2c_driver :
定义在 i2c.h 中,内核使用 struct i2c_driver 结构描述一个 i2c 设备驱动,其中必须实现的有 :probe、remove。

struct i2c_driver {
    int (*probe)(struct i2c_client *, const struct i2c_device_id *); //设备匹配成功调用的函数
    int (*remove)(struct i2c_client *);                              //设备移除之后调用的函数
    struct device_driver driver;                                     //设备驱动结构体
    const struct i2c_device_id *id_table;   //设备的ID表,匹配创建的client
};

②i2c_client :
内核使用 struct i2c_client 来描述一个设备信息。每一个 i2c slave device 都需要一个i2c_client 结构体来描述
在内核中通过设备树的方式创建

struct i2c_client {
	unsigned short flags; //标志,比如说设备地址 10 位还是 7 位
	unsigned short addr; //低 7 位为芯片地址
	char name[I2C_NAME_SIZE]; //设备名称,用来与 i2_driver 匹配
	struct i2c_adapter *adapter; //依附的 i2c_adapter,它表示一个 IIC 控制器
	struct i2c_driver *driver; //依附的 i2c_driver
	struct device dev; //设备结构体
	int irq; //设备所使用的中断号
	struct list_head detected; //设备链表
};

③i2c_adapter
i2c总线适配器其实就是一个i2c总线控制器,本质上是一个物理设备,硬件上每一对I2C总线都对应一个适配器来控制它,主要用来完成i2c总线控制器相关的数据通信,由芯片厂商提供

struct i2c_adapter {
	struct module *owner;
	unsigned int class; /*允许探测的类 */
	const struct i2c_algorithm *algo; /*访问总线的函数接口*/
	void *algo_data;
	/* data fields that are valid for all devices */
	struct rt_mutex bus_lock;
	int timeout; /*时间节拍*/
	int retries; /*重试*/
	struct device dev; /* 指向适配器的设备结构体 */
	int nr; /*总线编号*/
	char name[48]; /*适配器的名称*/
	struct completion dev_released;
	struct mutex userspace_clients_lock;
	struct list
}

④i2c_algorithm
用来描述适配器和设备之间的通信方法,由芯片厂商实现

struct i2c_algorithm {
    //传输函数指针,指向实现IIC总线通信协议的函数
    int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);        
};

⑤i2c_msg
封装要发送的数据

struct i2c_msg {
	__u16 addr; /* 目标地址  slave address */
	__u16 flags; /* 消息额外的标志,可选择的值有以下宏*/
	#define I2C_M_TEN 0x0010 /* 表示目标器件地址是 10 位的 */
	#define I2C_M_RD 0x0001 /* 在从设备中读取数据 */
	 						/* 没有专门定义一个写的标志,默认是写 */
	__u16 len; /* 传输的数据字节数量 */
	__u8 *buf; /* 接收/发送缓冲区 */
};

4、海思 I2C

1、i2c_device

在我们使用的Linux 4.x的版本中通过设备树的方式来描述设备信息
文件: linux-4.9.y-smp/arch/arm/boot/dts 目录下的hi3519av100.dtsi

i2c_bus1: i2c@04561000 {//节点名
		compatible = "hisilicon,hibvt-i2c"; //与 driver 中相同,进行匹配
		reg = <0x04561000 0x1000>; //寄存器地址
		clocks = <&clock HI3519AV100_I2C1_CLK>;
		clock-frequency = <100000>;
		status = "disabled";
		dmas = <&hiedmacv310_1 2 16>, <&hiedmacv310_1 3 17>;
		dma-names = "tx","rx";
};
最终内核会将这个设备树节点解析为一个i2c_client结构体与i2c_driver结构体进行匹配。

2、i2c_driver
文件:linux-4.9.y-smp/drivers/i2c/busses 目录下的 i2c-hibvt.c

//与设备树相匹配
static const struct of_device_id hibvt_i2c_match[] = {
	{ .compatible = "hisilicon,hibvt-i2c"},
	{ .compatible = "hisilicon,hi3516cv300-i2c"},
	{ .compatible = "hisilicon,hi3536dv100-i2c"},
	{},
};
//封装 i2c_driver 结构体
static struct platform_driver hibvt_i2c_driver = {
	.driver		= {
		.name	= "hibvt-i2c",
		.of_match_table = hibvt_i2c_match,
		.pm	= &hibvt_i2c_dev_pm,
	},
	.probe		= hibvt_i2c_probe,
	.remove		= hibvt_i2c_remove,
};
//如果设备树匹配成功,那么将会调用 probe 函数

在此文件中不仅实现了匹配设备,还实现了对i2c总线的时序操作

有些文件使用情况与模拟 i2c 相同,具体在下面分析

5、模拟 I2C

1、设置内核支持模拟 i2c
make ARCH=arm CROSS_COMPILE=arm-himix200-linux- menuconfig

Device Drivers  ---> 
	I2C support  ---> 
		I2C Hardware Bus support  --->
			<*> GPIO-based bitbanging I2C    

2、i2c_device
文件: linux-4.9.y-smp/arch/arm/boot/dts 目录下的hi3519av100.dtsi
添加如下代码

i2c-gpio {
			compatible = "i2c-gpio";		//匹配 driver
			gpios = <&gpio_chip3 1 0        //sda:gpio3_1 
					 &gpio_chip3 0 0        //scl:gpio3_0 
					>;
			i2c-gpio,sda-close-drain;		
			i2c-gpio,scl-open-drain;		//设置开漏
			i2c-gpio,delay-us = <5>;		//设置延时时间
			#address-cells = <1>;
			#size-cells = <0>;
};

3、i2c_driver
文件:linux-4.9.y-smp/drivers/i2c/busses 目录下的 i2c-gpio.c

//与设备树相匹配
static const struct of_device_id i2c_gpio_dt_ids[] = {
	{ .compatible = "i2c-gpio", },
}
static struct platform_driver i2c_gpio_driver = {
	.driver		= {
		.name	= "i2c-gpio",
		.of_match_table	= of_match_ptr(i2c_gpio_dt_ids),
	},
	.probe		= i2c_gpio_probe,
	.remove		= i2c_gpio_remove,
};
//初始化
static int __init i2c_gpio_init(void)
{
	int ret;
//将gpio模拟i2c总线 做为平台设备驱动注册
	ret = platform_driver_register(&i2c_gpio_driver);
	if (ret)
		printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret);

	return ret;
}

i2c_gpio_probe函数

static int i2c_gpio_probe(struct platform_device *pdev)
{
	struct i2c_gpio_private_data *priv;
	struct i2c_gpio_platform_data *pdata;
	struct i2c_algo_bit_data *bit_data;
	struct i2c_adapter *adap;
	unsigned int sda_pin, scl_pin;
	int ret;

	/* First get the GPIO pins; if it fails, we'll defer the probe. */
	if (pdev->dev.of_node) {//在设备树中定义了节点,进入
		ret = of_i2c_gpio_get_pins(pdev->dev.of_node, &sda_pin, &scl_pin);
		if (ret)
			return ret;
	} else {
		if (!dev_get_platdata(&pdev->dev))
			return -ENXIO;
		pdata = dev_get_platdata(&pdev->dev);
		sda_pin = pdata->sda_pin;
		scl_pin = pdata->scl_pin;
	}
	//申请相应的gpio pin
	ret = devm_gpio_request(&pdev->dev, sda_pin, "sda");
	if (ret) {
		if (ret == -EINVAL)
			ret = -EPROBE_DEFER;	/* Try again later */
		return ret;
	}
	ret = devm_gpio_request(&pdev->dev, scl_pin, "scl");
	if (ret) {
		if (ret == -EINVAL)
			ret = -EPROBE_DEFER;	/* Try again later */
		return ret;
	}

	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;
	adap = &priv->adap;
	bit_data = &priv->bit_data;
	pdata = &priv->pdata;

	if (pdev->dev.of_node) {
		pdata->sda_pin = sda_pin;
		pdata->scl_pin = scl_pin;
		//依据设备树中的定义 赋值结构体pdata
		of_i2c_gpio_get_props(pdev->dev.of_node, pdata);
	} else {
		memcpy(pdata, dev_get_platdata(&pdev->dev), sizeof(*pdata));
	}
	
	if (pdata->sda_is_open_drain) {//我们在设备树中设置的是非开漏,执行else
		gpio_direction_output(pdata->sda_pin, 1);
		bit_data->setsda = i2c_gpio_setsda_val;
	} else {
		gpio_direction_input(pdata->sda_pin);//设置 sda 输入
		bit_data->setsda = i2c_gpio_setsda_dir;//赋值函数指针 控制方向
	}

	if (pdata->scl_is_open_drain || pdata->scl_is_output_only) {
		gpio_direction_output(pdata->scl_pin, 1);//设置 scl 输出高电平
		bit_data->setscl = i2c_gpio_setscl_val; //赋值函数指针 设置高低电平
	} else {
		gpio_direction_input(pdata->scl_pin);
		bit_data->setscl = i2c_gpio_setscl_dir;
	}

	if (!pdata->scl_is_output_only)
		bit_data->getscl = i2c_gpio_getscl;
	bit_data->getsda = i2c_gpio_getsda;//赋值函数指针 获取 sda 的值
//设置延时时间
	if (pdata->udelay)
		bit_data->udelay = pdata->udelay;
	else if (pdata->scl_is_output_only)
		bit_data->udelay = 50;			/* 10 kHz */
	else
		bit_data->udelay = 5;			/* 100 kHz */
//设置超时时间
	if (pdata->timeout)
		bit_data->timeout = pdata->timeout;
	else
		bit_data->timeout = HZ / 10;		/* 100 ms */

	bit_data->data = pdata;
//以下就是封装适配器结构体i2c_adapter
	adap->owner = THIS_MODULE;
	if (pdev->dev.of_node)
		strlcpy(adap->name, dev_name(&pdev->dev), sizeof(adap->name));
	else
		snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", pdev->id);

	adap->algo_data = bit_data;
	adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
	adap->dev.parent = &pdev->dev;
	adap->dev.of_node = pdev->dev.of_node;

	adap->nr = pdev->id;
	ret = i2c_bit_add_numbered_bus(adap);
	if (ret)
		return ret;

	platform_set_drvdata(pdev, priv);

	dev_info(&pdev->dev, "using pins %u (SDA) and %u (SCL%s)\n",
		 pdata->sda_pin, pdata->scl_pin,
		 pdata->scl_is_output_only
		 ? ", no clock stretching" : "");

	return 0;
}

4、i2c_dev.c
该文件在 /drivers/i2c 目录下

实现了 I2C 适配器设备文件的功能,每一个 I2C 适配器都被分配一个设备。通过适配器访设备时的主设备号都为 89,次设备号为 0-255。I2c-dev.c 并没有针对特定的设备而设计,只是提供了通用的 read(),write(),和 ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的 I2C 设备的存储空间或寄存器,并控制 I2C 设备的工作方式

可以查看dev下的设备信息

~ # ls -l /dev/i2c-*
crw-------    1 root     root       89,   0 Jan 11 05:44 /dev/i2c-0
crw-------    1 root     root       89,   1 Jan 11 05:44 /dev/i2c-1
crw-------    1 root     root       89,  10 Jan 11 05:44 /dev/i2c-10
crw-------    1 root     root       89,   2 Jan 11 05:44 /dev/i2c-2
crw-------    1 root     root       89,   3 Jan 11 05:44 /dev/i2c-3
crw-------    1 root     root       89,   4 Jan 11 05:44 /dev/i2c-4
crw-------    1 root     root       89,   5 Jan 11 05:44 /dev/i2c-5
crw-------    1 root     root       89,   6 Jan 11 05:44 /dev/i2c-6
crw-------    1 root     root       89,   7 Jan 11 05:44 /dev/i2c-7
crw-------    1 root     root       89,   8 Jan 11 05:44 /dev/i2c-8
crw-------    1 root     root       89,   9 Jan 11 05:44 /dev/i2c-9

5、i2c-algo-bit.c
该文件在 /driver/i2c/algos 目录下

定义了 i2c 通信的时序代码,控制 gpio 口输入输出、高低电平、等待ACK等都是在这个文件中实现的

6、对i2c从设备进行读写操作流程:

①应用层将要发送的数据打包成i2c_msg结构,打开 /dev下的i2c设备,通过ioctl函数将封装好的 i2c_msg 传入驱动层;
②进入 i2c-dev.c,内核中使用核心层 i2c_core.c 中提供的i2c_transfer()函数,传输 i2c_msg,这个函数最终会回调相应的i2c_adapter->i2c_algorithm->master_xfer()接口,该接口在i2c-algo-bit.c中赋值
③最终在i2c-algo-bit.c文件中完成一系列的时序控制

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
I2C驱动是一种用于I2C总线通信的软件或硬件组件,帮助实现与I2C设备之间的通讯和数据传输。28335是一款微控制器,其内部集成了I2C总线控制器,可以用于连接各种外部设备,如传感器、显示器等。 I2C(Inter-Integrated Circuit)总线是一种串行通信协议,由若干个从设备和一个主设备组成。主设备控制整个通信过程,而从设备在主设备的控制下传输数据。I2C总线支持多主设备共享同一条总线,可以有效地管理多个设备的通信。 28335 I2C驱动主要有以下几个方面的功能: 1. 初始化:通过I2C驱动可以初始化I2C总线控制器,包括设置时钟频率、配置通信模式、地址模式等。 2. 数据传输:通过I2C驱动可以实现数据的读取和写入操作,主设备可以向从设备发送指令并读取响应数据。在数据传输过程中,I2C驱动负责处理起始条件、地址传输、数据传输、停止条件等。 3. 中断处理:I2C驱动可以配置中断,当有数据到达或传输完成时触发相应的中断,提高系统的实时性和效率。 4. 错误处理:I2C驱动还可以处理I2C通信中的错误,如通信超时、总线忙碌等情况,实现错误的检测和处理机制,保证通信的稳定性。 总之,28335 I2C驱动是为了与I2C设备进行通信而设计的软件或硬件组件,通过驱动可以实现I2C总线的初始化、数据传输、中断处理和错误处理等功能,从而实现与各种I2C设备的可靠通信。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值