STM32单片机入门之定时器理论篇

        定时器,实际上就是中断功能的延伸,上一篇我们介绍了外部中断,不过这个外部中断触发时间是不可以被设置的,而定时器的中断确是可以被设置的,也就是说,定时器的本质功能就是可以设置一个固定时间,时间到了就自动触发中断,这比起外部中断来说,是不是更加“智能”了一点呢?

        定时器分别为基本定时器,通用定时器,高级定时器。定时器是向下兼容的,也就是说,基本定时器和通用定时器拥有的功能高级定时器都有,高级定时器还具备一些基本定时器和通用定时器没有的功能。

        本篇文章主要学习通用定时器的定时中断和内外时钟源选择部分。

 

  1. 这里基本定时器(TIM6、TIM7)是最基础的,功能只包括定时中断,主模式触发ADC功能,该类定时器是挂在APB1总线上的。
  2. 通用定时器(TIM2、TIM3、TIM4、TIM5)包括定时中断,主模式触发DAC功能,内外时钟源选择功能,输出比较功能,输入捕获功能,编码器接口,主从模式触发功能,该类定时器是挂在APB1总线上的。
  3. 高级定时器(TIM1、TIM8)拥有通用定时器的所用功能,除此之外,还有重复计数器,死区生成,互补输出,刹车输入等功能,该类定时器是挂在APB2总线上的。

 

        首先学习基本定时器的框架图。

706ca2ece8ec4b48b75f462991347c39.jpg

         定时器最基本的电路模块就是时基单元了,其中,时基单元模块包括预分频器,计数器,自动重装载寄存器。当时钟源信号接到基本定时器后,首先会通过预分频器来选择分配系数,然后计数器再根据分频后的频率进行计数,CNT计数器从0开始计数,一直计到65535后,再清零。最后我们设置的重装载值(ARR)会被写入到自动重装载寄存器中,CNT计数器会不断与ARR进行比较,如果一致,CNT计数器就会清零,同时产生一个更新中断或者更新事件,这就是时基单元的工作流程,也就是定时中断功能。而基本定时器还能通过将主模式更新事件映射到TRGO上,使DAC不断连续产生下一个电压点,实现了硬件电路的自动化,避免了在主程序中不断使用软件触发中断,占用大量CPU资源。


        接下来学习通用定时器

20ffc97e447349b0a4786ed300907827.jpg

         通用定时器除了最基本时基单元外,还多了内外时钟源选择,输出比较,输入捕获模块,编码器接口模块,不过值得一说,通用定时器的时基单元的计数器可以选择向上计数,向下计数,中央对齐计数模式。

        这里我们先来细说内外时钟源选择部分吧~我们都知道,系统内部的时钟频率为72MHz,基本定时器用的时钟频率就是内部时钟频率,不可以选择,但是通用定时器除了选择内部时钟,还可以通过定时器的ETR引脚来提供外部时钟,像TIM2的ETR引脚在引脚定义表中可以找到,这里的ETR引脚就是PA0口。我们只需要在这个引脚接一个外部时钟源,就可以外部提供时钟了。ETR提供的时钟还可以选择极性,边缘检测,分频选择,最后经过滤波,滤波后的时钟信号就可以接入时基单元作为时钟源了。6a1b090b661549dc8b192838a372c2f7.jpg

 

        这里ETR引脚时钟信号经过滤波后,有两路输出(图中红色箭头部分),我们可以选择走触发控制器这一路(外部时钟模式2),也可以选择走TRGI这一路,这路主要是用做触发输入来使用的(外部时钟模式1),触发输入可以触发定时器的从模式,走这路与模式2只有一点不同,那就是模式1占用了触发输入的通道。

c490ac29b6714f55844335881d46ed64.jpg

         接下来还可以选择ITRX作为时钟源,这部分的定时信号来自于其它定时器,这里主要就是定时器的级联功能了,在主模式下,定时器的TRGO可以通向其它定时器,这里就可以接到ITRX引脚上,这里的ITR0-3分别来自其他定时器的TRGO。定时器级联表如下。52cdc5bb38664094b07c399faede6256.jpg

         假设这里我们要实现定时器2与定时器3的级联,就可以先初始化定时器3,使用主模式,把定时器3的更新事件映射到TRGO上,然后再初始化定时器2,选择定时器2的ITR2引脚作为外部时钟源,最后再选择模式为外部模式1,这样定时器3的更新事件就可以驱动定时器2的时基单元了,实现了定时器2与定时器3的级联。c9e531b5d4b948eb952326d059cb82f0.jpg

         接下来还有选择定时器CH1通道(TI1F_ED)作为时钟源的,经过输入捕获通道,这路的上升沿与下降沿均有效。f05dc99a300f496da4825a34495ddee7.jpg

         最后就是可以听通过CH1与CH2引脚获得时钟,CH1的TI1FP1与CH2的TI2FP2也可以作为时钟信号源驱动时基单元。

679bf03baddd4144a78ed62b5c9c5102.jpg

         这些走TRGI通道的模式统称为外部时钟模式1,当要用以上介绍的这些引脚信号源作为时钟源时,记得将模式设置为外部时钟模式1。

        在最后还有一点,与基本定时器一样,这里的主模式还可以将更新事件映射到TRGO引脚上,用于硬件自动化触发DAC功能。


        接下来我们就来学习一下预分频器参数由1变到2时,计数器的时序图。37d94bf7a6e1481495a1d66927fd99cc.jpg

         首先计数器使能(高电平工作),这里预分频器的时钟信号(CK_PSC)经过分频后,与定时器的时钟信号成倍数关系,经过1分频(给寄存器写0),计数器时钟无变化,经过2分频(给寄存器写1),计数器时钟频率变为预分频器时钟频率的1/2倍,当计数记到与ARR值相等时,产生一个更新。当预分频器参数由0变为1时,预分频器控制寄存器也由0变为1,但是预分频缓冲器不会立马由1变为2,而是上一个更新事件产生了之后,才会变为2,当预分频缓冲器参数变为1时,预分频计数器就会010101循环计数,每当计到0时,产生一个脉冲信号,实现分频。也就是说只有这里的预分频缓冲器参数变为1后,计数器时钟才会2分频。可以看出,这里实际产生作用的寄存器是缓冲寄存器,也叫影子寄存器。

5382ca4cfb1446cb9f404eef486fb614.jpg

         再来看计数器溢出时序图,经过分频后,计数器时钟频率驱动计数器寄存器计数,当计数器寄存器计到ARR值时,计数器就会溢出,就会产生一个更新事件。这个更新事件产生的频率就是计数器溢出频率,也就是我们定的时间。通过设置分频参数与ARR参数,可以改变我们定时的时间。

        在这里我们还可以通过设置ARPE位来选择自动重装值寄存器是否使用影子寄存器,置1为使用,如果使用了影子寄存器,那么在我们修改后不会立马生效,而是产生了更新事件后才会s生效,置0为不使用影子寄存器,不使用影子寄存器的话,当我们修改ARR的值时,就会立马生效。


        接下来重点学习定时中断以及内外时钟源选择的代码部分。 

        第一个功能是用定时器的定时中断来实现自动记时功能,并将时间显示在OLED屏幕上。

        我们根据以下框图来配置我们需要的部分,打通这条线路,就可以实现功能了。

b71b493cfc124b0ea7210ea4010ba544.jpeg

  1. 第一步是打开RCC定时器的时钟
  2. 下一步是选择时钟源,这里我们使用内部时钟源
  3. 接下来配置时基单元的参数
  4. 然后允许以下中断输出控制,最后在NVIC中打开定时器的通道,配置优先级
  5. 在最后,要使能计数器。
  6. 在中断函数中写我们需要实现的功能。

        总共6步即可实现内部cpu自动计时。

uint16_t Num;/*声明Num变量*/
void Timer_Init(void)
{
        RCC_APB1PeriphClockCmd(RCC_APB1periph_TIM2,ENALE);/*打开定时器2的时钟*/
        TIM_InternalClockConfig(TIM2);/*选择定时器2的时钟源为内部时钟,72MKZ*/

        TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;/*选择1分频*/
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_UP;/*选择向上计数模式*/
TIM_TimeBaseInitStructure.TIM_Period=10000-1;/*设置ARR值*/
TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1;/*设置PSC预分频值*/
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;/*没有用到高级定时器的重复计数器,置0*/
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);/*初始化时基单元*/

TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);/*使能更新中断*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);/*选择分组2*/

NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;/*打开定时器2的通道*/
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;/*使能通道*/
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;/*设置抢占优先级*/
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;/*设置响应优先级*/
NVIC_Init(&NVIC_InitStructure);

TIM_Cmd(TIM2,ENABLE);/*使能定时器2*/
}

void TIM2_IRQHandler(void)
{
        if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET);/*判断TIM2的更新中断位是否置1*/
        {
            Num++;
  TIM_ClearITPendingBit(TIM2,TIM_IT_Update);/*清除定时器2的更新中断标志位,防止卡死在中断中*/     
        }
}

        最后再利用OLED屏幕显示一下Num的值即可。在OLED屏幕上,我们可以观察到Num值每1秒就加1,这是因为计数器溢出频率为1HZ,时间为1s,这就意味着每1s就会产生一个更新中断,执行中断函数,在函数中实现每一秒Num就加1。

        最后在主函数中调用一下驱动函数,在while循环中利用OLED刷新显示Num即可。


        接下来就是选择外部时钟作为时钟源了,这里我们利用对射式红外传感器来模拟外部时钟源,实现每遮挡一次,计数器加1,每加到10,Num变量加1的功能。

        这里需要改的配置有初始化GPIOA的PA0引脚,因为TIM2的ETR引脚复用在了PA0口,然后选择时钟源这里要改为TIM2的外部时钟模式2。最后再把预分频值和ARR的值改小,因为手动模拟的时钟源速度很慢。再最后利用OLED显示屏显示一下CNT与Num的值即可。


	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0X0f);
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimeBaseInitStruct.TIM_CounterMode= TIM_CounterMode_Up;
	TIM_TimeBaseInitStruct.TIM_Period=10-1 ;
	TIM_TimeBaseInitStruct.TIM_Prescaler= 1-1;
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
	TIM_ClearFlag(TIM2,TIM_FLAG_Update);
	
	TIM_ITConfig(TIM2,TIM_IT_Update, ENABLE);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn ;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	NVIC_Init(&NVIC_InitStructure);
	
	
	TIM_Cmd(TIM2,ENABLE);

        以上就是关于定时器部分的定时中断以及内外时钟源选择代码部分,下一篇内容将继续探讨定时器的输出比较模块,利用定时器输出比较可以输出PWM波型,实现呼吸灯以及驱动电机。

图均源自网络,如有侵权请联系本人

 

STM32的1S #include "stm32f10x_it.h" /** @addtogroup STM32F10x_StdPeriph_Template * @{ */ /* Private typedef -----------------------------------------------------------*/ /* Private define ------------------------------------------------------------*/ /* Private macro -------------------------------------------------------------*/ /* Private variables ---------------------------------------------------------*/ extern u32 SystickCounter; extern u8 KeySwitch_Press; extern u8 KeyAdjust_Press; #define TRUE 1 #define FALSE 0 /* Private function prototypes -----------------------------------------------*/ /* Private functions ---------------------------------------------------------*/ /******************************************************************************/ /* Cortex-M3 Processor Exceptions Handlers */ /******************************************************************************/ /** * @brief This function handles NMI exception. * @param None * @retval None */ void NMI_Handler(void) { } /** * @brief This function handles Hard Fault exception. * @param None * @retval None */ void HardFault_Handler(void) { /* Go to infinite loop when Hard Fault exception occurs */ while (1) { } } /** * @brief This function handles Memory Manage exception. * @param None * @retval None */ void MemManage_Handler(void) { /* Go to infinite loop when Memory Manage exception occurs */ while (1) { } } /** * @brief This function handles Bus Fault exception. * @param None * @retval None */ void BusFault_Handler(void) { /* Go to infinite loop when Bus Fault exception occurs */ while (1) { } } /** * @brief This function handles Usage Fault exception. * @param None * @retval None */ void UsageFault_Handler(void) { /* Go to infinite loop when Usage Fault exception occurs */ while (1) { } } /** * @brief This function handles SVCall exception. * @param None * @ret
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值