Меняет буквы на LCD 1602 там где не должно

Здравствуйте
Небольшой код который сваял для тестирования меню.
Однако, при повороте энкодера меняются 2 символа в верхней строке дисплея. Что интересно, если повернуть сначала влево , меняет в слове МЕНЮ ->МЕИВ, а повернув сначала вправо МЕНЮ->МЕВК .
Вообще не понимаю как так может быть.

// Подключаем библиотеку русских сиволов
#define _LCD_TYPE 1       // Тип подключения дисплея: 1 - по шине I2C, 2 - десятиконтактное. Обязательно указывать ДО подключения библиотеки LCD_1602_RUS_ALL.h
#include "LiquidCrystal_I2C.h" 
#include <LCD_1602_RUS_ALL.h>
LCD_1602_RUS lcd(0x3F, 16, 2);

static PROGMEM const char menu_bar0[] = "Меню";
static PROGMEM const char menu_bar1[] = ">   Промывка   <";
static PROGMEM const char menu_bar2[] = ">    Налив          <";
static PROGMEM const char menu_bar3[] = ">   Настройка  <";
static PROGMEM const char menu_bar4[] = ">    Выход         <";

//  Функция обработки меню
void menu(){
 // flag_menu_bar = true;   //  Поднимаем флаг меню
  lcd.clear();
  lcd.setCursor(6, 0);    // Устанавливает курсор в (позицию,Строка) 
  lcd.print(F(menu_bar0)); 
  lcd.setCursor(0, 1);  // Устанавливает курсор в (позицию,Строка)
  lcd.print(menu_bar1);  
  int i =0;
  while (i!=2)
  {
    
    switch (i) {
      case 0:
        // выполнить, если значение == 0
        // запустить таймер выхода из меню
        break;
      case 1:
        // выполнить, если значение == 1
        Serial.println("Короткое нажатие");
        //  считать состояние 
        break;
      case 2:
        // выполнить, если значение == 2
        break;
      case 3:
        // выполнить, если значение == 3
        {
          Serial.println(" отработали козу Повернули вправо");
         // lcd.setCursor(6, 0);    // Устанавливает курсор в (позицию,Строка) 
         // lcd.print(F(menu_bar0)); 
          lcd.setCursor(0, 1);  // Устанавливает курсор в (позицию,Строка)
          lcd.print(F(menu_bar2)); 
        }
        break;
      case 4:
        // выполнить, если значение == 4
        {
          Serial.println(" отработали козу Повернули влево");
         // lcd.setCursor(6, 0);    // Устанавливает курсор в (позицию,Строка) 
         // lcd.print(F(menu_bar0)); 
          lcd.setCursor(0, 1);  // Устанавливает курсор в (позицию,Строка)
          lcd.print(F(menu_bar1));
        }  
        break;
    } 
    i = opros_encoder();   
  }
  
 // flag_menu_bar = false;  //  Опускаем флаг
  lcd.clear();
  lcd.setCursor(3, 0);  // Первый аргумент позиция, второй строка
  lcd.print("Наливатор"); 
  lcd.setCursor(4, 1);
  lcd.print("Версия 1");
}

//  Функция опроса энкодера
int opros_encoder(){ 
  int8_t otvet = 0;
  button.loop();  // опрос кнопки энкодера
  if(button.isPressed()){  // Кнопка энкодера нажата
    pressedTime = millis();
    isPressing = true;
    isLongDetected = false;
    }     
  if(button.isReleased()){  // Кнопка энкодера отпущена
    isPressing = false;
    releasedTime = millis();
    long pressDuration = releasedTime - pressedTime;
    if ( pressDuration < SHORT_PRESS_TIME ){
      Serial.println("Короткое нажатие зафиксировано"); 
      otvet = 1;
    }
  }
  if (isPressing == true && isLongDetected == false) {
    long pressDuration = millis() - pressedTime;
    if ( pressDuration > LONG_PRESS_TIME ) {
      Serial.println("Длинное нажатие определено");
      isLongDetected = true;
      otvet = 2;
    }
  }
  delay(10);
  if (encoder.getCount()>0){  //  Вращение вправо
    //Serial.println("Повернули вправо");
    otvet = 3;
  }
  else if (encoder.getCount()<0){ //Вращение влево
   // Serial.println("Повернули влево");
    otvet = 4;
  }
  encoder.setCount(0);  //  обнуляем энкодер
  return(otvet);
}

да, с русской библиотекой есть такой косяк, в чём конкретно - не разбирался
кстати - ‘button’ was not declared in this scope

Понял,буду использовать вывод обеих строк.
А про button . Я на него и не грешил, думал либо библиотека энкодера с прерываниями влияет. Поэтому и урезанный вариант выложил.
Спасибо.

Для того, чтобы можно было помочь скетч должен компилироваться и быть функциональным

  1. убрать из инклюдов библиотеку I2c, пусть её подтягивает RUS_ALL (в примерах так)

Этот case никогда не выполнится.

1 лайк

Он слишком большой и страшный. :face_with_hand_over_mouth:

#include "esp_task_wdt.h"
#include <HardwareSerial.h>
#include "ESP32Servo.h"
#include "WiFi.h"         //   библиотека подключения к сети wi-fi
#include <PubSubClient.h> //   Библиотека работы по протоколу mqtt

//============Плейер===============================================================================
#include <DFMiniMp3.h> // Библиотека работы с мелодиями dfplayer
class Mp3Notify; 
typedef DFMiniMp3<HardwareSerial, Mp3Notify> DfMp3; // define a handy type using serial and our notify class
DfMp3 dfmp3(Serial1);// instance a DfMp3 object, 
class Mp3Notify
{
public:
  static void PrintlnSourceAction(DfMp3_PlaySources source, const char* action)
  {
    if (source & DfMp3_PlaySources_Sd) 
    {
        Serial.print("SD Card, ");
    }
    if (source & DfMp3_PlaySources_Usb) 
    {
        Serial.print("USB Disk, ");
    }
    if (source & DfMp3_PlaySources_Flash) 
    {
        Serial.print("Flash, ");
    }
    Serial.println(action);
  }
  static void OnError([[maybe_unused]] DfMp3& mp3, uint16_t errorCode)
  {
    // see DfMp3_Error for code meaning
    Serial.println();
    Serial.print("Com Error ");
    Serial.println(errorCode);
  }
  static void OnPlayFinished([[maybe_unused]] DfMp3& mp3, [[maybe_unused]] DfMp3_PlaySources source, uint16_t track)
  {
    Serial.print("Play finished for #");
    Serial.println(track);  
  }
  static void OnPlaySourceOnline([[maybe_unused]] DfMp3& mp3, DfMp3_PlaySources source)
  {
    PrintlnSourceAction(source, "online");
  }
  static void OnPlaySourceInserted([[maybe_unused]] DfMp3& mp3, DfMp3_PlaySources source)
  {
    PrintlnSourceAction(source, "inserted");
  }
  static void OnPlaySourceRemoved([[maybe_unused]] DfMp3& mp3, DfMp3_PlaySources source)
  {
    PrintlnSourceAction(source, "removed");
  }
};


// Подключаем библиотеку русских сиволов
#define _LCD_TYPE 1       // Тип подключения дисплея: 1 - по шине I2C, 2 - десятиконтактное. Обязательно указывать ДО подключения библиотеки LCD_1602_RUS_ALL.h
#include "LiquidCrystal_I2C.h" 
#include <LCD_1602_RUS_ALL.h>
LCD_1602_RUS lcd(0x3F, 16, 2);

//========= НАСТРОЙКИ =============================================================================
//======= адресная лента ==========================================================================
#include <Adafruit_NeoPixel.h>    //  Библиотека адресной ленты
#define PIN_WS2812B	19            //  Пин подключения адресной ленты
#define NUM_PIXELS  5             //  Колличество светодиодов в адресной ленте WS2812b, равно колличеству рюмок
Adafruit_NeoPixel ws2812b(NUM_PIXELS, PIN_WS2812B, NEO_GRB + NEO_KHZ800); //  Создаем объект адресной ленты

//======= Wi-Fi и MQTT брокер =====================================================================
const char* ssid = "RT-GPON-89A0";       //  имя сети wi-fi
const char* password =  "*******";  //  пароль сети wi-fi

// MQTT данные с wqtt.ru
const char* mqtt_server = "*******";
const int mqtt_port = 18170;
const char* mqtt_user = "*******";
const char* mqtt_password = "*******";

WiFiClient espClient;
PubSubClient client(espClient);

// Рэле включения от алисы
const String relay_topic = "drink";
const int RELAY = 18;             //  Временно выводим состояние mqtt брокера , потом нужно буде запускать процедуру по приходящим данным
bool relay_on = false;

#define BUSY_PIN 23               //  пин готовности DF плеера низким, когда играет песня, и высоким, когда ничего не играет, не подключать на gpio12
Servo myservo;                    //  create servo object to control a servo
uint32_t servo_time_work  = 1000; //  Время задержки перед включением помпы необходимое для поворота сервы
#define SERVO_ATTACH  13          //  attaches the servo on pin 13 to the servo object
#define SHORT_PRESS_TIME 1000     //  1000 milliseconds
#define LONG_PRESS_TIME 1000      //  1000 milliseconds
#define SW_PIN 15                 //  пин GPIO15 ESP32 назначаем для кнопки SW энкодера
#define DT_PIN 2                  //  пин GPIO2 ESP32 назначаем для DT выхода энкодера
#define CLK_PIN 4                 //  пин GPIO4 ESP32 назначаем для CLK выхода энкодера
#define PUMP_PIN 14               //  Пин GPIO14 ESP32 назначаем для помпы. 
uint32_t  pump_time_work  = 3000; //  Время работы помпы
const uint8_t SW_pins[] = {32, 33, 25, 26, 27};   //  пины концевиков ESP32 для 5 стопок
const uint8_t Pos[] = {15, 45, 90, 130, 170, 0};  //  массив,в котором хранятся позиции сервы, в последней переменной хранится позиция парковки
bool flag_menu_bar  = false;      //  Флаг меню
//static PROGMEM const char   - помещает переменную во флешпамять
static PROGMEM const char menu_bar0[] = "Меню";
static PROGMEM const char menu_bar1[] = ">   Промывка   <";
static PROGMEM const char menu_bar2[] = ">    Налив     <";
static PROGMEM const char menu_bar3[] = ">   Настройка  <";
static PROGMEM const char menu_bar4[] = ">    Выход     <";

//======= Таски ===================================================================================
//TaskHandle_t TaskVolume;  //   Задача регулирования грамкости

//======= Энкодер =================================================================================
// Обязательно на сигнальные провода энкодера вешать керамические конденсаторы 0.1мкф
#include "ESP32Encoder.h"
#include <ezButton.h>           // Библиотека для использования SW  пина энкодера
bool isPressing = false;
bool isLongDetected = false;
unsigned long pressedTime  = 0; //  Время нажатия на кнопку энкодера
unsigned long releasedTime = 0; //  Время отпускания кнопки энкодера
ezButton button(SW_PIN);        //  создаем обьект ezButton на 15 пине;
// Обработчик прерываний
static IRAM_ATTR void enc_cb(void* arg) {
  ESP32Encoder* enc = (ESP32Encoder*) arg;
}
ESP32Encoder encoder(true, enc_cb);

//============Таймер===============================================================================
//создадим указатель на переменную с именем encButton_timer и типом type hw_timer_t для того чтобы в последующем производить настройку таймера.
hw_timer_t *encButton_timer = NULL; // обьявляем переменную таймера

//======= Функция подключения к MQTT брокеру ======================================================
void reconnect() {
  while (!client.connected()) {
    Serial.print("Попытка подключения по протоколу MQTT...");
    String clientId = "ESP32-" + WiFi.macAddress();
    if (client.connect(clientId.c_str(), mqtt_user, mqtt_password) ) {
      Serial.println("Подключено");
      
      client.subscribe( (relay_topic + "/#").c_str() ); // подписка на определенный топик

    } else {
      Serial.print("ошибка, rc=");
      Serial.print(client.state());
      Serial.println(" повторим попытку через 5 секунд");
      delay(5000);
    }
  }
}

//  Функция налива
void naliv() {

  //периодический вызов функции df mp3.loop() 
  // позволяет обрабатывать уведомления без прерываний
  //dfmp3.loop();
  lcd.clear();
  lcd.setCursor(3, 0);              // Устанавливает курсор в (позицию,Строка)
  lcd.print("Наливаем в"); 
  lcd.setCursor(4, 1);              // Устанавливает курсор в (позицию,Строка) 
  lcd.print("Рюмку"); 
  for (int i = 0; i < 5; i++) {     // опрашиваем концевики
    if(!digitalRead(SW_pins[i])){
      lcd.setCursor(10, 1);         // Устанавливает курсор в (Позиция,Строка) 
      lcd.print(i+1, DEC);          // Печатаем номер рюмки в которую льем
      myservo.write(Pos[i]);        //  Предвигаем серву в позицию " i "
      delay(servo_time_work);       //  Время задержки перед включением помпы
      digitalWrite(PUMP_PIN, HIGH); // ВКЛЮЧАЕМ помпу
      delay(pump_time_work);        // время налива 
      digitalWrite(PUMP_PIN, LOW);  // ВыКЛЮЧАЕМ помпу
      delay(1000);
    }  
  }
  myservo.write(Pos[5]);            //  возвращаем серву в парковочную позицию
  lcd.clear(); 
  lcd.setCursor(5, 0);
  lcd.print("Налито");              
  lcd.setCursor(3, 1);
  lcd.print("Тост пошел");          
  dfmp3.nextTrack();
  Serial.println(millis());
  delay(700);                 //  Обязательная задержка для опроса мр3 плейера                       
  while (!digitalRead(BUSY_PIN));   // Дожидаемся окончания проигрывания тоста 
  lcd.setCursor(3, 1);
  lcd.print("Приступаем");          


}

void updateStatePins(void){ // временная процедура включения реле
    if(relay_on){
      digitalWrite(RELAY, HIGH);
      Serial.println("вкл алиса");
      naliv();
    }else{
      digitalWrite(RELAY, LOW);
       Serial.println("алиса выключила лампу!");
    }

}

//======= функция обработки которая вызывается в client.loop ======================================
void callback(char* topic, byte* payload, unsigned int length) {
    
  String data_pay;
  for (int i = 0; i < length; i++) {
    data_pay += String((char)payload[i]);
  }

    Serial.println(data_pay);
    
  if( String(topic) == relay_topic ){
        if(data_pay == "ON" || data_pay == "1") relay_on = true;
        if(data_pay == "OFF" || data_pay == "0") relay_on = false;
    }

    updateStatePins();
}

void IRAM_ATTR onTimer(){     // обрабочкик таймера
}

//  Функция вывод на экран уровня громкости
void  volum_level(bool step){
  int i=0;
  lcd.clear(); 
  lcd.setCursor(0, 0);
  lcd.print("Громкость");
  lcd.setCursor(10, 0);  
  int volume  = dfmp3.getVolume();
  if (step) {
    if (volume==30) lcd.print("МАКС");
      else{
        dfmp3.setVolume(volume+1); //  Увеличивавем громкость динамика
        lcd.print(volume+1, DEC);  //  Выводит на экран переменную в десятичном формате
      }
  }
  else if (volume == 0) lcd.print("МИН");  //  Выводит на экран символы цифр;
    else {
      dfmp3.setVolume(volume-1); //  Уменьшаем громкость динамика         
      lcd.print(volume-1, DEC);  //  Выводит на экран переменную в десятичном формате
    }
  for ( i = 0; i < round( volume/2 ); i++)
  {
    lcd.setCursor(i,1);
    lcd.write(255); //  Выводит на экран символы по коду  
  }
}

