1.前言
上一篇文章介绍了Systick嘀嗒定时器控制LED的闪烁,这篇文章我们来介绍一下STM32单片机中的通用定时器的部分,使用定时器来控制两个LED灯的交替闪烁。同样地,我们所使用的硬件设备仍然是STM32RCT6单片机等基础设备,关于单片机的原理图以及LED灯的引脚在前文均已详细介绍,这里我们就不再赘述。
2.定时器详解
什么是定时器
定时器顾名思义就是可以用来定时的,我们可以设置想要的定时时间,然后去做很多事情。STM32的定时器功能很强大,可以用来定时、计数、PWM产生、输入捕获以及定时器中断等,它是存在于STM32单片机中的一个外设。STM32总共有8个定时器,分别是2个高级定时器(TIM1、TIM8),4个通用定时器(TIM2、TIM3、TIM4、TIM5)和2个基本定时器(TIM5、TIM6)。
定时器的时钟详解
先是8MHz的信号经过PLL倍频后送入系统时钟SYSCLK,SYSCLK经过AHB预分频后分别送至APB1和APB2定时器的时钟来自APB1和APB2的的倍频信号,最后经过APB1和APB2的预分频后送入通用定时器时钟。其框图如下所示:
通用定时器的工作原理
通用原理的工作框图如下所示,这个图包含了定时器的工作方式和工作原理。
通用定时器具有向上计数、向下计数、向上向下双向计数模式。
①向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
②向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件。
③中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。
这里我们主要使用向上计数模式,定时器时间有以下计算公式
定时器频率 =((1+TIM_Prescaler )/72M)*(1+TIM_Period )• 例:如果想要设置定时器频率为 1 秒,可以设置TIM_Prescaler=35999,TIM_Period=1999
向上计数模式的具体流程如下图所示:
3.软件部分
定时器初始化
定时器初始化使用的函数为:
TIM2_Configuration(void);
让我们看看这个函数的具体内容:
void TIM2_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_DeInit(TIM2);
TIM_TimeBaseStructure.TIM_Prescaler = 7199;
TIM_TimeBaseStructure.TIM_Period = 9;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM2, ENABLE);
}
这个函数首先使能了与TIM2相关的APB1时钟,再设置预分频系数和周期分别为7199和9,根据上述公式,那么定时器的时间为:
T=(7199+1)*(9+1)/72000000=0.001s=1ms
再是一个TIM_TimeBaseInit()语句,让我们来看看这个函数的具体内容
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct)
{
uint16_t tmpcr1 = 0;
/* Check the parameters */
assert_param(IS_TIM_ALL_PERIPH(TIMx));
assert_param(IS_TIM_COUNTER_MODE(TIM_TimeBaseInitStruct->TIM_CounterMode));
assert_param(IS_TIM_CKD_DIV(TIM_TimeBaseInitStruct->TIM_ClockDivision));
tmpcr1 = TIMx->CR1;
if((TIMx == TIM1) || (TIMx == TIM8)|| (TIMx == TIM2) || (TIMx == TIM3)||
(TIMx == TIM4) || (TIMx == TIM5))
{
/* Select the Counter Mode */
tmpcr1 &= (uint16_t)(~((uint16_t)(TIM_CR1_DIR | TIM_CR1_CMS)));
tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_CounterMode;
}
if((TIMx != TIM6) && (TIMx != TIM7))
{
/* Set the clock division */
tmpcr1 &= (uint16_t)(~((uint16_t)TIM_CR1_CKD));
tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_ClockDivision;
}
TIMx->CR1 = tmpcr1;
/* Set the Autoreload value */
TIMx->ARR = TIM_TimeBaseInitStruct->TIM_Period ;
/* Set the Prescaler value */
TIMx->PSC = TIM_TimeBaseInitStruct->TIM_Prescaler;
if ((TIMx == TIM1) || (TIMx == TIM8)|| (TIMx == TIM15)|| (TIMx == TIM16) || (TIMx == TIM17))
{
/* Set the Repetition Counter value */
TIMx->RCR = TIM_TimeBaseInitStruct->TIM_RepetitionCounter;
}
/* Generate an update event to reload the Prescaler and the Repetition counter
values immediately */
TIMx->EGR = TIM_PSCReloadMode_Immediate;
}
这个函数主要是初始化定时器参数,设置自动重装值,分频系数,计数方式等,第一个参数是确定是哪个定时器,第二个参数是定时器初始化参数结构体指针。这个结构体一共有 5 个成员变量,对于通用定时器只有前面四个参数有用,下列为各个参数的作用
第一个参数 TIM_Prescaler 是用来设置分频系数的
第二个参数 TIM_CounterMode 是用来设置计数方式第三个参数TIM_Period是设置自动重载计数周期值
第四个参数TIM_ClockDivision是用来设置时钟分频因子
定时器的中断发生
说到定时器中断,那么就一定与NVIC相关,因此这里用到一个函数为
NVIC_Configuration();
同样地,让我们看看这个函数的具体内容。
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
关于NVIC的内容在前面部分已经介绍,这里将不再赘述,唯一的不同就是将这里的NVIC中断通道改成了TIM2_IRQn,其余与之前的类似。
然后我们这里使用了一个函数来定义定时器中断服务函数。
void TIM2_IRQHandler(void);
这个函数的具体内容如下:
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
timer_count++;
}
}
这里主要是判断定时器中断是否发生,中断发生后进行相应的服务函数,并清除中断发生的标志位,这里我们是控制定时变量timer_count进行累加,来进行相应的定时判断。
4.完整代码
Main.c
#include "stm32f10x.h"
#include "bsp/led/bsp_led.h"
#include "bsp/GeneralTIM/bsp_GeneralTIM.h"
__IO uint16_t timer_count=0;
int main(void)
{
LED_GPIO_Init();
TIM2_Configuration();
NVIC_Configuration();
while (1)
{
if(timer_count==500)
{
LED1_ON;
LED2_OFF;
}
else if(timer_count==1000)
{
LED1_OFF;
LED2_ON;
timer_count = 0;
}
}
}
Main.c中主要包括LED灯、定时器和NVIC的初始化,同时在while(1)中通过判断timer_count的值进行计时的判定,我们之前说过定时器中断1ms发生一次,对应的timer_count累加一次,这里分别在500和1000处进行判定并清除计时标志,这样就实现了500毫秒一次的LED电平翻转功能。
GeneralTIM.c
#include "bsp/GeneralTIM/bsp_GeneralTIM.h"
extern uint16_t timer_count;
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void TIM2_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_DeInit(TIM2);
TIM_TimeBaseStructure.TIM_Prescaler = 7199;
TIM_TimeBaseStructure.TIM_Period = 9;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM2, ENABLE);
}
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
timer_count++;
}
}
这个文件中主要就是上述所说的定时器初始化、NVIC初始化以及中断服务函数的编写,均已在前文介绍。
5.实验效果
定时器控制LED灯闪烁
6.结束语
本次实验讲解了使用Stm32的定时器控制LED灯的交替闪烁,这也是STM32单片机核心的一部分内容,希望以上内容对各位朋友有所帮助。
最后,欢迎大家在评论区留言批评指正。谢谢!