
为什么你现在还是绕不开STM32F103C8T6很多人在2024年问我同一个问题都什么年代了Cortex-M4/M7甚至RISC-V满天飞怎么还要学STM32F103说句实话你打开任意一个电商平台搜“STM32F103C8T6最小系统板”销量依然能吓你一跳。这颗芯片64KB Flash、20KB SRAM、72MHz主频放在今天确实不算亮眼但它在入门学习、课设竞赛、智能家居小项目这些场景里依然是那个最不容易出错的默认选项。C8T6的核心竞争力就是便宜且生态成熟。一片散装芯片几块钱一个带USB转串口的最小系统板十来块配一个DAPLink下载器也就二十出头整套学习成本压到几十块。对比动不动上百的开发板这个门槛对新手极其友好。更关键的是资源。F103系列有标准外设库、HAL库、寄存器例程三套玩法网上教程密度高到吓人。你在B站搜“STM32F103C8T6”各路教程视频播放量都是几百万级别。遇到任何报错几乎都能在搜索框里找到一份现成解决方案。对新手来说学习的最大成本不是钱而是卡住后的时间成熟的生态恰好帮你省下了这笔时间。硬件准备也很简单以下东西足够你完成从零到能点灯、能调试的完整流程STM32F103C8T6最小系统板一块蓝色pill小板DAPLink下载器一个也有人叫CMSIS-DAP面包板加一捆杜邦线若干LED、220Ω左右电阻、按键USB转TTL模块CH340芯片做串口通信补充一句关于国产替代的题外话。现在你买到的板子芯片丝印可能不是STM而是GD32F103C8T6或APM32F103C8T6完全不用慌引脚和寄存器基本兼容工程可以直接用。唯一需要注意的是部分国产芯片Flash分页大小和原厂不太一样下载后偶尔出现校验不过的问题后面烧录章节我会专门提这个坑。建工程先搭目录90%的人跳过了这一步先问一个扎心的问题你上一次建Keil工程是不是新建项目后把main.c往里一扔其他文件全堆在同一个目录下我一开始也这样程序能跑但等到要加串口、加LED、加按键、加PWM的时候Source Files下面一片混乱找个文件要翻半天。更要命的是哪天想复用某个外设驱动根本不知道哪些文件是被依赖的。搭工程跟盖房子一个道理先画好图纸再动工后面能省一大半修补的功夫。对F103C8T6标准库工程我推荐这种目录结构ProjectRoot/ ├── Doc/ ├── User/ │ ├── main.c │ ├── stm32f10x_it.c │ ├── stm32f10x_it.h │ └── stm32f10x_conf.h ├── Periph/ │ ├── inc/ │ └── src/ ├── Core/ │ ├── startup_stm32f10x_hd.s │ ├── core_cm3.c │ ├── core_cm3.h │ ├── system_stm32f10x.c │ └── system_stm32f10x.h ├── BSP/ │ ├── bsp_led.c │ ├── bsp_led.h │ ├── bsp_key.c │ └── bsp_key.h ├── Project/ └── Output/各目录的职责如下目录放什么为什么这么分Usermain.c、中断服务函数、配置文件主逻辑和底层驱动分离后续好维护Periph标准外设库源码inc放头文件、src放源文件芯片底层驱动一般不需要改动Core启动文件、内核相关文件、系统时钟文件对应CMSIS层跟具体板子无关BSP自己写的板级驱动LED、按键、串口等换板子只改这里核心代码不动ProjectKeil工程文件.uvprojx工程文件和源码分开避免误删Output编译中间产物、hex文件保持源码目录干净Doc数据手册、原理图、设计文档养成留文档的习惯为什么单独拆一个BSP标准外设库解决的是“芯片本身”的操作比如GPIO寄存器读写BSP解决的是“板子上具体外设”的驱动比如把某个引脚初始化为LED控制、把串口封装成printf输出。这两个概念分开之后你换一块板子只需要改BSP核心逻辑完全不用动。关于库选型多说一句。现在F1工程主要有三条路线标准外设库、HAL库加CubeMX、寄存器裸写。既然标题是“从文件夹结构到编译调试”我会用标准库做主线讲解因为它逻辑直白、不需要额外工具生成代码在Keil5里手动加文件的过程非常容易搞懂。如果你以后想用HAL库顶层目录思路同样适用只需要把Periph换成CubeMX生成的Drivers目录即可。Keil5安装与器件包准备最劝退但最不必要的一步很多新手上来就卡在“Keil5里找不到STM32”或者“没有STM32库怎么办”。原因其实很简单从MDK 5.x开始芯片支持不再内置在软件里而是通过安装器件包来提供。你下载的Keil默认只带通用调试框架要开发STM32必须先装STM32F1系列的DFP包。安装时有几个点值得单独注意。第一MDK和C51的关系。如果你之前装过Keil C51用来开发51单片机再装MDK时不要把两者装到同一目录更不要共用同一个TOOLS.INI配置。最稳妥的做法是完全不同的安装路径。装完之后打开KeilARM工程会自动调用MDK编译器51工程调用C51编译器两套工具可以共存。安装顺序没有严格要求但一定不要装到同一路径。第二器件包建议离线安装。在线联网下载DFP有时候会比较慢中途还可能断掉。更可靠的方式是去Keil官网的Device Family Pack页面下载Keil.STM32F1xx_DFP.2.x.x.pack文件注意选择稳定的老版本比如2.3.0左右。下载完双击运行它会自动关联到已安装的MDK并导入。装好后新建工程时Device列表里就能看到STMicroelectronics - STM32F1系列。第三下载器驱动不用过度焦虑。现在最常用的DAPLink在Windows下通常免驱插上就是一个HID设备Keil的Debug下拉框里选CMSIS-DAP Debugger就能识别。ST-Link需要单独装驱动J-Link需要装SEGGER的驱动包。如果你用的是串口ISP下载那只需要CH340驱动。还有一个被反复问到的细节Target选项卡里XTAL变灰改不了是怎么回事这在MDK 5.x高版本里挺常见。当你选中具体芯片后芯片工作参数会由器件包里的PDSC文件自动带出界面上就不再允许随便改Xtal了。但晶振频率实际上不在这里设置真正的系统时钟是在system_stm32f10x.c中通过PLL参数配置的。外部8MHz晶振对应的HSE_VALUE宏定义默认就是8000000一般不需要动。如果你板子上用的其他频率晶振改system_stm32f10x.c里的PLL_M等参数即可。关于软件授权这里我不展开说也不建议去下载来路不明的注册工具。正规做法是去ARM官网注册账号申请MDK评估授权或者使用教育版授权。别在工具授权上省事否则后面工程出问题你分不清到底是环境问题还是代码问题。手把手新建一个能编译能下载的工程环境准备好之后照着目录结构一步步来。我假设你已经把标准外设库压缩包解压到某个目录或者从项目模板里找到了这些源码文件。第一步新建工程并选芯片。打开KeilProject - New uVision Project工程路径选到刚才说的Project目录工程名就叫Demo。接下来弹出的Device选择窗口按路径展开选择STM32F103C8。MDK里的命名是STM32F103C8Type显示为Medium-density performance line。选完芯片后MDK会问是否复制启动文件到工程文件夹。这里建议选“否”然后自己从库或模板里复制启动文件。原因很简单MDK自动添加的启动文件版本不一定和你的标准库匹配自己控制文件来源后续排查问题思路更清晰。第二步给工程分组。默认只有一个Target 1和Source Group 1。右键Target 1可以重命名比如改成STM32F103C8T6_Demo。右键Source Group 1选择Add New Group按规划建立以下分组UserCorePeriph_SrcPeriph_IncBSP分好组后往里面添加文件。这是最容易出错的地方每个分组的文件我列清楚User组main.c、stm32f10x_it.c、stm32f10x_conf.hCore组startup_stm32f10x_hd.s、core_cm3.c、system_stm32f10x.cPeriph_Src组stm32f10x_gpio.c、stm32f10x_rcc.c、stm32f10x_usart.c、misc.c用到什么加什么别一股脑全加BSP组bsp_led.c、bsp_key.c关于启动文件多说一句。C8T6属于中容量产品按常规应该用startup_stm32f10x_md.s但很多教程模板用的却是startup_stm32f10x_hd.s也没出问题。原因在于启动文件的核心作用是建立中断向量表、初始化栈和堆C8T6用md版是标准选择用hd版大多也能跑但严格来讲应该用md。这个细节初学者不用太较真关键是启动文件不能漏。第三步配置魔术棒。双击Target 1左侧的工程名或者点菜单栏魔术棒图标打开Options for Target。这里的配置直接决定你能不能生成烧录文件、能不能连上调试器。我当时第一次建工程最大的失误就是没勾Output标签页下的Create HEX File导致生成的文件夹里只有axf文件烧录工具根本找不到hex。这一步请务必勾上。然后是C/C标签页两个关键位置Define栏填STM32F10X_MD, USE_STDPERIPH_DRIVER注意用英文逗号分隔不要有空格Include Paths栏添加所有头文件目录点右侧三个点按钮逐个添加Core、Periph/inc、User、BSP这些包含.h文件的目录这两个宏的作用是什么USE_STDPERIPH_DRIVER告诉编译器你要使用标准外设库STM32F10X_MD告诉它目标芯片是中容量。stm32f10x.h里面就是靠这两个宏来决定要不要包含外设库头文件、以及选哪份Flash容量定义。Target标签页里如果是MDK 5高版本主要看Use MicroLIB是否勾选。勾选MicroLIB能让printf的重定向代码和整个代码体积变小但浮点格式化会不完整。学习阶段建议直接勾上简单省空间。以后做printf浮点数输出遇到问题再取消勾选并检查重定向函数。接下来Debug标签页在右边的Use下拉框选CMSIS-DAP Debugger或ST-Link Debugger然后点旁边的Settings。正常情况下SW Device区域能看到芯片IDCODE说明下载器已经连上了。Utilities标签页同样勾选Use Debug Driver点Settings后在Flash Download选项页勾选Erase Sectors、Program和Verify最关键的是勾选Reset and Run。这样程序下载完芯片会自动复位运行不用再手动按复位键。全部配置完先编译一下。如果没有意外Output窗口会显示0 Error和0 Warning同时Project目录下会多出Objects或List文件夹里面有Demo.hex文件。烧录调试阶段的翻车现场常见报错与排查链路编译通过只是第一步。真正折磨人的是下载和调试阶段这几个问题新手问得最多。5.1 cannot access memory出现位置进入仿真模式后左下角Command窗口里刷出cannot access memory。我第一次遇到这个报错时第一反应是代码写崩了查了半天发现是启动文件没加程序跑飞后PC指针指到了非法地址调试器去读内存就报这个错。排查链路建议这样走检查工程里是否遗漏了startup_stm32f10x_hd.s。启动文件最容易被漏漏掉后程序没有正常入口手动添加后重新编译下载试试。检查下载器接线。SWDIO接PA13SWCLK接PA14同时GND必须共地。很多DAPLink是3.3V电平如果杜邦线太长或接触不良首次连接会比较困难此时换个短线或重新插拔。如果程序里已经开启了低功耗模式或者把SWD引脚复用掉了仿真器同样连不上。遇到这种情况按住板子复位键不放点下载在弹出的进度开始时松手用这个时序差把程序擦掉。5.2 烧录失败或下载超时最常见的原因有两个一是没勾选Create HEX File就等着烧录工具去读hex文件根本不存在二是Debugger没选对Keil里默认可能是ULINK而你的下载器是CMSIS-DAP。还有一类情况是芯片被读保护锁住了体现在部分国产替代芯片上。有些GD32芯片默认读保护级别为1第一次能下载第二次就连接不上。解决办法是在Flash Download设置里把Program Limit区域的起始地址和大小都清零先执行整片擦除再重新下载。不是所有芯片都有这个问题遇到“连接不上、换了下载器也一样”的情况就往这个方向排查。5.3 下载成功但程序跑不起来如果你看到烧录进度条满格但板子上LED纹丝不动先别碰代码。我犯过最傻的错误就是BOOT0跳线帽插在了1上芯片一直在系统存储器模式里打转用户Flash里的程序当然不执行。正常运行时BOOT0应该接0。软件层面还有一个隐蔽的坑Utilities里没勾Reset and Run。程序烧进去了但调试器没发送复位命令芯片还停在烧录后的暂停状态。把它勾上下次下载完就能看到程序自动跑。5.4 Keil左侧目录消失了新版本MDK打开工程后左侧默认显示Project标签页。如果手滑关掉了按左下角的Project、Books、Functions、Templates标签页就能切换回来。如果整个窗口都没了点击菜单栏View - Project Window或者用快捷键AltP调出工程窗口。不是文件丢了只是窗口被隐藏了而已。5.5 编译报错找不到头文件fatal error: stm32f10x_gpio.h: No such file or directory这类报错基本就是Include Paths漏了加目录或者路径写错了。我建议把所有头文件目录都放在C/C标签页的Include Paths里而不是在源码里写一大串#include ../Core/stm32f10x.h相对路径后者一旦移动文件位置就全废了。调试阶段的高效套路从点灯到printf再到逻辑分析仪会编译会下载后调试才是日常大头。分享几个实际用下来很顺手的调试方式。最快最直接的是跑通串口printf。配置好USART1把fputc重定向到串口在main里写printf(temp%d\r\n, temp);电脑端开个串口助手就能看数据。这个方法在排查传感器读数、状态机跳转时极其管用。几个细节值得注意重定向时要加#include stdio.h检查GPIO复用是不是USART1的TX/RX引脚如果用串口2或串口3记得改RCC使能的外设时钟和对应AFIO配置。第二个好用的是Keil自带的逻辑分析仪。进入Debug模式后View菜单打开Analysis Windows - Logic Analyzer把想观察的引脚拖进窗口配置成Bit模式就能看到这个引脚的实时电平波形。它虽然不如真实逻辑分析仪能抓高速时序但对GPIO翻转、PWM占空比调节这类低速场景完全够用不需要额外硬件学习阶段性价比很高。第三个套路是善用断点。在函数入口、条件判断、中断服务函数里打上断点全速运行到断点后用Watch窗口看变量数值比盲目加printf去猜逻辑高效得多。Symbol窗口还能实时搜到当前工程中的全局变量和函数双击跳转到定义位置非常适合啃不熟悉的代码。我个人调试的小习惯是每加一个功能模块就单独编译一次点一下LED或串口打印一个标记确认硬件基础没问题再继续往下写。很多人喜欢一口气写完所有代码再一次性烧录出问题后根本不知道是哪一个环节引起的排查成本反而更高。给新手的几个实际建议和一个可复用的工程习惯最后这部分不说原理只说踩过几轮坑之后沉淀下来的习惯可以直接抄。建工程时一定先把输出目录和中间文件目录独立出去。我见过太多人编译完工程根目录下躺着几十个.o和.d文件找main.c都得用搜索。在Options - Output和Listing标签页里把Object文件和Listing文件分别指定到Output目录下的OBJ和LST子目录配合前面推荐的顶层文件夹结构整个工程会清爽很多。文件命名建议统一小写下划线风格比如bsp_led.c、bsp_led.h头文件用宏定义守卫避免重复包含。每个BSP模块都带一个Init函数和基础操作函数比如BSP_LED_Init、BSP_LED_Toggle这样main函数里只需要关注调用顺序和业务逻辑不用管底层寄存器细节。版本管理这件事越早建立越好。哪怕只是用Git在本地做提交每次跑通一个功能就commit一次改坏了也能回滚。一个教训是不要只保留一个“最终版”我恢复过太多次所谓的最终版发现里面缺了一整段功能代码。正确的做法是每个功能节点都留一个可编译、可烧录、可运行的快照。还有一点尽量用DAPLink而不是老式串口ISP下载。DAPLink支持在线调试、支持Keil直接下载不用反复按BOOT0进下载模式省掉的时间累积起来非常可观。插上USB线、Debug选CMSIS-DAP即可如果识别不到优先换数据线——有些MicroUSB线只能充电不能传数据这个问题遇到的人不在少数。