Пид контроллер не работает

Добрый день! Делаю нижний подогрев для пайки по примеру с ютуба, но другой датчик, и дисплей не OLED, а 7-сег. Скетч также по примеру автора, но у меня симистор не отключается по достижению заданной температуры, хаотически ВКЛ\ВЫКЛ но греет дальше (симистор сделан на отдельном модуле, раньше проверялся и переход через ноль и управление работает). Посмотрите пожалуйста, я не до конца понимаю как работает алгоритм.

Видео

МОЙ СКЕТЧ

#include <GyverMAX6675.h>
#include "LedControl.h"
#include "GyverPID.h"
#include <EncButton.h>
#include <CyberLib.h>

#define AC_LOAD      5                        // симистор
#define zeroPin      2                        // переход через ноль
#define LEFT_BUTTON  3                        // левая кнопка
#define RIGHT_BUTTON 4                        // правая кнопка
#define CLK_PIN      7                        // Пин SCK // MAX6675
#define DATA_PIN     9                        // Пин SO
#define CS_PIN       8                        // Пин CS

GyverMAX6675<CLK_PIN, DATA_PIN, CS_PIN> sens; // датчик тепературы
LedControl lc=LedControl(12,11,10,1);         // дисплей
Button left_btn(LEFT_BUTTON);                 // левая кнопка
Button right_btn(RIGHT_BUTTON);               // правая кнопка
GyverPID pid(1.6, 0.015, 17);                 // пид - контроллер
int period = 300;                             // частота измерений 

uint16_t setTemp = 40;                   // установленная температура
uint16_t curTemp = 0;                    // текущая температура
volatile int tic = 0;                    // счетчик тиков в прерывании
volatile int Dimmer = 128;               // уровень мощности (0-128)  128 = OFF, 0 = ON
unsigned long timer;                     // таймер для считывания тепературы ...

void setup() 
{
  pid.setpoint = setTemp;                 // установочная температура, к которой стремимся 
  pid.setDt(period);                      // период обновления пид значения

  pinMode(AC_LOAD, OUTPUT);               // Пин на выход симистор
  digitalWrite(AC_LOAD, 0);               // на всякий случай симистор выключаем сразу
  pinMode(zeroPin, INPUT);                // переход через ноль

  attachInterrupt(0, detect_up, FALLING); // Установить прерывание при переходе сетевого напряжжения через "0"
  StartTimer1(timer_interrupt, 40);       // время для одного разряда ШИМ
  StopTimer1();                           // остановить таймер

  lc.shutdown(0,false);                   // пробуждение дисплея
  lc.setIntensity(0,3);                   // установка яркости
  lc.clearDisplay(0);                     // очистка дисплея
}

void loop() 
{ 
  updateSetTemp();                // обработка кнопок и установка температуры 
  updateDisplay(1,setTemp);       // обноление дисплея 
  updateDisplay(0,curTemp);
  
  if (millis() - timer >= period) // (period - 300)
  {
    timer = millis();
    curTemp = sens.getTemp();         // считываем температуру
    pid.getResult();                  // получаем результат 
    Dimmer = pid.output;              // 
    if (Dimmer > 255) Dimmer = 255;   //
    if (Dimmer < 0) Dimmer = 0;       //
    Dimmer = 255 - Dimmer;            //
  }
}

//----------------------ОБРАБОТЧИКИ ПРЕРЫВАНИЙ--------------------------
void timer_interrupt()                      // прерывания таймера срабатывают каждые 40 мкс
{                                           
    tic++;                                  // счетчик
    if (tic > Dimmer)                       // если настало время включать ток
    digitalWrite(AC_LOAD, 1);               // врубить ток
}

void detect_up()                           // обработка внешнего прерывания на пересекание нуля снизу
{                         
    tic = 0;                                // обнулить счетчик
    ResumeTimer1();                         // перезапустить таймер
    attachInterrupt(0, detect_down, RISING);// перенастроить прерывание
}

void detect_down()                         // обработка внешнего прерывания на пересекание нуля сверху
{                       
    tic = 0;                                // обнулить счетчик
    StopTimer1();                           // остановить таймер
    digitalWrite(AC_LOAD, 0);               // вырубить ток
    attachInterrupt(0, detect_up, FALLING); // перенастроить прерывание
}

//----------------------ОБНОВЛЕНИЕ ДИСПЛЕЯ--------------------------
void updateDisplay(bool place, uint16_t number)
{
  uint8_t first, second, third;
  first = number / 100;
  second = number % 100 / 10;
  third = number % 10; 
  if (place == 0)
  {
    lc.setDigit(0,7,first,false);
    lc.setDigit(0,6,second,false);
    lc.setDigit(0,5,third,false);
  }
  else if (place == 1)
  {
    lc.setDigit(0,2,first,false);
    lc.setDigit(0,1,second,false);
    lc.setDigit(0,0,third,false);
  }
}

