Issues with a rotary encoder and 4 line display

Hi. I took a break from my project after getting it working as expected, but not quite where I wanted to be.

Last week I was motivated again and wanted to add a mains disconnect, a rotary encoder, an IIC 4 line display, and two buttons for preset up and down.

On the bread board everything was great, but irl the display is corrupted and turning the rotary encoder (counter)clockwise mostly results in sporadic impulses in the opposite direction. :man_facepalming:t2::thinking:. The buttons work fine. :tada:

The panel is connected with a 7 conductor, 18 AWG cable. At the ESP it’s connected to screw terminals; at the panel wires are soldered to the pins, but voltage with wago connectors and DuPont pins.

Could someone please offer some help/insight or point me in the right direction?

Thank you!

Try different I2C speed.

Thank you @blazoncek. Changing the config in the user mod from 400kHz to 100kHz seems to have solved the corruption, but the rotary encoder (which is not IIC) still behaves as described. Should I mention that the impulse pins are connected to R/O pins 36 and 39?

Edit. I see that aside from being RO, GPIO 35-39 have no pull-up or pull-down resistors. From what I can tell, the output of the encoder is high until an impulse comes, and the signal goes low, so should pull-down resistors be used at the encoder? I’m thinking that might effectively form the signal better, but that’s just speculation (and I don’t want to kill something testing my theory :wink:).

Rotary encoder usermod does not use interrupts to count rotary pulses and relies on being called quite often. If you have slow LEDs or plenty of them, usermod may not get much attention and may skip a pulse or more.
To overcome that it will have to be rewritten to employ interrupts to count pulses.

1 Like

Okay. That’s an explanation.

Out of curiosity, are there any user mods that implement Interrupts, so I could get an idea of what I might be able to accomplish?

Edit:
AttachInterrupt(x,y,z) would need to be called on both pins, and the Method y is called at each respective impulse?

And this would be moved from loop() to Method y?

Enc_A = readPin(pinA); // Read encoder pins
    Enc_B = readPin(pinB);
    if ((Enc_A) && (!Enc_A_prev))
    { // A has gone from high to low
      if (Enc_B == LOW)    //changes to LOW so that then encoder registers a change at the very end of a pulse
      { // B is high so clockwise
        switch(select_state) {
          case  0: changeBrightness(true);      break;
          case  1: changeEffectSpeed(true);     break;
          case  2: changeEffectIntensity(true); break;
          case  3: changePalette(true);         break;
          case  4: changeEffect(true);          break;
          case  5: changeHue(true);             break;
          case  6: changeSat(true);             break;
          case  7: changeCCT(true);             break;
          case  8: changePreset(true);          break;
          case  9: changeCustom(1,true);        break;
          case 10: changeCustom(2,true);        break;
          case 11: changeCustom(3,true);        break;
        }
      }
      else if (Enc_B == HIGH)
      { // B is low so counter-clockwise
        switch(select_state) {
          case  0: changeBrightness(false);      break;
          case  1: changeEffectSpeed(false);     break;
          case  2: changeEffectIntensity(false); break;
          case  3: changePalette(false);         break;
          case  4: changeEffect(false);          break;
          case  5: changeHue(false);             break;
          case  6: changeSat(false);             break;
          case  7: changeCCT(false);             break;
          case  8: changePreset(false);          break;
          case  9: changeCustom(1,false);        break;
          case 10: changeCustom(2,false);        break;
          case 11: changeCustom(3,false);        break;
        }
      }
    }
    Enc_A_prev = Enc_A;     // Store value of A for next time
    loopTime = currentTime; // Updates loopTime
  }

Edit 2:
No. There’s no need to readPin() as it must have triggered an interrupt to be here. The z in AttachInterrupt(x,y,z) tells it at which event to trigger. Let me think about that a bit…

Edit 3:
Looking at this:


If pinA where monitored with

void RotaryEncoderUIUsermod::setup()
{
  ..
  ..
  attachInterrupt(pinA, Method_y , FALLING)
  ..
  ..
}

Method_y should look like this?

void IRAM_ATTR RotaryEncoderUIUsermod::Method_y () {
 if (display && display->wakeDisplay()) {
    display->redraw(true);
    // Throw away wake up input
    return;
  }
  bool Rotation_CW = pinA==pinB;
  switch(select_state) {
          case  0: changeBrightness(Rotation_CW );      break;
          case  1: changeEffectSpeed(Rotation_CW );     break;
          case  2: changeEffectIntensity(Rotation_CW ); break;
          case  3: changePalette(Rotation_CW );         break;
          case  4: changeEffect(Rotation_CW );          break;
          case  5: changeHue(Rotation_CW );             break;
          case  6: changeSat(Rotation_CW );             break;
          case  7: changeCCT(Rotation_CW );             break;
          case  8: changePreset(Rotation_CW );          break;
          case  9: changeCustom(1,Rotation_CW );        break;
          case 10: changeCustom(2,Rotation_CW );        break;
          case 11: changeCustom(3,Rotation_CW );        break;
        }
  }

and these are no longer necessary?

    Enc_A_prev = Enc_A;     // Store value of A for next time
    loopTime = currentTime; // Updates loopTime

Or would a delay still be recommended?

Lol. Not even close. Where can documentation be found on how to program interrupts in a user mod?

arduino documentation, but be cautious on using interrupts.

I was playing with a few sketches in the Arduino IDE. The interrupts aren’t the issue. In the mod I can’t figure out:

  1. Where to put the method called by the interrupt (Method_y ).
  2. How to declare Method_y . I.e. void IRAM_ATTR RotaryEncoderUIUsermod::Method_y () or static void IRAM_ATTR RotaryEncoderUIUsermod::Method_y () or void IRAM_ATTR Method_y ()or static void IRAM_ATTR Method_y ()
  3. What to enter as the second parameter of attachInterrupt(pinA, Method_y , FALLING)

I’ve tried multiple times but get all kinds of syntax errors related to those 3 points regardless. At this point I should mention that C++ is not a language I’m familiar with… :melting_face:

you have it completely wrong, your code above is not how a rotary encoder is read.

why not just use a rotary encoder library that has it all figured out including interrupts?

as a complete beginner in C++ coding, programming on an embedded system with no operating system is very much a challenging task and learning it is not something you do over a weekend.

What am I getting wrong, other than not knowing where to place them? In setup() I’d attach an interrupt to the pin, also assigning the method to be called. In the method (method_y in this case), I was just “using” the switch statement to call change*. If an interrupt is triggered. method y should be called. Am I mistaken?

I have a couple downloaded, but afaik I’d need to completely rewrite the mod. I was hoping to migrate the switch block…

:rofl: I am aware, but also tenacious. That’s why I was hoping to find a document pertaining to usermod programing with interrupts, or a basic usermod that employs Interrupts to use as a guide.

your encoder function does not make any sense is what I mean.

the ISR callback function cannot be inside of the class. define it globally outside the class, also use global variables, much easier since this is only for your personal use, you can do that.

1 Like

Thank you for the nudge. I think that advice will help.

I can compile my idea, but I have a logic error, and an error:

Guru Meditation Error: Core 1 panic’ed (LoadStoreError). Exception was unhandled.
Backtrace: 0x4011c46e:0x3ffb1eb0 0x40116c05:0x3ffb1f20 0x4012195c:0x3ffb1f40 0x40122182:0x3ffb1f90 0x40144aed:0x3ffb1fb0 0x4008ceea:0x3ffb1fd0
#0 0x4011c46e:0x3ffb1eb0 in RotaryEncoderUIUsermod::loop() at wled00/src/dependencies/json/ArduinoJson-v6.h:1609 (discriminator 4)
#1 0x40116c05:0x3ffb1f20 in UsermodManager::loop() at wled00/um_manager.cpp:9 (discriminator 3)
#2 0x4012195c:0x3ffb1f40 in WLED::loop() at wled00/bus_manager.h:122
#3 0x40122182:0x3ffb1f90 in loop() at wled00/wled_main.cpp:23
#4 0x40144aed:0x3ffb1fb0 in loopTask(void*) at C:/Users/David/.platformio/packages/framework-arduinoespressif32@src-e9b1fbd6563a55e19ddae15e1fc09589/cores/esp32/main.cpp:23
#5 0x4008ceea:0x3ffb1fd0 in vPortTaskWrapper at /home/cschwinne/esp32-arduino-lib-builder/esp-idf/components/freertos/port.c:355 (discriminator 1)

This happens as soon as I try to read (I haven’t gotten to writing yet) the bits of the global byte variable.

So far I have this outside the class:

static volatile uint8_t addrPcf8574 = PCF8574_ADDRESS;  // has to be accessible in ISR

