ПИД ШИМ регулятор температурі

Givizub
Offline
Зарегистрирован: 01.01.2017

Здравствуйте, пытаюсь сделать ПИД ШИМ регулятор на основе Uno для регулировки включения ТЭНа обогревателя, так как в "режиме термостата" качество регулировки не очень удовлетворительное.

Температура измеряется с помощью DS18B20, ее значение подается на вход ПИД-регулятора (или туда нужен сигнал разбаланса?), регулятор выдает некоторое значение, которое в свою очередь подается на вход симметрического мультивибратора и определяет длительность импульсов на пин выхода к реле, которое управляет ТЭНом. 
 
Правильный ли такой алгоритм, или можно как-то упростить программу? Возможно у кого-то есть простые скетчи каких-то ПИД-регуляторов? Нашел только для управления серводвигателями и пытаюсь их разобрать. Кнопки и дисплей использоваться не будут.
 
 
P.S. занимаюсь программированием промышленных контроллеров на LD, FBD и ST почти два года, наконец прикупил Arduino, но пытаюсь применять методы оттуда прихожу в замешательство..
qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Givizub пишет:

P.S. занимаюсь программированием промышленных контроллеров на LD, FBD и ST почти два года, наконец прикупил Arduino, но пытаюсь применять методы оттуда прихожу в замешательство..

Ну можно программировать в Ардуино так как показано в учебных скетчах.

Но это не мой метод. Мой метод millis(),millis() и еще millis(). То есть многопоточное программирование. Чего нет в большом программировании.

Givizub
Offline
Зарегистрирован: 01.01.2017

Собственно, сейчас у меня что-то получилось, но на выходе не могу получить импульсы. Добился только плавного изменения яркости светодиода на пине в зависимости от температуры:

#include <PID_v1.h>
#include <DallasTemperature.h>
#include <OneWire.h>
#define ONE_WIRE_BUS 2// Подключение цифрового вывода датчика к 10-му пину Ардуино.
OneWire oneWire(ONE_WIRE_BUS);// Запуск интерфейса OneWire для подключения OneWire устройств первой линии.
DallasTemperature sensors(&oneWire);// Указание, что устройством oneWire является термодатчик от  Dallas Temperature.

double Setpoint, Input, Output;

PID myPID(&Input, &Output, &Setpoint,2,5,1, DIRECT);

void setup()
{

  Serial.begin(9600);// Запуск СОМ порта.
  Serial.println("Начало измерения температуры");
  sensors.begin(); // Запуск датчика.
  Setpoint = 30; //Задание регулятора

  //Запуск ПИД-регулятора
  myPID.SetMode(AUTOMATIC);
  pinMode(3, OUTPUT);
}

void loop()
{
   sensors.requestTemperatures(); // Команда опроса температуры.
    Serial.print("Tемпература = ");
    Serial.println(sensors.getTempCByIndex(0));  //Вывод температуры
    
  Input = (sensors.getTempCByIndex(0)); //Подача значения с датчика на вход ПИД регулятора
  myPID.Compute();
  analogWrite(3,Output); //Выход ПИД- регулятора
}

Надо как-то преобразовать сигнал с выхода регулятора в импульсы с определенной частотой.

P.S. Как отредактировать ошибки в тексте первого поста?

evgta
Offline
Зарегистрирован: 02.09.2016

А зачем управленние именно шимом? Если 220 и нагрузка более 1квт то применение шим дает помехи в сети.

В примерах к библиотеке пид есть скетч управления нагревателем

Никак не отредактировать

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

qwone пишет:

многопоточное программирование. Чего нет в большом программировании.

Точно? А мужики-то и не в курсе! :))))

Givizub
Offline
Зарегистрирован: 01.01.2017

evgta пишет:
Если 220 и нагрузка более 1квт то применение шим дает помехи в сети.

Спасибо, об этом не подумал. Допустим, это будет что-то менее мощное.

evgta пишет:
В примерах к библиотеке пид есть скетч управления нагревателем.

Не могли бы тыкнуть?

evgta
Offline
Зарегистрирован: 02.09.2016

С телефона, а по памяти , примеры, pid, и там будет три примера, один как выше, один вкл, выкл для обогревателя, а третий непомню какой

Givizub
Offline
Зарегистрирован: 01.01.2017

Касаемо помех в сети, если импульсы будут с маленькой частотой и большой скважностью (система инерционная), то помехи будут не так заметны. Например, импульс 5 сек, пауза 15 сек. Хотя и это нельзя будет назвать ШИМ, но такой метод подойдет даже лучше. Чего я и пытаюсь добиться.

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

Givizub пишет:

