From da37904f712f6feeffb939a3361e6948815dbe76 Mon Sep 17 00:00:00 2001 From: elios Date: Fri, 22 Aug 2025 17:10:00 +0000 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20firmware/encoder.ino?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- firmware/encoder.ino | 119 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 firmware/encoder.ino diff --git a/firmware/encoder.ino b/firmware/encoder.ino new file mode 100644 index 0000000..1a50ba2 --- /dev/null +++ b/firmware/encoder.ino @@ -0,0 +1,119 @@ +// ===== ESP32 + PCNT (rise-only) стабильный медленный поворот + LongPress ===== +// Модуль энкодера: "GND S1 S2 Key 5V" +// Подключение: S1->GPIO32 (A / pulse), S2->GPIO33 (B / dir), Key->GPIO26, 5V->3V3, GND->GND + +#include +#include +#include "driver/pcnt.h" + +// ---- ВЫБОР ДИСПЛЕЯ ---- +// U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE); // если SSD1306 +U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE); // SH1106 по умолчанию + +// ---- Пины энкодера ---- +#define ENC_A 32 // S1 +#define ENC_B 33 // S2 +#define ENC_SW 26 // кнопка + +// Направление (если «вверх/вниз» наоборот — поменяй на -1) +#define ENC_DIR (+1) + +// Сколько событий PCNT = 1 «щёлчок». +// Т.к. считаем ТОЛЬКО фронты A, для большинства модулей достаточно 1. +#define DETENT_DIV 1 + +// Аппаратный фильтр PCNT (микросекунды). Реально ограничивается ≈12.8 мкс. +#define FILTER_US 8 // не глушим реальные импульсы + +// ---- Кнопка ---- +bool btnPressed=false, btnShort=false, btnLongRel=false; +uint32_t btnDownAt=0; +const uint32_t DB_MS=15, 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 < DB_MS) return; + if (!raw && !btnPressed){ btnPressed=true; btnShort=false; btnLongRel=false; btnDownAt=now; } + else if (raw && btnPressed){ btnPressed=false; if(now-btnDownAt>=LONG_MS) btnLongRel=true; else btnShort=true; } +} + +// ---- PCNT (только фронты A; направление берём с B) ---- +void pcntInit() { + pcnt_config_t cfg = {}; + cfg.pulse_gpio_num = (gpio_num_t)ENC_A; // считаем импульсы по A + cfg.ctrl_gpio_num = (gpio_num_t)ENC_B; // направление по уровню B + cfg.unit = PCNT_UNIT_0; + cfg.channel = PCNT_CHANNEL_0; + + // Считаем ТОЛЬКО фронты (rise). Спады игнорируем — это даёт стабильность. + cfg.pos_mode = PCNT_COUNT_INC; // фронт A -> +1 + cfg.neg_mode = PCNT_COUNT_DIS; // спад A -> игнор + + // B=LOW -> реверс, B=HIGH -> keep (так квадратура даёт правильное направление) + cfg.lctrl_mode = PCNT_MODE_REVERSE; + cfg.hctrl_mode = PCNT_MODE_KEEP; + + pcnt_unit_config(&cfg); + + // Аппаратный фильтр (0..1023 тиков APB @ 80 МГц ≈ до 12.8 мкс) + uint16_t filt_ticks = (uint16_t)min(1023, (int)(FILTER_US * 80)); + pcnt_set_filter_value(PCNT_UNIT_0, filt_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 != 0) pcnt_counter_clear(PCNT_UNIT_0); + + accum += (long)cnt * ENC_DIR; + + long steps = accum / DETENT_DIV; // DETENT_DIV=1 -> каждый фронт = 1 шаг меню + if (steps != 0) accum -= steps * DETENT_DIV; + return steps; +} + +long value = 0; +uint32_t uiT=0; + +void draw(const char* msg=nullptr){ + u8g2.clearBuffer(); + u8g2.setFont(u8g2_font_6x12_t_cyrillic); + u8g2.setCursor(0,12); u8g2.print("PCNT rise-only (slow OK)"); + u8g2.setCursor(0,28); u8g2.print("Значение: "); u8g2.print(value); + u8g2.setCursor(0,42); u8g2.print("DETENT_DIV="); u8g2.print(DETENT_DIV); + if (msg){ u8g2.setCursor(0,58); u8g2.print(msg); } + u8g2.sendBuffer(); +} + +void setup(){ + Wire.begin(21,22); + u8g2.begin(); u8g2.enableUTF8Print(); u8g2.setI2CAddress(0x3C<<1); + + pinMode(ENC_A, INPUT_PULLUP); + pinMode(ENC_B, INPUT_PULLUP); + pinMode(ENC_SW, INPUT_PULLUP); + + pcntInit(); + draw("Крутите ручку"); +} + +void loop(){ + long d = readDetents(); + if (d) { value += d; draw(); } + + buttonUpdate(); + if (btnShort) { draw("Кнопка: короткое"); btnShort=false; } + if (btnLongRel) { draw("Кнопка: длительное"); btnLongRel=false; } + + if (millis()-uiT>200 && d==0 && !btnShort && !btnLongRel){ uiT=millis(); draw(); } +}