言成的博客

理解和表达

Embassy 是一个基于 Rust 语言,为嵌入式系统开发设计的异步运行时库,其官方文档《Embassy Book》中给出的所有示例都是基于真实的物理环境,没有任何基于模拟器的内容。

事实上,也很少有相关的工作基于 QEMU 使用 Embassy。确实,几十或者一百多块钱就能买到很好的开发板,而且商家还提供了很好的售后服务和工具链支持,这可比自己折腾模拟器要舒适多了。

话虽如此,但还是有一些理由让我们选择使用模拟器来做嵌入式的开发。这篇文章的主要目的就是整理和总结一些基于 QEMU 使用 Embassy 的过程中可能用到的资料和工具链。

官方 QEMU 的优点是最稳定,各种体系的处理器的仿真都支持。但 QEMU 的主要目标是支持高性能处理器的模拟仿真,单片机支持型号较少,而且最主要的问题是缺少了很多外围设备,而这些外围设备在 Embassy 的使用过程中可能是必须的,可以参考《使用 QEMU 运行 Embassy 示例》。总结起来,Embassy 程序会有一个 boot 过程检查并初始化目标开发板中的一些外围设备,如果缺失这些外围设备的话,程序就会陷入一个无止尽的循环中。以 embassyexamples/stm32f1 项目下的 blinky.rs 为例,第 12 行代码:

1
let p = embassy_stm32::init(Default::default());

即进行了芯片和外围设备的初始化工作。

因为 QEMU 本身是开源的,所以除 QEMU 官方外,很多第三方扩展支持了更多的开发板,有的支持了更多的外围设备仿真:

  • xPack QEMU Arm,缺点是只支持 stm32,且在 2022 年宣布可能后续被移除,不再被推荐使用,而且有些选项是独有的,和官方不兼容;

  • qemu_stm32,暂时没有更多的尝试,可能是一个比较好的选择;

  • sdk-debugger-qemu,这是一个国产开源嵌入式系统 RT-Thread 生态里的一个 QEMU 版本,它只是增加了几个芯片的支持,看起来不是一个很好的选择。

一个可执行目标文件,本身包含一条条二进制指令,所以其最基本的依赖是指令集体系结构Instruction Set Architecture, ISA),ISA 本身还受一些硬件标准的影响,如端序

调用外部操作,这个分为两个方面,操作系统和运行时的动态库,动态库的接口标准

一般用目标三元组(Target Triplet)来描述一个软件运行的目标平台

  • 处理器架构(ISA)
  • 操作系统(Operating System, OS)
  • 应用二进制接口(Application Binary Interface, ABI)标准:运行时库和操作系统提供的系统调用的接口标准

例如

  • arm-none-eabi
    • ISA:arm
    • OS:none,指没有操作系统的裸机
    • ABI:eabi,指嵌入式二进制接口
  • riscv64gc-unknown-none-elf
    • ISA:riscv64gc-unknown,其中 riscv 指的 RISC-V,gc 指 RISC-V 架构的 GC 扩展,unknown 指未知的 CPU 厂商
    • OS:none
    • ABI:elf,指没有任何系统调用的封装支持,但可以生成 ELF 格式的执行程序

交叉(cross),有一类程序,他的作用就是处理其它程序,于是这里就存在两个平台,host 和 target,在主机(host)平台上编译能在目标(target)平台运行的程序,还有构建这类程序的平台 build

ISA

在 CPU 架构中,可能包含小端序(little-endian, el),大端序(big-endian, eb)

OS

none

linux

运行时库

嵌入式应用二进制接口(Embedded Application Binary Interface, EABI)

GNU libc 封装了 Linux 系统调用,并提供 POSIX 接口为主的函数库

硬件浮点(Hard Float, HF)表示使用硬件浮点单元 (FPU) 来进行浮点运算,而不是软件实现的浮点运算,从而提高性能

工具链

Binutils:Binutils 是 GNU 工具之一,它包括链接器、汇编器和其它一些工具,它是二进制代码的处理和维护工具,GMU Binutils

GCC

GLibc

GDB

QEMU

查看可执行文件的格式:file
查看可执行文件的内容:rust-readobj
查看可执行文件的指令:rust-objdump
修改可执行文件:rust-objcopy

获取工具链

想要获取交叉工具链,可以直接检索下载对应的工具链,也可以选择从工具的源码制作,这个步骤同样有一些工具

ARM:

RISC-V

1

项目:embassy 中的 examples/stm32f1

board:stm32vldiscovery

microcontroler:stm32f100rbt6

项目用到的芯片是 stm32f103c8,但这里模拟的芯片是 stm32f100rbt6

