湖北省网站备案,东莞网站建设的方案,湛江建设工程交易中心网站,茶叶网站的建设策划书相比于之前学的异步全双工且需要两条通信线的串口通信#xff0c;I2C则为同步半双工#xff0c;仅需要一条通信线#xff0c;全双工与半双工区别如下#xff1a;
全双工#xff08;Full Duplex#xff09;半双工#xff08;Half Duplex#xff09;数据传输方式同时双向…相比于之前学的异步全双工且需要两条通信线的串口通信I2C则为同步半双工仅需要一条通信线全双工与半双工区别如下
全双工Full Duplex半双工Half Duplex数据传输方式同时双向传输交替单向传输通道数量两条独立通道一条发送一条接收一条通道或交替使用同一条通道效率高同时发送和接收较低不能同时发送和接收延迟低无需等待对方发送完毕较高需要等待对方发送完毕成本较高需要更多硬件支持较低硬件需求较少系统复杂度较高设计和维护较复杂较低设计和维护较简单应用场景电话通信、视频通话、实时数据传输等对讲机通信、低功耗无线传感器网络等
I2C通信一共需要四根线VCCGNDSDA数据线SCL时钟线因为有时钟线I2C通信为同步时序因此对时间的要求不是那么严格通常可以很好的用软件进行模拟
需要用I2C通信的模块一般有OLED显示屏MPU6050陀螺仪AT24C02存储器
在串口通信中通常为两个设备通过串口点对点的通信而在I2C通信中有一条I2C总线多个设备挂载到总线上同时进行通信所以I2C又分为一主多从固定多主多从可变多主多从
一主多从通信时只有一个主机而从机可以有多个主机控制整个通信过程包括初始化通信开始通信发送数据接收数据以及终止通信等等在该过程中主机就像一个教室里的班主任而从机就像学生学生的一切动作都需要听从班主任安排
固定多主多从通信时有多个主机且主机数量以固定其他从机不可变为主机多个从机通信时需要多个主机只有一个起作用中的一个跳出来充当主机若多个主机同时想充当主机时则需要进行总线仲裁获胜的设备可以充当主机在该过程中多个主机就相当于多位老师一起在讲台上正所谓一山不容二虎老师们需要比较之后才能得到上台讲话的机会
可变多主多从通信时有多个主机所有设备都可以充当主机在通信时需要一个设备跳出来充当主机在该过程中所有设备相当于学生开始全坐在需要发言时举手然后站起来说话
当然我们平常在使用stm32时只需要掌握一主多从模式就行了多主多从模式了解就行
下面是一张一主多从的硬件电路图 在硬件接线上所有设备SCL都要连一起SDA同理
为了避免两个设备同时输出且一个输出高电平一个输出低电平导致的电源短路SDA和SCL均要配置成开漏输出模式SDA和SCL个添加一个上拉电阻一般为4.7KΩ此时为弱上拉
SCL线从始至终都只能由主机控制而SDA在主机接收数据和接收应答的时候可由从机暂时控制
I2C时序基本单元SDA与SCL默认上拉为高电平
起始条件SCL高电平期间SDA下降沿
终止条件SCL高电平期间SDA上升沿 发送一个字节SCL低电平期间主机将数据位依次放到SDA线上高位先行然后释放SCL从机将在SCL高电平期间读取数据位所以SCL高电平期间SDA不允许有数据变化循环上述过程8次即可发送一个字节 接收一个字节SCL低电平期间从机将数据位依次放到SDA线上高位先行然后释放SCL主机将在SCL高电平期间读取数据位循环上述过程8次即可接收一个字节主机在接收之前需要释放SDA从机对SDA进行控制发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答 接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA) 从机地址用于主机确定从机设备的名字每个从机都具有唯一的地址从机地址具有7位和10位通常用7位地址就够了7位地址比较简单1且应用范围最广例MPU6050的地址为1101000地址中分为可变部分和不可变部分一般为最后几位MPU6050的可变地址为最后一位可以由AD0引脚确定引脚接低电平则它的地址为1101000反之则为1101001可变部分用于两个相同的模块在一条总线上时区分
I2C时序
指定地址写对于指定设备Slave Address在指定地址Reg Address下写入指定数据Data
时序逻辑主机首先发送一个起始条件然后发送一个字节8位7位地址加一位读写位0为写入1为读出之后从机给应答位0为应答1为非应答主机再发送一个字节此时为要操作的寄存器的地址从机发送应答位之后主机再向从机发送要写入的寄存器的数据了从机产生非应答最后主机产生停止条件
当前地址读对于指定设备Slave Address在当前地址指针指示的地址下读取从机数据
时序逻辑主机首先发送一个起始条件然后发送一个字节地址加读写位之后从机给应答位主机接收一个字节主机产生非应答最后主机产生停止条件通过与指定地址写对比当前地址读缺少了主机发送操作的寄存器的地址那么主机读取的数据来自从机的哪个寄存器在从机中所有寄存器被分配到了一个线性区域中一个单独的指针指向寄存器这个指针上电默认指向0地址每写入一个字节或读出一个字节后该指针自动自增一次由此可知那么主机读取的数据就是当前指针指示的寄存器并且指针自增加一
例若先调用一次指定地址写操作的寄存器地址为0x19那当前指针的值就变为0x1A了再调用一次当前地址读此时读取的数据就是0x1A寄存器中的数据了并且由于指针自加一下次再调用当前地址读就是读取0x1B的数据了
指定地址读由于当前地址读的应用范围有限所以有了另一个时序指定地址读对于指定设备Slave Address在指定地址Reg Address下读取从机数据
指定地址读为指定地址写的前半部分刚指定地址还没开始写数据的时候重新发送一个起始条件然后执行当前地址读操作
时序逻辑主机首先发送一个起始条件然后发送一个字节地址加读写位之后从机给应答位主机再发送一个字节此时为要读取的寄存器的地址从机发送应答位之后重新发送起始条件因为改变读写操作只能在起始条件的下一个字节的最后一位然后发送一个字节地址加读写位之后主机接收一个字节主机产生非应答最后主机产生停止条件 软件I2C工程代码
I2C初始化
void MyI2C_Init(void)
{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);GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
}
写SCLSDA读SDA
void MyI2C_W_SCL(uint8_t BitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);Delay_us(10);
}void MyI2C_W_SDA(uint8_t BitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);Delay_us(10);
}uint8_t MyI2C_R_SDA(void)
{uint8_t BitValue;BitValue GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);Delay_us(10);return BitValue;
}
BitAction 为枚举类型之中的值非0即1
起始条件
void MyI2C_Start(void)
{MyI2C_W_SDA(1);MyI2C_W_SCL(1);MyI2C_W_SDA(0);MyI2C_W_SCL(0);
}
停止条件
void MyI2C_Stop(void)
{MyI2C_W_SDA(0);MyI2C_W_SCL(1);MyI2C_W_SDA(1);
}
发送一个字节
void MyI2C_SendByte(uint8_t Byte)
{uint8_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(void)
{uint8_t i, Byte 0x00;MyI2C_W_SDA(1);for (i 0; i 8; i ){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_SDA(AckBit);MyI2C_W_SCL(1);MyI2C_W_SCL(0);
}
接收应答位
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;
}
MPU6050利用软件I2C通信MPU6050从机地址为0xD0所以将地址宏定义为MPU6050_ADDRESS
MPU6050指定地址写数据
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{MyI2C_Start();MyI2C_SendByte(MPU6050_ADDRESS);MyI2C_ReceiveAck();MyI2C_SendByte(RegAddress);MyI2C_ReceiveAck();MyI2C_SendByte(Data);MyI2C_ReceiveAck();MyI2C_Stop();
}
MPU6050指定地址读数据
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{uint8_t Data;MyI2C_Start();MyI2C_SendByte(MPU6050_ADDRESS);MyI2C_ReceiveAck();MyI2C_SendByte(RegAddress);MyI2C_ReceiveAck();MyI2C_Start();MyI2C_SendByte(MPU6050_ADDRESS | 0x01);MyI2C_ReceiveAck();Data MyI2C_ReceiveByte();MyI2C_SendAck(1);MyI2C_Stop();return Data;
}
MPU6050寄存器宏定义
#define MPU6050_SMPLRT_DIV 0x19
#define MPU6050_CONFIG 0x1A
#define MPU6050_GYRO_CONFIG 0x1B
#define MPU6050_ACCEL_CONFIG 0x1C#define MPU6050_ACCEL_XOUT_H 0x3B
#define MPU6050_ACCEL_XOUT_L 0x3C
#define MPU6050_ACCEL_YOUT_H 0x3D
#define MPU6050_ACCEL_YOUT_L 0x3E
#define MPU6050_ACCEL_ZOUT_H 0x3F
#define MPU6050_ACCEL_ZOUT_L 0x40
#define MPU6050_TEMP_OUT_H 0x41
#define MPU6050_TEMP_OUT_L 0x42
#define MPU6050_GYRO_XOUT_H 0x43
#define MPU6050_GYRO_XOUT_L 0x44
#define MPU6050_GYRO_YOUT_H 0x45
#define MPU6050_GYRO_YOUT_L 0x46
#define MPU6050_GYRO_ZOUT_H 0x47
#define MPU6050_GYRO_ZOUT_L 0x48#define MPU6050_PWR_MGMT_1 0x6B
#define MPU6050_PWR_MGMT_2 0x6C
#define MPU6050_WHO_AM_I 0x75
MPU6050初始化
void MPU6050_Init(void)
{MyI2C_Init();//软件I2C引脚初始化MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);//配置电源管理器1解除睡眠模式MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);//配置电源管理器2MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);//配置采样率分频--十分频值越小越快MPU6050_WriteReg(MPU6050_CONFIG, 0x06);//配置外部同步和数字低通滤波寄存器MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);//配置陀螺仪寄存器MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);//配置量程寄存器
}
MPU6050接收数据在主函数里传六个变量的地址达到返回六个值的目的还可以将这六个变量打包成一个结构体在主函数里直接传结构体的地址即可
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_ReadReg(MPU6050_ACCEL_XOUT_H);DataL MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);*AccX (DataH 8) | DataL;DataH MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);DataL MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);*AccY (DataH 8) | DataL;DataH MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);DataL MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);*AccZ (DataH 8) | DataL;DataH MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);DataL MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);*GyroX (DataH 8) | DataL;DataH MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);DataL MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);*GyroY (DataH 8) | DataL;DataH MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);DataL MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);*GyroZ (DataH 8) | DataL;
} 在主函数里接收数据
int16_t AX, AY, AZ, GX, GY, GZ;
MPU6050_GetData(AX, AY, AZ, GX, GY, GZ);
MPU6050读取ID号
uint8_t MPU6050_GetID(void)
{return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
硬件I2C
硬件I2C简介 I2C基本结构数据的收发由数据寄存器和移位寄存器来控制并且在移位寄存器中高位先行 硬件I2C工程代码由于硬件I2C有自己的库函数所以不需要用户自己再建立I2C模块了只需初始化对应GPIO口复用开漏输出
在软件模拟I2C中每个时序都是加了延时的阻塞型而在硬件I2C中每完成一步产生相应的标志位可以通过判断标志位的状态来控制时序的进行而有的状态需要多个标志位所以在硬件I2C中将标志位融合成了事件即通过判断事件是否发生即可知道时序的进行状态在判断事件的产生时有许多的while死循环一旦某个环节出错了之后程序就会卡死所以检查事件的产生还要加上一个超时判断
MPU6050事件是否产生
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;}}
}
MPU6050指定地址写数据 void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{I2C_GenerateSTART(I2C2, ENABLE);MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);I2C_SendData(I2C2, RegAddress);MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);I2C_SendData(I2C2, Data);MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);I2C_GenerateSTOP(I2C2, ENABLE);
}
MPU6050指定地址读数据 uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{uint8_t Data;I2C_GenerateSTART(I2C2, ENABLE);MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);I2C_SendData(I2C2, RegAddress);MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);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);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;
}
MPU6050初始化
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_InitStructure.I2C_ClockSpeed 50000;//配置I2C时钟频率I2C_InitStructure.I2C_DutyCycle I2C_DutyCycle_2;//配置I2C时钟占空比I2C_InitStructure.I2C_Ack I2C_Ack_Enable;//配置I2C应答位使能I2C_InitStructure.I2C_AcknowledgedAddress I2C_AcknowledgedAddress_7bit;//从机地址7位还是10位I2C_InitStructure.I2C_OwnAddress1 0x00;//从机地址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);
}