< Back
文章 - Linux 下配置 STM32 标准库(StdPeriph)开发环境
Linux 下配置 STM32 标准库(StdPeriph)开发环境
记录一下折腾半天试出的一套 Linux 下 STM32+StdPeriph 的方案

前言

这段时间正在折腾 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 目录中再创建 incsrc 两个目录,分别用于存放头文件和源文件。然后在 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 库,标准库的项目目录结构明显更加简洁,库的设计也更接近底层,对于理解原理依然有着很好的帮助。最后,自然也希望标准库的世界能带给我不一样的理解吧。