什么,都 3202 年了,你还在
用丑陋的Keil
开发嵌入式?不如来试试在 Linux 下使用现代化编辑器/IDE 优雅地开发嵌入式。本文将介绍如何在 Linux 下搭建完整的嵌入式开发工具链,以WSL2 + VSCode
为例进行嵌入式开发。
环境准备
- WSL2 Ubuntu 22.04 LTS
- VSCode
- GNU Make 4.3, cmake 3.26.0-rc6
- zsh 5.8.1 (x86_64-ubuntu-linux-gnu)
项目结构
整个项目的文件组织结构如下图所示:
以上结构仅是一种示例,大家可以根据自己的项目需求进行调整,编写对应的 CMakeLists.txt
或 Makefile
即可。
开发原理
下面是开发工具链的原理图
安装 ARM GNU Toolchain
交叉编译(Cross compiling)是指在一个平台上进行编译,生成在另一个不同平台上运行的可执行程序或库文件的过程。通常,交叉编译用于开发跨平台软件或嵌入式系统。
为什么需要交叉编译?主要有 3 个原因:
- 嵌入式系统开发:嵌入式系统通常运行在资源有限的硬件设备上,如嵌入式处理器、微控制器或单片机。这些设备通常不具备强大的计算能力和存储容量,因此需要在更强大的计算机上进行交叉编译,以生成适合嵌入式设备的二进制文件。
- 跨平台开发:在开发跨平台软件时,交叉编译可以简化开发流程。例如,如果你要开发一个同时运行在 Windows、Linux 和 macOS 上的应用程序,你可以在一种平台上编译应用程序,并生成可在其他平台上运行的可执行文件。
- 提高编译效率:有时候,主机平台上的编译器和开发环境可能更加强大和高效,因此进行交叉编译可以提高编译效率。通过利用更强大的计算机资源,可以更快地生成目标平台上的二进制文件。
交叉编译器的命名规则通常遵循一定的约定,以便清晰地标识其适用的平台和体系结构。通常至少包括 3 部分:[目标平台]-[目标OS]-[功能后缀]
- 目标平台:arm-, mips-, x86_64-, riscv64-
- 目标 OS:linux, win32, android, none
- 功能后缀:-gcc, -g++, -ld, -gdb
实际上命名也没有官方标准,也有很多命名确实没有按照这个规则。我们需要安装的 C 语言编译器应该是 arm-none-eabi-gcc
,其中 EABI(Embedded Application Binary Interface,EABI) 是一种为嵌入式系统设计的二进制接口标准,用于确保在不同的嵌入式平台上可移植性和互操作性。
使用包管理器安装
sudo apt install gcc-arm-none-eabi
但是 Ubuntu 官方 APT 源似乎对 gcc-arm-none-eabi
已经停止维护了,并且安装后不含有 arm-none-eabi-gdb
。可以考虑使用 gdb-multiarch
替代。
sudo apt install gdb-multiarch
gdb-multiarch 是一个针对多种体系结构的调试器,它是 GNU 调试器(GDB)的一个变体。它的设计目的是支持交叉编译环境中多个不同体系结构的调试工作。它支持同时调试多个不同体系结构的可执行文件,无需手动更改调试器设置或使用不同的调试器实例。通过选择正确的体系结构并加载对应的调试器插件,开发人员可以在同一个 gdb-multiarch 会话中进行跨体系结构的调试操作。
使用二进制文件安装
安装有些坑,解压后没有 Makefile
文件,需要手动安装。解压到 /opt/gcc-arm-none-eabi
,然后添加环境变量,可以在 ~/.zshrc
中添加如下一行,每次启动 zsh 的时候就能够自动加入环境变量了。
export PATH=$PATH:/opt/gcc-arm-none-eabi/bin
正常情况下 arm-none-eabi-gcc
等工具应该能正常使用了,但可能依然无法使用 arm-none-eabi-gdb
。如果你遇到如下报错:
arm-none-eabi-gdb: error while loading shared libraries: libncursesw.so.5: cannot open shared object file: No such file or directory
则,
sudo apt install -y libncursesw5
此外,ARM GNU Toolchain 还需要 Python3.8 支持,如果看到如下报错:
Could not find platform independent libraries <prefix>
Could not find platform dependent libraries <exec_prefix>
Consider setting $PYTHONHOME to <prefix>[:<exec_prefix>]
Python path configuration:
PYTHONHOME = (not set)
PYTHONPATH = (not set)
...
Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding
Python runtime state: core initialized
ModuleNotFoundError: No module named 'encodings'
则,
sudo apt install -y python3.8
如果一切正常,可以看到 GDB 版本如下
安装 usbipd
如果您是尊贵的 Linux 实体机用户,这一步请跳过。
按照微软文档操作即可,没有任何难度和坑。
usbipd
是用于将 Windows 本地连接的 USB 设备共享给其他机器的开源项目,包括 Hyper-V 虚拟机和 WSL 2。
安装 stlink-tools
该怎么把程序烧录到 STM32F401RET6 开发板上呢?一种办法是使用 stlink-tools
,另一种办法是使用 OpenOCD
。
stlink-tools
是一个开源的工具集,用于编程和调试 STMicroelectronics 制造的 STM32 设备和开发板。
可以直接使用包管理器安装,Github 上最后一次更新是 2021 年 4 月,包管理器安装就能获得最新版本。
sudo apt install stlink-tools
安装好后,插入 STM32F401RE 开发板,确认 stlink-tools
能识别,
st-info --probe
烧录使用st-flash
,需要指定 flash 的起始地址和二进制程序文件。
安装 OpenOCD
OpenOCD
(Open On-Chip Debugger)是一个开源的调试和编程工具,用于嵌入式系统开发中与芯片上调试接口(如 JTAG、SWD 等)通信并进行调试、编程和仿真操作。
OpenOCD
提供了一种通用的接口和协议,可以与多种嵌入式芯片和调试接口进行交互。它支持各种处理器架构(如 ARM、RISC-V 等)和调试接口(如 JTAG、SWD、BDM 等),可以与目标系统上的芯片进行连接,并通过调试接口与芯片进行通信。
主要功能有两个:
- 调试功能:OpenOCD 支持寄存器读写、内存读写、断点设置、单步执行等调试操作,允许开发人员在目标系统上进行调试。它与 GDB 调试器紧密集成,提供了与 GDB 之间的通信接口。
- 编程功能:OpenOCD 支持对芯片进行编程,包括烧录程序代码、擦除芯片、写入 Flash 存储器等操作。它能够与多种烧录器(如 J-Link、ST-Link 等)集成,实现对芯片的编程。
直接使用包管理器安装
sudo apt install openocd
安装后在 /usr/share/openocd/scripts/
下可以找到 interface
和 target
文件夹,后面使用 OpenOCD
会用到。
VSCode 配置(基础)
安装 clangd 插件
clangd —— teach your editor C++
当项目配置好后你会发现 vsc 常用的 C/C++ IntelliSense 插件补全速度实在太慢了,键入后大概要七八秒后才显示补全,约等于没有。这时候可以尝试一下 LLVM 的 clangd
了。
What is clangd ?
clangd is a language server that can work with many editors via a plugin. clangd understands your C++ code and adds smart features to your editor: code completion, compile errors, go-to-definition and more. clangd is based on the Clang C++ compiler, and is part of the LLVM project.
换句话说,clangd
是一个语言服务器,可以通过 LSP(Language Server Protocol,语言服务协议)与 IDE 或编辑器进行交互。
clangd
需要知道项目是如何构建的才能正常解析,需要 compile_commands.json
文件,用 CMake 构建的项目可以很容易得到该文件,
# 输出 compile_commands.json
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
至少在 clangd
的启动参数中加入以下参数以保证正常运行,
"clangd.arguments": [
// compelie_commands.json 文件的目录位置(相对于工作区,由于 CMake 生成的该文件默认在 build 文件夹中,故设置为 build)
"--compile-commands-dir=build",
// 询问编译器头文件的位置
"--query-driver=/opt/gcc-arm-none-eabi/bin/arm-none-eabi-gcc"
]
我个人使用的完整配置如下,仅供参考,关于 clangd 运行参数,在终端输入 clangd --help-list-hidden
可查看更多
// 禁用 C/C++ IntelliSense
"C_Cpp.errorSquiggles": "disabled",
"C_Cpp.intelliSenseEngineFallback": "disabled",
"C_Cpp.intelliSenseEngine": "disabled",
"C_Cpp.autocomplete": "disabled",
"clangd.path": "/usr/bin/clangd",
// clangd 运行参数(在终端/命令行输入 clangd --help-list-hidden 可查看更多)
"clangd.arguments": [
// 让 clangd 生成更详细的日志
"--log=verbose",
// 启用.clangd配置文件
"--enable-config",
// 输出的 JSON 文件更美观
"--pretty",
// 全局补全(输入时弹出的建议将会提供 CMakeLists.txt 里配置的所有文件中可能的符号,会自动补充头文件)
"--all-scopes-completion",
// 建议风格:打包(重载函数只会给出一个建议)。相反可以设置为detailed
"--completion-style=bundled",
// 跨文件重命名变量
"--cross-file-rename",
// 允许补充头文件
"--header-insertion=iwyu",
// 输入建议中,已包含头文件的项与还未包含头文件的项会以圆点加以区分
"--header-insertion-decorators",
// 在后台自动分析文件(基于 complie_commands,我们用CMake生成)
"--background-index",
// 启用 Clang-Tidy 以提供「静态检查」
"--clang-tidy",
// 同时开启的任务数量
"-j=8",
// pch优化的位置(memory 或 disk,选择memory会增加内存开销,但会提升性能) 推荐在板子上使用disk
"--pch-storage=disk",
// 启用这项时,补全函数时,将会给参数提供占位符,键入后按 Tab 可以切换到下一占位符,乃至函数末,我选择禁用
"--function-arg-placeholders=false",
// compelie_commands.json 文件的目录位置(相对于工作区,由于 CMake 生成的该文件默认在 build 文件夹中,故设置为 build)
"--compile-commands-dir=build",
//询问编译器头文件的位置
"--query-driver=/opt/gcc-arm-none-eabi/bin/arm-none-eabi-gcc"
]
clangd
还可以配置 project configuration file,具体来说,就是配置放在项目主目录的 .clangd
,下面是一个我的配置示例:
CompileFlags:
Add: [-Wno-format]
Compiler: /opt/gcc-arm-none-eabi/bin/arm-none-eabi-gcc
Diagnostics:
ClangTidy:
Remove: [bugprone-sizeof-expression]
安装 Cortex-Debug
Cortex-Debug is an extension to add debugging capabilities for ARM Cortex-M devices to Visual Studio Code.
VSCode Debug 配置
OpenOCD + ST-Link 调试配置
"configurations": [
{
"name": "ST-Link Cortex-M4 Debug",
"type": "cortex-debug",
"request": "launch",
"servertype": "openocd",
"cwd": "${workspaceRoot}",
"executable": "${workspaceRoot}/build/qingluan.elf",
"device": "STM32F401RE",
"configFiles": [
"interface/stlink-v2-1.cfg",
"target/stm32f4x.cfg"
],
"svdFile": "${workspaceRoot}/Script/STM32F401.svd",
"runToEntryPoint": "main",
},
]
J-Link 调试配置
"configurations": [
{
"name": "J-Link Cortex-M4 Debug",
"type": "cortex-debug",
"request": "launch",
"servertype": "jlink",
"cwd": "${workspaceRoot}",
"executable": "${workspaceRoot}/build/qingluan.elf",
"device": "STM32F401RE",
"interface": "swd",
"svdFile": "${workspaceRoot}/Script/STM32F401.svd",
"runToEntryPoint": "main",
}
]
为了能让 Cortex-Debug 知道开发板寄存器内存映射,在 Debug 的时候能够看寄存器的值,需要开发板对应的 CMSIS-SVD 文件(即上述配置中的 STM32F401.svd
),在下面的 GitHub 仓库可以找到。
The CMSIS System View Description format(CMSIS-SVD) formalizes the description of the system contained in ARM Cortex-M processor-based microcontrollers, in particular, the memory-mapped registers of peripherals. The detail contained in system view descriptions is comparable to the data in device reference manuals. The information ranges from high-level functional descriptions of a peripheral all the way down to the definition and purpose of an individual bit field in a memory-mapped register.
Patched SVD files for STM32 MCUs
打上断点,就可以正常单步 Debug 啦,
汇编代码亦可打断点调试,甚至可以从 Reset_Handler
的第一行代码开始调试
VSCode 配置(可选)
安装 RTOS Views
通过 RTOS Views
可以在单步调试的时候看到当前所有的任务状态、任务优先级、栈空间使用情况、栈空间使用峰值等信息。
为了让 RTOS Views
能正常监测,创建任务时需要使用 OSTaskCreateExt
和 OSTaskNameSet
通过 RTOS Views
我发现了代码的 BUG,有任务栈空间开小了导致栈溢出了
安装 Arm Assembly
Adds syntax highlighting for the Arm Assembly language to Visual Studio Code.
汇编代码语法高亮
安装 MemoryView
This is a memory viewer extension specially built to work with debuggers. It can be used with any debugger that supports memory reads (and optional writes). Currently
cppdbg
,cortex-debug
andcspy
are the debuggers supported. This extension is more suitable for low level programmers or embedded developers.
安装 SystemView
Converting ST-LINK On-Board Into a J-Link
µC/OS-II User Manual Trace Recording SEGGER SystemView
移植 SystemView 参考的上面 uCOS-II 的文档,坑点只有两处:
- 去掉
include "os_cpu.h"
和CPU_ERR local_err;
(部分架构移植 uCOS-II 有这个文件,我们没有这个头文件) - 记着添加
SEGGER_RTT_ASM_ARMv7M.s
和SEGGER_RTT_printf.c
这两个文件,网页文档的图似乎画漏了
下载后
sudo dpkg -i ./systemview_linux_deb64.deb
WSL2 目前是支持 GUI 的,详见 在适用于 Linux 的 Windows 子系统 上运行 Linux GUI 应用
此外,记着安装 J-Link 驱动,
sudo dpkg -i ./JLink_Linux_V788b_x86_64.deb
成功安装后运行效果如下图,Win98 风格,UI 丑了点,但是能跑
References
Install Arm GNU Toolchain on Ubuntu 22.04
clangd —— teach your editor C++
Patched SVD files for STM32 MCUs
Converting ST-LINK On-Board Into a J-Link
µC/OS-II User Manual Trace Recording SEGGER SystemView