当前位置:首页 > 技术 > 正文内容

MPU6050姿态传感器原理与软件I2C通信实现

访客 技术 2026年7月21日 3
  1. MPU6050概述

MPU6050是一款集成了三轴加速度计和三轴陀螺仪的6轴运动处理设备,能够测量设备在X、Y、Z轴上的加速度和角速度数据。通过算法融合这些数据,可以计算出设备的姿态角度,广泛应用于平衡车、无人机等需要实时姿态感知的智能设备中。

  • 三轴加速度计:检测设备在三个正交方向上的线性加速度
  • 三轴陀螺仪:测量设备围绕三个轴的旋转速率

1.1 技术规格

  • 16位模数转换器,数据范围:-32768~32767
  • 加速度测量范围:±2g、±4g、±8g、±16g可选
  • 陀螺仪测量范围:±250°/s、±500°/s、±1000°/s、±2000°/s可选
  • 可编程数字低通滤波器
  • 多种时钟源选择
  • 可配置采样频率

I2C从机地址:1101000(AD0=0)、1101001(AD0=1) 在编程实现中,I2C地址有两种常见表示方式。以1101000为例,第一种方式是将7位二进制直接转换为十六进制,即0x68。在I2C通信中,第一个字节的高7位是从机地址,最低位是读写位。因此,如果将0x68视为从机地址,发送第一个字节时需左移1位后再与读写位进行或运算。第二种方式是将0x68左移1位后的值作为从机地址,即0xD0,此时发送时若为写操作直接使用0xD0,读操作则使用0xD1(或上0x01)。

1.2 硬件连接

MPU6050模块原理图

XCL、XDA:这两个引脚用于扩展传感器,通常连接磁力计或气压计等外部设备。MPU6050的主机接口可以直接访问这些扩展芯片的数据,并通过其内部的DMP(数字运动处理器)单元进行数据融合和姿态解算。

1.3 内部结构

  1. 软件模拟I2C实现

2.1 连接方式

SCL连接到PB10,SDA连接到PB11。由于本节使用软件模拟I2C,即通过普通GPIO口手动控制电平变化,无需STM32内部外设支持,因此端口可自由选择,例如SCL接PA8,SDA接PA9等。

根据I2C协议规范,SCL和SDA线均需要上拉电阻,但模块已内置上拉电阻,无需外部添加。XCL和XDA为扩展接口,本应用中不使用。AD0引脚用于修改从机地址最低位,模块内部已接下拉电阻,悬空时相当于接地。INT为中断输出引脚,本应用中不使用。

2.2 代码实现

程序架构设计如下:

  1. 首先构建I2C通信层的.c和.h文件。在通信层中,实现GPIO初始化和六个基本时序单元(起始信号、停止信号、发送字节、接收字节、发送应答、接收应答)。
  2. 然后创建MPU6050的.c和.h文件。基于I2C通信层,实现指定地址读写功能,进而实现寄存器配置和数据读取功能。
  3. 最后在main.c中调用MPU6050模块,完成初始化、数据获取和显示。

由于使用软件模拟I2C,无需使用STM32的I2C库函数,只需基本的GPIO读写函数即可。

SoftI2C.c(避免与库函数重名)

#include "stm32f10x.h"
#include "Delay.h"

void SoftI2C_Initialize(void)
{
    // 完成两个任务:
    // ① 将SCL和SDA配置为开漏输出模式;
    // ② 将SCL和SDA设置为高电平。
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
}

// 封装GPIO操作函数
void SoftI2C_Set_SCL(uint8_t Level)
{
    GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)Level);
    Delay_us(10);
}

void SoftI2C_Set_SDA(uint8_t Level)
{
    GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)Level);
    Delay_us(10);
}

uint8_t SoftI2C_Get_SDA(void)
{
    uint8_t Level;
    Level = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
    Delay_us(10);
    return Level;
}

// 实现I2C的六个基本时序单元
//
// 1. 起始条件
void SoftI2C_Start(void)
{
    SoftI2C_Set_SDA(1);
    SoftI2C_Set_SCL(1);
    
    SoftI2C_Set_SDA(0);
    SoftI2C_Set_SCL(0);
}

