MPU6050传感器—姿态检测

news2024/11/24 14:20:18

本节主要介绍以下内容:

姿态检测的基本概念

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

MPU6050传感器介绍

实验:获取MPU6050原始数据

实验:移植官方DMP例程

一、姿态检测基本概念

1.1 姿态

在飞行器中,飞机姿态是非常重要的参数,以飞机自身的中心建立坐标系,当飞机绕坐标轴旋转的时候,会分别影响航偏角、横滚角及俯仰角。

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

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

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

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

假如我们知道飞机初始时是左上角的状态,只要想办法测量出基于原始状态的三个姿态角的变化量,再进行叠加,就可以获知它的实时姿态了。

1.2 坐标系

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

下图紫色是地理坐标系,红色是基于载体的载体坐标系,载体的姿态角就是根据载体坐标系与地理坐标系的夹角来确定的。

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

地球坐标系:以地球球心为原点,Z轴沿地球自转轴方向,XY轴在赤道平面内的坐标系。

地理坐标系:它的原点在地球表面(或运载体所在的点)Z轴沿当地地理垂线的方向(重力加速度方向)XY轴沿当地经纬线的切线方向。根据各个轴方向的不同,可选为“东北天”(x轴指向东Y指向北)、“东南天”、“西北天”等坐标系。这是我们日常生活中使用的坐标系,平时说的东南西北方向与这个坐标系东南西北的概念一致。

载体坐标系:载体坐标系以运载体的质心为原点,一般根据运载体自身结构方向构成坐标系,如Z轴上由原点指向载体顶部,Y轴指向载体头部,X轴沿载体两侧方向。上面说基于飞机建立的坐标系就是一种载体坐标系,可类比到汽车、舰船、人体、动物或手机等各种物体。

 地理坐标系与载体坐标系都以载体为原点,所以它们可以经过简单的旋转进行转换,载体的姿态角就是根据载体坐标系与地理坐标系的夹角来确定的。

假设初始状态中,飞机的Z轴、X轴及Y轴分别与地理坐标系的天轴、北轴、东轴平行。

当飞机绕自身的“Z”轴旋转,它会使自身的“Y”轴方向与地理坐标系的“南北”方向偏离一定角度,该角度就称为偏航角(Yaw)

当载体绕自身的“X”轴旋转,它会使自身的“Z”轴方向与地理坐标系的“天地”方向偏离一定角度,该角度称为俯仰角(Pitch)

当载体绕自身的“Y”轴旋转,它会使自身的“X”轴方向与地理坐标系的“东西”方向偏离一定角度,该角度称为横滚角。

姿态角的关系

坐标系间的旋转角度

说明

载体自身旋转

偏航角(Yaw)

Y轴与北轴的夹角

绕载体Z轴旋转可改变

俯仰角(Pitch)

Z轴与天轴的夹角

绕载体X轴旋转可改变

横滚角(Roll)

X轴与东轴的夹角

绕载体Y轴旋转可改变

使用陀螺仪检测角度

陀螺仪最直观的角度检测器,它可以检测物体绕坐标轴转动的“角速度”,如同将速度对时间积分可以求出路程一样,将角速度对时间积分就可以计算出旋转的“角度”。 

陀螺仪检测的缺陷

由于陀螺仪测量角度时使用积分,会存在积分误差,若积分时间Dt越小,误差就越小。这十分容易理解,例如计算路程时,假设行车时间为1小时,我们随机选择行车过程某个时刻的速度Vt乘以1小时,求出的路程误差是极大的,因为行车的过程中并不是每个时刻都等于该时刻速度的,如果我们每5分钟检测一次车速,可得到Vt1Vt2Vt3-Vt1212个时刻的车速,对各个时刻的速度乘以时间间隔(5分钟),并对这12个结果求和,就可得出一个相对精确的行车路程了,不断提高采样频率,就可以使积分时间Dt变小,降低误差。  

同样地,提高陀螺仪传感器的采样频率,即可减少积分误差,目前非常普通的陀螺仪传感器的采样频率都可以达到8KHz,已能满足大部分应用的精度要求。

