ПИД-регулятор и диммер
- Войдите на сайт для отправки комментариев
Приветствую форумчан!
Парни, выручайте, ибо мозг вывихнул напрочь.
Есть у нас нагреватель - пусть хоть чайник, в моем случае пивоварня, но значения не имеет.
Задача: набрать и удерживать в чайнике заданную температуру.
Мощность регулируется диммером на симисторе (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мс) вызывается. И функция прерывания отрабатывает только раз.
если сделать что-то вроде конструкции как в основном цикле,
работать ничего не будет - прерывание вызвалось, сравнило, условие НЕ выполнилось - функция отработала - пошел основной цикл
А если сделать зацикливание в прерывании да хоть тем же while - все равно будем ждать в функции прерывания те же 9,2мс
з.ы. в прерывании не работает delay()
а delayMicroseconds() вполне себе
поправьте, если не прав
з.ы. в прерывании не работает delay()
а delayMicroseconds() вполне себе
поправьте, если не прав
Вы напомнили мне анекдот
Проблема delay не в том что он работает или не работает delay. А в том что когда delay работает ничего не работает кроме прерываения.
Я понимаю это, поэтому, если Вы посмотрите на прогу выше, в основном цикле нет delay()
Но в прерывании я использовал delayMicroseconds() потому что абсолютно все равно как мы будем отсчитывать эту задержку. Хоть delay() хоть micros(). Мы не сможем в это время выполнять основной цикл. Либо мы можем покинуть прерывание и перейти к основному циклу, но тогда я не знаю как снова вернуться к прерыванию спустя к примеру 5000 мкс задержки.
Еще раз: задача не тормозить основной цикл пока идет отсчет в функции прерывания.
Вы сидите в прерывании attachInterrupt(0, dimmer, LOW); пока сигнал на входе прерывания 0 и выйти из него не можете.Не знаю как у вас организован сигнал но это очень забавно. Все delay организованы через прерывания, а в AVR нет приоритетов прерывания. Поэтому в прерывании нет других прерываний и все внутренние часы ардуины стоят. Все диммеры используют свободные таймеры для организации задержек. Или выставляют флаги, а основной цикл уже строит задержки, но тогда он должен быть очень коротким,чтобы не вносить ошибки. Float Int это шедевр (шутка). Субъект регулирования ПИД вприципе не правильно выбран. В вашем случае при подходе к уставке мощность оказывается равной нулю. Вы расчитываете мощность через ошибку. Чем меньше ошибка, тем меньше мощность. А должна быть мощность задана изначально через формулу зависимости от температуры и уже эту мощность корректировать ПИДом.
Еще раз: задача не тормозить основной цикл пока идет отсчет в функции прерывания.
Где-то так
Спасибо, qwone.
Ваш способ понравился : запоминаем в какой момент пересекли ноль, дальше обрабатываем в основном цикле.
Вопрос: переменные past_1 и Pid_ON не должны быть типа volatile?
И еще момент: поскольку мы будем открывать симистор в основном цикле, этот основной цикл должен быть как можно короче походу - можем проскочить момент включения на короткой задержке, что чревато ограниченной мощностью. У меня только цикл loop 28-29 мс. Видимо, вычисления выхода ПИД и опрос датчика спрячу и буду проводить раз в 5 секунд, чтобы основной цикл ускорить. Или due вместо меги закажу.
Спасибо, что помогли.
Вопрос: переменные past_1 и Pid_ON не должны быть типа volatile?
Скорее всего надо.
Радость была преждевременной, увы. Весь вечер бился - не горит.
Не хватает контроллеру быстродействия.
Вот так лампочка на нагрузке вообще не горит, раз в секунд 30 моргнет
А точно так же, только if (millis<zerotime){...} - горит исправно
А такое возможно только, если прерывание вызывается во время отработки оператора if. Т.е. мы взяли millis, хотим его сравнить с сохраненным ранее zerotime, но прежде чем контроллер успевает это сделать, прерывание вызывается еще раз и подмениеет zerotime новым, уже подросшим.
Блин.
У вас что там большая частота. Так возьмите micros()
Да и код правильнее такой
А еще правильнее так
Вы как в анекдоте: Теоретически если у автомобиля убрать колеса, то он будет летать. Но на практике автомобиль без колес даже не стронится с места.
qwone
Только что поборол.
Не работают функции millis() micros() без скобок. Вечер потрачен, но получена бесценная крупица опыта :)
вот так работает очень хорошо, как с delay, будь он неладен)
А почему лучше if (a-b>10) чем if (a>b+10)?
А почему лучше if (a-b>10) чем if (a>b+10)?
Ну у вас же а и b это
unsigned
long
Ну А "тупой компилятор " работает в int , да у millis раз в 48 дней идет переполнение в обнуление. Так что при работе с millis такая конструкция надежней.Решить проблему конечно интересно, но нахрена в пивоварне ПИД? Тепловая инерция такого объема жидкости сведет все на нет.
Ну во-первых, температуру надо удерживать довольно точно, в идеале +- 0,5 С, во-вторых температурных этапов несколько, переход автоматический. Ну и в-третьих, действительно было просто интересно решить проблему. Можно конечно было просто сделать свой шим и включать-выключать нагреватель к примеру раз в 5 секунд. Но слишком просто. Да и свет в кухне тогда моргает неприятно - нагрузка на сеть неслабая. А так ровненько все греется и горит :)
Ну во-первых, температуру надо удерживать довольно точно, в идеале +- 0,5 С, во-вторых температурных этапов несколько, переход автоматический. Ну и в-третьих, действительно было просто интересно решить проблему. Можно конечно было просто сделать свой шим и включать-выключать нагреватель к примеру раз в 5 секунд. Но слишком просто. Да и свет в кухне тогда моргает неприятно - нагрузка на сеть неслабая. А так ровненько все греется и горит :)
можете поделится схемой и кодом, ..