AceButton Documentation

repository·develop·Indexed 19 days ago

https://github.com/bxparks/acebutton

A lightweight, event-driven Arduino library for mechanical buttons providing debouncing and complex event detection such as double-clicks and long presses. It supports advanced input configurations including binary encoding (via Encoded4To2ButtonConfig, Encoded8To3ButtonConfig, and EncodedButtonConfig) to support more buttons with fewer pins, as well as resistor ladder inputs via the LadderButtonConfig class for multi-button detection on a single analog pin.

Tokens
19.3K
Snippets
52
Records
86
Agent score
65%

What's inside AceButton

  1. Overview of AceButton

    develop

    AceButton is an adjustable, compact, and event-driven button library designed for Arduino platforms. It handles mechanical button inputs (momentary, maintained, or switches) and provides debouncing logic.

    Key features include:

    • Event-driven architecture: Uses EventHandler callbacks or the IEventHandler interface to respond to button state changes.
    • Multiple event types: Supports events like kEventPressed, kEventReleased, kEventClicked, kEventDoubleClicked, kEventLongPressed, kEventRepeatPressed, kEventLongReleased, and kEventHeartBeat.
    • Low resource usage: Optimized for minimal static memory and CPU cycles.
    • Flexible hardware support: Supports standard digital pins, Binary Encoded buttons (multiple buttons on few pins), and Resistor Ladder buttons (multiple buttons on one analog pin).
  2. Use binary encoding to support more buttons with fewer pins

    develop

    Binary encoding allows you to support $2^N - 1$ buttons using only $N$ pins. This is useful when you have a limited number of microcontroller pins.

    Key constraints:

    • No simultaneous presses: You cannot detect multiple buttons being pressed at the same time using this method.
    • Hardware implementation: The software is agnostic to how you implement the encoding (diodes, logic gates, or integrated circuits like the 74LS148), but the hardware must pull the pins LOW when a button is pressed.
    • Pull-up resistors: You must configure the pins using pinMode(PIN, INPUT_PULLUP).
  3. Circuit 3: Parallel Resistor Ladder (Recommended)

    develop

    This is the recommended wiring method. Each button has its own dedicated resistor connected in parallel with a pull-up resistor ($Rp$).

    Characteristics:

    • Pros:
      • Simple Calculation: The voltage ratio is simply $V(i)/V_{cc} = R_p / (R(i) + R_p)$.
      • Isolation: Replacing or changing one resistor only affects that specific button's voltage level, not the others.
      • Power Efficient: No current flows when no buttons are pressed, making it ideal for battery-powered devices.
      • Scalable: Requires $N+1$ resistors for $N$ buttons.
    • Safety: Use a small resistor (e.g., 220 $\Omega$ or 330 $\Omega$) as $R0$ to limit current in case of mis-wiring.
    // Voltage ratio formula for Circuit 3:
    V(i)/Vcc = Rp / (R(i) + Rp)
  4. How AceButton and ButtonConfig work together

    develop

    The library separates button logic from configuration using two main classes:

    1. AceButton: Handles debouncing, event detection (Pressed, Clicked, etc.), and maintains the state for a specific physical button.
    2. ButtonConfig: Stores timing parameters (debounce delay, click delay, etc.) and provides hooks for hardware dependencies (getClock() and readButton()).

    This separation allows multiple AceButton instances to share a single ButtonConfig (e.g., a group of buttons with identical timing requirements), saving memory.

    const uint8_t PIN1 = 2;
    const uint8_t PIN2 = 4;
    
    ButtonConfig buttonConfig;
    AceButton button1(&buttonConfig, PIN1);
    AceButton button2(&buttonConfig, PIN2);
  5. Understand Resistor Ladders for Multi-Button Input

    develop
    A resistor ladder allows you to detect multiple buttons using a single analog pin. Each button is wired to create a unique voltage level on that pin. An Analog-to-Digital Converter (ADC) on the microcontroller (like an Arduino) reads these voltages as integer values (e.g., 0 to 1023 for a 10-bit ADC). The LadderButtonConfig class is designed to handle these voltage levels to identify which button is pressed.
  6. Use Binary Encoded Buttons for high pin efficiency

    develop

    To support a large number of buttons using only a few pins, you can use Binary Encoding (e.g., with a 74LS148 chip or diodes). AceButton provides specialized ButtonConfig subclasses for this:

    • Encoded4To2ButtonConfig: 3 buttons using 2 pins.
    • Encoded8To3ButtonConfig: 7 buttons using 3 pins.
    • EncodedButtonConfig: $M = 2^N - 1$ buttons using $N$ pins.

    Refer to docs/binary_encoding/README.md for implementation details.

  7. Distinguish between Clicked and DoubleClicked events

    develop

    Because a DoubleClicked event inherently contains a Clicked event, you may need specific strategies to prevent both from triggering unwanted actions. AceButton provides three methods:

    Method 1: Suppress Click before DoubleClick

    Uses kFeatureSuppressClickBeforeDoubleClick. The Clicked event is postponed until the DoubleClicked state is determined.

    • Pros: Simple configuration.
    • Cons: All Clicked events are delayed by ~600ms (kClickDelay + kDoubleClickDelay). The Released event is not postponed; it triggers immediately.

    Method 2: Use Released event instead of Clicked

    Uses kFeatureDoubleClick combined with kFeatureSuppressAfterClick and kFeatureSuppressAfterDoubleClick. You handle logic in the Released event instead of the Clicked event.

    • Pros: No response time lag for the Released event.
    • Cons: You must ignore the spurious Clicked event generated by a double-click.

    Method 3: Combined Approach

    Combines both methods to allow either a Released event or a delayed Clicked event to count as a "Click".

    // Method 1 Configuration
    ButtonConfig* buttonConfig = button.getButtonConfig();
    buttonConfig->setFeature(ButtonConfig::kFeatureDoubleClick);
    buttonConfig->setFeature(ButtonConfig::kFeatureSuppressClickBeforeDoubleClick);
    
    // Method 2 Configuration
    ButtonConfig* buttonConfig = button.getButtonConfig();
    buttonConfig->setEventHandler(handleEvent);
    buttonConfig->setFeature(ButtonConfig::kFeatureDoubleClick);
    buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterClick);
    buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterDoubleClick);
    
    // Method 3 Configuration
    ButtonConfig* buttonConfig = button.getButtonConfig();
    buttonConfig->setEventHandler(handleEvent);
    buttonConfig->setFeature(ButtonConfig::kFeatureDoubleClick);
    buttonConfig->setFeature(ButtonConfig::kFeatureSuppressClickBeforeDoubleClick);
    buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterClick);
    buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterDoubleClick);
  8. Understanding AutoBenchmark results

    develop

    Benchmark results are reported in microseconds. The samples column indicates the number of TimingStats::update() calls performed during the test.

    Scenarios measured include:

    • idle: One AceButton::check() call with no events.
    • press/release: One AceButton::check() call with Press and Release events.
    • click: One AceButton::check() call with a Click event.
    • double_click: One AceButton::check() call with a DoubleClick event.
    • long_press/repeat_press: One AceButton::check() call with LongPress and RepeatPress events.
    • Configuration-specific benchmarks: Various ButtonConfig types (e.g., ButtonConfigFast1, Encoded4To2ButtonConfig, LadderButtonConfig) are tested to show their specific overhead.
  9. Read buttons using binary encoding

    develop

    AceButton supports reading multiple buttons through a reduced number of pins using binary encoding. This is achieved through specific configuration classes that decode the pin states into button events.

    Available configuration classes:

    • Encoded4To2ButtonConfig: Decodes $M$ buttons using $N$ pins (e.g., 3 buttons using 2 pins).
    • Encoded8To3ButtonConfig: Decodes $M$ buttons using $N$ pins (e.g., 7 buttons using 3 pins).
    • EncodedButtonConfig: A general M-to-N class for arbitrary configurations (e.g., 15 buttons using 4 pins).
  10. Use Resistor Ladder Buttons on a single analog pin

    develop
    You can attach multiple buttons (typically 1-8) to a single analog pin using a resistor ladder. The LadderButtonConfig class handles reading these different voltages via analogRead() to identify which button was pressed.
  11. How level matching tolerance works in LadderButtonConfig

    develop

    Because of resistor tolerances and ADC conversion variance, the actual analogRead() values rarely match the theoretical values provided in the levels[] array. To handle this, LadderButtonConfig uses a fuzzy matching algorithm.

    Instead of exact matches, the algorithm looks for values within a band defined by the mid-points between adjacent levels. For a levels[] array, the matching range for a specific level is the interval from the midpoint of the level below it to the midpoint of the level above it.

    Example Logic: For a levels[] array of [0, 327, 512, 844, 1023]:

    • Button 0: Readings < 163
    • Button 1: Readings between 163 and 419
    • Button 2: Readings between 419 and 678
    • Button 3: Readings between 678 and 933
    • Button 4 (No button): Readings > 933

    Key Takeaway: A larger gap between levels in your levels[] array increases tolerance to fluctuations but limits the number of buttons you can support on a single pin (estimated maximum of 6-10 buttons with 5% resistors).

  12. Handle multiple buttons

    develop

    There are two primary patterns for managing multiple buttons:

    Option 1: Multiple ButtonConfigs Create a separate ButtonConfig instance for each button (or group of buttons). This allows each group to have its own unique EventHandler.

    Option 2: Multiple Button Discriminators Use a single ButtonConfig and a single EventHandler for all buttons. Inside the EventHandler, use button->getPin() or button->getId() to determine which button triggered the event using a switch statement.

    // Option 2: Single Config with Discriminator
    void buttonHandler(AceButton* button, uint8_t eventType, uint8_t buttonState) {
      switch (button->getPin()) {
        case 6:
          // Handle button on pin 6
          break;
        case 7:
          // Handle button on pin 7
          break;
      }
    }
    
    void setup() {
      ButtonConfig* config = ButtonConfig::getSystemButtonConfig();
      config->setEventHandler(buttonHandler);
    }