Rotary encoder menu system

Hello!

I'm a real beginner at code, although I'm pretty confident at the hardware side.

I'm working on a menu for an audio processor that is driven by rotary encoder with a push button. However, I can't make the rotary encoder and push button behave as expected. What I wish to achieve is behaviour like this:

Main menu page - The rotary encoder adjusts volume and prints the decibel (dB) value on the serial terminal. It also prints audio input and tone value settings. I did this with a void updateDisplay so don't need to repeat it.

If the button is pressed, the menu progresses to the next page. I did this with a switch case function.

Menu 2 - Selects audio input. If the button is pressed but the audio input selection has not changed it progresses to the next menu page.

If the button is pressed and the audio input selection has been changed by the rotary encoder, it sets that input to current and returns to the main page.

Menu 3 - If the button is pressed but the tone control value has not changed it goes back to the main page as this is the last menu.

If the button is pressed and the tone control value has been changed by the rotary encoder, it sets that tone value and returns to the main page.

I will use those set values for volume, audio input and tone value to write the relevant memory addresses to the audio processor over I2C, but that code is currently in a separate project.

The behaviour I am getting is that I see the serial print repeated 3 times when the rotary encoder is moved one step. Additionally the button logic to either progress to the next menu if nothing has been changed, or set the new setting to current if something has been changed, doesn't seem to work and I can't see the logic in what it is doing.

Appreciate some help!

// Rotary encoder variables
const byte inputCLK = 3; // Arduino pin the rotary encoder CLK pin is connected to.
const byte inputDT = 2; // Arduino pin the rotary encoder DT pin is connected to.
int currentStateCLK; // Current state of rotary encoder pin CLK. Used to identify rotation.
int previousStateCLK; // Current state of rotary encoder pin CLK. Used to identify rotation.
boolean encCCW; // Rotary encoder flag to denote counter-clockwise rotatation by one pulse.
boolean encCW; // Rotary encoder flag to denote clockwise rotatation by one pulse.

// Push button variables
const byte buttonPin = 4; // Arduino pin the push button is connected to.
byte previousButtonState = HIGH;  // Assume switch open because of pull-up resistor.
byte currentButtonState; // Will be populated by reading button pin satae.
const unsigned long debounceTime = 5;  // Milliseconds of debounce.
unsigned long buttonPressTime;  // Time since button last changed state. Used to differentialte button bounce vs. intentional press.
boolean buttonPressed = 0; // A flag variable to identify button presses. Returned from button debounce code. 

// Menu mode change variables
int mode = 1;  // Mode variable used to run switch case. 1 = volume, 2 = audio input, 3 = tone control.
int currentInputNumber = 1; // Audio input the system is currently set to.
int newInputNumber = 1; // Audio input user is in the process of choosing by rotary control.
int currentToneValue = 5; // Current tone control setting.
int newToneValue = 5; // Tone control setting the user is in the process of choosing by rotary control.

// Gain variables 
float volume; // Volume level in dB.
float linGain; // Volume level as a linear gain value between 0 (no level) and 1 (full level).
uint32_t vol32; // Linear gain as a 32 bit word to populate hex array.
byte volArray[4]; // 4 byte array to hold volume hex values ready to send over I2C to the audio processor.

void setup() { 
    
 volume = -45; // Set default volume level to -45dB.
    
 // Set encoder and push button pins as inputs.  
 pinMode (inputCLK,INPUT);
 pinMode (inputDT,INPUT);
 pinMode (buttonPin, INPUT_PULLUP);
 encCCW = 0; // Initial state for rotation flag not rotated.
 encCW = 0; // Initial state for rotation flag not rotated.
 previousStateCLK = digitalRead(inputCLK); // Read initial state of rotary encoder CLK pin. Assign to previousStateCLK so we can check for state changes.
 
 // Setup Serial Monitor
 Serial.begin (9600);
} 

