【I2C】硬件读写MPU6050

基于【软件I2C 】驱动 模块 增加【硬件I2C】驱动代码
  • 配置硬件I2C驱动用到的函数
void I2C_Init();//初始化I2C
void I2C_StructInit();//给I2C结构体赋一个默认的初始值
void I2C_Cmd();//使能I2C
I2C_GenerateSTART();//生成起始条件
I2C_GenerateSTOP();//生成终止条件
I2C_AcknowledgeConfig();//收到一个字节后是否给从机应答ACK
I2C_SendData();//发送数据
I2C_ReceiveData();//接收数据
I2C_Send7bitAddress();//发送7位地址
I2C_CheckEvent();//I2C状态监控函数(同时判断一个或多个标志位)
//【EVx_x】事件可能同时置多个标志位
//用GetFlagStatus函数读多次进行判断太麻烦
//之前写的【MyI2C】文件中通过手动翻转GPIO口实现软件I2C
//本例使用硬件进行I2C底层驱动,因此首先将 【MyI2C】移除

配置I2C外设参考【硬件电路框图】
在MPU6050.c中初始化函数 MPU6050_Init
void MPU6050_Init(void)
{
    //第一步:RCC开启I2C和GPIO的时钟(I2C1和I2C2都是APB1的外设)
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    
    //第二步:把两个GPIO引脚都初始化为【复用开漏】模式
    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外设
    I2C_InitTypeDef I2C_InitStructure;
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;//应答位配置(接收一个字节后是否给从机应答)
    //(默认给应答,之后可以单独用函数修改)
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//STM32作为从机可以响应几位地址
    I2C_InitStructure.I2C_ClockSpeed = 50000;//SCL时钟频率
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;//时钟占空比(快速100~400Hz有用)
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;//I2C模式
    I2C_InitStructure.I2C_OwnAddress1 = 0x00;//STM作为从机时自身的地址
    I2C_Init(I2C2,&I2C_InitStructure);
    
    //第四步:使能I2C
    I2C_Cmd(I2C2,ENABLE);
    
    //配置电源管理寄存器1
    MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);
    
    //配置电源管理寄存器2
    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);
    
    //配置之后陀螺仪内部开始连续不断地进行数据转换
    //输出的数据存放在对应的数据寄存器中
}

实现写时序参考【主机发送流程图】
在MPU6050.c中编写【指定地址写】函数 MPU6050_WriteReg
//指定地址写寄存器
/*
参数1:8位寄存器地址
参数2:8位数据
与软件I2C不同,在函数结束之后需要等待相应标志位以确保函数执行到位
//【接收应答】并不需要一个函数来操作————发送数据自带了【接收应答】的过程
*/
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)
{
    //调用Start产生起始条件开始传输
    I2C_GenerateSTART(I2C2,ENABLE);
    while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS );
    //等待指定事件发生(未发生EV5事件就一直空循环等待)
    
    //第一个字节时序(必须是发送)——字节内容:从机地址(7位)+读写位(1位)
    I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);
    while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);
    //等待指定事件发生(未发生EV6事件[发送模式]就一直空循环等待)
    
    //EV6事件之后是EV8_1事件(直接写入【数据寄存器DR】进行数据发送)    
    I2C_SendData(I2C2,RegAddress);
    //指定具体写的寄存器地址,这个字节存放在MPU6050的当前地址指针里
    while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);
    //等待指定事件发生(未发生EV8事件就一直空循环等待)
    //发生了EV8事件后可以继续写数据2
    I2C_SendData(I2C2,Data);
    while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS );
    //等待指定事件发生(未发生EV8_2事件就一直空循环等待)
    
    //调用Stop产生终止条件结束传输
    I2C_GenerateSTOP(I2C2,ENABLE);
    
    /*总结:
    有连续的数据需要发送时,在发送过程中需要等待EV8事件
    当发送完最后一个字节时,则需要等待EV8_2事件
    */
}

