在嵌入式开发中,姿态传感器是机器人、无人机等项目的核心组件。MPU6050作为一款高性价比的六轴传感器,集成了三轴加速度计和三轴陀螺仪,能够精确测量物体的运动状态。而STM32F103C8T6(俗称"蓝桥杯单片机")凭借其出色的性能和亲民的价格,成为学生党和嵌入式初学者的首选。
我刚开始接触MPU6050时,发现大多数教程都依赖硬件IIC或HAL库,这对于想深入理解底层原理的开发者来说并不友好。后来在实际项目中,我不得不使用软件模拟IIC(即软件IIC)来驱动MPU6050,这个过程踩了不少坑,但也积累了宝贵经验。本文将分享我从零开始构建这个驱动系统的完整过程。
硬件准备清单:
接线示意图:
注意:虽然MPU6050支持5V供电,但建议使用3.3V供电以获得更好的稳定性。如果使用5V供电,需要确保STM32的I/O口能承受5V电平(STM32F103系列大部分I/O都是5V容忍的)。
硬件IIC虽然方便,但在实际项目中经常会遇到引脚冲突、时序不稳定等问题。软件IIC通过GPIO模拟时序,具有更好的灵活性和可移植性。下面是我在项目中总结出的软件IIC实现要点。
IIC协议就像两个人对话,需要遵守严格的时序规则:
c复制// 起始信号实现
void MyI2C_Start(void) {
MyI2C_W_SDA(1); // 确保SDA为高
MyI2C_W_SCL(1); // 确保SCL为高
Delay_us(5); // 保持时间
MyI2C_W_SDA(0); // 产生下降沿
Delay_us(5);
MyI2C_W_SCL(0); // 拉低SCL准备数据传输
}
软件IIC对GPIO配置有特殊要求:
c复制void MyI2C_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11); // 初始状态为高
}
在实际调试中,我发现时序控制是最容易出问题的地方。比如有一次,因为延时太短导致数据读取不稳定,后来通过示波器观察波形才找到问题所在。建议初学者可以先用逻辑分析仪抓取波形,对照IIC时序图检查每个边沿是否合规。
MPU6050通过寄存器进行配置,主要需要设置以下几个关键寄存器:
| 寄存器地址 | 名称 | 默认值 | 功能说明 |
|---|---|---|---|
| 0x6B | PWR_MGMT_1 | 0x40 | 电源管理,设置时钟源 |
| 0x1B | GYRO_CONFIG | 0x00 | 陀螺仪量程设置(±2000°/s) |
| 0x1C | ACCEL_CONFIG | 0x00 | 加速度计量程设置(±2g) |
| 0x19 | SMPLRT_DIV | 0x00 | 采样率分频 |
| 0x1A | CONFIG | 0x00 | 数字低通滤波器设置 |
初始化代码示例:
c复制void MPU6050_Init(void) {
MyI2C_Init();
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); // 使用X轴陀螺仪作为时钟源
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); // 所有传感器使能
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x07); // 采样率=1kHz/(7+1)=125Hz
MPU6050_WriteReg(MPU6050_CONFIG, 0x06); // 低通滤波器带宽5Hz
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); // 陀螺仪±2000°/s量程
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);// 加速度计±16g量程
}
原始数据读取需要拼接高8位和低8位,这里有个效率优化的小技巧:
c复制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 | 0x00);
MyI2C_ReceiveAck();
MyI2C_SendByte(MPU6050_ACCEL_XOUT_H);
MyI2C_ReceiveAck();
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS | 0x01);
MyI2C_ReceiveAck();
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];
}
这种方法相比单独读取每个寄存器,减少了多次起始/停止信号的开销,实测数据读取速度提升约3倍。在需要高频采样的场景下(如平衡车控制),这种优化非常关键。
在调试过程中,我遇到过几个典型问题:
校准方法示例:
c复制// 简单的零偏校准
void MPU6050_Calibrate() {
int32_t acc_sum[3] = {0}, gyro_sum[3] = {0};
for(int i=0; i<100; i++) {
int16_t acc[3], gyro[3];
MPU6050_GetData(&acc[0], &acc[1], &acc[2],
&gyro[0], &gyro[1], &gyro[2]);
for(int j=0; j<3; j++) {
acc_sum[j] += acc[j];
gyro_sum[j] += gyro[j];
}
Delay_ms(10);
}
// 保存校准值
for(int j=0; j<3; j++) {
acc_offset[j] = acc_sum[j] / 100;
gyro_offset[j] = gyro_sum[j] / 100;
}
}
c复制// 简单的滑动平均滤波实现
#define FILTER_NUM 5
int16_t filter_buf[3][FILTER_NUM];
uint8_t filter_index = 0;
void Filter_Data(int16_t *x, int16_t *y, int16_t *z) {
static int32_t sum[3] = {0};
// 减去最旧的数据
for(int i=0; i<3; i++) {
sum[i] -= filter_buf[i][filter_index];
}
// 添加新数据
filter_buf[0][filter_index] = *x;
filter_buf[1][filter_index] = *y;
filter_buf[2][filter_index] = *z;
for(int i=0; i<3; i++) {
sum[i] += filter_buf[i][filter_index];
}
filter_index = (filter_index + 1) % FILTER_NUM;
// 输出平均值
*x = sum[0] / FILTER_NUM;
*y = sum[1] / FILTER_NUM;
*z = sum[2] / FILTER_NUM;
}
在实际项目中,我发现软件IIC虽然比硬件IIC慢,但对于MPU6050这种传感器已经足够。通过上述优化,我的系统最终实现了稳定的100Hz数据采集频率,完全满足大多数嵌入式应用的需求。