STM32定时器:从基础定时到PWM高级应用

一、STM32 基本定时器的原理与应用

1. 基本概念

定时器的作用一般是为了使用定时功能和中断功能(洗衣机、微波炉.....),当然在STM32中也可以利用定时器产生周期性的脉冲信号来控制不同的外设(灯的亮度、电机的转速、舵机的角度......),所以掌握STM32 中的定时器对于项目开发是很有必要的。

2. 外设种类

对于STM32F407 微处理器而言,内部一共集成了14 个定时器,其中有2 个基本定时器(TIM6和TIM7)、10 个通用定时器(TIM2TIM5、TIM9TIM14)、2 个高级定时器(TIM1 和TIM8)。

其中通用定时器TIM2 和TIM5 为32 位定时器,其他为16 位定时器,当然,定时器位数越大,定时时间越久。

3.基本特点

image-20251121132010746

4.原理分析

image-20251121132050859

5.程序设计

image-20251121132133109

/**
 * @name      Time_Config
 * @brief     配置TIM7定时器
 * @param     无
 * @return    无
 * @date      2025.7.25
 * @version   1.0
 * @note      配置TIM7定时器产生5秒周期中断,控制蜂鸣器
 */
void Time6_Config()
{
	/* 定义TIM6结构体变量 */
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	/* 定义NVIC结构体变量 */
	NVIC_InitTypeDef NVIC_InitStructure;

	/* 打开TIM7时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);
	
	/* 对Time6结构体变量赋值+初始化 */ 
	TIM_TimeBaseStructure.TIM_Prescaler = 8400-1;				// 84000000/8400=10000Hz,每100us计数一次
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式
	TIM_TimeBaseStructure.TIM_Period = 50000-1;  			    // 50000*100us=5秒中断一次
	TIM_TimeBaseInit(TIM7, &TIM_TimeBaseStructure);

	/* 对NVIC结构体变量赋值+初始化 */
	NVIC_InitStructure.NVIC_IRQChannel = TIM7_IRQn; 			// TIM7中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;   // 抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; 			// 子优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 			// 使能中断通道
	NVIC_Init(&NVIC_InitStructure);

	/* 打开定时器中断 */
	TIM_ITConfig(TIM7, TIM_IT_Update, ENABLE);

	/* 打开TIM7计数器 */
	TIM_Cmd(TIM7, ENABLE);
}

/**
 * @name      TIM7_IRQHandler
 * @brief     TIM7中断服务函数
 * @param     无
 * @return    无
 * @version   1.0
 * @note      TIM7更新中断,每5秒翻转PF8引脚状态
 */
void TIM7_IRQHandler(void)
{
	/* 检查TIM7更新中断标志 */
	if (TIM_GetITStatus(TIM7, TIM_IT_Update) != RESET)
	{
		/* 翻转PF8引脚状态(控制蜂鸣器) */
		GPIO_ToggleBits(GPIOF, GPIO_Pin_8);
		
		/* 清除TIM7更新中断标志 */
		TIM_ClearITPendingBit(TIM7, TIM_IT_Update);
	}
}

二、通用通用定时器及功能拓展

相比于基本定时器而言,通用定时器增加了输入捕获功能以及输出比较功能,并且通用定时器具有独立通道,就可以把GPIO 引脚连接到某个通道,利用通道进行输入信号的检测以及脉冲信号的输出。

STM32F407ZET7 一共提供10 个通用定时器(TIM2TIM5、TIM9TIM14),TIM2 和TIM5 是32bit 定时器,其他的定时器都是16bit 定时器。TIM2~TIM5 的计数方式有三种可以选择,分别为递增计数、递减计数、递增/递减计数。

image-20251121132840424

递增计数:计数器从 0 计数到自动重载值(TIMx_ARR 寄存器的内容),然后重新从 0 开始计数并生成计数器上溢事件。

递减计数:计数器从自动重载值(TIMx_ARR 寄存器)开始递减计数到 0,然后重新从自动重载值开始计数并生成计数器下溢事件。

中心对齐:计数器从 0 开始计数到自动重载值(TIMx_ARR 寄存器的内容)- 1,生成计数器上溢事件;然后从自动重载值开始向下计数到 1 并生成计数器下溢事件。之后从 0 开始重新计数。
对于通用定时器TIM2~TIM5,都挂载在APB1 外设总线下,定时器的频率为84MHZ

定时功能:基本上使用流程和基本定时器一致,只不过通用定时器的计数方式更灵活而已。