//----------------------ОБНОВЛЕНИЕ КНОПОК--------------------------
void updateSetTemp()
{
  left_btn.tick();         // опрос  и обработка кнопок
  right_btn.tick();        

  if (left_btn.click())
  {
    if (setTemp > 5) setTemp -=5; 
  }

  if (right_btn.click())
  {
    if (setTemp < 275) setTemp +=5; 
  }
}

СКЕТЧ АВТОРА С ПРИМЕРА

#include <Wire.h>
#include "GyverPID.h"
#include "GyverEncoder.h"
#include <Adafruit_MAX31865.h>
#include <EEPROM.h>
#include "U8glib.h"         // библиотека дисплея
#include <CyberLib.h>

#define AC_LOAD 3           // Выход для управления семистором
#define zeroPin 2
#define RREF      430.0
#define RNOMINAL  100.0

#define Enc1_Pin    7
#define Enc2_Pin    8
#define Button_Pin  9  //

//#define DEBUG_ENABLE      // вывод в сериал данных для отладки
#define PRINT_DECIMAL       // отображения десятисной температуры

volatile int Dimmer = 128; // Уровень мощности (0-128)  128 = OFF, 0 = ON
volatile int tic = 0;
int SetPoint;
int Temperatute = 0;
#ifdef PRINT_DECIMAL
byte dt = 0;
#endif
bool save = false;
unsigned long delaySave = 0;
unsigned long DisplayUpdate = 0;
bool HeatingAllow = false;
byte s = 0;
char Buffer[] = "1234";
uint16_t rtd;
unsigned long tmr;

U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0|U8G_I2C_OPT_NO_ACK|U8G_I2C_OPT_FAST);    // Fast I2C / TWI 
Adafruit_MAX31865 thermo = Adafruit_MAX31865(10);
Encoder enc1(Enc1_Pin, Enc2_Pin, Button_Pin);      // для работы c кнопкой

GyverPID pid(1.6, 0.015, 17);
int period = 300;

void draw(void) {
    u8g.firstPage();  
    do {
        
        if(save){
            u8g.setFont(u8g_font_fub42n);
            sprintf(Buffer,"%d", SetPoint);
            if(SetPoint > 99){
                u8g.drawStr( 15, 43, Buffer);
            }else{
                u8g.drawStr( 32, 43, Buffer);
            }
            u8g.setFont(u8g_font_fub14n);
            sprintf(Buffer,"%d", Temperatute); 
            u8g.drawStr( 66, 63, Buffer);
        }else{
            u8g.setFont(u8g_font_fub42n);
            sprintf(Buffer,"%d", Temperatute); 
            if(Temperatute > 99){
                u8g.drawStr( 15, 43, Buffer);
            }else{
                u8g.drawStr( 32, 43, Buffer);
            }
            
            u8g.setFont(u8g_font_fub14n);
            #ifdef PRINT_DECIMAL
                sprintf(Buffer,"%d", dt); 
                u8g.drawStr( 117, 43, Buffer);
            #endif

            sprintf(Buffer,"%d", SetPoint); 
            u8g.drawStr( 66, 63, Buffer);
            
            if(HeatingAllow){
                u8g.setFont(u8g_font_10x20_75r);
                switch (s) {
                    case 3: u8g.drawStr( 23, 63, "7776"); break;
                    case 0: u8g.drawStr( 23, 63, "6777"); break;
                    case 1: u8g.drawStr( 23, 63, "7677"); break;
                    case 2: u8g.drawStr( 23, 63, "7767"); break;
                }
            }
        }

    } while( u8g.nextPage() );
}

void setup() {
    
    SetPoint = EEPROM.read(0);
    if(SetPoint == 255){
        SetPoint = 60;
        EEPROM.update(0, SetPoint);
    }
    #ifdef DEBUG_ENABLE
        Serial.begin(9600);
    #endif
    Serial.setTimeout(50);
    pid.setpoint = SetPoint;                // то, к чему стремимся
    pid.setDt(period);                      // период измерения - 100 мс
    
    pinMode(AC_LOAD, OUTPUT);               // Пин на выход
    digitalWrite(AC_LOAD, 0);
    pinMode(zeroPin, INPUT);
    attachInterrupt(0, detect_up, FALLING);  // Установить прерывание при переходе сетевого напряжжения через "0"
    StartTimer1(timer_interrupt, 40);        // время для одного разряда ШИМ
    StopTimer1();                            // остановить таймер

    Wire.setClock(3400000);
    
    thermo.begin(MAX31865_3WIRE);  // set to 2WIRE or 4WIRE as necessary

    enc1.setType(TYPE2);
}

