I2C
前提
Mini_F0140原理图上I2C连接着24C02:
一.IIC介绍
I2C(Inter-Integrated Circuit)是一种通用的总线协议,实现I2C需要两根信号线完成信息交换,SCL时钟作为信号线,SDA作为数据输入/输出线。I2C属于同步通信,由于输入输出数据均使用一根线,因此通信方向为半双工。
主要特征有:
1. I2C 总线协议转换器/并行总线
2. 半双工同步操作
3. 支持主从模式
4. 支持 7 位和 10 位地址格式
5. 支持起始(START)、停止(STOP)、重新起始(RESTART)以及应答(ACK)信号的生成和检测
6. 支持标准模式(最大 100Kbps)、快速模式(最大 400Kbps)
7. 分别有 2 字节的发送和接收缓冲
8. 支持过滤毛刺功能
9. 支持 DMA 操作
10. 支持中断和查询操作
11. 支持多个从地址(详细见用户手册)
下图为信号变化及其特定含义:
当主发送器如下图所示传输数据时,从接收器在接收到的每个字节后产生一个 ACK 来响应主发送器。
二.24C02
24C02是低工作电压的2K位串行电可擦除只读存储器,内部组织为256个字节,每个字节8位,该芯片被广泛应用于低电压及低功耗的工商业领域。
主器件通过发送一个起始信号启动发送过程,然后发送它所要寻址的从器件的地址。8位从器件地址的高4位固定为1010。接下来的3位(A2、A1、A0)为器件的地址位。当接收数据时,与I2C连接的从器件名为0xA0。
三.例程
向从机写数据后,再读取数据。
1. I2C配置
RCC_APB1PeriphClockCmd(RCC_APB1ENR_I2C1, ENABLE); I2C_StructInit(&I2C_InitStruct); //Configure I2C as master mode I2C_InitStruct.Mode = I2C_CR_MASTER; I2C_InitStruct.OwnAddress = 0; I2C_InitStruct.Speed = I2C_CR_STD; I2C_InitStruct.ClockSpeed = 100000; I2C_Init(I2Cx, &I2C_InitStruct); I2C_Send7bitAddress(I2Cx, 0xA0, I2C_Direction_Transmitter); I2C_Cmd(I2Cx, ENABLE);
2. 定义24C02结构体
typedef struct { u8 busy; u8 ack; u8 fault; u8 opt; u8 sub; u8 cnt; u8* ptr; u8 sadd; } gEepromTypeDef;
3. 写数据
EEPROM_WriteByte(sub); while (cnt --) { EEPROM_WriteByte(*ptr); ptr++; } I2C_GenerateSTOP(I2C1, ENABLE); while((I2C_GetITStatus(I2C1, I2C_IT_STOP_DET)) == 0); gEeprom.ack = true; gEeprom.busy = false; EEPROM_WaitEEready(); return true;
4. 读数据
u8 i, flag = 0, _cnt = 0; for (i = 0; i < gEeprom.cnt; i++) { while(1) { //Write command is sent when RX FIFO is not full if ((I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_TFNF)) && (flag == 0)) { I2C_ReadCmd(I2C1); _cnt++; if (_cnt == gEeprom.cnt) flag = 1; } //Check receive FIFO not empty if (I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_RFNE)) { gEeprom.ptr[i] = I2C_ReceiveData(I2C1); break; } } }
5. 结果
来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
I2C接口简介
I2C接口是由数据线SDA和时钟线SCL构成,在标准模式下通信速度可达到100kHz,快速模式下则可以达到400kHz,增强快速模式可达到1MHz。一帧数据传输从开始信号开始,在结束信号后停止,在收到开始信号后总线被认为是繁忙的,当收到结束信号后,总线被认为再次空闲。I2C接口具有主机和从机模式、多主机功能、可编程建立和保持时间、时钟延展功能、DMA存取数据、支持SMBus 2.0协议等特点。
I2C接口通信
主机通信流程
主机通信初始化
1. 主机时钟初始化
在启动外设(I2CEN)之前,必须先设置I2Cx_CLKCTRL寄存器的各个位用以配置I2C主时钟。
DIV[7:0]:I2C时钟分频;
SDAD[3:0]:数据保持时间(tHD;DAT)
SCLD[3:0]:数据建立时间(tSU;DAT)
SCLH[7:0]:SCL高电平时间
SCLL[7:0]:SCL低电平时间
该寄存器的配置可以使用Artery_I2C_Timing_Configuration时钟配置工具计算,见第三章节。低电平控制:当检测到SCL总线为低电平时,内部SCLL计数器开始计数,当计数值达到SCLL值时,释放SCL线,SCL线变为高电平。
高电平控制:当检测到SCL总线为高电平时,内部SCLH计数器开始计数,当计数值达到SCLH值时,拉低SCL线,SCL线变为低电平,当在高电平期间,如果被外部总线拉低,那么内部SCLH计数器停止计数,并开始低电平计数,这为时钟同步提供了条件。
2. 主机通信初始化
在启动通讯前须先设定I2C_CTRL2寄存器中的几项参数:
1) 设置传输字节数
≤255字节
配置I2C_CTRL2的RLDEN=0,关闭重载模式
配置I2C_CTRL2的CNT[7:0]=N
>255字节
配置I2C_CTRL2的RLDEN=1,使能重载模式
配置I2C_CTRL2的CNT[7:0]=255
剩余传输字节数N=N-255
ASTOPEN=0:软件结束模式,当数据传输完成后,I2C_STS的TDC标志置1,软件设置GENSTOP=1或者GENSTART=1,发送STOP条件或者START条件。
ASTOPEN=1:自动结束模式,当数据传输完成后,自动发送STOP条件。
设置寻址的从机地址值(I2C_CTRL2的SADDR)
设置从机地址模式(I2C_CTRL2的ADDR10)
ADDR10=0:7位地址模式
ADDR10=1:10位地址模式
DIR=0:主机接收数据
DIR=1:主机发送数据
5) 开始传输
设置I2C_CTRL2的GENSTART=1,主机开始在总线上发送START条件和从机地址。
3. 主机10 bits寻址的特殊时序初始化
在10位地址传输模式下,I2C_CTRL2的READH10用于产生特殊时序,当READH10=1时,支持如下传输序:主机先发送数据给从机,然后再从从机读取数据,传输时序图如下图所示:
主机在软件结束模式(ASTOPEN=0)下,发送数据到从机,当数据发送完成后设置READH10=1,然后再从从机接收数据。
主机通信初始化软件接口
主机通信初始化所用到的软件接口通过独立的函数接口实现,如下:
i2c_init函数三个参数分别为:所使用的I2C、数字滤波值和主机时钟配置值。
i2c_transmit_set 函数用于初始化通信参数,包括:所使用的I2C、从机地址、传输字节数、停止条件产生模式和起始条件产生模式。
i2c_addr10_mode_enable函数用于使能10位地址模式。
i2c_addr10_header_enable函数用于使能10位地址头读取时序,即主机发送完整的10位从机地址读序列或主机只发送10位地址的前7位。
主机发送流程
1) I2C_TXDT数据寄存器为空,I2C_STS的TDIS=1;
2) 向TXDT数据寄存器写入数据,数据开始发送;
3) 重复1、2步骤直到发送CNT[7:0]个数据;
剩余字节数N>255:向CNT写入255,N=N-255,TCRLD被自动清0,传输继续; 剩余字节数N≤255:关闭重载模式(RLDEN=0),向CNT写入N,TCRLD被自动清0,传输继续。
停止条件产生: 软件结束模式(ASTOPEN=0):此时I2C_STS的TDC置1,设置GENSTOP=1产生STOP条件; 自动结束模式(ASTOPEN=1):自动产生STOP条件。 等待产生STOP条件,当STOP条件产生时,I2C_STS的STOPF置1,将I2C_CLR的STOPC写1,清除STOPF标志,传输结束。
主机发送流程软件接口
主机发送通过独立的函数接口实现,如下:
i2c_master_transmit函数为i2c_application.c文件所提供的应用层接口函数,参数包括:I2C结构体指针、从机地址、发送数据指针、发送数据字节数和函数超时时间。
注:此函数为Artery所提供的标准主机发送函数。用户也可根据前述主机发送流程,自行编写主机发送函数。
主机接收流程
1) 当收到数据后,RDBF=1,读取RXDT数据寄存器,RDBF被自动清零;
2) 重复步骤2直到接收CNT[7:0]个数据;
剩余字节数N>255:向CNT写入255,N=N-255,TCRLD被自动清0,传输继续; 剩余字节数N≤255:关闭重载模式(RLDEN=0),向CNT写入N,TCRLD被自动清0,传输继续。
4) 当在接收到最后一个字节时,主机会自动发送一个NACK。
5) 结束时序
停止条件产生: 软件结束模式(ASTOPEN=0):此时I2C_STS的TDC置1,设置GENSTOP=1产生STOP条件; 自动结束模式(ASTOPEN=1):自动产生STOP条件。 等待产生STOP条件,当STOP条件产生时,I2C_STS的STOPF置1,将I2C_CLR的STOPC写1,清除STOPF标志,传输结束。
主机接收流程软件接口
主机接收通过独立的函数接口实现,如下:
i2c_master_receive函数为i2c_application.c文件所提供的应用层接口函数,参数包括:I2C结构体指针、从机地址、接收数据指针、接收数据字节数和函数超时时间。
注:此函数为Artery所提供的标准主机接收函数。用户也可根据前述主机接收流程,自行编写主机接收函数。
从机通信流程
从机通信初始化
1. 从机地址配置
每个I2C从设备可同时支持2个从设备地址,由OADDR1和OADDR2指定
I2C_OADDR1
通过ADDR1EN使能
通过ADDR1MODE配置为7位(默认)或10位地址
通过ADDR2EN使能
固定7位地址模式
可通过ADDR2MASK[2:0]来在进行地址匹配比较时屏蔽掉0~7个LSB地址位
ADDR2MASK=0表示7位地址中的每一位都要参与匹配比较
ADDR2MASK=7表示任何非保留地址的7位地址都会被该从设备应答
2. 从机地址匹配
当I2C启用的地址选中匹配时,ADDRF中断状态标志会被置1,如果ADDRIEN位为1,就会产生一个中断。如果两个从地址都使能,在地址匹配产生ADDR中断时,可以查看状态寄存器中的ADDR[6:0]来得知是OADDR1还是OADDR2被寻址了。
3. 从机字节控制模式(通常SMBus模式下才使用)
从设备可以对每个收到的字节进行应答控制。
所需配置:SCTRL=1 & RLDEN=1 & STRETCH=0 & CNT≥1
从机字节控制流程:
1) 每收到一个字节TCRLD置位,时钟延展于第8和第9个脉冲之间
2) 软件读取RXDT中的值,并决定是否置位ACK
3) 软件重装载CNT=1来停止时钟延展
4) 应答或非应答信号在第9个脉冲时刻出现在总线上
注意:
置位SCTRL时,必须开启时钟延展,即STRETCH=0
从机通信初始化软件接口
从机通信初始化所用到的软件接口通过独立的函数接口实现,如下:
i2c_own_address1_set函数用于配置OADDR1地址模式以及ADDR1地址值。
i2c_own_address2_set函数用于配置ADDR2地址值以及ADDR2屏蔽位。
i2c_own_address2_enable函数用于使能ADDR2地址。
i2c_slave_data_ctrl_enable函数用于使能从机字节控制模式。
i2c_clock_stretch_enable函数用于使能从机时钟延展功能。
i2c_reload_enable函数用于使能发送数据重载模式。
从机发送流程
1) 响应主机地址,匹配时回复ACK;
2) TXDT为空时,置位TDIS,从设备写入发送数据;
3) 每发送一个字节会收到ACK,且置位TDIS;
置位NACKF,产生中断; 从设备自动释放SCL和SDA(以便主设备发送STOP或RESTART);
置位STOPF,产生中断;
当从机发送开启时钟延展(STRETCH=0)时,在等待ADDRF标志时和发送前一个数据的第9个时钟脉冲后,会把TXDT中的数据拷贝到移位寄存器中,如果此时TDIS还是置位,表示TXDT没有写进待发送数据,将发生时钟延展,如下流程图:
图9. I2C从机发送流程图
需要注意的是,在时钟延展关闭(STRETCH=1)的情况下,如果在将要传输数据的第一个Bit位开始发送之前,也就是SDA边沿产生之前,如果数据还未写入TXDT数据寄存器,那么会发生欠载错误,此时I2C_STS的OUF将会置1,并将0xFF发送到总线。
为了能及时的写入数据,可以在通信开始前,先将数据写入到DT寄存器:软件先将TDBE置1,目的是为了清空TXDT寄存器的数据,然后将第一个数据写入TXDT寄存器,此时TDBE清零。
从机发送流程软件接口
从机发送通过独立的函数接口实现,如下:
i2c_slave_transmit函数为i2c_application.c文件所提供的应用层接口函数,参数包括:I2C结构体指针、发送数据指针、发送数据字节数和函数超时时间。
注:此函数为Artery所提供的标准从机发送函数。用户也可根据前述从机发送流程,自行编写从机发送函数。
从机接收流程
1) 当收到数据后,RDBF=1,读取RXDT数据寄存器,RDBF被自动清零;
2) 重复步骤2直到所有数据接收完成;
3) 等待收到STOP条件,当收到STOP条件时,I2C_STS的STOPF置1,将I2C_CLR的STOPC写1,清除STOPF标志,传输结束。
图12. I2C从机接收时序图
从机接收流程软件接口
从机接收通过独立的函数接口实现,如下:
i2c_slave_receive函数为i2c_application.c文件所提供的应用层接口函数,参数包括:I2C结构体指针、接收数据指针、接收数据字节数和函数超时时间。
注:此函数为Artery所提供的标准从机接收函数。用户也可根据前述从机接收流程,自行编写从机接收函数。
I2C配置工具
功能简介
I2C配置工具Artery_I2C_Timing_Configuration.exe可以实现对主机和从机的时钟、数字滤波、模拟滤波配置。
资源准备
1) 选择芯片型号
选择当前使用的芯片型号,例如可以选择AT32F425。
Master:主模式,I2C作为主机;
Slave:从模式,I2C作为从机。
Standard-mode:标准模式,范围0~100kHz;
Fast-mode:快速模式,范围0~400kHz;
Fast-mode Plus:增强快速模式,范围0~1000kHz。
4) 设置I2C速度(单位kHz)
根据实际需求设置I2C通信速度,例如需要通信速度为10kHz,那么这里设置为10。
5) 设置I2C时钟源频率(单位kHz)
根据实际使用的I2C时钟源频率来配置,例如AT32425 I2C时钟源为PCLK1,当AT32425主频为144MHz,APB1为144MHz时,这里设置为144000。
On:打开;
Off:关闭。
7) 数字滤波(范围0~15)
数字滤波时间=数字滤波值xTI2C_CLK;
其中TI2C_CLK=1/I2C时钟源频率。
当值为0时,数字滤波关闭,当值>0时将过滤小于数字滤波时间的脉冲。
8) 上升时间(tr单位ns)
SCL和SDA总线的上升沿,如图18所示。I2C协议中规定了在标准模式(Standard-mode)、
快速模式(Fast-mode)、增强快速模式(Fast-mode Plus)下的范围,详情请参照表1。上升时间和上拉电阻的阻值关系很大,上拉电阻越小,上升时间越短,可以支持的通信速度就越快,但是功耗也越高。
表2中给出了一些常用上拉电阻阻值所对应的上升沿时间,实际可能会因为总线挂的设备数量、布线等差异而有所不同,仅供参考。
9) 下降时间(tf单位ns)
SCL和SDA总线的下降沿,如图18所示。I2C协议中规定了在标准模式(Standard-mode)、快速模式(Fast-mode)、增强快速模式(Fast-mode Plus)下的范围,详情请参照表1。
表2. 常用上拉电阻阻值的tr、tf参考值(VDD=3.3V)
注:该值是总线上连接两片AT32MCU,一个作为主机,一个作为从机测试出来的值,实际可能会因为总线挂的设备数量、布线等差异而有所不同。
10) 产生代码
点击产生代码,上诉配置的值,将会以代码的形式产生出来,如下图红框所示,只需要将右侧输出的代码替换到自己的程序即可。
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
1、EEPROM 简介
EEPROM全称为EEPROM(Electrically Erasable Programmable Read Only Memory)是电可擦除可编程只读存储器。虽然名称为只读存储器,但是擦除和写入都是直接使用电路控制,不需要再使用外部设备来擦写,即设备在运行过程中即可随时擦除和写入。可以按字节为单位修改数据,无需整个芯片擦除,且掉电后数据不丢失,一般用来存储一些配置信息,以便系统重新上电的时候加载。
2、EPROM 操作说明
2.1 通讯方式
主要通讯方式有 I2C 和 SPI 协议,本文将介绍如何通过 I2C 来驱动EEPROM。
2.2 设备地址
如果仅接入一个AT24C02,可以将设备的A0、A1、A2引脚全部接入低电平,那么此时该设备的地址为0xA0。
2.3 I2C配置流程
开时钟,外设清零,参数配置,初始化,引脚配置,使能
2.4 写入数据
写入一个字节:
MCU先向AT24C02写入一个地址,然后再写入数据。
写入一页:
MCU先向AT24C02写入一个地址,然后再依次写入数据,注意AT24C02一页有8个字节,每页开始地址均是8的整数倍,一次页写入操作地址不能超过当前页的尾地址。
连续写入:
AT24C02本身没有提供连续写入的操作,因此必须先将数据按页地址分为若干页,然后再依次调用页写入操作进行写入。
2.5 读出数据
读取当前字节:
MCU直接发起读操作,设备返回当前字节,当前字节自动加1,该操作较少使用。
读取指定地址一个字节:
MCU先向AT24C02写入一个地址,然后再发起一个读操作,AT24C02返回该地址存储的字节。
连续读取:
MCU发起读当前字节,或者读指定地址字节,设备返回数据,MCU发送ACK,设备继续返回后续地址数据,直到MCU发送NACK,设备不再返回数据。
2.6 样例测试
初始化两个数组,配置I2C,调用写入和读取的函数操作EEPROM,通过串口打印观察结果。
运行实例
来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
一、概述
CW32L083 内部集成 2 个 I2C 控制器,能按照设定的传输速率(标准,快速,高速)将需要发送的数据按照 I2C 规范串行发送到 I2C 总线上,或从总线上接收数据,并对通信过程中的状态进行检测,另外还支持多主机通信中的总线冲突和仲裁处理。
二、主要功能
• 支持主机发送 / 接收,从机发送 / 接收四种工作模式
• 支持时钟延展 ( 时钟同步 ) 和多主机通信冲突仲裁
• 支持标准 (100Kbps)/ 快速 (400Kbps)/ 高速 (1Mbps) 三种工作速率
• 支持 7bit 寻址功能
• 支持 3个从机地址
• 支持广播地址
• 支持输入信号噪声过滤功能
• 支持中断状态查询功能
1.协议介绍
I2C 总线使用两根信号线(数据线 SDA 和时钟线 SCL)在设备间传输数据。SCL 为单向时钟线,固定由主机驱动。SDA 为双向数据线,在数据传输过程中由收发两端分时驱动。I2C 总线上可以连接多个设备,所有设备在没有进行数据传输时都处于空闲状态(未寻址从机接收模式),任一设备都可以作为主机发送 START 起始信号来开始数据传输,在 STOP 停止信号出现在总线上之前,总线一直处于 被占用状态。I2C 通信采用主从结构,并由主机发起和结束通信。主机通过发送 START 起始信号来发起通信,之后发送 SLA+W/R 共 8bit 数据(其中,SLA 为 7bit 从机地址,W/R 为读写位),并在第 9个SCL 时钟释放 SDA 总线, 对应的从机在第 9个SCL 时钟占用 SDA 总线并输出 ACK 应答信号,完成从机寻址。此后根据主机发送的第 1 字 节的 W/R 位来决定数据通信的发端和收端,发端每发送 1个字节数据,收端必须回应 1个ACK 应答信号。数据传输完成后,主机发送 STOP 信号结束本次通信。
2.功能框图
I2C 模块主要包括时钟发生器、输入滤波器、地址比较器、协议控制逻辑、仲裁和同步逻辑、以及相关寄存器等。
CW32L083 支持用户灵活选择 GPIO 作为 I2C 通信引脚,如下表所示:
3.I2C中断
I2C 控制寄存器 I2Cx_CR 的 SI 位域为中断标志位。当 I2C 状态寄存器 I2Cx_STAT 的 STAT 位域值发生改变(变成 0xF8 除外)时,I2Cx_CR.SI 标志位就会被置位,同时产生中断请求。在用户 I2C 中断服务程序中,应查询 I2C 状态寄存器 I2Cx_STAT 的 STAT 位域值获取 I2C 总线的状态,以确定中断产生原因。设置 I2Cx_CR.SI 为 0 清除该标志位。
4.工作模式
I2C 控制器支持 4 种工作模式:主机发送模式、主机接收模式、从机发送模式、从机接收模式。另外还支持广播 接收模式,其工作方式和从机接收模式类似。
三、EEPROM(CW24C02AD)
1.功能简介
CW24C02是一个2Kbit的串行EEPROM存储芯片,可存储256个字节数据。芯片内部分为32页,每页8字节。工作电压范围为1.7V到5.5V,I2C接口时钟频率为 1MHz (5V,3V),400 KHz (1.7V)。器件地址为1010 A2 A1 A0,对于我们单板A2 A1 A0引脚全部接GND,故器件地址为1010000,即0x50。器件内部存储空间地址长度8 bit。
2.读写时序
字节写操作时序:起始信号+器件地址(7bit)+读写指示(1bit)+存储空间地址(8bit)+写入数据(8bit)+停止信号,即可完成指定字节写入操作。
页写操作时序:起始信号+器件地址(7bit)+读写指示(1bit)+存储空间地址(8bit)+写入数据(8bit*8)+停止信号,即可完成指定地址(必须是页起始地址)的页写入操作。
随机读操作时序:起始信号+器件地址(7bit)+读写指示(1bit)+存储空间地址(8bit)+重复起始信号+器件地址(7bit)+读写指示(1bit),之后器件会返回1字节数据,主机收到后发送停止信号,即可完成指定字节读取操作。
顺序读操作时序:和随机读时序类似,只是在主机接收到1字节数据后,不发送停止信号,而是继续发送时钟进行下一个字节数据的接收,直到所有所需读取的数据全部读取,之后再发送停止信号。
四、硬件连接
如下图所示,MCU和EEPROM通过I2C总线互连。
五、实例演示:MCU采用页写和顺序读操作时序完成EERPOM的访问。
1.I2C读写EEPROM芯片中断函数(I2C分为I2C1和I2C2)
void I2c1EepromReadWriteInterruptFunction(void) { u8State = I2C_GetState(CW_I2C1);// I2C:获取状态寄存器函数 switch(u8State) { case 0x08: //发送完START信号 I2C_GenerateSTART(CW_I2C1, DISABLE);// 发送START信号 I2C_Send7bitAddress(CW_I2C1, I2C_SLAVEADDRESS,0X00);// 做主时发送从机地址字节 break; case 0x10: //发送完重复起始信号 I2C_GenerateSTART(CW_I2C1, DISABLE); if(0 == SendFlg) { I2C_Send7bitAddress(CW_I2C1, I2C_SLAVEADDRESS,0X00); //写命令 } else { I2C_Send7bitAddress(CW_I2C1, I2C_SLAVEADDRESS,0X01); //读命令,eeprom 随机读 } break; case 0x18: //发送完SLA+W/R字节 I2C_GenerateSTART(CW_I2C1, DISABLE); I2C_SendData(CW_I2C1, u8Addr); //发送访问EEPROM的目标地址字节 break; case 0x20: //发送完SLA+W后从机返回NACK case 0x38: //主机在发送 SLA+W 阶段或者发送数据阶段丢失仲裁 或者 主机在发送 SLA+R 阶段或者回应 NACK 阶段丢失仲裁 case 0x30: //发送完一个数据字节后从机返回NACK case 0x48: //发送完SLA+R后从机返回NACK I2C_GenerateSTOP(CW_I2C1, ENABLE); I2C_GenerateSTART(CW_I2C1, ENABLE); break; case 0x58: //接收到一个数据字节,且NACK已回复 u8Recdata[u8RecvLen++] = I2C_ReceiveData(CW_I2C1);//所有数据读取完成,NACK已发送 receivedflag =1; I2C_GenerateSTOP(CW_I2C1, ENABLE);//发送停止条件 break; case 0x28: //发送完1字节数据:发送EEPROM中memory地址也会产生,发送后面的数据也会产生 if(0 == SendFlg) { if(u8SendLen <WRITELEN) { I2C_SendData(CW_I2C1,u8Senddata[u8SendLen++]); } else { u8SendLen = 0; Comm_flg = 1; SendFlg = 1; I2C_GenerateSTOP(CW_I2C1, ENABLE);//发送完数据,发送停止信号 } } else//SendFlg=1为读,SendFlg=0为写。读数据发送完地址字节后,重复起始条件 { CW_I2C1->CR_f.STA = 1; //set start //发送重复START信号,START生成函数改写后,会导致0X10状态被略过,故此处不调用函数 I2C_GenerateSTOP(CW_I2C1, DISABLE); } break; case 0x40: //发送完SLA+R信号,开始接收数据 u8RecvLen = 0; if(READLEN>1) { I2C_AcknowledgeConfig(CW_I2C1,ENABLE);//读取数据超过1个字节才发送ACK } break; case 0x50: //接收完一字节数据,在接收最后1字节数据之前设置AA=0; u8Recdata[u8RecvLen++] = I2C_ReceiveData(CW_I2C1); if(u8RecvLen==READLEN-1) { I2C_AcknowledgeConfig(CW_I2C1,DISABLE);; } break; } I2C_ClearIrq(CW_I2C1); }
2.设置系统时钟
void RCC_Configuration(void) { CW_SYSCTRL->APBEN1_f.I2C1 = 1U; }
3.设置GPIO口
void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure = {0}; CW_SYSCTRL->AHBEN_f.GPIOA = 1; CW_SYSCTRL->AHBEN_f.GPIOB = 1; CW_SYSCTRL->AHBEN_f.GPIOC = 1; CW_SYSCTRL->AHBEN_f.GPIOD = 1; CW_SYSCTRL->AHBEN_f.GPIOE = 1; CW_SYSCTRL->AHBEN_f.GPIOF = 1; PB10_AFx_I2C1SCL(); PB11_AFx_I2C1SDA(); GPIO_InitStructure.Pins = I2C1_SCL_GPIO_PIN | I2C1_SDA_GPIO_PIN; GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD; GPIO_Init(I2C1_SCL_GPIO_PORT, &GPIO_InitStructure); }
4.配置嵌套矢量中断控制器
void NVIC_Configuration(void) { __disable_irq(); NVIC_EnableIRQ(I2C1_IRQn); __enable_irq(); } void I2C1_IRQHandler(void) { I2c1EepromReadWriteInterruptFunction(); }
5.定义常量
#define I2C1_SCL_GPIO_PORT CW_GPIOB #define I2C1_SCL_GPIO_PIN GPIO_PIN_10 #define I2C1_SDA_GPIO_PORT CW_GPIOB #define I2C1_SDA_GPIO_PIN GPIO_PIN_11 //EEPROM内部地址 uint8_t u8Addr = 0x00; //地址字节 #define WRITELEN 8 //写数据长度 #define READLEN 8 //读数据长度 #define WriteReadCycle 35 //写读次数,每次写入数据为n+i(n为次数,i=0~7) uint8_t u8Senddata[8] = {0x66,0x02,0x03,0x04,0x05,0x60,0x70,0x20}; uint8_t u8Senddata2[8] = {0x55,0xAA,0xAA,0x55,0x55,0xAA,0x55,0xAA}; uint8_t u8Recdata[16]= {0x00}; uint8_t u8SendLen=0; uint8_t u8RecvLen=0; uint8_t SendFlg = 0,Comm_flg = 0; uint8_t u8recvflg=0; uint8_t u8State = 0; uint8_t receivedflag = 0; //读取完成标志
6.主程序:利用I2C接口,采用中断方式读写EEPROM芯片(CW24C02)。
int32_t main(void) { I2C_InitTypeDef I2C_InitStruct = {0}; uint16_t tempcnt = 0 ; RCC_Configuration();//时钟初始化 GPIO_Configuration();//IO口初始化 //I2C初始化 I2C_InitStruct.I2C_Baud = 0x01;//500K=(8000000/(8*(1+1)) ,波特率计数器配置 I2C_InitStruct.I2C_BaudEn = ENABLE;// 波特率计数器使能 I2C_InitStruct.I2C_FLT = DISABLE; //<FLT配置 I2C_InitStruct.I2C_AA = DISABLE; //<ACK配置 I2C1_DeInit(); I2C_Master_Init(CW_I2C1,&I2C_InitStruct);//初始化模块 NVIC_Configuration();//中断设置 //I2C模块开始工作 I2C_Cmd(CW_I2C1,ENABLE); //模块使能 tempcnt =0; for(uint8_t i=0; i<8; i++) { u8Senddata[i] = i; } while(1) { I2C_GenerateSTART(CW_I2C1, ENABLE); //开始信号 while(1) { while(!Comm_flg) ; //等待数据发送完成 FirmwareDelay(3000); Comm_flg = 0; //启动读数据过程 receivedflag=0; I2C_GenerateSTART(CW_I2C1, ENABLE); //开始信号 while(!receivedflag) ; //等待数据读取完成 receivedflag = 0; //初始化下一次写数据 SendFlg = 0; u8RecvLen = 0; tempcnt++; for(uint8_t i=0; i<8; i++) { u8Senddata[i] =tempcnt+i; } break; } if(tempcnt >=WriteReadCycle) //测试次数完成,退出 { break; } } while(1); }
7.程序流程
程序完成I2C主设备配置后,先将u8Senddata数组中的内容写入到EEPROM的第1页(CW24C02每页8字节):发送START信号后,I2C模块会产生状态改变中断,在中断服务程序中根据不同状态值进行不同处理,直到完成CW24C02的页写模式所有数据字节以及STOP信号发送,发送完成后置写操作流程完成标志。主循环中判断到写操作流程完成后,启动从EERROM的第1页数据读取流程:发送启动信号后,I2C模块会产生状态改变中断,在中断服务程序中根据不同状态值进行不同处理,直到完成CW24C02的顺序读模式所有数据字节发送及读取,在发送完STOP信号后置读操作流程完成标志。主循环中判断读操作流程完成后,初始化u8Senddata数组内容,重复下一次测试过程。完成WriteReadCycle变量设置的测试次数后退出。
来源:武汉芯源半导体
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
一般情况下, i2c 设备焊接没什么问题,按照设备手册一步步来,基本上就顺风顺水能够用起来。如果这么一个简单的东西,有时候想要的结果死活不出来,反复的检查问题的原因,查询解决办法,核查设备的数据手册,甚至发送和接收的每一条命令与数据都知道是什么意思,仍然无法解决问题,那该怎么办呢?
本文主要针对 i2c 设备,讲解如何解决 i2c 设备主机与从机直接无法正常数据交互的问题,侧重点是针对硬件设计不太合理、i2c 设备设计不标准导致总线故障的情况,并且通过分析现象,提出解决方案。对于在设备初始化中,没有设置相应的寄存器或者发送命令,而导致的无法获取想要的数据情况,不作详细介绍。
1、i2c 基本用法
i2c 总线是一种简单、双向二线制同步串行总线。所有主机在 SCL 线上产生它们自己的时钟来传输总线上的报文,SDA 线传输每个字节必须为 8 位,每次传输可以发送的字节数量不受限制,每个字节后必须跟一个响应位。在空闲状态时,SCL 与 SDA 均为高电平。
通常一些低功耗 i2c 设备,芯片引脚使用上拉输出即可满足与其正常数据交互,还有一些 i2c 设备,则需要在总线上外加一个上拉电阻,此时相应的 I/O 配置成开漏输出,其他的按照芯片手册进行标准配置。
2、硬件问题汇总
2.1 无法正常拉高拉低引脚
首先确定 SDA 与SCL 引脚能够被拉高、拉低,检测方式直接软件控制 I/O 口输出引脚低电平/高电平,测量引脚电压是否能够随着芯片引脚的设置输出相应的状态。
如果不能被拉低,检测虚焊、上拉电阻断开、i2c 设备是否正常、芯片引脚是否损坏等问题,确保能够正常被拉高或者拉低。
2.2、电气特性无法满足
如果正常拉高、拉低的情况下,依然无法正常读取数据。通常建议,根据负载电流更换小阻值的电阻。
如果需要详细知道原因,就具体查询 i2c 设备电气特性。大多数 i2c 设备电气特性,大致下图所示
通常这块内容在 i2c 设备电气特性这一块,主要讲解电平拉高拉低的最长时间、最短时间,以及处于高电平与电平的阈值与持续时间等等内容。
硬件设计,为了降低单片机的功耗与保护芯片引脚,在满足负载电流和负载电容相关要求的前提下,阻值设置通常比较大。如果同一个总线上挂载多个 i2c 设备, 即使在 I/O 口配置正确的前提下,也会导致驱动能力不足。
现象是拉高电压不足,在拉高、拉低过程中消耗时间过长。这两个问题通常还引起数据线与时钟线:拉高时,高电压持续时间过短;拉低时,低电压持续时间过短。用示波器抓取图形:从波形上看,显示是尖波、斜波、杂波等不符合 i2c 设备电气特性的波形;从数据上看,数据线高电平持续时间过小 ,上升沿时间过长 ,下降沿时间过长等等数据超出设备电气特性的有效值。典型杂波图,如下所示
如果出现此类异常,建议更换小一点的电阻,用来增强总线驱动能力,提高电平转换速度。应当注意的是每个 MCU 的耐受电流不一样,减小电阻应避免超过相应引脚承受电流的最大值。
3、SDA 死锁
如果i2c 设备的数据偶尔能够正确获取,但是仍然会在总线发送数据或者命令的时候,爆出总线读写错误,那么有可能遇到下面的死锁问题,死锁时候,就是数据线被拉低,主机无法拉高。死锁一般发生在从机上,且为数据线死锁。因为i2c总线是共享的,如果需要确定,是否是从机死锁,可以参照下面两幅图,串联电阻进行测试
如上图所示,如果从机死锁,即从机拉低电平,此时检测到的电压为1/3 Vcc。
如上图所示,如果主机死锁,即主机拉低电平,此时检测到的电压为 1/11 Vcc。依据这个原理,可以准确判定死锁的具体位置,多个传感器依据类似方式进行定位。
3.1 反复重启导致死锁
3.1.1 现象
如果设备需要反复重启,很有可能在从机设备返回数据的时候,SDA被锁住。具体原因是从机设备在回数据,还没有发送完成,主机时钟消失,从机等待时钟信号, MCU重启,如果从机设备的电源没有复位,从机继续等待 MCU 时钟信号,数据一直被钳住,总线无法完成数据交互。
3.1.2 解决方式
解决重启导致总线死锁,一种方式可以如同 rt-thread 驱动解决方式一样,在系统复位的时候,提供9个时钟信号,解初总线死锁;另一种是在按下复位键初始化的时候,给从机设备电源断电重启,这个需要引脚控制。
3.1.3 9 个时钟信号
i2c 设备进行读写操作的过程中,在从机钳住总线的期间,MCU 异常复位,会导致 SDA 死锁,异常产生出现在俩个阶段:从机响应阶段、从机发送数据阶段。下面将针对这两种异常,对时钟信号进行解释,并且总结其他原因,得出结论。
(a) 从机响应阶段
MCU 在开始信号后发送地址,得到从机设备响应,准备开始返回数据,在这个时候,从机将 SDA 信号拉为低电平,如果 MCU 异常复位,会导致总线上 SCL 停止发送时钟信号,从机等待 MCU 的时钟信号,产生钳住并且拉低 SDA 的现象。如果想要解锁 SDA,从机需要 9 个时钟信号,使得从机完成响应,释放 SDA 。
(b) 从机发送数据阶段
如果从机响应完成了,开始给 MCU 返回数据。这个数据有八位,每一位都有可能为低,如果在数据低位,MCU 异常复位,停止发送时钟信号,从机就会等待 MCU 的时钟信号,产生钳住并且拉低 SDA 的现象。如果想要解锁 SDA,从机需要 1-8 个时钟信号,使得从机完成数据响应,释放 SDA 。
(c)其他情况
在从机一个 8 位数据发送完成后,等待 MCU 响应, 即使属于 MCU 的,从机不再钳住 SDA,没有时钟,数据交互停止。
在主机发送数据阶段,总线所有权在主机,主机异常,数据交互停止,总线释放。所以,这些情况下,不存在 SDA 死锁的情况。
(d)结论
综上所述,解锁 SDA 从机最多需要 9 个时钟信号,也就是异常复位后,MCU 至少发送需要 9 个时钟信号,完成 i2c 总线的 SDA 解锁。所以,RT_Thread 为了避免此类问题的产生,在 i2c 驱动初始化,对总线进行判断,判断是否需要解锁,如果需要,就进行解锁,确保 i2c 设备不会因为这个问题导致数据交互失败。
3.2 多个 i2c 设备导致死锁
多 i2c 设备除了异常复位导致死锁,还会形成相互干扰的问题,一般情况下,不会把同种从机地址挂在同一条总线上,但除此之外,有些 i2c 设备设计不是按照标准的 i2c 总线协议设计,在 i2c 总线共享的前提条件下,有的设备只要总线上从机地址就会有响应。这样由于从机的错误响应,使得各个 i2c 总线异常,甚至钳住总线,导致 I2C 总线进人一种死锁状态。
解决方式,这样的不标准i2c设备,单独使用一个总线,避免干扰,或者单独一个独立引脚,控制电源。
来源:RT-Thread
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
在I2C中,通信是借助设备地址寻址实现的,大致可以分为两类:一对多、多对多通信。在多主机通信时,从机如果想接收多个主机的数据,就需要使用到从机多地址的功能。
本文是针对在MM32F013x上实现I2C多地址的功能应用。
配置方式
MM32F013x的多地址功能是通过配置I2C_SLAVMASK寄存器来实现的。通过I2C_SAR寄存器配置从机地址后,再配置I2C_SLAVMASK寄存器。MM32F013x是支持7位地址和10位地址格式的,所以需要按照自己的实际情况配置I2C_SLAVMASK寄存器的低九位,I2C_SLVRCVADDR寄存器会给出真实地址。
注意:在I2C中有些特殊地址是不会产生响应的。
寄存器描述
相关软件的实现
01、功能验证
制作一个主机设备发送16个字节的字符串,连接两块开发板的SDA/SCL,通过主机向从机发送数据,分别将目标地址设置为:0xA0、0xA2、0xA4、0xA6、0xA8、0xAA、0xAC、0xAE进行通信测试。
1.1 主机程序
void I2C_WRTest(void) { Write(0x00, gTxData, 0x10); DELAY_Ms(100); Read(0x00, gRxData, 0x10); DELAY_Ms(100); } s32 main(void) { DELAY_Init(); I2C_WRInit(); for(int i=0;i<8;i++) { I2C_Cmd(I2C1, DISABLE); I2C_SetDeviceAddr(I2C1, EEPROM_ADDR+2*i); I2C_Cmd(I2C1, ENABLE); I2C_WRTest(); } While(1) { } }
1.2 从机初始化部分
void I2C_NVIC_SlaveInit(void) { NVIC_InitTypeDef NVIC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = I2C1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_1); GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_1); GPIO_StructInit(&GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; //Need extra plus pull GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //I2C1 remap IO port GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // clock input GPIO_Init(GPIOB, &GPIO_InitStructure); I2C_SlaveMode(); } void I2C_NVIC_SlaveTest() { u32 i; while( gTxFlag | gRxFlag); for(i = 0; i < 16; i++) { printf("TX data%d is : %x \r\n", i, gTxBuff[i]); } for(i = 0; i < 16; i++) { printf("RX data%d is : %x \r\n", i, gRxBuff[i]); } gTxFlag = 1; gRxFlag = 1; } void I2C_SlaveMode() { I2C_InitTypeDef I2C_InitStructure; I2C_StructInit(&I2C_InitStructure); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); I2C_DeInit(I2C1); I2C_InitStructure. Mode = (I2C_CR_MASTER >> 1); I2C_InitStructure. OwnAddress = 0; I2C_InitStructure. Speed = I2C_CR_STD; I2C_InitStructure. ClockSpeed = 100000; I2C_Init(I2C1, &I2C_InitStructure); I2C_ITConfig( I2C1, I2C_IT_RD_REQ, ENABLE );//Read request I2C_ITConfig( I2C1, I2C_IT_RX_FULL, ENABLE );//Receive interrupt I2C_SendSlaveAddress(I2C1, 0xA8); I2C1->SLVMASK = 0x0F; I2C_Cmd(I2C1, ENABLE); } s32 main(void) { CONSOLE_Init(115200); I2C_NVIC_SlaveInit(); I2C_NVIC_SlaveTest(); while(1) { } }
以上程序将设备配置为从机模式,使能读请求/接收缓冲非空中断,从机地址配置为0xA8,SLVMASK配置为0x0F,表示地址的低四位不进行比较,则从机设备可以从总线上接受地址为0xA0、0xA2、0xA4、0xA6、0xA8、0xAA、0xAC、0xAE的数据包。
1.3 从机多地址中断处理程序
void I2C1_IRQHandler(void) { u16 stop_flag, start_flag; if(I2C_GetITStatus(I2C1, I2C_IT_RD_REQ)) { I2C_ClearITPendingBit(I2C1,I2C_IT_RD_REQ); //The master has sent a read request from the slave I2C1->DR = (u8)gTxBuff[gTxCnt]; I2C_TX_EmptyCheck(I2C1); while(!(I2C1->SR & 0x4)); I2C_GenerateSTOP( I2C1, ENABLE ); gTxCnt ++; if(gTxCnt == 16) { gTxCnt = 0; } gTxFlag = 0; } // interrupt receive if(I2C_GetITStatus(I2C1, I2C_IT_RX_FULL)) { //Master sends slave receive gRxBuff[gRxCnt++] = I2C_ReceiveData(I2C1); while(!(I2C1->SR & 0x4)); I2C_GenerateSTOP( I2C1, ENABLE ); if(gRxCnt == 16) { gRxCnt = 0; } gRxFlag = 0; } stop_flag = I2C1->STOP; start_flag = I2C1->START; if((stop_flag & start_flag) != ((u32)RESET)) //slave receive { I2C_ClearITPendingBit(I2C1,I2C_IT_STOP_DET); I2C_ClearITPendingBit(I2C1,I2C_IT_STOP_DET); } }
1.4 测试结果
通过UART1打印接收及发送的结果发现每次结果都如下图所示:
波形如下:
结合上述结果与调试过程,可知从机可以接收地址为:0xA0、0xA2、0xA4、0xA6、0xA8、0xAA、0xAC、0xAE的数据包。
转自:灵动微电子
IICvs SPI
现今,在低端数字通信应用领域,我们随处可见IIC (Inter-Integrated Circuit) 和 SPI (Serial Peripheral Interface)的身影。原因是这两种通信协议非常适合近距离低速芯片间通信。Philips(for IIC)和Motorola(for SPI) 出于不同背景和市场需求制定了这两种标准通信协议。
IIC 开发于1982年,当时是为了给电视机内的CPU和外围芯片提供更简易的互联方式。电视机是最早的嵌入式系统之一,而最初的嵌入系统是使用内存映射(memory-mapped I/O)的方式来互联微控制器和外围设备的。要实现内存映射,设备必须并联入微控制器的数据线和地址线,这种方式在连接多个外设时需大量线路和额外地址解码芯片,很不方便并且成本高。
为了节省微控制器的引脚和和额外的逻辑芯片,使印刷电路板更简单,成本更低,位于荷兰的Philips实验室开发了 ‘Inter-Integrated Circuit’,IIC 或 IIC ,一种只使用二根线接连所有外围芯片的总线协议。最初的标准定义总线速度为100kbps。经历几次修订,主要是1995年的400kbps,1998的3.4Mbps。
有迹象表明,SPI总线首次推出是在1979年,Motorola公司将SPI总线集成在他们第一支改自68000微处理器的微控制器芯片上。SPI总线是微控制器四线的外部总线(相对于内部总线)。与IIC不同,SPI没有明文标准,只是一种事实标准,对通信操作的实现只作一般的抽象描述,芯片厂商与驱动开发者通过data sheets和application notes沟通实现上的细节。
SPI
对于有经验的数字电子工程师来说,用SPI互联两支数字设备是相当直观的。SPI是种四根信号线协议(如图):
§ SCLK: Serial Clock (output from master);
§ MOSI; SIMO: Master Output, Slave Input(output from master);
§ MISO; SOMI: Master Input, Slave Output(output from slave);
§ SS: Slave Select (active low, outputfrom master).
SPI是[单主设备( single-master )]通信协议,这意味着总线中的只有一支中心设备能发起通信。当SPI主设备想读/写[从设备]时,它首先拉低[从设备]对应的SS线(SS是低电平有效),接着开始发送工作脉冲到时钟线上,在相应的脉冲时间上,[主设备]把信号发到MOSI实现“写”,同时可对MISO采样而实现“读”,如下图:
SPI有四种操作模式——模式0、模式1、模式2和模式3,它们的区别是定义了在时钟脉冲的哪条边沿转换(toggles)输出信号,哪条边沿采样输入信号,还有时钟脉冲的稳定电平值(就是时钟信号无效时是高还是低)。每种模式由一对参数刻画,它们称为时钟极(clock polarity)CPOL与时钟期(clock phase)CPHA。
[主从设备]必须使用相同的工作参数——SCLK、CPOL 和 CPHA,才能正常工作。如果有多个[从设备],并且它们使用了不同的工作参数,那么[主设备]必须在读写不同[从设备]间重新配置这些参数。以上SPI总线协议的主要内容。SPI不规定最大传输速率,没有地址方案;SPI也没规定通信应答机制,没有规定流控制规则。事实上,SPI[主设备]甚至并不知道指定的[从设备]是否存在。这些通信控制都得通过SPI协议以外自行实现。例如,要用SPI连接一支[命令-响应控制型]解码芯片,则必须在SPI的基础上实现更高级的通信协议。SPI并不关心物理接口的电气特性,例如信号的标准电压。在最初,大多数SPI应用都是使用间断性时钟脉冲和以字节为单位传输数据的,但现在有很多变种实现了连续性时间脉冲和任意长度的数据帧。
IIC
与SPI的单主设备不同,IIC 是多主设备的总线,IIC没有物理的芯片选择信号线,没有仲裁逻辑电路,只使用两条信号线—— ‘serial data’ (SDA) 和 ‘serial clock’ (SCL)。IIC协议规定:
§ 第一,每一支IIC设备都有一个唯一的七位设备地址;
§ 第二,数据帧大小为8位的字节;
§ 第三,数据(帧)中的某些数据位用于控制通信的开始、停止、方向(读写)和应答机制。
IIC 数据传输速率有标准模式(100 kbps)、快速模式(400 kbps)和高速模式(3.4 Mbps),另外一些变种实现了低速模式(10 kbps)和快速+模式(1 Mbps)。
物理实现上,IIC 总线由两根信号线和一根地线组成。两根信号线都是双向传输的,参考下图。IIC协议标准规定发起通信的设备称为主设备,主设备发起一次通信后,其它设备均为从设备。
IIC 通信过程大概如下。首先,主设备发一个START信号,这个信号就像对所有其它设备喊:请大家注意!然后其它设备开始监听总线以准备接收数据。接着,主设备发送一个7位设备地址加一位的读写操作的数据帧。当所设备接收数据后,比对地址自己是否目标设备。如果比对不符,设备进入等待状态,等待STOP信号的来临;如果比对相符,设备会发送一个应答信号——ACKNOWLEDGE作回应。
当主设备收到应答后便开始传送或接收数据。数据帧大小为8位,尾随一位的应答信号。主设备发送数据,从设备应答;相反主设备接数据,主设备应答。当数据传送完毕,主设备发送一个STOP信号,向其它设备宣告释放总线,其它设备回到初始状态。
基于IIC总线的物理结构,总线上的START和STOP信号必定是唯一的。另外,IIC总线标准规定SDA线的数据转换必须在SCL线的低电平期,在SCL线的高电平期,SDA线的上数据是稳定的。
在物理实现上,SCL线和SDA线都是漏极开路(open-drain),通过上拉电阻外加一个电压源。当把线路接地时,线路为逻辑0,当释放线路,线路空闲时,线路为逻辑1。基于这些特性,IIC设备对总线的操作仅有“把线路接地”——输出逻辑0。
IIC总线设计只使用了两条线,但相当优雅地实现任意数目设备间无缝通信,堪称完美。我们设想一下,如果有两支设备同时向SCL线和SDA线发送信息会出现什么情况。
基于IIC总线的设计,线路上不可能出现电平冲突现象。如果一支设备发送逻辑0,其它发送逻辑1,那么线路看到的只有逻辑0。也就是说,如果出现电平冲突,发送逻辑0的始终是“赢家”。
总线的物理结构亦允许主设备在往总线写数据的同时读取数据。这样,任何设备都可以检测冲突的发生。当两支主设备竞争总线的时候,“赢家”并不知道竞争的发生,只有“输家”发现了冲突——当它写一个逻辑1,却读到0时——而退出竞争。
10位设备地址
任何IIC设备都有一个7位地址,理论上,现实中只能有127种不同的IIC设备。实际上,已有IIC的设备种类远远多于这个限制,在一条总线上出现相同的地址的IIC设备的概率相当高。为了突破这个限制,很多设备使用了双重地址——7位地址加引脚地址(external configuration pins)。IIC 标准也预知了这种限制,提出10位的地址方案。
10位的地址方案对 IIC协议的影响有两点:
§ 第一,地址帧为两个字节长,原来的是一个字节;
§ 第二,第一个字节前五位最高有效位用作10位地址标识,约定是“11110”。
除了10位地址标识,标准还预留了一些地址码用作其它用途,如下表:
时钟拉伸
在 IIC 通信中,主设备决定了时钟速度。因为时钟脉冲信号是由主设备显式发出的。但是,当从设备没办法跟上主设备的速度时,从设备需要一种机制来请求主设备慢一点。这种机制称为时钟拉伸,而基于I²C结构的特殊性,这种机制得到实现。当从设备需要降低传输的速度的时候,它可以按下时钟线,逼迫主设备进入等待状态,直到从设备释放时钟线,通信才继续。
高速模式
原理上讲,使用上拉电阻来设置逻辑1会限制总线的最大传输速度。而速度是限制总线应用的因素之一。这也说明为什么要引入高速模式(3.4 Mbps)。在发起一次高速模式传输前,主设备必须先在低速的模式下(例如快速模式)发出特定的“High Speed Master”信号。为缩短信号的周期和提高总线速度,高速模式必须使用额外的I/O缓冲区。另外,总线仲裁在高速模式下可屏蔽掉。更多的信息请参与总线标准文档。
IIC vs SPI: 哪位是赢家?
我们来对比一下IIC 和 SPI的一些关键点:
第一,总线拓扑结构/信号路由/硬件资源耗费
IIC 只需两根信号线,而标准SPI至少四根信号,如果有多个从设备,信号需要更多。一些SPI变种虽然只使用三根线——SCLK, SS和双向的MISO/MOSI,但SS线还是要和从设备一对一根。另外,如果SPI要实现多主设备结构,总线系统需额外的逻辑和线路。用IIC 构建系统总线唯一的问题是有限的7位地址空间,但这个问题新标准已经解决——使用10位地址。从第一点上看,IIC是明显的大赢家。
第二,数据吞吐/传输速度
如果应用中必须使用高速数据传输,那么SPI是必然的选择。因为SPI是全双工,IIC 的不是。SPI没有定义速度限制,一般的实现通常能达到甚至超过10 Mbps。IIC 最高的速度也就快速+模式(1 Mbps)和高速模式(3.4 Mbps),后面的模式还需要额外的I/O缓冲区,还并不是总是容易实现的。
第三,优雅性
IIC 常被称更优雅于SPI。公正的说,我们更倾向于认为两者同等优雅和健壮。IIC的优雅在于它的特色——用很轻盈的架构实现了多主设备仲裁和设备路由。但是对使用的工程师来讲,理解总线结构更费劲,而且总线的性能不高。
SPI的优点在于它的结构相当的直观简单,容易实现,并且有很好扩展性。SPI的简单性不足称其优雅,因为要用SPI搭建一个有用的通信平台,还需要在SPI之上构建特定的通信协议软件。也就是说要想获得SPI特有而IIC没有的特性——高速性能,工程师们需要付出更多的劳动。另外,这种自定的工作是完全自由的,这也说明为什么SPI没有官方标准。IIC和SPI都对低速设备通信提供了很好的支持,不过,SPI适合数据流应用,而IIC更适合“字节设备”的多主设备应用。
小结
在数字通信协议簇中,IIC和SPI常称为“小”协议,相对Ethernet, USB, SATA, PCI-Express等传输速度达数百上千兆字节每秒的总线。但是,我们不能忘记的是各种总线的用途是什么。“大”协议是用于系统外的整个系统之间通信的,“小”协议是用于系统内各芯片间的通信,没有迹象表明“大”协议有必要取代“小”协议。IIC和SPI的存在和流行体现了“够用就好”的哲学。回应文首,IIC和SPI如此的流行,它是任何一位嵌入式工程师必备的工具。
版权声明:本文为CSDN博主「求佛_ce123」的原创文章,转载此文目的在于传递更多信息,版权归原作者所有。遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ce123_zhouwei/article/details/6878547