In traditional embedded development, we usually rely on physical development boards, hardware debuggers (ST-Link/J-Link), various drivers, and heavyweight IDEs such as IAR or Keil. This setup not only makes the environment cumbersome to configure, but also limits testing capabilities due to hardware availability, physical conditions, and debugging risks.
With the maturity of Renode (from Antmicro), we can finally:
Use VSCode + GCC for a lightweight, modern development experience
Use Renode to simulate CPUs and peripherals
Use GDB Server to debug firmware remotely
Even run Renode on a server (Raspberry Pi / Orange Pi) and access it from anywhere
Over the past few days, I built a complete workflow from VSCode → Renode → GDB, solved a series of pitfalls, and documented everything — configurations, scripts, common errors, and fixes.
📌 1. Why Renode + VSCode?
Modern embedded development is shifting away from:
❌ IAR + physical board + hardware programmer + serial cables
❌ Breakpoints and stepping limited to physical devices
❌ Reinstalling toolchains when switching MCU families
Towards:
✔ VSCode + GCC — lightweight, modern, cross-platform
✔ Renode — full simulation of STM32 / ESP32 devices, including UART, SPI, I2C, GPIO, DMA
✔ GDB Remote Debugging — platform-agnostic, can debug over network
✔ Automation-ready — suitable for CI/CD and reproducible testing
This workflow is very common in international companies because:
It dramatically improves development efficiency
Enables automated testing
Allows CI pipelines to run MCU simulations
Reduces dependency on hardware
However, it is still uncommon in domestic environments, so I created this article to serve as a practical guide.
Steps
Install Renode
Configure Renode (write .resc script)
Configure VSCode (.vscode/tasks.json / .vscode/launch.json)
Verify end-to-end debugging
Goals
Write code that interacts with Renode-simulated peripherals
Use VSCode + Renode for full-featured debugging workflows
📌 2. Installing Renode
Renode provides multiple binary distributions — choose any that works for your platform:
Repository:
https://github.com/renode/renode
My choice:
renode-latest.linux-portable-dotnet.tar.gz
Reasons:
Bundles its own .NET runtime
Does not require system-installed dotnet/mono
Runs on OrangePi / Raspberry Pi directly
Extract and launch:
./renode
📌 3. Renode Configuration
Create a minimal Renode .resc script (example for STM32F103).
This file describes the MCU configuration Renode simulates — peripherals, firmware, and debug setup.
A working version:
using sysbus
mach create "stm32f103"
machine LoadPlatformDescription @platforms/cpus/stm32f103.repl
Load ELF from OrangePi
machine LoadELF @/home/orangepi/object/renode_portable/firmware.elf
Enable UART2 analyzer
showAnalyzer sysbus.usart2
Start GDB server on port 3333 for VSCode debugging
machine StartGdbServer 3333 true
start
Save it as:
run_stm32.resc
📌 4. VSCode Debug Configuration
The key part is setting up launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug STM32F103 on Renode Remote",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/firmware.elf",
"cwd": "${workspaceFolder}",
"miDebuggerPath": "arm-none-eabi-gdb",
"miDebuggerServerAddress": "xxx.xxx.xxx.xxx:3333",
"preLaunchTask": "Run Renode Remote",
"stopAtEntry": true,
"setupCommands": [
{ "text": "set pagination off" },
{ "text": "set confirm off" },
{ "text": "set print pretty on" }
],
"postRemoteConnectCommands": [
{ "text": "monitor machine Pause" },
{ "text": "monitor sysbus LoadELF @/home/orangepi/object/renode_portable/firmware.elf" },
{ "text": "monitor cpu Reset" }
],
"externalConsole": false,
"targetArchitecture": "arm"
}
]
}
Core parameters:
miDebuggerPath → Local GDB executable
miDebuggerServerAddress → Remote Renode GDB endpoint
And:
"preLaunchTask": "Run Renode Remote"
VSCode automatically starts Renode on the remote board before debugging.
The post-connect sequence:
Pause simulation
Reload the ELF from the remote paths
Reset CPU and sync execution state
This mirrors what you'd normally do manually.
tasks.json
Required build + upload + execution pipeline:
{
"version": "2.0.0",
"tasks": [
{
"label": "CMake Configure",
"type": "shell",
"command": "cmake",
"args": [
"-S", "${workspaceFolder}",
"-B", "${workspaceFolder}/build",
"-DCMAKE_BUILD_TYPE=Debug"
]
},
{
"label": "CMake Build",
"type": "shell",
"command": "cmake",
"args": [
"--build", "${workspaceFolder}/build",
"--config", "Debug"
],
"dependsOn": ["CMake Configure"]
},
...
]
}
This pipeline uploads ELF and .resc to the remote machine, restarts Renode, and attaches GDB automatically.
Testing
Once configured, press F5 in VSCode:
Renode boots remotely
Firmware loads automatically
UART output and breakpoints work as expected
Top comments (0)