I2C通信(Inter IC BUS)
两根通信线:SCL(Serial Clock)、SDA(Serial Data)
同步、半双工(一根数据线兼顾数据收发)
带数据应答
支持总线挂载多设备(一主多从、多主多从)
I2C硬件电路设计
所有I2C设备的SCL连在一起,SDA连在一起
设备的SCL,SDA都要配置成开漏输出(强下拉,和浮空。所以外部接一个上拉电阻)
(1)杜绝电源短路的现象(被同时强下拉和强上拉)。
(2)避免来回切换GPIO模式,统一设置成开漏。
(3)可以执行多主机模式下的时钟同步和总线仲裁
SCL和SDA各加一个上拉电阻,4.7k左右
异步时序对时间要求严格,依赖硬件电路
同步时序对时间要求不严格,不依赖硬件电路(可以模拟时序)
I2C软件(时序)设计
1.起始和终止都是由主机完成的(一主多从)
2.发送一个字节(主机放数据到SDA)
八位数据(一个字节)。串口是低位先行。
如果主机进中断,时序(SCL和SDA)会暂停,等待恢复。
3.接收一个字节(从机放数据到SDA)
因为总线是线与,所以任何一个外设低电平都能拉低SDA,所以要释放SDA。
4.发送应答,接收应答(一位的数据,相当于发送一字节和接收一字节中取一位)
I2C时序
1.指定地址写(字节)
图6的波形(MPU6050的I2C):S起始位; 一个字节(前七位是从机地址,第八位书读/写(r/w)0:写入。1:读); 应答位(RA); 一个字节(地址); 应答(RA); 一个字节(发送的数据);应答(RA);停止位(P)
2.当前地址读(不能选地址读,用的不多)(一个字节)
3.指定地址读(一个字节)
前半部分相当于指定地址写(指定地址),后半部分相当于当前地址读
S起始位; 七位地址和一个读/写位; RA应答; 指定地址(写入从机的地址指针); Sr(重复其实条件); 七位地址和一个读/写位; RA应答; 接收一个字节数据; SA(非应答);P(停止)
主机给应答(RA)了,从机就会接着发。主机非应答(SA),从机就不发,就可以P(没有非应答的话,可能P会被拉下去,不会被停止)
软件I2C代码(GPIO模拟时序)
SCL高电平的时候,SDA能动的只有开始(S)和结束(P)
数据传输的时候,SCL高电平,SDA不能动
1. 起始(Start)
void MyI2C_Start(void)
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1); // 1:释放 0:拉低
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
2.停止(Stop)
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
3.发送一个字节
void MyI2C_SendByte(uint8_t Byte)
{
for(uint8_t i = 0; i<8; ++i)
{
MyI2C_W_SDA(Byte & (0x80 >> i));
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
4.接收一个字节
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t Byte = 0x00;
MyI2C_W_SDA(1);
for(uint8_t i = 0; i<8; i++)
{
MyI2C_W_SCL(1);
if(MyI2C_R_SDA() == 1){Byte |= (0x80 >>i);}
MyI2C_W_SCL(0);
}
return Byte;
}
5.发送应答
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
6.接收应答
uint8_t MyI2C_ReceiveAck(void)
{
uint8_t AckBit ;
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
AckBit = MyI2C_R_SDA() ;
MyI2C_W_SCL(0);
return AckBit;
}
硬件I2C代码(STM32外设,调库)
I2C功能框图
发送
数据写入→数据寄存器(DATA REGISTER ,DR)→数据移位寄存器(上一个数据移位完成后)。置状态寄存器的TXE = 1(表示发送寄存器为空)
接收
SDA→移位寄存器(收齐一个字节)→数据寄存器(DR),置状态寄存器的RXNE (表示接收寄存器非空)→可以从DR读数据
1.硬件I2C初始化
硬件I2C的SDA,SCL是固定的引脚,而且都要设置为复用开漏模式(F_OD)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //ACK使能,ACK标志位
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//选择七位地址或十位地址
I2C_InitStructure.I2C_ClockSpeed = 50000; //时钟速度 <400k
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;//占空比,高速时比较明显
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_OwnAddress1 = 0x00; //自己的地址,一主动从不用这个
I2C_Init(I2C2, &I2C_InitStructure);
I2C_Cmd(I2C2, ENABLE);
2.硬件I2C读写MPU6050寄存器
void MPU6050_W_Reg(uint8_t RegAddress, uint8_t Data )
{
I2C_GenerateSTART(I2C2, ENABLE);//S
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);//八位数据(MPU6050设备地址 + 一位读写(R/W))
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C2, RegAddress);//八位数据(MPU6050内部寄存器地址)
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);
I2C_SendData(I2C2, Data);//八位数据(要发送的数据Data)
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED) ;
I2C_GenerateSTOP(I2C2, ENABLE);//停止(Stop)
}
/* MPU6050读寄存器
*/
uint8_t MPU6050_R_Reg(uint8_t RegAddress)
{
uint8_t Data;
I2C_GenerateSTART(I2C2, ENABLE);//S
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) ;
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);//八位数据(MPU6050设备地址 + 一位读写(R/W))
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C2, RegAddress);//八位数据(MPU6050内部寄存器地址)
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED) ;
I2C_GenerateSTART(I2C2, ENABLE);//S
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) ;
/*#define I2C_Direction_Transmitter ((uint8_t)0x00)
#define I2C_Direction_Receiver ((uint8_t)0x01)*/
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);// 使用Receiver函数会自动把MPU6050_ADDRESS置1,相当一地址+1
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) ;
/*先置Ack和p,然后在判断状态寄存器*/
I2C_AcknowledgeConfig(I2C2, DISABLE);
I2C_GenerateSTOP(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED) ;
Data = I2C_ReceiveData(I2C2); //数据寄存器
I2C_AcknowledgeConfig(I2C2, ENABLE);
return Data;
}