I2C(Inter-Integrated Circuit BUS)是I2C BUS简称,中文为集成电路总线,是目前应用最广泛的总线之一,和IMX6ULL有些相关的是,刚好该总线是NXP前身的PHILIPS设计。当前仍然是应用最广泛的总线协议之一。
16.1 I2C协议 16.1.1 概述 I2C是一种串行通信总线,使用多主从架构,最初设计师为了让主板、嵌入式系统或手机用以连接低速周边设备而发展而来。在小数据量场合使用,有传输距离短,任意时刻只能有一个主机等特性。严格意义上讲,I2C应该是软硬件结合体,所以我们将分物理层和协议层来介绍该总线。(总线结构如下图)
对于I2C通信的过程,韦老师有个形象的说法:
传输数据,我们需要发数据,从主设备发送到从设备上去,也需要把数据从从设备传送到主设备上去,数据涉及到双向传输。
举个例子:
体育老师:可以把球发给学生,也可以把球从学生中接过来。
① 发球:
a. 老师说:注意了(start)
b. 老师对A学生说,我要球发给你(地址)。
c. 老师就把球发出去了(传输)。
d. A收到球之后,应该告诉老师一声(回应)。
e. 老师说下课(停止)
② 接球:
a. 老师说注意了(start),
b. 老师说:B把球发给我(地址)
c. B就把球发给老师(传输)
d. 老师收到球之后,给B说一声,表示收到球了(回应)。
e. 老师说下课(停止)
我们就使用这个简单的例子,来解释一下IIC的传输协议。
- 老师说注意了,表示开始信号(start)
- 老师告诉某个学生,表示发送地址(address)
- 老师发球/接球,表示数据的传输
- 老师/学生收到球,回应表示:回应信号(ACK)
- 老师说下课,表示IIC传输接受§
**特性1:**半双工(非全双工)
两条总线线路:
SDA(串行数据线): 主芯片通过一根SDA线既可以把数据发给从设备,也可以从SDA上读取数据,连接SDA线的引脚里面必然有两个引脚(发送引脚/接受引脚),具体可以参考下图device端I2Cn_SDA(output/input)。
**SCL(**串行时钟线):**同SDA的引脚电路结构一致,引脚的输出驱动与输入缓冲连在一起。其中输出为漏极开路的场效应管、输入缓冲为一只高输入阻抗的同相器。这样结构有如下特性:
a. 由于 SDA、SCL 为漏极开路结构,借助于外部的上拉电阻实现了信号的“线与”逻辑;
b. 引脚在输出信号的同时还将引脚上的电平进行检测,检测是否与刚才输出一致。为 “时钟同步”和“总线仲裁”提供硬件基础。
SDA和CLK连接线上连有两个上拉电阻,当总线空闲时,两根线均为高电平。连到总线上的任一器件输出的低电平,都将使总线的信号变低。(物理层连接如下图所示)
**特性2:**地址和角色可配置
每个连接到总线的器件都可以通过唯一的地址和其它器件通信,主机/从机角色和地址可配置,主机可以作为主机发送器和主机接收器。
特性3:多主机
IIC是真正的多主机总线,( IIC可以在通讯过程中,改变主机),如果两个或更多的主机同时请求总线,可以通过冲突检测和仲裁防止总线数据被破坏。
**特性4:**传输速率
传输速率在标准模式下可以达到100kb/s,快速模式下可以达到400kb/s。
**特性5:**负载和距离
节点的最大数量受限于地址空间以及总线电容决定。另外总电容也限制了实际通信距离只有几米。
16.1.2 协议层(1)数据有效性
I2C协议的数据有效性是靠时钟来保证的,在时钟的高电平周期内,SDA线上的数据必须保持稳定。数据线仅可以在时钟SCL为低电平时改变。
(2)起始和结束条件
**起始条件:**当SCL为高电平的时候,SDA线上由高到低的跳变被定义为起始条件。
**结束条件:**当SCL为高电平的时候,SDA线上由低到高的跳变被定义为停止条件,要注意起始和终止信号都是由主机发出的,连接到I2C总线上的器件,若具有I2C总线的硬件接口,则很容易检测到起始和终止信号。
总线在起始条件之后,视为忙状态,在停止条件之后被视为空闲状态。
(3)应答
每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,从机应答主机所需要的时钟仍是主机提供的,应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答
(4)数据帧格式
SDA线上每个字节必须是8位长,传输几个字节每个transfer是不限制的,每个字节后面必须跟一个ACK。数据首先用最高有效位(MSB)传输。
IMX6ULL的I2C提供了标准I2C从服务器和主服务器的功能,I2C是设计与标准的NXP I2C总线协议兼容,所以上面的通用知识完全可以应用在IMX6ULL I2C的编程和控制。
掌握IMX6ULL I2C控制器的使用,重点要熟悉IMX6ULL I2C的寄存器的操作。
重点介绍寄存器之前,我们先来看一下IMX6ULL的I2C控制器的框图,对IMX6ULL I2C的结构和特性有一个初步认识。
IMX6ULL的I2C控制器有如下额外特性:
① 多主机运行。
② 64种不同的串行时钟频率之一的软件可编程性。
③ 软件可选择的应答位。
④ 中断驱动,逐字节数据传输。
⑤ 仲裁丢失中断与自动模式切换从主到从。
⑥ 启动和停止信号生成/检测。
⑦ 重复启动信号生成。
⑧ 应答位生成和检测。
⑨ 总线忙检测。
另外的IMX6ULL 的I2C也支持两种模式:标准模式和快速模式,标准模式下I2C数据传输速率最高是100Kbits/s,在快速模式下数据传输速率最高为400Kbits/s。
通过上面的介绍对IMX6ULL的I2C控制器有了整体认识,下面结合I2C 框图对重点寄存器进行介绍。
16.2.1 I2C Memory MapI2C包含5个16-bit 的寄存器
注意:寄存器在偏移量0x0002/0x0006/0x000A/0x000E作为保留位。
可以看到I2C1的入口地址为21A_0000,这个我们重点关注,后面做实验,编程会使用到。
我们先把这些寄存器的用途做个介绍,后面在编程框架那里会结合位图来讲解。下面这部分也可以先跳过,回头当表来查。
16.2.2.1 I2C Address Register (I2Cx_IADR)地址寄存器,偏移量为0h。 a. bit15-8为保留位即只读为0。
b. bit7-1(ADR)位是I2C作为从机时的地址。从属模式是I2C的默认模式,这个地址是作为从机的相应地址,不能被软件复位。
16.2.2.2 I2C Frequency Divider Register (I2Cx_IFDR)分频寄存器,偏移量为4h。 a. bit15-6位为保留位,即只读为0。
b. bit5-1 位为I2C时钟频率
注意:该值在传输过程中不应该改变,但是可以在之前改变。
I2C_IFDR提供了一个可编程的预分频器,用于时钟配置以进行比特率选择,寄存器不会被软件重置。
寄存器IC位设置计算方法如下:
I2C的时钟源来源于IPG_CLK_ROOT=66Mhz
PLL2 = 528 MHz
PLL2_PFD2 = 528 MHz
IPG_CLK_ROOT = (PLL2_PFD2 / ahb_podf )/ ipg_podf = (528 MHz/4)/2 = 66Mhz
PER_CLK_ROOT = IPG_CLK_ROOT/perclk_podf = 66 MHz/1 = 66 MHz
设置I2C的波特率为100K, 因此当分频值=66000000/100000=660.
参考Table 31-3. I2C_IFDR Register Field Values 中只有640对应的0x15最接近
即寄存器IFDR的IC位设置为0X15。
16.2.2.3 I2C Control Register (I2Cx_I2CR)控制寄存器,偏移量为8h a. bit 15-8为保留位,即只读为0。
b. bit 7 为I2C使能位。(0 disable,1 enable)
c. bit 6 为I2C中断使能位。(0 disable,1 enable)
d. bit 5 为主/从模式选择位(0 slave mode,1 master mode )
e. bit 4 为传输方向模式选择位 (0 receive mode,1 transmit mode)
f. bit 3为应答使能位 (0 ACK , 1 NO ACK)
g. bit 2 重复开始信号(0 no repeat start,1 Generates repeat start)
h. bit 0 保留位
16.2.2.4 I2C Status Register (I2Cx_I2SR)状态寄存器,偏移量为Ch a. bit 15-8为保留位即只读为0。
b. bit 7 数据传输状态位(0 传输中,1 传输完成)
c. bit 6 I2C地址是否为从标识(0 不表示,1 是从机地址)
d. bit 5 I2C总线忙状态标识位(0 空闲,1 忙 )
e. bit 4 仲裁丢失位 (0 正常,1 仲裁丢失)
f. bit 3 保留位
g. bit 2从机读写标识位 (0 slave接收,主向从写 , 1 slave发送 主向从读)
h. bit 1 I2C中断(0无中断等待, 1有中断等待)
i. bit 0 应答信号标识位(0检测到ACK, 1检测到NO ACK)
6.2.2.5 I2C Data I/O Register (I2Cx_I2DR)数据寄存器,偏移量为10h a. bit 15-8为保留位即只读为0。
b. bit 7-1 数据字节
注意:在主接收模式下,读取数据寄存器允许发生读取并初始化下一个字节被接收。在从模式下,相同功能需要编址后生效。
低8位为有效数据位,发送数据时将数据写到这个寄存器中,如果要接收时直接读取该寄存器中的数据。
16.3 AP3216C操作方法 16.3.1 AP3216C简介 AP3216C 模块的核心就是这个芯片本身。这颗芯片集成了光强传感器(ALS: Ambient Light Sensor),接近传感器(PS: Proximity Sensor),还有一个红外LED(IR LED)。这个芯片设计的用途是给手机之类的使用,比如:返回当前环境光强以便调整屏幕亮度;用户接听电话时,将手机放置在耳边后,自动关闭屏幕避免用户误触碰。该芯片通过I2C接口作为slave与主控制器相连,支持中断。
16.3.2 AP3216C特性 ① 接口:I2C
② 速率:FS mode 可达400kbit/s
③ 模式:ALS/PS+IR/ALS+PS+IR/PD/ALS once/SW Reset/PS+IR once/ALS+PS+IR once。
④ 内置温度补偿电路。
⑤ 工作温度:-30℃ to +80℃
⑥ ALS
a. (0-65536)16位有效的线性输出
b. 4量程动态范围可选择
c. 防闪烁(50/60 HZ)
d. 高敏感性@黑玻璃
e. 窗口损失补偿
⑦ PS
a. (0-1023)10位有效线性输出
b. 4增益
c. 高环境光抑制
d. 串扰补偿
⑧ 封装大小:4.1mm x 2.4mm x 1.35mm
16.3.3 AP3216C结构图 下面是AP3216C的结构图以及典型电路应用图
从图上看AP3216C结构简单,应用方便。本来模块我们其实也不用太深究原理,所以重点我们放在对典型I2C从设备的应用上。
16.3.4 AP3216C寄存器及使用 a. I2C从设备地址
从设备地址有7bit ,一个读/写位应该由主设备附加到从设备地址以正确地与设备通信。
AP3216的地址是0X1E。
b. 系统寄存器表
同其他所有从设备一样,AP3216C有它的内部寄存器让我们来设置和操作。表格如下所示。可以知道。次级地址0x00为系统配置寄存器,并且提供了后面7bit组合的工作模式供选择。
后面会参考这张表格对AP3216C进行编程并读取数据。
16.4 I2C控制器编程_框架 之前的所有铺垫都是为了实现I2C通讯,所以怎么用代码实现也是尤为关键的一个问题。本节源码目录在裸机Git仓库 NoosProgramProject/(16_I2C编程\001_example_i2c_ap3216c_led_show\i2c.c和
裸机Git仓库 NoosProgramProject/(16_I2C编程\001_example_i2c_ap3216c_led_show\i2c.h
首先,需要定义I2C寄存器的入口地址,各个寄存器绝对地址,我们代码仅使用I2C1,故仅先定义I2C1相关寄存器。
/*
*The I2C contains five 16-bit registers.
*/
/* 绝对地址 寄存器名 位宽(bit)权限 复位值 章节/页
* 21A_0000 (I2C1_IADR) 16 R/W 0000h 31.7.1/1463
* 21A_0004 (I2C1_IFDR) 16 R/W 0000h 31.7.2/1463
* 21A_0008 (I2C1_I2CR) 16 R/W 0000h 31.7.3/1465
* 21A_000C (I2C1_I2SR) 16 R/W 0081h 31.7.4/1466
* 21A_0010 (I2C1_I2DR) 16 R/W 0000h 31.7.5/1468
*/
#define I2C1_BASE_ADDR (0x21A0000u)
/* I2C1 Base address */
#define I2C1 ((I2C_REGISTERS *)I2C1_BASE_ADDR)
有了上面的地址,定义如下寄存器结构体,这样在使用时,我们仅需要把入口地址定义成如下结构,那么就可以通用I2C1-IC25,在使用时传入相应的入口地址即可。
/* 寄存器地址的宏结构体定义,此种方式仅定义入口地址即可 */
/* all registers address is Base address + xh offset*/
typedef struct tagRegisters{
volatile uint16_t IADR; /*I2C Address Register, offset: 0x0 */
uint8_t ReservedIADR[2];
volatile uint16_t IFDR; /*I2C Frequency Divider Register, offset: 0x4 */
uint8_t ReservedIFDR[2];
volatile uint16_t I2CR; /*I2C Control Register, offset: 0x8 */
uint8_t ReservedI2CR[2];
volatile uint16_t I2SR; /*I2C Status Register, offset: 0xC */
uint8_t ReservedI2SR[2];
volatile uint16_t I2DR; /*I2C Data I/O Register, offset: 0x10 */
} I2C_REGISTERS;
有了基本的信息,我们也要有相应的操作,主要有读和写两个操作码。
typedef enum enI2C_OPCODE
{
I2C_WRITE = 0, /* 主机向从机写数据 */
I2C_READ = 1, /* 主机从从机读数据 */
I2C_DONOTHING_BULL
} I2C_OPCODE;
然后需要有个分配和传输承载信息角色的载体,我们仅以做master为例,如果做通用的结构体,还可以加上角色等信息。而master的主传输结构体包括目标以下信息:
typedef struct tagI2cTransfer
{
uint8_t ucSlaveAddress; /* 7位从机地址 */
uint32_t ulOpcode ; /* 操作码*/
uint32_t ulSubAddress; /* 目标寄存器地址 */
uint8_t ulSubAddressLen; /* 寄存器地址长度 */
volatile uint32_t ulLenth; /* 数据长度 */
uint8_t *volatile pbuf; /* 数据*/
} I2C_TRANSFER;
有了这些寄存器的基本信息、操作方式、传输结构,下面来实现具体的函数和功能。
16.4.1 i2c_initvoid i2c_init(I2C_REGISTERS *I2C_BASE);81 void i2c_init(I2C_REGISTERS *I2C_BASE);
通过传入I2C1的入口地址,完成初始化。
因为不使用中断服务函数模式,初始化代码非常简单,我们仅需要将I2CR(bit7)(位图如下)
置0后写入IFDR为0x15(位图如下),设置波特率为100k。(具体计算方法参考16.2 IMX6ULL的I2C控制器操作与寄存器介绍中关于分频寄存器),然后再使能I2C,将I2CR(bit7)置1 。
代码如下:
I2C_BASE->I2CR &= ~(1 I2CR |= (1
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?