目录
[1. 基本定时器的基本特点](#1. 基本定时器的基本特点)
[2. 原理分析](#2. 原理分析)
[3. 程序设计](#3. 程序设计)
[2. PWM脉冲宽度调制](#2. PWM脉冲宽度调制)
[3. 使用定时器通道PWM的程序设计](#3. 使用定时器通道PWM的程序设计)
上一章节解析的24bitSystick定时器,它由HSE提供时钟后经过PPL后倍频得到168MHz,把168MHz提供给AHB总线。
时钟 == 频率
时基就是定时用的
外设挂载在总线下,使用的是总线的时钟频率;总线的时钟频率由时钟源提供。
一、定时器的基本概念
定时器的作用 : 使用定时功能和中断功能。
定时器都有定时功能,定时时间到达后都会触发中断。
还可以在STM32中,利用通用定时器的通道产生周期性的脉冲信号来控制不同的外设(灯的亮度、电机的转速、舵机的角度......)。
二、外设种类
|---------------------------|----------------------------------|
| STM32F407微处理器 内部集成了14个定时器 | 2个基本定时器(TIM6和TIM7) |
| STM32F407微处理器 内部集成了14个定时器 | 10个通用定时器(TIM2~TIM5、TIM9~TIM14) |
| STM32F407微处理器 内部集成了14个定时器 | 2个高级定时器(TIM1和TIM8) |
其中通用++++定时器TIM2和TIM5为32位定时器,其他为16位定时器。++++
定时器位数越大,定时时间越久。
三、基本定时器
1. 基本定时器的基本特点

|-----------------|-----------------------------------|
| 基本定时器 TIM6、TIM7 | 16位自动重载计时器【计数方式:递增计数】 |
| 基本定时器 TIM6、TIM7 | 16位可编程预分频器: 分频系数介于1和 65536 之间 |
| 基本定时器 TIM6、TIM7 | 计数上溢时会生成中断 |
| 基本定时器 TIM6、TIM7 | 定时器彼此完全独立,不共享任何资源。每个定时器有属于自己的寄存器组 |
2. 原理分析

APB1、APB2、AHB总线的时钟频率,
|------|--------|
| AHB | 168MHz |
| APB1 | 42MHz |
| APB2 | 84MHz |
所以无论哪个定时器挂载哪个时钟总线下,168MHz分频都不等于1。按照手册,定时器的时钟频率最开始都是 " APBx的频率 * 2 "。
要选择定时器时钟频率,前提是已知使用了哪个定时器,通过stm32f407的手册查找该定时器存在哪个时钟总线,
3. 程序设计
利用基本定时器TIM6实现精准延时
cpp
/**
******************************************************************************
* @file main.c
* @author
* @version
* @date
* @brief 利用基本定时器TIM6实现精准延时
TIM6挂在在APB1下,而定时器时钟使用到8M的HSE,经过PLL后又分频-->要修改HSE_VALUE和PLL_M
BEEP――PF8
******************************************************************************
**/
#include "stm32f4xx.h" //必须要包含的头文件
void BEEP_Config()//BEEP--PF8
{
//定义外设的结构体变量
GPIO_InitTypeDef GPIO_InitStructure;
//打开外设时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
//对外设的结构体成员赋值
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//模式:输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//类型:推挽
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//输出速率:高速
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//电阻:无上拉下拉电阻
//对外设进行初始化
GPIO_Init(GPIOF, &GPIO_InitStructure);
//控制BEEP默认不响
GPIO_ResetBits(GPIOF,GPIO_Pin_8);
}
void TIM6_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
//Enable TIM clock using RCC_APBxPeriphClockCmd(RCC_APBxPeriph_TIMx, ENABLE) function
//1.打开TIM6的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
//Fill the TIM_TimeBaseInitStruct with the desired parameters.
//2.定义结构体变量并对结构体变量的成员进行赋值
TIM_TimeBaseStructure.TIM_Prescaler = 8400-1; //预分频值,TIM6的时钟是84MHZ,所以可以降低频率100us计数一次
TIM_TimeBaseStructure.TIM_Period = 5000-1; //计数次数,这个值会写入自动重载寄存器 100us *5000= 500ms
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数模式,基本定时器TIM6和TIM7只允许递增计数
//Call TIM_TimeBaseInit(TIMx, &TIM_TimeBaseInitStruct) to configure the Time Base
//3.对定时器进行初始化
TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure);
//Enable the NVIC if you need to generate the update interrupt.
//4.配置NVIC外设+对NVIC外设进行初始化
NVIC_InitStructure.NVIC_IRQChannel = TIM6_DAC_IRQn; //中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//Enable the corresponding interrupt using the function TIM_ITConfig(TIMx, TIM_IT_Update)
//5.选择TIM6的中断源 基本定时器TIM6和TIM7只能选择更新中断
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
//Call the TIM_Cmd(ENABLE) function to enable the TIM counter.
//6.打开定时器TIM6
TIM_Cmd(TIM6, ENABLE);
}
int main()
{
BEEP_Config();
TIM6_Config();
while(1)
{
}
}
//TIM6的中断服务程序
void TIM6_DAC_IRQHandler(void)
{
if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)//判断中断是否发生
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_8);//BEEP每隔500ms电平翻转一次
TIM_ClearITPendingBit(TIM6, TIM_IT_Update);//清除TIM6的中断标志
}
}
四、通用定时器
相比于基本定时器,通用定时器 增加了输入捕获 功能以及输出比较 功能,并且通用定时器具有独立通道,就可以把GPIO引脚连接到某个通道,利用通道进行输入信号的检测以及脉冲信号的输出。
1.通用定时器的基本概念
STM32F407ZET7一共提供10个通用定时器(TIM2~TIM5、TIM9~TIM14),TIM2和TIM5是32bit定时器,其他的定时器都是16bit定时器。
++TIM2~TIM5的计数方式有三种可以选择,分别为递增计数、递减计数、递增/递减计数。++

