实验四 TIMER—精准延时

一.实验目标

1.编程使用STM32F407的定时器来实现精准延时;

2.通过本实验掌握TIMER使用。

二.知识储备及设计思路

TIMER简介

定时器(Timer)最基本的功能就是定时了,比如定时发送USART
数据、定时采集AD数据等等。如果把定时器与GPIO
结合起来使用的话可以实现非常丰富的功能,可以测量输入信号的脉冲宽度,可以生产输出波形。定时器生成PWM控制电机状态是工业控制普遍方法。

STM32F42xxx 系列控制器有2 个高级控制定时器、10 个通用定时器和2
个基本定时器,还有2
个看门狗定时器。看门狗定时器不在本章讨论范围,有专门讲解的章节。控制器上所有定时器都是彼此独立的,不共享任何资源。

定时器类 型 Timer 计数器 分辨率 计数器类 型 预分频系数 DMA请 求生成 捕获/ 比较 通道 互补 输出 最大接口时 钟(MHz) 最大定时器 时钟(MHz)
高级控制 TIM1 和 TIM8 16 位 递增、递 减、递增/ 递减 1~65536(整数) 4 84 (APB2) 168
通用 TIM2, TIM5 递增、递 减、递增/ 递减 1~65536(整数) 4 42 (APB1) 84/168
TIM3, TIM4 16 位 递增、递 减、递增/ 递减 1~65536(整数) 4 42 (APB1) 84/168
TIM9 16 位 递增 1~65536(整数) 2 84 (APB2) 168
TIM10, TIM11 16 位 递增 1~65536(整数) 1 84 (APB2) 168
TIM12 16 位 递增 1~65536(整数) 2 42 (APB1) 84/168
TIM13, TIM14 16 位 递增 1~65536(整数) 1 42 (APB1) 84/168
基本 TIM6 和 TIM7 16 位 递增 1~65536(整数) 0 42 (APB1) 84/168

本节中我们使用的是TIM2,我们就介绍TIM2定时器。通用定时器框图如图:

由于STM32F407通用定时器比较复杂,这里我们不再多介绍,请大家直接参考《STM32F4xx中文参考手册》第392页,通用定时器一章。下面我们介绍一下与我们这章的实验密切相关的几个通用定时器的寄存器(以下均以TIM2~TIM5的寄存器介绍,TIM9~TIM14的略有区别,具体请看《STM32F4xx中文参考手册》对应章节)。

TIM2 到 TIM5 主要特性

● 16 位(TIM3 和 TIM4)或 32 位(TIM2 和 TIM5)
递增、递减和递增/递减自动重载计数器。

● 16 位可编程预分频器,用于对计数器时钟频率进行分频
(即运行时修改),分频系数介于 1 到 65536 之间。

● 多达 4 个独立通道,可用于:

— 输入捕获

— 输出比较

— PWM 生成(边沿和中心对齐模式)

— 单脉冲模式输出

● 使用外部信号控制定时器且可实现多个定时器互连的同步电路。

● 发生如下事件时生成中断/DMA 请求:

— 更新:计数器上溢/下溢、计数器初始化(通过软件或内部/外部触发)

— 触发事件(计数器启动、停止、初始化或通过内部/外部触发计数)

— 输入捕获

— 输出比较

● 支持定位用增量(正交)编码器和霍尔传感器电路

● 外部时钟触发输入或逐周期电流管理

TIM通用寄存器简介

TIMx 控制寄存器 1 (TIMx_CR1),寄存器的各位描述如图所示:

本节中只需配置CEN使能位1。

TIMx 预分频器 (TIMx_PSC),寄存器的各位描述如图所示:

这里,定时器的时钟来源有4个:

1)内部时钟(CK_INT)

2)外部时钟模式1:外部输入脚(TIx)

3)外部时钟模式2:外部触发输入(ETR),仅适用于TIM2、TIM3、TIM4

