当前位置:   article > 正文

第 8 章 机器人底盘Arduino端PID控制(自学二刷笔记)_arudino pid p i d计算方法

arudino pid p i d计算方法

重要参考:

课程链接:https://www.bilibili.com/video/BV1Ci4y1L7ZZ

讲义链接:Introduction · Autolabor-ROS机器人入门课程《ROS理论与实践》零基础教程

8.4.5 底盘实现_04Arduino端PID控制

上一节最后测试时,电机可能会出现抖动、顿挫的现象,显而易见的这是由于PID参数设置不合理导致的,本节将介绍ros_arduino_bridge中的PID调试,大致流程如下:

  1. 了解ros_arduino_bridge中PID调试的流程;
  2. 实现PID调试。
1.ros_arduino_bridge中PID调试源码分析

基本思想:

  1. 先定义调试频率(周期),并预先设置下一次的结束时刻;
  2. 当当前时刻大于预设的结束时刻时,即进行PID调试,且重置下一次调试结束时刻
  3. PID代码在diff_controller中实现,PID的目标值是命令输入的转速,当前转速则是通过读取当前编码器计数再减去上一次调试结束时记录的编码器计数获取;
  4. 最后输出 PWM

ROSArduinoBridge.ino 中和PID控制相关的变量:

  1. #ifdef USE_BASE
  2. /* Motor driver function definitions */
  3. #include "motor_driver.h"
  4. /* Encoder driver function definitions */
  5. #include "encoder_driver.h"
  6. /* PID parameters and functions */
  7. #include "diff_controller.h"
  8. /* Run the PID loop at 30 times per second */
  9. #define PID_RATE 30 // Hz PID调试频率
  10. /* Convert the rate into an interval */
  11. const int PID_INTERVAL = 1000 / PID_RATE; // PID调试周期
  12. /* Track the next time we make a PID calculation */
  13. unsigned long nextPID = PID_INTERVAL; //PID调试的结束时刻标记
  14. /* Stop the robot if it hasn't received a movement command
  15. in this number of milliseconds */
  16. #define AUTO_STOP_INTERVAL 5000
  17. long lastMotorCommand = AUTO_STOP_INTERVAL;
  18. #endif

ROSArduinoBridge.ino 的 runCommand()函数中:

  1. #ifdef USE_BASE
  2. case READ_ENCODERS:
  3. Serial.print(readEncoder(LEFT));
  4. Serial.print(" ");
  5. Serial.println(readEncoder(RIGHT));
  6. break;
  7. case RESET_ENCODERS:
  8. resetEncoders();
  9. resetPID();
  10. Serial.println("OK");
  11. break;
  12. case MOTOR_SPEEDS: //---------------------------------------------
  13. /* Reset the auto stop timer */
  14. lastMotorCommand = millis();
  15. if (arg1 == 0 && arg2 == 0) {
  16. setMotorSpeeds(0, 0);
  17. resetPID();
  18. moving = 0;
  19. }
  20. else moving = 1;
  21. //设置左右电机目标转速分别为参数1和参数2
  22. leftPID.TargetTicksPerFrame = arg1;
  23. rightPID.TargetTicksPerFrame = arg2;
  24. Serial.println("OK");
  25. break;
  26. case UPDATE_PID:
  27. while ((str = strtok_r(p, ":", &p)) != '\0') {
  28. pid_args[i] = atoi(str);
  29. i++;
  30. }
  31. Kp = pid_args[0];
  32. Kd = pid_args[1];
  33. Ki = pid_args[2];
  34. Ko = pid_args[3];
  35. Serial.println("OK");
  36. break;
  37. #endif

ROSArduinoBridge.ino 的 loop()函数中:

  1. #ifdef USE_BASE
  2. //如果当前时刻大于 nextPID,那么就执行PID调速,并在 nextPID 上自增一个PID调试周期
  3. if (millis() > nextPID) {
  4. updatePID();
  5. nextPID += PID_INTERVAL;
  6. }
  7. // Check to see if we have exceeded the auto-stop interval
  8. if ((millis() - lastMotorCommand) > AUTO_STOP_INTERVAL) {;
  9. setMotorSpeeds(0, 0);
  10. moving = 0;
  11. }
  12. #endif

