type
slug
status
summary
icon
category
date
tags
password
0 时钟

时钟的设置可以通过CubeMax图形化页面准确的算出。
(下面默认的例子是以STM32F407为例,F401会额外给出)
0.1 什么是时钟?
在数字电路里,“时钟”是一个周期性的方波信号,它的作用是:
- 控制处理器或外设中所有电路的运行节奏;
- 相当于“节拍器”或“鼓点”,每跳动一下,就执行一条指令或传输一个数据;
- 数字电路没有“自发”动作,只有在时钟驱动下才运行。(所以可以理解为时钟驱动MCU运行,从而驱动外设)
- 比如 1MHz 表示每秒100万次电平跳变。
在STM32这种微控制器中,有很多不一样的模块(CPU 核心、GPIO、USART、ADC、定时器等),每一个模块想要工作,都需要有对应的时钟信号输入。
0.2 为什么使用外设前需要打开时钟?
这是一个节能和资源管理的机制。
STM32芯片内部的每个外设(比如 USART、ADC、TIM)都是连接在 AHB、APB1、APB2 等总线上的,而这些总线都从系统主时钟 SYSCLK 分出来专门供给外设模块的“时钟信号”。
🔒 默认情况下,这些外设的时钟是“关闭的”(外设处于冻结状态),不工作也不耗电。
你可以把 STM32 想象成一个工厂:
- 电源总线是“系统电源”;
- 各个车间(外设)比如 UART、ADC、TIM 是“子工厂”;
- 要让这些车间开始运转,就必须“打开他们的供电开关”——也就是打开“外设的时钟”!
0.3 时钟的种类

0.4 时钟的分发结构(STM32的时钟树)
STM32 的时钟系统采用“时钟树”结构,主时钟通过分频器分发到不同部分。
1 STM32 定时器概述
1.1 定时器的概述
- 功能
- 定时器的基本功能,但是STM32的定时器除了具有定时功能之外,也具有定时器中断功能还具有输入捕获(检测外部信号)以及输出比较功能(输出不同的脉冲),可以利用STM32定时器输出某种频率的脉冲信号来控制产品(控灯亮度、控制直流电机的转速、控制能机的角度....)
- 定时器的种类
- STM32由于资源丰富,所以提供了多种定时器,一共提供 14个定时器(不包含系统定时器、不包含看门狗定时器),分为高级定时器(TIM1和TIM8)、通用定时器(TIM2~TIM5+TIM9~TIM14,一共10个)、基本定时器(TIM6 和TIM7)。
- 在F401中,定时器是TIM1,TIM2~TIM5,TIM9~TIM11,都是84MHZ
- 没有基本定时器TIM6和TIM7,和高级定时器TIM8
- 也没有TIM12~TIM14(辅助高级定时器)
1.2 基本定时器的使用
1.2.1 基本定时器框图

- 基本定时器只能向上计数:0~2^16-1;
- 在寄存器map里面看定时器挂载在哪里的总线上
- SYSCLK:168MHz
- APB1总线频率(外设时钟):42MHz;APB2总线频率(外设频率):84MHz
- APB1定时器时钟:84MHz;APB2定时器时钟:168MHz
- STM32提供TIM6和TIM7作为基本定时器,属于STM32内部资源(没有通道就没有输入捕获和输出比较功能),都是16bit定时器,并没有和GPIO引脚连接,TIM6和TIM7都挂载在APB1总线下,而APB1 定时器时钟频率为84MHZ.


- 基本定时器框图
- 这里的内部时钟CK_INT:就是前面所说的APB1的定时器频率84MHz。
- 如果不处理,那么84MHz即1s钟振荡84M次。
- 分频:预分频器可对计数器时钟频率进行分频,分频系数介于1和65536之间。TIMX_PSC寄存器中的16位寄存器所控制的16位计数器。
- (84-1),
从0开始???(因为这个是基于16位寄存器控制的16位计数器,所以是从0开始计数,想要n分频,设置寄存器的值应该为n-1),时钟频率降为1MHz(1s振荡10^6次,振荡1次计时1us) - (8400-1),时钟将为10000Hz,即1s振荡10000次,即1ms振荡10次,100us1次。所以分频越高,时钟频率越低,周期更长,计时越慢
- 基本定时器只能向上计数
- 计数器寄存器(TIMx_CNT):记录当前计数值,从0开始向上+1
- 预分频定时器(TIMx_PSC):设置计数一次的周期(单次时间)
- 自动重载寄存器(TIMx_ARR):设置定时的次数(定时总时间)
- 影子寄存器:如图所示有影子的寄存器即有影子寄存器。所以PSC和ARR有影子寄存器。
- 自动重载寄存器是预装载的(即最初需要写入值)。每次尝试对自动重载寄存器执行读写操作时,都会访问预装载寄存器。预装载寄存器的内容既可以直接传送到影子寄存器,也可以在每次发生更新事件UEV时传送到影子寄存器,这取决于TIMxCR1寄存器中的自动乘载预装载使能位(ARPE)。当计数器达到上溢值并且TIMx_CR1寄存器中的UDIS位为0时,将发送更新事件。该更新事件也可由软件产生。
- PSC寄存器有缓冲(即含有影子寄存器)。因此可以对预分频器进行实时更改。而新的预分频比将在下一场更新事件发生时被采用。
- 所以想要对基木定时器的定时功能进行设置,只需要把TIM_CNT、TIM_PSC、TIM_ARR 寄存器设置正确即可。
- 举个例子:TIM6的时钟频率为84MHZ,如果想要降低频率,公式:频率 = 84MHz /(PSC+1). 计数次数 = 定时时间/计数一次的时间 -1
- 1MHZ=84MHZ/(83+1),频率就降为1MHZ,也就意味着TIM_CNT寄存器累计计数1次的时间是1us,如果打算利用TIM6定时1ms,也就是说TIM_ARR寄存器的值应该设置为1000-1,只要TIM_CNT==TIM_ARR,就说明时间到了.(PSC=84-1=83,ARR=1000-1=999).
- 如果时间到了,TIM_CNT清0,重新从0计数到自动重载值(TIMX_ARR寄存器的内容),并生成计数器上溢事件。
- 对时钟进行分频的目的就是为了延长定时时间(因为ARR是最高为65536,但是每次计数所对应的时间是可以更改的,所以可以延长单位计时长度来延长总计时时间),但是不是必须分频,由用户指定是否分频。
- 计数值=自动重装值产生的中断,我们叫做更新中断,这个中断会通向NVIC,再配置好NVIC的定时器通道,就能得到CPU响应(即跳转到中断服务程序),编写中断服务程序就可以了。
- 计数值=自动重装值产生的事件,我们叫做更新事件。更新事件不会触发中断,但是可以触发内部其他电路的工作。
- 主从触发DAC的功能:内部硬件再不受程序控制的情况下实现自动运行
- 定时器的更新事件映射到触发输出(TRGO:Trigger Output)的位置,然后TRGO直接接到DAC的触发转换引脚上,整个过程不需要软件的驱动,硬件自动。


