目前,IIC总线已经成为了一种通用的通信协议,广泛应用于各种类型的电子设备中。IIC总线的稳定性、可靠性和灵活性都得到了广泛的认可,并且随着技术的不断发展,IIC总线仍然在不断地演化和升级,以适应新的应用场景和需求。
前言
随着嵌入式人工智能的不断发展,学习stm32单片机这门技术也越来越重要,很多人都开启了stm32单片机的学习,本文就介绍了stm32单片机中一个比较重要的通信协议。
一、IIC的通信方式
通俗来说,IIC其实就是一个片上外设,主要通过GPIO模拟IIC的时序进行通信。再深入往底层来讲,IIC是由总线拉低或拉高来控制发送信号。并且IIC支持多主机多从机进行通信。
二、总线的作用
总线是由时钟线(SCL)和数据线(SDA)两根线相辅相成,而总线上必须接两个上拉电阻,用于空闲时拉高总线。当多个主机同时发起IIC通信时,由总线仲裁决定,每个设备都有唯一的ID,只有主机能够发起IIC
时钟线SCL的作用:当SCL为高电平时,SDA上的数据才有效;当SCL为低电平时,SDA上的数据无效
总线仲裁则是指遵循低电平优先原则,一是看哪个设备先发出低电平,二是看哪个设备发出的低电平时间长
三、IIC协议的7个信号
1 . 起始信号(谁发起信号谁就是主机,主机能指定地址与该设备通信)
SCL为高电平期间,SDA由高电平变低电平,产生下降沿
2 . 停止信号
SCL为高电平期间,SDA由低电平变高电平,产生上升沿
3 . 应答信号
SCL为高电平时,SDA产生一个低电平
4 . 非应答信号
SCL为高电平时,SDA产生一个高电平
5 . 等待应答
拉高SCL线,等待SDA线上的高电平或低电平
6 . 发送数据
当SCL为高电平时,SDA上数据是稳定的,当SCL为低电平时,SDA上的数据可以改变
7 . 接收数据
当SCL为高电平时,SDA上数据是稳定的,当SCL为低电平时,SDA上的数据可以改变
四、IIC的时钟线初始化分析
1 . SCL时钟线初始化(GPIOB6)
设置开漏输出模式是因为确保没有数据传输时,两条线都处于高电平,所以外接有上拉电阻,也就是可以设置开漏输出了
2 . SDA数据线初始化(两条SDA)
设置浮空输入模式是因为数据传输时会把SDA拉高或拉低,而拉高表示传输完成,所以为了确保SDA传输完成时没有被拉高,所以外部电路会负责来拉高
五、IIC通信在不同设备的时序
1 . 主要分为起始条件、停止条件、ACK信号
a . 起始条件是指SDA数据线从高电平切换到低电平,同时SCL时钟线保持高电平的状态
b . 停止条件是指SDA数据线从低电平切换到高电平,同时SCL时钟线保持高电平的状态
c . ACK信号是指接收方通过拉低SDA线来确认已成功接收到数据的信号
2 . I2C_CR1和I2C_SR1寄存器
它们是用来设置和监测I2C通信协议的状态,并使用起始条件、停止条件和ACK信号等时序来确保正确的数据传输
六、IIC通信的内部传参
1 . 主机向从机发送写操作:
1. 发送I2C起始信号
IIC_START();
这个函数调用发送I2C起始信号,用于启动I2C通信。
2. 发送AT24C0X的I2C地址
IIC_SendByteData(0Xa0);
这个函数调用发送AT24C0X的I2C地址,用于与AT24C0X建立连接。0xa0是AT24C0X的I2C地址,因为它是一个7位地址,所以需要将它左移一位,以便在最后一位添加读写标志位。
3. 等待从设备的ACK信号
if(ACK!=IIC_WaitACK()){ //等待
IIC_STOP();
return;
}
这个函数用于等待从设备的ACK信号。如果从设备没有响应ACK信号,函数将停止I2C通信,并返回。
4. 发送要写入的地址
IIC_SendByteData(ADDR);
这个函数用于发送要写入的地址。它告诉AT24C0X从哪个地址开始写入数据。
5. 等待从设备的ACK信号
if(ACK!=IIC_WaitACK()){ //等待从机ACK
IIC_STOP();
return;
}
这个函数用于等待从设备的ACK信号。如果从设备没有响应ACK信号,函数将停止I2C通信,并返回。
6. 发送要写入的数据
IIC_SendByteData(DATA);
这个函数用于发送要写入的数据。它告诉AT24C0X需要写入的数据。
7. 等待从设备的ACK信号
if(ACK!=IIC_WaitACK()){ //等待从机ACK
IIC_STOP();
return;
}
这个函数用于等待从设备的ACK信号。如果从设备没有响应ACK信号,函数将停止I2C通信,并返回。
8. 发送I2C停止信号
IIC_STOP();
这个函数用于发送I2C停止信号,以结束I2C通信。
总体来说,这段代码的作用是向AT24C0X EEPROM存储器中写入一个字节的数据。
如果写入过程中出现错误,函数将停止I2C通信,并返回
2 . 主机向从机发送读操作
它使用了I2C通信协议进行数据传输。
- uint8_t:
这个函数返回一个8位的无符号整数,用于存储读取的数据。
- AT24C0X_ReadByteData(uint32_t ADDR):
这个函数带有一个参数ADDR,它是要读取的数据在EEPROM中的地址。
- IIC_START():
这个函数用于发送起始信号,标志着通信的开始。
- IIC_SendByteData(0xa0):
这个函数用于向I2C总线发送一个字节的数据,其中0xa0表示AT24C0X EEPROM存储器的地址。
这是一个写入请求,因为最后一位是0。
- ACK!=IIC_WaitACK():
这个语句用于等待从设备的应答信号。ACK表示应答信号,如果IIC_WaitACK()函数返回的值
不是ACK,则表示没有收到应答信号,需要停止通信。
- IIC_SendByteData(ADDR):
这个函数用于向EEPROM发送要读取的数据的地址。
- IIC_START():
这个函数用于发送起始信号,标志着通信的开始。
- IIC_SendByteData(0xa1):
这个函数用于向I2C总线发送一个字节的数据,其中0xa1表示AT24C0X EEPROM存储器的地址。
这是一个读取请求,因为最后一位是1。
- Rxdata = IIC_RxByteData(NoACK):
这个语句用于接收从EEPROM发送来的数据。NoACK表示不需要发送应答信号,因为这是最后一个字节。
- IIC_STOP():这个函数用于发送停止信号,标志着通信的结束。
七、IIC通信的详细实现步骤
1 . IIC的7个时序函数
#include "drv_iic.h"
#include "drv_systick.h"
//SCL时钟线初始化
void IIC_Config(void){
GPIO_InitTypeDef GPIO_InitStruct;
//1、打开时钟GPIOB(采用引脚模拟IIC通信)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
//2、初始化GPIO
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;//SCL
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出(外接有两个上拉电阻)
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
//SDA数据线输出的初始化
void IIC_SDA_OUT(void){
GPIO_InitTypeDef GPIO_InitStruct;
//初始化GPIO
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出(外接有两个上拉电阻)
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
//SDA数据线输入的初始化
void IIC_SDA_IN(void){
GPIO_InitTypeDef GPIO_InitStruct;
//初始化GPIO
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
//起始信号:SCL为高电平期间,SDA由高电平变低电平,产生下降沿
//发送信号都是由主机
void IIC_START(void){
//1、拉低SCL线,保证在改变SDA线时不会产生其他信号
SCL_LOW;
//2、输出起始信号,将SDA设置成输出模式
IIC_SDA_OUT();
//3、准备好有效数据后,拉高SDA线
SDA_HIGH;
//4、拉高SCL线让SDA的高电平有效
SCL_HIGH;
//5、保证两者的高电平维持5us有效
set_Nudelay(5);
//6、跳变低电平,产生下降沿,发起IIC通信
SDA_LOW;
//7、保证下降沿维持5us有效
set_Nudelay(5);
//8、发起IIC后让其他主机不能再发起
SCL_LOW;
}
//停止信号:SCL为高电平期间,SDA由低电平变高电平,产生上升沿
//发送信号都是由主机
void IIC_STOP(void){
//1、拉低SCL线,保证在改变SDA线时不会产生其他信号
SCL_LOW;
//2、输出停止信号,将SDA设置成输出模式
IIC_SDA_OUT();
//3、准备好有效数据后,拉低SDA线
SDA_LOW;
//4、拉高SCL线让SDA的低电平有效
SCL_HIGH;
//5、保证两者的电平差维持5us有效
set_Nudelay(5);
//6、跳变高电平,产生上升沿,发起停止
SDA_HIGH;
//7、保证上升沿维持5us有效
set_Nudelay(5);
}
//应答信号: SCL为高电平时,SDA产生一个低电平
void IIC_SendACK(void){
//1、拉低SCL线,保证在改变SDA线时不会产生其他信号
SCL_LOW;
//2、输出应答信号,将SDA设置成输出模式
IIC_SDA_OUT();
//3、准备好有效数据后,拉低SDA线
SDA_LOW; //SDA先拉低防止产生下降沿或上升沿
//4、拉高SCL线让SDA的低电平有效
SCL_HIGH;
//5、保证两者的电平差维持5us有效
set_Nudelay(5);
//6、让其他主机不能再发起
SCL_LOW;
}
//非应答信号: SCL为高电平时,SDA产生一个高电平
void IIC_SendNoACK(void){
//1、拉低SCL线,保证在改变SDA线时不会产生其他信号
SCL_LOW;
//2、输出非应答信号,将SDA设置成输出模式
IIC_SDA_OUT();
//3、准备好有效数据后,拉低SDA线
SDA_HIGH;
//4、拉高SCL线让SDA的高电平有效
SCL_HIGH;
//5、保证两者的高电平维持5us有效
set_Nudelay(5);
//6、让其他主机不能再发起
SCL_LOW;
}
//等待应答: 拉高SCL线,等待SDA线上的高电平或低电平
uint8_t IIC_WaitACK(void){
//1、超时等待的变量
uint8_t temp = 0;
//2、拉低SCL线,保证在改变SDA线时不会产生其他信号
SCL_LOW;
//3、等待应答信号,将SDA设置成输入模式,即接收
IIC_SDA_IN();
//4、拉高SCL线等待
SCL_HIGH;
set_Nudelay(5);
//5、等待高电平或低电平
while(SDA_READ){ //高电平表示非应答,低电平表示应答
temp++;
//如果没有应答,则停止IIC通信
if(temp>250){
IIC_STOP();
return NoACK;
}
}
//如果成功应答则拉低SCL线,让其他主机不能再发起IIC
SCL_LOW;
return ACK;
}
//发送数据: 当SCL为高电平时,SDA上的数据是稳定的, 当SCL为低电平时,SDA上的数据可以改变
//主机向从机发送数据,先发送地址,确认后再发送数据
void IIC_SendByteData(uint8_t data){
uint8_t i;
//拉低SCL线,保证在改变SDA线时不会产生其他信号
SCL_LOW;
//输出非应答信号,将SDA设置成输出模式
IIC_SDA_OUT();
//IIC是串行通信,所以一个字节分8次发送
for(i=0;i<8;i++){
//IIC是高位先行,先判断最高位的电平
if(data&0x80){ //SCL以拉低,SDA改变不影响,所以可以用来区分数据0/1
//高电平时SDA=1
SDA_HIGH;
}else{
//低电平时SDA=0
SDA_LOW;
}
//拉高SCL线让SDA发送数据有效
SCL_HIGH;
//维持5us有效时间
set_Nudelay(5);
//发送完前一个数据,拉低SCL才能等待发送下一个数据
SCL_LOW;
//维持5us有效时间
set_Nudelay(5);
//发送完最高位左移发送下一位
data = data<<1;
}
//让其他主机不能再发起IIC
SCL_LOW;
}
//接收数据:同发送数据一样
//主机接收从机发送过来的地址
uint8_t IIC_RxByteData(uint8_t ackflag){
uint8_t i;
uint8_t Rxdata=0;
//拉低SCL线,保证在改变SDA线时不会产生其他信号
SCL_LOW;
//接收数据,将SDA设置成输入模式
IIC_SDA_IN();
//IIC是串行通信,所以一个字节分8次接收
for(i=0;i<8;i++){
//左移要放判断前面,否则数据多左移了一次
Rxdata=Rxdata<<1;
//拉高SCL,让从机发出数据
SCL_HIGH;
//维持5us有效时间
set_Nudelay(5);
//IIC是高位先行,先判断最高位的电平
if(SDA_READ){
//如果是高电平则记录下来
Rxdata=Rxdata|0x01;
}
//拉低SCL线,让从机准备下一个数据发送
SCL_LOW;
//维持5us有效时间
set_Nudelay(5);
}
//让其他主机不能再发起IIC
SCL_LOW;
return Rxdata;
}
2 . IIC的读写操作
#include "drv_at24c02.h"
#include "drv_iic.h"
void AT24C0X_WriteByteData(uint8_t ADDR,uint8_t DATA){
IIC_START();
IIC_SendByteData(0Xa0);
if(ACK!=IIC_WaitACK()){ //等待
IIC_STOP();
return;
}
IIC_SendByteData(ADDR); //告诉从机往哪个地址写入数据
if(ACK!=IIC_WaitACK()){ //等待从机ACK
IIC_STOP();
return;
}
IIC_SendByteData(DATA); //告诉从机需要写入的数据
if(ACK!=IIC_WaitACK()){ //等待从机ACK
IIC_STOP();
return;
}
IIC_STOP();
}
uint8_t AT24C0X_ReadByteData(uint32_t ADDR){
uint8_t Rxdata = 0;
//第一个通信周期(发送读取请求)
IIC_START();
IIC_SendByteData(0xa0);//从设备的地址
if(ACK!=IIC_WaitACK()){//等待应答
IIC_STOP();
return 0;
}
IIC_SendByteData(ADDR);//要读取的数据的地址
if(ACK!=IIC_WaitACK()){//等待应答
IIC_STOP();
return 0;
}
//第二个通信周期(发送要读取的数据)
IIC_START();
IIC_SendByteData(0xa1);//从设备的地址(最后一位是读取标志)
if(ACK!=IIC_WaitACK()){//等待应答
IIC_STOP();
return 0;
}
Rxdata = IIC_RxByteData(NoACK);//接收从机发送来的数据
IIC_STOP();
return Rxdata;
}
总结
IIC总线由两根线组成:一个是数据线SDA,一个是时钟线SCL。
IIC通信是基于主从模式的,一个主设备可以控制多个从设备。
IIC通信的传输速率比较低,通常在100kHz或400kHz左右。
IIC通信的协议非常简单,通信的过程是由起始信号、地址、数据、停止信号四个部分组成。
IIC通信需要注意时序,例如数据的传输和接收都需要按照一定的时序进行。
IIC通信在硬件上需要使用IIC接口的控制器和外设芯片。
在程序开发中,需要按照IIC协议的通信过程进行编写,以实现数据的读写和设备的控制。
总的来说,IIC通信是一种简单、可靠、广泛应用的通信协议,对于嵌入式系统的开发和应用具有重要的意义。