计数方式:
递增计数:计数器从 0 计数到自动重载值(TIMx_ARR 寄存器的内容),然后重新从 0 开始计数并生成计数器上溢事件。
递减计数:计数器从自动重载值(TIMx_ARR 寄存器的内容)开始递减计数到 0,然后重新从自动重载值开始计数并生成计数器下溢事件。
中心对齐:计数器从 0 开始计数到自动重载值(TIMx_ARR 寄存器的内容)- 1,生成计数器上溢事件;然后从自动重载值开始向下计数到 1 并生成计数器下溢事件。之后从 0 开始重新计数。


定时功能:基本上使用流程和基本定时器一致,只不过通用定时器的计数方式更灵活而已。
输入捕获:可以把定时器的某个通道连接到GPIO引脚上,然后从外部输入脉冲信号,经过 通道的滤波以及边沿检测之后,可以记录某个电平信号的脉冲宽度以及周期。
输出比较:可以把定时器的某个通道连接到GPIO引脚上,主动从引脚输出一个固定的脉冲, 原理很简单,其实就是计数器(TIM_CNT)如果超过比较寄存器中的值,就可以输出一个电平信号(高电平或者低电平)。

对于TIM9~TIM14 而言,也可以进行定时功能,同样也具有输入捕获以及输出比较功能,但是只能采用向上计数的方式 ,并且相比于TIM2~TIM5,只有2个独立通道。

2. PWM脉冲宽度调制
PWM脉冲宽度调制的原理 : 根据相应载荷的变化来调制晶体管基极或MOS管栅极的偏置,来实现晶体管或MOS管导通时间的改变,从而++实现开关稳压电源输出的改变++ 。这种方式能++使电源的输出电压在工作条件变化时保持恒定++。
PWM利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。
++PWM技术的关键参数有两个,一个是频率 ,一个是占空比++。
频率 : 利用STM32的定时器通道输出脉冲的次数。
占空比 : 在一个脉冲周期中通电时间相对于总时间所占的比例,也可以简单理解为一个周期内高电平持续时间相对于总时间所占的比例(%)。

查阅芯片的数据手册可知,PF9引脚是连接在定时器TIM14的通道CH1上,所以就必须配置TIM14和CH1。