输入捕获:可以把定时器的某个通道连接到GPIO 引脚上,然后从外部输入脉冲信号,经过通道的滤波以及边沿检测之后,可以记录某个电平信号的脉冲宽度以及周期。

输出比较:可以把定时器的某个通道连接到GPIO 引脚上,主动从引脚输出一个固定的脉冲,原理很简单,其实就是计数器(TIM_CNT)如果超过比较寄存器中的值,就可以输出一个电平信号(高电平或者低电平)。

image-20251121133202204

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

image-20251121133219914

脉宽调制

PWM(Pulse Width Modulation)称为脉冲宽度调制,原理是根据相应载荷的变化来调制晶体管基极或MOS 管栅极的偏置,来实现晶体管或MOS 管导通时间的改变,从而实现开关稳压电源输出的改变。这种方式能使电源的输出电压在工作条件变化时保持恒定。

脉冲宽度调制能利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。

PWM 技术的关键参数有两个,一个是频率,一个是占空比。频率指的是利用STM32 的定时器通道输出脉冲的次数。占空比是指在一个脉冲周期中通电时间相对于总时间所占的比例,也可以简单理解为一个周期内高电平持续时间相对于总时间所占的比例(%)。

image-20251121133406357

要实现精确的占空比控制,该电路必须有恒定的工作频率做保证。其实频率固定,也就是工作电路周期固定。因为占空比控制的电路接通率是建立在恒定工作周期上的,如果电子控制模块控制电路的周期不能保证恒定,那么所谓的占空比控制是毫无意义的。

PWM 技术一般用在工业控制领域(控制电机的转速、控制舵机的角度........)。

利用PWM技术控制led灯

程序设计

/**
 ******************************************************************************
 * @file    pwm.c 
 * @author  qrshxc@163.com
 * @version V1.0
 * @date    2025.7.25
 * @brief   使用TIM14 PWM控制LED呼吸灯效果
 ******************************************************************************
 **/

#include "stm32f4xx.h"

/**
 * @name      delay_us
 * @brief     微秒级延时函数
 * @param     nus: 延时的微秒数
 * @return    无
 * @version   1.0
 * @note      使用SysTick定时器实现精确微秒延时
 */
void delay_us(u32 nus)
{
	SysTick->CTRL = 0;   					 // 关闭系统嘀嗒定时器
	SysTick->LOAD = nus * 21 -1; 			 // 计数次数,递减计数,0结束
	SysTick->VAL = 0;   					 // 清除当前数值寄存器
	SysTick->CTRL = 1;   		 			 // 打开系统嘀嗒定时器,使用参考时钟
	while ((SysTick->CTRL & 0x00010000)==0); // 等待计时完成
	SysTick->CTRL = 0;    					 // 关闭系统嘀嗒定时器
}

/**
 * @name      delay_ms
 * @brief     毫秒级延时函数
 * @param     nus: 延时的毫秒数
 * @return    无
 * @version   1.0
 * @note      使用SysTick定时器实现精确毫秒延时
 */
void delay_ms(u32 nus)
{
	SysTick->CTRL = 0;    			    	 // 关闭系统嘀嗒定时器
	SysTick->LOAD = nus * 21 * 1000 -1;		 // 计数次数,递减计数,0结束
	SysTick->VAL = 0;    					 // 清除当前数值寄存器
	SysTick->CTRL = 1;   					 // 打开系统嘀嗒定时器,使用参考时钟
	while ((SysTick->CTRL & 0x00010000)==0); // 等待计时完成
	SysTick->CTRL = 0;    					 // 关闭系统嘀嗒定时器
}

/**
 * @name      Time14_Config
 * @brief     配置TIM14定时器PWM输出
 * @param     无
 * @return    无
 * @version   1.0
 * @note      配置TIM14通道1输出PWM信号,控制LED呼吸灯
 */
