
1. 板子到手别急着连电脑先把这三件事确认了1.1 芯片丝印和第一脚位置一眼就要认准很多新手拿到 STM32F103 开发板的第一反应就是插USB、打开下载软件、找例程烧录。我建议你先别急先把板子翻过来看一遍芯片丝印顺手确认两个信息具体型号和第一脚位置。STM32F103 这个家族后缀差异很大。常见的 C8T6 是 64KB Flash、20KB RAM48 引脚小封装RBT6 是 128KB Flash、20KB RAMZET6 是 512KB Flash、64KB RAM144 引脚大封装。同样是 F103Flash 差了 8 倍后面做复杂项目时选错型号会让你怀疑人生。商家标“stm32-103 开发板”只是个笼统说法具体是 C8T6 还是 ZET6直接决定你将来能不能跑带操作系统的项目、能不能塞下完整的字库或者录音数据。还有第一脚确认。贴片芯片上一般有个圆形凹点或者斜切角正对着这个标记的左下角就是第 1 脚然后逆时针数。你在网上查原理图、看引脚定义表、接杜邦线的时候第一脚认错后面全乱。特别是想外接传感器或者自己画扩展板第一脚搞反轻则没反应重则直接顶到电源和地。1.2 BOOT0 跳线帽别嫌它麻烦F103 的启动模式由 BOOT0 和 BOOT1 两个引脚决定。绝大多数开发板上只留了一个 BOOT0 跳线帽BOOT1 默认接地。跳线帽在 0 的时候芯片从主 Flash 启动这是正常跑用户程序的状态跳到 1 的时候芯片从系统存储器启动也就是进入内置 bootloader这时候才能通过串口烧录。这里要记住一个很常见的操作用串口 ISP 方式烧录时一般要先跳到 BOOT01复位然后用下载软件连接烧完再把跳线帽拨回 0再次复位程序才会从 Flash 跑起来。很多新手烧完发现板子还是没动静其实就是忘了把跳线帽拨回去。我自己的习惯是下载完立刻拨回 0养成肌肉记忆不然下次上电就成“砖头”了。另外注意BOOT01 状态下不要插着 USB 转串口模块来回通断电源某些抄板设计在 boot 模式下会出现 IO 电平冲突虽然绝大多数情况下只是烧不进东西而不是烧坏芯片但没必要去赌这个风险。先短接复位、再拔线操作顺序稳一点板子寿命长一点。1.3 USB转串口芯片和驱动先把这个根基打好F103 开发板上几乎都集成了一颗 USB 转 TTL 芯片常见的有 CH340、CP2102、FT232。系统没识别到 COM 口后面的串口打印、ISP 烧录全部无从谈起。你插上 USB 线之后打开设备管理器如果看到带黄色感叹号的未知设备那就先装驱动。CH340 去官网下或者装驱动精灵一类的工具都能解决CP2102 装 Silicon Labs 的驱动FT232 通常 Windows 10 以上系统自带。装好驱动之后记住端口号比如 COM3。串口调试助手选波特率 115200 或者 9600跟你的工程配置对上才能看到 printf 输出。这一步虽然不起眼但是整个学习过程中你要靠它排掉大量问题。我见过太多人卡在“串口没反应”结果查了半天是电脑自动把 COM 口从 COM3 变成了 COM10或者驱动被安全软件静默卸载这种环境问题比代码问题更浪费时间。2. 开发环境搭建Keil5、芯片包和VSCode的配合2.1 Keil MDK与C51共存安装顺序和路径要记牢很多人是从 51 单片机转过来的电脑里可能已经装了一个 Keil C51。再来装 Keil MDK 的时候要注意 MDK 的安装路径和 C51 的安装路径最好是分开的比如一个装在 D:\Keil_v5另一个装在 D\KeilC51。理论上两个版本可以共存但如果你把 MDK 装到原来 C51 的目录里轻则提示文件冲突重则启动崩溃。安装 MDK 时安装包叫 MDK-ARM不是老的 Keil4。装完以后打开软件你会看到 Project - Manage - Pack Installer这一步就是用来装芯片包的。如果打开 Pack Installer 网络连不上可以去官网手动下载 STM32F1xx_DFP 这个 Pack 文件双击导入。没有这个 Pack你在 Device 列表里根本找不到“STM32F103C8”这个选项新建工程自然无从下手。2.2 新建工程时选哪个库标准库还是HAL库f103 的工程模板大体分两种流派一种是老教程最常见标准外设库Standard Peripheral Library英文缩写 STD 库另一种是 ST 主推的 HAL 库配 CubeMX 生成。标准库的好处是寄存器封装得很薄你看库函数源码能直接跟数据手册对应上非常适合学习原理。HAL 库的好处是 CubeMX 图形化配置外设初始化代码自动生成出工程快但里面套了一层又一层出了问题追起来费劲。我的建议是如果你刚开始学 F103先走标准库。网上大量教程、例程、毕设代码都是标准库风格的你现在把标准库搞明白了以后哪怕转 HAL 库思路也是通的。选择 F103 对应的库版本时注意一个“密度”问题启动文件有 ld.s、md.s、hd.s 之分C8T6 用 md.sZET6 用 hd.s选错了启动文件工程通常会报错或者直接跑飞。这个“密度”指的是 Flash 容量不是板子尺寸很多人一看到这几个文件就懵其实记住 C8/RB 用 mdZE 用 hd 就够了。2.3 VSCode里编译成功却怎么也烧录不进开发板这是这两年非常高频的搜索词因为越来越多人在 VSCode 里装 ARM 工具链、用 CMake 或者 EIDE 插件编译工程。编译成功了说明你的代码没问题、编译器配置没问题但下载这一步是独立的环节。烧录不进去先别怀疑 VSCode要单独查下载器和烧录配置。2.4 烧录失败的几个真实原因我把遇到过的烧录失败原因按频率排个序ST-Link 驱动没装好设备管理器里识别不到。ST-Link 需要装单独的驱动不少人以为插上就能用结果 OpenOCD 报“no device found”。线序接反了。SWD 接口常用四根线SWDIO、SWCLK、GND、3.3V。杜邦线一多就容易搞混尤其是把 SWDIO 和 SWCLK 交叉怎么连都是“cannot connect”。芯片型号选错。有的烧录软件需要你手动选 STM32F103C8有的开发板和下载器是自动检测但检测不准就会失败。你的程序里把 SWJ 调试口给禁用掉了。这是最坑的一种情况有人抄例程时为了省 IO调用了GPIO_Remap_SWJ_Disable或者类似函数把 SWD 引脚复用成普通 GPIO程序烧进去之后下载器就再也连不上芯片了。遇到下载不了尤其是之前还能下载、下载某次程序之后突然连不上的情况优先怀疑程序把调试口关了。解决办法是按住板子上的复位键点下载在下载器发出连接信号前的一瞬间松开复位让芯片在复位状态下进入调试模式。或者直接把 BOOT0 跳到 1让芯片跑 bootloader再烧一个空程序进去把 Flash 清掉。这个“按住复位烧录”的技巧是调试口被封之后最实用的救命招。3. 点灯之后第一个真正有价值的外设是定时器3.1 delay函数一烧进去就卡死问题出在时钟基准很多人的第一个实验是点灯点灯只要配置 GPIO 就行了不会卡死。真正开始卡死是在调用延时函数之后。“stm32 延时函数 delay 卡死”这个搜索词能排到热门说明踩坑的人非常多。先说明原理。不管是标准库里的SysTick_Handler配合延时函数还是简单粗暴的for循环空转延时都依赖一个正确的时钟基准。F103 默认外部晶振 8MHz经 PLL 倍频到 72MHz 作为系统时钟SysTick定时器再从 72MHz 分频出 1ms 的 tick。如果你的工程模板里没调用SystemInit()或者startup_stm32f10x_md.s里没正确执行系统初始化时钟还是默认的内部 8MHz那么延时时间会完全对不上严重时while (tick delay)永远等不到 tick 变化函数就卡死了。HAL 库环境则要注意HAL_Init()必须先于HAL_Delay()调用。HAL_Delay内部读的是uwTick这个变量是在 SysTick 中断里加的SysTick 没配好uwTick永远是 0delay 死循环。如果你在自己的中断函数里写了很长的处理逻辑而且中断优先级比 SysTick 高也会导致 SysTick 中断抢不到 CPUHAL_Delay表现为卡死。解决方法是中断处理函数尽量短不要在中断里做超长延时。3.2 用定时器捕获测频率和超声波测距定时器捕获是 F103 比较有魅力的功能之一。网上搜“stm32 定时器捕获测频率”和“stm32 超声波测距”核心都是输入捕获。输入捕获的原理就是定时器内部有个计数器每个时钟周期加 1当外部引脚出现上升沿时硬件自动把当前计数器的值存到一个寄存器里同时触发中断。你连续捕获两个上升沿的计数值相减后再除以计数频率就得到了信号的周期倒数就是频率。举个例子定时器 TIM2 挂在 APB1 总线上时钟 72MHz你设置预分频 PSC 71那么计数器频率是 72MHz / (711) 1MHz也就是计数周期 1 微秒。如果捕获到两次上升沿的计数值差是 500那信号周期就是 500 微秒频率 2kHz。这个计算方法我建议自己推一遍不要只会套 CubeMX 生成的代码。超声波模块 HC-SR04 用捕获就更直观了。你先给 Trig 引脚拉一个 10 微秒以上的高电平模块会发射超声波然后 Echo 引脚输出一个高电平高电平持续时间就是超声波从发射到碰到障碍物再返回的时间。用定时器捕获 Echo 引脚的两个沿上升沿和下降沿得到脉宽再用脉宽乘以声速 340m/s 除以 2就是距离。用定时器捕获而不是单纯while等待是因为声波传播时间只有几十到几百微秒用delay精度太差用捕获误差能控制在微秒级。3.3 电机控制里定时器的作用搜词里还有“五线四相步进电机 stm32”和“stm32 控制伺服电机 485”。步进电机的核心是控制脉冲的个数和频率一个脉冲走一步脉冲频率决定转速。F103 的定时器输出比较功能直接产生可调频率的方波比如你配置 PWM 输出模式改变自动重装载值 ARR 就能改变频率改变比较值 CCR 改变占空比。如果是五线四相步进电机还需要按 A-B-C-D 相的顺序轮流通电这就要用 GPIO 配合定时器中断或者用定时器更新事件来切换相序。伺服电机 485 控制走的是 Modbus RTU 协议你用 UART 发一串报文给伺服驱动器驱动器解析后执行位置或速度指令。这种场景下定时器更多是用来做周期轮询和超时判断因为 485 通信是半双工发完数据要等从机应答如果从机没反应需要定时超时重试。电路上要注意 RS485 收发器的 DIR 方向控制引脚发送之前拉高发送完拉低很多人忘了这个方向切换导致只能发不能收。4. 屏幕、CAN通信和“玄学”问题4.1 读ILI9341的ID总是0xA1A1八成不是屏坏了玩显示屏幕的朋友一定经历过“stm32 使用 ili9341 读 id 是 a1a1”这种问题。ILI9341 是一颗非常常用的 TFT LCD 驱动芯片很多 2.4 寸、2.8 寸屏都用它。程序里通常会先读取屏幕 ID 来判断驱动芯片型号再决定后续初始化参数。读出来 0xA1A1首先要理解这不是一个正常芯片 ID。正常工作的时候ILI9341 通过读寄存器 0xD3 能取回 3 个字节通常是 0x00 0x93 0x41其中 0x93 代表这个屏幕由 ILI9341 驱动。而你读回 0xA1A1说明通信链路是通的但数据对不上或者发的命令压根没被正确执行。我看到过很多例程用LCD_Read_ID函数直接发 0x04 命令但 ILI9341 读 ID 的标准命令其实是 0xD3而 0x04 是读状态。如果你是在某宝屏例程里抄的最好先对照芯片手册确认命令。接下来按顺序排查先检查 SPI 模式是否一致ILI9341 通常工作在 SPI Mode 0CPOL0CPHA0少数屏要求 Mode 2初始化顺序错了就会出现读到 0xA1A1 这种“看似有数据、实则是垃圾”的结果。再检查 DC 引脚命令和数据区分靠 DC 电平DC 接反或者初始化顺序不对屏幕会把命令当成数据或者反过来。最后检查复位时序ILI9341 上电后需要拉低 RST 至少 10 微秒再释放有些模块的 RST 脚被漏接了程序里也没做软件复位那读啥都可能不对。一个实操技巧把读 ID 这段逻辑单独跑用逻辑分析仪抓 MISO 引脚的信号看返回数据的波形如果看到的是高电平堆叠那就是 MISO 线没接好或者 SPI 配置成只写了如果是 0x00 0x93 0x41那说明屏没问题问题在前面初始化顺序。屏幕驱动这种东西80% 的问题都在接线和时序上芯片很少自己坏掉。4.2 CAN通信突然连不上终端电阻是永恒的怀疑对象CAN 总线在工业场景里很常用F103 的 bxCAN 外设支持标准帧和扩展帧。搜词里有个“stm32 can 通信突然连不上”这个“突然”特别有意思因为它往往意味着硬件上的不稳定状态而不是代码逻辑一开始就错。常见的连线问题排第一。CAN 总线两端必须各接一个 120 欧姆终端电阻用来匹配传输线阻抗防止信号反射。如果你只接了一块开发板那也要在板子附近接上这个电阻否则会看到通信时好时坏。有些小型开发板上已经焊了 120 欧姆电阻有些没有你要先看原理图确认。我见过有人用两根几十厘米的杜邦线接 CANH 和 CANL没加终端电阻低速时没问题提高到 500kbps 就丢帧加上终端电阻瞬间稳定。第二个常见原因是波特率不一致。CAN 是异步总线通信双方的波特率必须一致而且因为 CAN 没有时钟同步线靠的是位同步机制所以波特率误差要在 ±0.5% 以内。你用 250kbps 的工程去和 500kbps 的设备通信表现就是完全连不上或者时断时续。校准波特率时注意 APB1 时钟要和你的预分频、位时间配置匹配网上有 CAN 波特率计算器把 APB1 时钟填进去直接生成参数比自己手算可靠。还有一个很多人忽略的坑过滤器配置。F103 的 CAN 外设有多个过滤器组如果过滤器配置成只接收特定 ID其他帧全部丢弃那你会看到“明明总线上有数据但自己的程序收不到”。排查这种问题先把过滤器配置成接收全部消息也就是屏蔽位全 0、标识符寄存器全 0看能不能收到数据能收到再慢慢往里加过滤条件。4.3 把F103做成USB设备时钟和上拉电阻是两大门槛“stm32 如何做 usb 设备”在热搜里排得靠前。F103 内置了 USB 2.0 全速设备控制器可以做 HID 键盘、鼠标、自定义设备、虚拟串口等等。但它的 USB 对时钟要求很严格USB 全速模式需要精确的 48MHz 时钟。开发板如果外部晶振是 8MHz系统时钟倍频到 72MHz那 USB 时钟可以从 PLL 输出里分频得到72MHz 除以 1.5 正好是 48MHz。工程里要调用RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_1Div5)并打开 USB 时钟。如果你用了内部 HSI 时钟除非有专门校准的流程否则 USB 设备往往无法被电脑正常识别因为时钟精度不够。第二个门槛是 D 引脚的上拉电阻。USB 设备端 D 线上需要接一个 1.5kΩ 上拉到 3.3V用来告诉主机“这是一个全速设备”。很多成品板已经把上拉电阻集成进去了程序控制一个 GPIO 在初始化时把上拉打开但有些最小系统板没做这个设计需要你自己外接一个 1.5kΩ 电阻。这个细节不解决电脑永远提示“无法识别 USB 设备”。调试 USB 时尽量用短而粗的 USB 线不要用十几米的延长线更不要用那种花里胡哨的磁环线。USB 信号频率不高但波形质量要求不低线材太差会导致枚举失败。如果代码逻辑没问题换个 USB 口或者换个数据线经常能解决。5. 下一步做什么把学习拆成能验收的小项目5.1 从鱼缸控制器到巴法云把外设串起来很多人学完点灯、串口、定时器之后就不知道该干嘛了然后陷入“看教程—抄代码—再换下一个教程”的循环。我给一个可落地的建议找一个生活场景把几个外设组合成一个完整的小项目。搜词里恰好有两个现成的例子“stm32 鱼缸”和“stm32 巴法云”。鱼缸控制器可以拆成这些功能DS18B20 温度传感器用单总线读水温水位传感器检测缺水定时器控制水泵定时换水OLED 屏显示温度和状态再加一个按键设置阈值。这里涉及的模块分别是单总线、ADC、定时器、I2C/SPI 显示、GPIO 按键每一个都是 F103 学习的核心知识点。做完这个项目你对整个芯片的常用外设就有基本盘了。如果想联网可以再加一个 ESP8266 或者 ESP-01 模块通过串口 AT 指令连接 Wi-Fi再通过 MQTT 协议接入巴法云服务。巴法云是国内免费的物联网平台用起来很简单拿到一个主题 ID设备和手机端都订阅这个主题就能互相发消息。实际接口流程是STM32 通过串口给 ESP8266 发 ATCWMODE、ATCWJAP 等指令建立网络连接然后走 MQTT 的 TCP 端口发送 PUBLISH 报文。这部分代码不算复杂但是能把串口通信、字符串解析、TCP 协议全串起来对未来做物联网开发非常有帮助。5.2 调试工具要跟上逻辑分析仪和示波器差价很大个人学习调试全套示波器太贵但至少要有万用表和逻辑分析仪。万用表几十块钱就能搞定测电压、测通断非常实用。逻辑分析仪也只要几十块钱能同时抓多路信号波形调试 SPI、I2C、UART 总线时看波形比看代码猜快得多。我自己的经验是遇到看似“玄学”的问题先用逻辑分析仪抓引脚波形。比如读 ILI9341 ID 是 0xA1A1抓一下 MOSI 和 MISO 就知道命令有没有发出去CAN 通信不通抓一下 CANH 和 CANL 的差分波形就知道报文有没有上总线。你可能会发现很多现象其实是信号压根没出现或者是时序电平不对这样就直接把问题定位到硬件层而不是在软件里瞎改。5.3 一个值得养成的习惯修改前先备份可用的工程学 STM32 的过程本质是在不断“折腾”。折腾免不了把好用的工程改坏。我强烈建议你把每个能正常编译、正常下载、正常运行的工程单独存一份到一个working_backup目录里即使只是点个灯也值得备份。真要改坏了分分钟就能回退不用从头再来。另一个习惯是给工程加注释尤其是初始化顺序、关键参数的计算过程。你过一个月回头看自己写的代码如果没有注释一定会问“当时为什么这么配”。我在自己的例程里会把定时器预分频的计算式直接写在注释里比如PSC71 - 72MHz/(711)1MHz这样以后换芯片、换时钟频率照着改一遍就能复用。现在这块 F103 板子已经在你手里了从点灯到串口从定时器到屏幕再到 CAN 和 USB够折腾一阵子。建议先把它基础外设都过一遍别急着上操作系统也先别跳去学 F4、H7。同一个系列跑得越熟后面学什么都快。