赞
踩
目录
8.[扩展]为什么STM32硬件I2C为什么在速度快时会调节占空比;为什么有最大速度限制
I2C通信介绍部分可以看这一篇博客:STM32软件I2C通信详解-CSDN博客
STM32软件读取MPU6050可以看这一篇博客:STM32通过I2C硬件读写MPU6050-CSDN博客
硬件I2C具有高速传输、低占用率和稳定性高的优点,适用于对传输速度和稳定性要求较高的场景; 而软件I2C具有灵活性高和可移植性强的特点,适用于没有硬件I2C支持或需要扩展硬件I2C功能的场景
起始 STM32默认为从模式,所以需要下在START寄存器下写1就产生起始条件,由硬件自动清除。之后STM32由从模式转为主模式
发生EV5事件 可以把他当做大标志位。因为有的状态会产生多个标志位。所以EVx事件就是组合了很多标志位的一个大标志位 EV5事件,就是SB标志为1 。SB是状态寄存器的一个位。表示了硬件的状态(在状态寄存器SR1中可以找到这一位,置1代表起始条件已发送。由硬件自动清除)
发送一个字节 需要写入DR寄存器。硬件电路会自动把DR寄存器的值转入移位寄存器。再把这一个字节发送到I2C总线上。
应答 电路会自动判断。如果没有应答,则会置应答失败标志位。这个标志位可以申请中断。在寻址完成之后
会发生EV6事件,也就是ADDR为1。手册中查看到的意思是: 在主模式下,代表地址发送结束
发生EV8_1事件 此时 TXE = 1,移位寄存器为空、数据寄存器为空 这时需要我们写入数据寄存器DR进行数据发送了。
发生EV8事件 DR在填写数据之后,会立刻把数据转移到移位寄存器进行发送。 (也就是填写了数据1) 这时就是EV8事件:移位寄存器非空、数据寄存器空。
此时再写入DR 把数据放到数据寄存器中。同时因写入DR,导致EV8事件清除。 (也就是说已经写入了下一个数据:数据2)(在没有应答之前写入)
接收应答位 当从机接收到应答位之后,数据2就转入移位寄存器进行发送。
发送后又产生了EV8事件 移位寄存器非空、数据寄存器空。
此时再写入DR……等待应答….转入移位寄存器发送…产生EV8事件….写入…. (在没有应答之前写入)
直到数据发完,触发EV8_2 当移位寄存器空、数据寄存器也空时。就会触发EV8_2 EV8_2 是 EXT = 1 (数据寄存器为空 )、 BTF = 1(字节发送结束标志位 )(也就是移位寄存器移位完成后找数据寄存器要下一个数据时发现数据寄存器没数据时的标志位)
停止 (控制寄存器CR1中) STOP、这一位写1的时候,就会在当前字节或在起始条件产生停止条件
7位地址主机接收流程:
10位地址主机接收流程
7位详解:
其实同上,这里是在发送一个字节之后,在对面没有应答前,就填充好了下一次要发送的数据。产生的标志位也是一样的。
void I2C_DeInit(I2C_TypeDef* I2Cx);
- 恢复缺省配置
void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);
- 初始化I2C
void I2C_StructInit(I2C_InitTypeDef* I2C_InitStruct);
- 初始化结构体
void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
- 使能I2C
void I2C_DMACmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
- I2C MDA使能
void I2C_DMALastTransferCmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);
- 调用,生成起始条件
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);
- 调用,生成终止条件
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);
- 配置 I2C 的应答机制。1为应答
void I2C_OwnAddress2Config(I2C_TypeDef* I2Cx, uint8_t Address);
void I2C_DualAddressCmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_GeneralCallCmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_ITConfig(I2C_TypeDef* I2Cx, uint16_t I2C_IT, FunctionalState NewState);
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);
- 发送一个字节的数据。
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);
- 接收一个字节的数据。
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);
- 发送 7 位地址并指定通信方向(读或写)。
uint16_t I2C_ReadRegister(I2C_TypeDef* I2Cx, uint8_t I2C_Register);
void I2C_SoftwareResetCmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_NACKPositionConfig(I2C_TypeDef* I2Cx, uint16_t I2C_NACKPosition);
void I2C_SMBusAlertConfig(I2C_TypeDef* I2Cx, uint16_t I2C_SMBusAlert);
void I2C_TransmitPEC(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_PECPositionConfig(I2C_TypeDef* I2Cx, uint16_t I2C_PECPosition);
void I2C_CalculatePEC(I2C_TypeDef* I2Cx, FunctionalState NewState);
uint8_t I2C_GetPEC(I2C_TypeDef* I2Cx);
void I2C_ARPCmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_StretchClockCmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_FastModeDutyCycleConfig(I2C_TypeDef* I2Cx, uint16_t I2C_DutyCycle);
FlagStatus I2C_GetFlagStatus(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);
void I2C_ClearFlag(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);
ITStatus I2C_GetITStatus(I2C_TypeDef* I2Cx, uint32_t I2C_IT);
void I2C_ClearITPendingBit(I2C_TypeDef* I2Cx, uint32_t I2C_IT);
I2C的标志位,往往是由多个标志位组合起来的一个大标志位。
一个个去判断往往比较麻烦。
所以I2C的库在.h头文件的最后,
为我们提供了两种检测方案
I2C_CheckEvent()
函数 可以同时检测多个标志位来进行比较。 一般使用这个。I2C_GetLastEvent()
函数 (实际上是把SR1和SR2两个状态寄存器拼接成一个16位的数据扔给你,随便你处理。所以一般不用)I2C_GetFlagStatus()
函数 可以判断某一个标志位是否置一发送字节时
接收字节时,
- #include "stm32f10x.h" // Device header
- #include "MPU6050.h"
- #include "MPU6050_Reg.h"
-
- #define MPU6050ADDRESS 0xD0
-
- /**
- * 函 数:超时退出、检测标志位
- * 参 数:I2C_TypeDef* I2Cx, 定时器x
- uint32_t I2C_EVENT 标志位名称
- * 返 回 值:无
- * 注意事项:无
- */
- 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;
- }
- }
- }
-
- /**
- * 函 数:指定地址写一个字节
- * 参 数:RegAddress 指定的寄存器地址
- Data 指定写入的数据
- * 返 回 值:无
- */
- void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
- {
-
- I2C_GenerateSTART(I2C2,ENABLE);//非阻塞。所以要等待事件发送完成。
- MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//等待EV5事件发生
-
- I2C_Send7bitAddress(I2C2,MPU6050ADDRESS,I2C_Direction_Transmitter);//发送地址和读写操作(也能用发送一个字节来完成)(自动应答)
- MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//等待发送EV6事件发生
-
- I2C_SendData(I2C2,RegAddress);
-
- MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);//等待发送EV8事件发生(字节正在发送)
-
- I2C_SendData(I2C2,Data);//直接写入下一个要发的数据
- MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED );//等待EV8_2事件发生(发送完成,并且数据寄存器无)
-
- I2C_GenerateSTOP(I2C2,ENABLE);
- }
-
- /**
- * 函 数:指定地址读一个字节
- * 参 数:RegAddress 指定要读的寄存器地址
- * 返 回 值:无
- */
- uint8_t MPU6050_ReadeReg(uint8_t RegAddress)
- {
- uint8_t Data = 0x00;
-
- I2C_GenerateSTART(I2C2,ENABLE);//起始位
- MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//等待EV5事件发生
-
- I2C_Send7bitAddress(I2C2,MPU6050ADDRESS,I2C_Direction_Transmitter);//发送地址和读写操作(也能用发送一个字节来完成)(自动应答)
- MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//等待发送EV6事件发生
-
- I2C_SendData(I2C2,RegAddress);
- MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//等待发送EVEV8_2事件发生(字节发送完毕)
-
- I2C_GenerateSTART(I2C2,ENABLE);//重复起始
- MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//等待EV5事件发生
-
- I2C_Send7bitAddress(I2C2,MPU6050ADDRESS,I2C_Direction_Receiver);//发送地址和读写操作(也能用发送一个字节来完成)(自动应答)
- MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//等待发送EV6事件发生
-
- I2C_AcknowledgeConfig(I2C2,DISABLE);//在字节来之前,设置非应答。
- I2C_GenerateSTOP(I2C2,ENABLE);//直接停止。但是会接收字节完毕之后才停
-
- MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);//等待EV7事件到达。代表一个数据的字节已经在DR里了
- Data = I2C_ReceiveData(I2C2);
-
- I2C_AcknowledgeConfig(I2C2,ENABLE);//恢复默认状态,给从机应答
-
- return Data;//返回读取到的值
- }
-
- /**
- * 函 数:初始化MPU6050
- * 参 数:无
- * 返 回 值:无
- */
- void MPU6050_Init(void)
- {
- /*初始化I2C*/
- // MyI2C_Init();
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIO和I2C外设时钟
- 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); //初始化PB10、11为复用开漏
- /*初始化I2C外设*/
- I2C_InitTypeDef I2C_InitStructure;
- I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;//确定是否应答
- I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//指定STM32作为从机。可以响应几位的地址
- I2C_InitStructure.I2C_ClockSpeed = 50000;//最大400KHZ(快速)、标准为(100KHZ)(MPU6050最快也是400KHZ)
- I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;//配置占空比。进入快速模式后才有用(>100KHZ后) (默认为1:1)
- I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;//I2C模式
- I2C_InitStructure.I2C_OwnAddress1 = 0x00;//指定STM32作为从机。STM本身的地址
- I2C_Init(I2C2,&I2C_InitStructure);
- /*使能I2C外设*/
- I2C_Cmd(I2C2,ENABLE);
- /*初始化MPU6050*/
- MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01); //电源管理1:不复位、解除睡眠、不循环、温度传感器不失能、选择X轴陀螺仪时钟
- MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00); //电源管理2:不需要循环模式唤醒频率、每个轴都不需要待机
-
- MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09); //采样率分频:数据输出的快慢,越小输出越快.这里给10分频
- MPU6050_WriteReg(MPU6050_CONFIG,0x06); //配置寄存器:外部同步不需要、数字低通滤波器设置为110
- MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18); //陀螺仪配置寄存器:不自测、满量程选择:11最大量程
- MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18); //加速度计配置寄存器:不自测、满量程选择:11最大量程、不用高通滤波器
- }
-
- /**
- * 函 数:得到六轴传感器中的数据
- * 参 数:Str MPU6050_Data的地址
- * 返 回 值:无
- */
- SensorData MPU6050_Data;
- void MPU6050_GetData(SensorData *Str)//存放这种结构体类型的地址
- {
- Str->AccX = ( MPU6050_ReadeReg(MPU6050_ACCEL_XOUT_H) <<8|
- MPU6050_ReadeReg(MPU6050_ACCEL_XOUT_L));
- Str->AccY = ( MPU6050_ReadeReg(MPU6050_ACCEL_YOUT_H) <<8|
- MPU6050_ReadeReg(MPU6050_ACCEL_YOUT_L));
- Str->AccZ = ( MPU6050_ReadeReg(MPU6050_ACCEL_ZOUT_H) <<8|
- MPU6050_ReadeReg(MPU6050_ACCEL_ZOUT_L));
- Str->Temp = ( MPU6050_ReadeReg(MPU6050_TEMP_OUT_H) <<8|
- MPU6050_ReadeReg(MPU6050_TEMP_OUT_L));
- Str->GyroX = ( MPU6050_ReadeReg(MPU6050_GYRO_XOUT_H) <<8 |
- MPU6050_ReadeReg(MPU6050_GYRO_XOUT_L));
- Str->GyroY = ( MPU6050_ReadeReg(MPU6050_GYRO_YOUT_H) <<8 |
- MPU6050_ReadeReg(MPU6050_GYRO_YOUT_L));
- Str->GyroZ = ( MPU6050_ReadeReg(MPU6050_GYRO_ZOUT_H) <<8 |
- MPU6050_ReadeReg(MPU6050_GYRO_ZOUT_L));
- }
- //MPU6050_ACCEL_XOUT_H 0x3B
- //MPU6050_ACCEL_XOUT_L 0x3C
- //MPU6050_ACCEL_YOUT_H 0x3D
- //MPU6050_ACCEL_YOUT_L 0x3E
- //MPU6050_ACCEL_ZOUT_H 0x3F
- //MPU6050_ACCEL_ZOUT_L 0x40
- //MPU6050_TEMP_OUT_H 0x41
- //MPU6050_TEMP_OUT_L 0x42
- //MPU6050_GYRO_XOUT_H 0x43
- //MPU6050_GYRO_XOUT_L 0x44
- //MPU6050_GYRO_YOUT_H 0x45
- //MPU6050_GYRO_YOUT_L 0x46
- //MPU6050_GYRO_ZOUT_H 0x47
- //MPU6050_GYRO_ZOUT_L 0x48
-
- #ifndef __MPU6050_H
- #define __MPU6050_H
-
- /**
- * 函 数:初始化MPU6050
- * 参 数:无
- * 返 回 值:无
- */
- void MPU6050_Init(void);
-
- /**
- * 函 数:指定地址写一个字节
- * 参 数:RegAddress 指定的寄存器地址
- Data 指定写入的数据
- * 返 回 值:无
- */
- void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
- /**
- * 函 数:指定地址读一个字节
- * 参 数:RegAddress 指定要读的寄存器地址
- * 返 回 值:无
- */
- uint8_t MPU6050_ReadeReg(uint8_t RegAddress);
-
- //传感器数据
- typedef struct Data
- {
- int16_t AccX;
- int16_t AccY;
- int16_t AccZ;
- int16_t Temp;
- int16_t GyroX;
- int16_t GyroY;
- int16_t GyroZ;
- }SensorData;
-
- extern SensorData MPU6050_Data;
-
- /**
- * 函 数:得到六轴传感器中的数据
- * 参 数:Str MPU6050_Data的地址
- * 返 回 值:无
- */
- void MPU6050_GetData(SensorData *Str);
-
- #endif
-
- #include "stm32f10x.h" // Device header
- #include "oled.h"
- #include "Delay.h"
- #include "key.h"
- #include "MPU6050.h"
- /*
- 硬件读写I2C
- */
-
- int main()
- {
- OLED_Init();//初始化OLED;
- MPU6050_Init();//初始化MPU6050
-
-
- while(1)
- {
- MPU6050_GetData(&MPU6050_Data);
- //显示加速度
- OLED_ShowSignedNum(1,1,(int16_t)MPU6050_Data.AccX,5);
- OLED_ShowSignedNum(2,1,(int16_t)MPU6050_Data.AccY,5);
- OLED_ShowSignedNum(3,1,(int16_t)MPU6050_Data.AccZ,5);
- //显示陀螺仪
- OLED_ShowSignedNum(1,8,MPU6050_Data.GyroX,5);
- OLED_ShowSignedNum(2,8,MPU6050_Data.GyroY,5);
- OLED_ShowSignedNum(3,8,MPU6050_Data.GyroZ,5);
- //显示温度
- OLED_ShowSignedNum(4,4,MPU6050_Data.Temp,5);
-
-
- }
-
- }
-
由之前的知识我们可以了解到。
对于I2C而言,在硬件电路上采用了开漏输出+上拉电阻的形式
这就导致了下拉时为强下拉,上拉时为弱上拉。
这就导致了信号在由高电平往低电平时是十分迅速的,而从低电平回到高电平时是需要一定时间的。
在波形上的体现就是:通信速率越快,上升沿呈现出圆弧形越强。而下降沿几乎没有太大影响。
所以,在速度最快时,需要使得低电平的时间更长一些。
在图片中的反馈就是这样子的
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。