Виснет энкодер с библиотекой EncButton2

MaximSK

✩✩✩✩✩✩✩
28 Мар 2023
5
0
Добрый день, столкнулся со следующей проблемой. Вроде как все работает, но энкодер через какое-то количество срабатываний в подменю "Ручной режим" зависает. Проблема исчезает если либо заменить кирилицу на латиницу либо если перестать вызывать любую из функций внутри функции "manual_mod". Добавил кнопки в обход библиотеки EncButton2. После зависания энкодера программа продолжает работать при управлении кнопками. Помогите пожалуйста решить данную проблему.
Код:
#include <EEPROM.h>              // Подключаем библиотеку для работы с EEPROM
#include <U8g2lib.h>
#include <SPI.h>
#include <microDS18B20.h>        //библиотека датчика температуры
#include <EncButton2.h>          //библиотека энкодера

EncButton2<EB_ENCBTN> enc(INPUT_PULLUP, 2, 3, 4);  // энкодер с кнопкой
U8G2_ST7920_128X64_F_HW_SPI u8g2(U8G2_R0, /* CS=[I]/ 10, /[/I] reset=*/ 8);

#define heater_pin 9               //пин управление тэн
#define buzzer_pin 5               // Пин подключения пассивной пищалки
#define EB_DEB 20      // дебаунс кнопки, мс по умолчанию 50

MicroDS18B20<6> ds;                //датчик температуры на 6 пин       Раскоментировать

uint8_t main_ptr = 1;           //Указатель главного меню
uint8_t program_ptr = 1;        //Указатель меню выбора и редактирования программ
//bool end_loop;                  //Флаг выхода из меню выбора и редактирования программ
float cur_temp = 0;          //текущаяя температура
bool update = true;


void setup() {
  pinMode(buzzer_pin, OUTPUT);
  pinMode(heater_pin, OUTPUT);
  pinMode(A1, INPUT_PULLUP);  //Enter
  pinMode(A2, INPUT_PULLUP);  //Esc
  pinMode(A3, INPUT_PULLUP);  //внмз
  pinMode(A4, INPUT_PULLUP);  //Вверх
  pinMode(A5, OUTPUT);
  digitalWrite(A5, LOW); //Земля для кнопок
  Serial.begin(115200);
  attachInterrupt(0, isr, CHANGE);
  attachInterrupt(1, isr, CHANGE);
  u8g2.begin();   // enable UTF8 support for the Arduino print() function
  u8g2.enableUTF8Print();
}
void isr() {
  enc.tickISR();  // тикер в прерывании 
}

void loop() {
  enc.tick();
  u8g2.firstPage(); //очищает буфер экрана
  u8g2.setFont(u8g2_font_7x13_t_cyrillic);
  u8g2.setDrawColor(2);   
  u8g2.setCursor(22, 9);
  u8g2.print("Главное меню");
  u8g2.drawLine(20, 11, 106, 11); 
  u8g2.setCursor(2, 26);
  u8g2.print("Выбор программы");
  u8g2.setCursor(2, 40);
  u8g2.print("Редактор программ");
  u8g2.setCursor(2, 54);
  u8g2.print("Ручной режим");
  u8g2.drawRBox(0,main_ptr*14+1,127,14,0);
  u8g2.nextPage(); 
 
  if (enc.right() || digitalRead(A3) == LOW) {
    main_ptr++;
    if (main_ptr == 4) main_ptr = 1;
  }
  if (enc.left() || digitalRead(A4) == LOW) {   
    main_ptr--;
    if (main_ptr == 0) main_ptr = 3;
  }
  if (enc.click() || digitalRead(A1) == LOW) {
    //if (main_ptr == 1) { Program_selection(); }
    //if (main_ptr == 2) { Program_editing(); }
    if (main_ptr == 3) { Manual_mod(); }
  }
}



