STM32通过I2C硬件读写MPU6050

news2024/11/16 1:51:24

目录

STM32通过I2C硬件读写MPU6050

1. STM32的I2C外设简介

2. STM32的I2C基本框图

3. STIM32硬件I2C主机发送流程

10位地址与7位地址的区别

7位主机发送的时序流程

7位主机接收的时序流程

4. STM32硬件与软件的波形对比

5. STM32配置硬件I2C外设流程

6. STM32的I2C.h标准库函数介绍

1.I2C配置和使用函数

2.I2C状态监控功能

7. STM32编写代码实现I2C硬件读写MPU6050

7.1编写时需要注意的点

7.2程序文件简要说明:

MPU650.c

MPU650.h

main.c

8.[扩展]为什么STM32硬件I2C为什么在速度快时会调节占空比;为什么有最大速度限制


I2C通信介绍部分可以看这一篇博客:STM32软件I2C通信详解-CSDN博客

STM32软件读取MPU6050可以看这一篇博客:STM32通过I2C硬件读写MPU6050-CSDN博客

STM32通过I2C硬件读写MPU6050

1. STM32的I2C外设简介

硬件I2C具有高速传输、低占用率和稳定性高的优点,适用于对传输速度和稳定性要求较高的场景; 而软件I2C具有灵活性高和可移植性强的特点,适用于没有硬件I2C支持或需要扩展硬件I2C功能的场景

  • STM32内部集成了硬件12C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担
  • 支持多主机模型
  • 支持7位/10位地址模式
  • 支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)
  • 支持DMA
  • 兼容SMBus协议(System Management Bus 系统管理总线) 基于I2C改进而来的,主要用于电源管理系统
  • STM32F103C8T6 硬件I2C资源:12C1、12C2

2. STM32的I2C基本框图

3. STIM32硬件I2C主机发送流程

10位地址与7位地址的区别

  • 7位地址
    • 起始条件后的第一个字节是寻址(地址+读写位)
  • 10位地址
    • 起始条件后的两个字节都是寻址(11110+2位地址+读写位+8位地址)

7位主机发送的时序流程

  • 起始、从机地址+读写位、应答、数据1、应答、数据2、应答、….数据N、应答、停止
  1. 起始 STM32默认为从模式,所以需要下在START寄存器下写1就产生起始条件,由硬件自动清除。之后STM32由从模式转为主模式

  2. 发生EV5事件 可以把他当做大标志位。因为有的状态会产生多个标志位。所以EVx事件就是组合了很多标志位的一个大标志位 EV5事件,就是SB标志为1 。SB是状态寄存器的一个位。表示了硬件的状态(在状态寄存器SR1中可以找到这一位,置1代表起始条件已发送。由硬件自动清除)

  3. 发送一个字节 需要写入DR寄存器。硬件电路会自动把DR寄存器的值转入移位寄存器。再把这一个字节发送到I2C总线上。

  4. 应答 电路会自动判断。如果没有应答,则会置应答失败标志位。这个标志位可以申请中断。在寻址完成之后

  5. 会发生EV6事件,也就是ADDR为1。手册中查看到的意思是: 在主模式下,代表地址发送结束

  6. 发生EV8_1事件 此时 TXE = 1,移位寄存器为空、数据寄存器为空 这时需要我们写入数据寄存器DR进行数据发送了。

  7. 发生EV8事件 DR在填写数据之后,会立刻把数据转移到移位寄存器进行发送。 (也就是填写了数据1) 这时就是EV8事件:移位寄存器非空、数据寄存器空。

  8. 此时再写入DR 把数据放到数据寄存器中。同时因写入DR,导致EV8事件清除。 (也就是说已经写入了下一个数据:数据2)(在没有应答之前写入)

  9. 接收应答位 当从机接收到应答位之后,数据2就转入移位寄存器进行发送。

  10. 发送后又产生了EV8事件 移位寄存器非空、数据寄存器空。

  11. 此时再写入DR……等待应答….转入移位寄存器发送…产生EV8事件….写入…. (在没有应答之前写入)

  12. 直到数据发完,触发EV8_2 当移位寄存器空、数据寄存器也空时。就会触发EV8_2 EV8_2 是 EXT = 1 (数据寄存器为空 )、 BTF = 1(字节发送结束标志位 )(也就是移位寄存器移位完成后找数据寄存器要下一个数据时发现数据寄存器没数据时的标志位)

  13. 停止 (控制寄存器CR1中) STOP、这一位写1的时候,就会在当前字节或在起始条件产生停止条件

7位主机接收的时序流程

7位地址主机接收流程:

  • 起始、从机地址+读、接收、接收数据、发送应答、接收数据、发送应答…接收数据、非应答、停止