Касаемо помех в сети, если импульсы будут с маленькой частотой и большой скважностью (система инерционная), то помехи будут не так заметны. Например, импульс 5 сек, пауза 15 сек. Хотя и это нельзя будет назвать ШИМ, но такой метод подойдет даже лучше. Чего я и пытаюсь добиться.

Ищите Blink через millis(). А то мужики знают, но молчат как белорусские партизаны. Ну что с мужиков взять-то. Одним словом Мужики.

Givizub
Offline
Зарегистрирован: 01.01.2017

qwone пишет:

Ищите Blink через millis().

Это оно! Что-то похожее, только не совсем так, как работает импульсный регулятор. Наверное, импульс и пауза должны немного отличаться по длительности..

Код программы сейчас выглядит вот так (вместо реле встроенный светодиод):

 
#include 
#include 
#include 
#define ONE_WIRE_BUS 2// Подключение цифрового вывода датчика к 10-му пину Ардуино.
OneWire oneWire(ONE_WIRE_BUS);// Запуск интерфейса OneWire для подключения OneWire устройств первой линии.
DallasTemperature sensors(&oneWire);// Указание, что устройством oneWire является термодатчик от  Dallas Temperature.

double Setpoint, Input, Output;
int value = LOW;  
long previousMillis = 0;
long interval = 0;

PID myPID(&Input, &Output, &Setpoint,2,0.5,1, DIRECT);

void setup()
{

  Serial.begin(9600);// Запуск СОМ порта.
  Serial.println("Начало измерения температуры");
  sensors.begin(); // Запуск датчика.
  Setpoint = 22;

  //Запуск ПИД-регулятора
  myPID.SetMode(AUTOMATIC);
  pinMode(3, OUTPUT);
}

void loop()
{
   sensors.requestTemperatures(); // Команда опроса температуры.
    Serial.print("Tемпература = ");
    Serial.print(sensors.getTempCByIndex(0));
    Serial.println ("°");
    Serial.print("Выход = ");
    Serial.println(Output);

  Input = (sensors.getTempCByIndex(0));
  myPID.Compute();


  analogWrite(3,Output);
  Serial.println(interval);

  interval=Output*100;

 if (millis() - previousMillis > interval) {
    previousMillis = millis();
if (value == LOW and interval > 0)
      value = HIGH;
    else
      value = LOW;
 }
    digitalWrite(LED_BUILTIN, value);
    if (interval == 0) {
      value=LOW;}
  
}
}

 

Примерно то, что я желаю получить (температура в комнате, сигнал выхода на регуляторе, импульсы на реле)

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

И чтo вам мешает? Вот код над которым я работаю. Может навеет вам решение.

/*viktor001_cl.ino
#1
кнопка -> 2 (btn1_pin) 0 нажата/ 1 нет
#2
светодиод 1 -> 3 (Led1_pin) 1 погашено/ аналог горит по уровню
светодиод 2 -> 5 (Led2_pin)
светодиод 3 -> 6 (Led3_pin)
светодиод 4 -> 9 (Led4_pin)
Принцип кода: чем дольше держишь кнопку, тем ярче горят светодиоды(50%,100%)
*/
class Cl_Led {
  public:
    Cl_Led(int _pin, uint32_t _time_1, uint32_t _time_2);
    void setup();
    void loop(int _btn);
  private:
    int pin;// нога на светодиод
    int Led;// величина на светодиоде
    const int light0 = 0; // уровень не горит
    const int light50 = 50; // уровень  горит 50%
    const int light100 = 100; // уровень горит 100%
    void Out_Led(int _Led);
    uint32_t past_led;
    uint32_t time_1; // задержка времени вкл light0->light50 и обратно
    uint32_t time_2; // задержка времени вкл light50->light100 и обратно
};
Cl_Led::Cl_Led(int _pin, uint32_t _time_1, uint32_t _time_2) {
  pin = _pin;
  time_1 = _time_1;
  time_2 = _time_2;
}
void  Cl_Led::Out_Led(int _Led) { // функция  управ.свет с нужным уровнем
  switch (Led = _Led) {
    case 0:
      analogWrite(pin,  light0);
      break;
    case 1:
      analogWrite(pin,  light0);
      break;
    case 2:
      analogWrite(pin,  light50);
      break;
    default:
      analogWrite(pin,  light100);
  }
}
void  Cl_Led::setup() {
  past_led = 0;
  Out_Led(0);
}
void Cl_Led::loop(int _btn) {
  if (Led == 3 &&  millis() - past_led >= time_2) {
    past_led = millis() ;
    if ( _btn)
      Out_Led( 2);
  }
  if (Led == 2 &&  millis() - past_led >= time_2 ) {
    past_led = millis() ;
    if (_btn)
      Out_Led(0);
    else
      Out_Led(3);
  }
  if (Led == 1 &&  millis() - past_led >= time_1 ) {
    past_led = millis() ;
    Out_Led(2);
  }
  if (Led == 0 && ! _btn) {
    past_led = millis() ;
    Led = 1;
  }
}
//#1
const int btn1_pin = 2; //нога кнопки
bool btn1;// инверс сост кнопки 1 нажата /0 нет
//#2
const int Led1_pin = 3; // управление яркостью с помощью ШИМ
Cl_Led Led1(Led1_pin, 500, 3000); // 0,1 - негорит / 2 горит в полнакала / 3 горит полностью

const int Led2_pin = 5; // управление яркостью с помощью ШИМ
Cl_Led Led2(Led2_pin, 700, 3000); // 0,1 - негорит / 2 горит в полнакала / 3 горит полностью

const int Led3_pin = 6; // управление яркостью с помощью ШИМ
Cl_Led Led3(Led3_pin, 1000, 3000); // 0,1 - негорит / 2 горит в полнакала / 3 горит полностью

const int Led4_pin = 9; // управление яркостью с помощью ШИМ
Cl_Led Led4(Led4_pin, 1500, 3000); // 0,1 - негорит / 2 горит в полнакала / 3 горит полностью
void setup() {
  //#1
  pinMode(btn1_pin, INPUT_PULLUP);
  //#2
  Led1.setup();
  Led2.setup();
  Led3.setup();
  Led4.setup();
}

void loop() {
  //#1
  btn1 =  digitalRead(btn1_pin);
  //#2
  Led1.loop(btn1);
  Led2.loop(btn1);
  Led3.loop(btn1);
  Led4.loop(btn1);
}

А вот работаю с сервами

/*
#1 Серва1 упр. выв -> 12    (servo1_pin)
               +5В -> +5В
               GND -> GND
   Серва2 упр. выв -> 11    (servo2_pin)
               +5В -> +5В
               GND -> GND
   Серва3 упр. выв -> 10    (servo3_pin)
               +5В -> +5В
               GND -> GND
   Серва4 упр. выв -> 9     (servo4_pin)
               +5В -> +5В
               GND -> GND
   Серва5 упр. выв -> 8     (servo5_pin)
               +5В -> +5В
               GND -> GND
               
#2 резистор  нижний выв   -> GND
             средний выв  -> A0    (R1_pin)
             верхний выв  -> +5
   резистор  нижний выв   -> GND
             средний выв  -> A1    (R2_pin)
             верхний выв  -> +5
   резистор  нижний выв   -> GND
             средний выв  -> A2    (R3_pin)
             верхний выв  -> +5
   резистор  нижний выв   -> GND
             средний выв  -> A3    (R4_pin)
             верхний выв  -> +5
   резистор  нижний выв   -> GND
             средний выв  -> A4    (R5_pin)
             верхний выв  -> +5
*/
//#1
const int servo1_pin = 12;
uint16_t servo1_value = 544 ; // величина на серве 544-2400 мкс.
uint8_t servo1;//

const int servo2_pin = 11;
uint16_t servo2_value = 544 ; // величина на серве 544-2400 мкс.
uint8_t servo2;//

const int servo3_pin = 10;
uint16_t servo3_value = 544 ; // величина на серве 544-2400 мкс.
uint8_t servo3;//

const int servo4_pin =  9;
uint16_t servo4_value = 544 ; // величина на серве 544-2400 мкс.
uint8_t servo4;//

const int servo5_pin =  8;
uint16_t servo5_value = 544 ; // величина на серве 544-2400 мкс.
uint8_t servo5;//
//#2
const int R1_pin = A0;// нога резистора
const int R2_pin = A1;// нога резистора
const int R3_pin = A2;// нога резистора
const int R4_pin = A3;// нога резистора
const int R5_pin = A4;// нога резистора