void loop() {

  // Rotary encoder code.
  currentStateCLK = digitalRead(inputCLK); // Read the current state of inputCLK pin.

  if (currentStateCLK != previousStateCLK) { // If the previous and the current state of inputCLK are different, then the encoder and been rotated.
    if (currentStateCLK != digitalRead(inputDT)) { // If the inputCLK state is different than the inputDT state then the rotation is counterclockwise.
      encCCW = 1; // Set rotation flag counter-clockwise.
    }
    else { // If inputCLK and inputDT are the same then encoder is not rotating CCW.
      encCW = 1; // Set rotation flag clockwise.
    }
  }

  // Button press code with debounce.
  currentButtonState = digitalRead (buttonPin); 
  if (currentButtonState != previousButtonState){
    if (millis () - buttonPressTime >= debounceTime){ // debounce
      buttonPressTime = millis ();  // when we closed the switch 
      previousButtonState = currentButtonState;  // remember for next time 
      if (currentButtonState == LOW){ // When debounce criteria are met and button is still pressed.
        buttonPressed = 1; // Set button flag to 1.
      }
      else { // When debounce criteria are not met because boutton is not pressed or it is bouncing.
        buttonPressed = 0;  // Set button flag to 0.
      }
    }
  }

  // Limit Mode (menu page) and loop back to main menu.
  if (mode >= 4){
   mode = 1; 
  }
 
  // Convert dB volume level to hex array for sending by I2C to the audio processor.
  linGain = pow(10, volume/20); // Convert dB level to linear gain value.
  vol32 = linGain * 16777216; // Convert linear gain value stored as a float variable to a 32 bit integer.
  volArray[0] = (vol32 >> 24) & 0xFF; // Populate first byte of array with most significant 8 bits of the 32 bit volume word, by shifting word right by 24 bits. Adding 0xFF denotes it as a hex value.
  volArray[1] = (vol32 >> 16) & 0xFF; // Populate second byte of array with next most significant 8 bits of the 32 bit volume word, by shifting word right by 16 bits. Adding 0xFF denotes it as a hex value.
  volArray[2] = (vol32 >> 8) & 0xFF; // Populate third byte of array with next most significant 8 bits of the 32 bit volume word, by shifting word right by 8 bits. Adding 0xFF denotes it as a hex value.
  volArray[3] = vol32 & 0xFF; // Populate fourth byte of array with least significant 8 bits of the 32 bit volume word, naturally the first 8 bits of the word. Adding 0xFF denotes it as a hex value.
        

  switch (mode) { // Each menu page is a case operated by the 'mode' variable.
   
    case 1: // Main menu page, volume control.
     
      if (encCCW == 1) { // If the rotary encoder has been rotated counter-clockwise.
       volume --; // Decrement volume level by one. 
       updateDisplay(); 
      }

      if (encCW == 1) { // If the rotary encoder has been rotated clockwise.
        volume ++; // Increment volume level by one.   
        updateDisplay(); 
      }

      if (buttonPressed == 1) {
        mode = mode + 1 ; // If the button is pressed while on the main volume page (case 1), the menu progresses to the next page (case 2).
        updateDisplay(); 
      }


   
    // End of case 1.
      
    case 2:
     
      if (encCCW == 1) { // If the rotary encoder has been rotated counter-clockwise.
        newInputNumber -= 1; // subtract 1 from input number.
        updateDisplay(); 
      }

      if (encCW == 1) { // If the rotary encoder has been rotated clockwise.
        newInputNumber += 1; // Add 1 to the input number. 
        updateDisplay(); 
      }

      if (newInputNumber >= 6){ // If input number is more than 5 then loop back to 1.
         newInputNumber = 1;
      }

      if (newInputNumber <= 0){ // If input number is less than 1 then loop back to 5.
        newInputNumber = 5;
      }   

      if (buttonPressed == 1) {
        if(newInputNumber == currentInputNumber){ // If the button is pressed without changing input selection.
         mode = mode + 1 ; // Increment mode by 1 to progress to the next menu page.
         updateDisplay(); 
        }

        else{ // A new audio input is being selected.
         currentInputNumber = newInputNumber; // Set the current audio input to the new selection.
         mode = 1; // Go back to the main menu page (case 1)
         updateDisplay(); 
        }
      }


    // End of case 2.
    
    case 3:
     
      if (encCCW == 1) { // If the rotary encoder has been rotated counter-clockwise.
        newToneValue -= 1; // subtract 1 from tone value.
        updateDisplay(); 
      }

      if (encCW == 1) { // If the rotary encoder has been rotated clockwise.
        newToneValue += 1; // Add 1 to the tone value. 
        updateDisplay(); 
      }

      if (newToneValue >= 10){ // If tone value is more than 9.
        newToneValue = 9; // Limit tone value to 9.
      }

      if (newToneValue <= 0){ // If tone value is less than 1.
        newToneValue = 1; // Limit tone value to 1.
      }   

      if (buttonPressed == 1) {
        if(newToneValue == currentToneValue){ // If the button is pressed without changing input selection.
         mode = 1 ; // Go back to the main menu page, because this is the last menu.
         updateDisplay(); 
        }

        else{ // A new tone value has been selected.
         currentToneValue = newToneValue; // Set the current tone value to the new tone value.
         mode = 1; // Go back to the main menu page (case 1).
         updateDisplay(); 
        }
      }

    // End of case 3.

  } // End of switch.

  // Rotary encoder reset for next loop around.  
  previousStateCLK = currentStateCLK; // Update previousStateCLK with the current state of inputCLK.
  encCCW = 0; // Reset encoder counter-clockwise flag.
  encCW = 0; // Reset encoder clockwise flag.
} // end of loop.