10位地址主机接收流程

  • 起始、发送帧头(11110 + 两位地址 + 0(写))、发送第二个字节的8位地址、重复起始条件、发送枕头(11110 + 两位地址 + 1(写))、直接接收数据、发送应答、接收…发送…接收、非应答、停止(没有发送第二次的字节)

7位详解:

  1. 写入控制位的Start位、产生起始条件
  2. 等待EV5事件 表示已发送
  3. 发送地址(地址+读)
  4. 接收应答(ACK,写1 在接受一个字节后就返回一个应答)
  5. 产生EV6事件, 代表寻址已完成
  6. 产生EV6_1时间, 没有对应的标志事件。
  7. 接收数据1 代表数据正在通过移位寄存器输入
  8. 发送应答 硬件自动执行,需要配置是否应答。CR1寄存器中,ACK写1 应答, 否则不应答
  9. 产生EV7事件 因为此时就代表已经接收到数据了。 数据会通过移位寄存器转移到数据寄存器。 产生RxNE标志位,表示数据寄存器非空 (读DR寄存器来清除这个Ev7事件)
  10. 接收数据2 在我们正在读DR寄存器是 数据2就已经移位到移位寄存器中。等待移位到DR数据寄存器中。
  11. 发送应答
  12. 这事数据又被传入DR数据寄存器。EV7事件又来了。需要我们读取、
  13. 如此循环……直到停止 也就是需要设置ACK = 0 不应答, 和STOP请求。

其实同上,这里是在发送一个字节之后,在对面没有应答前,就填充好了下一次要发送的数据。产生的标志位也是一样的。

4. STM32硬件与软件的波形对比

5. STM32配置硬件I2C外设流程

  1. 开启外设和对应GPIO口的时钟
  2. 把I2C外设对应的GPIO口配置为复用开漏模式
  3. 使用结构体,对I2C进行配置
  4. 使能I2C

6. STM32的I2C.h标准库函数介绍

1.I2C配置和使用函数

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);
    • 用于控制 I2C 传输中的最后一次传输的相关操作。

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);
    • 设置 I2C 设备自身的地址。
  • 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);
    • 配置 I2C 的中断功能,根据不同的中断类型进行使能或禁用。

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);
    • 读取 I2C 设备的特定寄存器的值。
  • void I2C_SoftwareResetCmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
    • 通过软件方式对 I2C 进行复位操作。
  • void I2C_NACKPositionConfig(I2C_TypeDef* I2Cx, uint16_t I2C_NACKPosition);
    • 配置非应答信号(NACK)的位置。
  • void I2C_SMBusAlertConfig(I2C_TypeDef* I2Cx, uint16_t I2C_SMBusAlert);
    • 针对 SMBus 警报进行相关配置。
  • void I2C_TransmitPEC(I2C_TypeDef* I2Cx, FunctionalState NewState);
    • 控制是否传输 PEC(Packet Error Checking,数据包错误检查)信息。
  • void I2C_PECPositionConfig(I2C_TypeDef* I2Cx, uint16_t I2C_PECPosition);
    • 配置 PEC 的位置。
  • void I2C_CalculatePEC(I2C_TypeDef* I2Cx, FunctionalState NewState);
    • 决定是否计算 PEC 值。
  • uint8_t I2C_GetPEC(I2C_TypeDef* I2Cx);
    • 获取计算得到的 PEC 值。
  • 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);
    • 清除中断标志位

2.I2C状态监控功能

I2C的标志位,往往是由多个标志位组合起来的一个大标志位。

一个个去判断往往比较麻烦。

所以I2C的库在.h头文件的最后,

为我们提供了两种检测方案

  1. 基础状态监测
    • 使用 I2C_CheckEvent()函数 可以同时检测多个标志位来进行比较。 一般使用这个。

  1. 高级状态监测
    • 使用I2C_GetLastEvent()函数 (实际上是把SR1和SR2两个状态寄存器拼接成一个16位的数据扔给你,随便你处理。所以一般不用)

  1. 基于标志位的状态监控
    • 使用I2C_GetFlagStatus()函数 可以判断某一个标志位是否置一

7. STM32编写代码实现I2C硬件读写MPU6050

7.1编写时需要注意的点

  • 发送字节时

    • 对于发送一个字节,发送地址和读写的一个字节之后,在移位寄存器还未发送完成时,就要填充下一次要发送的字节到DR数据寄存器中。否则将发生EV8_2标志(移位寄存器空,数据寄存器空)。导致读写停止。 正常应为EV8事件发生(字节正在发送,移位寄存器非空,数据寄存器空)
    • 对于发送多个字节,只需要重复发送、等待EV8事件即可。不发送时给予不应答、停止即可。
  • 接收字节时,

    • 对于接收一个字节,在指定完地址,重复起始之后,接收第第一个字节中时,就要提前配置Ack非应答和停止条件。之后等数据全部移位到数据寄存器中,才能保证只接收了一个字节。读取DR寄存器即可。
    • 对于接收多个字节,可以重复应答、然后等待EV7事件到来,读取寄存器。直到不想接收时,在数据进行移位时(还未到达DR寄存器时)提前设置非应答与停止操作。

