风筝
发表于: 2021-2-4 10:11:30 | 显示全部楼层

在工业自动化和控制中,PID控制器已成为可用来稳定任何系统的输出响应的最可靠的控制算法之一。 PID代表比例积分微分(Proportional-Integral-Derivative)。这三种类型的控制机制被组合在一起,从而产生一个错误信号,并且该错误信号用作控制最终应用程序的反馈。 PID控制器可用于广泛的工业和商业应用,例如用于调节压力、线性运动和许多其他变量的控制器。 PID温度控制器是您可以在互联网上找到的最常见的应用程序。如果没有PID控制器,手动完成这项工作可能是一个乏味的过程。在这个数字电子和微控制器时代,在任何系统中设计和实现PID控制器变得更加容易。


我们决定拿出一篇有关PID控制器的文章,其中我们将详细介绍并了解其工作原理。接下来,我们将以编码器电机为例,我们将了解与之相关的问题。最后,我们将通过使用我们最喜欢的微控制器Arduino实现基于PID的控制算法来解决这些问题。


什么是PID控制器及其工作方式?

正如我们在前文提到的一样,PID是比例、积分和微分的首字母缩写。但是,这甚至意味着什么,有没有更简单的方法来理解它?。为此,让我们举一个使用Arduino的DIY智能吸尘机器人的示例,该示例是我们在之前的一个项目中所做的。对我来说,这是一个非常酷的项目,并且在电路和控制机制方面非常简单。但是它的主要缺点是它不具有任何基于PID的控制机制。现在,假设机器人正在自我清洁,并且它靠近楼梯,它在机器人下方有一个接近传感器,可以检测到这种情况并切断电动机的电源,但是由于惯性,机器人不会立即停止运转。如果发生这种情况,则机器人跳下楼梯的可能性很高。现在,想象一下您有一辆自动驾驶汽车,并且想要将其停在某个位置,如果没有PID,这将非常困难,因为如果您仅切断动力,由于其动力,汽车将绝对无法实现其目标。下图将使您对该过程有更好的了解。

PID-Controllers.jpg


现在我们知道了这个概念,可以继续了解一些高级部分。如果您在线搜索PID控制器,您将获得的第一个结果是PID控制器-Wikipedia,在文章中,您将找到一个框图以及一个方程式。但是,这个方程甚至意味着什么?我们如何在微控制器中实现它?很好的问题,现在继续进行,您将了解如何实现:

PID-Controller-Block-Diagram.png


该控制器的名称是根据误差的处理方式,在汇总之后再发送到工厂/过程中。让我解释!在框图中,您可以看到在比例路径中,误差乘以常数Kp。在积分路径中,误差乘以常数Ki,然后将其积分;在微分路径中,误差乘以Kd,然后求微分。之后,将三个值相加在一起以产生输出。现在,在控制器中,Kp、Kd和Ki参数称为增益。然后对它们进行调整,以满足一组特定的要求,并通过更改这些值,可以调整系统对这些不同参数(P、I或D参数)的敏感程度。让我通过单独检查每个参数来解释它。


P控制器:

Proportional-Controller.png


假设您在红线中看到的是系统误差随着时间而改变。在比例控制器中,输出是由增益Kp定义的误差。如您所见,当误差较大时,输出将产生较大的输出,而当误差为零时,输出误差为零,而当误差为负时,输出结果为负。


积分控制器:

Integral-Controller.png

在积分控制器中,随着误差值随时间变化,积分将开始对误差开始进行求和,并将其与常数Ki相乘。在这种类型的控制器中,很容易看到积分结果是曲线下方的区域,其中蓝色显示的区域为正区域,黄色显示的区域为负区域。在复杂的系统中,积分控制器用于消除控制系统中的恒定误差。不管恒定误差多么小,最终,误差的总和将足以调整控制器的输出。在上图中,误差用绿线表示。


微分控制器:

Derivative-Controller.png