void Time14_Config()
{
	/* 定义TIM14结构体变量 */
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	/* 定义GPIO引脚的结构体变量 */
	GPIO_InitTypeDef GPIO_InitStructure;
	/* 定义输出通道结构体变量 */
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	
	/* 打开TIM14时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14, ENABLE);
	
	/* 打开GPIOF时钟 */
	RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOF, ENABLE);
	  
	/* GPIOF9引脚配置+初始化 */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;          // PF9引脚
	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);

	/* PF9引脚复用为TIM14功能 */
	GPIO_PinAFConfig(GPIOF, GPIO_PinSource9, GPIO_AF_TIM14);
	
	/* 对TIM14结构体变量赋值+初始化 */
	TIM_TimeBaseStructure.TIM_Prescaler = 8400-1;   			// 预分频值,84MHz/8400=10kHz
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式
	TIM_TimeBaseStructure.TIM_Period = 100-1;       			// 自动重载值,10kHz/100=100Hz PWM频率
	TIM_TimeBaseInit(TIM14, &TIM_TimeBaseStructure);

	/* 通道1 PWM模式配置 */
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;             // PWM模式1
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 使能输出
	TIM_OCInitStructure.TIM_Pulse = 50;                    		  // 初始占空比50%
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; 	  // 输出极性低

	/* 初始化TIM14通道1 */
	TIM_OC1Init(TIM14, &TIM_OCInitStructure);

	/* 使能TIM14通道1预装载寄存器 */
	TIM_OC1PreloadConfig(TIM14, TIM_OCPreload_Enable);
	
	/* 使能TIM14自动重载预装载寄存器 */
	TIM_ARRPreloadConfig(TIM14, ENABLE);

	/* 打开TIM14计数器 */
	TIM_Cmd(TIM14, ENABLE);
}

/**
 * @name      main
 * @brief     主函数
 * @param     无
 * @return    int: 程序执行状态
 * @date      2025.10.2
 * @version   1.0
 * @note      实现LED呼吸灯效果
 */
int main()
{
	/* TIM14 PWM初始化 */
	Time14_Config();
	
	/* 主循环 - 呼吸灯效果 */
	while(1)
	{
		/* 亮度逐渐增加 */
		for(int i=0;i<=100;i++)
		{
			TIM14->CCR1 = i;    // 设置PWM占空比
			delay_ms(2);        // 延时2ms
		}
		
		/* 亮度逐渐减小 */
		for(int i=100;i>=0;i--)
		{
			TIM14->CCR1 = i;    // 设置PWM占空比
			delay_ms(2);        // 延时2ms
		}
	}
}

如果想要观察波形,可以使用逻辑分析仪或者示波器来进行观察,看到PWM 脉冲信号如下

image-20251121134552673

三、舵机的原理与应用

舵机是一种位置伺服的驱动器,通过接收PWM(脉冲宽度调制)信号来控制输出轴的角度。舵机的控制信号需要满足特定的时序要求:

关键参数:

  • 工作周期:20ms(50Hz)
  • 脉冲宽度:0.5ms - 2.5ms
  • 对应角度:0° - 180°

角度与脉冲宽度对应关系:

image-20251121134651510

软件模拟PWM控制舵机

程序设计

/**
 ******************************************************************************
 * @file    pwm.c 
 * @author  qrshxc@163.com
 * @version V1.0
 * @date    2025.7.25
 * @brief   利用延时模拟PWM控制SG90舵机(PB6引脚)
 ******************************************************************************
 **/

#include "stm32f4xx.h"

/**
 * @name      delay_us
 * @brief     微秒级延时函数
 * @param     nus: 延时的微秒数
 * @return    无
 * @version   1.0
 * @note      使用SysTick定时器实现精确微秒延时
 */
void delay_us(u32 nus)
{
	SysTick->CTRL = 0;   					 // 关闭系统嘀嗒定时器
	SysTick->LOAD = nus * 21 -1; 			 // 计数次数,递减计数,0结束
	SysTick->VAL = 0;   					 // 清除当前数值寄存器
	SysTick->CTRL = 1;   		 			 // 打开系统嘀嗒定时器,使用参考时钟
	while ((SysTick->CTRL & 0x00010000)==0); // 等待计时完成
	SysTick->CTRL = 0;    					 // 关闭系统嘀嗒定时器
}

/**
 * @name      delay_ms
 * @brief     毫秒级延时函数
 * @param     nus: 延时的毫秒数
 * @return    无
 * @version   1.0
 * @note      使用SysTick定时器实现精确毫秒延时
 */
void delay_ms(u32 nus)
{
	SysTick->CTRL = 0;    			    	 // 关闭系统嘀嗒定时器
	SysTick->LOAD = nus * 21 * 1000 -1;		 // 计数次数,递减计数,0结束
	SysTick->VAL = 0;    					 // 清除当前数值寄存器
	SysTick->CTRL = 1;   					 // 打开系统嘀嗒定时器,使用参考时钟
	while ((SysTick->CTRL & 0x00010000)==0); // 等待计时完成
	SysTick->CTRL = 0;    					 // 关闭系统嘀嗒定时器
}

/**
 * @name      SG90_Config
 * @brief     SG90舵机引脚配置
 * @param     无
 * @return    无
 * @version   1.0
 * @note      配置PB6引脚为输出模式,用于控制舵机信号
 */
