
快递是昨天下午到的。拆掉那层防震泡沫里面是一块蓝色PCB的STM32F103C8T6最小系统板、一个ST-Link V2下载器、一根USB线还有一小把杜邦线。从今天开始我正式进入STM32的世界。STM32F103这颗芯片在嵌入式圈子里几乎人手一块Cortex-M3内核、最高72MHz主频片上集成了GPIO、USART、SPI、I2C、定时器、ADC、CAN和USB等等外设价格却低得不像话所以很多人把它叫做入门神板。这个系列我会一个坑一个坑地记录从环境搭建到具体项目想学STM32的朋友点个关注咱们一起折腾。不管你是从51单片机跳过来还是完全没有单片机基础这块板子都能让你一步步看到代码是怎么变成现实物理动作的。1. 为什么是STM32F103而不是其它开发板1.1 入门为什么绕不开F103很多人问过一个特别现实的问题都这个年头了为什么还要拿STM32F103入门我的答案很简单它的学习成本极低知识密度却很高。F103C8T6这颗芯片主频72MHz片上Flash官方标称64KBSRAM 20KB虽然容量不大但麻雀虽小五脏俱全。两个I2C、两个SPI、三个USART、四个定时器、一个CAN控制器、一个USB Device外设单片机领域常用的通信接口它基本包圆了。你后面想用到的超声波测距、TFT屏幕显示、步进电机控制、485伺服通信、CAN组网全都能在这一颗芯片上跑起来。对比更早的51单片机F103的复杂度恰好跨了一个台阶。51更多是直接操作IO口F103则引入了时钟树、中断控制器NVIC、外设总线、引脚复用这些核心概念。这些概念一旦真正搞明白再去看其它ARM芯片都会轻松很多。对比ArduinoArduino把底层封装得太过友好代码写起来很爽但时间长了容易只会调库连寄存器长什么样都不清楚。对比ESP32这类带Wi-Fi和蓝牙的芯片它功能确实强大可上手时无线协议栈会分散太多精力价格功耗也不如F103这么适合纯练手。所以我的结论很明确想系统学单片机F103作为第一块芯片依然是最稳的起点。1.2 最小系统板还是大板子市面上STM32F103开发板主要分两类。一类是蓝色PCB的最小系统板最常见的C8T6核心板十几二十几块钱一片。板载一颗AMS1117稳压管、一个USB转串口芯片所有引脚都通过排针引出来。另一类是正点原子、野火或普中那种完整开发板板载LED、按键、屏幕接口、传感器、Flash甚至CAN收发器价格从一百到几百不等。我个人更推荐新手上手先买最小系统板加几个模块。原因很朴素引脚自己接一遍比看板载连线印象要深得多排错能力也正是在这个过程中练出来的。看到热搜里有人在问“普中A2开发板使用数码管与DS1302模块做时钟”那种整板确实省事模块都焊好了直接学时序。但如果你跟着我这套记录来学最小系统板加ST-Link加几块基础模块总成本控制在50元左右就够了。今天玩超声波就插超声波明天玩屏幕就插屏幕不会浪费后面想进阶再买整板也不迟。1.3 四步学习路线规划STM32F103的外设实在太多如果不做规划新手很容易看花眼。我给自己定的路线是四个阶段。第一步点灯和GPIO。目标是把引脚模式、上下拉电阻、复用功能搞懂让板子第一次听你指挥。第二步串口、中断和定时器。目标是能用串口打印调试信息、用定时器产生PWM、测频率和脉宽这是后面所有项目的地基。第三步系统攻破通信协议SPI、I2C、ADC让屏幕、传感器、RTC芯片之类的模块都能接进来。第四步综合项目从超声波测距、ILI9341屏幕、步进电机、CAN通信到485伺服控制再尝试USB设备和物联网云平台。为什么必须按这个顺序因为我看过太多人一上来就想驱动TFT屏幕或者搞CAN通信结果中断优先级、时钟配置、引脚复用全部卡住最后怀疑自己买到假芯片。其实屏幕上那些诡异ID比如很多人搜过“ILI9341读ID是0xA1A1”追根溯源都是基础没打通导致的。步子放稳正反馈才能建立起来不然很容易在第一天就劝退。2. 开发环境搭建新手的第一座大山2.1 Keil、CubeMX、VSCode各自扮演什么角色先把工具的角色说清楚。Keil MDK是老牌IDE负责编译、下载和在线调试STM32CubeMX是ST官方图形化配置工具选芯片型号、点引脚、配时钟和外设然后一键生成初始化代码VSCode是编辑器配合插件也能写STM32工程适合习惯现代编辑器的人。这三者不是替代关系可以配合使用。很多人搜过“Keil5兼容C51和STM32安装”这里多说一句。C51和STM32用的是两套完全不同的编译器Keil MDK本身不编译51程序。网上流传的“一个Keil搞定两个平台”本质是额外装51的Pack实际用起来芯片包非常容易串。我的做法是分开装MDK专门搞STM32C51版Keil单独留着搞51别去折腾那个兼容模式省下的时间够你写完一个点灯程序了。2.2 芯片包和固件库的选择MDK装完之后第一步是在Pack Installer里安装STM32F1xx系列的Device Family Pack。找不到的话就手动下载Keil.STM32F1xx_DFP.pack再双击安装。安装路径和工程路径都不能有中文和空格否则编译会报一堆匪夷所思的未定义错误。芯片包是MDK识别芯片型号、外设寄存器定义和Flash烧写算法的关键。没装对口你选不到STM32F103C8下载报“No target connected”基本都是这一步没弄好。固件库方面现在摆在你面前的有标准库、HAL库和LL库。标准库是老工程师最熟悉的网上资料最多但ST已停止维护新项目很少再用。HAL库是CubeMX自动生成代码所依托的库抽象层更厚但代码移植性好。我的建议是新手直接学HAL库因为你用CubeMX配置一次引脚、时钟和串口生成的代码就能跑省掉大量手写初始化的时间。但千万别只会拖图形界面遇到问题要能打开stm32f1xx_hal_gpio.c这类源文件去读底层实现。图形界面给你建好了高速公路你得知道路下面是什么结构。2.3 下载器、调试器与ld文件下载器强烈建议ST-Link V2十几块钱一个速度比串口ISP快得多还能在线打断点。Keil里在Options for Target的Debug页选择ST-Link Debugger然后在Settings里确认SWD模式能识别到芯片再到Utilities页勾选对应的Flash算法。如果用的是J-Link内核类型一定要选Cortex-M3SWDIO、SWCLK、GND三根线不要接反。很多“识别不到芯片”的求助帖最后查出来都是杜邦线接触不良或者驱动没装干净。要是你习惯VSCode可以用EIDE或STM32CubeCLT搭配CMake然后在launch.json里指定可执行文件路径、调试器类型和svd文件。热搜里有“vscode stm32调试powerlink如何设置launch.json”其实就是把configName设为cortex-debugservertype设成jlink或stlinksvdPath指向stm32f103.svd。这套配置好一次之后调试体验确实比Keil清爽很多。还有不少人问ld文件是干什么用的。它是GCC工具链的链接脚本定义了Flash和RAM的起始地址与大小以及中断向量表位置。纯Keil工程里对应的是分散加载文件。做应用开发一般不用改只有你想自定义Bootloader、把某段代码放到指定地址时才需要碰它。建议先知道它是干什么的等真有需求了再仔细研究没必要一上来就啃。2.4 时钟树是隐蔽的大坑F103上电后默认使用内部8MHz RC时钟也就是HSI精度一般而且外设频率配置会受限。大多数开发板上都有一颗8MHz外部晶振HSE。在CubeMX的RCC设置里必须选Crystal/Ceramic Resonator然后在Clock Configuration里把系统时钟SYSCLK配到72MHz。具体路径就是HSE经过PLL锁相环9倍频再经过AHB预分频逐级分发到APB1和APB2总线。这个步骤为什么重要因为串口波特率是从APB2总线时钟分频来的定时器计数频率来自APB1或APB2时钟。总线时钟没配好外设时序全都会错。网上一堆“串口乱码”“delay不准”的求助最后查出来几乎都是时钟树配置不对。新手阶段千万别跳过CubeMX手写时钟配置图形界面里点一遍就能把从HSE到AHB再到APB1/APB2的完整路径看得明明白白。等你看懂了这个树才算真正摸到F103的地基。3. 基础外设逐个跑通3.1 GPIO点灯先建立正反馈我上手后做的第一件事就是让一个LED按一秒钟的频率闪烁。CubeMX里把PA5设为GPIO_Output在生成的main函数里打开用户代码区写两个IO操作。代码非常简单while (1) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); HAL_Delay(500); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); HAL_Delay(500); }但这里有几个新手容易忽略的细节。第一确认你板子上的LED到底接在哪个引脚。一些蓝色最小系统板板载LED在PC13有些则根本没焊LED需要外接。第二注意LED的极性很多模块是共阳结构低电平点亮。代码逻辑和硬件极性搞反了灯就不会亮此时你第一反应往往是怀疑代码其实应该先看原理图。GPIO模式的选择也很关键。推挽输出适合驱动LED这种简单负载带负载能力强开漏输出则需要外部上拉电阻适合I2C这类“线与”结构。输入方面要区分上拉和下拉机械按键这类场景通常用上拉输入按键按下时引脚变低电平。这些模式不搞懂后面接个传感器也会一头雾水。点灯虽然简单但它是理解引脚复用和电平逻辑的第一次实操值得认真对待。3.2 串口调试之眼跑通点灯后第二个我必跑的外设是串口。F103的USART1默认引脚是PA9和PA10分别对应TX和RX在CubeMX里勾选后就能用。波特率设成1152008位数据无校验1位停止位调用HAL_UART_Transmit就能把数据发到电脑串口助手。每次写这个函数太啰嗦我直接做了printf重定向int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 0xFFFF); return ch; }工程设置里勾上MicroLIB就能愉快地使用printf打印调试信息了。我日常调试全靠这行输出变量值、执行状态、函数入口出口全都通过串口拉出来看。后面做闭环控制调PID参数时更离不开它把实时数据通过串口扔到上位机画波形比盲猜参数快太多了。如果你发现打印出来全是乱码优先怀疑时钟配置和波特率没对齐这两个原因占了九成。3.3 定时器PWM和输入捕获测频率定时器这部分值得多花耐心。F103里有基本定时器TIM6/TIM7通用定时器TIM2到TIM5还有高级定时器TIM1/TIM8。做PWM输出用通用定时器足够做电机控制就得用到高级定时器的互补通道和刹车功能。PWM要先理解两个数预分频值PSC和自动重装值ARR。定时器输入时钟先除以PSC得到计数时钟然后计数器从0数到ARR触发更新事件。比如把PSC设为7172MHz输入时钟就变成1MHz计数时钟ARR设为999PWM周期就是1ms频率1kHz。调节比较寄存器CCR的值就能改占空比。为什么这么算记住公式就行PWM频率 定时器时钟 / (PSC1) / (ARR1)。我一开始总是不明白PSC为什么要加一后来才反应过来硬件上预分频器就是0到PSC共PSC1个时钟周期才输出一个计数脉冲。测频率是另一个高频需求。配置定时器的某个通道为输入捕获模式上升沿触发每次上升沿到来时硬件会把当前计数值捕获到CCR寄存器。两次捕获值之差就是信号周期对应的计数值再乘以计数时钟周期就是信号周期取倒数得到频率。PSC设为71时计数时钟为1MHz每个计数值恰好代表1微秒测周期就是做一次减法。这个功能后面做超声波测距时还要用到只不过从测周期变成测高电平脉宽。我强烈建议把输入捕获的例程存一份后面能省很多事。3.4 中断和那个“卡死”的Delay中断是单片机响应外部事件的核心机制分为外部中断EXTI、定时器中断、串口中断等。CubeMX里给中断配置NVIC优先级时要注意分组方式。比如选中优先级分组4那么4位全部作为抢占优先级如果分组2则2位抢占、2位子优先级。优先级配置混乱很有可能会遇到系统莫名卡死的现象。“stm32延时函数delay卡死”这个搜索词热度非常高排查思路其实就三条。第一时钟有没有配好SysTick依赖系统时钟工作时钟没起来Delay必然卡死。第二是不是在中断回调函数里调用了HAL_Delay如果当前中断的优先级比SysTick高SysTick中断永远得不到执行程序就死在那里。第三是否在移植代码时无意中关闭了SysTick中断。很多例程改了NVIC配置却没处理好SysTick的中断优先级结果程序跑得悄无声息。遇到死机不要慌按这三条逐个查大多数都能定位。4. 结合实战的项目玩法4.1 ILI9341读ID是0xA1A1一次完整的排障我还没正式写屏幕驱动先把一个被大量搜索的经典坑拆开讲讲ILI9341读ID读到0xA1A1。正常情况ILI9341通过SPI读ID应该返回0x9341。出现0xA1A1意味着SPI链路根本没建立起有效通信。有些人读到的还是0xFFFF或其它怪值原因大同小异。按我的排障顺序来。第一步检查接线。ILI9341模块一般有VCC、GND、CS、RESET、DC/RS、SDIMOSI、SCK、LED这些引脚有些模块还会引出MISO。很多板子读不了ID就是因为MISO没接或者模块上根本没引出这个脚。第二步检查时序参数。软件模拟SPI时CPOL和CPHA必须匹配ILI9341在模式0或模式3都能工作但读操作时主机也必须持续提供SCK时钟而且数据位序要按高位在前。第三步检查复位时序。RESET引脚不能悬空至少拉低10微秒再拉高。很多电子模块上电初始化都没做好复位后续操作自然一塌糊涂。第四步检查D/C引脚。读ID时要区分命令周期和数据周期D/C引脚接反了芯片会把ID当成命令处理返回结果自然乱七八糟。第五步把SPI速度降下来。硬件SPI配置的时钟分频系数太大高速采样出错先降到低速验证链路是否正常。这些都没问题还是A1A1还有个补救办法跳过读ID直接按ILI9341初始化序列配置寄存器。但这是治标不治本因为后续写显存同样要经过这条SPI链路链路有问题迟早还会暴露。读ID相当于握手握手失败说明基础通信没通畅别急着怀疑芯片。4.2 超声波测距用定时器给声波掐表超声波模块HC-SR04的测距原理很直观。给Trig引脚送一个至少10微秒的高电平脉冲模块内部发射8个40kHz超声波然后Echo引脚输出一段高电平。这段高电平的持续时间就是声波从发射到碰到障碍物再返回的时间。声速约340米每秒所以距离厘米等于高电平时间微秒乘以0.034除以2也就是乘以0.0171。用普通Delay读Echo引脚也能测但精度受主循环调度影响太大。最稳的方式是用之前讲过的定时器输入捕获。PSC设为71后计数时钟1MHzEcho上升沿触发捕获记录计数值下降沿再捕获一次两者之差就是高电平持续微秒数。中断回调里做差值计算直接套公式得出距离。这里有个经验要提醒Echo高电平时间最长能到20多毫秒超过这个时间还没等到下降沿基本就是前方没有障碍物或者接线有问题必须做超时处理。我实测时第一次算出来的距离总是偏大后来推了一遍公式才发现把声速单位换算弄错了。这个项目把GPIO输出、定时器输入捕获、串口打印全部串起来特别适合作为第一个完整小项目。4.3 步进电机和DS3231这些模块型小活最小系统板搭配模块玩起来特别上瘾。五线四相步进电机28BYJ-48配上ULN2003驱动板是入门电机驱动的好素材。驱动逻辑就是按顺序给四相线圈通电常见的1-2相激励方式是A、AB、B、BC、C、CD、D、DA这八个节拍循环。每给一个节拍电机转一个最小步距角5.625度经过内部64比1的减速齿轮后输出轴精度更高。用普通GPIO延时就能转但要注意驱动板电源和逻辑电源要共地否则容易丢步转角就不准了。DS3231是另一块热门模块。这是一颗带I2C接口的实时时钟芯片内部集成温度补偿晶振一年误差通常在两分钟以内比F103内部RTC强太多。接线就用I2C引脚比如PB6接SCL、PB7接SDA然后读取时间寄存器。很多人拿它配合TFT屏幕做桌面电子钟屏幕显示时分秒夜里当钟表用。它的操作不复杂重点在于理解I2C总线的起始、停止、应答时序学会之后其他I2C传感器基本都会接了。4.4 CAN通信与485伺服控制F103自带一个CAN控制器做工业通信类项目非常合适。新手玩CAN最常见的坑是“两个板子发着发着突然就连不上了”。排查顺序要固定。第一总线两端是否各接了一个120欧终端电阻没有终端电阻信号会反射高波特率下必出问题。第二两端波特率是否完全一致CAN波特率由TQ和分段时间单元决定最好用CubeMX显式配置成相同参数。第三确认两个节点是否共地CAN收发器之间不共地会出现各种间歇性怪现象。第四先用回环模式Self Test自检回环模式下不接外部节点也能自发自收。这样一来问题就能快速定位在MCU配置还是外部接线。485控制伺服电机是工业现场特别典型的需求。RS485是半双工总线除了A、B两根差分线还需要一个方向控制引脚。发送前拉高DE发完之后必须延时再拉低否则数据会被自己打断。上层协议方面伺服驱动器普遍支持MODBUS-RTU网上有开源的agile_modbus可以直接移植省去自己写CRC校验和拆帧的麻烦。核心还是先看伺服手册里的寄存器地址表把控制字和状态字读明白。这个项目做完你对“现场总线”的理解会比闭门造车深得多。4.5 USB设备和物联网方向F103的USB外设虽然速度不高但做HID设备或虚拟串口绰绰有余。比如把板子配置成一个USB键盘CubeMX里打开USB功能选择HID类定义好报告描述符插到电脑上就是免驱键盘。这个项目能让人真正理解设备描述符、端点、传输类型这些概念比单纯点灯有技术含量得多。如果想做更实用的还可以让它枚举成虚拟串口代替USB转TTL调试工具效果完全能打。物联网方向最经济的方式是STM32外挂ESP8266模块用AT指令让模块连接Wi-Fi再通过MQTT协议接入巴法云这类平台。手机端只要能上网就能看到开发板上传感器的数据也能远程控制继电器。做出来的效果其实就是个小型智能家居网关但整个链路从硬件接线、协议封装、云平台配置到手机端显示全是你自己搭的学习价值极高。这些项目不建议一开始就碰先把串口、定时器、I2C这些基础玩熟再上手整个体验会顺畅很多不然会同时面对硬件、协议、平台三座大山很容易崩。5. 新手常见问题速查表我把这段时间搜过、踩过、见过的高频问题整理成一张表方便平时翻查。问题现象可能原因解决办法Keil选不到STM32F103C8芯片包未安装或版本不对Pack Installer安装STM32F1xx DFP下载提示No target connectedST-Link驱动异常或SWD接线错误重装驱动检查SWDIO、SWCLK、GND三线LED一直不亮引脚搞错或LED极性反向对照原理图确认引脚和高低电平串口乱码HSE未启用或波特率不一致CubeMX里配置外部晶振并核对时钟树HAL_Delay死机SysTick中断被关或中断里调用delay检查NVIC配置不在中断回调里延时ILI9341读ID是0xA1A1SPI接线、时序或复位有问题按4.1节逐项排查CAN突然连不上缺终端电阻或波特率不一致两端接120欧电阻统一波特率J-Link识别不到芯片内核类型选错或SWD接线问题选Cortex-M3整理杜邦线接触遇到非常奇怪的现象我始终建议先查硬件再查软件。软件BUG往往是定时器参数差一个数硬件问题则是那些“看起来没问题但其实接触不良”的杜邦线。用万用表量一下通断比改代码快十倍。特别是那些插拔过多次的排线氧化和松动很常见。逻辑分析仪真的是调试神器便宜的几十块钱就够用。不管是串口波形、SPI时序还是按键抖动拉到屏幕上看一眼就明白。以前我把读ID读到A1A1用逻辑分析仪抓了一遍发现MISO引脚根本无信号问题立刻定位到杜邦线接触不良。很多人舍不得这点小钱却在排查上浪费了大把时间算下来非常不划算。每个模块的规格书和数据手册一定要备好。ILI9341手册里有读ID命令的详细时序DS3231手册里有I2C寄存器地址格式伺服手册里有MODBUS寄存器映射。中文搜不到的细节去英文章节翻一翻答案往往就在那里。6. 写在最后给一起入坑的伙伴开发板买回来最怕的事情就是吃灰。我见过太多人板子到手当天很兴奋把环境和例程跑了一遍之后就再没打开过。其实学单片机最实在的方法就是把一个大目标拆成小目标每天哪怕点亮一个外设、读懂一段代码一个月下来积累就会很可观。我今天跑通GPIO明天打通串口后天再啃定时器每踩一个坑都会记录下来。踩坑不可怕可怕的是踩完没记下来下次又掉进同一个坑里。我接下来会继续把每个小项目的调试经过原原本本写出来包含那些失败的操作和解决思路想学STM32的朋友点个关注咱们一起把这个坑踩平。