本工程替代
2-1 STM32工程模板(标准库版),使用 STM32CubeMX + HAL 库 芯片:STM32F103C8T6 (Blue Pill / 最小系统板) 时钟:HSE 8MHz → PLL ×9 → SYSCLK = 72MHz 工具链:STM32CubeMX + Keil MDK v5
2-10 HAL工程模板/
├── Core/ ← 用户核心代码 (CubeMX 生成风格)
│ ├── Inc/ ← 头文件
│ │ ├── main.h ← 主头文件 (句柄声明 + 函数原型)
│ │ ├── stm32f1xx_hal_conf.h ← HAL 配置开关
│ │ └── stm32f1xx_it.h ← 中断声明
│ └── Src/ ← 源文件
│ ├── main.c ← 主程序 (LED 闪烁 + 外设初始化)
│ ├── stm32f1xx_it.c ← 中断服务函数
│ ├── stm32f1xx_hal_msp.c ← MSP 底层初始化 (GPIO+NVDIC+DMA)
│ └── system_stm32f1xx.c ← 系统时钟初始化
├── Drivers/ ← HAL 驱动库 (需从 CubeF1 包复制)
│ └── STM32F1xx_HAL_Driver/
│ └── (见下文"获取 HAL 源码")
├── Startup/ ← 启动文件 (需从 CubeF1 包复制)
│ └── startup_stm32f103xb.s
├── MDK-ARM/ ← Keil 项目文件
│ └── (见"Keil 工程配置")
├── CubeMX_Config/ ← CubeMX 配置文件说明
└── README.md ← 本文件 (完整指南)
STM32CubeF1 固件包包含了所有 HAL 驱动源码、启动文件和示例。两种方式获取:
- 打开 STM32CubeMX
- 菜单栏 → Help → Manage embedded software packages
- 找到 STM32CubeF1 → 点击 Install(约 500MB)
- 安装完成后,固件包在:
- Windows:
C:\Users\<用户名>\STM32Cube\Repository\STM32Cube_FW_F1_V1.8.x
- Windows:
下载后解压到任意目录即可。
从 CubeF1 包复制以下文件到本工程:
# HAL 驱动源码 (根据你启用的模块选择)
从: STM32Cube_FW_F1_V1.8.x\Drivers\STM32F1xx_HAL_Driver\
到: 本工程\Drivers\STM32F1xx_HAL_Driver\
必须复制:
├── Inc/ ← HAL 头文件
│ ├── stm32f1xx_hal.h ← HAL 总头文件
│ ├── stm32f1xx_hal_def.h ← HAL 通用定义
│ ├── stm32f1xx_hal_gpio.h ← GPIO
│ ├── stm32f1xx_hal_rcc.h ← RCC
│ ├── stm32f1xx_hal_cortex.h ← NVIC/SysTick
│ ├── stm32f1xx_hal_pwr.h ← 电源管理
│ ├── stm32f1xx_hal_dma.h ← DMA
│ ├── stm32f1xx_hal_uart.h ← USART
│ ├── stm32f1xx_hal_tim.h ← 定时器/PWM
│ └── stm32f1xx_hal_adc.h ← ADC
│
└── Src/ ← HAL 源文件 (按需复制)
├── stm32f1xx_hal.c ← HAL 核心
├── stm32f1xx_hal_gpio.c ← GPIO
├── stm32f1xx_hal_rcc.c ← RCC
├── stm32f1xx_hal_cortex.c ← NVIC/SysTick
├── stm32f1xx_hal_pwr.c ← 电源管理
├── stm32f1xx_hal_dma.c ← DMA
├── stm32f1xx_hal_uart.c ← USART
├── stm32f1xx_hal_tim.c ← 定时器/PWM
└── stm32f1xx_hal_adc.c ← ADC
# CMSIS 设备文件
从: STM32Cube_FW_F1_V1.8.x\Drivers\CMSIS\Device\ST\STM32F1xx\
到: 本工程\Drivers\CMSIS\Device\ST\STM32F1xx\
必须复制:
├── Include/
│ ├── stm32f103xe.h ← F103 寄存器定义 (大容量)
│ ├── stm32f1xx.h ← 系列总头文件
│ └── system_stm32f1xx.h ← 系统时钟头文件
# 启动文件
从: STM32Cube_FW_F1_V1.8.x\Drivers\CMSIS\Device\ST\STM32F1xx\Source\Templates\arm\
到: 本工程\Startup\
选择: startup_stm32f103xb.s ← F103C8 是 64KB Flash (中等容量)
注意: C8=64KB→startup_stm32f103xb.s
CB=128KB→startup_stm32f103xc.s (不要搞错)
- 打开 Keil uVision5
- Project → New uVision Project
- 选择路径:
2-10 HAL工程模板/MDK-ARM/ - 工程名:
HAL_Template - 芯片选择:STM32F103C8(或 STM32F103C8Tx)
- 弹出 Manage Run-Time Environment → 直接点 OK(我们不使用 RTE)
- 如果提示 "Copy STM32 startup file to project folder" → 点否(我们用自己的)
在 Keil 左侧 Project 窗口,右键 Target → Manage Project Items
创建 4 个 Group:
| Group | 添加的文件 | 来源 |
|---|---|---|
| Core | Core/Src/main.c |
本工程 |
Core/Src/stm32f1xx_it.c |
本工程 | |
Core/Src/stm32f1xx_hal_msp.c |
本工程 | |
Core/Src/system_stm32f1xx.c |
本工程 | |
| HAL_Driver | Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal.c |
CubeF1 包 |
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_gpio.c |
CubeF1 包 | |
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_rcc.c |
CubeF1 包 | |
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_cortex.c |
CubeF1 包 | |
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_pwr.c |
CubeF1 包 | |
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_dma.c |
CubeF1 包 | |
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_uart.c |
CubeF1 包 | |
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_tim.c |
CubeF1 包 | |
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_adc.c |
CubeF1 包 | |
| Startup | Startup/startup_stm32f103xb.s |
CubeF1 包 |
| Doc | (可选)放 README 链接 |
① Target 选项卡 (魔棒 → Target)
| 参数 | 值 | 说明 |
|---|---|---|
| Xtal (MHz) | 72.0 | 系统时钟频率,用于调试器 |
| Read/Only Memory Areas | IROM1: Start=0x8000000, Size=0x10000 | 64KB Flash |
| Read/Write Memory Areas | IRAM1: Start=0x20000000, Size=0x5000 | 20KB RAM |
② C/C++ 选项卡 (魔棒 → C/C++)
Define: USE_HAL_DRIVER, STM32F103xE
| 宏 | 含义 |
|---|---|
USE_HAL_DRIVER |
告诉 HAL 库被启用 |
STM32F103xE |
芯片型号——注意是 xE 不是 xB!因为 F103 大容量和中容量共用一套头文件,CubeMX 统一用 xE |
Include Paths (添加头文件搜索路径):
Core\Inc
Drivers\STM32F1xx_HAL_Driver\Inc
Drivers\CMSIS\Device\ST\STM32F1xx\Include
Drivers\CMSIS\Include
③ Debug 选项卡
使用你的调试器(ST-Link / J-Link / DAP-Link):
- ST-Link: 选 ST-Link Debugger → Settings → Flash Download → 勾选 Reset and Run
- DAP-Link: 选 CMSIS-DAP Debugger
- J-Link: 选 J-Link / J-Trace Cortex
④ Linker 选项卡
- 不需要额外的 linker scatter file,使用 Keil 默认即可
- F7 编译(Build Target)
- 如果编译通过,提示 "0 Error(s), 0 Warning(s)"
- Ctrl+F5 下载并调试
- 全速运行(F5),看到板载 LED 以 0.5 秒间隔闪烁
HAL 的优势是"按需加载",下面我演示如何从最简单的 LED 闪烁开始,逐步添加每个外设。
这是最低可行配置。确保以下三个条件满足后,你的工程就跑通了:
# 条件 1: stm32f1xx_hal_conf.h 中至少启用这些模块
#define HAL_MODULE_ENABLED
#define HAL_CORTEX_MODULE_ENABLED
#define HAL_GPIO_MODULE_ENABLED
#define HAL_PWR_MODULE_ENABLED
#define HAL_RCC_MODULE_ENABLED
#define HAL_DMA_MODULE_ENABLED
# 条件 2: main.c 中只保留 GPIO 初始化
main() 中调用: HAL_Init() → SystemClock_Config() → MX_GPIO_Init()
while(1) 中: HAL_GPIO_TogglePin(LED_PORT, LED_PIN) + HAL_Delay(500)
# 条件 3: 在 stm32f1xx_it.c 中实现 SysTick_Handler
void SysTick_Handler(void) { HAL_IncTick(); }
✅ 验证通过: LED 以 0.5s 间隔闪烁。说明:
- HAL 库正常工作
- SystemClock_Config() 成功配置 72MHz
- HAL_Delay() 基于 SysTick 正常工作
// ① main.c 中取消注释
MX_USART1_UART_Init();
// 在 while(1) 中添加:
HAL_UART_Transmit(&huart1, (uint8_t*)"Hello\r\n", 8, 100);
// ② stm32f1xx_hal_conf.h 中启用
#define HAL_UART_MODULE_ENABLED
// ③ stm32f1xx_hal_msp.c 中已有 USART 的 MSP 代码
// 只需确认引脚匹配: PA9=TX, PA10=RX✅ 验证通过: 串口助手收到 "Hello"。说明:
- HAL_UART_Init() 成功执行
- HAL_UART_MspInit() 配置了 GPIO 和 NVIC(无需你手动写)
- HAL_UART_Transmit() 的 timeout 机制不会死等
// ① main.c 中,在 MX_USART1_UART_Init() 之后启动
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
// ② 在 main.c 末尾实现回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1) {
// rx_byte 就是收到的数据
// 处理完后重新开启:
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
}
}// ① main.c 中取消注释
MX_TIM2_Init();
// 在初始化后启动:
HAL_TIM_Base_Start_IT(&htim2);
// ② stm32f1xx_hal_conf.h
#define HAL_TIM_MODULE_ENABLED
// ③ main.c 中实现回调
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2) {
HAL_GPIO_TogglePin(LED_PORT, LED_PIN); // 每 1s 翻转 LED
}
}// ① main.c 中 MX_TIM2_Init() 里取消 PWM 配置的注释
// ② 在 main 中启动 PWM:
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
// 修改占空比:
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 500); // 50%
// ③ stm32f1xx_hal_msp.c 中 HAL_TIM_PWM_MspInit 已配置 PA0// ① main.c 中取消注释
MX_ADC1_Init();
// ② 在 while(1) 中采集:
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 100);
uint16_t val = HAL_ADC_GetValue(&hadc1);三大要点:
- 不需要手动配置 PA0 为模拟输入(MSP 中已有)
- 不需要手动校准(HAL_ADC_Init 内部自动校准)
HAL_ADC_GetValue()返回 0~4095 的 12 位值
| 情景 | 标准库做法 | HAL 做法 |
|---|---|---|
| 外设不工作 | 查 GPIO 配置 + 时钟使能 | 查 MspInit 是否配置了 GPIO 和时钟 |
| 中断不触发 | 查 NVIC 优先级 + I 位 | 查 HAL_NVIC_SetPriority() 是否在 MSP 中 |
| 中断触发但没反应 | 查标志位和清标志 | 查回调函数是否被覆盖(__weak) |
| 串口收不到 | 查 RX 引脚配置 | 查是否调用了 HAL_UART_Receive_IT() |
| HAL_Delay 卡死 | - | 查 SysTick_Handler 是否调用了 HAL_IncTick() |
| 程序跑飞进 HardFault | 查栈/数组越界 | 同上,在 HardFault_Handler 加断点 |
| 编译报 undefined symbol | 查是否添加了 .c 文件 | 同左,别忘了加 HAL 源码到工程 |
最常用的 HAL 调试宏:
// 检查函数返回值 (几乎所有 HAL 函数都有返回)
if (HAL_UART_Transmit(&huart1, data, len, 100) != HAL_OK)
{
Error_Handler(); // 出错了!可能是超时或参数错
}
// 查看当前 SysTick 值
uint32_t now = HAL_GetTick(); // 毫秒级时间戳你的 Modules/ 封装库只需要改 .c 文件里的 HAL 调用,.h 接口完全不变。
// led.h — 只需要改 #include
// 原: #include "stm32f10x.h"
// 改为:
#include "stm32f1xx_hal.h"
// 引脚宏保持不变:
#define LED_GPIO_PORT GPIOC
#define LED_GPIO_PIN GPIO_Pin_13 // ← 注意: HAL 中 GPIO_Pin_13 和标准库值一样
// HAL 是 GPIO_PIN_13 (大写 PIN)
// 标准库是 GPIO_Pin_13 (小写 Pin)
// 值都是 0x2000,可以通用#include "led.h"
void LED_Init(void)
{
__HAL_RCC_GPIOC_CLK_ENABLE(); // ← 替代 RCC_APB2PeriphClockCmd
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_13; // ← 注意宏名变化
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // ← 宏名变化
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // ← 宏名变化
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); // ← 替代 GPIO_Init
LED_Off();
}
void LED_On(void)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // 低电平点亮
}
void LED_Off(void)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); // 高电平熄灭
}
void LED_Toggle(void)
{
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // ← 内置翻转!标准库没有
}其他模块同理。详见知识库笔记 [[concepts/从标准库到HAL库迁移指南]]。
.\Core\Inc
.\Drivers\STM32F1xx_HAL_Driver\Inc
.\Drivers\CMSIS\Device\ST\STM32F1xx\Include
.\Drivers\CMSIS\Include
USE_HAL_DRIVER, STM32F103xE
Core\Src\main.c
Core\Src\stm32f1xx_it.c
Core\Src\stm32f1xx_hal_msp.c
Core\Src\system_stm32f1xx.c
Startup\startup_stm32f103xb.s
Drivers\STM32F1xx_HAL_Driver\Src\stm32f1xx_hal.c
Drivers\STM32F1xx_HAL_Driver\Src\stm32f1xx_hal_gpio.c
Drivers\STM32F1xx_HAL_Driver\Src\stm32f1xx_hal_rcc.c
Drivers\STM32F1xx_HAL_Driver\Src\stm32f1xx_hal_cortex.c
Drivers\STM32F1xx_HAL_Driver\Src\stm32f1xx_hal_pwr.c
Drivers\STM32F1xx_HAL_Driver\Src\stm32f1xx_hal_dma.c
Drivers\STM32F1xx_HAL_Driver\Src\stm32f1xx_hal_uart.c
Drivers\STM32F1xx_HAL_Driver\Src\stm32f1xx_hal_tim.c
Drivers\STM32F1xx_HAL_Driver\Src\stm32f1xx_hal_adc.c
原因: SysTick_Handler 中没有调用 HAL_IncTick()
解决: 检查 stm32f1xx_it.c,确保:
void SysTick_Handler(void) { HAL_IncTick(); }原因: USE_FULL_ASSERT 被启用但没有实现 assert_failed()
解决: stm32f1xx_hal_conf.h 中确保 #define USE_FULL_ASSERT 0U(默认已关闭)
原因: SysTick 中断优先级高于当前中断,或者 SysTick 根本没在跑 解决:
- 检查
SysTick_Handler是否调用了HAL_IncTick() - 检查
HAL_Init()是否在SystemClock_Config()之前调用 - 在中断服务函数中不要调
HAL_Delay()
原因: 没有启动中断接收
解决: 在初始化后调用一次 HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
然后在回调中再次调用以持续接收
原因: 启用了太多不必要的 HAL 模块
解决: stm32f1xx_hal_conf.h 中注释掉不用的模块。只用 GPIO + RCC 时,HAL 代码约 8KB。加上 UART 约 12KB。加上 TIM 约 16KB。F103C8 有 64KB Flash,非常充裕。
💡 核心心法: 第一次用 HAL 时,你最大的不适应会是**"GPIO 配置去哪里了?"** 答案是:在
stm32f1xx_hal_msp.c里,被HAL_UART_Init()自动调用了。 你不需要去记 PA9 配什么模式——CubeMX 配一次后,MSP 代码自动生成。 这是 HAL 的"配置即代码"哲学,和标准库的"所有代码自己写"完全不同。