简介:STM32F103ZET6空工程模板,面向基于STM32F103ZET6芯片的嵌入式开发者和单片机学习者,提供了一套可直接运行的Keil MDK工程框架,省去芯片选型、启动文件配置、标准外设库导入等重复操作,便于直接进入功能开发。压缩包共178个文件、6.63MB,主要包含C源码与头文件、Keil工程文件以及编译输出的axf/hex/map等,已集成定时器、串口、ADC、CAN、I2C等标准外设库驱动,可直接编译并作为项目起点。模板在CSDN已有1086人学习/下载,对于快速搭建STM32开发环境具有实用参考价值。借助模板,开发者能聚焦于外设控制与业务逻辑,同时完整的库函数目录和链接脚本也适合对照学习STM32各模块的初始化流程,是日常开发与课程设计的实用基础工程。
1. 一份能编译、能烧录、能点灯的 STM32F103ZET6 空工程模板,为什么值得自己搭
某开发者从网上下了一个 ZET6 例程包,打开工程一看,十几个 .c 文件层层嵌套,外设中间层、事件驱动框架、打印日志组件全都塞在一起。想改一颗 LED 的闪烁频率,得先读懂三层封装。那一刻他就明白:例程是用来“看懂”的,不是用来“改”的。于是花一个下午,自己搭了一份干净到只有启动、时钟、点灯三步的空工程模板,之后所有项目都从这份地基开始盖楼。
这份模板解决的是两个真实痛点:一是刚拿到 ZET6 开发板的人,需要一个能确认“板子本身没坏、编译链没问题、调试器能连上”的最小程序;二是常年维护多个项目的人,需要一份没有历史包袱、删掉外设代码就能独立编译的工程骨架。它不追求功能,只追求确定性与可复现性——把黑匣子拆开,让你看见芯片上电后每一行代码在做什么。
2. 模板选型决策:寄存器、标准外设库还是 HAL/LL,先想清楚再动手
2.1 三种代码风格的取舍:依赖越小,离硬件越近
STM32F103ZET6 的开发代码风格大致有三条路:纯寄存器操作、标准外设库(SPL)、HAL/LL 库。空工程模板本质上是一个“最小启动骨架”,它的目标不是写完业务逻辑,而是把芯片从复位向量带到 main 函数,再把系统时钟配置到稳定状态。这个目标下,依赖越少越可控。
| 风格 | 编译依赖 | 启动流程透明性 | 适合模板的程度 | 代价 |
|---|---|---|---|---|
| 纯寄存器 | 无 | 完全透明,每行都知道在做什么 | 最合适 | 写外设驱动时要翻寄存器手册 |
| 标准外设库 | 需引入 SPL 头文件与源文件 | 透明,但封装了一层函数 | 合适,通用性强 | 版本较老,新库停止维护 |
| HAL/LL | 依赖整套 CMSIS 和 HAL 驱动 | 不透明,初始化流程被库封装 | 不太合适 | 编译体积大,启动过程难完全掌控 |
我一般会把空模板做成“寄存器为主、LL 库为辅”的形态:启动文件、时钟、GPIO 点灯这些硬件初始化部分用纯寄存器实现,业务层再按需引入库。这样模板本身永远保持极小体积,编译时间按秒计,而业务代码的选型自由度完全保留。
2.2 为什么时钟配置比外设初始化更关键
ZET6 上电默认使用内部 8MHz 的 HSI 时钟,这个频率跑点灯没问题,但一旦外接晶振、串口、定时器,时钟源和总线分频不对,所有外设的时序都会出错。时钟树是芯片运行的地基:外部晶振 HSE 经过 PLL 倍频成为系统时钟 SYSCLK,再经 AHB 分频到各总线。
模板里三个参数必须跟硬件严格对齐:HSE 晶振频率由开发板决定,PLL 倍频系数决定系统主频,APB1/APB2 分频决定外设总线频率。ZET6 的 APB1 上限为 36MHz,APB2 上限为 72MHz,这是硬约束。配错 APB1 分频,挂载在低速总线上的串口和定时器就会出现波特率偏移和计时异常。
| 参数 | 典型值 | 依据 |
|---|---|---|
| HSE | 8MHz | 板载晶振决定,需看原理图 |
| PLL 倍频 | ×9 | 8MHz × 9 = 72MHz |
| SYSCLK | 72MHz | ZET6 主频上限 |
| AHB 分频 | 1 | 72MHz 直接输出 |
| APB1 分频 | 2 | 36MHz,低于上限 |
| APB2 分频 | 1 | 72MHz,等于主频 |
2.3 模板要多大才算“空”
真正的空工程模板,文件清单应该精简到 6 个以内:启动汇编文件、系统时钟源文件、链接脚本、主函数源文件、一个板级头文件、一个 Makefile 或工程文件。不需要标准外设库全量源码,不需要 RTOS,不需要调试打印框架。
编译成功后,生成的固件体积应该只有几 KB——启动代码、时钟初始化、点灯循环,加起来不会超过 5KB。如果编译出的 .hex 或 .bin 有几十 KB,说明模板里混进了不必要的东西,回去看一下链接脚本和源文件列表。模板保持“空”,意义在于每次排除问题时,问题的搜索范围足够小。
3. 从零搭最小空工程:启动文件、时钟配置与点灯验证
3.1 目录结构与文件职责
我习惯把模板组织成三个目录加四个核心文件,业务代码和启动代码物理隔离,方便批量复制成新工程。
zet6_template/ ├── Core/ │ ├── main.c │ ├── system_clock.c │ └── board.h ├── Startup/ │ └── startup_stm32f103zet6.s ├── Linker/ │ └── stm32f103zet6_flash.ld └── Makefilemain.c 放主函数和业务入口,system_clock.c 放时钟初始化,board.h 放引脚和晶振宏定义,启动文件负责中断向量表和堆栈初始化,链接脚本告知编译器片内 Flash 与 RAM 的地址布局。Makefile 负责把以上文件编译链接成固件。这样的目录结构,复制到新工程时只需改 board.h 和链接脚本里的容量参数。
3.2 启动文件与链接脚本:先告诉编译器芯片长什么样
启动汇编文件是模板里唯一一个不需要逐行理解的“玄学文件”,但它的关键内容必须会看。它做的事是:定义栈顶地址、定义中断向量表、调用 SystemInit 时钟初始化函数、跳转 main。
; 向量表前两个入口由链接脚本确定 Vectors: .word _estack ; 栈顶地址,来自链接脚本 .word Reset_Handler ; 复位中断入口 .word NMI_Handler ; ... 其余中断向量保持不变链接脚本中最重要的三个符号是片内 Flash 起始地址、RAM 起始地址和栈大小。ZET6 的 Flash 固定从 0x08000000 开始,RAM 从 0x20000000 开始。栈大小决定局部变量深度,模板里设 1KB 起步,实际项目建议至少 2KB。
MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K }启动文件里向量表第一项指向_estack,这个符号来自链接脚本定义的栈顶地址。如果链接脚本里 RAM 的 ORIGIN 或 LENGTH 写错,程序一上电就会跑飞。换成其他型号时,第一件事就是核对这两个数值。
3.3 系统时钟与 GPIO 点灯代码
时钟初始化用寄存器直接操作,比调用现成库函数更能看到每一步在做什么。下面这段代码把 HSE 外部晶振切换到系统时钟源,并倍频到 72MHz。
void SystemClock_Init(void) { /* 开启 HSE 外部晶振并等待就绪,FLASH 等待周期配成 2 */ FLASH->ACR |= FLASH_ACR_LATENCY_2; RCC->CR |= RCC_CR_HSEON; while (!(RCC->CR & RCC_CR_HSERDY)); /* 配置 PLL:HSE 8MHz × 9 = 72MHz,并开启 PLL */ RCC->CFGR |= RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL9; RCC->CR |= RCC_CR_PLLON; while (!(RCC->CR & RCC_CR_PLLRDY)); /* 切换到 PLL 作为系统时钟,等待切换完成 */ RCC->CFGR |= RCC_CFGR_SW_PLL; while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); }上面两处while等待是硬性要求,晶振起振和 PLL 锁定都需要时间,未就绪前切换时钟会导致系统直接死机。FLASH 等待周期在 48MHz 以上必须设为 2,这是原厂参考手册里的硬性规定,配成 0 或 1 会出现随机死机。把这段代码移植到别的板子时,PLLMULL9要根据实际晶振频率反推:目标是 72MHz,晶振 12MHz 就配PLLMULL6,晶振 25MHz 则要换别的倍数组合。
接下来是点灯逻辑。GPIOB 的第 0 脚接一颗 LED,配置为推挽输出,主循环中翻转电平。
#include "stm32f10x.h" #define LED_PIN (1 << 0) /* PB0, 根据板子原理图修改 */ int main(void) { SystemClock_Init(); /* 开启 GPIOB 时钟,配置 PB0 为推挽输出,速度 50MHz */ RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; GPIOB->CRL &= ~(0x0F << 0); /* 清空 CNF0/MODE0 */ GPIOB->CRL |= (0x03 << 0); /* MODE0 = 11, 输出 50MHz */ GPIOB->CRL &= ~(0x03 << 2); /* CNF0 = 00, 推挽输出 */ while (1) { GPIOB->ODR ^= LED_PIN; volatile int i; for (i = 0; i < 720000; i++); /* 软件延时约 1 秒 */ } }PB0 是 CRL 寄存器里的低 4 位,前 2 位是模式 MODE0,后 2 位是配置 CNF0。寄存器位操作顺序是“先清位再置位”,防止残留的默认值干扰配置。软件延时里用了volatile修饰,防止编译器把空循环优化掉,这是模板阶段最省事的延时方式,不依赖定时器外设。LED 引脚实际接在 PA、PC 还是 PD,以自己板子的原理图为准,改引脚号时同步改端口基地址和 CRL/CRH 选择。
3.4 从模板生成新工程的标准动作
模板的价值体现在复用上。复制整个目录后,按三步生成新工程。
# 复制模板目录并重命名 cp -r zet6_template my_project # 替换板级配置头和链接脚本中的芯片容量 # 例如从 ZET6 换到 C8T6:FLASH 从 512K 改成 64K,RAM 从 64K 改成 20K换芯片型号时,链接脚本的 LENGTH 必须改,否则链接器可能报溢出或程序烧进错误地址。换晶振时,改 system_clock.c 里的 PLL 倍数和 board.h 里的宏。换开发板时,改 main.c 里的 LED 引脚号。三步之外不做任何多余操作,模板的“空”就是为快速生成服务的。
4. 把模板变成趁手工具:调试器配置、烧录脚本与版本管理
4.1 烧录与调试:从模板到跑起来的最小命令
ST-Link 是 ZET6 最常见的调试器,SWD 接口只需四根线:SWDIO、SWCLK、GND、NRST。NRST 不是必须,但接上后连接稳定性明显提高,特别是目标板供电不稳的场景,这属于血泪经验。
# ST-Link 通过 st-flash 烧录固件并复位运行 st-flash --connect-under-reset write build/zet6_template.bin 0x08000000 st-flash resetSWDIO 固定接到 PA13,SWCLK 固定接到 PA14,这是芯片物理引脚复用,无法更改。连接失败时先查这四根线的接通性,再查目标板是否独立供电。--connect-under-reset参数应对“程序把 SWD 引脚复用了”的情况,强制在复位期间建立连接。
4.2 工程模板纳入版本管理:忽略哪些文件
模板从第一天就应该放进 Git 仓库。编译产生的中间文件、链接产物、IDE 本地缓存都不该入库,否则模板会越用越脏。
build/ *.o *.d *.elf *.bin *.hex *.map .vscode/ .idea/模板纳入版本管理后,每次改动都有历史。某一次把时钟初始化的顺序调换导致某个外设异常,git diff能立刻定位改动点。团队协作时,模板仓库作为公共地基,成员各自从模板复制出自己的业务工程,互不干扰。
4.3 在 IDE 里使用模板的两种方式
如果习惯 IDE,把模板目录导入工程即可,但 IDE 生成的配置文件往往带绝对路径,换机器就得改。我更推荐命令行 Makefile 方式,模板用一套命令完成编译和烧录,跨机器、跨平台都能跑。
CC = arm-none-eabi-gcc TARGET = zet6_template CFLAGS = -mcpu=cortex-m3 -mthumb -Wall -O2 \ -I Core -I Startup -I Linker build/$(TARGET).elf: Core/main.c Core/system_clock.c \ Startup/startup_stm32f103zet6.s $(CC) $(CFLAGS) -T Linker/stm32f103zet6_flash.ld \ $^ -o $@ -lc -lm -lnosys arm-none-eabi-objcopy -O binary $@ build/$(TARGET).bin-mcpu=cortex-m3 -mthumb是 ZET6 的固定选项,Cortex-M3 内核只支持 Thumb 指令集,删掉-mthumb会链接失败。-T指定链接脚本,漏掉它编译器会按默认的 RAM 布局链接,程序烧进去直接跑飞。Makefile 里把源文件列表写明白,新增模块时往这个目标依赖里加一行即可,这是模板中最容易维护的部分。
5. 空模板搭建避坑:5 个最常见翻车现场与排查方法
5.1 编译通过但灯不闪:启动文件或链接脚本没对上
现象:编译、烧录都报告成功,复位后 LED 毫无反应,调试器能连接但 PC 停在 HardFault 或原地循环。
原因:启动汇编文件里向量表第一项_estack对应的栈顶地址没有在链接脚本中定义,或者 Flash 的 ORIGIN 写成了 0x08000000 之外的地址。另一个高发原因是startup_stm32f103zet6.s和芯片型号不匹配,比如换到 C8T6 却仍用 ZET6 的启动文件。
解决:先在链接脚本里确认_estack符号赋值正确,再核对 FLASH 起始地址。最简单的验证手段是去掉 main 里所有业务代码,只留一个while(1),如果灯还不闪,问题必然在启动链路。
5.2 串口输出乱码:HSE 晶振频率和实际硬件不一致
现象:模板里加了串口重定向后,用 115200 波特率收数据,终端上全是乱码,但用示波器看 TX 引脚电平变化又貌似正常。
原因:system_clock.c 里按 8MHz 晶振算的 PLL 分频倍频参数,实际板子上焊的是 12MHz 或 25MHz 晶振。时钟主频跑偏后,串口波特率发生器算出的分频值全部偏移,乱码是必然结果。
解决:翻开板子原理图,确认晶振标注频率,再调整 PLL 参数。调试时把系统主频值打印出来,或直接测 RCC 时钟输出引脚的频率,一量便知。
5.3 J-Link / ST-Link 连接失败:SWD 引脚状态与接线细节
现象:点击连接调试器,工具提示无法建立连接,目标板确认通电,线序检查过没有接反。
原因:上一份程序把 PA13 / PA14 复用成了普通 GPIO 并锁死了引脚状态,调试接口被占住。更隐蔽的原因是调试器和目标板没有共地,GND 线松脱时数据线电平没有参考点。
解决:优先用--connect-under-reset方式在复位瞬间抢占调试口。接线时强制把 GND 接牢,不依赖调试器的屏蔽地。仍连不上就按住板子复位键,点连接的同时松手,这是最原始的“玄学”手法,但对锁死调试口的程序确实有效。
5.4 HardFault_Handler 进死循环:中断向量表与数组越界
现象:模板跑通点灯后,加了定时器中断或串口接收中断,程序一进中断就卡在 HardFault_Handler 的while(1)里。
原因:启动文件里对应中断号的服务函数没有定义,编译器把缺省处理指向了默认死循环。另一个常见原因是中断服务函数里操作了大数组,栈大小 1KB 不够用,溢出写穿了 RAM 边界。
解决:模板阶段统一给所有中断向量写上空的弱定义,至少进中断不会直接死掉。栈大小调到 2KB 起步,数组变量改到全局区或堆区,避免占用稀缺的栈空间。
5.5 模板里混入 HAL 库之后编译爆炸
现象:模板原本用寄存器写得干干净净,为了用某个外设库函数,拷贝了 HAL 源文件进来,结果编译报错几百行,符号重复定义。
原因:HAL 依赖 CMSIS 和整套时钟配置,而模板自己的 system_clock.c 也定义了时钟初始化函数,两个符号重名,加上 HAL 默认中断处理被启动文件重复定义,冲突无可避免。
解决:寄存器模板和 HAL 不要混在一个工程里。需要 HAL 时,从原厂官方工程模板重新生成基础工程,再把业务代码迁移进去;需要保持轻量时,外设驱动自己用寄存器写,几十行的事比引入庞大 HAL 更可控。
6. 让模板具备扩展性:板级配置、调试输出与统一断言
空模板稳定后,下一步是给它增加“板级可配置性”。思路是把所有与硬件相关的参数收进一个头文件,换板子只改头文件,不碰源文件。
/* board.h */ #ifndef BOARD_H #define BOARD_H #define BOARD_LED_PORT GPIOB #define BOARD_LED_PIN (1 << 0) #define BOARD_HSE_VALUE 8000000UL #define BOARD_FLASH_END 0x08080000UL #endif我现在的习惯是,拿到任何新板子,第一件事就是花半天把空模板按新板的晶振、LED 引脚、Flash 容量调通,然后在这个“已知能跑”的地基上逐层加外设。所有排错都建立在“模板本身已验证”的前提上,遇到问题就能缩小到新增代码范围,而不是整块板子都是黑匣子。
调试输出方面,给模板加一个轻量串口重定向,方便确认程序走到哪一步。发布时把宏关闭即可,不影响业务代码结构。
int fputc(int ch, FILE *f) { while (!(USART1->SR & USART_SR_TXE)); USART1->DR = ch & 0xFF; return ch; }写断言处理函数同样值得早做。assert_failed默认实现在库内部是空转,模板里覆盖它,让断言失败时输出文件和行号,调试期的后悔药就备好了。这套习惯坚持下来,模板会从“一个工程”变成“一套方法”,它不替你写业务代码,但替你省掉大量低级定位时间,希望帮到你。
本文还有配套的精品资源,点击获取