void updateDisplay(){ // Display the menu mode, volume level and current audio input.
Serial.print("Mode:");
Serial.println(mode);
Serial.print("Volume: ");
Serial.print(volume);
Serial.println("dB");
Serial.print("Current Input Number:");
Serial.println(currentInputNumber);
Serial.print("New Input Number:");
Serial.println(newInputNumber);
Serial.print("Current Tone Control Value:");
Serial.println(currentToneValue);
Serial.print("New Tone Control Value:");
Serial.println(newToneValue);
Serial.println();
}


Which encoder? Post a link.

However, I can't make the rotary encoder and push button behave as expected.

Get this working perfectly before writing all the application code. I recommend the Stoffregen encoder library.

Sorry, I forgot to say the hardware.

Arduino Uno compatible.
Encoder is a clone of the 'KEYS' one.

I don't believe there is an issue with the encoder itself as it works with more simple code.

Did you write a short program to exercise the rotary encoder so you are confident in how to code for it?
Paul

What would that code be, and why are you not using it? Your problem is that you wrote all that application code, without making sure that the encoder is working properly.

This is the code I can get to work.

Rotary encoder part:

// Rotary encoder variables
const byte inputCLK = 3; // Arduino pin the rotary encoder CLK pin is connected to.
const byte inputDT = 2; // Arduino pin the rotary encoder DT pin is connected to.
int currentStateCLK; // Current state of rotary encoder pin CLK. Used to identify rotation.
int previousStateCLK; // Current state of rotary encoder pin CLK. Used to identify rotation.
boolean encCCW; // Flag that the encoder has been rotated counter-clockwise one step.
boolean encCW; // Flag that the encoder has been rotated clockwise one step.

void setup() {


  // Set encoder and push button pins as inputs.
  pinMode (inputCLK, INPUT);
  pinMode (inputDT, INPUT);

  encCCW = 0; // Initial state for rotation flag not rotated.
  encCW = 0; // Initial state for rotation flag not rotated.

  // Read initial state of rotary encoder CLK pin. Assign to previousStateCLK so we can check for state changes when the loop begins.
  previousStateCLK = digitalRead(inputCLK);

  // Setup Serial Monitor
  Serial.begin (9600);

}

void loop() {

  //This code must go at the start of the loop.

  // Read the current state of inputCLK so we can compare it.
  currentStateCLK = digitalRead(inputCLK);

  // Rotary encoder code.
  if (currentStateCLK != previousStateCLK) { // If the previous and the current state of inputCLK are different, then the encoder and been rotated.
    if (currentStateCLK != digitalRead(inputDT)) { // If the inputCLK state is different than the inputDT state then the rotation is counterclockwise.
      encCCW = 1; // set rotation flag high.
    }
    else {
      // Encoder is not rotating CCW
      encCW = 1;
    }
  }


  //This is the middle of the loop. Do stuff with encCCW and encCW here.
  // Serial print that the encoder has been rotated.
  if (encCCW == 1) {
    Serial.println("Counter-clockwise");
  }
  if (encCW == 1) {
    Serial.println("Clockwise");
  }

  //This code must go at the end of the loop to reset the encoder variables for the next time around.

  previousStateCLK = currentStateCLK; // Update previousStateCLK with the current state
  encCCW = 0; // Reset encCCW.
  encCW = 0; // Reset encCW.
}




I just tested my basic button code and it's not working :stuck_out_tongue: I must have screwed something up, as it was working before! I'll get back to you!

Here is the button press code that works and as far as far as I can see is the same as I'm using in my main program. Only the conditions of what happens when the button is pressed is differnt.

