Добавить firmware/encoder.ino
This commit is contained in:
parent
3a54580d7b
commit
da37904f71
1 changed files with 119 additions and 0 deletions
119
firmware/encoder.ino
Normal file
119
firmware/encoder.ino
Normal file
|
|
@ -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 <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(); }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue