note/stm32-dev-env

STM32 开发环境

ARM 开发总是有着太多的工具可以选择。因为种种原因,这里不涉及 IAR、KEIL MDK 等商业开发工具。

代码工具

GCC

工具链主要包含这些组件:arm-none-eabi-gcc, arm-none-eabi-binutils, arm-none-eabi-newlib, arm-none-eabi-gdb

由于 ARM 的诸多坑,初学者使用 GCC 工具链可能会有些棘手。主要的坑有这些:

  • Linker script。由于各种 ARM 型号的内存起止都不太一样,所以需要使用 linker script 来告诉 GCC 怎样去链接。但是这种东西会有点难以理解。

  • Syscalls。arm-none-eabi-gcc 默认没有提供 syscalls 们的定义,如 _srkb_exit 等,直接运行链接是通不过的:

    ld: libg.a(lib_a-exit.o): in function `exit'
    exit.c:(.text.exit+0x16): undefined reference to `_exit'

    解决方法是在链接时加一个 --specs=nosys.specs 或者加个 -lnosys。在 arm-none-eabi-gcc 的安装路径的 lib/ 下有个 libnosys.a,这个参数就是使用这个库的意思。Libnosys 是 newlib 的一个组件,它里面将各种 syscall 函数都定义为空函数stub functions,在不需要实际使用这些 syscalls 时可以使用包含这个库来通过编译。

    如果实际需要使用 syscalls(比如要用 newlib 中的 printf)时,那就需要自己写个 syscalls.c,自己定义 _write 这些函数了。(之前我这部分理解有误,已更正)

  • Newlib。Newlib 是 arm-none-eabi-gcc 工具链中的 libc,提供了 C 语言标准中的函数,如 printfmalloc 等。Newlib 是默认启用的,链接时指定 -lc 就已经在使用了。Newlib 比较大,可以用 newlib-nano 代替,方法是在链接时加个参数 --specs=nano.specs,或者用 -lc_nano 替代 -lc

编译演示

总之,你需要找个地方借个对应于你使用的 STM32 型号的 linker script 过来,然后下面是具体的编译方法:

  1. 首先把各个 C 文件编译为一个个对象文件:

    arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -c a.c -o a.o

    如果你使用了第三方库,那么你应该加个 -I 参数后面接上那个库的头文件路径;如果你使用的第三方库是以源文件提供的(如 ST 的各种库),那么你应该同时将库中的 C 文件也编译为对象文件,后面一同进行链接。

  2. 然后把所有对象文件链接在一起,形成一个可执行文件:

    arm-none-eabi-gcc a.o b.o -mcpu=cortex-m3 -mthumb -specs=nano.specs -lc -lm	-lnosys -Tyour_linker_script.ld -o project.elf

    这里要指定 linker script。

    如果你使用的第三方库是以 .a 文件提供的(如 libopencm3),那么这一步你需要加个 -L 参数指定 .a 文件所在的路径,并用 -l 参数指定库的名称。比如库文件为 ./libopencm3/lib/libopencm3_stm32f1.a 的话就是 -L./libopencm3/lib -l opencm3_stm32f1

    • .elf :编译和链接后的文件,包含了变量名和程序内容。可用于调试。
  3. 最后将可执行文件转换为二进制或者 Intel HEX 文件:

    arm-none-eabi-objcopy -O binary project.elf project.bin
    arm-none-eabi-objcopy -O ihex project.elf project.hex
    • .hex:Intel HEX 文件,不包含变量名,包含程序和地址。可用于烧写。由于文件已经包含地址的信息,烧写至 STM32 时不用指定起始地址。
    • .bin:二进制文件,只包含程序,可用于烧写。文件没有包含地址的信息,所以烧写时要指定起始地址。

将上面这些步骤写在 Makefile 中,就可以很方便地编译了。

STM32CubeMX

STM32CubeMX 可用于生成工程初始代码,可在 ST 官网获取邮件下载。这些代码包括 ST 的 HAL 库和 CMSIS,也可生成许多商业工具的工程文件。通过设置还可以生成 LL 库,不过这仅限 L1, L4, F2, F4, F7 系列

https://www.st.com/en/development-tools/stm32cubemx.html

现在的版本已经支持生成可用于 gcc 的 Makefile 和 linker script,新建工程后选择 Project Manager > Project > Toolchain / IDE 选择 Makefile 就可以了。

~/STM32Cube/Repository/STM32Cube_FW_F1_V1.7.0/Projects/STM32F103RB-Nucleo/Examples (类似于这样的)路径下可以找到实例程序。

CMSIS

早期的 ARM 各式各样,彼此之间互不兼容。为了统一乱象,ARM 开发了 CMSIS 库标准。CMSIS 同时也指 ARM 开发的一个满足 CMSIS 标准的库,比较底层。

HAL

ST 的库,基于 CMSIS,可用 STM32CubeMX 生成。头文件名类似于 stm32f1xx_hal.h stm32f1xx_hal_flash.h 这样。使用 STM32CubeMX 生成的话它的路径是 Drivers/STM32F1xx_HAL_Driver

除了官方文档(如 UM1850)外这个库的资料较少。如果你使用 STM32CubeMX 的话,在 ~/STM32Cube/Repository/STM32Cube_FW_F1_V1.7.0/Projects/STM32F103RB-Nucleo/Examples (类似于这样的)路径下可以找到实例程序。

标准外设库 / Standard Peripherals Library / SPL

ST 的库,基于 CMSIS。头文件名类似于 stm32f10x.h stm32f10x_flash.h 这样。现已被 ST 抛弃,取而代之的是 Low Layer Library。(然而大多数 STM32 中文书籍使用的仍是 SPL 😂)

Low Layer Library / LL

ST 的库,基于 CMSIS,可用 STM32CubeMX 生成。在 Advanced Settings 中将 HAL 改为 LL 就可以了,不过这仅限 L1, L4, F2, F4, F7 系列。其他系列就需要手动下载库了。

libopencm3

最近发现的一个用于 ARM Cortex-M 系列的库,专门为 GDB 工具链设计。包含 C 语言库、linker script 和一些 Makefile.common。库函数的命名采用了漂亮的小写字母加下划线的格式,不像 ST 官方的库中的大驼峰命名加下划线缝合怪命名。因为库的代码仓库是在 GitHub 上的,所以可以直接用 git-submodule 包含到你的项目中,无须繁琐的 CubeMX 配置。

按照 README 写这样一个 Makefile,编译环境就配好了:

DEVICE      = stm32f407zgt6 # 填入 STM32 的型号
OPENCM3_DIR = ./libopencm3  # libopencm3 的位置
OBJS        += foo.o        # 这里填入对象文件,即项目中所有 C 文件名(.c 替换为 .o)

CFLAGS      += -Os -ggdb3
CPPFLAGS    += -MD
LDFLAGS     += -static -nostartfiles
LDLIBS      += -Wl,--start-group -lc -lgcc -lnosys -Wl,--end-group

include $(OPENCM3_DIR)/mk/genlink-config.mk
include $(OPENCM3_DIR)/mk/gcc-config.mk

.PHONY: clean all
all: binary.elf binary.hex
clean:
	$(Q)$(RM) -rf binary.* *.o

include $(OPENCM3_DIR)/mk/genlink-rules.mk
include $(OPENCM3_DIR)/mk/gcc-rules.mk

运行 make,linker script 会自动生成,编译和链接也会自动完成。

调试器 / Dongle

调试协议有两种:JTAG 和 SWD。SWD 是 ARM 基于 JTAG 修改的协议,比起 JTAG 需要四个数据脚,SWD 只需要两个。

调试器分很多类,基于 J-LINK 的、基于 CMSIS-DAP 的、基于 ST-LINK 的。

调试协议指的是 dongle 和芯片连接的协议。虽然上面提及的几类 dongle 都实现了同样的 JTAG 或 SWD 调试协议,但是这几类 dongle 与计算机通讯的方式却完全不同。他们需要不同的驱动和不同的调试软件支持。

OpenOCD 的这个页面列出了更多种类的调试器:https://openocd.org/doc-release/html/Debug-Adapter-Hardware.html

J-LINK 是 SEGGER 的私有技术。由于 J-LINK 技术发展得早,SEGGER 凭借 J-LINK 技术已经形成了垄断,基于 J-LINK 的调试器价格不菲。

J-LINK 有 v8, v9, v10 之分。v8 采用的是 AT91SAM7S64 作为主控芯片,据说很容易掉固件;v9 采用的是 STM32F205RC 作为主控芯片,没有了上一代容易掉固件的缺点;v10 采用的是 NXP 的 LPC4322 作为主控芯片,速度较上一代有很大的提升。

