Hello,
I'm working on a project with an ESP32 board and a KY-040 rotary encoder, and I’m experiencing issues that I’m struggling to resolve. Here’s a summary of my setup and the problem:
- Hardware Configuration:
- Rotary Encoder KY-040 connected to the ESP32:
- CLK -> GPIO 5
- DT -> GPIO D2
- SW (Button) -> GPIO 32
- Power Supply: The encoder and ESP32 are powered by a 5V source in parallel to avoid voltage drops.
- Rotary Encoder KY-040 connected to the ESP32:
- Library: I’m using the RotaryEncoder library by Matthias Hertel.
Issue Description:
- When I turn the knob one step to the right, the displayed number sometimes increments but then freezes, causing the ESP32 to reset.
- If I press lightly without completing a step, the counter increases rapidly.
- Turning left often causes the program to stop responding entirely.
- The button (SW) does not reset the counter as expected.
I'm attaching a video (wasnt able to attach the video due to forum limitations) to illustrate the issue. As I’m new to working with encoders and the ESP32, I suspect the issue might be related to my code. I based my initial code on examples generated by ChatGPT, but I'm not sure if it’s optimized or correct for this setup.
Below is the code I'm currently using in the video but i want to use another code i've taken from another project of a led cube. The idea is to use de encoder to scroll through the diferents options in a menu.
// Objeto para el botón del encoder
OneButton button(BTN_PIN, true); // `true` para activar el modo pull-up
void resetCounter() {
// Reinicia el contador cuando se presiona el botón
counter = 0;
}
#include <Adafruit_SH110X.h>
#include <OneButton.h>
// Pines del rotary encoder KY-040
#define CLK_PIN 5 // Pin CLK del encoder (conectado al puerto 5)
#define DT_PIN 2 // Pin DT del encoder (conectado al puerto D2)
#define BTN_PIN 32 // Pin SW del botón del encoder (conectado al puerto 32)
// Configuración de la pantalla SH1106
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);
// Variables para el contador
int counter = 0; // Valor actual a mostrar en pantalla
int lastCounter = -1; // Último valor mostrado en pantalla para evitar actualizaciones constantes
// Objeto para el botón del encoder
OneButton button(BTN_PIN, true); // `true` para activar el modo pull-up
void IRAM_ATTR handleEncoder() {
// Lee el estado del encoder
int clkState = digitalRead(CLK_PIN);
int dtState = digitalRead(DT_PIN);
// Incrementa o decrementa el contador basado en el estado de CLK y DT
if (clkState == LOW) {
if (dtState == HIGH) {
counter++;
} else {
counter--;
}
}
}
void resetCounter() {
// Reinicia el contador cuando se presiona el botón
counter = 0;
}
void setup() {
// Configuración de la pantalla SH1106 con los pines de I2C
Wire.begin(21, 22); // SDA en el puerto 21, SCL en el puerto 22
display.begin(0x3C, true); // Dirección I2C de la pantalla (0x3C)
display.clearDisplay();
display.display();
// Configura los pines del rotary encoder
pinMode(CLK_PIN, INPUT_PULLUP);
pinMode(DT_PIN, INPUT_PULLUP);
pinMode(BTN_PIN, INPUT_PULLUP);
// Configura la interrupción para el encoder
attachInterrupt(digitalPinToInterrupt(CLK_PIN), handleEncoder, FALLING);
// Configura el botón del encoder para reiniciar el contador
button.attachClick(resetCounter);
// Muestra el valor inicial en pantalla
displayNumber();
}
void loop() {
// Actualiza el botón del encoder
button.tick();
// Actualiza la pantalla solo si el valor ha cambiado
if (counter != lastCounter) {
displayNumber();
lastCounter = counter;
}
}
void displayNumber() {
// Limpia y muestra el número en la pantalla
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SH110X_WHITE);
display.setCursor(0, 20); // Ajusta la posición del texto en pantalla
display.print("Count: ");
display.println(counter);
display.display();
}
Any guidance or suggestions to improve the code or troubleshooting steps would be greatly appreciated!
Thank you in advance for your help.
At last here is the actual code i want to finally use for my project.
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
#include <Fonts/FreeSans9pt7b.h>
#include <Arduino.h>
#include <RotaryEncoder.h>
#include "OneButton.h"
#include <FastLED.h>
#define NUM_LEDS 288
#define LED_PIN 27
#define BRIGHTNESS 170
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
uint8_t hue = 0;
uint8_t colorIndex[144];
CRGB leds[NUM_LEDS]; //color data for each led - sets up an array that we can manipulate to set/clear led data.
CRGB leds_buffor[144];
CRGB leds_segments[12];
#define PIN_IN1 2 //encoder pin
#define PIN_IN2 5 //encoder pin
#define ROTARYSTEPS 1 // encoder step
#define ROTARYMIN 1 // encoder min
#define ROTARYMAX 10 // encoder max
int lastPos = -1;
int newPos = 1;
int newPos2 = 1;
#define PIN_INPUT 32 //button pin
#define i2c_Address 0x3c //initialize with the I2C addr 0x3C Typically eBay OLED's
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // QT-PY / XIAO
Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
//---------------
volatile int interrupts;
int totalInterrupts;
hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
void IRAM_ATTR onTime() {
portENTER_CRITICAL_ISR(&timerMux);
interrupts++;
portEXIT_CRITICAL_ISR(&timerMux);
}
//-----------------Display start--------
void heart(){
for (int j=0; j<1; j++){ //only one time animation
for (byte i=0; i<100; i++){
display.drawBitmap(0, 0, epd_bitmap_allArray[i],64 ,64, SH110X_WHITE);
display.display();
delay(10);
display.clearDisplay();
}
display.drawBitmap(0, 0, myBitmap,128 ,64, SH110X_WHITE);
display.display();
delay(1000);
display.clearDisplay();
}
}
// A pointer to the dynamic created rotary encoder instance.
// This will be done in setup()
RotaryEncoder *encoder = nullptr;
// @brief The interrupt service routine will be called on any change of one of the input signals.
IRAM_ATTR void checkPosition()
{
encoder->tick(); // just call tick() to check the state.
}
OneButton button(PIN_INPUT, true);
unsigned long pressStartTime;
ICACHE_RAM_ATTR void checkTicks() {
// include all buttons here to be checked
button.tick(); // just call tick() to check the state.
}
// this function will be called when the button was pressed 1 time only.
void singleClick() {
Serial.println("singleClick() detected.");
newPos2 = newPos;
} // singleClick
// this function will be called when the button was held down for 1 second or more.
void pressStart() {
Serial.println("pressStart()");
pressStartTime = millis() - 1000; // as set in setPressMs()
} // pressStart()
// this function will be called when the button was released after a long hold.
void pressStop() {
Serial.print("pressStop(");
Serial.print(millis() - pressStartTime);
Serial.println(") detected.");
} // pressStop()
DEFINE_GRADIENT_PALETTE( girlcat_gp ) { //gradient for pattern usage
0, 173,229, 51,
73, 139,199, 45,
140, 46,176, 37,
204, 7, 88, 5,
255, 0, 24, 0};
CRGBPalette16 greenblue2 = girlcat_gp;
DEFINE_GRADIENT_PALETTE( spellbound_gp ) { //gradient2 for pattern usage
0, 232,235, 40,
12, 157,248, 46,
25, 100,246, 51,
45, 53,250, 33,
63, 18,237, 53,
81, 11,211,162,
94, 18,147,214,
101, 43,124,237,
112, 49, 75,247,
127, 49, 75,247,
140, 92,107,247,
150, 120,127,250,
163, 130,138,252,
173, 144,131,252,
186, 148,112,252,
196, 144, 37,176,
211, 113, 18, 87,
221, 163, 33, 53,
234, 255,101, 78,
247, 229,235, 46,
255, 229,235, 46};
CRGBPalette16 greenblue = spellbound_gp;
void setup()
{
//--------------------------------
// Configure Prescaler to 80, as our timer runs @ 80Mhz
// Giving an output of 80,000,000 / 80 = 1,000,000 ticks / second
timer = timerBegin(1'000'000);
timerAttachInterrupt(timer, &onTime);
// Fire Interrupt every 1m ticks, so 1s
timerWrite(timer, 5'000'000);
timerAlarm(timer, 5'000'000, true, 0);
//--------------------------------
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS); // setup leds
FastLED.setBrightness(BRIGHTNESS);
Serial.begin(9600);
while (!Serial)
;
// setup the rotary encoder functionality
encoder = new RotaryEncoder(PIN_IN1, PIN_IN2, RotaryEncoder::LatchMode::TWO03);
// register interrupt routine
attachInterrupt(digitalPinToInterrupt(PIN_IN1), checkPosition, CHANGE);
attachInterrupt(digitalPinToInterrupt(PIN_IN2), checkPosition, CHANGE);
encoder->setPosition(1 / ROTARYSTEPS); // start with the value of 10.
// setup interrupt routine
attachInterrupt(digitalPinToInterrupt(PIN_INPUT), checkTicks, CHANGE);
button.attachClick(singleClick);
button.setPressMs(1000); // that is the time when LongPressStart is called - not in use
button.attachLongPressStart(pressStart);
button.attachLongPressStop(pressStop);
display.begin(i2c_Address, true); // Address 0x3C default
display.display();
display.clearDisplay(); // Clear the buffer
heart();
for (int i = 0; i < 144; i++) {
colorIndex[i] = random8();
}
} // setup() close
void screen(){ //funtion for screen operation
display.setTextSize(1);
display.setTextColor(SH110X_WHITE);
display.setCursor(28, 0);
display.println("LIGHTING MODES");
display.drawLine(4, 12, 128, 12, SH110X_WHITE);
switch (newPos) {
case 0:
break;
case 1:
// display.setCursor(6, 20);
// display.println("Falling Star");
highlight();
display.setCursor(6, 32);
display.println(" Rainbow ");
display.setTextColor(SH110X_WHITE);
display.setCursor(6, 44);
display.println(" Matrix ");
// display.setCursor(95, 44);
// display.write(30);
// display.setCursor(110, 32);
// display.write(31);
break;
case 2:
display.setCursor(6, 20);
display.println(" Rainbow ");
highlight();
display.setCursor(6, 32);
display.println(" Matrix ");
display.setTextColor(SH110X_WHITE);
display.setCursor(6, 44);
display.println(" Spell Pattern ");
break;
case 3:
display.setCursor(6, 20);
display.println(" Matrix ");
highlight();
display.setCursor(6, 32);
display.println(" Spell Pattern ");
display.setTextColor(SH110X_WHITE);
display.setCursor(6, 44);
display.println(" Move Dots ");
break;
case 4:
display.setCursor(6, 20);
display.println(" Spell Pattern ");
highlight();
display.setCursor(6, 32);
display.println(" Move Dots ");
display.setTextColor(SH110X_WHITE);
display.setCursor(6, 44);
display.println(" Colour Snake ");
break;
case 5:
display.setCursor(6, 20);
display.println(" Move Dots ");
highlight();
display.setCursor(6, 32);
display.println(" Colour Snake ");
display.setTextColor(SH110X_WHITE);
display.setCursor(6, 44);
display.println(" Confetti ");
break;
case 6:
display.setCursor(6, 20);
display.println(" Colour Snake ");
highlight();
display.setCursor(6, 32);
display.println(" Confetti ");
display.setTextColor(SH110X_WHITE);
display.setCursor(6, 44);
display.println(" Random Star");
break;
case 7:
display.setCursor(6, 20);
display.println(" Confetti ");
highlight();
display.setCursor(6, 32);
display.println(" Random Star ");
display.setTextColor(SH110X_WHITE);
display.setCursor(6, 44);
display.println(" Colorful ");
break;
case 8:
display.setCursor(6, 20);
display.println(" Random Star ");
highlight();
display.setCursor(6, 32);
display.println(" Colorful ");
display.setTextColor(SH110X_WHITE);
display.setCursor(6, 44);
display.println(" Beat ");
break;
case 9:
display.setCursor(6, 20);
display.println(" Colorful ");
highlight();
display.setCursor(6, 32);
display.println(" Beat ");
display.setTextColor(SH110X_WHITE);
display.setCursor(6, 44);
display.println(" Snake ");
break;
case 10:
display.setCursor(6, 20);
display.println(" Beat ");
highlight();
display.setCursor(6, 32);
display.println(" Snake ");
display.setTextColor(SH110X_WHITE);
// display.setCursor(6, 44);
// display.println(" Snake ");
break;
}
display.display();
delay(100);
display.clearDisplay();
}
void highlight(){
display.setTextColor(SH110X_BLACK, SH110X_WHITE);
}
//------------------------Main loop--------------
void loop()
{
static int pos = 0;
button.tick();
encoder->tick(); // just call tick() to check the state.
newPos = encoder->getPosition() * ROTARYSTEPS;
if (newPos < ROTARYMIN) {
encoder->setPosition(ROTARYMIN / ROTARYSTEPS);
newPos = ROTARYMIN;
} else if (newPos > ROTARYMAX) {
encoder->setPosition(ROTARYMAX / ROTARYSTEPS);
newPos = ROTARYMAX;
}
if (lastPos != newPos) {
Serial.print(newPos);
lastPos = newPos;
screen();
}
switch (newPos2) {
case 0:
// Serial.print(newPos);
break;
case 1:
rainbow();
break;
case 2:
matrix();
break;
case 3:
SpellPattern();
break;
case 4:
dots();
break;
case 5:
ColourSnake();
break;
case 6:
confetti();
break;
case 7:
RandomStar();
break;
case 8:
colorful();
break;
case 9:
beat();
break;
case 10:
snake();
break;
}
//--------
if (interrupts > 0) {
portENTER_CRITICAL(&timerMux);
interrupts--;
portEXIT_CRITICAL(&timerMux);
totalInterrupts++;
Serial.print("totalInterrupts");
Serial.println(totalInterrupts);
}
//--------
}