//  Функция опроса энкодера
int opros_encoder(){ 
  int8_t otvet = 0;
  button.loop();  // опрос кнопки энкодера
  if(button.isPressed()){  // Кнопка энкодера нажата
    pressedTime = millis();
    isPressing = true;
    isLongDetected = false;
    }     
  if(button.isReleased()){  // Кнопка энкодера отпущена
    isPressing = false;
    releasedTime = millis();
    long pressDuration = releasedTime - pressedTime;
    if ( pressDuration < SHORT_PRESS_TIME ){
      Serial.println("Короткое нажатие зафиксировано"); 
      otvet = 1;
    }
  }
  if (isPressing == true && isLongDetected == false) {
    long pressDuration = millis() - pressedTime;
    if ( pressDuration > LONG_PRESS_TIME ) {
      Serial.println("Длинное нажатие определено");
      isLongDetected = true;
      otvet = 2;
    }
  }
  delay(10);
  if (encoder.getCount()>0){  //  Вращение вправо
    //Serial.println("Повернули вправо");
    otvet = 3;
  }
  else if (encoder.getCount()<0){ //Вращение влево
   // Serial.println("Повернули влево");
    otvet = 4;
  }
  encoder.setCount(0);  //  обнуляем энкодер
  return(otvet);
}

//======= Промывка системы ========================================================================
// Функция запускает насос на пробный налив, итоговый результат налива неопределен
// в рюмку может быть налито сколько угодно
void flushSystem(){
  // опросить концевики
  // если нет рюмки
  // выйти из промывки и сообщить , что нужна тара
  // если рюмка стоит, то налить 50- мл.
  // запустить какое нибудь прикольное сообщение

}

//======= Прогулка по меню вверх-вниз =============================================================
 void menuUpDown(int numMenu){

} 

//  Функция обработки меню
void menu(){
 // flag_menu_bar = true;   //  Поднимаем флаг меню
  lcd.clear();
  lcd.setCursor(6, 0);    // Устанавливает курсор в (позицию,Строка) 
  lcd.print(F(menu_bar0)); 
  lcd.setCursor(0, 1);  // Устанавливает курсор в (позицию,Строка)
  lcd.print(menu_bar1);  
  int i =0;
  while (i!=2)
  {
    
    switch (i) {
      case 0:
        // выполнить, если значение == 0
        // запустить таймер выхода из меню
        break;
      case 1:
        // выполнить, если значение == 1
        Serial.println("Короткое нажатие");
        //  считать состояние 
        break;
      case 2:
        // выполнить, если значение == 2
        break;
      case 3:
        // выполнить, если значение == 3
        {
          Serial.println(" отработали козу Повернули вправо");
         // lcd.setCursor(6, 0);    // Устанавливает курсор в (позицию,Строка) 
         // lcd.print(F(menu_bar0)); 
          lcd.setCursor(0, 1);  // Устанавливает курсор в (позицию,Строка)
          lcd.print(F(menu_bar2)); 
        }
        break;
      case 4:
        // выполнить, если значение == 4
        {
          Serial.println(" отработали козу Повернули влево");
         // lcd.setCursor(6, 0);    // Устанавливает курсор в (позицию,Строка) 
         // lcd.print(F(menu_bar0)); 
          lcd.setCursor(0, 1);  // Устанавливает курсор в (позицию,Строка)
          lcd.print(F(menu_bar1));
        }  
        break;
    } 
    i = opros_encoder();   
  }
  
 // flag_menu_bar = false;  //  Опускаем флаг
  lcd.clear();
  lcd.setCursor(3, 0);  // Первый аргумент позиция, второй строка
  lcd.print("Наливатор"); 
  lcd.setCursor(4, 1);
  lcd.print("Версия 1");
}

//======= Функция светомозыки =====================================================================
void CvetoMuzik() {
  while (!digitalRead(BUSY_PIN)){   // Пока проигрывает музыка выполняем 
  // Rainbow cycle along whole ws2812b. Pass delay time (in ms) between frames.
  // Hue of first pixel runs 3 complete loops through the color wheel.
  // Color wheel has a range of 65536 but it's OK if we roll over, so
  // just count from 0 to 3*65536. Adding 256 to firstPixelHue each time
  // means we'll make 3*65536/256 = 768 passes through this outer loop:
    for(long firstPixelHue = 0; firstPixelHue < 3*65536; firstPixelHue += 256) {
      for(int i=0; i<ws2812b.numPixels(); i++) { // For each pixel in ws2812b...
        // Offset pixel hue by an amount to make one full revolution of the
        // color wheel (range of 65536) along the length of the ws2812b
        // (ws2812b.numPixels() steps):
        int pixelHue = firstPixelHue + (i * 65536L / ws2812b.numPixels());
        // ws2812b.ColorHSV() can take 1 or 3 arguments: a hue (0 to 65535) or
        // optionally add saturation and value (brightness) (each 0 to 255).
        // Here we're using just the single-argument hue variant. The result
        // is passed through ws2812b.gamma32() to provide 'truer' colors
        // before assigning to each pixel:
        ws2812b.setPixelColor(i, ws2812b.gamma32(ws2812b.ColorHSV(pixelHue)));
      }
      ws2812b.show(); // Update ws2812b with new contents
      delay(1);  // Pause for a moment
    }
  }
}

//======= Функция которая будет выполнятся при работе задачи регулирования громкости

//======= Опрос концевиков ========================================================================
// Опрашивает концевики и синий цвет если рюмки нет, красный цвет если рюмка стоит
void opros_Pins(){

 for (int i = 0; i < 5; i++) {     // опрашиваем концевики
  ws2812b.setPixelColor(i, ws2812b.Color(0, 0, 250));  // указываем синий цвет пикселя
    if(!digitalRead(SW_pins[i])){
      ws2812b.setPixelColor(i, ws2812b.Color(250, 0, 0));  // указываем красный цвет пикселя
    }  
  }
   ws2812b.show();  //  Передаем состояние пикселей в ленту
}

void setup() {
  Serial.begin(115200);
  //===== Подключение к сети Wi-Fi ================================================================
  WiFi.begin(ssid, password);
   while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.println("Подключение к WiFi..");
  }
  Serial.println("Подключились к сети WiFi");
  
  //===== Реле включения от Алисы и настройки mqtt брокера ========================================
  pinMode(RELAY, OUTPUT);   // Пин реле как выход
  digitalWrite(RELAY, LOW); // Состояние выхода в "0"
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
  
  //===== Помпа ===================================================================================
  pinMode(PUMP_PIN, OUTPUT);    // настроем пин помпы в качестве выходного вывода
  digitalWrite(PUMP_PIN, LOW);  // ВЫКЛЮЧАЕМ помпу
  
  //===== Таски ===================================================================================

  
  //===== Лента адресных светодиодов ==============================================================
  ws2812b.begin();  //  инициализация адресной ленты WS2812B 
  pinMode(PIN_WS2812B, OUTPUT);
 
  //===== Серва ===================================================================================
  myservo.attach(SERVO_ATTACH);  
  myservo.write(Pos[5]);

  //===== Экран ===================================================================================
  lcd.init();           //Инициализация LCD (по умолчанию для ESP32 30Pin: 21 - SDA, 22 - SCL)
  lcd.backlight();
  lcd.setCursor(3, 0);  // Первый аргумент позиция, второй строка
  lcd.print("Наливатор"); 
  lcd.setCursor(4, 1);
  lcd.print("Версия 1");
  
  //===== Энкодер =================================================================================
    encoder.attachSingleEdge(DT_PIN, CLK_PIN);
    encoder.clearCount();
    encoder.setFilter(1023);// Задержка для фильтрации дребезга. На медленных чипах нужно уменьшать 
    // esp_task_wdt_add(loopTaskHandle);
    button.setDebounceTime(50); // устанавливаем время восстановления 50 миллисекундам

  //======= Концевики =============================================================================== 
    for (int i = 0; i < 5; i++) {         // установить концевики в положение высокого уровня 
      pinMode(SW_pins[i], INPUT_PULLUP);  // настроем пины рюмок в качестве входного вывода и включим внутренний подтягивающий резистор   
    }   
  
  //========Плейер=================================================================================
    pinMode(BUSY_PIN, INPUT); 
    dfmp3.begin(16,17);// UART0 является основным UART на ESP32 и по умолчанию подключается к пинам GPIO1 (TX0) и GPIO3 (RX0).
  // Он часто используется для связи с компьютером через серийный монитор и также используется для прошивки платы ESP32 новыми программами. 
  //Сообщения могут выводиться в консоль с помощью Serial.println().
  // UART1 Не разведен и используется для связи с помятью
  // Используем UART2 на пинах RX 16 и TX 17
  // for boards that support hardware arbitrary pins
  // dfmp3.begin(10, 11); // RX, TX

  // during development, it's a good practice to put the module
  // into a known state by calling reset().  
  // You may hear popping when starting and you can remove this 
  // call to reset() once your project is finalized
  dfmp3.reset(); 
  //uint16_t version = dfmp3.getSoftwareVersion();
  //Serial.print("version ");
  //Serial.println(version);

  // show some properties and set the volume
  //uint16_t volume = dfmp3.getVolume();
  dfmp3.setVolume(5); //Установка громкости динамика
  
  //uint16_t count = dfmp3.getTotalTrackCount(DfMp3_PlaySource_Sd); // колличсество треков на флешке
  //Serial.print("files ");
  //Serial.println(count);

 // uint16_t mode = dfmp3.getPlaybackMode();
 // Serial.print("playback mode ");
 // Serial.println(mode);
 // Serial.println("starting...");   
  dfmp3.playMp3FolderTrack(1); 
  delay(800);                       //  Обязательная задержка для опроса мр3 плейера
  CvetoMuzik();                     //  Играем огнями пока звучит приветственная музыка
  ws2812b.clear();
  ws2812b.show();
}

