Rust单片机编程:用一块STM32板发送http请求

本文多处参考了The Embedded Rust Book,建议对Rust嵌入式开发尚不熟悉的同学反复观看这本书。
由于例子使用的开发板为STM32F3DISCOVERY,与我们的有一定区别,因此各种操作与书中提及多有出入,以本文为准

实验材料

  • 一块STM32F407板,板载WIFI模块(ESP8266)
  • 服务器:Ubuntu 22.04.1,非常多的USB 3.0/3.1接口,无图形界面

环境配置

在一切开始之前,首先使用合适的Type-C线将服务器的任一USB口与板子的CMSIS-DAP接口连接。

基本的编译、烧写和调试(仿真等)环境可以参照这一章节配置,此处仅列举需要修改的步骤和有必要补充的信息:

自定义udev规则

注意:这一小节与The Embedded Rust Book出入非常大

我们希望OpenOCD等在不需要root权限的情况下就可以自由与开发板通讯,因此需要自定义udev的规则。运行lsusb命令列出USB设备信息,我们可能获得类似如下的内容:

Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 002 Device 002: ID 174c:3074 ASMedia Technology Inc. ASM1074 SuperSpeed hub Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 007: ID c251:f001 Keil Software, Inc. CMSIS-DAP Bus 001 Device 002: ID 174c:2074 ASMedia Technology Inc. ASM1074 High-Speed hub Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

这里注意行末设备名为CMSIS-DAP的设备即为我们的板子1。我们以这行为例,解读一下lsusb返回内容的含义:

  • Bus 001:表示设备连接到001号总线
  • Device 007:表示这是连接到该总线上的第007号设备
  • ID c251:f001: 这里的ID分为两部分:第一部分是idVendor(设备产商),值为c251;第二部分为idProduct(设备类型编号),值为f001

有了idVendoridProduct,接下来我们需要在/etc/udev/rules.d/70-st-link.rules文件里追加一行(若无文件则新建)2

# OWNER的值请填写开发者自己使用的用户名 ATTRS{idVendor}=="c251", ATTRS{idProduct}=="f001", OWNER="ysun"

写好规则后使用下述命令应用(无需拔掉设备重新插上):

# 这里的action参数至关重要 # 如果不指定它的值为change,内核会通知udev将所有设备都认作新设备 # 这可能有非常恶劣的影响 sudo udevadm control --reload-rules && sudo udevadm trigger --action=change

编译安装OpenOCD

The Embedded Rust Book中的指引为使用包管理器直接安装OpenOCD

但对于某些情况,比如我们的Ubuntu 22.04.1,通过apt安装的OpenOCD为v0.11.0版本。

这一版对STM32F4x系列的支持还比较糟糕,因此墙裂建议手动编译一下最新的OpenOCD以防止犯病:

# 安装(编译时)必需依赖 sudo apt install libtool libhidapi-dev libusb-1.0-0-dev git clone https://github.com/openocd-org/openocd.git && cd openocd ./configure --enable-cmsis-dap make sudo make install

环境验证

为检验工具和驱动是否已被正确配置,我们需要验证OpenOCD能否正常工作(与板上自带的ST-Link调试硬件通信)。在项目根目录下创建openocd.cfg文件,并写入:

# 设备接口配置文件 source [find interface/cmsis-dap.cfg] # 理论上udev规则配置好可以自动获取到,若不能则可以手动指定: # cmsis_dap_vid_pid 0xc251 0xf001 # 这是一个历史遗留问题,其实是CMSIS-DAP v2 bulk interface自动检测的bug # 对于一些v1的设备会误被判做是v2,导致通信失败,详见这个commit: # https://github.com/openocd-org/openocd/commit/3da0c2504c14ea0736b973fa0b5085c42a9c6ce8 # 该bug在v0.12.0被修复,对之前的版本可以加上下面这行 # cmsis_dap_backend hid # 目标芯片配置文件 source [find target/stm32f4x.cfg]

此时,运行openocd,应该会有类似输出:

Open On-Chip Debugger 0.11.0 Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html Info : auto-selecting first available session transport "swd". To override use 'transport select <transport>'. Info : Listening on port 6666 for tcl connections Info : Listening on port 4444 for telnet connections Info : CMSIS-DAP: SWD Supported Info : CMSIS-DAP: FW Version = ħŮ�Ƽ� Info : CMSIS-DAP: Interface Initialised (SWD) Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 0 TDO = 0 nTRST = 0 nRESET = 1 Info : CMSIS-DAP: Interface ready Info : clock speed 2000 kHz Info : SWD DPIDR 0x2ba01477 Info : stm32f4x.cpu: hardware has 6 breakpoints, 4 watchpoints Info : starting gdb server for stm32f4x.cpu on 3333 Info : Listening on port 3333 for gdb connections

并且程序阻塞住了控制台。

至此环境配置完毕。

仿真

在开始仿真调试前,建议先用cargo-generate从模板仓库开始初始化一个实验项目

仿真大致操作与The Embedded Rust Book无异,但由于我们的板子是STM32F4x,故QEMU的参数要做一定调整。

比如,.cargo/config.toml中需要增加这些内容:

