ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32入门必看:用Keil5.23从零创建工程并编译通过的完整指南

STM32入门必看:用Keil5.23从零创建工程并编译通过的完整指南 刚接触STM32那会儿我被一个问题卡了整整两天明明照教程建好了工程一编译就报错不是找不到器件就是提示缺个什么启动文件。后来才明白问题多半出在“固件包”和编译配置上而不是代码本身。这篇就专门聊聊如何用Keil5.23完整地创建一个STM32工程并顺利编译通过全程按自学的节奏来该解释的原理我会尽量说透该避的坑我也会提前标出来。我默认你手上已经装好了MDK5.23这个版本最大的特点就是自带了ARMCCAC5编译器和离线器件支持包的安装入口相比后面那些动不动就换编译器内核的版本5.23对新手反而更友好。文章会带着你把工程从零搭到编译通过哪怕你现在还分不清HAL库和标准库只要跟着做、跟着想也能在半小时内拿到一个能生成hex文件的工程外壳后面的学习都基于这个外壳继续展开。建议你准备一块常见的STM32核心板比如STM32F103C8T6或者F407VET6没有板子也没关系纯编译不烧录也完全能跑通流程。1. 准备工作先搞清楚Keil5.23、固件包和IDE的关系1.1 这里说的“固件包自带版本”到底指什么很多新手容易把“固件包”理解成某个固件库的源码压缩包比如STM32F10x_StdPeriph_Lib或者HAL库。但实际上在Keil MDK5.x的体系里“固件包”通常指芯片支持包Device Family Pack简称DFP它的英文全称是Keil.STM32F1xx_DFP.x.x.x.pack这类文件里面封装了芯片的SVD描述文件、Flash烧写算法、启动文件模板、芯片头文件等核心内容。MDK5.23安装完成后如果你是在官网下载的完整安装包那么Pack Installer这个工具是自带的它能直接在线下载和安装STM32系列芯片包。标题里说的“自带版本”我理解是安装包本身已经内置了较老版本的芯片包或者我们本地已经手动导入过pack文件。这里要特别注意Keil5.23安装完并不等于STM32芯片包也装完了如果你打开Device选择器发现器件列表是空的就需要单独安装对应的pack文件。1.2 为什么不推荐越新版越好的软件心态Keil版本有5.23、5.36、5.38甚至MDK6。对自学的人来说我反而建议用一个相对稳定、教程覆盖广的版本5.23就是很好的起点。原因有三个第一它默认使用ARMCC v5编译器网上绝大多数教程都是在AC5环境下写的语法、选项、报错信息都能直接对上第二它占用的内存小启动快在低配电脑上也不会太卡第三它的配置界面足够传统很多选项都摆在明面上更适合理解工程背后发生了什么。如果你一开始就装最新的MDK编译器可能会换成AC6或者默认调到AC6模式那么一些老教程里的代码和编译选项就可能对不上这对新手来说是非常痛苦的。当然如果以后你工作中必须用高版本再迁移也不迟核心知识点是通的。1.3 安装和验证环境的小细节安装路径我建议放在一个纯英文、没有空格的目录下比如D:\Keil_v5不要放在中文路径里不然编译时某些插件可能报错尤其是一些路径解析工具。安装过程一路Next就行不需要特别设置但到了安装Pack那一步建议先跳过等IDE装好以后再通过Pack Installer安装这样更可控。装完之后打开软件依次点击Project-Manage-Pack Installer在Devices选项卡的搜索框里输入STM32F103如果能看到设备列表并且旁边有Install按钮说明pack网络安装可以用。如果你手头有离线pack文件比如Keil.STM32F1xx_DFP.2.2.0.pack双击就能自动导入。确认芯片包已安装后整个准备工作就算完成了。2. 手动创建STM32工程从建文件夹到编译通过的完整步骤2.1 工程目录架构一个好结构能让你少走很多弯路很多教程会直接让大家新建一个工作区把所有源码丢进去这样在一两个文件时还好但学到后面项目变复杂就会乱成一锅粥。我建议从第一天就保持良好的目录习惯。以一个标准库项目为例工程根目录下通常有这么几类东西User存放用户自己的代码比如main.c、stm32f10x_it.c、中断服务函数Core存放启动文件startup_stm32f10x_hd.s以及系统时钟文件system_stm32f10x.cPeriph存放标准库外设驱动文件比如stm32f10x_gpio.c、stm32f10x_rcc.cObj存放编译生成的目标文件、hex文件、map文件。你在Keil里建的工程可以和文件夹结构保持对应关系也可以只在逻辑上分组物理上所有文件平铺。新手我建议先在磁盘上建好文件夹然后创建空的Keil工程再向工程里分组添加文件这样Keil工程列表和磁盘目录都能一目了然排查问题也方便。2.2 新建工程与Device选择的两个关键误操作打开Keil点击Project-New uVision Project选择你的工程目录输入工程名比如LED_Demo点击保存后会弹出Device选择窗口这里要展开STMicroelectronics选择你实际的芯片型号。比如STM32F103C8T6就在STM32F1系列下面找如果是STM32F407VET6就要去STM32F4系列找。懒人做法是直接在搜索框输入芯片型号关键词比如输入C8T6再点选。Device选择这一步必须小心常见的两种误操作是选错芯片家族导致外设寄存器地址不匹配或者选了带后缀的型号导致启动文件不对。启动文件与芯片型号严格对应比如startup_stm32f10x_hd.s对应高密度芯片startup_stm32f10x_md.s对应中等密度芯片C8T6属于中等密度应该选md版本。选错的话虽然编译可能通过但下载后程序大概率跑不起来或者中断向量错乱这是新手的经典踩坑点。2.3 运行时环境配置RTE与启动文件、标准库的取舍选好芯片后会弹出一个Manage Run-Time Environment对话框这是MDK5对旧版库管理方式的一种升级。在这个窗口里你可以勾选CMSIS的CORE组件也可以勾选Device选项下的Startup组件这两个是必须的。勾选CORE会加入核心头文件和系统初始化文件勾选Startup会加入启动文件。如果你不勾选Startup后面就要自己手工添加.s文件对新手来说很容易漏。接下来是库的选择问题。你可以在RTE窗口里勾选某个外设驱动也可以直接使用标准外设库但MDK5.23的RTE默认并不直接集成STM32F1标准库。更常见的做法是在RTE里只留CMSIS和Startup然后在工程文件组里手动添加标准库的源码文件。如果你用的是STM32CubeMX生成的HAL库工程那么在RTE里只需要勾选CMSISHAL库文件会被CubeMX直接放到指定目录Keil工程里引用即可。2.4 工程配置关键参数Output、C/C、Debug里的面板学问工程创建完成后先别急着写代码建议花五分钟把编译输出选项配置好。点击魔法棒图标打开Options for Target在Output选项卡里勾选Create HEX File这样才能生成我们烧录用的hex文件。如果项目里存在多个目标目标还要注意你选中的是有效目标。在C/C选项卡的Define输入框里STM32F103系列需要定义一个STM32F10X_MD类似的F407系列要定义STM32F40_41xxx这个宏决定了标准库中你到底编译哪些外设的代码。另外把优化等级设置为-O0或-O1保持默认也行但学调试阶段建议用O0变量观察更直观。下面的Include Paths里面要添加头文件目录比如User目录和标准库的CMSIS包含目录否则头文件会找不到。在Debug选项卡里如果你用ST-Link选择ST-Link Debugger在右侧点Settings确认SW Device能识别到芯片如果你用J-Link或DAP对应选择即可。Utilities选项卡一般保持默认如果下载失败检查这个选项卡里是否勾选了Use Debug Driver以及Flash Download里的算法列表是否有当前芯片的Flash算法。这些配置看着繁琐但都是一次性的配好以后整个工程外壳就牢固了。2.5 第一次编译看明白Build Output窗口里的关键行配置完成后点击Build按钮或者快捷键F7底部会打开Build Output窗口。正常的编译过程会依次编译启动文件、系统时钟文件、库文件、用户代码最后出现一行警告数量统计比如0 Error(s), 0 Warning(s)紧接着会显示生成的目标文件路径和hex文件路径。出现这个东西你的工程就算基本通了。我最想提醒的是不要把编译输出窗口只当来看结果的地方。编译过程中如果卡在某个文件上错误消息中的第一个错误通常才是根因后面的错误很可能是连锁反应。比如头文件路径不对会一连报几十个找不到类型定义的错误这时不要逐个看回到最上面的错误去修复路径所有后续错误会一起消失。3. 编译链路与工程文件解读知其然也知其所以然3.1 从main.c到hex文件编译过程到底发生了什么很多新手写代码会把编译看成“一键生成”但实际上编译过程包含预处理、编译、汇编、链接四个阶段理解它们对排查错误有很大帮助。预处理编译器读入源文件处理#include、#define把头文件内容展开到源文件中。这个阶段如果头文件路径不对就会报file not found。编译把预处理后的C代码翻译成汇编代码并检查语法和语义错误。这个阶段会产生很多关于未定义类型、函数未声明之类的报错。汇编把汇编代码翻译成机器指令生成目标文件.o。如果启动文件选的芯片型号不对汇编阶段就可能报指令不匹配。链接把所有目标文件和库文件组合分配地址生成最终的可执行文件。这个阶段的报错通常和“符号未定义”或“内存溢出”有关。在Keil里每个源文件左侧的“”号可以展开你能看到这个文件编译时产生的所有目标文件路径。双击某个源文件再点击编译可以单独编译该文件这种粒度有助于快速定位语法错误。3.2 启动文件和分散加载文件的角色启动文件startup_stm32f10x_hd.s里放着中断向量表和复位处理函数它决定了芯片上电后第一段代码去执行什么。简单类比它就像是电脑开机时的BIOS先把环境准备好再跳转进入main函数。如果你发现程序编译过、下载不跑首先怀疑启动文件是否添加、是否匹配芯片型号。链接阶段还需要一个分散加载描述文件.sct它告诉链接器哪段代码放Flash哪段数据放RAM栈和堆分配多大等。在Keil5.23里如果你不手动指定使用ARMCC编译器时链接器会根据IRAM1和IROM1的范围自动生成。如果你去改了Target选项卡里IROM1的地址比如给Bootloader腾位置就必须勾选Use Memory Layout from Target Dialog否则位置信息又乱了。3.3 一个能点亮LED的最小main.c示例等你把工程骨架搭好以后为了确认编译器各组件都正常我习惯直接放一个最小点灯程序进去。以STM32F103C8T6为例用寄存器方式操作PD2端口如果开发板LED接的是PC13就改成GPIOC代码可以很短#include stm32f10x.h void delay(void) { unsigned int i; for (i 0; i 500000; i); } int main(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_13; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOC, GPIO_InitStructure); while (1) { GPIO_SetBits(GPIOC, GPIO_Pin_13); delay(); GPIO_ResetBits(GPIOC, GPIO_Pin_13); delay(); } }这个程序就做了三件事打开GPIOC端口时钟把PC13配置为推挽输出然后循环让它高低电平交替。注意这里用到了标准库函数所以工程里必须添加stm32f10x_gpio.c和stm32f10x_rcc.c这两个文件并且定义了STM32F10X_MD。如果你用的是HAL库代码写法略有不同但工程配置的思路一致。把这段代码录入后编译如果只出现0 Error说明从芯片包到启动文件再到头文件路径整条链都是通的。细节上我想多说一句标准库函数里很多外设结构的命名比较长新手很容易敲错比如把GPIO_InitTypeDef敲成GPIO_InitTypeDefe这种错误编译一报错就能看出来。真正麻烦的是寄存器位的名字写错比如GPIO_Pin_13写成了GPIO_Pin_3这种编译不会报错但程序行为不对。遇到这种情况只能回到数据手册查位定义或者用调试器观察寄存器值。这也是为什么我建议新手从点灯开始因为行为最直观出了问题容易排查。4. 常见问题速查与避坑实录4.1 典型编译报错与核心排查方法我在帮人处理Keil5.23创建工程的问题时总结了几个出现频率最高的错误这里做成速查表省得你到处翻论坛。报错信息主要原因排查方向cannot open source input file stm32f10x.h头文件路径未添加或文件名错误检查Include Paths确认标准库核心头文件路径Error: L6218E: Undefined symbol某个函数有声明但没定义确认对应外设库.c文件已添加或者启动文件缺失target not created上一个错误太严重后续步骤无法继续先解决首个错误再重新编译no such file or directory文件路径中有中文/空格或者引用路径不对调整路径尽量用相对路径不要用长绝对路径startup_stm32f10x_hd.s: error: A1167E启动文件与芯片型号不匹配选对启动文件C8T6用mdZE用hd这个排查表看着简单但我希望你注意一个原则不要试图一次性解决所有报错。遇到几十行红色输出只处理第一条编译一次再处理下一条。因为很多报错都是第一次错误引发的“雪崩”真正的问题通常只有一个。还有个很容易被忽略的问题是编码。如果你的main.c是从网页或者某些编辑器里复制过来的代码里可能带有中文注释而Keil5.23默认编码是ANSIGBK如果源码文件是UTF-8编码中文注释会显示成乱码严重时会影响编译对字符串常量的处理。建议代码文件统一用UTF-8或者在Keil的Edit-Configuration里调整编码设置让源码保持一致。4.2 搜索不到芯片型号或下载失败的处理顺序在线安装Pack时有时会因为源服务器不稳定导致芯片列表刷不出来这时候不要反复点Refresh选择离线pack文件导入更稳妥。导入成功后在Device选择窗口里再找一次STM32F103应该就能看到了。如果依然找不到检查Pack Installer界面底部有没有红色错误提示通常是pack版本冲突或者权限问题右键以管理员身份运行Keil能解决大部分权限类问题。下载失败比编译失败更让人头疼。先确认三件事硬件连接中SWDIO、SWCLK、GND三根线是否接对Debug选项里是否选择了正确的调试器Utilities里的Flash Download算法是否匹配当前芯片型号。这三项都正确后再检查板子是否是3.3V供电有些核心板需要先把BOOT0拉低才能正常下载。说个我自己的经历一开始用ST-Link总是报Cannot access target后来发现是杜邦线太长导致信号质量差换成短一点的线立马就好了。硬件调试就是这样步骤都对但细节会出卖你。4.3 Keil5编译速度慢和缓存问题的几个调整很多人遇到过编译一次很慢的困扰这个和Keil5.23的版本特点有关。有一个容易被忽略的选项是Project-Options for Target-C/C里的One ELF Section per Function勾选后每个函数独立成段链接时可以丢弃未用函数不仅能减小固件体积还能让增量编译时只重新编译改动过的部分速度会明显提升。另外把Output选项卡里的Browse Information勾选去掉编译时不需要生成浏览信息文件速度也能快一点。这个功能用于右键跳转到定义如果你习惯了代码阅读可以保留但每次全量编译都会生成一个很大的.brows文件拖慢速度。增量编译模式下二次编译通常只要几秒到十几秒如果还是几十秒多半是杀毒软件在实时扫描工程目录把工程目录加入白名单能立竿见影。4.4 我建议的后续学习路径和几个自学资源方向当你成功把工程编译通过下一步不是急着去写复杂的外设代码而是建议做三件事。第一把工程里每个文件打开看一遍尤其是system_stm32f10x.c里的时钟配置函数看看SystemInit函数如何把芯片时钟从默认的HSI切换到外部晶振PLL这是理解整个STM32运行机制的关键。第二用调试器单步执行一下点灯程序在Watch窗口添加GPIO相关寄存器观察引脚数据寄存器在切换电平前后的变化这种直观观察比看十遍教程都管用。第三备份你现在这个干净的工程模板以后做任何新项目都从模板复制而不是每次重新创建一个工程。学习资源这块我不建议囤积几十G的视频教程真正用起来的时候还是得靠数据手册和勘误手册。STM32F10xx数据手册里的GPIO章节、时钟树章节值得反复看再就是官方标准库里的所有外设示例比如ADC、定时器、串口等每个示例工程就是一套完整的模板读懂了再动手改远比对着视频敲代码有效。最后再分享一个真实经验我见过不少朋友卡在工程创建这一步很久其实不是他们动手能力差而是总想着“一次弄明白所有配置再开始写代码”。正确的做法恰恰是先照抄一个能编译的工程然后想尽办法去“搞坏”它看它报什么错再修复它。来回折腾两三次你对Keil的工程结构、启动流程、编译参数的理解会比看十篇文章都深。工程能编译通过只是自学的第一步后面真正拉开差距的是你有没有持续折腾、持续追问到底的耐心。
返回列表