Rotary encoder stops working when adding code

Hi all,

I'm building a PWM fan controller using an Arduino NANO, a KY-040 rotary encoder and an i2c OLED. I started with writing the code to control the OLED and a simple function to read the rotary encoder and decode the results into the serial monitor. The rotary encoder works perfectly fine if I only call the rotary encoder read function in the loop.

However, as soon as I call a second function in the loop to print the rotary position on the OLED the entire rotary encoder stops working normally and keeps subtracting from the RotPosition variable, no matter what direction I turn.

I included the code below. I'm suspecting the library that controls the I2C OLED uses blocking code that somehow prevents the rotary encoder from being read properly. Would love to hear if anyone has a suggestion.

//begin display int
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeSansBold9pt7b.h>
#include <Fonts/FreeSans9pt7b.h>

//begin screen res int
#define SCREEN_WIDTH 128 //OLED breedte [PIXELS]
#define SCREEN_HEIGHT 64 //OLED Hoogte [PIXELS]

// SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

//SPLASH SCREEN
static const unsigned char PROGMEM logo_bmp[] =
{
};

//Rotary encoder pins
const int PinCLK = 12; // CLK 
const int PinDT = 11; // DT 
const int rotencSW = 7; //encoder SW

//Encoder variables
int RotPosition = 0;
int rotation;
int value;
boolean LeftRight;

void setup() {
  Serial.begin(9600);

  //Encoder setup
  pinMode(rotencSW, INPUT_PULLUP);
  pinMode(PinCLK, INPUT);
  pinMode(PinDT, INPUT);

  rotation = digitalRead(PinCLK);
  
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  //Show splashscreen
  display.display();
  delay(1000);

  //Clear the splashscreen out of buffer
  display.clearDisplay();
  display.display();

  //Testpixel
  display.drawPixel(10, 10, SSD1306_WHITE);
  display.display();
  delay(2000);
  
  InitializeOLED();
}

void loop() {
  check_rotaryData();
  //mainMenuOLED(RotPosition); //UNCOMMENTING THIS LINE BREAKS ENCODER
}


void check_rotaryData() {
  
     value = digitalRead(PinCLK);
     
     if (value != rotation){ //detect change, use DT to find rotation direction
     if (digitalRead(PinDT) != value) {  //Clockwise
       RotPosition ++;
       LeftRight = true;
     } else{ //Counterclockwise
       LeftRight = false;
       RotPosition--;
     }
     
     if (LeftRight){
       Serial.println ("clockwise");
     }else{  
       Serial.println("counterclockwise");
     }

     Serial.print("Encoder RotPosition: ");
     Serial.println(RotPosition);
   } 
   rotation = value;
}

void InitializeOLED(void){
  display.clearDisplay();
  display.setTextSize(1); //1;1 RATIO
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.setFont(&FreeSansBold9pt7b);
  display.setCursor(0, 20); //top-left
  display.println("Initialized");
  display.display();
  delay(2000);
}

void mainMenuOLED(int fanSpeedINT){
  display.clearDisplay();
  display.setTextSize(1); //1;1 RATIO
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.setFont(&FreeSansBold9pt7b);
  display.setCursor(0, 20); //top-left
  display.println("Fan Speed: ");
  display.setCursor(90, 20); //top-left
  display.print(fanSpeedINT);
  display.display();
}

Why is this at the end of setup?

What is the SPI library for?

Thanks for your reply. The line you mentioned is only to provide some feedback on the OLED that the setup completed, it's not necessary for the encoder to function. The SPI library is also not needed and is a leftover from when I had a different OLED connected. I forgot to remove both when minimalizing the code to post here, sorry.

I suspect you problem may be that when the sketch is doing other things it is missing changes in the rotary encoder?
You are probably best using an interrupt to react when the rotary encoder pins change.

I have a basic menu sketch here where I use a rotary encoder and i2c oled which may be of interest/use?

Hello
Each used delay() function will inhibit the realtime behaiviour of the sketch.

Setting the text size, colour and font every time seems unnecessary. Quite possibly slow too if the font details are put on the wire.

If this is anything like most of Adafruit's display libraries, the changes are buffered locally, but on a call to display, everything is sent, however little changed. It may just spend too much time on the display.

I only used a delay in the setup, so that shouldn't block the code in the loop.

Thanks for the tip, I removed all the font, color and size calls after the initial setup but sadly the rotary encoder still doesn't behave right.

Thanks alot, that looks like a very solid piece of code. I will try and see if I can use interruptions per your example to read the rotary encoder instead of the code I have now!

if you don't need the interrupt-capable IO-pins
then you can use an interrupt-based rotary-encoder-library

I recommed this one:

here is a minimal democode how to use it

#include "Arduino.h"
#include "NewEncoder.h"

// Pins 2 and 3 should work for many processors, including Uno. 
// explanation:

// NewEncoder myEncoder(2, 3, -20, 20, 0, FULL_PULSE);

// create an encoder-object called "myEncoder"
// "2" IO-pin for first channel of rotary-encoder
// "3" IO-pin for second channel of rotary-encoder
// "-20" lower limit to count down to
// "20"  upper limit to count up to
// 0 the value at initialisation
// "FULL_PULSE" type of pulse (other option HALF_PULSE)
// Use FULL_PULSE for encoders that produce one complete quadrature pulse per detnet, such as: https://www.adafruit.com/product/377
// Use HALF_PULSE for endoders that produce one complete quadrature pulse for every two detents, such as: https://www.mouser.com/ProductDetail/alps/ec11e15244g1/?qs=YMSFtX0bdJDiV4LBO61anw==&countrycode=US&currencycode=USD
// 
NewEncoder myEncoder(2, 3, -20, 20, 0, FULL_PULSE);

int value = 0;
int lastValue = -1;

void setup() {
  Serial.begin(115200);
  Serial.println("Starting");

  if (!myEncoder.begin()) {
    Serial.println("Encoder Failed to Start. Check pin assignments and available interrupts. Aborting.");
    while (1) {
      yield();
    }
  } else {
      Serial.print("Encoder Successfully Started at value = ");
      Serial.println(myEncoder);
  }
}

void checkEncoder() {
  value = myEncoder;

  if(value != lastValue) {
    Serial.print("Encoder value= ");
    Serial.println(value);
    lastValue = value;    
  }
}

void loop() {
  checkEncoder();
}

// the behaviour of the code that you can see in the serial monitor is
// the encoder-vale starts with 0 and depending on the rotating-direction
// counts up until 20 but not higher or counts down to -20 but not lower 
// so in the serial monitor you should see
/*
Starting
Encoder Successfully Started at value = 0
Encoder value= 0
Encoder value= 1
Encoder value= 2...counting up
Encoder value= 20
Encoder value= 19
Encoder value= 18...counting down
Encoder value= 2
Encoder value= 1
Encoder value= 0
Encoder value= -1
Encoder value= -2... counting down
Encoder value= -19
Encoder value= -20
*/

best regards Stefan

Thanks. Pin 2 and 3 were free so I rewrote the code to use interrupts and the encoder reads without any problems now!