风筝
发表于: 2021-11-15 15:59:20 | 显示全部楼层

在这篇文章中,我们决定制作一个基于PID控制器的加热器,可用于控制3D打印机热端的温度,或者通过稍微修改设备,它可以非常有效地控制直流烙铁的温度,甚至经过更多点的调整,您可以控制晶闸管实现控制交流电机或交流加热元件的转速,PID控制的领域是无限可能的。


什么是温度PID控制器?

顾名思义,温度PID控制器处理温度,PID温度控制是一种闭环控制算法,可以提高过程的准确性。 PID温度控制使用数学公式来计算当前温度和设定值之间的差异。然后它会尝试提供所需的功率以确保目标温度保持恒定,这不仅减少了对环境的影响,还减少了传统开关控制机制中可能出现的过冲。


温度PID控制器如何工作?

就像在任何 PID 控制中一样,我们首先需要知道输出或我们希望控制器做什么,对于这个项目,我们希望保持加热元件的某个温度,所以为了保持温度,我们需要读出温度,为此我们使用K型热电偶,结合MAX6675冷端补偿K型热电偶到数字转换器IC,可以测量数百摄氏度没有任何问题。来自热电偶的温度读数作为反馈。现在我们已经设置了我们想要达到的温度并且我们有温度值的实时读数,控制器可以计算误差值,并且在比例积分和微分控制的帮助下,系统可以实现它的目标,对于这个项目我们将使用计算出的输出值控制PWM信号。这就是基于温度的PID控制器的工作原理。


MAX6675 K型热电偶IC的工作过程

MAX6675-K-Thermocouple-IC-Circuit-Diagram.jpg

热电偶的作用是感应热电偶导线两端的温差。热电偶的热结可以在 0°C 至 +1023.75°C 范围内读取。冷端(安装 MAX6675 的电路板的环境温度)只能在 -20°C 至 +85°C 的范围内。在冷端温度波动的同时,MAX6675继续准确感应对端的温差。 MAX6675通过冷端补偿检测并校正环境温度的变化。该器件使用温度感应二极管将环境温度读数转换为电压。为了进行实际的热电偶温度测量,MAX6675测量来自热电偶输出和检测二极管的电压。该器件的内部电路将二极管的电压(感测环境温度)和热电偶电压(感测远程温度减去环境温度)传递给存储在 ADC 中的转换函数,以计算热电偶的热结温度。当热电偶冷端和MAX6675处于相同温度时,MAX6675可实现最佳性能。 MAX6675包括信号调理硬件,可将热电偶的信号转换为与ADC输入通道兼容的电压。 T+ 和 T 输入连接到内部电路,以减少从热电偶线引入的噪声误差。


所需的组件

下面列出了构建基于 MAX6675 的 PID 控制加热器所需的组件,我们使用非常通用的组件设计了该电路,这使得复制过程非常容易。

●    Arduino Nano开发板

●    12864 OLED显示屏模块

●    通用旋转编码器

●    MAX6675 模块

●    K型热电偶

●    面包板

●    连接导线

Components-Required-to-Build-PID-Enabled -ncoder-Motor-Controller.jpg


PID使能温度控制器电路图

PID-Enabled-Temperature-Controller-Circuit.jpg

在本文中,我们使用MAX6675 K型热电偶传感器从热电偶读取温度数据,在本节中,我们将借助原理图解释所有组件。MAX6675是冷端补偿 K 型热电偶到数字转换器模块,它根据原理图连接到Arduino。使用Arduino的+5V电源为电路供电。此外,为了设置温度和更改模式,我们使用通用旋转编码器。接下来,我们使用12864 OLED显示屏显示温度数据,还可以显示设定温度。一如既往,我们使用Arduino开发板作为项目的主控。通过按下旋转编码器上的按钮,我们可以在两种模式之间切换,一种是设置温度,另一种是从热电偶监测温度。

PID-Enabled-Temperature-Controller-Circuit-Diagram.jpg