//Daro Mod
/*State Transitions:
In a clockwise direction: 
0-0, 1-0, 1-1, 0-1, 0-0, 1-0, 0-1, etc. 
In a counterclockwise direction: 
0-0, 0-1, 1-1, 1-0, 0-0, 0-1, 1-1, etc.
Enc_Store 0 = Encoder_DT; 1 = Encoder_CLK; 2 =  Encoder_DT_Previous; 3 = Encoder_CLK_Previous
4 = Rotating Colckwise; 5 = State is valid; 6 = Okay to write value in loop()   */
static volatile IRAM_ATTR byte Enc_Store = 0b00000000;

static  void IRAM_ATTR isStateValid(){
  if (bitRead(Enc_Store,2) == 0 && bitRead(Enc_Store,3) == 0){
    if (bitRead(Enc_Store,0) == 1 && bitRead(Enc_Store,1) == 0){bitSet(Enc_Store,4); bitSet(Enc_Store,5); bitSet(Enc_Store,6);}
    if (bitRead(Enc_Store,0) == 0 && bitRead(Enc_Store,1) == 1){bitWrite(Enc_Store,4,false); bitWrite(Enc_Store,5, true); bitWrite(Enc_Store,6, true);} 
    return;
  }
  else if (bitRead(Enc_Store,2) == 0 && bitRead(Enc_Store,3) == 1){
    if (bitRead(Enc_Store,0) == 0 && bitRead(Enc_Store,1) == 0){bitSet(Enc_Store,4); bitSet(Enc_Store,5); bitSet(Enc_Store,6);}
    if (bitRead(Enc_Store,0) == 1 && bitRead(Enc_Store,1) == 1){bitWrite(Enc_Store,4,false); bitWrite(Enc_Store,5, true); bitWrite(Enc_Store,6, true);} 
    return;
  }
  else if (bitRead(Enc_Store,2) == 1 && bitRead(Enc_Store,3) == 0){
    if (bitRead(Enc_Store,0) == 1 && bitRead(Enc_Store,1) == 1){bitSet(Enc_Store,4); bitSet(Enc_Store,5); bitSet(Enc_Store,6);}
    if (bitRead(Enc_Store,0) == 0 && bitRead(Enc_Store,1) == 0){bitWrite(Enc_Store,4,false); bitWrite(Enc_Store,5, true); bitWrite(Enc_Store,6, true);} 
    return;
  }
  else if (bitRead(Enc_Store,2) == 1 && bitRead(Enc_Store,3) == 1){
    if (bitRead(Enc_Store,0) == 0 && bitRead(Enc_Store,1) == 1){bitSet(Enc_Store,4); bitSet(Enc_Store,5); bitSet(Enc_Store,6);}
    if (bitRead(Enc_Store,0) == 1 && bitRead(Enc_Store,1) == 0){bitWrite(Enc_Store,4,false); bitWrite(Enc_Store,5, true); bitWrite(Enc_Store,6, true);} 
    return;
  }
  //If the state is invalid, loop should not initiate action.
  bitWrite(Enc_Store,5, false); 
  bitWrite(Enc_Store,6, false);
  }

  static void IRAM_ATTR flankInteruptA() {
    noInterrupts();
    bitWrite(Enc_Store,0, digitalRead(ENCODER_DT_PIN)); 
    bitWrite(Enc_Store,1, digitalRead(ENCODER_CLK_PIN)); 
    isStateValid();
    if (! bitRead(Enc_Store,5)) {interrupts(); return;}
  
    bitWrite(Enc_Store,2, bitRead(Enc_Store,0));
    bitWrite(Enc_Store,3, bitRead(Enc_Store,1));
    interrupts();
  }
    //Daro Mod

// Interrupt routine to read I2C rotary state

In Setup I have this:

  initDone = true;
 //Daro mod
  // Enc_A = readPin(pinA); // Read encoder pins
  // Enc_B = readPin(pinB);
  // Enc_A_prev = Enc_A;
 
  Serial.println(F("Usermod Rotary Encoder setup() -  Attaching interrupt."));
  attachInterrupt(ENCODER_CLK_PIN, flankInteruptA, CHANGE); // RISING, FALLING, CHANGE, ONLOW, ONHIGH
  Serial.println(F("Usermod Rotary Encoder setup() - Interrupt attached."));
 //Daro mod
}

This is in loop():

     if (changedState) select_state = newState;
    }

    //Daro mod
    Serial.println(F("Usermod Rotary Encoder loop() - Turning off interrupts."));

    noInterrupts();
    Serial.println(F("Usermod Rotary Encoder loop() - Check if writting is enabled."));
    if (bitRead(Enc_Store,6)){
      Serial.println(F("Usermod Rotary Encoder loop() - Switch."));
      switch(select_state) {
        case  0: changeBrightness(bitRead(Enc_Store,4));      break;
        case  1: changeEffectSpeed(bitRead(Enc_Store,4));     break;
        case  2: changeEffectIntensity(bitRead(Enc_Store,4)); break;
        case  3: changePalette(bitRead(Enc_Store,4));         break;
        case  4: changeEffect(bitRead(Enc_Store,4));          break;
        case  5: changeHue(bitRead(Enc_Store,4));             break;
        case  6: changeSat(bitRead(Enc_Store,4));             break;
        case  7: changeCCT(bitRead(Enc_Store,4));             break;
        case  8: changePreset(bitRead(Enc_Store,4));          break;
        case  9: changeCustom(1,bitRead(Enc_Store,4));        break;
        case 10: changeCustom(2,bitRead(Enc_Store,4));        break;
        case 11: changeCustom(3,bitRead(Enc_Store,4));        break;
      }
      Serial.println(F("Usermod Rotary Encoder loop() - Turning on interrupts."));
      interrupts();
    }
    // Enc_A = readPin(pinA); // Read encoder pins
    // Enc_B = readPin(pinB);
    // if ((Enc_A) && (!Enc_A_prev))
    // { // A has gone from high to low
    //   if (Enc_B == LOW)    //changes to LOW so that then encoder registers a change at the very end of a pulse
    //   { // B is high so clockwise
    //     switch(select_state) {
    //       case  0: changeBrightness(true);      break;
    //       case  1: changeEffectSpeed(true);     break;
    //       case  2: changeEffectIntensity(true); break;
    //       case  3: changePalette(true);         break;
    //       case  4: changeEffect(true);          break;
    //       case  5: changeHue(true);             break;
    //       case  6: changeSat(true);             break;
    //       case  7: changeCCT(true);             break;
    //       case  8: changePreset(true);          break;
    //       case  9: changeCustom(1,true);        break;
    //       case 10: changeCustom(2,true);        break;
    //       case 11: changeCustom(3,true);        break;
    //     }
    //   }
    //   else if (Enc_B == HIGH)
    //   { // B is low so counter-clockwise
    //     switch(select_state) {
    //       case  0: changeBrightness(false);      break;
    //       case  1: changeEffectSpeed(false);     break;
    //       case  2: changeEffectIntensity(false); break;
    //       case  3: changePalette(false);         break;
    //       case  4: changeEffect(false);          break;
    //       case  5: changeHue(false);             break;
    //       case  6: changeSat(false);             break;
    //       case  7: changeCCT(false);             break;
    //       case  8: changePreset(false);          break;
    //       case  9: changeCustom(1,false);        break;
    //       case 10: changeCustom(2,false);        break;
    //       case 11: changeCustom(3,false);        break;
    //     }
    //   }
    // }
    // Enc_A_prev = Enc_A;     // Store value of A for next time
   //Daro mod
 
   loopTime = currentTime; // Updates loopTime
 

As soon as I get to
Serial.println(F(“Usermod Rotary Encoder loop() - Check if writting is enabled.”));
if (bitRead(Enc_Store,6)){

The ESP32 crashes. Do I need to acces global variables differently?

remove the IRAM_ATTR, other than that, don’t know.

I actually did that, and it stopped the crashes, but for some reason turning the encoder doesn’t trigger flankInteruptA()…

Whether it otherwise works, I can’t say because the WiFi never activates, so I can’t look.

Edit: I also changed the attachInterruptcall to:
attachInterrupt(digitalPinToInterrupt(ENCODER_CLK_PIN), flankInteruptA, CHANGE); // RISING, FALLING, CHANGE, ONLOW, ONHIGH

I used it here: WLED-ESPNow-Remote/source/WLED_ESPNow_Remote/WLED_ESPNow_Remote.ino at main · DedeHai/WLED-ESPNow-Remote · GitHub

You are the Wizmote creator? Nice job! :clap:

After many attempts with Serial.println() and re-compiling, I actually went with your other suggestion, and downloaded ESP32Encoder from Hephaestus with the PlatformIO library manager and added it to the project. The code modification is negligible, and using it in an Ardurino sketch works fine, but I can’t figure out how to compile it in PlatformIO because it requires libraries that seem to be in sub-directories of the %USERPROFILE%.platformio directory but aren’t being accessed.(?)

Are there tips to be found about how to get all these downstream dependencies integrated?

you just add the dependencies to your env AFAIK, see platformio.ini for examples

thanks, not actually THE Wizmote (but better :wink: )