I2C 应用编程

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 //如果设备地址不变,后面的设备地址可省略

使用 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
使用 SMBus 方式
示例代码: i2cget.c i2cset.c

4.5 编写 APP 直接访问 EEPROM

4.5.1 硬件连接

IMX6ULL I2C 模块连接方法:

4.5.2 AT24C02 访问方法

1 设备地址
        从芯片手册上可以知道,AT24C02 的设备地址跟它的 A2A1 A0 引脚有关:
打开 I2C 模块的原理图:
        从原理图可知,A2A1A0 都是 0 ,所以 AT24C02 的设备地址是: 0b1010000 ,即 0x50。
写数据
读数据
        可以读 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

使用 I2C-Tools 的源码

编译
        为 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

  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 在Linux应用层,我们可以使用相应的库函数来进行读写i2c设备的操作。下面是一个简单的示例代码: 首先,我们需要打开i2c设备文件,可以使用open函数来实现: ```c int fd = open("/dev/i2c-0", O_RDWR); if (fd < 0) { perror("Failed to open i2c device"); return -1; } ``` 接下来,我们需要设置要访问的i2c设备的地址,可以使用ioctl函数来实现: ```c int addr = 0x50; // 替换为你要访问的i2c设备地址 if (ioctl(fd, I2C_SLAVE, addr) < 0) { perror("Failed to set i2c device address"); return -1; } ``` 然后,我们可以使用read函数来读取i2c设备的数据: ```c unsigned char buffer[10]; int length = 5; // 要读取的字节数 if (read(fd, buffer, length) != length) { perror("Failed to read from i2c device"); return -1; } ``` 类似地,我们可以使用write函数来向i2c设备写入数据: ```c unsigned char data[10] = {0x00, 0x01, 0x02, 0x03, 0x04}; // 要写入的数据 int length = 5; // 要写入的字节数 if (write(fd, data, length) != length) { perror("Failed to write to i2c device"); return -1; } ``` 最后,我们需要关闭i2c设备文件,可以使用close函数来实现: ```c close(fd); ``` 通过以上的代码,我们可以在Linux应用层进行i2c设备的读写操作。请注意,示例中使用的设备文件路径和i2c设备地址可能需要根据实际情况进行修改。 ### 回答2: 在Linux中,要在应用层读写I2C设备,可以使用Linux提供的I2C工具以及编程接口。 1. 使用I2C工具:Linux提供了一些命令行工具来读写I2C设备,最常用的是`i2c-tools`包中的`i2cget`和`i2cset`命令。可以通过安装`i2c-tools`来获得这些工具。使用时,需要知道目标I2C设备的地址以及要读写的寄存器地址,然后可以使用`i2cget`命令读取该寄存器的值,或使用`i2cset`命令向该寄存器写入数据。 2. 使用编程接口:在应用程序中使用编程接口可以更灵活地读写I2C设备。在Linux中,可以使用标准的Linux编程接口,如`ioctl`和`open`等函数来操作I2C设备。首先需要使用`open`函数打开I2C设备文件,然后使用`ioctl`函数设置I2C设备的地址、通信速率等参数。接下来可以使用`read`和`write`函数来读取和写入I2C设备的数据。 这些方法都能实现在Linux应用层中对I2C设备进行读写操作。使用哪种方法取决于具体的需求和场景。如果只是简单的读写操作,可以选择使用I2C工具;如果需要更复杂的控制和处理逻辑,可以选择使用编程接口来实现。 ### 回答3: 在Linux应用层读写I2C,我们可以使用内核提供的i2c-dev驱动来实现。以下是一种基本的方法: 1. 打开I2C设备 首先,我们需要在应用程序中打开I2C设备文件。设备文件的路径通常为"/dev/i2c-N",其中N为I2C控制器的编号。通过调用open()函数打开设备文件,可以获得一个文件描述符(file descriptor)。 2. 设置I2C设备的从属地址 在进行I2C通信之前,我们需要设置I2C设备的从属地址(slave address)。通过ioctl()函数调用I2C_SLAVE命令,将从属地址传递给i2c-dev驱动。 3. 发送和接收数据 在已设置好从属地址的前提下,我们可以通过write()函数向I2C设备发送数据。数据应该是一个字节数组,可以包含多个字节。通过调用read()函数,可以从I2C设备中接收数据,同样以字节数组的形式返回。 4. 关闭I2C设备 在完成I2C通信后,我们应该关闭I2C设备文件,释放资源。通过调用close()函数,可以关闭文件描述符。 需要注意的是,读写I2C设备需要具有对应的权限。通常情况下,我们需要以超级用户或者具有I2C访问权限的用户身份运行应用程序。 以上是一个在Linux应用层读写I2C的简单示例。实际应用中,可能需要更复杂的数据处理和通信协议实现,但基本的读写操作是类似的。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

从入门到捕蛇者说

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值