跳转到指定楼层
风筝
发表于: 2021-11-15 16:26:15 | 显示全部楼层

基于MAX6675的PID温度控制器代码

首先您可以从下面给出的链接下载PID控制器库、MAX6675库、AAdafruit_SSD1306库,或者您可以使用开发板管理器方法安装库。

●    下载适用于Arduino的PID控制器库

●    下载适用于Arduino的MAX6675库

●    下载适用于Arduino的Adafruit_SSD1306库


代码非常简单,在本节中,我们将进行讲解。首先,包含所有必需的库,然后定义读取编码器、驱动OLED和MAX6675热电偶温度传感器所需的引脚编号。完成后,我们定义Kp、Ki 和 Kd 的所有值,以及所有必需的变量。


接下来,在代码中定义了 __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 30 // Proportional constant
  6. #define __Ki 0.7 // Integral Constant
  7. #define __Kd 200 // Derivative Constant
复制代码

接下来,我们声明所有必需的变量并创建三个实例,一个用于PID,一个用于OLED,最后一个用于热电偶。变量clockPin和clockPinState、debounce和encoder_btn_count这四个用于从编码器读取数据, temperature_value_c保存了热电偶的温度读数,最后encoder_btn_count保存了编码器按钮被按下的次数。

  1. #include <SPI.h>
  2. #include <Wire.h>
  3. #include <Adafruit_GFX.h>
  4. #include <Adafruit_SSD1306.h>
  5. #include <PIDController.h>
  6. #include "max6675.h"
  7. // Define Rotary Encoder Pins
  8. #define CLK_PIN 3
  9. #define DATA_PIN 4
  10. #define SW_PIN 2
  11. // MAX6675 Pins
  12. #define thermoDO 8
  13. #define thermoCS 9
  14. #define thermoCLINE 10
  15. // Mosfet Pin
  16. #define mosfet_pin 11
  17. // Serial Enable
  18. #define __DEBUG__
  19. #define SCREEN_WIDTH 128 // OLED display width, in pixels
  20. #define SCREEN_HEIGHT 64 // OLED display height, in pixels
  21. #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
  22. int clockPin; // Placeholder por pin status used by the rotary encoder
  23. int clockPinState; // Placeholder por pin status used by the rotary encoder
  24. int set_temperature = 1; // This set_temperature value will increas or decreas if when the rotarty encoder is turned
  25. float temperature_value_c = 0.0; // stores temperature value
  26. long debounce = 0; // Debounce delay
  27. int encoder_btn_count = 0; // used to check encoder button press
  28. MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO); // Create an instance for the MAX6675 Sensor Called "thermocouple"
  29. Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);// Create an instance for the SSD1306 128X64 OLED "display"
  30. PIDController pid; // Create an instance of the PID controller class, called "PID"
复制代码

接下来,在setup()函数中,首先我们启用串口监视器进行调试,Serial.begin() 法可以用#ifdef和#endif语句包装,以便我们可以在代码完成后轻松禁用串口监视器。

  1. void setup() {
  2. #ifdef __DEBUG__
  3.   Serial.begin(9600);
  4. #endif
复制代码

接下来,我们将所有必需的引脚设置为项目工作所需的输入和输出。

  1. pinMode(mosfet_pin, OUTPUT); // MOSFET output PIN
  2.   pinMode(CLK_PIN, INPUT); // Encoer Clock Pin
  3.   pinMode(DATA_PIN, INPUT); //Encoder Data Pin
  4.   pinMode(SW_PIN, INPUT_PULLUP);// Encoder SW Pin
复制代码

接下来,我们通过调用PID实例的begin()方法来初始化 PID 控制器。然后使用setpoint()方法添加设定点,PID算法将尝试通过控制输出来达到该值。接下来,我们调用tune()方法,这是我们放置 __Kp、__Ki 和 __Kd 的所有值的地方。最后,我们为 PID 控制器设置了一个极限值,这个极限值将允许计算输出在范围内,这确保计算输出不会超过我们的PWM值255的特定值。

  1. pid.begin();          // initialize the PID instance
  2. pid.setpoint(150);    // The "goal" the PID controller tries to "reach"
  3. pid.tune(__Kp, __Ki,__Kd);    // Tune the PID, arguments: kP, kI, kD
  4. pid.limit(0, 255);    // Limit the PID output between 0 and 255, this is important to get rid of integral windup!
复制代码

接下来,我们检查是否有可用的显示屏,如果显示屏可用,代码将继续,如果显示不可用,它将打印错误。

  1. if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
  2. #ifdef __DEBUG__
  3.     Serial.println(F("SSD1306 allocation failed"));
  4. #endif
  5.     for (;;); // Don't proceed, loop forever
  6. }
