STM32学习----基本定时器应用

什么是基本定时器

就是除了定时功能,没有其他什么功能。

基本定时器的结构组成

STM32学习----基本定时器应用_第1张图片

从图中可以看出基本定时器就这么些内容

1、时钟源(CLK)

2、预分频寄存器(PSC)

3、自动重装载寄存器(ARR)

4、计数器寄存器(CNT)

需要理解几个概念,什么是时钟源?什么是分频器?什么是自动重载?什么是计数器?什么是溢出?

举个栗子:

拿一个量杯,量杯的容量是65535mL;

往量杯里面滴水,每秒滴1mL;

65535秒后,杯子就装满了。

可以这么理解:

这个水滴就是时钟源,每滴就是1秒钟。

这个量杯就可以看作是一个计数器了,看量杯上的刻度就知道经过多长时间。

量杯满了,也叫溢出了。

量杯满了,就自动把水倒掉,重新接水计时,这个过程叫自动重载。

现在觉得1秒钟滴1mL太快了,改为10秒钟滴1mL,这个过程叫分频,10分频;如果改为5秒钟滴1mL就是5分频,改变时间间隔的就是分频器。

量杯的容量65536mL是实验室里面最大的量杯了,觉得太大了,可以换个50000mL的量杯,40000ml的杯子,25000mL的杯子。。。。。。这个容量可以理解为自动重装载寄存器的值。

量杯滴满了,除了把水倒掉重新接水外,还可以提醒你去干点啥吧,这个干点啥就可以理解为溢出产生的事件了,也可以理解为中断

STM32F103的基本定时器

F103的基本定时器有2个,TIM6和TIM7

TIM6和TIM7的特点:只能向上计数

什么叫向上计数:0、 1、 2 、3 。。。。。。99、100,从小往大的数叫向上计数;

什么叫向下计数:100、 99、 98 。。。。。。1、 0,从大往小的数叫向下计数;

与基本定时器相关的寄存器

STM32学习----基本定时器应用_第2张图片

这一个大表格看着就吓人,其实基本定时器就只有8个寄存器,

人心都是浮躁的,在哪个时代都是一样的,但是要想学点东西还是要静下心来,单片机要想学好,底层的寄存器一定要理解,别看STM32CubeMX一键生成代码很爽,在复杂的工程里面出点BUG你就GG了。

还是来看看那7个寄存器吧

控制寄存器1(TIMx_CR1)

STM32学习----基本定时器应用_第3张图片

APRE:Auto -reload Preload Enable 自动重装载预装载使能

0:自动重装载寄存器没有缓冲

1:自动重装载寄存器有缓冲

OPM:One-pulse mode 单脉冲模式

0:在发生更新事件时,计数器不停止

1:在发生下次更新事件时,计算器停止计数(清除CEN位)

URS:Update request source 更新请求源

该位由软件设置和清除,以选择UEV事件的请求源

0:所有状况都可以产生更新中断

1:只有计数器上溢或者下溢才可以产生更新中断或DMA

UDIS:Updata disable 禁止更新

0:更新事件使能

1:不产生更新事件

CEN :Counter Enable计数器使能

0:关闭计数器

1:使能计数器

控制寄存器2(TIMx_CR2)

STM32学习----基本定时器应用_第4张图片

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

STM32学习----基本定时器应用_第5张图片

UDE:Update DMA Request Enabel 更新DMA请求使能

0:禁止更新DMA请求

1:使能更新DMA请求

UIE:Updata interrupt enable 更新中断使能

0:禁止更新中断

1:使能更新中断

状态寄存器(TIMx_SR)

STM32学习----基本定时器应用_第6张图片

UIF:Update interrupt 更新中断标志

硬件在更新中断时设置该位,由软件清除

0:没有产生更新

1:产生了更新

事件产生寄存器(TIMx_EGR)

STM32学习----基本定时器应用_第7张图片

UG:Update Generation 产生更新事件

0:无作用

1:重新初始化定时器的计数器,并产生对寄存器的更新

计数器(TIMx_CNT)

