I2C通信协议及STM32代码解释

    科技2025-01-13  8

    I2C通信协议

    一:简介 IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接控制器及其外围设备。它是由数据线 SDA 和时钟线SCL 构成的串行总线,可发送和接收数据。在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 400kbps 以上。 I2C 总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对SCL和SDA线高低电平时序的控制,来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着默认高电平状态。 I2C通信方式为半双工,只有一根SDA线,同一时间只可以单向通信,485也为半双工,SPI和uart为双工。 二 I2C总线特征 1:I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(地址通过物理接地或者拉高,可以从I2C器件的数据手册得知,如TVP5158芯片,7位地址依次bit6~bit0:x101 1xxx, 最低三位可配,如果全部物理接地,则该设备地址为0x58, 而之所以7bit因为1个bit要代表方向,主向从和从向主),主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。 2: I2C总线上可挂接的设备数量受总线的最大电容400pF 限制,如果所挂接的是相同型号的器件,则还受器件地址位的限制。 3:I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。一般通过I2C总线接口可编程时钟来实现传输速率的调整,同时也跟所接的上拉电阻的阻值有关。 4: I2C总线上的主设备与从设备之间以字节(8位)为单位进行双向的数据传输

    三:I2C总线协议 1:起始信号和停止信号 I2C协议规定,总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生(意味着从设备不可以主动通信?所有的通信都是主设备发起的,主可以发出询问的command,然后等待从设备的通信)。 起始和结束信号产生条件:总线在空闲状态时,SCL和SDA都保持着高电平,当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件;当SCL为高而SDA由低到高的跳变,表示产生一个停止条件。 在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态。起始和结束如图所示: 2:数据传输 在了解起始条件和停止条件后,我们再来看看在这个过程中数据的传输是如何进行的。前面我们已经提到过,数据传输以字节为单位。主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位, 此时才认为一个字节真正的被传输完成。当然,并不是所有的字节传输都必须有一个应答位,比如:当从设备不能再接收主设备发送的数据时,从设备将回传一个否定应答位。数据传输的过程如图所示: 正点原子解释:发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收该字节没有成功。 对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P。 3:数据有效性 IIC总线进行数据传输时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟信号上的信号为低电平期间,数据线上的高电平或者低电平状态才允许变化。 即:数据在SCL的上升沿到来之前就需要准备好,并在下降沿到来之前必须稳定 4:地址 在前面我们还提到过,I2C总线上的每一个设备都对应一个唯一的地址,主从设备之间的数据传输是建立在地址的基础上,也就是说,主设备在传输有效数据之前要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位的,然后协议规定再给地址添加一个最低位用来表示接下来数据传输的方向,0表示主设备向从设备写数据,1表示主设备向从设备读数据。向指定设备发送数据的格式如图所示:(每一最小包数据由9bit组成,8bit内容+1bit ACK, 如果是地址数据,则8bit包含1bit方向)

    下图是完整的一帧I2C数据:

    四:I2C总线操作

    对I2C总线的操作实际就是主从设备之间的读写操作。大致可分为以下三种操作情况:

    主设备往从设备中写数据。数据传输格式如下:

    主设备从从设备中读数据。数据传输格式如下:

    主设备往从设备中写数据,然后重启起始条件,紧接着从从设备中读取数据;或者是主设备从从设备中读数据,然后重启起始条件,紧接着主设备往从设备中写数据。数据传输格式如下:

    第三种操作在单个主设备系统中,重复的开启起始条件机制要比用STOP终止传输后又再次开启总线更有效率。

    五:STM32代码详解

    #ifndef __MYIIC_H #define __MYIIC_H #include "sys.h" //IO输入输出方向设置,操作CRL寄存器 #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;} #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;} //设置IIC数据线和时钟线的引脚 #define IIC_SCL PBout(6) //SCL #define IIC_SDA PBout(7) //SDA #define READ_SDA PBin(7) //ÊäÈëSDA //IIC操作函数 void IIC_Init(void); //初始化IIC的IO口 void IIC_Start(void); //发送IIC开始信号 void IIC_Stop(void); //发送IIC停止信号 void IIC_Send_Byte(u8 txd); //发送一个字节数据 u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节 u8 IIC_Wait_Ack(void); //IIC等待应答信号 void IIC_Ack(void); //IIC产生应答信号 void IIC_NAck(void); //IIC产生非应答信号 #endif #include "myiic.h" #include "delay.h" //IIC初始化 void IIC_Init(void) { RCC->APB2ENR|=1<<3; //使能外设IO GPIOB->CRL&=0X00FFFFFF; //PB6/7推挽输出 GPIOB->CRL|=0X33000000; GPIOB->ODR|=3<<6; //PB6,7 输出高 } //产生IIC起始信号 //IIC起始信号产生的条件为:SCL为高电平时,SDA变为低电平 void IIC_Start(void) { SDA_OUT(); //设置SDA为输出模式 IIC_SDA=1; //设置初始状态都为高电平 IIC_SCL=1; delay_us(4); IIC_SDA=0;//起始信号,SDA由高变低 delay_us(4); IIC_SCL=0; //钳住I2C总线,准备发送或接收数据 } //产生IIC停止信号 //产生停止信号的条件为:SCL为高电平时,SDA由低变高 void IIC_Stop(void) { SDA_OUT();//SDA设置为输出 IIC_SCL=0; IIC_SDA=0;//起始都是低电平 delay_us(4); IIC_SCL=1; //SCL变为高电平 IIC_SDA=1;//SDA由低电平转变为高电平产生停止信号 delay_us(4); } //IIC主设备传输一个数据完成后,从设备产生应答信号,主设备等待应答信号到来 //产生条件:SCL为高电平期间,SDA时钟保持低电平。 //返回值:1,接收应答失败;0,接收应答成功 u8 IIC_Wait_Ack(void) { u8 ucErrTime=0; SDA_IN(); //SDA设置为输入 IIC_SDA=1;delay_us(1); //刚开始都为高电平 IIC_SCL=1;delay_us(1); while(READ_SDA)//读取数据线SDA的电平状态,如果持续低电平,则不会产生IIC_Stop信号,返回0 { ucErrTime++; if(ucErrTime>250) { IIC_Stop();//如果在SCL高电平期间,SDA信号线产生了一定时间的高电平则认为应答失败 return 1; } } IIC_SCL=0;//应答结束,时钟输出0 return 0; } //产生ACK应答信号 //产生条件为:SCL为高电平期间,SDA始终保持低电平 void IIC_Ack(void) { IIC_SCL=0; SDA_OUT(); IIC_SDA=0; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; } //产生非应答信号 //产生条件为:SCL为高电平期间,SDA也出现了高电平 void IIC_NAck(void) { IIC_SCL=0; SDA_OUT(); IIC_SDA=1; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; } //IIC发送一个字节 //发送条件为:SCL为低电平期间准备好数据,SCL为高电平期间保持数据 void IIC_Send_Byte(u8 txd) { u8 t; SDA_OUT(); //SDA设置为输出 IIC_SCL=0;//拉低时钟准备数据 for(t=0;t<8;t++) { if((txd&0x80)>>7) //从数据的最高位开始传输 IIC_SDA=1; //如果为1,则数据位为1 else IIC_SDA=0; //不为1,数据位为0 txd<<=1; //逐个传输 delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; delay_us(2); } } //读一个字节,ack=1时,发送ACK,ack=0,发送nACK //读取条件为:SCL为高电平期间,读取SDA的电平状态 u8 IIC_Read_Byte(unsigned char ack) { unsigned char i,receive=0; SDA_IN();//设置SDA为输入 for(i=0;i<8;i++ ) //逐个读8位 { IIC_SCL=0; delay_us(2); IIC_SCL=1; //SCL为高电平 receive<<=1; //逐个移动数据位 if(READ_SDA)receive++; //如果SDA为高,则相应的数据为+1,反之为0 delay_us(1); } if (!ack) IIC_NAck();//不产生ACK应答 else IIC_Ack(); //产生ACK应答 return receive; }

    六:通过IIC与EEPROM(24C02)通信 1:硬件连接 EEPROM的A0-A2默认接地,SDA数据线与SCL时钟线默认拉高,与PC11和PC12连接(不同单片机引脚可能不同),WP为写保护

    当A0\A1\A2接地时,设备地址为10100000时表示写入数据,即0xA0,10100001为读出数据,即0xA1

    2:写过程 24C02其实可以看做一个存储器,每个地址存储一个字节,写字节时过程如下图:首先是起始信号,然后发送设备地址(写时为0xA0),等待应答,发送准备写入字节的地址,等待应答,写入数据,等待应答,发送停止信号。

    //在AT24CXX指定地址写入一个数据 //WriteAddr :写入数据的目的地址,对于24C02为0-255 //DataToWrite:要写入的数据 void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite) { IIC_Start(); if(EE_TYPE>AT24C16)//防止更大容量的AT24CXX { IIC_Send_Byte(0XA0); //发送写命令 IIC_Wait_Ack(); IIC_Send_Byte(WriteAddr>>8);//发送高地址 }else IIC_Send_Byte(0XA0+((WriteAddr/256)<<1)); //发送器件地址,写数据 IIC_Wait_Ack(); //等待应答 IIC_Send_Byte(WriteAddr%256); //发送低地址 IIC_Wait_Ack(); IIC_Send_Byte(DataToWrite); //发送字节 IIC_Wait_Ack(); IIC_Stop(); //产生停止条件 delay_ms(10); //EEPROM的写入速度较慢,加入延时 }

    3:读过程 首先是起始信号,然后发送设备地址(此时先设置为写状态0xA0),等待应答,写入准备读出字节的地址,等待应答,重新启动,发送设备地址更改为读状态,等待应答,读出数据,等待应答,发送停止信号。

    //在AT24CXX指定地址读取一个数据 //ReadAddr:开始读数的地址 //返回值:读到的数据 u8 AT24CXX_ReadOneByte(u16 ReadAddr) { u8 temp=0; IIC_Start(); if(EE_TYPE>AT24C16) { IIC_Send_Byte(0XA0); //发送写命令 IIC_Wait_Ack(); IIC_Send_Byte(ReadAddr>>8);//发送高地址 }else IIC_Send_Byte(0XA0+((ReadAddr/256)<<1)); //发送器件地址0xA0,写数据 IIC_Wait_Ack(); IIC_Send_Byte(ReadAddr%256); //发送低地址 IIC_Wait_Ack(); IIC_Start(); IIC_Send_Byte(0XA1); //进入接收模式 IIC_Wait_Ack(); temp=IIC_Read_Byte(0); IIC_Stop();//产生一个停止条件 return temp; }

    参考:http://blog.csdn.net/w89436838/article/details/38660631 《STM32不完全手册_寄存器版本_V3.1》

    Processed: 0.024, SQL: 8