基本定时器

定时器就是计数器。以下是基本定时器的内容和相关HAL库函数

普通模式

代码实践一

实践一:定时器实现定时发送数据

配置界面如下:

**(1)**Prescaler:对于分频器的设置

​ Counter Period:对于自动重装载寄存器的值,计数周期,用于计算时间周期

​ auto-reload preload:自动重装载的影子寄存器

普通模式配置1

**(2)**开启串口

普通模式配置2

**(3)**开启串口

普通模式配置3

所用的HAL库:

1
2
3
4
5
6
HAL_TIM_Base_Start_IT(&htim4);//中断模式开启定时器(-IT)

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
//定时器更新中断函数,在到达设定的数值时,启动该函数,重新计数

__HAL_TIM_GetCounter(&htim4)//用一个变量获取定时器的数据

函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
...

/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "string.h"//包含相应的头文件
/* USER CODE END Includes */

...

/* USER CODE BEGIN PFP */
char date[]="😊😊😊😊";
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
if(htim==&htim4)//判断一下可以判断相关的
{
HAL_UART_Transmit_IT(&huart1,(uint8_t*)date,strlen(date));//串口发送相关的数据
}

}
/* USER CODE END 0 */

...

/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim4);//启动相关的定时器的口子
int counter = 0;
char message[20];
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
counter =__HAL_TIM_GetCounter(&htim4);
sprintf(message,"counter: %d",counter);//拼接字符串
HAL_Delay(99);


/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}
...

通用/高级定时器

通用、高级定时器有四个输入通道:

TI1-4:只有1和2接入触发器,TI1FP1、TI2FP2可以选择上、下、双边沿触发,TI1_ED只能双边沿检测。

通过外部时钟1进入从模式控制器

ETR:极性选择:上升沿还是下降沿

边沿检测:只能实现一种检测

预分频:降低输入的速度

输入滤波:滤去不必要的干扰

可以通过触发器进入从模式控制器,也可以直接进入触发控制器。

通用&高级模式

代码实践二

实践一:实现红外发射模块实现计数黑白条纹在OLED上显示

配置界面如下:

(1)选择“外部时钟1”

触发模式可以选择TI1FP1、TI2FP2、TI1_ED,由于精确计数,所以选择不分频

通用模式配置1

(2)

通用模式配置2

所用到的HAL库函数

1
2
3
HAL_TIM_Base_Start(&htim2);//阻塞模式下的定时器开启模式

__HAL_TIM_GetCounter(&htim4)//用一个变量获取定时器的数据

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
...

/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "string.h"
#include "oled.h"
/* USER CODE END Includes */

...

/* USER CODE BEGIN 2 */
HAL_Delay(20);
OLED_Init();
HAL_TIM_Base_Start(&htim2);
int counter=0;
char message[20]="";

/* USER CODE END 2 */

...

/* USER CODE BEGIN WHILE */
while (1)
{
OLED_NewFrame();
counter=__HAL_TIM_GET_COUNTER(&htim2);
sprintf(message,"counter:%d",counter);
OLED_PrintString(10,10,message,&font16x16,OLED_COLOR_NORMAL);
OLED_ShowFrame();
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}

...

定时器从模式

三种模式

复位模式

实践1:红外检测计数,每5秒自动重装载,或是外部触发,进行串口发送数据。

复位模式下,在正常计数的情况下,接收到触发信号就会中断,进行更新实践,同样会触发更新中断。(如果开启中断的情况下)

配置如下:由内部时钟提供计时(之前所说ETR的外部时钟2的作用与之类似,只不过一个内部时钟,一个外部时钟)(One Pulse Mode不用勾选);开启相应的串口。

复位模式配置

所用到的HAL库函数

1
2
3
4
5
6
7
8
9
10
11
__HAL_TIM_GET_FLAG(htim,TIM_FLAG_TRIGGER)
//获取标志位,第一位是定时器操作句柄的指针,第二个是获取操作位的值

__HAL_TIM_CLEAR_FLAG(htim,TIM_FLAG_TRIGGER);
//清理标志位,第一位是定时器操作句柄的指针,第二个是获取操作位的值

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
//中断更新函数

HAL_TIM_Base_Start_IT(&htim2);
//开启定时器中断

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/* USER CODE BEGIN 0 */
char update[]="自动重装载";
char trigger[]="从模式触发";
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
if(htim==&htim2)
{
if(__HAL_TIM_GET_FLAG(htim,TIM_FLAG_TRIGGER)==SET){
__HAL_TIM_CLEAR_FLAG(htim,TIM_FLAG_TRIGGER);
HAL_UART_Transmit(&huart1,(uint8_t*)trigger,strlen(trigger),100);

}
else{
HAL_UART_Transmit(&huart1,(uint8_t*)update,strlen(update),100);
}//判断相应的标志位,并改变不同的标志位数值


}
}
/* USER CODE END 0 */

/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim2);//开启定时器中断(非阻塞模式)
int counter =0;
char message[] ="";
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
counter=__HAL_TIM_GET_COUNTER(&htim2);//获取数据
sprintf(message,"counter:%d",counter);
HAL_UART_Transmit(&huart1,(uint8_t*)message,strlen(message),100);
HAL_Delay(500);
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}

门模式

实践1:红外检测计数,每5秒自动重装载,或是外部触发,进行串口发送数据。

门模式下,下降的时候暂停计时,上升时继续计数,实现计数暂停、继续的控制。

配置如下:由内部时钟提供计时,One Pulse Mode不用勾选;开启相应的串口。

门模式配置

所用到的HAL库函数

1
2
3
4
5
6
7
8
9
10
11
__HAL_TIM_GET_FLAG(htim,TIM_FLAG_TRIGGER)
//获取标志位,第一位是定时器操作句柄的指针,第二个是获取操作位的值

__HAL_TIM_CLEAR_FLAG(htim,TIM_FLAG_TRIGGER);
//清理标志位,第一位是定时器操作句柄的指针,第二个是获取操作位的值

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
//中断更新函数

HAL_TIM_Base_Start_IT(&htim2);
//开启定时器中断

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/* USER CODE BEGIN 0 */
char update[]="自动重装载";
char trigger[]="从模式触发";
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
if(htim==&htim2)
{

HAL_UART_Transmit(&huart1,(uint8_t*)update,strlen(update),100);


//触发更新中断,在定时器更新中断中传输相应的数据


}
}
/* USER CODE END 0 */

/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim2);//开启定时器中断(非阻塞模式)
int counter =0;
char message[] ="";
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if(__HAL_TIM_GET_FLAG(htim,TIM_FLAG_TRIGGER)==SET){
__HAL_TIM_CLEAR_FLAG(htim,TIM_FLAG_TRIGGER);
HAL_UART_Transmit(&huart1,(uint8_t*)trigger,strlen(trigger),100);
}//门模式下不再开启定时器更新中断,所以将判断触发的逻辑写在主函数中,以便清零标志位

counter=__HAL_TIM_GET_COUNTER(&htim2);//获取数据
sprintf(message,"counter:%d",counter);
HAL_UART_Transmit(&huart1,(uint8_t*)message,strlen(message),100);
HAL_Delay(500);
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}

触发模式

实践1:红外检测计数,外部触发开始计数,配合单脉冲模式,实现一个计数周期,进行串口发送数据。

触发模式下,只能启动定时器,而不能停止定时器,可以配合单脉冲模式(One Pulse Mode)使用,在达到设定的数据时,停止计数。

配置如下:由内部时钟提供计时;开启相应的串口。

触发模式配置

所用的HAL库函数

1
2
3
4
5
6
7
8
9
10
11
__HAL_TIM_GET_FLAG(htim,TIM_FLAG_TRIGGER)
//获取标志位,第一位是定时器操作句柄的指针,第二个是获取操作位的值

__HAL_TIM_CLEAR_FLAG(htim,TIM_FLAG_TRIGGER);
//清理标志位,第一位是定时器操作句柄的指针,第二个是获取操作位的值

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
//中断更新函数

HAL_TIM_Base_Start_IT(&htim2);
//开启定时器中断

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/* USER CODE BEGIN 0 */
char update[]="自动重装载";
char trigger[]="从模式触发";
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
if(htim==&htim2)
{

HAL_UART_Transmit(&huart1,(uint8_t*)update,strlen(update),100);


//触发更新中断,在定时器更新中断中传输相应的数据


}
}
/* USER CODE END 0 */

/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim2);//开启定时器中断(非阻塞模式)
int counter =0;
char message[] ="";
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if(__HAL_TIM_GET_FLAG(htim,TIM_FLAG_TRIGGER)==SET){
__HAL_TIM_CLEAR_FLAG(htim,TIM_FLAG_TRIGGER);
HAL_UART_Transmit(&huart1,(uint8_t*)trigger,strlen(trigger),100);
}//门模式下不再开启定时器更新中断,所以将判断触发的逻辑写在主函数中,以便清零标志位

