Having a rotary encoder with RGB illuminated push button, gives a nice feature to the project. The speed of the car can be controlled, and a start/stop state can be set and viewed with the illuminated push button.
Autonomous vehicles, HC-SR04 sensors
Step 1: Detecting objects with HC-SR04
Step 2: Multiple HC-SR04 with Arduino using interrupts
Step 2b: Multiple HC-SR04 with Arduino using only 1 interrupt pin
Step 3: Choosing the right vehicle
Step 4: Automatic steer correction with HC-SR04
Step 5a: Vehicle accessories – SSD1306 status display
Step 5b: Vehicle accessories – Rotary encoder illuminated button
Step 6: Autonomous vehicles, putting it all together
Alternative 1: Steer correction with HC-SR04: Single sensor, stop and scan
Alternative 2: Steer correction with HC-SR04: Servo controlled car

The rotary encoder with RGB illuminated push button, can be purchased as a complete set, or a separate switch, breakout board and optionally transparant knob.
Having a rotary encoder with RGB illuminated push button, gives a nice feature to the project.
- Turning clockwise (CW) or counter clockwise (CCW) can be used to increase or decrease the speed of the car
- The push button can serve as a start/stop mode
- The RGB led can be used as feedback of the robot state
Complete set
Since the rotary encoder switch is not breadboard friendly, a breakout board is a good solution. The pre-soldered version can be bought from Sparkfun, but it uses I2C (or optionally a Qwiic Twist connector), and is a bit more expensive. It will not be covered here, but you can find it at the Sparkfun website here.
Self assembled
Sparkfun also provides loose switches and breakout boards, and are available in 2 versions, so make sure you buy the right ones
- Rotary encoder with Red/Green (RG) LED illuminated button
Find it on the Sparkfun website here - Rotary encoder with Red/Green/Blue (RGB) LED illuminated button
Find it on the Sparkfun website here
For the breakout board, there is only one version. The top side is for the RGB version. Flip the board to the bottom side for the RG version.
The breakout board can be found here.

Optionally, a transparant knob can be added to the button.
It can also be found on the Sparkfun website here.
If all functions of the encoder button are used, 6 pins are needed, and off-course the GND and 5V pins will be needed as well.
- The encoder will be read using 1 interrupt pin, and 1 regular pin
- The push button will be read on a regular pin
- The RGB leds will be connected to regular pins (no PWM control)
Having one signal (A) of the encoder connected to an interrupt pin, allows for simple and accurate read (no missing triggers). The second signal (B) will just be used to determine the rotation direction. The common pin (pin C) will be connected to ground.
The push button has a common connection with the anode side of the LEDs. Therefore a pull-down resistor (1K – 10K) needs to be used in order to have a stable input value.
For the autonomous vehicle, all functions will be used
- Turning clockwise (CW) or counter clockwise (CCW) will increase or decrease the speed of the car
- The push button will set the car in auto or stopped mode
- The RGB led will give feedback on the state of the car
The push button will be used with the previous written class “class_button.h”. This class will take care of de-bouncing the button, and detecting rising and falling triggers.
Encoder
Since this rotary encoder can detect the rotation direction, it will be used on an interrupt pin watching for a changing signal (signal A). By looking at signal B, the rotation direction can be determined, and a counter is increased or decreased by 1.

