将定时器和GPIO 结合起来使用可以实现非常丰富的功能,例如测量输入信号的脉冲宽度、产生输出波形、产生PWM 控制电机状态等。
STM32F42xxx 系列控制器有 2 个高级控制定时器、10 个通用定时器和 2 个基本定时器,还有 2 个看门狗定时器。
控制器上所有定时器都是彼此独立的,不共享任何资源。
其中最大定时器时钟可通过 RCC_DCKCFGR 寄存器配置为 90MHz 或者 180MHz。
就功能上来说通用定时器包含所有基本定时器功能,而高级控制定时器包含通用定时器所有功能。
基本定时器
基本定时器主要两个功能:
- 第一就是基本定时功能,生成时基;
- 第二就是专门用于驱动数模转换器(DAC)。
基本定时器有两个,TIM6 和 TIM7,功能完全一样,但所用资源彼此都完全独立,可以同时使用。
功能框图
首先先看绿色框内容,第一个是带有阴影的方框,方框内容一般是一个寄存器名称,比如图中主体部分的自动重载寄存器(TIMx_ARR)或 PSC 预分频器(TIMx_PSC),这里要特别突出的是阴影这个标志的作用,它表示这个寄存器还自带有影子寄存器,在硬件结构上实际是有两个寄存器,源寄存器是我们可以进行读写操作,而影子寄存器我们是完全无法操作的,有内部硬件使用。影子寄存器是在程序运行时真正起到作用的,源寄存器只是给我们读写用的,只有在特定时候(特定事件发生时)才把源寄存器的值拷贝给它的影子寄存器。多个影子寄存器一起使用可以到达同步更新多个寄存器内容的目的。
图中的自动重载寄存器有影子寄存器,它左边有一个带有“U”字母的事件图标,表示在更新事件生成时就把自动重载寄存器内容拷贝到影子寄存器内。
寄存器右边的事件图标、中断和DMA 输出图标表示在自动重载寄存器值与计数器寄存器值相等时生成事件、中断和 DMA输出。
1. 时钟源
使用定时器之前都必须开启定时器时钟,基本定时器属于 APB1 总线外设。
基本定时器时钟只能来自内部时钟,高级控制定时器和通用定时器还可以选择外部时钟源或者直接来自其他定时器等待模式。
我们可以通过 RCC 专用时钟配置寄存器(RCC_DCKCFGR)的 TIMPRE 位设置所有定时器的时钟频率,我们一般设置该位为默认值 0,使得表中可选的最大定时器时钟为 90MHz,即基本定时器的内部时钟(CK_INT)频率为 90MHz。
基本定时器只能使用内部时钟,当 TIM6 和 TIM7 控制寄存器 1(TIMx_CR1)的 CEN 位置 1 时,启动基本定时器,并且预分频器的时钟来源就是 CK_INT。
2. 控制器
定时器控制器控制实现定时器功能,控制定时器复位、使能、计数是其基础功能,基本定时器还专门用于 DAC 转换触发。
3. 计数器
基本定时器计数过程主要涉及到三个寄存器内容,分别是计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR),这三个寄存器都是 16 位有效数字,即可设置值为 0 至 65535。
首先看上图中预分频器 PSC,它有一个输入时钟 CK_PSC 和一个输出时钟CK_CNT。输入时钟 CK_PSC 来源于控制器部分,基本定时器只有内部时钟源所以CK_PSC 实际等于 CK_INT,即 90MHz。在不同应用场所,经常需要不同的定时频率,通过设置预分频器 PSC 的值可以非常方便得到不同的 CK_CNT,实际计算为:fCK_CNT 等于 fCK_PSC/(PSC[15:0]+1)。
下图是将预分频器 PSC 的值从 1 改为 4 时计数器时钟变化过程。原来是 1 分频,CK_PSC 和 CK_CNT 频率相同。向 TIMx_PSC 寄存器写入新值时,并不会马上更新CK_CNT 输出频率,而是等到更新事件发生时,把 TIMx_PSC 寄存器值更新到影子寄存器中,使其真正产生效果。更新为 4 分频后,在 CK_PSC 连续出现 4 个脉冲后 CK_CNT 才产生一个脉冲。
在定时器使能(CEN 置 1)时,计数器 COUNTER 根据 CK_CNT 频率向上计数,即每来一个 CK_CNT 脉冲,TIMx_CNT 值就加 1。当 TIMx_CNT 值与 TIMx_ARR 的设定值相等时就自动生成事件并 TIMx_CNT 自动清零,然后自动重新开始计数,如此重复以上过程。为此可见,我们只要设置 CK_PSC 和 TIMx_ARR 这两个寄存器的值就可以控制事件生成的时间,而我们一般的应用程序就是在事件生成的回调函数中运行的。在 TIMx_CNT 递增至与 TIMx_ARR 值相等,我们叫做为定时器上溢。
自动重载寄存器 TIMx_ARR 用来存放于计数器值比较的数值,如果两个数值相等就生成事件,将相关事件标志位置位,生成 DMA 和中断输出。TIMx_ARR 有影子寄存器,可以通过 TIMx_CR1 寄存器的 ARPE 位控制影子寄存器功能,如果 ARPE 位置 1,影子寄存器有效,只有在事件更新时才把TIMx_ARR 值赋给影子寄存器。如果 ARPE 位为 0,修改TIMx_ARR 值马上有效。
4. 定时器周期计算
定时事件生成时间主要由 TIMx_PSC 和 TIMx_ARR 两个寄存器值决定,这个也就是定时器的周期。比如我们需要一个 1s 周期的定时器,假设先设置 TIMx_ARR 寄存器值为 9999,即当 TIMx_CNT从 0 开始计算,刚好等于 9999 时生成事件,总共计数 10000 次,那么如果此时时钟源周期为 100us 即可得到刚好 1s 的定时周期。
接下来问题就是设置 TIMx_PSC 寄存器值使得 CK_CNT 输出为 100us 周期(10000Hz)的时钟。预分频器的输入时钟 CK_PSC 为 90MHz,所以设置预分频器值为(9000-1)即可满足。
初始化结构体
-
TIM_Prescaler:定时器预分频器设置。时钟源经该预分频器才是定时器时钟,它设定TIMx_PSC 寄存器的值。可设置范围为 0 至 65535,实现 1 至 65536 分频。
-
TIM_CounterMode:定时器计数方式。可是在为向上计数、向下计数以及三种中心对齐模式。基本定时器只能是向上计数,即 TIMx_CNT 只能从 0 开始递增,并且无需初始化。
-
TIM_Period:定时器周期。实际就是设定自动重载寄存器的值,在事件生成时更新到影子寄存器。可设置范围为 0 至 65535。
-
TIM_ClockDivision:时钟分频。设置定时器时钟 CK_INT 频率与数字滤波器采样时钟频率分频比,基本定时器没有此功能,不用设置。
-
TIM_RepetitionCounter:重复计数器。属于高级控制寄存器专用寄存器位,利用它可以非常容易控制输出 PWM 的个数。这里不用设置。
基本定时器编程要点:
- 开启基本定时器时钟;
- 设置定时器周期和预分频器;
- 启动定时器中断,并开启定时器;
- 编写定时器中断服务函数。
bsp_tim.h
/**
******************************************************************************
* @file bsp_tim.h
* @author Waao
* @version V1.0.0
* @date 29-Jan-2019
* @brief This file contains some board support package's definition for the TIM.
*
******************************************************************************
* @attention
*
* None
*
******************************************************************************
*/
#ifndef __BSP_TIM_H_
#define __BSP_TIM_H_
#include <stm32f4xx_tim.h>
//================ TIM ==================
#define TIM_ TIM6
#define TIM_CLK RCC_APB1Periph_TIM6
//============== Interrupt ==============
#define TIM_Interrupt TIM6_DAC_IRQn
void TIM_Config(void);
#endif
bsp_tim.c
输入时钟频率为90MHz,预分频器设置为9000,周期设置为5000,故触发一次需时钟跳动45M次,即半个时钟周期,即0.5s
/**
******************************************************************************
* @file bsp_tim.c
* @author Waao
* @version V1.0.0
* @date 29-Jan-2019
* @brief This file contains some board support package's functions for the TIM.
*
******************************************************************************
* @attention
*
* None
*
******************************************************************************
*/
#include <bsp_tim.h>
/**
* @brief Initialize the TIM.
* @param None
* @retval None
*/
void TIM_Config(void)
{
TIM_TimeBaseInitTypeDef BASE_TIM_InitStructure;
RCC_APB1PeriphClockCmd(TIM_CLK, ENABLE);
BASE_TIM_InitStructure.TIM_Prescaler = 9000-1;
BASE_TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
BASE_TIM_InitStructure.TIM_Period = 5000-1;
TIM_TimeBaseInit(TIM_, &BASE_TIM_InitStructure);
TIM_ClearFlag(TIM_, TIM_FLAG_Update);
TIM_ITConfig(TIM_, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM_, ENABLE);
}
stm32f4xx_it.c
经检验,每经过0.5s,该中断会连续触发两次,但只有一次是TIM 更新中断成功置位的,故在中断函数中,需判断是否触发TIM 更新中断,否则中断函数代码将被执行两次
/**
* @brief This function handles TIM6_DAC_IRQHandler.
* @param None
* @retval None
*/
void TIM6_DAC_IRQHandler(void)
{
timer++;
if(TIM_GetITStatus(TIM_, TIM_IT_Update) == SET)
{
LED1_TOGGLE;
TIM_ClearITPendingBit(TIM_, TIM_IT_Update);
printf("\n%d", timer);
}
}