smart_chargeESP32/firmware/encoder.ino

119 lines
4.5 KiB
C++
Raw Permalink 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.

// ===== 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(); }
}