RGB LEDs
In this project, the PWM signals are used to control the DC motors, so not all LEDs will be able to be controlled by PWM signals to set the intensity. Since in this project only red and green are interesting (add other colours to your own liking) and the intensity is not important (unless you want different), all LEDs will be controlled by regular pins.
/**************************************************************** To be used as a local class ****************************************************************/ class BUTTON { private: unsigned long storeMillis = millis(); unsigned long tmrTON = millis(); unsigned long tmrTOF = millis(); bool REpassed = false; // Remember Rising Edge bool FEpassed = false; // Remember Falling Edge public: bool state = false; // State of the button to check int TON_delay = 1000; // Debounce/Delay time for ON/Rising Trigger detection int TOF_delay = 1000; // Debounce/Delay time for OFF/Falling Trigger detection bool Q = false; // Debounce/Delay time passed and button still high bool RE = false; // Rising Edge detected (remains high for only 1 cycle) int FE = false; // Falling Edge detected (remains high for only 1 cycle) // Call this every cycle void refresh() { // First check the timers if (!this->state) { this->tmrTON = millis(); } else // (this->state) { this->tmrTOF = millis(); } // How long is the button PRESSED ? if (this->state && (this->tmrTON + this->TON_delay < millis())) { // Button was continuously pressed for TON_delay time this->Q = true; // Set Q to HIGH state this->FE = false; // Falling Edge low (small change it's still high) this->FEpassed = false; // Falling Edge detection help var low // Rising Edge detected ? if(!this->REpassed) { this->RE = true; // Not yet -> Set Rising Edge detected HIGH this->REpassed = true; // Help var for Rising Edge } else { this->RE = false; // Already detected -> Set Rising Edge detected LOW } } // How long is the button RELEASED ? if (!this->state && (this->tmrTOF + this->TOF_delay < millis())) { // Button was continuously released for TOF_delay time this->Q = false; // Set Q to LOW state this->RE = false; // Rising Edge low (small change it's still high) this->REpassed = false; // Rising Edge detection help var low // Falling Edge detected ? if(!this->FEpassed) { this->FE = true; // Not yet -> Set Falling Edge detected HIGH this->FEpassed = true; // Help var for Falling Edge } else { this->FE = false; // Already detected -> Set Falling Edge detected LOW } } }; };
// Include button class #include "class_button.h"; BUTTON btnShortPress; BUTTON btnLongPress; /**************************************************************** Configuration ****************************************************************/ // Encoder #define pinEncA 3 // Encoder A signal (must be interrupt pin) #define pinEncB 4 // Encoder B signal (any pin is fine) #define pinEncButton 7 // Encoder push button #define pinEncLED_R 8 // Encoder RGB LED, Red #define pinEncLED_G 9 // Encoder RGB LED, Green #define pinEncLED_B 12 // Encoder RGB LED, Blue // Sensor delay #define debugDelay 200 // Debug output every x ms /***** END OF CONFIGUTATION *****/ volatile int encoderCounter; // Encoder counter value byte byteCounter = 0; // Poll timers unsigned long lastDebugMillis; /**************************************************************** SETUP ****************************************************************/ void setup() { Serial.begin(115200); while (!Serial) { ; // Wait for Serial } Serial.println("--- Serial monitor started ---"); // Set pins as input pinMode(pinEncA, INPUT_PULLUP); // Encoder signal A (C is GND !) pinMode(pinEncB, INPUT_PULLUP); // Encoder signal B (C is GND !) pinMode(pinEncButton, INPUT); // Encoder push button (common anode, no pull-up !) // Set pins as output pinMode(pinEncLED_R, OUTPUT); // Encoder RGB led, Red pinMode(pinEncLED_G, OUTPUT); // Encoder RGB led, Green pinMode(pinEncLED_B, OUTPUT); // Encoder RGB led, Blue // Set interrupt attachInterrupt(1, ISR_ENC, CHANGE); // Set interrupt call for encoder lastDebugMillis = millis(); } /**************************************************************** LOOP ****************************************************************/ void loop() { // Switch has common anode with LEDs (pull-down resistor) bool buttonState = digitalRead(pinEncButton); // Read button state btnShortPress.state = buttonState; btnLongPress.state = buttonState; btnShortPress.refresh(); // update button btnLongPress.refresh(); // update button // For debugging button class en testing timings // detectAllEdges(); // RGB LED control controlRGB(); // Interval call if (millis() - lastDebugMillis >= debugDelay) { showEncoderValue(); lastDebugMillis = millis(); } } /**************************************************************** RGB LED handling ****************************************************************/ void controlRGB() { /* Red: 1.88V (3V/20mA = 150E) Green: 2.98V (2V/20mA = 100E) Blue: 2.98V (2V/20mA = 100E) */ // Button settings btnShortPress.TON_delay = 25; // de-bounce btnShortPress.TOF_delay = 25; // de-bounce btnLongPress.TON_delay = 1000; // de-bounce + delay btnLongPress.TOF_delay = 25; // de-bounce // Long press set green to 50% (leave rest as is) // Short press toggles to next color if (btnLongPress.FE) { Serial.println("This time: LONG press"); digitalWrite(pinEncLED_R, not(byteCounter >> 2 & 1)); analogWrite(pinEncLED_G, 127); digitalWrite(pinEncLED_B, not(byteCounter >> 0 & 1)); } else if (btnShortPress.FE) { byteCounter++; Serial.println("This time: SHORT press"); digitalWrite(pinEncLED_R, not(byteCounter >> 2 & 1)); digitalWrite(pinEncLED_G, not(byteCounter >> 1 & 1)); digitalWrite(pinEncLED_B, not(byteCounter >> 0 & 1)); } } /**************************************************************** Encoder value ****************************************************************/ void showEncoderValue() { // Keep encoder values -100..100 (%) encoderCounter = constrain(encoderCounter, -100, 100); // Show current value Serial.print("Encoder value: "); Serial.println(encoderCounter); } /**************************************************************** Debug function ****************************************************************/ void detectAllEdges() { // Button settings btnShortPress.TON_delay = 25; // de-bounce btnShortPress.TOF_delay = 25; // de-bounce btnLongPress.TON_delay = 1000; // de-bounce + delay btnLongPress.TOF_delay = 1000; // de-bounce + deay // Just for the class_button.h DEBUG if (btnShortPress.RE) { Serial.println("Button Rising Edge Short Press"); } if (btnShortPress.FE) { Serial.println("Button Falling Edge Short Press"); } if (btnLongPress.RE) { Serial.println("Button Rising Edge Long Press"); } if (btnLongPress.FE) { Serial.println("Button Falling Edge Long Press"); } } /**************************************************************** INTERRUPT handling encoder ****************************************************************/ // Encoder pin void ISR_ENC() { // static unsigned byte encoder_state = 0; // Why not working ? static unsigned char encoder_state = 0; // Only exists in this funtion and init once encoder_state = encoder_state << 2; // Shift 2 bit to the left // encoder_state = encoder_state | digitalRead(pinEncA); // New value A on bit 0 // encoder_state = encoder_state | (digitalRead(pinEncB) << 1)); // New value B on bit 1 // For ATmega328(P) chips, INT1 = pin 3 = PD3 , pin 4 = PD4 byte pinRead = PIND; encoder_state = encoder_state | (PIND >> 3 & 1); // New value A on bit 0 [xxxxxxxA] encoder_state = encoder_state | (PIND >> 3 & 10); // New value B on bit 1 [xxxxxxBx] encoder_state = encoder_state & B00001111; // Mask with 0x0F // Format is now 0000 <prev B> <prev A> <cur B> <cur A> // Decide counter direction (comment / uncomment for best stability) switch (encoder_state) { case B1001: // B high to low ; A low to high // case B0110: // B low to high ; A high to low encoderCounter++; break; // case B0011: // A and B low to high case B1100: // A and B high to low encoderCounter--; break; default: ; // unsure state break; } } //END
Conclusion
Having the option to quickly adjust the speed without having to dive into the code and tweak a few parameters is a nice feature. Being able to visually see the status (started / stopped / something else) of the robot, is a must. With this rotary encoder switch, both can be achieved easily.
References
Arduino Boards: Arduino Uno R3 – Arduino Nano V3 – Arduino Micro
Arduino Software: Arduino IDE
Arduino Playground: Library NewPing
Arduino forum: Arduino Micro Interrupts 6
Arduino Reference: Port Registers – attachInterrupt() – interrupt() – noInterrrupt() – pulseIn() – pulseInLong() – Servo library
Random Nerd Tutorials: Complete Guide for Ultrasonic Sensor HC-SR04 with Arduin
Circuit Digest: Arduino Interrupts Tutorial
Electro Noobs: Arduino register control, timers and interruptions
Wikipedia: Speed of sound – Echolocation