ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

I2C通信外设与MPU6050(硬件I2C)(STM32标准库学习笔记)

I2C通信外设与MPU6050(硬件I2C)(STM32标准库学习笔记)

在前一章节中,通过软件模拟的方式实现了STM32对MPU6050传感器的数据读取,深入理解了I2C通信的基本时序与协议细节。该方法虽灵活且适用于任意引脚配置,但受限于CPU资源,存在响应延迟高、实时性差的问题,尤其在多设备交互或高频采样场景下表现不足。本章将转向STM32标准库中的硬件I2C外设,利用芯片内置的I2C模块完成与MPU6050的通信。硬件I2C不仅显著降低主控负担,提升数据传输效率与稳定性,还支持更复杂的通信模式,如DMA辅助传输与中断驱动机制。通过对比软硬实现的差异,进一步掌握STM32外设配置流程、寄存器操作逻辑以及如何合理利用开发工具优化系统性能。这一转变标志着从理论理解迈向工程应用的关键一步,为后续构建高精度姿态检测系统奠定坚实基础。

如果觉得有收获可以为我的文章点点小赞,这将激励我创作更好的内容!谢谢!

一、I2C通信外设与MPU6050

1.1 I2C外设简介

STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担

支持多主机模型

支持7位/10位地址模式

支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)

支持DMA

兼容SMBus协议

STM32F103C8T6 硬件I2C资源:I2C1、I2C2

1.2 I2C框图

1.3 I2C基本结构

1.4 主机发送

1.5 主机接收

1.6 软件/硬件波形对比

软件:

硬件:

1.7 实例一 10.2.1硬件I2C读写MPU6050

硬件结构参考:

读写时序参考(序列图):

配置stm32的硬件I2C外设的基本结构

第一步,配置I2C外设,对stm32的I2C2外设进行初始化;

第二步,控制外设电路,实现指定地址写的时序;

第三步,控制外设电路,实现指定地址读的时序;

配置stm32的硬件I2C外设的基本流程

第一步,开启I2C外设和对应GPIO口的时钟;

第二步,把I2C外设对应的GPIO口初始化为复用开漏模式;

第三步,使用结构体,对整个I2C进行配置;

第四步,I2C_Cmd,使能I2C。

可能用到的库函数:

//初始化方面

void I2C_DeInit(I2C_TypeDef* I2Cx);

void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);

void I2C_StructInit(I2C_InitTypeDef* I2C_InitStruct);

//使能I2C

void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);

//I2C_GenerateSTART生成起始条件

void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);

//I2C_GenerateSTOP生成停止条件

void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);

//I2C_AcknowledgeConfig配置在收到一个字节后,是否给从机应答

void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);

//I2C_SendData发送数据

void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);

//I2C_ReceiveData接收数据

uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);

//ErrorStatus I2C_CheckEvent状态监控:同时判断一个或多个标志位

ErrorStatus I2C_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT);

//I2C_GetLastEvent高级状态监控(不怎么高级):获取SR1和SR2状态寄存器的值

uint32_t I2C_GetLastEvent(I2C_TypeDef* I2Cx);

//I2C_GetLastEvent基于标志位的状态监控:获取状态标志位的值

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

MPU6050_Reg.h↓

-----------------------------

#ifndef __MPU6050_REG_H #define __MPU6050_REG_H #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 #endif

-----------------------------

MPU6050.h↓

-----------------------------

#ifndef __MPU6050_H #define __MPU6050_H void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data); uint8_t MPU6050_ReadReg(uint8_t RegAddress); void MPU6050_Init(void); void MPU6050_GetData(int16_t *AccX,int16_t *AccY,int16_t *AccZ, int16_t *GyroX,int16_t *GyroY,int16_t *GyroZ); uint8_t MPU6050_GetID(void); #endif

-----------------------------

MPU6050.c↓

-----------------------------

