EncButton Arduino Library

repository·main·Indexed 42 days ago

https://github.com/gyverlibs/encbutton

A lightweight, high-performance Arduino library for handling rotary encoders and buttons. It features software debouncing, support for four incremental encoder types, and event-driven interaction for clicks, long presses, and rotation. Optimized for low memory usage and compatible with AVR, ESP8266, and ESP32 via the GyverIO library.

Tokens
24.2K
Snippets
77
Records
101
Agent score
50%

What's inside EncButton

  1. Overview of EncButton features

    main

    EncButton provides high-performance handling for two main hardware components:

    Button Support

    • Event Processing: Handles clicks, long presses, hold, pulse hold, and click counters.
    • Advanced Logic: Supports 'pre-clicks' (detecting clicks that occurred before a hold event) and can treat two simultaneous button presses as a third unique button event.
    • Debouncing: Includes software-based debouncing.

    Encoder Support

    • Event Processing: Detects normal rotation, pressed rotation (button held while turning), and fast rotation.
    • Precision: Uses a high-precision algorithm for position determination.
    • Compatibility: Supports four different types of incremental encoders.
    • Performance: Optimized for use within interrupts and uses buffering to ensure no steps are missed.

    General Characteristics

    • Low Overhead: Extremely small footprint (~5 bytes SRAM per instance, ~350 bytes Flash for button processing).
    • Optimized Performance: Uses fast asynchronous polling and optimized pin reading for AVR, ESP8266, and ESP32 (via GyverIO).
    • Virtual Mode: Supports working with pin expanders via virtual pin mapping.
  2. Configure pin pull-up modes for encoders and buttons

    main

    The library allows you to choose between external and internal pull-up resistors for both the encoder pins and the button pin.

    Encoder Pins

    • External Pull-up: By default, encoder pins are configured as INPUT, assuming you have provided external pull-up resistors. This is recommended for better noise immunity.
    • Internal Pull-up: If your encoder does not have external pull-ups, you can use the microcontroller's internal pull-up by specifying INPUT_PULLUP during encoder initialization.

    Button Pins

    • External Pull-up: Set the pin mode to INPUT. This is recommended for real-world projects as it is less susceptible to interference than internal pull-ups.
    • Internal Pull-up: Use INPUT_PULLUP if you are connecting the button to GND without an external resistor.
  3. Best practices for asynchronous code in EncButton

    main

    EncButton is an asynchronous library. It does not block your code while processing. For optimal performance and to avoid missing button presses or encoder steps, follow these guidelines:

    • Minimize Loop Latency: Ensure your loop() executes as fast as possible. Avoid delay() calls longer than 50-100ms.
    • Use Non-blocking Code: Replace delay() with millis() based logic.
    • Use Hardware Interrupts: Connect the encoder to pins that support hardware interrupts and use tickISR().
    • Avoid Heavy Blocks: If you have heavy computations, wrap them in a check: if (!button.busy()) { /* heavy code */ }.
    • Offload Processing: If the main loop cannot be optimized, call tick() from a different context:
      • A timer interrupt (every ~50ms or faster).
      • A different CPU core (e.g., on ESP32).
      • A different FreeRTOS task.
      • Inside a yield() call.
  4. Understand button event types and states

    main

    The library processes button interactions through several distinct events and states:

    Events (triggered on specific actions)

    • press: Button is pressed (after debouncing).
    • hold: Button is held longer than the hold timeout.
    • step: Impulse event that activates repeatedly while the button is held (after hold timeout + step timeout).
    • release: Button is released.
      • click: Released before the retention timeout.
      • releaseHold: Released after retention timeout.
      • releaseStep: Released after impulse retention.
    • waiting: Waiting for a potential new click during the click timeout.

    State Conditions (polled during events)

    • pressing: Button is currently down.
    • holding: Button is currently held.
    • busy: The system is currently processing an interaction (waiting for timeouts or clicks).
    • waiting: The system is waiting for a potential subsequent click.
  5. Understand the tick() lifecycle and event polling

    main

    In v3, event polling functions (like click(), turn(), etc.) do not reset immediately after being called. Instead, they retain their value for all subsequent calls within the same iteration of the main loop. They are only reset during the next call to tick().

    CRITICAL: You must call tick() exactly once per loop iteration. Calling it multiple times will cause you to miss actions/events.

  6. Detect Button events and states

    main

    The library provides several ways to poll button interactions. Events are typically triggered once, while states can be checked continuously.

    Events (Return true once per occurrence):

    • press(): Button was pressed.
    • release(): Button was released.
    • click(): Button was clicked (released without being held).
    • hold(): Button was held longer than the timeout.
    • step(): Impulse hold (short hold).
    • releaseHold(): Button released after a hold.
    • releaseStep(): Button released after an impulse hold.
    • releaseHoldStep(): Button released after either a hold or impulse hold.
    • hasClicks(): Multiple clicks were detected.

    States (Return true while condition is met):

    • pressing(): Button is currently being pressed.
    • holding(): Button is currently being held.
    • waiting(): Button is waiting for more clicks to complete a multi-click sequence.
    • busy(): Processing is in progress (between first press and end of click sequence).
  7. How the tick() and ISR processing functions work

    main

    EncButton uses a "ticker" mechanism to process signals. You must call a processing function to update the internal state and detect events.

    Types of processing functions

    • tick(): The standard function to be called once per loop in your main loop() function. It returns true if an event occurred (for the encoder, it returns true on any rotation).
    • tickISR(): A specialized function designed to be called inside an Interrupt Service Routine (ISR) for high-precision encoder handling.

    Event Polling Types

    Once tick() is called, you can poll for events using two patterns:

    1. [event] (One-time events): Functions like click() or turn() return true exactly once when the event occurs. They reset automatically after the next tick() call.
    2. [state] (Continuous states): Functions that return true as long as a specific state is active (e.g., while a button is being held down).

    Important: Do not call tick() multiple times within a single loop iteration unless you are using the attach() callback method, as subsequent calls will reset the events detected by the first call.

    void loop() {
      btn.tick(); // Call once at the start of the loop
    
      if (btn.click()) {
        // This will execute once per click
        Serial.println("click");
      }
    }
  8. How processing and polling work

    main

    The library uses a two-step pattern: Processing (the tick function) and Polling (event functions like click(), turn(), etc.).

    1. Processing (tick): This function must be called once per loop cycle. It reads the hardware pins, performs debouncing/logic, and updates the internal state. It resets event flags.
    2. Polling ([event]): These functions check the state updated by tick.
      • Event functions (e.g., click(), turn()): Return true once when the event occurs and then reset.
      • State functions (e.g., hold(), fast()): Return true as long as the state is active.

    Important: Do not call tick() multiple times in a single loop cycle unless you are using an event handler (attach()), as subsequent calls will reset the events from the first call before you can poll them.

    void loop() {
      // 1. Process (once per cycle)
      btn.tick();
    
      // 2. Poll (multiple times if needed)
      if (btn.click()) Serial.println("click");
      if (btn.hold()) Serial.println("holding");
    }
  9. How the tick() function works

    main

    In v3, event polling functions (like click(), turn(), etc.) are not reset immediately after they are called. Instead, they are reset on the next call to tick(). This allows these event flags to remain valid for all subsequent calls within the same iteration of your main program loop.

    CRITICAL: You must call tick() exactly once per cycle. Calling it multiple times will cause you to miss actions.

  10. Use Virtual Encoder classes for ISR-based polling

    main

    Virtual encoder classes allow you to use a tick() method that does not require passing pin states in the main loop. This is useful if the encoder state is already being processed within an Interrupt Service Routine (ISR), preventing redundant pin polling in loop().

    To use this pattern:

    1. Use e.tickISR(pin1_state, pin2_state) inside your ISR.
    2. Call e.setEncISR(1) during setup.
    3. Call e.tick() in your main loop without arguments.
    VirtEncoder e;
    
    void isr() {
        e.tickISR(digitalRead(2), digitalRead(3));
    }
    
    void setup() {
        attachInterrupt(0, isr, CHANGE);
        attachInterrupt(1, isr, CHANGE);
        e.setEncISR(1);
    }
    
    void loop() {
        e.tick();   // no pin states passed here
    }
  11. Understand the EncButton class hierarchy

    main

    EncButton uses an inheritance-based architecture (an "onion" model) where higher-level classes inherit functionality from lower-level ones. To use all available functions for a class, you must also consider its parent classes.

    Class Layers

    1. Basic (Virtual) Classes

    These classes do not specify microcontroller pins directly; they work with transmitted values (e.g., from shift registers or pin extenders).

    • VirtButton: Provides all basic button features.
    • VirtEncoder: Determines encoder rotation direction and fact of rotation.
    • VirtEncButton: Base class for an encoder with a button; inherits VirtButton and VirtEncoder.

    2. Main Classes

    • Button / ButtonT: Button class (inherits VirtButton).
    • Encoder / EncoderT: Encoder class (inherits VirtEncoder).
    • EncButton / EncButtonT: Encoder with a button (inherits VirtEncButton, VirtButton, and VirtEncoder).

    Naming Conventions

    • T Suffix (e.g., ButtonT): These classes require pin numbers to be specified as constants (digits). This stores pin numbers in program memory, making the code faster and saving 1 byte of RAM per pin.
    • Virt Prefix: Indicates a virtual class that operates on values rather than direct hardware pins.
  12. How encoder processing works

    main

    The encoder tracks rotations and maintains a counter.

    • Fast Rotations: A rotation is considered "fast" if it occurs within the configured timeout of the previous rotation.
    • Interrupts: Rotations processed within an interrupt become active (triggering events) only after tick() is called.
    • Counter Access: The counter is a public variable. You can read, modify, or reset it directly.
    • Atomic Access (AVR): When accessing the 32-bit counter inside an interrupt on AVR platforms, use getCounter() and setCounter(value) to ensure atomicity.

    Encoder with Button

    • Rotating the encoder while the button is pressed blocks all subsequent events and clicks, except for the release event. The button's pressed state remains unchanged.
    • Rotating the encoder resets the system timeout() timer; the timeout will trigger after the specified time following the last rotation.
    // Direct counter manipulation
    Serial.println(eb.counter); // read
    eb.counter += 1234;         // modify
    eb.counter = 0;            // reset