From b72c2a96e44f48742c4e42a99c98f529661b5f13 Mon Sep 17 00:00:00 2001 From: elios Date: Thu, 21 Aug 2025 17:45:30 +0000 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20test-menu.ino?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test-menu.ino | 323 ++++++++++++++++++++++++++++---------------------- 1 file changed, 180 insertions(+), 143 deletions(-) diff --git a/test-menu.ino b/test-menu.ino index b9ec52b..1a3c719 100644 --- a/test-menu.ino +++ b/test-menu.ino @@ -1,57 +1,121 @@ /* - SmartCharge - Menu MVP (ESP32 + OLED + Encoder) [U8g2 + кириллица] - - I2C OLED @ 0x3C (сканер уже нашёл) - - USE_SH1106: 1 = SH1106, 0 = SSD1306 - - SDA=21, SCL=22 - - Encoder: A=32, B=33, BTN=26 + SmartCharge - Меню (ESP32 + OLED + Encoder) [U8g2 + стабильный энкодер] + - Драйвер OLED: USE_SH1106=1 для SH1106, 0 для SSD1306 + - I2C: SDA=21, SCL=22, адрес 0x3C (проверен сканером) + - Энкодер: A=GPIO32, B=GPIO33, BTN=GPIO26 (все с INPUT_PULLUP) + - Подключение энкодера: общий (COM) -> GND; A -> 32; B -> 33. + Кнопка: один контакт -> GND, второй -> 26. + - Рекомендация: к A и B поставить по 10нФ на землю для тихого вращения. */ -#define USE_SH1106 1 // <<< если у тебя SSD1306, поставь 0 +#define USE_SH1106 1 // 1 = SH1106, 0 = SSD1306 #include #include -#include #include -// -------- OLED (U8g2) -------- +// ------------ OLED ------------ #if USE_SH1106 -// Полный буфер, аппаратный I2C -U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /*reset=*/ U8X8_PIN_NONE); + U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE); #else -U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /*reset=*/ U8X8_PIN_NONE); + U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE); #endif +#define MENU_FONT u8g2_font_6x12_t_cyrillic -// Шрифт с кириллицей (компактный, читаемый) -#define MENU_FONT u8g2_font_6x12_t_cyrillic - -// -------- ENCODER -------- +// ------------ ПИНЫ ЭНКОДЕРА ------------ const int ENC_A_PIN = 32; const int ENC_B_PIN = 33; const int ENC_BTN_PIN = 26; -Bounce encBtn; -volatile int16_t encDelta = 0; -int8_t transTable[16] = { - 0, -1, +1, 2, - +1, 0, 2, -1, - -1, 2, 0, +1, - 2, +1, -1, 0 -}; -inline void encoderPoll() { - uint8_t a = digitalRead(ENC_A_PIN); - uint8_t b = digitalRead(ENC_B_PIN); - static uint8_t last = 0; - uint8_t s = (a << 1) | b; - uint8_t idx = ((last & 0x03) << 2) | (s & 0x03); - int8_t d = transTable[idx & 0x0F]; - if (d == +1) encDelta++; - else if (d == -1) encDelta--; - last = s; -} -int16_t encoderReadAndClear() { noInterrupts(); int16_t v=encDelta; encDelta=0; interrupts(); return v; } -uint32_t ms(){ return millis(); } +// ------------ УСТОЙЧИВАЯ ОБРАБОТКА ЭНКОДЕРА ------------ +// Алгоритм: принимаем только валидные переходы и считаем «полный шаг» = 4 тика. +// Плюс фильтр по времени, чтобы отсечь дребезг (микросекунды). +volatile int16_t encSteps = 0; // целые клики (детенты) +static uint8_t lastAB = 0; +static int8_t accum = 0; +static uint32_t lastEdgeUs = 0; -// -------- PROFILES -------- +const uint32_t ENC_DEBOUNCE_US = 500; // игнорируем переходы быстрее 0.5 мкс (защита от дребезга) +const int8_t trans[4][4] = { +/*from\to: 00 01 10 11 */ + /*00*/ { 0, +1, -1, 0 }, + /*01*/ { -1, 0, 0, +1 }, + /*10*/ { +1, 0, 0, -1 }, + /*11*/ { 0, -1, +1, 0 } +}; + +inline void encoderUpdate() { + // читаем сразу и A, и B (быстро) + uint8_t a = (uint8_t)digitalRead(ENC_A_PIN); + uint8_t b = (uint8_t)digitalRead(ENC_B_PIN); + uint8_t ab = (a << 1) | b; // 0..3 + + if (ab == lastAB) return; // нет изменений + + uint32_t now = micros(); + if (now - lastEdgeUs < ENC_DEBOUNCE_US) { // слишком быстро -> дребезг + lastAB = ab; + return; + } + lastEdgeUs = now; + + int8_t d = trans[lastAB & 0x03][ab & 0x03]; + lastAB = ab; + if (d == 0) return; // невалидный прыжок (дребезг) + + accum += d; + // Полный шаг у обычного энкодера — 4 валидных перехода + if (accum >= +4) { encSteps++; accum = 0; } + else if (accum <= -4) { encSteps--; accum = 0; } +} + +int16_t encoderReadAndClear() { + noInterrupts(); + int16_t v = encSteps; + encSteps = 0; + interrupts(); + return v; +} + +// ------------ КНОПКА (простой дебаунс) ------------ +bool btnPressed = false; +bool btnShortClick = false; +bool btnLongReleased = false; +uint32_t btnDownAt = 0; +const uint32_t BTN_DEBOUNCE_MS = 15; +const uint32_t BTN_LONG_MS = 800; + +void buttonUpdate() { + static bool lastRaw = true; // INPUT_PULLUP -> покой = HIGH (true) + static uint32_t lastChange = 0; + + bool raw = digitalRead(ENC_BTN_PIN); // HIGH=не нажата, LOW=нажата + uint32_t now = millis(); + + if (raw != lastRaw) { // изменение + lastRaw = raw; + lastChange = now; + } + + // дебаунс: ждём стабильности + if (now - lastChange < BTN_DEBOUNCE_MS) return; + + if (!raw && !btnPressed) { // нажали + btnPressed = true; + btnShortClick = false; + btnLongReleased = false; + btnDownAt = now; + } else if (raw && btnPressed) { // отпустили + btnPressed = false; + if (now - btnDownAt >= BTN_LONG_MS) btnLongReleased = true; + else btnShortClick = true; + } +} + +bool isShortClick(){ if (btnShortClick){ btnShortClick=false; return true;} return false; } +bool isLongPressReleased(){ if (btnLongReleased){ btnLongReleased=false; return true;} return false; } + +// ------------ ПРОФИЛИ/МЕНЮ (как раньше) ------------ enum : uint8_t { CHEM_LIION=0, CHEM_LFP=1, CHEM_LTO=2 }; static inline const char* chemToStr(uint8_t c){ switch(c){ case CHEM_LIION: return "Li-ion"; case CHEM_LFP: return "LiFePO4"; case CHEM_LTO: return "LTO"; } @@ -64,6 +128,7 @@ struct Profile { float rateC; // 0.1..1.0 float ItermA; // A }; + Preferences prefs; const int NUM_PROFILES = 5; Profile profiles[NUM_PROFILES]; @@ -95,7 +160,7 @@ void saveProfile(int i) { } void saveLastIndex(int idx){ prefs.begin("sc", false); prefs.putUChar("lastIdx", idx); prefs.end(); } -// -------- UI STATE -------- +// UI state enum UiState { UI_HOME=0, UI_START_MODE, UI_START_PROFILE, @@ -107,90 +172,59 @@ enum UiState { UiState ui = UI_HOME; int cursor=0, topIndex=0; -uint32_t btnDownAt=0, lastClickAt=0; -bool btnWasDown=false, lastClickUsed=false; enum StartMode { SM_CHARGE=0, SM_DISCHARGE, SM_RINT }; StartMode startMode = SM_CHARGE; int startProfileIdx = 0; uint16_t quickCap_mAh = 2500; float quickRateC=0.5f, quickItermA=0.10f; -// -------- DRAW HELPERS (U8g2) -------- +// --- helpers (U8g2) void setFont(){ u8g2.setFont(MENU_FONT); } - void drawHeader(const char* t){ setFont(); - // белая плашка u8g2.setDrawColor(1); u8g2.drawBox(0,0,128,12); - // чёрным по белому u8g2.setDrawColor(0); u8g2.setCursor(2,10); u8g2.print(t); - // вернуть белый цвет рисования u8g2.setDrawColor(1); } -void drawFooterHint(const char* s){ - setFont(); - u8g2.setCursor(0,64-2); // нижняя строка - u8g2.print(s); -} void drawMenuItem(int y, const String& text, bool sel){ setFont(); - if (sel){ - u8g2.drawBox(0,y-9,128,11); // выделенная строка - u8g2.setDrawColor(0); u8g2.setCursor(2,y); u8g2.print(text); - u8g2.setDrawColor(1); - } else { - u8g2.setCursor(2,y); u8g2.print(text); - } + if (sel){ u8g2.drawBox(0,y-9,128,11); u8g2.setDrawColor(0); u8g2.setCursor(2,y); u8g2.print(text); u8g2.setDrawColor(1); } + else { u8g2.setCursor(2,y); u8g2.print(text); } } +void drawFooter(const char* s){ setFont(); u8g2.setCursor(0,62); u8g2.print(s); } String fmtFloat(float v,int p){ char b[20]; dtostrf(v,0,p,b); return String(b); } -// -------- INPUT -------- -void pollInputs(){ - encoderPoll(); - encBtn.update(); - bool down = !encBtn.read(); - if (down && !btnWasDown) btnDownAt = ms(); - else if (!down && btnWasDown){ - uint32_t t = ms() - btnDownAt; - if (t < 1500){ - uint32_t now = ms(); - if (now - lastClickAt < 400 && !lastClickUsed) lastClickUsed = true; - else lastClickUsed = false; - lastClickAt = now; - } - } - btnWasDown = down; +// --- rotary input wrapper +int readRot(int step=1){ + int16_t d = encoderReadAndClear(); + if (!d) return 0; + // по одному пункту на «щелчок» + return (d>0)? +step : -step; } -bool isShortClick(){ static uint32_t lh=0; if(!btnWasDown){uint32_t now=ms(); if(now-lastClickAt<80 && !lastClickUsed && now-lh>150){lh=now; return true;}} return false; } -bool isDoubleClick(){ static uint32_t lh=0; if(!btnWasDown){uint32_t now=ms(); if(now-lastClickAt<120 && lastClickUsed && now-lh>150){lh=now; lastClickUsed=false; return true;}} return false; } -bool isLongPressReleased(){ static uint32_t lh=0; if(!btnWasDown){uint32_t dur=ms()-btnDownAt; if(dur>=1500 && (ms()-lh>200)){lh=ms(); return true;}} return false; } -int readRot(int step=1){ int16_t d=encoderReadAndClear(); if(!d) return 0; return d>0?+step:-step; } -// -------- SCREENS -------- +// --- screens void screenHome(){ - u8g2.clearBuffer(); - drawHeader("Главное меню"); - const char* items[] = {"Старт","Профили","Параметры","Сервис"}; int n=4; - int r=readRot(); if(r){ cursor+=(r>0?1:-1); if(cursor<0) cursor=n-1; if(cursor>=n) cursor=0; } + u8g2.clearBuffer(); drawHeader("Главное меню"); + const char* items[]={"Старт","Профили","Параметры","Сервис"}; int n=4; + int r=readRot(); if(r){ cursor += (r>0?1:-1); if(cursor<0) cursor=n-1; if(cursor>=n) cursor=0; } for(int i=0;i0?1:-1); if(cursor<0) cursor=n-1; if(cursor>=n) cursor=0; } + u8g2.clearBuffer(); drawHeader("Старт: режим"); + const char* items[]={"Заряд","Разряд (1S)","МиллиОм (1S)"}; int n=3; + int r=readRot(); if(r){ cursor += (r>0?1:-1); if(cursor<0) cursor=n-1; if(cursor>=n) cursor=0; } for(int i=0;i0?1:-1); if(startProfileIdx<0) startProfileIdx=n-1; if(startProfileIdx>=n) startProfileIdx=0; } - if(startProfileIdx=topIndex+4) topIndex=startProfileIdx-3; + u8g2.clearBuffer(); drawHeader("Старт: профиль"); + int n=NUM_PROFILES; int r=readRot(); if(r){ startProfileIdx += (r>0?1:-1); if(startProfileIdx<0) startProfileIdx=n-1; if(startProfileIdx>=n) startProfileIdx=0; } + if(startProfileIdx=topIndex+4) topIndex=startProfileIdx-3; for(int row=0; row<4; row++){ int i=topIndex+row; if(i>=n) break; String line="["+String(i+1)+"] "; line+=chemToStr(profiles[i].chem); - line+=" S="+String(profiles[i].S); line+=" "+String(profiles[i].capacity_mAh)+"mAh"; + line+=" S="+String(profiles[i].S)+" "+String(profiles[i].capacity_mAh)+"mAh"; drawMenuItem(12+(row+1)*11, line, i==startProfileIdx); } - drawFooterHint("Нажми: правка | Держи: назад"); + drawFooter("Нажми: правка | Держи: назад"); u8g2.sendBuffer(); if(isShortClick()){ @@ -224,15 +258,15 @@ void screenStartProfile(){ void editNumberInt(uint16_t &val,uint16_t minV,uint16_t maxV,uint16_t step,const char* title){ u8g2.clearBuffer(); drawHeader(title); int r=readRot(); if(r){ long nv=(long)val + (r>0?step:-step); if(nvmaxV) nv=maxV; val=(uint16_t)nv; } - setFont(); u8g2.setCursor(2, 22+10); u8g2.print(val); u8g2.print(" "); - drawFooterHint("Нажми: ОК | Держи: отмена"); + u8g2.setFont(MENU_FONT); u8g2.setCursor(2, 22+10); u8g2.print(val); u8g2.print(" "); + drawFooter("Нажми: ОК | Держи: отмена"); u8g2.sendBuffer(); } void editNumberFloat(float &val,float minV,float maxV,float step,const char* title){ u8g2.clearBuffer(); drawHeader(title); int r=readRot(); if(r){ float nv=val+(r>0?step:-step); if(nvmaxV) nv=maxV; val=nv; } - setFont(); u8g2.setCursor(2, 22+10); u8g2.print(fmtFloat(val,(step<0.1f?2:1))); - drawFooterHint("Нажми: ОК | Держи: отмена"); + u8g2.setFont(MENU_FONT); u8g2.setCursor(2, 22+10); char b[16]; dtostrf(val,0,(step<0.1f?2:1),b); u8g2.print(b); + drawFooter("Нажми: ОК | Держи: отмена"); u8g2.sendBuffer(); } @@ -243,12 +277,12 @@ void screenStartQuickIterm(){ editNumberFloat(quickItermA,0.01f,5.0f,0.01f,"Iter void screenStartConfirm(){ u8g2.clearBuffer(); drawHeader("Подтверждение"); Profile &p=profiles[startProfileIdx]; - setFont(); - u8g2.setCursor(2,12+10); u8g2.print("Режим: "); u8g2.print(startMode==SM_CHARGE?"Заряд":(startMode==SM_DISCHARGE?"Разряд":"МиллиОм")); - u8g2.setCursor(2,22+10); u8g2.print("Профиль: "); u8g2.print(chemToStr(p.chem)); u8g2.print(" S="); u8g2.print(p.S); - u8g2.setCursor(2,32+10); u8g2.print("Cap="); u8g2.print(quickCap_mAh); u8g2.print("mAh C="); u8g2.print(fmtFloat(quickRateC,1)); - u8g2.setCursor(2,42+10); u8g2.print("Iterm="); u8g2.print(fmtFloat(quickItermA,2)); u8g2.print("A"); - drawFooterHint("Нажми: ПУСК | Держи: назад"); + u8g2.setFont(MENU_FONT); + u8g2.setCursor(2,22); u8g2.print("Режим: "); u8g2.print(startMode==SM_CHARGE?"Заряд":(startMode==SM_DISCHARGE?"Разряд":"МиллиОм")); + u8g2.setCursor(2,33); u8g2.print("Профиль: "); u8g2.print(chemToStr(p.chem)); u8g2.print(" S="); u8g2.print(p.S); + u8g2.setCursor(2,44); u8g2.print("Cap="); u8g2.print(quickCap_mAh); u8g2.print("mAh C="); u8g2.print(quickRateC,1); + u8g2.setCursor(2,55); u8g2.print("Iterm="); u8g2.print(quickItermA,2); u8g2.print("A"); + drawFooter("Нажми: ПУСК | Держи: назад"); u8g2.sendBuffer(); if(isShortClick()){ ui=UI_MESSAGE; } @@ -257,14 +291,13 @@ void screenStartConfirm(){ void screenMessage(){ u8g2.clearBuffer(); drawHeader("Сообщение"); - setFont(); - u8g2.setCursor(2,14+8); u8g2.print("Старт (демо)."); - u8g2.setCursor(2,24+8); u8g2.print("Дальше добавим"); - u8g2.setCursor(2,34+8); u8g2.print("алгоритм и логи."); - drawFooterHint("Нажми: в меню"); + u8g2.setFont(MENU_FONT); + u8g2.setCursor(2,26); u8g2.print("Старт (демо)."); + u8g2.setCursor(2,38); u8g2.print("Дальше: алгоритм/логи"); + drawFooter("Нажми: в меню"); u8g2.sendBuffer(); - if(isShortClick() || isDoubleClick() || isLongPressReleased()){ ui=UI_HOME; cursor=0; } + if(isShortClick() || isLongPressReleased()){ ui=UI_HOME; cursor=0; } } // Профили @@ -273,16 +306,16 @@ int profListCursor=0, editProfileIdx=0; EditField editField=EF_CHEM; void screenProfilesList(){ u8g2.clearBuffer(); drawHeader("Профили"); - int n=NUM_PROFILES; int r=readRot(); if(r){ profListCursor+=(r>0?1:-1); if(profListCursor<0) profListCursor=n-1; if(profListCursor>=n) profListCursor=0; } + int n=NUM_PROFILES; int r=readRot(); if(r){ profListCursor += (r>0?1:-1); if(profListCursor<0) profListCursor=n-1; if(profListCursor>=n) profListCursor=0; } if(profListCursor=topIndex+4) topIndex=profListCursor-3; for(int row=0; row<4; row++){ int i=topIndex+row; if(i>=n) break; String line="["+String(i+1)+"] "; line+=chemToStr(profiles[i].chem); - line+=" S="+String(profiles[i].S); line+=" "+String(profiles[i].capacity_mAh)+"mAh"; + line+=" S="+String(profiles[i].S)+" "+String(profiles[i].capacity_mAh)+"mAh"; drawMenuItem(12+(row+1)*11, line, i==profListCursor); } - drawFooterHint("Нажми: редакт. | Держи: назад"); + drawFooter("Нажми: редакт. | Держи: назад"); u8g2.sendBuffer(); if(isShortClick()){ editProfileIdx=profListCursor; editField=EF_CHEM; ui=UI_PROFILE_EDIT_FIELD; } @@ -294,7 +327,7 @@ void screenProfileEditField(){ const char* items[]={"Химия","S (банок)","Емкость (mAh)","C-rate","Iterm (A)","Сохранить","Отмена"}; int n=7; int r=readRot(); if(r){ int c=(int)editField + (r>0?1:-1); if(c<0) c=n-1; if(c>=n) c=0; editField=(EditField)c; } for(int i=0;i0?1:-1); if(v<0) v=2; if(v>2) v=0; c=(uint8_t)v; } - setFont(); u8g2.setCursor(2, 22+10); u8g2.print(chemToStr(c)); - drawFooterHint("Нажми: ОК | Держи: отмена"); + u8g2.setFont(MENU_FONT); u8g2.setCursor(2, 22+10); u8g2.print(chemToStr(c)); + drawFooter("Нажми: ОК | Держи: отмена"); u8g2.sendBuffer(); if(isShortClick()||isLongPressReleased()) ui=UI_PROFILE_EDIT_FIELD; } @@ -323,67 +356,70 @@ void screenEditChem(){ void screenEditS(){ u8g2.clearBuffer(); drawHeader("S (1..4)"); uint8_t &S=profiles[editProfileIdx].S; int r=readRot(); if(r){ int v=(int)S+(r>0?1:-1); if(v<1) v=4; if(v>4) v=1; S=(uint8_t)v; } - setFont(); u8g2.setCursor(2,22+10); u8g2.print("S="); u8g2.print(S); - drawFooterHint("Нажми: ОК | Держи: отмена"); + u8g2.setFont(MENU_FONT); u8g2.setCursor(2,22+10); u8g2.print("S="); u8g2.print(S); + drawFooter("Нажми: ОК | Держи: отмена"); u8g2.sendBuffer(); if(isShortClick()||isLongPressReleased()) ui=UI_PROFILE_EDIT_FIELD; } +void editNumberInt(uint16_t &val,uint16_t minV,uint16_t maxV,uint16_t step,const char* title); +void editNumberFloat(float &val,float minV,float maxV,float step,const char* title); void screenEditCap(){ editNumberInt(profiles[editProfileIdx].capacity_mAh,100,10000,100,"Емкость (mAh)"); if(isShortClick()||isLongPressReleased()) ui=UI_PROFILE_EDIT_FIELD; } void screenEditRateC(){ editNumberFloat(profiles[editProfileIdx].rateC,0.1f,1.0f,0.1f,"C-rate"); if(isShortClick()||isLongPressReleased()) ui=UI_PROFILE_EDIT_FIELD; } void screenEditIterm(){ editNumberFloat(profiles[editProfileIdx].ItermA,0.01f,5.0f,0.01f,"Iterm (A)"); if(isShortClick()||isLongPressReleased()) ui=UI_PROFILE_EDIT_FIELD; } -// -------- SETTINGS & SERVICE (заглушки) -------- +// Заглушки void screenSettings(){ u8g2.clearBuffer(); drawHeader("Параметры"); - setFont(); - u8g2.setCursor(2,14+8); u8g2.print("Позже: лог/экран/сеть"); - drawFooterHint("Держи: назад"); + u8g2.setFont(MENU_FONT); + u8g2.setCursor(2,26); u8g2.print("Позже: лог/экран/сеть"); + drawFooter("Держи: назад"); u8g2.sendBuffer(); if(isShortClick()||isLongPressReleased()) ui=UI_HOME; } void screenService(){ u8g2.clearBuffer(); drawHeader("Сервис"); - setFont(); - u8g2.setCursor(2,14+8); u8g2.print("Позже: тесты/калибровка"); - drawFooterHint("Держи: назад"); + u8g2.setFont(MENU_FONT); + u8g2.setCursor(2,26); u8g2.print("Позже: тесты/калибровка"); + drawFooter("Держи: назад"); u8g2.sendBuffer(); if(isShortClick()||isLongPressReleased()) ui=UI_HOME; } -// -------- SETUP/LOOP -------- +// ------------ SETUP/LOOP ------------ void setup(){ Serial.begin(115200); Wire.begin(21,22); Wire.setClock(100000); u8g2.begin(); - u8g2.enableUTF8Print(); // <<< ключевое: печатать UTF-8 (русский) - u8g2.setI2CAddress(0x3C<<1); // 0x3C из сканера + u8g2.enableUTF8Print(); + u8g2.setI2CAddress(0x3C << 1); u8g2.setBusClock(100000); pinMode(ENC_A_PIN, INPUT_PULLUP); pinMode(ENC_B_PIN, INPUT_PULLUP); pinMode(ENC_BTN_PIN, INPUT_PULLUP); - encBtn.attach(ENC_BTN_PIN, INPUT_PULLUP); - encBtn.interval(5); + + // Инициализируем lastAB текущим состояние контактов, чтобы избежать «скачка» при старте + lastAB = ((uint8_t)digitalRead(ENC_A_PIN) << 1) | (uint8_t)digitalRead(ENC_B_PIN); loadProfiles(); - u8g2.clearBuffer(); - drawHeader("SmartCharge UI"); - setFont(); + // Экран приветствия + подсказка по пинам + u8g2.clearBuffer(); drawHeader("SmartCharge UI"); + u8g2.setFont(MENU_FONT); u8g2.setCursor(2,20); u8g2.print("Меню готово."); - u8g2.setCursor(2,30); u8g2.print("A=32 B=33 BTN=26"); - u8g2.setCursor(2,40); u8g2.print("Крутить/Жать/Держать"); + u8g2.setCursor(2,32); u8g2.print("Encoder A=32 B=33 BTN=26"); + u8g2.setCursor(2,44); u8g2.print("COM/BTN GND, pullups вкл."); u8g2.sendBuffer(); delay(700); - - ui=UI_HOME; cursor=0; topIndex=0; } void loop(){ - pollInputs(); + // Регулярно опрашиваем энкодер и кнопку + encoderUpdate(); + buttonUpdate(); switch(ui){ case UI_HOME: screenHome(); break; @@ -405,5 +441,6 @@ void loop(){ case UI_MESSAGE: screenMessage(); break; } - delay(6); + // Без лишних задержек, чтобы не пропускать тики + delay(1); }