ПИД-регулятор и диммер
- Войдите на сайт для отправки комментариев
Приветствую форумчан!
Парни, выручайте, ибо мозг вывихнул напрочь.
Есть у нас нагреватель - пусть хоть чайник, в моем случае пивоварня, но значения не имеет.
Задача: набрать и удерживать в чайнике заданную температуру.
Мощность регулируется диммером на симисторе (http://cyber-place.ru/showthread.php?t=191)
Соответственно, выход мощности считает ПИД-регулятор.
Так это выглядит:
#include <OneWire.h>
#include <DallasTemperature.h>
OneWire oneWire(52);
DallasTemperature sensors(&oneWire);
float del, err, errprev, Int, temp;
unsigned long lastreq;
void setup()
{
pinMode(22, OUTPUT); //этим пином включаем симистор
attachInterrupt(0, dimmer, LOW); //прерывание когда синусоида переходит 0
Serial.begin(9600);
sensors.begin();
sensors.setWaitForConversion(false);
Int=0;
errprev=0;
err=0;
lastreq=0;
}
void loop()
{
unsigned long start = millis();
if (start>=lastreq+750 or start<lastreq){ //здесь опрашиваем цифровой датчик температуры
temp = sensors.getTempCByIndex(0); //он медленный, поэтому обращаемся к нему раз в 750мс. переменная temp хранит температуру
sensors.requestTemperatures();
lastreq=start;
}
float Kp=1000; //начало ПИД-регулятора
float Ki=0;
float Kd=0;
float aim=30; //задание температуры
err=aim-temp;
Int = Int+err;
if (Int>2147000000) Int=2147000000;
float derr=err-errprev;
errprev=err;
float P=Kp*err+Ki*Int+Kd*derr; //расчет мощности в %
if (P<0) P=0;
if (P>100) P=100;
del=90*(100-P)+200; //расчет задержки включения симистора. Меньше задержка - больше мощность.
}
void dimmer()
{
delayMicroseconds(del); //задержка от момента переходе ч-з ноль
digitalWrite(22, HIGH);
delayMicroseconds(700); //задержка для включения симистора
digitalWrite(22, LOW);
}
Суть проблемы: когда мы приближаемся к заданию или уходим за него (перерегулирование), соответственно ПИД выдает мощность равной нулю. А значит задержка включения симистора равна длине полупериода или 10000мс(в моем случае 9200мкс). Таким образом, функция прерывания выполняется9200+700=9900мкс , а вызывается каждые 10000мкс, т.е. 99% всего времени под прерыванием. Естесственно, что основной цикл полностью стопорится.
Вопрос: можно ли как-то от этого уйти?
В основном цикле - не проблема, а в прерывании?
у вас и прерывания и делеи
Внутри функции обработки прерывания не работает delay(), значения возвращаемые millis() не изменяются. Возможна потеря данный передаваемых по последовательному соединению (Serial data) в момент выполнения функциии обработки прерывания. Переменные, изменяемые в функции, должным быть объявлены как volatile.
в библиотеке DallasTemperature тоже используются задержки
В основном цикле - не проблема, а в прерывании?
Вы сами и ответили. Проблема в прерывании. На millis() или micros() вы видно очень сильно забили.
Как там в песенке поется
так что доставай те millis() или micros() .
так прерывание один раз за полпериода(10мс) вызывается. И функция прерывания отрабатывает только раз.
если сделать что-то вроде конструкции как в основном цикле,
unsigned long start = millis(); if (start>=lastreq+750 or start<lastreq){ temp = sensors.getTempCByIndex(0); sensors.requestTemperatures(); lastreq=start; }работать ничего не будет - прерывание вызвалось, сравнило, условие НЕ выполнилось - функция отработала - пошел основной цикл
А если сделать зацикливание в прерывании да хоть тем же while - все равно будем ждать в функции прерывания те же 9,2мс
з.ы. в прерывании не работает delay()
а delayMicroseconds() вполне себе
поправьте, если не прав
з.ы. в прерывании не работает delay()
а delayMicroseconds() вполне себе
поправьте, если не прав
Вы напомнили мне анекдот
Проблема delay не в том что он работает или не работает delay. А в том что когда delay работает ничего не работает кроме прерываения.
Я понимаю это, поэтому, если Вы посмотрите на прогу выше, в основном цикле нет delay()
Но в прерывании я использовал delayMicroseconds() потому что абсолютно все равно как мы будем отсчитывать эту задержку. Хоть delay() хоть micros(). Мы не сможем в это время выполнять основной цикл. Либо мы можем покинуть прерывание и перейти к основному циклу, но тогда я не знаю как снова вернуться к прерыванию спустя к примеру 5000 мкс задержки.
Еще раз: задача не тормозить основной цикл пока идет отсчет в функции прерывания.
Вы сидите в прерывании attachInterrupt(0, dimmer, LOW); пока сигнал на входе прерывания 0 и выйти из него не можете.Не знаю как у вас организован сигнал но это очень забавно. Все delay организованы через прерывания, а в AVR нет приоритетов прерывания. Поэтому в прерывании нет других прерываний и все внутренние часы ардуины стоят. Все диммеры используют свободные таймеры для организации задержек. Или выставляют флаги, а основной цикл уже строит задержки, но тогда он должен быть очень коротким,чтобы не вносить ошибки. Float Int это шедевр (шутка). Субъект регулирования ПИД вприципе не правильно выбран. В вашем случае при подходе к уставке мощность оказывается равной нулю. Вы расчитываете мощность через ошибку. Чем меньше ошибка, тем меньше мощность. А должна быть мощность задана изначально через формулу зависимости от температуры и уже эту мощность корректировать ПИДом.
Еще раз: задача не тормозить основной цикл пока идет отсчет в функции прерывания.
Где-то так
/*millis_pid_int.ino */ // ПИД регулятор const byte PID_pin = 22; uint32_t past_1 = millis(); bool Pid_ON = 0; // флаг открытия симистора uint32_t time_ON = 1000; // время ШИМ 0 симистор всегда открыт / если максим значение, то симистор всегда закрыт bool Pid_OFF = 0; // флаг открытия симистора const uint32_t time_pulse = 2;// длительность импульса открытия void ON() { past_1 = millis; Pid_ON = 1; } //#термометр #include <OneWire.h> #include <DallasTemperature.h> OneWire oneWire(52); DallasTemperature sensors(&oneWire); float temp;// текущая температура void setup() { Serial.begin(9600); // ПИД регулятор pinMode(PID_pin, OUTPUT); //этим пином включаем симистор attachInterrupt(0, ON, LOW); //прерывание когда синусоида переходит 0 //#термометр sensors.begin(); sensors.setWaitForConversion(false); } void loop() { //#1 ПИД регулятор if (Pid_ON && millis - past_1 >= time_ON) { // если подошло время ,то открыть симистор digitalWrite(PID_pin, Pid_OFF = 1); Pid_ON = 0; }; if (Pid_OFF && millis - past_1 >= (time_ON + time_pulse)) {// симистор уже открыт, импульс подавать не надо digitalWrite(PID_pin, Pid_OFF = 0); }; //#2 термометр static uint32_t past_2 = millis(); if (millis - past_2 >= 750 ) { //здесь опрашиваем цифровой датчик температуры past_2 = millis; temp = sensors.getTempCByIndex(0); //он медленный, поэтому обращаемся к нему раз в 750мс. переменная temp хранит температуру sensors.requestTemperatures(); // time_ON = ??? ; здесь задавайте свой ШИМ } }Спасибо, qwone.
Ваш способ понравился : запоминаем в какой момент пересекли ноль, дальше обрабатываем в основном цикле.
Вопрос: переменные past_1 и Pid_ON не должны быть типа volatile?
И еще момент: поскольку мы будем открывать симистор в основном цикле, этот основной цикл должен быть как можно короче походу - можем проскочить момент включения на короткой задержке, что чревато ограниченной мощностью. У меня только цикл loop 28-29 мс. Видимо, вычисления выхода ПИД и опрос датчика спрячу и буду проводить раз в 5 секунд, чтобы основной цикл ускорить. Или due вместо меги закажу.
Спасибо, что помогли.
Вопрос: переменные past_1 и Pid_ON не должны быть типа volatile?
Скорее всего надо.
Радость была преждевременной, увы. Весь вечер бился - не горит.
Не хватает контроллеру быстродействия.
Вот так лампочка на нагрузке вообще не горит, раз в секунд 30 моргнет
volatile unsigned long zerotime=0; void setup() { pinMode(22, OUTPUT); attachInterrupt(0, zerocross, RISING); } void loop() { if( millis>=zerotime) { digitalWrite(22,1); delay(1); digitalWrite(22, 0); } } void zerocross() { zerotime=millis(); }А точно так же, только if (millis<zerotime){...} - горит исправно
А такое возможно только, если прерывание вызывается во время отработки оператора if. Т.е. мы взяли millis, хотим его сравнить с сохраненным ранее zerotime, но прежде чем контроллер успевает это сделать, прерывание вызывается еще раз и подмениеет zerotime новым, уже подросшим.
Блин.
У вас что там большая частота. Так возьмите micros()
Да и код правильнее такой
volatile unsigned long zerotime = 0; void setup() { pinMode(22, OUTPUT); attachInterrupt(0, zerocross, RISING); } void loop(){ if ( millis-zerotime >=1){//< -- сюда надо 1 или даже больше digitalWrite(22, 1); delay(1); digitalWrite(22, 0); } } void zerocross(){ zerotime = millis(); }А еще правильнее так
volatile unsigned long zerotime = 0; volatile bool flag = 0; void setup() { pinMode(22, OUTPUT); attachInterrupt(0, zerocross, RISING); } void loop() { if ( flag && millis - zerotime >= 1) { //< -- сюда надо 1 или даже больше flag = 0; digitalWrite(22, 1); delay(1); digitalWrite(22, 0); } } void zerocross() { zerotime = millis(); flag = 1; }Вы как в анекдоте: Теоретически если у автомобиля убрать колеса, то он будет летать. Но на практике автомобиль без колес даже не стронится с места.
qwone
Только что поборол.
Не работают функции millis() micros() без скобок. Вечер потрачен, но получена бесценная крупица опыта :)
вот так работает очень хорошо, как с delay, будь он неладен)
volatile unsigned long zerotime = 0; void setup() { pinMode(22, OUTPUT); attachInterrupt(0, zerocross, RISING); } void loop(){ if ( micros()-zerotime>=8000){digitalWrite(22, 1); delay(1); digitalWrite(22, 0); } } void zerocross(){ zerotime = micros(); }А почему лучше if (a-b>10) чем if (a>b+10)?
А почему лучше if (a-b>10) чем if (a>b+10)?
Ну у вас же а и b это
unsignedlongНу А "тупой компилятор " работает в int , да у millis раз в 48 дней идет переполнение в обнуление. Так что при работе с millis такая конструкция надежней.Решить проблему конечно интересно, но нахрена в пивоварне ПИД? Тепловая инерция такого объема жидкости сведет все на нет.
Ну во-первых, температуру надо удерживать довольно точно, в идеале +- 0,5 С, во-вторых температурных этапов несколько, переход автоматический. Ну и в-третьих, действительно было просто интересно решить проблему. Можно конечно было просто сделать свой шим и включать-выключать нагреватель к примеру раз в 5 секунд. Но слишком просто. Да и свет в кухне тогда моргает неприятно - нагрузка на сеть неслабая. А так ровненько все греется и горит :)
Ну во-первых, температуру надо удерживать довольно точно, в идеале +- 0,5 С, во-вторых температурных этапов несколько, переход автоматический. Ну и в-третьих, действительно было просто интересно решить проблему. Можно конечно было просто сделать свой шим и включать-выключать нагреватель к примеру раз в 5 секунд. Но слишком просто. Да и свет в кухне тогда моргает неприятно - нагрузка на сеть неслабая. А так ровненько все греется и горит :)
можете поделится схемой и кодом, ..