计时器的简单应用
计时器的应用
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)主程序流程图

(2)按钮中断

(3)计时器中断

(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,若为真则翻转outputFlag与signalFlag
由于不能直接访问两标识符,因此需要调用先前封装的函数
/**
* @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]()












浙公网安备 33010602011771号