VSCode + Renode:打造现代化的嵌入式仿真开发环

在传统的嵌入式开发中,我们往往需要依赖实体开发板、仿真器(ST-Link/J-Link)、各种驱动、复杂的 IDE(如 IAR、Keil),不仅环境搭建繁琐,还会受到硬件数量、条件、测试风险的限制。

但随着 Renode(Antmicro) 的成熟,现在我们终于可以:

  • 使用 VSCode + GCC 做现代化的编辑体验
  • 使用 Renode 模拟 **CPU + 外设 **
  • 使用 GDB Server 实现调试
  • 甚至可以在服务器(树莓派/OrangePi)上远程跑 Renode

过去几天,我一步步搭好了从 VSCode → Renode → GDB 的完整链路,并解决了一堆坑。 这篇文章记录完整的流程、关键配置、常见错误与解决方式。

📌 一、为什么选择 Renode + VSCode?

现代嵌入式开发已经从这样的方式:

❌ IAR + 实物板子 + 烧录器 + 串口线 + 硬件依赖 ❌ 调试断点和步进完全依赖物理板子 ❌ 一个芯片换平台就要重装一堆东西

逐渐走向:

✔ VSCode + GCC:轻量现代化开发体验 ✔ Renode:模拟 STM32/ESP32 等设备,包含 UART、SPI、I2C、GPIO、DMA ✔ GDB Remote:跨平台、可远程调试 ✔ 自动化、可脚本化

这套方式在国外公司非常常见,原因是:

  • 更高的开发效率
  • 测试可自动化
  • CI/CD 可跑仿真
  • 对硬件依赖更低

而在国内还不太普及,所以我自己实践后写下这篇文章,希望帮助更多人。

步骤:

  1. 安装Renode
  2. 配置Renode(编辑Renode的脚本)
  3. 配置VScode(.vscode/tasks.josn/.vscode/launch.josn)
  4. 测试

目标:

  1. 实现编写代码控制renode模拟外设工作
  2. 可以使用vscode配合renode对代码进行仿真

📌 二、安装 Renode

Renode 提供多个版本,你只需要选一个能跑的:

资源下载:[Renode的github仓库](renode/renode: Renode - Antmicro's open source simulation and virtual development framework for complex embedded systems)

我的选择:

复制代码
renode-latest.linux-portable-dotnet.tar.gz

理由:

  • 自带 dotnet runtime
  • 不依赖系统的 dotnet/mono
  • 可在 OrangePi / Raspberry Pi 上运行

解压后直接运行:

bash 复制代码
./renode

📌 三、配置Renode

写一个最基础的Renode.resc文件(STM32F103),这个Renode.resc相当于你要模拟MCU的配置表,Renode加载这个.resc,模拟出你想要的环境

这是我最终可运行的版本(可直接用):

bash 复制代码
using sysbus

mach create "stm32f103"
machine LoadPlatformDescription @platforms/cpus/stm32f103.repl

# 加载固件,renode加载orangepi上.elf文件
machine LoadELF @/home/orangepi/object/renode_portable/firmware.elf

# 打开 UART2
showAnalyzer sysbus.usart2

# 开 GDB 服务(端口 3333),让vscode可以使用GDB配合renode实现可视化debug
machine StartGdbServer 3333 true


start

你可以把它保存为:

复制代码
run_stm32.resc

📌 四、VSCode 调试配置

配置launch.json,这是最关键的一步。

直接贴可运行版本(适用于 ARM Cortex-M3 / STM32F1):

kotlin 复制代码
{
    "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" }
            ],

            /* On restart: pause, reload ELF, reset CPU (mimic your manual sequence) */
            "postRemoteConnectCommands": [
                { "text": "monitor machine Pause" },
                { "text": "monitor sysbus LoadELF @/home/orangepi/object/renode_portable/firmware.elf" },
                { "text": "monitor cpu Reset" }
            ],

            "externalConsole": false,

            "targetArchitecture": "arm"
        }
    ]
}

核心点:

json 复制代码
"miDebuggerPath": "arm-none-eabi-gdb",
"miDebuggerServerAddress": "xxx.xxx.xxx.xxx:3333",

解释:

  • miDebuggerPath:本机要使用的 GDB(你电脑上安装的)
  • miDebuggerServerAddress:远端 Renode 的 GDB server 监听地址
json 复制代码
"preLaunchTask": "Run Renode Remote",

VSCode 会先执行名称为 "Run Renode Remote" 的任务(在 tasks.json 里)。

json 复制代码
"postRemoteConnectCommands": [
    { "text": "monitor machine Pause" },
    { "text": "monitor sysbus LoadELF @/home/orangepi/object/renode_portable/firmware.elf" },
    { "text": "monitor cpu Reset" }
],

这是整个调试流程的灵魂。

逐句解释:

① 暂停仿真

css 复制代码
monitor machine Pause

防止 Renode 继续运行旧固件。