// 2. 停止条件
void SoftI2C_Stop(void)
{
    SoftI2C_Set_SDA(0);
    SoftI2C_Set_SCL(1);
    SoftI2C_Set_SDA(1);
}

// 3. 发送一个字节
void SoftI2C_TransmitByte(uint8_t Data)
{
    uint8_t i;
    for(i = 0; i < 8; i++)
    {
        SoftI2C_Set_SDA(Data & (0x80 >> i));
        SoftI2C_Set_SCL(1);
        SoftI2C_Set_SCL(0);
    }
}

// 4. 接收一个字节
uint8_t SoftI2C_ReceiveByte(void)
{
    uint8_t i, Data = 0x00;
    SoftI2C_Set_SDA(1);
    for(i = 0; i < 8; i++)
    {
        SoftI2C_Set_SCL(1);
        if(SoftI2C_Get_SDA() == 1)
        {
            Data |= (0x80 >> i);
        }
        SoftI2C_Set_SCL(0);
    }
    return Data;
}

// 5. 发送应答位
void SoftI2C_SendAck(uint8_t Ack)
{
    SoftI2C_Set_SDA(Ack);
    SoftI2C_Set_SCL(1);
    SoftI2C_Set_SCL(0);
}

// 6. 接收应答位
uint8_t SoftI2C_ReceiveAck(void)
{
    uint8_t Ack;
    SoftI2C_Set_SDA(1);
    SoftI2C_Set_SCL(1);
    Ack = SoftI2C_Get_SDA();
    SoftI2C_Set_SCL(0);
    return Ack;
}

SoftI2C.h

#ifndef __SOFTI2C_H
#define __SOFTI2C_H

void SoftI2C_Initialize(void);
void SoftI2C_Start(void);
void SoftI2C_Stop(void);
void SoftI2C_TransmitByte(uint8_t Data);
uint8_t SoftI2C_ReceiveByte(void);
void SoftI2C_SendAck(uint8_t Ack);
uint8_t SoftI2C_ReceiveAck(void);

#endif

MotionSensor.c

#include "stm32f10x.h"
#include "SoftI2C.h"
#include "SensorRegisters.h"

#define SENSOR_ADDRESS 0xD0

// 向指定寄存器写入数据
void Sensor_WriteRegister(uint8_t Reg, uint8_t Value)
{
    SoftI2C_Start();
    SoftI2C_TransmitByte(SENSOR_ADDRESS);
    SoftI2C_ReceiveAck();
    SoftI2C_TransmitByte(Reg);
    SoftI2C_ReceiveAck();
    SoftI2C_TransmitByte(Value);
    SoftI2C_ReceiveAck();
    SoftI2C_Stop();
}

// 从指定寄存器读取数据
uint8_t Sensor_ReadRegister(uint8_t Reg)
{
    uint8_t Value;
    
    SoftI2C_Start();
    SoftI2C_TransmitByte(SENSOR_ADDRESS);
    SoftI2C_ReceiveAck();
    SoftI2C_TransmitByte(Reg);
    SoftI2C_ReceiveAck();
    
    SoftI2C_Start();
    SoftI2C_TransmitByte(SENSOR_ADDRESS | 0x01);
    SoftI2C_ReceiveAck();
    Value = SoftI2C_ReceiveByte();
    SoftI2C_SendAck(1);
    SoftI2C_Stop();
    
    return Value;
}

void Sensor_Initialize(void)
{
    SoftI2C_Initialize();
    Sensor_WriteRegister(PWR_REG1, 0x01);
    Sensor_WriteRegister(PWR_REG2, 0x00);
    Sensor_WriteRegister(SAMPLE_RATE, 0x09);
    Sensor_WriteRegister(CONFIG_REG, 0x06);
    Sensor_WriteRegister(GYRO_CONFIG, 0x18);
    Sensor_WriteRegister(ACCEL_CONFIG, 0x18);
    
}

uint8_t Sensor_GetDeviceID(void)
{
    return Sensor_ReadRegister(WHO_AM_I_REG);
}

