Due to high demand expect some shipping delays at this time, orders may not ship for 3-4 business days. On MLK Day no orders will be shipped.
0

Macropad & Rotary Encoder
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Macropad & Rotary Encoder

by seanhagen on Fri Dec 03, 2021 3:31 pm

I've been trying to get my macropad set up, now that I know what I want to use it for ( filling it with not-often-used keyboard shortcuts for my editor so I don't have to look up all the debugging commands ).

Working from the example sketch, I ran into an issue with the rotary encoder and interrupts. At first I thought it had something to do with NeoPixels ( because they disable interrupts due to their timing-sensitive code ), but it turns out it's not NeoPixels -- it's interrupts!

This is all based on the example code from the tutorial, but not using the Arduino IDE -- I'm using PlatformIO with the following configuration:

Code: Select all | TOGGLE FULL SIZE
[env:pico_old]
platform = raspberrypi
board = pico framework =
arduino
lib_deps = mathertel/RotaryEncoder@^1.5.2


Reason for this is that I run into some compile issues when using the earlephilhower recommended setup that I won't go into here, but the jist of the issue is I can't get that to work right now; the barebones PlatformIO setup compiles & uploads so I'm sticking with that.

What's strange about the code is that it performed the same way whether I used the full example code or a stripped down rotary encoder test. Basically, the code seems to only randomly pick up on the encoder being rotated. If I turned the knob at a constant rate, the code seemed to pick up the pulses randomly. Sometimes my code would print out the debug string to Serial 10 times in a row, and then not print the next 20. Or it'll print out 2, then skip 3, then 1, then skip 10, then 5, then nothing for a long time. Basically, I wasn't getting every tick on the encoder and couldn't figure out why.

So at first I thought the issues was where encoder.tick() is called. In the example code it's called twice; once in the interrupt on line 14, and then again in every iteration of loop() on line 71. I figured that maybe the call to tick() was clobbering something within the library itself, causing it to not see when the encoder was twisted. To test, I removed the call to tick() within loop(), but my issue persisted.

Then, to make sure that the only thing I'm testing is the rotary encoder code, I stripped down the example to this:

Code: Select all | TOGGLE FULL SIZE
#include <Arduino.h>
#include <RotaryEncoder.h>

#define PIN_ROTB 18
#define PIN_ROTA 17

// Create the rotary encoder
RotaryEncoder encoder(PIN_ROTA, PIN_ROTB, RotaryEncoder::LatchMode::FOUR3);

volatile bool posCheck = false;

void checkPosition() {
  // just call tick() to check the state.
  encoder.tick();
  posCheck = true;
}

// our encoder position state
int encoder_pos = 0;

void setup() {
  Serial.begin(115200);
  delay(100); // RP2040 delay is not a bad idea

  Serial.println("Adafruit Macropad with RP2040");

  // set rotary encoder inputs and interrupts
  pinMode(PIN_ROTA, INPUT_PULLUP);
  pinMode(PIN_ROTB, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(PIN_ROTA), checkPosition, CHANGE);
  attachInterrupt(digitalPinToInterrupt(PIN_ROTB), checkPosition, CHANGE);
}

void loop() {
  if (posCheck) {
    Serial.println("encoder pos change");
    posCheck = false;
  }

  // encoder.tick();
  // check the encoder
  int newPos = encoder.getPosition();
  if (encoder_pos != newPos) {
    Serial.print("Encoder:");
    Serial.print(newPos);
    Serial.print(" Direction:");
    Serial.println((int)(encoder.getDirection()));
    encoder_pos = newPos;
  }
}


I had read that NeoPixels disable interrupts due to their timing-sensitive nature, and figured I should see if they were causing my issue.

Nope!

No change, the encoder still behaves erratically. So the issue isn't NeoPixels and how they stop interrupts, it's had to be something else.

I had an idea while writing this post: maybe try without any interrupts at all. Just call encoder.tick() in the loop. So I ended up with the following:

Code: Select all | TOGGLE FULL SIZE
#include <Arduino.h>
#include <RotaryEncoder.h>

#define PIN_ROTB 18
#define PIN_ROTA 17
#define PIN_SWITCH 0

// Create the rotary encoder
RotaryEncoder encoder(PIN_ROTA, PIN_ROTB, RotaryEncoder::LatchMode::FOUR3);

volatile bool btnPressed = false;
bool btnCurrentState = false;
void btnPress() { btnPressed = !btnPressed; }

// our encoder position state
int encoder_pos = 0;

void setup() {
  Serial.begin(115200);
  delay(100); // RP2040 delay is not a bad idea

  Serial.println("Adafruit Macropad with RP2040");

  pinMode(PIN_SWITCH, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(PIN_SWITCH), btnPress, CHANGE);
}

void loop() {
  if (btnCurrentState != btnPressed) {
    btnCurrentState = btnPressed;
    if (btnCurrentState) {
      Serial.println("encoder button released");
    } else {
      Serial.println("encoder button pressed");
    }
  }

  encoder.tick(); // check the encoder
  int newPos = encoder.getPosition();
  if (encoder_pos != newPos) {
    Serial.print("Encoder:");
    Serial.print(newPos);
    Serial.print(" Direction:");
    Serial.println((int)(encoder.getDirection()));
    encoder_pos = newPos;
  }
}


It works perfectly! I get a line printed out every single time I turn the knob. The button interrupt works fine as well; I get the "encoder button pressed" and "encoder button released" messages in the Serial output just fine.

So what gives?

I'm using the same RotaryEncoder library from the example ( RotaryEncoder by Matthias Hertel, only difference is the image shows v1.5.1 and PlatformIO gave me v1.5.2 ). Is this because I'm not using the Earle Philhower core, or is it an issue with the RotaryEncoder library and interrupts on the RP2040 in general? The issue also just seems to be creating an interrupt using PIN_ROTA or PIN_ROTB -- as soon as I change the pin used in the interrupt to anything other than those two pins, everything works fine again. Is this because the RotaryEncoder library uses digitalRead() within the tick() function and that doesn't work so great in an interrupt?

Since I solved my issue as I was writing this post I'm mostly just curious. I'd like to be able to use interrupts rather than just having the code check the encoder in every iteration of loop but I also realize I probably would have to drop interrupts anyways because of the NeoPixels.

I'm also writing this to document the issue so that hopefully someone else trying to code their macropad with PlatformIO ( but without the Earle Philehower core ) won't waste days trying to figure out what's going on.

seanhagen
 
Posts: 1
Joined: Mon Dec 30, 2013 2:09 am

Please be positive and constructive with your questions and comments.