4)内部触发输入(ITRx):使用A定时器作为B定时器的预分频器(A为B提供时钟)。

这些时钟,具体选择哪个可以通过TIMx_SMCR寄存器的相关位来设置。这里的CK_INT时钟是从APB1倍频的来的,除非APB1的时钟分频数设置为1(一般都不会是1),否则通用定时器TIMx的时钟是APB1时钟的2倍,当APB1的时钟不分频的时候,通用定时器TIMx的时钟就等于APB1的时钟。这里还要注意的就是高级定时器以及TIM9~TIM11的时钟不是来自APB1,而是来自APB2的。

这里顺带介绍一下TIMx_CNT寄存器,该寄存器是定时器的计数器,该寄存器存储了当前定时器的计数值。

TIMx 自动重载寄存器 (TIMx_ARR),寄存器的描述如图所示:

自动重载寄存器是预装载的。对自动重载寄存器执行写入或读取操作时会访问预装载寄存器。预装载寄存器的内容既可以直接传送到影子寄存器,也可以在每次发生更新事件
(UEV) 时传送到影子寄存器,这取决于 TIMx_CR1 寄存器中的自动重载预装载使能位
(ARPE)。当 计数器达到上溢值(或者在递减计数时达到下溢值)并且 TIMx_CR1
寄存器中的 UDIS 位为 0 时,将发送更新事件。该更新事件也可由软件产生。

TIMx 状态寄存器 (TIMx_SR),寄存器的各位描述如图所示:

该寄存器用来标记当前与定时器相关的各种事件/中断是否发生。

HAL库函数和cube生成函数介绍

通过以上一些寄存器的操作配置,我们就可以达到TIM最基本的配置了,接下来我们将着重讲解使用HAL库实现串口配置和使用的方法。在HAL库中,TIM相关的函数和定义主要在文件stm32f4xx_hal_tim.c和stm32f4xx_hal_tim.h中。接下来我们看看HAL库提供的TIM相关操作函数。

  1. TIM初始化。

TIM作为STM32的一个重要,HAL库为其配置了TIM初始化函数。接下来我们看看使用CUBE生成的TIM2初始化函数MX_TIM2_Init

void MX_TIM2_Init(void);

其中用到了初始化函数HAL_TIM_Base_Init相关知识,定义如下:

HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim);

该函数只有一个入口参数htim,为TIM_HandleTypeDef结构体指针类型,我们俗称其为句柄,它的使用会贯穿整个程序。一般情况下,我们会定义一个TIM_HandleTypeDef结构体类型全局变量,然后初始化各个成员变量。接下来我们看看结构体TIM_HandleTypeDef的定义:

{ TIM_TypeDef *Instance; /*!< Register base address */ TIM_Base_InitTypeDef Init; /*!< TIM Time Base required parameters */ HAL_TIM_ActiveChannel Channel; /*!< Active channel */ DMA_HandleTypeDef *hdma[7]; /*!< DMA Handlers array HAL_LockTypeDef Lock; /*!< Locking object */ __IO HAL_TIM_StateTypeDef State; /*!< TIM operation state */ } TIM_HandleTypeDef;

一般情况下载调用函数HAL_TIM_Base_Init对TIM进行初始化的时候,我们只需要先设置Instance和Init两个成员变量的值。接下来我们依次解释一下各个成员变量的含义。

Instance是TIM_TypeDef结构体指针类型变量,它是执行寄存器基地址,实际上这个基地址HAL库已经定义好了,如果是TIM2,取值为TIM2即可。

Init是TIM_Base_InitTypeDef结构体类型变量,它是用来设置TIM的各个参数,包括分频,计数等,它的使用方法非常简单。TIM_Base_InitTypeDef结构体定义如下:

typedef struct { uint32_t Prescaler; uint32_t CounterMode; uint32_t Period; uint32_t ClockDivision; uint32_t RepetitionCounter; uint32_t AutoReloadPreload; } TIM_Base_InitTypeDef;