1
2
3
4
qemu-system-arm \
-M stm32vldiscovery \
-kernel target/thumbv7m-none-eabi/release/blinky \
-s -S
1
2
3
arm-none-eabi-gdb \
-ex 'file target/thumbv7m-none-eabi/release/blinky' \
-ex 'target remote localhost:1234'

运行结果是 qemu 中没有任何输出,陷入了某个循环。GDB 查看,具体如下:

1
2
3
4
Program received signal SIGINT, Interrupt.
cortex_m_rt::HardFault_ (warning: Could not fetch required XPSR content. Further unwinding is impossible.
ef=0x20004fe0) at src/lib.rs:1046
1046 loop {}

似乎是某种初始化过程?但这个循环一直跳不出来,还是 cortex_m_rt::HardFault_ 指代发生了某种错误?

2

项目:stm32f401_embassy

board:netduinoplus2

microcontroler:stm32f405rgt6

项目用到的芯片是 stm32f401,但这里模拟的芯片是 stm32f405rgt6

1
2
3
4
qemu-system-arm \
-M netduinoplus2 \
-kernel target/thumbv7em-none-eabi/release/stm32f401_embassy \
-s -S
1
2
3
arm-none-eabi-gdb \
-ex 'file target/thumbv7em-none-eabi/release/stm32f401_embass' \
-ex 'target remote localhost:1234'

运行结果是 qemu 中没有任何输出,陷入了某个循环。GDB 查看,具体如下:

1
2
3
Program received signal SIGINT, Interrupt.
0x0800369c in embassy_stm32::rcc::_version::init (config=...) at src/rcc/f247.rs:136
136 PWR.cr1().modify(|w| w.set_vos(crate::pac::pwr::vals::Vos::SCALE1));

似乎也是某种初始化过程?循环也是一直跳不出来。

3

项目:embassy 中的 examples/stm32l4

board:b-l475e-iot01a

microcontroler:stm32l475vg

b-l475e-iot01a 这块板子在 qemu 9.0.0 以上的官方版本中有支持,而且其 MCU stm32l475vg 在 embassy-stm32 中也有支持

1
2
3
4
qemu-system-arm \
-M b-l475e-iot01a \
-kernel target/thumbv7em-none-eabi/release/blinky \
-s -S
1
2
3
arm-none-eabi-gdb \
-ex 'file target/thumbv7em-none-eabi/debug/blinky' \
-ex 'target remote localhost:1234'

芯片型号都已经完全对上了,初始化还是过不去,难绷

4

项目:embassy 中的 examples/stm32f1

board:BluePill

microcontroler:stm32f103

这一次尝试参考了博客:QEMU 仿真模拟 STM32F103 开发板。其中用到了一个经第三方修改后的 QEMU 版本:xPack QEMU Arm

1
2
3
qemu-system-gnuarmeclipse \
-board BluePill \
-image target/thumbv7m-none-eabi/debug/blinky
1
2
3
arm-none-eabi-gdb \
-ex 'file target/thumbv7m-none-eabi/release/blinky' \
-ex 'target remote localhost:1234'

可以通过初始化过程,程序可以运行

gdb 连不上,错误信息如下:

1
2
warning: Can not parse XML target description; XML support was disabled at compile time
Truncated register 18 in remote 'g' packet

通过 apt 包管理器下载了 gdb-multiarch,使用 gdb-multiarch 可以正常调试

Rust 选择了 async/await 作为其异步编程模型。async/await 模型性能高,还能支持底层编程,它的问题是内部实现机制有些复杂,理解和使用起来也不够简单。

在心智模型一节中,我们将完全抛开 Rust 去理解 async/await 模型。在实现一节,我们介绍在 Rust 中实现该模型时,必须引入的一些复杂性。在使用方法一节中,介绍如何在 Rust 中使用异步编程。

阅读全文 »

subproblem

编程即是描述解决问题的步骤,其核心在于如何理解“问题”本身。

有时候我们遇到的问题,解决起来是一环扣一环的。如果我想顺利完成我的2024 春夏开源操作系统训练营之旅,就必须先通过第一阶段,再通过第二阶段,最后完成第三阶段的任务。

另外一种情况是,当问题变得复杂时,我们可以尝试把一个大的父问题分解成相互独立的子问题,只要子问题的结果都有了,就可以用这些子问题的解来解决父问题。如果我想要顺利毕业,唯一的要求就是修够学分,我可以选择先修专业选修课1,再修专业选修课2,或者反过来也行,又或者我同时搞定它们。

阅读全文 »

项目目标:利用 Rust 语言的异步机制应用到操作系统内核,改善内核的并发性能

学习资料

Rust 异步编程:

Embassy:

阅读全文 »
0%