四旋翼飞行器的飞控实现「建议收藏」

尝试制作这个四旋翼飞控的过程,感触颇多,整理了思绪之后,把重要的点一一记下来;这个飞控是基于STM32,整合了MPU6050,即陀螺仪和重力加速计,但没有融合电子罗盘; 另外,四旋翼飞行器的运动方式请百度百科,不太复杂,具体不再赘述; 这是飞控程序的控制流程(一个执行周期):   比较重要的地方:1.i2c通信方式;

大家好,又见面了,我是你们的朋友全栈君。

尝试制作这个四旋翼飞控的过程,感触颇多,整理了思绪之后,把重要的点一一记下来;

这个飞控是基于STM32,整合了MPU6050,即陀螺仪和重力加速计,但没有融合电子罗盘;

 

另外,四旋翼飞行器的运动方式请百度百科,不太复杂,具体不再赘述;

 

这是飞控程序的控制流程(一个执行周期):

 

四旋翼飞行器的飞控实现「建议收藏」

 

 比较重要的地方:

1.i2c通信方式;

  因为我不是学电类专业,最开始对i2c这些是没有一点概念,最后通过Google了解了一些原理,然后发现STM32的开发库是带有i2c通信的相关函数的,但是我最后还是没有用这些函数。

我通过GPIO模拟i2c,这样也能获得mpu6050的数据,虽然代码多了一些,但是比较好的理解i2c的原理。

  STM32库实现的模拟i2c代码(注释好像因为编码问题跪了):

/*******************************************************************************

// file                :    i2c_conf.h

// MCU                : STM32F103VET6

// IDE                : Keil uVision4

// date                £º2014.2.28

*******************************************************************************/

#include “stm32f10x.h”

#define   uchar unsigned char

#define   uint unsigned int

#define      FALSE 0  

#define   TRUE  1

void I2C_GPIO_Config(void);

void I2C_delay(void);

void delay5ms(void);

int I2C_Start(void);

void I2C_Stop(void);

void I2C_Ack(void);

void I2C_NoAck(void);

int I2C_WaitAck(void);

void I2C_SendByte(u8 SendByte);

unsigned char I2C_RadeByte(void);

int Single_Write(uchar SlaveAddress,uchar REG_Address,uchar REG_data);

unsigned char Single_Read(unsigned char SlaveAddress,unsigned char REG_Address);

 

/*******************************************************************************

// file                :  i2c_conf.c

// MCU                : STM32F103VET6

// IDE                : Keil uVision4

// date                £º2014.2.28

*******************************************************************************/

#include “i2c_conf.h”

#define SCL_H         GPIOB->BSRR = GPIO_Pin_6       

#define SCL_L         GPIOB->BRR  = GPIO_Pin_6         

#define SDA_H         GPIOB->BSRR = GPIO_Pin_7

#define SDA_L         GPIOB->BRR  = GPIO_Pin_7

#define SCL_read      GPIOB->IDR  & GPIO_Pin_6        //IDR:¶Ë¿ÚÊäÈë¼Ä´æÆ÷¡£

#define SDA_read      GPIOB->IDR  & GPIO_Pin_7

                     

void I2C_GPIO_Config(void)

{

  GPIO_InitTypeDef  GPIO_InitStructure; 

 

  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6;

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;   //¿ªÂ©Êä³öģʽ

  GPIO_Init(GPIOB, &GPIO_InitStructure);

  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_7;

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;

  GPIO_Init(GPIOB, &GPIO_InitStructure);

}

void I2C_delay(void)

{

        

   int i=6; //ÕâÀï¿ÉÒÔÓÅ»¯ËÙ¶È    £¬¾­²âÊÔ×îµÍµ½5»¹ÄÜдÈë

   while(i) 

   { 

     i–; 

   }  

}

void delay5ms(void)

{

        

   int i=5000;  

   while(i) 

   { 

     i–; 

   }  

}

int I2C_Start(void)

{

    SDA_H;                                            //II2ЭÒé¹æ¶¨±ØÐëÔÚʱÖÓÏßΪµÍµçƽµÄÇ°ÌáÏ£¬²Å¿ÉÒÔÈà Êý¾ÝÏßÐźŸıä

    SCL_H;

    I2C_delay();

    

    if(!SDA_read)

        return FALSE;                //SDAÏßΪµÍµçƽÔò×ÜÏßæ,Í˳ö

    SDA_L;                                

    

    I2C_delay();

    

    if(SDA_read) 

        return FALSE;                //SDAÏßΪ¸ßµçƽÔò×ÜÏß³ö´í,Í˳ö

    SDA_L;                                

    

    I2C_delay();                    

    

    return TRUE;

}

void I2C_Stop(void)

{

    SCL_L;

    I2C_delay();

    SDA_L;

    I2C_delay();

    

    SCL_H;

    I2C_delay();

    SDA_H;

    I2C_delay();

void I2C_Ack(void)

{    

    SCL_L;

    I2C_delay();

    SDA_L;

    I2C_delay();

    SCL_H;

    I2C_delay();

    SCL_L;

    I2C_delay();

}   

void I2C_NoAck(void)

{    

    SCL_L;

    I2C_delay();

    SDA_H;

    I2C_delay();

    SCL_H;

    I2C_delay();

    SCL_L;

    I2C_delay();

int I2C_WaitAck(void)      //·µ»ØΪ:=1ÓÐACK,  =0ÎÞACK

{

    SCL_L;

    I2C_delay();

    SDA_H;            

    I2C_delay();

    SCL_H;

    I2C_delay();

    if(SDA_read)

    {

      SCL_L;

      I2C_delay();

      return FALSE;

    }

    SCL_L;

    I2C_delay();

    return TRUE;

}

void I2C_SendByte(u8 SendByte) //Êý¾Ý´Ó¸ßλµ½µÍλ//

{

    u8 i=8;

    while(i–)

    {

        SCL_L;

        I2C_delay();

            

      if(SendByte&0x80)        // 0x80 = 1000 0000;

        SDA_H;  

      else 

        SDA_L;   

            

        SendByte<<=1;   // SendByte×óÒÆһλ¡£

        I2C_delay();

            

                SCL_H;

        I2C_delay();

    }

    SCL_L;

}  

unsigned char I2C_RadeByte(void)  //Êý¾Ý´Ó¸ßλµ½µÍλ//

    u8 i=8;

    u8 ReceiveByte=0;

    SDA_H;                

    while(i–)

    {

      ReceiveByte<<=1;      //×óÒÆһ룬

            

      SCL_L;

      I2C_delay();

            

            SCL_H;

      I2C_delay();    

            

      if(SDA_read)

      {

        ReceiveByte”=0x01; //дÈë

      }

    }

    SCL_L;

    return ReceiveByte;

int Single_Write(uchar SlaveAddress,uchar REG_Address,uchar REG_data)             

{

      if(!I2C_Start())

            return FALSE;

    I2C_SendByte(SlaveAddress);   //·¢ËÍÉ豸µØÖ·+дÐźŠ   //I2C_SendByte(((REG_Address & 0x0700) >>7) | SlaveAddress & 0xFFFE);    //ÉèÖøßÆðʼµØÖ·+Æ÷¼þµØÖ· 

    

        if(!I2C_WaitAck())

        {

            I2C_Stop(); 

            return FALSE;

        }

    

        I2C_SendByte(REG_Address );   //ÉèÖõÍÆðʼµØÖ·      

    I2C_WaitAck();    

    

        I2C_SendByte(REG_data);

    I2C_WaitAck();   

    

        I2C_Stop(); 

    delay5ms();

    return TRUE;

}

unsigned char Single_Read(unsigned char SlaveAddress,unsigned char REG_Address)

{   

    unsigned char REG_data; 

    

        if(!I2C_Start())

            return FALSE;

    I2C_SendByte(SlaveAddress); //I2C_SendByte(((REG_Address & 0x0700) >>7) | REG_Address & 0xFFFE);//ÉèÖøßÆðʼµØÖ·+Æ÷¼þµØÖ· 

    if(!I2C_WaitAck())

        {

            I2C_Stop();

            return FALSE;

        }


 I2C_SendByte((u8) REG_Address);   //ÉèÖõÍÆðʼµØÖ·      

    I2C_WaitAck();

        I2C_Start();

    

        I2C_SendByte(SlaveAddress+1);

    I2C_WaitAck();

        REG_data= I2C_RadeByte();

    I2C_NoAck();

    I2C_Stop();

    return REG_data;

}     

 

2.mpu6050;

  然后用写好的模拟i2c函数读取mpu6050,根据mpu6050手册的各寄存器地址,读取到了重力加速计和陀螺仪的各分量;

传感器采样率设置为200Hz,这个值是因为我电调频率为200Hz,也就是说,我的程序循环一次0.005s,一般来说,采样率高点没问题,别比执行一次闭环控制的周期长就行了;

陀螺仪量程±2000°/s,加速计量程±2g, 量程越大,取值越不精确;

 

这里注意,由于我们没有采用磁力计,而陀螺仪存在零偏,所以最终在yaw方向上没有绝对的参考系,不能建立绝对的地理坐标系,这样最好的结果也仅仅是在yaw上存在缓慢漂移。

 

3.互补滤波;

  融合时,陀螺仪的积分运算很大程度上决定了飞行器的瞬时运动情况,而重力加速计通过长时间的累积不断矫正陀螺仪产生的误差,最终得到准确的机体姿态。

  这里我们采用Madgwick提供的UpdateIMU算法来得到姿态角所对应的四元数,之后只需要经过简单运算便可转换为实时欧拉角。感谢Madgwick大大为开源做出的贡献。

 1 #define sampleFreq    512.0f        // sample frequency in Hz

 2 #define betaDef        0.1f        // 2 * proportional gain

 3 

 4 

 5 volatile float beta = betaDef;                                

 6 volatile float q0 = 1.0f, q1 = 0.0f, q2 = 0.0f, q3 = 0.0f;     

 7 

 8 float invSqrt(float x);

 9 

10 void MadgwickAHRSupdateIMU(float gx, float gy, float gz, float ax, float ay, float az) {

11     float recipNorm;

12     float s0, s1, s2, s3;

13     float qDot1, qDot2, qDot3, qDot4;

14     float _2q0, _2q1, _2q2, _2q3, _4q0, _4q1, _4q2 ,_8q1, _8q2, q0q0, q1q1, q2q2, q3q3;

15 

16     // Rate of change of quaternion from gyroscope

17     qDot1 = 0.5f * (-q1 * gx – q2 * gy – q3 * gz);

18     qDot2 = 0.5f * (q0 * gx + q2 * gz – q3 * gy);

19     qDot3 = 0.5f * (q0 * gy – q1 * gz + q3 * gx);

20     qDot4 = 0.5f * (q0 * gz + q1 * gy – q2 * gx);

21 

22     // Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)

23     if(!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) {

24 

25         // Normalise accelerometer measurement

26         recipNorm = invSqrt(ax * ax + ay * ay + az * az);

27         ax *= recipNorm;

28         ay *= recipNorm;

29         az *= recipNorm;   

30 

31         // Auxiliary variables to avoid repeated arithmetic

32         _2q0 = 2.0f * q0;

33         _2q1 = 2.0f * q1;

34         _2q2 = 2.0f * q2;

35         _2q3 = 2.0f * q3;

36         _4q0 = 4.0f * q0;

37         _4q1 = 4.0f * q1;

38         _4q2 = 4.0f * q2;

39         _8q1 = 8.0f * q1;

40         _8q2 = 8.0f * q2;

41         q0q0 = q0 * q0;

42         q1q1 = q1 * q1;

43         q2q2 = q2 * q2;

44         q3q3 = q3 * q3;

45 

46         // Gradient decent algorithm corrective step

47         s0 = _4q0 * q2q2 + _2q2 * ax + _4q0 * q1q1 – _2q1 * ay;

48         s1 = _4q1 * q3q3 – _2q3 * ax + 4.0f * q0q0 * q1 – _2q0 * ay – _4q1 + _8q1 * q1q1 + _8q1 * q2q2 + _4q1 * az;

49         s2 = 4.0f * q0q0 * q2 + _2q0 * ax + _4q2 * q3q3 – _2q3 * ay – _4q2 + _8q2 * q1q1 + _8q2 * q2q2 + _4q2 * az;

50         s3 = 4.0f * q1q1 * q3 – _2q1 * ax + 4.0f * q2q2 * q3 – _2q2 * ay;

51         recipNorm = invSqrt(s0 * s0 + s1 * s1 + s2 * s2 + s3 * s3); // normalise step magnitude

52         s0 *= recipNorm;

53         s1 *= recipNorm;

54         s2 *= recipNorm;

55         s3 *= recipNorm;

56 

57         // Apply feedback step

58         qDot1 -= beta * s0;

59         qDot2 -= beta * s1;

60         qDot3 -= beta * s2;

61         qDot4 -= beta * s3;

62     }

63 

64     // Integrate rate of change of quaternion to yield quaternion

65     q0 += qDot1 * (1.0f / sampleFreq);

66     q1 += qDot2 * (1.0f / sampleFreq);

67     q2 += qDot3 * (1.0f / sampleFreq);

68     q3 += qDot4 * (1.0f / sampleFreq);

69 

70     // Normalise quaternion

71     recipNorm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);

72     q0 *= recipNorm;

73     q1 *= recipNorm;

74     q2 *= recipNorm;

75     q3 *= recipNorm;

76 }

77 

78 

79 float invSqrt(float x) {

80     float halfx = 0.5f * x;

81     float y = x;

82     long i = *(long*)&y;

83     i = 0x5f3759df – (i>>1);

84     y = *(float*)&i;

85     y = y * (1.5f – (halfx * y * y));

86     return y;

87 }

 

  四元数转欧拉角的算法可参考 http://www.cnblogs.com/wqj1212/archive/2010/11/21/1883033.html

 

4.获取期望姿态;

  也就是遥控部分了,让用户介入控制。

  本着拿来主义的原则,用上”圆点博士开源项目”提供的安卓的开源蓝牙控制端。

 四旋翼飞行器的飞控实现「建议收藏」

  圆点博士给出了数据包格式,同过HC-06蓝牙模块接连到STM32串口1,再无线连接到控制端,这样我们就可以获得控制端不断发送的数据包了,并实时更新期望姿态角,这里只需要注意输出的姿态角和实时姿态角方向一致以及数据包的校验就行了。

 

 

5.PID控制算法;

  由于简单的线性控制不可能满足四轴飞行器这个灵敏的系统,引入PID控制器来更好的纠正系统。

  简介:PID实指“比例proportional”、“积分integral”、“微分derivative”,这三项构成PID基本要素。每一项完成不同任务,对系统功能产生不同的影响。

四旋翼飞行器的飞控实现「建议收藏」

 

以Pitch为例:

 

四旋翼飞行器的飞控实现「建议收藏」

  

error为期望角减去实时角度得到的误差;

iState为积分i参数对应累积过去时间里的误差总和;

if语句限定iState范围,繁殖修正过度;

微分d参数为当前姿态减去上次姿态,估算当前速度(瞬间速度);

总调整量为p,i,d三者之和;

 

这样,P代表控制系统的响应速度,越大,响应越快。

I,用来累积过去时间内的误差,修正P无法达到的期望姿态值(静差);

D,加强对机体变化的快速响应,对P有抑制作用。

 

PID各参数的整定需要综合考虑控制系统的各个方面,才能达到最佳效果。

 

 

输出PWM信号:

PID计算完成之后,便可以通过STM32自带的定时资源很容易的调制出四路pwm信号,采用的电调pwm格式为50Hz,高电平持续时间0.5ms-2.5ms;

我以1.0ms-2.0ms为每个电机的油门行程,这样,1ms的宽度均匀的对应电调的从最低到最高转速。

 

 

至此,一个用stm32和mpu6050搭建的飞控系统就算实现了。

 

剩下的调试PID参数了。


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/127902.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 傅里叶变换 意义_傅里叶变换表达式

    傅里叶变换 意义_傅里叶变换表达式看到论坛有一个朋友提问为什么傅里叶变换可以将时域变为频域?这个问题真是问到了灵魂深处。在这我只能简单讲讲我的理解,要深刻理解翻信号处理教科书是最好的方法。1.如何描述信号我们常常用数学模型去抽象物理事件。信号也可以用数学模型来表示。有了信号的数学模型,我们就可以利用数学计算对信号模型做各种各样的改变。如果加以计算机,模电,数电的相关知识,我们就可以将我们对信号模型的改变转换为对物理信号的…

    2022年10月20日
  • 原生微信小程序flyio封装多baseURL配置请求,如同axios一样非常爽利的使用api

    原生微信小程序flyio封装多baseURL配置请求,如同axios一样非常爽利的使用api1.下载引入flyio基于promiseJavascripthttp请求的终极解决方案。也就是说,在任何能够执行Javascript的环境,只要具有访问网络的能力,Fly都能运行在其上,提供统一的API。fly下载地址2.request.js配置fly请求体//importFlyfrom’flyio/dist/npm/wx’;constFly=…

  • UpdatePanel 用法

    UpdatePanel 用法局部更新是ajax技术的最基本,也是最重要的用法,今天大概把asp.netajax中的局部更新控件updatepanel的用法记录下,大家可以共同探讨UpdatePanel控制页面的局部更新,这个更新功能依赖于scriptManger控件的EnablePartialRendering属性,如果这个属性设置为false局部更新会失去作用(scriptManger控件的EnablePartia

  • U盘安装window系统[通俗易懂]

    U盘安装window系统[通俗易懂]U盘安装window系统:1.制作系统启动U盘,推荐使用老毛桃。2.电脑上插入U盘,启动系统,选择U盘启动。3.进入老毛桃选择界面,选择生成PE系统。推荐win8,之前在一个戴尔电脑上使用win

  • Maven中pom.xml中的scope讲解

    Maven中pom.xml中的scope讲解一、compile:编译范围compile是默认的范围;如果没有提供一个范围,编译范围依赖在所有的classpath 中可用,同时它们也会被打包。而且这些dependency会传递到依赖的项目中。二、provided:已提供范围provided 明了dependency 由JDK或者容器提供。例如如果开发了一个web 应用,可能在编译 classpath 中需要可用的Servlet API…

  • 串口服务器调试助手使用教程,comassistant串口调试助手使用说明.pdf

    串口服务器调试助手使用教程,comassistant串口调试助手使用说明.pdf作者:温子祺wenziqi@wenziqi@单片机多功能调试助手简介单片机多功能调试助手简介单单片片机机多多功功能能调调试试助助手手简简介介1111简介图1单片机多功能调试助手单片机多功能调试助手一款集串口/USB/网络调试、进制转换、字模与数码管字型码制作、常用校验值计算、UNICODE码转换、位图输出C文件等众多功能于一身的综合型调试软件,最值得庆幸的是该软件会一直保持更新,并支持在…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号