定时器定时计数功能
1 定时器概述
- 定时器是对周期固定的脉冲信号进行计数,如MCU内部的外设时钟(APB)。
- 计数器是对周期不确定的脉冲信号进行计数,如MCU的I/O引脚所引入的外部脉冲信号。
- 定时器和计数器本质上都是计数器,定时器是计数器的一种特例。
2 STM32定时器分类
对于STM32F103共存在6个外设定时器:高级定时器TIM1,通用定时器TIM2、TIM3、TIM4。
2.1 内核定时器
- 系统节拍定时器:Systick定时器是属于内核中的一个外设,内嵌在NVIC中。
2.2 外设定时器
- 常规定时器> STM32F103xx增强型产品中,内置了多达3个可同步运行的标准定时器(TIM2、TIM3和TIM4)。每个定时器都有一个16位的自动加载递加/递减计数器、一个16位的预分频器和4个独立的通道,每个通道都可用于输入捕获、输出比较、PWM和单脉冲模式输出.
- 专用定时器
3 定时器的时钟频率
4 定时器的主要功能
4.1 定时计数
- 计数内部时钟,即定时器模式
- 计数外部脉冲,即计数器模式
4.2 输出比较
- PWM输出
- 电平翻转
- 单脉冲输出
- 强制输出
4.3 输入捕获
- 捕获时保存定时器的当前计数值
- 捕获时,可选择触发捕获中断
- 触发捕获的信号边沿类型可选择(上升沿,下降沿,双边沿)
5 HAL库的定时器设计
5.1 定时器句柄结构的组成
5.2 三种外设编程模型
- 以后缀区分编程模型
- 入口参数均为外设句柄的指针
方式 | 函数 |
---|---|
轮询方式 | HAL_TIM_Base_Start(TIM_HandleTypeDef *htim );HAL_TIM_Base_Stop TIM_HandleTypeDef *htim ); |
中断方式 | HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);HAL_TIM_Base_Stop_IT(TIM_HandleTypeDef *htim); |
DMA方式 | HAL_TIM_Base_Start_DMA(TIM_HandleTypeDef *htim, uint32_t *pData, uint16_t Length); |
6 定时器的定时/计数功能
6.1 时基单元
①预分频模块
- 预分频计数器:对预分频时钟CK_PSC进行分频
- 预分频寄存器:TIMx_PSC:设置预分频系数PSC
- 作用1:扩大定时器的定时范围
- 作用2:获取精确的计数时钟
预分频模块工作原理: 定时器启动后,预分频计数器的初值为0,预分频时钟CK_PSC每来一个时钟,预分频计数器的值就加1。当计数值等于预分频寄存器所设定的预分频系数PSC时,预分频计数器的值将清零,开始下一轮计数。
预分频时序图:
假设预分频系数PSC=3,预分频计数器从0计数到PSC实际计数值为PSC+1,也就是进行了四分频。
②计数模块
- 核心计数器:对计数时钟CK_CNT进行二次计数
- 计数器寄存器:TIMx_CNT:存放核心计数器运行时的当前计数值
③自动重载模块
- 自动重载模块由自动重载寄存器TIMx_ARR组成
- 递增计数模式:TIMx_ARR的值作为核心计数器的计数终值
- 递减计数模式:TIMx_ARR的值作为核心计数器的计数初值
④计数模式
计数模式 | 计数器溢出值 | 计数器重载值 |
---|---|---|
递增计数 | CNT=ARR | CNT=0 |
递减计数 | CNT=0 | CNT=ARR |
中心对齐计数 | CNT=ARR-1CNT=1 | CNT=ARRCNT=0 |
6.2 定时器时序图
①定时时间公式
②相关寄存器
- 预分频寄存器TIMx_PSC: 设置预分频系数,将预分频时钟(CK_PSC)进行1~65536之间的任意值分频,得到计数时钟(CK_CNT)。
- 计数器寄存器TIMx_CNT : 存放核心计数器运行时的当前计数值,便于用户实时掌握核心计数器的当前计数值。芯片复位后,默认值为0。
- 自动重载寄存器TIMx_ARR: 为计数器设置计数边界或重载值。比如计数器递增计数时,记到多少发生溢出;递减计数时,从多少开始往下计数。
6.3 外部脉冲计数
6.4 数据类型和接口函数
成员变量:
成员变量ClockDivision的取值范围:
TIM_CLOCKDIVISION_DIV1 | 对定时器时钟TIM_CLK进行1分频 |
---|---|
TIM_CLOCKDIVISION_DIV2 | 对定时器时钟TIM_CLK进行2分频 |
TIM_CLOCKDIVISION_DIV4 | 对定时器时钟TIM_CLK进行4分频 |
成员变量CounterMode的取值范围
TIM_COUNTERMODE_UP | 递增计数模式 |
---|---|
TIM_COUNTERMODE_DOWN | 递减计数模式 |
TIM_COUNTERMODE_CENTERALIGNED1 | 中心对齐计数模式1 |
TIM_COUNTERMODE_CENTERALIGNED2 | 中心对齐计数模式2 |
TIM_COUNTERMODE_CENTERALIGNED3 | 中心对齐计数模式3 |
成员变量AutoReloadPreload的取值范围
TIM_AUTORELOAD_PRELOAD_DISABLE | 预装载功能关闭 |
---|---|
TIM_AUTORELOAD_PRELOAD_ENABLE | 预装载功能开启 |
- 用于设置自动重载寄存器TIMx_ARR的预装载功能,即自动重装寄存器的内容是更新事件产生时写入有效,还是立即写入有效;
- 预装载功能在多个定时器同时输出信号时比较有用,可以确保多个定时器的输出信号在同一个时刻变化,实现同步输出;
- 单个定时器输出时,一般不开启预装载功能。
接口函数:
时基单元初始化函数:
HAL_TIM_Base_Init
函数原型 HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim) 功能描述 按照定时器句柄中指定的参数初始化定时器时基单元 入口参数 htim:定时器句柄的地址 返回值 HAL状态值 注意事项 1. 该函数将调用MCU底层初始化函数HAL_TIM_Base_MspInit完成引脚、时钟和中断的设置2. 该函数由CubeMX自动生成 轮询模式启动函数:
HAL_TIM_Base_Start
函数原型 HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim) 功能描述 在轮询方式下启动定时器运行 入口参数 htim:定时器句柄的地址 返回值 HAL状态值 注意事项 1. 该函数在定时器初始化完成之后调用2. 函数需要由用户调用,用于轮询方式下启动定时器运行 中断模式启动函数:
HAL_TIM_Base_Start_IT
函数原型 HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim) 功能描述 使能定时器的更新中断,并启动定时器运行 入口参数 htim:定时器句柄的地址 返回值 HAL状态值 注意事项 1. 该函数在定时器初始化完成之后调用2. 函数需要由用户调用,用于使能定时器的更新中断,并启动定时器运行3. 启动前需要调用宏函数 __HAL_TIM_CLEAR_IT 来清除更新中断标志 定时器中断通用处理函数
HAL_TIM_IRQHandler
函数原型 void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim) 功能描述 作为所有定时器中断发生后的通用处理函数 入口参数 htim:定时器句柄的地址 返回值 无 注意事项 1. 函数内部先判断中断类型,并清除对应的中断标志,最后调用回调函数完成中断处理2. 该函数由CubeMX自动生成 定时器更新中断回调函数
HAL_TIM_PeriodElapsedCallback
函数原型 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) 功能描述 回调函数,用于处理所有定时器的更新中断,用户在该函数内编写实际的任务处理程序 入口参数 htim:定时器句柄的地址 返回值 无 注意事项 1.该函数由定时器中断通用处理函数HAL_TIM_IRQHandler调用,完成所有定时器的更新中断的任务处理2.函数内部需要根据定时器句柄的实例来判断是哪一个定时器产生的本次更新中断3.函数由用户根据具体的处理任务编写 计数值读取函数
__HAL_TIM_GET_COUNTER
#define __HAL_TIM_GET_COUNTER(__HANDLE__) ((__HANDLE__)- >Instance- >CNT)
__HANDLE__
:定时器句柄的地址该函数通过直接访问计数器寄存器TIMx_CNT来获取计数器的当前计数值。
定时器中断标志清除函数
__HAL_TIM_CLEAR_IT
#define __HAL_TIM_CLEAR_IT(__HANDLE__, __INTERRUPT__) ((__HANDLE__)- >Instance- >SR = ~(__INTERRUPT__))
__HANDLE__
:定时器句柄的地址__INTERRUPT__
:定时器中断标志
任务实践1
基于STM32F103C8T6,开发板原理图
利用开发板上的按键KEY2来触发外部脉冲,按键每按下一次,就利用PA1引脚发送一个周期2ms左右的脉冲,送到定时器2的外部触发引脚ETR(PA0)进行计数,并将计数结果通过串口发送到PC上显示。
注:本任务例程使用的开发板,KEY2与PA5相连接。KEY2原理图如下:
使用按键时,需要设置PA5为输入上拉模式,这样在KEY2没有按下时,PA5可以读取到高电平,KEY2按下时PA5可以读取到低电平。
配置PA1为GPIO_Output(User Label:PULSE),PA5为GPIO_Input(User Label:KEY2),上拉模式。
上述操作在stm32f1xx_hal_gpio.c
中生成GPIO引脚初始化函数MX_GPIO_Init
,并在main.c中调用void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); /*Configure GPIO pin : PA1 */ GPIO_InitStruct.Pin = GPIO_PIN_1; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /*Configure GPIO pin : PA5 */ GPIO_InitStruct.Pin = GPIO_PIN_5; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); }
配置定时器2时钟源为外部触发引脚ETR2,自动重载寄存器ARR配置为一个相对较大的计数,防止溢出。外部脉冲信号采用默认配置:不使用滤波,不进行脉冲信号反相,不进行脉冲信号分频。
上述操作在tim.c
生成引脚初始化函数:void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(tim_baseHandle- >Instance==TIM2) { /* USER CODE BEGIN TIM2_MspInit 0 */ /* USER CODE END TIM2_MspInit 0 */ /* TIM2 clock enable */ __HAL_RCC_TIM2_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**TIM2 GPIO Configuration PA0-WKUP ------ > TIM2_ETR */ GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* USER CODE BEGIN TIM2_MspInit 1 */ /* USER CODE END TIM2_MspInit 1 */ } } void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle) { if(tim_baseHandle- >Instance==TIM2) { /* USER CODE BEGIN TIM2_MspDeInit 0 */ /* USER CODE END TIM2_MspDeInit 0 */ /* Peripheral clock disable */ __HAL_RCC_TIM2_CLK_DISABLE(); /**TIM2 GPIO Configuration PA0-WKUP ------ > TIM2_ETR */ HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0); /* USER CODE BEGIN TIM2_MspDeInit 1 */ /* USER CODE END TIM2_MspDeInit 1 */ } }
上述操作在
tim.c
生成如下代码完成初始化定时器,并在main.c
中调用/* TIM2 init function */ void MX_TIM2_Init(void) { /* USER CODE BEGIN TIM2_Init 0 */ /* USER CODE END TIM2_Init 0 */ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; /* USER CODE BEGIN TIM2_Init 1 */ /* USER CODE END TIM2_Init 1 */ htim2.Instance = TIM2; htim2.Init.Prescaler = 0; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 65535; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_ETRMODE2; sClockSourceConfig.ClockPolarity = TIM_CLOCKPOLARITY_NONINVERTED; sClockSourceConfig.ClockPrescaler = TIM_CLOCKPRESCALER_DIV1; sClockSourceConfig.ClockFilter = 0; if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM2_Init 2 */ /* USER CODE END TIM2_Init 2 */ }
其中调用的
HAL_TIM_Base_Init
函数将调用HAL_TIM_Base_MspInit
完成引脚初始化。配置串口外设USART1,选择异步模式,无硬件流控。
上述操作在usart.c中生成串口配置相关函数,并在main.c中调用。usart的封装逻辑与tim相同,不再赘述。void MX_USART1_UART_Init(void) { /* USER CODE BEGIN USART1_Init 0 */ /* USER CODE END USART1_Init 0 */ /* USER CODE BEGIN USART1_Init 1 */ /* USER CODE END USART1_Init 1 */ huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN USART1_Init 2 */ /* USER CODE END USART1_Init 2 */ } void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(uartHandle- >Instance==USART1) { /* USER CODE BEGIN USART1_MspInit 0 */ /* USER CODE END USART1_MspInit 0 */ /* USART1 clock enable */ __HAL_RCC_USART1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**USART1 GPIO Configuration PA9 ------ > USART1_TX PA10 ------ > USART1_RX */ GPIO_InitStruct.Pin = GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_InitStruct.Pin = GPIO_PIN_10; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* USER CODE BEGIN USART1_MspInit 1 */ /* USER CODE END USART1_MspInit 1 */ } } void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle) { if(uartHandle- >Instance==USART1) { /* USER CODE BEGIN USART1_MspDeInit 0 */ /* USER CODE END USART1_MspDeInit 0 */ /* Peripheral clock disable */ __HAL_RCC_USART1_CLK_DISABLE(); /**USART1 GPIO Configuration PA9 ------ > USART1_TX PA10 ------ > USART1_RX */ HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10); /* USER CODE BEGIN USART1_MspDeInit 1 */ /* USER CODE END USART1_MspDeInit 1 */ } }
程序编写
使用串口输出,需要在Keil的Options中勾选Use MicroLIB.
在main.c中重定义printf
和scanf
函数/* USER CODE BEGIN Includes */ #include < stdio.h > /* USER CODE END Includes */ /* USER CODE BEGIN 4 */ int fputc (int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; } int fgetc(FILE *f) { uint8_t ch = 0; HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; } /* USER CODE END 4 */
用户变量定义代码
/* USER CODE BEGIN PV */ uint8_t Result = 0; /* USER CODE END PV */
用户变量初始化代码
/* USER CODE BEGIN 2 */ HAL_TIM_Base_Start_IT(&htim2); printf("Timer count function test: n"); /* USER CODE END 2 */
用户应用代码
/* USER CODE BEGIN 3 */ if (HAL_GPIO_ReadPin(GPIOA, KEY2_Pin) == GPIO_PIN_RESET) { HAL_Delay(10); if (HAL_GPIO_ReadPin(GPIOA, KEY2_Pin) == GPIO_PIN_RESET) { HAL_GPIO_WritePin(GPIOA, PULSE_Pin, GPIO_PIN_SET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOA, PULSE_Pin, GPIO_PIN_RESET); HAL_Delay(1); Result = __HAL_TIM_GET_COUNTER(&htim2); printf("Count = %d", Result); } while (HAL_GPIO_ReadPin(GPIOA, KEY2_Pin) == GPIO_PIN_RESET); } } /* USER CODE END 3 */
实验现象
任务实践2
设计电子时钟,从00:00:00开始计时,并将计时信息通过串口UART1发送到PC进行显示。
配置定时器1产生1s的更新中断。计算PSC和ARR的值,将T=1s,TIM_CLK=8000000Hz带入公式
得可取值PSC=1999,ARR=3999.
在CubeMX中使能TIM1,采用内部时钟 (8MHz) ,设置PSC和ARR值,并在嵌套向量中断控制器NVIC设置中使能TIM1的更新中断。
上述操作在tim.c
中生成TIM1初始化函数,并在main.c
中调用配置USART1串口输出,生成代码后应在工程中重定向
printf
和scanf
函数,方法同任务实践1,这部分不再赘述。编写代码
在main.c
中进行用户数据类型定义/* USER CODE BEGIN PTD */ typedef struct { uint8_t hour; uint8_t minutes; uint8_t second; }CLOCK_Typedef; /* USER CODE END PTD */
用户变量定义
/* USER CODE BEGIN PV */ CLOCK_Typedef clock = {0}; /* USER CODE END PV */
用户初始化代码
/* USER CODE BEGIN 2 */ // 清除更新中断标志,避免定时器一启动就进入中断 __HAL_TIM_CLEAR_IT(&htim1, TIM_IT_UPDATE); HAL_TIM_Base_Start_IT(&htim1); /* USER CODE END 2 */
用户应用代码
/* USER CODE BEGIN 3 */ printf("Time:%02d:%02d:%02d.rn", clock.hour, clock.minutes, clock.second); HAL_Delay(1000); } /* USER CODE END 3 */
编写定时器更新中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim- >Instance == TIM1) // 判断时钟源 { clock.second++; if (clock.second == 60) { clock.second = 0; clock.minutes++; if (clock.minutes == 60) { clock.minutes = 0; clock.hour++; if (clock.hour == 24) { clock.hour = 0; } } } } }
-
mcu
+关注
关注
146文章
16642浏览量
347616 -
STM32
+关注
关注
2256文章
10821浏览量
352356 -
计数器
+关注
关注
32文章
2241浏览量
93954 -
定时器
+关注
关注
23文章
3216浏览量
113633 -
脉冲信号
+关注
关注
6文章
375浏览量
36766
发布评论请先 登录
相关推荐
评论