If you are writing C for the Raspberry Pi in 2026, the rules have changed. The Raspberry Pi 5 replaced the legacy BCM SoC GPIO controller with the external RP1 southbridge chip. This architectural shift broke legacy C libraries like WiringPi and older builds of pigpio. Python users simply updated gpiozero, but C programmers need a modern, kernel-agnostic solution that respects the RP1's strict 3.3V logic and new memory mapping.

The direct answer for Raspberry Pi C programming on the Pi 5 is the lgpio library. It provides microsecond-precision GPIO control, hardware PWM, and interrupt callbacks without relying on deprecated /dev/mem hacks. Below is the complete blueprint for building, coding, and debugging a microsecond-precision HC-SR04 ultrasonic distance sensor on the Pi 5 using C.

The Decision Matrix: Which C GPIO Library to Pick?

Before writing a single line of code, you must select the right library for your board and timing requirements. Do not default to libgpiod if you need microsecond timing; its standard user-space API introduces OS-level jitter that will ruin high-speed sensor reads.

Library Best For Pi 5 (RP1) Support Microsecond Timing Verdict
lgpio Pi 5 GPIO, PWM, precise alerts Native / Full Excellent (via alerts) DEFAULT PICK for Pi 5
pigpio Pi 4 and older, hardware timed PWM Broken / Deprecated Excellent Use only on Pi 4 / Zero 2 W
libgpiod Standard Linux GPIO, buttons, relays Native (Kernel backed) Poor (ms-level jitter) Use for simple on/off relays
WiringPi Legacy tutorials from 2018 None N/A Abandon immediately
Decision Path Terminated: If you are on a Raspberry Pi 5 and need to read a pulse width, bit-bang a WS2812B LED, or drive a stepper motor, install lgpio. If you are on a Pi 4, stick to pigpio. This guide assumes the Raspberry Pi 5 (8GB) and lgpio.

Project Build: Microsecond-Precision HC-SR04 on Pi 5

The HC-SR04 ultrasonic sensor measures distance by firing a 40kHz pulse and timing the echo. The echo pin outputs a 5V HIGH signal for the duration of the flight time. Reading this accurately requires microsecond polling, which Python's GIL and garbage collection routinely fumble, resulting in wild distance spikes. C with lgpio locks the thread and reads the pin state directly from the RP1 registers.

Parts List & Specifications

  • Board: Raspberry Pi 5 (4GB or 8GB variant)
  • Sensor: HC-SR04 Ultrasonic Module (Standard 5V version)
  • Resistors: 1x 1kΩ, 1x 2kΩ (1/4W carbon film for voltage divider)
  • Wiring: Breadboard, male-to-female jumper wires
  • OS: Raspberry Pi OS (64-bit, Bookworm or later)
Difficulty Rating: Intermediate (Requires basic C compilation, breadboard wiring, and Linux terminal familiarity).
Time to Complete: 30 minutes.

Wiring and Pinout Configuration

The Raspberry Pi 5's RP1 chip is strictly 3.3V tolerant. Feeding the HC-SR04's 5V Echo pin directly into a Pi 5 GPIO will permanently fry the RP1 southbridge. You must use a voltage divider to step the 5V Echo signal down to a safe 3.3V.

Pin Mapping Table

HC-SR04 Pin Pi 5 Physical Pin BCM / GPIO Number Notes
VCC Pin 2 5V Power Requires 5V to generate 40kHz pulse reliably.
TRIG Pin 29 GPIO 5 Pi outputs 3.3V; HC-SR04 accepts this as HIGH.
ECHO Pin 31 GPIO 6 MUST pass through voltage divider first.
GND Pin 6 Ground Shared ground with Pi and breadboard.

Numbered Wiring Steps

  1. Connect HC-SR04 VCC to Pi 5V (Physical Pin 2).
  2. Connect HC-SR04 GND to Pi GND (Physical Pin 6).
  3. Connect HC-SR04 TRIG directly to Pi GPIO 5 (Physical Pin 29).
  4. Place the 1kΩ resistor in series with the HC-SR04 ECHO pin on the breadboard.
  5. Connect the other end of the 1kΩ resistor to Pi GPIO 6 (Physical Pin 31).
  6. Connect the 2kΩ resistor between the GPIO 6 junction and the breadboard ground rail.
  7. Verify the voltage divider math: Vout = 5V * (2k / (1k + 2k)) = 3.33V. Safe for RP1.

The C Code: Compiling and Running with lgpio

Before compiling, install the lgpio C development headers. Open your terminal and run:

sudo apt update
sudo apt install liblgpio-dev

Below is the complete, compilable C code. It claims the GPIO lines, fires a 10-microsecond trigger pulse, and uses clock_gettime to measure the echo return with nanosecond-resolution struct math.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <lgpio.h>

// Pin Definitions (BCM Numbering)
#define TRIG_PIN 5
#define ECHO_PIN 6

// Raspberry Pi 5 uses gpiochip4 (Pi 4 uses 0)
#define CHIP_NUM 4 

// Speed of sound in cm/us (approx 343 m/s at 20C)
#define SPEED_OF_SOUND_CM_US 0.0343

long long get_time_us() {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return (long long)ts.tv_sec * 1000000LL + ts.tv_nsec / 1000;
}

int main() {
    // 1. Open the GPIO chip
    int h = lgGpiochipOpen(CHIP_NUM);
    if (h < 0) {
        fprintf(stderr, "Error opening gpiochip%d: %s\n", CHIP_NUM, lgErrorMessage(h));
        return EXIT_FAILURE;
    }

    // 2. Claim pins (No active-low inversion)
    lgGpioClaimOutput(h, 0, TRIG_PIN, 0);
    lgGpioClaimInput(h, 0, ECHO_PIN);

    printf("Sensor initialized. Press Ctrl+C to exit.\n");

    while(1) {
        // 3. Send 10us Trigger Pulse
        lgGpioWrite(h, TRIG_PIN, 1);
        usleep(10);
        lgGpioWrite(h, TRIG_PIN, 0);

        // 4. Wait for Echo to go HIGH
        long long start_time = get_time_us();
        while (lgGpioRead(h, ECHO_PIN) == 0) {
            if (get_time_us() - start_time > 50000) {
                fprintf(stderr, "Timeout waiting for echo start.\n");
                goto next_loop;
            }
        }

        // 5. Measure duration of HIGH pulse
        long long echo_start = get_time_us();
        while (lgGpioRead(h, ECHO_PIN) == 1) {
            if (get_time_us() - echo_start > 50000) {
                fprintf(stderr, "Timeout waiting for echo end.\n");
                goto next_loop;
            }
        }
        long long echo_end = get_time_us();

        // 6. Calculate Distance
        long long pulse_duration_us = echo_end - echo_start;
        double distance_cm = (pulse_duration_us * SPEED_OF_SOUND_CM_US) / 2.0;

        printf("Distance: %.2f cm\n", distance_cm);

        next_loop:
        usleep(200000); // 200ms delay between reads to prevent echo overlap
    }

    lgGpiochipClose(h);
    return EXIT_SUCCESS;
}

Compilation Command

Save the file as sonar.c and compile it, explicitly linking the lgpio library:

gcc -o sonar sonar.c -llgpio
./sonar

Debugging: When the Echo Pulse Fails

Embedded C on Linux is unforgiving. If your code segfaults, hangs, or throws errors, follow this ranked troubleshooting path. Do not guess; read the exact error string.

Ranked Causes and Fixes

Exact Error String Root Cause Fix
lgGpiochipOpen: No such file or directory Wrong chip number. You are using Pi 5 but set CHIP_NUM to 0. Change #define CHIP_NUM 4 for Pi 5. (Use 0 for Pi 4).
lgGpioClaimOutput: GPIO busy Another process (like lgpiod daemon or a Python script) holds the line. Run sudo lsof | grep gpio to find the PID, then kill it.
Timeout waiting for echo start. Hardware fault. The sensor isn't firing, or the voltage divider is wired wrong. Verify 5V at VCC. Check 1k/2k resistor junction with a multimeter.
Distance: 0.00 cm or erratic spikes OS thread preemption. Linux paused your C thread during the while loop. Run with sudo or set SCHED_FIFO real-time priority in code.

The First Three Things to Check When It Fails

  1. The Chip Number: Run ls /dev/gpiochip*. If you only see gpiochip4, your code must use 4. If you see gpiochip0, use 0.
  2. The Voltage Divider: Disconnect the Pi. Use your multimeter's continuity mode to ensure the 2kΩ resistor is actually bridging the Echo line to Ground, not floating.
  3. Permissions: While lgpio uses the standard /dev/gpiochip character device, your user must be in the gpio group. Run sudo usermod -aG gpio $USER and reboot, or just test with sudo ./sonar.
Safety & Hardware Warning: Never hot-swap the HC-SR04 VCC pin while the Pi is powered on. The 5V rail on the Pi 5 is directly tied to the USB-C PD input and can cause brownouts if a sensor draws high inrush current. Always wire power with the board de-energized.

Extending and Simplifying the Build

Once the baseline C code is compiling and reading distances, you have two paths forward depending on your end goal.

Path A: Simplify (For Basic Automation)

If you just need to know "is an object within 10cm?" to trigger a relay, abandon the microsecond polling loop. Use lgpio's alert callbacks. Register a callback on the ECHO pin for both rising and falling edges. This offloads the timing to the kernel's interrupt handler, completely eliminating CPU jitter and allowing your main C thread to sleep or handle network MQTT tasks.

Path B: Extend (For Production Robotics)

If you are building a roving robot where millimeter precision matters, standard Linux scheduling will eventually cause a missed pulse. Extend the build by implementing POSIX real-time scheduling:

#include <sched.h>
// Inside main(), before the while loop:
struct sched_param sp = { .sched_priority = 99 };
if (sched_setscheduler(0, SCHED_FIFO, &sp) == -1) {
    perror("Failed to set real-time priority");
    // Must run with sudo to succeed
}

This locks your thread into the highest CPU priority, ensuring the OS will not preempt your while(lgGpioRead...) loop, guaranteeing sub-microsecond timing accuracy on the RP1 chip.

For deeper API references, consult the official lgpio C library documentation by Joan2937, and review the Raspberry Pi 5 RP1 architecture guide to understand the underlying memory-mapped I/O shifts that make this library necessary.