复制代码

接下来,我们设置显示屏,首先旋转显示屏并在显示屏上播放启动画面,接下来我们擦除显示,使用setTextSize和setTextColour设置为白色。接下来,我们将光标设置在不同的位置并在显示屏上打印 PID Temperatur Control,然后延时2秒结束。

  1. display.setRotation(2); //Rotate the Display
  2. display.display(); //Show initial display buffer contents on the screen -- the library initializes this with an Adafruit splash screen.
  3. display.clearDisplay(); // Cleear the Display
  4. display.setTextSize(2); // Set text Size
  5. display.setTextColor(WHITE); // set LCD Colour
  6. display.setCursor(48, 0); // Set Cursor Position
  7. display.println("PID"); // Print the this Text
  8. display.setCursor(0, 20);  // Set Cursor Position
  9. display.println("Temperatur"); // Print the this Text
  10. display.setCursor(22, 40); // Set Cursor Position
  11. display.println("Control"); // Print the this Text
  12. display.display(); // Update the Display
  13. delay(2000); // Delay of 200 ms
复制代码

set_temp()函数中,我们设置加热器的温度,并检查按钮状态,如果状态为2,那么我们首先擦除显示设置文本大小,并将设置的温度打印在显示上。read_encoder()函数读取编码器并检查编码器是顺时针还是逆时针旋转,如果编码器顺时针旋转,我们增加计数器如果编码器逆时针旋转我们减少。最后,我们还通过编码器按钮检查编码器按钮状态,我们可以在温度设置模式和监控模式之间切换。

  1. void set_temp(){
  2.   if (encoder_btn_count == 2) // check if the button is pressed twice and its in temperature set mode.
  3.   {
  4.     display.clearDisplay(); // clear the display
  5.     display.setTextSize(2); // Set text Size
  6.     display.setCursor(16, 0); // set the diplay cursor
  7.     display.print("Set Temp."); // Print Set Temp. on the display
  8.     display.setCursor(45, 25); // set the cursor
  9.     display.print(set_temperature);// print the set temperature value on the display
  10.     display.display(); // Update the Display
  11.   }
  12. }

  13. void read_encoder() // In this function we read the encoder data and increment the counter if it is rotating clockwise and decrement the counter if it's rotating counterclockwise
  14. {
  15.   clockPin = digitalRead(CLK_PIN); // we read the clock pin of the rotary encoder
  16.   if (clockPin != clockPinState && clockPin == 1) { // if this condition is true then the encoder is rotaing counter clockwise and we decremetn the counter
  17.     if (digitalRead(DATA_PIN) != clockPin) set_temperature = set_temperature - 3;  // decrmetn the counter.
  18.     else set_temperature = set_temperature + 3; // Encoder is rotating CW so increment
  19.     if (set_temperature < 1 )set_temperature = 1; // if the counter value is less than 1 the set it back to 1
  20.     if (set_temperature > 150 ) set_temperature = 150; //if the counter value is grater than 150 then set it back to 150
  21. #ifdef __DEBUG__
  22.     Serial.println(set_temperature); // print the set temperature value on the serial monitor window
  23. #endif
  24.   }
  25.   clockPinState = clockPin; // Remember last CLK_PIN state
  26.   if ( digitalRead(SW_PIN) == LOW)   //If we detect LOW signal, button is pressed
  27.   {
  28.     if ( millis() - debounce > 80) { //debounce delay
  29.       encoder_btn_count++; // Increment the values
  30.       if (encoder_btn_count > 2) encoder_btn_count = 1;
  31. #ifdef __DEBUG__
  32.       Serial.println(encoder_btn_count);
  33. #endif
  34.     }
  35.     debounce = millis(); // update the time variable
  36.   }
  37. }