实现读时序参考【主机读取流程图】
在MPU6050.c中编写【指定地址读】函数 MPU6050_ReadReg
//指定地址读寄存器
/*
参数:指定主机读取的地址
//【发送应答】并不需要一个函数来操作————接收数据自带了【发送应答】的过程
复合格式写+读
*/
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
    //定义变量存储DR
    uint8_t Data;
    
    //调用Start产生起始条件开始传输
    I2C_GenerateSTART(I2C2,ENABLE);
    while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS );
    //等待指定事件发生(未发生EV5事件就一直空循环等待)
    
    //第一个字节时序(必须是发送)——字节内容:从机地址(7位)+写位(1位)
    I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);
    while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);
    //等待指定事件发生(未发生EV6事件就一直空循环等待)
    
    //EV6事件之后是EV8_1事件(直接写入【数据寄存器DR】进行数据发送)    
    I2C_SendData(I2C2,RegAddress);
    //指定具体写的寄存器地址,这个字节存放在MPU6050的当前地址指针里
    while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS);
    //等待指定事件发生(未发生EV8事件就一直空循环等待)
    
    //调用Start产生【重复起始】(重新指定读写位)
    I2C_GenerateSTART(I2C2,ENABLE);
    while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS );
    //等待指定事件发生(未发生EV5事件就一直空循环等待)
    
    //第一个字节时序(选择接收)——字节内容:从机地址(7位)+读位(1位)
    I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver);
    while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS);
    //等待指定事件发生(未发生EV6事件[接收模式]就一直空循环等待)
    
    I2C_AcknowledgeConfig(I2C2,DISABLE);//ACK置0,不给从机应答
    I2C_GenerateSTOP(I2C2,ENABLE);//STOP置1,产生终止条件
    //在最后一个字节数据接收之前,提前把ACK置0,STOP置1
    
    while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);
    //等待指定事件发生(未发生EV7事件[数据2整体转移到【数据寄存器】]就一直空循环等待)
    
    //读取数据寄存器DR
    Data = I2C_ReceiveData(I2C2);
    
    I2C_AcknowledgeConfig(I2C2,ENABLE);//恢复默认状态ACK位为1,给从机应答
    
    return Data;
}
//主函数和读取DR函数不变,硬件I2C仅改变初始化和底层读写函数
实现功能:上电后初始化I2C和OLED模块,显示芯片ID号,在循环中通过软件I2C不断读取加速度计和陀螺仪数据寄存器,并通过OLED显示对应XYZ轴数值(与软件I2C实现功能相同)

需要改进的地方:当前程序中存在大量while死循环等待,一旦有个事件一直没有产生会导致程序卡死
//由于STM32的硬件I2C存在缺陷,这种情况确实存在(比如我的硬件I2C就会卡在发送的EV5事件)
  • 解决方法:使用【超时退出机制】
//超时退出机制函数
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
    uint32_t Timeout;
    Timeout = 1000;//自定义最大等待值
    while(I2C_CheckEvent(I2C2,I2C_EVENT) != SUCCESS)
    {
        Timeout --;
        if(Timeout == 0)
        {
            break;
        }
    }
}
  • 用超时退出机制函数替换掉之前的while循环
例:
//调用Start产生起始条件开始传输
    I2C_GenerateSTART(I2C2,ENABLE);
    while( I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS );
    //等待指定事件发生(未发生EV5事件就一直空循环等待)
//调用Start产生起始条件开始传输
    I2C_GenerateSTART(I2C2,ENABLE);
    MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);
    //等待指定事件发生(未发生EV5事件就一直空循环等待)

