完成定时器的计时功能

master
高宏宇 11 months ago
parent 71a30d1353
commit 124760c2e6

@ -0,0 +1,403 @@
[TOC]
# 1. 定时器概述
定时器的工作如同闹钟,单不只是闹钟这么简单。
## 1.1. 定时器工作原理
1. 计数模式:对引脚输人的外部脉冲信号进行计数(如何实现?)。
2. 定时模式对处理器内部的周期性时钟信号进行计数时钟信号的频率是确定的设置一个数一个时钟信号递减1当减少到0的时候这个过程的时间就是定时的时间
3. 定时时钟:在定时模式下,输人定时器的周期性时钟信号称为定时时钟(为定时器提供的稳定时钟信号)。
4. 计数时间:在定时模式下,定时器内部的计数单元记一次数所花费的时间称为计数时间,该值为定时时钟频率的倒数。
根据定时时钟和计数时间的定义,我们可以得到定时器的定时时间计算公式:
定时时间=计数值x计数时间
定时时间=计数值/定时时钟频率
## 1.2. STM32定时器介绍
![image-20240917190453681](./img/image-20240917190453681.png)
专用定时器(略):
1. 开门狗(什么是看门狗?有什么用?);
2. 实时时钟:就是我们所说的时间和日期;
3. 低功耗定时器:低功耗模式(如休眠)下任然工作,用于休眠中唤醒的定时器。
常规定时器:
1. 基本定时器:几乎没有任何对外的输入/输出通道,常用作时间基准(时基),实现基本的定时功能。
2. 通用定时器:具备多路独立的捕获/比较通道,可以完成定时/计数、输入捕获、输出比较等功能还可以连接其他的传感器接口,如编码器和霍尔传感器。
3. 高级定时器:高级定时器的功能最为强大,除了具备通用定时器的功能外,还增加了重复计数器带死区控制的互补信号输出等功能,可用于电机控制等领域。
[视频-时钟树](https://www.bilibili.com/video/BV1ph4y1e7Ey/?spm_id_from=333.788&vd_source=3c8e333d6657680a469ddf0238f01d6a)
![image-20240922094517185](./img/image-20240922094517185.png)
总线结构中文手册25页
![image-20240917193958697](./img/image-20240917193958697.png)
[视频-定时器](https://www.bilibili.com/video/BV11u4y1A7gS/?spm_id_from=333.788&vd_source=3c8e333d6657680a469ddf0238f01d6a)
无论哪一种定时器,最基本的功能都是定时和计数在这两个功能的基础上又衍生出其他的功能。在实际的工程应用中最常用的定时器功能有以下三种P205
① 定时/计数功能:用于产生时间基准以及测量外部脉冲的个数。
②输出比较功能:包括 PWM 输出、电平翻转、单脉冲输出以及强制输出等功能。
③ 输入捕获功能:用于测量输人信号的脉冲宽度。
# 2. HAL库外设模块设计方法
## 2.1. 外设句柄设计略p205
本质上是用一个结构体来描述一个外设定时器、串口、I2C等寄存器等
![image-20240922194116122](./img/image-20240922194116122.png)
## 2.2. 外设编程模型
HAL库根据微控制器和外设的数据传输方式设计了轮询一般在主程序中去读取外设的状态从而进行相应的处理、中断通过中断服务进行外设状态变化的函数回调和 DMA类似中断不过关联了DMA的功能 三种编程模型,用于外设的数据传输。
以定时器的定时功能为例,三种编程模型下的接口函数分别是:
![image-20240922194440003](./img/image-20240922194440003.png)
> 在下面的例子中我们将会使用到中断方式启用外设。DMA方式将在下一章串口中进行讲解。
## 2.3. 外设通用接口函数设计p208
1. 初始化函数;
2. I/O操作函数
3. 控制函数;
4. 状态函数;
# 3. 定时/计数功能
## 3.1. 时钟源
时钟源主要有四种p209我们目前主要介绍内部时钟源CK_INT。注意这里所说的内部时钟源是统称表示总线上的是时钟不是指MCU提供时钟的时钟源。为MCU提供时钟信号的也分为内部和外部但最终都会形成总线上的时钟信号AHB
通过设置相关的寄存器选择对应的时钟源后该时钟源将作为时基单元的预分频时钟CK_PSC送人时基单元。时钟源选择的示意图如图8-3所示。
![image-20240922105450832](./img/image-20240922105450832.png)
## 3.2. 时基单元
时基单元是定时器的核心控制单元,负责时钟源的分频、计数和溢出重载等基本功能。它主要由三个模块组成:预分频模块、计数模块和自动重载模块。时基单元的功能框图如图 8-4所示。
![image-20240922105559848](./img/image-20240922105559848.png)
### 3.2.1. 预分频模块
预分频模块的工作原理如下:定时器启动后预分频计数器的初值为0。预分频时钟CK_PSC每输入一个脉冲预分频计数器的计数值就自动加一。当计数值等于预分频寄存器中存放的预分频系数PSC时计数值清零并开始下一轮计数。
![image-20240922105932899](./img/image-20240922105932899.png)
预分频时钟 CK_PSC 经过预分频模块后得到计数时钟CK_CNT它的计算公式如下
![image-20240922140840757](./img/image-20240922140840757.png)
注意0表示不分频1表示2分频因此需要PSC+1
### 3.2.2. 计数模块
计数模块由核心计数器和计数器寄存器TIMx_CNT组成核心计数器用来对预分频模块输出的计数时钟 CK_CNT进行二次计数。计数时钟CK_CNT每输入一个脉冲核心计数器的计数值就自动加一或减一(根据用户设置的不同计数模式来决定是加一还是减一)。计数器寄存器则用来存放核心计数器运行时的计数值,便于用户读取。
### 3.2.3. 自动重载模块
自动重载模块由自动重载寄存器TIMx_ARR构成用来设置计数器的计数终值或计数初值,决定计数脉冲的多少(计数模式)或定时周期(定时模式)的长短。我们将TMx_ARR寄存器的内容记为自动重载值ARR。当定时器设置为递增计数模式时ARR作为计数器的计数终值表示记到ARR时发生溢出。当定时器设置为递减计数模式时ARR作为计数器的计数初值表示从ARR开始向下计数。
### 3.2.4. 计数模式
定时器的计数模块支持三种计数模式:递增计数、递减计数和中心对齐计数,并产生溢出事件,作为定时器的更新中断(定时中断)。
1. 递增计数计数器从0开始向上计数当计数值等于ARR时产生计数器上溢事件并从0开始新一轮的计数周期。
2. 递减计数计数器从ARR开始向下计数当计数值等于0时产生计数器下溢事件并从ARR开始新一轮的计数周期。
3. 中心对齐计数(递增/递减计数)计数器从0开始向上计数当计数值等于ARR-1时产生计数器上溢事件然后从ARR 开始向下计数当计数值等于1时产生计数器下溢事件。之后再从0开始新一轮的计数周期。
三种计数模式的计数过程如图 8-6所示。
![image-20240922110842727](./img/image-20240922110842727.png)
> 预装载功能和影子寄存器p212
### 3.2.5. 定时时间计算公式
当定时器工作于定时模式时,预分频时钟 CK_PSC 等于定时时钟 TIMx_CLK。定时时间由 TIMx_CLK 的频率、预分频系数 PSC 和自动重载值 ARR 三者决定。
假设PSC=1ARR=36采用递增计数模式计数器寄存器的初值为0。定时器的时序如图 8-7 所示。
![image-20240922111151540](./img/image-20240922111151540.png)
预分频时钟 CK_PSC经过分频后得到计数时钟CK_CNT,送入计数器。计数器的计数时间为1/C_KCNT代入式(8-3),可以得到计数时间为(PSC+1)/CK_PSC。由于实际的计数值为 ARR+1将计数值和计数时间代入式(8-1),可以得到定时时间的计算公式:
![image-20240922111416244](./img/image-20240922111416244.png)
![image-20240922111452751](./img/image-20240922111452751.png)
## 3.3. 外部脉冲计数p214略
对外部的脉冲信号进行计数例如通过传感器统计生产线上物料个数。这是外部传感器在有物料通过时可以产生一个脉冲输入到MCU使用定时器的计数模式进行统计。
定时器的计数模式在硬件上有极性选择、分频、滤波等电路,可以实现较高频率的脉冲计数,同时可以对输入波形进行滤波,减少干扰的影响。
> 其实如果对低频脉冲的统计可以使用IO的外部中断即可每个中断对全局变量进行累加但是IO中断并不提供分频、滤波等功能因此不适合高频率的脉冲抗干扰能力也较弱。
![image-20240922195130424](./img/image-20240922195130424.png)
> 定时器的ETR引脚和书上的不一致请参考芯片手册。
## 3.4. 定时/计数功能的数据类型p215
在 stm32f1xx_hal_tim.h 的大约 46 行:
```c
typedef struct
{
uint32_t Prescaler; /*!< Specifies the prescaler value used to divide the TIM clock.
This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */
uint32_t CounterMode; /*!< Specifies the counter mode.
This parameter can be a value of @ref TIM_Counter_Mode */
uint32_t Period; /*!< Specifies the period value to be loaded into the active
Auto-Reload Register at the next update event.
This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF. */
uint32_t ClockDivision; /*!< Specifies the clock division.
This parameter can be a value of @ref TIM_ClockDivision */
uint32_t RepetitionCounter; /*!< Specifies the repetition counter value. Each time the RCR downcounter
reaches zero, an update event is generated and counting restarts
from the RCR value (N).
This means in PWM mode that (N+1) corresponds to:
- the number of PWM periods in edge-aligned mode
- the number of half PWM period in center-aligned mode
GP timers: this parameter must be a number between Min_Data = 0x00 and
Max_Data = 0xFF.
Advanced timers: this parameter must be a number between Min_Data = 0x0000 and
Max_Data = 0xFFFF. */
uint32_t AutoReloadPreload; /*!< Specifies the auto-reload preload.
This parameter can be a value of @ref TIM_AutoReloadPreload */
} TIM_Base_InitTypeDef;
```
![image-20240922201006253](./img/image-20240922201006253.png)
ClockDivision 用于计数和输入捕获的滤波(略)。
![image-20240922201054848](./img/image-20240922201054848.png)
![image-20240922201213498](./img/image-20240922201213498.png)
## 3.5. 定时/计数功的接口函数p217
### 3.5.1. 时基单元初始化函数(HAL_TIM_Base_Init)
该函数用于时基单元的初始化,将用户设定的配置参数写人对应的寄存器,具体描述如表 8-7 所示。
![image-20240922201428110](./img/image-20240922201428110.png)
### 3.5.2. 定时器轮询方式启动函数(HAL_TIM_Base_Start)
该函数用于在轮询方式下启动定时器运行,具体描述如表 8-8所示。
![image-20240922201535346](./img/image-20240922201535346.png)
### 3.5.3. 定时器中断方式启动函数(HAL_TIM_Base_Start_IT)
该函数用于在中断方式下启动定时器运行,具体描述如表8-9所示。
![image-20240922201635738](./img/image-20240922201635738.png)
### 3.5.4. 定时器中断通用处理函数(HAL_TIM_IROHandler)
该函数是所有定时器中断发生后的通用处理程序。任何一个定时器的相关中断(如更新中断或捕获中断)发生后都会通过中断向量表中的定时器中断服务程序TMxIROHandler()(x表示定时器编号1~11)调用该函数。在函数内部会进行中断类型的判断,并清除对应的中断标志,然后再根据不同的中断类型,调用不同的回调函数来完成具体的中断处理任务。具体描述如表 8-10所示。
![image-20240922201819520](./img/image-20240922201819520.png)
### 3.5.5. 定时器更新中断回调函数(HAL_TIM_PeriodElapsedCallback)
该函数用于处理发生更新中断后的具体任务。任何一个定时器发生更新中断后,都会调用更新中断回调函数。因此,在函数内部需要判断是哪一个定时器产生的本次更新中断然后再执行具体的中断处理任务。具体描述如表8-11所示。
![image-20240922201943442](./img/image-20240922201943442.png)
> 注意这个函数有缺省的实现是用weak标识的因此一般情况下需要重新定义该函数来完成用户逻辑的处理。
### 3.5.6. 定时器中断标志清除函数(__HAL_TIM_CLEAR_IT)
该函数用于清除定时器的各类中断标志,采用带参数的宏实现(宏函数),与普通函数相比,宏函数省去了函数调用的过程,执行效率较高。具体描述如表 8-12所示。
![image-20240922202146788](./img/image-20240922202146788.png)
### 3.5.7. 定时器计数值读取函数(__HAL_TIM_GET_COUNTER)
该函数用于读取定时器的计数值,采用带参数的宏实现(宏函数),具体描述如表 8-13
所示。
![image-20240922202324216](./img/image-20240922202324216.png)
## 3.6. 基础任务:定时闪烁指示灯
### 3.6.1. 实现过程
使用绿色LEDPA7进行显示定时器4以中断方式运行每隔1秒钟切换一次电平。
1. 以前面讲的方式建立一个项目,名称是 TimerLED
2. 设置PA7为输出模式并设置标签为LED
3. 使用TIM4作为定时器。检查TIM4挂接的总线[STM32定时器介绍](#STM32定时器介绍),知道是挂接在APB1上
4. 检查时钟配置:
![image-20240922185613665](./img/image-20240922185613665.png)
知道APB1的定时器时钟是8M。注意定时器的中断时间是需要按照定时器所在总线的时钟进行设置的。
5. 配置时钟:
![image-20240922191931460](./img/image-20240922191931460.png)
勾选2位置以便使用内部时钟指APB总线时钟不是外部时钟晶体和内部RC时钟源在3位置设置PSC为7999在4位置设置ARR为999根据8-4公式可以知道此时的时钟计时周期为1秒
6. 开启TIM4的全局中断并保存生成代码框架。
![image-20240922192310094](./img/image-20240922192310094.png)
7. 在 main.c 的 /* USER CODE BEGIN 2 */ 位置插入下列代码:
```c
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim4);
/* USER CODE END 2 */
```
8. 在 main.c 文件的 /* USER CODE BEGIN 0 */ 位置插入定时中断回调函数:
```c
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM4)
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
/* USER CODE END 0 */
```
好了下载到开发板后绿色LED应该亮一秒灭一秒一直循环。
### 3.6.2. 代码分析
main.c 的 43 行定义了外设TIM4的句柄
```c
TIM_HandleTypeDef htim4;
```
这个书上没有讲,其实就是针对 TIM4 这个外设的结构体包括配置信息和相关的操作函数指针有点像Java中的类定义有兴趣可以看看。
main.c 的 147 行左右是定时器的初始化:
```c
static void MX_TIM4_Init(void) {
/* USER CODE BEGIN TIM4_Init 0 */
/* USER CODE END TIM4_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = { 0 };
TIM_MasterConfigTypeDef sMasterConfig = { 0 };
/* USER CODE BEGIN TIM4_Init 1 */
/* USER CODE END TIM4_Init 1 */
htim4.Instance = TIM4;
htim4.Init.Prescaler = 7999;
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 999;
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim4) != HAL_OK) {
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK) {
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig)
!= HAL_OK) {
Error_Handler();
}
/* USER CODE BEGIN TIM4_Init 2 */
/* USER CODE END TIM4_Init 2 */
}
```
/* USER CODE BEGIN 2 */ 位置的代码:
```
HAL_TIM_Base_Start_IT(&htim4);
```
表示以中断方式启动TIM4
stm32f1xx_it.c 的大约 204 行:
```c
void TIM4_IRQHandler(void)
{
/* USER CODE BEGIN TIM4_IRQn 0 */
/* USER CODE END TIM4_IRQn 0 */
HAL_TIM_IRQHandler(&htim4);
/* USER CODE BEGIN TIM4_IRQn 1 */
/* USER CODE END TIM4_IRQn 1 */
}
```
此代码是自动生成的,函数 TIM4_IRQHandler(void) 是 TIM4 溢出中断调用的函数,该函数调用了 HAL_TIM_IRQHandler(),这个函数,并传入了 htim4 这个结构体的指针标识TIM4中断接着看 stm32f1xx_hal_tim.c 的大约 3882 行开始的 HAL_TIM_IRQHandler() 这个函数这个函数很复杂在大约3959行调用了 HAL_TIM_PeriodElapsedCallback(htim); 这个函数。因此该中断的调用过程是:
TIM4_IRQHandler->HAL_TIM_IRQHandler->HAL_TIM_PeriodElapsedCallback
HAL_TIM_PeriodElapsedCallback 是我们在主函数中定义的用户逻辑函数。
## 3.7. 进阶任务:外部脉冲计数(略)
# 4. PWM输出功能
## 4.1. 捕获/比较通道
## 4.2. PWM实现原理
## 4.3. PWM输出功能的数据类型
## 4.4. PWM输出功能的接口函数
## 4.5. 基础任务输出PWM信号
## 4.6. 进阶任务:实现呼吸灯
# 5. 输入捕获功能
## 5.1. 输入捕获功能概述
## 5.2. 输入捕获功能的数据类型
## 5.3. 输入捕获功能的接口函数
## 5.4. 挑战任务:信号测量

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.
Loading…
Cancel
Save