该结构体第一个参数Prescaler为psc时钟分频系数,它用来确定工作频率。第二个参数CounterMode位计数器模式,包括向上/向下/中心。第三个参数Period为自动从装寄存器的值。第四个参数ClockDivision为内部时钟分频系数。

函数MX_TIM2_Init使用的一般格式为:

void MX_TIM2_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; htim2.Instance = TIM2; htim2.Init.Prescaler = 83; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 999999; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) { Error_Handler();} sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) { Error_Handler(); } }

这里我们需要说明的是,初始化除了使用函数HAL_TIM_Base_Init,还通过函数HAL_TIM_ConfigClockSource配置tim的时钟源,函数HAL_TIMEx_MasterConfigSynchronization配置关闭triger
output模式。

因为我们在SystemClock_Config函数里面已经初始化APB1的时钟为4分频,所以APB1的时钟为42M,而从STM32F407的内部时钟树图得知:当APB1的时钟分频数为1的时候,TIM2~7以及TIM12~14的时钟为APB1的时钟,而如果APB1的时钟分频数不为1,那么TIM2~7以及TIM12~14的时钟频率将为APB1时钟的两倍。因此,TIM2的时钟为84M,再根据我们设计的arr和psc的值,就可以计算中断时间了。计算公式如下:

Tout= ((arr+1)*(psc+1))/Tclk;

其中: Tclk:TIM2是输入时钟频率(单位为Mhz)。

Tout:TIM2溢出时间(单位为us)。

调用HAL_TIM_Base_Init时,内部会调用使能函数使能相应tim,所以调用了该函数之后我们就不需要重复使能。

__HAL_RCC_TIM2_CLK_ENABLE();

三.程序设计

本节,我们首先讲解使用HAL库配置TIM的一般步骤。然后我们会具体讲解我们实验程序实现。

这里我们在main函数调用cube生成的初始化函数MX_TIM2_Init初始化参数配置,具体函数如下:

void MX_TIM2_Init(void)

通过上面一个函数,我们就配置了TIM相关初始化设置。接下来就是编写TIM实现精确延时服务函数AccurateDelay。

//利用TIM2定时,实现精确延时 void AccurateDelay(uint32_t delayMs) { // 经分频后,TIM2的时钟频率为1MHz,即周期为1us // 将ms转换为us uint32_t delayUs = delayMs * 1000; if (delayUs > 0) { // 设置TIM2定时周期 htim2.Init.Period = delayUs - 1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { Error_Handler(); } // TIM2开始计时前,清除UPDATE标志 __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE); // TIM2开始计时 HAL_TIM_Base_Start(&htim2); // 等待计时 while (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) != SET) { } // 计时结束,停止TIM2 __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE); HAL_TIM_Base_Stop(&htim2); } }

该函数用于实现ms级别的延时。首先设定定时器周期为delayMs *
1000,然后初始化定时器;清除UPDATE标志后,TIM2开始计时,然后一直停在while处,等待计时标志TIM_FLAG_UPDATE清0完成。然后停止TIM2计数。这个过程就完成了精准延时。

接下来,我们来看一下主函数main:

int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); while (1) { AccurateDelay(500); //精确延时500ms HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_6); AccurateDelay(1000); //精确延时1000ms HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_6); AccurateDelay(2000); //精确延时2000ms HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_6); } }

初始化函数实现硬件配置的初始化,不再讲述。再while循环中,通过TIM2实现延时,分别隔500ms/1000ms/2000ms翻转LED1管脚PB6的电平。

四.实验结果

我们把程序下载到STM32F407开发板,观察LED1闪烁间隔时间。

五.STM32CubeMx配置TIM2

本小节讲解使用STM32CubeMX配置TIM方法,我们配置TIM2,所以首先我们要选择内部时钟作为时钟源,时钟源频率设置。预分频设置89,向上计数等。

关于使用STM32CubeMX配置tim的方法就给大家介绍到这里。