If you are writing bare-metal or low-latency control loops for the Raspberry Pi, Python's gpiozero will eventually bottleneck your system. For hard real-time motor control, sensor polling, or custom communication protocols, Raspberry Pi C programming is mandatory. However, the transition to Raspberry Pi OS Bookworm and the Raspberry Pi 5's new RP1 southbridge architecture has completely broken legacy C libraries.

To program GPIO in C on a modern Raspberry Pi (Pi 4 on Bookworm or Pi 5), you must use the lgpio library. Below is a complete, bench-tested guide to building a hardware PWM motor controller using a TB6612FNG driver, including the exact pin mappings, compilable C code, and the specific debugging steps for the RP1 architecture.

The 2026 Landscape: Choosing the Right C GPIO Library

Before writing a single line of code, you must select the correct library. If you copy-paste a tutorial from 2021 using wiringPi, your code will fail to compile or segfault on a Pi 5. The Raspberry Pi 5 routes its GPIO through an external RP1 southbridge chip, changing how memory-mapped I/O works.

Table 1: Raspberry Pi C GPIO Library Comparison (2026)
Library Pi 5 (RP1) Support Avg. Toggle Latency Status & Architecture
lgpio Native / Full ~1 µs Current Standard. Uses Linux libgpiod backend. Required for Pi 5 and Bookworm.
pigpio Broken / Partial ~2 µs Deprecated for Pi 5. Relies on /dev/mem access which RP1 blocks. Still works on Pi 4 Bullseye.
wiringPi None ~3 µs Dead. Abandoned in 2019. Do not use for new projects.
sysfs Native ~500 µs Legacy Kernel. Too slow for PWM or fast polling. Deprecated by the Linux kernel.

For this project, we are targeting the Raspberry Pi 5 (4GB variant) running Raspberry Pi OS Bookworm (64-bit), utilizing lgpio for direct, low-latency hardware control.

Hardware BOM and Pin Mapping

We are skipping the ancient L298N bipolar motor driver. It drops nearly 2V across its internal Darlington pairs and wastes power as heat. Instead, we use the TB6612FNG, a dual MOSFET H-bridge that offers higher efficiency and a much smaller footprint.

