Advertisement

MPU6050传感器—姿态检测

阅读量:

本节主要介绍以下内容:

姿态检测的基本概念

姿态传感器的工作原理及参数

MPU6050传感器介绍

实验:获取MPU6050原始数据

实验:移植官方DMP例程

一、姿态检测基本概念

1.1 姿态

在飞行器领域中,飞行器的姿态具有重要意义,基于自身中心的坐标系建立后,当该飞行器绕着坐标轴进行旋转时,会分别受到航偏角、横滚角及俯仰角的影响

那么我们检测偏航角、横滚角以及俯仰角就可以知道飞行姿态了

偏航角:飞机机头朝向偏航的角度

横滚角:飞机的机翼相对于水平面翻滚的角度

俯仰角:飞机的机头朝向的角度

假设我们已经知道了飞机最初处于左上角的状态,则只需要测定基于初始状态的三个姿态角的变化值,并将这些变化值叠加起来即可确定其实时姿态。

1.2 坐标系

抽象来说,姿态是“载体坐标系”与“地理坐标系”之前的转换关系。

此图中用紫色标注为地理坐标系,在红色标注为基于载体的载体坐标系中,其中, 载体的姿态角即由其相对于地心参考框架的位置关系所决定。

下面是 三种常用的坐标系:

在地球坐标系中:以地心为参考点,在赤道平面上设置X和Y轴,在通过地极方向上设置Z轴。

地理坐标系: 它基于地球表面(或运载工具所在的位置)设立原点,并以当地地理垂直方向为Z轴(即重力加速度的方向)。X和Y轴则分别沿着当地的经向和纬向切线延伸。根据具体应用需求可以选择以下几种常见的天文学坐标系统:东北天、东南天以及西北天等。这也是我们日常生活中使用的基础坐标系。

在载体系中, 载体坐标系是以运载工具的质量中心被设作基准点, 并依据载具自身的结构走向来确定各轴向的方向. 其中, Z轴通常从基准点延伸至载具顶部, Y轴则指向载具头部, X轴沿著载具两侧水平延伸. 例如, 在飞机上建立的这种坐标系统被视为典型的载体系示例, 这种概念同样适用于汽车、船舶以及其他生物或电子设备.

基于同一个参考点(即同一原点),地理坐标系与载体坐标系可以经过旋转转换**;其姿态由两者的相对位置关系决定。

基于初始状态下飞机坐标系的XYZ三个主轴分别对应于地理坐标系中的天地平面法线方向。

当飞机自身围绕"Z"轴旋转时,在空间中其自身在Y轴方向会与地理坐标的南北方向产生一定角度的变化;这一变化的角度即被定义为偏航角(Yaw)。

当载体绕自身的X轴旋转时,在这种情况下它的Z轴方向与地理坐标系中的'天地'方向会发生偏移。这个偏移的角度被称为俯仰角(Pitch)。

当载体绕其自身的Y轴旋转时,在这种情况下它的X轴方向会倾斜于地理坐标系的"东西"方向一定的角度参数。这种倾斜的角度参数被定义为横滚角。

姿态角的关系

坐标系间的旋转角度 说明 载体自身旋转
偏航角**(Yaw)** Y轴与北轴的夹角 绕载体Z轴旋转可改变
俯仰角**(Pitch)** Z轴与天轴的夹角 绕载体X轴旋转可改变
横滚角**(Roll)** X轴与东轴的夹角 绕载体Y轴旋转可改变

使用陀螺仪检测角度

陀螺仪是最直接的角度测量工具,在机械系统中它能够准确捕捉物体绕坐标轴旋转的速度变化。类似于通过线性运动的速度与时间积分来确定位移的过程,在这种情况下,在给定时间段内对角速度进行积分即可得出旋转累积的角度信息。

陀螺仪检测的缺陷

由于陀螺仪在测量角度时采用积分法运算,在实际应用中会产生一定的积分误差;当积分时间Dt减小时;积算出的误差也会随之减小;这一问题非常直观易懂;例如在计算行驶路程时;如果我们假设车辆在整个行驶过程中始终以某一特定速度Vt匀速行驶1小时;那么所计算出的路程与实际行驶情况之间的偏差将是巨大的;每隔一段时间我们就会采集一次车辆的速度数据;并获得若干个不同时间段内的瞬时速度值;将每个时间段内的速度数值乘以该时间段的时间长度(即5分钟);然后将这些乘积累加起来;则可以得到一个更为准确的车辆行驶路程估计值;通过不断提高数据采集频率能够显著降低积算区间Dt的大小

同样而言,在提升陀螺仪传感器的采样率时,则可降低积分误差;当前常见的一般陀螺仪传感器均能达到8千赫兹的采样率水平,并足以满足多数应用场景的需求

在解决这个问题的过程中发现,在处理器件自身产生的误差上更为棘手。举个例子来说, 比如有一种陀螺仪其工作精度为每秒0.1度, 在这种情况下, 无论长时间保持静态状态还是持续保持静态状态达一小时, 都会因为正负累积效应无法完全抵消而导致最终结果偏差明显, 从而使得系统性能受到影响

具体来说, 当这种陀螺仪处于完全静止状态时, 理论上讲其期望角速度应为零, 因此在正常工作状态下积分得到的角度变化始终为零。然而实际上由于存在一定的系统误差(本例中提到的工作精度即为此种情况), 在这种情况下采样所得的速度读数始终保持在+0.1度/秒这一偏移值上。因此经过一段时间后将导致明显的累计偏差: 假设持续保持静态状态达一分钟, 将会得到6度的结果; 若持续保持静态状态达一小时, 则会得到360度的结果——即完成了一整圈的变化量

利用加速度计检测角度

考虑到长时间测量时使用陀螺仪会导致角度测量累积误差的积累现象出现,因此我们采用了检测倾角的传感器作为替代方案。

最常见的用于测量倾角的例子是建筑中使用的水平仪。在受到重力作用时,在水平仪内部的气泡大致反映了水柱所在直线与重力方向之间的角度关系。采用T型水平仪,则能够检测出横滚角和俯仰角;然而,在这种情况下则无法测定的是偏航角。

在电子设备中通常采用加速度传感器来测定倾角其工作原理是通过测量器件在不同方向上的形变情况从而获取受力数据基于F=ma的物理定律这些数据会被转换为加速度值因此这种传感器被称为加速度传感器由于地球引力场的存在重力始终会对传感器施加强作用当传感器处于静止状态(实际上此时的总加速度为零)时它会在所受重力方向测得该方向上的重力加速度值为g需要注意的是不能认为在测量时测得的重力方向上的加速度值为g就表示此时传感器正在做加速运动

当传感器的姿态发生变化时,在其自身的各个坐标轴上测量得到的重力加速度值不同。通过各方向的数据进行分析,并结合力的分解理论来计算每个坐标轴与重力之间的夹角。

由于重力方向与地理坐标系中的'天地'轴保持固定,在测定载体坐标系各轴相对于重力方向的角度之后,则可由此获得该系统相对于地理坐标的姿态信息

