Build a rotary-encoder menu as a small state machine with three separate stages: turning the encoder changes a position, pressing commits or cancels, and rendering draws only what changed. Qualify every event first, and define what happens when a click is missed or bounced.
Separate the three stages
Most encoder-menu bugs come from mixing three jobs in one place. Turning moves a cursor. Pressing selects. Drawing shows the current state. When those are tangled, a bounce in the encoder can trigger a redraw mid-selection, or a slow draw can cause a turn to be dropped. Keeping them separate makes each one testable on its own.
Treat movement as qualified events
The official Arduino Debounce example points out that contact bounce can turn one physical transition into multiple state changes, and it qualifies a changed state using elapsed-time checking before accepting it. An encoder’s contacts behave the same way: unqualified, one detent can register as several steps. The attachInterrupt() reference adds a second guardrail, to keep interrupt service routines short, so an encoder interrupt should set a flag or adjust a counter and leave the menu logic and drawing to the main loop.
Pick the right pins for the board
The attachInterrupt() reference lists pins 2 and 3 as interrupt-capable on the UNO Rev3 and notes that mappings vary across boards; it recommends digitalPinToInterrupt(pin). So do not hard-code a pin number. Confirm the actual board’s interrupt-capable pins first.
Bound the values
A menu position should have a defined range and a defined behaviour at the ends, either wrap or clamp. Leaving it unbounded lets a run of qualified steps push the cursor off the menu. Direction detection depends on the specific encoder’s contact ordering, so treat rotation counting as something to verify against your encoder’s datasheet rather than a solved decoder.
| Stage | Input | Its job | Guard rail |
|---|---|---|---|
| Movement | qualified encoder transitions | maintain a bounded position value | debounce each transition; reject impossible jumps |
| Selection | a qualified press | commit or cancel the current item | one action per qualified event |
| Rendering | current menu state | draw only when state changes | decouple drawing speed from input handling |
| Symptom | Likely stage | First thing to check |
|---|---|---|
| One click jumps several items | movement | is each transition debounced before counting? |
| Direction reverses unexpectedly | movement | encoder contact ordering, against its datasheet |
| Menu flickers or redraws constantly | rendering | draw only on state change |
| Clicks sometimes do nothing | selection or ISR | is the ISR short, with work deferred to the loop? |
- Confirm the board’s interrupt-capable pins and use digitalPinToInterrupt().
- Qualify every encoder transition with elapsed-time debounce before counting it.
- Keep the ISR short: set a flag or adjust a counter, nothing more.
- Give the menu a bounded position with explicit wrap-or-clamp behaviour.
- Separate selection from rendering, and redraw only on change.
- Test the missed-click and double-count cases deliberately, since those are where menus break.
Reader questions
Should I poll the encoder or use interrupts?
Interrupts are good at catching short bursts, provided the routine is short. Polling is simpler if your loop is fast enough to catch every transition without missing one.
How do I stop one click registering as several?
Apply the Debounce pattern: qualify the changed state with elapsed-time checking before accepting it, exactly as the official example does.
Is a switch debounce example enough for a quadrature encoder?
It handles bounce, but it is not a validated quadrature decoder. Direction detection depends on the encoder’s contact ordering and electrical behaviour, so check your encoder’s datasheet.
Define it: either wrap to the other end or clamp. Leaving it undefined is how cursors wander off the menu.
The qualified-event idea here mirrors the counting logic in the wheel pulse-sensing reference.
Documentation
- Arduino attachInterrupt() reference (official reference-en source)
- Arduino Debounce example (official arduino-examples source)
Documentation note: the sources used here cover interrupt usage and switch debounce only. No independent testing was done, and the encoder’s contact ordering and electrical specifications are not covered. This is user-interface logic, not a sole safety control.
