1. 为什么STM32F1到今天还值得深挖
如果你最近在逛各类嵌入式社区,会发现一个很有意思的现象:一边是各种高性能新片层出不穷,另一边是大量量产项目、教学课程、DIY作品依然在围绕STM32F1系列打转。这个系列推出至今已经超过十五年,按电子元器件的迭代速度早该进博物馆了,但现实是它的出货量依然庞大,二手开发板在交易平台上流通得热火朝天,很多公司的老产品线至今还在用它做维护和衍生型号。
我个人的判断是,STM32F1系列之所以长盛不衰,核心原因有三个。第一是生态成熟度极高,你能想到的任何外设驱动、RTOS移植、通信协议栈,几乎都能找到现成的参考实现,踩坑成本极低。第二是资料密度大,中文社区里关于它的笔记、教程、问答帖数量是其他系列的好几倍,遇到问题搜索一下基本都有答案。第三是价格和供货稳定,基础型号的单价已经压到很低的位置,对于成本敏感的量产产品来说非常友好。
这篇文章适合三类人看:刚入门嵌入式、手里拿着一块F1开发板不知道从哪下手的新手;做过一些项目、但想系统梳理F1架构和开发套路的进阶者;以及需要维护老产品线、想快速回忆F1关键细节的工程师。我会从架构选型、时钟体系、外设配置、调试排查几个维度,把F1系列的核心要点和实操经验完整拆一遍,尽量做到你看完就能直接上手改代码、调板子。
2. STM32F1系列的架构与型号选型逻辑
2.1 内核与总线结构到底意味着什么
STM32F1系列基于ARM Cortex-M3内核,这是理解它一切行为的基础。Cortex-M3是哈佛架构,指令总线和数据总线分开,配合三级流水线,在72MHz主频下能跑到大约1.25 DMIPS/MHz的性能。这个数字放在今天不算亮眼,但你要知道它面对的大多数场景是电机控制、传感器采集、简单人机交互,这些任务对算力的需求其实很有限。
真正值得关注的是它的总线矩阵。F1内部有AHB系统总线、APB1和APB2两条外设总线。AHB连接内核、DMA、SRAM和Flash控制器,速度最快;APB2挂载高速外设,比如GPIO、ADC、USART1、SPI1;APB1挂载低速外设,比如USART2/3、I2C、定时器2到7。这个分层的意义在于:APB1的最高频率只有36MHz,APB2可以到72MHz,所以你在配置外设时钟分频时,必须清楚目标外设挂在哪条总线上,否则会出现"明明代码没错但外设不工作"的情况。
我见过不少新手在这里翻车:给挂在APB1上的USART2配置了72MHz的时钟源,结果波特率算出来完全不对,串口输出一堆乱码。所以记住一个口诀——先查总线,再算时钟,最后配寄存器。
2.2 型号命名规则与选型对照
F1系列的型号命名是有规律的,读懂型号基本就能知道这颗芯片的资源上限。以常见的STM32F103C8T6为例,拆解一下:STM32代表产品家族,F代表基础型,103是子系列,C是引脚数(48脚),8是Flash容量(64KB),T是封装(LQFP),6是温度范围(-40到85度)。
不同子系列之间的差异主要体现在外设数量和Flash大小上。下面这张表是我根据实际项目经验整理的常见型号对照,方便你选型时快速定位:
| 型号 | Flash | RAM | 引脚 | 典型用途 | 备注 |
|---|---|---|---|---|---|
| F103C8T6 | 64KB | 20KB | 48 | 入门学习、小项目 | 性价比最高,资料最多 |
| F103RCT6 | 256KB | 48KB | 64 | 中等复杂度产品 | 外设丰富,适合多任务 |
| F103ZET6 | 512KB | 64KB | 144 | 复杂系统、多外设 | 开发板常用,资源充足 |
| F103VET6 | 512KB | 64KB | 100 | 工业控制 | 引脚多,接口全 |
选型时我的建议是:不要一上来就选最大的。很多新手觉得资源越多越好,结果焊了一块144脚的板子,发现大部分引脚根本用不上,反而增加了布线和调试的复杂度。正确的做法是先列出项目需要的外设清单——几个串口、几个定时器、要不要CAN、要不要USB——然后对照数据手册选刚好够用、留一点余量的型号。
还有一个容易被忽略的点是封装和焊接难度。LQFP48手工焊接还算友好,LQFP144没有热风枪基本很难搞定。如果你是自己做板子,优先选引脚少的封装;如果是买现成开发板,那就无所谓了。
2.3 启动模式与BOOT引脚的实际影响
F1系列有两个BOOT引脚,组合决定了芯片上电后从哪里启动。BOOT0=0时从主Flash启动,这是正常运行模式;BOOT0=1、BOOT1=0时从系统存储器启动,也就是进入出厂预置的Bootloader,可以通过串口下载程序;BOOT0=1、BOOT1=1时从SRAM启动,一般用于调试。
这里有个实操细节很多人不知道:用串口下载程序时,需要先把BOOT0拉高,下载完再拉低复位。有些开发板设计了跳线帽,操作起来很方便;但如果你是自己画的板子,忘了引出BOOT0,那就只能用SWD下载了。所以画板子的时候,BOOT0一定要引出来,哪怕只是留一个焊盘。
另外,系统存储器的Bootloader是只读的,你没法修改它。它的存在意味着即使你把主Flash擦得干干净净,芯片也不会变砖,总能通过串口重新烧录。这个特性在量产时特别有用,产线工人不需要昂贵的调试器,一根USB转串口线就能完成烧录。
3. 时钟系统:F1最容易出错也最核心的部分
3.1 时钟树全解析与配置思路
时钟是STM32的"心脏",也是新手最容易迷糊的地方。F1的时钟树看起来复杂,但理清之后其实很直观。它有几个时钟源:HSI是内部8MHz RC振荡器,精度一般但不需要外部元件;HSE是外部晶振,通常接8MHz,精度高;LSI是内部40kHz低速时钟,给独立看门狗用;LSE是外部32.768kHz晶振,给RTC用;PLL是锁相环,可以把HSE或HSI倍频到72MHz。
系统时钟SYSCLK的来源可以是HSI、HSE或者PLL输出。绝大多数项目都会选择HSE经PLL九倍频到72MHz这条路径,因为8MHz乘以9正好是72MHz,配置简单且精度高。具体流程是:HSE 8MHz先经过一个二分频变成4MHz,再进入PLL乘以18,得到72MHz。等等,这里有个细节——F1的PLL配置寄存器里,HSE先除以2是固定的(当PLL源为HSE时),然后PLL倍频系数可选2到16。所以8MHz除以2得4MHz,再乘以9得36MHz?不对,这样算出来是36MHz。
让我重新理一遍:实际上F1的PLL输入是HSE直接输入或者HSE/2,然后倍频系数是2到16。要得到72MHz,如果HSE是8MHz,那么8乘以9等于72,倍频系数设为9即可,不需要二分频。我之前记混了,这里更正一下。正确的配置是:PLL源选HSE,HSE不分频,PLL倍频系数设为9,输出72MHz。这个细节我在早期项目里也搞错过,导致系统跑在36MHz,串口波特率全乱,排查了半天才发现是PLL配置问题。
配置完SYSCLK后,还需要设置AHB、APB1、APB2的分频系数。AHB通常不分频,保持72MHz;APB2也不分频,72MHz;APB1必须二分频,因为它的上限是36MHz。这些分频系数决定了挂载在各条总线上的外设能拿到的时钟频率,进而影响定时器周期、串口波特率、ADC采样时间等参数的计算。
3.2 时钟配置代码实操与验证方法
用标准外设库配置时钟的典型代码如下,我加了详细注释说明每一步的意图:
void RCC_Configuration(void) { // 1. 使能HSE,等待就绪 RCC_HSEConfig(RCC_HSE_ON); while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET); // 2. 配置PLL:HSE作为源,9倍频 RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); // 3. 使能PLL,等待锁定 RCC_PLLCmd(ENABLE); while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); // 4. 配置各总线分频 RCC_HCLKConfig(RCC_SYSCLK_Div1); // AHB = 72MHz RCC_PCLK2Config(RCC_HCLK_Div1); // APB2 = 72MHz RCC_PCLK1Config(RCC_HCLK_Div2); // APB1 = 36MHz // 5. 切换系统时钟到PLL RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while(RCC_GetSYSCLKSource() != 0x08); // 6. 更新SystemCoreClock变量 SystemCoreClockUpdate(); }这段代码看起来简单,但有几个坑点值得注意。第一,等待标志位时一定要加超时机制,否则晶振不起振的话程序会死循环在这里。我一般会加一个计数器,超过一定次数就切换到HSI并报错。第二,切换时钟源之前要确保目标时钟已经稳定,顺序不能乱。第三,SystemCoreClockUpdate()必须调用,否则后续用SysTick做延时的时候,计数值会基于错误的频率计算,导致延时时间不对。
验证时钟配置是否正确,最直接的方法是用示波器测MCO引脚(PA8)输出的时钟信号。把MCO配置为输出SYSCLK的几分之一,测出来的频率乘以分频系数就是实际系统时钟。如果没有示波器,也可以写一段基于SysTick的延时程序,翻转一个GPIO,用逻辑分析仪或者甚至用手机慢动作视频看LED闪烁频率来粗略判断。
3.3 时钟安全系统与低功耗场景的取舍
F1有一个时钟安全系统(CSS),当HSE失效时会自动切换到HSI并产生中断。这个功能在工业控制场景里很有价值,因为外部晶振可能因为振动、温度变化等原因停振,如果没有CSS,系统会直接死机。使能CSS的方法很简单,在RCC初始化里调用RCC_ClockSecuritySystemCmd(ENABLE)即可,然后在中断服务函数里做降级处理。
但CSS也有代价:它会增加一点功耗,而且在调试时如果HSE没接好,会频繁触发中断,干扰调试。所以我的建议是:产品最终固件里开启CSS,调试阶段可以先关掉。
低功耗场景下,时钟配置的策略完全不同。比如你要做电池供电的传感器节点,可能会选择HSI作为系统时钟,因为HSI启动快、不需要外部晶振,虽然精度差一点但对很多应用足够了。在STOP模式下,所有时钟都停止,只有LSI或LSE还在跑,用来唤醒。唤醒后可以选择用HSI快速启动,等HSE稳定后再切回去。这套流程在ST的参考手册里有详细描述,但实际写代码时,唤醒后的时钟切换顺序很容易出错,建议参考官方例程的写法。
4. GPIO与中断:最基础也最见功力的部分
4.1 GPIO八种模式的适用场景
F1的GPIO有八种工作模式,分别是浮空输入、上拉输入、下拉输入、模拟输入、开漏输出、推挽输出、复用开漏输出、复用推挽输出。新手最常问的问题是"我该用哪种模式",其实判断逻辑很清晰。
输入模式下,如果你要读按键,用上拉或下拉输入,具体取决于按键另一端接的是地还是电源。如果按键一端接地,另一端接引脚,那就用上拉输入,这样按键未按下时引脚被内部上拉电阻拉到高电平,按下时被拉到低电平。如果要读模拟信号,比如电位器分压,那就用模拟输入,此时施密特触发器被禁用,信号直接进入ADC。
输出模式下,推挽输出能输出强高低电平,适合驱动LED、继电器等;开漏输出只能输出低电平,高电平需要外部上拉电阻,适合I2C总线或者需要电平转换的场景。复用模式则是当引脚被外设(如USART、SPI)占用时使用,此时GPIO的控制权交给外设,你不需要手动操作输出寄存器。
这里有个实操经验:配置GPIO之前一定要先使能对应端口的时钟。比如你要用PA口,就得先调用RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE)。我见过太多人忘了这一步,然后纳闷为什么引脚没反应。这个错误太常见了,以至于我养成了一个习惯:写GPIO初始化函数时,第一行永远是使能时钟。
4.2 外部中断配置与优先级管理
F1的外部中断通过EXTI控制器管理,每个GPIO都可以配置为中断源,但要注意同一条EXTI线不能同时映射到多个端口。比如EXTI0只能选择PA0、PB0、PC0中的一个,不能同时用。这个限制在引脚规划阶段就要考虑好,否则后期改起来很麻烦。
配置外部中断的步骤是:使能GPIO时钟和AFIO时钟,配置GPIO为输入模式,用GPIO_EXTILineConfig()把EXTI线映射到具体端口,配置EXTI触发条件(上升沿、下降沿或双边沿),配置NVIC优先级,最后写中断服务函数。
NVIC优先级管理是另一个容易出问题的地方。F1的中断优先级分为抢占优先级和响应优先级,数值越小优先级越高。抢占优先级高的中断可以打断正在执行的低抢占优先级中断,而响应优先级只在同时挂起时决定谁先执行。实际项目中,我一般把实时性要求最高的中断(比如电机换相、编码器计数)设为最高抢占优先级,通信类中断设为中等,按键、显示刷新等设为最低。
中断服务函数里要做的事情越少越好。我见过有人在中断里做浮点运算、调用printf、甚至延时,这些都是大忌。正确的做法是:中断里只做标志位设置、数据搬运等轻量操作,复杂处理放到主循环里根据标志位执行。如果数据量大,配合DMA使用,让DMA在后台搬运,CPU完全不参与。
4.3 按键消抖与中断触发的实战技巧
按键处理是GPIO和中断的经典应用场景,但要做好并不简单。机械按键按下和释放时会有抖动,持续时间通常在5到20毫秒之间。如果用轮询方式,可以在检测到电平变化后延时20毫秒再确认;如果用中断方式,就不能在中断里延时了,通常的做法是中断触发后启动一个定时器,定时器到期后再读取引脚状态。
我个人的偏好是用定时器中断做按键扫描,每10毫秒扫一次,连续两次读到相同状态才确认。这种方式不需要外部中断,节省了EXTI资源,而且天然实现了消抖。代码结构大概是:定时器中断里调用按键扫描函数,扫描函数维护每个按键的状态机,检测到稳定按下后设置标志位,主循环检测到标志位后执行对应功能。
还有一种情况是按键需要支持长按和短按。这时候状态机就要多几个状态:空闲、按下消抖、短按确认、长按确认、释放消抖。长按的时间阈值一般设为1秒左右,具体看用户体验。这个逻辑用状态机写出来很清晰,但新手容易写成嵌套的if-else,后期维护很痛苦。建议一开始就用状态机或者查表法来组织代码。
5. 定时器、串口与DMA的协同使用
5.1 定时器选型与PWM输出配置
F1系列的定时器分为高级定时器(TIM1、TIM8)、通用定时器(TIM2到TIM5)和基本定时器(TIM6、TIM7)。高级定时器支持互补输出、死区插入、刹车输入,适合电机控制;通用定时器功能齐全,适合PWM输出、输入捕获、编码器接口;基本定时器只能做定时,没有外部引脚。
PWM输出的配置流程是:使能定时器和GPIO时钟,配置GPIO为复用推挽输出,配置定时器的时基结构体(预分频、自动重装载值、计数模式),配置输出比较结构体(PWM模式、占空比、极性),使能定时器和输出通道。
频率和占空比的计算公式是:PWM频率 = 定时器时钟 / ((预分频+1) * (自动重装载值+1)),占空比 = 比较值 / (自动重装载值+1)。举个例子,如果定时器时钟是72MHz,预分频设为71,自动重装载值设为999,那么PWM频率就是72000000 / (72 * 1000) = 1000Hz,也就是1kHz。如果比较值设为500,占空比就是50%。
这里有个细节:预分频和自动重装载值都是16位寄存器,最大值65535。如果你需要很低的PWM频率,比如1Hz,那预分频和重装载值的乘积要达到72000000,这时候就需要用两个寄存器配合,或者降低定时器时钟源。我在一个LED呼吸灯项目里就遇到过这个问题,想要0.5Hz的呼吸效果,最后是把定时器时钟降到1MHz,再用大的重装载值实现的。
5.2 串口通信的三种模式与DMA搬运
F1的USART支持三种模式:轮询、中断和DMA。轮询模式最简单,但会阻塞CPU,只适合发送少量数据;中断模式在收发完成时触发中断,CPU利用率高一些,但每个字节都要进一次中断,波特率高的时候中断开销很大;DMA模式让DMA控制器自动搬运数据,CPU完全不参与,适合高速率、大数据量的场景。
我一般这样选择:调试输出用轮询或中断,产品通信接口用DMA。比如用串口打印调试信息,数据量小、实时性要求不高,轮询就够了。但如果是和上位机通信、传输传感器数据,那就必须用DMA,否则CPU会被串口中断占满,其他任务没法执行。
串口DMA发送的配置要点是:使能DMA时钟,配置DMA通道(USART1_TX对应DMA1_Channel4),设置传输方向为存储器到外设,使能DMA和USART的DMA发送请求。发送时只需要把数据放进缓冲区,调用DMA_Cmd(ENABLE),剩下的DMA会自动完成。发送完成的判断可以通过查询DMA传输完成标志,或者使能DMA传输完成中断。
串口DMA接收稍微复杂一点,因为你要知道数据什么时候来、来了多少。常用的方案是空闲中断加DMA:DMA一直处于接收状态,当总线空闲时触发USART空闲中断,在中断里读取DMA已经接收的字节数,然后重新配置DMA准备下一次接收。这个方案能很好地处理不定长数据帧,我在多个项目里都用过,稳定性很好。
5.3 定时器触发ADC与DMA的联动
在电机控制、音频采集等场景里,需要以固定频率采样模拟信号。这时候可以用定时器触发ADC,ADC转换完成后触发DMA搬运数据,形成一个完全由硬件驱动的采集链路,CPU只需要在缓冲区满的时候处理数据。
配置流程是:定时器配置为PWM模式或输出比较模式,但不对应GPIO,只产生内部触发信号;ADC配置为外部触发模式,触发源选对应的定时器事件;DMA配置为从ADC数据寄存器搬运到内存缓冲区,循环模式。这样定时器每产生一次事件,ADC就转换一次,DMA就搬运一次,整个过程不需要CPU干预。
这个方案的关键参数是采样率和缓冲区大小。采样率由定时器频率决定,缓冲区大小决定了CPU多久处理一次数据。如果采样率是10kHz,缓冲区大小是1000,那么CPU每100毫秒处理一次,这个间隔对大多数应用来说足够了。但如果采样率是100kHz,缓冲区还是1000,CPU每10毫秒就要处理一次,负担就比较重了。所以要根据实际需求平衡采样率和缓冲区大小。
6. 常见问题排查与避坑经验实录
6.1 程序下载失败与芯片识别问题
下载失败是新手遇到最多的问题,原因通常有几类。第一类是BOOT引脚状态不对,用SWD下载时BOOT0必须为低,用串口下载时BOOT0为高。第二类是调试器连接问题,SWDIO和SWCLK接反、接触不良、或者调试器驱动没装好。第三类是芯片被读保护,如果之前烧录过程序并开启了读保护,再次下载时会被拒绝,需要用调试器解除保护。
排查顺序我一般是这样的:先确认BOOT0电平,再检查SWD四根线(VCC、GND、SWDIO、SWCLK)是否接对,然后用调试器软件看能否识别到芯片ID。如果识别不到,降低SWD时钟频率试试,有时候线太长或者干扰大会导致高速通信失败。如果识别到但下载报错,检查Flash是否被保护,用调试器的解锁功能处理。
还有一个隐蔽的问题是电源不稳。F1的工作电压是2.0到3.6V,如果供电低于2.0V,芯片可能部分工作但下载会失败。我遇到过一次,用USB供电时正常,用电池供电时下载失败,后来发现电池电压只有1.8V。所以下载前用万用表量一下VDD引脚电压,确保在正常范围内。
6.2 串口乱码与波特率偏差排查
串口输出乱码,九成以上是波特率不对。波特率的计算公式是:波特率 = 外设时钟 / (16 * USARTDIV),其中USARTDIV是一个定点数,整数部分和小数部分分别配置。如果外设时钟是72MHz,要得到115200的波特率,USARTDIV = 72000000 / (16 * 115200) = 39.0625。整数部分是39,小数部分是0.0625乘以16等于1,所以USARTDIV寄存器写39.1。
但这里有个误差问题:39.0625实际能表示的是39 + 1/16 = 39.0625,正好精确。但如果波特率是9600,USARTDIV = 72000000 / (16 * 9600) = 468.75,整数部分468,小数部分0.75乘以16等于12,所以写468.12。这个也是精确的。但如果外设时钟不是72MHz,比如APB1上的USART2只有36MHz,那USARTDIV = 36000000 / (16 * 115200) = 19.53125,整数19,小数0.53125乘以16等于8.5,只能取8或9,存在误差。误差超过一定范围就会乱码。
所以用APB1上的串口时,尽量选波特率能整除的数值,或者把APB1的分频系数改小(但APB1上限是36MHz,改不了)。我一般建议高速通信优先用USART1,因为它挂在APB2上,时钟是72MHz,波特率精度更高。
6.3 中断不触发与优先级冲突
中断配置了但就是不触发,排查思路是这样的:先确认中断使能位是否打开(外设的中断使能寄存器和NVIC的使能寄存器都要开),再确认中断标志位是否被正确清除(有些中断标志需要手动清除,否则会一直触发或者不再触发),最后检查优先级分组和优先级数值是否合理。
优先级冲突的典型表现是低优先级中断一直不执行。比如你把某个中断的抢占优先级设得很低,而另一个中断频繁触发且抢占优先级很高,那低优先级中断就永远得不到执行机会。解决方法是合理分配优先级,把频繁触发的中断优先级设低一些,把实时性要求高的中断优先级设高一些,但不要让某个中断长时间占用CPU。
还有一个坑是在中断里调用了不可重入的函数。比如在中断里调用printf,而主循环里也在调用printf,两者可能同时操作串口,导致输出混乱甚至死锁。正确的做法是中断里只设置标志,主循环里根据标志执行printf。如果非要在中断里输出,那就用DMA发送,避免CPU直接操作串口。
6.4 常见问题速查表
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 下载失败 | BOOT引脚错误 | 测量BOOT0电平 | SWD下载时BOOT0拉低 |
| 下载失败 | 芯片读保护 | 调试器查看选项字节 | 解除读保护后重新下载 |
| 串口乱码 | 波特率不匹配 | 检查外设时钟和USARTDIV | 调整波特率或时钟配置 |
| 串口乱码 | 时钟源错误 | 确认系统时钟频率 | 修正PLL配置 |
| 中断不触发 | NVIC未使能 | 检查NVIC使能寄存器 | 调用NVIC_EnableIRQ |
| 中断不触发 | 标志未清除 | 检查中断标志寄存器 | 在ISR中清除标志 |
| GPIO无输出 | 时钟未使能 | 检查RCC使能寄存器 | 使能对应GPIO时钟 |
| GPIO无输出 | 模式配置错误 | 检查GPIO模式寄存器 | 改为推挽输出 |
| PWM无输出 | 定时器未使能 | 检查TIM使能位 | 调用TIM_Cmd(ENABLE) |
| ADC采样不准 | 参考电压不稳 | 测量VREF引脚 | 加滤波电容 |
| DMA不搬运 | 通道配置错误 | 检查DMA通道映射 | 参考手册修正通道 |
| 程序跑飞 | 堆栈溢出 | 检查栈大小和局部变量 | 增大栈或减少局部变量 |
这张表里的每一条都是我或者身边同事实际踩过的坑,尤其是"GPIO无输出"和"PWM无输出"这两条,新手几乎必踩。记住一个原则:外设不工作,先查时钟,再查配置,最后查引脚。这个顺序能帮你快速定位大部分问题。
7. 从F1迁移到其他系列的注意事项
虽然这篇文章主题是F1,但很多读者最终会面临升级换代的需求。从F1迁移到F4或者G0、L4等系列时,有几个地方需要特别注意。首先是时钟树结构变了,F4的PLL配置更灵活,但分频系数和倍频系数的组合也更多,需要重新计算。其次是HAL库取代了标准库,函数命名和调用方式完全不同,但逻辑是相通的,熟悉标准库的人转HAL库大概需要一周适应期。
外设方面,F4的GPIO速度更快、支持更多复用功能,但基本的使用逻辑没变。F4的定时器位数更多、功能更强,但PWM配置的流程和F1类似。F4的DMA控制器更复杂,有DMA1和DMA2,流和通道的概念需要重新理解。总的来说,F1打下的基础在升级时依然有用,你学到的时钟配置思路、中断管理方法、外设调试技巧都是通用的。
我个人在实际操作中的体会是:不要为了升级而升级。F1能满足需求就用F1,它的生态和资料优势是其他系列短期内比不了的。只有当F1确实遇到瓶颈——比如算力不够、RAM不足、外设数量不够——才考虑迁移。迁移时优先选引脚兼容或者至少封装兼容的型号,这样硬件改动最小,软件移植也更容易。
最后分享一个小技巧:如果你手头有F1的旧项目需要维护,但又想用新库开发,可以先把标准库的代码整理成模块化的驱动层,把硬件相关的部分隔离出来。这样将来迁移到新系列时,只需要重写驱动层,应用层代码几乎不用动。这个习惯我从早期项目就开始培养,后来几次平台迁移都省了大量时间。