diff_controller.h 中的PID调试代码:

  1. /* Functions and type-defs for PID control.
  2. Taken mostly from Mike Ferguson's ArbotiX code which lives at:
  3. http://vanadium-ros-pkg.googlecode.com/svn/trunk/arbotix/
  4. */
  5. /* PID setpoint info For a Motor */
  6. typedef struct {
  7. double TargetTicksPerFrame; // target speed in ticks per frame 目标转速
  8. long Encoder; // encoder count 编码器计数
  9. long PrevEnc; // last encoder count 上次的编码器计数
  10. /*
  11. * Using previous input (PrevInput) instead of PrevError to avoid derivative kick,
  12. * see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-derivative-kick/
  13. */
  14. int PrevInput; // last input
  15. //int PrevErr; // last error
  16. /*
  17. * Using integrated term (ITerm) instead of integrated error (Ierror),
  18. * to allow tuning changes,
  19. * see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-tuning-changes/
  20. */
  21. //int Ierror;
  22. int ITerm; //integrated term
  23. long output; // last motor setting
  24. }
  25. SetPointInfo;
  26. SetPointInfo leftPID, rightPID;
  27. /* PID Parameters */
  28. int Kp = 20;
  29. int Kd = 12;
  30. int Ki = 0;
  31. int Ko = 50;
  32. unsigned char moving = 0; // is the base in motion?
  33. /*
  34. * Initialize PID variables to zero to prevent startup spikes
  35. * when turning PID on to start moving
  36. * In particular, assign both Encoder and PrevEnc the current encoder value
  37. * See http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-initialization/
  38. * Note that the assumption here is that PID is only turned on
  39. * when going from stop to moving, that's why we can init everything on zero.
  40. */
  41. void resetPID(){
  42. leftPID.TargetTicksPerFrame = 0.0;
  43. leftPID.Encoder = readEncoder(LEFT);
  44. leftPID.PrevEnc = leftPID.Encoder;
  45. leftPID.output = 0;
  46. leftPID.PrevInput = 0;
  47. leftPID.ITerm = 0;
  48. rightPID.TargetTicksPerFrame = 0.0;
  49. rightPID.Encoder = readEncoder(RIGHT);
  50. rightPID.PrevEnc = rightPID.Encoder;
  51. rightPID.output = 0;
  52. rightPID.PrevInput = 0;
  53. rightPID.ITerm = 0;
  54. }
  55. /* PID routine to compute the next motor commands */
  56. //左右电机具体调试函数
  57. void doPID(SetPointInfo * p) {
  58. long Perror;
  59. long output;
  60. int input;
  61. //Perror = p->TargetTicksPerFrame - (p->Encoder - p->PrevEnc);
  62. input = p->Encoder - p->PrevEnc;
  63. Perror = p->TargetTicksPerFrame - input;
  64. //根据 input 绘图
  65. //Serial.println(input);
  66. /*
  67. * Avoid derivative kick and allow tuning changes,
  68. * see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-derivative-kick/
  69. * see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-tuning-changes/
  70. */
  71. //output = (Kp * Perror + Kd * (Perror - p->PrevErr) + Ki * p->Ierror) / Ko;
  72. // p->PrevErr = Perror;
  73. output = (Kp * Perror - Kd * (input - p->PrevInput) + p->ITerm) / Ko;
  74. p->PrevEnc = p->Encoder;
  75. output += p->output;
  76. // Accumulate Integral error *or* Limit output.
  77. // Stop accumulating when output saturates
  78. if (output >= MAX_PWM)
  79. output = MAX_PWM;
  80. else if (output <= -MAX_PWM)
  81. output = -MAX_PWM;
  82. else
  83. /*
  84. * allow turning changes, see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-tuning-changes/
  85. */
  86. p->ITerm += Ki * Perror;
  87. p->output = output;
  88. p->PrevInput = input;
  89. }
  90. /* Read the encoder values and call the PID routine */
  91. //PID调试
  92. void updatePID() {
  93. /* Read the encoders */
  94. leftPID.Encoder = readEncoder(LEFT);
  95. rightPID.Encoder = readEncoder(RIGHT);
  96. /* If we're not moving there is nothing more to do */
  97. if (!moving){
  98. /*
  99. * Reset PIDs once, to prevent startup spikes,
  100. * see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-initialization/
  101. * PrevInput is considered a good proxy to detect
  102. * whether reset has already happened
  103. */
  104. if (leftPID.PrevInput != 0 || rightPID.PrevInput != 0) resetPID();
  105. return;
  106. }
  107. /* Compute PID update for each motor */
  108. doPID(&rightPID);
  109. doPID(&leftPID);
  110. /* Set the motor speeds accordingly */
  111. setMotorSpeeds(leftPID.output, rightPID.output);
  112. }
2.PID调试

调试时,需要在 diff_controller.h 中打印 input 的值,然后通过串口绘图器输入命令: m 参数1 参数2,根据绘图结果调试:Kp、Ki和Kd的值。

  • 调试时,可以先调试单个电机的PID,比如,可以先注释 doPID(&rightPID);
  • PID算法不同,即便算法相同,如果参与运算的数据单位不同,都会导致不同的调试结果,不可以直接复用之前的调试结果。

PID调试技巧可以参考之前介绍。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/890494
推荐阅读
相关标签
  

闽ICP备14008679号