目录
1.运动学概念
2.MPU6050工作原理
3.MPU6050参数
4.MPU6050量程选择
5.MPU6050从机地址配置
6.硬件电路
7.MPU6050框图
8.MPU60X0寄存器映射(常用)
寄存器映射框图各部分作用介绍
常用寄存器简介
9.软件I2C和硬件I2C的区别
10.软件I2C读写MPU6050编写步骤
10.1程序整体构架
10.2需要注意的点
10.3软件I2C编写步骤
10.4软件I2C读写MPU6050编写步骤
11.代码编写:
程序文件简要说明:
MyI2C.c
MyI2C.h
MPU6050.c
MPU6050.h
main.c
12.结果
13.数据验证
I2C通信部分的介绍可以看我的这篇博客:STM32软件I2C通信详解
MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景
3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度
3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度
1.运动学概念
-
欧拉角:
- 欧拉角是用来描述三维空间中刚体旋转的三个角度:俯仰角(Pitch)、滚转角(Roll)和偏航角(Yaw)。
- 俯仰角(Pitch):飞机机头上下倾斜的角度。
- 滚转角(Roll):飞机左右倾斜的角度。
- 偏航角(Yaw):飞机左右转向的角度。
任何一种传感器,都不能获得精确且稳定的欧拉角。要想获得,就需要进行数据融合,综合几种传感器的数据,取长补短。通常有互补滤波、卡尔曼滤波等
2.MPU6050工作原理
-
加速度计: 加速度计的内部可以想象为这个样子。是一个滑变电阻+两个弹簧
-
测量在X、Y、Z轴方向上测量加速度。通过检测重力加速度,可以推断出设备的倾斜角度。例如,在一个静止状态下,水平放置。 X和Y就是0。Z轴会受到一个向下重力所引的加速度;再例如,现在在自由落体。则X,Y,Z都为0。
-
在使用加速度计求角度的时候,只能是静态时才行。否则不行, 例如一个水平的小车,水平加速。如图,小人(芯片)收到的是向后和向下的力。 这时用三角函数求小车姿态,得到的就是小车停在一个斜坡上受到的力。
总结就是:加速度具有静态稳定性,不具有动态稳定性
-
-
陀螺仪:
- 测量设备在X、Y、Z轴上的角速度。陀螺仪可以用于检测设备的旋转运动,比如快速转动或者缓慢旋转等。
- 想要通过角速度的到角度,只需要对角速度进行积分即可。但是陀螺仪测角速度也有局限性。 当物体静止时,角速度的值会因为噪声无法完全归零。经过积分的不断累积,角速度的值就会产生缓慢的漂移。
总结就是:陀螺仪具有动态稳定性,但不具备静态稳定性。
所以,在使用时,进行互补滤波,就能得到静态和动态都稳定的都稳定的姿态角了
3.MPU6050参数
ADC和数据存储
- 16位ADC:MPU6050集成了16位的ADC,用于将模拟信号转换为数字信号,量化范围为-32768到32767。
- 量化过程:ADC将模拟信号转换为数字信号,并以两个字节进行存储。
**加速度计满量程选择:**±2、±4、±8、±16(g)
如果测量的物体的运动非常剧烈,就可以选择量程大一些。分辨率会更加粗糙
如果测量的物体的运动比较平缓,就可以选择量程小一些。分辨率会更加细腻
仪满量程选择: ±250、±500、±1000、±2000(°/sec)
陀螺仪的选择也是同上,满量程越大,测量范围越广、满量程越小,测量分辨率就越高
可配置的数字低通滤波器
- 允许用户根据应用需求通报配置寄存器,来配置滤波器,滤除噪声和干扰。
可配置的时钟源、采样分频
- 支持多种时钟源,包括内部振荡器、外部参考时钟。经过采样分频之后为AD和其他内部电路提供时钟。控制了分频系数,就控制了AD转换的快慢了。
I2C从机地址:
当AD0引脚接低电平(AD0=0):地址为1101 000 (0x68)。 当AD0引脚接高电平(AD0=1): 地址为1101 001 (0x69)。 具体地址配置决定了在I2C总线上的唯一性,避免地址冲突。
4.MPU6050量程选择
满量程选择
- **剧烈运动:**选择较大的满量程,确保测量范围足够大。
- **平缓运动:**选择较小的满量程,提升测量分辨率。
加速度计满量程示例:
- ±16g:
- 读取的ADC值为最大值32768时,对应实际加速度为16g。
- ADC值为32768的一半(16384)时,对应加速度为8g。
- ±2g:
- 读取的ADC值为最大值32768时,对应实际加速度为2g。
- ADC值为32768的一半(16384)时,对应加速度为1g。
测量分辨率:
- 满量程越小,测量分辨率越高,测量越精细。
- 满量程越大,测量范围越广。
- ADC值与加速度值呈线性关系,可以通过乘一个系数从ADC值计算出实际加速度。
5.MPU6050从机地址配置
二进制地址转换为十六进制:
- 以从机地址110 1000为例。
- 把7位二进制数1101000转换为十六进制,即分割低4位和高3位:0110 1000,转换后为0x68。
I2C时序中的地址格式:
- 在I2C通信时,需要发送7位从机地址110 1000加上1位读写位。共八位
- 使用0x68作为从机地址,需要将0x68左移1位,再加上读写位(0或1)。
- 转换步骤:
- 将0x68左移1位:1101 0000(即0xD0)。
- 再与读写位(0或1)进行或操作:(0x68 << 1) | 0 或 (0x68 << 1) | 1
- 再转换成二进制其实就是0xD0或0xD1
- 这样其实就是融入了读写位的从机地址
实际应用:
写操作时 直接发送一个字节。从机地址为0xD0。 读操作时 直接发送一个字节。从机地址为0xD1。
6.硬件电路
引脚 | 功能 |
---|---|
VCC、GND | 电源3.3v |
SCL、SDA | I2C通信引脚 |
XCL、XDA | 主机I2C通信引脚 |
AD0 | 从机地址最低位 |
INT | 中断信号输出 |
7.MPU6050框图
左边一列是自测响应。用来验证芯片是否正常
左下角的Charge Pump 是电荷泵,也叫做充电泵。是外接电容的。是用来升压的
有各种传感器。甚至还有个温度传感器。
这些东西测出来的值经过ADC进行模数转换。
这些数据统一放到数据寄存器中。
我们读取就OK了。其他的都是全自动执行的
右边部分
- Interrupt Status Register 中断控制寄存器,可以控制内部的事件到中断引脚的输出
- FIFO 先入先出寄存器,可以对数据流进行缓存
- Config Registors 配置寄存器,对内部的各个电路进行配置
- Sensor Registers 传感器寄存器(数据寄存器),存储了各个传感器的数据
- Factory Calibratilor 工厂校准,内部的传感器都进行了校准。
- Digital Motion Processor(DMP) 数字运动处理器,芯片内部自带的姿态解算的硬件算法。需要配合官方的DMP库,进行姿态解算。
- FSYNC 帧同步,
- 剩下的这些这些(上边仨)就是通信接口的部分了。 上边是MPU6050与STM32进行通信的接口。 下边是MPU6050作为主机的扩展接口
- 其中SerialInterface Bypass Mux 是用来进行旁路控制的, 他可以控制 下边的接口是直接接到STM32的总线上(黄线所连接),作为STM32的直接大哥 或者是MPU6050自己去控制这些外设(蓝色线所连接)。使STM32是MPU6050的大哥。MPU6050是这些外设的大哥
8.MPU60X0寄存器映射(常用)
寄存器映射框图各部分作用介绍
每个寄存器都是八位的,他们有唯一的地址。
常用寄存器简介
按照标黄部分从上往下介绍
第一部分
SMPLRT_DIV
采样频率分频器
- 陀螺仪刷新率(采样频率) = 时钟频率(内部晶振、陀螺仪晶振输出和或者外部时钟引脚方波) / (1+分频值)
- 使用了滤波器,陀螺仪时钟就是1KHZ。 不使用滤波器,陀螺仪时钟是8KHZ
CONFIG
配置寄存器
-
(外部同步这里不用)
-
低通滤波器可以让输出数据更加平滑,参数越大,输出都懂就越小 配置的参数可以是如下的0-7 (最后一位是保留位没用到)
GYRO_CONFIG
陀螺仪配置寄存器
-
高三位是XYZ轴的自测使能位 自测响应 = 自测使能时数据 - 自测失能时数据 得到的值在范围内就算通过自测。
-
中间两位是满量程选择位 量程选择,则需要根据实际情况来。量程越大,量程越广。量程越小,分辨率越高
ACCEL_CONFIG
加速度计配置寄存器
-
同上,高三位为自测使能位
-
中间两位是满量程选择位
-
不过后面还多了三位,这三位是配置 高通滤波器的 在这里暂时用不到。(是内置小功能需要用到的。比如自由落体检测,运动检测、零运动检测等)对我们的输入输出没有影响。
第二部分
这一大块,是数据寄存器
其中_H 表示高八位 _L表示第八位
加速度计XYZ轴
ACCEL_XOUT_H
ACCEL_XOUT_L
ACCEL_YOUT_H
ACCEL_YOUT_L
ACCEL_ZOUT_H
ACCEL_ZOUT_L
- 这是加速度计的数据寄存器 在读取数据时直接读取数据寄存器就OK了。 是一个16位的有符号数 以二进制补码的方式存储
温度传感器
TEMP_OUT_H
TEMP_OUT_L
- 同上
陀螺仪XYZ轴
GYRO_XOUT_H
GYRO_XOUT_L
GYRO_YOUT_H
GYRO_YOUT_L
GYRO_ZOUT_H
GYRO_ZOUT_L
- 同上
第三部分
PWR_MGMT_1
电源管理寄存器 1
- 第一位,设备复位 :写1 时 所有设备恢复默认值
- 第二位,睡眠模式:写1 时 芯片睡眠,进入低功耗。芯片不工作。
- 第三位,循环模式:写1时 设备进入低功耗,过一段时间启动一次。 并且唤醒的频率由电源管理寄存器2决定。
- 第四位,温度传感器失能:写1时 禁用内部温度传感器
- 最后三位,用来选择系统时钟来源
PWR_MGMT_2
电源管理寄存器 2
- 这两位决定了唤醒的频率
- 后面6位可以分别控制6个轴进入待机模式 可以省电用。如果只需要部分数据的话
WHO_AM_I
我是谁**(器件ID号)**
- 这个寄存器是只读的。
- 中间六位固定为 110100 所以读出这个寄存器的值就为0x68
- 实际上ID号就是这个芯片的I2C地址。
- 并且通过外部引脚AD0 修改设备号最低位的时候 是不反映在寄存器中的。寄存器并不会根据AD0的引脚的电平来变化。
第四部分
芯片上电之后,默认的是所有寄存器为0x00。
除了两个:
- Register 107: 0x40 也就是电源管理寄存器1。0x40即0100 0000 对应寄存器也就是睡眠模式为1 也就是上电默认随眠
- Register 117: 0x68 这个就是ID号的寄存器了。
9.软件I2C和硬件I2C的区别
-
硬件电路件I2C的波形更加规整,时钟周期和占空比非常一致。
每个时钟周期后都有严格的延时,保证每个周期的时间相同。
硬件电路需要专门的某些GPIO口才能实现
-
软件I2C的波形较为不规整,每个时钟周期和空闲时间都不一致。
软件I2C时的引脚操作会有一定的延时,因此各个时钟周期的间隔和占空比都不均匀。
但是软件实现I2C更加的方便,随便那个GPIO口都可以实现。
这里我们使用的是软件I2C
10.软件I2C读写MPU6050编写步骤
10.1程序整体构架
首先建立I2C通信层的.c和.h模块,再建立MPU6050.c, 最后是main.c
- Main.c
- 调用MPU6050初始化函数。
- 循环读取数据并进行显示。
- MPU6050.c
- 基于I2C通信协议,实现指定地址读,指定地址写
- 实现写寄存器来配置芯片,读寄存器得到传感器数据。
- I2C.c
- 初始化GPIO。
- 编写基本的I2C操作函数,包括起始条件、终止条件、发送/接收一个字节、发送/接收应答等。
10.2需要注意的点
-
开漏输出模式
- 开漏输出模式下GPIO有强下拉,弱上拉的特性,所以需要借助外部上拉电阻来输出高电平。
- 当Out输出为1时,晶体管截止。此时可以直接读取输入数据寄存器,就可以得到开漏模式下输出的值。
-
写起始条件时
在SCL高电平期间,SDA由高电平变为低电平,产生起始条件(Start Condition)。这表示一次I2C通信的开始。 (先拉低SDA再拉低SDA)
-
起始条件不仅要能在SDA和SCL都为高电平时按照先SDA再SCL顺序拉低。他们谁先拉高都是无所谓的。
-
也要兼容在重复起始条件。在重复起始条件时。 刚刚完成了应答的操作, SCL将要到拉低为低电平,而SDA的电平不敢确定。所以为了保险起见,需要趁SCL为低电平时 抓紧上拉SDA,再 上拉SCL才能保证不触发终止。举个栗子如下:
所以综上来说,在写起始条件时。顺序应该为SDA 1、SCL 1、SDA 0、 SCL 0
-
-
写终止条件时
- 同上, 在上次应答完成之后。**SCL下一个周期一定为下降沿。**而SDA的状态不确定。 所以为了确保在终止条件时,SDA一定能在SCL为高时产生上升沿, 需要先趁着SCL为低电平把SDA拉低,再把SCL拉高。 再拉高SDA。 这样就能产生在SCl为高电平时的上升沿了
-
写发送字节时
- 发送字节是高位先行
- 起始条件之后,SCL和SDA一定为低电平
- 除了起始条件和终止条件,其他时候都是SDA再SCL低电平时变化
- 取出一个字节(8bit)的任一位。可以使用 & 操作,比如 1011 0000 & 0x40 = 1011 0000 & 0100 0000 = 0000 0000(0) 1111 0000 & 0x40 = 1011 0000 & 0100 0000 = 0100 0000(非0即1) 所以发送的电平一定是0 或 1
- 循环8次 发送一个字节
-
写接收字节时
- 首先主机要释放SDA的控制权。也就是输出1,使mos管截止
- 从机在SCL低电平期间 控制SDA 拉高或拉低
- 主机控制SCL回到高电平。主机读取SDA
- 拉低SCL,从机控制SDA。
- 循环八次..接收一个字节
10.3软件I2C编写步骤
-
将打算用作SCL和SDA的GPIO引脚都初始化为开漏输出模式
-
将SCL和SDA都置高电平
-
写 写SCL函数(可选择加延时);
-
写 写SDA函数(可选择加延时);
-
写 读SDA函数(可选择加延时);
-
写起始条件函数: 拉高SDA、拉高SCL、拉低SDA、拉低SCL
-
写终止条件函数: 拉低SDA、拉高SCL、拉高SDA、
-
发送的一个字节函数 发送一个字节的最高位; 拉高SCL(从机读取) 拉低SCL(准备下次写入) 发送一个字节的次高位; 拉高SCL(从机读取) 拉低SCL(准备下次写入)
…循环八次。可使用for循环
-
接收一个字节的函数 主机释放SDA(SDA输出1,SDA对 地 相当于断开)(这时候SCL为低,从机已经在SDA存放了数据) 主机拉高SCL(准备读取) 主机输入并存入数据(可以用或来存入) 主机拉低SCL(从机SDA存放数据) 主机拉高SCL(准备读取) 主机输入并存入数据(可以用或来存入) …..循环 返回循环后接收到的一个字节
-
发送应答的函数 (发送一个字节的简化版) 发送应答 SCL高电平(从机读取应答) SCL低电平(进入下一个时序单元)
-
接收应答的函数 主机释放SDA(SDA输出1,SDA对 地 相当于断开)(这时候SCL为低,从机已经在SDA存放了数据) 主机拉高SCL(准备读取) 主机输入并存入数据(可以用或来存入) 主机拉低SCL(从机SDA存放数据) 返回读出的值
10.4软件I2C读写MPU6050编写步骤
有了软件I2C的初始化,写MPU就很方便了。
只需要调用已经写好的起始、结束、发送字节、接受字节的时序就可以。
-
初始化MPU6050 包括调用I2C初始化函数。解除睡眠模式、设置采样频率、配置陀螺仪和加速度计等寄存器
-
编写“指定地址写一个字节“的函数
- 起始
- 发送字节(设备地址+0)(我要写入)
- 接收从机应答
- 发送字节(指定要写入的地址)
- 接收从机应答
- 发送字节(发送要写入的数据)
- 接收从机应答
- 停止
其中接受从机应答可以去判断,并输出对应的报错信息。这里没有添加。
-
编写“指定地址读一个字节“的函数
- 起始
- 发送字节(设备地址+0)(我要写入)
- 接收从机应答
- 发送字节(指定要写入的地址)
- 接收应答位
- 重复起始
- 发送字节(设备地址+1)我要读取
- 接收应答位
- 接收并保存从机发送的字节
- 主机不应答(如果应答,从机会继续发送)
- 停止
- 返回读取到的值
-
编写“读取六轴传感器各个寄存器的值”
可以直接传地址,也可以用结构体或者数组。方法很多。
11.代码编写:
程序文件简要说明:
- MyI2C.c:初始化I2C所需要的引脚、编写时序的基本组成(起始、终止、发送/接收一个字节、应答、发送应答)
- MyI2C.h:函数声明
- MPU6050.c:初始化MPU6050的寄存器。在MyI2C的基础上,编写对指定地址发送指定值、读取指定地址的函数
- MPU6050.h:函数声明、数据结构体声明
- main.c:测试I2C通信结果。
MyI2C.c
#include "stm32f10x.h" // Device header
#include "Delay.h"#define SCL_Clock RCC_APB2Periph_GPIOA
#define SCL_PORT GPIOA
#define SCL_Pin GPIO_Pin_11#define SDA_Clock RCC_APB2Periph_GPIOA
#define SDA_PORT GPIOA
#define SDA_Pin GPIO_Pin_12/*** 函 数:初始化I2C* 参 数:无* 返 回 值:无*/
void MyI2C_Init(void)
{Delay_Init();//初始化延时函数/*开启SCL和SDA时钟*/RCC_APB2PeriphClockCmd(SCL_Clock,ENABLE);RCC_APB2PeriphClockCmd(SDA_Clock,ENABLE);/*初始化SCL和SDA引脚*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Pin = SCL_Pin;GPIO_Init(SCL_PORT,&GPIO_InitStructure); //SCL配置为开漏输出GPIO_InitStructure.GPIO_Pin = SDA_Pin;GPIO_Init(SDA_PORT,&GPIO_InitStructure); //SDA配置为开漏输出GPIO_SetBits(SCL_PORT,SCL_Pin);//拉高SCLGPIO_SetBits(SDA_PORT,SDA_Pin);//拉高SDA
}/*** 函 数:写一位SCL* 参 数:0或1* 返 回 值:无*/
void MyI2C_W_SCL (uint8_t BitValue)
{GPIO_WriteBit(SCL_PORT,SCL_Pin,(BitAction)BitValue);//BitAction是一个typedef的枚举类型值。Delay_us(10); //延时,给从机读取时间
}/*** 函 数:写一位SDA* 参 数:0或1* 返 回 值:无*/
void MyI2C_W_SDA (uint8_t BitValue)
{GPIO_WriteBit(SDA_PORT,SDA_Pin,(BitAction)BitValue);Delay_us(10); //延时,给从机读取时间
}/*** 函 数:读一位SDA* 参 数:无* 返 回 值:BitValue*/
uint8_t MyI2C_R_SDA (void)
{uint8_t BitValue = 0x00; BitValue = GPIO_ReadInputDataBit(SDA_PORT,SDA_Pin);Delay_us(10);return BitValue;
}/*** 函 数:起始时序单元* 参 数:无* 返 回 值:无*/
void MyI2C_Start (void)
{MyI2C_W_SDA(1);MyI2C_W_SCL(1);//按照先SDA拉高再SCL拉高。防止在重复起始时触发终止条件MyI2C_W_SDA(0);MyI2C_W_SCL(0);//先SDA拉低,再SCL拉低。触发起始条件
}/*** 函 数:结束时序单元* 参 数:无* 返 回 值:无*/
void MyI2C_Stop (void)
{MyI2C_W_SDA(0);//在SCL为低电平时,先把SDA拉高,为后续做准备MyI2C_W_SCL(1);MyI2C_W_SDA(1);//先SCL拉高,再SDA拉高。触发结束条件
}/*** 函 数:发送一个字节* 参 数:Byte* 返 回 值:无*/
void MyI2C_SendByte (uint8_t Byte)
{uint8_t i = 0;for(i = 0; i < 8; i++){MyI2C_W_SDA( Byte & (0x80 >> i) );//按照最高位依次往右取出bit写入MyI2C_W_SCL(1);//从机读取MyI2C_W_SCL(0);//准备下次写入}
}/*** 函 数:接收一个字节* 参 数:无* 返 回 值:Byte*/
uint8_t MyI2C_ReceiveByte (void)
{MyI2C_W_SDA(1);//释放SDA控制uint8_t Byte = 0x00;//存放得到的bituint8_t i = 0;for(i = 0; i < 8; i++){MyI2C_W_SCL(1);//主机准备读取if (MyI2C_R_SDA() == 1)//如果读到的这一位为1{Byte |= (0x80 >> i);//就把这一位置1,否则默认为0}MyI2C_W_SCL(0);//从机放下一位bit的数据}return Byte;
}/*** 函 数:发送应答* 参 数:AckBit* 返 回 值:无*/
void MyI2C_SenndAck (uint8_t AckBit)
{MyI2C_W_SDA(AckBit);//应答MyI2C_W_SCL(1);//从机读取MyI2C_W_SCL(0);//准备下次动作
}/*** 函 数:接收应答* 参 数:无* 返 回 值:reply*/
uint8_t MyI2C_ReceiveAck (void)
{MyI2C_W_SDA(1);//释放SDA控制uint8_t AckBit = 0x00;//存放得到的bitMyI2C_W_SCL(1);//从机填写AckBit = MyI2C_R_SDA();//主机读取MyI2C_W_SCL(0);//准备下次动作return AckBit;
}
MyI2C.h
#ifndef __MYI2C_H
#define __MYI2C_H//函 数:初始化I2C
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_SenndAck (uint8_t AckBit);//函 数:接收应答
uint8_t MyI2C_ReceiveAck (void);#endif
MPU6050.c
#include "stm32f10x.h" // Device header
#include "MPU6050.h"
#include "MyI2C.h"#include "MPU6050_Reg.h"#define MPU6050ADDRESS 0xD0/*** 函 数:指定地址写一个字节* 参 数:RegAddress 指定的寄存器地址Data 指定写入的数据* 返 回 值:无*/
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{MyI2C_Start();//起始MyI2C_SendByte(MPU6050ADDRESS);//指定设备地址加写操作MyI2C_ReceiveAck();//接收应答位MyI2C_SendByte(RegAddress);//指定寄存器地址MyI2C_ReceiveAck();//接收应答位MyI2C_SendByte(Data);//指定写入指定的数据MyI2C_ReceiveAck();//接收应答位MyI2C_Stop();//停止
}/*** 函 数:指定地址读一个字节* 参 数:RegAddress 指定要读的寄存器地址* 返 回 值:无*/
uint8_t MPU6050_ReadeReg(uint8_t RegAddress)
{uint8_t Data = 0x00;MyI2C_Start();//起始MyI2C_SendByte(MPU6050ADDRESS);//指定设备地址加写操作MyI2C_ReceiveAck();//接收应答位MyI2C_SendByte(RegAddress);//指定寄存器地址MyI2C_ReceiveAck();//接收应答位MyI2C_Start();//重复起始MyI2C_SendByte(MPU6050ADDRESS | 0x01);//指定设备地址 加 读操作MyI2C_ReceiveAck();//接收应答位Data = MyI2C_ReceiveByte();//接收从机发送的字节MyI2C_SenndAck(1);//主机发送应答位(应答会继续读)MyI2C_Stop();//停止return Data;//返回读取到的值
}/*** 函 数:初始化MPU6050* 参 数:无* 返 回 值:无*/
void MPU6050_Init(void)
{/*初始化I2C*/MyI2C_Init();/*初始化MPU6050*/MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01); //电源管理1:不复位、解除睡眠、不循环、温度传感器不失能、选择X轴陀螺仪时钟MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00); //电源管理2:不需要循环模式唤醒频率、每个轴都不需要待机MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09); //采样率分频:数据输出的快慢,越小输出越快.这里给10分频MPU6050_WriteReg(MPU6050_CONFIG,0x06); //配置寄存器:外部同步不需要、数字低通滤波器设置为110MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18); //陀螺仪配置寄存器:不自测、满量程选择:11最大量程MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18); //加速度计配置寄存器:不自测、满量程选择:11最大量程、不用高通滤波器
}/*** 函 数:得到六轴传感器中的数据* 参 数:Str MPU6050_Data的地址* 返 回 值:无*/
SensorData MPU6050_Data;
void MPU6050_GetData(SensorData *Str)//存放这种结构体类型的地址
{Str->AccX = ( MPU6050_ReadeReg(MPU6050_ACCEL_XOUT_H) <<8|MPU6050_ReadeReg(MPU6050_ACCEL_XOUT_L));Str->AccY = ( MPU6050_ReadeReg(MPU6050_ACCEL_YOUT_H) <<8|MPU6050_ReadeReg(MPU6050_ACCEL_YOUT_L));Str->AccZ = ( MPU6050_ReadeReg(MPU6050_ACCEL_ZOUT_H) <<8|MPU6050_ReadeReg(MPU6050_ACCEL_ZOUT_L));Str->Temp = ( MPU6050_ReadeReg(MPU6050_TEMP_OUT_H) <<8|MPU6050_ReadeReg(MPU6050_TEMP_OUT_L));Str->GyroX = ( MPU6050_ReadeReg(MPU6050_GYRO_XOUT_H) <<8 |MPU6050_ReadeReg(MPU6050_GYRO_XOUT_L));Str->GyroY = ( MPU6050_ReadeReg(MPU6050_GYRO_YOUT_H) <<8 |MPU6050_ReadeReg(MPU6050_GYRO_YOUT_L));Str->GyroZ = ( MPU6050_ReadeReg(MPU6050_GYRO_ZOUT_H) <<8 |MPU6050_ReadeReg(MPU6050_GYRO_ZOUT_L));
}
//MPU6050_ACCEL_XOUT_H 0x3B
//MPU6050_ACCEL_XOUT_L 0x3C
//MPU6050_ACCEL_YOUT_H 0x3D
//MPU6050_ACCEL_YOUT_L 0x3E
//MPU6050_ACCEL_ZOUT_H 0x3F
//MPU6050_ACCEL_ZOUT_L 0x40
//MPU6050_TEMP_OUT_H 0x41
//MPU6050_TEMP_OUT_L 0x42
//MPU6050_GYRO_XOUT_H 0x43
//MPU6050_GYRO_XOUT_L 0x44
//MPU6050_GYRO_YOUT_H 0x45
//MPU6050_GYRO_YOUT_L 0x46
//MPU6050_GYRO_ZOUT_H 0x47
//MPU6050_GYRO_ZOUT_L 0x48
MPU6050.h
#ifndef __MPU6050_H
#define __MPU6050_H/*** 函 数:初始化MPU6050* 参 数:无* 返 回 值:无*/
void MPU6050_Init(void);/*** 函 数:指定地址写一个字节* 参 数:RegAddress 指定的寄存器地址Data 指定写入的数据* 返 回 值:无*/
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
/*** 函 数:指定地址读一个字节* 参 数:RegAddress 指定要读的寄存器地址* 返 回 值:无*/
uint8_t MPU6050_ReadeReg(uint8_t RegAddress);//传感器数据
typedef struct Data
{int16_t AccX;int16_t AccY;int16_t AccZ;int16_t Temp;int16_t GyroX;int16_t GyroY;int16_t GyroZ;
}SensorData;
extern SensorData MPU6050_Data;/*** 函 数:得到六轴传感器中的数据* 参 数:Str MPU6050_Data的地址* 返 回 值:无*/
void MPU6050_GetData(SensorData *Str);#endif
main.c
#include "stm32f10x.h" // Device header
#include "oled.h"
#include "Delay.h"
#include "key.h"
#include "MPU6050.h"
/*指定6050设备,读取117位寄存器(设备号)在OLED显示出来
*/int main()
{OLED_Init();//初始化OLED;MPU6050_Init();//初始化MPU6050while(1) {MPU6050_GetData(&MPU6050_Data);//显示加速度OLED_ShowSignedNum(1,1,(int16_t)MPU6050_Data.AccX,5);OLED_ShowSignedNum(2,1,(int16_t)MPU6050_Data.AccY,5);OLED_ShowSignedNum(3,1,(int16_t)MPU6050_Data.AccZ,5);//显示陀螺仪OLED_ShowSignedNum(1,8,MPU6050_Data.GyroX,5);OLED_ShowSignedNum(2,8,MPU6050_Data.GyroY,5);OLED_ShowSignedNum(3,8,MPU6050_Data.GyroZ,5);//显示温度OLED_ShowSignedNum(4,4,MPU6050_Data.Temp,5);}}/*测试读写寄存器*/
//#include "stm32f10x.h" // Device header
//#include "oled.h"
//#include "Delay.h"
//#include "key.h"
//#include "MPU6050.h"
///*
// 指定6050设备,读取117位寄存器(设备号)
// 写入寄存器,并读取出来。检查是否正确写入
// 在OLED显示出来
//*///int main()
//{
// OLED_Init();//初始化OLED;
// MPU6050_Init();//初始化MPU6050
// /*读取0x75寄存器设备号并显示*/
// uint8_t ID = MPU6050_ReadeReg(0x75);
// OLED_ShowHexNum(1, 1, ID, 2);
// /*解除芯片睡眠模式,并测试写入*/
// MPU6050_WriteReg(0x6B,0x00);//解除
// MPU6050_WriteReg(0x19,0xAA);//写入
// uint8_t Data = MPU6050_ReadeReg(0x19);//读出
// OLED_ShowHexNum(2, 1, Data, 2);//显示
// MPU6050_WriteReg(0x19,0x66);//写入
// Data = MPU6050_ReadeReg(0x19);//读出
// OLED_ShowHexNum(3, 1, Data, 2);//显示
//}/*测试I2C时序*/
//#include "stm32f10x.h" // Device header
//#include "oled.h"
//#include "Delay.h"
//#include "key.h"
//#include "MyI2C.h"
///*
// 查询所有7位I2C地址
// 找到总线上挂载的所有设备
// 在OLED显示出来
//*///int main()
//{
// OLED_Init();//初始化OLED;
// MyI2C_Init();//初始化I2C
//
// /*
// 从机地址为 1101 000
// 左移后补0 = 1101 0001 = 0xD0(表示指定这个设备写)
// */
// uint8_t Row = 1;
// uint8_t i = 0x00;
// for (i = 0x00; i < 0x7F; i++)
// {
// MyI2C_Start();//起始时序
// MyI2C_SendByte((i << 1) | 0);//发送字节。写模式
// uint8_t Ack = MyI2C_ReceiveAck();//接收应答
// MyI2C_Stop();//结束时序
// if(Ack == 0)
// {
// OLED_ShowHexNum(Row,1,i,2);
// OLED_ShowBinNum(Row,4,i,7);
// Row++;
// }
// }
//}
12.结果
13.数据验证
- 陀螺仪旋转检测
- 陀螺仪绕Z轴旋转,陀螺仪Z轴会输出对应的角速度。
- 图示中,三维空间的坐标轴X、Y、Z对应陀螺仪的三个方向。
- 通过陀螺仪的测量,可以获得绕某一轴的旋转角速度信息,帮助理解物体的旋转状态。
- 加速度计检测
- 在正方体中放置一个小球,小球压在哪个面上就产生对应轴的输出。
- 当前芯片水平放置,对应正方体的X轴、Y轴数据基本为0。
- 小球在底面上,产生1个g的重力加速度,这里显示的数据是1943。
- 1943代表Z轴方向的支持力,所以Z轴加速度为正。
- 数据计算
- 根据测量值1943和满量程32768(16位ADC),计算得出加速度的实际值。
- 根据测量值1943和满量程32768(16位ADC),计算得出加速度的实际值。
- 公式: 1943/32768 = Z/16g
- 所以Z轴的加速度为0.95g。
- 测量值比例公式
- 读到的ADC值与满量程值之间的比例关系。
- 公式: 读到的数据/32768 = X/满量程 (其中,满量程在16位系统中为-32768到32767)