非标准STM32F407学习笔记(一)

前情提要

学习硬件设备固然是很有意思的一件事,不过使用某绿色(或者蓝色)礦ision配上带劲的音乐在Linux设备上并不是什么容易的事情,因此在本篇中,我将简要地介绍如何配置一套用常用组件组合起来的开发、编译、调试环境。

调试设备:Pico Probe

介绍

树莓派Pico,是一款使用了RP2040的微控制器,可以通过刷入Pico Probe(debugprobe)固件使其转换为一个支持标准CMSIS-DAP协议(即SWCLK和SWDIO)的调试设备。在这种情况下,我们就可以省去ST-Link或类似的调试设备,而仅仅是通过一个简单的开发板来完成连接工作。
可以通过官方GitHub连接获取Pico Probe固件,即(debugprobe_on_pico.uf2)。

安装

获取到这个固件之后,按住树莓派Pico上的BOOTSEL按钮并插入USB线到电脑上,会识别到出于FS Mode的树莓派Pico,此时只需要将上述uf2格式的固件复制到这个模拟U盘中,就可以自动安装相应的固件。Pico会自动重启,并且工作在预期的工作模式:

1
2
3
4
$ lsusb
...
Bus 003 Device 015: ID 2e8a:000c Raspberry Pi Debugprobe on Pico (CMSIS-DAP)
...

udev配置

在现代Linux系统上,一般是使用udev来管理USB设备的权限,理论上在安装OpenOCD后会自动具有通用CMSIS-DAP的配置,不过我在这里附一个参考的:

1
2
# CMSIS-DAP compatible adapters
ATTRS{product}=="*CMSIS-DAP*", MODE="660", GROUP="plugdev", TAG+="uaccess"
1
sudo udevadm control --reload-rules && sudo udevadm trigger   

接线指南

根据树莓派Debug on Pico指南(上述GitHub仓库中提到),位于Getting Started with Pico PDF。

其附录 A.2. Debug with a second Pico or Pico 2 给出了接线指南:

GPIO 功能
GND 地线
VBUS 5V USB电源
GP2 SWCLK
GP3 SWDIO
GP4 TXD
GP5 RXD

验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ openocd -f interface/cmsis-dap.cfg                       
Open On-Chip Debugger 0.12.0+dev-01445-gb80f554fd-dirty (2026-04-11-11:03)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Warn : An adapter speed is not selected in the init scripts. OpenOCD will try to run the adapter at very low speed (100 kHz).
Warn : To remove this warnings and achieve reasonable communication speed with the target, set "adapter speed" or "jtag_rclk" in the init scripts.
Info : Using CMSIS-DAPv2 interface with VID:PID=0x2e8a:0x000c, serial=530328472925599C
Info : CMSIS-DAP: SWD supported
Info : CMSIS-DAP: Atomic commands supported
Info : CMSIS-DAP: Test domain timer supported
Info : CMSIS-DAP: FW Version = 2.0.0
Error: CMSIS-DAP: JTAG not supported

编译环境: arm-none-eabi-gcc + STM32CubeMX + Ninja(CMake)

arm-none-eabi-gcc

理论上可以用arm官方提供的gcc,不过鉴于我们都用Linux了,为何不用包管理器呢:

1
sudo pacman -S arm-none-eabi-binutils arm-none-eabi-gcc
1
2
3
4
5
6
7
8
9
$ arm-none-eabi-gcc -v
Using built-in specs.
COLLECT_GCC=arm-none-eabi-gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/arm-none-eabi/16.2.0/lto-wrapper
Target: arm-none-eabi
Configured with: /build/arm-none-eabi-gcc/src/gcc-16.2.0/configure --target=arm-none-eabi --prefix=/usr --with-sysroot=/usr/arm-none-eabi --with-native-system-header-dir=/include --libexecdir=/usr/lib --enable-languages=c,c++ --enable-plugins --disable-decimal-float --disable-libffi --disable-libgomp --disable-libmudflap --disable-libquadmath --disable-libssp --disable-libstdcxx-pch --disable-nls --disable-shared --disable-threads --disable-tls --with-gnu-as --with-gnu-ld --with-system-zlib --with-newlib --with-headers=/usr/arm-none-eabi/include --with-python-dir=share/gcc-arm-none-eabi --with-gmp --with-mpfr --with-mpc --with-isl --with-libelf --enable-gnu-indirect-function --with-host-libstdcxx='-static-libgcc -Wl,-Bstatic,-lstdc++,-Bdynamic -lm' --with-pkgversion='Arch Repository' --with-bugurl=https://gitlab.archlinux.org/archlinux/packaging/packages/arm-none-eabi-gcc/-/issues --with-multilib-list=rmprofile
Thread model: single
Supported LTO compression algorithms: zlib zstd
gcc version 16.2.0 (Arch Repository)

