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

news2024/12/25 9:14:21

 目录简述

前言:

一、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硬件框架:

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

(3)I2C软件框架

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

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

如下图所示:

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

具体的数据传输两个核心要点,如下图所示:

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

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

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

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

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

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

  • 当某一个芯片不想影响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(符号): 

它在I2C协议基础上加入了command code(命令字节、一般表示芯片内部的寄存器地址)、byte count(数据长度)、data byte(数据字节,支持8位、16位)以及PEC校验码机制。 

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

  • 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 数据  

 如何表示I2C device?

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

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

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

 如何表示要传输的数据?

 这里数据用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(数据)

四、访问I2C设备(AP3216C)

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

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

 (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)函数传输数据。

五、编写APP访问EEPROM(AT24C02)

(1)AT24C02访问方法

1.设备地址

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

2.写数据

这里的数据时序为:

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

3.读数据

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

(2)使用I2C-Tools编程

1.具体示例:(SMBus)

#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <i2c/smbus.h>
#include "i2cbusses.h"
#include <time.h>


/* ./at24c02 <i2c_bus_number> w "100ask.taobao.com"
 * ./at24c02 <i2c_bus_number> 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/483426.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

欧姆龙NX/NJ系列PLC对接Oracle数据库,实现PLC与数据库双向数据通讯

智能网关IGT-DSER方便实现PLC与数据库之间的数据通讯&#xff0c;既可以读取PLC的数据上报到数据库&#xff0c;也可以从数据库查询数据后写入到PLC的寄存器。 网关安装在设备侧&#xff0c;与设备同时起停&#xff0c;不担心数据丢失&#xff1b;在断网、服务器维护上报数据有…

ID3算法报告理解笔记

专有名词解释&#xff1a; 决策树&#xff1a;决策树是根据已知若干条件&#xff0c;来对事件做出判断。从根节点到叶子结点。自上而下生成&#xff0c;每个决策或事件都可能引发两个或多个事件。将这些事件根据不同的特征进行划分&#xff0c;最后将类别分出&#xff0c;得到…

【Java校招面试】基础知识(四)——JVM

目录 前言一、基础概念二、反射三、类加载器ClassLoader四、JVM内存模型后记 前言 本篇主要介绍Java虚拟机——JVM的相关内容。 “基础知识”是本专栏的第一个部分&#xff0c;本篇博文是第四篇博文&#xff0c;如有需要&#xff0c;可&#xff1a; 点击这里&#xff0c;返回…

java 泛型知识整理

Java泛型这个特性是从JDK 1.5才开始加入的&#xff0c;因此为了兼容之前的版本&#xff0c;Java泛型的实现采取了“伪泛型”的策略&#xff0c;即Java在语法上支持泛型&#xff0c;但是在编译阶段会进行所谓的“类型擦除”&#xff08;Type Erasure&#xff09;&#xff0c;将所…

【网络协议详解】——GNS3的使用(学习笔记)

&#x1f4d6; 前言&#xff1a;在IT领域&#xff0c;网络协议的理解和掌握是至关重要的。GNS3和Wireshark是非常实用的工具&#xff0c;它们可以帮助你深入了解TCP/IP协议和网络的运作情况。 目录 &#x1f552; 1. 网络协议分析工具——GNS3&#x1f558; 1.1 快速上手&#…

磁盘和固态磁盘

磁盘和固态磁盘 磁盘的物理结构 ​ 磁盘的表面由一些磁性的物质组成&#xff0c;可以用这些磁性物质来记录二进制数据。磁盘的盘面被划分成一个个磁道&#xff0c;这样一个“圈”就是一个磁道。同一磁盘上不同磁道上记录的信息量相同&#xff0c;因此内侧磁道上的数据密度较大…

嵌入式C语言自我修养笔记1-ARM体系结构与编译运行

目录 ARM 体系结构ARM 体系结构ARM 汇编指令ARM 寻址方式ARM 伪指令C 与汇编混合编程 程序编译链接与安装运行预处理过程编译过程链接过程程序安装apt-get链接静态库动态链接共享库插件工作原理Linux 内核模块运行机制Linux 内核编译与启动分析 ARM 体系结构 ARM 体系结构 AR…

计算机基础书籍

一操作系统 二常见问题总结 1.操作系统的特征&#xff1f; 并发、共享、虚拟、异步性 2.进程阻塞与唤醒的条件 等待 I/O 操作完成请求系统资源失败等待信号量或事件等待子进程结束被高优先级进程抢占 3.如何避免死锁&#xff1f; 1、避免资源竞争 2、破坏循环等待条件 3、优…

【云原生网关】Kong 使用详解

目录 一、前言 二、Kong介绍 三、Kong核心组件 3.1 kong组件介绍 3.1.1 Kong Server 3.1.2 Apache Cassandra/PostgreSQL 3.1.3 Kong dashboard 3.2 传统网关与Kong工作模式对比 四、Kong网关特征与架构 4.1 kong网关特征 4.1.1 可扩展性 4.1.2 模块化 4.1.3 在任…

vue+element仿原神实现好看的个人中心

目录 一、仿原神效果图 二、代码实现 1.项目截图 2.路由配置 完整源码 3.个人中心index源码 4.用户信息页面源码 5.我的合集源码 三、总结 一、仿原神效果图 2011年&#xff0c;24岁的上海交通大学研究生刘伟、蔡浩宇、罗宇皓三人拿到上海市科技创业中心大学生创业基金…

【力扣】根据二叉树的前序和中序遍历结果还原该二叉树(以及后序和中序还原)

一 前序和中序还原二叉树 连接&#xff1a;根据二叉树的前序和中序遍历结果还原该二叉树 思路是这样的&#xff1a; 这个算法的目的是根据前序遍历和中序遍历的结果&#xff0c;重建一棵二叉树。前序遍历的特点是&#xff0c;第一个元素一定是根节点&#xff0c;后面的元素…

从一到无穷大 #6 盘满排查过程

文章目录 引言df/du 原理排查思路文件系统预留空间进程占用句柄挂载覆盖 引言 核心在于执行df和du的时候发现显示的存储量完全不同&#xff0c;我本地系统盘有99G空间&#xff0c;du显示占用了45G&#xff0c;但是df却显示使用了99G&#xff0c;排查的过程本文所示。 先记录几…

网络协议与攻击模拟-03-ARP协议

ARP 协议&#xff08;地址解析协议&#xff09; 一、 ARP 协议 将一个已知的 IP 地址解析为 MAC 地址&#xff0c;从而进行二层数据交互 是一个三层的协议&#xff0c;但是工作在二层&#xff0c;是一个2.5层协议 二、工作流程 1、两个阶段 ARP 请求 ARP 相应 2、 ARP 协议…

TinyML:使用 ChatGPT 和合成数据进行婴儿哭声检测

故事 TinyML 是机器学习的一个领域,专注于将人工智能的力量带给低功耗设备。该技术对于需要实时处理的应用程序特别有用。在机器学习领域,目前在定位和收集数据集方面存在挑战。然而,使用合成数据可以以一种既具有成本效益又具有适应性的方式训练 ML 模型,从而消除了对大量…

链表,栈,队列,递归行为,哈希表,有序表

文章目录 链表1.单链表/双链表的反转2.删除链表中指定的值 队列1.数组循环队列的实现2. 双向链表实现双端队列 栈1.用数组实现栈 栈和队列的面试题1. 实现最小栈2. 两个栈实现一个队列3.两个队列实现一个栈4.用栈实现图的广度优先遍历5.用队列实现图的深度优先遍历 递归Master公…

Spring Boot 3.0.x 自动配置文件加载原理

我们知道 Spring Boot 开启自动配置使用的是 EnableAutoConfiguration 注解&#xff0c;一般使用组合了它的 SpringBootApplication 主注解即可。那么 Spring Boot 是如何加载包含了各种需要自动配置的类的配置文件的呢&#xff1f;本文我们基于 Spring Boot 3.0.6 一起看看 Sp…

ChatGLM实战 - 文本信息抽取

1. ChatGLM介绍 ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM) 架构&#xff0c;具有 62 亿参数。结合模型量化技术&#xff0c;用户可以在消费级的显卡上进行本地部署&#xff08;INT4 量化级别下最低只需 6GB 显存&…

微服务之事务处理

Informal Essay By English Hi guys、happy labor day. Everyone should have a good time to relax during the Labor Day holiday. But don’t forget to improve yourself during the holiday period 参考书籍&#xff1a; “凤凰架构” “微服务架构设计模式” 引言 …

K8S[Kubernetes]快速安装组件(Kubectl Kubeadam Kubeinit)

文章目录 配置K8S主从集群前置准备操作一&#xff1a;主节点操作 查看主机域名->编辑域名1.1 编辑HOST 从节点也做相应操作1.2 从节点操作 查看从节点102域名->编辑域名1.3 从节点操作 查看从节点103域名->编辑域名 二&#xff1a;安装自动填充&#xff0c;虚拟机默认…

汇编语言学习笔记四

字符 字符是以ASCII码的形式存储的&#xff0c;一个字符对应着8为二进制数&#xff0c;2位16进制数。 所以可以得到对应的字符地址。 assume ds:data data segmentdb hellodb world data endsand or指令 根据ASCII码&#xff0c;字符的大写和小写相差一个0010 0000&#xff…