【五一创作】Linux---I2C应用编程

目录

前言:

一、I2C协议

(1)概述

(2)I2C硬件框架:

(3)I2C软件框架

(4)I2C数据格式

二、SMBus协议

三、I2C系统重要的结构体

四、访问I2C设备(AP3216C)

(1)使用SMBus协议:  ​​​​

 (2)使用I2C协议:

 (3)I2C-Tools源码分析:

五、编写APP访问EEPROM(AT24C02)

(1)AT24C02访问方法

1.设备地址

2.写数据

3.读数据

(2)使用I2C-Tools编程

1.具体示例:(SMBus)

2.编译、实际效果


前言:

经典环节:带着问题思考和实践

(1)I2C协议

  • I2C是什么?有什么特点?
  • I2C传输数据流程具体是什么样的?
  • 具体I2C是如何产生开始信号(S)、结束信号(P)、响应信号(ACK)以及发送数据呢?
  • I2C是双向传输的,那么是如何实现在SDA上双向传输呢?

(2)SMBus协议

  • SMBus协议是什么?
  • 它相较于I2C有什么异同?

(3)Linux里I2C实现

  • 在Linux里,是如何表示I2C控制器、I2C设备以及要传输的数据?

(4)I2C应用编程实战

  • AP3216C
  • EEPROM-AT24C02

接下来的文章内容,将详细的解答上面的问题。如果有所帮助,多多支持,给与我更多创作的动力。

一、I2C协议

(1)概述

I2C是什么?它有什么特点?

在消费电子,工业电子等领域,会使用各种类型的芯片,如微控制器,电源管理,显示驱动,传感器,存储器,转换器等,他们有着不同的功能,有时需要快速的进行数据的交互,为了使用最简单的方式使这些芯片互联互通,于是I2C诞生了,I2C(Inter-Integrated Circuit)是一种通用的总线协议。它是由Philips(飞利浦)公司,现NXP(恩智浦)半导体开发的一种简单的双向两线制总线协议标准。

对于硬件设计人员来说,只需要2个管脚,极少的连接线和面积,就可以实现芯片间的通讯,对于软件开发者来说,可以使用同一个I2C驱动库,来实现实现不同器件的驱动,大大减少了软件的开发时间。极低的工作电流,降低了系统的功耗,完善的应答机制大大增强通讯的可靠性。

特点如下:

  • I2C是半双工
  • I2C支持多主多从模式
  • 从GPIO占用上来看,I2C占用两个GPIO
  • I2C有应答响应机制,数据可靠性更高
  • I2C速率不会太高,最高速率3.4Mbps
  • I2C通过器件地址来选择从机,从机数量的增加不会导致GPIO的增加
  • I2C在SCL高电平器件进行数据采样。
  • 大多应用于板内器件短距离通讯。

(2)I2C硬件框架:

【五一创作】Linux---I2C应用编程_第1张图片

  • 在一个芯片(Soc)内部,有一个或多个I2C控制器。
  • 在一个I2C控制器上,可以连接一个或多个I2C设备。---一定要知道地址的
  • I2C总线只需要两条线:时钟线SCL、数据线SDA。
  • 在I2C总线的SCL、SDA线上,都有上拉电阻。

(3)I2C软件框架

【五一创作】Linux---I2C应用编程_第2张图片

在Linux上,APP访问I2C设备是要经由I2C Device Driver解析数据和I2C Controller Driver收发数据。

以 I2C 接口的存储设备 AT24C02 为例:

  • APP:
    • 提出要求:把字符串"hello world!"写入 AT24C02 地址16开始的地方
    • 不关心底层实现的细节,它只需要调用设备驱动程序提供的接口
  • AT24C02 驱动:
    • 它知道 AT24C02 要求的地址、数据格式
    • 它知道发出什么信号才能让 AT24C02 执行擦除、烧写工作
    • 它知道怎么判断数据是否烧写成功 它构造好一系列的数据,发给 I2C 控制器
  • I2C 控制器驱动
    • 它根据 I2C 协议发出各类信号:I2C 设备地址、I2C 存储地址、数据
    • 它根据 I2C 协议判断

(4)I2C数据格式

I2C传输数据流程具体是什么样的?

以写操作为例: 

  • 主芯片要发出一个start信号
  • 然后发出一个设备地址(用来确定这个设备是否存在),然后就可以传输数据
  • 主设备发送一个字节数据给从设备,并等待回应
  • 每传输一字节数据,接收方要由一个回应信号(确定数据是否接受完成),然后再传输下一个数据
  • 数据发送完之后,主芯片就会发送一个停止信号。

【五一创作】Linux---I2C应用编程_第3张图片

具体I2C是如何产生开始信号(S)、结束信号(P)、响应信号(ACK)以及发送数据呢?

如下图所示:

  • 开始信号:SDA有高到低,SCL保持高电平时,产生开始信号(S)
  • 结束信号:SCL保持高电平,SDA从低到高,产生结束信号(P)
  • 响应信号:接收器收到8位数据后,在第9个时钟周期会去拉低SDA,产生响应信号(ACK)

【五一创作】Linux---I2C应用编程_第4张图片具体的数据传输两个核心要点,如下图所示:

  • 当SCL高电平时,SDA数据要保持稳定。
  • 当SCL低电平时,SDA可以发生变化(SDA高电平(1),低电平(0))。
  • 这时在SCL高电平时,查询SDA上电平,获取数据。

【五一创作】Linux---I2C应用编程_第5张图片

从上面我们可以看到,I2C是双向传输的,那么如何实现在SDA上双向传输呢?

  1. 由上面的情况,是有两个驱动(主设备和从设备),如果是直接接上的话,假设出故障的话,一个输出高电平,一个输出低电平,是会发生电路短路的。
  2. 双方设备中,某个设备发送数据时,另一方怎样才能不影响SDA上的数据?

这里采用了一个三极管或者CMOS管,来调控和预防这个问题。示例如下:

【五一创作】Linux---I2C应用编程_第6张图片

上面电路对应的真值表如下:

【五一创作】Linux---I2C应用编程_第7张图片

 从真值表和电路可以知道:

  • 当某一个芯片不想影响SDA线时,那就不驱动三极管
  • 想让SDA输出高电平,双方都不驱动三极管
  • 想让SDA输出低电平,就驱动三极管

这样的情况下,实现数据传输,就可以是这样:

  • 前8个clk,从设备不驱动三极管,主设备决定数据;发送1时不驱动,发送0时驱动三极管。
  • 第9个clk,主设备不驱动三极管,从设备决定数据;回应信号,驱动三极管让SDA变为0。

 注:为什么要用上拉电阻?

在第 9 个时钟之后,如果有某一方需要更多的时间来处理数据,它可以一直驱动三极管把SCL拉低。

二、SMBus协议

SMBus:System Management Bus,系统管理总线。

SMBus是基于I2C协议的,它要求更严格,是I2C协议的子集。它被用来连接各种设备,包括电源相关设备,系统传感器,EEPROM通讯设备等等。

SMBus有哪些更严格的要求?跟一般的I2C协议有哪些差别?

  • VDD 的极限值不一样

    • I2C 协议:范围很广,甚至讨论了高达12V的情况

    • SMBus:1.8V~5V

  • 最小时钟频率、最大的 Clock Stretching(某个设备需要更多时间进行内部处理时,它可以把SCL拉低占住I2C总线)

    • I2C:时钟频率最小值无限制,Clock Stretching 时长也没有限制

    • SMBus:时钟频率最小值是 10KHz,Clock Stretching 的最大时间值也有限制

  • 地址回应(Address Acknowledge):一个 I2C 设备接收到它的设备地址后,是否必须发出回应信号?

    • I2C 协议:没有强制要求必须发出回应信号

    • SMBus:强制要求必须发出回应信号,这样对方才知道该设备的状态: busy,failed,或是被移除了

  • SMBus 协议明确了数据的传输格式

    • I2C 协议:它只定义了怎么传输数据,但是并没有定义数据的格式,这完全由设备来定义

    • SMBus:定义了几种数据格式

  • REPEATED START Condition(重复发出S信号)---SMBus

    • 在写、读之间,可以不发出 P 信号,而是直接发出 S 信号:这个 S 信号就是REPEATED START。

SMBus协议分析,它的具体内容:

SMBus symbols(符号): 

【五一创作】Linux---I2C应用编程_第8张图片它在I2C协议基础上加入了command code(命令字节、一般表示芯片内部的寄存器地址)、byte count(数据长度)、data byte(数据字节,支持8位、16位)以及PEC校验码机制。 

以 SMBus Block Read(较为复杂)为例:

【五一创作】Linux---I2C应用编程_第9张图片

  • I2C-tools 中的函数:i2c_smbus_read_block_data()。

  • 先发出command code(理解分析:Address为从设备地址,command code一般为芯片内部的寄存器地址)
  • 再发起读操作
    • 先读到一个字节(Block count),表示后续要读的字节数
    • 然后读取全部数据

了解更多的数据格式,参照以下的文章: 

Linux系统驱动之SMBus协议_i2c_smbus_read_byte_data_韦东山的博客-CSDN博客

注:在很多设备都实现了 SMBus,而不是更宽泛的 I2C 协议,所以优先使用SMBus。
即使 I2C 控制器没有实现 SMBus,软件方面也是可以使用 I2C 协议来模拟 SMBus。
所以: Linux 建议优先使用 SMBus。

三、I2C系统重要的结构体

由第一块内容I2C的硬件框架,I2C传输着重关注I2C controller、I2C device以及传输的数据。

如何表示I2C Controller? 

  • 是第几个I2C Controller        
  • I2C Controller如何收发数据?

这里I2C Controller是用i2c_adapter来表示,具体对应的结构体如下: 

  • nr表示第几个I2C Controller
  • i2c_algorithm,里面有该 I2C BUS 的传输函数,用来收发 I2C 数据 【五一创作】Linux---I2C应用编程_第10张图片 

 如何表示I2C device?

  • 一定有设备地址
  • 它是挂载在哪个I2C Controller上    ---即对应的I2C_adapter是什么?

 这里I2C device用i2c_client来表示,具体对应的结构体如下:

  • addr:设备地址
  • adapter:对应上哪个I2C Controller

【五一创作】Linux---I2C应用编程_第11张图片

 如何表示要传输的数据?

 这里数据用i2c_msg 来表示:

  • i2c_msg 中的 flags 用来表示传输方向:bit 0 等于 I2C_M_RD 表示读,bit 0 等于 0 表示写

  • 举例:设备地址为 0x50 的 EEPROM,要读取它里面存储地址为 0x10 的一个字节,要构造 2 个 i2c_msg

    • 第一个 i2c_msg 表示写操作,把要访问的存储地址 0x10 发给设备
    • 第二个 i2c_msg 表示读操作
  • 着重关注,addr(地址)、flags(方向)、len(长度)、buf(数据)

  • 【五一创作】Linux---I2C应用编程_第12张图片

四、访问I2C设备(AP3216C)

APP访问硬件是需要驱动程序,对于I2C设备,这里可以调用内核提供的驱动程序drivers/i2c/i2c-dev.c。通过它可以直接使用I2C Controller Driver里的adapter driver来访问I2C设备(AP3216C)。 

【五一创作】Linux---I2C应用编程_第13张图片

AP3216C 是红外、光强、距离三合一的传感器,以读出光强、距离值为例,步骤如下:

  • 复位:往寄存器 0 写入 0x4
  • 使能:往寄存器 0 写入 0x3
  • 读光强:读寄存器 0xC、0xD 得到 2 字节的光强
  • 读距离:读寄存器 0xE、0xF 得到 2 字节的距离值 

这里可以使用I2C-Tools来操作传感器AP3212C,有两种方式(SMBus以及I2C)访问设备:

(1)使用SMBus协议:  ​​​​

i2cset和i2cget函数用法,后面依次为:
命令(-f、-y)
I2CBUS(0)
设备地址
寄存器地址
数据data

【五一创作】Linux---I2C应用编程_第14张图片

 (2)使用I2C协议:

i2ctransfer函数用法,后面依次为:
命令(-f、-y)
I2CBUS(0)
描述符(读写w1/w2 + @设备地址 )
寄存器地址
数据data

 (3)I2C-Tools源码分析:

结合上面的流程分析,具体流程步骤:

  • 打开/dev/i2c-0节点(open),会访问该 I2C 控制器下的设备。(注:i2c-dev.c 为每个 I2C 控制器(I2C Bus、I2C Adapter)都生成一个设备节点:/dev/i2c-0、/dev/i2c-1 等等;)
  • 指定I2C设备的地址(非强制:ioctl(file, I2C_SLAVE, address),强制: ioctl(file, I2C_SLAVE_FORCE, address) )
  • 之后采用相应的SMBus数据格式,使用ioctl(file, I2C_SMBUS, &args)函数传输数据。

【五一创作】Linux---I2C应用编程_第15张图片

五、编写APP访问EEPROM(AT24C02)

(1)AT24C02访问方法

1.设备地址

【五一创作】Linux---I2C应用编程_第16张图片

 由数据手册和硬件A2A1A0都接地可知,AT24C02的设备地址是0b1010000,即0x50。

2.写数据

这里的数据时序为:

  • 开始位 + 设备地址 + 写 + 相应位
  • 寄存器地址 + 数据 

【五一创作】Linux---I2C应用编程_第17张图片

3.读数据

可以读一个字节,也可以连续读出多个字节。连续多个字节时,芯片内部的地址会自动累加。当地址到达存储空间最后一个地址时,会从0开始。 如下图所示:

【五一创作】Linux---I2C应用编程_第18张图片

(2)使用I2C-Tools编程

1.具体示例:(SMBus)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "i2cbusses.h"
#include 


/* ./at24c02  w "100ask.taobao.com"
 * ./at24c02  r
 */

int main(int argc, char **argv)
{
	unsigned char dev_addr = 0x50;
	unsigned char mem_addr = 0;
	unsigned char buf[32];

	int file;
	char filename[20];
	unsigned char *str;

	int ret;

	struct timespec req;
	
	if(argc != 3 && argc != 4)
	{
		printf("Usage:\n");
		printf("%Write EEprom: %s /dev/i2c-0|1|2 w str\n", argv[0]);
		printf("%Read  EEprom: %s /dev/i2c-0|1|2 r str\n", argv[0]);
		return -1;
	}

	//第一步:打开节点
	file = open_i2c_dev(argv[1][0] - '0', filename, sizeof(filename), 0);
	if(file < 0)
	{
		printf("can't open %s\n", filename);
		return -1;
	}
	
	if(set_slave_addr(file, dev_addr, 1))
	{
		printf("can't set_slave_addr\n");
		return -1;
	}
	
	//第二步:I2C读写数据
	if(argv[2][0] == 'w')
	{
		//write
		str = argv[3];
        
        //这里添加一定的休眠时间,完成1字节数据传输后,EEPROM会进入一个写循环(需要时间)
		req.tv_sec  = 0;
		req.tv_nsec = 20000000; /* 20ms */

		while(*str)
		{
			//mem_addr, *str
			//mem_addr++, str++
			ret = i2c_smbus_write_byte_data(file, mem_addr, *str);
			if(ret)
			{
				printf("i2c_smbus_write_byte_data err\n");
				return -1;

			}
			//等待EEPROM写完数据
			nanosleep(&req, NULL);
			mem_addr++;
			str++;
		}
		ret = i2c_smbus_write_byte_data(file, mem_addr, 0); // string end char
		if (ret)
		{
			printf("i2c_smbus_write_byte_data err\n");
			return -1;
		}

	}
	else
	{
		//read
		ret = i2c_smbus_read_i2c_block_data(file, mem_addr, sizeof(buf), buf);
		if (ret < 0)
		{
			printf("i2c_smbus_read_i2c_block_data err\n");
			return -1;
		}
		
		buf[31] = '\0';
		printf("get data: %s\n", buf);
	}
}

2.编译、实际效果

a.设置交叉编译工具链

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin

b.编写Makefile:设置好工具链后,make就会执行程序

all:
$(CROSS_COMPILE)gcc -I ./include -o at24c02_test at24c02_test.c i2cbusses.c smbus.c

   这里有用到库文件: i2cbusses.c  i2cbusses.h smbus.c

c.上机测试

//nfs挂载
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt

//复制、执行程序
cp /mnt/at24c02_test /bin
at24c02_test 0 w helloworld
at24c02_test 0 r

你可能感兴趣的:(嵌入式Linux驱动,驱动开发,嵌入式硬件)