1. 工程创建前的准备工作
第一次接触STM32开发的朋友可能会被各种开发环境搞得晕头转向。Keil MDK作为ARM芯片开发的经典工具,虽然界面看起来有些"复古",但它的稳定性和对ARM Cortex-M系列芯片的完善支持,使其成为STM32开发的首选IDE之一。我至今还记得2015年第一次用Keil建立STM32工程时的手忙脚乱,现在就把这些年积累的经验系统地分享给大家。
1.1 工具链安装要点
在开始建立工程前,需要确保开发环境完整。Keil MDK-ARM(现在叫Keil Studio)的安装有几个关键点需要注意:
-
Keil MDK基础安装:从官网下载最新版安装包(当前最新为v5.38),安装路径不要包含中文和空格。我习惯安装在
C:\Keil_v5这样的路径下。安装时会提示安装ARM Compiler,务必勾选最新版本(建议V6.18以上)。 -
设备支持包安装:Keil安装完成后,打开Pack Installer(工具栏的蓝色立方体图标),搜索"STM32F1"安装对应的DFP(Device Family Pack)。对于F103系列,需要安装"Keil.STM32F1xx_DFP"这个包,它包含了芯片定义文件、启动代码和基本外设库。
注意:如果Pack Installer下载速度慢,可以手动从Keil官网下载.pack文件后双击安装。遇到过公司内网环境下自动更新失败的情况,手动安装更可靠。
- ST-Link驱动:如果你使用ST-Link调试器,需要单独安装驱动。推荐使用Zadig工具安装libusb-win32驱动,这样兼容性更好。我遇到过官方驱动在某些Win10版本上无法识别设备的问题,改用Zadig安装后问题解决。
1.2 工程目录结构设计
良好的工程目录结构能极大提升后续开发效率。建议在开始前规划好以下目录(以项目名STM32F103_Demo为例):
code复制STM32F103_Demo/
├── Docs/ # 存放数据手册、参考文档
├── Drivers/ # 硬件驱动层
│ ├── CMSIS/ # Cortex核心支持文件
│ └── STM32F1xx_HAL_Driver/ # HAL库文件
├── Middlewares/ # 中间件(如FreeRTOS)
├── Projects/ # Keil工程文件
├── Src/ # 应用源代码
│ ├── main.c
│ ├── stm32f1xx_it.c
│ └── system_stm32f1xx.c
├── Inc/ # 头文件
└── Utilities/ # 实用工具(如调试脚本)
这种结构参考了STM32CubeMX生成的工程,但做了适当简化。实际项目中可以根据需要增减,关键是保持一致性。我见过有些工程师把所有文件堆在根目录下,后期维护时苦不堪言。
2. 创建基础工程框架
2.1 新建工程步骤详解
打开Keil MDK,点击Project → New μVision Project:
-
选择保存路径:定位到刚才创建的
Projects目录,命名工程为STM32F103_Demo.uvprojx。建议工程名不要带空格和特殊字符。 -
选择设备型号:在弹出的设备选择窗口中,输入"STM32F103"筛选,根据你的具体芯片选择。例如常用的STM32F103C8T6属于Medium-density devices,选择"STM32F103C8"即可。如果找不到确切型号,选择同系列相近型号也可以,后续可以修改设备参数。
-
运行环境配置:接下来会弹出"Manage Run-Time Environment"窗口,这是Keil的特色功能。对于初学者,建议先不勾选任何组件,我们后续手动添加需要的文件。直接点击OK创建空工程。
2.2 添加核心系统文件
创建空工程后,需要手动添加STM32运行必需的核心文件:
-
启动文件:从安装的DFP包中复制启动文件。路径通常为:
C:\Keil_v5\ARM\PACK\Keil\STM32F1xx_DFP\2.4.0\CMSIS\Device\ST\STM32F1xx\Source\Templates\arm对于STM32F103C8T6,选择
startup_stm32f103xb.s(xb代表中等容量)。将这个文件添加到工程的Drivers/CMSIS目录下。 -
系统初始化文件:同路径下找到
system_stm32f1xx.c,复制到Src目录并添加到工程。 -
中断处理文件:复制模板目录下的
stm32f1xx_it.c和stm32f1xx_it.h到Src和Inc目录,并添加到工程。
实操技巧:在Keil中添加文件时,建议先在工程目录中创建好物理文件,再通过"Add Existing Files"添加。避免Keil自动复制文件到不明位置,导致后期管理混乱。
2.3 基础工程配置
右键点击Target 1选择Options for Target,进行关键配置:
-
Target标签页:
- 晶振频率设为8.0MHz(外部晶振常见值)
- 勾选Use MicroLIB(简化版C库,节省空间)
-
Output标签页:
- 勾选Create HEX File(用于烧录)
- 设置输出目录为
Projects\output
-
C/C++标签页:
- Define中添加:
STM32F103xB, USE_STDPERIPH_DRIVER - Include Paths添加:
../Inc,../Drivers/CMSIS/Include,../Drivers/CMSIS/Device/ST/STM32F1xx/Include
- Define中添加:
-
Debug标签页:
- 选择你的调试器(如ST-Link Debugger)
- 点击Settings,Port选择SW,Max Clock设为1MHz
- 勾选Reset and Run,这样下载后自动运行
这些配置是工程能正常编译和调试的基础。特别是Define和Include Paths设置不正确会导致各种头文件找不到的错误,新手常在这里踩坑。
3. HAL库与时钟配置
3.1 添加HAL库支持
虽然STM32支持标准外设库(SPL)和HAL库,但ST官方主推HAL库,对新项目建议使用HAL:
-
从STM32CubeF1包中复制以下文件到工程:
- HAL驱动:
Drivers/STM32F1xx_HAL_Driver/Src和Inc下的全部文件 - CMSIS支持:
Drivers/CMSIS/Device/ST/STM32F1xx下的头文件
- HAL驱动:
-
在工程中创建HAL组,添加必要的源文件。基础工程通常需要:
stm32f1xx_hal.cstm32f1xx_hal_cortex.cstm32f1xx_hal_rcc.cstm32f1xx_hal_gpio.c
-
在
main.c中包含基础头文件:c复制#include "stm32f1xx.h" #include "stm32f1xx_hal.h"
避坑指南:HAL库文件较多,全部添加会导致编译缓慢。建议按需添加,用到哪个外设再添加对应的HAL文件。我曾在一个工程中盲目添加所有HAL文件,导致每次编译要2分钟,后来优化后只需20秒。
3.2 系统时钟配置
STM32F103的时钟树相对复杂,正确配置是工程稳定的基础。推荐在main.c中实现以下初始化:
c复制void SystemClock_Config(void) {
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// 配置外部高速振荡器(HSE)
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; // 8MHz * 9 = 72MHz
HAL_RCC_OscConfig(&RCC_OscInitStruct);
// 配置系统时钟
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 = 72MHz
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; // PCLK1 = 36MHz
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // PCLK2 = 72MHz
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
// 更新系统核心时钟变量
SystemCoreClockUpdate();
}
然后在main()函数开始时调用:
c复制HAL_Init();
SystemClock_Config();
这个配置将系统时钟设置为72MHz,是STM32F103的最高运行频率。实际项目中要根据具体需求调整,比如低功耗应用可能选择更低频率。
调试心得:如果程序运行异常,首先检查时钟配置是否正确。我曾遇到USART通信波特率不准的问题,最后发现是PLL倍频设置错误导致系统时钟不对。用逻辑分析仪测量GPIO翻转频率是验证时钟的好方法。
4. 编写用户代码与调试
4.1 基础GPIO控制实现
让我们实现一个简单的LED闪烁程序验证工程:
- 在
main.c中添加LED初始化代码:
c复制void LED_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOC_CLK_ENABLE(); // 使能GPIOC时钟
// 配置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);
}
- 修改main函数:
c复制int main(void) {
HAL_Init();
SystemClock_Config();
LED_Init();
while (1) {
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
HAL_Delay(500); // 500ms延时
}
}
- 编译并下载到开发板,应该能看到LED每隔1秒闪烁一次。
4.2 常见问题排查
新手在建立工程时常会遇到以下问题:
-
编译错误:未定义HAL函数
- 原因:忘记添加HAL库源文件或未定义
USE_HAL_DRIVER - 解决:检查HAL文件是否添加,
Options for Target → C/C++ → Define中确保有USE_HAL_DRIVER
- 原因:忘记添加HAL库源文件或未定义
-
链接错误:无足够内存
- 原因:STM32F103C8只有64KB Flash,启用过多功能可能导致溢出
- 解决:优化代码,禁用不必要功能,或换用更大容量型号
-
程序下载后不运行
- 原因1:Boot引脚配置错误
- 解决:确保BOOT0=0,BOOT1=0(从主Flash启动)
- 原因2:未正确配置复位和运行选项
- 解决:在
Options for Target → Debug → Settings中勾选Reset and Run
- 解决:在
- 原因1:Boot引脚配置错误
-
HAL_Delay不准确
- 原因:SysTick中断优先级配置问题
- 解决:在
HAL_Init()后调用HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
调试技巧:当程序行为异常时,首先检查:
- 电源是否稳定(测量3.3V电压)
- 复位电路是否正常(NRST引脚应为高电平)
- 时钟配置是否正确(用示波器测量晶振是否起振)
这些基础检查能解决大部分硬件相关问题。
4.3 工程优化建议
基础工程运行稳定后,可以考虑以下优化:
-
使用LL库替代HAL:对性能敏感的应用,可以使用ST提供的Low Layer库,它比HAL更接近寄存器操作,效率更高。
-
启用编译优化:在
Options for Target → C/C++中设置Optimization为-O2,可以显著减小代码体积和提高运行速度。 -
添加版本控制:使用Git管理工程,忽略临时文件。典型的.gitignore内容:
code复制*.uvgui.* *.uvoptx *.uvprojx.user build/ output/ -
实现日志输出:添加基于串口的调试信息输出,便于问题诊断。例如:
c复制#include <stdio.h> int _write(int file, char *ptr, int len) { HAL_UART_Transmit(&huart1, (uint8_t*)ptr, len, HAL_MAX_DELAY); return len; }然后就可以使用
printf()输出调试信息了。
经过这些步骤,你已经建立了一个完整的STM32F103开发工程框架。这个工程可以作为后续所有项目的基础模板,根据具体需求添加外设驱动和应用代码即可。