void setup() {
  //#1

  pinMode      (servo1_pin, OUTPUT);
  digitalWrite (servo1_pin, servo1 = 0);
  pinMode      (servo2_pin, OUTPUT);
  digitalWrite (servo2_pin, servo2 = 0);
  pinMode      (servo3_pin, OUTPUT);
  digitalWrite (servo3_pin, servo3 = 0);
  pinMode      (servo4_pin, OUTPUT);
  digitalWrite (servo4_pin, servo4 = 0);
  pinMode      (servo5_pin, OUTPUT);
  digitalWrite (servo5_pin, servo5 = 0);

  //#2
}
void loop() {
  //#1 20 миллисекунд обслуживание сервы 1 и 2
  static uint32_t past_1 = 0 ;

  if  (servo1 && (micros() - past_1 >= servo1_value))
    digitalWrite (servo1_pin, servo1 = 0);
  if  (servo2 && (micros() - past_1 >= servo2_value))
    digitalWrite (servo2_pin, servo2 = 0);
  if  (servo3 && (micros() - past_1 >= servo3_value))
    digitalWrite (servo3_pin, servo3 = 0);
  if  (servo4 && (micros() - past_1 >= servo4_value))
    digitalWrite (servo3_pin, servo3 = 0);
  if  (servo5 && (micros() - past_1 >= servo5_value))
    digitalWrite (servo5_pin, servo5 = 0);


  if (micros() - past_1 >= 20000) { // если прошло 20000 микросек
    past_1 = micros() ;

    digitalWrite (servo1_pin, servo1 = 1);
    digitalWrite (servo2_pin, servo2 = 1);
    digitalWrite (servo3_pin, servo3 = 1);
    digitalWrite (servo4_pin, servo4 = 1);
    digitalWrite (servo5_pin, servo5 = 1);

  }
  //#2
  static uint32_t past_2 = 0 ;
  if (millis() - past_2 >= 200) { // если прошло 200000 микросек
    past_2 = millis() ;

    servo1_value = map (analogRead(R1_pin), 0, 1023, 544, 2400);
    servo2_value = map (analogRead(R2_pin), 0, 1023, 544, 2400);
    servo3_value = map (analogRead(R3_pin), 0, 1023, 544, 2400);
    servo4_value = map (analogRead(R4_pin), 0, 1023, 544, 2400);
    servo5_value = map (analogRead(R5_pin), 0, 1023, 544, 2400);

  }
}

 

Givizub
Offline
Зарегистрирован: 01.01.2017

Cпасибо, попробую разбораться, с сервами более понятно. Копать в millis(). Там где uint16_t - это какие-то таймеры?

Для своего регулятора, думаю, целесообразно изменить алгоритм работы. Например, выдавать импульсы через определенные промежутки времени, например 10 сек?  Длинна этих импульсов будет зависеть от значения на выходе ПИД. Хотя и это не совсем правильно. 

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

uint16_t это тип переменной беззнаковое int и все.

Почему не правильно. Если нет в аппаратной части, приходится реализовывать программно. Считайте, что ставите программный драйвер ШИМ.

Givizub
Offline
Зарегистрирован: 01.01.2017

Добился того, чтобы "мигалка" изменяла периоды паузы и свечения поуродовав код "blink без delay":

unsigned long interval = 1000;
unsigned long interval2 = 5000;
unsigned long previousMillis = 0;
unsigned long previousMillis2 = 0;
uint32_t time1 = 0;

void setup() {
  pinMode (LED_BUILTIN, OUTPUT);

}

void loop() {

  static unsigned long previousMillis = 0;
  if (millis() - previousMillis > interval) {
    previousMillis = millis();
    digitalWrite(LED_BUILTIN, HIGH  );
    if (millis() - previousMillis2 > interval2) {
      previousMillis2 = millis();
      digitalWrite(LED_BUILTIN, LOW);
    }
  }
}

Подозреваю, что есть решения получше, но пока на ум больше ничего не приходит..

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

/* Led_resist.ino
#1 подстроечник -> A0 (R1_pin) 0...1023
#2 светодиод -> 2 (led_pin) 1 горит / 0 нет
*/
//#1
const int R1_pin = A0;// нога светодиода
int R1;
//#2
const int led_pin = 2;// нога светодиода
bool led;
const uint16_t period = 5000; // полный период ШИМ
uint16_t time_ON;

void setup() {
  //#1
  //#2
  pinMode (led_pin, OUTPUT);
  digitalWrite(led_pin, led = 0 );
}