SEGGER 的 J-LINK 还分 Base, Edu 版本。Edu 和 Base 没有显著的差别,价格相对便宜许多,但不允许商业使用。Base 的价格在 2000 元左右,Edu 的价格在 500 元左右。

当然使用 J-LINK 自然有这样做的好处。由于垄断、使用的人更多,J-LINK 在多数 ARM 开发和调试平台上都是被列为主要支持对象的,如 KEIL MDK, OpenOCD。

淘宝上售卖的 J-LINK 多为盗版(或者称作兼容版?Clone?),盗版 v8 的价格在 40 元左右,v9 的价格在 100 元左右,v10 的价格在 280 元左右。这些 clone 要么采用官方的外壳(但没有 Base, Pro, Edu 等标识),要么采用的是一个黑色的写着 “ARM 仿真器” 字样的外壳。

还有个版本称作 J-LINK OB,这个版本阉割掉了 JTAG 接口,只保留 SWD 协议。这个版本在驱动以及调试软件支持上都和其他版本无异,只是要记得选择使用 SWD 接口。

基于 CMSIS-DAP 的

和库的乱象一样,为了统一调试器的乱象、避免厂商重复造轮子,ARM 开发了 CMSIS-DAP 协议,并且基于 CMSIS-DAP 开发了 DAPLink 调试器固件。与 J-LINK 不同,这项技术是开源的。

DAPLink 固件有个有趣的功能:“拖拽烧录”,连接上计算机后它将被识别为一个储存装置,只需要将文件写入那个装置就可以进行烧录,不需要驱动。

市面上的 DAPLink 常常会标 “迷你版” 或 “高速版” 这样的字样。“迷你版” 通常采用的是闪存较小的芯片,无法支持 DAPLink 的全部功能,不是阉割掉了 JTAG 就是阉割掉了 “拖拽烧录”。而高速版的外观常常和 J-LINK 相似,是一个 “黑色砖头”。高速版采用闪存更大的芯片,可以保留全部 DAPLink 的功能,并且常常会有 J-LINK 那样的更完善的外围电路。

OpenOCD 只支持 ULINK v1,不支持 ULINK v2。

ST-LINK 只支持 ST 的芯片。ST-LINK, ST-LINK/V2 和 ST-LINK-V3 的协议是不同的,需要完全不同的驱动。官方的 ST-LINK/V2 长这样:

市场上还有着许多 U 盘大小的铝壳的廉价的 ST-LINK/V2 兼容版,它们常常是一颗 STM32F103C8T6,几乎没有外围电路,没有 JTAG 接口。它们采用的固件和官方的一致,所以如果只使用 SWD 的话使用起来应该也是没有问题的。

不过我买到了几个主控为 CS32F103C8T6 的铝壳 ST-LINK/V2,使用 ST 官方提供的烧录工具没有问题,但无法用于 texane/stlink 烧录,也无法用于 OpenOCD 调试。报错不认识这个 idcodecoreid详见这里。

Black Magic Probe / BMP

https://github.com/blacksphere/blackmagic/wiki/

一个开源的调试解决方案。最大的特点是内置 GDB server,连接上电脑后会被识别为一个串口设备。这样只需在 GDB 中 target extended-remote /dev/ttyACM0 就可以开始调试了。不需要再用其他软件与 dongle 通讯,作为 GDB 和 dongle 之间的中间层才能调试,少了很多麻烦。

调试 / 烧写工具

OpenOCD

这是比较主流的开源调试方案了。OpenOCD 本质上是一个 TCL 语言的执行环境,并且内置了很多用于调试的脚本。这些脚本在 /usr/share/openocd/scripts/ 下。

要启动 OpenOCD,你需要写一个初始化的脚本 openocd.cfg。在这个脚本中你需要用 source 引用 /usr/share/openocd/scripts/ 下的脚本,来指定你的调试器和目标芯片。比如我用 J-LINK 来调试 STM32F103C8

source [find interface/jlink.cfg]
source [find target/stm32f1x.cfg]

Linux 下使用 JLink 与 OpenOCD 配合嵌入式开发

然后在当前路径运行 openocd,这时 OpenOCD 会开启两个服务器:

  • localhost:4444, telnet server,可用 telnet localhost 4444 连接上进入 OpenOCD 的交互模式。交互模式下可使用 OpenOCD 内置的一些 TCL 指令来调试。

    下载程序:

    > program blinky.bin 0x8000000 verify reset
  • localhost:3333, GDB server,可在 GDB 中使用 target extended-remote localhost:3333 来连接。

