Исходный код программы (скетча)
if ( digitalRead(SW_PIN) == LOW) //If we detect LOW signal, button is pressed
{
if ( millis() – debounce > 80) { //debounce delay
encoder_btn_count ; // Increment the values
if (encoder_btn_count > 2) encoder_btn_count = 1;
#ifdef __DEBUG__
Serial.println(encoder_btn_count);
#endif
}
debounce = millis(); // update the time variable
}
}
void loop()
{
read_encoder(); //Call The Read Encoder Function
set_temp(); // Call the Set Temperature Function
if (encoder_btn_count == 1) // check if the button is pressed and its in Free Running Mode — in this mode the arduino continiously updates the screen and adjusts the PWM output according to the temperature.
{
temperature_value_c = thermocouple.readCelsius(); // Read the Temperature using the readCelsius methode from MAX6675 Library.
int output = pid.compute(temperature_value_c); // Let the PID compute the value, returns the optimal output (вычисляем оптимальное значение с помощью ПИД алгоритма)
analogWrite(mosfet_pin, output); // Write the output to the output pin
pid.setpoint(set_temperature); // Use the setpoint methode of the PID library to (устанавливаем целевую точку для ПИД алгоритма)
display.clearDisplay(); // Clear the display
display.setTextSize(2); // Set text Size
display.setCursor(16, 0); // Set the Display Cursor
display.print(“Cur Temp.”); //Print to the Display
display.setCursor(45, 25);// Set the Display Cursor
display.print(temperature_value_c); // Print the Temperature value to the display in celcius
display.display(); // Update the Display
#ifdef __DEBUG__
Serial.print(temperature_value_c); // Print the Temperature value in *C on serial monitor (выводим значение температуры в окно монитора последовательной связи)
Serial.print(” “); // Print an Empty Space
Serial.println(output); // Print the Calculate Output value in the serial monitor.
#endif
delay(200); // Wait 200ms to update the OLED dispaly. (ждем 200 мс чтобы обновить OLED дисплей)
}
}
#include <SPI.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <PIDController.h> #include “max6675.h” // Define Rotary Encoder Pins (контакты энкодера) #define CLK_PIN 3 #define DATA_PIN 4 #define SW_PIN 2 // MAX6675 Pins #define thermoDO 8 #define thermoCS 9 #define thermoCLK 10 // Mosfet Pin (контакты Mosfet транзистора) #define mosfet_pin 11 // Serial Enable #define __DEBUG__ #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) /*In this section we have defined the gain values for the * proportional, integral, and derivative controller I have set * the gain values with the help of trial and error methods. */ #define __Kp 30 // Proportional constant #define __Ki 0.7 // Integral Constant #define __Kd 200 // Derivative Constant intclockPin;// Placeholder por pin status used by the rotary encoder intclockPinState;// Placeholder por pin status used by the rotary encoder intset_temperature=1;// This set_temperature value will increas or decreas if when the rotarty encoder is turned (значение температуры, будет изменяться при вращении ручки энкодера) floattemperature_value_c=0.0;// stores temperature value (будет хранить значение температуры) longdebounce=0;// Debounce delay (задержка для устранения эффекта дребезга контактов) intencoder_btn_count=0;// used to check encoder button press (счетчик нажатий кнопки энкодера) MAX6675thermocouple(thermoCLK,thermoCS,thermoDO);// Create an instance for the MAX6675 Sensor Called “thermocouple” (создаем объект для работы с термопарой) Adafruit_SSD1306display(SCREEN_WIDTH,SCREEN_HEIGHT,&Wire,OLED_RESET);// Create an instance for the SSD1306 128X64 OLED “display” (создаем объект для работы с дисплеем) PIDControllerpid;// Create an instance of the PID controller class, called “pid” (создаем объект для работы с ПИД алгоритмом) voidsetup(){ #ifdef __DEBUG__ Serial.begin(9600); #endif pinMode(mosfet_pin,OUTPUT);// MOSFET output PIN pinMode(CLK_PIN,INPUT);// Encoer Clock Pin pinMode(DATA_PIN,INPUT);//Encoder Data Pin pinMode(SW_PIN,INPUT_PULLUP);// Encoder SW Pin pid.begin(); // initialize the PID instance (инициализируем ПИД алгоритм) pid.setpoint(150); // The “goal” the PID controller tries to “reach” (задаем целевую точку для его работы) pid.tune(__Kp,__Ki,__Kd); // Tune the PID, arguments: kP, kI, kD (производим начальную настройку ПИД алгоритма) pid.limit(0,255); // Limit the PID output between 0 and 255, this is important to get rid of integral windup! (задаем пределы работы ПИД алгоритма) if(!display.begin(SSD1306_SWITCHCAPVCC,0x3C)){ #ifdef __DEBUG__ Serial.println(F(“SSD1306 allocation failed”)); #endif for(;;);// Don’t proceed, loop forever (бесконечный цикл) } // display.setRotation(2);//Rotate the Display display.display();//Show initial display buffer contents on the screen — the library initializes this with an Adafruit splash screen. display.clearDisplay();// Cleear the Display display.setTextSize(2);// Set text Size display.setTextColor(WHITE);// set LCD Colour display.setCursor(48,0);// Set Cursor Position display.println(“PID”);// Print the this Text display.setCursor(0,20); // Set Cursor Position display.println(“Temperatur”);// Print the this Text display.setCursor(22,40);// Set Cursor Position display.println(“Control”);// Print the this Text display.display();// Update the Display delay(2000);// Delay of 200 ms } voidset_temp() { if(encoder_btn_count==2)// check if the button is pressed twice and its in temperature set mode. (если кнопка нажата дважды, то используем режим установки температуры) { display.clearDisplay();// clear the display display.setTextSize(2);// Set text Size display.setCursor(16,0);// set the diplay cursor display.print(“Set Temp.”);// Print Set Temp. on the display display.setCursor(45,25);// set the cursor display.print(set_temperature);// print the set temperature value on the display display.display();// Update the Display } } voidread_encoder()// In this function we read the encoder data and increment the counter if its rotaing clockwise and decrement the counter if its rotating counter clockwis { clockPin=digitalRead(CLK_PIN);// we read the clock pin of the rotary encoder if(clockPin!=clockPinState &&clockPin==1){// if this condition is true then the encoder is rotaing counter clockwise and we decremetn the counter (энкодер вращается по часовой стрелке) if(digitalRead(DATA_PIN)!=clockPin)set_temperature=set_temperature–3; // decrmetn the counter. else set_temperature=set_temperature 3;// Encoder is rotating CW so increment (энкодер вращается против часовой стрелке) if(set_temperature<1)set_temperature=1;// if the counter value is less than 1 the set it back to 1 if(set_temperature>150)set_temperature=150;//if the counter value is grater than 150 then set it back to 150 #ifdef __DEBUG__ Serial.println(set_temperature);// print the set temperature value on the serial monitor window (выводим установленное значение температуры в окно монитора последовательной связи) #endif } clockPinState=clockPin;// Remember last CLK_PIN state (запоминаем последнее состояние CLK_PIN) if(digitalRead(SW_PIN)==LOW) //If we detect LOW signal, button is pressed { if(millis()–debounce>80){//debounce delay encoder_btn_count ;// Increment the values if(encoder_btn_count>2)encoder_btn_count=1; #ifdef __DEBUG__ Serial.println(encoder_btn_count); #endif } debounce=millis();// update the time variable } } voidloop() { read_encoder();//Call The Read Encoder Function set_temp();// Call the Set Temperature Function if(encoder_btn_count==1)// check if the button is pressed and its in Free Running Mode — in this mode the arduino continiously updates the screen and adjusts the PWM output according to the temperature. { temperature_value_c=thermocouple.readCelsius();// Read the Temperature using the readCelsius methode from MAX6675 Library. intoutput=pid.compute(temperature_value_c); // Let the PID compute the value, returns the optimal output (вычисляем оптимальное значение с помощью ПИД алгоритма) analogWrite(mosfet_pin,output); // Write the output to the output pin pid.setpoint(set_temperature);// Use the setpoint methode of the PID library to (устанавливаем целевую точку для ПИД алгоритма) display.clearDisplay();// Clear the display display.setTextSize(2);// Set text Size display.setCursor(16,0);// Set the Display Cursor display.print(“Cur Temp.”);//Print to the Display display.setCursor(45,25);// Set the Display Cursor display.print(temperature_value_c);// Print the Temperature value to the display in celcius display.display();// Update the Display #ifdef __DEBUG__ Serial.print(temperature_value_c);// Print the Temperature value in *C on serial monitor (выводим значение температуры в окно монитора последовательной связи) Serial.print(” “);// Print an Empty Space Serial.println(output);// Print the Calculate Output value in the serial monitor. #endif delay(200);// Wait 200ms to update the OLED dispaly. (ждем 200 мс чтобы обновить OLED дисплей) } } |
Уроки ардуино. разработка контроллера элемента пельтье. пид регулятор температуры. | оборудование, технологии, разработки
Продолжим разработку контроллера элемента Пельтье начатую в уроке 36. Добавим к проекту регулятор температуры.
Предыдущий урок Список уроков Следующий урок
Регулятор температуры выполняет главную функцию холодильника – стабилизацию температуры в камере. Именно он во многом определяет основные параметры устройства: точность поддержания температуры и скорость реакции на возмущающие воздействия. Поэтому будем реализовывать регулятор температуры по закону ПИД регулирования.
Еще раз приведу структурную схему связи регуляторов мощности и температуры.
Регулятор температуры получает измеренное значение температуры в камере, сравнивает его с заданным и формирует значение заданной мощности для регулятора мощности. Итак, в регуляторе температуры:
- регулируемый параметр – температура в камере;
- регулирующий элемент – заданная мощность для регулятора мощности.
Для того, чтобы стабилизировать температуру надо ее измерять. Поэтому первое, что добавим в программу – это измерение температуры в камере. Заодно реализуем измерение температуры радиатора горячей стороны элемента Пельтье.
Реализация измерения температуры в камере и температуры радиатора горячей стороны модуля Пельтье.
Температуру измеряем интегральными датчиками DS18B20. Достаточно подробно о подключении датчиков такого типа к Ардуино рассказано в уроке 26. Для тех, кто не знает, как работать с этими термодатчиками следует внимательно прочитать урок 26.
Подключим датчики DS18B20 к плате Ардуино по стандартной схеме. Еще в начале разработки мы решили, что будем использовать аналоговые выводы A2 и A3 в дискретном режиме.
Собранная схема у меня выглядит так.
Теперь будем дорабатывать программу.
Подключим библиотеку OneWire:
#include <OneWire.h>
Добавим переменные для измеренных температур:
float measureTempRef; // измеренная температура в камере (< -200 – ошибка)
float measureTempRad; // измеренная температура радиатора (< -200 – ошибка)
Создадим объекты OneWire (датчики температуры).
OneWire sensTempRef (16); // датчик температуры в камере подключен к выводу 16 (A2)
OneWire sensTempRad (17); // датчик температуры радиатора подключен к выводу 17 (A3)
При измерении температуры могут возникнуть аппаратные ошибки датчиков DS18B20. Для индикации этих ошибок не будем заводить отдельные признаки. Договоримся, что значение температуры менее – 200 C° говорит об аппаратной ошибки измерения.
Последовательность действий для измерения температуры с помощью датчиков DS18B20 описана в уроке 26. Она состоит из трех операций:
- инициализация измерения температуры;
- пауза не менее 750 мс;
- чтение результата измерения из датчика и проверка контрольной суммы.
Инициализация измерения температуры реализуется 3 функциями библиотеки OneWire:
sensTempRef.reset(); // сброс шины 1-Wire
sensTempRef.write(0xCC, 1); // пропуск ROM
sensTempRef.write(0x44, 1); // инициализация измерения
Чтение результата измерения из датчика и проверки контрольной суммы требует следующих функций:
sensTempRef.reset(); // сброс шины 1-Wire
sensTempRef.write(0xCC, 1); // пропуск ROM
sensTempRef.write(0xBE, 1); // команда чтения памяти датчика
sensTempRef.read_bytes(bufData, 9); // чтение памяти датчика, 9 байтов
if ( OneWire::crc8(bufData, 8) == bufData[8] ) { //проверка CRC
Понятно, что блок инициализации надо выполнить в начале длинного цикла (1 сек) программы, а блок чтения результата ближе к концу длинного цикла. Время между выполнением этих блоков должно быть не менее 750 мс.
Но вопрос заключается в том, сколько времени требуется на выполнение функций класса OneWire. Можно ли для выполнения операций инициализации измерения и чтения результата использовать по одному циклу 20 мс или необходимо ”размазать” их на несколько циклов. Не забудем, что мы измеряем температуру с помощью двух датчиков DS18B20.
Я измерил время выполнения функций библиотеки OneWire. Методику измерения и результаты привел на форуме сайта в этой теме. Получилось, что:
- операция инициализации измерения температуры требует 2063 мкс;
- чтение результата с проверкой контрольной суммы занимает 6,8 мс.
В результате я решил инициализацию измерения температуры для обоих датчиков выполнить в одном цикле 20 мс на интервале 5.
//————————— интервал 5, инициализация измерения температуры
// датчик в камере
sensTempRef.reset(); // сброс шины 1-Wire
sensTempRef.write(0xCC, 1); // пропуск ROM
sensTempRef.write(0x44, 1); // инициализация измерения
// датчик радиатора
sensTempRad.reset(); // сброс шины 1-Wire
sensTempRad.write(0xCC, 1); // пропуск ROM
sensTempRad.write(0x44, 1); // инициализация измерения
}
А чтение результата с проверкой контрольной суммы для датчиков температур в камере и радиатора сделал в отдельных интервалах 45 и 46.
if (cycle20mcCount == 45) {
//————————— интервал 45, чтение датчика температуры камеры
sensTempRef.reset(); // сброс шины 1-Wire
sensTempRef.write(0xCC, 1); // пропуск ROM
sensTempRef.write(0xBE, 1); // команда чтения памяти датчика
sensTempRef.read_bytes(bufData, 9); // чтение памяти датчика, 9 байтов
if ( OneWire::crc8(bufData, 8) == bufData[8] ) { // проверка CRC
// правильно
measureTempRef= (float)((int)bufData[0] | (((int)bufData[1]) << 8)) * 0.0625 0.03125;
if ( (measureTempRef < MEASURE_MIN_TEMP) || (measureTempRef > MEASURE_MAX_TEMP))
measureTempRef= -300.; // ошибка измерения
}
else measureTempRef= -300.; // ошибка измерения
}
if (cycle20mcCount == 46) {
//————————— интервал 46, чтение датчика температуры радиатора
sensTempRad.reset(); // сброс шины 1-Wire
sensTempRad.write(0xCC, 1); // пропуск ROM
sensTempRad.write(0xBE, 1); // команда чтения памяти датчика
sensTempRad.read_bytes(bufData, 9); // чтение памяти датчика, 9 байтов
if ( OneWire::crc8(bufData, 8) == bufData[8] ) { // проверка CRC
// правильно
measureTempRad= (float)((int)bufData[0] | (((int)bufData[1]) << 8)) * 0.0625 0.03125;
if ( (measureTempRad < MEASURE_MIN_TEMP) || (measureTempRad > MEASURE_MAX_TEMP))
measureTempRad= -300.; // ошибка измерения
}
else measureTempRad= -300.; // ошибка измерения
}
Полученные результаты проверяются еще на верхнее и нижнее максимальное значение. Если температура в нашей системе больше 100 или ниже 40 C° это явная ошибка.
Осталось вывести измеренные температуры на компьютер для проверки.
Serial.print(” t=”); Serial.print(measureTempRef, 2); // температура в камере
Serial.print(” t=”); Serial.print(measureTempRad, 2); // температура радиатора
Полностью скетч программы с измерением температур в камере и радиатора можно загрузить по этой ссылке:
Загружаем скетч в плату, открываем монитор последовательного порта. Все работает.
Температура измеряется правильно.
Реализация ПИД регулятора температуры.
При разработке регулятора мощности (урок 39) я совершил небольшую ошибку. Для быстрого выключения регулятора я использовал условие setPower == 0. Т.е. при заданной мощности равной 0 регулятор мгновенно выключался, интегральное звено сбрасывалось. Быстрое выключение требуется для реализации в будущем аварийных режимов.
Но заданная мощность может на короткое время стать равной 0 под действием дифференцирующей составляющей. Я изменил условие аварийного выключения регулятора мощности:
if ( setPower >= 0) {
…
}
Теперь выключение регулятора происходит при любом отрицательном значении заданной мощности.
Итак. Создаем все переменные и константы, необходимые для регулятора температуры.
#define koeffRegTmpInt 0.002 // интегральный коэффициент регулятора температуры
#define koeffRegTmpPr 0.5 // пропорциональный коэффициент регулятора температуры
#define koeffRegTmpDif 50 // дифференцирующий коэффициент регулятора температуры
#define MAX_POWER 5. // максимальная выходная мощность контроллера
float regPwrInt=0; // интегральное звено регулятора мощности
float maxSetPower=MAX_POWER; // максимальная заданная мощность
float regTmpInt=0; // интегральное звено регулятора температуры
float regTmpPr; // пропорциональное звено регулятора температуры
float regTmpDif; // дифференциальное звено регулятора температуры
float regTmpErr; // ошибка рассогласования температуры
float regTmpErrPrev; // предыдущая ошибка рассогласования температуры
float setTempRef; // заданная температура в камере
Поясню только параметры для ограничения мощности.
Константа MAX_POWER определяет максимальную мощность, которую способен создать контроллер. Это аппаратные ограничения на блок питания, ключевой стабилизатор, модуль Пельтье и т.п.
Переменная maxSetPower задается пользователем и в любой момент может быть изменена кнопками контроллера. Она вводит дополнительное ограничение на выходную мощность. Например, кто-то хочет ограничить потребляемую мощность для экономии электроэнергии.
Сам регулятор выполним в цикле 20 мс на интервале 47, сразу после получения результатов измерения температуры.
if (cycle20mcCount == 47) {
//————————— интервал 47, регулятор температуры в камере
setTempRef= 20.; // временно заданная температура
regTmpErr= measureTempRef – setTempRef; // вычисление ошибки рассогласования
regTmpInt= regTmpInt regTmpErr * koeffRegTmpInt; // интегральная часть
if ( regTmpInt > maxSetPower) regTmpInt= maxSetPower; // ограничение сверху
if ( regTmpInt < 0 ) regTmpInt= 0; // ограничение снизу
regTmpPr= regTmpErr * koeffRegTmpPr; // пропорциональная часть
regTmpDif= (regTmpErr – regTmpErrPrev) * koeffRegTmpDif; // дифференцирующая часть
regTmpErrPrev= regTmpErr; // перегрузка предыдущей ошибки
setPower= regTmpInt regTmpPr regTmpDif; // сумма составляющих
if ( setPower > maxSetPower) setPower= maxSetPower; // ограничение сверху
if ( setPower < 0 ) setPower= 0; // ограничение снизу
}
Я даже не знаю, что в нем пояснять. Все “разжевано” в предыдущем уроке. Обратите внимание на полярность ошибки рассогласования регулятора.
- В регуляторе мощности при реальной мощности меньше заданной надо было увеличивать ШИМ. Больше ШИМ, больше мощность.
- В регуляторе температуры мощность надо увеличивать при реальной температуре больше заданной. Мы охлаждаем, а не нагреваем. И чем больше мощность, тем меньше температура.
Остается вывести промежуточные и основные результаты работы регулятора на компьютер. Я решил, что интересно будет видеть:
- ошибку рассогласования;
- заданную мощность;
- интегральную составляющую;
- пропорциональную составляющую;
- дифференциальную составляющую.
Заданная мощность это результат работы регулятора. Все остальное – результаты промежуточных вычислений. Но они показывают работу регулятора и облегчают настройку коэффициентов.
С выводом данных на компьютер через последовательный порт все не так просто. Функция Serial.print() помещает данные в передающий буфер последовательного порта. А встроенный класс Serial передает эти данные через аппаратный интерфейс UART. Но размер буфера Serial не безграничен.
Для Arduino UNO R3 размеры буферов приема и передачи составляют 64 байт. Поэтому при большем размере передаваемых данных мы должны делать паузы на передачу данных через аппаратный интерфейс UART. Т.е. для того, чтобы данные ”ушли” из буфера в UART. В нашем случае скорость передачи данных 19200 бод. Один байт передается за 0,5 мс. За цикл 20 мс будет передано 40 байтов.
За один цикл 20 мс все данные не передать. Надо передачу данных на компьютер разбивать на интервалы по 20 мс. Пока я выбрал 10 и 11 интервалы.
if (cycle20mcCount == 10) {
//————————— интервал 10, передача информации на компьютер
Serial.print(“U=”); Serial.print(measureU, 2); // напряжение
Serial.print(” I=”); Serial.print(measureI, 2); // ток
Serial.print(” P=”); Serial.print(measureP, 2); // мощность
Serial.print(” p=”); Serial.print(regPwrInt, 2); // интегральное звено регулятора мощности
Serial.print(” t=”); Serial.print(measureTempRef, 2); // температура в камере
Serial.print(” t=”); Serial.print(measureTempRad, 2); // температура радиатора
}
if (cycle20mcCount == 11) {
//————————— интервал 11, передача информации на компьютер
Serial.print(” E=”); Serial.print(regTmpErr, 2); // ошибка рассогласования
Serial.print(” W=”); Serial.print(setPower, 2); // заданная мощность
Serial.print(” I=”); Serial.print(regTmpInt, 2); // интегральная часть
Serial.print(” P=”); Serial.print(regTmpPr, 2); // пропорциональная часть
Serial.print(” D=”); Serial.print(regTmpDif, 2); // дифференциальная часть
Serial.println(” “);
}
Все. Регулятор температуры готов. Можете загрузить итоговый скетч программы:
Проверка ПИД регулятора температуры.
Сразу скажу, что окончательная проверка и отладка регулятора будет производиться на реальном объекте. К тому же очень трудно отлаживать контроллер по данным монитора последовательного порта. Когда разработка будет закончена, я напишу программу верхнего уровня, с помощью которой можно будет наблюдать все параметры в виде графиков на мониторе компьютера. Тогда все окончательно и отладим.
А сейчас мы проверим принципиальную работу всех составляющих регулятора. Собственно системы с элементом Пельтье у нас еще нет.
Выбор коэффициентов регулятора.
Выберем коэффициенты, заодно разберем их физический смысл в нашей системе.
Выбор коэффициентов сильно зависит от конструкции системы охлаждения и прежде всего от инерционности системы.
Самый инерционный вариант это радиатор холодной стороны Пельтье в камере, который охлаждает воздух. Самый быстрый – датчик температуры, расположенный непосредственно на холодной поверхности элемента Пельтье. В первом случае время реакции измеряется десятками минут, во втором – секундами. Поэтому регулятор настраивается на реальный объект. В будущем я затрону эту тему. А сейчас я выберу коэффициенты эмпирически для инерционного варианта холодильника. Возможно, я ошибусь. Истину покажет эксперимент. Сейчас важнее проверить работоспособность регулятора.
Пропорциональный коэффициент. Я выбрал значение koeffRegTmpPr = 0,5. Это значит, что при ошибке рассогласования 1 C° пропорциональное звено даст 0,5 Вт на элементе Пельтье. Допустим, мы включили нагретый холодильник. Разница между заданной и реальной температурами 10 C°. В этом случае пропорциональная составляющая мгновенно задаст регулятору мощности 5 Вт.
Интегральный коэффициент. Я выбрал koeffRegTmpInt = 0,002. Это значит, что при ошибке рассогласования 1 C° каждые 1 сек выходная мощность будет увеличиваться на 0,002 Вт. За 8 минут она увеличится на 1 Вт. При ошибке рассогласования 10 C° мощность будет увеличиваться в 10 раз быстрее, т.е. на 1 Вт за 50 сек.
Скорее это слишком большой коэффициент. В реальной системе я бы сделал его еще на порядок меньше.
Дифференцирующий коэффициент. Я задал koeffRegTmpDif = 50. Это значит, что при изменении ошибки рассогласования на 0,1 C° мощность изменится на 5 Вт.
С этим коэффициентом не все так просто. Я включил дифференцирующую составляющую только в демонстрационных целях. Думаю, необходимости в ней в нашей системе нет. Она имеет смысл только в системах с малой инерционность. К тому же не совсем понятно, как она будет работать в нашей системе.
Представьте себе. Температура увеличилась на 0,1 C°. Дифференцирующая составляющая выдала уменьшение заданной мощности на 5 вт. Но уменьшение мощности будет сформировано только в течение одной единицы временной дискретности регулятора, 1 сек. Вряд ли система охлаждения успеет отработать. Еще есть регулятор мощности. В принципе его быстродействие позволяет отработать за такое время, но мы значительно снизили скорость его работы из соображений щадящего режима для модуля Пельтье.
Мы можем значительно увеличить дифференцирующий коэффициент. Тогда при медленном регуляторе мощности произойдет следующее. Допустим, при изменении ошибки рассогласования дифференцирующее звено даст такую составляющую, которая уменьшит заданную мощность до 0. Это значение будет держаться в течение 1 сек. Регулятор мощности у нас работает в цикле 20 мс, т.е. 50 проходов за 1 сек. Я хочу сказать, что в этом случае выброс дифференцирующей составляющей в какой-то мере запомнится в интегральном звене. Но как это будет работать, зависит от нескольких коэффициентов.
Есть еще проблема с дифференцирующим звеном. Датчик температуры у нас дискретный, с разрешением 0,0625 C°. Как и любой узел преобразования аналоговой величины в цифровую он имеет особенность, связанную со значением выходного кода на границе ступеней преобразования. Код на границах ”скачет” на плюс минус единицу преобразования. В нашем случае значение температуры будет ”дергаться” на 0,0625 C°. Это будет воспринято дифференцирующим звеном и не лучшим образом повлияет на работу регулятора в целом. Ниже я покажу этот эффект.
Если кому-нибудь действительно необходим полный ПИД регулятор температуры, то он должен изменить временную дискретность дифференцирующего регулятора и обработать код измеренной температуры, чтобы исключить ”дергание” на единицу дискретности. Сделать цифровую фильтрацию и ввести гистерезис.
Проверка ПИД регулятора температуры на реальной схеме.
К прежней схеме добавились два датчика температуры. А в остальном все также: блок питания, плата Ардуино, импульсный регулятор, нагрузка 20 Ом.
Установил в программе заданную температуру 20 C°.
setTempRef= 20.; // временно заданная температура
Загрузил скетч с выбранными коэффициентами в плату.
Запустил монитор последовательного порта. Блок питания 12 В пока не подключал.
Ошибка рассогласования E = 3.22 C°.
Пропорциональная составляющая должна быть 3,22 * koeffRegTmpPr = 3,22 * 0,5 = 1,61 Вт. Так и есть P=1.61.
Интегральная составляющая должна увеличиваться каждую секунду на 3,22 * koeffRegTmpInt = 3,22 *0,002 = 0,0064 Вт. То же все правильно. За 20 секунд (20 строчек) I увеличилось с 0 до 0,13.
В последних строчках видно, что ошибка рассогласования увеличилась на 0,08 C°. Дифференциальная составляющая должна быть 0,08 * koeffRegTmpDif = 0,06 * 50 = 3 Вт. Регулятор отработал правильно D=3,13. Небольшая неточность, объясняется тем, что мы выводим только 2 знака после запятой.
Установил заданную температуру 25 C° и увидел, что ошибка рассогласования стала отрицательной E=-1.53 C°, регулятор отключился, пропорциональное звено дало правильное значение -1,53 * 0,5 = -0,765 Вт.
Вернул заданную температуру 20 C°. Подключил блок питания 12 В. Увидел, как меняется реальная мощность на нагрузке.
Мощность растет и останавливается на заданном в программе пределе 5 Вт. Все правильно.
Скачек температуры на 0,07 C° дифференциальное звено отработало правильно, но на выходной мощности это не отразилось.
А вот те самые ”скачки” температуры на границе ступеней преобразования датчика.
Видно, как они отражаются на интеграторе регулятора мощности p, т.е. на ШИМ. Выше я писал об этом.
Как я уже сказал, полную проверку и настройку регулятора температуры будем производить на реальном объекте с программой верхнего уровня, регистрирующей состояние системы.
Но в принципе контроллер уже должен работать. В нем нет возможности оперативной установки заданной температуры, индикации, управления вентилятором горячей стороны модуля Пельтье, защитных функций и т.п. Будем добавлять все эти функции в следующих уроках.
Предыдущий урок Список уроков Следующий урок