I2C简介
I2C 通讯协议(Inter-Integrated Circuit)是由Phiilps公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要USART、CAN等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。
I2C物理层的特点
1.它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在一个I2C通讯总线中,可连接多个I2C通讯设备,支持多个通讯主机及多个通讯从机。 2.一个I2C总线只使用两条总线线路,一条双向串行数据线(SDA) , 一条串行时钟线 (SCL)。数据线即用来表示数据,时钟线用于数 据收发同步。 3.每个连接到总线的设备都有一个独立的地址,主机可以利用这个 地址进行不同设备之间的访问。 4.总线通过上拉电阻接到电源。当I2C设备空闲时,会输出高阻态,而当 所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
5.多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由 哪个设备占用总线。 具有三种传输模式:标准模式传输速率为100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多I2C设备尚不支持高 速模式。
I2C的协议层
I2C的协议定义了通讯的起始和停止信号、数据有效性、响应、 仲裁、时钟同步和地址广播等环节。 I2C基本读写过程 主机写数据到从机:
I2C基本读写过程 通讯复合格式:
通讯的起始和停止信号
? 当 SCL 线是高电平时 SDA 线从高电平向低电平切换,这个情况 表示通讯的起始。 ? 当 SCL 是高电平时 SDA 线由低电平向高电平切换,表示通讯的 停止。 ? 起始和停止信号一般由主机产生。
数据有效性
I2C使用SDA信号线来传输数据,使用SCL信号线进行数据同步。 SDA数据线在SCL的每个时钟周期传输一位数据。
? SCL为高电平的时候SDA表示的数据有效,即此时的SDA为高电平时 表示数据“1”,为低电平时表示数据“0”。 ? 当SCL为低电平时,SDA的数据无效,一般在这个时候SDA进行电平 切换,为下一次表示数据做好准备。
地址及数据方向
? I2C总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA信号线发送设备地址(SLAVE_ADDRESS)来查找从机。设备地址可以是7位或10位。 ? 紧跟设备地址的一个数据位R/W用来表示数据传输方向,数据方向位为 “1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据。
响应
I2C的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。 传输时主机产生时钟,在第9个时钟时,数据发送端会释放SDA的控制权,由数据接收端控制SDA,若SDA为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。
STM32的I2C特性及架构 软件模拟协议:使用CPU直接控制通讯引脚的电平,产生出符合通讯协议标准的逻辑。 硬件实现协议:由STM32的I2C片上外设专门负责实现I2C通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理I2C协议的方式减轻了CPU的工作,且使软件设计更加简单。
1.通讯引脚 STM32芯片有多个I2C外设,它们的I2C通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚. 2.时钟控制逻辑
SCL线的时钟信号,由I2C接口根据时钟控制寄存器(CCR)控制, 控制的参数主要为时钟频率。 ? 可选择I2C通讯的“标准/快速”模式,这两个模式分别I2C对应 100/400Kbit/s的通讯速率。 ? 在快速模式下可选择SCL时钟的占空比,可选Tlow/Thigh=2或 Tlow/Thigh=16/9模式。 ? CCR寄存器中12位的配置因子CCR,它与I2C外设的输入时钟源共同 作用,产生SCL时钟。STM32的I2C外设输入时钟源为PCLK1。
3.数据控制逻辑
I2C的SDA信号主要连接到数据移位寄存器上,数据移位寄存器的数据来源及目标是数据寄存器(DR)、地址寄存器(OAR)、PEC寄存器以及SDA数据线。 ? 当向外发送数据的时候,数据移位寄存器以“数据寄存器”为数据源,把数据一位一位地通过SDA信号线发送出去; ? 当从外部接收数据的时候,数据移位寄存器把SDA信号线采样到的数据一位一位地存储到“数据寄存器”中。
STM32的I2C通讯过程
使用I2C外设通讯时,在通讯的不同阶段它会对“状态寄存器(SR1及SR2)”的不同数据位写入参数,通过读取这些寄存器标志来了解通讯状态。 1.主发送器 在发送了地址和清除了ADDR位后, 主设备通过内部移位寄存器将字节从DR寄存器发送到SDA线上。 主设备等待,直到TxE被清除,(见图245的EV8)。 当收到应答脉冲时: ● TxE位被硬件置位,如果设置了INEVFEN和ITBUFEN位,则产生一个中断。 如果TxE被置位并且在上一次数据发送结束之前没有写新的数据字节到DR寄存器,则BTF被硬件置位,在清除BTF之前I2C接口将保持SCL为低电平;读出I2C_SR1之后再写入I2C_DR寄存器 将清除BTF位。关闭通信在DR寄存器中写入最后一个字节后,通过设置STOP位产生一个停止条件(见图245的EV8_2),然后I2C接口将自动回到从模式(M/S位清除)。
2.主接收器 在发送地址和清除ADDR之后,I2C接口进入主接收器模式。在此模式下,I2C接口从SDA线接收数据字节,并通过内部移位寄存器送至DR寄存器。在每个字节后,I2C接口依次执行以下操作: ● 如果ACK位被置位,发出一个应答脉冲。 ● 硬件设置RxNE=1,如果设置了INEVFEN和ITBUFEN位,则会产生一个中断(见图246的EV7)。 如果RxNE位被置位,并且在接收新数据结束前,DR寄存器中的数据没有被读走,硬件将设置BTF=1,在清除BTF之前I2C接口将保持SCL为低电平;读出I2C_SR1之后再读出I2C_DR寄存器将清除BTF位。 关闭通信主设备在从从设备接收到最后一个字节后发送一个NACK。接收到NACK后,从设备释放对SCL和SDA线的控制;主设备就可以发送一个停止/重起始条件。 ● 为了在收到最后一个字节后产生一个NACK脉冲,在读倒数第二个数据字节之后(在倒数第二个RxNE事件之后)必须清除ACK位。 ● 为了产生一个停止/重起始条件,软件必须在读倒数第二个数据字节之后(在倒数第二个RxNE事件之后)设置STOP/START位。 ● 只接收一个字节时,刚好在EV6之后(EV6_1时,清除ADDR之后)要关闭应答和停止条件的产生位。 在产生了停止条件后,I2C接口自动回到从模式(M/SL位被清除)
3.从发送器
在接收到地址和清除ADDR位后,从发送器将字节从DR寄存器经由内部移位寄存器发送到SDA线上。 从设备保持SCL为低电平,直到ADDR位被清除并且待发送数据已写入DR寄存器。(见下图中的EV1和EV3)。 当收到应答脉冲时: ● TxE位被硬件置位,如果设置了ITEVFEN和ITBUFEN位,则产生一个中断。 如果TxE位被置位,但在下一个数据发送结束之前没有新数据写入到I2C_DR寄存器,则BTF位被置位,在清除BTF之前I2C接口将保持SCL为低电平;读出I2C_SR1之后再写入I2C_DR寄存器 将清除BTF位。 4.从接收器 在接收到地址并清除ADDR后,从接收器将通过内部移位寄存器从SDA线接收到的字节存进DR 寄存器。I2C接口在接收到每个字节后都执行下列操作: ● 如果设置了ACK位,则产生一个应答脉冲 ● 硬件设置RxNE=1。如果设置了ITEVFEN和ITBUFEN位,则产生一个中断。 如果RxNE被置位,并且在接收新的数据结束之前DR寄存器未被读出,BTF位被置位,在清除BTF之前I2C接口将保持SCL为低电平;读出I2C_SR1之后再写入I2C_DR寄存器将清除BTF位。 关闭从通信 在传输完最后一个数据字节后,主设备产生一个停止条件, I2 C接口检测到这一条件时: ● 设置STOPF=1,如果设置了ITEVFEN位,则产生一个中断。
硬件设计
24C02 的 SCL 和 SDA 分别连在 STM32F1 的 PB6 和 PB7 上的 24C02 (EEPROM)芯片 本实验板中的 EEPROM 芯片 (型号:AT24C02) 的 SCL 及 SDA 引脚连接到了 STM32 对应的 I2C引脚中,结合上拉电阻,构成了 I2C 通讯总线,它们通过 I2C 总线交互。EEPROM 芯片的设备地址一共有 7 位,其中高 4 位固定为:1010 b,低 3 位则由 A0/A1/A2 信号线的电平决定,见图EEPROM 设备地址 ,图中的 R/W 是读写方向位,与地址无关。
按照我们此处的连接,A0/A1/A2 均为 0,所以 EEPROM 的 7 位设备地址是:101 0000b,即 0x50。 由于 I2C 通讯时常常是地址跟读写方向连在一起构成一个 8 位数,且当 R/W 位为 0 时,表示写方向,所以加上 7 位地址,其值为“0xA0”,常称该值为 I2C 设备的“写地址”;当 R/W 位为 1时,表示读方向,加上 7 位地址,其值为“0xA0”,常称该值为“读地址”。 EEPROM 芯片中还有一个 WP 引脚,具有写保护功能,当该引脚电平为高时,禁止写入数据,当引脚为低电平时,可写入数据,我们直接接地,不使用写保护功能。
编程要点
(1) 配置通讯使用的目标引脚为开漏模式; (2) 使能 I2C 外设的时钟; (3) 配置 I2C 外设的模式、地址、速率等参数并使能 I2C 外设; (4) 编写基本 I2C 按字节收发的函数; (5) 编写读写 EEPROM 存储内容的函数; (6) 编写测试程序,对读写数据进行校验。
代码详解
串口初始化
void GPIOx_Config(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO_X,ENABLE);
GPIO_InitTypeDef GPIO_InitStuct;
GPIO_InitStuct.GPIO_Mode = GPIO_Mode_X;
GPIO_InitStuct.GPIO_Pin = GPIO_Pin_SCL|GPIO_Pin_SDA;
GPIO_InitStuct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIO_X,&GPIO_InitStuct);
}
I2C初始化
void Hardware_Config(void)
{
GPIOx_Config();
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
I2C_DeInit(EEPROM_I2C_X);
I2C_InitTypeDef I2C_InitStuct;
I2C_InitStuct.I2C_Mode = I2C_Mode_I2C;
I2C_InitStuct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStuct.I2C_OwnAddress1 = I2Cx_OWN_ADDRESS7;
I2C_InitStuct.I2C_Ack =I2C_Ack_Enable;
I2C_InitStuct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStuct.I2C_ClockSpeed = I2C_Speed;
I2C_Init(EEPROM_I2C_X,&I2C_InitStuct);
I2C_Cmd(EEPROM_I2C_X, ENABLE);
}
向EEPROM写入一个字节
void EEPROM_Bety_Write(uint8_t addr,uint8_t date)
{
I2C_GenerateSTART(EEPROM_I2C_X,ENABLE);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
I2C_Send7bitAddress(EEPROM_I2C_X,EEPROM_Block0_ADDRESS,I2C_Direction_Transmitter);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
I2C_SendData(EEPROM_I2C_X, addr);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_BYTE_TRANSMITTING)==ERROR){;}
I2C_SendData(EEPROM_I2C_X, date);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_BYTE_TRANSMITTED)==ERROR);
I2C_GenerateSTOP(EEPROM_I2C_X, ENABLE);
EEPROM_WaitForWriteEnd();等待EEPROM响应函数,自己定义
}
写入多个字节(一次最多8bit)
void EEPROM_page_Write(uint8_t addr,uint8_t *date,uint8_t numByteToWrite)
{
I2C_GenerateSTART(EEPROM_I2C_X,ENABLE);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
I2C_Send7bitAddress(EEPROM_I2C_X,EEPROM_Block0_ADDRESS,I2C_Direction_Transmitter);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
I2C_SendData(EEPROM_I2C_X, addr);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_BYTE_TRANSMITTING)==ERROR);
while(numByteToWrite)
{
I2C_SendData(EEPROM_I2C_X, *date);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_BYTE_TRANSMITTED)==ERROR);
numByteToWrite--;
date++;
}
I2C_GenerateSTOP(EEPROM_I2C_X, ENABLE);
EEPROM_WaitForWriteEnd();
}
读取EEPROM的数据
void EEPROM_Read(uint8_t addr,uint8_t *date,uint8_t numByteToRead)
{
I2C_GenerateSTART(EEPROM_I2C_X,ENABLE);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
I2C_Send7bitAddress(EEPROM_I2C_X,EEPROM_Block0_ADDRESS,I2C_Direction_Transmitter);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
I2C_SendData(I2C1, addr);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_BYTE_TRANSMITTING)==ERROR){;}
I2C_GenerateSTART(EEPROM_I2C_X,ENABLE);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
I2C_Send7bitAddress(EEPROM_I2C_X,EEPROM_Block0_ADDRESS,I2C_Direction_Receiver);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)==ERROR);
while(numByteToRead)
{
if(numByteToRead == 1)
{
I2C_AcknowledgeConfig(EEPROM_I2C_X,DISABLE);
}
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_BYTE_RECEIVED)==ERROR);
*date = I2C_ReceiveData(EEPROM_I2C_X);
date++;
numByteToRead--;
}
I2C_GenerateSTOP(EEPROM_I2C_X,ENABLE);
I2C_AcknowledgeConfig(EEPROM_I2C_X,ENABLE);
}
void EEPROM_WaitForWriteEnd(void)
{
do
{
I2C_GenerateSTART(EEPROM_I2C_X,ENABLE);
while(I2C_GetFlagStatus(EEPROM_I2C_X,I2C_FLAG_SB)==RESET);
I2C_Send7bitAddress(EEPROM_I2C_X,EEPROM_Block0_ADDRESS,I2C_Direction_Transmitter);
}
while(I2C_GetFlagStatus(EEPROM_I2C_X,I2C_FLAG_ADDR)==RESET);
I2C_GenerateSTOP(EEPROM_I2C_X,ENABLE);
}
示例代码
.c文件
#include "Hardware.h"
uint16_t EEPROM_ADDRESS;
void GPIOx_Config(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO_X,ENABLE);
GPIO_InitTypeDef GPIO_InitStuct;
GPIO_InitStuct.GPIO_Mode = GPIO_Mode_X;
GPIO_InitStuct.GPIO_Pin = GPIO_Pin_SCL|GPIO_Pin_SDA;
GPIO_InitStuct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIO_X,&GPIO_InitStuct);
}
void Hardware_Config(void)
{
GPIOx_Config();
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
I2C_DeInit(EEPROM_I2C_X);
I2C_InitTypeDef I2C_InitStuct;
I2C_InitStuct.I2C_Mode = I2C_Mode_I2C;
I2C_InitStuct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStuct.I2C_OwnAddress1 = I2Cx_OWN_ADDRESS7;
I2C_InitStuct.I2C_Ack =I2C_Ack_Enable;
I2C_InitStuct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStuct.I2C_ClockSpeed = I2C_Speed;
I2C_Init(EEPROM_I2C_X,&I2C_InitStuct);
I2C_Cmd(EEPROM_I2C_X, ENABLE);
}
void EEPROM_Bety_Write(uint8_t addr,uint8_t date)
{
I2C_GenerateSTART(EEPROM_I2C_X,ENABLE);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
I2C_Send7bitAddress(EEPROM_I2C_X,EEPROM_Block0_ADDRESS,I2C_Direction_Transmitter);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
I2C_SendData(EEPROM_I2C_X, addr);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_BYTE_TRANSMITTING)==ERROR){;}
I2C_SendData(EEPROM_I2C_X, date);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_BYTE_TRANSMITTED)==ERROR);
I2C_GenerateSTOP(EEPROM_I2C_X, ENABLE);
EEPROM_WaitForWriteEnd();
}
void EEPROM_page_Write(uint8_t addr,uint8_t *date,uint8_t numByteToWrite)
{
I2C_GenerateSTART(EEPROM_I2C_X,ENABLE);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
I2C_Send7bitAddress(EEPROM_I2C_X,EEPROM_Block0_ADDRESS,I2C_Direction_Transmitter);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
I2C_SendData(EEPROM_I2C_X, addr);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_BYTE_TRANSMITTING)==ERROR);
while(numByteToWrite)
{
I2C_SendData(EEPROM_I2C_X, *date);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_BYTE_TRANSMITTED)==ERROR);
numByteToWrite--;
date++;
}
I2C_GenerateSTOP(EEPROM_I2C_X, ENABLE);
EEPROM_WaitForWriteEnd();
}
void EEPROM_Read(uint8_t addr,uint8_t *date,uint8_t numByteToRead)
{
I2C_GenerateSTART(EEPROM_I2C_X,ENABLE);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
I2C_Send7bitAddress(EEPROM_I2C_X,EEPROM_Block0_ADDRESS,I2C_Direction_Transmitter);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
I2C_SendData(I2C1, addr);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_BYTE_TRANSMITTING)==ERROR){;}
I2C_GenerateSTART(EEPROM_I2C_X,ENABLE);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
I2C_Send7bitAddress(EEPROM_I2C_X,EEPROM_Block0_ADDRESS,I2C_Direction_Receiver);
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)==ERROR);
while(numByteToRead)
{
if(numByteToRead == 1)
{
I2C_AcknowledgeConfig(EEPROM_I2C_X,DISABLE);
}
while(I2C_CheckEvent(EEPROM_I2C_X,I2C_EVENT_MASTER_BYTE_RECEIVED)==ERROR);
*date = I2C_ReceiveData(EEPROM_I2C_X);
date++;
numByteToRead--;
}
I2C_GenerateSTOP(EEPROM_I2C_X,ENABLE);
I2C_AcknowledgeConfig(EEPROM_I2C_X,ENABLE);
}
void EEPROM_WaitForWriteEnd(void)
{
do
{
I2C_GenerateSTART(EEPROM_I2C_X,ENABLE);
while(I2C_GetFlagStatus(EEPROM_I2C_X,I2C_FLAG_SB)==RESET);
I2C_Send7bitAddress(EEPROM_I2C_X,EEPROM_Block0_ADDRESS,I2C_Direction_Transmitter);
}
while(I2C_GetFlagStatus(EEPROM_I2C_X,I2C_FLAG_ADDR)==RESET);
I2C_GenerateSTOP(EEPROM_I2C_X,ENABLE);
}
.h文件
#ifndef _HARDWART_H_
#define _HARDWART_H_
#include "stm32f10x.h"
#define RCC_APB2Periph_GPIO_X RCC_APB2Periph_GPIOB
#define GPIO_Mode_X GPIO_Mode_AF_OD
#define GPIO_Pin_SCL GPIO_Pin_6
#define GPIO_Pin_SDA GPIO_Pin_7
#define GPIO_X GPIOB
#define I2Cx_OWN_ADDRESS7 0X0A
#define EEPROM_Block0_ADDRESS 0xA0
#define I2C_Speed 400000
#define EEPROM_I2C_X I2C1
void Hardware_Config(void);
void EEPROM_Bety_Write(uint8_t addr,uint8_t date);
void EEPROM_WaitForWriteEnd(void);
void EEPROM_Read(uint8_t addr,uint8_t *date,uint8_t numByteToRead);
void EEPROM_page_Write(uint8_t addr,uint8_t *date,uint8_t numByteToWrite);
#endif
main.c文件
#include "main.h"
int main()
{
uint8_t date[10];
uint8_t date1[10]={1,2,3,4,5,6,7,8};
u8 i;
Usart1_Init(115200);
printf("abc\r\n");
Hardware_Config();
EEPROM_Bety_Write(11,0X50);
EEPROM_Read(11,date,1);
EEPROM_page_Write(8,date1,8);
EEPROM_Read(8,date,8);
printf("\r\n接收到的数据0x%x\r\n",date[0]);
for(i=0;i<8;i++)
{
printf("\r\n接收到的数据%d\r\n",date[i]);
}
while(1)
{
;
}
}
|