Skip to content

1123Chase1123/FreeRTOS-Environment-Monitor

Repository files navigation

STM32 HAL 工程模板 — 完整构建指南

本工程替代 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                      ← 本文件 (完整指南)

一、获取 HAL 源码和启动文件

STM32CubeF1 固件包包含了所有 HAL 驱动源码、启动文件和示例。两种方式获取:

方式 A:通过 CubeMX 下载(推荐)

  1. 打开 STM32CubeMX
  2. 菜单栏 → Help → Manage embedded software packages
  3. 找到 STM32CubeF1 → 点击 Install(约 500MB)
  4. 安装完成后,固件包在:
    • Windows: C:\Users\<用户名>\STM32Cube\Repository\STM32Cube_FW_F1_V1.8.x

方式 B:官网下载

https://www.st.com/en/embedded-software/stm32cubef1.html

下载后解压到任意目录即可。

需要复制的文件

从 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 工程配置

2.1 新建 Keil 工程

  1. 打开 Keil uVision5
  2. Project → New uVision Project
  3. 选择路径:2-10 HAL工程模板/MDK-ARM/
  4. 工程名:HAL_Template
  5. 芯片选择:STM32F103C8(或 STM32F103C8Tx)
  6. 弹出 Manage Run-Time Environment → 直接点 OK(我们不使用 RTE)
  7. 如果提示 "Copy STM32 startup file to project folder" → 点否(我们用自己的)

2.2 添加源文件到工程

在 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 链接

2.3 配置编译选项

① 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 默认即可

2.4 编译和下载

  1. F7 编译(Build Target)
  2. 如果编译通过,提示 "0 Error(s), 0 Warning(s)"
  3. Ctrl+F5 下载并调试
  4. 全速运行(F5),看到板载 LED 以 0.5 秒间隔闪烁

三、逐步添加外设 — 从 LED 到全功能

HAL 的优势是"按需加载",下面我演示如何从最简单的 LED 闪烁开始,逐步添加每个外设。

第一步:GPIO + 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 正常工作

第二步:添加 USART 串口

// ① 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 机制不会死等

第三步:添加 USART 中断接收

// ① 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
    }
}

第五步:添加 PWM

// ① 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

第六步:添加 ADC

// ① 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 vs 标准库 —— 调试技巧对照

情景 标准库做法 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 适配到 HAL

你的 Modules/ 封装库只需要改 .c 文件里的 HAL 调用,.h 接口完全不变。

led.h — 改为 HAL 版本

// 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,可以通用

led.c — 改为 HAL 实现

#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库迁移指南]]。


六、完整工程路径配置总结

Keil 头文件包含路径

.\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

七、常见问题

Q1: 编译报错 "undefined symbol HAL_IncTick"

原因: SysTick_Handler 中没有调用 HAL_IncTick() 解决: 检查 stm32f1xx_it.c,确保:

void SysTick_Handler(void) { HAL_IncTick(); }

Q2: 编译报错 "error: L6218E: Undefined symbol assert_param"

原因: USE_FULL_ASSERT 被启用但没有实现 assert_failed() 解决: stm32f1xx_hal_conf.h 中确保 #define USE_FULL_ASSERT 0U(默认已关闭)

Q3: HAL_Delay() 卡死

原因: SysTick 中断优先级高于当前中断,或者 SysTick 根本没在跑 解决:

  1. 检查 SysTick_Handler 是否调用了 HAL_IncTick()
  2. 检查 HAL_Init() 是否在 SystemClock_Config() 之前调用
  3. 在中断服务函数中不要调 HAL_Delay()

Q4: USART 发送正常但接收不到

原因: 没有启动中断接收 解决: 在初始化后调用一次 HAL_UART_Receive_IT(&huart1, &rx_byte, 1); 然后在回调中再次调用以持续接收

Q5: 编译后代码体积很大(超过 64KB?)

原因: 启用了太多不必要的 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 的"配置即代码"哲学,和标准库的"所有代码自己写"完全不同。

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors