前言
这段时间正在折腾 STM32。由于我的主力系统是 Linux,因此就有了这个使用 Linux 开发 STM32 标准库的需求。目前 HAL 库非常流行,而 HAL 对各平台都有很好的支持,其中自然也包括 Linux。不过主流的 STM32 标准库开发流程通常是使用 Windows + Keil,而使用这种古董软件有时像是一种折磨。为此,我在网上查了许多资料,而使用 Linux 开发 STM32 的资料却寥寥无几。为此,我踩了许多坑,最终终于尝试出来了一套 Linux 可用的 STM32 标准库开发配置。
我的环境:
- OS: Arch Linux x86_64
- Linux kernel: 7.1.8
- CMake: 4.4.2
- OpenOCD: 0.12.0
- arm-none-eabi-gcc: 16.2.0
- STM32F103C8T6
- ST-Link v2
准备工作
必备软件
必备软件自然是 OpenOCD 和交叉编译器 arm-none-eabi-gcc,安装过程这里我就不赘述了。
标准库下载
下载 STM32F103C8T6 对应的 STM32F10x 标准库文件: https://www.st.com/en/embedded-software/stsw-stm32054.html
下载需要一个账号,直接使用访客下载会遇到几道邮件验证的流程,比较麻烦。因此这里还是建议先建好账号再来下载。
创建项目
首先新建文件夹。接着观察下载好的标准库压缩包的目录结构,可以看到其中的几个目录。其中 Libraries 目录就是我们需要的库文件。解压 Libraries 目录并放到项目文件夹中。
接下来,创建一个 User 目录,里面就是我们需要自己编写的部分。为了规范,在 User 目录中再创建 inc 和 src 两个目录,分别用于存放头文件和源文件。然后在 src 目录中新建一个 main.c,在 inc 目录中新建一个 stm32f10x_conf.h(需要用户自己定义的配置文件)。如果使用了 USE_FULL_ASSERT 宏,可以在 src 目录下创建一个 stm32f10x_conf.c 用于存放 assert_failed() 实现。
下一步,在项目目录下新建一个 CMakeLists.txt。
最后,我们在项目目录下还需要一个链接脚本 STM32F103xx_FLASH.ld。这个可以从各种地方获取,我自己是直接从已有的 CubeMX 项目粘贴过来的。
最终的项目结构应该是类似这样的:
.
├── CMakeLists.txt
├── Libraries
│ ├── CMSIS
│ │ ├── CM3
│ │ ├── CMSIS changes.htm
│ │ ├── CMSIS debug support.htm
│ │ ├── Documentation
│ │ └── License.doc
│ └── STM32F10x_StdPeriph_Driver
│ ├── inc
│ ├── LICENSE.txt
│ ├── Release_Notes.html
│ └── src
├── STM32F103xx_FLASH.ld
└── User
├── inc
│ └── stm32f10x_conf.h
└── src
├── main.c
└── stm32f10x_conf.c
配置项目
首先我们需要编写 CMakeLists.txt。下面我给了一个我跑通的示例,注意在项目中中使用的外设库源文件需要确保已添加,这里我使用了 RCC 和 GPIO:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(stm32-spl-test C ASM)
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR cortex-m3)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
add_executable(${CMAKE_PROJECT_NAME}.elf
Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/gcc_ride7/startup_stm32f10x_md.s
Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/system_stm32f10x.c
User/src/main.c
User/src/stm32f10x_conf.c
# 需要使用的库源文件
Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_gpio.c
Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_rcc.c
)
target_compile_definitions(${CMAKE_PROJECT_NAME}.elf PRIVATE
STM32F10X_MD
USE_STDPERIPH_DRIVER
)
target_include_directories(${CMAKE_PROJECT_NAME}.elf PRIVATE
User/inc
Libraries/CMSIS/CM3/CoreSupport
Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x
Libraries/STM32F10x_StdPeriph_Driver/inc
)
target_compile_options(${PROJECT_NAME}.elf PRIVATE
-mcpu=cortex-m3
-mthumb
-mfloat-abi=soft
-O0
-g3
-Wall
-Wextra
-ffunction-sections
-fdata-sections
)
target_link_options(${PROJECT_NAME}.elf PRIVATE
-mcpu=cortex-m3
-mthumb
-mfloat-abi=soft
--specs=nosys.specs
-T${CMAKE_SOURCE_DIR}/STM32F103xx_FLASH.ld
-Wl,--gc-sections
-Wl,-Map=${PROJECT_NAME}.map
)
接下来 User 目录下的文件如何编写我就不过多赘述了,这些都是 STM32 标准库编程的内容。这里我放一个示例:
// main.c
#include "stm32f10x.h"
int main(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_ResetBits(GPIOC, GPIO_Pin_13);
while (1);
}
// stm32f10x_conf.h
#ifndef __STM32F10X_CONF_H
#define __STM32F10X_CONF_H
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#ifdef USE_FULL_ASSERT
#define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
void assert_failed(uint8_t* file, uint32_t line);
#else
#define assert_param(expr) ((void)0)
#endif
#endif
// stm32f10x_conf.c
#include "stm32f10x_conf.h"
#include <stdio.h>
void assert_failed(uint8_t *file, uint32_t line)
{
printf("\r\n 输入参数错误,错误文件%p,行号=%u", file, (unsigned int) line);
}
不出意外的话,现在运行 cmake,就可以构建成功了。cmake 的构建目录下应该可以找到我们需要的 stm32-spl-test.elf 文件。
下载 / 烧录
在拿到我们需要的 elf 文件后,我们就可以准备使用 OpenOCD 下载了。将 ST-Link 插入电脑,确保 ST-Link 可以被识别后,运行命令:
openocd \
-f interface/stlink.cfg \
-f target/stm32f1x.cfg \
-c "program cmake-build-debug/stm32-spl-test.elf verify reset exit"
这里我使用的是 CLion,因此默认的 cmake 构建目录就在 cmake-build-debug。这里可以根据 elf 文件的地址自行修改。不出意外的话,运行完这个命令,就下载完成了。如果希望更方便,也可以创建一个 shell 文件存放指令,实现自动化下载。
结语
经过了许久的折腾,我总算是找出了这样一个可行的“优雅”开发的方法。相比于 HAL 库,标准库的项目目录结构明显更加简洁,库的设计也更接近底层,对于理解原理依然有着很好的帮助。最后,自然也希望标准库的世界能带给我不一样的理解吧。