- 对于STMF401,说明书我们可以看到没有TIM8,TIM12,TIM13,TIM14,TIM6,TIM7.所以STM32F401没有一般定时器(TIM6和TIM7)
- TIM1为高级定时器,TIM2~TIM5为通用定时器(TIM3和TIM4 16bit;TIM2和TIM5 32bit(定时范围更广)),TIM9~TIM11为补充定时器
定时器号 | 所属总线 | 定时器类型 | 位宽 | 主要功能 |
TIM1 | APB2 | 高级定时器 | 16位 | PWM、互补输出、死区、中心对齐等复杂功能 |
TIM2 | APB1 | 通用定时器 | 32位 | 标准定时功能、输入捕获、PWM |
TIM3 | APB1 | 通用定时器 | 16位 | 标准定时功能、输入捕获、PWM |
TIM4 | APB1 | 通用定时器 | 16位 | 标准定时功能、输入捕获、PWM |
TIM5 | APB1 | 通用定时器 | 32位 | 标准定时功能、输入捕获、PWM |
TIM9 | APB2 | 补充定时器 | 16位 | 简化版高级定时器,2个通道 |
TIM10 | APB2 | 补充定时器 | 16位 | 简化定时器,1个通道 |
TIM11 | APB2 | 补充定时器 | 16位 | 简化定时器,1个通道 |
SysTick | 内核专用 | 系统定时器 | 24位 | Cortex-M4 内核自带定时器(常用于 OS) |


由图和分频系数可知,AHB的频率是84MHz。APB1的总线频率是42MHz,APB2的总线频率是84MHz。APB1的定时频率就是84MHz,APB2的定时器时钟就是84MHz。
定时器 | 所属总线 | 总线频率 | 实际定时器时钟频率 |
TIM1 | APB2 | 84 MHz | 84 MHz |
TIM9 | APB2 | 84 MHz | 84 MHz |
TIM10 | APB2 | 84 MHz | 84 MHz |
TIM11 | APB2 | 84 MHz | 84 MHz |
TIM2–5 | APB1 | 42 MHz | 84 MHz |
1.2.2 定时器的结构体配置
思考:那应该如何去配置基本定时器的相关参数??可以通过结构体进行配置,可以査阅stm32f4xx_tim.h头文件中包含了定时器的参数配置结构体,如下图

- 设置定时器的分频系数 TIM _Prescaler 范围 0x0000 and 0xFFFF

- 设置定时器的计数模式 TIM_CounterMode
- 基本定时器只能设向上计数
- 中心对齐模式123:关键寄存器:TIMx_CR1,STM32的中心对齐模式由TIMx_CR1寄存器的CMS[1:0]位控制:
- 中心对齐模式1:首先从0开始向上计数,直到计数值等于自动重载寄存器(ARR)值,产生一个上溢事件,然后计数器开始向下计数,直到计数值为0时,产生一个下溢事件.随后计数器重新从0开始计数,如此循环往复。
- 中心对齐模式2:其他同中心对齐模式1,更新事件发生在CNT=ARR时,产生一个上溢事件
- 中心对齐模式3,上升下降都有更新事件。

STM32的中心对齐模式由 TIMx_CR1 寄存器的CMS[6:5] 位控制:


- 设置定时器计数周期 TIM_Period 指的是重载寄存器的值
- 指定要加载到ARR寄存器的值。在下一次更新事件时自动重新加载寄存器。范围0x0000 ~ 0xFFFF(65536)
- 设置定时器的时钟分频因子 TIM_ClockDivision 在输入捕获的采样使用 不需要设置
- 设置定时器的重复计数器 TIM_RepetitionCounter 只有高级定时器需要设置
1.2.3 定时器编程
如何利用基本定时器实现定时中断的功能?流程可以参考stm32f4xx_tim,c中的代码注释

- 打开基本定时器时钟 TIM6和TIM7 在APB1总线,所以打开APB1时钟
- 设置定时器的相关参数(预分频、计数模式、计数周期)+初始化定时器
- 第一个函数是“真正初始化”定时器(TIMx)的时基(TimeBase)部分,也就是基本定时功能。它会根据你设置好的结构体
TIM_TimeBaseInitStruct
的内容,去配置对应定时器的这些寄存器:ARR,PSC,向上向下计数,CKD(时钟分频因子),计数器重复次数(仅限高级定时器) - 第二个函数是将传入的结构体初始化为默认值(不是配置定时器)!这个函数的作用是为了:避免你手动初始化每一个字段(比如忘记设置会有未定义行为);先用它设置默认值,再改你关心的字段,确保结构体内容完整合法。
- 初始化定时器可以通过TIM_TimeBaseInit实现
- 函数原型:void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
- 参数一:TIMx 打算配置参数的定时器号 如TIM6
- 参数二:TIM_TimeBaseInitStruct 配置时基单元的地址 记得&


- 设置NVIC的相关参数(中断通道和中断优先级)初始化NVIC