在微分控制器中,误差的变化率决定了输出信号。例如,当误差变化相对缓慢时,我们可以使用正弦波的起始位置。如上图所示,微分输出将很小(以绿线表示)。误差变化越快,输出越大。


现在,您可以总结三个输出,并且有了PID控制器。但是通常您不需要所有三个控制器一起工作,而是可以通过将设定值设置为零来删除。例如,我们可以通过将D值设置为零来拥有一个PI控制器,也可以通过将I参数设置为零来拥有一个PD控制器。现在我们可以进行实际的硬件示例。


什么是编码器电机,它如何工作?

编码器电机的概念非常简单:这是一种有刷直流电机,上面附有编码器。

Encoder-Motor.jpg


在编码器电机中,旋转编码器安装在直流电动机上,该直流电动机通过跟踪电动机轴的速度或位置向系统提供反馈。有许多不同类型的电动机可用,所有这些电动机都可以具有不同类型的编码器配置,例如增量或绝对式、光学、空心轴、磁力,并且还在不断增加。针对不同类型的应用制造了不同类型的电动机。不仅直流电动机,而且许多伺服电动机、步进电动机和交流电动机都带有内置编码器。在上图中,您可以看到一台N20永磁型编码电动机,该电动机在连接的变速箱的帮助下将输出RPM降低到15。您还可以看到两个霍尔传感器连接到PCB。这些霍尔传感器获取电动机旋转的方向,借助微控制器,我们可以很容易地读取它。


所需的组件

●    Arduino Nano开发板

●    N20编码器电机

●    BD139

●    BD140

●    BC548

●    100R电阻

●    4.7K电阻

●    面包板

●    跳线

●    电源

Encoder-Motor-Controller-Components.jpg


启用PID的编码器电机控制器的示意图

PID启用编码器电机控制器的完整示意图如下所示。该电路的工作原理非常简单,下面将对其进行描述。

PID-Enabled-Encoder-Motor-Controller-Circuit-Diagram.png


首先,在示意图中,我们使用N20编码器电机,该电机具有六个引脚,这些引脚标记为M1、M2,用于为电机供电,因为这是一个额定值为3.3V的非常小的电机。接下来,我们使用VCC和GND引脚为编码器电路供电。要为编码器电路供电,必须给它提供+ 5V电压,否则编码器电路将无法正常工作。接下来,我们有电动机的PIN_A和PIN_B。这两个引脚直接连接到编码器。通过读取这些引脚的状态,我们可以轻松地测量RPM,这台15RPM N20电动机的齿轮比为1:2098,这意味着主电动机轴需要旋转2098次才能使辅助轴旋转一次。 PIN_A和PIN_B连接到Arduino的引脚9,引脚9和10的引脚10都具有PWM功能;所选的引脚必须具有PWM功能,否则代码将无法工作。 PID控制器通过控制PWM来控制电动机。


接下来,我们使用H桥电机驱动器,电机驱动器的制作方法使得我们仅需使用Arduino的两个引脚就可以控制电机,甚至可以防止电机错误触发。

PID-Enabled-Motor-Controller.jpg


用于启用PID的编码器电机控制器的Arduino代码

添加所需的头文件和源文件后,您应该能够直接编译Arduino代码,而不会出现任何错误。您可以从下面给出的链接下载PID控制器库,也可以使用板管理器方法安装该库。

下载用于Arduino的PID控制器库


代码说明。文件如下。首先,我们首先包含所有必需的库。在此程序中,我们仅使用PID控制器库,因此我们需要首先包含它。之后,我们定义读取编码器和驱动电机所需的所有必需的引脚。完成后,我们将定义Kp、Ki和Kd的所有值。

  1. #include <PIDController.h>
  2. /* ENCODER_A and ENCODER_B pins are used to read the encoder
  3. * Data from the microcontroller, the data from the encoder
  4. * comes very fast so these two pins have to be interrupt enabled
  5. * pins
  6. */
  7. #define ENCODER_A 2
  8. #define ENCODER_B 3
  9. /* the MOTOR_CW and MOTOR_CCW pins are used to drive the H-bridge
  10. * the H-bridge then drives the motors, These two pins must have to
  11. * be PWM enabled, otherwise the code will not work.
  12. */
  13. #define MOTOR_CW 9
  14. #define MOTOR_CCW 10
