提示:STM32F103读取MPU6050原始数据,并通过串口助手显示,文件下载免费。
前言
MPU6050的使用对于初学者来说,如果没有详细的教程资料学习了解,便想利用MPU6050来制作飞控,平衡小车,穿戴设备等,这毫无疑问是非常难的。自己刚学习MPU6050的时候,苦于没有直接现成详细的原始数据获取教程,一上手的便是DMP库获取姿态数据,对于MPU6050了解甚少的我而言,这无疑是个挑战,这其中的心酸可谓是一个苦。为了便于自己以后能够快速掌握,写CSDN文章或许是一个最佳办法,分析自身学习思路的同时,利于资源共享。
一、MPU6050简介
MPU6050内部整合了三轴MEMS陀螺仪、三轴MEMS加速度计以及一个可扩展的数字运动处理器DMP(Digital Motion Processor),而且还可以连接一个第三方数字传感器(如磁力计),这样的话,就可以通过IIC接口输出一个9轴信号(链接第三方数字传感器才可以输出九轴信号,否则只有六轴信号)。更加方便的是,有了DMP,可以结合InvenSense公司提供的运动处理资料库,实现姿态解算。通过自带的DMP,可以通过IIC接口输出9轴融合演算的数据,大大降低了运动处理运算对操作系统的负荷,同时也降低了开发难度。其实,简单一句话说,陀螺仪就是测角速度的,加速度传感器就是测角加速度的,二者数据通过算法就可以得到PITCH、YAW、ROLL角了。 个人认为MPU6050的学习,应该遵循由浅入深,由易入难的学习原则。应当先掌握原始数据的获取,其次学会使用一阶滤波,卡尔曼滤波,或者DMP数据处理器等方法来获取姿态脚。
二、底层文件配置详解
1.IIC通信部分文件内容
MPU6050是通过IIC通信来与主机实现信息通信,所以本次讲解也将自下而上来讲,即底层到顶层。由于软件模拟IIC通信更加稳定,所以选用PB6和PB7作为SCL和SDA。下图包含的头文件无需过多理会,我们须知IIC通信,至少包含起始信号,结束信号,主机应答信号,从机应答信号,写数据,读数据。下面内容除了包含这些内容,还涉及SDA和SCL数据线所使用的管脚的初始化程序,写单个字节和读单个字节。如下为MPU_IIC.c
#include "Mpu_IIC.h"
#include "Pin_Init.h"
#include "Delay_time.h"
#include "MPU6050.h"
void MPU_IIC_Delay(void)
{
delay_us(2);
}
void MPU_IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7);
}
void MPU_IIC_Start(void)
{
MPU_SDA_OUT();
MPU_IIC_SDA=1;
MPU_IIC_SCL=1;
MPU_IIC_Delay();
MPU_IIC_SDA=0;
MPU_IIC_Delay();
MPU_IIC_SCL=0;
}
void MPU_IIC_Stop(void)
{
MPU_SDA_OUT();
MPU_IIC_SCL=0;
MPU_IIC_SDA=0;
MPU_IIC_Delay();
MPU_IIC_SCL=1;
MPU_IIC_SDA=1;
MPU_IIC_Delay();
}
u8 MPU_IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
MPU_SDA_IN();
MPU_IIC_SDA=1;
MPU_IIC_Delay();
MPU_IIC_SCL=1;
MPU_IIC_Delay();
while(MPU_READ_SDA)
{
ucErrTime++;
if(ucErrTime>100)
{
MPU_IIC_Stop();
return 1;
}
MPU_IIC_Delay();
}
MPU_IIC_SCL=0;
return 0;
}
void MPU_IIC_Ack_Or(u8 i)
{
MPU_IIC_SCL=0;
MPU_SDA_OUT();
if(i==0) MPU_IIC_SDA=0;
else MPU_IIC_SDA=1;
MPU_IIC_Delay();
MPU_IIC_SCL=1;
MPU_IIC_Delay();
MPU_IIC_SCL=0;
}
void MPU_IIC_Send_Byte(u8 dat)
{
u8 t;
MPU_SDA_OUT();
MPU_IIC_SCL=0;
for(t=0;t<8;t++)
{
if((dat&0x80)>>7)
MPU_IIC_SDA=1;
else MPU_IIC_SDA=0;
dat<<=1;
MPU_IIC_Delay();
MPU_IIC_SCL=1;
MPU_IIC_Delay();
MPU_IIC_SCL=0;
MPU_IIC_Delay();
}
}
u8 MPU_IIC_Read_Byte(u8 ack)
{
u8 i,receive=0;
MPU_SDA_IN();
for(i=0;i<8;i++ )
{
MPU_IIC_SCL=0;
MPU_IIC_Delay();
MPU_IIC_SCL=1;
receive<<=1;
if(MPU_READ_SDA)receive++;
MPU_IIC_Delay();
}
MPU_IIC_Ack_Or(ack);
return receive;
}
u8 MPU_Write_Byte(u8 reg,u8 data)
{
MPU_IIC_Start();
MPU_IIC_Send_Byte((MPU_ADDR<<1)|0);
if(MPU_IIC_Wait_Ack())
{
MPU_IIC_Stop();
return 1;
}
MPU_IIC_Send_Byte(reg);
MPU_IIC_Wait_Ack();
MPU_IIC_Send_Byte(data);
if(MPU_IIC_Wait_Ack())
{
MPU_IIC_Stop();
return 1;
}
MPU_IIC_Stop();
return 0;
}
u8 MPU_Read_Byte(u8 reg)
{
u8 res;
MPU_IIC_Start();
MPU_IIC_Send_Byte((MPU_ADDR<<1)|0);
MPU_IIC_Wait_Ack();
MPU_IIC_Send_Byte(reg);
MPU_IIC_Wait_Ack();
MPU_IIC_Start();
MPU_IIC_Send_Byte((MPU_ADDR<<1)|1);
MPU_IIC_Wait_Ack();
res=MPU_IIC_Read_Byte(1);
MPU_IIC_Stop();
return res;
}
IIC通信的头文件如下:MPU_IIC.h
#ifndef _MPU_IIC_H
#define _MPU_IIC_H
#include "Pin_Init.h"
#define MPU_SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
#define MPU_SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
#define MPU_IIC_SCL PBout(6)
#define MPU_IIC_SDA PBout(7)
#define MPU_READ_SDA PBin(7)
void MPU_IIC_Delay(void);
void MPU_IIC_Init(void);
void MPU_IIC_Start(void);
void MPU_IIC_Stop(void);
void MPU_IIC_Send_Byte(u8 dat);
u8 MPU_IIC_Read_Byte(u8 ack);
u8 MPU_IIC_Wait_Ack(void);
void MPU_IIC_Ack_Or(u8 i);
u8 MPU_Write_Byte(u8 reg,u8 data) ;
u8 MPU_Read_Byte(u8 reg);
#endif
2.MPU6050对应设置及数据获取内容
MPU6050对应的源文件内容如下(这部分内容和大部分编写者的内容可能想象,作了部分增删,使文件更简洁,便于理解。这部分主要是实现对MPU6050的初始化成功。其中涉及到MPU6050电源配置寄存器,加速度,角速度量程配置寄存器,以及采样频率,数字低通滤波配置。其中获取器件地址的部分内容是对IIC通信是否正确加以验证,同时对MPU6050是否有效的测试。MPU6050的IIC通信支持单字节读写,同样也支持多字节的读写。所以不需要怀疑IIC通信内容是否有问题):MPU6050.c
#include "Mpu_IIC.h"
#include "Pin_Init.h"
#include "MPU6050.h"
#include "Delay_time.h"
#include "Usart.h"
#include "stdio.h"
char tc[100];
u8 MPU_Init(void)
{
u8 res;
MPU_IIC_Init();
delay_ms(1000);
MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X80);
delay_ms(100);
MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X00);
MPU_Set_Gyro_Fsr(3);
MPU_Set_Accel_Fsr(0);
MPU_Set_Rate(50);
MPU_Write_Byte(MPU_INT_EN_REG,0X00);
MPU_Write_Byte(MPU_USER_CTRL_REG,0X00);
MPU_Write_Byte(MPU_FIFO_EN_REG,0X00);
MPU_Write_Byte(MPU_INTBP_CFG_REG,0X80);
res=MPU_Read_Byte(MPU_DEVICE_ID_REG);
sprintf(tc,"得到的器件ID返回值为0x%02x\r\n",res);
Send_string(tc);
if(res==MPU_ADDR)
{
MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X01);
MPU_Write_Byte(MPU_PWR_MGMT2_REG,0X00);
MPU_Set_Rate(50);
}
else return 1;
return 0;
}
u8 MPU_Set_Gyro_Fsr(u8 fsr)
{
return MPU_Write_Byte(MPU_GYRO_CFG_REG,fsr<<3);
}
u8 MPU_Set_Accel_Fsr(u8 fsr)
{
return MPU_Write_Byte(MPU_ACCEL_CFG_REG,fsr<<3);
}
u8 MPU_Set_LPF(u16 lpf)
{
u8 data=0;
if(lpf>=188)data=1;
else if(lpf>=98)data=2;
else if(lpf>=42)data=3;
else if(lpf>=20)data=4;
else if(lpf>=10)data=5;
else data=6;
return MPU_Write_Byte(MPU_CFG_REG,data);
}
u8 MPU_Set_Rate(u16 rate)
{
u8 data;
if(rate>1000)rate=1000;
if(rate<4)rate=4;
data=1000/rate-1;
data=MPU_Write_Byte(MPU_SAMPLE_RATE_REG,data);
return MPU_Set_LPF(rate/2);
}
short MPU_Get_Temperature(void)
{
u8 buf[2];
short raw;
float temp;
MPU_Read_Len(MPU_ADDR,MPU_TEMP_OUTH_REG,2,buf);
raw=((u16)buf[0]<<8)|buf[1];
temp=36.53+((double)raw)/340;
return temp*100;;
}
u8 MPU_Get_Gyroscope(short *gx,short *gy,short *gz)
{
u8 buf[6],res;
res=MPU_Read_Len(MPU_ADDR,MPU_GYRO_XOUTH_REG,6,buf);
if(res==0)
{
*gx=((u16)buf[0]<<8)|buf[1];
*gy=((u16)buf[2]<<8)|buf[3];
*gz=((u16)buf[4]<<8)|buf[5];
}
return res;
}
u8 MPU_Get_Accelerometer(short *ax,short *ay,short *az)
{
u8 buf[6],res;
res=MPU_Read_Len(MPU_ADDR,MPU_ACCEL_XOUTH_REG,6,buf);
if(res==0)
{
*ax=((u16)buf[0]<<8)|buf[1];
*ay=((u16)buf[2]<<8)|buf[3];
*az=((u16)buf[4]<<8)|buf[5];
}
return res;;
}
u8 MPU_Write_Len(u8 addr,u8 reg,u8 len,u8 *buf)
{
u8 i;
MPU_IIC_Start();
MPU_IIC_Send_Byte((addr<<1)|0);
if(MPU_IIC_Wait_Ack())
{
MPU_IIC_Stop();
return 1;
}
MPU_IIC_Send_Byte(reg);
MPU_IIC_Wait_Ack();
for(i=0;i<len;i++)
{
MPU_IIC_Send_Byte(buf[i]);
if(MPU_IIC_Wait_Ack())
{
MPU_IIC_Stop();
return 1;
}
}
MPU_IIC_Stop();
return 0;
}
u8 MPU_Read_Len(u8 addr,u8 reg,u8 len,u8 *buf)
{
MPU_IIC_Start();
MPU_IIC_Send_Byte((addr<<1)|0);
if(MPU_IIC_Wait_Ack())
{
MPU_IIC_Stop();
return 1;
}
MPU_IIC_Send_Byte(reg);
MPU_IIC_Wait_Ack();
MPU_IIC_Start();
MPU_IIC_Send_Byte((addr<<1)|1);
MPU_IIC_Wait_Ack();
while(len)
{ if(len==1)*buf=MPU_IIC_Read_Byte(1);
else *buf=MPU_IIC_Read_Byte(0);
len--;
buf++;
}
MPU_IIC_Stop();
return 0;
}
MPU6050对应的头文件 MPU6050.h
#include "stm32f10x.h"
#ifndef _MPU6050_H
#define _MPU6050_H
#define MPU_SELF_TESTX_REG 0X0D
#define MPU_SELF_TESTY_REG 0X0E
#define MPU_SELF_TESTZ_REG 0X0F
#define MPU_SELF_TESTA_REG 0X10
#define MPU_SAMPLE_RATE_REG 0X19
#define MPU_CFG_REG 0X1A
#define MPU_GYRO_CFG_REG 0X1B
#define MPU_ACCEL_CFG_REG 0X1C
#define MPU_MOTION_DET_REG 0X1F
#define MPU_FIFO_EN_REG 0X23
#define MPU_I2CMST_CTRL_REG 0X24
#define MPU_I2CSLV0_ADDR_REG 0X25
#define MPU_I2CSLV0_REG 0X26
#define MPU_I2CSLV0_CTRL_REG 0X27
#define MPU_I2CSLV1_ADDR_REG 0X28
#define MPU_I2CSLV1_REG 0X29
#define MPU_I2CSLV1_CTRL_REG 0X2A
#define MPU_I2CSLV2_ADDR_REG 0X2B
#define MPU_I2CSLV2_REG 0X2C
#define MPU_I2CSLV2_CTRL_REG 0X2D
#define MPU_I2CSLV3_ADDR_REG 0X2E
#define MPU_I2CSLV3_REG 0X2F
#define MPU_I2CSLV3_CTRL_REG 0X30
#define MPU_I2CSLV4_ADDR_REG 0X31
#define MPU_I2CSLV4_REG 0X32
#define MPU_I2CSLV4_DO_REG 0X33
#define MPU_I2CSLV4_CTRL_REG 0X34
#define MPU_I2CSLV4_DI_REG 0X35
#define MPU_I2CMST_STA_REG 0X36
#define MPU_INTBP_CFG_REG 0X37
#define MPU_INT_EN_REG 0X38
#define MPU_INT_STA_REG 0X3A
#define MPU_ACCEL_XOUTH_REG 0X3B
#define MPU_ACCEL_XOUTL_REG 0X3C
#define MPU_ACCEL_YOUTH_REG 0X3D
#define MPU_ACCEL_YOUTL_REG 0X3E
#define MPU_ACCEL_ZOUTH_REG 0X3F
#define MPU_ACCEL_ZOUTL_REG 0X40
#define MPU_TEMP_OUTH_REG 0X41
#define MPU_TEMP_OUTL_REG 0X42
#define MPU_GYRO_XOUTH_REG 0X43
#define MPU_GYRO_XOUTL_REG 0X44
#define MPU_GYRO_YOUTH_REG 0X45
#define MPU_GYRO_YOUTL_REG 0X46
#define MPU_GYRO_ZOUTH_REG 0X47
#define MPU_GYRO_ZOUTL_REG 0X48
#define MPU_I2CSLV0_DO_REG 0X63
#define MPU_I2CSLV1_DO_REG 0X64
#define MPU_I2CSLV2_DO_REG 0X65
#define MPU_I2CSLV3_DO_REG 0X66
#define MPU_I2CMST_DELAY_REG 0X67
#define MPU_SIGPATH_RST_REG 0X68
#define MPU_MDETECT_CTRL_REG 0X69
#define MPU_USER_CTRL_REG 0X6A
#define MPU_PWR_MGMT1_REG 0X6B
#define MPU_PWR_MGMT2_REG 0X6C
#define MPU_FIFO_CNTH_REG 0X72
#define MPU_FIFO_CNTL_REG 0X73
#define MPU_FIFO_RW_REG 0X74
#define MPU_DEVICE_ID_REG 0X75
#define MPU_ADDR 0X68
u8 MPU_Init(void);
u8 MPU_Write_Len(u8 addr,u8 reg,u8 len,u8 *buf);
u8 MPU_Read_Len(u8 addr,u8 reg,u8 len,u8 *buf);
u8 MPU_Set_Gyro_Fsr(u8 fsr);
u8 MPU_Set_Accel_Fsr(u8 fsr);
u8 MPU_Set_LPF(u16 lpf);
u8 MPU_Set_Rate(u16 rate);
u8 MPU_Set_Fifo(u8 sens);
short MPU_Get_Temperature(void);
u8 MPU_Get_Gyroscope(short *gx,short *gy,short *gz);
u8 MPU_Get_Accelerometer(short *ax,short *ay,short *az);
#endif
3.滴答定时器延时程序设定
考虑到大部分CSDN的作者使用的是正点原子提供的延时函数,这里我为了便于初学者了解,同时内容精简,我特地借鉴了下面这样的延时函数。 Delay_time.c
#include "Delay_time.h"
static u8 fac_us=0;
static u16 fac_ms=0;
void SysTick_Init(u8 SYSCLK)
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
fac_us=SYSCLK/8;
fac_ms=(u16)fac_us*1000;
}
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us;
SysTick->VAL=0x00;
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16)));
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;
SysTick->VAL =0X00;
}
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD=(u32)nms*fac_ms;
SysTick->VAL =0x00;
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16)));
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;
SysTick->VAL =0X00;
}
延时函数对应的头文件如下:Delay_time.h
#ifndef _Delay_time_H_
#define _Delay_time_H_
#include "stm32f10x.h"
void SysTick_Init(u8 SYSCLK);
void delay_ms(u16 nms);
void delay_us(u32 nus);
#endif
4.主函数
下面内容是主函数内容
#include "stm32f10x.h"
#include "Usart.h"
#include "Mpu_IIC.h"
#include "Pin_Init.h"
#include "MPU6050.h"
#include "Delay_time.h"
#include "stdio.h"
short Gyro_x,Gyro_y,Gyro_z;
short Accel_x,Accel_y,Accel_z;
u8 Transmit[100];
int main()
{
SysTick_Init(72);
Usart_Pin_Init() ;
Usart1_Init(115200);
while( MPU_Init()==1);
delay_ms(2000);
Send_string("串口正常\r\n");
while(1)
{
delay_ms(1000);
MPU_Get_Gyroscope(&Gyro_x,&Gyro_y,&Gyro_z);
sprintf(Transmit,"角速度:x %d\r\n角速度:y %d\r\n角速度:z %d\r\n",Gyro_x,Gyro_y,Gyro_z);
Send_string(Transmit);
MPU_Get_Accelerometer(&Accel_x,&Accel_y,&Accel_z);
sprintf(Transmit,"加速度:x %d\r\n加速度:y %d\r\n加速度:z %d\r\n",Accel_x,Accel_y,Accel_z);
Send_string(Transmit);
}
return 0;
}
总结
本篇文章由于只是STM32对MPU6050的直接读取,并未使用DMP库进行数据处理,但是也正是如此,整体代码量少,更利于初学者对MPU6050理解,更方便对DMP数据处理进行由浅入深的了解。后期,我将对DMP方式读取角度,获取姿态数据进行详细讲解,加深自己对MPU6050的了解。 考虑到CSDN关于直接读取原始数据的文件大部分都需付费,本人的程序代码将免费上传,需要下载的只需要关注即可。
|