Optical Rotary Encoder goes mad after (slow) turn counter clockwise

Hello,

I have a problem with a Grove Optical Rotary Encoder on my MKR1000 and am running out of ideas. I would like to use it for counting steps I dial in manually - like for menu entries or animations - that I would like to show on an OLED display. It does so perfectly as long as I don’t call a second (more time consuming) function in the void loop() - like “Set_Lines”. If I do so, it starts of working perfectly fine, but after a while it does not only start to “skip” certain steps, but it completely gets lost. That is from the Serial Monitor:

22:57:07.068 → -22
22:57:07.458 → -16
22:57:07.568 → -14
22:57:07.676 → 0
22:57:07.781 → 13
22:57:07.886 → 30
22:57:08.235 → 32
22:57:09.350 → 124 → here it just “hopped” over 94 steps?!

I hope someone can help me, as this “problem” slowly drives me nuts… Thank you in advance!

#include <ezButton.h>
#include <U8g2lib.h>

#define ENCODER_OPTIMIZE_INTERRUPTS
#include <Encoder.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

U8G2_SH1107_PIMORONI_128X128_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

ezButton leftButton(4);  // create ezButton object that attach to pin 3;
ezButton rightButton(3);  // create ezButton object that attach to pin 4;
ezButton centerButton(5);  // create ezButton object that attach to pin 5;

Encoder myEnc(6, 7);

int entry = 1;
long oldPosition  = -999;

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

  u8g2.begin(); // start display

  u8g2.setContrast(255);
  
  u8g2.clearBuffer();
  u8g2.setDrawColor(1);
  u8g2.setFont(u8g2_font_8x13B_tf);
  u8g2.drawStr(49,70, "V2.1");
  u8g2.sendBuffer();

  leftButton.setDebounceTime(50); // set debounce time to 50 milliseconds
  rightButton.setDebounceTime(50); // set debounce time to 50 milliseconds
  centerButton.setDebounceTime(50); // set debounce time to 50 milliseconds

  delay(50);

}

void loop(){
  encode_now();

  leftButton.loop(); // MUST call the loop() function first
  rightButton.loop(); // MUST call the loop() function first
  centerButton.loop(); // MUST call the loop() function first
 
  if(leftButton.isPressed()) Serial.println("The button 1 is pressed");
  if(leftButton.isReleased()) Serial.println("The button 1 is released");
  if(rightButton.isPressed()) Serial.println("The button 2 is pressed");
  if(rightButton.isReleased()) Serial.println("The button 2 is released");
  if(centerButton.isPressed()) Serial.println("The button 3 is pressed");
  if(centerButton.isReleased()) Serial.println("The button 3 is released");

  Set_Lines(9,64,64,64,oldPosition/4);


}

void encode_now(){
  int newPosition = myEnc.read();
  if (newPosition != oldPosition) 
    Serial.println(oldPosition);
    oldPosition = newPosition;
}

void Set_Lines(int points, double radius, int centerx, int centery, int twist_angle)
{   
    int twistangle;
    twistangle = map(twist_angle, 0, 9, 0, 36);
    String y = "";
  
    u8g2.clearBuffer();
    double slice = 2 * M_PI / points;
    double offset_ = -M_PI / 2;
    for (int i = 0; i < points; i++)
    {
        double angle = slice * i;
        int newX_1 = (int)(centerx + radius * cos(angle + offset_));
        int newY_1 = (int)(centery + radius * sin(angle + offset_));
        int newX_2 = (int)(centerx + (radius-(twistangle+30)) * cos((angle+10) + offset_));
        int newY_2 = (int)(centery + (radius-(twistangle+30)) * sin((angle+10) + offset_));
        u8g2.drawLine(newX_2, newY_2, newX_1, newY_1);
        u8g2.setDrawColor(1);

        switch (twistangle) {
    case 0:
      y = "a";
      break;
    case 1:
      y = "b";
      break;
      case 2:
      y = "c";
      break;
      case 3:
      y = "d";
      break;
      case 4:
      y = "e";
      break;
      case 5:
      y = "f";
      break;
      case 6:
      y = "g";
      break;
      case 8:
      y = "h";
      break;
      case 9:
      y = "i";
      break;
    default:
      y = "j";
      break;
  }
        u8g2.setFont(u8g2_font_8x13B_tf);
        u8g2.drawStr(43,70, String(y).c_str());
    }
    u8g2.sendBuffer();
}

I think you might be corrupting values with the use of Strings.

String y = "";
u8g2.drawStr(43,70, String(y).c_str());

First, u8g2.drawStr() takes a constant char * not a String.

u8g2_uint_t U8g2::drawStr(u8g2_uint_t x, u8g2_uint_t y, const char *s)

The recasting of the returned pointer to a String looks wrong as well.

Try this function

void Set_Lines(int points, double radius, int centerx, int centery, int twist_angle)
{
  int twistangle;
  twistangle = map(twist_angle, 0, 9, 0, 36);
  //String y = "";
  const char* y[10] = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"};
  u8g2.clearBuffer();
  double slice = 2 * M_PI / points;
  double offset_ = -M_PI / 2;
  for (int i = 0; i < points; i++)
  {
    double angle = slice * i;
    int newX_1 = (int)(centerx + radius * cos(angle + offset_));
    int newY_1 = (int)(centery + radius * sin(angle + offset_));
    int newX_2 = (int)(centerx + (radius - (twistangle + 30)) * cos((angle + 10) + offset_));
    int newY_2 = (int)(centery + (radius - (twistangle + 30)) * sin((angle + 10) + offset_));
    u8g2.drawLine(newX_2, newY_2, newX_1, newY_1);
    u8g2.setDrawColor(1);
    
    u8g2.setFont(u8g2_font_8x13B_tf);
    //u8g2.drawStr(43,70, String(y).c_str());
    u8g2.drawStr(43, 70, y[twistangle]);
  }
  u8g2.sendBuffer();
}

cattledog:
I think you might be corrupting values with the use of Strings.

String y = "";

u8g2.drawStr(43,70, String(y).c_str());




First, u8g2.drawStr() takes a constant char * not a String.



u8g2_uint_t U8g2::drawStr(u8g2_uint_t x, u8g2_uint_t y, const char *s)




The recasting of the returned pointer to a String looks wrong as well.

Try this function



void Set_Lines(int points, double radius, int centerx, int centery, int twist_angle)
{
 int twistangle;
 twistangle = map(twist_angle, 0, 9, 0, 36);
 //String y = “”;
 const char* y[10] = {“a”, “b”, “c”, “d”, “e”, “f”, “g”, “h”, “i”, “j”};
 u8g2.clearBuffer();
 double slice = 2 * M_PI / points;
 double offset_ = -M_PI / 2;
 for (int i = 0; i < points; i++)
 {
   double angle = slice * i;
   int newX_1 = (int)(centerx + radius * cos(angle + offset_));
   int newY_1 = (int)(centery + radius * sin(angle + offset_));
   int newX_2 = (int)(centerx + (radius - (twistangle + 30)) * cos((angle + 10) + offset_));
   int newY_2 = (int)(centery + (radius - (twistangle + 30)) * sin((angle + 10) + offset_));
   u8g2.drawLine(newX_2, newY_2, newX_1, newY_1);
   u8g2.setDrawColor(1);
   
   u8g2.setFont(u8g2_font_8x13B_tf);
   //u8g2.drawStr(43,70, String(y).c_str());
   u8g2.drawStr(43, 70, y[twistangle]);
 }
 u8g2.sendBuffer();
}

Thank you very much! That already helped a lot - it doesn’t get off to much anymore. But how is it possible, that the drawStr() method affects my counter? And it still has some smaller skipping (see below). Is there a way to get rid of it as well?
10:58:26.953 → 18
10:58:27.059 → 16
10:58:27.171 → 12
10:58:27.312 → 10
10:58:27.414 → 7
10:58:27.519 → 4
10:58:27.653 → 2
10:58:28.153 → 0
10:58:28.292 → -14 → here it is jumping from 0 to 14
10:58:28.429 → -17
10:58:28.534 → -16
10:58:28.675 → -14
10:58:29.067 → -12
10:58:29.175 → -11

Have you tested the encoder and its software in a sketch by itself, just displaying the position every few seconds, so that you could rotate it to e.g. 900, 1800, 2700, 1 turn, 2 turns etc., at different speeds and confirm the counts? That would eliminate wiring problems, electrical noise etc.

It’ usually easier to break things down to components, especially when a sketch is getting quite complex.

Yep, I did that and it worked with various encoder-codes. Even the simplest Encoder (from the arduino-homepage) version, written with a single interrupt worked perfectly fine. I rotated slow, quick, in different directions, with different accelerations. I also tried to move it “ultra-slow” so that the LEDs on the Encoder-Board fade in and out - even that worked. I also changed pins, as on my MKR1000 there seems to be an LED on Pin6 and I checked for having Interrupts on my pins. I swapped the pins - that swapped the “problematic direction” as well - it then was clockwise.

Today I wrote another (similar) function it works without problems:

void Set_Counter(int points, double radius, int centerx, int centery, int zaehler)
{
String countdown;
  if(zaehler < 10)
    countdown = "00:0" + String(zaehler-1);
  else
    countdown = "00:" + String(zaehler-1);
  
  
    u8g2.clearBuffer();
    double slice = 2 * M_PI / points;
    double offset_ = -M_PI / 2.0;
    for (int i = 0; i < zaehler; i++)
    {
        double angle = slice * i;
        int newX_1 = (int)(centerx + radius * cos(angle + offset_));
        int newY_1 = (int)(centery + radius * sin(angle + offset_));
        int newX_2 = (int)(centerx + (radius-10) * cos(angle + offset_));
        int newY_2 = (int)(centery + (radius-10) * sin(angle + offset_));
        //display.fillCircle(newX, newY, 2, WHITE);
        u8g2.drawLine(newX_2, newY_2, newX_1, newY_1);
        u8g2.setDrawColor(1);
        u8g2.setFont(u8g2_font_8x13B_tf);
        u8g2.drawStr(43,70, countdown.c_str());
    }
    u8g2.sendBuffer();
}

I have a problem with a Grove Optical Rotary Encoder on my MKR1000 and am running out of ideas.

I would like to use it for counting steps I dial in manually - like for menu entries or animations

How do you manually control the rotating optical disc. Are there detents? How does the transition between detents correspond to the number of quadrature counts? Do you expect the count to increase by only 1 at each location?

I have my doubts about the interrupt use of the Encoder.h library with the MKR1000.

In the utility file interrupt_pins.h I see

// Arduino Zero - TODO: interrupts do not seem to work // please help, contribute a fix!

elif defined(SAMD21G18A)

What happens if you develop your code without the library?

OMG problem solved: I just put the pins to 6+8 instead of 6+7 and 7+8, now everything works perfectly.

Why can't i just put them next to each other? Is there some crosstalk?

I just put the pins to 6+8 instead of 6+7 and 7+8, now everything works perfectly. Why can't i just put them next to each other? Is there some crosstalk?

It's not clear to me that the library gets the interrupt numbers correct for all the pins on the MKR1000.

There may be cross talk if the encoder board does not have pullups, and the library does not engaged the internal pullups. If you set the pins you use to pinMode INPUT_PULLUP can you place them on 6 and 7?

No, I didn’t do any changes ... but with the qdec code, there is no pin mode. It just uses “attachInterrupt(digitalPinToInterrupt(ROTARY_PIN_A), IsrForDEC, CHANGE).

However, if I change the direction, it takes one step to get it recognize the step.

And another strange behaviour is, that now the Buttons don’t work like expected anymore. The “ispressed” function is called after “isreleased” (if I have them directly underneath each other) and ispressed is not “held” ... so it just flickers and is gone.

Such a little program, so many problems :(

Please post your current code.

What is "the qdec code"?

However, if I change the direction, it takes one step to get it recognize the step.

This can be an expected result depending on the code to read the encoder and the detent positions in relationship to the optical disc being read.