更难以解决的是器件本身误差带来的问题。例如,某种陀螺仪的误差是0.1/秒,当陀螺仪静止不动时,理想的角速度应为0,无论它静止多久,对它进行积分测量得的旋转角度都是0,这是理想的状态;而由于存在0.1/秒的误差,当陀螺仪静止不动时,它采样得的角速度一直为0.1/秒,若静止了1分钟,对它进行积分测量得的旋转角度为6度,若静止了1小时,陀螺仪进行积分测量得的旋转角度就是360度,即转过了一整圈,这就变得无法忍受了。只有当正方向误差和负方向误差能正好互相抵消的时候,才能消除这种累计误差。

利用加速度计检测角度

由于直接用陀螺仪测量角度在长时间测量时会产生累计误差,因而我们又引入了检测倾角的传感器。

测量倾角最常见的例子是建筑中使用的水平仪,在重力的影响下,水平仪内的气泡能大致反映水柱所在直线与重力方向的夹角关系,利用T字型水平仪,可以检测横滚角与俯仰角,但是偏航角是无法以这样的方式检测的。

在电子设备中,一般使用加速度传感器来检测倾角,它通过检测器件在各个方向的形变情况而采样得到受力数据,根据F=ma转换,传感器直接输出加速度数据,因而被称为加速度传感器。由于地球存在重力场,所以重力在任何时刻都会作用于传感器,当传感器静止的时候(实际上加速度为0),传感器会在该方向检测出加速度g,不能认为重力方向测出的加速度为g,就表示传感器在该方向作加速度为g的运动。

当传感器的姿态不同时,它在自身各个坐标轴检测到的重力加速度是不一样的,利用各方向的测量结果,根据力的分解原理,可求出各个坐标轴与重力之间的夹角

因为重力方向是与地理坐标系的“天地”轴固连的,所以通过测量载体坐标系各轴与重力方向的夹角即可求得它与地理坐标系的角度旋转关系,从而获知载体姿态。

由于这种倾角检测方式是利用重力进行检测的,它无法检测到偏航角(Yaw),原理跟T字型水平仪一样,无论如何设计水平仪,水泡都无法指示这样的角度。

另一个缺陷是加速度传感器并不会区分重力加速度与外力加速度,当物体运动的时候,它也会在运动的方向检测出加速度,特别在震动的状态下,传感器的数据会有非常大的数据变化,此时难以反应重力的实际值。

磁场检测

为了弥补加速度传感器无法检测偏航角(Yaw)的问题,我们再引入磁场检测传感器,它可以检测出各个方向上的磁场大小,通过检测地球磁场,它可实现指南针的功能,所以也被称为电子罗盘。由于地磁场与地理坐标系的“南北”轴固联,利用磁场检测传感器的指南针功能,就可以测量出偏航角(Yaw)了。

磁场检测器的缺陷

与指南针的缺陷一样,使用磁场传感器会受到外部磁场干扰,如载体本身的电磁场干扰,不同地理环境的磁铁矿干扰等等。

GPS检测

使用GPS可以直接检测出载体在地球上的坐标,假如载体在某时刻测得坐标为A,另一时刻测得坐标为B,利用两个坐标即可求出它的航向,即可以确定偏航角,且不受磁场的影响,但这种检测方式只有当载体产生大范围位移的时候才有效(GPS民用精度大概为10米级)

姿态融合与四元数

        可以发现,使用陀螺仪检测角度时,在静止状态下存在缺陷,且受时间影响,而加速度传感器检测角度时,在运动状态下存在缺陷,且不受时间影响,刚好互补。假如我们同时使用这两种传感器,并设计一个滤波算法,当物体处于静止状态时,增大加速度数据的权重,当物体处于运动状时,增大陀螺仪数据的权重,从而获得更准确的姿态数据。

        同理,检测偏航角,当载体在静止状态时,可增大磁场检测器数据的权重,当载体在运动状态时,增大陀螺仪和GPS检测数据的权重。这些采用多种传感器数据来检测姿态的处理算法被称为姿态融合

        在姿态融合解算的时候常常使用“四元数”来表示姿态,它由三个实数及一个虚数组成,因而被称之为四元数。使用四元数表示姿态并不直观,但因为使用欧拉角(即前面说的偏航角、横滚角及俯仰角)表示姿态的时候会有“万向节死锁”问题,且运算比较复杂,所以一般在数据处理的时候会使用四元数,处理完毕后再把四元数转换成欧拉角。

        也就是说,四元数是姿态的另一种表示方式,感兴趣的话可自行查阅相关资料。

二、传感器工作原理

2.1 工作原理

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

        敏感元件直接感受被测物理量,并输出与该物理量有确定关系的信号,经过转换元件将该物理量信号转换为电信号,变换电路对转换元件输出的电信号进行放大调制,最后输出容易检测的电信号量。

        例如,温度传感器可把温度量转化成电压信号量输出,且温度值与电压值成比例关系,我们只要使用ADC测量出电压值,并根据转换关系即可求得实际温度值。而前文提到的陀螺仪、加速度及磁场传感器也是类似的,它们检测的角速度、加速度及磁场强度与电压值有确定的转换关系。

2.2. 传感器参数

参数

说明

线性误差

指传感器测量值与真实物理量值之间的拟合度误差。

分辨率

指传感器可检测到的最小物理量的单位。

采样频率

指在单位时间内的采样次数。

 其中误差与分辨率是比较容易混淆的概念,以使用尺子测量长度为例,误差就是指尺子准不准,使用它测量出10厘米,与计量机构标准的10厘米有多大区别,若区别在5毫米以内,我们则称这把尺子的误差为5毫米。而分辨率是指尺子的最小刻度值,假如尺子的最小刻度值为1厘米,我们称这把尺子的分辨率为1厘米,它只能用于测量厘米级的尺寸,对于毫米级的长度,这就无法用这把尺子进行测量了。如果把尺子加热拉长,尺子的误差会大于5毫米,但它的分辨率仍为1厘米,只是它测出的1厘米值与真实值之间差得更远了。

2.3. 物理量的表示方法

大部分传感器的输出都是与电压成比例关系的,电压值一般采用ADC来测量,而ADC一般有固定的位数,如8ADC12ADC等,ADC的位数会影响测量的分辨率及量程。

假设用一个2位的ADC来测量长度,2位的ADC最多只能表示0123这四个数,假如它的分辨率为20厘米,那么它最大的测量长度为60厘米,假如它的分辨率为10厘米,那么它的最大测量长度为30厘米,由此可知,对于特定位数的ADC,量程和分辨率不可兼得。

在实际应用中,常常直接用ADC每位表征的物理量值来表示分辨率,如每位代表20厘米,我们称它的分辨率为1LSB/20cm,它等效于5位表示1米:5LSB/m。其中的LSBLeast Significant Bit),意为最ADC的低有效位。

使用采样得到的ADC数值,除以分辨率,即可求取得到物理量。例如使用分辨率为5LSB/m、线性误差为0.1m的传感器进行长度测量,其ADC采样得到数据值为“20”,可计算知道该传感器的测量值为4米,而该长度的真实值介于3.9-4.1米之间。

三、MPU6050传感器介绍

3.1 MPU6050简介

MPU6050模块,它是一种六轴传感器模块,采用InvenSense公司的MPU6050作为主芯片,能同时检测三轴加速度、三轴陀螺仪(三轴角速度)的运动数据以及温度数据。

利用MPU6050芯片内部的DMP模块(Digital Motion Processor数字运动处理器),可对传感器数据进行滤波、融合处理,它直接通过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位,它们的量程及分辨率可选多种模式

 

从表中还可了解到传感器的加速度及陀螺仪的采样频率分别为1000Hz8000Hz,它们是指加速度及角速度数据的采样频率,我们可以使用STM32控制器把这些数据读取出来然后进行姿态融合解算,以求出传感器当前的姿态(即求出偏航角、横滚角、俯仰角)

而如果我们使用传感器内部的DMP单元进行解算,它可以直接对采样得到的加速度及角速度进行姿态解算,解算得到的结果再输出给STM32控制器,即STM32无需自己计算,可直接获取偏航角、横滚角及俯仰角,该DMP每秒可输出200次姿态数据。

3.3 引脚说明

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

        其中的SDA/SCL、XDA/XCL 通讯引脚分别为两组I2C 信号线。当模块与外部主机通讯时,使用SDA/SCL,如与STM32 芯片通讯;;而XDA/XCL 则用于MPU6050 芯片与其它I2C 传感器通讯时使用,例如使用它与磁场传感器连接,MPU6050 模块可以把从主机SDA/SCL 接收的数据或命令通过XDA/XCL 引脚转发到磁场传感器中。但实际上这种功能比较鸡肋,控制麻烦且效率低,一般会直接把磁场传感器之类的I2C 传感器直接与MPU6050 挂载在同一条总线上(即都连接到SDA/SCL),使用主机直接控制。

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

