正点原子STM32HAL库学习记录(stm32f103zet6)——基本定时器以及利用定时器中断翻转LED灯

基本定时器简介

  • stm32f103有两个基础定时器TIM6/TIM7
  • 主要功能:没有输入输出通道,常用作时基,即定时功能
  • 主要特性:16 位自动重载递增计数器, 16 位可编程预分频器,预分频系数 1~65536,用于对计数器时钟频率进行分频,还可以触发 DAC 的同步电路,以及生成中断/DMA 请求。

基本定时器框图

基本定时器框图

1. 时钟源
  • 时钟源:定时器核心就是计数器,要实现计数功能就需要一个时钟源。基本定时器时钟挂载在APB1总线,因此它的时钟来自于APB1。但是它的时钟并不是直接由APB1直接提供的,需要经过一个倍频器。当预分频系数为1时,倍频器系数为一,定时器时钟频率=APB1总线时钟频率; APB1 的预分频器系数≥2 分频时,这个倍频器系数就为2 ,即定时器的时钟频率等于APB1总线时钟频率的两倍。

在这里插入图片描述

2. 控制器
  • 控制器:控制器除了控制定时器复位、使能、计数等功能之外,还可以用于触发 DAC 转换。
3. 时基单元
  • 时基单元包括:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR) 。

时基单元中的预分频器 PSC,它有一个输入和一个输出。输入 CK_PSC 来源于控制器部分,实际上就是来自于内部时钟(CK_INT),即 2 倍的 APB1 总线时钟频率(72MHz)。输出 CK_CNT是分频后的时钟,它是计数器实际的计数时钟,通过设置预分频器寄存器(TIMx_PSC)的值可以得到不同频率 CK_CNT,计算公式如下:
f(CK_CNT)=f(CK_PSC)/(PSC[15:0]+!)
上式中, PSC[15:0]是写入预分频器寄存器(TIMx_PSC)的值。
另外:预分频器寄存器(TIMx_PSC)可以在运行过程中修改它的数值,新的预分频数值将在下一个更新事件时起作用。因为更新事件发生时,会把 TIMx_PSC 寄存器值更新到其影子寄存器中,这才会起作用。
什么是影子寄存器?框图中PSC预分频器下有个影子,那就是影子寄存器。 影子寄存器是一个实际起作用的寄存器, 不可直接访问。只有等到更新事件发生时,预分频器寄存器的值才会被自动写入其影子寄存器中,这时才真正起作用。所以PSC寄存器起到了缓冲的作用。对于自动重装载寄存器由ARPE决定是否具有缓冲作用。

基本定时器的更新事件

  • 更新事件的产生有两种情况,一是由软件产生,将 TIMx_EGR 寄存器的位UG 置 1,产生更新事件后,硬件会自动将 UG位清零。二是由硬件产生,满足以下条件即可:计数器的值等于自动重装载寄存器影子寄存器的值。一般都是利用硬件更新事件。
  • 基本定时器的计数器(CNT)是一个递增的计数器,当寄存器(TIMx_CR1)的 CEN 位置1,即使能定时器,每来一个 CK_CNT 脉冲,TIMx_CNT 的值就会递增加 1。当 TIMx_CNT 值与 TIMx_ARR 的设定值相等时, TIMx_CNT的值就会被自动清零并且会生成更新事件(如果开启相应的功能,就会产生 DMA 请求、产生中断信号或者触发 DAC同步电路),然后下一个CK_CNT 脉冲到来, TIMx_CNT 的值就会递增加 1,如此循环。在此过程中, TIMx_CNT等于TIMx_ARR 时,我们称之为定时器溢出,因为是递增计数,故而又称为定时器上溢。 定时器溢出就伴随着更新事件的发生。

TIM6/TIM7重要的寄存器

具体的细节描述可以参考STM32F10xx参考手册,这里只提供部分

  • 控制寄存器 1(TIMx_CR1)

  • DMA/中断使能寄存器(TIMx_DIER)

  • 状态寄存器(TIMx_SR)
    在这里插入图片描述

  • 计数器寄存器(TIMx_CNT)
    在这里插入图片描述

  • 预分频寄存器(TIMx_PSC)
    在这里插入图片描述

  • 自动重载寄存器(TIMx_ARR)
    在这里插入图片描述

基本定时器的运用——利用定时器中断每隔固定间隔翻转LED灯

如图 20.1.3.1 所示, CNT 计数器从 0 开始计数,当 CNT 的值和 ARR 相等时(t1),产生一个更新中断,然后 CNT 复位(清零),然后继续递增计数,依次循环。图中的 t1、 t2、 t3 就是定时器更新中断产生的时刻。通过修改 ARR 的值,可以改变定时时间。另外,通过修改 PSC 的值,使用不同的计数频率(改变图中 CNT 的斜率),也可以改变定时时间。

硬件设计

1. 实现功能
  • LED0 用来指示程序运行,每 200ms 翻转一次。我们在更新中断中,将 LED1 的状态取反。
  • LED1 用于指示定时器发生更新事件的频率, 500ms 取反一次。
2. 硬件资源(基于正点原子战舰v4开发板)

1)LED灯
LED0 - PB5
LED1 - PE5
2)定时器6

程序设计

定时器的HAL库驱动
  • 需要将HAL库的STM32F1xx_hal_tim.c 和 STM32F1xx_hal_tim_ex.c 添加到工程中来。

    1. HAL_TIM_Base_Init 函数
      HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim);
      此函数是用来初始化定时器的,形参为定时器句柄,结构体定义如下:
typedef struct
{
TIM_TypeDef *Instance; /* 外设寄存器基地址 */
TIM_Base_InitTypeDef Init; /* 定时器初始化结构体*/
HAL_TIM_ActiveChannel Channel; /* 定时器通道 */
DMA_HandleTypeDef *hdma[7]; /* DMA 管理结构体 */
HAL_LockTypeDef Lock; /* 锁定资源 */
__IO HAL_TIM_StateTypeDef State; /* 定时器状态 */
}TIM_HandleTypeDef;

在具体使用中,我们需要定义一个定时器句柄变量,设置它的外设寄存器基地址也就是TIM6的地址。接着,需要配置定时器初始化结构体TIM_Base_InitTypeDef Init; /* 定时器初始化结构体*/,它的结构体成员如下:

typedef struct
{
uint32_t Prescaler; /* 预分频系数 */
uint32_t CounterMode; /* 计数模式 */
uint32_t Period; /* 自动重载值 ARR */
uint32_t ClockDivision; /* 时钟分频因子 */
uint32_t RepetitionCounter; /* 重复计数器 */
uint32_t AutoReloadPreload; /* 自动重载预装载使能 */
} TIM_Base_InitTypeDef;

我们需要设置预分频系数、计数模式(基本定时器只有向上计数)、自动重装载值、自动重装载使能,其他的基本定时器不具备。

    1. HAL_TIM_Base_Start_IT 函数
      HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
      它是更新定时器中断和使能定时器的函数,形参同样为定时器句柄。该函数调用了__HAL_TIM_ENABLE_IT 和__HAL_TIM_ENABLE 两个函数宏定义,分别是更新定时器中断和使能定时器的宏定义。

下面分别列出单独使能/关闭定时器中断和使能/关闭定时器方法:

__HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE); /* 使能句柄指定的定时器更新中断 */
__HAL_TIM_DISABLE_IT (htim, TIM_IT_UPDATE); /* 关闭句柄指定的定时器更新中断 */
__HAL_TIM_ENABLE(htim); /* 使能句柄 htim 指定的定时器 */
__HAL_TIM_DISABLE(htim); /* 关闭句柄 htim 指定的定时器 */
定时器中断的配置步骤


在这里插入图片描述

代码实现

  • 代码基于战舰开发板中的跑马灯实验上实现的,所以只进行基本定时器6的编程
  1. 先在drivers目录下的BSP文件夹下新建TIM文件夹,并新建tim.c与tim.h文件
  2. 编写定时器6初始化函数
    设置好定时器6的基地址、预分频数、自动重装载值,就可以调用定时器初始化函数 HAL_TIM_Base_Init
    在HAL_TIM_Base_Init中,会调用定时器的Msp初始化函数。此函数是用来配置定时器GPIO、NVIC、CLOCK的。所以我们还需要对他进行重定义(默认为虚函数)。最后在开启中定时器的中断。定时器6初始化和Mspinit函数编写如下:
TIM_HandleTypeDef g_tim6_handle;			//定义定时器句柄全局变量
 