PWM技术的原理其实很简单,就是利用STM32定时器中的某个通道的输出比较功能来输出周期性的脉冲信号,然后调节脉冲的宽度(调节占空比)达到控制外设的目的。
3. 使用定时器通道PWM的程序设计
需求:利用通用定时器TIM14实现呼吸灯效果。
cpp
/**
******************************************************************************
* @file GPIO/GPIO_IOToggle/main.c
* @author
* @version
* @date 03-August-2026
* @brief 利用通用定时器TIM14实现呼吸灯效果
LED0----PF9
PF9--TIM14_CH1
******************************************************************************
**/
#include "stm32f4xx.h" //必须要包含的头文件
void delay_us(uint32_t nus);
void delay_ms(uint32_t nms);
void delay_s(uint32_t ns);
void LED_Config(void);
void TIM14_Config(void)
{
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
GPIO_InitTypeDef GPIO_InitStructure;
//Enable TIM clock using RCC_APBxPeriphClockCmd(RCC_APBxPeriph_TIMx, ENABLE) function
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14, ENABLE);
/* GPIOC clock enable */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
//Configure the TIM pins by configuring the corresponding GPIO pins
/* GPIOC Configuration: PF9--TIM14_CH1*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource9, GPIO_AF_TIM14);
//Configure the Time base
TIM_TimeBaseStructure.TIM_Period = 100-1; //500us * 100 = 500ms
TIM_TimeBaseStructure.TIM_Prescaler = 42000-1; //f=2000Hz T=500us 84000000/42000=2000
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM14, &TIM_TimeBaseStructure);
//Fill the TIM_OCInitStruct
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 100;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
//Call TIM_OCxInit(TIMx, &TIM_OCInitStruct) to configure the desired channel
TIM_OC1Init(TIM14, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM14, TIM_OCPreload_Enable);
//TIM_OC4PreloadConfig(TIM14, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM14, ENABLE);
//Call the TIM_Cmd(ENABLE) function to enable the TIM counter.
TIM_Cmd(TIM14, ENABLE);
}
int main()
{
TIM14_Config();
delay_s(1);
while(1)
{
for(int i=100;i>0;i--)
{
TIM14->CCR1=i;
delay_ms(50);
}
for(int i=0;i<100;i++)
{
TIM14->CCR1=i;
delay_ms(50);
}
}
}
void LED_Config()
{
//定义外设的结构体变量
GPIO_InitTypeDef GPIO_InitStructure;
//打开外设时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
//对外设的结构体成员赋值
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//模式:输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//类型:推挽
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//输出速率:高速
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//电阻:无上拉下拉电阻
//对外设进行初始化
GPIO_Init(GPIOF, &GPIO_InitStructure);
//控制灯默认不亮
GPIO_SetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10);
}
void delay_us(uint32_t nus)
{
SysTick->CTRL = 0; // 关闭systick的控制状态寄存器――该寄存器的bit0置0
SysTick->LOAD = ( 21 * nus - 1); // systick的重载寄存器――计数时长 = 计数周期 * 计数次数
SysTick->VAL = 0; // 清空systick的当前数值寄存器
SysTick->CTRL = 1; // 使能systick的控制状态寄存器――该寄存器的bit0置1
while ((SysTick->CTRL & 0x00010000)==0);// 递减计数到了则控制状态寄存器的bit16为1,计数次数没到则bit16为0.
SysTick->CTRL = 0; //关闭systick的控制状态寄存器
}
void delay_ms(uint32_t nms)
{
SysTick->CTRL = 0; // 关闭systick的控制状态寄存器――该寄存器的bit0置0
SysTick->LOAD = ( 21*1000*nms - 1); // systick的重载寄存器――计数时长 = 计数周期 * 计数次数
SysTick->VAL = 0; // 清空systick的当前数值寄存器
SysTick->CTRL = 1; // 使能systick的控制状态寄存器――该寄存器的bit0置1
while ((SysTick->CTRL & 0x00010000)==0);// 递减计数到了则控制状态寄存器的bit16为1,计数次数没到则bit16为0.
SysTick->CTRL = 0; //关闭systick的控制状态寄存器
}
void delay_s(uint32_t ns)
{
while(ns--)
{
delay_ms(500);
delay_ms(500);
}
}
五、模拟脉冲信号的宽度来调节灯的亮度
如果IO口没有连接定时通道(比如LED1使用的PF10IO口没有连接定时通道),就没有办法用定时器来调节脉冲宽度了,此时只能"用延时模拟脉冲信号"来实现。
Q:如何调节脉冲宽度的?
A:可以用"定时器通道来生成脉冲信号",也可以"用延时模拟脉冲信号"。

六、使用逻辑分析仪来观察PWM脉冲信号

七、舵机的原理与应用
1.舵机的原理

2.定时器通道输出脉冲信号控制舵机
cpp
/**
******************************************************************************
* @file GPIO/GPIO_IOToggle/main.c
* @author
* @version
* @date 03-August-2026
* @brief 利用通用定时器TIM14实现呼吸灯效果
舵机的信号线----PE5
PE5--TIM9_CH1
******************************************************************************
**/
#include "stm32f4xx.h" //必须要包含的头文件
void delay_us(uint32_t nus);
void delay_ms(uint32_t nms);
void delay_s(uint32_t ns);
void LED_Config(void);
void TIM9_Config(void)
{
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
GPIO_InitTypeDef GPIO_InitStructure;
//Enable TIM clock using RCC_APBxPeriphClockCmd(RCC_APBxPeriph_TIMx, ENABLE) function
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM9, ENABLE);
/* GPIOE clock enable */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
//Configure the TIM pins by configuring the corresponding GPIO pins
/* GPIOC Configuration: PF9--TIM14_CH1*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource5, GPIO_AF_TIM9);
//Configure the Time base
TIM_TimeBaseStructure.TIM_Prescaler = 42000-1; //预分频值,TIM9的时钟是168MHZ,所以可以降低频率 250us计数一次
TIM_TimeBaseStructure.TIM_Period = 80-1; //250us * 80 = 500ms
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM9, &TIM_TimeBaseStructure);
//Fill the TIM_OCInitStruct
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //PWM模式1 CNT < CCR 通道是有效的
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 4; //20ms中高电平持续0.5ms,舵机角度为0度
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性为高电平,就是通道有效时的电平状态
//Call TIM_OCxInit(TIMx, &TIM_OCInitStruct) to configure the desired channel
TIM_OC1Init(TIM9, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM9, TIM_OCPreload_Enable);
//TIM_OC4PreloadConfig(TIM9, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM9, ENABLE);
//Call the TIM_Cmd(ENABLE) function to enable the TIM counter.
TIM_Cmd(TIM9, ENABLE);
}
int main()
{
TIM9_Config();
delay_s(1);
while(1)
{
TIM9->CCR1 = 2; // 高电平持续0.5ms ,对应的是0度
delay_ms(100);
TIM9->CCR1 = 4; // 高电平持续1.0ms ,对应的是45度
delay_ms(100);
TIM9->CCR1 = 6; // 高电平持续1.5ms ,对应的是90度
delay_ms(100);
TIM9->CCR1 = 8; // 高电平持续2.0ms ,对应的是135度
delay_ms(100);
TIM9->CCR1 = 10; // 高电平持续2.5ms ,对应的是180度
delay_ms(100);
TIM9->CCR1 = 8; // 高电平持续2.0ms ,对应的是135度
delay_ms(100);
TIM9->CCR1 = 6; // 高电平持续1.5ms ,对应的是90度
delay_ms(100);
TIM9->CCR1 = 4; // 高电平持续1.0ms ,对应的是45度
delay_ms(100);
TIM9->CCR1 = 2; // 高电平持续0.5ms ,对应的是0度
delay_ms(100);
}
}
void delay_us(uint32_t nus)
{
SysTick->CTRL = 0; // 关闭systick的控制状态寄存器――该寄存器的bit0置0
SysTick->LOAD = ( 21 * nus - 1); // systick的重载寄存器――计数时长 = 计数周期 * 计数次数
SysTick->VAL = 0; // 清空systick的当前数值寄存器
SysTick->CTRL = 1; // 使能systick的控制状态寄存器――该寄存器的bit0置1
while ((SysTick->CTRL & 0x00010000)==0);// 递减计数到了则控制状态寄存器的bit16为1,计数次数没到则bit16为0.
SysTick->CTRL = 0; //关闭systick的控制状态寄存器
}
void delay_ms(uint32_t nms)
{
SysTick->CTRL = 0; // 关闭systick的控制状态寄存器――该寄存器的bit0置0
SysTick->LOAD = ( 21*1000*nms - 1); // systick的重载寄存器――计数时长 = 计数周期 * 计数次数
SysTick->VAL = 0; // 清空systick的当前数值寄存器
SysTick->CTRL = 1; // 使能systick的控制状态寄存器――该寄存器的bit0置1
while ((SysTick->CTRL & 0x00010000)==0);// 递减计数到了则控制状态寄存器的bit16为1,计数次数没到则bit16为0.
SysTick->CTRL = 0; //关闭systick的控制状态寄存器
}
void delay_s(uint32_t ns)
{
while(ns--)
{
delay_ms(500);
delay_ms(500);
}
}