- 设置定时器中断源
- 参数1:TIMx 打算产生中断的定时器号 如TIM6
- 参数2:TIM_IT 指定定时器的中断源 一般为TIM_IT_Update 表示更新(数到了)中断
- 参数3:NewState 打开和关闭中断源 ENABLE or DISABLE.


函数原型:void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState)
- 打开定时器
- 编写定时器中断服务函数 中断服务函数名字去汇编文件复制(检测状态和清除状态)

练习:根据基本定时器的理解,编写代码实现利用TIM6 每隔 200ms 让 LED0 的电平翻转次,利用TIM7每隔400ms让LED1的电平翻转一次,并对比观察效果是否一致。
main.c LED1对应9,LED2对应10
1.2.4 出现的一些问题
- NVIC初始化的时候,中断通道该怎么选?
- 产生中断,那么就会有对应的中断服务程序,中断服务程序的名字在启动文件的向量表里面。即发生中断的时候会自动跳转到中断服务程序里面。所以需要打开中断通道之后才能发生中断时跳转。


- 中断服务程序的编写
- 潜规则:默认要把所有中断服务程序写入stm32f4xx_it.c
- 检测中断是否发生
- 函数原型:
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT)
- 第一个参数,选中的定时器
- 第二个参数,中断源。
- 返回值:SET(发生中断),RESET(没有发生中断)
- 清除中断状态
- 函数原型:void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT)
- 第一个参数:选中的定时器
- 第二个参数:中断源
- 执行中断服务程序


- 代码的优化
- 定时器时间的设置
1.3 通用定时器

- 区别在:多种计数模式,多个通道(可以实现定时器互联,GPIO关联),多个功能(输入捕获、输出比较、PWM)
- 相比于基本定时器而言,通用定时器增加了输入捕获功能以及输出比较功能,并且通用定时器具有独立通道,就可以把GPIO引脚连接到某个通道,利用通道进行输入信号的检测以及脉冲信号的输出。
- 对于通用定时器 TIM2~TIM5,都挂载在 APB1 定时总线下,定时器的频率为84MHZ,如下图

- TIM2~TIM5的计数方式有多种可以选择,分别为递增计数、递减计数、递增/递減计数。
- 递增计数:计数器从0计数到自动重载值(TIMx_ARR寄存器的内容),然后重新从0开始计数并生成计数器上溢事件。(所以填入ARR的实际值需要目标计数值-1)
- 递减计数:计数器从自动重载值(TIMXARR寄存器的内容)开始递减计数到0,然后重新从自动重载值开始计数并生成计数器下溢事件。((所以填入ARR的实际值需要目标计数值-1))
- 中心对齐:计数器从0开始计数到自动重载值(TIMxARR存器的内容),生成计数器上溢事件;然后从自动重载值开始向下计数到0并生成计数器下溢事件。

1.3.1 通用定时器框图
- 内外时钟源选择
- 滤波:过滤掉不稳定的不需要的波形
- 边沿检测:比如说设置检测上升沿,信号就是周期信号,两个上升沿可以算出带宽,周期等等
- 输入捕获的两种途径:第一个就是从通道产生,第二个就跟CNT自产信号比较
- 输出比较:可以输出PWM(通过脉冲宽度调整得出),调整舵机转动角度

三种功能:
- 定时功能:基本上使用流程和基本定时器一致,只不过通用定时器的计数方式更灵活
- 开启定时器时钟(总线时钟)→配置定时器参数(结构体)→配置NVIC(开启中断)→编写中断服务程序→使能定时器
- 输入捕获:可以把定时器的某个通道连接到GPIO引脚上,然后从外部输入脉冲信号,经过通道的滤波以及边沿检测之后,可以记录某个电平信号的脉冲宽度以及周期。
- 输出比较:可以把定时器的某个通道连接到GPIO引脚上,主动从引脚输出一个固定的脉冲,原理很简单,其实就是计数器(TIM_CNT)如果超过比较寄存器中的值,就可以输出一个电平信号(高电平或者低电平)。
对于TIM9~TIM14而言,也可以进行定时功能,同样也具有输入捕获以及输出比较功能,但是只能采用向上计数的方式,并且相比于TIM2~TIM5,只有2个独立通道。

注意在f401中只有TIM9~TIM11

1.4 高级定时器

- STM32 中提供了TIM1和TIM8 高级定时器,相比于其他定时器而言,高级定时器增加了带可编程死区控制的互补输出、重复计数器、断路输入功能(一般用于控制工业中的高精度电机),但是如果作为定时功能来说,和其他的定时器没啥区别。
- 高级级定时器TIM1和TIM8都挂载在APB2总线下,所以TIM1和TIM8的定时时钟频率为 168MHZ.
- 这里需要插一嘴的是STM32F401

1.5 回顾定时器通道
定时器通道(Channel)是定时器用于“比较输出”或“捕获输入”的子模块,每个通道都能独立配置成PWM、输入捕获、中断等功能。

关键词 | 解释 |
通道 | 定时器内的子功能模块(最多有 4 个) |
通道编号 | CH1、CH2、CH3、CH4 |
每个通道功能 | 都可以独立配置为 PWM、输入捕获、比较输出等 |
配套引脚 | 每个通道绑定一个或多个特定的 GPIO 引脚(AF 复用) |
TIM14_CH1 | 意味着“TIM14 的通道1”,绑定在 GPIOF9 上(复用) |
2 PWM
2.1 PWM的概述
- PWM的概念
- PWM 指的是脉冲宽度调制,是一种利用微处理器的数字输出能力来控制模拟电路的技术。 PWM 技术的关键参数有两个,一个是频率,一个是占空比。
- 频率指的是利用STM32的定时器通道输出脉冲的次数
- 占空比指的是一个周期内高电平所占的比例(准确来说是占空比是“有效电平”在一个周期中所占的比例。默认为有效电平为高电平,所以默认的占空比是高电平时间占一个周期的比例)
- PWM技术一般用在工业控制领域(控制电机的转速、控制舵机的角度…)



2.2 PWM的原理