#include "stm32f10x.h" // Device header #include "MPU6050_Reg.h" #define MPU6050_ADDRESS 0xD0 //MPU6050的I2C从机地址 /** 函 数:MPU6050等待事件 参 数:同I2C_CheckEvent 返 回 值:无 / 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) //自减到0后,等待超时 { /超时的错误处理代码,可以添加到此处/ break; //跳出等待,不等了 } } } /** 函 数:MPU6050写寄存器 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述 参 数:Data 要写入寄存器的数据,范围:0x00~0xFF 返 回 值:无 */ void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data) { I2C_GenerateSTART(I2C2,ENABLE); //硬件I2C生成起始条件 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5 I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter); //硬件I2C发送从机地址,方向为发送 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //等待EV6 I2C_SendData(I2C2,RegAddress); //硬件I2C发送寄存器地址 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING); //等待EV8 I2C_SendData(I2C2,Data); //硬件I2C发送数据 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //等待EV8_2 I2C_GenerateSTOP(I2C2,ENABLE); //硬件I2C生成终止条件 } /** 函 数:MPU6050读寄存器 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述 返 回 值:读取寄存器的数据,范围:0x00~0xFF */ uint8_t MPU6050_ReadReg(uint8_t RegAddress) { uint8_t Data; I2C_GenerateSTART(I2C2,ENABLE); //硬件I2C生成起始条件 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5 I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter); //硬件I2C发送从机地址,方向为发送 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //等待EV6 I2C_SendData(I2C2,RegAddress); //硬件I2C发送寄存器地址 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //等待EV8_2 I2C_GenerateSTART(I2C2,ENABLE); //硬件I2C生成重复起始条件 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5 I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver); //硬件I2C发送从机地址,方向为接收 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 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_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_InitStructure.I2C_OwnAddress1 = 0x00; I2C_Init(I2C2,&I2C_InitStructure); I2C_Cmd(I2C2,ENABLE); MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); //电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪 MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); //电源管理寄存器2,保持默认值0,所有轴均不待机 MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); //采样率分频寄存器,配置采样率 MPU6050_WriteReg(MPU6050_CONFIG, 0x06); //配置寄存器,配置DLPF MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); //陀螺仪配置寄存器,选择满量程为±2000°/s MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18); //加速度计配置寄存器,选择满量程为±16g } /** 函 数:MPU6050获取ID号 参 数:无 返 回 值:MPU6050的ID号 */ uint8_t MPU6050_GetID(void) { return MPU6050_ReadReg(MPU6050_WHO_AM_I); //返回WHO_AM_I寄存器的值 } /** 函 数:MPU6050获取数据 参 数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767 参 数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767 返 回 值:无 */ 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; //定义数据高8位和低8位的变量 DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H); //读取加速度计X轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); //读取加速度计X轴的低8位数据 *AccX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回 DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H); //读取加速度计Y轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L); //读取加速度计Y轴的低8位数据 *AccY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回 DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H); //读取加速度计Z轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L); //读取加速度计Z轴的低8位数据 *AccZ = (DataH << 8) | DataL; //数据拼接,通过输出参数返回 DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H); //读取陀螺仪X轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L); //读取陀螺仪X轴的低8位数据 *GyroX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回 DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H); //读取陀螺仪Y轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L); //读取陀螺仪Y轴的低8位数据 *GyroY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回 DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H); //读取陀螺仪Z轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L); //读取陀螺仪Z轴的低8位数据 *GyroZ = (DataH << 8) | DataL; //数据拼接,通过输出参数返回 }

-----------------------------

main.c↓

-----------------------------

#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "MPU6050.h" uint8_t ID; int16_t AX,AY,AZ,GX,GY,GZ; int main(void) { /模块初始化/ OLED_Init(); MPU6050_Init(); /*显示ID号*/ OLED_ShowString(1,1,"ID:"); ID = MPU6050_GetID(); OLED_ShowHexNum(1,4,ID,2); while(1) { MPU6050_GetData(&amp;AX,&amp;AY,&amp;AZ,&amp;GX,&amp;GY,&amp;GZ); OLED_ShowSignedNum(2,1,AX,5); OLED_ShowSignedNum(3,1,AY,5); OLED_ShowSignedNum(4,1,AZ,5); OLED_ShowSignedNum(2,8,GX,5); OLED_ShowSignedNum(3,8,GY,5); OLED_ShowSignedNum(4,8,GZ,5); Delay_ms(100); } }

-----------------------------

二、总结

本文围绕 STM32 的硬件 I2C 外设与 MPU6050 传感器展开,从外设原理到实例代码完整走了一遍流程,主要可以归纳为以下几点:

  • 硬件 I2C 的价值:STM32 内部集成了 I2C 收发电路,时钟生成、起始与终止条件、应答位收发、数据收发都由硬件自动完成,相比软件模拟 I2C 大幅减轻了 CPU 负担,同时支持多主机模型、7 位/10 位地址、标准 100 kHz 与快速 400 kHz 速率、DMA 以及 SMBus 协议兼容。
  • 结构与时序理解:通过 I2C 框图与基本结构图,可以看清 SCL、SDA 引脚、数据寄存器和移位寄存器之间的关系;主机发送与主机接收的流程差异,核心在于方向位、重复起始条件以及最后一个字节前提前失能应答并申请停止条件。
  • 软硬件波形对比:软件模拟与硬件外设产生的波形在时序细节上存在差异,硬件 I2C 的波形更规整、速率更稳定,也更容易通过状态寄存器判断事件是否完成。
  • 配置流程:硬件 I2C 的配置可概括为开启 I2C 与 GPIO 时钟、将对应 GPIO 配置为复用开漏模式、用结构体配置 I2C 参数、最后调用I2C_Cmd使能外设四步;在此基础上再实现指定地址的写时序与读时序。
  • 实例落地:以 MPU6050 为例,通过MPU6050_WriteReg与MPU6050_ReadReg封装寄存器读写,配合MPU6050_WaitEvent做超时等待,完成初始化、ID 读取与加速度计/陀螺仪六轴数据的采集,并在 OLED 上实时显示。

整体来看,硬件 I2C 的关键在于事件等待与状态判断:每一步操作后都要确认对应的 EV 事件已经发生,再继续下一步,否则容易出现卡死或数据错乱。掌握这套流程后,读写其他 I2C 从机设备(如 EEPROM、OLED、各类传感器)都可以复用同样的思路,只需替换从机地址与寄存器定义即可。

都看到这了点点赞吧!求求了!

返回列表