文章目录
一、通用定时器工作原理
1.1 时钟源
- 内部时钟
这个和基本定时器一样,可回顾基本定时器这部分的内容。 - 外部时钟模式1(TI1、TI2)
在外部时钟模式1中,时钟信号由外部来源提供。该模式下,时钟信号经过一下路径输入到定时器:外部时钟信号→IO→TIMx_CH1(或TIMx_CH2)。需强调的是,仅TIMx_CH1和TIMx_CH2通道可用于时钟信号的输入,而TIMx_CH3和TIMx_CH4则不支持此功能。为了实现IO与定时器通道的链接,必须设置IO的复用功能。
如上图所示,当使用通道2(CH2)作为时钟源时,该时钟源信号被标记为TI2,代表其在定时器内部的标识。TI2信号首先通过一个可通过ICF[3:0]设置的滤波器,此滤波器也可以被配置为不启用。随后,信号进入边沿检测器,其检测边沿的类型(上升或下降)由CC2P位决定。之后,信号通过触发输入选择器,该选择器使用TS[4:0]位来选定触发输入信号(TRGI)的来源。如上图所示,三种触发输入信号包括TI1F_ED(来自CH1的双边沿信号,未经边沿检测器处理)、TI1FP1(来自CH1并经过边沿检测器的信号)、TI2FP2(来自CH2并经过边沿检测器的信号)。以CH2为例,选择TI2FP2作为信号源。若以CH1为例,则可选择TI1F_ED或TI1FP1。最终,通过模式选择器(由ECE位和SMS[2:0]位设置)选择定时器的时钟源。在外部时钟模式1中,将ECE设置为0,SMS[2:0]设为111,从而允许CK_PSC信号经预分频后到达计数器进行计数。 - 外部时钟模式2(ETR)
此模式下,时钟源进入定时器的流程如下:外部时钟源信号→IO→TIMx_ETR。
在外部时钟模式2中,如上图所示,定时器的时钟信号起始于ETR引脚。信号首先通过外部触发极性选择器,该选择器使用ETP位决定时上升沿有效还是下降沿有效。若选择下降沿有效,则信号将被反相。随后,信号进入外部触发预分频器,预分频器的系数由ETPS[1:0]位设置,可选的系数包括1、2、4、8。接下来,信号可能会通过一个滤波器,其滤波模式由ETF[3:0]位决定,滤波器可以被配置为不启用。fDTS的频率由TIMx_CR1寄存器的CKD位确定。最终,通过模式选择器(由ECE位和SMS[2:0]位设置)确定定时器的时钟源。在外部时钟模式2下,将ECE直接设置为1。之后,CK_PSC信号通过定时器的预分频器后,到达计数器进行计数。 - 内部触发输入(ITRx)
内部触发输入是使用一个定时器作为另一个定时器的预分频器,即实现定时器的级联。
TIM1作为TIM2的预分频器,需完成如下配置:
1、TIM1_CR2寄存器的MMS[2:0]位设置为010,即TIM1的主模式选择为更新(选择更新事件作为触发输出(TRGO))。
2、TIM2_SMCR寄存器的TS[2:0]位设置为000,即使用ITR1作为内部触发。TS[2:0]位用于配置触发选择。
3、TIM2_SMCR寄存器的SMS[2:0]位设置为111,即从模式控制器选择外部时钟模式1.
4、TIM1和TIM2的GEN位都要置1,即启动计数器。
1.2 控制器
控制器包括:从模式控制器、编码器接口和触发控制器(TRGO)。从模式控制器可以控制计数器复位、启动、递增/递减、计数。编码器接口针对编码器计数。触发控制器用来提供触发信号给别的外设,比如为其它定时器提供时钟或者DAC/ADC的触发转换信号。
1.3 时基单元
时基单元包括:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR)。通用定时器的计数模式有三种:递增计数模式、递减计数模式和中心对齐模式,这部分内容在基本定时器中已经讲到,可回顾基本定时器中的内容。
1.4 输入捕获
输入捕获一般要和第⑤部分一起完成测量功能。IO端口通过复用功能与定时器的通道相连。将需要测量的信号输入到相应的IO端口,输入捕获可以对输入信号的上升沿,下降沿或者双边沿进行捕获。
1.5 输入捕获和输出比较公用部分
该部分需要结合第④部分或者第⑥部分共同完成相应功能。
1.6 输出比较
一般应用是要和第⑤部分一起完成定时器输出功能。
程序员首先需设定CCR1寄存器的比较值。此比较值需传递至相应的捕获/比较影子寄存器,方能有效执行。根据上图展示的逻辑,比较值得转移需满足三个条件:CCR1非写入阶段、CC1S[1:0]设置为0以配置为输出模式、以及OC1PE位为0或者OC1PE位为1且发生更新事件(该事件可通过软件或硬件触发)。
一旦CCR1寄存器的值成功转移到影子寄存器,该新值便会与定时器计数器的值进行比较。比较结果将在第⑥部分中影响定时器的输出行为。
输出参考信号oc1ref,当为高电平时有效,并在三个因素的影响下变化:由OC1M[3:0]设定的输出比较模式、比较器的比较结果、以及OC1CE位控制的外部ETRF信号。外部的ETRF信号能够强制将oc1ref的电平置为0。通常,计数器值与捕获/比较寄存器值匹配时,oc1ref的电平会依据设置的输出比较模式而变化,这可能触发比较中断(如果已启用)。CC1P位负责设置通道输出的极性,而设置CC1E则激活通道输出。随后,OC1信号通过TIMx_CH1通道输出至IO端,最终传递到外部。
二、基本定时器工作原理
STM32F103有两个基本定时器TIM6和TIM7,它们的功能完全相同,资源是完全独立的,可以同时使用。其主要特性如下:16位自动重载递增计数器,16位可编程预分频器,预分频系数1~65536,用于对计数器时钟进行分频,还可以触发DAC的同步电路,以及生成中断/DMA请求。
2.1 通用定时器输出PWM
脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制。是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的计数。我们可以让定时器产生PWM,在计数器频率固定时,PWM频率或者周期由自动重载寄存器(TIMx_ARR)的值决定,其占空比由捕获/比较寄存器(TIMx_CCRx)的值决定。PWM产生原理示意图如下。
定时器工作在递增计数模式,纵轴是计数器的计数值CNT,横轴表示时间。当CNT<CCRx时,IO输出低电平;当CNT≥CCRx时,IO输出高电平;当CNT=ARR时,定时器溢出,CNT的值被清零,然后继续递减,依次循环。在这个循环中,改变CCRx的值,就可以改变PWM的占空比,改变ARR的值,就可以改变PWM的频率。
定时器产生PWM的方式有许多种,下面我们以边沿对齐模式(即递增计数模式/递减计数模式)为例,PWM模式1或者PWM模式2产生PWM的示意图,如下图所示。
2.2 通用定时器输出PWM配置步骤
- 配置定时器基础工作参数:HAL_TIM_PWM_Init()
- 定时器PWM输出MSP初始化:HAL_TIM_PWM_MspInit()
- 配置PWM模式/比较值等:HAL_TIM_PWM_ConfigChannel()
- 使能输出并启动计数器:HAL_TIM_PWM_Start()
- 修改比较值控制占空比(可选):_HAL_TIM_SET_CMOPARE()
- 使能通道预装载值(可选):_HAL_TIM_ENABLE_OCxPRELOAD()
三、程序示例
头文件定义如下:
#include "./SYSTEM/sys/sys.h"
/* TIMX PWM输出定义
* 这里输出的PWM控制LED0(RED)的亮度
* 默认是针对TIM2~TIM5
* 注意: 通过修改这几个宏定义,可以支持TIM1~TIM8任意一个定时器,任意一个IO口输出PWM
*/
#define GTIM_TIMX_PWM_CHY_GPIO_PORT GPIOB
#define GTIM_TIMX_PWM_CHY_GPIO_PIN GPIO_PIN_5
#define GTIM_TIMX_PWM_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
/* TIMX REMAP设置
* 因为我们LED0接在PB5上, 必须通过开启TIM3的部分重映射功能, 才能将TIM3_CH2输出到PB5上
* 因此, 必须实现GTIM_TIMX_PWM_CHY_GPIO_REMAP
* 对那些使用默认设置的定时器PWM输出脚, 不用设置重映射, 是不需要该函数的!
*/
#define GTIM_TIMX_PWM_CHY_GPIO_REMAP() do{__HAL_RCC_AFIO_CLK_ENABLE();\
__HAL_AFIO_REMAP_TIM3_PARTIAL();\
}while(0) /* 通道REMAP设置, 该函数不是必须的, 根据需要实现 */
#define GTIM_TIMX_PWM TIM3
#define GTIM_TIMX_PWM_CHY TIM_CHANNEL_2 /* 通道Y, 1<= Y <=4 */
#define GTIM_TIMX_PWM_CHY_CCRX TIM3->CCR2 /* 通道Y的输出比较寄存器 */
#define GTIM_TIMX_PWM_CHY_CLK_ENABLE() do{ __HAL_RCC_TIM3_CLK_ENABLE(); }while(0) /* TIM3 时钟使能 */
void gtim_timx_int_init(uint16_t arr, uint16_t psc); /* 通用定时器 定时中断初始化函数 */
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc); /* 通用定时器 PWM初始化函数 */
定时器源文件代码如下:
#include "./BSP/TIMER/gtim.h"
#include "./BSP/LED/led.h"
TIM_HandleTypeDef g_timx_pwm_chy_handle; /* 定时器x句柄 */
/**
* @brief 通用定时器TIMX 通道Y PWM输出 初始化函数(使用PWM模式1)
* @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_pwm_chy_init(uint16_t arr, uint16_t psc)
{
TIM_OC_InitTypeDef timx_oc_pwm_chy = {0}; /* 定时器PWM输出配置 */
g_timx_pwm_chy_handle.Instance = GTIM_TIMX_PWM; /* 定时器x */
g_timx_pwm_chy_handle.Init.Prescaler = psc; /* 定时器分频 */
g_timx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_pwm_chy_handle.Init.Period = arr; /* 自动重装载值 */
HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle); /* 初始化PWM */
timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1; /* 模式选择PWM1 */
timx_oc_pwm_chy.Pulse = arr / 2; /* 设置比较值,此值用来确定占空比 */
/* 默认比较值为自动重装载值的一半,即占空比为50% */
timx_oc_pwm_chy.OCPolarity = TIM_OCPOLARITY_LOW; /* 输出比较极性为低 */
HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle, &timx_oc_pwm_chy, GTIM_TIMX_PWM_CHY); /* 配置TIMx通道y */
HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, GTIM_TIMX_PWM_CHY); /* 开启对应PWM通道 */
}
/**
* @brief 定时器底层驱动,时钟使能,引脚配置
此函数会被HAL_TIM_PWM_Init()调用
* @param htim:定时器句柄
* @retval 无
*/
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == GTIM_TIMX_PWM)
{
GPIO_InitTypeDef gpio_init_struct;
GTIM_TIMX_PWM_CHY_GPIO_CLK_ENABLE(); /* 开启通道y的CPIO时钟 */
GTIM_TIMX_PWM_CHY_CLK_ENABLE();
gpio_init_struct.Pin = GTIM_TIMX_PWM_CHY_GPIO_PIN; /* 通道y的CPIO口 */
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(GTIM_TIMX_PWM_CHY_GPIO_PORT, &gpio_init_struct);
GTIM_TIMX_PWM_CHY_GPIO_REMAP(); /* IO口REMAP设置, 是否必要查看头文件配置的说明 */
}
}
主函数代码如下:
#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_chy_handle; /* 定时器x句柄 */
int main(void)
{
uint16_t ledrpwmval = 0;
uint8_t dir = 1;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
gtim_timx_pwm_chy_init(500 - 1, 72 - 1);/* 1Mhz的计数频率,2Khz的PWM. */
while (1)
{
delay_ms(10);
if (dir)ledrpwmval++; /* dir==1 ledrpwmval递增 */
else ledrpwmval--; /* dir==0 ledrpwmval递减 */
if (ledrpwmval > 300)dir = 0; /* ledrpwmval到达300后,方向为递减 */
if (ledrpwmval == 0)dir = 1; /* ledrpwmval递减到0后,方向改为递增 */
/* 修改比较值控制占空比 */
__HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle, GTIM_TIMX_PWM_CHY, ledrpwmval);
}
}
整体代码流程如下图所示。