I2C协议

tech2023-11-09  95

Inter-Integrated Circuit Bus 半双工通信

物理层

支持多设备的总线,支持多通讯主机以及多个通讯从机。双向串行数据线SDA用于传输数据,串行时钟线SCL用于数据收发同步。连接到总线的设备都有一个独立的地址。总线通过上拉电阻接到电源。I2C设备空闲时,输出高阻态;当所有设备都空闲时,由上拉电阻把总线拉成高电平。多个主机同时使用总线,仲裁决定由哪个设备占用总线。标准模式传输速率100kbit/s,快速模式400kbit/s,高速模式3.4Mbit/s。连接到相同总线的I2C数量受到总线的最大电容400pF限制。

协议层

基本读写过程

​ (1) 主机的I2C接口产生传输起始信号(S),所有的从机都会接收到该信号。 ​ (2) 主机广播从机地址信号+传输方向的选择位,0-主机向从机写数据,1-主机由从机读数据。 ​ (3) 从机接收到匹配的地址后,从机返回应答(ACK)或非应答(NACK)信号,主机收到应答信号后,才能继续发送或接收数据。 ​ (4) “写数据”-主机每发送完一个字节数据,都要等待从机的应答信号。数据传输完成后,主机向从机发送停止信号§。 ​ (5) “读数据”-从机每发送完一个字节数据,都要等待主机的应答信号。主机想停止接收数据,向从机发送非应答信号(NACK)。 ​ (6) 复合格式-两次起始信号;寻找从设备,发送从设备内部的寄存器或存储器地址,读写该地址的内容。

起始和停止信号

​ SCL时钟电平为高 ​ SDA数据线由高 -> 低为总线开始条件 ​ SDA数据线由低 -> 高为总线停止条件

数据有效性

地址及数据方向

​ 读数据方向-由从机控制SDA信号线,主机接收信号 ​ 写数据方向-由主机控制SDA信号线,从机接收信号

响应

​ 在第9个时钟,数据发送端释放SDA的控制权,由数据接收端控制SDA,SDA为高电平表示非应答信号(NACK),SDA为低电平表示应答信号(ACK)。

模拟I2C实现

IO配置
/************************************************ 函数名称 : I2C_Delay 功 能 : I2C延时(非标准延时,请根据MCU速度 调节大小) 参 数 : 无 返 回 值 : 无 作 者 : strongerHuang *************************************************/ static void I2C_Delay(void) { uint16_t cnt = 100; while(cnt--); } /************************************************ 函数名称 : I2C_GPIO_Configuration 功 能 : I2C引脚配置(开漏输出) 参 数 : 无 返 回 值 : 无 作 者 : strongerHuang *************************************************/ void I2C_GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = PIN_I2C_SCL | PIN_I2C_SDA; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_Init(PORT_I2C_SCL, &GPIO_InitStructure); } /************************************************ 函数名称 : I2C_Initializes 功 能 : I2C初始化 参 数 : 无 返 回 值 : 无 作 者 : strongerHuang *************************************************/ void I2C_Initializes(void) { I2C_GPIO_Configuration(); I2C_SCL_HIGH; //置位状态 I2C_SDA_HIGH; } /************************************************ 函数名称 : I2C_SDA_SetOutput 功 能 : I2C_SDA设置为输出 参 数 : 无 返 回 值 : 无 作 者 : strongerHuang *************************************************/ void I2C_SDA_SetOutput(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = PIN_I2C_SDA; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_Init(PORT_I2C_SDA, &GPIO_InitStructure); } /************************************************ 函数名称 : I2C_SDA_SetInput 功 能 : I2C_SDA设置为输入 参 数 : 无 返 回 值 : 无 作 者 : strongerHuang *************************************************/ void I2C_SDA_SetInput(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = PIN_I2C_SDA; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(PORT_I2C_SDA, &GPIO_InitStructure); }
开始和停止信号
/************************************************ 函数名称 : I2C_Start 功 能 : I2C开始 参 数 : 无 返 回 值 : 无 作 者 : strongerHuang *************************************************/ void I2C_Start(void) { I2C_SCL_HIGH; //SCL高 I2C_Delay(); I2C_SDA_HIGH; //SDA高 -> 低 I2C_Delay(); I2C_SDA_LOW; //SDA低 I2C_Delay(); I2C_SCL_LOW; //SCL低(待写地址/数据) I2C_Delay(); } /************************************************ 函数名称 : I2C_Stop 功 能 : I2C停止 参 数 : 无 返 回 值 : 无 作 者 : strongerHuang *************************************************/ void I2C_Stop(void) { I2C_SDA_LOW; //SDA低 -> 高 I2C_Delay(); I2C_SCL_HIGH; //SCL高 I2C_Delay(); I2C_SDA_HIGH; //SDA高 I2C_Delay(); }
应答位信息
主机写,从机应答,主机读取应答
/************************************************ 函数名称 : I2C_GetAck 功 能 : I2C主机读取应答(或非应答)位 参 数 : 无 返 回 值 : I2C_ACK ----- 应答 I2C_NOACK --- 非应答 作 者 : strongerHuang *************************************************/ uint8_t I2C_GetAck(void) { uint8_t ack; I2C_SCL_LOW; //SCL低 -> 高 I2C_Delay(); I2C_SDA_SetInput(); //SDA配置为输入模式(开漏模式可以不用切换方向) I2C_SCL_HIGH; //SCL高(读取应答位) I2C_Delay(); if(I2C_SDA_READ) ack = I2C_NOACK; //非应答 else ack = I2C_ACK; //应答 I2C_SCL_LOW; //SCL低 I2C_Delay(); I2C_SDA_SetOutput(); //SDA配置为输出模式 return ack; //返回应答位 }
主机读,主机产生应答
/************************************************ 函数名称 : I2C_PutAck 功 能 : I2C主机产生应答(或非应答)位 参 数 : I2C_ACK ----- 应答 I2C_NOACK --- 非应答 返 回 值 : 无 作 者 : strongerHuang *************************************************/ void I2C_PutAck(uint8_t Ack) { I2C_SCL_LOW; //SCL低 I2C_Delay(); if(I2C_ACK == Ack) I2C_SDA_LOW; //应答 else I2C_SDA_HIGH; //非应答 I2C_Delay(); I2C_SCL_HIGH; //SCL高 -> 低 I2C_Delay(); I2C_SCL_LOW; //SCL低 I2C_Delay(); }
I2C写一字节
/************************************************ 函数名称 : I2C_WriteByte 功 能 : I2C写一字节 参 数 : Data -------- 数据 返 回 值 : I2C_ACK ----- 应答 I2C_NOACK --- 非应答 作 者 : strongerHuang *************************************************/ uint8_t I2C_WriteByte(uint8_t Data) { uint8_t cnt; for(cnt=0; cnt<8; cnt++) { I2C_SCL_LOW; //SCL低(SCL为低电平时变化SDA有效) I2C_Delay(); if(Data & 0x80) I2C_SDA_HIGH; //SDA高 else I2C_SDA_LOW; //SDA低 Data <<= 1; I2C_Delay(); I2C_SCL_HIGH; //SCL高(发送数据) I2C_Delay(); } I2C_SCL_LOW; //SCL低(等待应答信号) I2C_Delay(); return I2C_GetAck(); //返回应答位 }
I2C读一字节
/************************************************ 函数名称 : I2C_ReadByte 功 能 : I2C读一字节 参 数 : ack --------- 产生应答(或者非应答)位 返 回 值 : data -------- 读取的一字节数据 作 者 : strongerHuang *************************************************/ uint8_t I2C_ReadByte(uint8_t ack) { uint8_t cnt; uint8_t data; I2C_SCL_LOW; //SCL低 I2C_Delay(); I2C_SDA_SetInput(); //SDA配置为输入模式 for(cnt=0; cnt<8; cnt++) { I2C_SCL_HIGH; //SCL高(读取数据) I2C_Delay(); data <<= 1; if(I2C_SDA_READ) data |= 0x01; //SDA为高(数据有效) I2C_SCL_LOW; //SCL低 I2C_Delay(); } I2C_SDA_SetOutput(); //SDA配置为输出模式 I2C_PutAck(ack); //产生应答(或者非应答)位 return data; //返回数据 }
EEPROM写一字节
/************************************************ 函数名称 : EEPROM_WriteByte 功 能 : EEPROM写一字节 参 数 : Addr -------- 地址 Data -------- 数据 返 回 值 : I2C_ACK ----- 应答 I2C_NOACK --- 非应答 作 者 : strongerHuang *************************************************/ uint8_t EEPROM_WriteByte(uint16_t Addr, uint8_t Data) { uint8_t ack; /* 1.开始 */ I2C_Start(); /* 2.设备地址/写 */ ack = I2C_WriteByte(EEPROM_DEV_ADDR | EEPROM_WR); if(I2C_NOACK == ack) { I2C_Stop(); return I2C_NOACK; } /* 3.数据地址 */ #if (8 == EEPROM_WORD_ADDR_SIZE) ack = I2C_WriteByte((uint8_t)(Addr&0x00FF)); //数据地址(8位) if(I2C_NOACK == ack) { I2C_Stop(); return I2C_NOACK; } #else ack = I2C_WriteByte((uint8_t)(Addr>>8)); //数据地址(16位) if(I2C_NOACK == ack) { I2C_Stop(); return I2C_NOACK; } ack = I2C_WriteByte((uint8_t)(Addr&0x00FF)); if(I2C_NOACK == ack) { I2C_Stop(); return I2C_NOACK; } #endif /* 4.写一字节数据 */ ack = I2C_WriteByte(Data); if(I2C_NOACK == ack) { I2C_Stop(); return I2C_NOACK; } /* 5.停止 */ I2C_Stop(); return I2C_ACK; }
EEPROM读一字节
/************************************************ 函数名称 : EEPROM_ReadByte 功 能 : EEPROM读一字节 参 数 : Addr -------- 地址 Data -------- 数据 返 回 值 : I2C_ACK ----- 应答 I2C_NOACK --- 非应答 作 者 : strongerHuang *************************************************/ uint8_t EEPROM_ReadByte(uint16_t Addr, uint8_t *Data) { uint8_t ack; /* 1.开始 */ I2C_Start(); /* 2.设备地址/写 */ ack = I2C_WriteByte(EEPROM_DEV_ADDR | EEPROM_WR); if(I2C_NOACK == ack) { I2C_Stop(); return I2C_NOACK; } /* 3.数据地址 */ #if (8 == EEPROM_WORD_ADDR_SIZE) ack = I2C_WriteByte((uint8_t)(Addr&0x00FF)); //数据地址(8位) if(I2C_NOACK == ack) { I2C_Stop(); return I2C_NOACK; } #else ack = I2C_WriteByte((uint8_t)(Addr>>8)); //数据地址(16位) if(I2C_NOACK == ack) { I2C_Stop(); return I2C_NOACK; } ack = I2C_WriteByte((uint8_t)(Addr&0x00FF)); if(I2C_NOACK == ack) { I2C_Stop(); return I2C_NOACK; } #endif /* 4.重新开始 */ I2C_Start(); /* 5.设备地址/读 */ ack = I2C_WriteByte(EEPROM_DEV_ADDR | EEPROM_RD); if(I2C_NOACK == ack) { I2C_Stop(); return I2C_NOACK; } /* 6.读一字节数据 */ *Data = I2C_ReadByte(I2C_NOACK);//只读取1字节(产生非应答) /* 7.停止 */ I2C_Stop(); return I2C_ACK; }
最新回复(0)