1. 为什么STM32F1至今仍是嵌入式入门的首选1.1 一颗芯片的江湖地位如果你在电子工程相关的社区里待过一段时间大概率会发现一个现象每隔几年就会有人说“STM32F1过时了该换F4或者G0了”但打开任何一个电子论坛的提问区关于STM32F1的帖子依然占据半壁江山。这不是偶然而是由这颗芯片本身的定位决定的。STM32F1系列是意法半导体基于ARM Cortex-M3内核推出的32位微控制器主频覆盖36MHz到72MHz片上Flash从16KB到1MB不等SRAM从6KB到96KB。这些参数放在今天看确实不算亮眼但它的杀手锏在于外设资源极其均衡——几乎每一款型号都标配了USART、SPI、I2C、ADC、定时器、DMA这些常用外设没有明显的短板。对于初学者来说这意味着你不需要为了学一个外设去换芯片对于产品开发来说这意味着选型时不用反复权衡“这个型号有没有我需要的接口”。我接触过的很多工程师包括我自己第一个真正跑通的32位项目几乎都是基于STM32F1完成的。原因很简单资料多、坑少、社区活跃。你遇到的绝大多数问题早在十年前就有人踩过并留下了解决方案。这种“前人栽树”的生态优势是任何新出的芯片短期内都无法复制的。1.2 谁适合从STM32F1入手这个问题需要分情况讨论。如果你是完全零基础的电子爱好者之前只玩过51单片机或者Arduino那STM32F1是一个非常好的进阶跳板。它的寄存器操作比51复杂但比直接啃ARM Cortex-M3权威指南要友好得多它的HAL库封装程度适中既不会让你完全脱离底层也不会让你被繁琐的寄存器配置劝退。如果你是有一定经验的嵌入式开发者正在寻找一颗稳定可靠的芯片做产品STM32F1同样值得考虑。它的供货情况虽然经历过波动但整体上比很多小众型号要稳定得多而且价格区间覆盖很广从几块钱的基础型号到几十块的高配型号都有。更重要的是它的温度等级和封装选项非常丰富从LQFP48到LQFP144从消费级到工业级基本能满足大多数常规应用的需求。当然如果你要做的是需要浮点运算、需要更高主频、需要更大RAM的应用那F1确实力不从心该上F4就上F4。但对于绝大多数控制类、采集类、通信类的项目F1的性能是绰绰有余的。1.3 这篇文章能给你什么我不打算写一篇“STM32F1从入门到精通”的教科书式文章那种内容网上已经够多了。我想做的是把我在实际项目中使用STM32F1时积累的经验、踩过的坑、总结的技巧用最直接的方式分享出来。包括但不限于开发环境怎么选、时钟树怎么配、外设初始化有哪些容易忽略的细节、中断优先级怎么规划、低功耗模式怎么用、程序跑飞了怎么排查。这些内容不会出现在芯片的数据手册里也不会出现在官方的参考手册里但它们恰恰是决定一个项目能不能顺利跑起来的关键。我会尽量用生活化的类比来解释技术概念让即使没有太多基础的人也能看懂。同时我也会给出具体的寄存器配置和代码示例让有经验的开发者可以直接参考。2. 开发环境搭建与工具链选型2.1 IDE的选择没有最好只有最合适STM32F1的开发环境大致可以分为三类官方系的STM32CubeIDE、Keil MDK、IAR EWARM以及开源系的VSCodePlatformIO或者VSCodeMakefile。每种环境都有它的拥趸也都有它的槽点。Keil MDK是国内用户最多的选择原因很简单资料多、教程多、破解容易。它的编辑器虽然简陋但调试功能非常成熟尤其是配合ST-Link使用时断点、单步、寄存器查看、内存查看这些功能都很稳定。缺点是代码补全和跳转功能比较弱大型项目里找函数定义会比较痛苦。IAR的编译效率是公认最好的生成的代码体积通常比Keil小5%到10%这在Flash容量紧张的型号上很有意义。但它的授权费用较高而且界面风格和快捷键与主流编辑器差异较大需要一定的适应时间。STM32CubeIDE是官方推出的免费IDE基于Eclipse框架集成了CubeMX配置工具可以图形化配置引脚和外设自动生成初始化代码。对于新手来说这个功能非常友好可以大大降低入门门槛。但Eclipse系的编辑器在代码补全和响应速度上确实不如VSCode流畅。我个人的选择是VSCodePlatformIO作为日常开发环境配合STM32CubeMX生成初始化代码。这样既有VSCode强大的编辑功能又有CubeMX的图形化配置还能用PlatformIO管理编译和下载整体体验比较均衡。当然如果项目需要复杂的调试我还是会切回Keil或者IAR。2.2 下载器与调试器ST-Link还是J-LinkST-Link是官方推出的调试器价格便宜兼容性好配合STM32CubeProgrammer可以完成下载、擦除、选项字节配置等操作。它的缺点是速度相对较慢而且山寨版本比较多质量参差不齐。我建议买一个原厂的ST-Link V2或者V3虽然贵一点但稳定性有保障。J-Link的速度和功能都比ST-Link强尤其是配合J-Flash和J-Scope使用时可以实时查看变量波形对调试电机控制、音频处理这类应用非常有帮助。但J-Link的价格较高而且山寨版本同样泛滥。如果你只是做常规的嵌入式开发ST-Link完全够用如果你需要更高级的调试功能可以考虑J-Link。注意无论使用哪种调试器都要确保它的固件版本与你的IDE兼容。我曾经遇到过因为ST-Link固件太旧导致Keil无法识别的问题升级固件后解决。所以拿到调试器后第一件事就是检查并更新固件。2.3 必备的软件工具除了IDE和调试器还有几个工具是STM32F1开发中经常会用到的。STM32CubeMX用于图形化配置引脚和外设生成初始化代码可以大大减少手动配置寄存器的工作量。STM32CubeProgrammer用于下载和擦除芯片支持多种格式的固件文件。串口调试助手用于查看串口输出调试通信协议时必不可少。逻辑分析仪用于抓取SPI、I2C、UART等总线的波形分析通信时序问题。这些工具中STM32CubeMX和STM32CubeProgrammer是官方免费的串口调试助手和逻辑分析仪软件有很多开源选择。我建议至少准备一个USB逻辑分析仪价格不贵但在调试通信问题时能帮你节省大量时间。3. 时钟树配置系统稳定运行的根基3.1 时钟源的选择与权衡STM32F1的时钟源有三个HSI内部高速时钟、HSE外部高速时钟、LSI内部低速时钟和LSE外部低速时钟。HSI的频率是8MHz精度一般在1%左右受温度影响较大。HSE需要外接晶振常见的是8MHz精度可以做到几十ppm稳定性远高于HSI。对于大多数应用我建议使用HSE作为主时钟源因为它的精度和稳定性更好尤其是在需要串口通信、USB通信或者精确定时的场景下。HSI可以作为HSE启动失败时的备用时钟源提高系统的可靠性。LSE是32.768kHz的低速时钟主要用于RTC实时时钟。如果你需要精确的日历功能LSE是必须的。LSI是40kHz左右的内部低速时钟精度较差一般只用于看门狗或者低功耗模式下的唤醒。3.2 PLL倍频的计算方法STM32F1的PLL结构相对简单HSE或HSI经过一个预分频器PLLXTPRE后进入PLLSRC选择器然后经过PLL倍频器PLLMUL最后输出到系统时钟。以最常见的8MHz HSE为例如果要得到72MHz的系统时钟计算过程如下8MHz经过PLLXTPRE不分频得到8MHz然后选择PLLMUL为9倍频得到72MHz。这就是最经典的“8MHz晶振倍频到72MHz”的配置。但这里有一个容易被忽略的细节PLL的输出频率不能超过72MHz否则芯片可能工作不稳定甚至损坏。同时APB1总线的时钟不能超过36MHzAPB2总线不能超过72MHz。所以在配置时钟树时需要根据外设的需求合理设置APB1和APB2的分频系数。我通常的做法是系统时钟72MHzAPB1分频系数为2得到36MHzAPB2分频系数为1得到72MHz。这样定时器、SPI等外设可以获得较高的时钟频率而串口、I2C等对时钟要求不高的外设则运行在36MHz下降低功耗和电磁干扰。3.3 时钟配置的常见陷阱第一个陷阱是HSE启动时间不足。STM32F1的HSE启动时间默认是几百微秒但有些晶振的启动时间可能更长。如果HSE还没稳定就切换系统时钟会导致程序跑飞。解决方法是在时钟配置代码中增加HSE启动超时检测如果超时则自动切换到HSI。第二个陷阱是Flash等待周期设置错误。当系统时钟超过24MHz时需要设置Flash的等待周期。72MHz对应的是2个等待周期。如果忘记设置或者设置错误会导致取指错误程序运行异常。这个参数在标准库的SystemInit函数中会自动设置但如果你自己写启动代码就需要手动配置。第三个陷阱是外设时钟忘记使能。STM32F1的外设时钟默认是关闭的使用任何外设之前都需要先使能对应的时钟。我见过很多初学者在配置GPIO时忘记使能GPIO的时钟然后困惑为什么引脚没有输出。这个问题的排查方法很简单检查RCC_APB2ENR寄存器的对应位是否置1。4. GPIO与中断系统的实战要点4.1 GPIO的八种工作模式STM32F1的GPIO有八种工作模式输入浮空、输入上拉、输入下拉、模拟输入、开漏输出、推挽输出、开漏复用输出、推挽复用输出。每种模式都有它的适用场景选错了模式会导致引脚无法正常工作。输入浮空模式一般用于外部已经有上拉或下拉电阻的场景比如I2C总线。输入上拉和输入下拉模式则用于按键检测等需要确定电平状态的场景。模拟输入模式用于ADC采集此时引脚的数字功能被关闭信号直接进入ADC模块。推挽输出模式是最常用的输出模式可以输出高电平和低电平驱动能力较强。开漏输出模式只能输出低电平高电平需要外部上拉电阻一般用于I2C总线或者需要线与功能的场景。复用输出模式则用于SPI、USART等外设的引脚此时引脚的控制权交给外设而不是GPIO寄存器。实操心得在配置GPIO时我习惯把每个引脚的配置都写清楚包括模式、速度、上下拉。虽然标准库的GPIO_Init函数可以一次性配置多个引脚但为了代码的可读性和可维护性我建议按功能分组配置并在注释中说明每个引脚的用途。4.2 外部中断的配置流程STM32F1的外部中断EXTI配置需要经过以下几个步骤使能GPIO时钟和AFIO时钟、配置GPIO为输入模式、配置AFIO的EXTI线映射、配置EXTI的触发方式、配置NVIC的中断优先级、使能EXTI中断。这里有一个容易忽略的点AFIO时钟必须使能。AFIO负责管理外部中断线、重映射等功能如果不使能AFIO时钟EXTI的配置不会生效。这个坑我踩过不止一次尤其是在使用寄存器直接操作时很容易忘记。另一个需要注意的点是中断服务函数的命名。STM32F1的中断向量表是固定的中断服务函数的名称必须与启动文件中的向量表一致。比如EXTI0的中断服务函数名是EXTI0_IRQHandler如果写成别的名字中断触发时不会执行你的代码。这个问题在编译时不会报错但运行时中断不响应排查起来比较费时间。4.3 中断优先级的规划策略STM32F1的中断优先级分为抢占优先级和响应优先级通过NVIC的IPR寄存器配置。抢占优先级高的中断可以打断正在执行的抢占优先级低的中断而响应优先级只在同时发生多个中断时决定哪个先执行不能打断正在执行的中断。在实际项目中我通常按照以下原则规划中断优先级实时性要求最高的中断如电机控制、紧急保护设置为最高的抢占优先级通信类中断如串口接收、SPI传输设置为中等优先级按键、LED刷新等对实时性要求不高的中断设置为最低优先级。还有一个细节中断服务函数要尽量短。在中断中做太多事情会导致其他中断被延迟响应甚至丢失中断。我通常的做法是在中断中只做标志位的设置或者数据的搬运具体的处理逻辑放到主循环中执行。如果数据量较大可以考虑使用DMA来搬运数据进一步减少中断的负担。5. 常用外设的配置与调试5.1 USART串口通信的完整配置USART是STM32F1中最常用的外设之一配置流程包括使能USART时钟和GPIO时钟、配置GPIO为复用推挽输出TX和浮空输入或上拉输入RX、配置USART的波特率、数据位、停止位、校验位、使能USART、配置中断如果需要。波特率的计算是很多人容易出错的地方。STM32F1的USART波特率计算公式是波特率 fCK / (16 * USARTDIV)其中fCK是USART的时钟频率USARTDIV是一个浮点数整数部分写入BRR寄存器的[15:4]位小数部分写入[3:0]位。以72MHz时钟、115200波特率为例USARTDIV 72000000 / (16 * 115200) 39.0625。整数部分39写入BRR[15:4]小数部分0.0625 * 16 1写入BRR[3:0]所以BRR的值是0x271。注意如果波特率误差超过2%通信可能会不稳定。在72MHz时钟下115200波特率的误差约为0.16%完全满足要求。但如果使用HSI作为时钟源由于HSI的精度只有1%左右波特率误差可能会超过2%导致通信失败。所以使用串口通信时建议使用HSE作为时钟源。5.2 SPI总线的四种模式SPI总线有四种工作模式由CPOL时钟极性和CPHA时钟相位组合决定。CPOL0表示时钟空闲时为低电平CPOL1表示空闲时为高电平。CPHA0表示数据在时钟的第一个边沿采样CPHA1表示在第二个边沿采样。选择哪种模式取决于从设备的要求。比如常见的W25Q系列Flash芯片支持模式0和模式3而一些传感器可能只支持模式0。在配置SPI时需要根据从设备的数据手册确定CPOL和CPHA的值。SPI的时钟频率也需要根据从设备的支持范围来设置。STM32F1的SPI时钟最高可以到36MHzAPB2或18MHzAPB1但实际使用时需要根据从设备的最高时钟频率来分频。比如W25Q64的最高SPI时钟是80MHz但STM32F1的SPI最高只有36MHz所以可以直接使用最高频率。5.3 I2C通信的时序与调试I2C是两线制总线包括SCL时钟线和SDA数据线。STM32F1的硬件I2C外设在使用上存在一些已知的问题比如在某些情况下会产生总线锁死需要重新初始化才能恢复。这也是很多开发者选择用GPIO模拟I2C的原因。如果使用硬件I2C需要注意以下几点SCL和SDA需要外接上拉电阻典型值是4.7kΩI2C的时钟频率不能超过400kHz快速模式在发送起始条件之前需要确保总线空闲。如果总线被锁死可以通过发送9个时钟脉冲来解锁或者直接复位I2C外设。如果使用GPIO模拟I2C灵活性更高可以避免硬件I2C的一些问题但需要自己实现时序控制。模拟I2C的关键是控制好SCL和SDA的时序关系尤其是在起始条件、停止条件和应答位的处理上。我通常会在模拟I2C的代码中加入适当的延时确保时序满足从设备的要求。5.4 ADC采集的精度优化STM32F1的ADC是12位逐次逼近型ADC最高采样率1MHz支持最多16个外部通道。ADC的参考电压通常使用VDDA模拟电源所以VDDA的稳定性直接影响采集精度。提高ADC采集精度的方法有以下几个使用独立的模拟电源和模拟地避免数字电路的噪声干扰在ADC输入端增加RC滤波电路滤除高频噪声使用DMA搬运ADC数据减少CPU干预提高采样效率对多次采样结果取平均值降低随机噪声的影响。还有一个容易被忽略的点ADC的采样时间需要根据信号源的阻抗来设置。如果信号源的内阻较大需要增加采样时间让采样电容充分充电。STM32F1的ADC采样时间可以设置为1.5、7.5、13.5、28.5、41.5、55.5、71.5、239.5个时钟周期。对于大多数应用55.5或71.5个周期是比较稳妥的选择。6. 常见问题排查与避坑指南6.1 程序下载失败的原因分析程序下载失败是初学者最常遇到的问题之一。根据我的经验原因大致可以分为以下几类硬件连接问题比如SWDIO和SWCLK接反、复位引脚被拉低、电源电压不足芯片配置问题比如读保护被启用、选项字节配置错误软件配置问题比如调试器选择错误、下载算法不匹配。排查时我通常按照以下顺序进行首先检查硬件连接用万用表测量电源电压和SWD引脚的通断然后检查芯片是否处于复位状态可以尝试手动复位后再下载如果还不行尝试用STM32CubeProgrammer连接芯片看是否能识别到设备如果能识别但下载失败检查读保护和选项字节配置如果完全无法识别可能是芯片损坏或者焊接问题。实操心得我遇到过一种情况芯片的BOOT0引脚被拉高导致芯片进入系统存储器启动模式无法正常下载程序。解决方法是将BOOT0接地重新上电后再下载。这个坑花了我一个下午才找到原因所以现在每次画板子都会确保BOOT0有明确的下拉电阻。6.2 串口乱码的排查思路串口乱码通常是由波特率不匹配、时钟配置错误、数据格式不一致引起的。排查时首先确认发送端和接收端的波特率、数据位、停止位、校验位是否一致然后检查STM32的时钟配置是否正确尤其是HSE是否正常起振如果使用的是HSI尝试切换到HSE看是否解决问题。还有一个容易被忽略的点串口引脚的复用功能是否正确配置。STM32F1的USART1默认使用PA9TX和PA10RX但如果使用了重映射功能引脚会变成PB6和PB7。如果重映射配置错误串口不会有任何输出。6.3 中断不响应的常见原因中断不响应的问题可以从以下几个方面排查中断使能位是否置1包括外设的中断使能位和NVIC的中断使能位中断优先级配置是否正确如果优先级分组设置不当可能导致中断被屏蔽中断标志位是否清除如果标志位没有清除中断会反复触发或者不再触发中断服务函数名是否正确必须与启动文件中的向量表一致。我遇到过一次中断不响应的问题排查了很久才发现是NVIC的优先级分组设置错误。STM32F1的优先级分组有5种默认是分组0即4位全部用于抢占优先级。如果代码中使用了分组22位抢占优先级2位响应优先级但NVIC的配置没有相应调整就会导致优先级判断错误。所以使用中断时一定要先调用NVIC_PriorityGroupConfig函数设置优先级分组。6.4 程序跑飞的调试方法程序跑飞是嵌入式开发中最棘手的问题之一可能由数组越界、指针错误、堆栈溢出、中断冲突等原因引起。调试时我通常采用以下方法使用硬件看门狗在程序跑飞时自动复位在关键位置设置断点观察程序执行流程查看HardFault中断分析出错时的寄存器和堆栈信息使用printf输出调试信息定位出错位置。HardFault是ARM Cortex-M3的一个异常当程序出现非法访问、除零错误、未对齐访问等问题时会触发。在HardFault的中断服务函数中可以通过查看LR寄存器的值判断出错前的堆栈指针然后从堆栈中恢复出错时的PC值定位到出错的代码位置。这个方法虽然有些复杂但在没有调试器的情况下非常有用。7. 低功耗模式与唤醒机制7.1 三种低功耗模式的区别STM32F1支持三种低功耗模式睡眠模式、停止模式、待机模式。睡眠模式只关闭CPU时钟外设继续运行唤醒时间最短功耗最高。停止模式关闭所有时钟保留SRAM和寄存器的内容唤醒时间中等功耗较低。待机模式关闭所有电源只保留备份域唤醒后相当于复位功耗最低。选择哪种模式取决于应用的需求。如果只是短暂地降低功耗比如等待一个事件睡眠模式就足够了。如果需要长时间低功耗运行比如电池供电的采集设备停止模式是比较好的选择。如果设备大部分时间处于关机状态偶尔需要唤醒工作待机模式最合适。7.2 唤醒源的配置STM32F1的唤醒源包括外部中断、RTC闹钟、独立看门狗复位等。在停止模式下可以使用任意一个外部中断线唤醒在待机模式下只能使用WKUP引脚、RTC闹钟、独立看门狗复位等有限的唤醒源。配置唤醒源时需要注意以下几点唤醒源的时钟必须在进入低功耗模式之前使能否则无法唤醒唤醒后的初始化代码需要重新执行尤其是时钟配置和外设初始化在进入低功耗模式之前需要关闭不需要的外设时钟进一步降低功耗。实操心得我在一个电池供电的项目中使用停止模式发现功耗比预期高很多。排查后发现是某个GPIO引脚配置为浮空输入在低功耗模式下产生了漏电流。后来将所有未使用的GPIO配置为模拟输入或者推挽输出低电平功耗才降到预期值。所以进入低功耗模式之前一定要检查所有GPIO的状态。8. 项目实战从零搭建一个数据采集系统8.1 系统需求与方案设计假设我们需要做一个数据采集系统通过ADC采集两路模拟信号通过串口将数据发送到上位机同时支持按键控制采集的启停。这个系统虽然简单但涵盖了GPIO、ADC、USART、中断、定时器等多个外设非常适合作为练手项目。方案设计如下使用TIM2定时器触发ADC采样采样率设置为1kHzADC使用DMA搬运数据减少CPU干预串口使用USART1波特率115200通过中断发送数据按键使用EXTI外部中断用于控制采集的启停LED用于指示系统状态。8.2 关键代码与配置说明首先是时钟配置使用HSE 8MHz倍频到72MHzAPB1分频系数2APB2分频系数1。然后是GPIO配置PA0和PA1配置为模拟输入用于ADC采集PA9和PA10配置为复用推挽输出和浮空输入用于USART1PA11配置为下拉输入用于按键PC13配置为推挽输出用于LED。ADC配置为独立模式扫描模式使能连续转换模式关闭外部触发选择TIM2的TRGO事件DMA使能。TIM2配置为向上计数模式自动重装载值为72-1预分频系数为1000-1这样定时器的溢出频率就是72MHz / 1000 / 72 1kHz。USART1配置为115200波特率8位数据位1位停止位无校验接收中断使能。8.3 调试过程与问题记录在调试过程中我遇到了几个问题。第一个问题是ADC采集的数据跳动比较大排查后发现是VDDA的滤波电容容量不足增加了一个10uF的钽电容后明显改善。第二个问题是串口发送数据时偶尔会丢包排查后发现是发送中断的优先级设置过低被ADC中断抢占调整优先级后解决。第三个问题是按键抖动导致误触发在中断服务函数中增加了20ms的延时消抖后解决。这个项目虽然简单但涉及的知识点比较全面适合作为STM32F1的入门实战。完成这个项目后你对STM32F1的时钟配置、GPIO操作、中断管理、外设驱动都会有比较深入的理解。9. 关于STM32F1的一些个人体会用了这么多年STM32F1我最大的感受是它可能不是性能最强的也不是最便宜的但它是最“稳”的。这种稳体现在多个方面资料稳不管遇到什么问题基本都能找到答案供货稳虽然经历过缺货但整体上比其他型号好很多生态稳各种库、工具、社区支持都非常成熟。当然它也有它的局限性。没有浮点运算单元做复杂的数学运算会比较吃力RAM和Flash的容量有限跑RTOS或者复杂的协议栈会比较紧张外设的版本比较老某些功能不如新系列灵活。但这些局限性并不妨碍它成为一颗经典的芯片就像有人说的“STM32F1不是最好的但它是最合适的。”如果你正在学习嵌入式开发或者正在寻找一颗适合产品原型的芯片STM32F1值得你花时间去了解。它不会让你失望也不会让你觉得太难。它就像一位老朋友虽然不常联系但需要的时候总是在那里。
阅读完成 · 觉得有帮助?