IIC硬件电路
所有I2C设备的SCL连在一起,SDA连在一起
设备的SCL和SDA均要配置成开漏输出模式
SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
-
在IIC通信过程中设备的SCL和SDA均要配置成开漏输出模式 作用:开漏输出只能输出低电平。
-
SCL和SDA各添加一个上拉电阻 作用:在开漏输出开关断开时,会处于浮空模式,上拉电阻会把浮空模式下的电平拉高,都是高电平对通信没有影响。
如果设备想要相互通信,发送方就可输出低电平,接收方默认高电平就可以接收。
IIC时序基本单元
起始单元
起始条件:SCL高电平期间,SDA从高电平切换到低电平
终止条件:SCL高电平期间,SDA从低电平切换到高电平
- SCL迟到早退
发送单元
发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
接收单元
接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间从机在SDA上有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
应答单元
发送应答:主机在接收完从机发送的一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答(如果发送的是0表示主机接收到数据,从机继续发送。如果发送数据1表示主机没有应答,可能是主机没有收到数据,或者是主机不想让从机继续发送数据)
接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA,接着从机接管SDA,如果从机在SDA发送的是0,表示从机接收数据成功,如果从机在SDA发送的是1,表示未接受成功)
指定地址写数据
- 对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)。
- 首字节第八位R/W位:0是主机向从机写数据,1是主机向从机读数据。
当前地址读数据
对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)
指定地址读数据
- 首先写入一个寄存器地址,写入的该寄存器地址会存储在地址指针里,再进行起始开始读,就可以读取到上一步地址指针写入的地址,从而读取该设备寄存器上的地址。
- 对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)
I2C初始化函数
/*封装函数SCL把引脚置为高低电平*/
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);
Delay_us(10);
}
/*封装函数SDA把引脚置为高低电平*/
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);
Delay_us(10);
}
/*读取SDA的高低电平*/
uint8_t MyI2C_R_SDA()
{
uint8_t BitVlalue;
BitVlalue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
Delay_us(10);
return BitVlalue;
}
void MyI2C_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//PB10PB11都配置成开漏输出的模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
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_Stop()
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
/*写入一个字节数据*/
void MyI2C_SendByte(uint8_t Byte)
{
uint16_t i;
for(i = 0;i < 8;i++)
{
MyI2C_W_SDA(Byte & (0x80 >> i));
MyI2C_W_SCL(1);//拉高时钟线从机读取数据
MyI2C_W_SCL(0);//再拉低数据
}
}
/*接收一个字节数据*/
uint8_t MyI2C_ReceiveByte()
{
uint16_t i;
uint8_t Byte = 0x00;
for(i = 0;i < 8;i ++)
{
MyI2C_W_SDA(1);//主机释放SDA,从机把数据写到SDA上
MyI2C_W_SCL(1);//SCL高电平,主机读取上一步从机发送的数据
if(MyI2C_R_SDA() == 1)
{
Byte |= (0x80>>i);
}
MyI2C_W_SCL(0);//SCL时钟拉低,让从机接着发数据
}
return Byte;
}
/*发送ACK*/
void MyI2C_SendACK(uint8_t ACK)
{
uint16_t i;
MyI2C_W_SDA(ACK);
MyI2C_W_SCL(1);//拉高时钟线从机读取数据
MyI2C_W_SCL(0);//再拉低数据
}
/*接收ACK*/
uint8_t MyI2C_ReceiveACK()
{
uint8_t ACK;
MyI2C_W_SDA(1);//主机释放SDA,从机把数据写到SDA上
MyI2C_W_SCL(1);//SCL高电平,主机读取上一步从机发送的数据
ACK = MyI2C_R_SDA();//接收的应答位赋值给ACK
MyI2C_W_SCL(0);//SCL时钟拉低,进入下个时序单元
return ACK;
}
/*主函数*/
/*可在MPU6050 AD0接高电平用于更改地址名(将原来的0xD0改为0XD2)*/
int main(void)
{
MyI2C_Init();
MyI2C_Start();
MyI2C_SendByte(0xD0);//发送一个地址
uint16_t ACK = MyI2C_ReceiveACK();//查看该从机是否接收到了该地址,如果接收到了应答位为0,没接收到应答位为1
MyI2C_Stop();
Serial_Init();
Serial_SendByte(ACK);
while (1)
{
}
}
MPU6050+I2C通信
#define MPU5050_ADDRESS 0XD2
/*指定地址写寄存器 寄存器地址 写入的数据*/
void MPU6050_Write(uint8_t ReAddress, uint8_t Data)
{
MyI2C_Start();
MyI2C_SendByte(MPU5050_ADDRESS);//写入从机地址
MyI2C_ReceiveACK();
MyI2C_SendByte(ReAddress); //写入寄存器地址
MyI2C_ReceiveACK();
MyI2C_SendByte(Data);//写入指定寄存器下的数据
MyI2C_ReceiveACK();
MyI2C_Stop();
}
/*读取指定地址寄存器的数据*/
uint8_t MPU6050_Read(uint8_t ReAddress)
{
uint8_t Data;
MyI2C_Start();
MyI2C_SendByte(MPU5050_ADDRESS );//写入从机地址
MyI2C_ReceiveACK();
MyI2C_SendByte(ReAddress); //写入寄存器地址
MyI2C_ReceiveACK();
MyI2C_Start();
MyI2C_SendByte(MPU5050_ADDRESS | 0x01);//写入从机地址并把第八位置1,改为读数据
MyI2C_ReceiveACK();
Data = MyI2C_ReceiveByte();//读取一个字节数据
MyI2C_SendACK(1);//应答位为1停止读数据
MyI2C_Stop();
return Data;
}
void MPU6050_Init()
{
MyI2C_Init();
MPU6050_Write(MPU6050_PWR_MGMT_1,0x01);//接触睡眠,选择陀螺仪时钟
MPU6050_Write(MPU6050_PWR_MGMT_2,0x00);//六个轴不待机
MPU6050_Write(MPU6050_SMPLRT_DIV,0x09);//采样分频为10
MPU6050_Write(MPU6050_CONFIG,0x06);//滤波参数给最大
MPU6050_Write(MPU6050_GYRO_CONFIG,0x18);//陀螺仪选择最大量程
MPU6050_Write(MPU6050_ACCEL_CONFIG,0x18); //加速度计选择最大量程
}
/*通过指针把主函数变量的地址传递到子函数内,从而把子函数想要的值传递进来,调用该函数就把子函数的值输出返回*/
void MPU6050_GetData(int16_t *Accx,int16_t *Accy,int16_t *Accz,int16_t *Gyrox,int16_t *Gyroy,int16_t *Gyroz)
{
uint8_t DataH,DataL;
DataH = MPU6050_Read(MPU6050_ACCEL_XOUT_H);//读取加速度寄存器X轴高八位的值
DataL = MPU6050_Read(MPU6050_ACCEL_XOUT_L);//读取加速度寄存器X轴低八位的值
*Accx = (DataH<<8) | DataL; //将读取加速度寄存器X轴16位的值赋值给输出参数*Accx
DataH = MPU6050_Read(MPU6050_ACCEL_YOUT_H);
DataL = MPU6050_Read(MPU6050_ACCEL_YOUT_L);
*Accy = (DataH<<8) | DataL;
DataH = MPU6050_Read(MPU6050_ACCEL_ZOUT_H);
DataL = MPU6050_Read(MPU6050_ACCEL_ZOUT_L);
*Accz = (DataH<<8) | DataL;
DataH = MPU6050_Read(MPU6050_GYRO_XOUT_H);//读取陀螺仪寄存器X轴高八位的值
DataL = MPU6050_Read(MPU6050_GYRO_XOUT_L);//读取陀螺仪寄存器X轴低八位的值
*Gyrox = (DataH<<8) | DataL; //将读取陀螺仪寄存器X轴16位的值赋值给输出参数*Gyrox
DataH = MPU6050_Read(MPU6050_GYRO_YOUT_H);
DataL = MPU6050_Read(MPU6050_GYRO_YOUT_L);
*Gyroy = (DataH<<8) | DataL;
DataH = MPU6050_Read(MPU6050_GYRO_ZOUT_H);
DataL = MPU6050_Read(MPU6050_GYRO_ZOUT_L);
*Gyroz = (DataH<<8) | DataL;
}
/*主函数功能实现*/
int16_t AX,AY,AZ,GX,GY,GZ;//这六个值用来接收XYZ轴的加速度值和陀螺仪值
int main(void)
{
MPU6050_Init();
while (1)
{
/*把6个子函数变量的值地址*Accx,*Accy,*Accz,*Gyrox,*Gyroy,*Gyroz传递给AX,AY,AZ,GX,GY,GZ */
MPU6050_GetData(&AX,&AY,&AZ,&GX,&GY,&GZ);
Serial_Init();
Serial_SendNumber(AX,5);
Serial_SendString("\r\n");
Serial_SendNumber(AY,5);
Serial_SendString("\r\n");
Serial_SendNumber(AZ,5);
Serial_SendString("\r\n");
Serial_SendNumber(GX,5);
Serial_SendString("\r\n");
Serial_SendNumber(GY,5);
Serial_SendString("\r\n");
Serial_SendNumber(GZ,5);
Serial_SendString("\r\n");
Serial_SendString("\r\n");
Serial_SendString("\r\n");
Delay_ms(1000);
}
}
硬件实现I2C协议
由STM32的IIC片上外设专门负责实现IIC通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理IIC协议的方式减轻了CPU的工作,且使软件设计更加简单。
STM32的IIC外设可用作通讯的主机及从机,支持100Kbit/s和400Kbits/s的速率,支持7位、10位设备地址,支持DMA数据传输,并具有数据校验功能。
I2C基本结构
主机发数据从GPIO引脚写入到数据寄存器DR,从而转移到移位寄存器,移位寄存器空闲时数据寄存器才能把数据转移到移位寄存器。
主机接收数据时,从机把数据写入到数据寄存器,然后转移到移位寄存器,接着主机接受移位寄存器的值,每接收一位数据,移位寄存器就空闲,紧接着从机就发送到移位寄存器上,依次循环,直到数据接收完成。
硬件I2C读写MPU6050函数
#define MPU6050_ADDRESS 0xD2
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
uint32_t Timeout;
Timeout = 10000;
while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)//把函数的形参传过来,如果在这个时间范围内没有检测到该事件,就跳出循环
{
{
Timeout --;
if (Timeout == 0)
{
break;
}
}
}
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
I2C_GenerateSTART(I2C2, ENABLE);//生成起始条件
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//监测EV5事件:EV5事件是主机模式已选择事件
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);//发送一个地址,并把最低位设置成写模式
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//监测EV6事件:EV6事件是发送模式已选择事件
I2C_SendData(I2C2, RegAddress);//发送寄存器地址
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);//EV8事件:往IIC的数据寄存器DR写入要发送的数据
I2C_SendData(I2C2, Data);//发送字节数据
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);//监测EV8_2事件:EV8_2事件是数据发送完毕
I2C_GenerateSTOP(I2C2, ENABLE);//终止时序
}
/*读取指定地址寄存器的数据*/
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
I2C_GenerateSTART(I2C2, ENABLE);//生成起始条件
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//监测EV5事件:EV5事件是主机模式已选择事件
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);//发送一个地址,并把最低位设置成写模式
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//监测EV6事件:EV6事件是发送模式已选择事件
I2C_SendData(I2C2, RegAddress);//发送寄存器地址
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);//监测EV8_2事件:EV8_2事件是数据发送完毕
I2C_GenerateSTART(I2C2, ENABLE);//接着产生起始时序
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);//发送一个地址,并把最低位设置成读模式
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//监测EV6事件:这个EV6事件是接收模式已选择事件
I2C_AcknowledgeConfig(I2C2, DISABLE);//应答位非应答:在接受最后一位数据之前就要提前设置ACK位和STOP位
I2C_GenerateSTOP(I2C2, ENABLE);//申请产生终止条件
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);//监测EV7事件:EV7事件产生后一个字节的数据就已经存到DR寄存器里了,此刻使用DR读取函数就能把这个数据读取出来了
Data = I2C_ReceiveData(I2C2);//接收的数据
I2C_AcknowledgeConfig(I2C2, ENABLE);//ACK位置1,为了方便指定地址
return Data;
}
void MPU6050_Init(void)
{
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_Mode = I2C_Mode_I2C;//模式选择I2C模式
I2C_InitStructure.I2C_ClockSpeed = 50000;//标准速度(高达100 kHz),快速(高达400 kHz)
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;//标准速度下默认占空比是1:1
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;//默认应答位为0
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//这个是指定STM32作为从机,可以响应几位的地址
I2C_InitStructure.I2C_OwnAddress1 = 0x00;//用于指定从机STM32的自身地址,方便主机呼叫它
I2C_Init(I2C2, &I2C_InitStructure);
I2C_Cmd(I2C2, ENABLE);
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);
MPU6050_WriteReg(MPU6050_CONFIG, 0x06);
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}
/*通过指针把主函数变量的地址传递到子函数内,从而把子函数想要的值传递进来,调用该函数就把子函数的值输出返回*/
void MPU6050_GetData(int16_t *Accx,int16_t *Accy,int16_t *Accz,int16_t *Gyrox,int16_t *Gyroy,int16_t *Gyroz)
{
uint8_t DataH,DataL;
DataH = MPU6050_Read(MPU6050_ACCEL_XOUT_H);//读取加速度寄存器X轴高八位的值
DataL = MPU6050_Read(MPU6050_ACCEL_XOUT_L);//读取加速度寄存器X轴低八位的值
*Accx = (DataH<<8) | DataL; //将读取加速度寄存器X轴16位的值赋值给输出参数*Accx
DataH = MPU6050_Read(MPU6050_ACCEL_YOUT_H);
DataL = MPU6050_Read(MPU6050_ACCEL_YOUT_L);
*Accy = (DataH<<8) | DataL;
DataH = MPU6050_Read(MPU6050_ACCEL_ZOUT_H);
DataL = MPU6050_Read(MPU6050_ACCEL_ZOUT_L);
*Accz = (DataH<<8) | DataL;
DataH = MPU6050_Read(MPU6050_GYRO_XOUT_H);//读取陀螺仪寄存器X轴高八位的值
DataL = MPU6050_Read(MPU6050_GYRO_XOUT_L);//读取陀螺仪寄存器X轴低八位的值
*Gyrox = (DataH<<8) | DataL; //将读取陀螺仪寄存器X轴16位的值赋值给输出参数*Gyrox
DataH = MPU6050_Read(MPU6050_GYRO_YOUT_H);
DataL = MPU6050_Read(MPU6050_GYRO_YOUT_L);
*Gyroy = (DataH<<8) | DataL;
DataH = MPU6050_Read(MPU6050_GYRO_ZOUT_H);
DataL = MPU6050_Read(MPU6050_GYRO_ZOUT_L);
*Gyroz = (DataH<<8) | DataL;
}