void loop() {
  //#1
  R1 = analogRead(R1_pin);
  //#2
  time_ON = map (R1, 0, 1023, 0, period);
  static uint32_t past_2 = 0;
  if (led == 1 && millis() - past_2 >= time_ON) {
    digitalWrite(led_pin, led = 0 );
  }
  if (millis() - past_2 >= period) {
    past_2 = millis();
    digitalWrite(led_pin, led = 1 );
  }
}

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Чёт ругается компилятор :((( Говорит, что past_1 Пушкин будет описывать in this fucked scope :(((

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

Вот тема http://arduino.ru/forum/programmirovanie/upravlenie-tenami-propuskom-per...
Тут и ПИД и ШИМ мдленный. Может интересно будет ознакомиться.
Про ШИМ медленный http://arduino.ru/forum/programmirovanie/upravlenie-tenami-propuskom-per...

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

Исправил

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

qwone пишет:


/* Led_resist.ino
#1 подстроечник -> A0 (R1_pin) 0...1023
#2 светодиод -> 2 (led_pin) 1 горит / 0 нет
*/
//#1
const int R1_pin = A0;// нога светодиода
int R1;
//#2
const int led_pin = 2;// нога светодиода
bool led;
const uint16_t period = 5000; // полный период ШИМ
uint16_t time_ON;

void setup() {
  //#1
  //#2
  pinMode (led_pin, OUTPUT);
  digitalWrite(led_pin, led = 0 );
}

void loop() {
  //#1
  R1 = analogRead(R1_pin);
  //#2
  time_ON = map (R1, 0, 1023, 0, period);
  static uint32_t past_2 = 0;
  if (led == 1 && millis() - past_2 >= time_ON) {
    digitalWrite(led_pin, led = 0 );
  }
  if (millis() - past_2 >= period) {
    past_2 = millis();
    digitalWrite(led_pin, led = 1 );
  }
}

Еще пара варийантов

#define led 13
int period=2000;//период медленного ШИМ
int fill=250;//заполнение в мс
unsigned long currentTime=0;//для текущенго времени
unsigned long startFillTime=0;//для запоминания времени
void setup(){
  pinMode(led,OUTPUT);
  digitalWrite(led,0);
}
void loop(){
  currentTime=millis();//запоминаем текущее время
  if(currentTime-startFillTime>=1){//если прошла милисекунда
    startFillTime=currentTime;//запоминаем текущее время
    if (period !=0){//если период не прошол
      period--;//убавляем
      digitalWrite(led,(period>(fill))?0:1);//управляем выходом
    }
    else{//если период прошел
      period=2000;//начинаем заново
    }
  }
}//END LOOP

Этот лучше.

#define led 13
int period=2000;
int fill=250;
unsigned long startPeriodTime=0;//
unsigned long currentTime=0;//для запоминания текущего времени
void setup(){
  pinMode(led,OUTPUT);
  digitalWrite(led,0);
}
void loop(){
  currentTime=millis();
  digitalWrite(led,(currentTime-startPeriodTime>=fill)?0:1);//управляем выходом в зависимости от прошло ли заполнение шим?
  if (currentTime-startPeriodTime>=period){//если время периода прошло
    startPeriodTime=currentTime;//запоминаем время и начинаем отсчет нового периода
  }
}//END LOOP

но скважность лучше задавать в процентах от периода fill=(период/100*%заполнения)

evgta
Offline
Зарегистрирован: 02.09.2016

вот стандартный пример для управления реле

/********************************************************
 * PID RelayOutput Example
 * Same as basic example, except that this time, the output
 * is going to a digital pin which (we presume) is controlling
 * a relay.  the pid is designed to Output an analog value,
 * but the relay can only be On/Off.
 *
 *   to connect them together we use "time proportioning
 * control"  it's essentially a really slow version of PWM.
 * first we decide on a window size (5000mS say.) we then 
 * set the pid to adjust its output between 0 and that window
 * size.  lastly, we add some logic that translates the PID
 * output into "Relay On Time" with the remainder of the 
 * window being "Relay Off Time"
 ********************************************************/

#include <PID_v1.h>
#define RelayPin 6

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint,2,5,1, DIRECT);

int WindowSize = 5000;
unsigned long windowStartTime;
void setup()
{
  windowStartTime = millis();
  
  //initialize the variables we're linked to
  Setpoint = 100;

  //tell the PID to range between 0 and the full window size
  myPID.SetOutputLimits(0, WindowSize);

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
}

void loop()
{
  Input = analogRead(0);
  myPID.Compute();

  /************************************************
   * turn the output pin on/off based on pid output
   ************************************************/
  if(millis() - windowStartTime>WindowSize)
  { //time to shift the Relay Window
    windowStartTime += WindowSize;
  }
  if(Output < millis() - windowStartTime) digitalWrite(RelayPin,HIGH);
  else digitalWrite(RelayPin,LOW);

}

 

Givizub
Offline
Зарегистрирован: 01.01.2017

evgta пишет:
вот стандартный пример для управления реле

То, что нужно, попробую еще так. Когда в самописный код с millis() пытаюсь вставить регулятор, чтобы задавать значение интервала, то задержки не срабатывают, мигает с одной частотой.

Также решил не очень кошерным способом через FLProg. Сгеренированный код показывать не буду, так как он мерзкий. Тем не менее, похоже, что работает нормально.