Can anyone figure out why KEY_LEFT_SHIFT is not being sent out?

And yet ... here we all are

This is the

Wokwi version
//#include <Keyboard.h>

const int encoderClk = 2;  // Rotary encoder pin 1
const int encoderDT  = 3;  // Rotary encoder pin 2
const int buttonPin = 4;    // Rotary encoder button pin

int lastState;
int currentState;
int section = 0;

const int numSections = 7;
const char* sectionNames[] = {"Text Entry", "Types", "Instruments", "Styles", "Banks", "User", "Blank Section"};

#define KEY_TAB "KEY_TAB"
#define KEY_LEFT_SHIFT "KEY_LEFT_SHIFT"
#define KEY_RETURN "KEY_RETURN"


struct kbt {
  void begin();
  void write(String aKey);
  void release(String aKey);
} Keyboard;

void kbt::begin() {}
void kbt::write(String aKey) {
  Serial.print(aKey + " ");
}
void kbt::release(String aKey) {
  Serial.print(aKey + "_released ");
}

void setup() {
  Serial.begin(115200);
  Keyboard.begin();
  pinMode(encoderClk, INPUT_PULLUP);
  pinMode(encoderDT, INPUT_PULLUP);
  pinMode(buttonPin, INPUT_PULLUP);
  lastState = digitalRead(encoderClk);
}

void loop() {
  // Read the current state of the rotary encoder
  int state = digitalRead(encoderClk);
  if (state != lastState) {
    lastState = state;  // moved here from the wrong place ...
    if (!state && digitalRead(encoderDT) == HIGH) {
      section++;
      
      Serial.println("section++\t"+String(section));
      if (section >= numSections) { section = 0;  }
      if (section == 6) {
        // Send 5 tabs for Blank Section
        for (int i = 0; i < 5; i++) {
          Keyboard.write(KEY_TAB);
        }
      } else {
        Keyboard.write(KEY_TAB);
      }

      Serial.print("Section: ");
      Serial.println(sectionNames[section]);
      delay(100); // debounce delay

    } else {
      if (!state && digitalRead(encoderDT) == LOW){
        section--;
        Serial.println("section--\t"+String(section));
        if (section < 0) {
          section = numSections - 1;
        }
        if (section == 6) {
          // Send 5 shift+tabs for Blank Section
          Keyboard.write(KEY_LEFT_SHIFT);
          for (int i = 0; i < 5; i++) {
            Keyboard.write(KEY_TAB);
          }
          Keyboard.release(KEY_LEFT_SHIFT);
        } else {
          Keyboard.write(KEY_LEFT_SHIFT);
          Keyboard.write(KEY_TAB);
          Keyboard.release(KEY_LEFT_SHIFT);
          Keyboard.release(KEY_TAB);
        }
        Serial.print("Section: ");
        Serial.println(sectionNames[section]);
        delay(100); // debounce delay
      }  
    }
  }
  //lastState = state;  // Wrong place here!!!! Should be inside the "if (state != lastState))

  // Check if the encoder button has been pressed
  if (digitalRead(buttonPin) == LOW) {
    Keyboard.write(KEY_RETURN);
    Serial.println("Enter");
    delay(500); // debounce delay
    Keyboard.release(KEY_RETURN);
  }
}

See also https://wokwi.com/projects/361381330125280257

It is not the code I would personally write because the content of loop() is too long, I prefer smaller functions with meaningful names.

Chat GPT is just not using the encoder pins in the right way, I changed that.

Be aware that the sketch above is adopted to Wokwi.

This should (hopefully ) work on your machine:
(If not I kindly ask my fellow members to assist ... :wink: )

#include <Keyboard.h>

const int encoderClk = 2;  // Rotary encoder pin 1
const int encoderDT  = 3;  // Rotary encoder pin 2
const int buttonPin = 4;    // Rotary encoder button pin

int lastState;
int currentState;
int section = 0;

const int numSections = 7;
const char* sectionNames[] = {"Text Entry", "Types", "Instruments", "Styles", "Banks", "User", "Blank Section"};

/*
#define KEY_TAB "KEY_TAB"
#define KEY_LEFT_SHIFT "KEY_LEFT_SHIFT"
#define KEY_RETURN "KEY_RETURN"


struct kbt {
  void begin();
  void write(String aKey);
  void release(String aKey);
} Keyboard;

void kbt::begin() {}
void kbt::write(String aKey) {
  Serial.print(aKey + " ");
}
void kbt::release(String aKey) {
  Serial.print(aKey + "_released ");
}
*/

void setup() {
  Serial.begin(115200);
  Keyboard.begin();
  pinMode(encoderClk, INPUT_PULLUP);
  pinMode(encoderDT, INPUT_PULLUP);
  pinMode(buttonPin, INPUT_PULLUP);
  lastState = digitalRead(encoderClk);
}

void loop() {
  // Read the current state of the rotary encoder
  int state = digitalRead(encoderClk);
  if (state != lastState) {
    lastState = state;  // moved here from the wrong place ...
    if (!state && digitalRead(encoderDT) == HIGH) {
      section++;
      
      Serial.println("section++\t"+String(section));
      if (section >= numSections) { section = 0;  }
      if (section == 6) {
        // Send 5 tabs for Blank Section
        for (int i = 0; i < 5; i++) {
          Keyboard.write(KEY_TAB);
        }
      } else {
        Keyboard.write(KEY_TAB);
      }

      Serial.print("Section: ");
      Serial.println(sectionNames[section]);
      delay(100); // debounce delay

    } else {
      if (!state && digitalRead(encoderDT) == LOW){
        section--;
        Serial.println("section--\t"+String(section));
        if (section < 0) {
          section = numSections - 1;
        }
        if (section == 6) {
          // Send 5 shift+tabs for Blank Section
          Keyboard.write(KEY_LEFT_SHIFT);
          for (int i = 0; i < 5; i++) {
            Keyboard.write(KEY_TAB);
          }
          Keyboard.release(KEY_LEFT_SHIFT);
        } else {
          Keyboard.write(KEY_LEFT_SHIFT);
          Keyboard.write(KEY_TAB);
          Keyboard.release(KEY_LEFT_SHIFT);
          Keyboard.release(KEY_TAB);
        }
        Serial.print("Section: ");
        Serial.println(sectionNames[section]);
        delay(100); // debounce delay
      }  
    }
  }
  //lastState = state;  // Wrong place here!!!! Should be inside the "if (state != lastState))

  // Check if the encoder button has been pressed
  if (digitalRead(buttonPin) == LOW) {
    Keyboard.write(KEY_RETURN);
    Serial.println("Enter");
    delay(500); // debounce delay
    Keyboard.release(KEY_RETURN);
  }
}

as it is 10:45 pm now here in Central Europe ... and I will quit now for the next days ...

Good luck!
ec2021

Keyboard.write() presses and releases a key. Use Keyboard.press() for keys you want to hold down.

Thanks for the suggestion. Not knowing how to correctly implement this suggestion, I entered Serial.print(loop) on line 23 of the code, thinking that might be the way it works. As I'm sure you have predicted: it did not work.

Could you tell me how to write for this type of Serial.print?

I highly recommend that you go through this

https://www.arduino.cc/reference/en/

and the linked pages regarding Serial, data types etc.

The Arduino language reference is much better than trial and error with Chat GPT. Without knowledge of the basics you are lost.

Chat GPT seems to work (at least in this case) as a beginner who glues together different things found at more or less reliable places and is wondering why it's not working..

I also recommend to make use of the Wokwi simulation. If you add a sensor or actuator most of them come with examples how to use them. Load the examples and try to understand how they work. They are reliable and usually of a simple structure.

And finally I produced a version that might do what you want:

//#define WOKWI  // For use with keyboard.h just make this line a comment -> //#define WOKWI

#ifdef WOKWI
  #define KEY_TAB "KEY_TAB"
  #define KEY_LEFT_SHIFT "KEY_LEFT_SHIFT"
  #define KEY_RETURN "KEY_RETURN"

  struct kbt {
    void begin();
    void write(String aKey);
    void press(String aKey);
    void release(String aKey);
  } Keyboard;

  void kbt::begin() {}

  void kbt::write(String aKey) {
    Serial.println(aKey + "_press&released");
  }

  void kbt::press(String aKey) {
    Serial.println(aKey + "_pressed");
  }

  void kbt::release(String aKey) {
    Serial.println(aKey + "_released");
  }

#else
  #include <Keyboard.h>
#endif


const int encoderClk = 2;  // Rotary encoder pin 1
const int encoderDT  = 3;  // Rotary encoder pin 2
const int buttonPin  = 4;    // Rotary encoder button pin

int section = 0;

const int numSections = 7;
const char* sectionNames[] = {"Text Entry", "Types", "Instruments", "Styles", "Banks", "User", "Blank Section"};


enum  Direction  {NONE, LEFT, RIGHT};

struct encoderType {
  byte buttonPin;
  byte encoderClk;
  byte encoderDT;
  boolean buttonReleased();
  Direction turned();
  void init(byte bPin, byte ClkPin, byte DTPin);
};

void encoderType::init(byte bPin, byte ClkPin, byte DTPin) {
  encoderType::encoderClk = ClkPin;
  encoderType::encoderDT = DTPin;
  encoderType::buttonPin = bPin;
  pinMode(ClkPin, INPUT_PULLUP);
  pinMode(DTPin, INPUT_PULLUP);
  pinMode(bPin, INPUT_PULLUP);
}

boolean encoderType::buttonReleased() {
  static unsigned lastChange = 0;
  static byte state = HIGH;
  static byte lastState = HIGH;
  byte actState = digitalRead(encoderType::buttonPin);
  if (actState != lastState) {
    lastChange = millis();
    lastState = actState;
  }
  if (state != lastState && millis() - lastChange > 30) {
    state = lastState;
    if (state) return true;
  }
  return false;
}

Direction encoderType::turned() {
  static byte lastClk = HIGH;
  Direction returnValue = NONE;
  byte newClk = digitalRead(encoderType::encoderClk);
  if (newClk != lastClk) {
    // There was a change on the CLK pin
    lastClk = newClk;
    byte dtValue = digitalRead(encoderType::encoderDT);
    if (newClk == LOW && dtValue == HIGH) {  // clockwise
      returnValue = RIGHT;
    }
    if (newClk == LOW && dtValue == LOW) {  // counterclockwise
      returnValue = LEFT;
    }
  }
  return returnValue;
};

encoderType myEncoder;

void setup() {
  Serial.begin(115200);
  myEncoder.init(buttonPin, encoderClk, encoderDT);
  Keyboard.begin();
}


void loop() {
  Direction d = myEncoder.turned();
  if (d == LEFT)   DecreaseSection();
  if (d == RIGHT)  IncreaseSection();
  if (myEncoder.buttonReleased()) SendReturn();
}

void SendReturn() {
  Keyboard.write(KEY_RETURN);
}

void IncreaseSection() {
  section++;
  if (section >= numSections) {
    section = 0;
  }
  Serial.println("section++\t" + String(section));
  if (section == 6) {
    // Send 5 tabs for Blank Section
    for (int i = 0; i < 5; i++) {
      Keyboard.write(KEY_TAB);
    }
  } else {
    Keyboard.write(KEY_TAB);
  }
  Serial.print("Section: ");
  Serial.println(sectionNames[section]);
}

void DecreaseSection() {
  section--;
  if (section < 0) {
    section = numSections - 1;
  }
  Serial.println("section--\t" + String(section));
  if (section == 6) {
    // Send 5 shift+tabs for Blank Section
    Keyboard.press(KEY_LEFT_SHIFT);
    for (int i = 0; i < 5; i++) {
      Keyboard.write(KEY_TAB);
    }
    Keyboard.release(KEY_LEFT_SHIFT);
  } else {
    Keyboard.press(KEY_LEFT_SHIFT);
    Keyboard.write(KEY_TAB);
    Keyboard.release(KEY_LEFT_SHIFT);
  }
  Serial.print("Section: ");
  Serial.println(sectionNames[section]);
}

see also https://wokwi.com/projects/361432757751536641

If you un-comment the first line it should also compile on your board but instead of sending keys via USB it will print readable text to Serial. This way you can check if it behaves like you intended.

I also tried to consider what @oqibidipo wrote in post #23 regarding ".press" and ".write" ...

Good luck!

Random thought: I wonder if ChatGPT trained on Instructables.

2 Likes

Thank you so much for all your help, ec-that's very nice of you!

Alas, none of the code attempts really work. This last one has definitely come much closer: it is making the cursor go backwards as well as forwards through the sections, and that's one of the more important design considerations I have had. There's a lot of bouncing going on, however, and I'm still not quite enough up to speed to be able to go into someone else's code and figure out what's wrong.

So over the course of yesterday, this has become clear to me: the biggest thing standing in my way of making that code I've posted work properly is my deep igonorance on how to properly program Encoders. They have seemed extremely wonky (and they are)-especially as compared to Pots (or-even though they're considered digital-even Buttons). This fact is also what makes them so powerful.

I discovered a couple of vids on YouTube: one by MoThunders and one by Mario's Ideas, and they both were very good explainers on the subject, so much so that I have been encouraged enough to say I think the only solution to this whole thing is to code it myself. I don't know a lot, but I know a lot more today than yesterday, and so I'm just going to buckle down and learn how to properly program Rotary Encoders.

I need to do this anyway, just on principle: I have a lot of projects which are going to require them, and I can depend on the good graces of posters on this forum (like you) only so much.

If you're going to make any headway with programming, can I suggest that you sharpen-up your attention to detail?

Maybe your wiring is the reason ...

There is one thing you can easily try:

Change the pin no. of the encoderClk to 3 and encoderDT to 2 and upload that.

And might I suggest to you that if you want something, a much nicer way of going about getting it is just to ask for it, rather than questioning someone’s abilities.

I didn’t initially share the link to this video deliberately, and for specific reasons, but I am more than happy to share it for anyone who shows interest.

I will try that when I get home-thanks for the suggestion!

Cattledog earlier made a suggestion about rewriting the code as a State Machine. Mo Thunders actually speaks about writing up a State Machine in this video.

It’s a really good video. Very clear and informative.

In the meantime I googled a bit about rotary encoder debouncing and found several links, here are just a few::

https://forum.arduino.cc/t/debounce-rotary-encoder/967691

https://garrysblog.com/2021/03/20/reliably-debouncing-rotary-encoders-with-arduino-and-esp32/

https://www.best-microcontroller-projects.com/rotary-encoder.html

From the latter I have taken the function to read the encoder and very slightly modified it to fit into the existing sketch from above. It should work with most "nervous" rotary encoders ...

//#define WOKWI  // For use with keyboard.h just make this line a comment -> //#define WOKWI

#ifdef WOKWI
  #define KEY_TAB "KEY_TAB"
  #define KEY_LEFT_SHIFT "KEY_LEFT_SHIFT"
  #define KEY_RETURN "KEY_RETURN"

  struct kbt {
    void begin();
    void write(String aKey);
    void press(String aKey);
    void release(String aKey);
  } Keyboard;

  void kbt::begin() {}

  void kbt::write(String aKey) {
    Serial.println(aKey + "_press&released");
  }

  void kbt::press(String aKey) {
    Serial.println(aKey + "_pressed");
  }

  void kbt::release(String aKey) {
    Serial.println(aKey + "_released");
  }

#else
  #include <Keyboard.h>
#endif


const int encoderClk = 2;  // Rotary encoder pin 1
const int encoderDT  = 3;  // Rotary encoder pin 2
const int buttonPin  = 4;    // Rotary encoder button pin

int section = 0;

const int numSections = 7;
const char* sectionNames[] = {"Text Entry", "Types", "Instruments", "Styles", "Banks", "User", "Blank Section"};


enum  Direction  {NONE, LEFT, RIGHT};

struct encoderType {
  byte buttonPin;
  byte encoderClk;
  byte encoderDT;
  boolean buttonReleased();
  Direction turned();
  void init(byte bPin, byte ClkPin, byte DTPin);
};

void encoderType::init(byte bPin, byte ClkPin, byte DTPin) {
  encoderType::encoderClk = ClkPin;
  encoderType::encoderDT = DTPin;
  encoderType::buttonPin = bPin;
  pinMode(ClkPin, INPUT_PULLUP);
  pinMode(DTPin, INPUT_PULLUP);
  pinMode(bPin, INPUT_PULLUP);
}

boolean encoderType::buttonReleased() {
  static unsigned lastChange = 0;
  static byte state = HIGH;
  static byte lastState = HIGH;
  byte actState = digitalRead(encoderType::buttonPin);
  if (actState != lastState) {
    lastChange = millis();
    lastState = actState;
  }
  if (state != lastState && millis() - lastChange > 30) {
    state = lastState;
    if (state) return true;
  }
  return false;
}

// The following function was derived from https://www.best-microcontroller-projects.com/rotary-encoder.html
Direction encoderType::turned() {
  static int8_t rot_enc_table[] = {0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0};
  static uint8_t prevNextCode = 0;
  static uint16_t store=0;
  prevNextCode <<= 2;
  if (digitalRead(encoderType::encoderDT)) prevNextCode |= 0x02;
  if (digitalRead(encoderType::encoderClk)) prevNextCode |= 0x01;
  prevNextCode &= 0x0f;

   // If valid then store as 16 bit data.
   if  (rot_enc_table[prevNextCode] ) {
      store <<= 4;
      store |= prevNextCode;
      if ((store&0xff)==0x2b) return LEFT;
      if ((store&0xff)==0x17) return RIGHT;
   }
   return NONE;
}

encoderType myEncoder;

void setup() {
  Serial.begin(115200);
  myEncoder.init(buttonPin, encoderClk, encoderDT);
  Keyboard.begin();
}


void loop() {
  Direction d = myEncoder.turned();
  if (d == LEFT)   DecreaseSection();
  if (d == RIGHT)  IncreaseSection();
  if (myEncoder.buttonReleased()) SendReturn();
}

void SendReturn() {
  Keyboard.write(KEY_RETURN);
}

void IncreaseSection() {
  section++;
  if (section >= numSections) {
    section = 0;
  }
  Serial.println("section++\t" + String(section));
  if (section == 6) {
    // Send 5 tabs for Blank Section
    for (int i = 0; i < 5; i++) {
      Keyboard.write(KEY_TAB);
    }
  } else {
    Keyboard.write(KEY_TAB);
  }
  Serial.print("Section: ");
  Serial.println(sectionNames[section]);
}

void DecreaseSection() {
  section--;
  if (section < 0) {
    section = numSections - 1;
  }
  Serial.println("section--\t" + String(section));
  if (section == 6) {
    // Send 5 shift+tabs for Blank Section
    Keyboard.press(KEY_LEFT_SHIFT);
    for (int i = 0; i < 5; i++) {
      Keyboard.write(KEY_TAB);
    }
    Keyboard.release(KEY_LEFT_SHIFT);
  } else {
    Keyboard.press(KEY_LEFT_SHIFT);
    Keyboard.write(KEY_TAB);
    Keyboard.release(KEY_LEFT_SHIFT);
  }
  Serial.print("Section: ");
  Serial.println(sectionNames[section]);
}

The test version to be found at https://wokwi.com/projects/361719624996514817

Please make sure that the CLK pin of the encoder is connected to pin 2 and the DT pin is connected to pin 3 of your board!

State Machines are absolutely useful if you have an application that stays in different states and leaves theses states when certain events take place. In this specific case the application is generally idling around in loop() waiting for one of three events:

  • Right turn
  • Left turn
  • Button pressed

to take a specific action. It returns immediately after the action to loop(). That can be handled without a state machine ... Anyway it is worth to learn how that concept works!

Good luck!

...or find another pin for DT, and not waste the chance to use pin 3's interrupt for a second encoder

I tried to leave at least this as Chat GPT suggested :wink:

(I agree with you but would already be glad if the encoder would do something reliable; some said (see the arduino forum post I linked) that using an interrupt would be required for some encoders ...)

And we have not talked about the TO's application that shall be controlled. Looks like the UI of a MIDI software on a PC. The Tabs and Shift-Tabs require that encoder software and UI are synchronized all the time.

I would prefer to have hot keys in the PC application instead of "tabbing" around ...

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.