Добавить 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