复制代码

接下来,我们为代码定义了__Kp、__Ki和__Kd值。这三个常量负责为我们的代码设置输出响应。在这一点上,请注意,对于该项目,我使用了反复试验方法来设置常量,但是还有其他方法可以很好地完成工作。

  1. /*In this section we have defined the gain values for the
  2. * proportional, integral, and derivative controller I have set
  3. * the gain values with the help of trial and error methods.
  4. */
  5. #define __Kp 260 // Proportional constant
  6. #define __Ki 2.7 // Integral Constant
  7. #define __Kd 2000 // Derivative Constant
复制代码

接下来,我们定义了此代码中所有必需的变量。首先,我们使用了encoder_count变量,该变量用于计算所产生的中断数。因此,它计算匝数。接下来,我们定义了一个无符号的int类型变量整数值,该值存储了我们放入串口监视器中的值。接下来,我们定义了一个char类型的变量传入字节,该变量临时存储传入的串行数据。接下来,我们在此代码中定义了最重要的变量,它是通过PWM算法计算出数据并将其存储在此变量之后的motor_pwm_value变量。定义这些变量后,我们将为PID控制器创建一个实例。完成此操作后,就可以进入setup()函数了。

  1. volatile long int encoder_count = 0; // stores the current encoder count
  2. unsigned int integerValue = 0; // stores the incoming serial value. Max value is 65535
  3. char incomingByte; // parses and stores each character one by one
  4. int motor_pwm_value = 255; // after PID computation data is stored in this variable.
  5. PIDController pid_controller;
复制代码

在setup函数中,我们已将ENCODER_A和ENCODER_B引脚分配为输入,并已将MOTOR_CW和MOTOR_CCW引脚定义为输出。接下来,我们将ENCODER_A分配为中断,并在上升沿,将调用函数encoder();。接下来的三行代码是最重要的,因为我们已经使用begin()方法启用了PID控制器,并且还使用Kp、Ki和Kd值对控制器进行了调整。最后,我们为PID控制器输出设置了上限。

  1. void setup() {
  2.   Serial.begin(115200); // Serial for Debugging
  3.   pinMode(ENCODER_A, INPUT); // ENCODER_A as Input
  4.   pinMode(ENCODER_B, INPUT); // ENCODER_B as Input
  5.   pinMode(MOTOR_CW, OUTPUT); // MOTOR_CW as Output
  6.   pinMode(MOTOR_CCW, OUTPUT); // MOTOR_CW as Output
  7. /* attach an interrupt to pin ENCODER_A of the Arduino, and when the pulse is in the RISING edge called the function encoder().
  8. */
  9.   attachInterrupt(digitalPinToInterrupt(ENCODER_A), encoder, RISING);
  10.   pidcontroller.begin(); // initialize the PID instance
  11.   pidcontroller.tune(__Kp , __Ki , __Kd); // Tune the PID, arguments: kP, kI, kD
  12.   pidcontroller.limit(-255, 255); // Limit the PID output between -255 to 255, this is important to get rid of integral windup!
  13. }
复制代码

接下来,在loop()函数中,我们首先检查串口是否可用。如果串口可用,我们将解析整数值并将其保存到整数值变量。接下来,我们有一个'/ n'字符。我们将其放在入库字节变量中,并使用if语句检查此变量,如果为true,则继续循环,接下来,使用 pidcontroller.setpoint(integerValue);设置目标点并传递刚刚从串口接收的整数值。接下来,我们打印接收到的值以进行调试。
我们有motor_pwm_value变量,我们计算PID值并将其放在此变量中。如果该值大于零,则调用motor_ccw(motor_pwm_value)函数并传递该值,否则,我们调用motor_cw(abs(motor_pwm_value))函数。

  1. void loop() {
  2.   while (Serial.available() > 0) {
  3.     integerValue = Serial.parseInt(); // stores the integerValue
  4.     incomingByte = Serial.read(); // stores the /n character
  5.     pidcontroller.setpoint(integerValue); // The "goal" the PID controller tries to "reach",
  6.     Serial.println(integerValue); // print the incoming value for debugging
  7.     if (incomingByte == '\n') // if we receive a newline character we will continue in the loop
  8.       continue;
  9.   }
  10.   motor_pwm_value = pidcontroller.compute(encoder_count);  //Let the PID compute the value, returns the calculated optimal output
  11.   Serial.print(motor_pwm_value); // print the calculated value for debugging
  12.   Serial.print("   ");
  13.   if (motor_pwm_value > 0) // if the motor_pwm_value is greater than zero we rotate the  motor in clockwise direction
  14.     MotorCounterClockwise(motor_pwm_value);
  15.   else // else, we move it in a counter-clockwise direction
  16.     MotorClockwise(abs(motor_pwm_value));
  17.   Serial.println(encoder_count);// print the final encoder count.
  18. }
复制代码

接下来,我们介绍encoder()函数。当ENCODER_B中出现上升沿中断时,将调用此函数。如果为true,则再次使用if(digitalRead(ENCODER_B)== HIGH)检查该语句。一旦为真,我们的计数器变量就会增加。否则,它会递减。

  1. void encoder() {
  2.   if (digitalRead(ENCODER_B) == HIGH) // if ENCODER_B is high increase the count
  3.     Encoder_count++; // increment the count
  4.   else // else decrease the count
  5.     Encoder_count--; // decrement the count
  6. }
复制代码

接下来,我们具有使电动机顺时针旋转的函数motor_cw。调用此函数时,它将检查该值是否大于100。如果是,请沿顺时针方向旋转电动机,否则将停止电动机。

  1. void motor_cw(int power) {
  2.   if (power > 100) {
  3.     analogWrite(MOTOR_CW, power);
  4.     digitalWrite(MOTOR_CCW, LOW);
  5.   }
  6. // both of the pins are set to low
  7.   else {
  8.     digitalWrite(MOTOR_CW, LOW);
  9.     digitalWrite(MOTOR_CCW, LOW);
  10.   }
  11. }
复制代码

逆时针旋转电机的函数也是一样。调用此函数时,我们检查该值并逆时针旋转电动机。

  1. void motor_ccw(int power) {
  2.   if (power > 100) {
  3.     analogWrite(MOTOR_CCW, power);
  4.     digitalWrite(MOTOR_CW, LOW);
  5.   }
  6.   else {
  7.     digitalWrite(MOTOR_CW, LOW);
  8.     digitalWrite(MOTOR_CCW, LOW);
  9.   }
  10. }
复制代码

测试启用PID的电机控制器

以下设置用于测试电路。 如您所见,我使用了带有一些双面胶带的电箱将电机固定在适当的位置,并且当电机以3.3V运行时,我使用了小型降压转换器模块为电机供电。

Encoder-Motor-Controller.jpg


您还可以看到,我们已将USB电缆与Arduino连接,用于设置PID控制器的设定值。 我们还可以通过USB从Arduino获得调试信息。 在本文中,它将给出当前的编码器计数。


希望您喜欢这篇文章并学到新知识。 如果您对本文有任何疑问,可以在本帖下面进行回复。

跳转到指定楼层
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

主题 705 | 回复: 1492



手机版|

GMT+8, 2024-11-21 18:43 , Processed in 0.055739 second(s), 8 queries , Gzip On, MemCache On. Powered by Discuz! X3.5

YiBoard一板网 © 2015-2022 地址:河北省石家庄市长安区高营大街 ( 冀ICP备18020117号 )

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