STM32 I2C 通信协议
1、原理
1、硬件电路
一主多从,单片机作为总线主机
SDA:数据线
SCL:时钟线
主机对SCL线完全控制,从机只能读取;在空闲状态下,主机可以主动发起对SDA的控制,只有在从机发送数据和从机应答的时候,主机才会暂时转交SDA的控制权给从机
I2C禁止所有设备输出强上拉的高电平,采用外置弱上拉加开漏输出的电路结构。高电平驱动能力弱,通信线由低电平变到高电平时,上升沿耗时较长,会限制I2C的最大通信速度。为了防止出现两个引脚同时处于输出状态,如一个高电平,一个低电平,会发生电源短路。
开漏输出,输出低电平,下管导通,强下拉;输出高电平,下管断开,引脚浮空。为了避免高电平造成的引脚浮空需要在总线外面给SCL和SDA各外置一个上拉电阻(弱上拉)。
从机地址设备:每个从机设备都有一个唯一确定的7位设备地址,一般器件地址的最后几位是可以在电路中改变的。
2、时序基本单元
1、起始、终止
2、发送字节
低电平主机放数据,高电平从机读数据
3、接收字节
低电平从机放数据,高电平主机读数据
主机在接收数据时,必须释放对SDA的控制权(让SDA恢复高电平),让从机控制SDA,发送数据
4、应答
发送:主机释放SDA后,从机立刻将SDA拉低电平,应答位为0,说明从机已经收到(默认高电平,低电平说明从机进行了回应) ;应答位为1,说明从机没有进行回应,发送的字节可能没有收到。
接收:告诉从机是否要继续发送数据。得到主机应答,从机继续发送;没得到应答,从机会释放SDA
3、地址时序
1、指定地址写
发送7位从机地址+1位读写位,在之后的时序中,主机的操作,0写1读
2、当前地址读
调用当前地址读的时序时,主机没有指定读取哪个地址,从机会返回当前指针指向的寄存器的值。
指针上电默认指向地址0,每写入、读出一个字节,指针自动自增一次。
3、指定地址读
前半部分是指定地址写,只指定了地址,还没写入数据;后半部分是当前地址读,加在一起就是指定地址读。
第一个字节发送从机地址,第二个字节用来发送从机的寄存器地址,从机接收到之后,寄存器指针就指向这个地址。为了修改读写标志位,Sr重复起始条件,相当于另起一个时序,指针不会因为时序的消失而消失或改变。
2、软件模拟I2C代码
//使用宏定义配置SCL和SDA的GPIO口 #define MySCL_PORT GPIOB #define MySCL_GPIOPIN GPIO_Pin_10 #define MySDA_PORT GPIOB #define MySDA_GPIOPIN GPIO_Pin_11
将读写SCL、SDA数据封装成一个函数,方便添加延时函数
void MyI2C_W_SCL(uint8_t BitValue) { GPIO_WriteBit(MySCL_PORT, MySCL_GPIOPIN,(BitAction)BitValue); Delay_ms(10); } void MyI2C_W_SDA(uint8_t BitValue) { GPIO_WriteBit(MySDA_PORT, MySDA_GPIOPIN,(BitAction)BitValue); Delay_ms(10); } //STM32库函数中读和写不是同一个寄存器 uint8_t MyI2C_R_SDA() { uint8_t BitValue; BitValue=GPIO_ReadInputDataBit(MySDA_PORT, MySDA_GPIOPIN); Delay_ms(10);
GPIO口初始化
void MyI2C_Init() { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); //初始SCL和SDA置高电平,释放总线 GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11); }
起始时序
void MyI2C_Start() { MyI2C_W_SDA(1); MyI2C_W_SCL(1); MyI2C_W_SDA(0); MyI2C_W_SCL(0); } void MyI2C_End() { MyI2C_W_SDA(0); MyI2C_W_SCL(1); MyI2C_W_SDA(1); }
收发字节
uint8_t MyI2C_ReceiveByte() { uint8_t Byte=0x00; uint8_t i; //接收数据前主机先确保释放SDA,避免干扰从机发送 MyI2C_W_SDA(1); for(i=0;i>i); } MyI2C_W_SCL(0); } return Byte; }
收发应答
void MyI2C_SendAck(uint8_t AckBit) { MyI2C_W_SCL(0); MyI2C_W_SDA(AckBit); MyI2C_W_SCL(1); MyI2C_W_SCL(0); } uint8_t MyI2C_ReceiveAck() { uint8_t AckBit; MyI2C_W_SDA(1); MyI2C_W_SCL(0); MyI2C_W_SCL(1); AckBit=MyI2C_R_SDA(); MyI2C_W_SCL(0); return AckBit; }