
刚把手头这块STM32F103的开发板拆开树脂味还没散我就在琢磨怎么把这条学习路线写得清楚一点。对于刚入门的同学来说STM32这几个字母可能已经听出茧子了但真正拿到板子之后很多人反而会愣住芯片型号怎么读、用什么工具链、第一段代码怎么烧进去每一步都有坑。这篇文章我不打算讲那种大而全的教程而是以一个刚拿到STM32F103开发板的用户视角从环境、点灯、定时器、串口、传感器到常见故障把最关键的操作路径和一些容易踩的坑全部过一遍。不管你是学过51单片机想升级还是直接零基础开整只要手里有这块板子这篇内容就能当一份开箱之后的实操手册。我拿到的是市面很常见的STM32F103C8T6核心板也就是所谓的蓝色药丸板。说实话这类板子现在已经很成熟了几十块钱包邮还带USB转串口非常适合第一个STM32项目。全文我会尽量说人话中间穿插一些我实际调板子的经验和教训希望能帮你少走弯路。1. 先把手里的开发板认透1.1 读懂芯片型号与板卡构成很多初学者拿到板子第一反应就是找引脚图其实更应该先搞懂芯片型号本身。就拿F103C8T6来说这串字符信息量非常大STM32表示是意法半导体的32位ARM Cortex-M3产品线F代表通用型103代表基础型系列C代表引脚数量为48 pin8代表Flash容量是64KBT代表LQFP封装形式6代表工作温度范围是-40℃到85℃。如果你是ZET6系列Z代表144 pinE代表512KB Flash那个板子一般就是霸道战舰那种大板资源更夸张但学习方法完全一样。开发板上除了芯片本身通常还会有一个USB转串口芯片比如CH340或者CP2102一两个用户按键一颗电源指示灯一两颗可编程LED灯再加上晶振和复位电路。我建议拿到板子先别急着接电把板子布局看明白确认USB口是左上还是右下下载器座子是2x3的SWD还是4针的JTAG这样后面接线才不会乱。供电方面要特别提醒STM32F103的工作电压是2.0V到3.6V大多数板子板载了3.3V LDO稳压器所以通过USB供电没问题。但如果你想用外部电源同时驱动大电流外设那就要注意共地否则信号线参考电位不一致通信就会变得非常诡异。判断开发板是否正常上电最简单的办法就是看那颗VCC电源指示LED一般接到电源轨上亮起来代表板载稳压器在工作。1.2 芯片第一脚怎么确认这个知识点看着白痴但实际操作中很多人翻过车。STM32芯片本体上会有一个圆点或者一个倒角切边那个位置就是第一脚所在方向。以LQFP48封装为例从圆点那一侧开始得按逆时针方向数引脚第一脚在左上角然后向下是2、3、4直到24脚再从右下拐上来这样一圈走完刚好48脚。市面上有些盗版或者打磨过的芯片丝印不太清楚这时可以拿万用表量一下VDD和VSS的关系来辅助确认通常VSS脚GND会和板子上的GND铜箔直接相连把所有GND针脚的位置先找出来再按对称性反推也能锁定引脚位置。另外很多蓝色药丸板上的芯片丝印并不是正对着你而是成品封装的时候转了90度所以PCB上的丝印指示往往比芯片本身的印字更可靠。你画的第一个外设接线图最好以板子引脚排针丝印为基准不要想当然按芯片封装图去套。引脚排布大概要记住哪几个关键位置PA0PA15和PB0PB15是两组通用GPIO其中大部分有复用功能PB2是BOOT1引脚上电期间尽量不要拿来当普通IO用PA13和PA14默认是SWDIO和SWCLKPA9和PA10默认是USART1_TX和USART1_RX。这些默认功能在数据手册里都能查到但刚上手阶段只需要记常用复用后面用得多自然就熟了。2. 开发环境三条路线怎么选2.1 Keil MDK 入坑最稳没有特殊原因建议刚学还是用Keil MDK。原因不是它功能最强而是网上教程最多遇到问题搜索时命中率高而且STM32的老用户、老代码、老工程基本都围绕Keil建立了一套完整生态。MDK-ARM和C51是分开的如果你以前装过Keil C51那么装MDK时要注意它们是可以共存的但需要不同的许可证。装好MDK-ARM之后还需要安装对应芯片的器件支持包Device Family Pack比如Keil_STM32F1xx_DFP这个如果不装新建工程时器件列表里根本没有STM32F103C8。工程创建的时候有一个容易被新手忽略的启动文件选择问题。F103系列的启动文件分为hd、md、ld几种分别对应大容量、中容量、小容量芯片。C8T6有64KB Flash属于中容量medium density用 startup_stm32f10x_md.sZET6属于高密度用 hd。选错了启动文件轻则中断向量错乱重则程序直接跑飞。这也是为什么我建议你新建工程时用一套整理好的标准库工程模板而不是每次从头手动添加文件别把时间浪费在无意义的重复劳动上。KL指纹说一下桌面上的Keil图标可以识别打开Pack Installer里有CM3和STM32F1的全套支持就算装对了。如果发现打开设备列表里连ST都找不到说明MDK安装过程有问题重新以管理员身份运行一次安装程序一般能解决。2.2 CubeMX HAL 提升效率如果你以前用过51或者自己会些寄存器操作那在标准库或者寄存器层面挣扎一段时间之后可以过渡到STM32CubeMXHAL库的组合。CubeMX是一个图形化配置工具能直接生成初始化代码时钟树、GPIO、串口、定时器都能鼠标点点就配好甚至能自动帮你生成Keil工程。很多刚转STM32的人担心用了HAL库以后是不是不懂底层了其实这是误解。在F103这个平台上HAL库中间层的封装并没有复杂到看不懂而且配置代码是全自动生成的你更专注于业务逻辑。对于做毕业设计、比赛、工装这类场景这个效率优势非常明显。但我给你一个实际建议刚开始学点灯和串口那几天别急着用CubeMX生成完整项目先手动建几个标准库工程搞清楚启动文件、时钟树、GPIO初始化这几步操作到底在干什么。有了这个底子再用CubeMX时你看着生成的代码不会两眼一黑。反过来如果纯手动搞了一周还在点灯那就赶紧切到CubeMX别和自己过不去。2.3 VSCode 与烧录差异这两年用VSCode配ARM工具链做STM32开发的越来越多了配合EIDE插件或者PlatformIO插件编译体验其实比Keil更现代代码补全和git集成也好很多。不过新手阶段用VSCode踩的坑主要集中在两个地方一是arm-none-eabi-gcc工具链的下载和安装二是debug配置里的launch.json。很多人Keil里编译成功很得意换个VSCode就发现同样代码烧不进开发板原因往往是Debugger类型没选对或者烧录器型号配置错了。我个人的态度是VSCode可以作为备选但入门阶段还是以Keil为主力原因无他当你在一个环境卡住了换工具链的成本比解决问题本身还高。等问题积累多了再往VSCode迁移那时候你对编译参数、链接脚本、烧录器的理解都够了迁移只是半天时间的事。3. 第一个工程从点亮LED到理解时钟3.1 标准库工程骨架与点灯代码这里我给出一套推荐的标准库工程目录结构Project/ ├── Core/ │ ├── inc/ // 头文件 │ └── src/ // 应用代码 ├── Libraries/ │ ├── CMSIS/ // 内核寄存器定义 │ └── StdPeriph/ // 标准外设库 ├── Startup/ // 启动文件 └── Objects/ // 编译输出也就是.axf/hex放这里点灯的GPIO操作其实主要就是三步。第一步开启GPIO时钟GPIO是挂载在APB2总线上的外设C8T6里PA口对应RCC_APB2Periph_GPIOA需要用RCC_APB2PeriphClockCmd(那个参数, ENABLE)打开。注意STM32里绝大部分外设在使用前都要先开时钟忘了这一步是最常见的低级错误。第二步配置引脚模式和速率用GPIO_InitTypeDef结构体指定Pin、Speed、Mode这三个字段比如推挽输出模式GPIO_Mode_Out_PP输出速度选GPIO_Speed_50MHz。第三步就是拉高拉低往ODR寄存器写数据或者用GPIO_WriteBit。下面是一个最传统的PB1引脚控制LED灯闪烁的示意代码#include stm32f10x.h void Delay_Init(void); void Delay_ms(uint32_t ms); int main(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); 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); while (1) { GPIO_WriteBit(GPIOB, GPIO_Pin_1, Bit_SET); Delay_ms(500); GPIO_WriteBit(GPIOB, GPIO_Pin_1, Bit_RESET); Delay_ms(500); } }注意上面这个Delay_ms我用的是自定义延时函数没有用官方库的SysTick版本。初学为了简单可以直接用空循环计数但后面正经工程建议用SysTick做时基不然定时精度很差。每个开发板的外置晶振和芯片内部时钟配置会不一样点灯这个阶段不需要折腾时钟树默认用内部8MHz或者板载晶振都行。3.2 点灯背后的时钟与引脚复用我以前带人玩F103时发现一个现象代码明明看起来没问题但LED就是微亮或者干脆不亮。排查到最后大多数是GPIO输出速度没配置或者引脚复用没关导致其他功能抢占了控制权。F103的GPIO引脚引脚默认都是浮空输入状态上电后几乎所有引脚都是高阻所以如果你要用某个引脚做输出GPIO_Init必须把方向模式改到位。还有一个坑是PA13、PA14、PA15以及PB3、PB4这几个引脚默认是JTAG相关复用标准库里如果不关闭JTAG功能你把它们配置成普通IO输出时会出现怎么也驱动不起来的现象实际是因为调试口占用了这些复用功能。要用这几根引脚做普通IO需要在初始化时调用GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE)但这么做了以后JTAG下载功能就废了只能靠SWD下载所以很多人干脆把这两个下载方式都用起来平时只用SWD。关于时钟F103有一个内部RC振荡器HSI和一个外部晶振HSE。绝大多数开发板外接了8MHz晶振上电后默认是HSI作为系统时钟你如果想让系统跑在72MHz必须在SystemInit函数里切到HSE并且设置PLL倍频。STM32标准库自带的SystemInit已经默认把时钟配置到72MHz前提是你系统初始化时能正常启动HSEHSE起振失败的话代码会卡在等待超时的循环里。这也是为什么你发现程序在SystemInit里面死掉时先量一下外部晶振两个引脚有没有波形如果没有检查晶振是否虚焊或者是劣质晶振。4. 拿得出手的实战目标定时器、串口与传感器4.1 定时器PWM输出和频率捕获点灯只是起步真正让F103从玩具变工具的是它的定时器资源。F103里有TIM1和TIM8两个高级定时器TIM2到TIM5四个通用定时器TIM6和TIM7两个基本定时器还有TIM10到TIM14一批加塞的定时器。每种定时器能做的事不一样基本定时器只能做时基和触发DAC通用定时器可以输出PWM、输入捕获、编码器接口高级定时器还多了互补输出和刹车功能适合做电机控制。PWM输出本质是利用定时器的比较寄存器生成一个可调占空比的方波。以TIM2的通道1为例你需要配置预分频器PSC和自动重装载值ARR这两个数值共同决定PWM的周期然后设置CCR1的值来控制占空比。假设系统时钟72MHzPSC719ARR999那么计数器时钟是100kHz一个PWM周期是0.01秒也就是100Hz如果你想输出50%占空比就设CCR1为500。PWM调光、呼吸灯、舵机角度控制都是这个套路学通了之后非常万能。定时器输入捕获测频率是另一个高频需求。它的原理是引脚上的边沿信号会触发捕获寄存器锁存当前计数器的值通过两次相邻捕获值的差值再结合预分频系数就能算出信号频率。如果你要测一个低频方波可以把预分频配大一点如果要测高频则要留意输入捕获的溢出处理否则计数器回绕会造成测量值完全错误。实测下来用PA0复用映射到TIM2_CH1来测PWM信号非常顺手但注意PA0上经常同时接了板上按键的输入按键的上下拉会影响信号测频率前最好把按键功能从板子上摘掉。4.2 串口printf调试大法串口是嵌入式调试很重要的手段F103的USART1挂在APB2上USART2和USART3挂在APB1上。最简单的串口输出流程是配置GPIO复用推挽输出配置USART参数使能USART。这里重点说重定向printf的那段标准操作在Keil里用microlib模式后写一个fputc函数把单个字符送到串口发送寄存器即可比如int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t)ch); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); return ch; }这个函数的难点在于理解重定向原理。C标准库printf函数最终会调用底层字符输出函数在ARM平台上这个底层默认是往调试器的ITM通道写如果你没有配置调试端口printf的输出就丢到黑洞去了。重定向成串口之后printf就能把文本打到串口助手里这对调试状态机、传感器数值、协议解析都特别好用。接收数据是另一个话题。如果你要用串口收不定长的数据帧建议直接开接收中断在中断里把字节放进缓冲区再配合定时器超时判断一帧数据是否完整。刚入门时很容易把主循环里的阻塞延时和串口中断混在一起结果导致一个字节没读完整个系统就卡死这类问题归根结底是对中断优先级和临界区理解不够。如果发现串口数据偶发乱码先查波特率误差和外部晶振精度很多串口通信问题最稳定的排查路径就是拿示波器量TX脚的波形周期。4.3 传感器通信I2C、单总线与超声波测距STM32接传感器几乎是每个项目都躲不开的环节。拿超声波测距来说最常见的HC-SR04模块逻辑很简单给TRIG引脚一个至少10us的高电平模块会自动发出8个40kHz脉冲然后ECHO引脚会输出一个高电平高电平的时间就是声音往返的时间距离等于时间乘声速除以2。HC-SR04虽然不用串口也不需要I2C但它的时序非常依赖微秒级延时F103里用DWT内核定时器或者其他硬件定时器来做us延时最稳用纯软件循环的话注意优化级别不同可能导致延时偏差很大。BH1750这类数字光强度传感器走的就是标准I2C协议。F103硬件I2C外设在这颗芯片上口碑一般很多人用着用着就会遇到总线卡死根本原因是I2C的忙标志和超时处理不完善。我个人的经验是对于F103平台驱动I2C传感器优先考虑GPIO模拟I2C虽然代码多几行但是可控性和排查难度都明显改善。硬件I2C如果要稳定驱动必须写好错误处理尤其是总线忙时要能把SCL拉出发9个时钟脉冲来恢复总线这个细节很多教程压根不提。另外BH1750这种传感器和OLED屏可以挂在同一条I2C总线上只要地址不同就能共存非常省引脚。如果遇到OLED显示字模乱码或者花屏先排查通讯速率是不是降到100kHz以下再检查电源纹波最后再看初始化顺序。我见过不少OLED不亮的案例最终原因是3.3V和5V供电混接导致逻辑电平不对I2C上拉电阻接到5V上把GPIO引脚灌坏了一半。5. 常见问题与排查技巧实录5.1 烧录失败问题清单Keil编译成功后烧录不进去这个问题绝对能排进STM32新手问题前五。最常见的现象是点击下载后弹出Error: Flash Download failed - Cortex-M3原因可能有很多我列一个实际排查顺序供你参考排查点操作方法对应解决思路烧录器连接确认SWDIO/SWCLK/GND三条线都接了且接触可靠杜邦线松动是最大的隐性杀手目标板供电确认开发板有独立供电或者在调试器供电模式目标板没上电时SWD无法初始化BOOT0引脚把BOOT0接地BOOT1任意BOOT0拉高会进入ISP模式无法用调试器擦写代码禁用调试口检查代码里是否把PA13/PA14设为普通IO禁用SWD之后下载器找不到内核烧录器型号确认Keil里Debug选项选的是ST-Link还是JLinkON-chip和外部仿真器选错会直接报错芯片写保护高级选项里关闭Flash的Readout Protection读保护开启后无法正常擦写还有一个很隐蔽的坑某些JLink或者ST-Link克隆版驱动不稳定特别是USB线质量特别差的时候。你可以把USB线从机箱前置口换到主板原生口试试淘汰掉劣质线和USB HUB之后成功率能提升不少。如果你的工程模板是自己建的还有一个常见原因是Flash地址范围配置错了。F103C8T6是64KB Flash起始地址0x08000000大小0x00010000如果你配置成128KB下载算法还是会报错。这个地址配置在Keil的Options for Target - Target - IROM1里修改后点下载前先看Memory显示的地址范围是否合理。5.2 运行时“死刑”现象盘点程序烧进去了却运行不对这类问题更隐蔽。我遇到几个高频症状一个是Delay函数卡死。最常见的原因是中断环境里用了基于SysTick的延时但SysTick中断优先级和别的中断冲突导致延时函数一直等待计数标志位完成但永远等不到。另一个可能是你在初始化里把SysTick关了却没有重开。遇到卡死第一件事就是暂停调试模式看PC指针停在哪个函数如果停在Delay的某个循环里基本可以断定是中断向量或者SysTick配置问题。另一个高频问题是CAN通信突然连不上。F103的CAN控制器挂在APB1上硬件上还必须有外部CAN收发器比如TJA1050并且要在总线两端加120欧姆终端电阻。很多人板子单独测试收发器都没问题一旦接到现场总线上就连不上原因多半是没有共地、没有终端电阻或者波特率不匹配。CAN波特率不是随便配的它由预分频和同步跳转宽度决定不同厂家对位时间段的定义可能不同一定要统一配置策略。还有F103的CAN发送邮箱写满之后会有报错但如果你在中断里处理接收又同时在主循环里发送很容易出现发送超时解决办法是每帧发送后设置一个超时看门狗不要死等。JTAG被禁用是第三个经典问题。很多人在调下载时发现板子突然不能烧了排查后发现代码里执行了类似GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE)的语句它把整个SWJ调试端口关闭了这样下载器自然失联。修复方法是进入ISP模式把Flash擦空或者从BOOT0拉高进入系统存储器引导模式。所以我前面说调试期间尽量只用SWD模式不要全部禁用SWJ。实际上PA15/PB3/PB4如果非用不可也只禁用JTAG而保留SWD这样至少还能继续下载。6. 接下来怎么学一条可执行的路线建议6.1 标准库到HAL库的过渡节奏我经常被问到一个问题现在学STM32到底学标准库还是HAL库我的回答是对于F103平台两个都要接触但先后顺序要明确。前面学点灯、按键、串口、定时器建议用标准库因为标准库的代码更贴近寄存器你能看到每个外设的配置过程对底层结构印象深。等你把GPIO、时钟、中断、外设数据手册这四个维度理解得差不多了再切到CubeMXHAL库。HAL库的学习重点不是每个函数参数而是学会读生成的初始化代码PA9_USART1_TX是哪个宏决定的TIM2的时钟为什么是72MHz分频下来的这些理解了HAL库在你手上就只是加速工具而不是黑盒。有一些人一开始就从B站视频抄HAL配置点灯倒是快但遇到一个奇怪问题仍然无从下手只能整个工程推翻重来。这就是缺少底层感觉的表现。好在F103的寄存器手册和库代码写得比较直白花一个星期过渡完全来得及。6.2 小项目驱动学习嵌入式学习最大的敌人是“看起来都会了”。给你提一套我亲自试过比较有效的项目推进顺序第一个项目是用GPIO做按键控制双色灯。这个项目会让你学会输入模式、上下拉、抖动消除。第二个项目是定时器PWM输出用舵机或者LED亮度调节验证效果。第三个项目是串口通信做一套PC端上位机交互按键发指令控制LED状态。第四个项目是I2C读取温湿度传感器并在OLED上显示把这个调通你对通信协议的认知会直接上一个台阶。每一个项目都要从头到尾自己搭工程中途可以查参考代码但不要整篇复制。到了这个阶段你自然会遇到中断、DMA、环形缓冲区这些概念。这很正常遇到一个学一个不要一次性把所有外设细节背完。F103的资源在当今很多消费级芯片面前不算亮眼但它胜在资料多、案例全、出问题好搜。凭借这块103开发板入门之后再转STM32F4、H7甚至国产的CH32、GD32迁移成本都不是问题。在我实际接触的这些新手案例里学得最快的往往不是基础最好的人而是最敢动手试错的人。程序烧不进去就一遍遍查接线和配置串口乱码就换波特率测波形代码卡死就挂调试器看指针位置。这套调试思路一旦建立起来比背下整个HAL库都有用。最后再分享一个小技巧这块F103开发板上如果留有LED和按键尽量从一开始就把它们定义成固定引脚并且写进一个bsp_driver文件里。后面所有项目都复用这套驱动时间长了你就拥有了一套自己的GPIOLED、按键、串口映射库。不同板子换着用的时候只要改引脚宏和时钟使能宏其他逻辑完全不用动这体验比每次从零复制粘贴舒服太多了。