smart_chargeESP32/firmware/test-menu.ino

523 lines
21 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
SmartCharge UI (меню обновлённое) + идеальный энкодер на PCNT + МиллиОм (1S)
Изменения:
- Подсказки снизу только на главном экране.
- Старт→Профиль: "Быстрый старт" или "Изменить параметры".
- МиллиОм (1S): без профилей, один клик = измерение Voc/нагрузка (INA219 + FR120N).
Пины:
I2C OLED: SDA=21, SCL=22, addr 0x3C
Encoder: S1=GPIO32, S2=GPIO33, Key=GPIO26 (модуль "GND S1 S2 Key 5V" -> 3V3)
MOSFET: GPIO25 (FR120N модуль вход "PWM/EN")
DS18B20: GPIO16 (опционально)
*/
#define USE_SH1106 1 // 1=SH1106, 0=SSD1306
#include <Wire.h>
#include <Preferences.h>
#include <U8g2lib.h>
#include "driver/pcnt.h"
#include <Adafruit_INA219.h>
#include <OneWire.h>
#include <DallasTemperature.h>
// ---------- OLED ----------
#if USE_SH1106
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
#else
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
#endif
#define MENU_FONT u8g2_font_6x12_t_cyrillic
// ---------- Пины ----------
#define ENC_A 32 // S1 (A/pulse)
#define ENC_B 33 // S2 (B/dir)
#define ENC_SW 26 // кнопка
#define MOSFET_PIN 25
#define ONEWIRE_PIN 16
// ---------- Энкодер/PCNT ----------
#define ENC_DIR (+1)
#define DETENT_DIV 1 // 1 фронт = 1 шаг (самая отзывчивая)
#define FILTER_US 8 // аппаратный антидребезг PCNT (~макс 12.8 мкс)
// Кнопка
bool btnPressed=false, btnShort=false, btnLongRel=false;
uint32_t btnDownAt=0; const uint32_t BTN_DB_MS=15, BTN_LONG_MS=700;
void buttonUpdate(){
static bool lastRaw=true; static uint32_t lastT=0;
bool raw = digitalRead(ENC_SW); // PULLUP: 1=отпущена, 0=нажата
uint32_t now=millis();
if (raw!=lastRaw){ lastRaw=raw; lastT=now; }
if (now-lastT < BTN_DB_MS) return;
if (!raw && !btnPressed){ btnPressed=true; btnShort=false; btnLongRel=false; btnDownAt=now; }
else if (raw && btnPressed){ btnPressed=false; if(now-btnDownAt>=BTN_LONG_MS) btnLongRel=true; else btnShort=true; }
}
inline bool isClick(){ if(btnShort){btnShort=false; return true;} return false; }
inline bool isHold(){ if(btnLongRel){btnLongRel=false; return true;} return false; }
void pcntInit(){
pcnt_config_t cfg = {};
cfg.pulse_gpio_num = (gpio_num_t)ENC_A;
cfg.ctrl_gpio_num = (gpio_num_t)ENC_B;
cfg.unit = PCNT_UNIT_0;
cfg.channel = PCNT_CHANNEL_0;
cfg.pos_mode = PCNT_COUNT_INC; // фронт A -> +1
cfg.neg_mode = PCNT_COUNT_DIS; // спад игнорируем
cfg.lctrl_mode = PCNT_MODE_REVERSE; // B=LOW -> инверсия
cfg.hctrl_mode = PCNT_MODE_KEEP; // B=HIGH -> keep
pcnt_unit_config(&cfg);
uint16_t ticks = (uint16_t)min(1023, (int)(FILTER_US * 80));
pcnt_set_filter_value(PCNT_UNIT_0, ticks);
pcnt_filter_enable(PCNT_UNIT_0);
pcnt_counter_pause(PCNT_UNIT_0);
pcnt_counter_clear(PCNT_UNIT_0);
pcnt_counter_resume(PCNT_UNIT_0);
}
long readDetents(){
static long accum = 0;
int16_t cnt=0;
pcnt_get_counter_value(PCNT_UNIT_0, &cnt);
if (cnt) pcnt_counter_clear(PCNT_UNIT_0);
accum += (long)cnt * ENC_DIR;
long steps = accum / DETENT_DIV;
if (steps) accum -= steps * DETENT_DIV;
return steps;
}
int readRot(){
long d = readDetents();
if (!d) return 0;
if (d > 3) d = 3; if (d < -3) d = -3;
return (int)d;
}
// ---------- Сенсоры ----------
Adafruit_INA219 ina219;
OneWire oneWire(ONEWIRE_PIN);
DallasTemperature dallas(&oneWire);
// ---------- Профили/меню ----------
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"; }
return "Li-ion";
}
struct Profile {
uint8_t chem; // CHEM_*
uint8_t S; // 1..4
uint16_t capacity_mAh; // 100..10000
float rateC; // 0.1..1.0
float ItermA; // A
};
Preferences prefs;
const int NUM_PROFILES=5;
Profile profiles[NUM_PROFILES];
int lastProfileIndex=0;
enum UiState {
UI_HOME=0,
UI_START_MODE, UI_START_PROFILE, UI_START_ACTION,
UI_START_QUICK_CAP, UI_START_QUICK_RATEC, UI_START_QUICK_ITERM, UI_START_CONFIRM,
UI_RINT_READY,
UI_PROFILES_LIST, UI_PROFILE_EDIT_FIELD,
UI_EDIT_CHEM, UI_EDIT_S, UI_EDIT_CAP, UI_EDIT_RATEC, UI_EDIT_ITERM,
UI_SETTINGS, UI_SERVICE, UI_MESSAGE
};
UiState ui=UI_HOME;
int cursor=0, topIndex=0;
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;
// МиллиОм параметры (простые дефолты)
uint16_t rintPulseMs = 250;
uint8_t rintSamples = 12;
float rLeads_mOhm = 0.0f;
bool rintHasResult=false;
float rint_Voc=0, rint_Vload=0, rint_I=0, rint_RmOhm=0, rint_Tc= NAN;
// ---------- отрисовка ----------
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 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); }
}
// ---------- profiles load/save ----------
void loadProfiles(){
prefs.begin("sc", true);
for(int i=0;i<NUM_PROFILES;i++){
String k = String("p")+i+"_";
profiles[i].chem = prefs.getUChar((k+"chem").c_str(), i==0?CHEM_LIION:CHEM_LFP);
profiles[i].S = prefs.getUChar((k+"S").c_str(), (i==1)?3:1);
profiles[i].capacity_mAh = prefs.getUShort((k+"cap").c_str(), (i==0)?2500:1000);
profiles[i].rateC = prefs.getFloat((k+"rate").c_str(), 0.5f);
float itdef = (profiles[i].chem==CHEM_LIION?0.05f:0.02f) * profiles[i].capacity_mAh/1000.0f;
profiles[i].ItermA = prefs.getFloat((k+"iterm").c_str(), itdef);
}
lastProfileIndex = prefs.getUChar("lastIdx", 0);
prefs.end();
}
void saveProfile(int i){
prefs.begin("sc", false);
String k = String("p")+i+"_";
prefs.putUChar((k+"chem").c_str(), profiles[i].chem);
prefs.putUChar((k+"S").c_str(), profiles[i].S);
prefs.putUShort((k+"cap").c_str(), profiles[i].capacity_mAh);
prefs.putFloat((k+"rate").c_str(), profiles[i].rateC);
prefs.putFloat((k+"iterm").c_str(),profiles[i].ItermA);
prefs.end();
}
void saveLastIndex(int idx){ prefs.begin("sc", false); prefs.putUChar("lastIdx", idx); prefs.end(); }
// ---------- screens ----------
void screenHome(){
u8g2.clearBuffer(); drawHeader("Главное меню");
const char* items[]={"Старт","Профили","Параметры","Сервис"}; int n=4;
int r=readRot(); if(r){ cursor += r; if(cursor<0) cursor=n-1; if(cursor>=n) cursor=0; }
for(int i=0;i<n;i++) drawMenuItem(12+(i+1)*11, items[i], i==cursor);
// Подсказка снизу ТОЛЬКО здесь:
setFont(); u8g2.setCursor(0,62); u8g2.print("Крутить: выбор Клик: вход Держать: назад");
u8g2.sendBuffer();
if(isClick()){
if(cursor==0){ ui=UI_START_MODE; cursor=0; }
else if(cursor==1){ ui=UI_PROFILES_LIST; cursor=0; topIndex=0; }
else if(cursor==2){ ui=UI_SETTINGS; }
else { ui=UI_SERVICE; }
}
}
// --- Старт ---
void screenStartMode(){
u8g2.clearBuffer(); drawHeader("Старт: режим");
const char* items[]={"Заряд","Разряд (1S)","МиллиОм (1S)"}; int n=3;
int r=readRot(); if(r){ cursor += r; if(cursor<0) cursor=n-1; if(cursor>=n) cursor=0; }
for(int i=0;i<n;i++) drawMenuItem(12+(i+1)*11, items[i], i==cursor);
u8g2.sendBuffer();
if(isClick()){
startMode=(StartMode)cursor;
if (startMode==SM_RINT) { ui=UI_RINT_READY; return; } // миллиОм — без профилей
ui=UI_START_PROFILE; startProfileIdx=lastProfileIndex;
}
if(isHold()){ ui=UI_HOME; cursor=0; }
}
void screenStartProfile(){
u8g2.clearBuffer(); drawHeader("Старт: профиль");
int n=NUM_PROFILES; int r=readRot(); if(r){ startProfileIdx += r; if(startProfileIdx<0) startProfileIdx=n-1; if(startProfileIdx>=n) startProfileIdx=0; }
if(startProfileIdx<topIndex) topIndex=startProfileIdx;
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)+" "+String(profiles[i].capacity_mAh)+"mAh";
drawMenuItem(12+(row+1)*11, line, i==startProfileIdx);
}
// Ни подсказок
u8g2.sendBuffer();
if(isClick()){
// после выбора профиля — экран действия: быстрый старт или правка параметров
ui=UI_START_ACTION; saveLastIndex(startProfileIdx);
}
if(isHold()){ ui=UI_START_MODE; cursor=0; }
}
void screenStartAction(){
u8g2.clearBuffer(); drawHeader("Действие");
const char* items[]={"Быстрый старт","Изменить параметры"}; int n=2;
int r=readRot(); if(r){ cursor += r; if(cursor<0) cursor=n-1; if(cursor>=n) cursor=0; }
for(int i=0;i<n;i++) drawMenuItem(12+(i+1)*11, items[i], i==cursor);
u8g2.sendBuffer();
if(isClick()){
if(cursor==0){
// Быстрый старт: сразу в "сообщение" (заглушка алгоритма)
ui=UI_MESSAGE;
}else{
// Правка параметров перед стартом:
Profile &p=profiles[startProfileIdx];
quickCap_mAh=p.capacity_mAh; quickRateC=p.rateC;
quickItermA=(p.ItermA>0.001f)?p.ItermA:((p.chem==CHEM_LIION?0.05f:0.02f)*p.capacity_mAh/1000.0f);
ui=UI_START_QUICK_CAP;
}
}
if(isHold()){ ui=UI_START_PROFILE; }
}
// Быстрые параметры
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(nv<minV) nv=minV; if(nv>maxV) nv=maxV; val=(uint16_t)nv; }
u8g2.setCursor(2, 22+10); u8g2.print(val); u8g2.print(" ");
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(nv<minV) nv=minV; if(nv>maxV) nv=maxV; val=nv; }
char b[16]; dtostrf(val,0,(step<0.1f?2:1),b);
u8g2.setCursor(2, 22+10); u8g2.print(b);
u8g2.sendBuffer();
}
void screenStartQuickCap(){ editNumberInt(quickCap_mAh,100,10000,100,"Емкость (mAh)"); if(isClick()){ ui=UI_START_QUICK_RATEC; } if(isHold()){ ui=UI_START_ACTION; } }
void screenStartQuickRateC(){ editNumberFloat(quickRateC,0.1f,1.0f,0.1f,"C-rate"); if(isClick()){ ui=UI_START_QUICK_ITERM; } if(isHold()){ ui=UI_START_ACTION; } }
void screenStartQuickIterm(){ editNumberFloat(quickItermA,0.01f,5.0f,0.01f,"Iterm (A)"); if(isClick()){ ui=UI_START_CONFIRM; } if(isHold()){ ui=UI_START_ACTION; } }
void screenStartConfirm(){
u8g2.clearBuffer(); drawHeader("Подтверждение");
Profile &p=profiles[startProfileIdx];
u8g2.setCursor(2,22); u8g2.print("Режим: "); u8g2.print(startMode==SM_CHARGE?"Заряд":"Разряд");
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");
u8g2.sendBuffer();
if(isClick()){ ui=UI_MESSAGE; } // сюда позже прикрутим реальный старт
if(isHold()){ ui=UI_START_ACTION; }
}
// --- МиллиОм (1S) ---
float avgMany(std::function<float()> f, uint8_t n){
double s=0; for(uint8_t i=0;i<n;i++){ s += f(); delay(2); } return (float)(s/n);
}
void runRintOnce(){
rintHasResult=false;
// 0) проверка наличия батареи
float Voc = avgMany([](){ return ina219.getBusVoltage_V(); }, rintSamples);
if (Voc < 2.5f){ // универсальный безопасный порог
rint_Voc=Voc; rint_Vload=Voc; rint_I=0; rint_RmOhm=0; rint_Tc=NAN; rintHasResult=true;
return;
}
// 1) импульс нагрузки
digitalWrite(MOSFET_PIN, HIGH);
delay(40); // спад фронта
float Vload = avgMany([](){ return ina219.getBusVoltage_V(); }, rintSamples);
float Iload = avgMany([](){ return ina219.getCurrent_mA()/1000.0f; }, rintSamples);
delay(rintPulseMs-40);
digitalWrite(MOSFET_PIN, LOW);
// 2) температура (если датчик есть)
dallas.requestTemperatures();
float Tc = dallas.getTempCByIndex(0);
if (Tc < -100 || Tc > 150) Tc = NAN;
// 3) расчёт
float dV = Voc - Vload;
float R = (Iload > 0.01f) ? (dV / Iload) : 0.0f; // Ом
float Rm = R*1000.0f - rLeads_mOhm; if (Rm < 0) Rm = 0;
rint_Voc=Voc; rint_Vload=Vload; rint_I=Iload; rint_RmOhm=Rm; rint_Tc=Tc; rintHasResult=true;
}
void screenRintReady(){
u8g2.clearBuffer(); drawHeader("МиллиОм (1S)");
u8g2.setCursor(2,22); u8g2.print("Клик: измерить");
u8g2.setCursor(2,33); u8g2.print("Pulse="); u8g2.print(rintPulseMs); u8g2.print("ms N="); u8g2.print(rintSamples);
u8g2.setCursor(2,44); u8g2.print("R_leads="); u8g2.print(rLeads_mOhm,1); u8g2.print(" mОм");
if (rintHasResult){
u8g2.setCursor(2,55);
if (rint_I < 0.2f) { u8g2.print("I низкий, проверь контакты"); }
else { /* ничего */ }
}
u8g2.sendBuffer();
if(isClick()){
runRintOnce();
// сразу показать результат:
u8g2.clearBuffer(); drawHeader("Rвнутр (итог)");
u8g2.setCursor(2,22); u8g2.print("Rint=");
u8g2.print(rint_RmOhm,1); u8g2.print(" мОм");
u8g2.setCursor(2,33); u8g2.print("Voc="); u8g2.print(rint_Voc,3); u8g2.print("В");
u8g2.setCursor(2,44); u8g2.print("Vld="); u8g2.print(rint_Vload,3); u8g2.print("В I=");
u8g2.print(rint_I,3); u8g2.print("А");
if (!isnan(rint_Tc)){ u8g2.setCursor(2,55); u8g2.print("T="); u8g2.print(rint_Tc,1); u8g2.print("C"); }
u8g2.sendBuffer();
delay(900); // чуть задержим, чтоб было видно результат
}
if(isHold()){ ui=UI_START_MODE; }
}
// --- Профили ---
int profListCursor=0, editProfileIdx=0;
enum EditField { EF_CHEM=0, EF_S, EF_CAP, EF_RATEC, EF_ITERM, EF_SAVE, EF_CANCEL };
EditField editField=EF_CHEM;
void screenProfilesList(){
u8g2.clearBuffer(); drawHeader("Профили");
int n=NUM_PROFILES; int r=readRot(); if(r){ profListCursor += r; if(profListCursor<0) profListCursor=n-1; if(profListCursor>=n) profListCursor=0; }
if(profListCursor<topIndex) topIndex=profListCursor; 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)+" "+String(profiles[i].capacity_mAh)+"mAh";
drawMenuItem(12+(row+1)*11, line, i==profListCursor);
}
u8g2.sendBuffer();
if(isClick()){ editProfileIdx=profListCursor; editField=EF_CHEM; ui=UI_PROFILE_EDIT_FIELD; }
if(isHold()){ ui=UI_HOME; cursor=0; }
}
void screenProfileEditField(){
u8g2.clearBuffer(); drawHeader("Редакт.профиля");
const char* items[]={"Химия","S (банок)","Емкость (mAh)","C-rate","Iterm (A)","Сохранить","Отмена"}; int n=7;
int r=readRot(); if(r){ int c=(int)editField + r; if(c<0) c=n-1; if(c>=n) c=0; editField=(EditField)c; }
for(int i=0;i<n;i++) drawMenuItem(12+(i+1)*11, items[i], i==(int)editField);
u8g2.sendBuffer();
if(isClick()){
switch(editField){
case EF_CHEM: ui=UI_EDIT_CHEM; break;
case EF_S: ui=UI_EDIT_S; break;
case EF_CAP: ui=UI_EDIT_CAP; break;
case EF_RATEC: ui=UI_EDIT_RATEC; break;
case EF_ITERM: ui=UI_EDIT_ITERM; break;
case EF_SAVE: saveProfile(editProfileIdx); ui=UI_PROFILES_LIST; break;
case EF_CANCEL:ui=UI_PROFILES_LIST; break;
}
}
if(isHold()){ ui=UI_PROFILES_LIST; }
}
void screenEditChem(){
u8g2.clearBuffer(); drawHeader("Химия");
uint8_t &c=profiles[editProfileIdx].chem; int r=readRot(); if(r){ int v=(int)c+r; if(v<0) v=2; if(v>2) v=0; c=(uint8_t)v; }
u8g2.setCursor(2, 22+10); u8g2.print(chemToStr(c));
u8g2.sendBuffer();
if(isClick()||isHold()) { ui=UI_PROFILE_EDIT_FIELD; }
}
void screenEditS(){
u8g2.clearBuffer(); drawHeader("S (1..4)");
uint8_t &S=profiles[editProfileIdx].S; int r=readRot(); if(r){ int v=(int)S+r; if(v<1) v=4; if(v>4) v=1; S=(uint8_t)v; }
u8g2.setCursor(2,22+10); u8g2.print("S="); u8g2.print(S);
u8g2.sendBuffer();
if(isClick()||isHold()) { ui=UI_PROFILE_EDIT_FIELD; }
}
void screenEditCap(){
u8g2.clearBuffer(); drawHeader("Емкость (mAh)");
int r=readRot(); if(r){ long nv=(long)profiles[editProfileIdx].capacity_mAh + (r>0?100:-100);
if(nv<100) nv=100; if(nv>10000) nv=10000; profiles[editProfileIdx].capacity_mAh=(uint16_t)nv; }
u8g2.setCursor(2,22+10); u8g2.print(profiles[editProfileIdx].capacity_mAh);
u8g2.sendBuffer();
if(isClick()||isHold()) { ui=UI_PROFILE_EDIT_FIELD; }
}
void screenEditRateC(){
u8g2.clearBuffer(); drawHeader("C-rate");
int r=readRot(); if(r){ float nv=profiles[editProfileIdx].rateC+(r>0?0.1f:-0.1f);
if(nv<0.1f) nv=0.1f; if(nv>1.0f) nv=1.0f; profiles[editProfileIdx].rateC=nv; }
u8g2.setCursor(2,22+10); u8g2.print(profiles[editProfileIdx].rateC,1);
u8g2.sendBuffer();
if(isClick()||isHold()) { ui=UI_PROFILE_EDIT_FIELD; }
}
void screenEditIterm(){
u8g2.clearBuffer(); drawHeader("Iterm (A)");
int r=readRot(); if(r){ float nv=profiles[editProfileIdx].ItermA+(r>0?0.01f:-0.01f);
if(nv<0.01f) nv=0.01f; if(nv>5.0f) nv=5.0f; profiles[editProfileIdx].ItermA=nv; }
u8g2.setCursor(2,22+10); u8g2.print(profiles[editProfileIdx].ItermA,2);
u8g2.sendBuffer();
if(isClick()||isHold()) { ui=UI_PROFILE_EDIT_FIELD; }
}
// --- Настройки/Сервис (заглушки) ---
void screenSettings(){
u8g2.clearBuffer(); drawHeader("Параметры");
u8g2.setCursor(2,26); u8g2.print("Позже: сеть/лог/экран");
u8g2.sendBuffer();
if(isClick()||isHold()) { ui=UI_HOME; cursor=0; }
}
void screenService(){
u8g2.clearBuffer(); drawHeader("Сервис");
u8g2.setCursor(2,26); u8g2.print("Позже: тесты/калибр.");
u8g2.sendBuffer();
if(isClick()||isHold()) { ui=UI_HOME; cursor=0; }
}
// --- Сообщение (заглушка запуска charge/discharge) ---
void screenMessage(){
u8g2.clearBuffer(); drawHeader("Сообщение");
u8g2.setCursor(2,26);
if (startMode==SM_CHARGE) u8g2.print("Старт заряда (демо)");
else u8g2.print("Старт разряда (демо)");
u8g2.setCursor(2,38); u8g2.print("Дальше прикрутим логику");
u8g2.sendBuffer();
if(isClick()||isHold()){ ui=UI_HOME; cursor=0; }
}
// ---------- setup / loop ----------
void setup(){
Serial.begin(115200);
Wire.begin(21,22);
Wire.setClock(100000);
u8g2.begin();
u8g2.enableUTF8Print();
u8g2.setI2CAddress(0x3C<<1);
u8g2.setBusClock(100000);
pinMode(ENC_A, INPUT_PULLUP);
pinMode(ENC_B, INPUT_PULLUP);
pinMode(ENC_SW, INPUT_PULLUP);
pcntInit();
pinMode(MOSFET_PIN, OUTPUT);
digitalWrite(MOSFET_PIN, LOW);
if (!ina219.begin()){
Serial.println("INA219 not found!");
}
dallas.begin();
loadProfiles();
u8g2.clearBuffer(); drawHeader("SmartCharge UI");
u8g2.setCursor(2,20); u8g2.print("OLED ок. PCNT ок.");
u8g2.setCursor(2,32); u8g2.print("S1=32 S2=33 Key=26");
u8g2.setCursor(2,44); u8g2.print("Клик для входа");
u8g2.sendBuffer();
delay(600);
}
void loop(){
buttonUpdate();
switch(ui){
case UI_HOME: screenHome(); break;
case UI_START_MODE: screenStartMode(); break;
case UI_START_PROFILE: screenStartProfile(); break;
case UI_START_ACTION: screenStartAction(); break;
case UI_START_QUICK_CAP: screenStartQuickCap(); break;
case UI_START_QUICK_RATEC: screenStartQuickRateC(); break;
case UI_START_QUICK_ITERM: screenStartQuickIterm(); break;
case UI_START_CONFIRM: screenStartConfirm(); break;
case UI_RINT_READY: screenRintReady(); break;
case UI_PROFILES_LIST: screenProfilesList(); break;
case UI_PROFILE_EDIT_FIELD: screenProfileEditField(); break;
case UI_EDIT_CHEM: screenEditChem(); break;
case UI_EDIT_S: screenEditS(); break;
case UI_EDIT_CAP: screenEditCap(); break;
case UI_EDIT_RATEC: screenEditRateC(); break;
case UI_EDIT_ITERM: screenEditIterm(); break;
case UI_SETTINGS: screenSettings(); break;
case UI_SERVICE: screenService(); break;
case UI_MESSAGE: screenMessage(); break;
}
}