1. 10年过去,为什么F1系列还是我手里最常用的“老伙计”
调试器还没拔下来,微信就弹来一条消息:老项目又要改版,客户指定要兼容上一代PCB,主控尽量不动。我瞥了一眼板子丝印,STM32F103C8T6,2015年那一批,到现在还在产线上稳定跑。这情况不是个例——我做工业设备、仪器仪表、小家电的朋友,手里主力方案依然是STM32F1系列,好几个人干脆把F1当“看家芯片”对待。网上总有人说它陈旧,但现实是它的文档生态、供应链和产品稳定性,让不少负责选型的人宁可继续选它,也不愿意为一个新内核重新折腾验证流程。
1.1 F1为什么还值得选:四个层次的逻辑
STM32F1系列是芯片原厂最早大规模推向市场的Cortex-M3内核MCU,整个家族覆盖了从低到高几十个型号。我这里说的“低”,不是性能弱,而是引脚数、Flash、RAM这些规格上的精细分级。以最典型的STM32F103系列为例,主频72MHz,Flash从16KB到512KB,RAM从4KB到64KB,引脚从36到144。后面还有STM32F105、STM32F107,额外集成了USB OTG和以太网MAC。单看纸面参数,如今随便一颗Cortex-M0+内核的芯片都能在价格上把它比下去,甚至不少国产芯片直接做到了引脚兼容、寄存器兼容。
但选型不是做参数对比题。我理解的F1优势有四层:
第一,生态成熟。图形化配置工具、HAL库、标准外设库、寄存器例程,十多年沉淀下来的代码和踩坑记录多到看不完,新入行的同学基本不用从零摸索。碰到一个冷门外设,网上至少能搜到几个可用的参考工程。
第二,资料透明。参考手册、勘误表、应用笔记齐全,做产品最怕的是芯片行为不明,而F1的行为模式早被摸透了。某些状态寄存器在特定条件下有已知异常,这些信息在勘误表里都写得清清楚楚,至少在出现问题时能查得着依据。
第三,供应稳定。无论常规年份还是缺货周期,F1的货源都比大部分新物料充足,量产排期不容易被卡。对做产品的人来说,这种“不会突然断供”的安全感,比多出来的几十MHz主频重要得多。
第四,性价比合理。在功能要求不夸张的场景里,它没有明显短板。一颗几块钱的芯片,就能把采集、控制、通信和简单界面全部做完,而且每个外设都有成熟的参考用法。
1.2 这篇内容适合谁看
这篇文章不打算复述数据手册,而是从我做项目的实际角度,把F1系列的选型逻辑、内部结构、开发方式和最常见的问题串一遍。适合三类人看:刚接触嵌入式、正纠结要不要用F1入门的同学;想从其他平台转过来做F1项目的工程师;以及已经在用F103但想系统排查项目隐患、学习进阶用法的开发者。下文提到的经验,全部来自我实际调试过的F1项目,涉及型号以STM32F103为主,个别场景会用F105、F107做对比。
2. 先摸清F1的家底:型号命名、子系列差异与选型检查单
2.1 型号解码:一次看懂STM32F103C8T6
F1型号其实就是一串有规律的编码。以STM32F103C8T6为例,拆开看:
- STM32——产品线前缀;
- F1——主流型Cortex-M3系列;
- 103——增强型,功能最全的型号;
- C——引脚数,对应48引脚封装;
- 8——Flash容量等级,这里表示64KB;
- T——封装形式,LQFP;
- 6——温度范围,-40℃到85℃。
引脚数和Flash容量是绑定的,常见对应关系我整理成了一张表:
| 引脚数 | 命名字母 | 常见型号举例 | Flash容量等级 |
|---|---|---|---|
| 36 | F | STM32F103F8P6 | 8表示64KB |
| 48 | C | STM32F103C8T6 | 8表示64KB |
| 64 | R | STM32F103RCT6 | C表示256KB |
| 100 | V | STM32F103VET6 | E表示512KB |
| 144 | Z | STM32F103ZET6 | E表示512KB |
选完引脚数之后,Flash容量也基本被卡定了。很多人在这块吃过亏:代码写到一半,Flash不够了,想换成大容量版本,结果连封装都得换,PCB全部推翻。我的习惯是在选型阶段就把Flash余量按最终代码体积的两倍以上留。尤其是有Bootloader、OTA、字库或者本地日志存储需求的项目,Flash余量比主频更值钱。
补充一个容易被忽略的细节:F103的C8T6虽是市场最常见的型号,但64KB Flash在小项目里够用,一旦加上RTOS、通信协议栈和OTA,存储立刻紧张。所以我现在更倾向于同封装的CBT6(128KB Flash),同样是48脚,PCB不用改,容量却翻倍,成本差异往往只有一块钱上下。
2.2 子系列差异:F101、F103、F105、F107各自的位置
F1系列内部至少分三个分支:
| 子系列 | 定位 | 最高主频 | USB | CAN | 以太网 | 典型用途 |
|---|---|---|---|---|---|---|
| F101 | 基本型 | 36MHz | 无 | 部分有 | 无 | 低成本控制 |
| F103 | 增强型 | 72MHz | 设备控制器 | 有 | 无 | 工业控制、仪表、通信 |
| F105/107 | 互联型 | 72MHz | OTG | 有 | 107有MAC | 网关、协议转换 |
如果项目需要以太网,常见方案是F107外接一颗百兆PHY芯片,跑TCP/IP协议栈;如果只是USB转串口或U盘读写,F103自带的USB 2.0全速设备控制器就够了,不需要上105/107。很多人一看到USB就想选F105,其实F103的USB已经覆盖了大量设备类场景,105/107多出来的主要是OTG主机能力和更灵活的电源管理,对纯设备类产品价值不大。
2.3 我的选型决策清单
我没有统一公式,但每次选型都会按顺序回答一组问题:
- GPIO和外设数量的需求是否爆表?把需要同时工作的外设列一遍,数清引脚;
- 是否需要USB、CAN、以太网?这决定是选103还是105/107;
- Flash和RAM余量够不够?按最终代码体积的150%~200%估算;
- 工作温度能否覆盖产品环境?工业现场选7后缀,-40℃到105℃;
- 有没有备份货源或第二替代方案?考虑引脚兼容的国产芯片,能显著降低供应链风险。
把这些问题过完,型号基本锁定。最近我做一个温湿度采集节点,最终选了STM32F103CBT6,48脚,128KB Flash,20KB RAM。功能上只有传感器采集、LCD显示、Modbus通信和一个简单的OTA。按最低要求,64KB其实也够,但留了128KB之后,后续加日志和远程升级轻松很多,基本不用再纠结存储迁移。
3. 内部结构比参数表更重要:时钟树、总线和启动模式
3.1 时钟树是第一道坎:从8MHz到72MHz的配置逻辑
我见过大量新手第一次调试F103,程序就是不跑,最后发现是时钟配置问题。F1上电后默认使用内部的8MHz HSI RC振荡器,系统主频只有8MHz。从默认状态提升到72MHz,需要配置PLL的输入源、倍频系数和总线分频器,三者必须严格匹配,否则轻则卡死,重则程序飞掉。
标准的72MHz配置路径是这么走的:
- 外部8MHz晶振作为HSE输入;
- HSE经过预分频(通常1分频)进入PLL;
- PLL倍频设置为9倍;
- PLL输出作为SYSCLK,得到72MHz;
- AHB预分频1分频,HCLK为72MHz;
- APB1预分频2分频,PCLK1为36MHz;
- APB2预分频1分频,PCLK2为72MHz。
这里有一个非常隐蔽的细节:F1的APB1定时器时钟是PCLK1的两倍。也就是说,虽然PCLK1只有36MHz,但挂在APB1上的TIM2~TIM7的时钟源实际是72MHz。很多人在计算定时器溢出周期时误用了36MHz,结果实际定时时间直接差了一倍。类似问题在波特率计算、PWM频率计算、ADC触发间隔里都会出现。你应该记住一句话:F1的APB1外设时钟是36MHz,但定时器内核时钟例外,是72MHz。
3.2 总线分配:为什么USART2的波特率老是偏
F1的GPIO、USART1、ADC1、SPI1挂在APB2(72MHz)上,USART2/3、SPI2、I2C1/2、CAN、USB挂在APB1(36MHz)上。总线频率直接决定了外设能跑多快。比如USART2的波特率发生器,它的基准时钟是PCLK1,也就是36MHz。如果你沿用USART1例程里的分频计算方法,把基准时钟当成72MHz去算,波特率就会差一倍,通信大概率乱码。这类问题在系统时钟不是标准72MHz时尤其常见。
GPIO速度配置同样容易被忽略。GPIO的翻转速度由寄存器里的速度位决定,分2MHz、10MHz、50MHz三档。注意这个速度不是越高越好。我在长走线的PCB上测试,50MHz档位下GPIO翻转沿会有明显过冲,严重时影响信号完整性。日常低速信号用10MHz档就够了,50MHz档留给SPI、SDIO这类高速通信。
3.3 启动模式:BOOT0和BOOT1的组合到底怎么选
F1有三种启动方式,由BOOT0和BOOT1引脚电平决定:
| BOOT0 | BOOT1 | 启动来源 | 说明 |
|---|---|---|---|
| 0 | 任意 | 主Flash | 正常运行 |
| 1 | 0 | 系统存储器 | 内置Bootloader,常用于串口下载 |
| 1 | 1 | SRAM | 调试用,掉电即失 |
实际产品中,BOOT0建议用10k电阻下拉,并且不设计跳线,防止误触。有一次我在批量产线调试时,发现一批板子程序烧录后没反应,排查了半天,最后发现是贴片厂把BOOT0的下拉电阻焊错成分压结构,导致引脚电平不对。开发阶段几乎遇不到这种问题,但量产时一出现就是批量事故。所以BOOT引脚相关的阻容件,我会要求在PCB阶段单独检查一遍。
4. 寄存器、标准库、HAL库:三套开发方式怎么选怎么切换
4.1 三种开发方式的适用边界
F1这些年经历了三套主流开发方式:
- 寄存器直接操作:最底层的写法,代码效率高,但可读性和可移植性差,适合吃透外设原理;
- 标准外设库:原厂早期库,把寄存器操作封装成函数,结构清晰,当年是主流;
- HAL/LL库:当前官方主力,配合图形化配置工具,能自动生成初始化代码,开发效率最高。
很多人纠结到底学哪一套。我的建议分阶段:学原理时,至少要能读寄存器手册,能看懂外设状态机;做产品原型时,用CubeMX配合HAL库能大幅提速;做小批量、高性价比产品时,标准库或LL库完全够用,生成的代码体积也更小。但对F1这个特定平台来说,最关键的不是库,而是你对外设行为的理解——库只是工具,外设行为是固定的。
4.2 图形化配置工具里的常见疏忽
用CubeMX生成F1工程,有几个我基本必改的操作:
- 时钟配置页面出现红色警告时,一定回去看倍频和分频,不要直接忽略;
- 中断优先级分组统一,我习惯用Group_2,避免多个中断嵌套时优先级行为不可控;
- 定时器的底层初始化函数里,GPIO时钟不要和回调里的初始化混在一起,重复初始化会积累麻烦;
- DMA传输方向、缓冲区大小和外设设置必须匹配,尤其做USART不定长接收时,要手动开启空闲中断。
一个实际案例:我用CubeMX生成USART1+DMA接收工程,默认配置只开了传输完成中断,没开空闲中断。结果数据只有缓冲区填满时才触发回调,串口不定长命令完全无法解析。加一个IDLE空闲中断,配合状态机,问题才解决。这个坑并非F1特有,但在F1的HAL库封装里,空闲中断需要手动配置,确实容易漏。
4.3 代码体积和运行效率的取舍
同一个功能,HAL库实现可能比标准库多占20%~30%的Flash。如果成本和容量敏感,建议打开编译器优化,并裁剪HAL库中不用的功能。调试阶段可以不开优化,但正式发布前,至少要在O2优化下重新验证一遍时序敏感的外设,比如PWM输出、定时器捕获、串口超时。优化选项改变后,中断延迟和代码执行顺序都可能变化,这种验证不能省。
5. 实战中我踩过的那些坑,按频率排序排查
F1资料多,但真正让工程师深夜怀疑人生的,往往是几个老问题。我按出现频率排了队。
5.1 电源与复位:VCAP电容和上电时序
现象:板子刚上电,程序偶尔起不来,按下复位键反而好了。查到最后是电源波动引起的复位失败,更具体说,是VCAP1/VCAP2引脚电容或布局不对。F103的内核电压是1.8V,由内置稳压器提供,外部需要在VCAP引脚接2.2μF电容,并且要紧靠引脚。这个电容离远了,内核电压不稳,程序跑飞的概率会显著增加。在PCB布局时,VCAP电容位置比很多高速信号都重要。
5.2 GPIO浮空输入:漏电流和误触发
F1的GPIO默认是浮空输入状态。低功耗产品里,没有外部上下拉电阻的引脚,浮空状态会引入漏电流,按键检测也容易误触发。我的处理方案:按键、拨码开关引脚配置为上拉输入;LED输出引脚配置为推挽输出并加限流电阻;中断引脚先查外部上下拉,再怀疑代码逻辑。这条习惯虽然基础,但能省下大量查bug的时间。
5.3 中断回调里做延时:系统卡死的经典原因
这是F1开发里非常典型的问题。HAL_Delay依赖Systick中断,如果在更高优先级的中断回调里调用HAL_Delay,且该中断优先级高于Systick,就会导致Systick中断无法被响应,从而进入死循环。解决方法是:中断服务函数中绝不延时;如果需要等待外设完成某个动作,改用状态机加超时计数。
5.4 串口波特率偏差与接收溢出
波特率偏差在长线传输和高温场景下会变得非常明显。F1的USART基准时钟来自PCLK1或PCLK2,如果在系统中改了APB分频,却没有同步调整波特率寄存器计算,通信就会不稳定。另一个问题是接收溢出:使用HAL库时,如果不及时在中断回调中取走数据,RXNE溢出标志可能把后续数据全部吞掉。我的方案是:接收用DMA+IDLE中断,发送用阻塞式,接收缓冲用环形队列,出错时清理标志。
5.5 硬件I2C的总线死锁
F1的硬件I2C在总线繁忙状态下容易进入异常状态,表现为SCL或SDA一直为低,只能复位恢复。很多人因此直接用GPIO模拟I2C。我的建议:如果时序要求不苛刻,优先用GPIO模拟I2C;如果坚持硬件I2C,务必加总线释放检测和超时恢复逻辑。官方勘误表里也提到过一些硬件I2C的已知限制,用前值得查阅。
5.6 ADC采样跳动:信号源内阻才是根因
F1的ADC是12位逐次逼近型,输入阻抗有限。信号源内阻过高时,采样结果会明显偏低且抖动,单纯软件多次滤波也救不回来。解决办法是外部加运放做跟随器,或至少加一个0.1μF采样电容。同时,ADC采样时间可以配置得更长,例如239.5周期,对高内阻信号有明显改善。实际项目中,我用过多次采样取中值,比平均值更抗异常值。
6. 进阶玩法:低功耗、双Bank OTA和掉电识别
6.1 低功耗模式:停止与待机的电流细节
F1常用的低功耗模式有睡眠、停止和待机。停止模式电流在μA级别,待机模式更低,但只能通过复位、WKUP引脚或RTC闹钟唤醒。如果产品需要电池供电,待机前要把未使用的GPIO全部配置为模拟输入,否则引脚漏电可能远大于MCU本身的待机电流。实测中,漏配一个处于上拉的GPIO,待机电流就可能高出一个数量级。这个检查点,我在每个低功耗项目里都做成清单,逐项勾选。
6.2 Flash双Bank与OTA分区方案
F103大容量型号支持Flash双Bank。虽然它没有新内核那种硬件双Bank独立执行能力,但通过软件规划可以实现“掉电不失效”的OTA。常用做法是把Flash分为Boot区、App区、备份区三块:Boot区引导,App区运行,备份区先暂存升级包。升级时先写备份区,校验通过后再拷贝到App区,最后跳转复位。这个方案对时间要求不敏感的产品非常可靠。要注意的是,写Flash时需要把中断关掉,防止在擦写过程中被中断打断导致状态错乱。
6.3 利用备份寄存器做掉电识别
F103的备份寄存器在待机模式下可以保持数据。利用这个特性,可以实现“开机后判断上次是正常关机还是异常掉电”。方法很简单:正常关机流程写一个魔数到备份寄存器,上电启动时读取校验。魔数正确则认为上次正常关机,魔数不对则说明上次异常掉电,进入恢复流程。这个技巧成本极低,但对日志记录和故障分析很有用,我用在过一个需要记录断电次数的计量设备上。
7. 写到最后:我的一点使用习惯和收尾技巧
7.1 我的极简经验清单
如果给STM32F1使用经历做个极简总结,我会写这几条:
- 别迷信“最新”芯片,F1仍然是很多成本敏感产品的合理选择,但选它之前,一定要想清楚供电、时钟和Flash规划;
- 工具链上,至少掌握寄存器阅读能力和一种库的使用方式,不要把全部希望押在图形化配置上;
- 调试时多用串口和LED做状态指示,能大幅提高排查效率,“printf大法”在嵌入式里依然有效;
- 遇到诡异问题,先查硬件连接、电源和复位,再查配置,不要一上来就怀疑芯片坏了。
7.2 一个帮你定位“程序跑飞”的调试小技巧
最后分享一个小技巧:调试F103时,我会在外部中断服务函数入口放一个GPIO翻转,用逻辑分析仪同时抓这个GPIO和主循环里的另一个GPIO。这样中断是否触发、中断执行时长、主循环卡在哪里,一眼就能看出来。这个做法帮我定位过至少五次“程序莫名其妙跑飞”的问题。
STM32F1系列,年龄不小,但它在各种电子设备里的影响力依旧真实。只要你还愿意打开参考手册,愿意从实际项目中攒经验,这套芯片还能陪你打很多年。