目录
前言:
一、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是什么?它有什么特点?
在消费电子,工业电子等领域,会使用各种类型的芯片,如微控制器,电源管理,显示驱动,传感器,存储器,转换器等,他们有着不同的功能,有时需要快速的进行数据的交互,为了使用最简单的方式使这些芯片互联互通,于是I2C诞生了,I2C(Inter-Integrated Circuit)是一种通用的总线协议。它是由Philips(飞利浦)公司,现NXP(恩智浦)半导体开发的一种简单的双向两线制总线协议标准。
对于硬件设计人员来说,只需要2个管脚,极少的连接线和面积,就可以实现芯片间的通讯,对于软件开发者来说,可以使用同一个I2C驱动库,来实现实现不同器件的驱动,大大减少了软件的开发时间。极低的工作电流,降低了系统的功耗,完善的应答机制大大增强通讯的可靠性。
特点如下:
在Linux上,APP访问I2C设备是要经由I2C Device Driver解析数据和I2C Controller Driver收发数据。
以 I2C 接口的存储设备 AT24C02 为例:
I2C传输数据流程具体是什么样的?
以写操作为例:
具体I2C是如何产生开始信号(S)、结束信号(P)、响应信号(ACK)以及发送数据呢?
如下图所示:
从上面我们可以看到,I2C是双向传输的,那么如何实现在SDA上双向传输呢?
这里采用了一个三极管或者CMOS管,来调控和预防这个问题。示例如下:
上面电路对应的真值表如下:
从真值表和电路可以知道:
这样的情况下,实现数据传输,就可以是这样:
注:为什么要用上拉电阻?
在第 9 个时钟之后,如果有某一方需要更多的时间来处理数据,它可以一直驱动三极管把SCL拉低。
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()。
了解更多的数据格式,参照以下的文章:
Linux系统驱动之SMBus协议_i2c_smbus_read_byte_data_韦东山的博客-CSDN博客
注:在很多设备都实现了 SMBus,而不是更宽泛的 I2C 协议,所以优先使用SMBus。
即使 I2C 控制器没有实现 SMBus,软件方面也是可以使用 I2C 协议来模拟 SMBus。
所以: Linux 建议优先使用 SMBus。
由第一块内容I2C的硬件框架,I2C传输着重关注I2C controller、I2C device以及传输的数据。
如何表示I2C Controller?
- 是第几个I2C Controller
- I2C Controller如何收发数据?
这里I2C Controller是用i2c_adapter来表示,具体对应的结构体如下:
如何表示I2C device?
- 一定有设备地址
- 它是挂载在哪个I2C Controller上 ---即对应的I2C_adapter是什么?
这里I2C device用i2c_client来表示,具体对应的结构体如下:
如何表示要传输的数据?
这里数据用i2c_msg 来表示:
i2c_msg 中的 flags 用来表示传输方向:bit 0 等于 I2C_M_RD 表示读,bit 0 等于 0 表示写
举例:设备地址为 0x50 的 EEPROM,要读取它里面存储地址为 0x10 的一个字节,要构造 2 个 i2c_msg
着重关注,addr(地址)、flags(方向)、len(长度)、buf(数据)
APP访问硬件是需要驱动程序,对于I2C设备,这里可以调用内核提供的驱动程序drivers/i2c/i2c-dev.c。通过它可以直接使用I2C Controller Driver里的adapter driver来访问I2C设备(AP3216C)。
AP3216C 是红外、光强、距离三合一的传感器,以读出光强、距离值为例,步骤如下:
这里可以使用I2C-Tools来操作传感器AP3212C,有两种方式(SMBus以及I2C)访问设备:
i2cset和i2cget函数用法,后面依次为:
命令(-f、-y)
I2CBUS(0)
设备地址
寄存器地址
数据data
i2ctransfer函数用法,后面依次为:
命令(-f、-y)
I2CBUS(0)
描述符(读写w1/w2 + @设备地址 )
寄存器地址
数据data
结合上面的流程分析,具体流程步骤:
由数据手册和硬件A2A1A0都接地可知,AT24C02的设备地址是0b1010000,即0x50。
这里的数据时序为:
可以读一个字节,也可以连续读出多个字节。连续多个字节时,芯片内部的地址会自动累加。当地址到达存储空间最后一个地址时,会从0开始。 如下图所示:
#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);
}
}
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