1. 从“点灯”到“控世界”STM32理论到底在讲什么很多人第一次接触STM32都是从一块F103C8T6最小系统板开始的。板子到手装好Keil打开例程编译下载LED亮了然后呢然后就没有然后了。我见过太多人卡在这个“然后”上——会点灯但不知道为什么灯会亮会抄代码但换个引脚就报错会用库函数但说不清HAL库和标准库到底差在哪。STM32理论这个词听起来很虚但它恰恰是把你从“抄代码的人”变成“写代码的人”的那道分水岭。这篇文章想聊的就是围绕STM32F103C8T6这颗经典芯片把GPIO的八种工作模式、中断函数的执行逻辑、PWM从占空比计算到实际输出的完整链路、定时器的各种模式选择以及串口中断回调、编码器接口、时钟树配置这些核心理论点用从业者的视角掰开揉碎讲清楚。不管你是刚入门的电子专业学生还是从51单片机转过来的工程师或者是正在做毕业设计需要快速上手STM32的开发者这些内容都能帮你建立起一套完整的认知框架。我不会只告诉你“这样配置就行”而是会解释“为什么必须这样配置”以及“不这样配置会出什么问题”。STM32的理论体系其实可以拆成三条主线第一条是时钟与总线它决定了芯片内部各个外设的运行节奏第二条是GPIO与复用功能它是芯片与外部世界交互的物理通道第三条是定时器与中断它是实现精确时序控制和实时响应的核心机制。这三条线交织在一起构成了STM32绝大部分应用的基础。理解了这三条线再看PWM调速、串口通信、编码器测速、超声波测距这些具体应用就会发现它们不过是这三条线的不同组合方式而已。2. GPIO的八种工作模式选错了电路就废了2.1 八种模式到底在解决什么问题GPIO的八种工作模式经常被当成八选一的填空题来记但如果你不理解每种模式背后的电路结构选型时就只能靠猜。这八种模式本质上是四个维度的组合输入还是输出、推挽还是开漏、上拉还是下拉、复用还是普通。把这四个维度理清了八种模式自然就记住了。先看输入方向。浮空输入模式下引脚内部既不上拉也不下拉电平完全由外部电路决定。这种模式适合外部已经有明确驱动源的场景比如外部接了上拉电阻的按键或者由另一个芯片驱动的信号线。但如果你把一个浮空输入的引脚悬空读到的值就是随机的因为引脚处于高阻态任何微弱的干扰都能改变它的电平。上拉输入和下拉输入则是在内部接入了一个几十千欧的电阻分别把引脚默认拉到高电平或低电平。按键检测是最典型的应用按键一端接引脚另一端接地引脚配置为上拉输入按下时读到低电平松开时读到高电平。这样就不需要外部再加电阻了。模拟输入模式比较特殊它直接关闭了引脚的数字输入通道让信号原封不动地送到ADC模块。如果你用ADC采集电压但引脚配置成了浮空输入读出来的值会跳得厉害因为施密特触发器还在工作会把模拟信号整形成数字信号再送给ADC那采集出来的就不是真实电压了。再看输出方向。推挽输出是最常用的模式它内部有两个MOS管一个负责拉高一个负责拉低输出能力强高低电平都有驱动能力。开漏输出则只有下拉的MOS管拉高需要外部上拉电阻配合。开漏输出的优势在于可以实现“线与”逻辑——多个开漏引脚接在同一条线上任何一个拉低整条线就是低电平。I2C总线的SDA和SCL就是典型的开漏输出应用因为总线上有多个设备谁都可以拉低但谁都不能强行拉高。复用推挽和复用开漏则是把引脚的控制权交给片上外设比如定时器的PWM输出、串口的TX引脚。这时候你不能再通过写GPIO的输出数据寄存器来控制引脚了引脚电平完全由外设决定。很多人配置PWM输出时忘了把引脚设为复用模式结果PWM死活出不来就是因为引脚还处于普通输出模式定时器的信号根本传不到引脚上。2.2 GPIO地址与寄存器映射为什么库函数能操作硬件STM32的GPIO之所以能被程序控制是因为它的寄存器被映射到了内存地址空间。以F103C8T6为例GPIOA的基地址是0x40010800GPIOB是0x40010C00GPIOC是0x40011000每个GPIO端口占用0x400的地址空间。在这个空间里偏移0x00是配置低寄存器偏移0x04是配置高寄存器偏移0x08是输入数据寄存器偏移0x0C是输出数据寄存器偏移0x10是位设置寄存器偏移0x14是位复位寄存器。标准库里的GPIO_SetBits(GPIOA, GPIO_Pin_5)本质上就是往GPIOA的位设置寄存器写入0x0020。HAL库里的HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET)最终也是操作同一个寄存器。理解这一层你就能明白为什么库函数能操作硬件——它不过是对内存地址的读写封装而已。更重要的是当你在调试时发现某个引脚行为异常你可以直接在调试器的内存窗口里查看这些寄存器的值看看配置位到底写对没有这比反复检查代码要高效得多。2.3 GPIO模式选择的实战判断流程面对一个具体的应用场景怎么快速确定该用哪种GPIO模式我总结了一个判断流程先问这个引脚是输入还是输出再问信号是数字还是模拟然后问是否需要外部多个设备共享最后问控制权在GPIO还是外设。按键检测用上拉或下拉输入I2C用复用开漏SPI用复用推挽PWM输出用复用推挽ADC采集用模拟输入普通LED驱动用推挽输出多个LED共阳极驱动用开漏输出。这个对应关系不是死记硬背的而是由电路原理决定的。比如为什么I2C必须用开漏因为I2C总线上可以挂多个主从设备如果都用推挽输出一个设备输出高电平另一个输出低电平两个MOS管直接对电源和地短路芯片就烧了。开漏输出则不会出现这个问题因为没有人能强行拉高大家只能拉低或者释放总线。注意配置GPIO之前必须先使能对应端口的时钟。F103的GPIOA挂在APB2总线上忘记调用RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE)或者HAL库的__HAL_RCC_GPIOA_CLK_ENABLE()后面所有配置都不会生效而且不会报错引脚就是没反应。这个坑我踩过不止一次。3. 中断函数从触发到返回的完整链路3.1 中断优先级与嵌套的底层逻辑STM32的中断系统比51单片机复杂得多核心在于NVIC嵌套向量中断控制器。每个中断通道都有两个优先级抢占优先级和响应优先级。抢占优先级决定了一个中断能否打断另一个正在执行的中断响应优先级则决定了同一抢占优先级下多个中断同时到来时的处理顺序。举个例子假设中断A的抢占优先级是1中断B的抢占优先级是2。当B正在执行时A触发了A可以打断B因为A的抢占优先级更高。等A执行完B继续执行。但如果A和B的抢占优先级相同只是响应优先级不同那么A不能打断B必须等B执行完才能处理A。这个机制在实际项目中非常关键比如你在做电机控制PWM更新中断需要极高的实时性而串口接收中断可以稍微慢一点那就应该把PWM中断的抢占优先级设得比串口高。优先级分组决定了抢占优先级和响应优先级各占几位。F103支持5种分组方式从0到4。分组0表示4位全用于响应优先级没有抢占优先级所有中断都不能互相打断。分组4表示4位全用于抢占优先级没有响应优先级。最常用的是分组22位抢占优先级2位响应优先级这样既有嵌套能力又有足够的优先级区分度。3.2 中断服务函数的编写规范中断服务函数的函数名不是随便起的它必须和启动文件里的中断向量表里的名字完全一致。比如串口1的中断服务函数在标准库中叫USART1_IRQHandler在HAL库中也是这个名字你写成USART1_IRQHandler少一个字母都不行编译能过但中断永远不会触发。中断服务函数里要做的事情应该尽可能少。我见过有人在串口中断里做协议解析、做数据存储、甚至做浮点运算结果就是中断执行时间过长导致其他中断被延迟甚至丢失。正确的做法是在中断里只做最紧急的事情——比如把接收到的数据存到一个缓冲区然后设置一个标志位主循环里检测到标志位再做后续处理。这就是所谓的“快进快出”原则。还有一个容易忽略的点是中断标志位的清除。很多中断在进入服务函数后需要手动清除标志位否则中断会反复触发。比如串口接收中断你读取了数据寄存器DR硬件会自动清除RXNE标志。但如果你用的是HAL库的HAL_UART_IRQHandler它内部会帮你处理这些标志位。如果你自己写中断服务函数就必须在合适的位置调用__HAL_UART_CLEAR_FLAG或者直接读寄存器来清除标志。3.3 串口中断回调函数的执行流程HAL库把中断处理分成了两层底层的中断服务函数和上层的回调函数。以串口接收为例USART1_IRQHandler被触发后会调用HAL_UART_IRQHandler这个函数会根据中断类型做相应处理然后调用弱定义的HAL_UART_RxCpltCallback。你只需要在代码里重写这个回调函数就能在收到数据时执行自己的逻辑。这个机制的好处是解耦——你不需要关心底层寄存器怎么操作只需要关注“收到数据后要做什么”。但坏处是执行路径变长了从触发中断到你的回调函数被执行中间经过了多层函数调用。对于波特率很高的场景比如115200甚至更高如果回调函数里做的事情太多就可能来不及处理下一个字节。这时候可以考虑用DMA加空闲中断的方式让DMA自动搬运数据空闲中断只负责通知主循环“一帧数据收完了”。提示使用HAL库的串口接收中断时HAL_UART_Receive_IT函数每次只能接收指定数量的字节收完后需要重新调用才能继续接收。如果你忘记在回调函数里再次调用它串口就只能收到一次数据。这个坑在新手中极其常见。4. PWM从理论到输出占空比、频率与故障保护4.1 PWM的基本原理与占空比计算公式PWM的全称是脉冲宽度调制说白了就是通过调节方波的高电平时间占总周期的比例来等效地控制输出功率。比如一个5V的电源用50%占空比的PWM驱动一个LEDLED两端的平均电压就是2.5V亮度大约是一半。如果用80%占空比平均电压就是4V亮度明显更亮。这就是PWM调光的原理。占空比的计算公式很简单占空比 高电平时间 / 周期时间 × 100%。但在STM32里占空比是通过比较寄存器CCR和自动重装载寄存器ARR来设置的。假设ARR的值是999CCR的值是300那么占空比就是300/100030%。这里为什么是除以1000而不是999因为计数器从0数到999一共是1000个计数周期。这个细节很多人会搞错导致占空比总是差一点点。PWM的频率则由ARR和预分频器PSC共同决定。公式是PWM频率 定时器时钟频率 / ((PSC1) × (ARR1))。假设定时器时钟是72MHzPSC设为71ARR设为999那么PWM频率就是72MHz / (72 × 1000) 1kHz。这个频率下LED调光不会看到闪烁电机调速也能平稳运行。4.2 不同应用场景下的PWM频率选择PWM频率选多少取决于你驱动的是什么负载。LED调光一般用1kHz到5kHz太低会看到闪烁太高则可能因为LED的响应速度跟不上而导致亮度非线性。舵机控制必须用50Hz也就是周期20ms因为舵机的内部电路就是按照这个标准设计的高电平宽度在0.5ms到2.5ms之间对应0度到180度。如果频率不对舵机会抖动甚至不转。直流电机调速的频率范围比较宽从1kHz到20kHz都可以。频率太低电机会有啸叫声频率太高则MOS管的开关损耗会增加。我一般用10kHz到15kHz这个范围内电机运行平稳MOS管也不会太热。步进电机的PWM频率则取决于细分设置和转速要求需要根据具体驱动器的手册来定。有一个热词提到了“mg310p20_v7.4v用多少pwm频率”这应该是一个具体的电机型号。对于这类直流减速电机我建议从10kHz开始试如果电机发热严重就降低频率如果有啸叫就提高频率。没有绝对的标准值要根据实际负载和驱动电路来调整。4.3 PWM故障保护与刹车功能在一些安全要求高的应用中比如电机驱动PWM输出需要具备故障保护功能。STM32的高级定时器如TIM1和TIM8支持刹车输入当刹车引脚检测到有效电平时硬件会自动把PWM输出强制到预设的安全状态比如全部拉低或者全部拉高而不需要软件干预。这个响应速度是纳秒级的比软件中断快得多。配置刹车功能需要设置刹车极性、刹车后的输出状态、以及是否自动恢复。刹车极性决定了刹车引脚是高电平有效还是低电平有效。刹车后的输出状态可以设为高阻、拉高或拉低具体取决于你的驱动电路。自动恢复功能则决定了刹车信号消失后PWM是自动恢复输出还是需要软件手动清除标志后才能恢复。在电机驱动中我通常会把刹车后的输出设为拉低这样电机就会自由滑行而不是被短路制动。4.4 PWM呼吸灯与RGB调光的实现细节呼吸灯的本质是让PWM占空比随时间做周期性变化。最简单的做法是在主循环里用一个变量记录当前占空比每次循环增加或减少一个步进值到达边界后改变方向。但这种方式占空比的变化是线性的人眼对亮度的感知却是指数型的所以线性变化的呼吸灯看起来会“中间亮得久两头暗得快”。更好的做法是用查表法预先计算好一条指数曲线或者正弦曲线的占空比序列存在数组里主循环里依次读取。这样呼吸效果会非常自然。RGB调光也是类似的道理三个通道分别用PWM控制通过调节三个通道的占空比来混合出不同的颜色。需要注意的是RGB LED的三个通道亮度特性可能不一致需要分别校准否则混出来的白色会偏色。5. 定时器STM32最复杂的片上外设5.1 定时器的分类与选型STM32F103C8T6内部有多个定时器分为高级定时器、通用定时器和基本定时器。高级定时器TIM1功能最全支持PWM互补输出、刹车输入、死区插入适合电机控制。通用定时器TIM2到TIM5支持PWM输出、输入捕获、编码器接口适合大多数常规应用。基本定时器TIM6和TIM7只能做定时中断没有外部引脚适合做系统时基。选型的原则很简单需要互补PWM和刹车就用高级定时器需要编码器接口就用通用定时器只需要定时中断就用基本定时器。但要注意引脚冲突因为定时器的通道和GPIO引脚是绑定的比如TIM2的通道1只能在PA0上输出如果你PA0已经被占用了就只能换其他定时器或者重映射引脚。5.2 定时器模式详解从单脉冲到编码器定时器的模式非常多每一种模式解决一类特定问题。单脉冲模式适合需要精确控制脉冲宽度的场景比如超声波测距的触发信号你只需要输出一个特定宽度的高电平脉冲然后定时器自动停止。输入捕获模式用来测量外部信号的频率和占空比比如读取编码器的脉冲或者测量超声波回波的高电平时间。编码器接口模式则是专门为增量式编码器设计的它可以自动根据A相和B相的相位关系进行加减计数不需要软件干预。PWM输入模式是输入捕获的一个特例它可以同时测量一个PWM信号的频率和占空比只需要一个定时器通道。原理是同一个信号同时接到两个捕获通道一个捕获上升沿一个捕获下降沿通过两个捕获值的时间差计算占空比。这个模式在解码遥控接收机的PWM信号时非常有用。5.3 定时器时钟树与频率计算定时器的时钟来源不是直接来自系统时钟而是经过APB总线分频后再倍频。F103的APB1总线最高36MHzAPB2最高72MHz。但定时器的时钟有一个特殊规则如果APB分频系数是1定时器时钟等于APB时钟如果APB分频系数大于1定时器时钟等于APB时钟的2倍。所以当APB1分频系数为2时APB1时钟是36MHz但挂载在APB1上的定时器时钟是72MHz。这个规则很容易让人算错定时时间。比如你要用TIM2做一个1ms的定时中断TIM2挂在APB1上时钟是72MHz。你设置PSC71ARR999那么定时周期就是(711)×(9991)/72MHz 1ms。如果你误以为TIM2的时钟是36MHz算出来的定时时间就会差一倍。6. 常见问题与排查技巧实录6.1 程序下载后没反应怎么办这是新手遇到最多的问题。程序编译通过了下载也提示成功了但板子就是没反应。排查顺序应该是这样的先确认时钟配置对不对用调试器看看SystemInit函数有没有被正确执行时钟频率是不是你期望的值。再确认GPIO时钟使能了没有这是最常见的遗漏。然后确认引脚配置对不对用万用表量一下引脚电压如果是推挽输出高电平应该是3.3V低电平应该是0V。最后确认程序有没有卡在某个死循环里比如HAL库的HAL_Delay在中断里调用会导致死锁因为HAL_Delay依赖SysTick中断而中断里不能再被中断。6.2 串口通信乱码或收不到数据串口问题一般出在三个地方波特率、时钟配置、引脚连接。波特率不匹配是最常见的发送方和接收方必须用同一个波特率。但即使波特率设成一样的如果系统时钟配置错了实际波特率也会偏。比如你外部晶振是8MHz但代码里按12MHz来配置PLL系统时钟就不是72MHz串口波特率自然也不对。用示波器量一下TX引脚测量一个位的宽度就能反推出实际波特率。引脚连接方面TX要接对方的RXRX要接对方的TX这个不用多说。但有时候开发板上的USB转串口芯片和MCU之间的连接是交叉的你再用杜邦线接出去的时候又交叉了一次结果就变成了TX接TX自然收不到数据。6.3 PWM输出没有波形PWM没输出的原因通常有这几个定时器时钟没使能、GPIO没配置为复用模式、定时器没启动、比较值设置不对。我遇到过一次定时器配置都正确但PWM就是没输出最后发现是GPIO的复用功能重映射没开启。F103的某些定时器通道默认不在常用的引脚上需要调用GPIO_PinRemapConfig开启重映射才能输出到指定引脚。还有一个隐蔽的问题是高级定时器的PWM输出需要额外使能主输出。TIM1和TIM8的PWM输出默认是关闭的必须调用TIM_CtrlPWMOutputs(TIM1, ENABLE)才能让PWM到达引脚。这个在标准库里是一个单独的步骤很多人会漏掉。6.4 中断不触发或反复触发中断不触发先检查中断使能了没有包括外设层面的中断使能和NVIC层面的中断使能两个都要开。再检查中断标志位清了没有如果标志位没清中断会反复触发看起来像是程序卡死在中断里。还有一种情况是中断优先级配置冲突两个中断的抢占优先级和响应优先级完全一样同时触发时行为不确定。反复触发最常见的原因是忘记清除中断标志。比如定时器更新中断你需要在中断服务函数里调用TIM_ClearITPendingBit或者HAL库的__HAL_TIM_CLEAR_IT。如果用的是HAL库的HAL_TIM_IRQHandler它内部会帮你清除标志但如果你自己写了中断服务函数而没有调用HAL的处理函数就必须手动清除。6.5 常见问题速查表现象可能原因排查方法程序下载后无反应GPIO时钟未使能检查RCC配置代码串口乱码系统时钟配置错误用示波器测位宽反推波特率PWM无输出GPIO未设为复用模式检查GPIO初始化结构体中断不触发NVIC未使能检查NVIC_EnableIRQ调用中断反复触发标志位未清除在中断服务函数中清除标志定时时间不对定时器时钟计算错误确认APB分频与倍频关系ADC读数跳动引脚未设为模拟输入检查GPIO_Mode配置编码器计数方向反A/B相接线颠倒交换A/B相引脚或取反计数提示调试STM32时善用调试器的外设寄存器查看功能。在Keil的Debug模式下可以通过System Viewer直接查看GPIO、TIM、USART等外设的寄存器值比在代码里加打印要直观得多。特别是配置了但没生效的时候看一眼寄存器就知道问题出在哪。7. 工具链与开发环境从Keil到VSCode7.1 Keil5兼容C51和STM32的安装要点很多人电脑上同时装了Keil C51和Keil MDK两个版本装在一起容易冲突。正确的做法是先装C51再装MDK并且装在不同的目录下。装完之后需要把C51的安装目录下的UV4文件夹里的文件复制到MDK的对应目录或者用注册表工具合并。但更省事的做法是用Keil5它本身就支持多内核只需要安装对应的芯片包就能同时开发C51和STM32。芯片包的安装也很关键。F103C8T6需要安装Keil.STM32F1xx_DFP包安装完成后在新建工程时才能找到对应的器件。如果找不到器件多半是芯片包没装或者装错了版本。STM32G070CBT6则需要STM32G0系列的芯片包不同系列的包不能混用。7.2 VSCode配置STM32开发环境用VSCode开发STM32需要几个组件编译器用arm-none-eabi-gcc调试器用OpenOCD或者ST-Link Utility构建系统用Make或者CMake。VSCode本身只负责编辑和调试界面实际的编译和下载还是靠命令行工具。配置步骤大致是先安装arm-none-eabi-gcc并添加到PATH再安装OpenOCD然后在VSCode里安装Cortex-Debug插件。工程目录下需要有一个Makefile或者CMakeLists.txt来定义编译规则还需要一个OpenOCD的配置文件来指定调试器类型和目标芯片。这套环境搭起来比Keil麻烦但好处是跨平台、免费、而且可以用VSCode强大的代码补全和Git集成功能。7.3 ST-Link Utility与程序下载ST-Link Utility是ST官方提供的下载工具支持读取、写入、擦除芯片Flash还可以查看芯片的UID和选项字节。当你用IDE下载程序失败时可以用ST-Link Utility先连接一下看看能不能识别到芯片。如果能识别但下载失败可能是Flash被读保护了需要在选项字节里解除保护。如果连芯片都识别不到那就是硬件连接问题检查SWDIO和SWCLK两根线有没有接反复位引脚有没有被拉低。8. 从理论到项目几个典型应用的理论映射8.1 超声波测距中的定时器输入捕获超声波测距模块HC-SR04的工作流程是给Trig引脚一个10微秒的高电平脉冲模块发射超声波然后Echo引脚会输出一个高电平高电平的持续时间就是超声波往返的时间。用STM32测量这个时间最合适的方法就是定时器输入捕获。配置思路是用一个定时器通道捕获Echo引脚的上升沿和下降沿上升沿时记录计数器的值下降沿时再记录一次两次之差就是高电平的持续时间。假设定时器时钟是1MHz计数一次是1微秒那么时间差乘以声速340米/秒再除以2就是距离。这里除以2是因为超声波从发射到接收走了一个来回。实际项目中要注意的是超声波模块的Echo引脚输出是5V电平而STM32的GPIO是3.3V耐压直接接上去可能会损坏引脚。需要在Echo和MCU之间加一个电平转换电路最简单的做法是用两个电阻分压把5V降到3.3V左右。8.2 编码器测速中的定时器编码器接口增量式编码器输出A相和B相两路方波相位差90度。正转时A相超前B相反转时B相超前A相。STM32的定时器编码器接口模式可以自动识别相位关系并加减计数不需要软件干预。配置时把A相接定时器的通道1B相接通道2然后调用TIM_EncoderInterfaceConfig选择编码器模式。编码器模式有三种只在A相计数、只在B相计数、在A相和B相都计数。第三种模式是四倍频分辨率最高。假设编码器线数是500线四倍频后转一圈就是2000个计数。如果定时器在1秒内读到的计数值是2000那么转速就是1圈/秒也就是60RPM。8.3 串口中断回调在协议解析中的应用很多传感器模块通过串口输出数据比如GPS模块、蓝牙模块、WiFi模块。这些模块的数据通常是按帧发送的每帧有固定的帧头和帧尾。用串口中断接收时可以在回调函数里逐字节判断帧头收到帧头后开始把数据存入缓冲区收到帧尾后置位一个标志主循环里检测到标志就解析这一帧数据。这种方式的难点在于处理帧头误判和缓冲区溢出。如果数据内容里恰好出现了和帧头一样的字节就会导致解析错误。解决办法是用状态机来解析而不是简单的字节匹配。状态机根据当前状态决定下一个字节是帧头、数据还是帧尾这样即使数据里出现了帧头字节只要状态不对就不会误判。9. 时钟树STM32的心跳与节奏9.1 时钟源的选择与PLL配置STM32F103有三个时钟源HSI内部高速时钟8MHz、HSE外部高速时钟通常8MHz晶振、LSI内部低速时钟40kHz。系统时钟一般选择HSE经过PLL倍频到72MHz。PLL的配置是HSE先除以一个预分频系数得到PLL输入时钟然后乘以倍频系数得到PLL输出时钟。F103的PLL输入时钟必须在4MHz到16MHz之间所以8MHz的HSE可以直接输入不需要预分频。配置PLL的代码在标准库里是RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9)意思是HSE不分频倍频9倍8MHz×972MHz。在HAL库里则是通过RCC_OscInitStruct.PLL.PLLM和PLLN来设置F1系列的HAL库PLL配置略有不同需要参考具体的HAL版本。9.2 总线分频与外设时钟系统时钟72MHz出来之后要经过AHB分频器、APB1分频器、APB2分频器分别给不同的外设提供时钟。AHB最高72MHzAPB2最高72MHzAPB1最高36MHz。所以APB1的分频系数至少是2APB2的分频系数可以是1。外设挂载的总线决定了它的最高时钟频率。GPIO、USART1、SPI1、ADC1挂在APB2上可以跑到72MHz。USART2、USART3、I2C1、I2C2、TIM2到TIM7挂在APB1上最高36MHz。但前面说过定时器有时钟倍频机制所以TIM2到TIM7的实际时钟还是72MHz。这个细节在计算定时器参数时一定要搞清楚否则定时时间会差一倍。9.3 时钟安全系统与备份域STM32还有一个时钟安全系统CSS当HSE失效时CSS会自动切换到HSI并产生一个中断通知软件。这个功能在工业控制中很重要因为外部晶振可能因为振动、温度等原因停振如果没有CSS系统就会死机。配置CSS只需要调用RCC_ClockSecuritySystemCmd(ENABLE)然后在中断服务函数里处理切换后的善后工作。备份域则涉及到RTC和备份寄存器它们由VBAT供电即使主电源断开只要纽扣电池还有电RTC就能继续走时。备份域的时钟源可以是LSI、LSE或者HSE的128分频。LSE是外部32.768kHz晶振精度最高适合做RTC时钟源。10. 我个人的一些实操体会STM32的理论知识看再多不动手都是空的。我的建议是每学一个外设就找一个具体的应用场景去实现它。学GPIO就做按键控制LED学定时器就做PWM呼吸灯学中断就做旋转编码器计数学串口就做上位机通信。做的过程中遇到问题再去查手册、查资料这样学到的知识才是活的。还有一点不要怕看英文参考手册。STM32的参考手册有上千页但真正用到的可能就那几十页。学会用PDF的搜索功能根据关键词定位到相关章节比从头到尾看要高效得多。寄存器描述部分看起来枯燥但当你遇到库函数解决不了的问题时直接操作寄存器往往是最快的解决途径。最后代码要养成规范注释的习惯。STM32的配置代码往往很长过两个月再看如果没有注释你自己都不记得当时为什么这么配。特别是时钟配置和GPIO配置注释清楚每个参数的含义以后移植到其他项目时能省很多时间。