void Manual_mod() { //Ручной режим
  int8_t ptr = 1;    // Указатель 
  bool flag = false; // Флаг выхода на температурный режим
  uint32_t Tmr_ms;   //Таймер температурного режима
  static int8_t temp_pow[] = {50, 80};   // температура, мощность
  bool heater_on = false; //Нагреватель выключен

  for (;;) {
    get_temp();   
    u8g2.firstPage(); //очищает буфер экрана     
    u8g2.setFont(u8g2_font_6x12_t_cyrillic);
    u8g2.setDrawColor(2);     
    //u8g2.setCursor(28, 7);
    u8g2.drawUTF8(28, 7, "Ручной режим");   
    //u8g2.print("Ruchnoy rezhim");
    u8g2.drawLine(26, 10, 100, 10);
    //u8g2.setCursor(2, 22);
    //u8g2.print("Tek. temp-ra ");
    u8g2.drawUTF8(2, 22, "Тек. темп-ра ");
    u8g2.setCursor(90, 22);
    u8g2.print(cur_temp,1);
    u8g2.setFont(u8g2_font_6x12_tf);   
    u8g2.print("°C");
    u8g2.setFont(u8g2_font_6x12_t_cyrillic);
    //u8g2.setCursor(2, 34);
    //u8g2.print("Ust. temp-ra ");
    u8g2.drawUTF8(2, 34, "Уст. темп-ра ");
    u8g2.setCursor(90, 34);       
    u8g2.print(temp_pow [0]);
    u8g2.setFont(u8g2_font_6x12_tf);   
    u8g2.print("°C");
    u8g2.setFont(u8g2_font_6x12_t_cyrillic);
    //u8g2.setCursor(2, 46);
    //u8g2.print("Moschnost TENa ");
    u8g2.drawUTF8(2, 46, "Мощность ТЭНа ");   
    u8g2.setCursor(90, 46);
    u8g2.print(temp_pow [1]);
    u8g2.print("%");       
    //u8g2.setCursor(2, 58);
    if (heater_on){
      //u8g2.print("TEN Vkl ");
      u8g2.drawUTF8(2, 58, "ТЭН Вкл");
    } else {
      //u8g2.print("TEN Vikl ");
      u8g2.drawUTF8(2, 58, "ТЭН Выкл");
    }
    u8g2.drawRBox(0,ptr*12+13,127,12,0);
    
    u8g2.nextPage();  // Выводит содержимое буфера на экран   
    
        
    if (cur_temp > temp_pow[0] - 0.5 && !flag){
      flag = true;
      Tmr_ms = millis();
    }
    
    if (heater_on) heat(temp_pow[0], temp_pow[1]);
    else digitalWrite(heater_pin, LOW); 

    enc.tick();
    if (enc.leftH()) {
      if (ptr < 3) {
        temp_pow[ptr-1]--;
      } else {
        heater_on = !heater_on;
      }
      if(ptr == 1 && flag){
        flag = false;
        digitalWrite(buzzer_pin, LOW);
        
      }
    }

    if (enc.left() || digitalRead(A4) == LOW){ 
      ptr--;
      if(ptr == 0) ptr=3;       
    }

    if (enc.rightH()) {
      if (ptr < 3) {
        temp_pow[ptr-1]++;
      } else {
        heater_on = !heater_on;
      }
      if(ptr == 1 && flag){
        flag = false;
        digitalWrite(buzzer_pin, LOW);       
      }
    }

    if (enc.right() || digitalRead(A3) == LOW){       
      ptr++;
      if(ptr == 4) ptr=1;             
    }   
    
    if ((enc.click() || digitalRead(A1) == LOW) && ptr == 3) heater_on = !heater_on;
    
    temp_pow[0] = constrain(temp_pow[0], 20, 100);
    temp_pow[1] = constrain(temp_pow[1], 10, 100);
    
    if (enc.held() || digitalRead(A2) == LOW) break; 
  } 
  digitalWrite(heater_pin, LOW);
  digitalWrite(buzzer_pin, LOW);
}

void get_temp() {
  static uint32_t temp_timer = 0;
  if (millis() - temp_timer >= 1000) {
    temp_timer = millis();
    if (ds.readTemp()) {           
      cur_temp = ds.getTemp();
      ds.requestTemp();
    }
  }
}

void heat(int8_t set_temp, int8_t power){
 
  static uint16_t activePeriod = 0;
  static uint32_t pwm_tmr = 0;

  if (millis() - pwm_tmr >= 1000) {
    pwm_tmr = millis();   
    if (set_temp < 100){ // пид регулятор     
      const float kp = 25;
      const float ki = 0.3;
      const float kd = 15;
      static float integral = 0, prevErr = 0;

      float err = set_temp - cur_temp; // Пропорциональная составляющая             
      if(err < 0) integral = constrain(integral + err, (-2 + err * 10), (2 - 10 * err)); //Интегральная составляющая
      else integral = constrain(integral + err, (-2 - err * 10), (2 + 10 * err)); //Интегральная составляющая
      float D = err - prevErr; //Дифференциальная состовляющая
      prevErr = err;
      activePeriod = round(constrain(err * kp + integral * ki + D * kd, 0, power)) * 10;
      Serial.print(cur_temp);
      Serial.print("   ,");
      Serial.print(err);
      Serial.print("    ,");
      Serial.print(err * kp);
      Serial.print("   ,");             
      Serial.print(integral * ki);
      Serial.print("   ,");
      Serial.print(D * kd);
      Serial.print("     ,");
      Serial.println(activePeriod/10);
    } else {
      activePeriod = power * 10;     
    }
  }
  if (millis() - pwm_tmr <= activePeriod) digitalWrite(heater_pin, HIGH);
  else {
    digitalWrite(heater_pin, LOW);   
  }
}
 