7.2程序文件简要说明:

  • MPU6050.c:初始化MPU6050的寄存器。使用STM32 自带的I2C外设,编写对指定地址发送指定值、读取指定地址的函数。以及等待标志位且超时退出的函数
  • MPU6050.h:函数声明、数据结构体声明
  • main.c:测试I2C通信结果。

MPU650.c

#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

MPU650.h

#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

main.c

#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);
        
        
    }
    
}

8.[扩展]为什么STM32硬件I2C为什么在速度快时会调节占空比;为什么有最大速度限制

由之前的知识我们可以了解到。

对于I2C而言,在硬件电路上采用了开漏输出+上拉电阻的形式

这就导致了下拉时为强下拉,上拉时为弱上拉。

这就导致了信号在由高电平往低电平时是十分迅速的,而从低电平回到高电平时是需要一定时间的。

在波形上的体现就是:通信速率越快,上升沿呈现出圆弧形越强。而下降沿几乎没有太大影响。

所以,在速度最快时,需要使得低电平的时间更长一些。

在图片中的反馈就是这样子的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2049688.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hadoop如何搭建计算和存储节点分离

在业内存在着一种看起来比较离谱的搭建方式&#xff0c;叫计算节点与存储节点分离&#xff0c;说它比较离谱&#xff0c;是因为hadoop架构本身不直接支持将这两者分开&#xff0c;因为hadoop本身的一大优势就是计算本地化&#xff0c;这种分开搭建的方式抛弃了这种优势&#xf…

Linux 软件编程学习第十五天

1.TCP粘包问题&#xff1a; TCP发送数据是连续的&#xff0c;两次发送的数据可能粘连成一包被接收到 1.解决粘包问题方法&#xff1a; 1.接收指定长度&#xff1a;&#xff08;不稳定&#xff09; 发送5个字节 接收5个字节 2.睡眠&#x…

用户画像实时标签数据处理流程图

背景 在用户画像中&#xff0c;有一类实时标签&#xff0c;我们既要它能够实时的对外提供数据统计&#xff0c;也要保存到大数据组件中用于后续的对数&#xff0c;圈选的逻辑&#xff0c;本文就看一下用户画像的实时标签的数据流转图 实时标签数据流转图 首先我们肯定是要使…

GoMail发送邮件的性能优化策略有哪些方法?

GoMail发送邮件如何配置服务器&#xff1f;GoMail发信功能如何&#xff1f; GoMail是一款广受欢迎的Go语言邮件发送库&#xff0c;具备高效、易用等优点&#xff0c;但在高并发场景下&#xff0c;GoMail发送邮件的性能优化显得尤为重要。AokSend将探讨几种有效的GoMail发送邮件…

图像数据处理14

三、空域滤波 3.3 统计排序滤波器 统计排序滤波器属于非线性空域滤波器&#xff0c;常见的统计排序滤波器有中值滤波器、最大值滤波器、最小值滤波器。 中值滤波器、最大值滤波器和最小值滤波器是三种常见的统计排序滤波器&#xff0c;它们在图像处理和信号处理中发挥着重要…

WUP-MY-LABEL-PRINTER 旻佑热敏打印机标签打印uniapp插件使用说明

插件地址&#xff1a;WUP-MY-LABEL-PRINTER 旻佑热敏打印机标签打印安卓库 简介 本插件主要用于旻佑热敏打印机打印标签&#xff0c;不支持票据打印。适用于旻佑的各型支持标签打印的热敏打印机。本插件开发时使用的打印机型号为MY-805嵌入式面板打印机&#xff0c;其他型号请…

Cisco交换机SSH使用RSA公钥免密登录(IOS与Nexus,服务器以RHEL8为例)

目录 需求实验步骤0. 实验环境1. Linux2. CiscoIOS基础设置保存密钥登陆测试 3. CiscoNexus基础配置保存密钥登陆测试 需求 在实际工作中&#xff0c;常会遇到自动化的需求&#xff0c;那么在自动采集、配置等对网络设备的自动化需求中&#xff0c;不可避免的会遇到需要登录-&…

tensorboard显示一片空白解决方案

OK艾瑞巴蒂 不知道看这个视频几个小土堆过来的&#xff0c;今天已经发了一篇博文探讨快速下载tensorboard了 下面用的时候叒出现问题了 from torch.utils.tensorboard import SummaryWriter writer SummaryWriter("logs")# writer.add_image() # Yx for i in range…