void SG90_Config()
{
	/* 定义GPIO外设的结构体变量 */
	GPIO_InitTypeDef  GPIO_InitStructure;
	
	/* 使能GPIOB端口的时钟 */
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
	
	/* 对PB6引脚进行配置 */
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_6;        // 舵机控制引脚
	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(GPIOB, &GPIO_InitStructure);
}

/**
 * @name      Set_Servo_Angle
 * @brief     设置舵机角度
 * @param     angle: 目标角度(0-180度)
 * @return    无
 * @date      2025.10.2
 * @version   1.0
 * @note      通过延时模拟PWM信号控制舵机角度
 */
void Set_Servo_Angle(uint8_t angle)
{
    uint32_t pulse_width;
    
    /* 计算脉冲宽度:0.5ms + (角度/180) * 2ms */
    pulse_width = 500 + (angle * 2000) / 180;  // 单位:微秒
    
    /* 生成PWM信号 */
    GPIO_SetBits(GPIOB, GPIO_Pin_6);          // 输出高电平
    delay_us(pulse_width);                    // 保持脉冲宽度
    GPIO_ResetBits(GPIOB, GPIO_Pin_6);        // 输出低电平
    delay_us(20000 - pulse_width);            // 补足20ms周期
}

/**
 * @name      main
 * @brief     主函数
 * @param     无
 * @return    int: 程序执行状态
 * @version   1.0
 * @note      实现舵机角度循环扫描
 */
int main()
{
	/* 舵机引脚初始化 */
	SG90_Config();
	
	/* 主循环 - 舵机角度扫描 */
	while(1)
	{
		/* 0°位置 */
		GPIO_SetBits(GPIOB, GPIO_Pin_6);  // 高电平开始
		delay_us(500);                    // 0.5ms脉冲(0°)
		GPIO_ResetBits(GPIOB, GPIO_Pin_6); // 低电平
		delay_us(19500);                  // 补足20ms周期
		delay_ms(100);                    // 保持100ms
		
		/* 45°位置 */
		GPIO_SetBits(GPIOB, GPIO_Pin_6);
		delay_us(1000);                   // 1.0ms脉冲(45°)
		GPIO_ResetBits(GPIOB, GPIO_Pin_6);
		delay_us(19000);                  // 补足20ms周期
		delay_ms(100);                    // 保持100ms
		
		/* 90°位置 */
		GPIO_SetBits(GPIOB, GPIO_Pin_6);
		delay_us(1500);                   // 1.5ms脉冲(90°)
		GPIO_ResetBits(GPIOB, GPIO_Pin_6);
		delay_us(18500);                  // 补足20ms周期
		delay_ms(100);                    // 保持100ms
		
		/* 135°位置 */
		GPIO_SetBits(GPIOB, GPIO_Pin_6);
		delay_us(2000);                   // 2.0ms脉冲(135°)
		GPIO_ResetBits(GPIOB, GPIO_Pin_6);
		delay_us(18000);                  // 补足20ms周期
		delay_ms(100);                    // 保持100ms
		
		/* 180°位置 */
		GPIO_SetBits(GPIOB, GPIO_Pin_6);
		delay_us(2500);                   // 2.5ms脉冲(180°)
		GPIO_ResetBits(GPIOB, GPIO_Pin_6);
		delay_us(17500);                  // 补足20ms周期
		delay_ms(100);                    // 保持100ms
		
		/* 返回135°位置 */
		GPIO_SetBits(GPIOB, GPIO_Pin_6);
		delay_us(2000);                   // 2.0ms脉冲(135°)
		GPIO_ResetBits(GPIOB, GPIO_Pin_6);
		delay_us(18000);                  // 补足20ms周期
		delay_ms(100);                    // 保持100ms
		
		/* 返回90°位置 */
		GPIO_SetBits(GPIOB, GPIO_Pin_6);
		delay_us(1500);                   // 1.5ms脉冲(90°)
		GPIO_ResetBits(GPIOB, GPIO_Pin_6);
		delay_us(18500);                  // 补足20ms周期
		delay_ms(100);                    // 保持100ms
		
		/* 返回45°位置 */
		GPIO_SetBits(GPIOB, GPIO_Pin_6);
		delay_us(1000);                   // 1.0ms脉冲(45°)
		GPIO_ResetBits(GPIOB, GPIO_Pin_6);
		delay_us(19000);                  // 补足20ms周期
		delay_ms(100);                    // 保持100ms
	}
}
posted @ 2025-11-22 23:50  九思0404  阅读(191)  评论(0)    收藏  举报