Skip to content

eZioPan/rust_on_stm32_notebook

Repository files navigation

Rust on STM32 Notebook

用 Rust 编写 STM32F413VGT6 程序的笔记

Note

这个笔记最早是基于 STM32F411RET6 编写的,不过 STM32F411RET6 的模块相对较少,后来又买了一个 STM32F412RET6;再之后为了验证一些操作,因此又买了一个 STM32F413VGT6,目前就将所有的代码都迁移到 F413 上了

一些基础概念

Armv7E-M

Arm 推出一个指令集架构,指令集架构大致规定了一类 CPU 应该支持的(人类可理解的)指令,以及指令对应的(机器可理解的)二进制表示,以及最基本的寄存器配置要求 Armv7-E 是 Cortex-M4 处理器所属的指令集架构,同属该架构的处理器还有 Cortex-M7
Armv7E-M实际上是 ARMv7-M 的一个扩展架构,前者比后者多了 DSP 相关的指令和电路

Cortex-M4

一颗处理器(等同于 CPU),上面有一颗处理器所必要的电路,
准确来说是 ARM 公司提供的一颗处理器的电路设计稿,而实际生产“这颗处理器”的厂家可以微微修改一些部分
比如 STM32F4 / STM32F7 / STM32G3 系列的 CPU 都基于 Cortex-M4 这个设计稿实现的

STM32F413VGT6

购买了 ARM 提供的“图纸”的厂家(比如 STMicroelectronics)不仅会制造处理器,还会一同把周边的所需的电路(电源、时钟、一些必要的外部设备)一同设计和制造出来。
而且,由于制造出来的芯片已经包含了 处理器、电源、时钟、基本 IO 等等功能,是一个较为完整的系统,且它们还都封装在一个芯片里,
这种芯片又称为 片上系统(System on Chip),英文简称 SoC。

我们手上的 STM32F413VGT6 就是这颗 SoC 的名称,就是那个在电路板上黑色的大方块,上面会写着 STM32F413VGT6 的字样

开发板 / 最小板 / 核心板

而要让这颗 SoC 成功启动,并与我们现有的其它系统交互,它还可以一些简单的外部电路,
比如将外部输入电源稳压到 3.3V(稳压模块、电源滤波模块),将细小的芯片引脚放大到我们可以简单连接导线(2.45mm 引脚),或者是依照要求,给出精度更高的外部时钟源(比如晶振),又或是添加一些按钮、指示灯、USB 口等等,
这部分电路就可以由我们自己设计制造,或者交由 开发板/核心板 制造商制造,
最后我们大概率会获得一个块带着 SoC,有一些外周电路,并可能具有 USB 口的电路板

thumbv7em

Cortex-M4 所使用的指令集的名称,
该名称可以简单拆解为两个部分 thumb 和 v7em,前者是 ARM 推出的用于微控制器的指令集 thumb,后者则直接截取自指令集架构的后三个字母
这个概念常见于软件层面,特别是在编译时指定编译目标时

常见问题

Q00: 就学习 STM32F413VGT6 最重要的文档是哪些

A00: 最重要的莫过于

  1. STM32F413VGT6 的 Datasheet,里面包含了 Block Diagram、引脚定义、GPIO 功能表、内存映射关系 等等编程需要知道的信息,以及各种 电气特性 等等硬件设计需要知道的信息。

  2. STM32F413VGT6 的 Reference Manual,里面包含了详尽的设备功能与使用方法,以及对应的寄存器配置 的信息

  3. 如果涉及到 Cortex 核心自带的功能,那么还需要参考 STM32 Cortex®-M4 MCUs and MPUs programming manual 这个文件,
    比如 SysTick 系统定时器和 NVIC 嵌套向量中断控制器 的内容

    特别的:NVIC 很特殊,它的电路是设计在 Cortex 核心里的,但它的内容则随着芯片型号的不同而不同,因此需要同时参考 2 和 3 中提到的两个文档

  4. 在学习与 Cortex 核心紧密相关的内容时,Armv7-M Architecture Reference Manual 这个文件也常常会出现

Q01: 向 STM32F413VGT6 刷写 程序需要编程器(Flasher)/排错器(Debugger,比如 STLink/JLink/CMSIS-DAP/DAPLink)么?

A01: 理论上不需要,当我们修改 BOOT PIN 至 1 后,会从芯片的 System Memory(是一段 ROM)启动,该程序会启动 USART, USB OTG FS, DFU, I2C, SPI,
然后它们各自会监听特殊的信号,然后再进入刷写阶段,(见 ST 文档 AN2606、AN3155、AN3156、AN4286) 因此,实际上我们只需要一个串口转 USB 的设备,连接好 USART 端口,然后用特定的软件(比如 FLASHER-STM32/STM32CubeProg),就可以刷写程序到板子上了

Q02: 所以排错器是不重要的?

A02: 非也,相反,排错器非常重要,毕竟我们是写程序,那么控制 STM32F413VGT6 的运行的能力,比如 随时中断程序的运行、查看栈空间/外设寄存器的功能则非常的重要
这些只能靠 Debugger 实现。

Q03: 串口是 Debug 口么?

A03: 串口不是 STM32F413VGT6 原生的 Debug 口,有这种误解是因为,我们经常使用已经写好程序的微控制器,而那些程序会启动串口打印一些信息。
STM32F413VGT6 硬件支持的 Debug 接口就两种 JTAG 和 SW

Q04: 如何创建最小程序

A04: 可能必须得使用的库 crate 有 cortex-m-rt、stm32f4 以及 panic-halt 了。
注意,编译成功需要的是 cortex-m-rt 和 panic-halt,不过要能刷写到 STM32F413VG 上,就得加入 stm32f4 这个 crate 了

