
开发板到手这事我太有同感了。STMicroelectronics的STM32F103系列尤其是C8T6这种经典芯片几乎是我见过新手入门单片机时最绕不开的一个选择。标题说买回来了接下来最大的问题往往不是怎么点亮LED而是我到底该从哪里开始这一大堆文件、软件、连线到底怎么配合。这篇内容就是站在一个已经折腾过不少板子的老手的角度把从拆箱到跑通第一个程序、再到做几个有含金量的小项目整条路上的关键节点和容易翻车的地方一次性捋清楚。网上STM32的教程多到看不完但问题恰恰是太散。今天看一个点灯教程明天看一个串口教程后天又去碰一个传感器模块到最后连GPIO的时钟来自哪条总线都没搞明白。这篇文章不想做成那种复制粘贴就能跑的代码仓库而是想帮你把买回来的开发板变成真正属于你自己的开发工具知道每一步为什么要这么做以及出问题时从哪里下手查。1. 为什么到了2025年F103依然适合当第一块入门板先说个可能跟你想的不一样的结论STM32F103虽然不是最新、最快、外设最炫的芯片但它仍然是目前新手友好度最高的选择之一。原因不是性能而是生态。F103的C8T6这颗芯片72MHz的主频64KB Flash20KB RAM单看参数连如今不少蓝牙MCU都比不过。但它有两样东西是其他芯片比不了的海量的中文资料和几乎能覆盖所有入门外设的参考设计。你想学USART、I2C、SPI、定时器输入捕获、PWM输出、ADC采样、USB从机这颗芯片全部具备而且每一类外设你都能在网上找到至少三种不同写法的例程。还有个很现实的因素是价格。一块 minimalist 的核心板或者带各种外设的入门板价格很低折腾坏了也不心疼。我第一次用F103学习的时候就是因为不小心把3.3V接到了5V引脚上烧掉了一块板子但换新的成本完全在承受范围内。这种敢折腾的底气对于新手建立信心特别重要。再者说F103用的Cortex-M3内核是所有Cortex-M系列里最基础、最容易理解的一种。你在这颗芯片上学到的NVIC中断管理、SysTick延时、启动文件、链接脚本这些概念之后无论是换到STM32F4、H7还是换到GD32、AT32这些国产替代芯片绝大部分知识都是直接平移的。内核和寄存器操作的底层逻辑基本一致变的最多是时钟树和外设的增强功能。F103的另一个常见争论是该学标准库还是HAL库。我的看法是入门第一遍标准库更合适。原因很直接标准库的代码是看得见寄存器的你能从库函数的名字和实现里大概猜出它在操作哪个寄存器这帮你建立硬件概念。HAL库封装得太狠了很多东西锁在黑盒里新手容易变成调函数机器出了问题完全不知道怎么查。等到你用标准库把GPIO、串口、定时器都亲手操作过一遍再去看HAL或者直接看寄存器操作都会快很多。提示如果手头板子用的是F103VET6或者ZET6这种大容量型号学习路径是一样的只是Flash和RAM更大引脚更多后面做复杂项目时余量更足。2. 开发板到手别急着接线先把三个身份确认清楚很多新手拆开包装之后第一步就是找根USB线插上然后发现电脑没有反应就开始怀疑板子是不是坏的。其实大多数情况下板子没坏而是你还没搞清楚这块板子的三个身份。2.1 板子上的芯片到底是哪一颗先看主控芯片表面的丝印。最常见的是STM32F103C8T6这是20KB RAM的中容量型号。如果是CBT6那就是128KB Flash的大容量版本。还有一些板子用的是STM32F103RCT6引脚更多。确认芯片型号的意义在于你后面用Keil或者STM32CubeMX建工程时要选择对应的Device选错了编译出来的程序可能没法在这个芯片上正常运行。顺带说一个新手很容易忽略的点芯片第一脚怎么确认。看芯片表面通常有一个圆点或者一个小凹口对应的就是1号引脚。以C8T6的LQFP48封装为例圆点在左上角从1号脚开始逆时针数到24脚是芯片左边25到48脚是右边。板子丝印上通常也会有1脚的标注接线、查原理图时都以这个为基准。2.2 板载调试器是哪种现在市面上的F103入门板调试器有三种常见情况。有的板子板载了ST-Link V2插上USB就能识别出一个调试器。有的板子是板载DAP-LinkCMSIS-DAP这种在Keil里选调试器时要选CMSIS-DAP而不是ST-Link。还有的板子干脆没有板载调试器只留了SWD接口的排针需要你外接一个ST-Link或者J-Link。这里有个非常容易踩的坑如果你的板子是ST-Link和串口二合一的设计插上USB之后电脑会识别出两个设备一个是调试器一个是串口。Keil里如果选错了设备下载时就会报No Target connected。我见过不少新手在这儿卡了一整天其实只需要在Debug设置里把调试器类型从ST-Link换成DAP-Link问题立刻解决。2.3 USB口到底是不是USB转串口这个身份最容易混淆。F103芯片本身有USB从机外设但那需要你写USB协议栈代码才能用不是插上数据线电脑就认的。绝大多数开发板上的USB口物理上接的是板载的CH340或者CP2102这类USB转串口芯片它在电脑上出现的是一个COM口。也就是说这个USB口只是用来供电和提供串口通信的跟STM32内部那个USB外设没有关系。搞清楚这一点的现实意义是当你以后想实现STM32如何做USB设备比如模拟一个键盘、一个U盘、一个串口设备你需要看原理图上USB_DM和USB_DP这两个引脚接在芯片的PA11和PA12上而不是随便找一个USB口就能做实验。很多人在这一步就搞混了导致做USB项目时走了大弯路。开发板到手之后我建议你做三件事再通电第一找出原理图PDF确认板子上哪几个LED接在哪些GPIO上一般是PC13或者PB12这种第二确认板载调试器型号第三看清楚电源电路能不能接受5V供电因为有的板子只有3.3V LDO直接从5V排针输入会烧。把这三个身份确认完这块板子在你手里才算有了地图。3. 搭建开发环境时慢一点后面能省出大量时间环境这块我不推荐为了图快直接跳过一些步骤因为后面每次编译报错、下载失败大概率都能回溯到环境配置上。我用的是最主流的组合Keil MDK5 STM32CubeMX ST-Link驱动这套组合覆盖了九成以上的入门教程。3.1 Keil MDK安装的三个关键节点Keil MDK5的安装包在官网可以找到下载后安装没啥难度麻烦的是后面几件事。第一是激活MDK需要License你可以用社区版或者其他方式但注意Keil的破解需要以管理员身份运行否则会提示写入失败。第二是安装芯片支持包这个特别容易被忽略。装完MDK后你需要从Pack Installer里下载STM32F1系列的Device Family Pack否则建工程时你根本找不到STM32F103C8这个型号。很多新手报找不到芯片根本不是Keil的问题而是Pack没装。第三是调试器的驱动。如果板载ST-LinkWindows 10以上系统一般会自动安装驱动但老版本系统可能不识别。可以装一下ST官方提供的STSW-LINK009驱动包。如果板子是DAP-Link需要在设备管理器里确认它枚举出来的设备名是什么Keil里选的调试器要和设备管理器对应。我在安装时遇到过一个很经典的问题Keil能正常编译但一按Download就提示RDDI-DAP Error。这个多半不是软件问题而是线的问题。SWD接口的四根线SWDIO、SWCLK、GND、3.3V必须接对而且线不能太长。我试过用十几厘米的杜邦线去连SWD经常下载失败换成短杜邦线或者直接用板载调试器就完全正常。SWD接口对线长和接触质量比较敏感这算是硬件层面的玄学其实是有物理原因的——高频信号在长线上衰减。3.2 STM32CubeMX不是偷懒工具是时钟树地图很多人对CubeMX有误解觉得用它就是偷懒不算真正懂STM32。我个人的体验是CubeMX最大的价值不是生成初始化代码而是把芯片的时钟树可视化了。你可以在图形界面上看到APB1、APB2、AHB这些总线是怎么分频的某个外设的时钟挂在哪个总线上一目了然。举个例子你想用USART1它的时钟挂在APB2上USART2挂在APB1上。如果你用寄存器写代码要记得开启RCC-APB2ENR或者APB1ENR对应的位。但如果你用CubeMX配置它会自动帮你把时钟使能写好还能根据你的波特率反推分频系数。这相当于把最容易出错的一步变成了自动的。但CubeMX生成的代码也不是完美的。它生成的main.c里SystemClock_Config函数看着很长很复杂你最好花点时间读懂它。因为它就是这颗芯片的时钟树全部设置理解了它就等于理解了整个芯片的能量供给结构。很多网上教程会告诉你不要动这个函数但我的建议正好相反拷贝一份工程故意改里面的分频系数然后看程序运行会出什么问题比如串口波特率变成乱码、定时器频率变了这是最快的理解时钟树的方式。3.3 第一个工程点灯背后的三行代码用标准库建工程的方式网上有一套从0建工程的方法核心就是你要自己添加启动文件、标准库的src和inc文件夹、以及系统时钟配置文件。第一次做这个流程确实繁琐但做完一次之后你对一个工程为什么由哪些文件组成会有非常清晰的认识。点亮LED的代码如果用库函数写最核心的是这三部分开启GPIO时钟、配置引脚模式、输出电平。以PB1接LED为例RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); GPIO_SetBits(GPIOB, GPIO_Pin_1);这里面有个很容易忽略的细节为什么开启的是APB2的时钟因为GPIOB这个外设挂在APB2总线上。如果你用的是GPIOA同样要开APB2的对应位。如果你用到了USART3或者TIM2这种挂在APB1上的外设就要开RCC-APB1PeriphClockCmd。这个哪个外设在哪个总线上的关系就是F103手册里的那张总线图建议打印出来贴墙上后面所有外设配置都绕不开它。编译下载之后如果你看到LED亮了恭喜你从这一刻起你对单片机的掌控感真正建立起来了。但点灯只是开始真正拉开差距的是接下来怎么理解GPIO的配置模式。4. GPIO的八种模式与推挽输出的真实含义很多教程把GPIO的点灯讲完就跳到下一章了但我觉得这里有必要停下来深挖一下。因为GPIO的配置模式搞不清楚后面做按键检测、做I2C模拟时序、做开漏输出驱动外设都会踩坑。4.1 输入模式里的浮空和上拉/下拉有什么区别F103的GPIO输入模式有四种模拟输入、浮空输入、上拉输入、下拉输入。新手最常用的是浮空输入和上拉输入。按键检测的标准做法是按键一端接地另一端接GPIO同时GPIO内部配置为上拉输入。这样按键没按下时引脚被内部上拉电阻拉到高电平按下时引脚被接地拉到低电平。如果你配置成浮空输入引脚就悬空了电平不确定按键检测会产生随机值。这里有个坑是内部上拉电阻的阻值在F103里大概是30-50k欧姆这个阻值比较大抗干扰能力弱。如果你的按键线比较长或者环境里有电磁干扰建议外接一个10k欧姆的上拉电阻比内部上拉更可靠。我做过一个项目按键离主控板二十多厘米用内部上拉时偶尔会误触发后来在按键端并了一个10k电阻到3.3V误触发立刻消失。4.2 输出模式里的推挽和开漏不只是名字不同输出模式有两个大方向推挽输出和开漏输出。推挽输出的意思是引脚既可以输出高电平由内部PMOS驱动到VCC也可以输出低电平由内部NMOS驱动到GND。这种模式驱动能力强点LED、控制数字逻辑信号都用推挽。开漏输出则只负责拉低不负责拉高。引脚要么是低电平要么是高阻态。正因为是高阻态你可以在这个引脚上外接一个上拉电阻到任意电压比如5V或者12V这样这个引脚就变成了一个电平转换器——单片机内部是3.3V逻辑但引脚可以输出5V高电平。典型的应用是I2C总线和一些需要电平匹配的外设。体现在代码上差别就是GPIO_Mode_Out_PP和GPIO_Mode_Out_OD的区别。但用库函数时开漏模式还要记得设置GPIO_Speed这个速度参数其实是GPIO内部驱动电路的翻转速率上限不是实际频率。很多人不明白为什么GPIO配置里要选10MHz、2MHz这样的档位觉得没用其实这个参数影响的是EMI和功耗高速信号才需要高挡位。4.3 复用功能和重映射这里是新手最容易晕的地方GPIO除了输入输出还有复用功能AF也就是说这个引脚可以交给某个外设去控制。比如PA9和PA10就是USART1的TX和RXPA2和PA3是USART2的TX和RX。这种对应关系在芯片的引脚定义表里有你在做串口通信时查一下就行。但F103有一个特殊机制叫重映射它允许某些外设的引脚换到别的GPIO上去。比如USART1默认在PA9/PA10但通过配置AFIO的重映射寄存器可以挪到PB6/PB7。这个功能在PCB设计时很实用能解决引脚冲突。但新手用的时候要注意使用重映射之前必须先开启AFIO时钟RCC_APB2Periph_AFIO否则重映射不生效。这个细节网上很多教程都没写导致你按教程配置了重映射却死活不工作查半天发现是忘了开AFIO时钟。注意F103的GPIO引脚耐压特性决定了它通常不能直接承受5V输入除非该引脚标注了FTFive-volt tolerant。做5V传感器对接时先查引脚是不是FT不是就老老实实加电平转换电路。5. 从点灯到实战一条够得着的进阶路线点灯之后你的下一个目标不应该是一头扎进某个复杂的项目而是按照外设的依赖关系一级一级往上爬。我的建议路线是GPIO点灯、按键输入、外部中断、定时器延时、PWM呼吸灯、串口打印、I2C读取传感器、SPI驱动屏幕。这条路走通之后你再看网上那些STM32超声波测距STM32驱动ILI9341屏幕STM32读取MPU6050姿态传感器的项目就能一眼看懂它们大概是怎么构成的。5.1 定时器不是只有一个每个都有分工F103有多个定时器其中最常用的是TIM2、TIM3、TIM4这种通用定时器以及TIM1这种高级定时器。新手有个常见误区是定时器就是用来延时的实际上定时器的核心能力是计数和比较。定时器延时的原理很好理解内部时钟源经过预分频器分频后得到一个比较慢的计数时钟计数器从0计到ARR自动重装载值再触发更新事件。所以延时时间 (ARR1) × (预分频系数1) / 定时器时钟频率。把这个公式掌握之后你再去看STM32定时器捕获测频率这个具体应用就很好理解了。输入捕获模式的本质是计数器在后台一直跑当捕获引脚上出现电平跳变时硬件自动把当前的计数值锁存到捕获寄存器里。你只需要读取相邻两次捕获的计数值之差再用定时器时钟频率除掉就能算出输入信号的周期进而得到频率。这个功能用来测PWM信号、编码器转速、超声波回波时间都特别实用。5.2 串口是所有调试的基础串口USART是我认为F103里最应该学透的外设。不是因为串口本身多难而是因为它几乎是调试的唯一窗口。你写的代码在芯片里跑成什么样靠LED闪烁只能看出非常粗糙的状态而串口能把你关心的变量值、程序的执行流程、错误出现的位置全部实时打印出来。串口配置的步骤很简单开启时钟、配置GPIO复用为串口功能、配置串口参数波特率、数据位、停止位、校验位、使能串口和发送中断。需要注意的一点是串口的波特率时钟源也是挂在总线上的不同串口挂的总线可能不同你的分频系数必须根据对应总线的时钟来计算。如果时钟树配置不对就会出现在CubeMX里设置115200波特率但实际PC端收到的全是乱码的情况。调试串口时有个我常用的技巧不用printf重定向到串口而是写一个最简的发送字节函数然后在调试时直接调用。这样做的好处是代码干净、不依赖C库也避免了一些嵌入式环境下printf因为microlib和标准库冲突而卡死的问题。等到程序稳定了再决定要不要上printf。5.3 常见组合项目超声波、屏幕和传感器把上述外设打通之后你就可以尝试做一个组合项目了。网上热度很高的STM32超声波测距项目本质上就是GPIO输出一个触发脉冲然后切换成输入捕获模式测量回波高电平时间再用声速公式换算距离。这个项目看似简单其实用到了GPIO输出、定时器输入捕获、串口打印、可能的LCD显示。一次项目四个外设全练到。STM32使用ILI9341读ID是a1a1这个热搜词说明很多人卡在屏幕初始化上。ILI9341是一款经典的TFT屏幕驱动芯片通过SPI或并口与MCU通信。读ID这个步骤是用来验证SPI通信是否正常的正确地发0xD3命令屏幕会返回三个字节其中后两个是屏幕ID正常的读取结果应该是0x93、0x41对应9341。如果读出来是a1a1这种通常说明SPI时序有问题或者初始化序列写错了。这个问题的排查思路也是在做任何SPI外设时通用的先读ID验证通信再操作功能不要上来就刷屏。5.4 触碰一下CAN和其他总线把基础外设练熟之后可以碰一碰CAN通信了。F103的bxCAN模块在工业控制和汽车电子里用得非常多。它的双线差分结构让它在长距离、高噪声环境下依然可靠。做CAN实验时你需要一个CAN收发器芯片比如TJA1050把F103的CAN_TX和CAN_RX引脚转换成差分信号。CAN通信有个容易让新手困惑的问题是为什么写着写着就突然连不上了。最常见的原因是波特率配置不一致两个节点必须使用相同的波特率而且总线两端还要各加一个120欧姆的终端电阻。如果你忘了加终端电阻总线信号会反射通信状态就变得时好时坏表现出来就是突然连不上。这个问题我遇到过不止一次排查方法是把终端电阻加上再把波特率统一基本就能解决九成的问题。6. 复盘几个高频问题从芯片包安装到延时卡死最后这一部分我把网上提问频率最高的几个问题集中复盘一下每个问题背后其实都是一个典型的理解误区。6.1 STM32芯片包安装不上怎么办MDK的Pack Installer在线安装偶尔会因为网络问题失败。替代方案是手动下载Device Family Pack的离线包然后双击安装。下载时要注意区分是F1系列还是F4系列的包装错了选型号时还是找不到。还有一种情况是公司网络限制了下载站点可以在家里把Pack下载好拷贝到工作电脑上安装这个操作是完全可行的。6.2 标准库新建工程时启动文件选错了会怎样启动文件有不同版本比如startup_stm32f10x_md.s中容量、startup_stm32f10x_hd.s大容量、startup_stm32f10x_ld.s小容量。如果你用的是C8T6中容量却选了hd工程能编译但大概率下载后程序跑飞或者Debug时PC指针跑到奇怪的位置。这个文件决定了芯片启动时怎么初始化堆栈、怎么建立中断向量表选错了等于地基选错了。6.3 为什么外部中断进不去外部中断配置看似简单但有几个容易忘的地方首先要开启AFIO时钟其次要把对应的GPIO配置成输入模式再次要使能EXTI线最后要设置NVIC优先级。很多新手配好EXTI使能却没配NVIC中断永远不触发。NVIC是整个Cortex-M内核管理中断的总闸门Peripheral使能了NVIC没开等于门锁开了但大门关着。6.4 延时函数delay卡死是怎么回事很多人在用自写延时函数时会遇到程序运行到delay就卡死操作LED等动作也不响应了。这个问题一半是Delay函数本身的问题比如使用了SysTick中断服务函数但没清除中断标志导致中断反复进入另一半是中断优先级的问题如果你的某个中断函数执行时间过长或者陷入死循环低优先级的delay代码就会被卡住。用HAL库的话还有个典型案例在中断回调函数里直接调用HAL_Delay结果因为HAL_Delay依赖滴答定时器的中断而当前中断优先级比滴答定时器的优先级高就造成了死锁。这个问题的通用解决方案是中断服务函数里只做快速处理需要延时的逻辑迁移到主循环或任务里去。我以前刚接触F103时遇到过一个问题调了一晚上都没找到原因后来发现是某个变量在中断里被修改主循环里又读取它做判断但由于编译器优化主循环里始终读到的是旧值。这个问题的本质是变量缺乏volatile修饰。这也算是嵌入式开发里一个经典但又特别隐蔽的坑建议你在学到中断和主循环交互时就建立起这个意识凡是中断和主循环共享的变量一律加volatile。用新学的知识武装你的第二块板子STM32F103这块板子说实话不像是现在市面上那些集成了一大堆传感器、屏幕、WiFi模块的全家桶开发板那么花哨。但它恰恰是那种能让你把底子打扎实的板子。等你把GPIO、串口、定时器、中断这几个基础外设全部亲手配置过一遍再去看基于ESP32、基于Linux的MPU比如RK3588那种应用处理器的项目你会发现很多东西是相通的只不过换了一个更快的内核换了一套更复杂的软件栈。我的建议是接下来把精力放在用这个板子去实现一个足够完整的小系统上不要停留在跑例程。比如把超声波测距、LCD显示、串口上报这三个功能组合起来做一个带屏幕的测距仪。项目不大但麻雀虽小五脏俱全从硬件接线到软件分层每一步都自己来做完之后你对整个嵌入式开发流程的理解会上升一个台阶。至少在我看来这比收藏一百个教程、却一个都没跑通要有用得多。