// Push button variables
const byte buttonPin = 4; // Arduino pin the push button is connected to.
byte previousButtonState = HIGH;  // Assume switch open because of pull-up resistor.
byte currentButtonState; // Will be populated by reading button pin satae.
const unsigned long debounceTime = 10;  // Milliseconds of debounce.
unsigned long buttonPressTime;  // Time since button last changed state. Used to differentialte button bounce vs. intentional press.
boolean buttonPressed = 0; // A flag variable to identify button presses. Returned from button debounce code. 

void setup() {
     pinMode (buttonPin, INPUT_PULLUP); // Set button pin as input.
Serial.begin(9600); // Begin Serial monitor.
}

void loop() {
  
  // Button press code with debounce.
  currentButtonState = digitalRead (buttonPin); 
  if (currentButtonState != previousButtonState){
    if (millis () - buttonPressTime >= debounceTime){ // debounce
      buttonPressTime = millis ();  // when we closed the switch 
      previousButtonState = currentButtonState;  // remember for next time 
      if (currentButtonState == LOW){ // When debounce criteria are met and button is still pressed.
        buttonPressed = 1; // Set button flag to 1.
        Serial.println("Button Pressed");
      }
      else { // When debounce criteria are not met because boutton is not pressed or it is bouncing.
        buttonPressed = 0;  // Set button flag to 0.
      }
    }
  }

}

It's something to do with the fact I'm using the 'buttonPressed' flag from outside of the button press code.

This works:

// Push button variables
const byte buttonPin = 4; // Arduino pin the push button is connected to.
byte previousButtonState = HIGH;  // Assume switch open because of pull-up resistor.
byte currentButtonState; // Will be populated by reading button pin satae.
const unsigned long debounceTime = 10;  // Milliseconds of debounce.
unsigned long buttonPressTime;  // Time since button last changed state. Used to differentialte button bounce vs. intentional press.
boolean buttonPressed = 0; // A flag variable to identify button presses. Returned from button debounce code. 

void setup() {
  pinMode (buttonPin, INPUT_PULLUP); // Set button pin as input.
  Serial.begin(9600); // Begin Serial monitor.
}

void loop() {
  
  // Button press code with debounce.
  currentButtonState = digitalRead (buttonPin); 
  if (currentButtonState != previousButtonState){
    if (millis () - buttonPressTime >= debounceTime){ // debounce
      buttonPressTime = millis ();  // when we closed the switch 
      previousButtonState = currentButtonState;  // remember for next time 
      if (currentButtonState == LOW){ // When debounce criteria are met and button is still pressed.
        buttonPressed = 1; // Set button flag to 1.
        Serial.println("Button Pressed");
      }
      else { // When debounce criteria are not met because boutton is not pressed or it is bouncing.
        buttonPressed = 0;  // Set button flag to 0.
      }
    }
  }
  
 
}

This doesn't:

// Push button variables
const byte buttonPin = 4; // Arduino pin the push button is connected to.
byte previousButtonState = HIGH;  // Assume switch open because of pull-up resistor.
byte currentButtonState; // Will be populated by reading button pin satae.
const unsigned long debounceTime = 10;  // Milliseconds of debounce.
unsigned long buttonPressTime;  // Time since button last changed state. Used to differentialte button bounce vs. intentional press.
boolean buttonPressed = 0; // A flag variable to identify button presses. Returned from button debounce code. 

void setup() {
  pinMode (buttonPin, INPUT_PULLUP); // Set button pin as input.
  Serial.begin(9600); // Begin Serial monitor.
}

void loop() {
  
  // Button press code with debounce.
  currentButtonState = digitalRead (buttonPin); 
  if (currentButtonState != previousButtonState){
    if (millis () - buttonPressTime >= debounceTime){ // debounce
      buttonPressTime = millis ();  // when we closed the switch 
      previousButtonState = currentButtonState;  // remember for next time 
      if (currentButtonState == LOW){ // When debounce criteria are met and button is still pressed.
        buttonPressed = 1; // Set button flag to 1.
     
      }
      else { // When debounce criteria are not met because boutton is not pressed or it is bouncing.
        buttonPressed = 0;  // Set button flag to 0.
      }
    }
  }
  
   if(buttonPressed == 1){
   Serial.println("Button Pressed");
   }
}

remove this

and add

buttonPressed = 0;

where you act on the button, like this

   if(buttonPressed == 1){
       Serial.println("Button Pressed");
       buttonPressed = 0;
   }

If you like that, maybe buttonPressed could have a better name.

