第二章:平台搭建——创建第一个虚拟平台
好,咱们正式开始动手。上一章聊了 Renode 能干什么,这一章我们就来搭一个能跑起来的虚拟平台。说白了,就是让 Renode 认识你的硬件——CPU 是什么、外设挂在哪、内存多大。
我个人习惯,每开始一个新项目,第一件事就是写平台描述文件。这玩意儿后缀是 .repl,全称 Renode Platform Description Language。你把它当成硬件原理图的文本版就行。
2.1 平台描述文件(.repl)长什么样?
先看一个最简单的例子。假设我们要模拟一个 STM32F103 级别的 Cortex-M3 芯片,最小系统长这样:
cpu: CPU.CortexM @ sysbus
frequency: 72000000
init:
VectorTableOffset: 0x00000000
flash: Memory.MappedMemory @ sysbus 0x00000000
size: 0x00100000
ram: Memory.MappedMemory @ sysbus 0x20000000
size: 0x00020000
uart: UART.STM32F4_USART @ sysbus 0x40013800
-> cpu@0x00000053
这段代码干了三件事:
- 声明 CPU:Cortex-M3 内核,主频 72MHz,中断向量表从 0x0 开始。
- 挂载内存:Flash 1MB 在 0x00000000,RAM 128KB 在 0x20000000。
- 挂载外设:UART 寄存器基址 0x40013800,中断号 0x53(即 IRQ 83)。
注意那个 @ sysbus 的写法。sysbus 是 Renode 内部的系统总线,所有挂载在它下面的设备,地址空间都是全局可见的。你想想看,这跟真实芯片的 AHB/APB 总线是一个道理。
-> cpu@0x00000053 表示这个外设的中断连接到 CPU 的 0x53 号中断线。这个编号必须跟芯片手册的 NVIC 中断号一致。我刚开始写的时候经常搞错,后来养成了先查手册再写 repl 的习惯。
2.2 外设挂载的两种方式
实际项目中,外设挂载有两种常见写法。我列个表对比一下:
| 方式 | 语法 | 适用场景 |
|---|---|---|
| 直接挂载 | UART.STM32F4_USART @ sysbus 0x40013800 |
外设直接挂在系统总线上,地址固定 |
| 桥接挂载 | apb1: Bus.BusBridge @ sysbus 0x40000000 -> uart: UART.STM32F4_USART @ apb1 0x00003800 |
外设在桥接总线后面,地址需要偏移 |
直接挂载最简单,但真实芯片很少这么干。STM32 的 APB1 总线基址是 0x40000000,UART2 的偏移是 0x00003800,所以实际地址是 0x40003800。用桥接挂载能更真实地模拟总线拓扑。
2.3 外设配置的细节
外设挂上去之后,还得配参数。比如 UART 的波特率、GPIO 的初始电平、定时器的预分频值。这些配置写在 init: 块里:
uart: UART.STM32F4_USART @ sysbus 0x40013800
init:
BaudRate: 115200
Parity: None
StopBits: 1
DataBits: 8
嗯,这里要注意:init: 块里的参数是 Renode 启动时一次性写入的,不是运行时动态配置。如果你想模拟驱动初始化过程,那得在测试脚本里写。
我个人习惯,把外设的默认配置写在 repl 里,这样每次启动环境都是一致的。测试驱动时,再通过故障注入来改变这些配置,看看驱动能不能正确处理异常情况。
2.4 完整的平台描述文件示例
下面是一个稍微完整点的例子,模拟了一个带 GPIO、UART、Timer 的 Cortex-M4 平台:
cpu: CPU.CortexM @ sysbus
frequency: 168000000
init:
VectorTableOffset: 0x08000000
flash: Memory.MappedMemory @ sysbus 0x08000000
size: 0x00200000
ram: Memory.MappedMemory @ sysbus 0x20000000
size: 0x00040000
gpioa: GPIO.STM32F4_GPIO @ sysbus 0x40020000
init:
Mode: [0, 1, 2, 3, 4, 5, 6, 7] -> Output
OutputType: PushPull
uart1: UART.STM32F4_USART @ sysbus 0x40011000
init:
BaudRate: 115200
-> cpu@0x00000037
timer2: Timer.STM32F4_TIM @ sysbus 0x40000000
init:
Prescaler: 168
Period: 1000
-> cpu@0x0000001C
这个平台有 2MB Flash、256KB RAM、一组 GPIO、一个 UART、一个定时器。够跑一个简单的 RTOS 或者裸机程序了。
2.5 知识结构图
下面这张图总结了平台描述文件的核心组成和挂载流程:
2.6 启动你的第一个虚拟平台
文件写好了,怎么跑?很简单,在 Renode 命令行里执行:
(monitor) mach create "my_board"
(machine-0) machine LoadPlatformDescription @/path/to/my_board.repl
(machine-0) sysbus.cpu StartGdbServer 3333
(machine-0) sysbus.cpu ExecutionMode SingleStep
这三条命令的意思是:
- 创建一个名为 my_board 的虚拟机实例。
- 加载我们写的平台描述文件。
- 启动 GDB 服务器,端口 3333,方便调试。
然后你就可以用 arm-none-eabi-gdb 连接上去,下载固件、设断点、单步执行了。跟真芯片调试一模一样。
2.7 常见问题与排查
我遇到过不少新手在第一步就卡住。这里列几个常见问题:
- 平台加载失败:检查 repl 文件路径是否正确,语法有没有拼写错误。Renode 的报错信息还算友好,仔细看。
- CPU 不执行代码:确认 VectorTableOffset 指向了正确的 Flash 地址。如果固件是 0x08000000 开始的,但 VectorTableOffset 设成了 0x00000000,CPU 会直接跑飞。
- 外设寄存器读不到:检查地址是否跟芯片手册一致。我曾经把 UART 的基址写错了一位,结果读出来的数据全是 0xFF。