极客工坊

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 21641|回复: 10

三部曲之一,单维度PID调整,上视频

[复制链接]
发表于 2013-6-2 10:54:20 | 显示全部楼层 |阅读模式
冷静一点后,觉得四轴飞行器一步到位不太现实,不如分三步曲去做

计划:
1. 用PID控制单维度(如角度),达到较好的自动化效果
2. 自平衡小车(实际上就是1加上轮子)
3. 四轴飞行器

第一步昨天做通了,在PID参数上有些体会,先上视频,效果是让陀螺仪保持45度水平角


再上代码


  1. #include <I2Cdev.h>
  2. #include <MPU6050.h>
  3. #include <Wire.h>//添加必须的库文件
  4. //====一下三个定义了陀螺仪的偏差===========
  5. #define Gx_offset 0.2
  6. #define Gy_offset 0.81
  7. #define Gz_offset -0.88

  8. //====================
  9. MPU6050 accelgyro;

  10. int16_t ax,ay,az;
  11. int16_t gx,gy,gz;//存储原始数据

  12. double aax,aay,aaz,ggx,ggy,ggz;//存储量化后的数据
  13. double tx,ty;
  14. double Ax,Ay,Az;//单位 g(9.8m/s^2)
  15. double Gx,Gy,Gz;//单位 °/s

  16. static double vari_estima_X= 0.1;
  17. static double vari_estima_Y= 0.1;
  18. static double vari_metron=0.0001;
  19. static double KG_X = 0.5;
  20. static double KG_Y = 0.5;
  21. static double metron_X =0;
  22. static double metron_Y =0;
  23. static double estima_X = 0.2;
  24. static double estima_Y = 0.2;
  25. static int time_span_micro =50;
  26. static float time_span =0.05;
  27. double now_error, last_error, error_diff, error_int;
  28. double fusion_angle =0;

  29. double Angel_accX,Angel_accY,Angel_accZ;//存储加速度计算出的角度

  30. long LastTime,NowTime,TimeSpan;//用来对角速度积分的
  31.    
  32. void setup()//MPU6050的设置都采用了默认值,请参看库文件
  33. {
  34.   Wire.begin();

  35.   Serial.begin(38400);

  36.   Serial.println("Initializing I2C device.....");
  37.   accelgyro.initialize();

  38.   Serial.println("Testing device connections...");
  39.   Serial.println(accelgyro.testConnection() ? "MPU6050 connection successful":"MPU6050 connection failure");

  40. }

  41. void loop()
  42. {
  43.   accelgyro.getMotion6(&ax,&ay,&az,&gx,&gy,&gz);//获取三个轴的加速度和角速度
  44. //  Serial.print(ax/16384.00);
  45. //  Serial.print(gx/131.00);
  46. //======一下三行是对加速度进行量化,得出单位为g的加速度值
  47.    Ax=ax/16384.00;
  48.    Ay=ay/16384.00;
  49.    Az=az/16384.00;
  50.    //==========以下三行是用加速度计算三个轴和水平面坐标系之间的夹角
  51. //请参考:[url]http://www.geek-workshop.com/forum.php?mod=viewthread&tid=2328&page=1#pid27876[/url]
  52. //个人觉得原帖中case0算的不对,应该是z/sqrt(x*x+y*y),估计是江南楼主写错了

  53.    Angel_accX=atan(Ax/sqrt(Az*Az+Ay*Ay))*180/3.14 -Gx_offset; //offset
  54.    Angel_accY=atan(Ay/sqrt(Ax*Ax+Az*Az))*180/3.14 -Gy_offset; //offset
  55.    Angel_accZ=atan(Az/sqrt(Ax*Ax+Ay*Ay))*180/3.14;
  56.    //==========以下三行是对角速度做量化==========
  57.    
  58. //   ggx=gx/131.00;
  59. //   ggy=gy/131.00;
  60. //   ggz=gz/131.00;

  61.   //===============以下是对角度进行积分处理================
  62.   NowTime=millis();//获取当前程序运行的毫秒数
  63.   TimeSpan=NowTime-LastTime;//积分时间这样算不是很严谨
  64. //下面三行就是通过对角速度积分实现各个轴的角度测量,当然假设各轴的起始角度都是0

  65.   Gx=Gx+(ggx-Gx_offset)*TimeSpan/1000;
  66.   Gy=Gy+(ggy-Gy_offset)*TimeSpan/1000;
  67.   Gz=Gz+(ggz-Gz_offset)*TimeSpan/1000;

  68.   
  69.   automatic();
  70.   
  71.   LastTime=NowTime;  
  72.   
  73. //  analogWrite(6, 0);
  74. //  analogWrite(5, 80); //conter clockwise
  75.   
  76. //  KG_X = KG_cal(vari_estima_X, vari_metron) ;
  77. //  KG_Y = KG_cal(vari_estima_Y, vari_metron) ;
  78.   KG_X =0.1;
  79.   KG_Y =0.1;
  80.   estima_X = estima_cal(estima_X, Angel_accX, KG_X);
  81.   estima_Y = estima_cal(estima_Y, Angel_accY, KG_Y);
  82. //  vari_estima_X = vari_estima_cal(KG_X, vari_estima_X);
  83. //  vari_estima_Y = vari_estima_cal(KG_Y, vari_estima_Y);

  84.   
  85.   //==============================
  86. //    Serial.print(Angel_accX);
  87. //    Serial.print(",");
  88. //    Serial.println(Angel_accY);
  89.      
  90. //    Serial.print(estima_X);
  91. //    Serial.print(",");
  92. //    Serial.println(estima_Y);  
  93.    
  94. //    Serial.print(Ax);
  95. //    Serial.print(",");
  96. //    Serial.println(Ay);  
  97.       
  98.     //delay(time_span_micro);//这个用来控制采样速度
  99. }

  100. //void updateKalman (double my_metron_X,  double my_metron_Y){
  101. //  KG_X = KG_cal(vari_estima_X, vari_metron);
  102. //  KG_Y = KG_cal(vari_estima_Y, vari_metron);
  103. //  estima_X = estima_cal(estima_X, my_metron_X, KG_X);
  104. //  estima_Y = estima_cal(estima_Y, my_metron_Y, KG_Y);
  105. //  vari_estima_X = vari_estima_cal(KG_X, vari_estima_X);
  106. //  vari_estima_Y = vari_estima_cal(KG_Y, vari_estima_Y);
  107. //}

  108. //------------------------------------------------------------------
  109. double KG_cal(double vari_estima, double vari_metron){
  110.   double temp = vari_estima*vari_estima / (vari_estima*vari_estima+ vari_metron*vari_metron);
  111.   double result =sqrt(temp);
  112.   return result;
  113. }
  114. //------------------------------------------------------------------
  115. double estima_cal (double estima, double metron, double KG){
  116.   double result1 = estima + KG*(metron - estima);
  117.   return result1;
  118. }
  119. //------------------------------------------------------------------
  120. double vari_estima_cal (double KG, double vari_estima){
  121.   double result2 = vari_estima * sqrt(1-KG);
  122.   return result2;
  123. }

  124. double PID_control(double error, double error_diff, double error_int ){
  125.   double P=2;
  126.   double I=1;
  127.   double D=1;
  128.   double command = P*error + I*error_int + D*error_diff;

  129.   return command;
  130. }



  131. void automatic(){
  132.   
  133. //  Serial.print("now_error=");
  134. //  Serial.println(now_error);
  135. //  Serial.print("error_diff=");
  136. //  Serial.println(error_diff);

  137.   double command = PID_control(now_error, error_diff, error_int);
  138. //  Serial.print("command=");
  139. //  Serial.println(command);
  140.   
  141.   int regu_pwm = int (command);
  142.   
  143.   //Serial.println(regu_pwm);
  144.   
  145.   if(regu_pwm >= 250){
  146.     regu_pwm=250;
  147.     analogWrite(6, 0);
  148.     analogWrite(5, regu_pwm); //conter clockwise
  149.     Serial.println("clock");
  150.    
  151.   }else if (regu_pwm > 0 & regu_pwm<250){
  152.     analogWrite(6, 0);
  153.     analogWrite(5, regu_pwm); //conter clockwise
  154.     Serial.println("clock");
  155.   
  156.   }else if (regu_pwm <= 0 & regu_pwm > -250){
  157.     analogWrite(5, 0);
  158.     analogWrite(6, regu_pwm); // clockwise
  159.     Serial.println("CONTER CLOCK");
  160.   }else if (regu_pwm <=-250){
  161.     regu_pwm=250;
  162.     analogWrite(5, 0);
  163.     analogWrite(6, regu_pwm); // clockwise
  164.     Serial.println("CONTER CLOCK");
  165.   }
  166.   delay (time_span_micro);
  167.   //fusion_angle = update_Estimat(fusion_angle, estima_Y, Gy );
  168.   autoCorrection();
  169.   Serial.print("estima_Y=");
  170.   Serial.println(estima_Y);
  171.   
  172. //  uodate_error(time_span, 45, Angel_accY);
  173.   uodate_error(time_span, 45, estima_Y);

  174.   //uodate_error(time_span, 45, fusion_angle);
  175. }
  176. void uodate_error(double time_span, double consigne, double mesure){
  177.   now_error = consigne - mesure;
  178.   error_diff = (now_error - last_error)/time_span;
  179.   error_int = error_int + now_error*time_span;
  180.   last_error =now_error ;
  181. }

  182. double update_Estimat(double estimate, double metron_1, double metron_2){
  183.   double temp = 0.4* estimate + 0.3*metron_1 + 0.3*metron_2;
  184.   return temp;
  185. }

  186. void autoCorrection (){
  187.   if ( Angel_accX > -0.2 & Angel_accX < 0.2 ){
  188.   Gx=0;
  189.   }
  190. }