实时手势识别(1)- 基于手部检测+手部分类

目录 前言 1.实现效果 2.非端到端实现的原因 3.分类网络与数据准备 4.训练结果 5.测试结果 6.训练代码 7.训练日志 7.1ResNet18训练日志 7.2ShuffleNet_v2训练日志 前言 利用YOLOv8获取手部区域&#xff0c;然后对手部区域进行分类&#xff0c;实现手势识别。 本文使…

powershell 终端 执行 pnpm -v报错

1.问题描述&#xff1a; 明明全局已安装 pnpm &#xff0c;但在vscode默认终端 powershell 执行 pnpm -v 却报错&#xff1a; 2.问题根因&#xff1a; 原因是 PowerShell 执行策略问题。 3.解决方案&#xff1a; 以管理员身份运行 PowerShell 查看 PowerShell 的执行策略…

初探systemⅡ·慢思考

本篇笔记记录于 May 30th, 2023 oai联合创始人Andrej曾在微软大会上的报告中有提到LLMs对于人类快、慢思考两种认知推理模式的当下探索与未来展望&#xff0c;这里曾经得到的启示是&#xff1a;未来在模型的训练与推理侧是否会出现一种新的长链认知范式&#xff1f;如在RLHF过程…

秋招突击——8/13——并查集——复习{有塔一面}——新作{亲戚关系}

文章目录 引言复习并查集模板复习——有塔一面 新作亲戚关系 总结 引言 这两天准备腾讯的第二面&#xff0c;看了很多人的面经&#xff0c;发现考并查集的题目蛮多的&#xff0c;这里整理学习一下&#xff01; 复习 并查集模板 这里学习了B站的麦克老师的课程&#xff0c;对…

MySQL与SQLserver

与MySQL的差别 SQL Server和MySQL都是广泛使用的关系数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;它们的SQL语法有很多相似之处&#xff0c;但也存在一些差异。以下是一些主要的语法区别&#xff1a; 1. 数据库和表的创建 SQL Server CREATE DATABASE databas…

Ma Spaghet!

目录 一、题目 二、思路 三、payload 3.1 方案一 3.2 方案二(官方) 四、思考与总结 一、题目 <!-- Challenge --> <h2 id"spaghet"></h2> <script>spaghet.innerHTML (new URL(location).searchParams.get(somebody) || "Somebody…

产品经理-​你做产品经理有什么优势?(39)

你做产品经理有什么优势&#xff1f; 这是一个关于自我认知、个人优势的问题 人贵有自知之明&#xff0c;求职者应该对自己的优缺点有一个客观、深入的认识 大公司往往更加看重你的基本素质&#xff08;逻辑分析、学习能力、潜力等&#xff09; 因为大公司有相对成熟的培养体系…

OpenDDS的Rtps_Udp传输协议可靠性QoS收发基本流程

OpenDDS中,实现了Rtps_Udp传输协议(非纯udp)的可靠性传输。传输的线程包括: 1)发送方线程主要线程和定时器 《1》应用线程 《2》网络异步发送线程 《3》Heartbeat定时器 《4》Nak_response定时器 2)接收方主要线程和定时器 《1》网络异步接收线程 《2》heartbeat_respons…

Java | Leetcode Java题解之第344题反转字符串

题目&#xff1a; 题解&#xff1a; class Solution {public void reverseString(char[] s) {int n s.length;for (int left 0, right n - 1; left < right; left, --right) {char tmp s[left];s[left] s[right];s[right] tmp;}} }

【C++】智能指针详解

一、从new和delete谈起 在C中&#xff0c;可以使用new和delete关键字进行对象的创建和销毁&#xff0c;new一个对象实际上是在堆上分配内存&#xff0c;而new出来的对象也要自己用delete释放&#xff0c;从而回收内存&#xff0c;否则会造成内存的泄露。由程序员自己new来分配…

[手机Linux PostmarketOS]五, docker安装和使用

docker容器 一&#xff0c;docker安装和配置 安装 docker 和 docker-compose&#xff1a; sudo apk add docker docker-cli-compose #安装docker sudo service docker start #启动docker服务 sudo rc-update add docker default #设置docker为自启动可选关…

【PostgreSQL003】PostgreSQL数据表空间膨胀,磁盘爆满,应用宕机(经验总结,已更新)

1.一直以来想写下基于PostgreSQL的系列文章&#xff0c;作为较火的数据ETL工具&#xff0c;也是日常项目开发中常用的一款工具&#xff0c;最近刚好挤时间梳理、总结下这块儿的知识体系。 2.熟悉、梳理、总结下PostgreSQL数据库相关知识体系。空间膨胀&#xff08;主键、外键、…