Skip to content

3. Controlling GPIO Input with a Tactile Switch

This guide explains how to use a tactile switch (push button) with the Raspberry Pi's GPIO pins. We'll use a C++ program that utilizes the Linux GPIO v2 character device interface to detect button presses.

Tactile switch

Required Hardware

  • Raspberry Pi (any model)
  • Tactile switch/push button (1)
  • Jumper wires
  • Breadboard (optional, but recommended)

Hardware Setup

This guide uses GPIO pin 22 for the tact switch connection. Connect the components as follows:

  1. Connect one leg of the tact switch to GPIO 22
  2. Connect the other leg of the tact switch to GND (ground)
graph LR
    subgraph RPi["Raspberry Pi"]
        GPIO22["GPIO 22 (with internal pull-up)"]
        GND["GND"]
    end

    GPIO22 --- Switch["Tactile Switch"]
    Switch --- GND

    style Switch fill:#4a90e2

How it Works

When you press the button, it connects GPIO 22 to GND, creating a LOW signal. The program detects this state change and responds accordingly.

The code uses internal pull-up resistors, so no external resistors are required. When the button is not pressed, the pin reads HIGH; when pressed, it reads LOW (active-low configuration).

Code Explanation

This program uses the Linux GPIO v2 Character Device API to monitor a tact switch:

#include <fcntl.h>
#include <linux/gpio.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <chrono>
#include <cstring>
#include <iostream>
#include <thread>

/**
 * Simple tact switch monitoring program using GPIO v2 API
 *
 * This program waits for a button press on a GPIO pin
 * and then exits when the button is pressed.
 */
int main()
{
    constexpr char kGpioChipDevice[] = "/dev/gpiochip0";
    constexpr int kSwitchPin = 22;       // GPIO pin number for the tact switch
    constexpr int kPollIntervalMs = 100; // Poll interval in milliseconds
    constexpr char kConsumerLabel[] = "tact-switch";

    // Open GPIO chip
    int chip_fd = open(kGpioChipDevice, O_RDONLY);
    if (chip_fd < 0)
    {
        std::cerr << "Failed to open GPIO chip device" << std::endl;
        return 1;
    }

    // Using GPIO v2 API
    // First, get the GPIO line using v2 API
    struct gpio_v2_line_request req{};

    // Configure which lines we want
    req.offsets[0] = static_cast<__u32>(kSwitchPin);
    req.num_lines = 1;

    // Configure line as input
    struct gpio_v2_line_config config{};
    config.flags = GPIO_V2_LINE_FLAG_INPUT | GPIO_V2_LINE_FLAG_BIAS_PULL_UP;
    req.config = config;

    // Set consumer label
    std::strncpy(req.consumer, kConsumerLabel, sizeof(req.consumer) - 1);

    // Request the line
    if (ioctl(chip_fd, GPIO_V2_GET_LINE_IOCTL, &req) < 0)
    {
        std::cerr << "Failed to get GPIO v2 line: " << strerror(errno)
                  << std::endl;
        close(chip_fd);
        return 1;
    }

    int line_fd = req.fd;

    std::cout << "Waiting for button press on GPIO " << kSwitchPin << "..."
              << std::endl;

    // Wait for button press
    bool button_pressed = false;
    while (!button_pressed)
    {
        // Read switch state using v2 API
        struct gpio_v2_line_values data{};
        data.mask = 1; // We want to read the first line

        if (ioctl(line_fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &data) < 0)
        {
            std::cerr << "Failed to get GPIO line value: " << strerror(errno)
                      << std::endl;
            close(line_fd);
            close(chip_fd);
            return 1;
        }

        // Active LOW logic with pull-up resistor (0 = pressed, 1 = released)
        button_pressed = ((data.bits & 1) == 0);

        if (button_pressed)
        {
            std::cout << "Button pressed!" << std::endl;
        }
        else
        {
            // Small delay to reduce CPU usage
            std::this_thread::sleep_for(
                std::chrono::milliseconds(kPollIntervalMs));
        }
    }

    // Clean up resources
    close(line_fd);
    close(chip_fd);

    std::cout << "Program completed." << std::endl;
    return 0;
}

Key Program Elements

  1. GPIO Line Configuration:

    • The program configures GPIO pin 22 as an input with a pull-up resistor
    • The pull-up resistor ensures the pin reads HIGH when the button is not pressed
  2. Button State Detection:

    • The program polls the GPIO pin at 100ms intervals
    • It uses active-LOW logic: 0 (LOW) means button pressed, 1 (HIGH) means button released
  3. GPIO v2 API:

    • Uses the newer GPIO v2 API, which provides better features compared to the older interface
    • Includes proper error handling and resource cleanup
  4. Debouncing Considerations:

    • The 100ms polling interval provides basic debouncing
    • For a production application, more sophisticated debouncing might be needed

Compiling and Running

  1. Save the code to a file, e.g., gpio_button.cpp

  2. Compile the program:

    g++ -o gpio_button gpio_button.cpp -std=c++11 -pthread
    
  3. Run the program with root privileges (needed for GPIO access):

    sudo ./gpio_button
    
  4. The program will wait until you press the button, then exit.

How It Works in Detail

  1. Internal Pull-up Resistor: The program enables an internal pull-up resistor on GPIO 22, which keeps the pin HIGH when the button is not pressed.

  2. Ground Connection: When the button is pressed, it connects GPIO 22 to ground, pulling the pin LOW.

  3. Detection Logic: The program constantly checks the pin value and detects when it goes LOW.

  4. Polling Approach: This program uses polling rather than interrupts. While not as efficient, it's simpler to implement.

Advanced Considerations

  1. Interrupt-based Implementation: For better efficiency, consider using GPIO interrupts instead of polling.

  2. Debouncing: Mechanical switches often "bounce," causing multiple rapid transitions. Consider implementing a more robust debouncing algorithm.

  3. Multiple Buttons: The GPIO v2 API supports monitoring multiple lines simultaneously. You can extend this example to handle several buttons.

  4. Error Handling: The program includes basic error handling. In a production application, you might want more comprehensive error management.

Troubleshooting

  1. Permission Issues: If you get "Failed to open GPIO chip device," ensure you're running the program with sudo.

  2. Button Not Detected: Check your wiring and ensure the button connects to the correct GPIO pin and ground.

  3. Inconsistent Behavior: If the button press detection is inconsistent, implement a better debouncing solution.