viktor1703

★★★✩✩✩✩
9 Дек 2021
531
131
Ну а как ты хотел? Применённые библиотеки, конечно, не изучал. Тикер энкодера должен вызываться как можно чаще, а Manual_mod() упирается длинной портянкой в пол, да еще и с бесконечным циклом внутри, где постоянно что-то выводится на дисплей, даже если никаких изменений и не нужно.
Что нам пишет товарищ @AlexGyver про опрос датчика температуры?
Чтение температуры
Чтение температуры делится на два этапа - запрос и получение данных. Запрос делается функцией requestTemp(). После получения запроса датчик начинает измерение температуры, которое длится от 90 до 750 мс в зависимости от настроенной точности (по умолчанию точность максимальная, преобразование длится 750 мс). Если прочитать температуру до окончания преобразования - датчик вернёт результат предыдущего измерения, поэтому в примерах используется задержка или опрос по таймеру на 1 секунду.
Ну да ладно, чтение температуры, вроде как, не должно оказывать влияния на энкодер, он же по прерыванию работает, просто температуру должно показывать одну и ту же. Но смотрим дальше. Для общения Ардуино с датчиком использует microOneWire.h, а вот она уже тозапрещает, то снова разрешает прерывания. И, возможно, хрен её знает, может я и ошибаюсь, где-то прерывание запрещается и больше не разрешается.
 

MaximSK

✩✩✩✩✩✩✩
28 Мар 2023
5
0
Хотел бы что все нормально работало :) Прерывания продолжают работать, проверял. Энкодер перестает работать полностью, даже кнопка, а она без прерываний работает. Похоже полностью перестает работать библиотека EncButton2. Проблема вроде решилась использованием библиотеки просто
EncButton без 2.

Проблема вернулась когда еще немного увеличил код.
 
Изменено:

Старик Похабыч

★★★★★★★
14 Авг 2019
4,159
1,267
Москва
Для начала ограничьте частоту вывода данных на дисплей, строки 44-57, у вас это простое меню и выводить данные для максимального FPS не имеет смысла, это не шутер.
Ну а проблема явно в функции manual_mod , а не в библиотеке. Serial у вас открыт, выставляйте контрольный вывод (редкий!) в этой функции и смотрите где все крутится . Постввьте вывод в районе тика энкодер. Посмотрите как интервал обработки этого тика. Только непонятно зачем обрабатывать просто тик и тик в прерывании. Если есть прерывание, то обычный уже не нужен - все должно обрабатываться внутри прерывания.
 

MaximSK

✩✩✩✩✩✩✩
28 Мар 2023
5
0
Для начала ограничьте частоту вывода данных на дисплей,
Делал, проблему это не решает, здесь же я привел максимально урезанный код в котором проблема сохраняется.

Serial у вас открыт, выставляйте контрольный вывод (редкий!) в этой функции и смотрите где все крутится . Постввьте вывод в районе тика энкодер. Посмотрите как интервал обработки этого тика.
Тоже делал, программа продолжает крутится как и задумано, кроме того специально добавил кнопки в обход библиотеки EncButton и все работает. Прерывания срабатывают, функция isr вызывается, просто нет ни какой реакции на энкодер. Такое впечатления что просто функции enc.right(),
(enc.left(), и тд перестают возвращать 1.

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

MaximSK

✩✩✩✩✩✩✩
28 Мар 2023
5
0
Изменил float cur_temp на uint16_t cur_temp,. В функции get_temp() прописал cur_temp = ds.getTemp() * 100; а в коде везде вместо cur_temp прописал (float)cur_temp/100 и проблема вроде как опять ушла. С чем то всетаки эта библиотека конфликтует. Да кстати проблема не в железе, пробовал на разных ардуинках(все нано)
 

MaximSK

✩✩✩✩✩✩✩
28 Мар 2023
5
0
Проблема решена! она заключалась в нехватке оперативной памяти. Оптимизировал код и все заработало. Сам спросил сам ответил :)
 

viktor1703

★★★✩✩✩✩
9 Дек 2021
531
131
Manual_mod() упирается длинной портянкой в пол, да еще и с бесконечным циклом внутри, где постоянно что-то выводится на дисплей, даже если никаких изменений и не нужно.
Я намекал давно. Вывод на дисплей память потребляет же
 
Изменено: