计时器的简单应用

计时器的应用

1、概念

单片机计时器是单片机内部用于定时、计数或产生中断的硬件模块,常见于51单片机、STM32等型号‌。以下是核心要点:
基本功能

定时:通过预设时间间隔触发中断或执行任务(如延时、周期性操作)。
计数:对外部事件进行计数(如脉冲信号检测)。
中断:计数溢出后触发中断,通知CPU处理事件。 ‌

工作原理

时钟源:内部振荡器(如12MHz晶振)或外部时钟信号提供基准频率。
计数器:根据时钟信号递增计数,达到预设值后溢出并触发中断。 ‌

应用场景

软件定时:通过编程实现延时或周期性任务(占用CPU资源)。
硬件定时:精确控制时间间隔(如串口通信、PWM信号生成)。 ‌

2、硬件初始化

(I)配置时钟

使能RCC

  • System Core内找到RCC,使能外部晶振
    img

配置时钟树

  • 转到Clock Configuration,将锁相环源复用器改为外部晶振,系统时钟复用器改为锁相环
    img

(II)配置计时器

使能TIM

  • 回到Pinout & Configuration按照如下步骤找到TIM4,选择内部时钟
    img
  • 在参数设定处选择将预分频器改为8400 - 1,重装载值设为10000 - 1

计算频率

  • TIM4属于通用计时器,挂载在APB1总线上
    img
  • 由图可知,此时计时器总线的时钟频率为84MHz,而TIM4预分频器为8400 - 1
    img
  • 所以TIM4的频率为 \(f = \frac{84 \times 10 ^ 6}{(8400 - 1) + 1} = 1 \times 10 ^ 4 Hz\)
  • 重装载值为10000 - 1,因此每隔$ \frac{1 \times 10 ^ 4}{(10000 - 1) + 1} = 1$秒后,会触发一次中断

(III)配置串口

  • Connectivity中找到USART2,模式选择异步通讯,参数设定与自己的串口工具保持一致
    img
  • NVIC Settings中选择使能串口中断
    img

(IV)配置NVIC

  • NVIC中将优先级组设置为0,TIM4子优先级设置为2,USART2子优先级设置为1,EXTI_10_15,优先级设置为0,确保能正常输出计时器的数值
    img

3、写入代码

(I)目的

  • 让小灯以1Hz频率闪烁,每次摁下按钮,单片机向串口输出当前计数值

(II)流程图

(1)主程序流程图

img

(2)按钮中断

img

(3)计时器中断

img

(III)准备部分

(1)标识符

  • main.h内引入stdbool.h头文件,用于设置bool型变量
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdbool.h>
/* USER CODE END Includes */
  • 声明signalFlag,用于信号消抖,outputFlag用于表示是否传输数据
/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
volatile bool outputFlag = false;//The flag of output. True means sending data to UART, while false means not.
//volatile bool blinkFlag = false;//The flag of blinking. True means blinking, while false means off.
volatile bool signalFlag = false;//The flag of CPU. False means spare, while true means busy.
int counter;//The value of counter in TIM4
/* USER CODE END PV */
  • 因为中断处理函数位于stm32f4xx_it.c,所以中断函数不能直接访问标识符的值,需要通过封装函数返回值
  • main.h内声明以下函数:
/* USER CODE BEGIN EFP */
void toggleOutputFlag(void);
//void toggleBlinkFlag(void);
void toggleSignalFlag(void);
bool getSignalFlag(void);
/* USER CODE END EFP */
  • main.c中进行定义
/* USER CODE BEGIN 4 */
/**
  * @brief  Toggle the status of outputFlag
  * @retval none
  */
void toggleOutputFlag(void){
	outputFlag = !outputFlag;
}
/**
  * @brief  Toggle the status of blinkFlag
  * @retval none
  */
void toggleSignalFlag(void){
	signalFlag = !signalFlag;
}
/**
  * @brief  Return the status of signalFlag
  * @retval bool
  */
bool getSignalFlag(void){
	return signalFlag;
}

(2)为串口输出准备

  • main.c中引入stdio.h
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */
  • 因为输出要传输至串口而不是终端窗口,因此需要对stdio.h中的int fputc(int ch, FILE *stream)函数进行重定向
int fputc(int ch, FILE *stream){
		HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 10);
	return ch;
}
/* USER CODE END 4 */

(3)开始计时

  • 在进入消抖前开始计时
  /* USER CODE BEGIN 2 */
	HAL_TIM_Base_Start_IT(&htim4);
  /* USER CODE END 2 */

(IV)消抖

  • 判断signalFlag是否为真,若为真则延时0.1秒再翻转该标识符
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		//Filt the signal
		if(signalFlag){
			HAL_Delay(99);
			signalFlag = !signalFlag;
		}

(V)按钮处中断

  • 先判断signalFlag的值是否为false且中断请求来自按钮处GPIO,若为真则翻转outputFlagsignalFlag

由于不能直接访问两标识符,因此需要调用先前封装的函数

/**
  * @brief This function handles EXTI line[15:10] interrupts.
  */
void EXTI15_10_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI15_10_IRQn 0 */
	if(!getSignalFlag() && __HAL_GPIO_EXTI_GET_FLAG(B1_Pin)){
		toggleSignalFlag();
		toggleOutputFlag();
	}
  /* USER CODE END EXTI15_10_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(B1_Pin);
  /* USER CODE BEGIN EXTI15_10_IRQn 1 */

  /* USER CODE END EXTI15_10_IRQn 1 */
}

(VI)计时器中断

  • 将LED处GPIO信号翻转
/**
  * @brief This function handles TIM4 global interrupt.
  */
void TIM4_IRQHandler(void)
{
  /* USER CODE BEGIN TIM4_IRQn 0 */
	HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
  /* USER CODE END TIM4_IRQn 0 */
  HAL_TIM_IRQHandler(&htim4);
  /* USER CODE BEGIN TIM4_IRQn 1 */

  /* USER CODE END TIM4_IRQn 1 */
}

(VII)输出部分

  • outputFlag的值为true,则翻转outputFlag并输出此时TIM4计数器的寄存器内数值
		if(outputFlag){
			outputFlag = !outputFlag;
			counter = __HAL_TIM_GET_COUNTER(&htim4);//Get value from counter
			printf("counter:%d\r\n", counter);
		}
    /* USER CODE END WHILE */

4、实验效果

  • 每个1秒,LED灯闪烁一次
    img
    img
  • 当摁下按钮后,向串口输出计时器数值
    img
posted @ 2025-11-09 21:31  奶龙大王  阅读(16)  评论(0)    收藏  举报