该倾角检测方法基于重力原理运行,在这种情况下无法测定偏航角(Yaw)。其工作原理与经典的T型水平仪相似,在任何优化设计下也无法准确测定偏航角

另一个问题是该传感器无法分辨自由落体产生的重力加速度与外部施加的其他方向上的加速度。当物体处于运动状态时,该传感器会在运动过程中检测到相应的加速度值。尤其是在振动状态下,传感器会输出显著的变化数据,并且此时很难准确测定真实的重力值。

磁场检测

为了解决加速度传感器无法测量偏航角(Yaw)的局限性问题, 我们并引入磁场探测器, 它能够测定不同方向的磁感应强度大小. 通过分析地球周围的磁场分布, 这种装置便具备指南针的功能, 因此得名电子罗盘仪. 由于地磁场与地理坐标系中的南北方向是固联的, 利用这种装置便能实现对偏航角(Yaw)的有效测量.

磁场检测器的缺陷

如同指南针所具有的缺陷相似,在应用磁场传感器时也会面临外部磁场的影响。例如由于载体自身产生的电磁场干扰以及在不同地理环境下的磁铁矿也会造成干扰等。

GPS****检测

通过GPS可以直接测得载体现有位置。假设在某一时间点测得位置A,在另一时间点测得位置B。通过这两个坐标点即可计算出飞行方向,并从而可确定偏航角数值。该方法仅适用于载具发生显著位移的情况(GPS民用精度通常在10米级)。

姿态融合与四元数

观察到采用陀螺仪进行角度测量时,在静止状态下存在不足,并受到时间的影响;而采用加速度传感器进行角度测量时,在运动状态下存在不足,并不受时间的影响;两者恰好相互补充。若能同时运用这两种传感器并构建一个滤波机制:当物体处于静止状态时,则提高加速度数据的比重;当物体处于运动状态时,则提高陀螺仪数据的比重;从而实现更为精确的姿态数据获取。

类似地,在监测偏航角的过程中发现以下规律:当载体处于静止状态时(即非运动状态),应赋予磁场检测器数据更高的权重;而当载体处于运动状态时,则应增加陀螺仪和GPS检测数据的权重系数。这些通过综合运用多种传感器数据来进行姿态估计的技术被称为姿态融合算法

通常,在进行姿态融合解算时,“四元数值”被用来表示姿态。它由三个实部与一个虚部组成,并被称为“复变数字体”。采用“复变数字体”来表示的姿态虽然不够直观性直白,“但因为采用欧拉参数(如仰俯角、偏航角与滚转轴等)来描述的姿态则会遇到“万向节锁定”现象并运算较为繁琐”,因此,在数据处理过程中通常会选择“复变数字体”作为主要手段进行计算与分析。

具体来说,在姿态表示领域中四元数被用作一种替代表达方向余弦矩阵的方法。如需进一步了解,请参考相关文献。

二、传感器工作原理

2.1 工作原理

在电子技术中,传感器一般是指把物理量转化成电信号量的装置。

敏感元件能够直接接收被测物理量,并生成与该物理量存在明确对应关系的信号;通过转换元件将该物理量信号成功地转变为电信号;变换电路会对转换元件输出的电信号进行相应的放大并调节;最终输出易于进行检测的电信号值。

比如,在温度测量系统中

**2.2.**传感器参数

参数 说明
线性误差 指传感器测量值与真实物理量值之间的拟合度误差。
分辨率 指传感器可检测到的最小物理量的单位。
采样频率 指在单位时间内的采样次数。

其中误差与分辨率是比较容易混淆的概念。以使用尺子测量长度为例:误差指的是尺子是否精确;用它测量得到10厘米后;与标准值10厘米相比;若两者差距不超过5毫米;则称这把尺子的误差为5毫米。而分辨率代表了尺子能够区分的最小长度间隔;如果一把尺子的最小刻度为1厘米;则称其分辨率为1厘米;这种尺子只能用于测量厘米级的尺寸;而对毫米级的长度则无法进行精确测量。如果将这把尺子加热导致其伸缩;那么其误差可能会超过5毫米;但其分辨率为1厘米仍然不变;只是测得的1厘米数值与其真实值之间的偏差会更加明显了。

**2.3.**物理量的表示方法

大部分传感器的输出与其电压呈正比关系。其中电压值通常通过专用的数字转换器(ADC)进行采集。这些数字转换器具有固定的分辨率(如8位、12位等),其参数直接影响着测量系统的灵敏度和量程范围。

假设采用一个两位数的ADC来进行长度测量,则其能表示的数据范围仅限于从零到三这四个数值。若其分辨率设定为每二十厘米,则该装置的最大可测长度即为六十分米;而当分辨率调整至每十分米时,则其最大可测长度缩减为三十分米。由此可见,在固定位数的情况下,默认值与分辨率二者之间存在不可兼得的关系。

在实际应用中,在ADC每个单元中所体现出来的物理量值常被用来衡量分辨率。例如,在某些设计中会设定每个单元对应的实际长度为20厘米,则该设备的分辨率为每20厘米对应1个最低有效位(1LSB/20cm)。这相当于采用5个ADC单元累计达到1米时的情况(即5LSB/m)。其中,在此定义下的最低有效位(LSB)指的是每个单元中最细微的变化量。

通过采用分辨率5LSB/m且线性误差仅为0.1m的传感器对长度进行测量时,在获得ADC采样所得的数据值为"20"的基础上(此处应写作"20"而非"二〇"),可以通过计算得出被测物体的实际尺寸正好是4米;然而需要注意的是,在实际应用中由于各种因素的影响(如环境温度波动等),所测得的真实值可能会出现微小偏差(具体范围在±0.1米之间)。

三、MPU6050传感器介绍

3.1 MPU6050简介

该微控制器单元是一种六自由度运动传感器装置。基于InvenSense公司的微控制器单元MPU6050设计实现。其核心组件包括三个正交方向的加速度计子系统、三个正交方向的陀螺仪子系统(由三轴角速度计组成)以及温度采集电路。该装置能够同步采集三个方向的加速度数据、三个方向的角速度数据以及环境温度信息。

通过内建的DMP模块(Digital Motion Processor)这一功能模块的运用,在MPU6050芯片中实现了对传感器数据的有效滤除噪声并整合数据源的能力。该模块主要依靠I2C接口将解算后的姿态信息传递给主控单元,在此过程中主控单元的工作负担得到显著减轻。特别适合于那些对姿态控制精度和实时性要求较高的应用环境,在这类场景下其解算频率能够达到最高200Hz(通过整合多个角速度原始数据以及加速度信息来计算出最终的姿态角)。值得注意的是该技术广泛应用于手机、智能手环、四轴飞行器以及步数计数设备等领域

该坐标系通过旋转符号进行标注,标识了MPU6050传感器XYZ轴上的加速度矢量具有角速度正方向。

3.2 MPU6050****的特性参数

参数 说明
供电 3.3V-5V
通讯接口 I2C协议,支持的I2C时钟最高频率为400KHz
测量维度 加速度:3维 陀螺仪:3维
ADC****分辨率 加速度:16位 陀螺仪:16位
加速度测量范围 ±2g、±4g、±8g、±16g 其中g为重力加速度常数,g=9.8m/s ²
加速度最高分辨率 16384 LSB/g 65536/4 = 16384
加速度线性误差 0.1g
加速度输出频率 最高1000Hz
陀螺仪测量范围 ±250 º/s 、±500 º/s 、±1000 º/s、±2000 º/s、
陀螺仪最高分辨率 131 LSB/( º/s) 65536/500 = 131.072
陀螺仪线性误差 0.1 º/s
陀螺仪输出频率 最高 8000Hz
DMP****姿态解算频率 最高200Hz
温度传感器测量范围 -40~ +85℃
温度传感器分辨率 340 LSB/℃
温度传感器线性误差 ±1℃
工作温度 -40~ +85℃
功耗 500uA~3.9mA (工作电压3.3V)

加速度和陀螺仪传感器使用的ADC模块均为16位,并支持调节量程和分辨率,并提供多种工作模式选项

根据表中的数据显示可知, 该传感器与三轴加速计配合使用, 其采样频率为1000Hz, 同时与数字陀螺仪集成, 其工作频率达到8000Hz. 具体来说, 这些数值代表的是加速度计与三轴运动传感器所采集的速度变化率以及陀螺仪测量到的空间旋转速率. 通过STM32控制器获取这些数据, 进而完成后续的数据处理与分析. 最终目标是精确地计算出设备当前的姿态参数, 包括但不仅限于偏航角、横滚角与俯仰角的具体数值.

通过采用DMP单元对采样获得的加速度和角速度进行姿态解算,并将这些解算结果传递给STM32控制器。因此,在此配置下,STM32无需自行计算相关参数即可获取偏航角、横滚角以及俯仰角信息;值得注意的是,在此方案中每隔一定时间间隔即可输出一次完整的姿态数据。

3.3 引脚说明

该模块引出的8 个引脚功能说明见下表

其中的SDA/SCL和XDA/XCL的通讯引脚分别构成两组I2C信号线。当设备与外部主控单元进行通信操作时,则会采用SDA/SCL作为主总线;另一组则用于MPU6050微控制器与其他支持I2C接口的传感器进行通信。例如通过该引脚将磁场传感器连接至微控制器以获取相关数据。实际上这种设计相对笨拙且效率低下因此多数情况下会将多个I2C传感器直接集成至同一根总线上

四、实验:获取MPU6050原始数据

4.1 硬件设计

MPU6050 模块的硬件原理图如下:

从硬件结构来看较为简单。观察右上角标注可知,该模块的I2C通讯引脚SDA及SCL已接入上拉电阻,因此在与外部I2C主机进行通讯时可直接导线连接即可;而对于其他传感器间的通讯所使用的XDA、XCL引脚则未接上拉电阻,使用时需特别注意其配置参数以确保正常工作。模块内部的I2C设备地址可通过AD0引脚电平控制来设定,当AD0接地时设备地址设定为七位制的0x68,而当AD0接电源时则变为七位制的0x69值。此外,每当传感器传输新的数据时会通过INT引脚向STM32发送通知。

在进行MPU6050检测时,在其自身的中心坐标系下定义了该传感器的姿态信息以及旋转符号来标识XYZ轴方向上的加速度具有角速度正方向的意义。因此,在安装模块的过程中需要考虑其与设备整体坐标系统的关联

在实验前,我们先用杜邦线把STM32 开发板与该MPU6050 模块连接起来

硬件I2C无法与LCD屏并行工作。由于FSMC中的NADV引脚与I2C1总线的数据线(SDA)共用同一个物理端口,在这种情况下会产生相互干扰。所有例程均基于软件I2C协议设计用于控制MPU6050芯片,并且其底层软件I2C控制器的功能特性与EEPROM配置基本一致。本章重点讲述上层应用及其相关接口功能。

4.2 软件设计

4.2.1 编程要点
  1. 配置STM32 I2C接口
  2. 启用I2C总线以供MPU6050发送控制指令
  3. 定期采集来自加速度计、角速度计和温度传感器的数据
4.2.2 代码分析

本实验中的I2C 硬件定义见代码

复制代码
 /**************************I2C参数定义,I2C1或I2C2********************************/

    
 #define             SENSORS_I2Cx                                I2C1
    
 #define             SENSORS_I2C_APBxClock_FUN                   RCC_APB1PeriphClockCmd
    
 #define             SENSORS_I2C_CLK                             RCC_APB1Periph_I2C1
    
 #define             SENSORS_I2C_GPIO_APBxClock_FUN              RCC_APB2PeriphClockCmd
    
 #define             SENSORS_I2C_GPIO_CLK                        RCC_APB2Periph_GPIOB     
    
 #define             SENSORS_I2C_SCL_PORT                        GPIOB   
    
 #define             SENSORS_I2C_SCL_PIN                         GPIO_Pin_6
    
 #define             SENSORS_I2C_SDA_PORT                        GPIOB 
    
 #define             SENSORS_I2C_SDA_PIN                         GPIO_Pin_7

这些宏根据传感器使用的I2C 硬件封装起来了

①初始化I2C

复制代码
 /** * @brief  I2C1 I/O配置
    
   * @param  无
    
   * @retval 无
    
   */
    
 static void I2C_GPIO_Config(void)
    
 {
    
   GPIO_InitTypeDef  GPIO_InitStructure; 
    
  
    
 	/* 使能与 I2C1 有关的时钟 */
    
 	SENSORS_I2C_APBxClock_FUN ( SENSORS_I2C_CLK, ENABLE );
    
 	SENSORS_I2C_GPIO_APBxClock_FUN ( SENSORS_I2C_GPIO_CLK, ENABLE );
    
 	
    
     
    
   /* PB6-I2C1_SCL、PB7-I2C1_SDA*/
    
   GPIO_InitStructure.GPIO_Pin = SENSORS_I2C_SCL_PIN;
    
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;	       // 开漏输出
    
   GPIO_Init(SENSORS_I2C_SCL_PORT, &GPIO_InitStructure);
    
 	
    
   GPIO_InitStructure.GPIO_Pin = SENSORS_I2C_SDA_PIN;
    
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;	       // 开漏输出
    
   GPIO_Init(SENSORS_I2C_SDA_PORT, &GPIO_InitStructure);	
    
 	
    
 	
    
 }
    
  
    
  
    
 /** * @brief  I2C 工作模式配置
    
   * @param  无
    
   * @retval 无
    
   */
    
 static void I2C_Mode_Configu(void)
    
 {
    
   I2C_InitTypeDef  I2C_InitStructure; 
    
  
    
   /* I2C 配置 */
    
   I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    
 	
    
 	/* 高电平数据稳定,低电平数据变化 SCL 时钟线的占空比 */
    
   I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    
 	
    
   I2C_InitStructure.I2C_OwnAddress1 =I2Cx_OWN_ADDRESS7; 
    
   I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;
    
 	
    
 	/* I2C的寻址模式 */
    
   I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    
 	
    
 	/* 通信速率 */
    
   I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;
    
   
    
 	/* I2C1 初始化 */
    
   I2C_Init(SENSORS_I2Cx, &I2C_InitStructure);
    
   
    
 	/* 使能 I2C1 */
    
   I2C_Cmd(SENSORS_I2Cx, ENABLE);   
    
 }

②对读写函数的封装

在初始化完成之后,编写I2C 读写函数成为下一步的工作。这一功能模块与EERPOM相似,并主要依赖STM32标准库函数来完成对数据寄存器及其相关标志位的读写操作。可参考附录中的代码实现部分获取详细说明

复制代码
 /** * @brief   写数据到MPU6050寄存器
    
   * @param   
    
   * @retval  
    
   */
    
 void MPU6050_WriteReg(u8 reg_add,u8 reg_dat)
    
 {
    
 	I2C_ByteWrite(reg_dat,reg_add); 
    
 }
    
  
    
 /** * @brief   从MPU6050寄存器读取数据
    
   * @param   
    
   * @retval  
    
   */
    
 void MPU6050_ReadData(u8 reg_add,unsigned char* Read,u8 num)
    
 {
    
 	I2C_BufferRead(Read,reg_add,num);
    
 }
复制代码
 /** * @brief   写一个字节到I2C设备中
    
   * @param   
    
   *		@arg pBuffer:缓冲区指针
    
   *		@arg WriteAddr:写地址 
    
   * @retval  正常返回1,异常返回0
    
   */
    
 uint8_t I2C_ByteWrite(u8 pBuffer, u8 WriteAddr)
    
 {
    
   /* Send STRAT condition */
    
   I2C_GenerateSTART(SENSORS_I2Cx, ENABLE);
    
 	
    
 	I2CTimeout = I2CT_FLAG_TIMEOUT;
    
  
    
  
    
   /* Test on EV5 and clear it */
    
   while(!I2C_CheckEvent(SENSORS_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
    
   {
    
     if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback();
    
   } 
    
  
    
   /* Send slave address for write */
    
   I2C_Send7bitAddress(SENSORS_I2Cx, MPU6050_SLAVE_ADDRESS, I2C_Direction_Transmitter);
    
   
    
 	I2CTimeout = I2CT_FLAG_TIMEOUT;
    
   /* Test on EV6 and clear it */
    
   while(!I2C_CheckEvent(SENSORS_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) 
    
 	{
    
     if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback();
    
   }  
    
       
    
   /* Send the slave's internal address to write to */
    
   I2C_SendData(SENSORS_I2Cx, WriteAddr);
    
   
    
 	I2CTimeout = I2CT_FLAG_TIMEOUT;
    
   /* Test on EV8 and clear it */
    
   while(!I2C_CheckEvent(SENSORS_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
    
 	{
    
     if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback();
    
   } 
    
  
    
   /* Send the byte to be written */
    
   I2C_SendData(SENSORS_I2Cx, pBuffer); 
    
 	
    
 	I2CTimeout = I2CT_FLAG_TIMEOUT;
    
    
    
   /* Test on EV8 and clear it */
    
   while(!I2C_CheckEvent(SENSORS_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))	
    
 	{
    
     if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback();
    
   } 
    
 	
    
   /* Send STOP condition */
    
   I2C_GenerateSTOP(SENSORS_I2Cx, ENABLE);
    
 	
    
 	return 1; //正常返回1
    
 }
    
  
    
  
    
  
    
 /** * @brief   从I2C设备里面读取一块数据 
    
   * @param   
    
   *		@arg pBuffer:存放从slave读取的数据的缓冲区指针
    
   *		@arg WriteAddr:接收数据的从设备的地址
    
   *     @arg NumByteToWrite:要从从设备读取的字节数
    
   * @retval  正常返回1,异常返回0
    
   */
    
 uint8_t I2C_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead)
    
 {  
    
   I2CTimeout = I2CT_LONG_TIMEOUT;
    
 	
    
   while(I2C_GetFlagStatus(SENSORS_I2Cx, I2C_FLAG_BUSY)) // Added by Najoua 27/08/2008    
    
   {
    
     if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback();
    
    }
    
 	
    
   I2C_GenerateSTART(SENSORS_I2Cx, ENABLE);
    
   
    
 	I2CTimeout = I2CT_FLAG_TIMEOUT;
    
 	 
    
   /* Test on EV5 and clear it */
    
   while(!I2C_CheckEvent(SENSORS_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
    
 	{
    
     if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback();
    
    }
    
 	
    
   /* Send slave address for write */
    
   I2C_Send7bitAddress(SENSORS_I2Cx, MPU6050_SLAVE_ADDRESS, I2C_Direction_Transmitter);
    
  
    
 	I2CTimeout = I2CT_FLAG_TIMEOUT;
    
 	 
    
   /* Test on EV6 and clear it */
    
   while(!I2C_CheckEvent(SENSORS_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) 
    
 	{
    
     if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback();
    
    }
    
 	
    
   /* Clear EV6 by setting again the PE bit */
    
   I2C_Cmd(SENSORS_I2Cx, ENABLE);
    
  
    
   /* Send the slave's internal address to write to */
    
   I2C_SendData(SENSORS_I2Cx, ReadAddr);  
    
  
    
 	I2CTimeout = I2CT_FLAG_TIMEOUT;
    
 	 
    
   /* Test on EV8 and clear it */
    
   while(!I2C_CheckEvent(SENSORS_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
    
 	{
    
     if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback();
    
    }
    
 	
    
   /* Send STRAT condition a second time */  
    
   I2C_GenerateSTART(SENSORS_I2Cx, ENABLE);
    
   
    
 	I2CTimeout = I2CT_FLAG_TIMEOUT;
    
   /* Test on EV5 and clear it */
    
   while(!I2C_CheckEvent(SENSORS_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
    
 	{
    
     if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback();
    
    }
    
 		
    
   /* Send slave address for read */
    
   I2C_Send7bitAddress(SENSORS_I2Cx, MPU6050_SLAVE_ADDRESS, I2C_Direction_Receiver);
    
   
    
 	I2CTimeout = I2CT_FLAG_TIMEOUT;
    
 	 
    
   /* Test on EV6 and clear it */
    
   while(!I2C_CheckEvent(SENSORS_I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
    
 	{
    
     if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback();
    
    }
    
   
    
   /* While there is data to be read */
    
   while(NumByteToRead)  
    
   {
    
     if(NumByteToRead == 1)
    
     {
    
       /* Disable Acknowledgement */
    
       I2C_AcknowledgeConfig(SENSORS_I2Cx, DISABLE);
    
       
    
       /* Send STOP Condition */
    
       I2C_GenerateSTOP(SENSORS_I2Cx, ENABLE);
    
     }
    
  
    
     /* Test on EV7 and clear it */
    
     if(I2C_CheckEvent(SENSORS_I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED))  
    
     {      
    
       /* Read a byte from the slave */
    
       *pBuffer = I2C_ReceiveData(SENSORS_I2Cx);
    
  
    
       /* Point to the next location where the byte read will be saved */
    
       pBuffer++; 
    
       
    
       /* Decrement the read bytes counter */
    
       NumByteToRead--;        
    
     }   
    
   }
    
  
    
   /* Enable Acknowledgement to be ready for another reception */
    
   I2C_AcknowledgeConfig(SENSORS_I2Cx, ENABLE);
    
 	
    
 	return 1; //正常,返回1
    
 }

③MPU6050 的寄存器定义

MPU6050 包含多种不同的寄存器用于调节运行模式,并通过宏定义的方式将这些寄存器的地址以及位数精确记录在mpu6050.h 文件中

④初始化MPU6050

根据MPU6050 的寄存器功能定义,我们使用I2C 往寄存器写入特定的控制参数

复制代码
 /** * @brief   写数据到MPU6050寄存器
    
   * @param   
    
   * @retval  
    
   */
    
 void MPU6050_WriteReg(u8 reg_add,u8 reg_dat)
    
 {
    
 	I2C_ByteWrite(reg_dat,reg_add); 
    
 }
    
  
    
 /** * @brief   从MPU6050寄存器读取数据
    
   * @param   
    
   * @retval  
    
   */
    
 void MPU6050_ReadData(u8 reg_add,unsigned char* Read,u8 num)
    
 {
    
 	I2C_BufferRead(Read,reg_add,num);
    
 }
    
  
    
  
    
 /** * @brief   初始化MPU6050芯片
    
   * @param   
    
   * @retval  
    
   */
    
 void MPU6050_Init(void)
    
 {
    
   int i=0,j=0;
    
   //在初始化之前要延时一段时间,若没有延时,则断电后再上电数据可能会出错
    
   for(i=0;i<1000;i++)
    
   {
    
     for(j=0;j<1000;j++)
    
     {
    
       ;
    
     }
    
   }
    
 	MPU6050_WriteReg(MPU6050_RA_PWR_MGMT_1, 0x00);	     //解除休眠状态
    
 	MPU6050_WriteReg(MPU6050_RA_SMPLRT_DIV , 0x07);	    //陀螺仪采样率
    
 	MPU6050_WriteReg(MPU6050_RA_CONFIG , 0x06);	
    
 	MPU6050_WriteReg(MPU6050_RA_ACCEL_CONFIG , 0x01);	  //配置加速度传感器工作在4G模式
    
 	MPU6050_WriteReg(MPU6050_RA_GYRO_CONFIG, 0x18);     //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
    
 }

这段代码主要通过 MPU6050_ReadData 与 MPU6050_WriteRed 函数实现了对 I2C 底层读写的封装,并在 MPU6050_Init 过程中利用这些函数对 MPU6050 存储单元进行了参数设置以配置采样率与量程(分辨率)。

Gyroscope Output Rate / (1 + SMPLRT_DIV) = 1kHZ 设置采样频率为1Khz

这段代码首先通过MPU6050_ReadData 及MPU6050_WriteRed 函数实现了I2C 通信的底层读写功能,并将这些功能封装为独立的功能模块;随后,在MPU6050 初始化阶段(即MPU6050_Init 函数),系统将利用这些功能模块向目标寄存器发送相应的控制指令,并设置其采样率以及工作量程(即分辨率)参数。

⑤读传感器ID

初始化完成后, 可以通过利用读取其"WHO AM I"寄存器内容的手段来判断硬件是否正常运行. 其存储了ID编号为0x68的信息, 如图所示.

复制代码
 /** * @brief   读取MPU6050的ID
    
   * @param   
    
   * @retval  正常返回1,异常返回0
    
   */
    
 uint8_t MPU6050ReadID(void)
    
 {
    
 	unsigned char Re = 0;
    
     MPU6050_ReadData(MPU6050_RA_WHO_AM_I,&Re,1);    //读器件地址
    
 	if(Re != 0x68)
    
 	{
    
 		MPU_ERROR("MPU6050 dectected error!\r\n检测不到MPU6050模块,请检查模块与开发板的接线");
    
 		return 0;
    
 	}
    
 	else
    
 	{
    
 		MPU_INFO("MPU6050 ID = %d\r\n",Re);
    
 		return 1;
    
 	}
    
 		
    
 }

⑥读取原始数据

若传感器检测正常,就可以读取它数据寄存器获取采样数据

复制代码
 void SysTick_Handler(void)

    
 {
    
 	int i;
    
   
    
   for(i=0; i<TASK_DELAY_NUM; i++)
    
   {
    
     Task_Delay_Group[i] ++;                   //任务计时,时间到后执行
    
   }
    
   
    
   /* 处理任务0 */
    
   if(Task_Delay_Group[0] >= TASK_DELAY_0)     //判断是否执行任务0
    
   {
    
     Task_Delay_Group[0] = 0;                  //置0重新计时
    
     
    
     /* 任务0:翻转LED */
    
     LED2_TOGGLE;
    
   }
    
   
    
   /* 处理任务1 */
    
   if(Task_Delay_Group[1] >= TASK_DELAY_1)     //判断是否执行任务1
    
   {
    
     Task_Delay_Group[1] = 0;                  //置0重新计时
    
     
    
     /* 任务1:MPU6050任务 */
    
     if( ! task_readdata_finish )
    
     {
    
       MPU6050ReadAcc(Acel);
    
       MPU6050ReadGyro(Gyro);
    
       MPU6050_ReturnTemp(&Temp);
    
       
    
       task_readdata_finish = 1; //标志位置1,表示需要在主循环处理MPU6050数据
    
     }
    
   }
    
   
    
   /* 处理任务2 */
    
   //添加任务需要修改任务总数的宏定义 TASK_DELAY_NUM
    
   //并且添加定义任务的执行周期宏定义 TASK_DELAY_x(x就是一个编号),比如 TASK_DELAY_2
    
 }
复制代码
 /** * @brief   读取MPU6050的加速度数据
    
   * @param   
    
   * @retval  
    
   */
    
 void MPU6050ReadAcc(short *accData)
    
 {
    
     u8 buf[6];
    
     MPU6050_ReadData(MPU6050_ACC_OUT, buf, 6);
    
     accData[0] = (buf[0] << 8) | buf[1];
    
     accData[1] = (buf[2] << 8) | buf[3];
    
     accData[2] = (buf[4] << 8) | buf[5];
    
 }
    
  
    
 /** * @brief   读取MPU6050的角加速度数据
    
   * @param   
    
   * @retval  
    
   */
    
 void MPU6050ReadGyro(short *gyroData)
    
 {
    
     u8 buf[6];
    
     MPU6050_ReadData(MPU6050_GYRO_OUT,buf,6);
    
     gyroData[0] = (buf[0] << 8) | buf[1];
    
     gyroData[1] = (buf[2] << 8) | buf[3];
    
     gyroData[2] = (buf[4] << 8) | buf[5];
    
 }
    
  
    
 /** * @brief   读取MPU6050的原始温度数据
    
   * @param   
    
   * @retval  
    
   */
    
 void MPU6050ReadTemp(short *tempData)
    
 {
    
 	u8 buf[2];
    
     MPU6050_ReadData(MPU6050_RA_TEMP_OUT_H,buf,2);     //读取温度值
    
     *tempData = (buf[0] << 8) | buf[1];
    
 }
    
  
    
 /** * @brief   读取MPU6050的温度数据,转化成摄氏度
    
   * @param   
    
   * @retval  
    
   */
    
 void MPU6050_ReturnTemp(float *Temperature)
    
 {
    
 	short temp3;
    
 	u8 buf[2];
    
 	
    
 	MPU6050_ReadData(MPU6050_RA_TEMP_OUT_H,buf,2);     //读取温度值
    
   temp3= (buf[0] << 8) | buf[1];	
    
 	*Temperature=((double) temp3/340.0)+36.53;
    
  
    
 }

以下是改写后的文本

⑥main.c

复制代码
   ****************************************************************************** */
    
   
    
 #include "stm32f10x.h"
    
 #include "stm32f10x_it.h"
    
 #include "./systick/bsp_SysTick.h"
    
 #include "./led/bsp_led.h"
    
 #include "./usart/bsp_usart.h"
    
 #include "./mpu6050/mpu6050.h"
    
 #include "./i2c/bsp_i2c.h"
    
  
    
  
    
 /* MPU6050数据 */
    
 short Acel[3];
    
 short Gyro[3];
    
 float Temp;
    
  
    
 /** * @brief  主函数
    
   * @param  无  
    
   * @retval 无
    
   */
    
 int main(void)
    
 {
    
 	/* LED 端口初始化 */
    
 	LED_GPIO_Config();
    
 	/* 串口通信初始化 */
    
 	USART_Config();
    
  
    
 	//I2C初始化
    
 	I2C_Bus_Init();
    
 	//MPU6050初始化
    
 	MPU6050_Init();
    
   //检测MPU6050
    
 	if( MPU6050ReadID() == 0 )
    
 	{
    
 		printf("\r\n没有检测到MPU6050传感器!\r\n");
    
 		LED_RED;
    
 		while(1);	//检测不到MPU6050 会红灯亮然后卡死
    
 	}
    
 	
    
   /* 配置SysTick定时器和中断 */
    
   SysTick_Init(); //配置 SysTick 为 1ms 中断一次,在中断里读取传感器数据
    
   SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; //启动定时器
    
   
    
   
    
   while(1)
    
   {
    
     if( task_readdata_finish ) //task_readdata_finish = 1 表示读取MPU6050数据完成
    
     {
    
       
    
       printf("加速度:%8d%8d%8d",Acel[0],Acel[1],Acel[2]);
    
       
    
       printf("    陀螺仪%8d%8d%8d",Gyro[0],Gyro[1],Gyro[2]);
    
       
    
       printf("    温度%8.2f\r\n",Temp);
    
       
    
       task_readdata_finish = 0; // 清零标志位
    
       
    
     }
    
   }
    
   
    
 }
    
 /*********************************************END OF FILE**********************/

在本实验中未采用中断检测机制,在代码中定义了Task_Delay变量用于定时时间控制,在每次Systick中断触发时,在1ms的时间间隔内递减该变量值;当该变量归零时表明定时时间已至。在循环期间持续监测定时时间;一旦到时,则读取加速度、角速度及温度数据并发送至电脑端。

五、MPU6050—利用DMP 进行姿态解算

在上一小节中, 我们仅通过MPU6050获取原始数据. 对于具备相关知识的人士而言, 在深入研究后可以自行开发相应的算法. 并基于这些数据, 使用STM32进行姿态计算, 并根据结果输出相应的角度信息.

而由于MPU6050内部集成有DMP模块,则无需依赖STM32进行解算操作即可直接获取姿态角数据。同时也不需要深入理解解算原理即可完成运算操作非常方便本章将介绍如何利用DMP进行姿态角计算。在实验过程中所使用的主代码库是基于STM32F4开发并经过适当修改后移植到这里其中还包含了一些详细的使用说明请务必充分参考官方提供的所有资源

5.1 程序设计要点

  1. 本系统将提供I2C总线接口的读写功能、定时服务功能以及中断处理功能。
  2. 通过陀螺仪采集原始数据进行处理。
  3. 完成代码更新后进行程序运行以获取结果。

5.2 代码分析

官方驱动主要来源于MPL软件库(Motion Processing Library),移植该软件库时需要配置I2C读写接口模块、定时服务模块以及MPU6050数据更新标志位。若需向上位机发送调试信息,则需配置串口通信模块。

①I2C读写接口

MPL库的内部实现主要依赖于i2c_write及i2c_read函数,在文件"inv_mpu"中详细说明了它们的接口格式

这些接口遵循我们之前定义的I2C 读写操作规范,并且Sensors_I2C_ReadRegister 和 Sensors_I2C_WriteRegister 函数同样适用;因此可以直接采用宏替代。

②提供定时服务

MPL软件库包含延时和时间戳功能,并对这些功能有具体实现要求。软件库要求实现延迟函数delay_ms以支持毫秒级别延迟,并且必须提供一个名为get_ms的函数用于获取当前时间戳。这些接口的具体格式可在"inv_mpu.c"源代码中找到详细说明。

我们针对接口提供的Delay_ms和get_tick_count函数定义在bsp_SysTick.c文件中,并采用SysTick每隔一毫秒触发中断以实现计时功能

上述代码中的TimingDelay_Decrement 和TimeStamp_Increment 函数是在Ssistc 的中断服务函数中被调用的,请参考附录A。ssistc 被配置成每隔一毫秒触发一次中断事件。每次中断时会递减s\texttt{\_}\texttt{g\_ul\_ms\_ticks}变量的值并增加s\texttt{\_}\texttt{g\_ul\_ms\_ticks}变量的计数值。这些函数分别用于实现Delay_ms 函数所需的时间延迟以及通过s\texttt{\_}\texttt{g\_ul\_ms\-ticks}变量获取当前时间戳的目的。

提供串口调试接口

MPL代码库中的调试信息输出功能模块均位于log_stm32.c文件内。我们建议在这些函数中加入串行口通信接口,并配置相应的数据传输参数设置以确保信息能够正确传递至远方终端设备。

复制代码
 int fputcc(int ch)

    
 {
    
 		/* 发送一个字节数据到USART1 */
    
 		USART_SendData(DEBUG_USARTx, (uint8_t) ch);
    
 		
    
 		/* 等待发送完毕 */
    
 		while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);		
    
 	
    
 		return (ch);
    
 }
    
  
    
 void eMPL_send_quat(long *quat)
    
 {
    
     char out[PACKET_LENGTH];
    
     int i;
    
     if (!quat)
    
     return;
    
     memset(out, 0, PACKET_LENGTH);
    
     out[0] = '$';
    
     out[1] = PACKET_QUAT;
    
     out[3] = (char)(quat[0] >> 24);
    
     out[4] = (char)(quat[0] >> 16);
    
     out[5] = (char)(quat[0] >> 8);
    
     out[6] = (char)quat[0];
    
     out[7] = (char)(quat[1] >> 24);
    
     out[8] = (char)(quat[1] >> 16);
    
     out[9] = (char)(quat[1] >> 8);
    
     out[10] = (char)quat[1];
    
     out[11] = (char)(quat[2] >> 24);
    
     out[12] = (char)(quat[2] >> 16);
    
     out[13] = (char)(quat[2] >> 8);
    
     out[14] = (char)quat[2];
    
     out[15] = (char)(quat[3] >> 24);
    
     out[16] = (char)(quat[3] >> 16);
    
     out[17] = (char)(quat[3] >> 8);
    
     out[18] = (char)quat[3];
    
     out[21] = '\r';
    
     out[22] = '\n';
    
 		
    
     for (i=0; i<PACKET_LENGTH; i++) {
    
       fputcc(out[i]);
    
     }
    
 }

在代码中定义的fputcc 是我们自行编写的串口输出接口,在某种程度上其功能与被重定向的printf函数定义的fputc函数基本一致。此外,在同一个log_stm32.c文件中还存在一个名为_MLPrintLog 的辅助函数用于输出日志信息,并且还有一个辅助函数eMPL_send_data负责将原始数据发送至专用上位机设备进行显示。这些不同的实现单元都调用了我们的串口输出接口fputcc 进行相应的数据传输操作以确保系统正常运行。

MPU6050 的中断接口

与上一小节中的基础实验有所区别,在进行高效采样数据处理时,MPL代码库采用了基于MPU6050的INT中断源,并因此需要对相应的中断接口进行配置。

复制代码
 /// IO 线中断

    
 void EXTI_INT_FUNCTION (void)
    
 {
    
 //	MPU_DEBUG("intterrupt");
    
 	if(EXTI_GetITStatus(EXTI_LINE) != RESET) //确保是否产生了EXTI Line中断
    
 	{
    
     LED1_TOGGLE;
    
 		  /* Handle new gyro*/
    
 		gyro_data_ready_cb();
    
 		EXTI_ClearITPendingBit(EXTI_LINE);     //清除中断标志位
    
 	}  
    
 }

每当新数据生成时,在本系统中会触发中断服务函数的调用,在整个工程体系中设置了标志位来实现指示与保护功能以确保FIFO缓冲区的安全运行

复制代码
 void gyro_data_ready_cb(void)

    
 {
    
  
    
     hal.new_gyro = 1;
    
 }

main 函数执行流程

熟悉其移植接口后, 我们深入研究main函数以掌握如何利用MPL库分析姿态数据

该主函数篇幅较大,在原工程代码中还包含其他代码并带有不同的条件判断分支。支持磁场数据解算功能(此功能由MPU9150实现而MPU6050不具备),同时还有其他工作模式下的控制示例总结如下:

  1. 配置STM32硬件设置(包括Systick、LED、调试串口以及INT中断引脚等);
  2. 调用MPL库函数mpu_init设定传感器的基本工作模式(后续相关操作均基于MPL库函数执行);
  3. 调用inv_init_mpl函数初始化MPL软件库功能模块,在此之后才能正常执行数据解算;
  4. 设置必要的运算参数(如启用四元数运算功能inv_enable_quaternion以及选择6轴或9轴数据融合模式inv_enable_9x_sensor_fusion等);
  5. 配置传感器的工作模式(通过mpu_set_sensors函数设定驱动模式)、指定采样率(mpu_set_sample_rate函数调用)、配置传感器分辨率(inv_set_gyro_orientation_and_scale函数设置)等;
  6. 在所有初始化配置完成之后开始执行循环过程;
  7. 在循环体内持续接收串口输入数据:当接收到有效输入字符时将触发相应的工作状态切换操作;这部分功能主要实现上位机通过命令进行控制的功能(如开启或关闭加速度数据采集以及输出调试信息等);
  8. 在循环体内检测到新的传感器数据更新(当hal.new_gyro标志为真且DMP工作状态处于启用状态时),触发INT中断事件以及时执行相关处理代码;
  9. 使用dmp_read_fifo从FIFO缓冲区读取最新的传感器数据;这里定义的FIFO仅用于临时存储最新采集的数据包;
  10. 调用inv_build_gyro、inv_build_temp、inv_build_accel以及inv_build_quat等函数分别对角速度、温度测量值以及加速度向量进行计算,并将标志变量new_data标记为有效值状态;
  11. 在循环体内持续检查new_data状态标志位:当检测到该标志位被置位时将触发相应的处理操作;
  12. 根据当前采集到的数据更新所有的状态信息及数值结果;
  13. 向主机系统输出最新的测量数据包。

数据输出接口

在主程序中最后调用了read_from_mpl函数展示了如何使用MPL数据输出接口借助这些接口我们可以获取所需的数据

复制代码
 /* Get data from MPL.

    
  * TODO: Add return values to the inv_get_sensor_type_xxx APIs to differentiate
    
  * between new and stale data.
    
  */
    
 static void read_from_mpl(void)
    
 {
    
     long msg, data[9];
    
     int8_t accuracy;
    
     unsigned long timestamp;
    
     float float_data[3] = {0};
    
  
    
     MPU_DEBUG_FUNC();
    
     if (inv_get_sensor_type_quat(data, &accuracy, (inv_time_t*)&timestamp)) {
    
    /* Sends a quaternion packet to the PC. Since this is used by the Python
    
     * test app to visually represent a 3D quaternion, it's sent each time
    
     * the MPL has new data.
    
     */
    
     eMPL_send_quat(data);
    
  
    
     /* Specific data packets can be sent or suppressed using USB commands. */
    
     if (hal.report & PRINT_QUAT)
    
         eMPL_send_data(PACKET_DATA_QUAT, data);
    
     }
    
  
    
     if (hal.report & PRINT_ACCEL) {
    
     if (inv_get_sensor_type_accel(data, &accuracy,
    
         (inv_time_t*)&timestamp))
    
         eMPL_send_data(PACKET_DATA_ACCEL, data);
    
     }
    
     if (hal.report & PRINT_GYRO) {
    
     if (inv_get_sensor_type_gyro(data, &accuracy,
    
         (inv_time_t*)&timestamp))
    
         eMPL_send_data(PACKET_DATA_GYRO, data);
    
  
    
     }
    
 #ifdef COMPASS_ENABLED
    
     if (hal.report & PRINT_COMPASS) {
    
     if (inv_get_sensor_type_compass(data, &accuracy,
    
         (inv_time_t*)&timestamp))
    
         eMPL_send_data(PACKET_DATA_COMPASS, data);
    
     }
    
 #endif
    
     if (hal.report & PRINT_EULER) {
    
     if (inv_get_sensor_type_euler(data, &accuracy,
    
         (inv_time_t*)&timestamp))
    
         eMPL_send_data(PACKET_DATA_EULER, data);
    
  
    
     }
    
 		/*********发送数据到匿名四轴上位机**********/
    
     if(1)
    
     {
    
 				#ifdef USE_LCD_DISPLAY
    
  
    
 					char cStr [ 70 ];
    
  
    
 				#endif
    
  
    
 				unsigned long timestamp,step_count,walk_time;
    
  
    
 			
    
 				/*获取欧拉角*/
    
 			  if (inv_get_sensor_type_euler(data, &accuracy,(inv_time_t*)&timestamp))
    
 						{
    
 							float Pitch,Roll,Yaw;
    
 							Pitch =data[0]*1.0/(1<<16) ;
    
 							Roll = data[1]*1.0/(1<<16);
    
 							Yaw = data[2]*1.0/(1<<16);
    
 							
    
 							/*向匿名上位机发送姿态*/
    
 							Data_Send_Status(Pitch,Roll,Yaw);
    
 							/*向匿名上位机发送原始数据*/
    
 							Send_Data((int16_t *)&sensors.gyro.raw,(int16_t *)&sensors.accel.raw);
    
 							
    
 							#ifdef USE_LCD_DISPLAY
    
  
    
 								sprintf ( cStr, "Pitch :   %.4f   ",Pitch );	//inv_get_sensor_type_euler读出的数据是Q16格式,所以左移16位.
    
 								ILI9341_DispString_EN(30,90,cStr);
    
  
    
 								sprintf ( cStr, "Roll :   %.4f   ", Roll );	//inv_get_sensor_type_euler读出的数据是Q16格式,所以左移16位.
    
 								ILI9341_DispString_EN(30,110,cStr);
    
  
    
 								sprintf ( cStr, "Yaw :   %.4f   ", Yaw );	//inv_get_sensor_type_euler读出的数据是Q16格式,所以左移16位.
    
 								ILI9341_DispString_EN(30,130,cStr);
    
  
    
 								/*温度*/
    
 								mpu_get_temperature(data,(inv_time_t*)&timestamp); 
    
 								
    
 								sprintf ( cStr, "Temperature :   %.2f   ", data[0]*1.0/(1<<16) );	//inv_get_sensor_type_euler读出的数据是Q16格式,所以左移16位.
    
 								ILI9341_DispString_EN(30,150,cStr);
    
 							#endif
    
 						
    
 						}
    
 						
    
 					/*获取步数*/        
    
 				get_tick_count(&timestamp);
    
 				if (timestamp > hal.next_pedo_ms) {
    
  
    
 						hal.next_pedo_ms = timestamp + PEDO_READ_MS;
    
 						dmp_get_pedometer_step_count(&step_count);
    
 						dmp_get_pedometer_walk_time(&walk_time);
    
 					
    
 						#ifdef USE_LCD_DISPLAY
    
  
    
 							sprintf(cStr, "Walked steps :  %ld  steps over  %ld  milliseconds..",step_count,walk_time);
    
 						
    
 							ILI9341_DispString_EN(0,180,cStr);
    
 						#endif
    
  
    
 				}
    
 			}
    
  
    
  
    
     if (hal.report & PRINT_ROT_MAT) {
    
     if (inv_get_sensor_type_rot_mat(data, &accuracy,
    
         (inv_time_t*)&timestamp))
    
         eMPL_send_data(PACKET_DATA_ROT, data);
    
     }
    
     if (hal.report & PRINT_HEADING) {
    
     if (inv_get_sensor_type_heading(data, &accuracy,
    
         (inv_time_t*)&timestamp))
    
         eMPL_send_data(PACKET_DATA_HEADING, data);
    
     }
    
     if (hal.report & PRINT_LINEAR_ACCEL) {
    
     if (inv_get_sensor_type_linear_acceleration(float_data, &accuracy, (inv_time_t*)&timestamp)) {
    
     	MPL_LOGI("Linear Accel: %7.5f %7.5f %7.5f\r\n",
    
     			float_data[0], float_data[1], float_data[2]);                                        
    
      }
    
     }
    
     if (hal.report & PRINT_GRAVITY_VECTOR) {
    
         if (inv_get_sensor_type_gravity(float_data, &accuracy,
    
             (inv_time_t*)&timestamp))
    
         	MPL_LOGI("Gravity Vector: %7.5f %7.5f %7.5f\r\n",
    
         			float_data[0], float_data[1], float_data[2]);
    
     }
    
     if (hal.report & PRINT_PEDO) {
    
     unsigned long timestamp;
    
     get_tick_count(&timestamp);
    
     if (timestamp > hal.next_pedo_ms) {
    
         hal.next_pedo_ms = timestamp + PEDO_READ_MS;
    
         unsigned long step_count, walk_time;
    
         dmp_get_pedometer_step_count(&step_count);
    
         dmp_get_pedometer_walk_time(&walk_time);
    
         MPL_LOGI("Walked %ld steps over %ld milliseconds..\n", step_count,
    
         walk_time);
    
     }
    
     }
    
  
    
     /* Whenever the MPL detects a change in motion state, the application can
    
      * be notified. For this example, we use an LED to represent the current
    
      * motion state.
    
      */
    
     msg = inv_get_message_level_0(INV_MSG_MOTION_EVENT |
    
         INV_MSG_NO_MOTION_EVENT);
    
     if (msg) {
    
     if (msg & INV_MSG_MOTION_EVENT) {
    
         MPL_LOGI("Motion!\n");
    
     } else if (msg & INV_MSG_NO_MOTION_EVENT) {
    
         MPL_LOGI("No motion!\n");
    
     }
    
     }
    
 }

上述代码详细说明了采用inv_get_sensor_type_quat 、inv_get_sensor_type_accel 、 inv_get_sensor_type_gyro、inv_get_sensor_type_euler 及dmp_get_pedometer_step_count函数的具体实现过程,在实现过程中分别实现了获取四元数、加速度、角速度、欧拉角以及计步器数据的功能。

send_data()函数通过串口采用PYTHON标准的上位机数据格式发送数据给上位机系统。上位机系统接收到这些数据后会据此对三维模型进行相应的姿态调整。

六、MPU6050—使用第三方上位机

在获得数据之后,在本实验中将遵循"匿名飞控"上位机的数据格式要求上传数据。

6.1 程序设计要点:

了解上位机的通讯协议

根据协议格式上传数据到上位机

表格详细描述了两种数据帧分别为STATUS帧和SENSOR帧这些数据包的基本组成部分包括四个关键字段:每个数据包以两个字节的0xAA开头表示该字段用于标识类型接着是功能字长度字段指示后续主体数据所占的字节数最后是校验字段负责验证所有前部信息的总和

其 STATUS 帧负责将横滚角、俯仰角以及偏航角的数值(放大了 100 倍)发送给上位机;SENSOR 帧则负责传递加速度、 角速度以及磁场强度的原始数据。

计算出欧拉角后, 通过 Data.Send_Status 的方式将数据格式化后上传至远方;在获得这些数据之后立即通过 Send.Data 发送给远方处理。

利用匿名上位机显示如下:

写的比较粗糙有需要的同学可以私信我代码~

全部评论 (0)

还没有任何评论哟~