
简介面向嵌入式入门学习者这是一份基于Keil μVision的STM32F10x系列新建工程模板对应实验0-1的Template工程。模板已预先集成标准外设库、Cortex-M3内核文件、STM32启动汇编代码、系统时钟配置及常用外设驱动可直接作为后续实验或项目开发的起点有效解决新手手动创建Keil工程时易遗漏启动文件和库文件、编译报错频发的问题。压缩包共69个文件以31个头文件和30个C源文件为主体涵盖寄存器定义、外设驱动接口与中断处理逻辑另含汇编启动文件、hex烧录文件、Keil工程配置文件和清理脚本等整体仅287KB。目前已有779人学习下载适合正在学习STM32基础、希望掌握Keil建工程流程并复用现成模板的开发者。拿到手即可打开工程参照模板结构快速完成初始化代码与主程序编写减少重复配置耗时。1. Template工程模板为什么值得单独做一章把新建工程单独列为实验0-1并且命名为Template工程模板是因为这一章真正要解决的并不是点几下鼠标新建一个工程而是以后每次新建工程都不再重来一次。很多做单片机开发两三年的工程师拿到一块新板子第一反应是翻旧工程找到一个相近的demo直接另存为改了芯片型号、改了引脚、改了时钟配置编译报错了再回头补配置。这种做法快是快但工程里残留了大量与当前项目无关的文件、无效的Include路径和过时的调试器设置换电脑、换人接手、批量出样机时问题会集中爆发。Keil里真正值钱的其实不是那些业务代码而是藏在Options for Target、Flash Download和Debug设置里的工程配置。同一个main.c放进两个uvprojx一个能编译能烧录能进调试另一个在链接阶段就失败差别几乎全在配置。Template工程模板想固化的就是这套东西用一个最小可跑的固件作为骨架把芯片型号、启动文件、宏定义、Include路径、编译选项和下载算法一次性配好存成一个独立目录后续所有项目都从这个骨架长出去。这篇文章按照这个思路先拆模板内容的构成再从零走一遍Keil新建工程的完整流程最后把报错现场和自检技巧都收进来。2. Keil新建工程前先拆清楚Template工程模板的内容2.1 模板里到底要装哪些东西很多人以为模板就是一个能编译的工程目录所以复制一份能用的demo就算完事。这个理解差得比较远。Demo解决的是这条业务怎么实现模板解决的是新工程能不能稳定启动。我做模板的时候只会放四类东西工程文件、启动链路、外设驱动骨架、配置参数业务代码一行都不放。2.1.1 工程文件作为模板的骨架一个Keil工程至少包含三个层次的文件。最顶层是.uvprojx这是keil的工程文件记录target、设备的DFP版本、源文件分组、编译选项、调试器配置旁边还有.uvoptx记录窗口布局和断点之类的用户偏好再往下是源代码目录和输出目录Obj、List。复制模板时.uvoptx往往会在别的电脑上产生窗口尺寸错乱的问题我一般直接删掉或者不纳入版本管理让Keil按新环境重新生成。2.1.2 芯片启动链路不能缺件新建工程最常见的编译0错误但烧进去跑不起来多半是启动文件没加对。以STM32F103C8T6为例标准外设库需要的是startup_stm32f10x_md.sMDK5里也可以靠Run-Time Environment自动添加。除了启动文件还要有system_stm32f10x.c把72MHz时钟配好和CMSIS的core_cm3.h、core_cm3.c。这一链条决定复位后先跑什么、向量表在哪、SystemInit由谁调用任何一环缺失都表现成程序没进main或者链接时报符号找不到。2.2 Keil里值得固化成模板的配置参数工程配置面上真正跨项目通用的是以下几个Target页的晶振和MicroLIB选项、C/C页的Define和Include Paths、Debug页的调试器类型与接口、Flash Download页的编程算法和Reset and Run。下表是配置MAP模板只需改其中需要变化的两到三格配置位置推荐值以STM32F103C8T6为例的参数跨项目是否变化Device实际芯片型号STM32F103C8变化Target外接晶振数值、Use MicroLIB8.0 MHz、MicroLIB勾选一般不变化C/CDefine、Include PathsSTM32F10X_MD, USE_STDPERIPH_DRIVER变化DebugST-Link / J-Link 等SW模式ST-Link Debugger SW一般不变化Flash Download编程算法、复位运行STM32F10x Med-density FlashReset and Run一般不变化2.2.1 用相对路径做Include Paths模板工程要能跟着目录整体挪动Include路径就必须用相对路径。Keil里常用的快捷方式有两个$PROJ_DIR$表示uvprojx所在目录$TARGET_NAME$表示当前target名称。假设uvprojx放在Template\Project\下标准库放在Template\Libraries\下Include路径写成$PROJ_DIR$\..\Libraries\CMSIS\Include比写死一个C盘绝对路径稳得多。很多人从别人那里拷工程回来编译报错排到最后发现是绝对路径指向了别人机器上的目录这是Template模板最容易踩的隐性坑。2.2.2 Define的作用不是给人看的C/C选项卡里的Define直接参与代码裁剪。STM32标准库通过USE_STDPERIPH_DRIVER打开外设驱动通过STM32F10X_MD选择器件密度对应的寄存器定义HAL库里则通常用STM32F103xB来匹配Flash容量和中断向量。宏定义错了最常见的症状是外设寄存器结构体和实际芯片对不上编译能过跑起来行为异常。模板里要把宏和芯片型号做成强绑定换芯片时同时改Device和Define不能只改其中一个。提示Keil的Pack包位置也值得在模板里固化下来。默认装在C盘用户目录下重装系统后如果没有备份Pack打开模板会直接提示Device找不到。常见做法是把离线Pack文件放到一个公共盘目录用Pack Installer的Manual Install指定模板注释里写清依赖的DFP版本号。3. 用Keil从零新建一个可编译、可烧录的工程3.1 创建工程与器件选择实际操作一次Keil新建工程流程大概是这样的打开uVision5菜单选择Project New uVision Project工程名建议直接用日期加板卡名比如20240518_led_template路径选到一个纯英文、无空格的目录下。弹出的Device窗口里选择具体芯片。左侧按厂商展开STMicroelectronics STM32F1 Series STM32F103 STM32F103C8点选后右侧会显示芯片的Flash和RAM大小确认无误再点OK。随后出现的Manage Run-Time Environment对话框里勾选CMSIS下的CORE和Device下的Startup这两个是启动链路的最低要求。标准库使用者到这里可以关闭对话框不需要勾选HAL类组件。Keil会询问是否拷贝启动文件到工程目录选择是让startup_stm32f10x_md.s出现在工程里。这一步最需要注意的是Device窗口内容为空的情况。如果Pack没有安装或者安装路径含中文器件列表就是空的新建流程根本走不下去。先到Tools Pack Installer里确认STM32F1系列DFP处于Installed状态再回来新建工程。3.2 C/C选项卡Define与Include路径新建出来的工程还只有启动文件没有库路径也没有宏定义直接编译会报一堆找不到头文件。以标准外设库V3.5版本的目录为例需要把以下Include路径加进C/C选项卡的Include Paths$PROJ_DIR$\..\Libraries\CMSIS\Include $PROJ_DIR$\..\Libraries\CMSIS\Device\ST\STM32F1xx\Include $PROJ_DIR$\..\Libraries\STM32F10x_StdPeriph_Driver\inc $PROJ_DIR$\..\User\bsp $PROJ_DIR$\..\User\app对应地Define栏填入STM32F10X_MD,USE_STDPERIPH_DRIVER第一个宏决定器件密度第二个宏决定是否把外设驱动函数带进编译。这两个宏属于不配绝对不行的一类少掉任何一个都会让stm32f10x.h里大量代码走错分支。C99 Mode和Use MicroLIB建议在这里顺手勾上MicroLIB能在后面调试串口重定向时少很多麻烦。3.3 最小工程代码main.c与板级初始化模板的第一版代码只需要覆盖两个动作点亮一个LED、往串口丢一串字符。这两件事一旦通了说明时钟、GPIO、UART、中断优先级和调试链路全部是好的。以F103C8T6和标准库为例main.c可以写成下面这样#include stm32f10x.h #include bsp_led.h #include bsp_uart.h #include stdio.h int main(void) { /* SystemInit已经在启动文件中被调用这里只需要开外设时钟 */ bsp_led_init(); bsp_uart_init(115200); printf(Template OK\r\n); while (1) { bsp_led_toggle(); delay_ms(500); } } /* 把printf重定向到串口1需要配合Keil的MicroLIB选项 */ int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); USART_SendData(USART1, (uint8_t)ch); return ch; }// bsp_led.c #include bsp_led.h void bsp_led_init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_2MHz; GPIO_Init(GPIOB, GPIO_InitStructure); } void bsp_led_toggle(void) { GPIOB-ODR ^ GPIO_Pin_0; }这段代码的核心逻辑很直接bsp_led_init里打开GPIOB时钟并配置PB0为推挽输出bsp_led_toggle翻转引脚电平fputc承担printf的底层字符发送。这里的delay_ms可以临时用简单循环等模板稳定后再换成定时器实现。需要留意的是fputc的入参类型和Keil的MicroLIB编译器强相关如果工程没有勾MicroLIBprintf的浮点打印会在链接时报r6002错误后面第五章会展开。3.4 编译下载与Flash Download设置代码写完后点击Rebuild正常情况应该看到0 Error, 0 Warning。接着配置下载器路径在Options for Target Debug右边下拉框选择ST-Link Debugger点击Settings确认SW接口能识别到目标芯片SW Device窗口里能看到一个Cortex-M3核心。然后切到Utilities选项卡选择Use Debug Driver让烧录动作和调试器配置一致。Flash Download页里要做两件事勾选Reset and Run并且确认Programming Algorithm列表里有对应Flash算法。STM32F103C8对应的是STM32F10x Med-density Flash如果这里空白或只有High-density项烧录时会提示算法不匹配。全部配好后点Load串口助手上能看到Template OK一个可编译可烧录的最小工程就算建完了。4. 把新建好的工程固化成Template并复用4.1 换壳不改内核模板重命名的规则新建好一次工程后需要把里面跟具体项目绑定的痕迹清掉保留通用配置。需要清理的有三个位置.uvprojx里的Target名称、某个例程main文件里的业务代码、以及Include Paths里如果写死过的产品目录。Target名称在Project窗口里右键Rename即可不影响uvprojx文件名main.c里的业务代码则全部剥离只留下LED和UART自检用的小段代码作为模板的验证基线。清理完成后这个目录就是一个完整的工程模板可以被反复复制。4.1.1 哪些文件不能进版本库.uvoptx文件建议排除在Git或SVN之外它记录的是本机窗口状态./Obj和./List是编译产物目录也应该加入.gitignore。保留的只有.uvprojx、.s启动文件、Libraries/、User/、以及一份README说明这个模板对应的芯片型号、DFP版本号和宏定义组合。4.2 用批处理脚本一键生成新工程纯手工复制模板再改名很容易漏项目文件我用一个Windows批处理脚本做这件事echo off set /P TARGET_NAMEInput new project name: set SOURCE_DIRD:\Templates\STM32F103_Template set TARGET_DIRD:\Projects\%TARGET_NAME% xcopy /E /I /Y %SOURCE_DIR% %TARGET_DIR% del /Q %TARGET_DIR%\*.uvoptx del /Q %TARGET_DIR%\*.uvguix.* ren %TARGET_DIR%\STM32F103_Template.uvprojx %TARGET_NAME%.uvprojx echo New project created at %TARGET_DIR% pause脚本逻辑分四步用set /P接收新工程名xcopy以镜像模式复制整个模板目录随后删除两个用户级配置文件避免把本机的窗口布局和调试断点带过去最后把uvprojx重命名为新工程名。这里的*.uvguix.*是Keil的GUI布局文件属于用户态垃圾复制时应当清理。至于uvprojx内部的Device名称和文件名是解耦的重命名工程文件不影响芯片配置。4.3 换芯片型号时模板怎么改同一个模板不会只用在一种芯片上。从F103C8T6换到F103RCT6只需要改四处Device重新选择、启动文件换成startup_stm32f10x_hd.s、Define改成STM32F10X_HD、Flash Download算法换成STM32F10x High-density Flash。这四处的对应关系是硬约束只改其中一处编出来的固件行为就不对。换到F4系列时差异更大启动文件命名规则不同、时钟树完全不同模板需要另建一个分支不建议在同一个工程里强行切换Device。芯片密度启动文件Define宏Flash算法低密度 LDstartup_stm32f10x_ld.sSTM32F10X_LDLow-density中密度 MDstartup_stm32f10x_md.sSTM32F10X_MDMed-density高密度 HDstartup_stm32f10x_hd.sSTM32F10X_HDHigh-density互联型 XLstartup_stm32f10x_xl.sSTM32F10X_XLXL-density提示用CubeMX生成工程后再转成模板是很多团队的常见做法。CubeMX负责把时钟树和引脚配置生成出来但生成的工程里包含大量临时测试代码和重复的HAL中间层上手后第一步应该是删掉App目录里默认生成的User任务把HAL_MspInit和Error_Handler的调用梳理干净再放进模板目录。直接用CubeMX输出当模板拆起来比从零搭更费劲。5. Keil新建工程报错的5个典型现场与处理方法5.1 新建不了工程、设备列表为空这个现象在keil5里很常见打开New uVision Project后Device窗口一片空白输入型号也搜不到或者新建完工程自动弹出的RTE对话框里没有任何可勾选项。根因基本是Pack没有正确安装。Pack分为设备包和MDK软件包设备包缺失直接导致器件列表为空。处理路径是Tools Pack Installer在左侧Packs标签页里找到对应厂商系列的Device Family Pack点击Install如果是离线环境用Pack Installer File Import导入从官网下载的.pack文件注意导包路径不能放在带空格的目录否则校验会通过但KEIL识别不到。5.2 编译报Target not createdTarget not created不是代码语法问题而是工程层面的构建失败导致目标文件没有生成本质上是个笼统的链接错误提示。出现这个信息后要往上翻Build Output窗口看具体是哪个文件报错常见的几类找不到头文件说明Include Paths没配全找不到startup_xxx.s说明启动文件没添加进工程还有一类是AC6和AC5编译器切换后老的编译中间文件残留导致路径错乱执行Project Clean Target后重新Build。如果连工具链都找不到去Manage Project Items里确认使用的是AC5还是AC6并把对应的编译器路径检查一遍。5.3 提示缺少.axf链接阶段已经失败axf是ARM的可执行文件烧录和调试都依赖它。Keil报No such file or directory: xxx.axf时不要以为是文件被删了要去看Build Output窗口里有没有更早的链接错误。最常见的两个原因工程里加了某个源文件但该源文件编译不通过链接直接中止或者工程使用了分隔的List/Obj目录目录不存在链接器写不出中间文件。排错时先Rebuild一次打开Output选项卡的Linker Listing看Map文件是否部分生成。一个容易忽视的点是工程路径里的中文和空格Windows命令行工具会对空格做转义路径尽量保持纯ASCII。5.4 Pack版本与设备不匹配从别人那里拷来的模板在自己机器上打开Keil显示Device不兼容或者打开后芯片型号变成Unknown Device这是DPack版本不一致造成的。uvprojx里记录的是创建时使用的芯片型号字符串如果新机器只装了更新版本的DFP型号字符串匹配逻辑就可能变化。此时不要硬编译回到Device窗口重新选择当前机器上存在的同型号芯片然后检查启动文件是否跟随变化。如果模板在F103C8和F103RB之间切换过uvprojx里的Flash大小信息会滞后手动重新选择一次可以解决大部分问题。5.5 Debugger常见错误no Ulink Device Found与r6002no uLink device found这类提示字面上是找不到ULink设备实际上只要是调试器相关错误Keil都会带这句话ST-Link/J-Link也会出现。检查顺序是Debug选项卡里选的是不是ST-Link Debugger、Settings里USB是否识别到设备、SW模式下SW Device窗口有没有扫描到Cortex核心。USB线和接口接触问题是最容易忽略的。还有error r6002这是ARMCC的老报错意思是浮点格式没有链接常见于printf使用%f却没有开MicroLIB。解决办法是在Options for Target Target里勾选Use MicroLIB或者在打印逻辑里避免直接输出浮点把浮点拆成整数和小数部分分两次打印。调模板阶段建议统一用%d和字符串先把链路跑通再处理浮点。6. 模板自检技巧用空模板提前半小时排查Keil环境模板就位后不要急着往里堆业务先做一个自检专用Target专门验证四件事编译器能完整跑完启动代码、链接脚本生成的向量表地址正确、调试器和Flash算法匹配、printf重定向可用。这个Target里不写任何外设逻辑只读芯片UID并打印#include stm32f10x.h #include stdio.h #define UID_BASE 0x1FFFF7E8 int main(void) { volatile uint32_t *uid (volatile uint32_t *)UID_BASE; uint8_t i; for (i 0; i 8; i) { USART_SendData(USART1, 0123456789ABCDEF[(uid[i / 4] (8 * (i % 4))) 0x0F]); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); } while (1); }这段代码通过在固定地址读取96位UID并用串口以十六进制形式往外发绕开了printf对浮点库的依赖。它能跑通的潜台词是启动文件的向量表被正确加载GPIO和UART的时钟配置正常外部晶振起振调试器能把程序拉到main。如果时钟没有起来这一串字符的一个字节都不会出现如果Flash Algorithm选错Keil会在烧录时就停下来根本走不到运行这一步。然后看一个关键地址来验证链接脚本在Debug模式下全速运行后暂停打开View Memory窗口输入0x00000000前四个字节能看到__initial_sp的值紧接着的四个字节是Reset_Handler的地址。这两个值指向启动文件在原始地址空间的映射确认它们落在RAM和Flash合法区间说明模板的链接配置没有问题。配合Keil调试器里的Step Out可以一次性跨过SystemInit这类初始化长函数直接回到main效率比一行行单步快得多也符合很多团队在Discussion区提到的keil debug step out用法。这台自检Target保持独立的好处是后续任何一次换电脑、升级MDK版本、更换DPack都先把它烧进去跑一遍。假如UID能正常读回且串口有稳定输出说明环境链路全部健康此时再往模板上挂自己的产品代码都不会被环境问题干扰如果读出来是全0或全F就回到第5章那几类场景里逐条核对而不是在业务代码里反复试错。本文还有配套的精品资源点击获取