我买到了几个主控为 CS32F103C8T6 的铝壳 ST-LINK/V2,无法用于 OpenOCD 调试。报错不认识这个 idcode

Warn : UNEXPECTED idcode: 0x2ba01477
Error: expected 1 of 1: 0x1ba01477

有人说可以在 openocd.cfg 中加一句 set CPUTAPID 0x2ba01477,就像这样:

source [find interface/stlink-v2.cfg]
set CPUTAPID 0x2ba01477
source [find target/stm32f1x.cfg]

这样是可以启动了,但是用 telnet 连上后:

> program blinky.bin 0x8000000 verify reset
timed out while waiting for target halted
TARGET: STM32F103C8Tx.cpu - Not halted
in procedure 'program' 
in procedure 'reset' called at file "embedded:startup.tcl", line 500
in procedure 'ocd_bouncer'

embedded:startup.tcl:476: Error: ** Unable to reset target **
in procedure 'program' 
in procedure 'program_error' called at file "embedded:startup.tcl", line 501
at file "embedded:startup.tcl", line 476
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x08000144 msp: 0x20000400

https://github.com/texane/stlink/

如果你用的下载器是 STLINK 的话,可以用这个工具来下载或调试程序。

下载程序:

$ st-flash write blinky.bin 0x8000000

最后一项是地址,ARM Cortrex-M 的 flash 地址开头是 0x8000000,别忘了。

调试的话是使用 st-util 建立一个 GDB server,然后用 gdb 连上进行调试。

使用盗版 ST-LINK 可能会报错不认识这个 coreid,我目前还没有找到解决方法。

下载地址: STSW-LINK004

上面说的我买到的山寨 ST-LINK 无法用于 texane/st-link 和 OpenOCD,但它们可以用于这个烧写工具。这是 ST 官方的一个 Windows 程序。

STM32CubeProgrammer

https://www.st.com/en/development-tools/stm32cubeprog.html

也是 ST 官方的烧写工具,同样需要邮件验证才能下载。这个工具支持我买到的山寨 ST-LINK,可在 Linux, Windows, macOS 上运行。

Linux 下它依赖于 openjdkopenjfx。我的 OpenJDK 版本是 8,不知道其他版本会不会出问题。

压缩包中的 SetupSTM32CubeProgrammer-x.x.x.linux 即为用于 Linux 的安装向导,可以更改安装路径,默认会安装在你的 $HOME 中,这样就不会弄乱你的根目录。安装向导最后在 ~/.local/share/applications创建的快捷方式拉的屎有点乱。我们删除掉 *.txt.desktop,然后编辑 STM32CubeProgrammer.desktop,删除掉这两行:

Terminal=
TerminalOptions=

这样在开始菜单中启动时就不会显示终端了。

IDE

STM32CubeIDE

STM32CubeIDE 是 ST 推出的免费的 STM32 集成开发环境,支持 Linux, Mac, Windows。可在 ST 官网输入个人信息,获取邮件后下载。

https://www.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-ides/stm32cubeide.html

AUR 上也有这个包。不过需要注意,由于授权是私有的,其他人不能再分发 STM32CubeIDE。所以从 AUR 安装时,你需要手动从 ST 网站上下载下来,放在你克隆下来的 AUR 仓库路径下(如果你使用 yay 就是 ~/.cache/yay/stm32cubeide/),然后再运行 makepkg -sci

VSCode 插件 Cortex-Debug

支持多种调试工具,包括 stutil, OpenOCD, Black Magic Probe 等。

https://hbfsrobotics.com/blog/configuring-vs-code-arm-development-stm32cubemx

其他解决方案

Mbed 生态

Mbed 是 ARM 主导的开源的嵌入式 OS 以及软件生态。Mbed 官网上有在线的编译器,注册之后就可以用了。这是它的官方文档。

除了 Online Complier 以外,还可以使用 Mbed 提供的 mbed-cli,一个用 Python 写的编译工具。

不过好像还得买他家的开发板?

stm32-rs

用 Rust 写 STM32 的程序。

https://github.com/stm32-rs/stm32-rs

STM32duino

为 Arduino IDE 添加 STM32 支持。库的使用与 Arduino 库类似。

About Me