a7

Thanks for the help! That does indeed seem to get the button press registering only once.

I now have another issue. In each menu case I want the serial monitor to be updated when I move the rotary encoder. So I added 'updateDisplay' inside each if statement - a void I wrote at the bottom of the code. See line 92 and line 97 for example.

This works okay, until I add the same to case 2 and case 3. This way regardless of which case I am in, it posts to the serial monitor as many times as I have cases. I thought only code within the active case is run?

This code only prints once, but when I change case I then get no serial monitor output.

// Rotary encoder variables
const byte inputCLK = 3; // Arduino pin the rotary encoder CLK pin is connected to.
const byte inputDT = 2; // Arduino pin the rotary encoder DT pin is connected to.
int currentStateCLK; // Current state of rotary encoder pin CLK. Used to identify rotation.
int previousStateCLK; // Current state of rotary encoder pin CLK. Used to identify rotation.
boolean encCCW; // Rotary encoder flag to denote counter-clockwise rotatation by one pulse.
boolean encCW; // Rotary encoder flag to denote clockwise rotatation by one pulse.

// Push button variables
const byte buttonPin = 4; // Arduino pin the push button is connected to.
byte previousButtonState = HIGH;  // Assume switch open because of pull-up resistor.
byte currentButtonState; // Will be populated by reading button pin satae.
const unsigned long debounceTime = 5;  // Milliseconds of debounce.
unsigned long buttonPressTime;  // Time since button last changed state. Used to differentialte button bounce vs. intentional press.
boolean buttonPressed = 0; // A flag variable to identify button presses. Returned from button debounce code. 

// Menu mode change variables
int mode = 1;  // Mode variable used to run switch case. 1 = volume, 2 = audio input, 3 = tone control.
int currentInputNumber = 1; // Audio input the system is currently set to.
int newInputNumber = 1; // Audio input user is in the process of choosing by rotary control.
int currentToneValue = 5; // Current tone control setting.
int newToneValue = 5; // Tone control setting the user is in the process of choosing by rotary control.

// Gain variables 
float volume; // Volume level in dB.
float linGain; // Volume level as a linear gain value between 0 (no level) and 1 (full level).
uint32_t vol32; // Linear gain as a 32 bit word to populate hex array.
byte volArray[4]; // 4 byte array to hold volume hex values ready to send over I2C to the audio processor.

void setup() { 
    
 volume = -45; // Set default volume level to -45dB.
    
 // Set encoder and push button pins as inputs.  
 pinMode (inputCLK,INPUT);
 pinMode (inputDT,INPUT);
 pinMode (buttonPin, INPUT_PULLUP);
 encCCW = 0; // Initial state for rotation flag not rotated.
 encCW = 0; // Initial state for rotation flag not rotated.
 previousStateCLK = digitalRead(inputCLK); // Read initial state of rotary encoder CLK pin. Assign to previousStateCLK so we can check for state changes.
 
 // Setup Serial Monitor
 Serial.begin (9600);
} 

