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<8;i++)
{
MyI2C_W_SCL(0);
MyI2C_W_SCL(1);
if(MyI2C_R_SDA()==1)
{
Byte|=(0x80>>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;
}