STM32CubeMX

可以从STM32官网获取STM32CubeMX,接着配置即可。
可以选取cmake作为编译器。

1
2
3
4
5
mkdir build && cd build
cmake -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=../cmake/gcc-arm-none-eabi.cmake \
-DCMAKE_BUILD_TYPE=Debug ..
ninja

烧录环境: OpenOCD

1
openocd -f interface/cmsis-dap.cfg -f target/stm32f4x.cfg -c "program LED.elf verify reset exit"

调试环境:GDB

1
2
3
4
5
6
7
8
$ openocd -f interface/cmsis-dap.cfg -f target/stm32f4x.cfg
Info : [stm32f4x.cpu] starting gdb server on 3333
Info : Listening on port 3333 for gdb connections
Info : accepting 'gdb' connection on tcp/3333
$ gdb
(gdb) target remote localhost:3333
(gdb) file LED.elf
(gdb) c

附录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/**
******************************************************************************
* @file main.c
* @brief STM32F407 PC13 LED 100ms 周期性闪烁 (完整版)
* @note 默认低电平点亮,高电平熄灭。若您的硬件相反,请交换 RESET/SET。
******************************************************************************
*/

#include "stm32f4xx_hal.h"

// 函数声明
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
void Error_Handler(void);

/**
* @brief 主函数
* @retval int
*/
int main(void)
{
// 1. 初始化HAL库
HAL_Init();

// 2. 配置系统时钟 (使用HSI内部振荡器 -> PLL倍频至168MHz)
SystemClock_Config();

// 3. 初始化GPIO (PC13)
MX_GPIO_Init();

// 4. 主循环:LED以100ms间隔亮灭
while (1)
{
// PC13输出低电平 -> LED点亮 (注:多数开发板PC13为低电平有效)
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_Delay(100); // 延时100ms

// PC13输出高电平 -> LED熄灭
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
HAL_Delay(100); // 延时100ms
}
}

/**
* @brief 系统时钟配置 (使用HSI -> PLL)
* @note 最终时钟: HSI 16MHz / 16 * 336 / 2 = 168MHz
* HCLK = 168MHz, APB1 = 42MHz, APB2 = 84MHz
* @retval 无
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

// 使能电源接口并配置电压调节器为模式1 (168MHz需要)
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

// 配置振荡器:使用HSI (16MHz) 作为PLL输入
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 16; // 分频: 16MHz / 16 = 1MHz
RCC_OscInitStruct.PLL.PLLN = 336; // 倍频: 1MHz * 336 = 336MHz
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; // 主PLL分频: 336MHz / 2 = 168MHz
RCC_OscInitStruct.PLL.PLLQ = 7; // 用于USB/SDIO: 336/7 = 48MHz

if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}

// 配置时钟源、AHB/APB分频
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // HCLK = 168MHz
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; // APB1 = 42MHz (最大允许)
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; // APB2 = 84MHz (最大允许)

// 配置Flash等待周期 (168MHz需要5个等待周期)
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}

/**
* @brief GPIO初始化函数 (PC13)
* @retval 无
*/
static void MX_GPIO_Init(void)
{
// 使能GPIOC时钟
__HAL_RCC_GPIOC_CLK_ENABLE();

GPIO_InitTypeDef GPIO_InitStruct = {0};

// 配置PC13为推挽输出,无上下拉,低速度 (PC15~PC13驱动能力受限)
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

// 默认熄灭LED (输出高电平)
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
}

/**
* @brief 错误处理函数 (当时钟或外设初始化失败时进入死循环)
* @retval 无
*/
void Error_Handler(void)
{
// 用户可以在此添加调试代码(如点亮错误指示灯)
while (1)
{
// 死循环
}
}