常见的操作注意事项

  1. 除了我们编写程序、debug 最常用的 VSCode + Cortex-Debug 插件 + OpenOCD 的流程,
    还有一个很常用的控制单片机的方法,就是直接通过命令,通过 OpenOCD 来控制单片机,其具体方法为,通过 telnet 连接上 OpenOCD

    telnet 127.0.0.1 4444

    这里要注意,OpenOCD 默认会在 4444 端口上监听 telnet 连接,具体连接到那个端口,还需要在启动 OpenOCD 之后,观察 OpenOCD 的窗口,才能准确知道地址,之后就可以输入 OpenOCD 的指令了

  2. 如果是手动刷写程序到芯片上,比如通过 OpenOCD 的 telnet 端口刷写程序,那么

    1. 一定要先停机,在刷写,执行命令 halt 可以停机
      就刷写程序来说,我们最好执行的是“重置并立刻停机”,对应的命令为 reset init

    2. 对于已经写了数据的部分,需要先清空,再刷写, 这是由于我们使用的
      或者直接使用 flash write_image erase XXXX.elf,由 OpenOCD 依照 ELF 文件的情况,自动清除所需要的 Flash

  3. 有时候,由于我们配置错误、或者编写的错误、或者向量表不正确,会导致程序无法刷写,甚至是停机和重置都无法完成
    此时有一个方法可以试一试,那就是使用 OpenOCD 的 telnet 命令行

    1. 通过 telnet 链接上 OpenOCD 后

    2. 在 telnet 中输入 reset init,但不要回车执行

    3. 接着按住开发板上的 Reset 按钮

    4. 按回车键执行 reset init,之后立刻松开 Reset 按钮

      此时,OpenOCD 大概率会出现
      [stm32f4x.cpu] halted due to debug-request, current mode: Thread
      这就表示我们停机成功了
      然后我们就可以正常执行刷写流程,将正确的程序刷写到 Flash 中了

  4. 在 VSCode 插件 Cortex-Debug 脱离 OpenOCD 的时候,一般会将 cortex 核心置于 halt 状态
    此时可在 OpenOCD 的 telnet 中使用 resume 命令,让 cortex 核心继续运行

  5. 如果为了排查问题(比如检测 ELF 包含的 segments 的状态)要保留编译过程中输出的中间文件(比如 单个 .rs 源文件编译出来的 .o 文件)
    我们可以使用以下执行进行编译

    cargo rustc --bin <bin 类型源码名> -- emit=obj

    让 rustc 在编译后保留 .o 文件,该文件会留存在 target/<target-triple>/dep/ 目录下
    之后我们就可以通过 readelf 简单读取 ELF 文件的“各种”头部信息,或者通过 arm-none-eabi-objdump 命令详细解析 ELF 文件的内容,常见的参数搭配有

    • 读取 ELF 文件中,每个节头记录的数据

      readelf --program-headers <ELF 文件>
    • 将整个 ELF 文件的内容以 HEX 的格式、分节的方式 dump 下来,若遇到可执行的节,则反汇编其内容,在解析符号时,将 rustc 使用的“转义字符”“反转义回来”,并配上对应的源码

      arm-none-eabi-objdump --full-content --disassemble --demangle=rust --source <ELF 文件> > <输出文件>

      若希望仅展示某个节,则可以追加参数 --section <节名>,若不需非可执行节之外的节的内容,则可以去掉 --full-content 参数

常见的 OpenOCD 指令

help [<命令>]

查看简单的帮助

stm32f4x.cpu curstate

查看 stm32f4x cortex 内核的当前的运行状态

halt

让 cortex 核心立即在当前的执行状态下停机(暂停运行)

resume

让 cortex 核心从当前的状态继续运行

reset [run|halt|init]

重置 cortex 核心的运行状态,并依照参数将核心的运行模式设置为

run

直接运行程序

halt

重置之后立刻停机

init

重置之后立刻停机,但会执行 reset-init 脚本
reset-init 一般出现在 openocd/script/board 和 openocd/script/target 下的 .cfg 文件中
常用于在目标 MCU 重置之后,简单设置与 debug 相关的配置

不给出参数时,等价于 reset run

read_memory <address> <width> <count>

从指定的地址开始,以给定的位数(8/16/32 位)连续读取指定个数的数据
这个命令常用于检查寄存器的状态

write_memory <address> <width> <data>

在指定的地址,以给的定的位数,写入一个数据
这个命令常用于修改寄存器的状态

debug_level [0|1|2|3|4]

OpenOCD 返回的 debug 信息的详细程度

0

仅显示 Error

1

显示 Error 和 Warning

2

显示 Error、Warning 和 Info

3

显示 Error、Warning、Info 和 Debug

4

显示 Error、Warning、Info、Debug 以及低层级 debug 信息

不带参数时,显示当前的 debug_level 默认级别为 2

stm32f4x.cpu arp_examine

当你的 DAPLink 无法自动检测到开发板时,使用该命令,可以强制 OpenOCD 再次检测目标板
这个方法在测试低功耗模式(low-power mode)时会比较有用

rtt start

启动 RTT
这个命令本身,其实非常的常用,但经常出现在启动脚本里出现,反而不太需要手动输入
这里记录下来,主要是为了目标板处于低功耗模式时,可能需要手动,因为在低功耗模式下,OpenOCD 可能并无法正确检测到开发板,也因此无法检测 RTT 的状态,因此,一般来说,我们在 arp_examine 之后,可以接一个 rrt start 来让 OpenOCD 读取到 RTT 的信息