STM32F103C8T6核心板驱动MPU6050:从I2C时序到OLED显示的保姆级教程
2026/5/9 15:36:17 网站建设 项目流程

STM32F103C8T6核心板驱动MPU6050:从I2C时序到OLED显示的保姆级教程

当你第一次拿到STM32F103C8T6核心板和MPU6050模块时,可能会被I2C通信、寄存器配置、数据解析等一系列概念搞得晕头转向。这篇文章将带你从零开始,一步步实现MPU6050数据的读取和OLED显示,过程中遇到的每一个坑我都会提前预警。

1. 硬件准备与连接

在开始写代码之前,正确的硬件连接是成功的第一步。STM32F103C8T6核心板(俗称"蓝莓派")因其价格低廉且功能完善,成为许多嵌入式初学者的首选。MPU6050模块则集成了3轴加速度计和3轴陀螺仪,通过I2C接口与主控通信。

所需材料清单:

  • STM32F103C8T6核心板 ×1
  • MPU6050模块 ×1
  • 0.96寸OLED显示屏(SSD1306驱动) ×1
  • 杜邦线若干
  • USB转TTL模块(用于程序烧录)

硬件连接时特别注意以下几点:

  1. MPU6050的VCC接3.3V,绝对不能接5V,否则可能损坏模块
  2. I2C通信需要上拉电阻,如果模块上没有集成,需在SDA和SCL线上各接4.7kΩ电阻到3.3V
  3. OLED显示屏同样使用I2C接口,可以与MPU6050共用I2C总线

具体接线方式:

STM32引脚MPU6050引脚OLED引脚
PB6SCLSCL
PB7SDASDA
3.3VVCCVCC
GNDGNDGND

提示:如果使用硬件I2C,SCL应接PB6,SDA接PB7;如果使用软件模拟I2C,则可以任意选择两个GPIO口。

2. 软件I2C驱动实现

STM32的硬件I2C外设配置复杂且容易出问题,对于初学者来说,软件模拟I2C是更可靠的选择。下面我们从头构建一个稳定的软件I2C驱动。

2.1 I2C基础时序实现

I2C通信的核心是精确控制SCL和SDA线的时序。我们先定义基本的GPIO操作函数:

// MyI2C.h #ifndef __MYI2C_H #define __MYI2C_H #include "stm32f10x.h" void MyI2C_Init(void); void MyI2C_Start(void); void MyI2C_Stop(void); void MyI2C_SendByte(uint8_t byte); uint8_t MyI2C_ReceiveByte(void); void MyI2C_SendAck(uint8_t ack); uint8_t MyI2C_ReceiveAck(void); #endif

对应的实现文件中,我们需要特别注意时序延迟。MPU6050的工作频率最高为400kHz(快速模式),但作为初学者,我们先使用100kHz的标准模式:

// MyI2C.c #include "MyI2C.h" #include "Delay.h" #define I2C_SCL_PIN GPIO_Pin_6 #define I2C_SDA_PIN GPIO_Pin_7 #define I2C_PORT GPIOB // 初始化I2C GPIO void MyI2C_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 开漏输出 GPIO_InitStructure.GPIO_Pin = I2C_SCL_PIN | I2C_SDA_PIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(I2C_PORT, &GPIO_InitStructure); GPIO_SetBits(I2C_PORT, I2C_SCL_PIN | I2C_SDA_PIN); // 总线空闲状态 } // 产生起始条件 void MyI2C_Start(void) { GPIO_SetBits(I2C_PORT, I2C_SDA_PIN); GPIO_SetBits(I2C_PORT, I2C_SCL_PIN); Delay_us(5); GPIO_ResetBits(I2C_PORT, I2C_SDA_PIN); Delay_us(5); GPIO_ResetBits(I2C_PORT, I2C_SCL_PIN); } // 产生停止条件 void MyI2C_Stop(void) { GPIO_ResetBits(I2C_PORT, I2C_SDA_PIN); GPIO_SetBits(I2C_PORT, I2C_SCL_PIN); Delay_us(5); GPIO_SetBits(I2C_PORT, I2C_SDA_PIN); Delay_us(5); }

2.2 完整I2C通信函数

发送和接收字节是I2C通信的核心功能,需要严格按照时序图实现:

