开发板拿到手的第一天我就给它换了个家——从纸箱里取出来静电袋还没撕完板子已经被我举到灯光下翻来覆去看了好几遍。STM32F103这个名字在嵌入式圈子里几乎等同于“入门标配”无数人第一次点灯、第一次跑串口、第一次做小项目都是从这片板子开始的。我这次入手的就是一块经典的STM32F103开发板接下来打算把整个学习路线、外设玩法、踩坑记录都陆续整理出来。这篇内容既是给自己做知识沉淀也是给想入坑STM32的朋友一份参考地图从开箱到跑通常见外设全部是实测过的路径。1. 板子到手先别急着插电开箱检查与第一印象1.1 为什么选STM32F103而不是C8T6或者H7很多新手在选板子的时候会被型号绕晕这里我直接说结论STM32F103系列是学习曲线最平缓的选择。它基于ARM Cortex-M3内核主频72MHzFlash从64KB到512KB不等RAM从20KB到64KB这个配置在上位机、传感器、电机控制、简单的协议处理等场景下完全够用。相比之下F4系列主频高、带DSP和FPUH7更是双核加高主频性能确实强但对新手来说反而容易迷失——外设太多中断优先级系统更复杂功耗和PCB布线的坑也更深。F103的寄存器手册、库函数、例程资料、网上教程数量多得惊人你遇到任何问题基本都能搜到解决方案。我选的这块板子是经典的“最小系统板外设扩展”结构上面除了芯片本身还带了USB转串口芯片、一颗8MHz晶振、复位按键、BOOT选择跳线帽以及一排排的排针引脚。板子背面丝印很干净每个引脚都标了对应的GPIO编号比如PA0、PB5这种这对后续接线和查手册来说太重要了。不要小看这些丝印很多廉价板子丝印模糊甚至标错会让人排查半天。1.2 上电前的三个检查步骤拿到板子之后别急着插线先做三件事。第一检查板子上的跳线帽是不是都在正确位置。常见的是BOOT0和BOOT1的跳线正常运行时BOOT0要接GND如果它在VCC位置上电后会进入系统存储器引导模式你会发现程序烧不进去或者跑的都是默认的ISP程序。第二确认USB转串口芯片型号常见的是CH340和CP2102这两种芯片的驱动不通用系统不识别的时候先看看是不是驱动问题。第三用万用表量一下3.3V和GND之间的电阻正常应该在几十到几百欧姆之间如果接近短路就要警惕可能是焊接问题或者芯片已经损坏。插上USB线之后电脑应该会识别出一个串口设备设备管理器里能看到对应的COM口编号。这里有个小细节如果你用的是CH340系统第一次连接时需要安装驱动Windows会提示“设备未识别”这是正常的去WCH官网下载对应驱动装上就好。不要慌这一步卡住的人特别多。注意MCU芯片的供电电压是3.3V但USB口输出的是5V。板子上的USB转串口芯片通常带LDO稳压器会自己把5V转成3.3V所以直接插USB供电没问题。但如果你同时用外部电源和USB供电务必先断开其中一个否则电压叠加很容易烧芯片。2. 环境搭建别踩坑Keil、芯片包与标准库的完整安装流程2.1 Keil MDK的版本选择与安装细节STM32开发最主流的IDE还是Keil MDK虽然VSCode GCC的组合这两年越来越火但Keil在下载调试、查看外设寄存器、生成工程方面仍然是最省心的方案。我这里用的是Keil MDK 5.x版本注意它和Keil C51不是一个软件前者是ARM内核用的后者是8051内核用的两者虽然都叫Keil但完全独立安装互不相干。安装的时候有几个要点。第一安装路径不要带中文和空格建议直接装到D盘根目录比如 D:/Keil_v5后面你会感谢这个决定因为很多GCC工具链和脚本对路径的解析能力非常脆弱中文路径会引发各种莫名其妙的问题。第二安装过程中如果弹出安装驱动或者PACK的选项先全部跳过等主软件装完再手动添加。第三激活和License的问题我不多说但请一定使用正版授权或评估版本这是基本的合规意识。2.2 芯片支持包PACK的安装与路径问题Keil本身不内置STM32的设备支持它需要通过Pack Installer来安装对应芯片的Device Family Pack。打开Pack Installer有两种方式一种是Keil菜单栏的Project - Manage - Pack Installer另一种是直接运行安装目录下的PackInstaller.exe。在Pack Installer里找到STMicroelectronics - STM32F1 Series展开后选最新的版本安装。这里有一个新手最容易卡住的地方Pack下载慢或者下载失败。原因多半是网络访问国外服务器不稳定。解决方案有两个一是用Pack Installer自带的“Import”功能从本地手动导入已经下载好的.pack文件二是直接在搜索引擎找到对应Pack的镜像下载源。安装Pack之后在Keil的Device选项卡里就能看到STM32F103C8、STM32F103RCT6、STM32F103ZET6等具体型号了。选什么型号取决于你的板子丝印或包装盒上的标注核心板最常见的是C8T6和RCT6。提示如果新建工程时选错了芯片型号后面烧录时会提示“Flash Download Failed”或者“No Target Connected”排查时先检查Device选型而不是急着怀疑板子坏了。2.3 标准库StdPeriph_Lib还是HAL库新手的选择策略这是一道经典的“送命题”。我的建议很明确学习阶段优先用标准外设库也就是StdPeriph_Lib也叫SPL库。原因有三个第一标准库的命名非常直观比如GPIO_Init、TIM_Cmd、USART_SendData每一个函数做什么一目了然对理解寄存器操作有巨大的帮助第二网上十年积累的教程和例程绝大多数都是基于标准库写的你遇到问题搜出来的代码能直接复用第三标准库的代码量小不依赖CubeMX生成那一堆抽象层代码编译速度快逻辑链路短适合新手追踪程序执行流程。HAL库也不是不好它胜在跨芯片移植方便配合STM32CubeMX可以图形化配置时钟树和引脚生成工程代码的效率极高。但它的层次封装重、回调函数多、初始化流程复杂新手往往会在HAL_UART_Transmit和UART_Receive_IT的配合关系上绕圈子。等到你用标准库把GPIO、定时器、串口、中断这些基础吃透了再切换到HAL库和CubeMX做快速原型开发会平滑很多。不要贪多先把一条路走通。2.4 新建工程的完整步骤与烧录配置新建标准库工程其实不难但步骤繁琐我建议固定一套流程避免每次重复踩坑。第一步在Keil里选择Project - New uVision Project命名工程名比如“01_LED_Blink”保存到项目文件夹。第二步在Device里选好芯片型号。第三步因为标准库不通过Pack自动安装需要手动把标准库源码复制到工程目录下的Libraries文件夹包含CMSIS核心头文件、启动文件startup_stm32f10x_hd.s或md.s取决于你的芯片、以及STM32F10x_StdPeriph_Driver里的inc和src目录。第四步在工程管理里添加Group把对应.c文件加进去同时配置头文件包含路径C/C选项卡里的Include Paths。第五步在C/C选项卡里定义宏USE_STDPERIPH_DRIVER, STM32F10X_MD或HD这是标准库的条件编译开关。第六步配置Debug选项选择ST-Link或者J-Link并在Utilities里勾选“Reset and Run”。这样烧录后程序会自动运行不用每次手动复位。这几个步骤第一次做会有点晕但做三次之后你就会发现其实核心就三件事包含路径对不对、宏定义对不对、启动文件选没选对。后面我会单独写一篇保姆级工程创建流程把每一步截图配齐。3. GPIO点灯与延时函数从“卡死”到真正理解3.1 点灯不是无聊的开始而是外设操作的“最小闭环”在STM32的世界里点灯这件事跟Arduino那种直接digitalWrite完全不同。你必须在代码里先启动GPIO的时钟通过RCC_APB2PeriphClockCmd再配置引脚模式GPIO_InitStructure里的Pin、Mode、Speed最后才能调用GPIO_WriteBit或GPIO_SetBits操作电平。这一套流程下来你其实是在练习所有外设共有的操作逻辑开时钟、配寄存器、操作数据。后面你用I2C、SPI、UART本质上都是同一套思路。很多教程会把GPIO模式分成输入浮空、输入上拉、输入下拉、模拟输入、推挽输出、开漏输出、复用推挽、复用开漏这八种刚开始记不住很正常。我的建议是先用两个输入上拉和推挽输出。按键扫描用输入上拉LED控制用推挽输出。等这两招用熟了再慢慢去琢磨开漏输出在I2C和电平转换里的妙处。3.2 延时函数delay卡死的两个经典原因热词里有一条“stm32延时函数delay卡死”这几乎是每个人都遇到过的问题。我结合自己的经验总结出两个最常见的原因。第一个原因没有初始化SysTick定时器就调用延时函数。在标准库的例程里延时函数通常依赖DelayInit()或者SystemCoreClock的时钟配置。如果你直接复制了一个Delay_us或者Delay_ms函数但是没有在main函数里先调用初始化函数那么SysTick计数器根本不会跑程序就像死循环一样卡在那里。排查方法很简单单步调试时查看SysTick-CTRL寄存器的第16位COUNTFLAG是否有变化或者直接把延时函数体注释掉看主流程能否跑通。这个坑的本质是“依赖关系没满足”。第二个原因中断服务函数里调用延时函数。很多新手会在定时器中断或者外部中断回调里写Delay_ms然后发现程序卡死。原因是SysTick的优先级通常设置得比当前中断低当中断被打断后SysTick中断一直无法响应延时永远走不到头。正确的做法是中断服务函数里绝对不要用不依赖于中断的延时函数处理完标志位和数据就立即返回让主循环去处理耗时的逻辑。3.3 关于RCC时钟你第一眼看到的“SystemInit”到底干了什么在标准的main函数里一般会看到SystemInit()这个函数它并不是Keil自动加进去的而是标准库启动文件里的调用。它的作用是把系统时钟从默认的HSI切换到HSE外部高速晶振然后配置PLL倍频到72MHz并把AHB、APB1、APB2的分频系数设好。启动文件在调用main之前就已经把SystemInit执行完了所以你在main里通常看不到它但它决定了整个芯片的运行节奏。APB1和APB2的最大允许频率不同APB1最高36MHzAPB2最高72MHz。这也是为什么UART1和UART2不能直接用同一个波特率计算方式的原因因为挂在APB2上的USART1用的时钟可能是72MHz而挂在APB1上的USART2用的可能是36MHz。你在配置波特率寄存器时要注意这个区别否则串口输出乱码的概率极高。4. 从热词看常见应用USB设备、超声波测距与定时器捕获4.1 STM32如何做USB设备比你想象的要麻烦但也没那么恐怖热词里有“stm32 如何做usb设备”这个问题很典型。STM32F103内部有一个USB 2.0全速外设可以当作USB设备接入主机比如做成USB键盘、USB鼠标、USB串口CDC类或者自定义的HID设备。但它的USB模块和USB OTG不太一样没有内置的PHY也没有VBUS检测等功能需要外部接1.5k上拉电阻到D引脚才能让主机识别到设备连接。做USB设备最崩溃的地方在于USB协议栈的处理。你不光要配置端点、描述符、回调函数还要处理各种控制传输的枚举流程。好在官方提供了USB固件库例程里就有HID键盘、虚拟串口VCP、大容量存储MSC等现成的模板。我的建议是先用虚拟串口CDC模式练手因为它不需要写任何上位机代码直接用串口助手就能看到数据验证枚举和收发过程特别直观。等你能把数据从板子发到电脑再反向收回来USB的神秘感就消失了大半。4.2 定时器捕获测频率输入捕获的实用价值定时器除了做延时、产生PWM还有一个很重要的功能就是输入捕获。通过配置定时器为捕获模式可以测量外部信号的频率和脉宽。这个功能在测速、解码遥控器、测量PWM占空比时非常有用。STM32F103的定时器支持通道1到通道4可以配置为上升沿捕获、下降沿捕获或者双边沿捕获。捕获到边沿时定时器会把当前计数器值锁存到捕获寄存器里同时可以触发中断。做输入捕获测频率的关键是要计算两次上升沿之间的定时器计数差值再除以定时器时钟频率得到周期然后取倒数得到频率。这里最容易出错的是计数器溢出。如果你的输入信号频率很低两次边沿之间的时间超过了定时器最大计数值16位定时器最大65535那就需要开启更新中断用溢出次数来扩展计数范围。这个“溢出补偿”的思路几乎是所有测频测距项目的必备技能。我在做超声波测距的时候就遇到过这个问题。4.3 STM32超声波测距经典HC-SR04的完整处理逻辑超声波测距模块HC-SR04几乎是每个STM32玩家的第一课。它的原理非常简单给Trig引脚一个10us以上的高电平脉冲模块内部会发射超声波然后Echo引脚会返回一个高电平高电平持续的时间就是超声波从发射到接收的总时间。距离 高电平时间 × 声速340m/s / 2。这里的关键不在于发射而在于精准测量Echo高电平的持续时间。最可靠的方式就是使用定时器输入捕获配置定时器为上升沿捕获触发中断后切换为下降沿捕获下降沿到来时读取当前计数值两个值相减再换算成时间。另一个方式是用外部中断加一个软件计时循环但这个方式误差大且容易受中断优先级影响我不推荐。实测下来HC-SR04模块在30cm到1m范围内的测量精度还是不错的超过2m后由于波束发散和回波衰减数据会明显波动可能需要用多个回波取中值滤波。你如果只是做倒车雷达这类简易项目F103的捕获能力完全够用。5. 显示与编码问题的背后逻辑5.1 ILI9341读ID是a1a1一个引脚驱动短路的典型排查案例热词里提到“stm32使用ili9341读id是a1a1”这个问题我遇到过而且相当经典。ILI9341是一款很常见的TFT LCD驱动芯片分辨率240×320通过SPI或并口和MCU通信。很多时候我们会先写一段读ID的代码来验证初始化是否正确正常情况下应该读出0x9341但如果你读出来的ID是a1a1说明通讯链路出了问题常见原因是D/C引脚数据/命令选择没有配置正确或者SPI的时序太快导致数据错位。解决思路分三步第一步降低SPI时钟频率很多初始化失败都是因为SPI跑得太快LCD控制器跟不上第二步仔细检查D/C和CS引脚的GPIO配置确认模式是推挽输出且初始电平正确第三步用逻辑分析仪抓SPI波形对比时序图看MISO上的数据是不是正确的24位ID。读ID时通常会先发送0xD3命令读ID4命令然后在三个字节内依次读到型号、版本和生产商信息。如果你是按照别人的例程接线但读不到先检查引脚是否接对再看初始化序列里是否需要额外的延迟。这个排查过程比单纯“重装驱动”更能帮助你理解SPI协议。5.2 STM32 GBK转UTF8嵌入式里那点Pitfall做屏幕显示或者与服务器交互时经常碰到编码问题。“stm32 gbk转utf8”这个热词背后是无数做物联网项目的人踩过的坑。STM32的数据源可能来自串口接收、DHT传感器、GPS模块也可能是从SD卡读取的文件它们的字符串编码不一定统一。最典型的就是显示中文时如果不做编码转换屏幕上会出现乱码或者发送到服务器的数据变成一堆奇怪的字节。GBK和UTF8之间的转换不是简单的按字节替换而是要查表。GBK采用双字节编码UTF8对汉字部分通常采用三个字节两者之间没有线性换算关系。嵌入式环境里一般有两种做法一是预先把需要显示的字符串转成UTF8编码再写进代码这样运行时不做转换二是挂一张映射表在运行时临时转换。第一种做法适合字符串量少的项目第二种适合动态生成内容的项目。编码转换没有捷径但可以借助一些开源的量化表格或者Python脚本在PC端预生成数组下载到MCU里查表效率很高。这个问题的核心认知是UTF8和GBK的“可见字符串”相同但字节流完全不同调试串口输出时一定要确认PC终端的编码设置和板子一致否则你会看到一篇乱码。6. 通信接口与典型问题排查6.1 UART管脚定义与printf重定向UART是嵌入式开发中最常用、也最容易被忽略的通信接口。很多人用STM32与PC通信用的就是串口转USB模块。F103上有USART1到USART3还有UART4和UART5。每个串口的引脚映射还不完全一样比如USART1的默认引脚是PA9TX和PA10RX但通过重映射功能也可以把它们移到PB6和PB7。如果你发现串口发不出数据第一步不是检查波特率而是先确认你操作的引脚和外部连接是否一致——选错引脚是超级常见的问题。printf重定向也是新手必踩的坑。默认的C库printf是面向文件的你需要用fputc函数把输出重定向到串口发送寄存器同时注意Keil里MicroLib的启用选项。如果你不启用MicroLib链接的时候可能会报半主机模式Semihosting相关的错误那是因为没有实现对应的调试环境接口。我的经验是直接用MicroLib并且把fputc改成串口发送的轮询函数这样printf就能愉快地工作了。6.2 CAN通信突然连不上现场排查思路“stm32 can通信突然连不上”是另一个高频问题。CAN总线在工业控制、车载电子中应用极广STM32F103内置的是bxCAN外设。它突然连不上的原因通常不是代码逻辑突然坏了而是物理层的总线状态变了。排查顺序是这样的先看是不是只有一块板子掉线如果所有节点都掉线说明总线阻抗匹配出问题两个终端节点的120Ω终端电阻可能有一个断了或者总线上有节点把电平拉死。如果只有特定节点掉线重点检查它是不是进入了Bus Off状态这种状态下它会持续发送错误帧导致整个总线都被干扰。调试CAN的时候强烈建议用一个USB-CAN分析仪来抓总线上的报文。它能区分出到底是板子根本没发数据、发了但报文格式不对、还是总线存在电平冲突。如果是波特率不匹配导致的掉线报文会全部显示错误帧此时你只要用波特率扫描功能就能发现真正的速率。这个工具在排查CAN问题上比示波器还高效属于必备配件。6.3 禁用JTAG为什么你的PA15、PB3、PB4不受控很多人在用PA15、PB3、PB4这些引脚做GPIO输出时发现电平完全不受控制查来查去也没错其实原因在于这几个引脚默认是JTAG调试接口。STM32F103的JTAG功能占用PA13JTMS、PA14JTCK、PA15JTDI、PB3JTDO、PB4NJTRST这五个脚。当你把PA15配置成推挽输出时JTAG功能会以更高优先级抢占这个引脚你的软件配置不会生效。解决办法是对GPIO复用时钟和AFIO进行特殊处理在代码里调用GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE)把JTAG模式禁用仅保留SWD。这样PA15、PB3、PB4就可以作为普通GPIO使用了同时你用ST-Link的SWD接口仍然可以下载调试。这个知识在很多项目里都会用到尤其是当引脚不够用的时候。但注意不要禁用SWD——如果你同时禁用SWJ芯片就可能无法再通过ST-Link连接只能通过串口ISP模式苦苦挣扎。7. 进阶方向与热门玩法从单纯点灯到完整小项目的思路转变7.1 VSCode搭建STM32开发环境不依赖Keil也能开发当你开始嫌弃Keil的界面老旧、代码补全太弱、编译速度太慢的时候就可以考虑切换到VSCode EIDE GCC了。这个组合的本质是用开源的arm-none-eabi-gcc工具链编译用OpenOCD或pyOCD完成下载调试VSCode只是作为编辑器壳。EIDE插件几乎是为嵌入式开发量身定做的它可以直接导入Keil工程也可以从零创建GCC工程自动管理编译器的路径和烧录脚本。VSCode环境的优势是代码索引快、支持智能补全、配合clangd能实现精准跳转这些体验远超Keil。但它也有代价工程配置文件是json需要你理解编译器的Include Paths、宏定义和链接脚本。尤其是ld文件如果不小心改错程序可能跑飞。我的经验是先在Keil里把工程调通再导入VSCode做调试和代码阅读等到手里有几个熟练的小项目后再完全切到VSCode。不要一开始就用VSCode否则你会被无穷无尽的配置问题磨掉热情。7.2 五线四相步进电机与伺服电机的485控制热词里的“五线四相步进电机stm32”和“stm32控制伺服电机485”是两个非常实用的运动控制方向。五线四相步进电机通常用ULN2003驱动板驱动只需要四路GPIO输出节拍脉冲就能让电机转动。你需要写一个步进顺序表按A-B-C-D或者A-AB-B-BC-C-CD-D-DA的时序循环输出高电平。关键是控制节拍频率太快了电机丢步太慢了电机振动明显。对F103来说用定时器中断来驱动步进电机的节拍是最合适的方案主循环负责处理其他逻辑。伺服电机和步进电机的控制逻辑完全不一样。伺服电机通常带位置反馈通过485接口发送Modbus RTU或者厂商自定义协议来控制位置、速度和力矩。对STM32来说难点不在发送指令而在于485半双工通信的方向切换——发送时要把DE/RE引脚拉高接收时拉低切换时机不对就会导致数据不完整。我调试伺服电机时踩过的坑是指令帧的CRC校验码算错导致电机没任何反应。CRC16的计算代码网上很多但要确认初始值和多项式与厂商协议一致。运动控制类项目对精度的要求远高于普通的传感采集数据的完整性和时序的稳定性必须优先保障。7.3 STM32接巴法云低成本物联网方案热词里还有“stm32 巴法云”这也是这几年比较火的玩法。巴法云是一个对嵌入式设备友好的物联网平台支持MQTT和HTTP协议提供了免费层级特别适合个人学习和小型产品原型验证。STM32通过ESP8266或者内置的网络模块接入WiFi然后通过MQTT协议订阅和发布主题就能实现远程控制LED、读取温湿度数据、甚至上报设备状态到微信小程序。这个方案的落地难点在于ESP8266的AT指令流控和不稳定问题。ESP8266在弱网环境下会出现TCP连接断开、数据堆积的问题你在代码里必须实现心跳重连机制定时检查WiFi连接状态发现断开就重启模块或者重新初始化连接。另外MQTT的Topic命名和订阅规则要规划清楚设备端和云端保持在同一个Topic的消息格式一致否则服务端收不到有效消息。巴法云这类平台优势是接入门槛低劣势是消息频率和长度有限制适合做“低频控制”和“状态上报”不适合做高吞吐的数据流传输。7.4 从简单项目到毕业设计选题思路和建议如果你正在为“基于STM32的毕业设计”发愁我的建议是不要临时找那种冷门课题。一个高性价比的毕业设计应该是“一个传感器 一个控制器 一种通信方式 一个上位机或APP”。传感器可以选温湿度、超声波、MPU6050陀螺仪、光敏电阻或者称重传感器控制器就是STM32F103本身通信方式可以是串口、CAN、蓝牙、WiFi或者ESP8266 MQTT上位机可以用Qt、Python的PyQt甚至微信小程序。这四个模块任意组合就能构成一个结构完整、工作量适中、答辩可讲清的项目。真正让你脱颖而出的是“控制逻辑的闭环”和“异常处理的完善”。比如同样是做一个倒车雷达能够显示波形、设置报警阈值、记录历史数据、处理传感器盲区的方案和仅仅在LCD上打个数值的方案工作量差不多但评价完全不同。在做项目规划时先想清楚“核心功能是什么”“哪些功能是锦上添花”“哪些功能是最后万不得已可以砍掉的”这样时间安排才合理。F103的性价比和资料丰富度决定了它是一个容错率极高的平台前期多踩坑后面就顺。8. 还在路上我踩过的坑和接下来想做的事这块板子买回来不到两周我已经从单纯的“点灯”走到了串口通信、定时器捕获和简单的传感器采集。说实话STM32的学习曲线并不陡但它的门槛在于“遗忘”——你很容易记住API的名字却忘了为什么要开时钟、为什么用复用功能、为什么引脚会冲突。我目前的习惯是每做一个小功能就写一篇笔记把寄存器配置的关键步骤和调试过程中用到的工具记录下来这样过几个月回来查的时候思路依然是清晰的。如果你也正在学STM32我的建议是准备一个最小系统板搭配一个ST-Link下载器、一块OLED屏、一个HC-SR04和一个按键就足够你折腾两三个月了。不要一上来就买各种高端传感器和模块先把最核心的GPIO、定时器、串口、中断这四个基础吃透再看什么做什么都会轻松很多。接下来我打算写一篇完整的“标准库新建工程保姆级教程”把从安装到点灯的每一步都拆开讲然后做一个用定时器输入捕获测速的小项目。学嵌入式这件事本来就是一点一点“磨”出来的能坚持到把第一个自己设计的小系统跑起来你就算正式入门了。