- 这里的高低电平有效,通道有效和PWM模式有点懵
- 高/低电平有效
- 希望”在PWM高电平的时候是“有效”输出,还是低电平的时候为“有效”输出。本质是“有效”状态用什么电平来表示。这里的“有效”不是逻辑 true/false。
- 极性设置决定“有效电平是高电平还是低电平”;
- 这个设置定义了当通道处于“有效”状态时,输出什么电平。
- 高电平有效 (
CCxP = 0
): 当通道有效时,输出高电平 (1
)。当通道无效时,输出低电平 (0
)。 - 低电平有效 (
CCxP = 1
): 当通道有效时,输出低电平 (0
)。当通道无效时,输出高电平 (1
)。 - 通道有效/通道无效
- 输出比较通道是否启用输出,通道有效并输出比较事件生效时,才会有波形输出。
- 通道是否使能决定是否输出波形
- “通道有效” 表示输出应该驱动为有效电平所定义的那个电平。
- “通道无效” 表示输出应该驱动为无效电平所定义的那个电平(即有效电平的反)
- PWM模式
- PWM模式决定“输出有效电平出现在哪段时间”;

模式 | 有效电平极性(OCxPolarity) | 有效区间 | 输出波形(向上计数) | 备注 |
PWM1 | 高电平有效 | 先有效再无效 | 起始为高,数到 CCR 后为低 | 占空比 = CCR / ARR |
PWM1 | 低电平有效 | 先有效再无效 | 起始为低,数到 CCR 后为高 | 波形反相 |
PWM2 | 高电平有效 | 先无效再有效 | 起始为低,数到 CCR 后为高 | 和 PWM1+低极性 等价 |
PWM2 | 低电平有效 | 先无效再有效 | 起始为高,数到 CCR 后为低 | 和 PWM1+高极性 等价 |
- 思考:外设到底是和哪个定时器的哪个通道连接在一起的? 参考芯片数据手册和原理图
- 可以看到PF9引脚和TIM4_CH1是相关联的,所以就需要把PF9引脚的功能设置为复用功能,复用为定时器功能。



- 函数原型:void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF);
- 参数一:GPIOx 要复用的引脚端口
- 参数二:GPIO_PinSource 要复用的引脚编号 GPIO_PinSourcex(x为0~15)
- 参数三:GPIO_AF 指的是要复用的引脚功能 如GPIO_AF_TIM14
- 这里是一些寄存器的说明



2.3 PWM的使用
- PWM 的使用流程可以参考 stm32f4xx_tim.c源文件的注释部分,如下图所示

- 打开GPIO引脚的时钟 + 定时器的时钟
- 配置GPIO引脚(引脚模式需要设置为复用模式)+初始化GPIO
- 把GPIO引脚功能复用为对应的定时器 配置GPIO为复用模式,但你还需要告诉它“复用成哪个外设”。
- 配置定时器的定时时间(预分频+自动重装载值)+初始化TIM (PWM周期)
- 配置定时器通道的参数(输出极性、PWM模式)+初始化定时器通道 TIM_OCxInit()
- 这里为什么是50()而不是50-1.因为在说明书里面可以知道<50时候为有效状态,那么就是0~49为有效状态即高电平输出,
(设置PWM占空比)




- 使能预装载寄存器 调用TIM_OCxPreloadConfig
- 意思是:当你用软件修改 CCR1(占空比)时,不会立即生效,而是要等到下一个计数周期开始。这样可防止 PWM 输出中间跳变,保证波形稳定
- 使能自动重载、预装载寄存器的ARPE位
- 和上面一样,让
ARR
修改也是等下个周期开始才生效 - 函数原型:void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx,Functionalstate Newstate)


- 打开定时器TIM_cmd(ENABLE)
- 注意:PWM技术使用的是定时器通道的输出比较功能,所以不需要配置NVIC,也不需要编写中断服务函数。
- 前面我们设置了有效电平是高电平,使用的模式是PWM1(CNT<CCR1时输出有效电平)。然后设置PSC=83999和Period(ARR)=100-1,即一次pwm周期为100ms.CNT从0数到99.设置占空比为50%,即有效电平(高电平为0~49),即前0~49的时候输出高电平,后面50~99的时候输出低电平。
- 这个函数的本质就是设置CCR1寄存器的值,一张寄存器的图如下。这里
TIM_SetCompare1(TIM14, 30); // 设置 CCR1 = 30。所以CNT<30的时候为高电平

寄存器名称 | 中文解释 | 作用 |
ARR | 自动重装寄存器 | 控制一个 PWM 周期的总时间(最大计数值) |
CCR1 | 捕获/比较寄存器1 | 控制 PWM 中高电平持续的时间 → 决定占空比=CCR1/(ARR+1) |
CNT | 计数器 | 实时计数值,从 0 递增到 ARR,或递减(根据模式) |
PSC | 预分频器 | 控制定时器的计数速度(时钟分频) |

