forked from markqvist/RNode_Firmware
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathEncoder.h
More file actions
129 lines (112 loc) · 5.2 KB
/
Copy pathEncoder.h
File metadata and controls
129 lines (112 loc) · 5.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Copyright (C) 2024, Mark Qvist
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#ifndef ENCODER_H
#define ENCODER_H
// Common EC11 case: one detent = four quadrature counts. If a physical
// click moves the menu cursor by two positions instead of one on real
// hardware, this part detents every half-cycle instead - drop to 2.
#define ENCODER_STEPS_PER_DETENT 4
#define ENC_PRESSED LOW
#define ENC_RELEASED HIGH
// Forward declarations - implemented in Menu.h. wrap defaults false here,
// so the physical encoder always clamps at list ends - only the main
// button's cycling (menu_button_press()/menu_button_process()) passes
// wrap=true.
void menu_encoder_rotate(int8_t dir, bool wrap = false);
void menu_encoder_button(unsigned long duration);
// Classic 4-state Gray-code quadrature transition table, indexed by
// (prev_AB<<2)|curr_AB. Illegal transitions (both channels changed
// between reads - noise the board's RC filtering didn't fully catch)
// yield 0 and are silently discarded.
static const int8_t ENCODER_TABLE[16] = {
0, -1, 1, 0,
1, 0, 0, -1,
-1, 0, 0, 1,
0, 1, -1, 0
};
volatile uint8_t encoder_prev_ab = 0;
volatile int8_t encoder_quarter = 0;
volatile int8_t encoder_delta = 0;
// ESP-IDF's spinlock (portMUX_TYPE) has no nRF52 equivalent - a plain
// global interrupt gate is all that's needed here anyway, since the
// critical sections just protect a couple of int8_t's from a single-core
// MCU's own ISR.
#if MCU_VARIANT == MCU_ESP32
portMUX_TYPE encoder_mux = portMUX_INITIALIZER_UNLOCKED;
#define ENCODER_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&encoder_mux)
#define ENCODER_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&encoder_mux)
#define ENCODER_ENTER_CRITICAL() portENTER_CRITICAL(&encoder_mux)
#define ENCODER_EXIT_CRITICAL() portEXIT_CRITICAL(&encoder_mux)
#else
#define ENCODER_ENTER_CRITICAL_ISR() noInterrupts()
#define ENCODER_EXIT_CRITICAL_ISR() interrupts()
#define ENCODER_ENTER_CRITICAL() noInterrupts()
#define ENCODER_EXIT_CRITICAL() interrupts()
#endif
void ISR_VECT encoder_isr() {
uint8_t curr_ab = (digitalRead(pin_encoder_up) << 1) | digitalRead(pin_encoder_down);
int8_t step = ENCODER_TABLE[(encoder_prev_ab << 2) | curr_ab];
encoder_prev_ab = curr_ab;
if (step != 0) {
ENCODER_ENTER_CRITICAL_ISR();
encoder_quarter += step;
if (encoder_quarter >= ENCODER_STEPS_PER_DETENT) { encoder_delta = 1; encoder_quarter = 0; }
if (encoder_quarter <= -ENCODER_STEPS_PER_DETENT) { encoder_delta = -1; encoder_quarter = 0; }
ENCODER_EXIT_CRITICAL_ISR();
}
}
// Encoder push-button debounce state. Self-contained (own PRESSED/
// RELEASED constants) rather than reusing Input.h's, since this is a
// separate physical pin with its own state machine.
int enc_btn_state = ENC_RELEASED;
int enc_btn_debounce_state = enc_btn_state;
unsigned long enc_btn_debounce_last = 0;
const unsigned long ENC_BTN_DEBOUNCE_DELAY = 25;
unsigned long enc_btn_down_last = 0;
void encoder_init() {
pinMode(pin_encoder_up, INPUT_PULLUP);
pinMode(pin_encoder_down, INPUT_PULLUP);
pinMode(pin_encoder_press, INPUT_PULLUP);
encoder_prev_ab = (digitalRead(pin_encoder_up) << 1) | digitalRead(pin_encoder_down);
attachInterrupt(digitalPinToInterrupt(pin_encoder_up), encoder_isr, CHANGE);
attachInterrupt(digitalPinToInterrupt(pin_encoder_down), encoder_isr, CHANGE);
}
// Called every loop() iteration: drains rotation steps captured by the
// ISR and runs the press-button debounce, dispatching into Menu.h.
void encoder_process() {
int8_t d = 0;
ENCODER_ENTER_CRITICAL();
d = encoder_delta;
encoder_delta = 0;
ENCODER_EXIT_CRITICAL();
// Rotation/button state above is still tracked regardless (so nothing's
// left half-updated if this gets re-enabled later), but only actually
// reaches the menu if the board's encoder is flagged as populated -
// see encoder_enabled, MENU_ITEM_ENCODER.
if (d != 0 && encoder_enabled) menu_encoder_rotate(d);
int reading = digitalRead(pin_encoder_press);
if (reading != enc_btn_debounce_state) {
enc_btn_debounce_last = millis();
enc_btn_debounce_state = reading;
}
if ((millis() - enc_btn_debounce_last) > ENC_BTN_DEBOUNCE_DELAY) {
if (reading != enc_btn_state) {
enc_btn_state = reading;
if (enc_btn_state == ENC_PRESSED) {
enc_btn_down_last = millis();
} else if (encoder_enabled) {
menu_encoder_button(millis() - enc_btn_down_last);
}
}
}
}
#endif