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 |
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)
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
- Connect HC-SR04 VCC to Pi 5V (Physical Pin 2).
- Connect HC-SR04 GND to Pi GND (Physical Pin 6).
- Connect HC-SR04 TRIG directly to Pi GPIO 5 (Physical Pin 29).
- Place the 1kΩ resistor in series with the HC-SR04 ECHO pin on the breadboard.
- Connect the other end of the 1kΩ resistor to Pi GPIO 6 (Physical Pin 31).
- Connect the 2kΩ resistor between the GPIO 6 junction and the breadboard ground rail.
- 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
- The Chip Number: Run
ls /dev/gpiochip*. If you only seegpiochip4, your code must use 4. If you seegpiochip0, use 0. - 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.
- Permissions: While
lgpiouses the standard/dev/gpiochipcharacter device, your user must be in thegpiogroup. Runsudo usermod -aG gpio $USERand reboot, or just test withsudo ./sonar.
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.