完整代码如下:
2.4 回顾GPIO
2.4.1 什么是GPIO
GPIO(General Purpose Input/Output)是通用输入输出引脚,是你与外设通信、控制电平状态、读取信号的最基础接口。
STM32 的每一个 GPIO 引脚都是可配置的,并具有多种功能:
功能模式 | 说明 |
输入模式 | 读取外部信号(按钮、电平、传感器) |
输出模式 | 输出高/低电平(控制LED、继电器等) |
复用功能 | 用作其他外设(比如 TIM/PWM、USART、SPI)输入输出 |
模拟功能 | 用于 ADC/DAC 模拟输入输出 |
2.4.2 GPIO配置结构体GPIO_InitTypeDef
stm32使用一个结构体来配置引脚
字段 | 作用 |
GPIO_Mode | 设置工作模式(输入/输出/复用/模拟) |
GPIO_OType | 输出类型(推挽 / 开漏) |
GPIO_PuPd | 上拉 / 下拉 / 无 |
GPIO_Speed | 输出速度(驱动能力) |
GPIO_Pin | 要配置的具体引脚(如 GPIO_Pin_9) |
GPIO_OType
—— 设置输出类型(仅当为输出或复用模式时有意义)
类型 | 说明 | 典型用途 |
推挽 | 可输出高或低电平,驱动能力强 | PWM、普通IO |
开漏 | 只能输出低电平,高电平靠上拉 | I2C总线 |
GPIO_PuPd
—— 内部上拉下拉电阻配置
类型 | 说明 | 用途举例 |
无 | 引脚悬空时电平不确定(容易漂浮) | 外部电路有自己电平控制 |
上拉 | 默认高电平,适合捕获下降沿 | 按键接地、I2C |
下拉 | 默认低电平,适合捕获上升沿 | 捕获高电平脉冲 |
GPIO_Speed
—— 输出速度(实际上是驱动能力)
并不是“波形切换频率”,而是驱动速率与电磁干扰控制有关。
速度 | 用途 |
低速 | 慢速IO、LED、蜂鸣器 |
高速/非常高速 | PWM、SPI、USART、频繁变化的信号线 |
2.4.3 GPIO配置流程
步骤 | 内容 |
1 | 开启 GPIO 时钟( RCC_AHB1PeriphClockCmd ) |
2 | 配置结构体 GPIO_InitTypeDef 的各项参数 |
3 | 调用 GPIO_Init() 应用配置 |
4 | 如需复用功能,还需 GPIO_PinAFConfig() 设置引脚与外设的连接关系 |
2.4.4 为什么需要配置GPIO?
- 大多数引脚都是多功能复用引脚(比如一个引脚可以做GPIO(普通IO输入输出),也可以当作UART、PWM、ADC等外设)。你想让某个引脚用于定时器输出PWM信号(就是定时器产生PWM波,但是输出靠GPIO口),或者捕获输入信号的高电平宽度,那它就不能当作普通GPIO使用,必须:

- 回顾一下推挽输出:内部同时有高电平驱动和低电平下拉能力
- 在“推挽”模式下,引脚可以主动输出高电平(推)或低电平(拉),通过控制两个开关器件(如上下MOS管)交替导通,实现稳定、强驱动能力的电平输出。
- 回顾一下下拉:内部下拉电阻,在引脚悬空时自动拉低,常用于输入捕获模式,比如用来捕获一个高电平脉冲宽度,低电平默认有助于识别高电平跳变。
- “下拉”是一种电气属性,在 输入模式或复用模式下,当引脚悬空或未被外部驱动时,会通过内部连接一个电阻到 GND,把引脚电平拉低。
- GPIO和定时器的关联
- 定时器负责生成PWM波形,GPIO引脚负责把波形“搬运”到芯片外部。
- 必须通过GPIO的“复用功能”把定时器的输出通道映射到某个具体的引脚上,二者才能打通。
3 PWM驱动舵机

- 注意:需要20ms的脉冲宽度
- 按照这里的预分频和单次计时周期可以得出总计时时长20ms计数2000次(写入Period(ARR寄存器))
- 根据角度对应的高电平算出对应计数的次数(写入一度对应的次数,这样可能会得到小数,往误差小的上取和下取),写入CCR寄存器,使用SetCompare
- 实现顺时针和逆时针调节(延时观察现象)
- 一些有点遗忘的知识点
- “预装载寄存器机制”:你写进去的新值,暂时放在一个影子寄存器中,等当前计数周期结束(更新事件UEV到来)时再统一加载。
- 启动CCR1的预装载功能:
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
- 如果不开启预装载,你每次调用
TIM_SetCompare1()
改占空比,立即生效,可能会导致 PWM 信号瞬间跳变。 - 如果开启预装载(推荐),那么改完占空比之后,会在下一个更新事件(计数周期结束)时统一生效,平滑安全。
- 启动ARR的预装载功能:
TIM_ARRPreloadConfig(TIM3, ENABLE);
名称 | 中文名 | 控制内容 |
ARR (Auto-Reload Register) | 自动重装载寄存器 | 控制 PWM 的周期(计数最大值) |
CCRx (Capture/Compare Register) | 捕获比较寄存器 | 控制 PWM 的占空比(什么时候翻转) |
- (了解一下)时钟分频因子的知识点:
TIM_BaseStruct.TIM_ClockDivision = TIM_CKD_DIV1
TIM_ClockDivision
是用于“数字滤波器”相关功能(例如输入捕获、编码器模式等)时,对定时器时钟的进一步分频。它不影响基本的 PWM 输出节拍。- 它控制的是定时器的内部采样时钟(tDTS),不是主时钟频率(你设定的 prescaler)。
- 主要用于数字滤波器的采样节拍,比如输入捕获时用于消除抖动。

- 在我们配置TIM的PWM模式时
TIM_OCStruct.TIM_Pulse = 1500; // 设置1.5ms为中间值,一次为1us
- 设置了PWM输出的“高电平持续时间”
- 更直白一些是:在每个PWM周期内,输出高电平持续N个计数器周期。
- 更底层一些:设置每个周期里的CCR为1500.(ARR=20000-1)
- 占空比为CCR/(ARR+1)
- 这个地方是初始化就固定好了占空比
- VS:
TIM_SetCompare1(TIM3, new_value);
- 前者初始化使用,在结构体里面
- 后者运行时使用,实时修改占空比,即实时修改CRR。new_value=CRR
4 PWM驱动呼吸灯
- 呼吸灯需要实现的效果:从暗到亮再到暗再到亮…
- 占空比:从小到大再从大到小,占空比就对应pwmVal这个变量
- dir:控制方向:1,正在变亮pwmVal++;0正在变暗,pwmVal—
5 小组资料与补充
5.1 PWM概念
- Pulse Width Modulation,脉冲宽度调制
- 一种利用微处理器的数字输出来对模拟电路进行控制的技术
- 通过物理可以知道高低电平跳变可以等效电压(图一中虚线部分)不同,这样亮度/转速都不同。可以调节高电平时间来确定。
- 一般等效是线性的即50%占空比,高电平是5V,低电平是0V,那么等效电压就是2.5V。使用PWM波形就可以在数字系统等输出等效模拟量