// 读取传感器数据
void Sensor_ReadMotionData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
                            int16_t *RotX, int16_t *RotY, int16_t *RotZ)
{
    uint8_t HighByte, LowByte;
    
    HighByte = Sensor_ReadRegister(ACCEL_X_HIGH);
    LowByte = Sensor_ReadRegister(ACCEL_X_LOW);
    *AccX = (HighByte << 8) | LowByte;
    
    HighByte = Sensor_ReadRegister(ACCEL_Y_HIGH);
    LowByte = Sensor_ReadRegister(ACCEL_Y_LOW);
    *AccY = (HighByte << 8) | LowByte;
    
    HighByte = Sensor_ReadRegister(ACCEL_Z_HIGH);
    LowByte = Sensor_ReadRegister(ACCEL_Z_LOW);
    *AccZ = (HighByte << 8) | LowByte;
    
    HighByte = Sensor_ReadRegister(GYRO_X_HIGH);
    LowByte = Sensor_ReadRegister(GYRO_X_LOW);
    *RotX = (HighByte << 8) | LowByte;
    
    HighByte = Sensor_ReadRegister(GYRO_Y_HIGH);
    LowByte = Sensor_ReadRegister(GYRO_Y_LOW);
    *RotY = (HighByte << 8) | LowByte;
    
    HighByte = Sensor_ReadRegister(GYRO_Z_HIGH);
    LowByte = Sensor_ReadRegister(GYRO_Z_LOW);
    *RotZ = (HighByte << 8) | LowByte;
}

MotionSensor.h

#ifndef __MOTIONSENSOR_H
#define __MOTIONSENSOR_H

void Sensor_WriteRegister(uint8_t Reg, uint8_t Value);
uint8_t Sensor_ReadRegister(uint8_t Reg);

void Sensor_Initialize(void);
void Sensor_ReadMotionData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
                            int16_t *RotX, int16_t *RotY, int16_t *RotZ);
uint8_t Sensor_GetDeviceID(void);

#endif

SensorRegisters.h

#ifndef __SENSORREGISTERS_H
#define __SENSORREGISTERS_H

#define	SAMPLE_RATE_REG		0x19
#define	CONFIG_REG			0x1A
#define	GYRO_CONFIG_REG		0x1B
#define	ACCEL_CONFIG_REG	0x1C

#define	ACCEL_X_HIGH		0x3B
#define	ACCEL_X_LOW			0x3C
#define	ACCEL_Y_HIGH		0x3D
#define	ACCEL_Y_LOW			0x3E
#define	ACCEL_Z_HIGH		0x3F
#define	ACCEL_Z_LOW			0x40
#define	TEMPERATURE_HIGH	0x41
#define	TEMPERATURE_LOW		0x42
#define	GYRO_X_HIGH			0x43
#define	GYRO_X_LOW			0x44
#define	GYRO_Y_HIGH			0x45
#define	GYRO_Y_LOW			0x46
#define	GYRO_Z_HIGH			0x47
#define	GYRO_Z_LOW			0x48

#define	PWR_REG1			0x6B
#define	PWR_REG2			0x6C
#define	WHO_AM_I_REG		0x75

#endif

main.c

#include "stm32f10x.h"
#include "Delay.h"
#include "Display.h"
#include "MotionSensor.h"

int16_t AccX, AccY, AccZ, RotX, RotY, RotZ;

int main(void)
{
    Display_Initialize();
    Sensor_Initialize();
    
    Display_ShowString(1, 1, "ID:");
    Display_ShowHexNum(1, 4, Sensor_GetDeviceID(), 2);
    
    Display_ShowString(1, 7, "Acc|Rot");
    Display_ShowString(2, 1, "X       |");
    Display_ShowString(3, 1, "Y       |");
    Display_ShowString(4, 1, "Z       |");
    
    while(1)
    {
        Sensor_ReadMotionData(&AccX, &AccY, &AccZ, &RotX, &RotY, &RotZ);
        Display_ShowSignedNum(2, 3, AccX, 5);
        Display_ShowSignedNum(3, 3, AccY, 5);
        Display_ShowSignedNum(4, 3, AccZ, 5);
        Display_ShowSignedNum(2, 10, RotX, 5);
        Display_ShowSignedNum(3, 10, RotY, 5);
        Display_ShowSignedNum(4, 10, RotZ, 5);
    }
}

其他相关头文件和实现代码可参考:Display.h(OLED调试工具)、Delay.h(延时函数实现)

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。