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 函数都定义为空函数,在不需要实际使用这些 syscalls 时可以使用包含这个库来通过编译。如果实际需要使用 syscalls(比如要用 newlib 中的
printf
)时,那就需要自己写个syscalls.c
,自己定义_write
这些函数了。(之前我这部分理解有误,已更正)Newlib。Newlib 是 arm-none-eabi-gcc 工具链中的 libc,提供了 C 语言标准中的函数,如
printf
、malloc
等。Newlib 是默认启用的,链接时指定-lc
就已经在使用了。Newlib 比较大,可以用 newlib-nano 代替,方法是在链接时加个参数--specs=nano.specs
,或者用-lc_nano
替代-lc
。
编译演示
总之,你需要找个地方借个对应于你使用的 STM32 型号的 linker script 过来,然后下面是具体的编译方法:
首先把各个 C 文件编译为一个个对象文件:
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -c a.c -o a.o
如果你使用了第三方库,那么你应该加个
-I
参数后面接上那个库的头文件路径;如果你使用的第三方库是以源文件提供的(如 ST 的各种库),那么你应该同时将库中的 C 文件也编译为对象文件,后面一同进行链接。然后把所有对象文件链接在一起,形成一个可执行文件:
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
:编译和链接后的文件,包含了变量名和程序内容。可用于调试。
最后将可执行文件转换为二进制或者 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 的
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 那样的更完善的外围电路。
ULINK
OpenOCD 只支持 ULINK v1,不支持 ULINK v2。
基于 ST-LINK 的
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 调试。报错不认识这个 idcode
或 coreid
,详见这里。
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]
然后在当前路径运行 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
来连接。
盗版 ST-LINK
我买到了几个主控为 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
texane/stlink
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
,我目前还没有找到解决方法。
STM32 ST-LINK Utilty
下载地址: 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 下它依赖于 openjdk
和 openjfx
。我的 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 官网输入个人信息,获取邮件后下载。
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 库类似。