5.2 配置时基单元

- 预分频器(Prescaler)
- 接收一个输入时钟信号(CK_PSC),并将这个信号分频(降低为原来的1/N),产生一个频率较低的输出时钟信号(CK_CNT)
- 实际分频系数 = 预分频值(PSC) + 1
CK_CNT = CK_PSC / (PSC+1)

- 计数器(CNT)
- 计数时钟每来一个上升沿,(在向上计数模式中)计数器的值加一,不断自增直至达到目标值
- 通用和高级定时器还支持向下计数模式和中央对齐模式。
- 自动重装寄存器(ARR)
- 存储计数目标值(计时总时长)
5.3 输出比较(OC:Output Compare)
输出比较:Output Compare 输入捕获:Input Capture
输入捕获/输出比较单元:Capture/Compare
- 输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形
- CNT就是时基单元的计数器,CCR就是捕获/比较寄存器(输入捕获和输出比较共用),当使用输入捕获时就是捕获就是捕获寄存器,当输出比较的时候就是比较寄存器
- 以输出比较为例,当CNT<>CCR的时候,就会输出相应的0,1电平,从而形成周期性波形。
- 每个高级定时器和通用定时器都拥有4个输出比较通道
- 高级定时器的前3个通道额外拥有死区生成和互补输出的功能,这个是用来驱动三相无刷电机的
- 通用定时器的输出比较电路
- REF:参考信号,此时为一个频率、占空比均可调的PWM波形
- 极性选择:寄存器写0时信号电平不翻转,写1信号通过一个非门取反,输出信号就是一个高低电平反转的信号。
- 输出使能电路。OC1引脚这个就是CH1通道的引脚,引脚定义表里就可以知道具体是哪个GPIO口了。
- 什么时候给RED高/低电平?输出模式控制器:输入时CNT和CCR的比较结果,输出是REF的高低电平。通过寄存器OC1M[2:0]配置

左边是CCR和CNT比较的结果,右边就是输出比较电路,最后通过TIM_CH1输出到GPIO上。


匹配时电平翻转,可以输出一个50%的PWM波形。更新事件两次为一个周期。
这里可以看到PWM模式1和PWM模式2是相反的两个波形。然后再后面的极性选择的地方又可以选择一次输出。所以PWM模式1的正极性输出和PWM模式2的反极性输出最终输出结果是一样的。

CCR是我们自己设置的,CNT每次+1都在不断与CCR进行比较,蓝色线是CNT的值,黄色线是ARR的值,红色线是CCR的值。绿色线就是输出。REF就是一个频率可调,占空比可调的PWM波形。
- 高级定时器输出比较电路
- REF:参考信号,此时为一个频率、占空比均可调的PWM波形
- 极性选择:寄存器写0时信号电平不翻转,写1信号通过一个非门取反,输出信号就是一个高低电平反转的信号。
- 外部会接一个电路:MOS管(大功率开关),上接高电平,下接地。左边给电平信号,例如给高电平,MOS管导通,低电平MOS管断开,即最基本的推挽输出。中间是输出
- 上管导通下管断开,输出为高电平。上管断开,下管导通,输出为低电平。如果上下管都导通,电源短路。
- 两个这样的推挽电路就构成了H桥电路,可以控制直流电机正反转。三个推挽电路就可以驱动三相无刷电机。
- OC1和OC1N是两个互补的输出端口,控制推挽输出中上下管的导通和关闭
- 死区发生器:上下管导通的时候可能会因为器件反应不理想,推挽输出上下导通的时候短路。某一个管关闭的时候,延迟一小段时间再导通另一个管

- 计算公式
- 占空比(Duty)=CCR/(ARR+1)
- 因为CNT<CCR的时候有效,比如说CCR=50,那么0~49就是有效的,共50个节拍,就是CCR的值。但是ARR填入的时候需要-1,因为是从0开始计数。
- PWM频率(Freq)(向上计数且为PWM模式1)
- PWM周期(注意这里的CCR:为占空比所对应的计数次数。而真正的周期为ARR的值)
- 实际频率CK_CNT=CK_PSC/(PSC+1),即1s多少次
- 周期T=(ARR+1)/CK_CNT→多少秒
- Freq=CK_CNT/(ARR+1)=CK_PSC/(PSC+1)/(ARR+1)
- 分辨率:占空比的最小变化单元(CCR能调的步长)
- 分辨率=1/(ARR+1)

5.4 电机和电调
- 基本的电磁知识
- 电流的磁效应:通有电流的导线会在其周围产生磁场
- 安培右手定则:可判断磁场的方向
- 磁场可以产生“力”:洛伦兹力。通电导线+磁场=会转动
- 磁铁:能产生并改变磁场方向,与永磁体有相同的性质(同极相斥,异极相吸)->在有刷电机中,电磁铁充当转子,不断转动;在无刷电机中,电磁铁作为定子,不会转动。
5.4.1 电机
- 有刷电机
- “通电线圈”变成转子(能动的部分),再放进一个磁铁结构(固定的部分,叫定子)中。
- 主要结构:定子+转子+电刷,通过旋转磁场获得转动力矩,从而输出动能。电刷与换向器不断接触摩擦,在转动中起到导电和换向作用。
- 为什么需要电刷和换向器
- 导线在磁场中,电流方向决定了力的方向。如果我们不改变电流方向,它转一圈就会卡住。所以需要:电刷 + 换向器的组合:在转动过程中不断“切换电流方向”,让磁力持续产生扭矩 ➜ 就能一直转下去。
- 有刷电机采用机械换向,磁极不动,线圈旋转。电机工作时,电磁铁作为转子,在其两侧各放置一块弧形永磁体,分别使他们的N极和S极指向转子,固定不动,称为定子。给转子通入电流,产生磁场,由于同极相斥,转子就会开始转动,转到一定位置时,就会由于异极相吸而停住。如果要让转子继续转动,就需要改变线圈中的电流方向,来切换转子中的磁极方向。
- 如何改变线圈中的电流方向:使用换向片和电刷。换向片固定在转子中轴上,与线圈相连,而电刷在弹簧的作用下紧贴换向片,且位置固定不动,并与电源相连。当线圈磁场方向需要反转时,两个电刷就各自切换所接触的换向片,线圈中的电流方向就发生了反转,磁场方向也随之反转。
- 缺点:相互滑动,会摩擦碳刷,造成损耗,需要定期更换碳刷;碳刷与线圈接线头之间通断交替,会发生电火花,产生电磁破,干扰电子设备。