[target.thumbv7em-none-eabihf] runner = "qemu-system-arm -cpu cortex-m4 -machine netduinoplus2 -nographic -semihosting-config enable=on,target=native -kernel" [build] target = "thumbv7em-none-eabihf"

然后就可以正常仿真了(会隐式地以上述参数调用QEMU):

cargo run --example hello --release

如果需要用GDB调试,只需要将启动QEMU的参数如上述.cargo/config.toml文件中替换即可。

上硬件

内存布局

这部分最重要的就是搞清楚板子的内存布局,要确定Flash和SRAM的起始点和长度以确保编译和运行时的行为正确。

主要参照ST的RM0090文档(Rev 19)

太长不看

在项目根目录的memory.x文件中,写入:

MEMORY { /* NOTE 1 K = 1 KiBi = 1024 bytes */ /* TODO Adjust these memory regions to match your device memory layout */ /* These values correspond to the LM3S6965, one of the few devices QEMU can emulate */ FLASH : ORIGIN = 0x08000000, LENGTH = 512K RAM : ORIGIN = 0x20000000, LENGTH = 128K }

详细依据见下文。

Flash布局

架构图在第73页:

我们可以看到,从interface到memory是一条总线连接的,即主内存在使用上是不分区的,即Flash的长度应为所有Sector的大小之和。为了提升性能,对指令码和数据码各自有对应的cache,但不影响主内存的分布。

因此参照表3

Flash应为从0x08000000开始,到0x080FFFFF结束。用末尾减去开头,刚好是1024K。

但上述其实不过是对最高配置的介绍,并非所有的STM32F40x都有高达1M的实际Flash大小,具体仍要参照型号:

STM32F407/417 products

由于我们的板子的详细型号是STM32F407VET6,因此Flash只有512K。

SRAM布局

参照第68页的描述:

由于我们的板子不存在SRAM3,所以SRAM的起始地址应为0x20000000。Backup SRAM只在特殊的电压条件下才会被调用,因此SRAM的长度应标注为192K

但在编译时,还应结合板子的整体架构考虑:

在文字描述中高达192K的“系统”SRAM当中其实还包含了只能经由D-bus主线直接与MCU通信的、大小为64K的CCM内存。

因此实际在编译时可以声明的内存大小只有192 - 64 = 128K。

ESP8266篇

待续。

其他选择

STM32CubeIDE也许是不错的一体化集成开发环境。

常见问题

0. 为什么已经插在USB上了,但lsusb看不到板子?

可能接错到USB-Slave接口上了。可以用sudo dmesg查看详细日志:

1. 使用GDB调试时出现内存错误

如果内存布局设置错误或有严重的编程失误导致内存错误,可能会在OpenOCD输出中看到类似如下报错:

Error: Failed to read memory at 0x2002ffd0

同时GDB也会反馈类似内容:

0x080013aa in cortex_m_rt::HardFault_ (ef=0x0) at src/lib.rs:563 563 atomic::compiler_fence(Ordering::SeqCst);

可能板子被锁住:

出现这种情况时,请首先确认内存布局正确

注意,更改memory.x文件后需要重新cargo build,这点在The Embedded Rust Book中也有提及

另外,建议使用最新版本的OpenOCD,可能在重新开启OpenOCD进程或烧写程序(GDB的load指令)时自动解锁。

如果仍被锁住,则可按照如下步骤处理:

  1. 开启一个openocd进程与板子连接
  2. 在另外一个终端里使用telnetopenocd通讯:
telnet localhost 4444
  1. 在telnet提示符下尝试解锁:
stm32f4x unlock 0

如果顺利的话,会出现如下提示:

stm32f2x unlocked. INFO: a reset or power cycle is required for the new settings to take effect.
  1. 在telnet提示符下继续输入shutdown,然后将板子断电再通电。
  2. 如果上述过程无效,尝试在telnet提示符下使用下面的命令彻底擦除Flash存储中的内容:
stm32f4x mass_erase 0

然后重复第4步。

2. cargo build报错:cannot find linker script memory.x

无论当前项目是否有子crates、目录如何组织,memory.x必须位于项目的根目录下(和最外层的Cargo.toml同级)4

确实有一种特殊情况可以不提供memory.x文件:项目代码中直接引入了BSC (Board Support Crate),比如stm32f3-discovery。除此之外,使用属于其他抽象级别的crates都必须为cortex-m-rt的编译提供memory.x5

1

这里只看到CMSIS-DAP,没有看到STM32相关的信息。sudo dmesg可以在日志中看到细节:

2

这里文件名不是必需为70-st-link.rules,但建议前缀的数字要大于50,否则有可能覆写操作系统的一些默认规则。 有关udev规则,详见这篇指南

3

头几个sector的大小比较小是因为许多板子并未达到1M的Flash大小,因此只有头几个sector,后面较大的则被舍弃了。
这样的设计使得不同型号的板之间的sector布局尽量保持一致,程序移植的兼容性比较好。

4

https://github.com/rust-lang/cargo/issues/9537

5

https://docs.rust-embedded.org/book/start/registers.html#memory-mapped-registers