复制代码



接下来是体会,有这么几点:
1。  P参数越大,回复速度越快,但同时越容易调整过头出现震荡,因此适合同时调大D参数,预计惯性累加量来及时减小电机转动输出(接近要到达位置时)。故曰:大P配大D,小P配小D

2 。 卡尔曼滤波函数 updateKalman 其实没有完全调通,主要是卡尔曼增益一下子就趋近于0了,导致角度响应变得非常慢。不过,如果一直将卡尔曼增益固定在0.1 (见estima_cal函数),反而波形变得很平滑而且响应速度很好。

3。 自己做的融合算法简直就是狗P (见fusion_angle),暂时不报以希望。网上说通过三轴角速度得到的角度容易受运动影响,我保持一个角度拿着mpu6050,突然超某方向运动(但维持角度不变),在串口输出上看到影响不是那么大,可能是因为已经滤过波的吧。

4。 PID函数仍然不那么精确, 理论上说在平衡位置(这里设的是45度)时,PID控制的输出量为零,但我这里还有个十几,当然十几的pwm是带不动电机的,所以视频上面表现的还可以。

5。 autoCorrection函数是用来修正陀螺仪误差的,每次三轴加速度得到的角度为零时,也会将陀螺仪积分的角度归零,可囧的是,如果一直保持非水平位置的话,好像都没有修正的机会