<think>好的,用户想了解如何通过硬件I2C接口对MPU6050进行读写操作。我需要先回顾一下I2C协议的基本流程,以及MPU6050的寄存器配置。首先,I2C通信需要设备地址,MPU6050的地址通常是0x68或者0x69,具体取决于AD0引脚的状态。然后,初始化I2C外设,配置时钟和GPIO引脚。接下来是MPU6050的初始化步骤,比如唤醒设备、设置量程等。读写数据时,需要先发送寄存器地址,然后取或写入数据。需要注意的是,写操作时先发送设备地址(写模式),然后寄存器地址和数据;操作则要先写寄存器地址,再切换到模式取数据。可能还需要处理连续取的情况,比如取多个寄存器的数据。另外,用户提供的引用[1]中提到Main.c和MPU6050.c的分工,可以参考这个结构来组织代码。还要注意I2C的时序和应答信号,确保通信正确。最后,用户可能需要具体的代码示例,所以应该给出初始化函数和读写函数的示例,并解释关键步骤。同时,考虑到可能的常见问题,比如地址错误、时钟配置不正确、寄存器配置错误等,可以在相关问题中提出,帮助用户进一步排查问题。</think>### 通过硬件I2C读写MPU6050的实现方法 #### 一、硬件I2C通信基础 1. **设备地址** MPU6050的默认I2C地址为$0x68$(AD0引脚接地)或$0x69$(AD0接高电平)[^1] 2. **通信时序** I2C协议包含起始信号、地址帧、数据帧和停止信号。操作需先发送寄存器地址,再发起重复起始条件进行取: ``` START → 写设备地址 → 写寄存器地址 → REPEAT START → 设备地址 → 取数据 → STOP ``` #### 二、实现步骤 **1. I2C外设初始化** ```c void I2C_Init() { // 配置GPIO为复用开漏模式(SCL/SDA引脚) GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // 假设使用PB6/PB7 // 配置I2C时序参数 hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 400000; // 标准模式400kHz hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; HAL_I2C_Init(&hi2c1); } ``` **2. MPU6050初始化** ```c #define MPU6050_ADDR 0xD0 // 左移1位后的地址(原始0x68) void MPU6050_Init() { // 唤醒设备(电源管理寄存器0x6B) uint8_t data = 0x00; HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, 0x6B, I2C_MEMADD_SIZE_8BIT, &data, 1, 100); // 配置陀螺仪量程(寄存器0x1B) data = 0x18; // ±2000°/s HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, 0x1B, I2C_MEMADD_SIZE_8BIT, &data, 1, 100); // 配置加速度计量程(寄存器0x1C) data = 0x10; // ±8g HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, 0x1C, I2C_MEMADD_SIZE_8BIT, &data, 1, 100); } ``` **3. 数据取实现** ```c void MPU6050_ReadData(int16_t *accel, int16_t *gyro) { uint8_t buffer[14]; // 从0x3B寄存器开始连续取14字节 HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, 0x3B, I2C_MEMADD_SIZE_8BIT, buffer, 14, 100); // 解析加速度计数据 accel[0] = (int16_t)(buffer[0] << 8 | buffer[1]); // X轴 accel[1] = (int16_t)(buffer[2] << 8 | buffer[3]); // Y轴 accel[2] = (int16_t)(buffer[4] << 8 | buffer[5]); // Z轴 // 解析陀螺仪数据 gyro[0] = (int16_t)(buffer[8] << 8 | buffer[9]); // X轴 gyro[1] = (int16_t)(buffer[10] << 8 | buffer[11]); // Y轴 gyro[2] = (int16_t)(buffer[12] << 8 | buffer[13]); // Z轴 } ``` #### 三、关键注意事项 1. **时序控制**:I2C时钟频率需与从设备兼容,MPU6050支持标准模式(100kHz)和快速模式(400kHz) 2. **数据对齐**:传感器数据为16位有符号数,存储格式为大端模式 3. **温度数据**:寄存器0x41-0x42包含温度数据,计算公式为: $$T = \frac{T_{raw}}{340.0} + 36.53$$ #### 四、调试技巧 - 使用逻辑分析仪捕获I2C波形验证时序 - 检查HAL库返回状态(HAL_OK表示操作成功) - 通过WHO_AM_I寄存器(0x75)验证设备连接,返回值应为$0x68$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值