一、定时器概述
1.1 软件定时原理
使用纯软件(CPU死等)的方式实现定时(延时)功能;
不精准的延迟:
/* 微秒级延迟函数* 不精准* stm32存在压出栈过程需要消耗时间* 存在流水线,执行时间不确定*/
void delay_us(uint32_t us)
{us*=72; /* 延时因子 */while(us--);
}
1.2 定时器原理
使用精准的时基,通过硬件的方式,实现定时功能;
定时器的核心是计数器,原理:
精准的时钟源(产生CLK时钟信号)->预分频器(PSC,频率做一个除法,得到计数器工作频率->CNT计数器(没来一个时钟计一个数)->产生溢出(时间到,并自动重装CNT计数器值)
1.3 STM32定时器分类
常规定时器:基本定时器、通用定时器、高级定时器
专用定时器:独立看门狗、窗口看门狗、实时时钟、低功耗定时器
内核定时器:SysTick定时器
(1)定时器配置(F1系列)
基本定时器TIM6/TIM7(16位0-65535,递增,分频系数1~65535);
通用定时器TIM2-5(16位、递增/递减/中央对齐,分频系数1~65535,捕获比较通道4);
高级定时器TIM1/TIM8(16位、递增/递减/中央对齐,分频系数1~65535,捕获比较通道4,互补输出)
(2)功能区别
基本定时器:没有输入输出通道,常用作时基,即定时功能;
通用定时器:具有多路独立通道,可用作输入捕获/输出比较,也可用作时基;
高级定时器:除具备通用定时器所有功能外,还具备带死区控制的互不信号输出、刹车输入等功能(可用作电机控制、数字电源设计等)
二、定时器计数模式及溢出条件
2.1 基本定时器简介
1. 基本定时器:TIM6/TIM7(F1);
2. 主要特性:
16位递增计数器(计数值:0~65535);
16位预分频器(分频系数:1~65536,等于PSC寄存器值+1);
可用于触发DAC;
在更新事件(计数器溢出)时,可产生中断/DMA请求;
2.2 定时器计数模式及溢出条件
递增计数模式:CNT==ARR(自动重装载计数器设定)
递减计数模式:CNT==0
中心对齐模式:CNT==ARR-1、CNT==1
2.3 实例说明
(1)递增计数模式
PSC=1; /* 分频系数=PSC+1=2 */
ARR=36; /* 计数到36时产生溢出 */
CK_PSC为时钟信号,CNT_EN为使能信号(使能使计数器起作用计数) ,定时器时钟(经过PSC+1分频后的计数),计数寄存器每来一个定时器时钟进行加一,计数器上溢/更新事件/更新中断标志(达到溢出计数值时触发置1);
(2)递减模式
PSC=1; /* 分频系数=PSC+1=2 */
ARR=36; /* 计数到36时产生溢出 */
(3)中心对齐模式
PSC=0; /* 分频系数=PSC+1=1 */
ARR=6; /* 计数到6时产生溢出 */
上图开始时计数器寄存器递减直至0时触发置1,再递增直至到6后触发置1再递减。
三、定时器相关寄存器及溢出时间计算方法
3.1 定时器中断相关寄存器(F1)
(1)TIM6和TIM7控制寄存器1(TIMx_CR1)
ARPE(位7):自动重装载使能,默认为0(TIMx_ARR寄存器没有缓存,操控ARR后立即赋值到影子寄存器生效),置1(操控ARR后,需等到U事件发生再赋值到影子寄存器);
CEN(位0):0关闭计数器,1使能计数器;
(2)TIM6和TIM7 DMA/中断使能寄存器(TIMx_DIER)
UDE(位8):更新DMA请求使能,0禁止更新DMA请求,1使能更新DMA请求;
UIE(位0):更新中断使能,0禁止更新中断,1使能更新中断;
(3)TIM6和TIM7 状态寄存器(TIMx_SR)
UIF(位0):用于判断是否产生了更新中断,由硬件置1,软件清除。更新中断标志,硬件在更新中断设置时设置该位,由软件清除。0没有产生更新,1产生了更新中断。
(4)TIM6和TIM7 计数器(TIMx_CNT)
计数器实时数值,可用于设置计时器初始值,范围:0~65535;
(5)TIM6和TIM7 预分频器(TIMx_PSC)
计数器的时钟频率CK_CNT等于f(ck_psc)/(PSC+1);
(6)TIM6和TIM7 自动重装载寄存器(TIMx_ARR)
3.2 定时器溢出时间计算方法
定时器溢出时间计算公式:
其中,Tout是定时器溢出时间,Ft是定时器的时钟源频率(未经过分频),ARR是自动重装载寄存器的值,PSC是预分频寄存器的值。
四、基本定时器中断实验配置
4.1 中断实验配置步骤
(1)配置定时器基础工作参数
HAL_TIM_Base_Init()
主要寄存器:CR1、ARR、PSC
功能:初始化定时器基础参数
(2)定时器基础MSP初始化
HAL_TIM_Base_MspInit()
功能:存放NVIC、CLOCK、GPIO初始化代码
(3)使能更新中断并启动计数器
HAL_TIM_Base_Start_IT()
寄存器:DIER、CR1
功能:使能更新中断并启动计数器
(4)设置优先级,使能中断
HAL_NVIC_SetPriority()、HAL_NVIC_Enable()
(5)编写中断服务函数
TIMx_IRQHandler()等->调用HAL_TIM_IRQHandler()
寄存器:SR
功能:定时器中断处理公用函数,处理各种中断
(6)编写定时器更新中断回调函数
HAL_TIM_PeriodElapsedCallback()
功能:定时器更新中断回调函数,由用户重定义
4.2 基本定时器中断程序设计
实现功能:使用定时器6,实现500ms定时器更新中断,在中断里翻转LED0
参数设定:
(1)挂载在APB1(36MHz),定时器时钟x2=72MHz,Ft=72MHz;
(2)分频系数:PSC设为7199(加1刚好为7200);
(3)ARR=0.5*72000000/7200=5000->ARR=4999。
源码:
主函数:
int main(void)
{HAL_Init(); /* 初始化HAL库 */sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */delay_init(72); /* 延时初始化 */usart_init(115200); /* 串口初始化为115200 */led_init(); /* 初始化LED */btim_timx_int_init(5000 - 1, 7200 - 1); /* 10Khz的计数频率,计数5K次为500ms */
}
定时器头文件:
#ifndef __BTIM_H
#define __BTIM_H#include "./SYSTEM/sys/sys.h"#define BTIM_TIMX_INT TIM6
#define BTIM_TIMX_INT_IRQn TIM6_DAC_IRQn
#define BTIM_TIMX_INT_IRQHandler TIM6_DAC_IRQHandler
#define BTIM_TIMX_INT_CLK_ENABLE() do{ __HAL_RCC_TIM6_CLK_ENABLE(); }while(0) /* TIM6 时钟使能 */void btim_timx_int_init(uint16_t arr, uint16_t psc); /* 基本定时器 定时中断初始化函数 */#endif
定时器代码:
#include "./BSP/TIMER/btim.h"TIM_HandleTypeDef g_timx_handle; /* 定时器句柄 *//* 定时器中断初始化函数 */
void btim_timx_int_init(uint16_t arr, uint16_t psc)
{g_timx_handle.Instance = BTIM_TIMX_INT; /* 通用定时器X */g_timx_handle.Init.Prescaler = psc; /* 设置预分频系数 */g_timx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */g_timx_handle.Init.Period = arr; /* 自动装载值 */HAL_TIM_Base_Init(&g_timx_handle);HAL_TIM_Base_Start_IT(&g_timx_handle); /* 使能定时器x及其更新中断 */
}/*
* 定时器基础MSP初始化函数
* 定时器底层驱动,开启时钟,设置中断优先级
* 此函数会被HAL_TIM_Base_Init()函数调用*/
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{if (htim->Instance == BTIM_TIMX_INT){BTIM_TIMX_INT_CLK_ENABLE(); /* 使能TIM时钟 */HAL_NVIC_SetPriority(BTIM_TIMX_INT_IRQn, 1, 3); /* 抢占1,子优先级3,组2 */HAL_NVIC_EnableIRQ(BTIM_TIMX_INT_IRQn); /* 开启ITM3中断 */}
}/* 定时器6中断服务函数 */
void BTIM_TIMX_INT_IRQHandler(void)
{HAL_TIM_IRQHandler(&g_timx_handle); /* 定时器中断公共处理函数 */
}/* 定时器溢出中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if (htim->Instance == BTIM_TIMX_INT){LED1_TOGGLE(); /* LED1反转 */}
}
五、通用定时器
5.1 通用定时器简介
(1)通用定时器(F1):TIM2~TIM5;
(2)主要特性:16位递增、递减、中心对齐计数器(计数值:0~65535);16位预分频器(1~65535),可用于触发DAC和ADC;在更新事件、触发事件、输入捕获、输出比较,会产生终端/DMA请求;
(3)4个独立通道,可用于:输入捕获、输出比较、输出PWM、单脉冲模式;
(4)使用外部信号控制定时器,且可实现多个定时器互连的同步电路;
(5)支持编码器和霍尔传感器电路等;
5.2 计数器时钟源寄存器设置
(1)内部时钟(CK_INT):设置TIMx_SMCR的SMS=000
(2)外部时钟模式1(外部输入引脚TIx):
设置TIMx_SMCR的SMS=111(ECE为0),输入:TI1_ED(双边沿检测,通道1)、TI1FP1(单边沿,通道1)、TI2FP2(单边沿,通道2);
TS寄存器:100(TI1的边沿检测器TI1_ED)、101滤波后的定时器输入1(TI1FP1)、110(滤波后的定时器输入2)、111(外部触发输入ETFR)
ICF[3:0]寄存器(滤波器配置):0000无滤波器(以fDTS采样),0001采样频率fSAMPLING=fCK_INT(N=2),0010采样频率fSAMPLING=fCK_INT(N=4),0011采样频率fSAMPLING=fCK_INT(N=8),0100采样频率fSAMPLING=fDTS/2(N=6),0101采样频率fSAMPLING=fDTS/2(N=8),0110采样频率fSAMPLING=fDTS/4(N=6),0111采样频率fSAMPLING=fDTS/4(N=8),1000采样频率fSAMPLING=fDTS/8(N=6),....1111采样频率fSAMPLING=fDTS/32(N=8)
如当配置0011,即采样频率为72MHz,滤波次数N=8,即采样到连续8次的高/低电平,才会更改电平。
CKD寄存器(配置fDTS):00(tDTS=tCK_INT内部时钟源频率),01(tDTS=2xtCK_INT),10(tDTS=4xtCK_INT),11(保留);
CC1P:输入/捕获1输出极性,0(捕获上升沿),1(捕获下降沿)
(3)外部时钟模式2(外部输入触发ETR):
设置TIMx_SMCR的ECE=1,引脚输入:ETR引脚输入
ETP寄存器:0(高电平或上升沿有效)、1(低电平或下降沿有效);
ETPS寄存器:00关闭预分频、01ETPR频率/2、10ETPR频率/4、11ETPR频率/8;
ETP寄存器:外部触发滤波,与外部时钟1中的ICF相同定义
使用一个定时器作为来一个定时器的预分频器
MMS寄存器:主模式选择,010(更新,更新事件被选为触发输入。主定时器的时钟可以被用作分定时器的预分频器);
SMS=111;
TS:当从定时器为TIM2,000(定时器1作为主)、001(定时器8作为主)、010(定时器2作为主),011(定时器4为主)
(4)内部触发输入
5.3 通用定时器中断实验
与基本定时器异同:通用定时器有三种模式——递增、递减、中间对齐
(1)主函数编写
int main(void)
{HAL_Init(); /* 初始化HAL库 */sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */delay_init(72); /* 延时初始化 */usart_init(115200); /* 串口初始化为115200 */led_init(); /* 初始化LED */gtim_timx_int_init(5000 - 1, 7200 - 1); /* 10Khz的计数频率,计数5K次为500ms */}
(2)通用定时器头文件编写
#ifndef __GTIM_H
#define __GTIM_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 通用定时器 定义 *//* TIMX 中断定义 * 默认是针对TIM2~TIM5.* 注意: 通过修改这4个宏定义,可以支持TIM1~TIM8任意一个定时器.*/
#define GTIM_TIMX_INT TIM3
#define GTIM_TIMX_INT_IRQn TIM3_IRQn
#define GTIM_TIMX_INT_IRQHandler TIM3_IRQHandler
#define GTIM_TIMX_INT_CLK_ENABLE() do{ __HAL_RCC_TIM3_CLK_ENABLE(); }while(0) /* TIM3 时钟使能 */
/******************************************************************************************/
void gtim_timx_int_init(uint16_t arr, uint16_t psc); /* 通用定时器 定时中断初始化函数 */
#endif
(3)通用定时器.c文件编写
#include "./BSP/TIMER/gtim.h"
#include "./BSP/LED/led.h"
TIM_HandleTypeDef g_timx_handle; /* 定时器x句柄 */
/*** @brief 通用定时器TIMX定时中断初始化函数* @note* 通用定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候* 通用定时器的时钟为APB1时钟的2倍, 而APB1为36M, 所以定时器时钟 = 72Mhz* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.* Ft=定时器工作频率,单位:Mhz** @param arr: 自动重装值。* @param psc: 时钟预分频数* @retval 无*/
void gtim_timx_int_init(uint16_t arr, uint16_t psc)
{GTIM_TIMX_INT_CLK_ENABLE(); /* 使能TIMx时钟 */g_timx_handle.Instance = GTIM_TIMX_INT; /* 通用定时器x */g_timx_handle.Init.Prescaler = psc; /* 预分频系数 */g_timx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */g_timx_handle.Init.Period = arr; /* 自动装载值 */HAL_TIM_Base_Init(&g_timx_handle);HAL_NVIC_SetPriority(GTIM_TIMX_INT_IRQn, 1, 3); /* 设置中断优先级,抢占优先级1,子优先级3 */HAL_NVIC_EnableIRQ(GTIM_TIMX_INT_IRQn); /* 开启ITMx中断 */HAL_TIM_Base_Start_IT(&g_timx_handle); /* 使能定时器x和定时器x更新中断 */
}/*** @brief 定时器中断服务函数* @param 无* @retval 无*/
void GTIM_TIMX_INT_IRQHandler(void)
{/* 以下代码没有使用定时器HAL库共用处理函数来处理,而是直接通过判断中断标志位的方式 */if(__HAL_TIM_GET_FLAG(&g_timx_handle, TIM_FLAG_UPDATE) != RESET){LED1_TOGGLE();__HAL_TIM_CLEAR_IT(&g_timx_handle, TIM_IT_UPDATE); /* 清除定时器溢出中断标志位 */}
}
六、通用定时器PWM输出
6.1 通用定时器输出PWM原理
(1)输出比较框图(定时器1)
捕获/比较预装载寄存器->满足输出条件->捕获比较影子寄存器->与计数器一同进入比较器->输出
满足输出条件:1)CCR1寄存器已写完;2)CC1S配置为0则设定定时器1可输出;3)OC1PE(0禁止TIMx_CCR1寄存器预装载功能,1开启TIMx_CCR1预转载功能--需发送UEV更新事件)
(2)寄存器配置
CC1S:00(通道1为输出),01-11均为输入;
OC1M:输出模式选择,共有8种(F1)。110和111均为PWM输出模式。CCxP=0(高电平有效),CCxP=1(低电平有效)
110:向上计数时,一旦TIMx_CNT<TIMx_CCR1时,通道1为有效电平,否则为无效电平。向下计数时,TIMx_CNT>TIMx_CCR1时,通道1为有效电平,否则为无效电平;
111:向上计数时,一旦TIMx_CNT<TIMx_CCR1时,通道1为无效电平,否则为有效电平。向下计数时,TIMx_CNT>TIMx_CCR1时,通道1为无效电平,否则为有效电平;
ETRF:强制清零,默认为0不受ETRF影响;当设为1时,一旦检测到ETRF高电平,清除输出OC1REF=0;
CC1P:输出极性选择,0时OC1高电平有效,1时OC1低电平有效;
CC1E:0关闭——OC1禁止输出,1开启——OC1开启输出
(3)输出PWM原理
假设:递增计数模式
ARR:自动重装载寄存器的值;CCRx:捕获/比较寄存器x的值;
即,当CNT<CCRx,IO输出0;当CNT>=CCRx,IO输出1。
总结:PWM波周期或频率由ARR决定,PWM占空比由CCRx决定。
6.2 PWM输出程序配置步骤
(1)配置定时器基础工作参数:HAL_TIM_PWM_Init(),
主要寄存器:CR1、ARR、PSC,初始化定时器基础参数;
(2)定时器PWM输出MSP初始化:HAL_TIM_MspInit(),
配置NVIC、CLOCK、GPIO等;
(3)配置PWM模式/比较值等:HAL_TIM_PWM_ConfigChanel()
主要寄存器:CCMRx、CCRx、CCER,配置PWM模式、比较值、输出极性等;
(4)使能输出并启动计数器:HAL_TIM_PWM_Start()
主要寄存器:CCER、CR1,使能输出比较并启动计数器
(5)修改比较值控制占空比(可选):__HAL_TIM_SET_COMPARE()
主要寄存器:CCRx,修改比较值
(6)使能通道预装载(可选)
主要寄存器:CCER,使能通道预装载
6.3 PWM定时器输出实验
(1)确定PWM波的周期/频率Tout=(ARR+1)*(PSC+1)/Ft,2KHz为例,PSC=71,ARR=499.
(2)配置输出比较模式:PWM模式1,通道输出极性位:低电平有效。
(3)main函数编写
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/gtim.h"extern TIM_HandleTypeDef g_timx_pwm_handle; /* 定时器x句柄 */
int main(void)
{HAL_Init(); /* 初始化HAL库 */sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */delay_init(72); /* 延时初始化 */usart_init(115200); /* 串口初始化为115200 */led_init(); /* 初始化LED *//* 定时器及pwm初始化,2kHz */gtim_timx_pwm_init(500-1, 72-1);uint16_t ledrpwmval=0;uint8_t dir=1;while (1){delay_ms(5);if (dir)ledrpwmval++;else ledrpwmval--;if (ledrpwmval > 300) dir=0;if (ledrpwmval == 0) dir=1;/* 修改定时器占空比进而实现灯亮度调节->呼吸灯 */__HAL_TIM_SET_COMPARE(&g_timx_pwm_handle, TIM_CHANNEL_2, ledrpwmval); }
}
(4)gtim.c文件编写
#include "./BSP/TIMER/gtim.h"
#include "./BSP/LED/led.h"TIM_HandleTypeDef g_timx_pwm_handle; /* 定时器x句柄 *//* 通用定时器PWM输出初始化函数 */
void gtim_timx_pwm_init(uint16_t psc, uint16_t arr)
{g_timx_pwm_handle.Instance=TIM3; /* 选择定时器3 */g_timx_pwm_handle.Init.Prescaler=psc; /* 分频系数 */g_timx_pwm_handle.Init.Period=arr; /* 自动重装置值 */g_timx_pwm_handle.Init.CounterMode=TIM_COUNTERMODE_UP; /* 向上计数模式 */HAL_TIM_PWM_Init(&g_timx_pwm_handle); TIM_OC_InitTypeDef timx_oc_pwm_chy; /* 定义pwm配置句柄 */timx_oc_pwm_chy.OCMode=TIM_OCMODE_PWM1; /* 配置为PWM1模式 */timx_oc_pwm_chy.Pulse=arr / 2; /* 比较值设为arr一半->占空比50% */ timx_oc_pwm_chy.OCPolarity=TIM_OCPOLARITY_LOW; /* 输出极性设置为低 *//* PWM初始化分别输入定时器配置、pwm配置以及通道 */HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_handle, &timx_oc_pwm_chy, TIM_CHANNEL_2);/* 开启PWM */HAL_TIM_PWM_Start(&g_timx_pwm_handle, TIM_CHANNEL_2);
}/* 通用定时器PWM MSP初始化函数* 配置NVIC/CLOCK/GPIO
*/
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{if(htim->Instance==TIM3) /* 如果为定时器3 */{GPIO_InitTypeDef gpio_init_struct; /* GPIO初始化句柄 */__HAL_RCC_GPIOB_CLK_ENABLE(); /* GPIOB使能 */__HAL_RCC_TIM3_CLK_ENABLE(); /* 定时器使能 */gpio_init_struct.Pin=GPIO_PIN_5; /* 使能GPIOB5,LED0,TIM3 通道2*/gpio_init_struct.Mode=GPIO_MODE_AF_PP; /*推挽复用*/gpio_init_struct.Pull=GPIO_PULLUP; /* 上拉 */gpio_init_struct.Speed=GPIO_SPEED_FREQ_HIGH; /* 高速 */HAL_GPIO_Init(GPIOB, &gpio_init_struct);__HAL_RCC_AFIO_CLK_ENABLE(); /* 重映射时钟使能 */__HAL_AFIO_REMAP_TIM3_PARTIAL(); /* 定时器3部分重映射(通道2手册对应选项) */}
}
(5)gtim.h文件编写
#ifndef __GTIM_H
#define __GTIM_H#include "./SYSTEM/sys/sys.h"/* 通用定时器PWM输出初始化函数 */
void gtim_timx_pwm_init(uint16_t psc, uint16_t arr);/* 通用定时器PWM MSP初始化函数 */
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim);#endif