希望各位为我做指点。
回复

使用道具 举报

发表于 2013-6-2 11:16:34 | 显示全部楼层
感谢分享{:soso_e179:}
回复 支持 反对

使用道具 举报

发表于 2013-6-2 13:00:16 | 显示全部楼层
我看很多人說互補濾波也不錯,樓主可以試試看~。
回复 支持 反对

使用道具 举报

发表于 2013-6-2 13:09:59 | 显示全部楼层
請問command最後怎麼跟控制值融合的啊??
是哪一行?啊?

回复 支持 反对

使用道具 举报

发表于 2013-6-2 13:16:06 | 显示全部楼层
留爪待学···
回复 支持 反对

使用道具 举报

 楼主| 发表于 2013-6-2 13:17:23 | 显示全部楼层
TTTTTTT33 发表于 2013-6-2 13:09
請問command最後怎麼跟控制值融合的啊??
是哪一行?啊?

double command = PID_control(now_error, error_diff, error_int);
int regu_pwm = int (command);

然后防饱和处理,如果大于pwm范围255就用极值, 这里用的250,
if.. else if ... else if ....
分正转和反转写入analog pin
analogWrite(6, 0);172.   
analogWrite(5, regu_pwm); //conter clockwise
回复 支持 反对

使用道具 举报