void loop() {
  if (!client.connected()) { // Постояння проверка подключение к mqtt брокеру
    reconnect();
  }
  client.loop();
  opros_Pins();
  switch (opros_encoder()) {
    case 0:
      // выполнить, если значение == 0
    break;
    case 1:
      // выполнить, если значение == 1
      Serial.println("Короткое нажатие");
      naliv() ; // Запускаем налив
    break;
    case 2:
      // выполнить, если значение == 2
      Serial.println("Длинное нажатие");
      {
        menu();  //  Переходим в меню
      }
    break;
    case 3:
      // выполнить, если значение == 3
      Serial.println(" отработали козу Повернули вправо");
      volum_level(1);
    break;
    case 4:
      // выполнить, если значение == 4
      Serial.println(" отработали козу Повернули влево");
      volum_level(0);
    break;
  }


 
}


выполняется, возвращается в исходное меню.

Как - не скажу, а что происходит - понятно. При переходе на следующей уровень меню вы обновляете текст и путаетесь в количестве букв на экране и их положениях. Поэтому при переходе от Меню к Налив получается Меив, а при входе в Промывку - Мевк

наверно вы не внимательно смотрели код,но вроде я обновляю вторую строку, а первую не трогаю.

lcd.setCursor(0, 1);  // Устанавливает курсор в (позицию,Строка)
lcd.print(F(menu_bar2)); 

А первая строка закоментирована.

        // lcd.setCursor(6, 0);    // Устанавливает курсор в (позицию,Строка) 
         // lcd.print(F(menu_bar0));

Я все таки подозреваю, что прерывания от энкодера как то не дружат с библиотекой русских символов для дисплея.Но у меня нехватает опыта и знаний. в отладке.

расскажите это своему дисплею

Да, есть такая проблемка с библиотекой LCD_1602_RUS_ALL. При первом проходе искажает свои, уже нарисованные, символы. Костыль был такой:

    lcd.noDisplay();
  sensor();  //bd
  sensor();  //норм
  lcd.display();

Этот костыль не помог, :cold_face:
Когда оборачиваю опрос энкодера, дисплей в этом случае светится только в момент изменения состояния энкодера. :crazy_face:

Что то не то ты делаешь. Ты сначала просто повтори вывод нового сообщения, без гашения дисплея, что бы убедиться в положительном результате.