4.1 硬件设计 

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

        它的硬件非常简单, SDA 与SCL 被引出方便与外部I2C 主机连接,看图中的右上角,可知该模块的I2C 通讯引脚SDA 及SCL 已经连接了上拉电阻,因此它与外部I2C 通讯主机通讯时直接使用导线连接起来即可;而MPU6050 模块与其它传感器通讯使用的XDA、XCL 引脚没有接上拉电阻,要使用时需要注意。模块自身的I2C 设备地址可通过AD0 引脚的电平控制,当AD0 接地时,设备地址为0x68(七位地址),当AD0 接电源时,设备地址为0x69(七位地址)。另外,当传感器有新数据的时候会通过INT 引脚通知STM32。

        由于MPU6050 检测时是基于自身中心坐标系的,它表示的坐标系及旋转符号标出了MPU6050 传感器的XYZ 轴的加速度有角速度的正方向。所以在安装模块时,需要考虑它与所在设备的坐标系统的关系。

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

使用硬件I2C时不能与液晶屏同时使用,因为FSMC的NADV 与I2C1 的SDA 是同一个引脚,互相影响了,例程都默认使用软件I2C来驱动MPU6050,底层的软件I2C驱动跟EEPROM基本本一致,本章中重点讲述上层的MPU6050 应用及接口。

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 寄存器写入控制参数,设置了MPU6050 的采样率、量程(分辨率)。

 

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

 

这段代码首先使用MPU6050_ReadData 及MPU6050_WriteRed 函数封装了I2C 的底层读写驱动,接下来用它们在MPU6050_Init 函数中向MPU6050 寄存器写入控制参数,设置了MPU6050 的采样率、量程(分辨率)。

⑤读传感器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;

}

其中前以上三个函数分别用于读取三轴加速度、角速度及温度值,这些都是原始的ADC数值(16 位长),对于加速度和角速度,把读取得的ADC值除以分辨率,即可求得实际物理量数值。最后一个函数MPU6050_ReturnTemp 展示了温度ADC值与实际温度值间的转换,它是根据MPU6050 的说明给出的转换公式进行换算的,注意陀螺仪检测的温度会受自身芯片发热的影响,严格来说它测量的是自身芯片的温度,所以用它来测量气温是不太准确的。对于加速度和角速度值我们没有进行转换,在后面我们直接利用这些数据交给DMP 单元,求解出姿态角。

⑥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**********************/

本实验中控制MPU6050 并没有使用中断检测,我们是利用Systick 定时器进行计时,隔一段时间读取MPU6050 的数据寄存器获取采样数据的,代码中使用Task_Delay 变量来控制定时时间,在Systick 中断里会每隔1ms 对该变量值减1,所以当它的值为0 时表示定时时间到。就在whlie 循环里判断定时时间,定时时间到后就读取加速度、角速度及温度值,并使用串口打印信息到电脑端。

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

        上一小节我们仅利用MPU6050 采集了原始的数据,如果您对姿态解算的算法深有研究,可以自行编写姿态解算的算法,并利用这些数据,使用STM32 进行姿态解算,解算后输出姿态角。

        而由于MPU6050 内部集成了DMP,不需要STM32 参与解算,可直接输出姿态角,也不需要对解算算法作深入研究,非常方便,本章讲解如何使用DMP 进行解算。实验中使用的代码主体是从MPU6050 官方提供的驱动《motion_driver_6.12》移植过来的,该资料包里提供了基STM32F4 控制器的源代码(本工程正是利用该代码移植到STM32F1 上的)及使用python 语言编写的上位机,资料中还附带了说明文档,请您充分利用官方自带的资料学习。

5.1 程序设计要点

  1. 提供I2C读写接口、定时服务及INT中断处理;
  2. 从陀螺仪中获取原始数据并处理
  3. 更新代码并输出

5.2 代码分析 

        官方驱动主要是MPL软件库(Motion Processing Library),要移植该软件库我们需要为它提供I2C读写接口、定时服务以及MPU6050的数据更新标志。若需要输出调试信息到上位机,还需要提供串口接口。

①I2C读写接口

        MPL库的内部对I2C读写时都使用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 函数是在Systick 的中断服务函数中被调用的,见代码清单 50-12。systick 被配置为每毫秒产生一次中断,而每次中断中会对TimingDelay 变量减1,对g_ul_ms_ticks 变量加1。它们分别用于Delay_ms 函数利用TimingDelay 的值进行阻塞延迟,而get_tick_count 函数获取的时间戳即g_ul_ms_ticks的值。

提供串口调试接口 

MPL 代码库的调试信息输出函数都集中到了log_stm32.c 文件中,我们可以为这些函数提供串口输出接口,以便把这些信息输出到上位机

剩下的明天更新!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1405850.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot打包成Docker镜像

SpringBoot打包成Docker镜像 1、第一种方式 1.1 编写一个springboot项目并且打包成jar包 package com.example.demo.controller;import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;RestContr…

Python基础之数据库操作

一、安装第三方库PyMySQL 1、在PyCharm中通过 【File】-【setting】-【Python Interpreter】搜索 PyMySQL进行安装 2、通过PyCharm中的 Terminal 命令行 输入: pip install PyMySQL 注&#xff1a;通过pip安装&#xff0c;可能会提示需要更新pip&#xff0c;这时可执行&#…

【趣味游戏-08】20240123点兵点将点到谁就是谁(列表倒置reverse)

背景需求&#xff1a; 上个月&#xff0c;看到大4班一个孩子在玩“点兵点将点到谁就是谁”的小游戏&#xff0c;他在桌上摆放两排奥特曼卡片&#xff0c;然后点着数“点兵点将点到谁就是谁”&#xff0c;第10次点击的卡片&#xff0c;拿起来与同伴的卡片进行交换。他是从第一排…

怎么把一个已经压缩好的大容量的压缩包,分卷后发给别人

环境&#xff1a; Win10 专业版 7Z 360压缩 问题描述&#xff1a; 怎么把一个已经压缩好的大压缩包&#xff0c;分卷 解决方案&#xff1a; 使用压缩软件&#xff1a;许多常用的压缩软件&#xff0c;如WinRAR、7-Zip等&#xff0c;都支持将大的压缩包分卷压缩。您可以使…

归一化是是什么意思,为什么要归一化

归一化 归一化是指将数据转换为标准尺度或相对比例的过程。在数据处理中&#xff0c;归一化的目标是使数据具有统一的尺度&#xff0c;以便更好地适应模型的训练和提高模型性能。归一化通常是通过线性变换将数据映射到一个特定的范围或分布。 为什么要进行归一化&#xff1f; …

7.12、中间人攻击(ARP欺骗)

一、ARP协议原理 地址解析协议(Address Resolution Protocol&#xff0c;ARP)&#xff0c;负责把目的主机的IP 地址解析成目的MAC地址&#xff0c;地址解析的目标就是发现逻辑地址与物理地址的映射关系。网络中的计算机、交换机、路由器等都会定期维护自己的ARP缓存表。 为什么…

智谱AI官网再升级,GLM-4,智能体,AI作图长文档全部搞定

创建智能体 智能体体验中心 可以看到智谱AI也推出了自己的智能体&#xff0c;并且官方内置了丰富多样的智能体供大家免费体验。 GLM-4 原生支持自动联网、图片生成、数据分析等复杂任务&#xff0c;现开放体验中&#xff0c;快来开启更多精彩。写一篇《繁花》的影评&#xf…

Linux的例行性工作(计划任务)

目录 一、单一执行的例行性任务--at&#xff08;一 次性&#xff09; 1、安装 2、启动服务 3、at命令详解 1&#xff09;格式 2&#xff09;参数 3&#xff09;时间格式 4、实例 二、循环执行的例行性任务-- crontab&#xff08;周期性&#xff09; 1、crontd服务 2…

嵌入式未来发展的一些建议

嵌入式工程师分布在各行各业上面&#xff0c;这其中包括了消费电子、工业电子、汽车电子和军用电子等。 从功能上面看&#xff0c;嵌入式本身包括了51、STM32、MCU、SOC、SOCbaseband等很多形式。 从开发的结构上看&#xff0c;有些同学专注于底层&#xff0c;比如boot&#xf…

【GitHub项目推荐--一个简单的绘图应用程序(Rust + GTK4)】【转载】

一个用 Rust 和 GTK4 编写的简单的绘图应用程序来创建手写笔记。 Rnote 旨在成为一个简单但实用的笔记应用程序&#xff0c;用于手绘或注释图片或文档。它最终能够导入/导出各种媒体文件格式。而且输出的作品是基于矢量的&#xff0c;这使其在编辑和更改内容时非常灵活。 地址…

Anaconda + Tensorflow 安装及跑通例子 - 2024年1月20日

前言 我尝试了Tensorflow官网的方式进行安装&#xff0c;但是报错了。至于原因&#xff0c;我猜测和网络情况有关。于是尝试通过百度和B站&#xff0c;去看其他人的安装方式。总结下来&#xff0c;直接用Anaconda来安装最为方便。网络上也有不少用 Anaconda 的&#xff0c;但是…

Linux:FTP vs SSH

一&#xff0c;FTP FTP是一种文件下载协议&#xff0c;相比SSH&#xff0c;其安全性较低&#xff0c;在文件传输时并未做加密处理。登录FTP服务器的方法有2种&#xff1a;一种是FTP图形客户端&#xff08;可以用于windows电脑连接linux系统&#xff09;&#xff1b;另一种是使…

【设计模式】阿里终面:你觉得这个例子是策略模式吗?

什么是策略模式&#xff1f; 策略模式&#xff0c;举几个贴近生活的例子&#xff1a;当我们出行的时候&#xff0c;不同的出行方式就是不同的策略&#xff0c;例如走路、开车、骑自行车、坐飞机、坐邮轮等等&#xff0c;每一种出行方式都代表着不同的费用和时间&#xff1b;当…

E - Souvenir(图论典型例题)

思路&#xff1a;对于有很多询问的题&#xff0c;一般都是先初始化。我们求出每个点到其他点的最短路径以及相同路径下最大的价值和即可。 代码&#xff1a; #include <bits/stdc.h> #define pb push_back #define a first #define b second using namespace std; type…

模型之气体的行为

气体的行为 “探索气体动理论&#xff1a;分子运动与温度的统计关系” 气体动理论由丹尼尔•伯努利在1738年提出&#xff0c;后来又由麦克斯韦、玻尔兹曼等人在19世纪后半叶推进。根据这种理论&#xff0c;气体是由运动着的分子组成的&#xff0c;气体的许多性质——如温度和…

C++大学教程(第九版)6.38汉诺塔问题

文章目录 题目代码运行截图 题目 (汉诺塔问题)在这一章中大家了解了既可以用递归方法又可以用迭代方法很容易实现的函数。不过&#xff0c;在这道练习题中&#xff0c;我们提出的问题若用递归来解决&#xff0c;则尽显递归之优雅:若用迭代来实现&#xff0c;恐怕没那么容易。 …

Shell脚本的编程规范和变量类型

一. 了解编程 1.程序编程风格 面向过程语言 开发的时候 需要一步一步执行 问题规模小&#xff0c;可以步骤化&#xff0c;按部就班处理 以指令为中心&#xff0c;数据服务于指令 C&#xff0c;shell 面向对象语言 开发的时候 将任务当成一个整体 将编程看成是一个…

云原生全栈监控解决方案(全面详解)

【作者】JasonXu 前言 当前全球企业云化、数字化进程持续加速&#xff0c;容器、微服务等云原生技术在软件架构中快速渗透&#xff0c;IT 架构云化、复杂化持续驱动性能监控市场。企业云化、数字化持续转型&#xff0c;以及为了考虑系统的弹性、效率&#xff0c;企业软件开发中…

《小学生作文辅导》期刊投稿邮箱

《小学生作文辅导》是国家新闻出版总署批准的正规教育类期刊&#xff0c;适用于全国各小学语文老师事业单位及个人&#xff0c;具有原创性的学术理论、工作实践、科研成果和科研课题及相关领域等人员评高级职称时的论文发表&#xff08;单位有特殊要求除外&#xff09;。 栏目…

RTDETR 引入 超越自注意力:面向医学图像分割的可变形大卷积核注意力

医学图像分割在转换器模型的应用下取得了显著的进展,这些模型擅长捕捉广泛的上下文和全局背景信息。然而,这些模型随着标记数量的平方成比例增长的计算需求限制了它们的深度和分辨率能力。大多数当前的方法通过逐层处理D体积图像数据(称为伪3D),在处理过程中错过了关键的跨…