void loop() {

  // Rotary encoder code.
  currentStateCLK = digitalRead(inputCLK); // Read the current state of inputCLK pin.

  if (currentStateCLK != previousStateCLK) { // If the previous and the current state of inputCLK are different, then the encoder and been rotated.
    if (currentStateCLK != digitalRead(inputDT)) { // If the inputCLK state is different than the inputDT state then the rotation is counterclockwise.
      encCCW = 1; // Set rotation flag counter-clockwise.
    }
    else { // If inputCLK and inputDT are the same then encoder is not rotating CCW.
      encCW = 1; // Set rotation flag clockwise.
    }
  }

  // Button press code with debounce.
  currentButtonState = digitalRead (buttonPin); 
  if (currentButtonState != previousButtonState){
    if (millis () - buttonPressTime >= debounceTime){ // debounce
      buttonPressTime = millis ();  // when we closed the switch 
      previousButtonState = currentButtonState;  // remember for next time 
      if (currentButtonState == LOW){ // When debounce criteria are met and button is still pressed.
        buttonPressed = 1; // Set button flag to 1.
      }
    }
  }

  // Limit Mode (menu page) and loop back to main menu.
  if (mode >= 4){
   mode = 1; 
  }
 
  // Convert dB volume level to hex array for sending by I2C to the audio processor.
  linGain = pow(10, volume/20); // Convert dB level to linear gain value.
  vol32 = linGain * 16777216; // Convert linear gain value stored as a float variable to a 32 bit integer.
  volArray[0] = (vol32 >> 24) & 0xFF; // Populate first byte of array with most significant 8 bits of the 32 bit volume word, by shifting word right by 24 bits. Adding 0xFF denotes it as a hex value.
  volArray[1] = (vol32 >> 16) & 0xFF; // Populate second byte of array with next most significant 8 bits of the 32 bit volume word, by shifting word right by 16 bits. Adding 0xFF denotes it as a hex value.
  volArray[2] = (vol32 >> 8) & 0xFF; // Populate third byte of array with next most significant 8 bits of the 32 bit volume word, by shifting word right by 8 bits. Adding 0xFF denotes it as a hex value.
  volArray[3] = vol32 & 0xFF; // Populate fourth byte of array with least significant 8 bits of the 32 bit volume word, naturally the first 8 bits of the word. Adding 0xFF denotes it as a hex value.
        

  switch (mode) { // Each menu page is a case operated by the 'mode' variable.
   
    case 1: // Main menu page, volume control.
     
      if (encCCW == 1) { // If the rotary encoder has been rotated counter-clockwise.
       volume --; // Decrement volume level by one. 
       updateDisplay(); 
      }

      if (encCW == 1) { // If the rotary encoder has been rotated clockwise.
        volume ++; // Increment volume level by one.   
        updateDisplay(); 
      }

      if (buttonPressed == 1) {
        mode = mode + 1 ; // If the button is pressed while on the main volume page (case 1), the menu progresses to the next page (case 2).
      }

  buttonPressed = 0;
   
    // End of case 1.
      
    case 2:
     
      if (encCCW == 1) { // If the rotary encoder has been rotated counter-clockwise.
        newInputNumber -= 1; // subtract 1 from input number.
      }

      if (encCW == 1) { // If the rotary encoder has been rotated clockwise.
        newInputNumber += 1; // Add 1 to the input number. 
      }

      if (newInputNumber >= 6){ // If input number is more than 5 then loop back to 1.
         newInputNumber = 1;
      }

      if (newInputNumber <= 0){ // If input number is less than 1 then loop back to 5.
        newInputNumber = 5;
      }   

      if (buttonPressed == 1) {
        if(newInputNumber == currentInputNumber){ // If the button is pressed without changing input selection.
         mode = mode + 1 ; // Increment mode by 1 to progress to the next menu page.
        }

        else{ // A new audio input is being selected.
         currentInputNumber = newInputNumber; // Set the current audio input to the new selection.
         mode = 1; // Go back to the main menu page (case 1)

        }
      }

  buttonPressed = 0;
    // End of case 2.
    
    case 3:
     
      if (encCCW == 1) { // If the rotary encoder has been rotated counter-clockwise.
        newToneValue -= 1; // subtract 1 from tone value.
      }

      if (encCW == 1) { // If the rotary encoder has been rotated clockwise.
        newToneValue += 1; // Add 1 to the tone value. 
      }

      if (newToneValue >= 10){ // If tone value is more than 9.
        newToneValue = 9; // Limit tone value to 9.
      }

      if (newToneValue <= 0){ // If tone value is less than 1.
        newToneValue = 1; // Limit tone value to 1.
      }   

      if (buttonPressed == 1) {
        if(newToneValue == currentToneValue){ // If the button is pressed without changing input selection.
         mode = 1 ; // Go back to the main menu page, because this is the last menu.
        }

        else{ // A new tone value has been selected.
         currentToneValue = newToneValue; // Set the current tone value to the new tone value.
         mode = 1; // Go back to the main menu page (case 1).
        }
      }
  buttonPressed = 0;
    // End of case 3.

  } // End of switch.

  // Rotary encoder reset for next loop around.  
  previousStateCLK = currentStateCLK; // Update previousStateCLK with the current state of inputCLK.
  encCCW = 0; // Reset encoder counter-clockwise flag.
  encCW = 0; // Reset encoder clockwise flag.

} // end of loop.

void updateDisplay(){ // Display the menu mode, volume level and current audio input.
  Serial.print("Mode:");
  Serial.println(mode);
  Serial.print("Volume: ");
  Serial.print(volume);
  Serial.println("dB");
  Serial.print("Current Input Number:");
  Serial.println(currentInputNumber);
  Serial.print("New Input Number:");
  Serial.println(newInputNumber);
  Serial.print("Current Tone Control Value:");
  Serial.println(currentToneValue);
  Serial.print("New Tone Control Value:");
  Serial.println(newToneValue);
  Serial.println();
}

This code outputs serial monitor in each case, but it does it 3 times regardless of which case is running!

// Rotary encoder variables
const byte inputCLK = 3; // Arduino pin the rotary encoder CLK pin is connected to.
const byte inputDT = 2; // Arduino pin the rotary encoder DT pin is connected to.
int currentStateCLK; // Current state of rotary encoder pin CLK. Used to identify rotation.
int previousStateCLK; // Current state of rotary encoder pin CLK. Used to identify rotation.
boolean encCCW; // Rotary encoder flag to denote counter-clockwise rotatation by one pulse.
boolean encCW; // Rotary encoder flag to denote clockwise rotatation by one pulse.

// Push button variables
const byte buttonPin = 4; // Arduino pin the push button is connected to.
byte previousButtonState = HIGH;  // Assume switch open because of pull-up resistor.
byte currentButtonState; // Will be populated by reading button pin satae.
const unsigned long debounceTime = 5;  // Milliseconds of debounce.
unsigned long buttonPressTime;  // Time since button last changed state. Used to differentialte button bounce vs. intentional press.
boolean buttonPressed = 0; // A flag variable to identify button presses. Returned from button debounce code. 

// Menu mode change variables
int mode = 1;  // Mode variable used to run switch case. 1 = volume, 2 = audio input, 3 = tone control.
int currentInputNumber = 1; // Audio input the system is currently set to.
int newInputNumber = 1; // Audio input user is in the process of choosing by rotary control.
int currentToneValue = 5; // Current tone control setting.
int newToneValue = 5; // Tone control setting the user is in the process of choosing by rotary control.

// Gain variables 
float volume; // Volume level in dB.
float linGain; // Volume level as a linear gain value between 0 (no level) and 1 (full level).
uint32_t vol32; // Linear gain as a 32 bit word to populate hex array.
byte volArray[4]; // 4 byte array to hold volume hex values ready to send over I2C to the audio processor.

void setup() { 
    
 volume = -45; // Set default volume level to -45dB.
    
 // Set encoder and push button pins as inputs.  
 pinMode (inputCLK,INPUT);
 pinMode (inputDT,INPUT);
 pinMode (buttonPin, INPUT_PULLUP);
 encCCW = 0; // Initial state for rotation flag not rotated.
 encCW = 0; // Initial state for rotation flag not rotated.
 previousStateCLK = digitalRead(inputCLK); // Read initial state of rotary encoder CLK pin. Assign to previousStateCLK so we can check for state changes.
 
 // Setup Serial Monitor
 Serial.begin (9600);
} 

void loop() {

  // Rotary encoder code.
  currentStateCLK = digitalRead(inputCLK); // Read the current state of inputCLK pin.

  if (currentStateCLK != previousStateCLK) { // If the previous and the current state of inputCLK are different, then the encoder and been rotated.
    if (currentStateCLK != digitalRead(inputDT)) { // If the inputCLK state is different than the inputDT state then the rotation is counterclockwise.
      encCCW = 1; // Set rotation flag counter-clockwise.
    }
    else { // If inputCLK and inputDT are the same then encoder is not rotating CCW.
      encCW = 1; // Set rotation flag clockwise.
    }
  }

  // Button press code with debounce.
  currentButtonState = digitalRead (buttonPin); 
  if (currentButtonState != previousButtonState){
    if (millis () - buttonPressTime >= debounceTime){ // debounce
      buttonPressTime = millis ();  // when we closed the switch 
      previousButtonState = currentButtonState;  // remember for next time 
      if (currentButtonState == LOW){ // When debounce criteria are met and button is still pressed.
        buttonPressed = 1; // Set button flag to 1.
      }
    }
  }

  // Limit Mode (menu page) and loop back to main menu.
  if (mode >= 4){
   mode = 1; 
  }
 
  // Convert dB volume level to hex array for sending by I2C to the audio processor.
  linGain = pow(10, volume/20); // Convert dB level to linear gain value.
  vol32 = linGain * 16777216; // Convert linear gain value stored as a float variable to a 32 bit integer.
  volArray[0] = (vol32 >> 24) & 0xFF; // Populate first byte of array with most significant 8 bits of the 32 bit volume word, by shifting word right by 24 bits. Adding 0xFF denotes it as a hex value.
  volArray[1] = (vol32 >> 16) & 0xFF; // Populate second byte of array with next most significant 8 bits of the 32 bit volume word, by shifting word right by 16 bits. Adding 0xFF denotes it as a hex value.
  volArray[2] = (vol32 >> 8) & 0xFF; // Populate third byte of array with next most significant 8 bits of the 32 bit volume word, by shifting word right by 8 bits. Adding 0xFF denotes it as a hex value.
  volArray[3] = vol32 & 0xFF; // Populate fourth byte of array with least significant 8 bits of the 32 bit volume word, naturally the first 8 bits of the word. Adding 0xFF denotes it as a hex value.
        

  switch (mode) { // Each menu page is a case operated by the 'mode' variable.
   
    case 1: // Main menu page, volume control.
     
      if (encCCW == 1) { // If the rotary encoder has been rotated counter-clockwise.
       volume --; // Decrement volume level by one. 
       updateDisplay(); 
      }

      if (encCW == 1) { // If the rotary encoder has been rotated clockwise.
        volume ++; // Increment volume level by one.   
        updateDisplay(); 
      }

      if (buttonPressed == 1) {
        mode = mode + 1 ; // If the button is pressed while on the main volume page (case 1), the menu progresses to the next page (case 2).
      }

  buttonPressed = 0;
   
    // End of case 1.
      
    case 2:
     
      if (encCCW == 1) { // If the rotary encoder has been rotated counter-clockwise.
        newInputNumber -= 1; // subtract 1 from input number.
        updateDisplay();
      }

      if (encCW == 1) { // If the rotary encoder has been rotated clockwise.
        newInputNumber += 1; // Add 1 to the input number. 
       updateDisplay();
      }

      if (newInputNumber >= 6){ // If input number is more than 5 then loop back to 1.
         newInputNumber = 1;
      }

      if (newInputNumber <= 0){ // If input number is less than 1 then loop back to 5.
        newInputNumber = 5;
      }   

      if (buttonPressed == 1) {
        if(newInputNumber == currentInputNumber){ // If the button is pressed without changing input selection.
         mode = mode + 1 ; // Increment mode by 1 to progress to the next menu page.
        }

        else{ // A new audio input is being selected.
         currentInputNumber = newInputNumber; // Set the current audio input to the new selection.
         mode = 1; // Go back to the main menu page (case 1)

        }
      }

  buttonPressed = 0;
    // End of case 2.
    
    case 3:
     
      if (encCCW == 1) { // If the rotary encoder has been rotated counter-clockwise.
        newToneValue -= 1; // subtract 1 from tone value.
        updateDisplay();
      }

      if (encCW == 1) { // If the rotary encoder has been rotated clockwise.
        newToneValue += 1; // Add 1 to the tone value. 
        updateDisplay();
      }

      if (newToneValue >= 10){ // If tone value is more than 9.
        newToneValue = 9; // Limit tone value to 9.
      }

      if (newToneValue <= 0){ // If tone value is less than 1.
        newToneValue = 1; // Limit tone value to 1.
      }   

      if (buttonPressed == 1) {
        if(newToneValue == currentToneValue){ // If the button is pressed without changing input selection.
         mode = 1 ; // Go back to the main menu page, because this is the last menu.
        }

        else{ // A new tone value has been selected.
         currentToneValue = newToneValue; // Set the current tone value to the new tone value.
         mode = 1; // Go back to the main menu page (case 1).
        }
      }
  buttonPressed = 0;
    // End of case 3.

  } // End of switch.

  // Rotary encoder reset for next loop around.  
  previousStateCLK = currentStateCLK; // Update previousStateCLK with the current state of inputCLK.
  encCCW = 0; // Reset encoder counter-clockwise flag.
  encCW = 0; // Reset encoder clockwise flag.

} // end of loop.

void updateDisplay(){ // Display the menu mode, volume level and current audio input.
  Serial.print("Mode:");
  Serial.println(mode);
  Serial.print("Volume: ");
  Serial.print(volume);
  Serial.println("dB");
  Serial.print("Current Input Number:");
  Serial.println(currentInputNumber);
  Serial.print("New Input Number:");
  Serial.println(newInputNumber);
  Serial.print("Current Tone Control Value:");
  Serial.println(currentToneValue);
  Serial.print("New Tone Control Value:");
  Serial.println(newToneValue);
  Serial.println();
}

Oh! I forgot to add

break;

at the end of each case doh!