• STC单片机15——MPU6050六轴数据融合,互补滤波,时间常数可调,可稳定运行,串口显示角度值


     51单片机用模拟IIC的方式读取MPU6050的原始数据,之后经过换算转成三轴加速度和三轴角速度。设定定时器,以固定的频率采集以上得到的数据,并加入互补滤波,去除加速度的噪声以及陀螺仪的零飘。注意,本次程序不能测量位移,只能测量对重力的倾角。

    完整资料打包:

    51单片机读取MPU6050角度(采用互补滤波_串口显示角度值)_51单片机读取mpu6050-单片机文档类资源-CSDN下载51单片机读取MPU6050角度,串口显示角度值。STC89C52单片机,x和y轴数据是采用互补滤波51单片机读取mpu6050更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/fengyuzhe13/85319557?spm=1001.2014.3001.5503

     关键程序:

    //****************************************
    //
    // 注意MPU6050必须芯片正面朝上
    // 功能: 显示角度值
    //****************************************
    // GY-52 MPU6050 IIC测试程序
    // 使用单片机STC89C52
    // 晶振:11.0592M ,波特率19200
    //****************************************
    #include     
    #include     //Keil library  
    //#include    //Keil library    
    #include
    typedef unsigned char  uchar;
    typedef unsigned short ushort;
    typedef unsigned int   uint;
    //****************************************
    // 定义51单片机端口
    //****************************************

    sbit    SCL=P2^2;            //IIC时钟引脚定义
    sbit    SDA=P2^3;            //IIC数据引脚定义

    //****************************************
    // 定义MPU6050内部地址
    //****************************************
    #define    SMPLRT_DIV        0x19    //陀螺仪采样率,典型值:0x07(125Hz)
    #define    CONFIG            0x1A    //低通滤波频率,典型值:0x06(5Hz)
    #define    GYRO_CONFIG        0x1B    //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
    #define    ACCEL_CONFIG    0x1C    //加速计自检、测量范围及高通滤波频率,典型值:0x01(不自检,2G,5Hz)
    #define    ACCEL_XOUT_H    0x3B
    #define    ACCEL_XOUT_L    0x3C
    #define    ACCEL_YOUT_H    0x3D
    #define    ACCEL_YOUT_L    0x3E
    #define    ACCEL_ZOUT_H    0x3F
    #define    ACCEL_ZOUT_L    0x40
    #define    TEMP_OUT_H        0x41
    #define    TEMP_OUT_L        0x42
    #define    GYRO_XOUT_H        0x43
    #define    GYRO_XOUT_L        0x44    
    #define    GYRO_YOUT_H        0x45
    #define    GYRO_YOUT_L        0x46
    #define    GYRO_ZOUT_H        0x47
    #define    GYRO_ZOUT_L        0x48
    #define    PWR_MGMT_1        0x6B    //电源管理,典型值:0x00(正常启用)
    #define    WHO_AM_I            0x75    //IIC地址寄存器(默认数值0x68,只读)
    #define    SlaveAddress    0xD0    //IIC写入时的地址字节数据,+1为读取

    在此还发现一个有意思的问题:

    //****************************************
    //发现一个神奇的地方!!!!!
    //如果不写串口中断函数,以下函数尽管没有调用,但是不能删除,一旦删除,程序就不能正常运行。
    //这是因为打开了串口中断,但是没有写中断函数,导致程序存储空间中串口中断入口地址被其他代码占用,而执行错误的程序。
    //所以打开中断时,必须写出对应的中断函数。!!!!!!!!!!
    //****************************************

    //主程序
    //*********************************************************

    void main()
    {
        int Ax,Ay,Az,Gx,Gy,Gz;
        unsigned long Gravity ;
        float time,tosc;
        float AngleX1,AngleY1,AngleZ1,AngleX2,AngleY2,AngleZ2,dx,dy,dz;
        float Filter;
        Filter=0.8; //互补滤波系数
        delay(500);        //上电延时        

         init_uart();
        InitMPU6050();    //初始化MPU6050
        delay(150);
        tosc=12.0/11059200.0;
        AngleX1=0;
        AngleY1=0;
        AngleZ1=0;
        AngleX2=0;
        AngleY2=0;
        AngleZ2=0;
        dx=0;
        dy=0;
        dz=0;

        while(1)
        {
          Ax=GetData(ACCEL_XOUT_H);
            Ay=GetData(ACCEL_YOUT_H);
            Az=GetData(ACCEL_ZOUT_H);
            Gx=GetData(GYRO_XOUT_H);
            Gy=GetData(GYRO_YOUT_H);
            Gz=GetData(GYRO_ZOUT_H);
            
            //计算采样时间
            TR0=0;
            time=TH0<<8 + TL0;
            time=(float)(time+tn*65536.0)*tosc;
    //        Show_int(tn);
    //        Show_int((int)(time*1000));
            TH0=0;
            TL0=0;
            tn=0;
            TR0=1;
            
            
           //sqrt的输入必须严格是float
            Gravity=sqrt((float)Ax*Ax+(float)Ay*Ay+(float)Az*Az);  //Ax*Ax+Ay*Ay+Az*Az
            AngleX1=acos((float)Ax/Gravity)*180.0/3.14-90;;
            AngleY1=acos((float)Ay/Gravity)*180.0/3.14-90;
            AngleZ1=acos((float)Az/Gravity)*180.0/3.14;
            dy=time*Gx/-16.4; //陀螺仪测的转角y
            dx=time*Gy/16.4; //陀螺仪测的转角x
            dz=time*Gz/16.4; //陀螺仪测的转角z

        //x和y轴数据是融合加速度计和陀螺仪数据, z轴只采用陀螺仪数据
            AngleX2=Filter*(AngleX2+dx)+(1-Filter)*AngleX1;        
            AngleY2=Filter*(AngleY2+dy)+(1-Filter)*AngleY1;    
        // z轴数据有两种方式,一种是只使用陀螺仪的数据,舍弃z轴加速度(z轴零飘严重):
            AngleZ2=AngleZ2+dz;        // 注意MPU6050必须芯片正面朝上
        
            
            //显示角度
            SeriPushSend('x');
            SeriPushSend(':');
            Show_int((int)(AngleX2));
            SeriPushSend('y');
            SeriPushSend(':');        
            Show_int((int)(AngleY2));    
            SeriPushSend('z');
            SeriPushSend(':');
            Show_int((int)(AngleZ2));   
           
            SeriPushSend(0x0d);
        SeriPushSend(0x0a);//换行,回车
            ES=0;
            while(RI==0);
            RI=0;
            ES=1;
    //        delay(80);
        }
    }

    ——————————————————————分割线——————————————————

    有网友咨询,为什么用我的代码不能正常显示角度值?现在我来仔细回答一下。

    首先用我的代码必须将串口助手设定为自动发送模式,比如自动发送周期为100ms:

    其次,来回答一下为什么这么做。

    以前我做单片机程序的时候,也喜欢串口“无脑”向外发送数据,在这里经常会遇到一个问题:电脑蓝屏!经常体会到被蓝屏支配的恐惧。那这为啥会引起蓝屏呢?假如我们重新将USB转串口模块插入电脑后,电脑要经过几秒才能将这个驱动运行起来,在这个过程中,如果单片机向电脑发送了大量数据,势必会导致电脑串口缓存区沾满,从而崩溃,进而产生蓝屏。

    为了防止这种事情的发送,那么最保险的办法就是采用“应答”式通讯,即上位机向单片机发送数据后,单片机才能将所需数据上传。在本次程序中,就是串口助手向单片机发送任意一字节数据,那么才能收到MPU6050的姿态数据。串口助手发送的数据频率,决定了上位机接收的数据频率。

    另外,也询问程序中的“time”变量是做什么用?其实,它就是记录单片机两次上传数据的时间间隔,即串口发送数据的时间间隔。用陀螺仪测量转角,需要对时间进行积分,时间长度由串口向单片机发送两次数据的间隔确定,最好以固定频率向单片机发送数据(建议10Hz),这样就能持续获得转角。如果时间间隔很大,那么陀螺仪的零飘就会严重影响计算的结果。

    最后,是我的一些实验过程:

    X轴:

    Y轴:

    Z轴:

    还有很早之前用OpenGL做的联合运动:

  • 相关阅读:
    深入探讨java -jar命令:详解及代码演示
    Docker 完整版教程
    代码随想录笔记_动态规划_279完全平方数
    golang查看CPU使用率与内存及源码中的//go:指令
    Android 12 “Bug 连连”:除了一加、三星,谷歌自家手机都被“坑”了
    nodejs上传文件到七牛云代码实现
    Sim3相似变换
    SpinalHDL学习笔记(1)——Scala和sbt安装
    如何将字符串反转?
    简易租赁合同(免费)
  • 原文地址:https://blog.csdn.net/fengyuzhe13/article/details/127681988