详解通信协议之IIC通信协议
本文结合AT24C02对IIC通信协议原理进行了描述。
IIC通信协议(以AT24C02为例)
IIC通讯协议(Inter-Integrated Circuit)是由 Philips 公司开发双向同步半双工串行总线,只需要两根线(SDA、SCL)即可在连接于总线上的器件之间传送信息。IIC总线是一种共享的串行总线,是用于两个设备之间的短距离低速速率(250K左右)通信。长距离用can总线。
- IIC数据有效性
数据在时钟线(SCL)为高电平时,数据线(SDA)要稳定保持稳定,时钟线为低电平时,数据线任意变化。
- 起始和结束条件
起始条件:当SCL为高电平时,SDA由高到低的跳变为起始信号。
结束条件:当SCL为高电平时,SDA由低到高的跳变为结束信号。
- 应答信号
当主机向从机发送完一个字节的数据,主机需要从机给出一个应答信号,用来确认是否接收到了数据。从机的应答信号的时钟仍然是主机提供的,应答信号出现在8位数据之后的那一个时钟周期。低电平:表示接收成功,高电平:表示接收失败。 - 数据帧格式
起始信号–>从机地址+数据传输方向(0为主机发送,1为主机接收)–>数据交换–>结束信号。
注意:在数据方向进行变换时,需要重新发送起始信号和期间地址+读写状态。
- 以AT24C02为例,模拟IIC控制代码
AT24C02的存储容量为2K,芯片地址为1010,其地址控制字格式为1010-A2A1A0-R/W。其中A2,A1,A0可编程地址选择位。A2,A1,A0引脚接高、低电平后得到确定的三位编码,与1010形成7位编码,即为该器件的地址码。R/W为芯片读写控制位,该位为0是写,1是读。
void I2CInit(void) //IIC引脚初始化,初始化为**开漏输出**,外接上拉电阻提高引脚的驱动能力
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
GPIO_InitStructure.Pin = GPIO_PIN_7 | GPIO_PIN_6;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pull = GPIO_PULLUP;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
}
/*****SDA、SCL引脚模式配置*****/
void SDA_Input_Mode()//设置数据线为输入模式
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
GPIO_InitStructure.Pin = GPIO_PIN_7;
GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
GPIO_InitStructure.Pull = GPIO_PULLUP;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
}
void SDA_Output_Mode() //设置数据线输出模式配置
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
GPIO_InitStructure.Pin = GPIO_PIN_7;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
}
void SDA_Output( uint16_t val ) //设置数据线输出电平
{
if ( val )
{
GPIOB->BSRR |= GPIO_PIN_7;
}
else
{
GPIOB->BRR |= GPIO_PIN_7;
}
}
void SCL_Output( uint16_t val ) //设置时钟线的输出电平
{
if ( val )
{
GPIOB->BSRR |= GPIO_PIN_6;
}
else
{
GPIOB->BRR |= GPIO_PIN_6;
}
}
uint8_t SDA_Input(void) //读取数据线输入状态
{
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_SET){
return 1;
}else{
return 0;
}
}
static void delay1(unsigned int n) //80MHz延时0.1us根据自己的主频进行调节
{
uint32_t i;
for ( i = 0; i < n; ++i);
}
void I2CStart(void) //起始信号
{
SDA_Output(1); //数据线为高电平为下降沿做准备
delay1(20); //延时2us
SCL_Output(1); //时钟线变为高电平
delay1(20); //延时2us
SDA_Output(0); //时钟线为高时,数据线变为低
delay1(20); //延时2us
SCL_Output(0); //时钟线变为低
delay1(20); //延时2us
}
void I2CStop(void) //结束信号
{
SCL_Output(0); //时钟线为低
delay1(20); //延时2us
SDA_Output(0); //数据线为低 为上升做准备
delay1(20); //延时2us
SCL_Output(1); //时钟线变为高
delay1(20); //延时2us
SDA_Output(1); //时钟线为高时,数据线上升沿结束信号
delay1(20); //延时2us
}
unsigned char I2CWaitAck(void) //等待响应信号
{
unsigned short cErrTime = 5;
SDA_Input_Mode(); //数据线设置为输入模式
delay1(20); //延时2us
SCL_Output(1); //时钟线输出高
delay1(20); //延时2us
while(SDA_Input()) //判断数据线输出是否为低
{
cErrTime--;
delay1(20);
if (0 == cErrTime)
{
SDA_Output_Mode();
I2CStop();
return ERROR;
}
}
SDA_Output_Mode(); //切换模式
SCL_Output(0); //数据线输出0
delay1(20); //延时2us
return SUCCESS; //返回成功
}
/*******IIC字符读写*********/
void I2CSendByte(unsigned char cSendByte) //发送一个字符,也就是8bit数据
{
unsigned char i = 8;
while (i--)
{
SCL_Output(0);
delay1(20);
SDA_Output(cSendByte & 0x80);
delay1(20);
cSendByte += cSendByte;
delay1(20);
SCL_Output(1);
delay1(20);
}
SCL_Output(0);
delay1(20);
}
unsigned char I2CReceiveByte(void) //接收一个字符的数据
{
unsigned char i = 8;
unsigned char cR_Byte = 0;
SDA_Input_Mode();
while (i--)
{
cR_Byte += cR_Byte;
SCL_Output(0);
delay1(20);
delay1(20);
SCL_Output(1);
delay1(20);
cR_Byte |= SDA_Input();
}
SCL_Output(0);
delay1(20);
SDA_Output_Mode();
return cR_Byte;
}
uchar eeprom_read (uchar address) ///读取某一地址的数据
{
uchar date;
I2CStart(); //启动 IIC
I2CSendByte(0XA0); //写指令
I2CWaitAck(); //等待有效响应
I2CSendByte(address); //发送读取内容的地址
I2CWaitAck(); //等待有效响应
I2CStop(); //发送停止信号
I2CStart(); //启动 IIC
I2CSendByte(0XA1); //读数据指令
I2CWaitAck(); //等待有效响应
date=I2CReceiveByte(); //读取数据
I2CWaitAck();//读取完成的应答信号
I2CStop(); //发送停止信号
return date;
}
void eeprom_write (uchar address,uchar date) ///给某一地址写数据
{
I2CStart(); //启动 IIC
I2CSendByte(0XA0); //写指令
I2CWaitAck(); //等待有效响应
I2CSendByte(address); //发送内容写到的地址
I2CWaitAck(); //等待有效响应
I2CSendByte(date);
I2CWaitAck(); //等待发送完成
I2CStop(); //发送停止信号
}
初始化引脚之后调用读写就可以了。