STM32学习----基本定时器应用_第8张图片

预分频器(TIMx_PSC)

STM32学习----基本定时器应用_第9张图片

自动重装载寄存器(TIMx_ARR)

STM32学习----基本定时器应用_第10张图片

基本定时器的使用步骤

查询使用

1、参数初始化

2、打开定时器计数

3、关闭定时器

打开定时器对应的函数

//在HAL库中,使用HAL_TIM_Base_Start()函数来打开定时器
HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim)
//
//其实HAL_TIM_Base_Start()函数就是调用__HAL_TIM_ENABLE(__HANDLE__) 函数;
//而__HAL_TIM_ENABLE(__HANDLE__) 函数就是将CR1寄存器的CEN位设置为1
__HAL_TIM_ENABLE(__HANDLE__) 

关闭定时器对应的函数

//在HAL库中,使用HAL_TIM_Base_Stop()函数来关闭定时器
HAL_StatusTypeDef HAL_TIM_Base_Stop(TIM_HandleTypeDef *htim)
//
//其实HAL_TIM_Base_Stop()函数就是调用__HAL_TIM_DISABLE(__HANDLE__)函数;
//而__HAL_TIM_DISABLE(__HANDLE__)函数就是将CR1寄存器的CEN位设置为0
__HAL_TIM_DISABLE(__HANDLE__);

设置与读取计数器寄存器的初值,

默认复位之后,计数器寄存器的初值就是0,也可以自己设置个初值

//设置计数器寄存器的初值
__HAL_TIM_SET_COUNTER(__HANDLE__, __COUNTER__)  

//读取计数器寄存器的值
__HAL_TIM_GET_COUNTER(__HANDLE__)  ((__HANDLE__)->Instance->CNT)

设置自动重装载寄存器的值

定时器就是从计数器的初值,计到自动重装载的值之后,又从新计数;

一般计数器的初值是0,那这个自动重装的值就是周期,就是使用定时器经常看到的那个Preriod;

//__HAL_TIM_SET_AUTORELOAD()函数就是设置ARR寄存器
#define __HAL_TIM_SET_AUTORELOAD(__HANDLE__, __AUTORELOAD__) \
  do{                                                    \
    (__HANDLE__)->Instance->ARR = (__AUTORELOAD__);  \
    (__HANDLE__)->Init.Period = (__AUTORELOAD__);    \
  } while(0)

用基本定时器TIM6来做一个准确的延时

基本定时器只能使用系统内部时钟,看自己设置的是多少,一般设置为72MHz,72分频之后就是1MHz,一个时钟周期就是1us

使用STM3CubeMX自动生成了一些配置代码

tim源文件代码如下


#include "tim.h"

TIM_HandleTypeDef htim6;

void MX_TIM6_Init(void)
{
  htim6.Instance                 = TIM6; //使用基本定时器TIM6
  htim6.Init.Prescaler           = 72-1;  //72分频,72MHz 72分频之后就是1MHz
  htim6.Init.CounterMode         = TIM_COUNTERMODE_UP; //向上计数
  htim6.Init.Period              = 0; //自动重装载的值先不设定,在延时函数里面设置
  htim6.Init.AutoReloadPreload   = TIM_AUTORELOAD_PRELOAD_DISABLE; //自动预装载不使能,自动装载值马上更新
    
  if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
  {
    Error_Handler();
  }
}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
  if(tim_baseHandle->Instance==TIM6)
  {
    __HAL_RCC_TIM6_CLK_ENABLE();
  }
}

void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM6)
  {
    __HAL_RCC_TIM6_CLK_DISABLE();

  }
}

