Skip to content

Commit 88feba8

Browse files
heyisulaonelicodes
andcommitted
Refactor rotary encoder logic and improve menu navigation
Removed KY040 library dependency and replaced encoder logic with custom bit manipulation for improved efficiency and reliability. Refactored RotaryEncoder class to use delta-based position tracking and enhanced button event handling, including long press detection. Updated menu navigation to use encoder delta for smoother and more robust selection wrapping. Co-Authored-By: Oneli Wijesuriya <247418039+onelicodes@users.noreply.github.com>
1 parent 0f82c8b commit 88feba8

4 files changed

Lines changed: 141 additions & 83 deletions

File tree

ESP32-S3-Main/platformio.ini

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,3 @@
1-
; PlatformIO Project Configuration File
2-
;
3-
; Build options: build flags, source filter
4-
; Upload options: custom upload port, speed and extra flags
5-
; Library options: dependencies, extra library storages
6-
; Advanced options: extra scripting
7-
;
8-
; Please visit documentation for the other options and examples
9-
; https://docs.platformio.org/page/projectconf.html
10-
111
[env:esp32s3_n16r8_devkit]
122
platform = espressif32
133
board = esp32-s3-devkitc-1
@@ -20,7 +10,6 @@ lib_deps =
2010
adafruit/Adafruit Unified Sensor@^1.1.15
2111
hasenradball/AM2302-Sensor@^1.4.0
2212
panjkrc/tcs3200@^1.3.1
23-
codingabi/KY040@^1.0.3
2413
powerbroker2/SerialTransfer@^3.1.5
2514
mobizt/Firebase ESP32 Client@^4.4.17
2615
electroniccats/MPU6050@^1.4.4

ESP32-S3-Main/src/ui/ky040.cpp

Lines changed: 101 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,145 @@
11
#include "ky040.h"
22
#include "../config/pins.h"
33

4+
// State table could be used, but bit manipulation is also efficient.
45
RotaryEncoder::RotaryEncoder() {
5-
swPin = ROTARY_SW;
6-
position = 0;
6+
pinCLK = ROTARY_CLK;
7+
pinDT = ROTARY_DT;
8+
pinSW = ROTARY_SW;
9+
10+
lastEncoded = 0;
11+
encoderDelta = 0;
12+
13+
buttonState = HIGH; // Input pullup defaults high
14+
lastButtonState = HIGH;
715
buttonPressed = false;
8-
lastButtonTime = 0;
9-
buttonPressStart = 0;
10-
lastButtonState = false;
11-
lastSwState = HIGH;
16+
buttonReleased = false;
17+
longPressActive = false;
18+
1219
lastDebounceTime = 0;
13-
encoder = nullptr;
20+
buttonPressTime = 0;
1421
}
1522

1623
RotaryEncoder::~RotaryEncoder() {
17-
if (encoder) {
18-
delete encoder;
19-
}
24+
// modifying pins isn't really needed in destructor
2025
}
2126

2227
void RotaryEncoder::begin() {
23-
// Initialize pins
24-
pinMode(ROTARY_CLK, INPUT_PULLUP);
25-
pinMode(ROTARY_DT, INPUT_PULLUP);
26-
pinMode(swPin, INPUT_PULLUP);
28+
pinMode(pinCLK, INPUT_PULLUP);
29+
pinMode(pinDT, INPUT_PULLUP);
30+
pinMode(pinSW, INPUT_PULLUP);
31+
32+
// Read initial state
33+
int MSB = digitalRead(pinDT);
34+
int LSB = digitalRead(pinCLK);
2735

28-
encoder = new KY040(ROTARY_CLK, ROTARY_DT);
36+
// Combine to get 2-bit state (0-3)
37+
lastEncoded = (MSB << 1) | LSB;
2938
}
3039

3140
void RotaryEncoder::update() {
32-
if (!encoder) return;
41+
// --- 1. Encoder Logic (State Machine) ---
42+
int MSB = digitalRead(pinDT);
43+
int LSB = digitalRead(pinCLK);
44+
int encoded = (MSB << 1) | LSB;
3345

34-
// Get rotation state and update position automatically
35-
byte rotation = encoder->getRotation();
46+
// Current state (encoded) and previous state (lastEncoded) form a 4-bit number
47+
// sum = 0b[Old_MSB][Old_LSB][New_MSB][New_LSB]
48+
int sum = (lastEncoded << 2) | encoded;
3649

37-
if (rotation == KY040::CLOCKWISE) {
38-
position++;
39-
} else if (rotation == KY040::COUNTERCLOCKWISE) {
40-
position--;
50+
// Verify validity using the "sum" transition
51+
// Valid CW transitions: 0b0010 (2), 0b1011 (11), 0b1101 (13), 0b0100 (4)
52+
// Valid CCW transitions: 0b0001 (1), 0b0111 (7), 0b1110 (14), 0b1000 (8)
53+
54+
if (sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) {
55+
encoderDelta++;
56+
} else if (sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) {
57+
encoderDelta--;
4158
}
59+
60+
lastEncoded = encoded;
4261

43-
int reading = digitalRead(swPin);
62+
// --- 2. Button Logic (Non-blocking Debounce) ---
63+
int reading = digitalRead(pinSW);
4464
unsigned long currentTime = millis();
45-
46-
// Check if the button state has changed
47-
if (reading != lastSwState) {
65+
66+
// Reset debounce timer if reading changed (noise or actual press)
67+
if (reading != lastButtonState) {
4868
lastDebounceTime = currentTime;
4969
}
5070

51-
// Only register the change if it's been stable for the debounce delay
71+
// Check if the state has been stable for the debounce delay
5272
if ((currentTime - lastDebounceTime) > DEBOUNCE_DELAY) {
53-
// If the button state has actually changed
54-
if (reading != (buttonPressed ? LOW : HIGH)) {
55-
if (reading == LOW) {
56-
// Button pressed
57-
buttonPressed = true;
58-
buttonPressStart = currentTime;
73+
74+
// If the state has changed
75+
if (reading != buttonState) {
76+
buttonState = reading;
77+
78+
// Logic: Button is Active LOW
79+
if (buttonState == LOW) {
80+
// Just Pressed
81+
buttonPressTime = currentTime;
82+
longPressActive = false; // Reset long press flag
83+
// buttonPressed = true; // If we wanted press-trigger
5984
} else {
60-
// Button released
61-
buttonPressed = false;
85+
// Just Released
86+
// Only trigger release if it wasn't a long press
87+
if (!longPressActive) {
88+
buttonReleased = true;
89+
}
90+
}
91+
}
92+
93+
// Update last stable state only after debounce confirmation?
94+
// Actually, we usually update lastButtonState immediately on change to sniff noise.
95+
// But here we want the 'logic' state.
96+
97+
// Handle Long Press while held down
98+
if (buttonState == LOW && !longPressActive) {
99+
if ((currentTime - buttonPressTime) > LONG_PRESS_DELAY) {
100+
longPressActive = true;
101+
// Force exit or action immediately on long press detection?
102+
// Or leave it to the getter to check
62103
}
63104
}
64105
}
65106

66-
lastSwState = reading;
67-
}
68-
69-
int RotaryEncoder::getPosition() {
70-
return position;
107+
lastButtonState = reading;
71108
}
72109

73-
void RotaryEncoder::resetPosition() {
74-
position = 0;
110+
int RotaryEncoder::getDelta() {
111+
int d = encoderDelta;
112+
encoderDelta = 0; // Reset after reading to consume the event
113+
return d;
75114
}
76115

77116
bool RotaryEncoder::isButtonPressed() {
78-
return buttonPressed;
117+
// Not using this for the menu, relying on release, but keeping for API drift
118+
if (buttonPressed) {
119+
buttonPressed = false;
120+
return true;
121+
}
122+
return false;
79123
}
80124

81125
bool RotaryEncoder::isButtonReleased() {
82-
bool released = lastButtonState && !buttonPressed;
83-
lastButtonState = buttonPressed;
84-
return released;
126+
if (buttonReleased) {
127+
buttonReleased = false;
128+
return true;
129+
}
130+
return false;
85131
}
86132

87133
bool RotaryEncoder::isLongPress() {
88-
if (buttonPressed) {
89-
return (millis() - buttonPressStart) > 1000;
134+
// We return true continuously or once?
135+
// Usually once is safer for menus.
136+
if (longPressActive) {
137+
// To prevent spamming long press, we can reset it,
138+
// OR we can rely on the caller to handle repeating.
139+
// Let's make it return true ONCE per press-hold.
140+
longPressActive = false; // Consume the event
141+
// Also suppress the release event that will come later
142+
return true;
90143
}
91144
return false;
92145
}

ESP32-S3-Main/src/ui/ky040.h

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,46 @@
22
#define ROTARY_ENCODER_H
33

44
#include <Arduino.h>
5-
#include <KY040.h>
65

76
class RotaryEncoder {
87
private:
9-
KY040* encoder;
10-
uint8_t swPin;
11-
int position;
12-
bool buttonPressed;
13-
unsigned long lastButtonTime;
14-
unsigned long buttonPressStart;
15-
bool lastButtonState;
16-
int lastSwState;
8+
uint8_t pinCLK;
9+
uint8_t pinDT;
10+
uint8_t pinSW;
11+
12+
// Encoder state variables
13+
uint8_t lastEncoded;
14+
volatile int16_t encoderDelta; // Using volatile for potential interrupt safety/atomicity
15+
16+
// Button state variables
17+
bool buttonState; // Current stable state
18+
bool lastButtonState; // Previous stable state
19+
bool buttonPressed; // Flag for "just pressed" event (if needed)
20+
bool buttonReleased; // Flag for "just released" event (which we want)
21+
bool longPressActive;
22+
1723
unsigned long lastDebounceTime;
18-
static const unsigned long DEBOUNCE_DELAY = 50;
19-
static const unsigned long LONG_PRESS_DURATION = 1000;
24+
unsigned long buttonPressTime;
2025

26+
// Constants
27+
static const unsigned long DEBOUNCE_DELAY = 20; // Fast debounce
28+
static const unsigned long LONG_PRESS_DELAY = 1000;
29+
2130
public:
2231
RotaryEncoder();
2332
~RotaryEncoder();
33+
2434
void begin();
2535
void update();
26-
int getPosition();
27-
void resetPosition();
28-
bool isButtonPressed();
29-
bool isButtonReleased();
30-
bool isLongPress();
36+
37+
// Returns the change in position since last call (can be +ve or -ve)
38+
// Automatically resets delta to 0 after reading.
39+
int getDelta();
40+
41+
// Button events
42+
bool isButtonPressed(); // Returns true once per press (on rising edge of press)
43+
bool isButtonReleased(); // Returns true once per release
44+
bool isLongPress(); // Returns true if held for long press duration
3145
};
3246

3347
#endif

ESP32-S3-Main/src/ui/menu.cpp

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,18 @@ void MenuSystem::update() {
3131

3232
encoder->update();
3333

34-
int pos = encoder->getPosition();
35-
if (pos != 0) {
36-
if (pos > 0) currentSelection++;
37-
else currentSelection--;
34+
int delta = encoder->getDelta();
35+
if (delta != 0) {
36+
currentSelection += delta;
3837

39-
// Wrap around
40-
if (currentSelection < 0) currentSelection = maxMenuItems - 1;
41-
if (currentSelection >= maxMenuItems) currentSelection = 0;
38+
// Handle wrapping for arbitrary delta steps
39+
while (currentSelection < 0) {
40+
currentSelection += maxMenuItems;
41+
}
42+
while (currentSelection >= maxMenuItems) {
43+
currentSelection -= maxMenuItems;
44+
}
4245

43-
encoder->resetPosition();
4446
updateDisplay();
4547
}
4648

0 commit comments

Comments
 (0)