② 在 Renode 内重新加载 ELF

arduino 复制代码
monitor sysbus LoadELF @/home/orangepi/object/renode_portable/firmware.elf

原因:

  • VSCode 每次 build 完成本地有一个 ${workspaceFolder}/build/firmware.elf
  • 但是远程服务器上的 ELF 通常在另一个目录,比如 /home/orangepi/object/...
  • 重新加载 ELF 可以让 Renode 与最新符号一致

这是 "本地 build → scp → 远端自动加载" 的机制。

③ 重置 CPU(类似 reset halt)

复制代码
monitor cpu Reset

相当于:

  • 程序从头开始运行
  • 下一步 VSCode 会停在 main()

这个序列相当于模拟 FreeRTOS 中的 debug reset。

Tasks.json配置

出了launch.json,还有tasks.json需要配置,配置如下:

perl 复制代码
{
    "version": "2.0.0",
    "tasks": [

        /* === 1. CMake Configure === */
        {
            "label": "CMake Configure",
            "type": "shell",
            "command": "cmake",
            "args": [
                "-S", "${workspaceFolder}",
                "-B", "${workspaceFolder}/build",
                "-DCMAKE_BUILD_TYPE=Debug"
            ],
            "problemMatcher": ["$gcc"]
        },

        /* === 2. CMake Build === */
        {
            "label": "CMake Build",
            "type": "shell",
            "command": "cmake",
            "args": [
                "--build", "${workspaceFolder}/build",
                "--config", "Debug"
            ],
            "dependsOn": ["CMake Configure"],
            "problemMatcher": ["$gcc"]
        },

        /* === 3. 上传 ELF === */
        {
            "label": "Upload ELF",
            "type": "shell",
            "command": "scp",
            "args": [
                "${workspaceFolder}/build/firmware.elf",
                "root@192.168.31.135:/home/orangepi/object/renode_portable/"
            ],
            "dependsOn": ["CMake Build"],
            "problemMatcher": []
        },

        /* === 4. 上传 RESC === */
        {
            "label": "Upload RESC",
            "type": "shell",
            "command": "scp",
            "args": [
                "${workspaceFolder}/renode/platform.resc",
                "root@xxx.xxx.xxx.xxx:/home/orangepi/object/renode_portable/"
            ],
            "problemMatcher": []
        },

        /* === 5. 关闭旧 Renode 实例 === */
        {
            "label": "Close Renode Remote",
            "type": "shell",
            "command": "ssh",
            "args": [
                "root@xxx.xxx.xxx.xxx",
                "pkill renode || true"
            ],
            "problemMatcher": []
        },

        /* === 6. 启动 Renode Remote(后台任务) === */
        {
            "label": "Run Renode Remote",
            "type": "shell",
            "command": "ssh",
            "args": [
                "root@xxx.xxx.xxx.xxx",
                "/home/orangepi/object/renode_portable/renode /home/orangepi/object/renode_portable/platform.resc"
            ],
            "dependsOn": [
                "Upload ELF",
                "Upload RESC",
                "Close Renode Remote"
            ],
            "isBackground": true,

            "problemMatcher": {
                "owner": "Renode",
                "pattern": [
                    {
                        "regexp": "^(?!.*)",
                        "file": 0,
                        "line": 0,
                        "message": 0
                    }
                ],
                "background": {
                    "activeOnStart": true,
                    "beginsPattern": "Renode, version .*",
                    "endsPattern": ".*Loaded SVD.*"
                }
            }
        }
    ]
}

测试

使用vscode的一键debug,可以看到vscode正常和renode通讯,打断点和uart输出正常:

相关推荐
世转神风-15 小时前
linux-嵌入式开发基础-网线直连-局域网传输文件-快速完成文件替换
linux·嵌入式
北京迅为1 天前
【北京迅为】iTOP-4412精英版使用手册-第七十八章 Qt界面切换
linux·人工智能·嵌入式·4412
大聪明-PLUS1 天前
关于 systemd 和桌面应用程序自动启动
linux·嵌入式·arm·smarc
Channon_2 天前
专题四:内存战场的无声战役——压缩、共享与空间复用
缓存·嵌入式·空间复用
大聪明-PLUS2 天前
了解 Linux 系统中用于流量管理的 libnl 库
linux·嵌入式·arm·smarc
大聪明-PLUS2 天前
使用 Shell 脚本生成配置文件的 6 种方法
linux·嵌入式·arm·smarc
大聪明-PLUS2 天前
Linux 下的 C 语言编程:创建命令行 shell:第二部分
linux·嵌入式·arm·smarc
大聪明-PLUS2 天前
在 Linux 6.8 中创建自定义系统调用
linux·嵌入式·arm·smarc
大聪明-PLUS2 天前
使用 Linux 命令轻松构建数据库
linux·嵌入式·arm·smarc
大聪明-PLUS2 天前
如何编写你的第一个 Linux 内核模块
linux·嵌入式·arm·smarc