Why Remote VS Code is the Standard for Raspberry Pi 5 GPIO
Writing embedded C++ directly on a Raspberry Pi using a desktop environment like Thonny or Geany is a bottleneck. The Raspberry Pi 5 (4GB variant, currently ~$60) has the compute power to run a local IDE, but you still lose the advanced IntelliSense, CMake integration, and remote debugging capabilities of a full desktop workstation. Using Raspberry Pi Visual Studio Code via the Remote - SSH extension bridges this gap: you write, compile, and step through breakpoints on your host machine while the code executes natively on the Pi's RP1 southbridge chip.
Time to Complete: 45 minutes
Parts List & Exact Variants
- Microcontroller: Raspberry Pi 5 (4GB RAM, 2023/2024 revision) running Raspberry Pi OS Bookworm (64-bit)
- Input: KY-040 Rotary Encoder module (breakout board with built-in pull-ups)
- Output: 5mm Diffused Blue LED with 330Ω 1/4W carbon film resistor
- Wiring: 27-point solderless breadboard, 6x Female-to-Male 20AWG jumper wires
- Host Machine: Windows 11, macOS, or Ubuntu desktop with VS Code Remote - SSH extension installed
Hardware Wiring and Pin Mapping for the Pi 5
The Raspberry Pi 5 uses the new RP1 I/O controller. Unlike the Pi 4, the physical pins map to a different internal GPIO chip device tree node. We are using BCM (Broadcom) numbering for the software definitions.
| Component Pin | Pi 5 Physical Pin | BCM GPIO Number | Function in Code |
|---|---|---|---|
| KY-040 CLK | 11 | 17 | Encoder Clock (Input with Pull-up) |
| KY-040 DT | 13 | 27 | Encoder Data (Input with Pull-up) |
| KY-040 GND | 9 | N/A | System Ground |
| LED Anode (+) | 32 | 12 | PWM Output (via 330Ω Resistor) |
| LED Cathode (-) | 14 | N/A | System Ground |
Configuring Visual Studio Code for Remote C++ Compilation
To compile C++ on the Pi from your host, we use the lgpio library. The older pigpio and wiringPi libraries have severe compatibility issues with the Pi 5's RP1 chip. lgpio is the modern, officially recommended GPIO library for Bookworm.
- Install Dependencies on the Pi: Open your VS Code integrated terminal (connected via SSH to the Pi) and run:
sudo apt update && sudo apt install build-essential cmake liblgpio-dev - Initialize CMake: In your project root, create a
CMakeLists.txtfile:cmake_minimum_required(VERSION 3.16) project(pi5_encoder CXX) set(CMAKE_CXX_STANDARD 17) add_executable(encoder_main main.cpp) target_link_libraries(encoder_main PRIVATE lgpio) - Configure VS Code Launch: Create a
.vscode/launch.jsonto enable hardware breakpoints. Use thegdbdebugger targeting the remote ARM64 architecture.
The Complete Compilable C++ Source Code
This code targets the Raspberry Pi 5 (4GB). It reads the quadrature signals from the rotary encoder and adjusts the PWM duty cycle of the LED. It includes explicit error handling for GPIO claims, which is critical when debugging hardware faults.
#include <iostream>
#include <lgpio.h>
#include <unistd.h>
#include <csignal>
// --- Pin Definitions (BCM) ---
// Pi 5 RP1 southbridge maps the main 40-pin header to gpiochip4
#define GPIO_CHIP 4
#define ENC_CLK 17 // Encoder Clock
#define ENC_DT 27 // Encoder Data
#define LED_PWM 12 // PWM LED output
static int gpio_handle = -1;
volatile bool keep_running = true;
void signal_handler(int signum) {
keep_running = false;
}
int main() {
signal(SIGINT, signal_handler);
std::cout << "Initializing Pi 5 GPIO via lgpio...\n";
// Open the GPIO chip (Chip 4 on Pi 5)
gpio_handle = lgGpiochipOpen(GPIO_CHIP);
if (gpio_handle < 0) {
std::cerr << "Failed to open GPIO chip " << GPIO_CHIP
<< ". Error code: " << gpio_handle << std::endl;
return 1;
}
// Claim pins with error checking
if (lgGpioClaimInput(gpio_handle, 0, ENC_CLK) < 0) {
std::cerr << "Failed to claim ENC_CLK (Pin " << ENC_CLK << ")\n";
lgGpiochipClose(gpio_handle);
return 1;
}
if (lgGpioClaimInput(gpio_handle, 0, ENC_DT) < 0) {
std::cerr << "Failed to claim ENC_DT (Pin " << ENC_DT << ")\n";
lgGpiochipClose(gpio_handle);
return 1;
}
if (lgGpioClaimOutput(gpio_handle, 0, LED_PWM, 0) < 0) {
std::cerr << "Failed to claim LED_PWM (Pin " << LED_PWM << ")\n";
lgGpiochipClose(gpio_handle);
return 1;
}
int last_clk = lgGpioRead(gpio_handle, ENC_CLK);
int current_clk, current_dt;
int duty_cycle = 50; // Start at 50% brightness
std::cout << "Reading encoder. Ctrl+C to exit.\n";
while (keep_running) {
current_clk = lgGpioRead(gpio_handle, ENC_CLK);
// Detect falling edge on Clock pin
if (current_clk == 0 && last_clk == 1) {
current_dt = lgGpioRead(gpio_handle, ENC_DT);
if (current_dt != current_clk) {
duty_cycle += 5; // Clockwise
} else {
duty_cycle -= 5; // Counter-clockwise
}
// Clamp duty cycle between 0 and 100
if (duty_cycle > 100) duty_cycle = 100;
if (duty_cycle < 0) duty_cycle = 0;
// Apply PWM (lgpio uses 0-1000000 for duty cycle in microseconds,
// but lgGpioWrite is digital. For true hardware PWM on Pi 5,
// we use lgTxPwm)
lgTxPwm(gpio_handle, LED_PWM, 1000.0, duty_cycle, 0, 0);
std::cout << "Duty Cycle: " << duty_cycle << "%\n";
}
last_clk = current_clk;
usleep(2000); // 2ms debounce delay
}
// Cleanup
std::cout << "\nCleaning up GPIO...\n";
lgTxPwm(gpio_handle, LED_PWM, 0, 0, 0, 0); // Stop PWM
lgGpiochipClose(gpio_handle);
return 0;
}
Troubleshooting: Exact Error Strings and Ranked Causes
When bridging host IDEs and remote ARM hardware, compilation and runtime errors often look cryptic. Here are the exact error strings you will encounter and how to fix them.
Error 1: "lgGpiochipOpen: ERROR: /dev/gpiochip4: Permission denied"
Ranked Causes:
- User not in the gpio group: By default, Pi OS Bookworm restricts raw GPIO access. Run
sudo usermod -aG gpio $USERand reboot. - Wrong Chip Number: You are running this code on a Pi 4 (which uses chip 0) instead of a Pi 5. Change
#define GPIO_CHIP 4to0. - Udev rules missing: The
liblgpio-devpackage failed to trigger the udev reload. Runsudo udevadm control --reload-rules && sudo udevadm trigger.
Error 2: "/usr/bin/ld: cannot find -llgpio: No such file or directory"
Ranked Causes:
- Missing Dev Package: You installed
liblgpiobut not the development headers. Runsudo apt install liblgpio-dev. - CMake Cache Stale: VS Code's CMake integration cached the old environment. Delete the
build/directory and runcmake ..again.
Error 3: "Cannot connect to the remote debugger. Timeout after 10000 ms"
Ranked Causes:
- GDB Server Missing: The Pi doesn't have the debug server installed. Run
sudo apt install gdbserver. - SSH Keep-Alive Dropping: The debug port tunnel is timing out. Add
ServerAliveInterval 60to your~/.ssh/configon the host machine.
- Verify the GPIO Chip: Run
ls /dev/gpiochip*in the Pi terminal. If you don't seegpiochip4, your kernel or board variant is different than expected. - Check Group Permissions: Run
groups. Ifgpiois missing, the hardware access layer will block your binary at runtime. - Validate SSH Tunnel: Ensure VS Code isn't trying to bind the debug port to a local process. Check the "Ports" tab in the VS Code bottom panel to confirm the remote gdbserver port is forwarded.
Extending and Simplifying the Build
How to Simplify: If C++ and CMake feel like overkill for a simple blink or read task, strip the build down to Python. Install gpiozero (sudo apt install python3-gpiozero) and use the VS Code Python extension. You lose hardware-level PWM precision and microsecond interrupt latency, but you eliminate the CMake compilation step entirely. Change the launch.json to use debugpy instead of gdb.
How to Extend: To turn this into a robust industrial-style input, add hardware debouncing via the RP1's internal filters. The lgpio library supports lgGpioSetDebounce(). Add lgGpioSetDebounce(gpio_handle, ENC_CLK, 5000); (5000 microseconds) immediately after claiming the pin. This offloads the debounce logic from the CPU to the hardware level, freeing up your main loop to handle MQTT telemetry or display rendering without missing encoder ticks.
Frequently Asked Questions
How do I run Raspberry Pi Visual Studio Code without a monitor?
You must enable headless SSH before disconnecting the display. On the Pi, run sudo raspi-config, navigate to Interface Options > SSH, and enable it. On your host machine, generate an SSH key (ssh-keygen -t ed25519) and copy it to the Pi using ssh-copy-id pi@raspberrypi.local. In VS Code, press F1, type Remote-SSH: Connect to Host, and enter pi@raspberrypi.local. This allows full IDE access, including GUI forwarding for X11 apps if you install an X-server like VcXsrv on Windows.
Is Raspberry Pi Visual Studio Code better than Thonny for Python?
For beginners writing simple gpiozero scripts, Thonny is superior because it includes a built-in micro-stepper and requires zero configuration. However, for any project exceeding 300 lines of code, requiring external API integrations, or utilizing C/C++ for performance-critical loops, VS Code is vastly better. VS Code provides Git integration, multi-file refactoring, and remote debugging that Thonny simply cannot match in a production or advanced hobbyist environment.
Why does my Raspberry Pi Visual Studio Code IntelliSense show red squiggles for GPIO?
Red squiggles under headers like #include <lgpio.h> occur because VS Code's IntelliSense engine runs on your host machine (Windows/Mac), which doesn't have the ARM64 Linux headers installed locally. To fix this, install the C/C++ Extension Pack and configure the c_cpp_properties.json file. Set the compilerPath to the remote GCC path (/usr/bin/aarch64-linux-gnu-gcc) and ensure the configurationProvider is set to ms-vscode.cmake-tools so it reads the include directories directly from your CMake configuration.