- 无刷电机
- 与有刷电机刚好相反,无刷电机使用永磁铁作为转子,线铁芯制成的电磁铁作为定子。采取电子换向,线圈不动,磁极旋转。使用控制电路不断改变线圈的通电情况,同极相斥驱动转子不断地转动下去。(三相电路+霍尔传感器+MCU做电子换向)“线圈不动,磁铁动;电流不自己换方向,由MCU控制。”
- 结构对比:
- MCU + 霍尔传感器 + 三相驱动 ➜ 电子换向系统组成
- 工作过程详细分解(以三相120°驱动为例子)
- 三相驱动工作
- 霍尔传感器工作
- 无刷电机如何检测转子所处的位置:
- ①应用反电动势效应,电调并没有让所有的线圈都同时导通,当转子磁铁经过没有导通的线圈时,就会给这个线圈感应出电压,然后被控制器检测到,这个过程就像是在发电,电调会一直检测绕组,看它何时供上了电,何时发出了电,从而确定转子的相对位置。
- ②使用霍尔传感器来检测转子相对于定子的位置。检测到转子相对于定子的位置之后就能适时切换线圈中电流的方向,保证产生正确方向的磁力,来驱动电机。消除了有刷电机的缺点。

项目 | 有刷电机 | 无刷电机 |
转子(会转) | 线圈(电磁铁) | 永磁铁(通常是多极磁钢) |
定子(固定) | 永磁铁 | 多相线圈(ABC三相绕组) |
换向方式 | 物理换向:电刷 + 换向器 | 电子换向:MCU + 霍尔传感器 |
部件 | 作用 |
MCU 或驱动芯片 | 控制哪一相线圈什么时候通电 |
霍尔传感器 | 检测转子位置,告诉MCU“磁铁转哪了” |
三相电路 | 负责将电流送入 A、B、C 三组线圈 |


5.4.2 电调
- 电调(ESC):Electronic Speed Controller,电子调速器
- 电调(ESC)是“给无刷电机喂电”的大脑和双手——把直流电变成能让电机旋转的“节奏感强”的三相交流电。
- ESC功能如下:
- 无刷电机工作必须要有电调,否则是不能转动的。必须通过无刷电调将直流电转化为三相交流电,输给无刷电机才能转动。随时改变着固定线圈内部电流的方向,保证它跟永磁体之间的作用力是相互排斥,持续保持转动。
功能 | 解释 |
💡 整流/变换电流 | 将输入的直流电(电池)转换为三相交流电供电给无刷电机。 |
🧠 控制电机转速 | 根据接收到的控制信号(通常是PWM),改变三相电的频率、占空比,实现电机调速。 |
👂 接受外部指令 | 比如来自飞控、舵机控制器、遥控器接收模块的信号。 |
🛡️ 保护功能 | 如欠压保护、过流保护、堵转保护等,保护电机和电池。 |
- 无刷电调的输入和输出
- 电调分为有刷电调和无刷电调,无刷电机配合无刷电调使用。
- 输入:直流电,两根线,红正黑负,可以接锂电池,或者稳压电路。具体供电需要参考说明书,我们使用的好盈20A电调的供电是2-3节锂电池。输出线中,白色是信号线,用于接受PWM信号,红色接5V,黑色线接地。
- 输入端(电源和控制信号)
- 输出端(接无刷电机)
- 实际使用中的接法(好盈20A)
- 电调是如何驱动无刷电机的?
- 本质上:电调在模拟“旋转磁场”,吸引转子转动,类似你用手轮流推动一个陀螺。
- 电调和PWM占空比的关系
- 这和舵机的控制方式完全一样,兼容舵机控制器。
- 注意事项:
- 电调 = 给无刷电机“跳舞打节拍”的节拍器 + 电流放大器。它根据 PWM 信号的节奏,不断调节三相电输出,模拟旋转磁场,从而驱动永磁体转子不断旋转。
引脚 | 说明 |
🔴 红线(VCC) | 正极供电,一般来自锂电池,如 2S(7.4V)、3S(11.1V)等 |
⚫ 黑线(GND) | 地线,和主控板共地 |
⚪ 白线(Signal) | 信号线,用来接收来自MCU的PWM信号,比如STM32 |
输入信号是一种“伪舵机信号”:50Hz,周期20ms,高电平宽度控制转速
高电平时间 | 意义(一般) |
1ms | 停止或最慢转速 |
1.5ms | 中间速度 |
2ms | 最大转速 |
线数 | 含义 |
3 根粗线 | 三相交流输出:U、V、W(接到电机任意三根线即可,转向不对就换两根) |
这些线不是正负极,而是交替输出不同相位的“伪正弦波”或“方波”电流。
类型 | 说明 |
电调型号 | 好盈(Hobbywing)20A 无刷电调 |
电源输入 | 2-3节锂电池(7.4~11.1V) |
信号输入 | 白色为信号线,控制PWM占空比 |
控制方式 | 舵机 PWM,20ms 周期,控制 0~100% 转速 |
MCU 接法 | 信号线接 TIMx_PWM 输出引脚,GND 共地,电调自带供电不接VCC |
注意GND共地。

PWM高电平宽度 | 电调反应 |
0.5ms | 停止、最慢、反转等初始化位置(取决于设置) |
1ms | 最低转速 |
1.5ms | 中间速度 |
2ms | 最大转速 |

- 电调的接法:

- 输入:
- 第一幅图含香蕉头的两根线是的电源线
- 第三幅图是信号线:其中三根一起的线是信号线,接到接收机(?油门控制线)(转接板开了四个口),然后那一根黄色线是(可接入另外一个通道)
- 输出:
- 第二幅图的电机线和电机随便接。
- 关于我们无人机的结构是:
- 将四个电调电源线的香蕉头剪掉,通过焊接在无人机的金属板上。同时将电池电源线也焊接在板子上,实现电池对四个电调供电。
- 这是我们的机架图就明白走线了
- 蓝色部分为电池的焊接,红色部分为电调线的连接。
- 即实现了电池正极接接电调的四个正极,电池负极接四个电调的负极。
- 同时还有转接板的正负极也和电池相连。
- 实现了所有元件共地共电源

- 设置反推刹车:
- 油门最大,上电。听到电调响第一声,油门拉到最低,听到电调第二声、第三声,第三声为反推刹车设置完毕。再推大最大油门,即设置保存好了反推刹车。油门拉到最小后,断电。
- 反推刹车就是让电调控制电机“短时间反转”或“短接绕组”,从而实现快速减速或刹停的功能。(适合于小车)
- 油门行程校准
- 油门推到最大,上电,滴滴两声后,油门拉到低。电机开始最低速转动,则油门行程校准完成。此时不动油门,拨动反推开关,电机反转。关闭反推开关,电机正转。


- 电调解锁逻辑与正常启动流程
电调接上电池,鸣叫提示音“123”,表示上电正常自检OK,发出长鸣音“哔——”N声短鸣音,表示锂电池节数推油门可随时起飞
- 电调解锁:
- 初始化信号
- STM32上电后,立即输出最低油门PWM信号(脉宽为1000us~2000us)
- 接通电调电源
- 保持最低油门信号的同时,给电调供电(接电池)。
- 电调鸣叫
123
音阶 → 表示检测到最低油门信号 - 随后发出 N声短鸣 → 表示检测到的锂电池节数(如3S电池鸣3声)
- 最后发出 长鸣"哔——" → 系统准备就绪(解锁完成)
5.5 修改寄存器直接驱动电机
在 STM32 微控制器中,通过操作寄存器完成电机驱动:
是因为这是硬件与软件交互的基础方式。寄存器是 MCU 内部的特定存储单元,直接映射到硬件功能上,通过操作寄存器可以控制硬件的行为。
5.5.1 寄存器是什么
寄存器是微控制器内部的小型存储器,用于存储特定信息(通常是状态或控制信息)。
每个外设模块(如 RCC、GPIO、TIM 等)都通过寄存器暴露功能接口。
- 寄存器分类:
- 控制寄存器:设置硬件的工作模式(如启用/禁用外设、配置时钟源等)。
- 状态寄存器:存储硬件当前的状态信息(如中断标志位)。
- 数据寄存器:用于存储传输中的数据。
5.5.2 操作寄存器的优点
- 高效直接:
- 寄存器操作是直接与硬件交互,速度快且资源消耗低。
- 没有中间抽象层,硬件响应时间最短。
- 标准化接口:
- STM32 的所有外设都通过寄存器暴露功能,统一操作方式便于开发和调试。
- 深入了解硬件:
- 操作寄存器需要阅读数据手册,能更好地理解硬件工作原理。
- 在调试或需要非常精细的控制时,直接操作寄存器更加可靠。
5.5.3 代码思路

5.6 知识点
5.6.1启用GPIOA和TIM2时钟
5.6.1.1 内存映射
5.6.1.2 GPIO
RCC->AHB1ENR |= (1 << 0);
//使能 GPIOA 外设的时钟,只有时钟信号被使能后,GPIOA 的寄存器才可以被正确访问和操作。
//RCC (Reset and Clock Control):RCC 是 STM32 的时钟管理模块,用于控制所有外设的时钟开关。各个外设的时钟通过 RCC 的寄存器来配置。
//GPIO 外设挂载在AHB1总线,(AHB1ENR)AHB1外设时钟使能寄存器,每一位对应一个AHB1上的外设,置 1 代表使能该外设的时钟。
//GPIOA 时钟使能对应 AHB1ENR 的第 0 位。置 1 代表使能GPIOA时钟。
//位移运算符是 C 语言中的一种二进制操作符:
左移运算符 (
<<
): 将一个数的二进制位向左移动指定的位数。右移运算符 (
>>
): 将一个数的二进制位向右移动指定的位数。Value<<n:被操作数左移n位

按位或操作(
|
):两个操作数对应位有一个是1,结果就是1;只有两个位都是0,结果才是0;
当需要在寄存器的某些位上设置为 1,但不改变其他位的值时,使用按位或。
按位与操作(
&
):两个操作数对应位都为1,结果为1,只要有一个是0,结果就是0;
当需要在寄存器中将某些位清零,使用按位与。
按位与和取反结合使用:通过对目标位取反,再与原值按位与,就可清楚特定位,同时保护其他位。
Q:为什么需要保护其他位?
A:在硬件寄存器中,多个位可能控制不同的功能。例如,
MODER
寄存器的每两位决定一个 GPIO 引脚的模式。如果直接将目标位清零,而不小心影响了其他位,可能导致:临近引脚的配置被意外更改。不相关的外设功能被误触发。因此,安全的位操作是:仅修改目标位,而不影响其他位。
//构造掩码
什么是掩码:掩码是一个二进制数,它的每一位对应寄存器中的某一位:
- 掩码中值为 1 的位: 表示目标位(我们关心的位)。
- 掩码中值为 0 的位: 表示非目标位(我们希望忽略的位)。
- 改为二进制码和掩码的优劣
为什么需要掩码:掩码可以确保只操纵目标位,避免对非目标位造成干扰,从而达到安全操作寄存器的功能。

+4,32位。。。。
p137



5.7 库函数
pwm.c
main.c
delay.c
5.8 寄存器
pwm.c
pwm.h
main.c
5.9 汇编
- 作者:🐟🐟
- 链接:https://www.imyuyu.top//article/Quadcopter1-6
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。