1. I2C 框架结构
1.1 I2C 硬件框架
I2C 总线拓扑图
⚫
在一个芯片
(SoC)
内部,有一个或多个
I2C
控制器
⚫
在一个
I2C
控制器上,可以连接一个或多个
I2C
设备
⚫
I2C
总线只需要
2
条线:时钟线
SCL
、数据线
SDA
⚫
在
I2C
总线的
SCL
、
SDA
线上,都有上拉电阻
1.2 I2C 软件框架
以 I2C 接口的存储设备 AT24C02 为例:
⚫
APP
:
◼ 提出要求:把字符串
"www.100ask.net"
写入
AT24C02
地址
16
开始的地方
◼ 它是大爷,不关心底层实现的细节
◼ 它只需要调用设备驱动程序提供的接口
⚫
AT24C02
驱动:
◼ 它知道
AT24C02
要求的地址、数据格式
◼ 它知道发出什么信号才能让
AT24C02
执行擦除、烧写工作
◼ 它知道怎么判断数据是否烧写成功
◼ 它构造好一系列的数据,发给 I2C
控制器
⚫
I2C
控制器驱动
◼ 它根据
I2C
协议发出各类信号:
I2C
设备地址、
I2C
存储地址、数据
◼ 它根据
I2C
协议判断
1.3 我们讲什么
1.3.1 对于 Linux I2C 结构
从上到下:
⚫
先讲
I2C
协议
⚫
APP
可以通过两类驱动程序访问设备
◼ I2C
设备自己的驱动程序
◼ 内核自带的
i2c-dev.c
驱动程序,它是
i2c
控制器驱动程序暴露给用户空间的驱动程序(i2c-dev.c)
⚫
I2C Device Driver
◼ I2C
设备自己的驱动程序
◼ 内核自带的
i2c-dev.c
驱动程序,它是
i2c
控制器驱动程序暴露给用户空间的驱动程序(i2c-dev.c)
⚫
I2C Controller Driver
◼ 芯片
I2C
控制器的驱动程序
(
称为
adapter)
◼ 使用
GPIO
模拟的
I2C
控制器驱动程序
(i2c-gpio.c)
1.3.2 对于单片机/裸机
从上到下:
⚫
先讲
I2C
协议
⚫
APP
⚫
I2C Device Driver
⚫
I2C Controller Driver(
也被称为
adapter)
2 I2C 协议
2.1 硬件连接
I2C 在硬件上的接法如下所示,主控芯片引出两条线
SCL,SDA
线,在一条 I2C 总线上可以接很多
I2C
设备,我们还会放一个上拉电阻(放一个上拉电阻的原因以后我们再说)。
2.2 传输数据类比
怎么通过 I2C
传输数据,我们需要把数据从主设备发送到从设备上去,也需要把数据从从设备传送到主设备上去,数据涉及到双向传输。
举个例子:
体育老师:可以把球发给学生,也可以把球从学生中接过来。
⚫
发球:
◼ 老师:开始了
(start)
◼ 老师:
A
!我要发球给你!
(
地址
/
方向
)
◼ 学生
A
:到!
(
回应
)
◼ 老师把球发出去(传输)
◼ A
收到球之后,应该告诉老师一声(回应)
◼ 老师:结束(停止)
⚫
接球:
◼ 老师:开始了
(start)
◼ 老师:
B
!把球发给我!
(
地址
/
方向
)
◼ 学生
B
:到!
◼ B
把球发给老师(传输)
◼ 老师收到球之后,给
B
说一声,表示收到球了(回应)
◼ 老师:结束(停止)
我们就使用这个简单的例子,来解释一下
IIC
的传输协议:
⚫
老师说开始了,表示开始信号
(start)
⚫
老师提醒某个学生要发球,表示发送地址和方向
(address/read/write)
⚫
老师发球
/
接球,表示数据的传输
⚫
收到球要回应:回应信号
(ACK)
⚫
老师说结束,表示
IIC
传输结束
(P)
2.3 IIC 传输数据的格式
2.3.1 写操作
⚫
主芯片要发出一个
start
信号
⚫
然后发出一个设备地址
(
用来确定是往哪一个芯片写数据
)
,方向
(
读
/
写,
0
表示写,1
表示读
)
⚫
从设备回应
(
用来确定这个设备是否存在
)
,然后就可以传输数据
⚫
主设备发送一个字节数据给从设备,并等待回应
⚫
每传输一字节数据,接收方要有一个回应信号(确定数据是否接受完成
)
,然后再传输下一个数据。
⚫
数据发送完之后,主芯片就会发送一个停止信号。
⚫
下图:白色背景表示
"
主
→
从
"
,灰色背景表示
"
从
→
主
"
2.3.2 读操作
流程如下:
⚫
主芯片要发出一个
start
信号
⚫
然后发出一个设备地址
(
用来确定是往哪一个芯片写数据
)
,方向
(
读
/
写,
0
表示写,1
表示读
)
⚫
从设备回应
(
用来确定这个设备是否存在
)
,然后就可以传输数据
⚫
从设备发送一个字节数据给主设备,并等待回应
⚫
每传输一字节数据,接收方要有一个回应信号(确定数据是否接受完成
)
,然后再传输下一个数据。
⚫
数据发送完之后,主芯片就会发送一个停止信号。
下图:白色背景表示
"
主
→
从
"
,灰色背景表示
"
从
→
主
"
2.3.3 I2C 信号
I2C 协议中数据传输的单位是字节,也就是
8
位。但是要用到
9
个时钟:前面 8
个时钟用来传输
8
数据,第
9
个时钟用来传输回应信号。传输时,先传输最高位(MSB)
。
⚫
开始信号(
S
):
SCL
为高电平时,
SDA 由
高电平向低电平跳变,开始传送数据。
⚫
结束信号(
P
):
SCL
为高电平时,
SDA
由低电平向高电平跳变,结束传送数据。
⚫
响应信号
(ACK)
:接收器在接收到
8
位数据后,在第
9
个时钟周期,(从设备)拉低
SDA
⚫
SDA
上传输的数据必须在
SCL
为高电平期间保持稳定,
SDA
上的数据只能在SCL 为低电平期间变化
I2C 协议信号如下(前半部分数据是发送地址位和方向,后半部分是数据,发送原理一样):
在 SCL 为高电平时,SDA 拉高,读取到 1;在SCL为高电平时,SDA拉低,读取到0。
2.3.4 协议细节
⚫
如何在
SDA
上实现双向传输?
◼ 主芯片通过一根
SDA
线既可以把数据发给从设备,也可以从
SDA
上读取数据,连接 SDA
线的引脚里面必然有两个引脚(发送引脚
/
接受引脚)。
⚫
主、从设备都可以通过
SDA
发送数据,肯定不能同时发送数据,怎么错开时间?在 9
个时钟里:
◼ 前
8
个时钟由主设备发送数据的话,第
9
个时钟就由从设备发送数据;
◼ 前
8
个时钟由从设备发送数据的话,第
9
个时钟就由主设备发送数据。
⚫
双方设备中,某个设备发送数据时,另一方怎样才能不影响
SDA
上的数据?
◼ 设备的
SDA
中有一个三极管,使用开极
/
开漏电路
(
三极管是开极,
CMOS 管是开漏,作用一样)
,如下图:
真值表如下:
从真值表和电路图我们可以知道:
⚫
当某一个芯片不想影响
SDA
线时,那就不驱动这个三极管
⚫
想让
SDA
输出高电平,双方都不驱动三极管
(SDA
通过上拉电阻变为高电平
)
⚫
想让
SDA
输出低电平,就驱动三极管
从下面的例子可以看看数据是怎么传的(实现双向传输)。
举例:主设备发送(
8bit
)给从设备
⚫
前
8
个
clk
◼ 从设备不要影响
SDA
,从设备不驱动三极管
◼ 主设备决定数据,主设备要发送
1
时不驱动三极管,要发送
0
时驱动三极管
⚫
第
9
个
clk
,由从设备决定数据
◼ 主设备不驱动三极管
◼ 从设备决定数据,要发出回应信号的话,就驱动三极管让
SDA
变为
0 从这里也可以知道 ACK
信号是低电平
从上面的例子,就可以知道怎样在一条线上实现双向传输,这就是 SDA
上要使用上拉电阻的原因。
为何 SCL
也要使用上拉电阻?在第
9
个时钟之后,如果有某一方需要更多的时间来处理数据,它可以一直驱动三极管把 SCL
拉低。当 SCL
为低电平时候,大家都不应该使用
IIC
总线,只有当
SCL
从低电平变为高电平的时候,IIC
总线才能被使用。
当它就绪后,就可以不再驱动三极管,这是上拉电阻把 SCL
变为高电平,其他设备就可以继续使用 I2C
总线了。
对于 IIC
协议它只能规定怎么传输数据,数据是什么含义由从设备决定。
3 SMBus 协议
3.1 SMBus 是 I2C 协议的一个子集
SMBus: System Management Bus
,系统管理总线。
SMBus
最初的目的是为智能电池、充电电池、其他微控制器之间的通信链路而定义的。
SMBus
也被用来连接各种设备,包括电源相关设备,系统传感器,
EEPROM
通讯设备等等。
SMBus
为系统和电源管理这样的任务提供了一条控制总线,使用
SMBus
的系统,设备之间发送和接收消息都是通过 SMBus
,而不是使用单独的控制线,这样可以节省设备的管脚数。
SMBus
是基于
I2C
协议的,
SMBus
要求更严格,
SMBus
是
I2C
协议的子集。
SMBus
有哪些更严格的要求?跟一般的
I2C
协议有哪些差别?
⚫
VDD
的极限值不一样
◼ I2C
协议:范围很广,甚至讨论了高达
12V
的情况
◼ SMBus
:
1.8V~5V
⚫
最小时钟频率、最大的
Clock Stretching
◼ 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
信号
)
比如读 EEPROM
时,涉及
2
个操作:
1、把存储地址发给设备
2、读数据
在写、读之间,可以不发出 P
信号,而是直接发出
S
信号:这个
S
信号就是 REPEATED START,如图
⚫
SMBus Low Power Version
:
SMBus
也有低功耗的版本
3.2 SMBus 协议分析
对于 I2C
协议,它只定义了怎么传输数据,但是并没有定义数据的格式,这完全由设备来定义。
对于
SMBus
协议,它定义了几种数据格式。
注意:
下面文档中的
Functionality flag
是
Linux
的某个
I2C
控制器驱动所支持的功能。比如 Functionality flag: I2C_FUNC_SMBUS_QUICK
,表示需要 I2C
控制器支持
SMBus Quick Command
3.2.1 symbols(符号)
S (1 bit) : Start bit(开始位)
Sr (1 bit) : 重复的开始位
P (1 bit) : Stop bit(停止位)
R/W# (1 bit) : Read/Write bit. Rd equals 1, Wr equals 0.(读写位)
A, N (1 bit) : Accept and reverse accept bit.(回应位)
Address(7 bits): I2C 7 bit address. Note that this can be expanded as usual to
get a 10 bit I2C address.
(地址位,7 位地址)
Command Code (8 bits): Command byte, a data byte which often selects a register on
the device.
(命令字节,一般用来选择芯片内部的寄存器)
Data Byte (8 bits): A plain data byte. Sometimes, I write DataLow, DataHigh
for 16 bit data.
(数据字节,8 位;如果是 16 位数据的话,用 2 个字节来表示:DataLow、DataHigh)
Count (8 bits): A data byte containing the length of a block operation.
(在 block 操作总,表示数据长度)
[..]: Data sent by I2C device, as opposed to data sent by the host
adapter.
(中括号表示 I2C 设备发送的数据,没有中括号表示 host adapter 发送的数据)
3.2.2 SMBus Quick Command
只是用来发送一位数据:R/W#
本意是用来表示读或写,但是在
SMBus
里可以用来表示其他含义。比如某些开关设备,可以根据这一位来决定是打开还是关闭。
Functionality flag: I2C_FUNC_SMBUS_QUICK
3.2.3 SMBus Receive Byte
I2C-tools 中的函数:
i2c_smbus_read_byte()
。读取一个字节,
Host adapter 接收到一个字节后不需要发出回应信号
(
上图中
N
表示不回应
)
。
Functionality flag: I2C_FUNC_SMBUS_READ_BYTE
3.2.4 SMBus Send Byte
I2C-tools 中的函数:
i2c_smbus_write_byte()
。发送一个字节。
Functionality flag: I2C_FUNC_SMBUS_WRITE_BYTE
3.2.5 SMBus Read Byte
I2C-tools 中的函数:
i2c_smbus_read_byte_data()
。先发出
Command Code(它一般表示芯片内部的寄存器地址
)
,再读取一个字节的数据。上面介绍的 SMBus Receive Byte 是不发送
Comand
,直接读取数据。
Functionality flag: I2C_FUNC_SMBUS_READ_BYTE_DATA
3.2.6 SMBus Read Word
I2C-tools 中的函数:
i2c_smbus_read_word_data()
。先发出
Command Code(它一般表示芯片内部的寄存器地址
)
,再读取
2
个字节的数据。
Functionality flag: I2C_FUNC_SMBUS_READ_WORD_DATA
3.2.7 SMBus Write Byte
3.2.8 SMBus Write Word
I2C-tools 中的函数:
i2c_smbus_write_word_data()
。先发出
Command Code(它一般表示芯片内部的寄存器地址
)
,再发出
1
个字节的数据。
Functionality flag: I2C_FUNC_SMBUS_WRITE_WORD_DATA
3.2.9 SMBus Block Read
I2C-tools 中的函数:
i2c_smbus_read_block_data()
。先发出
Command Code(它一般表示芯片内部的寄存器地址
)
,再发起度操作:
⚫
先读到一个字节
(Block Count)
,表示后续要读的字节数
⚫
然后读取全部数据
Functionality flag: I2C_FUNC_SMBUS_READ_BLOCK_DATA
3.2.10 SMBus Block Write
I2C-tools 中的函数:
i2c_smbus_write_block_data()
。先发出
Command Code(它一般表示芯片内部的寄存器地址
)
,再发出
1
个字节的
Byte Conut(
表示后续要发出的数据字节数)
,最后发出全部数据。
Functionality flag: I2C_FUNC_SMBUS_WRITE_BLOCK_DATA
3.2.11 I2C Block Read
在一般的 I2C
协议中,也可以连续读出多个字节。它跟
SMBus Block Read 的差别在于设备发出的第
1
个数据不是长度
N
,如下图所示:
I2C-tools 中的函数:i2c_smbus_read_i2c_block_data()。先发出 Command Code(它一般表示芯片内部的寄存器地址),再发出 1 个字节的 Byte Conut(表示后续要发出的数据字节数),最后发出全部数据。
Functionality flag: I2C_FUNC_SMBUS_READ_I2C_BLOCK
3.2.12 I2C Block Write
在一般的 I2C
协议中,也可以连续发出多个字节。它跟
SMBus Block Write 的差别在于发出的第 1
个数据不是长度
N
,如下图所示:
I2C-tools 中的函数:
i2c_smbus_write_i2c_block_data()
。先发出 Command Code(它一般表示芯片内部的寄存器地址
)
,再发出
1
个字节的
Byte Conut(表示后续要发出的数据字节数
)
,最后发出全部数据。
3.2.13 SMBus Block Write - Block Read Process Call
先写一块数据,再读一块数据。
Functionality flag: I2C_FUNC_SMBUS_BLOCK_PROC_CALL
3.2.14 Packet Error Checking (PEC)
PEC 是一种错误校验码,如果使用
PEC
,那么在
P
信号之前,数据发送方要发送一个字节的 PEC
码
(
它是
CRC-8
码
)
。以
SMBus Send Byte
为例,下图中,一个未使用 PEC
,另一个使用
PEC
:
3.3 SMBus 和 I2C 的建议
因为很多设备都实现了 SMBus
,而不是更宽泛的
I2C
协议,所以优先使用SMBus。即使
I2C
控制器没有实现
SMBus
,软件方面也是可以使用
I2C
协议来模拟 SMBus
。所以:
Linux
建议优先使用
SMBus
。
4 I2C 系统的重要结构体
使用一句话概括 I2C
传输:
APP
通过
I2C Controller
与
I2C Device
传输数据。
在 Linux
中要思索下面几个问题。
⚫
怎么表示
I2C Controller
⚫
一个芯片里可能有多个
I2C Controller
,比如第
0
个、第
1
个、
……
⚫
对于使用者,只要确定是第几个
I2C Controller
即可
⚫
使用
i2c_adapter
表示一个
I2C BUS
,或称为
I2C Controller
,里面有
2 个重要的成员:
a) nr
:第几个
I2C BUS(I2C Controller)
b) i2c_algorithm
,里面有该
I2C BUS
的传输函数,用来收发
I2C
数据 i2c_adapter 原型:
i2c_algorithm
原型:
4.1 怎么表示 I2C Device
⚫
一个
I2C Device
,一定有
设备地址
⚫
它连接在哪个
I2C Controller
上,即对应的
i2c_adapter
是什么
使用 i2c_client
来表示一个
I2C Device
4.2 怎么表示要传输的数据
在上面的i2c_algorithm
结构体中可以看到要传输的数据被称为:
i2c_msg 。
i2c_msg 原型:
◼
i2c_msg
中的
flags
用来表示传输方向:
bit 0
等于
I2C_M_RD
表示读,bit 0
等于
0
表示写
◼
一个
i2c_msg
要么是读,要么是写
举例:设备地址为
0x50
的
EEPROM
,要读取它里面存储地址为
0x10
的一个字节,应该构造几个 i2c_msg
?要构造
2
个
i2c_msg
c) 第一个
i2c_msg
表示写操作,把要访问的存储地址
0x10
发给设备
d) 第二个
i2c_msg
表示读操作
代码如下
u8 data_addr = 0x10;
i8 data;
struct i2c_msg msgs[2];
msgs[0].addr = 0x50;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = &data_addr;
msgs[1].addr = 0x50;
msgs[1].flags = I2C_M_RD;
msgs[1].len = 1;
msgs[1].buf = &data;
4.3 内核里怎么传输数据
使用一句话概括 I2C
传输:
a) APP
通过
I2C Controller
与
I2C Device
传输数据
b) APP
通过
i2c_adapter
与
i2c_client
传输
i2c_msg
c)
内核函数
i2c_transfer
i2c_msg
里含有
addr
,所以这个函数里不需要
i2c_client
4.4 无需编写驱动程序即可访问 I2C 设备
APP 访问硬件肯定是需要驱动程序的,对于
I2C
设备,内核提供了驱动程序
drivers/i2c/i2c-dev.c
,通过它可以直接使用下面的
I2C
控制器驱动程序来访问 I2C
设备。
I2C控制器框架如下:
i2c-tools
是一套好用的工具,也是一套示例代码。
4.4.1 体验 I2C-Tools
使用一句话概括 I2C
传输:
APP
通过
I2C Controller
与
I2C Device
传输数据。
所以使用 I2C-Tools
时也需要指定:
⚫ 哪个
I2C
控制器
(
或称为
I2C BUS
、
I2C Adapter)
⚫ 哪个 I2C
设备
(
设备地址
)
⚫ 数据:读还是写、数据本身
1 交叉编译
⚫
配置环境:
vim ~/.bashrc
export ARCH=arm
export CROSS_COMPILE= arm-buildroot-linux-gnueabihf-gcc-
export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueab
ihf_sdk-buildroot/bin
⚫
修改
I2C-Tools
的
Makefile
指定交叉编译工具链
CC ?= gcc
AR ?= ar
STRIP ?= strip
改为
(
指定交叉编译工具链前缀
,
去掉问号
)
:
CC = $(CROSS_COMPILE)gcc
AR = $(CROSS_COMPILE)ar
STRIP = $(CROSS_COMPILE)strip
在 Makefile
中,“
?=
”在第一次设置变量时才会起效果,如果之前设置过该变量,则不会起效果。
2 执行 make
⚫
执行
make
时,是动态链接,需要把
libi2c.so
也放到单板上
⚫
想静态链接的话,执行:
make USE_STATIC_LIB=1
3 用法
⚫
i2cdetect
:
I2C
检测
// 列出当前的 I2C Adapter(或称为 I2C Bus、I2C Controller)
i2cdetect -l
// 打印某个 I2C Adapter 的 Functionalities, I2CBUS 为 0、1、2 等整数
i2cdetect -F I2CBUS
// 看看有哪些 I2C 设备, I2CBUS 为 0、1、2 等整数
i2cdetect -y -a I2CBUS
// 效果如下
# i2cdetect -l
i2c-1 i2c STM32F7 I2C(0x40013000) I2C adapter
i2c-2 i2c STM32F7 I2C(0x5c002000) I2C adapter
i2c-0 i2c STM32F7 I2C(0x40012000) I2C adapter
# i2cdetect -F 0
Functionalities implemented by /dev/i2c-0:
I2C yes
SMBus Quick Command yes
SMBus Send Byte yes
SMBus Receive Byte yes
SMBus Write Byte yes
SMBus Read Byte yes
SMBus Write Word yes
SMBus Read Word yes
SMBus Process Call yes
SMBus Block Write yes
SMBus Block Read yes
SMBus Block Process Call yes
SMBus PEC yes
I2C Block Write yes
I2C Block Read yes
// --表示没有该地址对应的设备, UU 表示有该设备并且它已经有驱动程序,
// 数值表示有该设备但是没有对应的设备驱动
# i2cdetect -y -a 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- UU -- -- -- 1e --
20: -- -- UU -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
⚫
i2cget
:
I2C
读
使用说明如下:
# i2cget
Usage: i2cget [-f] [-y] [-a] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x03 - 0x77, or 0x00 - 0x7f if -a is given)
MODE is one of:
b (read byte data, default)
w (read word data)
c (write byte/read byte)
Append p for SMBus PEC
使用示例:
// 读一个字节: I2CBUS 为 0、1、2 等整数, 表示 I2C Bus; CHIP-ADDRESS 表示设备地址
i2cget -f -y I2CBUS CHIP-ADDRESS
// 读某个地址上的一个字节:
// I2CBUS 为 0、1、2 等整数, 表示 I2C Bus
// CHIP-ADDRESS 表示设备地址
// DATA-ADDRESS: 芯片上寄存器地址
// MODE:有 2 个取值, b-使用`SMBus Read Byte`先发出 DATA-ADDRESS, 再读一个字节, 中间无
P 信号
// c-先 write byte, 在 read byte,中间有 P 信号
i2cget -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS MODE
// 读某个地址上的 2 个字节:
// I2CBUS 为 0、1、2 等整数, 表示 I2C Bus
// CHIP-ADDRESS 表示设备地址
// DATA-ADDRESS: 芯片上寄存器地址
// MODE:w-表示先发出 DATA-ADDRESS,再读 2 个字节
i2cget -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS MODE
⚫
i2cset
:
I2C
写
使用说明如下:
# i2cset
Usage: i2cset [-f] [-y] [-m MASK] [-r] [-a] I2CBUS CHIP-ADDRESS DATA-ADDRESS [VALUE]
... [MODE]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x03 - 0x77, or 0x00 - 0x7f if -a is given)
MODE is one of:
c (byte, no value)
b (byte data, default)
w (word data)
i (I2C block data)
s (SMBus block data)
Append p for SMBus PEC
使用示例:
// 写一个字节: I2CBUS 为 0、1、2 等整数, 表示 I2C Bus; CHIP-ADDRESS 表示设备地址
// DATA-ADDRESS 就是要写的数据
i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS
// 给 address 写 1 个字节(address, value):
// I2CBUS 为 0、1、2 等整数, 表示 I2C Bus; CHIP-ADDRESS 表示设备地址
// DATA-ADDRESS: 8 位芯片寄存器地址;
// VALUE: 8 位数值
// MODE: 可以省略,也可以写为 b
i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE [b]
// 给 address 写 2 个字节(address, value):
// I2CBUS 为 0、1、2 等整数, 表示 I2C Bus; CHIP-ADDRESS 表示设备地址
// DATA-ADDRESS: 8 位芯片寄存器地址;
// VALUE: 16 位数值
// MODE: w
i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE w
// SMBus Block Write:给 address 写 N 个字节的数据
// 发送的数据有:address, N, value1, value2, ..., valueN
// 跟`I2C Block Write`相比, 需要发送长度 N
// I2CBUS 为 0、1、2 等整数, 表示 I2C Bus; CHIP-ADDRESS 表示设备地址
// DATA-ADDRESS: 8 位芯片寄存器地址;
// VALUE1~N: N 个 8 位数值
// MODE: s
i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE1 ... VALUEN s
// I2C Block Write:给 address 写 N 个字节的数据
// 发送的数据有:address, value1, value2, ..., valueN
// 跟`SMBus Block Write`相比, 不需要发送长度 N
// I2CBUS 为 0、1、2 等整数, 表示 I2C Bus; CHIP-ADDRESS 表示设备地址
// DATA-ADDRESS: 8 位芯片寄存器地址;
// VALUE1~N: N 个 8 位数值
// MODE: i
i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE1 ... VALUEN i
⚫
i2ctransfer
:
I2C
传输
(
不是基于
SMBus)
使用说明如下:
# i2ctransfer
Usage: i2ctransfer [-f] [-y] [-v] [-V] [-a] I2CBUS DESC [DATA] [DESC [DATA]]...
I2CBUS is an integer or an I2C bus name
DESC describes the transfer in the form: {r|w}LENGTH[@address]
1) read/write-flag 2) LENGTH (range 0-65535) 3) I2C address (use last one if omit
ted)
DATA are LENGTH bytes for a write message. They can be shortened by a suffix:
= (keep value constant until LENGTH)
+ (increase value by 1 until LENGTH)
- (decrease value by 1 until LENGTH)
p (use pseudo random generator until LENGTH with value as seed)
Example (bus 0, read 8 byte at offset 0x64 from EEPROM at 0x50):
# i2ctransfer 0 w1@0x50 0x64 r8
Example (same EEPROM, at offset 0x42 write 0xff 0xfe ... 0xf0):
# i2ctransfer 0 w17@0x50 0x42 0xff-
使用举例:
// Example (bus 0, read 8 byte at offset 0x64 from EEPROM at 0x50):
# i2ctransfer -f -y 0 w1@0x50 0x64 r8
// Example (bus 0, write 3 byte at offset 0x64 from EEPROM at 0x50):
# i2ctransfer -f -y 0 w9@0x50 0x64 val1 val2 val3
// Example
// first: (bus 0, write 3 byte at offset 0x64 from EEPROM at 0x50)
// and then: (bus 0, read 3 byte at offset 0x64 from EEPROM at 0x50)
# i2ctransfer -f -y 0 w9@0x50 0x64 val1 val2 val3 r3@0x50
# i2ctransfer -f -y 0 w9@0x50 0x64 val1 val2 val3 r3 //如果设备地址不变,后面的设备地址可省略
4 使用 I2C-Tools 操作传感器 AP3216C
百问网的开发板上有光感芯片
AP3216C
:
AP3216C
是红外、光强、距离三合一的传感器,以读出光强、距离值为例,步骤如下:
⚫
复位:往寄存器
0
写入
0x4
⚫
使能:往寄存器
0
写入
0x3
⚫
读光强:读寄存器
0xC
、
0xD
得到
2
字节的光强
⚫
读距离:读寄存器
0xE
、
0xF
得到
2
字节的距离值
AP3216C
的设备地址是
0x1E
,假设节在
I2C BUS0
上,操作命令如下:
⚫
使用
SMBus
协议
i2cset -f -y 0 0x1e 0 0x4
i2cset -f -y 0 0x1e 0 0x3
i2cget -f -y 0 0x1e 0xc w
i2cget -f -y 0 0x1e 0xe w
⚫
使用
I2C
协议
i2ctransfer -f -y 0 w2@0x1e 0 0x4
i2ctransfer -f -y 0 w2@0x1e 0 0x3
i2ctransfer -f -y 0 w1@0x1e 0xc r2
i2ctransfer -f -y 0 w1@0x1e 0xe r2
4.4.2 I2C-Tools 访问 I2C 设备的 2 种方式
I2C-Tools 可以通过
SMBus
来访问
I2C
设备,也可以使用一般的
I2C
协议来访问 I2C
设备。
使用一句话概括
I2C
传输:
APP
通过
I2C Controller
与
I2C Device
传输数据。
在
APP
里,有这几个问题:
怎么指定
I2C
控制器?
⚫
i2c-dev.c
为每个
I2C
控制器
(I2C Bus
、
I2C Adapter)
都生成一个设备节点:/dev/i2c-0
、
/dev/i2c-1
等等;
⚫
open
某个
/dev/i2c-X
节点,就是去访问该
I2C
控制器下的设备;
怎么指定 I2C
设备?
通过
ioctl
指定
I2C
设备的地址
⚫
ioctl(file, I2C_SLAVE, address)
◼
如果该设备已经有了对应的设备驱动程序,则返回失败。
⚫
ioctl(file, I2C_SLAVE_FORCE, address)
◼
如果该设备已经有了对应的设备驱动程序但是还是想通过
i2c-dev
驱动来访问它,则使用这个 ioctl
来指定
I2C
设备地址。
怎么传输数据?
两种方式
⚫
一般的
I2C
方式:
ioctl(file, I2C_RDWR, &rdwr)
⚫
SMBus
方式:
ioctl(file, I2C_SMBUS, &args)
4.4.3 源码流程分析
1 使用 I2C 方式
示例代码:
i2ctransfer.c
2 使用 SMBus 方式
示例代码:
i2cget.c
、
i2cset.c
4.5 编写 APP 直接访问 EEPROM
4.5.1 硬件连接
IMX6ULL
的
I2C
模块连接方法:
4.5.2 AT24C02 访问方法
1 设备地址
从芯片手册上可以知道,AT24C02
的设备地址跟它的
A2、A1、
A0
引脚有关:
打开
I2C
模块的原理图:
从原理图可知,A2A1A0
都是
0
,所以
AT24C02
的设备地址是:
0b1010000
,即 0x50。
2 写数据
3 读数据
可以读 1
个字节,也可以连续读出多个字节。连续读多个字节时,芯片内部的地址会自动累加。当地址到达存储空间最后一个地址时,会从 0
开始。
4.5.3 编译
编译应用程序需要设置交叉编译工具链:
vim ~/.bashrc
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
1 使用 I2C-Tools 的源码
2 编译
为 IMX6ULL
编译时,有如下错误:
这是因为 IMX6ULL
的工具链自带的
include
目录中,没有
smbus.h
,需要我们自己提供这个头文件,解决方法:
⚫
提供头文件:
⚫
修改
Makefile
指定头文件目录
all:
$(CROSS_COMPILE)gcc -I ./include -o at24c02_test at24c02_test.c i2cbusses.c smbus.c
4.5.4 上机测试
注意:
以下命令在开发板中执行。
⚫
挂载
NFS
vmware 使用桥接,或者不使用
vmware
而是直接使用服务器:假设
Ubuntu IP 为
192.168.1.137
[root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
⚫
复制、执行程序
[root@100ask:~]# cp /mnt/at24c02_test /bin
[root@100ask:~]# at24c02_test 0 w www.100ask.net
[root@100ask:~]# at24c02_test 0 r
get data: www.100ask.net