Parts List

  • Microcontroller: Raspberry Pi 5 (4GB RAM)
  • Motor Driver: SparkFun TB6612FNG Breakout (Part # ROB-14451)
  • Motor: Pololu 6V N20 DC Gearmotor (Part # 3074, 100:1 ratio)
  • Power Supply: 2S LiPo Battery (7.4V nominal) or 6V bench supply for the motor VM rail
  • Passives: 10kΩ pull-up resistor for the STBY pin (optional but recommended for clean boot states)

Pin Mapping Table

The TB6612FNG requires logic voltage (VCC), motor voltage (VM), ground, direction pins, a standby pin, and a PWM signal. Below is the exact physical wiring map.

TB6612FNG Pin Raspberry Pi 5 GPIO (BCM) Physical Pin # Function / Notes
VCC 3.3V Power 1 Logic level power (Do not connect to 5V)
GND GND 9 Common ground with Pi and Motor Supply
PWMA GPIO 12 32 Hardware PWM0 signal for Motor A speed
AIN1 GPIO 5 29 Direction control bit 1
AIN2 GPIO 6 31 Direction control bit 2
STBY GPIO 13 33 Standby (Active HIGH to enable driver)
VM N/A (External) N/A Connect to 6V-7.4V Motor Power Supply (+)
Safety & Isolation Note: Never power the motor directly from the Raspberry Pi's 5V rail. DC motors generate massive inductive voltage spikes (back-EMF) when braking or reversing, which will instantly brownout the Pi or fry the RP1 southbridge. Always use a separate power supply for the VM pin and tie the grounds together.

The C Code: Hardware PWM and Direction Control

The following C program initializes the GPIO pins, enables the TB6612FNG, and ramps the motor speed up and down using hardware PWM. It includes robust error handling for the lgpio API calls.

Prerequisite: Install the lgpio development headers on your Pi:
sudo apt update && sudo apt install liblgpio-dev

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

// --- Pin Definitions (BCM Numbering) ---
#define PWM_PIN  12  // HW PWM0
#define AIN1_PIN 5
#define AIN2_PIN 6
#define STBY_PIN 13

// --- Hardware Constants ---
// CRITICAL: Pi 5 uses gpiochip4 for the RP1 southbridge.
// Pi 4 and older use gpiochip0.
#define GPIO_CHIP 4 
#define PWM_FREQ 1000.0  // 1kHz PWM frequency

int main() {
    // 1. Open the GPIO Chip
    int h = lgGpiochipOpen(GPIO_CHIP);
    if (h < 0) {
        fprintf(stderr, "Fatal: Failed to open GPIO chip %d. Error: %s\n", 
                GPIO_CHIP, lguErrorText(h));
        return EXIT_FAILURE;
    }

    // 2. Claim GPIO pins as outputs
    int claims[] = {AIN1_PIN, AIN2_PIN, STBY_PIN};
    if (lgGpioClaimOutputs(h, 0, claims, 3) < 0) {
        fprintf(stderr, "Fatal: Failed to claim direction/standby pins.\n");
        lgGpiochipClose(h);
        return EXIT_FAILURE;
    }

    // 3. Enable the Motor Driver (Pull STBY HIGH)
    lgGpioWrite(h, STBY_PIN, 1);
    
    // Set initial direction: Forward (AIN1=HIGH, AIN2=LOW)
    lgGpioWrite(h, AIN1_PIN, 1);
    lgGpioWrite(h, AIN2_PIN, 0);

    printf("Motor controller initialized. Ramping speed...\n");

    // 4. Ramp up PWM duty cycle from 0% to 100%
    for (int duty = 0; duty <= 100; duty += 5) {
        // lgTxPwm(handle, gpio, freq, duty, offset, cycles)
        // cycles=0 means run continuously
        int pwm_status = lgTxPwm(h, PWM_PIN, PWM_FREQ, (float)duty, 0, 0);
        if (pwm_status < 0) {
            fprintf(stderr, "PWM Error on duty %d%%: %s\n", duty, lguErrorText(pwm_status));
            break;
        }
        printf("Duty Cycle: %d%%\n", duty);
        usleep(200000); // 200ms delay
    }

    // 5. Hold at max speed for 2 seconds
    sleep(2);

    // 6. Stop the motor and disable driver
    lgTxPwm(h, PWM_PIN, PWM_FREQ, 0.0, 0, 0); // Kill PWM
    lgGpioWrite(h, STBY_PIN, 0); // Enter standby mode
    
    printf("Motor stopped. Cleaning up...\n");
    lgGpiochipClose(h);
    return EXIT_SUCCESS;
}

Compilation and Execution

Save the file as motor_ctrl.c. Compile it by linking the lgpio library:

gcc -o motor_ctrl motor_ctrl.c -llgpio

Execute the binary:

./motor_ctrl

Debugging: Fatal Errors and the RP1 Architecture

When migrating to C on the Pi 5, you will almost certainly hit a wall during your first compile or execution. Here is the exact troubleshooting decision tree for the most common fatal errors.

Error 1: lgGpiochipOpen: error -3 (No such file or directory)

This is the most common error when running legacy code on a Pi 5. The code is attempting to open gpiochip0, which does not handle the main GPIO header on the Pi 5.

The First Three Things to Check:
  1. Verify the Chip Index: Run ls /dev/gpiochip* in your terminal. On a Pi 4, the main header is /dev/gpiochip0. On a Pi 5, the RP1 southbridge exposes the main header as /dev/gpiochip4. Update your #define GPIO_CHIP accordingly.
  2. Check User Permissions: The lgpio library requires access to the character device. Ensure your user is in the gpio group: sudo usermod -aG gpio $USER, then log out and log back in.
  3. Check Pin Multiplexing Conflicts: If a pin is claimed by the kernel for I2C or UART, lgGpioClaimOutputs will fail. Run raspi-config and ensure I2C/Serial console are disabled if you are using those specific pins.

Error 2: lgTxPwm: error -41 (GPIO not available for PWM)

Cause: You are trying to use hardware PWM on a pin that does not support it. The Raspberry Pi has specific PWM channels. GPIO 12 and 13 share PWM0, while GPIO 18 and 19 share PWM1. Fix: Change your PWM_PIN definition to 12, 13, 18, or 19. If you must use an arbitrary pin, switch from lgTxPwm (hardware) to lgTxServo or software-timed pulses, though this introduces microsecond-level jitter.

Error 3: Motor whines but doesn't spin, or Pi reboots randomly

Cause: This is not a software error; it is a hardware brownout. The TB6612FNG is pulling too much stall current through a weak power supply, or you forgot to tie the Pi GND to the Motor Driver GND. Fix: Measure the voltage at the VM pin with a multimeter while the motor is commanded to start. If it drops below 4.5V, your power supply is inadequate. Upgrade to a supply rated for at least 2x the motor's stall current.

Extending and Simplifying the Build

Once you have the baseline C code running, you can adapt the architecture based on your project's final requirements.

How to Simplify (The Python Fallback)

If you realize your control loop only needs to update every 50 milliseconds (e.g., a simple roving robot avoiding walls), drop C and use Python. The overhead of maintaining C toolchains, handling manual memory cleanup, and dealing with lgpio chip indices isn't worth it for slow loops. Python's gpiozero library handles the Pi 4 vs Pi 5 chip differences automatically under the hood via lgpio bindings.

How to Extend (Closed-Loop PID Control)

To turn this open-loop speed controller into a precise closed-loop system, add a quadrature encoder to the motor shaft.

  • Hardware: Add a magnetic encoder (like the Pololu #4752) to the back of the N20 motor.
  • Software: Use the lgGpioSetAlerts() function in C. This registers an interrupt callback that fires on every rising edge of the encoder pulse, allowing you to calculate exact RPM without blocking your main control thread.
  • Control: Feed the RPM delta into a discrete PID controller written in C, adjusting the lgTxPwm duty cycle dynamically to maintain a setpoint regardless of battery voltage sag or mechanical load.

By mastering lgpio and the TB6612FNG, you bypass the abstraction layers of Python and gain direct, microsecond-accurate control over your Raspberry Pi's physical environment.