ПИД-регулятор и диммер

Lostby
Offline
Зарегистрирован: 27.02.2017

Приветствую форумчан!

Парни, выручайте, ибо мозг вывихнул напрочь.

Есть у нас нагреватель - пусть хоть чайник, в моем случае пивоварня, но значения не имеет.

Задача: набрать и удерживать в чайнике заданную температуру.

Мощность регулируется диммером на симисторе (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% всего времени под прерыванием. Естесственно, что основной цикл полностью стопорится.

Вопрос: можно ли как-то от этого уйти?

В основном цикле - не проблема, а в прерывании?

vvadim
Offline
Зарегистрирован: 23.05.2012

у вас и прерывания и делеи

Внутри функции обработки прерывания не работает delay(), значения возвращаемые millis() не изменяются. Возможна потеря данный передаваемых по последовательному соединению (Serial data) в момент выполнения функциии обработки прерывания. Переменные, изменяемые в функции, должным быть объявлены как volatile.

в библиотеке DallasTemperature тоже используются задержки

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Lostby пишет:

В основном цикле - не проблема, а в прерывании?

Вы сами и ответили. Проблема в прерывании. На millis() или micros() вы видно очень сильно забили.

Как там в песенке поется 

Один глупый лесоруб -
Знаете такого? -
Захотел себе тулуп
Сделать без портного.
Положил он свой топор
Далеко на полку
И не может до сих пор
Нитку вдеть в иголку!

так что доставай те millis() или micros() .

Lostby
Offline
Зарегистрирован: 27.02.2017

так прерывание один раз за полпериода(10мс) вызывается. И функция прерывания отрабатывает только раз.

если сделать что-то вроде конструкции как в основном цикле,

unsigned long start = millis();          
 if (start>=lastreq+750 or start<lastreq){
  temp = sensors.getTempCByIndex(0);      
  sensors.requestTemperatures();
  lastreq=start;
 }

работать ничего не будет - прерывание вызвалось, сравнило, условие НЕ выполнилось - функция отработала - пошел основной цикл

А если сделать зацикливание в прерывании да хоть тем же while - все равно будем ждать в функции прерывания те же 9,2мс

Lostby
Offline
Зарегистрирован: 27.02.2017

з.ы. в прерывании не работает delay()

а delayMicroseconds() вполне себе

поправьте, если не прав

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Lostby пишет:

з.ы. в прерывании не работает delay()

а delayMicroseconds() вполне себе

поправьте, если не прав

Вы напомнили мне анекдот 

Мужик спрашивает бармена:
-Сколько стоит капля водки?
-Ничего.
- Тогда накапайсте мне стакан.

Проблема delay не в том что он работает или не работает delay. А в том что когда delay работает ничего не работает кроме прерываения. 

Lostby
Offline
Зарегистрирован: 27.02.2017

Я понимаю это, поэтому, если Вы посмотрите на прогу выше, в основном цикле нет delay()

Но в прерывании я использовал delayMicroseconds() потому что абсолютно все равно как мы будем отсчитывать эту задержку. Хоть delay() хоть micros(). Мы не сможем в это время выполнять основной цикл. Либо мы можем покинуть прерывание и перейти к основному циклу, но тогда я не знаю как снова вернуться к прерыванию спустя к примеру 5000 мкс задержки. 

 

Еще раз: задача не тормозить основной цикл пока идет отсчет в функции прерывания.

nik182
Offline
Зарегистрирован: 04.05.2015

Вы сидите в прерывании attachInterrupt(0, dimmer, LOW); пока сигнал на входе прерывания 0 и выйти из него не можете.Не знаю как у вас организован сигнал но это очень забавно. Все delay организованы через прерывания, а в AVR  нет приоритетов прерывания. Поэтому в прерывании нет других прерываний и все внутренние часы ардуины стоят. Все диммеры используют свободные таймеры для организации задержек. Или выставляют флаги, а основной цикл уже строит задержки, но тогда он должен быть очень коротким,чтобы не вносить ошибки. Float Int это шедевр (шутка). Субъект регулирования ПИД вприципе не правильно выбран. В вашем случае при подходе к уставке мощность оказывается равной нулю. Вы расчитываете мощность через ошибку. Чем меньше ошибка, тем меньше мощность. А должна быть мощность задана изначально через формулу зависимости от температуры и уже эту мощность корректировать ПИДом.      

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Lostby пишет:

Еще раз: задача не тормозить основной цикл пока идет отсчет в функции прерывания.

Где-то так 

/*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  = ??? ; здесь задавайте свой ШИМ
  }
}

 

Lostby
Offline
Зарегистрирован: 27.02.2017

Спасибо, qwone. 

Ваш способ понравился : запоминаем в какой момент пересекли ноль, дальше обрабатываем в основном цикле.

Вопрос: переменные past_1 и Pid_ON не должны быть типа volatile? 

И еще момент: поскольку мы будем открывать симистор в основном цикле, этот основной цикл должен быть как можно короче походу - можем проскочить момент включения на короткой задержке, что чревато ограниченной мощностью. У меня только цикл loop 28-29 мс. Видимо, вычисления выхода ПИД и опрос датчика спрячу и буду проводить раз в 5 секунд, чтобы основной цикл ускорить. Или due вместо меги закажу.

Спасибо, что помогли.

 

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Lostby пишет:

Вопрос: переменные past_1 и Pid_ON не должны быть типа volatile? 

Скорее всего надо. 

Lostby
Offline
Зарегистрирован: 27.02.2017

Радость была преждевременной, увы. Весь вечер бился - не горит.

Не хватает контроллеру быстродействия.

Вот так лампочка на нагрузке вообще не горит, раз в секунд 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 новым, уже подросшим.

Блин.

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

У вас что там большая частота. Так возьмите 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();
}

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

А еще правильнее так 

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;
}

Вы как в анекдоте: Теоретически если у автомобиля убрать колеса, то он будет летать. Но на практике автомобиль без колес даже не стронится с места.

Lostby
Offline
Зарегистрирован: 27.02.2017

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)?

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Lostby пишет:

А почему лучше if (a-b>10) чем if (a>b+10)?

Ну у вас же а и b  это unsigned long Ну А "тупой компилятор " работает в int , да у millis раз в 48 дней идет переполнение в обнуление. Так что при работе с  millis  такая конструкция надежней.

bwn
Offline
Зарегистрирован: 25.08.2014

Решить проблему конечно интересно, но нахрена в пивоварне ПИД? Тепловая инерция такого объема жидкости сведет все на нет.

Lostby
Offline
Зарегистрирован: 27.02.2017

Ну во-первых, температуру надо удерживать довольно точно, в идеале +- 0,5 С, во-вторых температурных этапов несколько, переход автоматический. Ну и в-третьих, действительно было просто интересно решить проблему. Можно конечно было просто сделать свой шим и включать-выключать нагреватель к примеру раз в 5 секунд. Но слишком просто. Да и свет в кухне тогда моргает неприятно - нагрузка на сеть неслабая. А так ровненько все греется и горит :)

dizzyy
Offline
Зарегистрирован: 19.04.2018

Lostby пишет:

Ну во-первых, температуру надо удерживать довольно точно, в идеале +- 0,5 С, во-вторых температурных этапов несколько, переход автоматический. Ну и в-третьих, действительно было просто интересно решить проблему. Можно конечно было просто сделать свой шим и включать-выключать нагреватель к примеру раз в 5 секунд. Но слишком просто. Да и свет в кухне тогда моргает неприятно - нагрузка на сеть неслабая. А так ровненько все греется и горит :)


можете поделится схемой и кодом,  ..