119 lines
4.5 KiB
C++
119 lines
4.5 KiB
C++
// ===== 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 <Wire.h>
|
||
#include <U8g2lib.h>
|
||
#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(); }
|
||
}
|