// 发送一个字节 void MyI2C_SendByte(uint8_t byte) { uint8_t i; for(i = 0; i < 8; i++) { if(byte & 0x80) { GPIO_SetBits(I2C_PORT, I2C_SDA_PIN); } else { GPIO_ResetBits(I2C_PORT, I2C_SDA_PIN); } Delay_us(2); GPIO_SetBits(I2C_PORT, I2C_SCL_PIN); Delay_us(5); GPIO_ResetBits(I2C_PORT, I2C_SCL_PIN); Delay_us(2); byte <<= 1; } // 释放SDA线用于接收ACK GPIO_SetBits(I2C_PORT, I2C_SDA_PIN); } // 接收一个字节 uint8_t MyI2C_ReceiveByte(void) { uint8_t i, byte = 0; GPIO_SetBits(I2C_PORT, I2C_SDA_PIN); // 释放SDA线 for(i = 0; i < 8; i++) { GPIO_SetBits(I2C_PORT, I2C_SCL_PIN); Delay_us(3); byte <<= 1; if(GPIO_ReadInputDataBit(I2C_PORT, I2C_SDA_PIN)) { byte |= 0x01; } Delay_us(2); GPIO_ResetBits(I2C_PORT, I2C_SCL_PIN); Delay_us(5); } return byte; }

3. MPU6050驱动开发

有了可靠的I2C驱动后,我们就可以开始与MPU6050通信了。首先需要了解MPU6050的寄存器映射和配置方法。

3.1 MPU6050寄存器配置

MPU6050有多个配置寄存器,我们需要重点关注以下几个:

寄存器地址名称功能描述
0x6BPWR_MGMT_1电源管理,解除睡眠模式
0x1BGYRO_CONFIG陀螺仪量程配置
0x1CACCEL_CONFIG加速度计量程配置
0x19SMPLRT_DIV采样率分频器
0x1ACONFIG数字低通滤波器配置
0x75WHO_AM_I器件ID(0x68)

创建MPU6050的驱动头文件:

// MPU6050_Reg.h #ifndef __MPU6050_REG_H #define __MPU6050_REG_H #define MPU6050_ADDRESS_AD0_LOW 0xD0 #define MPU6050_ADDRESS_AD0_HIGH 0xD1 // 寄存器地址定义 #define MPU6050_SMPLRT_DIV 0x19 #define MPU6050_CONFIG 0x1A #define MPU6050_GYRO_CONFIG 0x1B #define MPU6050_ACCEL_CONFIG 0x1C #define MPU6050_WHO_AM_I 0x75 #define MPU6050_PWR_MGMT_1 0x6B #define MPU6050_PWR_MGMT_2 0x6C #define MPU6050_ACCEL_XOUT_H 0x3B #define MPU6050_ACCEL_XOUT_L 0x3C // ...其他数据寄存器省略 #endif

3.2 MPU6050初始化

初始化过程需要按照特定顺序配置多个寄存器:

// MPU6050.c #include "MPU6050_Reg.h" #include "MyI2C.h" #include "Delay.h" // 向指定寄存器写入数据 void MPU6050_WriteReg(uint8_t regAddr, uint8_t data) { MyI2C_Start(); MyI2C_SendByte(MPU6050_ADDRESS_AD0_LOW); // 写操作 MyI2C_ReceiveAck(); MyI2C_SendByte(regAddr); MyI2C_ReceiveAck(); MyI2C_SendByte(data); MyI2C_ReceiveAck(); MyI2C_Stop(); } // 从指定寄存器读取数据 uint8_t MPU6050_ReadReg(uint8_t regAddr) { uint8_t data; MyI2C_Start(); MyI2C_SendByte(MPU6050_ADDRESS_AD0_LOW); // 写操作 MyI2C_ReceiveAck(); MyI2C_SendByte(regAddr); MyI2C_ReceiveAck(); MyI2C_Start(); MyI2C_SendByte(MPU6050_ADDRESS_AD0_LOW | 0x01); // 读操作 MyI2C_ReceiveAck(); data = MyI2C_ReceiveByte(); MyI2C_SendAck(1); // NACK MyI2C_Stop(); return data; } // MPU6050初始化 void MPU6050_Init(void) { Delay_ms(100); // 上电延时 MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x80); // 复位设备 Delay_ms(100); MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x00); // 解除休眠 MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); // 所有轴都工作 MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x07); // 采样率1kHz MPU6050_WriteReg(MPU6050_CONFIG, 0x06); // 低通滤波器5Hz MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); // 陀螺仪±2000°/s MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); // 加速度计±16g }

3.3 读取传感器数据

MPU6050的加速度计和陀螺仪数据都是16位有符号数,存储在连续的寄存器中:

// 读取6轴数据 void MPU6050_GetData(int16_t* accX, int16_t* accY, int16_t* accZ, int16_t* gyroX, int16_t* gyroY, int16_t* gyroZ) { uint8_t buf[14]; MyI2C_Start(); MyI2C_SendByte(MPU6050_ADDRESS_AD0_LOW); // 写操作 MyI2C_ReceiveAck(); MyI2C_SendByte(MPU6050_ACCEL_XOUT_H); MyI2C_ReceiveAck(); MyI2C_Start(); MyI2C_SendByte(MPU6050_ADDRESS_AD0_LOW | 0x01); // 读操作 MyI2C_ReceiveAck(); // 连续读取14个字节(6轴数据+温度) for(int i = 0; i < 13; i++) { buf[i] = MyI2C_ReceiveByte(); MyI2C_SendAck(0); // ACK } buf[13] = MyI2C_ReceiveByte(); MyI2C_SendAck(1); // NACK MyI2C_Stop(); // 组合高低字节 *accX = (buf[0] << 8) | buf[1]; *accY = (buf[2] << 8) | buf[3]; *accZ = (buf[4] << 8) | buf[5]; *gyroX = (buf[8] << 8) | buf[9]; *gyroY = (buf[10] << 8) | buf[11]; *gyroZ = (buf[12] << 8) | buf[13]; }

4. OLED数据显示实现

最后一步是将读取到的传感器数据可视化显示在OLED屏幕上。我们使用常见的SSD1306驱动的0.96寸OLED屏。

4.1 OLED驱动初始化

首先初始化OLED显示屏:

// OLED.c #include "OLED.h" #include "Delay.h" void OLED_Init(void) { // 初始化I2C MyI2C_Init(); // 发送初始化命令序列 OLED_WriteCommand(0xAE); // 关闭显示 OLED_WriteCommand(0xD5); // 设置显示时钟分频 OLED_WriteCommand(0x80); OLED_WriteCommand(0xA8); // 设置多路复用率 OLED_WriteCommand(0x3F); // ...更多初始化命令 OLED_WriteCommand(0xAF); // 开启显示 OLED_Clear(); } // 写命令 void OLED_WriteCommand(uint8_t cmd) { MyI2C_Start(); MyI2C_SendByte(0x78); // OLED地址 MyI2C_ReceiveAck(); MyI2C_SendByte(0x00); // 命令标识 MyI2C_ReceiveAck(); MyI2C_SendByte(cmd); MyI2C_ReceiveAck(); MyI2C_Stop(); }

4.2 数据显示实现

在OLED上清晰展示6轴数据,并添加适当的标签:

// 主函数 #include "stm32f10x.h" #include "Delay.h" #include "OLED.h" #include "MPU6050.h" int main(void) { int16_t accX, accY, accZ; int16_t gyroX, gyroY, gyroZ; Delay_init(); OLED_Init(); MPU6050_Init(); // 显示静态标签 OLED_ShowString(1, 1, "Acc:"); OLED_ShowString(3, 1, "Gyro:"); OLED_ShowString(1, 6, "X:"); OLED_ShowString(2, 6, "Y:"); OLED_ShowString(3, 6, "Z:"); OLED_ShowString(1, 12, "Y:"); OLED_ShowString(2, 12, "P:"); OLED_ShowString(3, 12, "R:"); while(1) { MPU6050_GetData(&accX, &accY, &accZ, &gyroX, &gyroY, &gyroZ); // 显示加速度计数据 OLED_ShowSignedNum(1, 8, accX, 5); OLED_ShowSignedNum(2, 8, accY, 5); OLED_ShowSignedNum(3, 8, accZ, 5); // 显示陀螺仪数据 OLED_ShowSignedNum(1, 14, gyroX, 5); OLED_ShowSignedNum(2, 14, gyroY, 5); OLED_ShowSignedNum(3, 14, gyroZ, 5); Delay_ms(100); // 100ms刷新一次 } }

5. 常见问题与调试技巧

在实际开发过程中,你可能会遇到各种问题。以下是几个常见问题及其解决方法:

  1. I2C通信失败

    • 检查硬件连接是否正确,特别是SDA和SCL线是否接反
    • 用逻辑分析仪或示波器观察I2C波形,确认时序是否符合标准
    • 尝试降低I2C时钟频率(增加Delay时间)
  2. MPU6050无响应

    • 确认MPU6050的电源电压为3.3V
    • 检查AD0引脚的电平,确保使用的I2C地址正确
    • 读取WHO_AM_I寄存器(0x75),返回值应为0x68
  3. 数据跳动严重

    • 尝试配置数字低通滤波器(CONFIG寄存器)
    • 对数据进行软件滤波处理(如移动平均)
    • 确保MPU6050固定牢固,避免机械振动影响
  4. OLED显示异常

    • 检查OLED的I2C地址(通常是0x78或0x7A)
    • 确认初始化命令序列正确
    • 如果显示内容错位,检查GRAM的写入逻辑
// 简单的移动平均滤波示例 #define FILTER_SIZE 5 int16_t filterBuffer[FILTER_SIZE]; uint8_t filterIndex = 0; int16_t applyFilter(int16_t newValue) { static int32_t sum = 0; sum -= filterBuffer[filterIndex]; filterBuffer[filterIndex] = newValue; sum += newValue; filterIndex = (filterIndex + 1) % FILTER_SIZE; return sum / FILTER_SIZE; }

在实际项目中,我发现最影响MPU6050性能的是电源噪声。使用LDO稳压器为MPU6050单独供电,并添加适当的去耦电容(100nF靠近VCC引脚),可以显著提高数据稳定性。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询