counter=__HAL_TIM_GET_COUNTER(&htim2);//获取数据
sprintf(message,"counter:%d",counter);
HAL_UART_Transmit(&huart1,(uint8_t*)message,strlen(message),100);
HAL_Delay(500);
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}

一个小知识

程序刚启动/复位时,会输出“自动重装载”?
原因: MX_TIM2_Init();将标志位置为1,

​ HAL_TIM_Base_Start_IT(&htim2);开启中断

​ NVIC检测到,因此输出“自动重装载”

解决办法:将HAL_TIM_CLEAR_FLAG(&htim2,TIM_FLAG_UPDATE);

​ 或者HAL_TIM_CLEAR_IT(&htim2,TIM_FLAG_UPDATE);

​ 放在HAL_TIM_Base_Start_IT(&htim2);前即可。

输入捕获

同一个信号通过两种不同通道,实现对上升沿与下降沿的间距测量,即一个设为直接输入,一个输入为间接捕获,TI1和TI2、TI3和TI4为一对

输入捕获

代码实践三

实践1:超声波模块实现测距,并显示在OLED屏幕上

配置如下

(1)开启输入捕获的直接通道和简介通道

输入捕获配置1

(2)开启GPIO_Output,实现对超声波模块的激活。

输入捕获配置2

(3)开启输入捕获中断

输入捕获配置3

(4)开启相应的两个通道,分别设置为上升沿和下降沿,直接与间接。

输入捕获配置4

所用到的HAL库函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
//输入捕获中断回调函数
}

HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_3)

HAL_TIM_Base_Start(&htim1);//开启定时器

HAL_TIM_IC_Start(&htim1,TIM_CHANNEL_3);
//开启输入捕获,第一个为定时器操作的指针,第二个为通道

HAL_TIM_IC_Start_IT(&htim1,TIM_CHANNEL_4);
//开启输入捕获,中断完成后提示,所以用_IT;第一个为定时器操作的指针,第二个为通道

__HAL_TIM_SET_COUNTER(&htim1,0);//置零计数器,防止达到设置的计数周期的上限,触发自动重装载

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
...

/* USER CODE BEGIN Includes */
#include "oled.h"
#include "stdio.h"
#include "string.h"
/* USER CODE END Includes */

...

/* USER CODE BEGIN 0 */
int up=0;
int down =0;
float distance=0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){//输入捕获中断回调函数

if(htim==&htim1 && htim->Channel==HAL_TIM_ACTIVE_CHANNEL_4)
{
//注意变量为HAL_TIM_ACTIVE_CHANNEL_X

up = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_3);
down = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_4);
distance = ((down-up)*0.034)/2;
}

}
/* USER CODE END 0 */

...

/* USER CODE BEGIN 2 */
HAL_Delay(20);
OLED_Init();
HAL_TIM_Base_Start (&htim1);//开启定时器
HAL_TIM_IC_Start (&htim1,TIM_CHANNEL_3);//开始输入捕获
HAL_TIM_IC_Start_IT (&htim1,TIM_CHANNEL_4);//开启输入捕获(中断)
char message[20]="";
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_GPIO_WritePin(Trig_GPIO_Port,Trig_Pin,GPIO_PIN_SET);
HAL_Delay(1);
HAL_GPIO_WritePin(Trig_GPIO_Port,Trig_Pin,GPIO_PIN_RESET);
//根据手册给超声波模块一个输入电压
__HAL_TIM_SET_COUNTER(&htim1,0);//置零计数周期,防止超出上限
HAL_Delay(20);

OLED_NewFrame();
sprintf(message,"距离 :%.2fcm",distance);
OLED_PrintString(10,10,message,&font16x16,OLED_COLOR_NORMAL);
OLED_ShowFrame();
HAL_Delay(500);//在OLED屏幕上显示

/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}
...

一些主要参考资料

1.【STM32】第16集 动画告诉你, STM32的定时器到底怎么回事_哔哩哔哩_bilibili

2.【STM32】动画讲解定时器外部时钟 & 实战传送带测速装置_哔哩哔哩_bilibili

3.【STM32】一看就懂的定时器从模式讲解_哔哩哔哩_bilibili

4.【STM32】动画讲解输入捕获 并实现超声波测距_哔哩哔哩_bilibili