/* 定时器初始化函数 */
void tim6_init(uint16_t arr,uint16_t psc)
{
    g_tim6_handle.Instance=TIM6;                    //选择定时器6
    g_tim6_handle.Init.Prescaler = psc;             //设置预分频器
    g_tim6_handle.Init.Period = arr;                //设置自动重装载值
    HAL_TIM_Base_Init( &g_tim6_handle);             //初始化定时器6
    
    HAL_TIM_Base_Start_IT(&g_tim6_handle);          //开启定时器6中断

}

/* 重定义基础MSP初始化函数 */
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM6)                       //先判断是否为定时器6
    {
        __HAL_RCC_TIM6_CLK_ENABLE();                //使能定时器6时钟
        HAL_NVIC_SetPriority(TIM6_IRQn,1,3);        //设置定时器6中断优先级 抢占1 响应3
        HAL_NVIC_EnableIRQ(TIM6_IRQn);               //使能定时器6中断
    }
}
  1. 编写中断服务函数
    首先找到定时器6的中断服务函数,并在其中调用HAL库的公共定时器中断处理函数。在公共处理函数中,会根据中断类型调用相应的中断回调函数。由于我们使用的是定时器溢出中断,因此需要重定义定时器的溢出中断回调函数,以便在其中实现特定的功能,例如翻转LED0。
/* 定时器6中断服务函数 */
void TIM6_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_tim6_handle);            //调用HAL库定公共定时器中断处理函数
}

/* 重定义定时器溢出中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM6)                       //先判断是否为定时器6
    {
        LED0_TOGGLE();                               //翻转LED0
    }
}
  1. 主函数编写
    首先将 tim6_init在tim.h里声明,然后引用tim.h头文件。
    在正点原子的sys_stm32_clock_init 函数里面已经初始化 APB1 的时钟为 HCLK 的 2 分频所以这里的频率为72Mhz(APB1频率为36Mhz,预分频系数为2所以是72Mhz),此时我们只需要设置arr和psc的值就可以计算中断时间了。
    公式如下:
    在这里插入图片描述
    因为我们是让LED1隔500ms翻转一次,先设置预分频寄存器的值这里我们设置成7199,代入公式计算就可以算出自动重装载寄存器值为4999了,所以main函数代码如下:
int main(void)
{
     HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟,72M */
    delay_init(72);                             /* 初始化延时函数 */
    led_init();                                 /* 初始化LED */
    tim6_init(5000-1,7200-1);                   /* 定时器6初始化 10Khz 的计数频率,计数 5K 次为 500ms */
    while(1)
    {
        LED0_TOGGLE();
        delay_ms(200);
    }
}

附录

tim.c 文件

#include "./BSP/TIMER/tim.h"
#include "./BSP/LED/led.h"

TIM_HandleTypeDef g_tim6_handle;
 
/* 定时器初始化函数 */
void tim6_init(uint16_t arr,uint16_t psc)
{
    g_tim6_handle.Instance=TIM6;                    //选择定时器6
    g_tim6_handle.Init.Prescaler = psc;             //设置预分频器
    g_tim6_handle.Init.Period = arr;                //设置自动重装载值
    HAL_TIM_Base_Init( &g_tim6_handle);             //初始化定时器6
    
    HAL_TIM_Base_Start_IT(&g_tim6_handle);          //开启定时器6中断

}

/* 重定义基础MSP初始化函数 */
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM6)                       //先判断是否为定时器6
    {
        __HAL_RCC_TIM6_CLK_ENABLE();                //使能定时器6时钟
        HAL_NVIC_SetPriority(TIM6_IRQn,1,3);        //设置定时器6中断优先级 抢占1 响应3
        HAL_NVIC_EnableIRQ(TIM6_IRQn);               //使能定时器6中断
    }
}

/* 定时器6中断服务函数 */
void TIM6_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_tim6_handle);            //调用HAL库定公共定时器中断处理函数
}

/* 重定义定时器溢出中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM6)                       //先判断是否为定时器6
    {
        LED0_TOGGLE();                               //翻转LED0
    }
}

tim.h

#ifndef _TIM_H_
#define _TIM_H_

#include "./SYSTEM/sys/sys.h"

///* 定时器初始化函数 */
void tim6_init(uint16_t arr,uint16_t psc);
    
#endif

main.c

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/tim.h"


int main(void)
{
     HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟,72M */
    delay_init(72);                             /* 初始化延时函数 */
    led_init();                                 /* 初始化LED */
    tim6_init(5000-1,7200-1);                   /* 定时器6初始化 */
    while(1)
    {
        LED0_TOGGLE();
        delay_ms(200);
    }
}
  • 34
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值