/* USER CODE BEGIN 1 */
//
/* 函数名 Delay_us
 * us级别的延时,t表示t us;
 * 前提是将定时器的时钟分频到1MHz,系统时钟72MHz的话,72分频即可
*/
void Delay_us(uint16_t t)
{
    uint16_t count = 0;
    __HAL_TIM_SET_AUTORELOAD(&htim6, t); //写入自动重装载寄存器的值,就是计数周期
    __HAL_TIM_SET_COUNTER(&htim6, 0); //将计数器初值设置为0
    __HAL_TIM_ENABLE(&htim6); //打开定时器开始计数
    
    while(count != t) //没有计到自动装载的值,就一直等着
    {
        count = __HAL_TIM_GET_COUNTER(&htim6); //读取计数器里面的值,赋值给count
    }
    
    __HAL_TIM_MOE_DISABLE(&htim6); //关闭定时器
}

/*
 *
 *ms级的延时就是1000个us级的延时
 *
**/
void Delay_ms(uint16_t t)
{
    uint16_t cnt;
    for(cnt=0; cnt

main源文件

#include "main.h"
#include "tim.h"
#include "gpio.h"

void SystemClock_Config(void);

int main(void)
{

  HAL_Init();

  SystemClock_Config();

  MX_GPIO_Init();
  MX_TIM6_Init();

  while (1)
  {
        //让LED间隔1s闪缩
        HAL_GPIO_TogglePin(TEST_LED_GPIO_Port, TEST_LED_Pin);
        Delay_ms(1000);
  }

}


void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }


  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}


void Error_Handler(void)
{
  __disable_irq();
  while (1)
  {
  }

}

中断使用

1、参数初始化

1、设置定时器的预分频系数

2、设置计数模式

3、设置自动重装载寄存器的值

4、预装载是否使能

2、使能定时器及其溢出中断

3、编写中断服务函数

1、检查更新中断标志位

2、检查对应的中断是不是使能了

3、清空中断标志位

4、做想做的事情,该做的事情

使用基本定时器的中断来实现LED灯闪缩

定时器tim.c源文件

#include "tim.h"

TIM_HandleTypeDef htim6;

/* TIM6 init function */
void MX_TIM6_Init(void)
{
  htim6.Instance                                 = TIM6; //使用定时器6
  htim6.Init.Prescaler                     = 72-1;  //72分频
  htim6.Init.CounterMode                 = TIM_COUNTERMODE_UP; //向上计数
  htim6.Init.Period                         = 1000-1;  //自动重装载值设置为1000
  htim6.Init.AutoReloadPreload     = TIM_AUTORELOAD_PRELOAD_DISABLE; //禁止预装载
  if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
  {
    Error_Handler();
  }

    HAL_TIM_Base_Start_IT(&htim6);  //开启定时器以及中断
}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
  if(tim_baseHandle->Instance==TIM6)
  {
    __HAL_RCC_TIM6_CLK_ENABLE();

    /* TIM6 interrupt Init */
    HAL_NVIC_SetPriority(TIM6_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM6_IRQn);
  }
}

void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM6)
  {

    __HAL_RCC_TIM6_CLK_DISABLE();

    /* TIM6 interrupt Deinit */
    HAL_NVIC_DisableIRQ(TIM6_IRQn);

  }
}

在中断源文件中编写回调函数

void TIM6_IRQHandler(void)
{
  HAL_TIM_IRQHandler(&htim6);
}


uint16_t count = 0;

//设置的自动重装载值为1000,相当于1000us,也就是1ms中断一次,要1s后执行中断任务,就需要等1000次中断
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    count++;
    if(count == 1000)
    {
        count = 0;
        HAL_GPIO_TogglePin(TEST_LED_GPIO_Port,TEST_LED_Pin);
        HAL_TIM_Base_Start_IT(&htim6); //在中断回调函数中再次使能中断
    }
}

HAL库里面的很多函数实在是很冗长,用起来是很方便,但是最好自己还是多研究一下底层的寄存器。

用寄存器操作结合HAL库,效率会高很多,代码看起来也会精简很多,后面有机会用简化的HAL库函数来实现定时器的中断。。。。。。

STM32的定时器功能非常强大,也很复杂,看使用手册就知道,100多页,比任何其他的外设都多,在实际的应用中,定时器也是用的非常频繁,尤其在电机控制方面,所以基本定时器的应用只是个开始。

你可能感兴趣的:(STM32学习,stm32,单片机,嵌入式硬件)