复制代码

loop()函数中我们调用read_encoder() 和set_temp() 函数,这些函数会被不断调用,在该循环中,我们还会检查按钮模式,如果设置为1,我们从热电偶读取温度并放入它通过PID实例的计算方法。计算完成后,我们直接将计算值放入输出PWM 信号的模拟写入函数中。一切都完成后,我们只需更新显示。

  1. void loop()
  2. {
  3.   read_encoder(); //Call The Read Encoder Function
  4.   set_temp(); // Call the Set Temperature Function
  5.   if (encoder_btn_count == 1) // check if the button is pressed and it's in Free Running mode -- in this mode, the Arduino continuously updates the screen and adjusts the PWM output according to the temperature.
  6.   {
  7.     temperature_value_c = thermocouple.readCelsius(); // Read the Temperature using the readCelsius methode from MAX6675 Library.
  8.     int output = pid.compute(temperature_value_c);    // Let the PID compute the value, returns the optimal output
  9.     analogWrite(mosfet_pin, output);           // Write the output to the output pin
  10.     pid.setpoint(set_temperature); // Use the setpoint methode of the PID library to
  11.     display.clearDisplay(); // Clear the display
  12.     display.setTextSize(2); // Set text Size
  13.     display.setCursor(16, 0); // Set the Display Cursor
  14.     display.print("Cur Temp."); //Print to the Display
  15.     display.setCursor(45, 25);// Set the Display Cursor
  16.     display.print(temperature_value_c); // Print the Temperature value to the display in celcius
  17.     display.display(); // Update the Display
  18. #ifdef __DEBUG__
  19.     Serial.print(temperature_value_c); // Print the Temperature value in *C on serial monitor
  20.     Serial.print(" "); // Print an Empty Space
  21.     Serial.println(output); // Print the Calculate Output value in the serial monitor.
  22. #endif
  23.     delay(200); // Wait 200ms to update the OLED dispaly.
  24.   }
  25. }
复制代码

PID温度控制器测试

为了测试电路,使用了以下设置,Meco 450B+万用表显示Arduino引脚11的输出PWM信号的占空比。我使用了包含12V加热元件的3D打印机作为加热器,同时使用5V电源为Arduino供电。

PID-Enabled-Temperature-Controller-Testing-Setup.jpg

现在,要设置温度,您需要按下旋转编码器的按钮,该按钮设置了设定值或PID算法的目标温度,再次按下按钮使更改生效,加热器块开始加热,您可以看到占空比也增加了,我将温度设置为64℃。

PID-Enabled-Temperature-Controller-Testing-at-64C.jpg


一旦达到所需温度,PWM占空比就会降低,当控制器想要补偿误差并提高温度时,您可以观察到占空比中的某个尖峰。

PID-Enabled-Temperature-Controller-Testing.jpg

以上就是本文的全部内容,希望您喜欢这篇文章并学到了一些新东西。如果您对文章有任何疑问,可以随时在本帖下面进行回复。

回复

使用道具 举报

GNDC
发表于: 2021-12-23 11:58:44 | 显示全部楼层

您好,试着这个实验,有点问题请教:thermoCLINE应该为thermoCLK吧?旋转编码器只能加不能减,而且很不灵敏,是未连接MAX6675模块的缘故?还是代码哪里有冲突啊?
回复

使用道具 举报

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

本版积分规则

主题 700 | 回复: 1480



手机版|

GMT+8, 2024-4-20 14:07 , Processed in 0.137024 second(s), 6 queries , Gzip On, MemCache On. Powered by Discuz! X3.5

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

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