void loop() {
    if(!save){
        if (millis() - tmr >= period) {
            tmr = millis();
            rtd = thermo.readRTD();
            pid.input = thermo.temperature(RNOMINAL, RREF);
            Temperatute = int(pid.input);
            #ifdef PRINT_DECIMAL
                dt = int((pid.input - Temperatute)*10);
            #endif   
            if(HeatingAllow){
                pid.getResult();
                Dimmer = pid.output;
                if (Dimmer > 255) Dimmer = 255;
                if (Dimmer < 0) Dimmer = 0;
                Dimmer = 255 - Dimmer;
            }else{
                Dimmer = 255;
            }
    
            #ifdef DEBUG_ENABLE
                Serial.print(pid.input); Serial.print(' ');
                Serial.print(pid.output); Serial.print(' ');
                Serial.print(pid.integral); Serial.print(' ');
                Serial.println(pid.setpoint);
            #endif
        }
    }else{
        Dimmer = 255;
    }
    
    #ifdef DEBUG_ENABLE
        parsing();
    #endif
    
    enc1.tick();
    if (enc1.isRight()){
        SetPoint++;
        if(SetPoint > 210){
            SetPoint = 210;
        }
        pid.setpoint = SetPoint;
        delaySave = millis();
        save = true;
        draw();
    }
    if (enc1.isLeft()){
        SetPoint--;
        if(SetPoint < 0){
            SetPoint = 0;
        }
        pid.setpoint = SetPoint;
        delaySave = millis();
        save = true;
        draw();
    }
    if (enc1.isClick()) HeatingAllow = !HeatingAllow;       // кнопка нажата
    
    if(save){
        if((millis() - 3000) >= delaySave){
            EEPROM.update(0, SetPoint);
            save = false;
        }
    }
    if((millis() - DisplayUpdate) >= 500){
        DisplayUpdate = millis();
        s++;
        if(s>3){s=0;};
        draw();
    }
    
}

#ifdef DEBUG_ENABLE
// ------------------управление через плоттер-------------------------
void parsing() {
  if (Serial.available() > 1) {
    char incoming = Serial.read();
    float value = Serial.parseFloat();
    switch (incoming) {
      case 'p': pid.Kp = value; break;
      case 'i': pid.Ki = value; break;
      case 'd': pid.Kd = value; break;
      case 's': pid.setpoint = value; SetPoint = value; break;
      case 'h': HeatingAllow = !HeatingAllow; break;
    }
  }
}
#endif

//----------------------ОБРАБОТЧИКИ ПРЕРЫВАНИЙ--------------------------
void timer_interrupt() {                    // прерывания таймера срабатывают каждые 40 мкс
    tic++;                                  // счетчик
    if (tic > Dimmer)                       // если настало время включать ток
    digitalWrite(AC_LOAD, 1);               // врубить ток
}

void  detect_up() {                         // обработка внешнего прерывания на пересекание нуля снизу
    tic = 0;                                // обнулить счетчик
    ResumeTimer1();                         // перезапустить таймер
    attachInterrupt(0, detect_down, RISING);// перенастроить прерывание
}

void  detect_down() {                       // обработка внешнего прерывания на пересекание нуля сверху
    tic = 0;                                // обнулить счетчик
    StopTimer1();                           // остановить таймер
    digitalWrite(AC_LOAD, 0);               // вырубить ток
    attachInterrupt(0, detect_up, FALLING); // перенастроить прерывание
}

С этим - обращаться на форум Гайвера.

удали все говно что ты накачал, пытаясь решить проблему тупым тыканьем и перебором вариантов, по типу гиббона, тыкающего палкой в муравейник, отложи паяльник, изучи теорию, потом пробуй. Теория вот: https://habr.com/ru/articles/345972/
ПС. Ардуино тоже удали, юзай рекомедованные производителем ПЛК тулчейны и библиотеки. Но сначала теория.

Описанная вами проблема, обычно возникает из-за неправильно подобранных коэффицентов ПИД регулятора. Чтобы их подобрать нужно хорошо понимать принцып работы ПИД. Если не понимаете, тогда удаляйте его из своего проекта совсем.
Для работы нижнего подогрева совсем не обязательна та точность, которую может обеспечить ПИД регулятор. Для начала будет достаточно просто сравнивать реальную температуру стола с требуемой. Если реальная температура выше требуемой - подогрев отключить, если ниже - влючить.

Спасибо, тогда попробую подобрать значения, думал 100% ошибка в коде, если не выйдет сделаю как вы говорите по простому

Хочется добавить что включать и выключать следует не точно при равенстве заданной температуре, а используя небольшой зазор - гистерезис. Иначе управление будет дёргаться в близи уставки. Включать чуть ниже, выключать чуть выше заданной температуры.

Как бы в этом и суть) Не, ну можно руками выключатель тогда щёлкать раз в минуту)
Электроника вообще создана, чтобы “дёргаться” вместо человека)

Небольшой зазор там будет обеспечивать оптррн с зерокросс и еще инертность стола с нагревателем, поэтому в донном конкретном случае, прграммный гистерезис дельть нужды нет.

1 лайк

Ну-ну. Когда датчик стоит у порогового значения он всегда “дребезжит”. Гистерезис нужен что б не замечать этот дребезг. Зерокросс вообще ни как к этому явлению.