发表于 2013-6-2 13:25:10 | 显示全部楼层
camion 发表于 2013-6-2 13:17
double command = PID_control(now_error, error_diff, error_int);
int regu_pwm = int (command);

可以用這個設定範圍constrain(X, outMin, outMax);
回复 支持 反对

使用道具 举报

发表于 2013-6-2 15:29:27 | 显示全部楼层
我用互補濾波時發現,如果採樣速度太慢會衝過頭,但採樣速度變快時會對抖動的影響加大,不知道該怎麼辦......
回复 支持 反对

使用道具 举报

 楼主| 发表于 2013-6-2 21:34:03 | 显示全部楼层
靠,搞清楚为什么融合算法失效了

原因是: 原帖代码中三轴加速度的XY 和 陀螺仪的XY 是反过来的
现在上正确的融合算法,比较了一下,两种角度测法结果差别不是特别大,最多可能差十几度吧,融合后介于两者之间
  1. #include <I2Cdev.h>
  2. #include <MPU6050.h>
  3. #include <Wire.h>//添加必须的库文件
  4. //====一下三个定义了陀螺仪的偏差===========
  5. #define Gx_offset 0.2
  6. #define Gy_offset 0.81
  7. #define Gz_offset -0.88

  8. //====================
  9. MPU6050 accelgyro;

  10. int16_t ax,ay,az;
  11. int16_t gx,gy,gz;//存储原始数据

  12. double aax,aay,aaz,ggx,ggy,ggz;//存储量化后的数据
  13. double tx,ty;
  14. double Ax,Ay,Az;//单位 g(9.8m/s^2)
  15. double Gx,Gy,Gz;//单位 °/s

  16. static double vari_estima_X= 0.1;
  17. static double vari_estima_Y= 0.1;
  18. static double vari_metron=0.0001;
  19. static double KG_X = 0.5;
  20. static double KG_Y = 0.5;
  21. static double metron_X =0;
  22. static double metron_Y =0;
  23. static double estima_X = 0.2;
  24. static double estima_Y = 0.2;
  25. static int time_span_micro =50;
  26. static float time_span =0.05;
  27. double now_error, last_error, error_diff, error_int;
  28. double fusion_angle =0;

  29. double Angel_accX,Angel_accY,Angel_accZ;//存储加速度计算出的角度

  30. long LastTime,NowTime,TimeSpan;//用来对角速度积分的
  31.    
  32. void setup()//MPU6050的设置都采用了默认值,请参看库文件
  33. {
  34.   Wire.begin();

  35.   Serial.begin(38400);

  36.   Serial.println("Initializing I2C device.....");
  37.   accelgyro.initialize();

  38.   Serial.println("Testing device connections...");
  39.   Serial.println(accelgyro.testConnection() ? "MPU6050 connection successful":"MPU6050 connection failure");

  40. }

  41. void loop()
  42. {
  43.   accelgyro.getMotion6(&ax,&ay,&az,&gx,&gy,&gz);//获取三个轴的加速度和角速度
  44. //  Serial.print(ax/16384.00);
  45. //  Serial.print(gx/131.00);
  46. //======一下三行是对加速度进行量化,得出单位为g的加速度值
  47.    Ax=ax/16384.00;
  48.    Ay=ay/16384.00;
  49.    Az=az/16384.00;
  50.    //==========以下三行是用加速度计算三个轴和水平面坐标系之间的夹角
  51. //请参考:[url]http://www.geek-workshop.com/forum.php?mod=viewthread&tid=2328&page=1#pid27876[/url]
  52. //个人觉得原帖中case0算的不对,应该是z/sqrt(x*x+y*y),估计是江南楼主写错了

  53.    Angel_accX=atan(Ax/sqrt(Az*Az+Ay*Ay))*180/3.14 -Gx_offset; //offset
  54.    Angel_accY=atan(Ay/sqrt(Ax*Ax+Az*Az))*180/3.14 -Gy_offset; //offset
  55.    Angel_accZ=atan(Az/sqrt(Ax*Ax+Ay*Ay))*180/3.14;
  56.    //==========以下三行是对角速度做量化==========
  57.    
  58.    ggx=gx/131.00;
  59.    ggy=gy/131.00;
  60.    ggz=gz/131.00;

  61.   //===============以下是对角度进行积分处理================
  62.   NowTime=millis();//获取当前程序运行的毫秒数
  63.   
  64. //下面三行就是通过对角速度积分实现各个轴的角度测量,当然假设各轴的起始角度都是0
  65.   TimeSpan=NowTime-LastTime;//积分时间这样算不是很严谨
  66.   
  67.   Gx=Gx+(ggx-Gx_offset)*time_span_micro/1000;
  68.   Gy=Gy+(ggy-Gy_offset)*time_span_micro/1000;
  69.   Gz=Gz+(ggz-Gz_offset)*time_span_micro/1000;
  70.   Serial.print("Gy=");
  71.   Serial.println(Gx);  
  72.   automatic();
  73.   

  74.   LastTime=NowTime;  
  75.   
  76. //  KG_X = KG_cal(vari_estima_X, vari_metron) ;
  77. //  KG_Y = KG_cal(vari_estima_Y, vari_metron) ;
  78.   KG_X =0.1;
  79.   KG_Y =0.1;
  80.   estima_X = estima_cal(estima_X, Angel_accX, KG_X);
  81.   estima_Y = estima_cal(estima_Y, Angel_accY, KG_Y);
  82. //  vari_estima_X = vari_estima_cal(KG_X, vari_estima_X);
  83. //  vari_estima_Y = vari_estima_cal(KG_Y, vari_estima_Y);

  84.   //==============================
  85. //    Serial.print(Angel_accX);
  86. //    Serial.print(",");
  87. //    Serial.println(Angel_accY);
  88.      
  89. //    Serial.print(estima_X);
  90. //    Serial.print(",");
  91. //    Serial.println(estima_Y);  
  92.    
  93. //    Serial.print(Gx);
  94. //    Serial.print(",");

  95.       
  96.     //delay(time_span_micro);//这个用来控制采样速度
  97. }

  98. void updateKalman (double my_metron_X,  double my_metron_Y){
  99.   KG_X = KG_cal(vari_estima_X, vari_metron);
  100.   KG_Y = KG_cal(vari_estima_Y, vari_metron);
  101.   estima_X = estima_cal(estima_X, my_metron_X, KG_X);
  102.   estima_Y = estima_cal(estima_Y, my_metron_Y, KG_Y);
  103.   vari_estima_X = vari_estima_cal(KG_X, vari_estima_X);
  104.   vari_estima_Y = vari_estima_cal(KG_Y, vari_estima_Y);
  105. }

  106. //------------------------------------------------------------------
  107. double KG_cal(double vari_estima, double vari_metron){
  108.   double temp = vari_estima*vari_estima / (vari_estima*vari_estima+ vari_metron*vari_metron);
  109.   double result =sqrt(temp);
  110.   return result;
  111. }
  112. //------------------------------------------------------------------
  113. double estima_cal (double estima, double metron, double KG){
  114.   double result1 = estima + KG*(metron - estima);
  115.   return result1;
  116. }
  117. //------------------------------------------------------------------
  118. double vari_estima_cal (double KG, double vari_estima){
  119.   double result2 = vari_estima * sqrt(1-KG);
  120.   return result2;
  121. }

  122. double PID_control(double error, double error_diff, double error_int ){
  123.   double P=1.8;
  124.   double I=1;
  125.   double D=1;
  126.   double command = P*error + I*error_int + D*error_diff;
  127.   return command;
  128. }

  129. void automatic(){
  130.   
  131. //  Serial.print("now_error=");
  132. //  Serial.println(now_error);
  133. //  Serial.print("error_diff=");
  134. //  Serial.println(error_diff);

  135.   double command = PID_control(now_error, error_diff, error_int);
  136. //  Serial.print("command=");
  137. //  Serial.println(command);
  138.   
  139.   int regu_pwm = int (command);
  140.   
  141.   //Serial.println(regu_pwm);
  142.   
  143. //  if(regu_pwm >= 250){
  144. //    regu_pwm=250;
  145. //    analogWrite(6, 0);
  146. //    analogWrite(5, regu_pwm); //conter clockwise
  147. //    //Serial.println("clock");
  148. //   
  149. //  }else if (regu_pwm > 0 & regu_pwm<250){
  150. //    analogWrite(6, 0);
  151. //    analogWrite(5, regu_pwm); //conter clockwise
  152. //    //Serial.println("clock");
  153. //  
  154. //  }else if (regu_pwm <= 0 & regu_pwm > -250){
  155. //    analogWrite(5, 0);
  156. //    analogWrite(6, regu_pwm); // clockwise
  157. //    //Serial.println("CONTER CLOCK");
  158. //  }else if (regu_pwm <=-250){
  159. //    regu_pwm=250;
  160. //    analogWrite(5, 0);
  161. //    analogWrite(6, regu_pwm); // clockwise
  162. //    //Serial.println("CONTER CLOCK");
  163. //  }
  164.   delay (time_span_micro);
  165.   
  166.   
  167.   fusion_angle = update_Estimat(fusion_angle, estima_Y, Gx );
  168.   autoCorrection();
  169. //  Serial.print("estima_Y=");
  170. //  Serial.println(estima_Y);
  171. //  Serial.print("fusion_angle=");
  172. //  Serial.println(fusion_angle);
  173.   
  174. //  uodate_error(time_span, 45, Angel_accY);
  175. //  uodate_error(time_span, 0, estima_Y);

  176.   uodate_error(time_span, 45, fusion_angle);
  177. }
  178. void uodate_error(double time_span, double consigne, double mesure){
  179.   now_error = consigne - mesure;
  180.   error_diff = (now_error - last_error)/time_span;
  181.   error_int = error_int + now_error*time_span;
  182.   last_error =now_error ;
  183. }

  184. double update_Estimat(double estimate, double metron_1, double metron_2){
  185.   double temp = 0.4* estimate + 0.3*metron_1 + 0.3*metron_2;
  186.   return temp;
  187. }

  188. void autoCorrection (){
  189.   
  190.     Serial.print("Angel_accY=");
  191.     Serial.println(Angel_accY);
  192.   if ( Angel_accY > -0.5 & Angel_accY < 0.5 ){
  193.     Serial.println("00000000000000");
  194.     Gx=0;
  195.   }
  196.   
  197.   if ( Angel_accX > -0.5 & Angel_accX < 0.5 ){
  198.     Serial.println("00000000000000");
  199.     Gy=0;
  200.   }
  201. }
复制代码
回复 支持 反对

使用道具 举报

发表于 2013-6-25 13:21:05 | 显示全部楼层
楼主分享的真不错 感谢分享 希望继续分享 看了好多天的MPU6050这东西的算法 还是算不出X和Y轴方向不受重力影响的加速度  现在想研究角度 通过时时的陀螺仪角度 利用空间几何算算法把 X和Y轴运动时的加速度重力分量去掉 希望得到楼主指导。有没有更简捷的方法得到我想要的数据
回复 支持 反对

使用道具 举报

发表于 2014-3-26 23:05:01 | 显示全部楼层
mark!!!!!!!!!!!!!!!!!!!!!!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|联系我们|极客工坊

GMT+8, 2026-6-8 19:43 , Processed in 0.084165 second(s), 22 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表