手把手教你用STM32F103C6T6的HAL库软件IIC驱动MPU6050,从CubeMX配置到数据读取全流程

张开发
2026/4/19 15:53:55 15 分钟阅读

分享文章

手把手教你用STM32F103C6T6的HAL库软件IIC驱动MPU6050,从CubeMX配置到数据读取全流程
STM32F103C6T6实战HAL库软件IIC驱动MPU6050全流程解析当你第一次拿到STM32F103C6T6这块被称为蓝桥杯神器的开发板和MPU6050模块时可能会被各种专业术语和复杂的配置步骤吓到。别担心这篇文章将用最接地气的方式带你从零开始完成整个开发流程。不同于网上那些只贴代码的教程我会详细解释每个步骤背后的原理并分享实际调试中可能遇到的坑。1. 开发环境搭建与CubeMX配置在开始编写代码前我们需要先搭建好开发环境。这里推荐使用Keil MDK作为IDE配合STM32CubeMX进行初始化配置。CubeMX是ST官方提供的图形化配置工具能极大简化外设初始化的工作量。1.1 创建CubeMX工程打开CubeMX后按照以下步骤创建新工程选择MCU型号STM32F103C6T6配置系统时钟源为外部晶振如果板载有8MHz晶振在Pinout视图中启用SWD下载接口PA13和PA14配置系统时钟树确保主频达到72MHz这是F103系列的最高频率注意如果使用内部RC振荡器时钟精度会稍差但对MPU6050的基本功能影响不大。1.2 GPIO引脚配置由于我们要使用软件模拟I2C需要选择两个GPIO作为SCL和SDA线。这里我推荐使用PB6和PB7这两个引脚通常不会与其他功能冲突。在CubeMX中这样配置PB6GPIO_Output初始状态高电平标签设为I2C_SCLPB7GPIO_Output初始状态高电平标签设为I2C_SDA配置完成后点击Generate Code生成工程。记得选择MDK-ARM作为Toolchain/IDE。2. 软件I2C驱动实现硬件I2C虽然方便但在某些情况下特别是F1系列可能会遇到稳定性问题。软件模拟I2C虽然速度稍慢但更加灵活可靠。2.1 基础时序函数首先创建soft_i2c.c和soft_i2c.h文件实现最基本的时序控制// soft_i2c.h #ifndef __SOFT_I2C_H #define __SOFT_I2C_H #include stm32f1xx_hal.h #define I2C_SCL_PIN GPIO_PIN_6 #define I2C_SCL_PORT GPIOB #define I2C_SDA_PIN GPIO_PIN_7 #define I2C_SDA_PORT GPIOB void I2C_Init(void); void I2C_Start(void); void I2C_Stop(void); void I2C_Ack(void); void I2C_NAck(void); uint8_t I2C_WaitAck(void); void I2C_SendByte(uint8_t data); uint8_t I2C_ReadByte(void); #endif对应的实现文件中最关键的是时序控制。I2C标准模式时钟频率为100kHz每个时钟周期至少5μs// soft_i2c.c #include soft_i2c.h void Delay_us(uint32_t us) { uint32_t ticks us * (SystemCoreClock / 1000000) / 5; while(ticks--); } void I2C_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin I2C_SCL_PIN | I2C_SDA_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull GPIO_PULLUP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(I2C_SCL_PORT, GPIO_InitStruct); HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_PIN_SET); } void I2C_Start(void) { HAL_GPIO_WritePin(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_SET); Delay_us(5); HAL_GPIO_WritePin(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_PIN_RESET); Delay_us(5); HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_RESET); }2.2 完整I2C协议实现继续在soft_i2c.c中实现完整的I2C协议函数void I2C_Stop(void) { HAL_GPIO_WritePin(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_SET); Delay_us(5); HAL_GPIO_WritePin(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_PIN_SET); Delay_us(5); } uint8_t I2C_WaitAck(void) { uint8_t ack 0; HAL_GPIO_WritePin(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_SET); Delay_us(5); if(HAL_GPIO_ReadPin(I2C_SDA_PORT, I2C_SDA_PIN) GPIO_PIN_RESET) ack 1; HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_RESET); Delay_us(5); return ack; } void I2C_SendByte(uint8_t data) { for(uint8_t i 0; i 8; i) { HAL_GPIO_WritePin(I2C_SDA_PORT, I2C_SDA_PIN, (data 0x80) ? GPIO_PIN_SET : GPIO_PIN_RESET); data 1; HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_SET); Delay_us(5); HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_RESET); Delay_us(5); } }3. MPU6050驱动开发有了软件I2C基础现在可以开始实现MPU6050的具体驱动了。3.1 寄存器定义与初始化创建mpu6050.h定义关键寄存器地址// mpu6050.h #ifndef __MPU6050_H #define __MPU6050_H #include soft_i2c.h #define MPU6050_ADDR 0xD0 // 寄存器地址 #define SMPLRT_DIV 0x19 #define CONFIG 0x1A #define GYRO_CONFIG 0x1B #define ACCEL_CONFIG 0x1C #define ACCEL_XOUT_H 0x3B #define ACCEL_XOUT_L 0x3C #define ACCEL_YOUT_H 0x3D #define ACCEL_YOUT_L 0x3E #define ACCEL_ZOUT_H 0x3F #define ACCEL_ZOUT_L 0x40 #define TEMP_OUT_H 0x41 #define TEMP_OUT_L 0x42 #define GYRO_XOUT_H 0x43 #define GYRO_XOUT_L 0x44 #define GYRO_YOUT_H 0x45 #define GYRO_YOUT_L 0x46 #define GYRO_ZOUT_H 0x47 #define GYRO_ZOUT_L 0x48 #define PWR_MGMT_1 0x6B #define WHO_AM_I 0x75 void MPU6050_Init(void); void MPU6050_ReadRawData(int16_t* accel, int16_t* gyro); #endif初始化函数需要配置MPU6050的工作模式、量程等参数// mpu6050.c #include mpu6050.h void MPU6050_WriteReg(uint8_t reg, uint8_t data) { I2C_Start(); I2C_SendByte(MPU6050_ADDR); I2C_WaitAck(); I2C_SendByte(reg); I2C_WaitAck(); I2C_SendByte(data); I2C_WaitAck(); I2C_Stop(); } uint8_t MPU6050_ReadReg(uint8_t reg) { uint8_t data; I2C_Start(); I2C_SendByte(MPU6050_ADDR); I2C_WaitAck(); I2C_SendByte(reg); I2C_WaitAck(); I2C_Start(); I2C_SendByte(MPU6050_ADDR | 0x01); I2C_WaitAck(); data I2C_ReadByte(); I2C_NAck(); I2C_Stop(); return data; } void MPU6050_Init(void) { I2C_Init(); // 唤醒MPU6050 MPU6050_WriteReg(PWR_MGMT_1, 0x00); HAL_Delay(100); // 设置采样率1kHz MPU6050_WriteReg(SMPLRT_DIV, 0x07); // 设置低通滤波器带宽44Hz MPU6050_WriteReg(CONFIG, 0x03); // 设置陀螺仪量程±2000°/s MPU6050_WriteReg(GYRO_CONFIG, 0x18); // 设置加速度计量程±16g MPU6050_WriteReg(ACCEL_CONFIG, 0x18); }3.2 数据读取与处理实现原始数据读取函数并将原始值转换为实际物理量void MPU6050_ReadRawData(int16_t* accel, int16_t* gyro) { uint8_t buf[14]; I2C_Start(); I2C_SendByte(MPU6050_ADDR); I2C_WaitAck(); I2C_SendByte(ACCEL_XOUT_H); I2C_WaitAck(); I2C_Start(); I2C_SendByte(MPU6050_ADDR | 0x01); I2C_WaitAck(); for(int i 0; i 13; i) { buf[i] I2C_ReadByte(); I2C_Ack(); } buf[13] I2C_ReadByte(); I2C_NAck(); I2C_Stop(); accel[0] (buf[0] 8) | buf[1]; // Accel X accel[1] (buf[2] 8) | buf[3]; // Accel Y accel[2] (buf[4] 8) | buf[5]; // Accel Z gyro[0] (buf[8] 8) | buf[9]; // Gyro X gyro[1] (buf[10] 8) | buf[11]; // Gyro Y gyro[2] (buf[12] 8) | buf[13]; // Gyro Z }4. 系统集成与调试4.1 主程序实现在main.c中集成所有功能并添加简单的数据处理#include stm32f1xx_hal.h #include soft_i2c.h #include mpu6050.h int16_t accel[3], gyro[3]; float accel_g[3], gyro_dps[3]; int main(void) { HAL_Init(); SystemClock_Config(); MPU6050_Init(); while(1) { MPU6050_ReadRawData(accel, gyro); // 转换为实际物理量 // 加速度计量程±16g灵敏度2048 LSB/g for(int i 0; i 3; i) { accel_g[i] accel[i] / 2048.0f; } // 陀螺仪量程±2000°/s灵敏度16.4 LSB/(°/s) for(int i 0; i 3; i) { gyro_dps[i] gyro[i] / 16.4f; } HAL_Delay(100); // 10Hz采样率 } }4.2 常见问题排查在实际调试中你可能会遇到以下问题I2C通信失败检查硬件连接SCL和SDA线是否接反上拉电阻是否合适通常4.7kΩ用逻辑分析仪或示波器观察I2C波形确认时序是否符合标准尝试降低I2C时钟频率增加延时MPU6050无响应确认设备地址正确AD0引脚接地时为0x68接VCC时为0x69检查电源电压是否稳定3.3V读取WHO_AM_I寄存器0x75返回值应为0x68数据跳动严重确保MPU6050固定牢固避免振动干扰尝试启用内置的数字低通滤波器在软件中实现简单的移动平均滤波// 简单的移动平均滤波示例 #define FILTER_SIZE 5 int16_t accel_filter[3][FILTER_SIZE]; uint8_t filter_index 0; void ApplyFilter(int16_t* raw, float* filtered) { for(int i 0; i 3; i) { accel_filter[i][filter_index] raw[i]; int32_t sum 0; for(int j 0; j FILTER_SIZE; j) { sum accel_filter[i][j]; } filtered[i] sum / (float)FILTER_SIZE; } filter_index (filter_index 1) % FILTER_SIZE; }通过以上步骤你应该已经成功实现了STM32F103C6T6通过软件I2C驱动MPU6050的完整流程。在实际项目中你可能还需要考虑传感器校准、数据融合如互补滤波或卡尔曼滤波等进阶技术。

更多文章