- 2.1、基本定时器简介
- 2.2、基本定时器框图
- 2.3、定时器计数模式及溢出条件
- 2.4、定时器中断实验相关寄存器
- 2.5、定时器溢出时间计算方法
- 2.6、定时器中断实验配置步骤
- 2.7、编程实战:定时器中断实验
- 4.1、高级定时器简介
- 4.2、高级定时器框图
- 4.3、高级定时器输出指定个数PWM实验
- 4.4、高级定时器输出比较模式实验
- 4.5、高级定时器互补输出带死区控制实验
- 4.6、高级定时器PWM输入模式实验
一、定时器概述
1.1、软件定时原理
软件定时的原理是利用CPU执行特定的指令序列来实现延时功能,即在程序中通过循环等待的方式来达到延时的效果。通常情况下,软件定时的实现会通过以下步骤进行:
-
确定延时时间: 程序中确定所需的延时时间,以满足特定的需求。
-
编写延时函数: 编写延时函数来实现延时的功能。这个函数会在程序中占用一定的时间,使得程序在执行到该函数时会停顿一段时间。
-
实现延时逻辑: 在延时函数中,使用循环等待的方式来实现延时。通常是通过执行空循环、计数循环等方式来实现。
-
调用延时函数: 在程序中需要延时的地方调用该延时函数,从而实现延时的效果。
软件定时的缺点包括:
-
延时不精准: 基于纯软件实现的延时往往不够精确,受到CPU执行指令的速度、流水线机制、缓存机制、压栈出栈操作等因素的影响,导致实际延时时间与期望值有一定的误差。
-
CPU死等: 在延时期间,CPU需要持续执行空循环或者计数循环等操作,这会导致CPU处于忙等待状态,不能执行其他的任务,降低了系统的效率。
由于软件定时的这些缺点,通常在对精准度要求较高、延时时间较长、需要充分利用CPU执行其他任务的情况下,会采用硬件定时器或者其他硬件模块来实现定时功能。
1.2、定时器定时原理
定时器定时的原理是利用硬件定时器模块中的计数器和自动重载寄存器来实现精准的定时功能。以下是定时器定时的基本原理:
-
时钟源选择: 定时器需要一个时钟源来驱动计数器的计数。通常情况下,时钟源可以是微控制器的系统时钟或者外部时钟源。
-
预分频器设置: 时钟源经过预分频器(PSC)进行预分频,以得到更低的时钟频率。预分频器的设置可以调节定时器的计数速率,从而影响定时的精度和范围。
-
计数器计数: 经过预分频后的时钟信号将驱动定时器的计数器(CNT)开始计数。计数器从零开始计数,每个时钟周期加1,直到达到自动重载值(ARR)为止。
-
溢出检测: 当计数器的值达到自动重载值时,会触发溢出事件,计数器将重新从零开始计数,并发出一个溢出中断信号。
-
重新加载: 自动重载寄存器(ARR)存储着计数器重新加载的值。当计数器溢出时,自动重载寄存器的值会重新加载到计数器中,从而实现周期性的定时功能。
通过以上步骤,定时器可以实现精准的定时功能,而且由于定时器的计数过程由硬件实现,不需要CPU的干预,因此不会占用CPU资源,能够同时执行其他任务,提高了系统的效率。定时器定时在嵌入式系统中被广泛应用于周期性任务的调度、PWM信号生成、输入捕获等领域。
1.3、STM32定时器分类
在STM32系列微控制器中,定时器可以按功能和用途进行分类,主要分为以下三类:
-
常规定时器:
- 基本定时器(Basic Timers): 通常用于产生简单的定时中断,具有较为简单的功能和较低的精度要求。
- 通用定时器(General-purpose Timers): 包括TIM2、TIM3、TIM4等,功能强大,适用于复杂的定时任务,具有多种工作模式和功能。
- 高级定时器(Advanced Timers): 包括TIM1和TIM8等,具有更高级别的功能和精度,通常用于需要高性能和精准度的定时任务。
-
专用定时器:
- 独立看门狗(Independent Watchdog,IWDG): 用于监控系统运行状态,防止程序死循环或意外死锁。
- 窗口看门狗(Window Watchdog,WWDG): 监控程序运行时效性,提前唤醒中断。
- 实时时钟(Real-time Clock,RTC): 提供实时时间和日期信息,通常用于实时时钟和日历功能。
- 低功耗定时器(Low-power Timer,LPTIM): 用于低功耗模式下的定时任务,具有较低的功耗和精度。
-
内核定时器:
- SysTick定时器: 是由内核提供的用于系统定时的定时器,可用于操作系统的时钟节拍、延时函数等,通常用于系统调度和时间基准。
这些定时器在STM32微控制器中提供了灵活且多样化的定时功能,可以根据具体应用需求选择合适的定时器类型和配置。
1.4、STM32定时器特性表
定时器类型 | 特性 |
---|---|
基本定时器 | - 简单的定时功能 |
- 16位定时器 | |
- 有一个16位自动重装载寄存器 | |
- 可以直接连接到GPIO口输出PWM信号 | |
- 适用于简单的定时任务 | |
通用定时器 | - 多功能、灵活的定时器 |
- 16位或32位定时器,具有多个通道 | |
- 多种计数模式,包括向上计数、向下计数、中心对齐计数等 | |
- 可以产生PWM信号、捕获输入信号、触发输出比较等功能 | |
- 适用于复杂的定时任务,如PWM控制、编码器接口等 | |
高级定时器 | - 高级功能、高精度的定时器 |
- 16位或32位定时器,具有多个通道 | |
- 支持同步、异步触发、定时器联动等高级功能 | |
- 适用于需要高性能和精准度的定时任务,如高速PWM、电机控制等 | |
独立看门狗 | - 监控系统运行状态,防止程序死循环或死锁 |
- 12位递减计数器 | |
- 内部时钟源(LSI)提供时钟 | |
- 可以产生系统复位信号 | |
窗口看门狗 | - 监控程序运行时效性,提前唤醒中断 |
- 7位递减计数器 | |
- 可以设置窗口期,超出窗口期产生中断 | |
- 可以产生系统复位信号 | |
实时时钟 | - 提供实时时间和日期信息 |
- 可以设置闹钟、日历、定时器等功能 | |
- 用于实时时钟和日历功能 | |
低功耗定时器 | - 用于低功耗模式下的定时任务 |
- 16位定时器,适用于低功耗模式下的长期计时任务 | |
- 低功耗模式下工作,具有较低的功耗 | |
SysTick定时器 | - 内核提供的系统定时器 |
- 24位定时器,用于系统时钟节拍、延时函数等 | |
- 用于操作系统的时钟节拍、延时函数等 |
1.5、STM32基本、通用、高级定时器的功能整体区别
定时器类型 | 主要功能 |
---|---|
基本定时器 | - 没有输入输出通道,常用作时基,即定时功能 |
- 16位定时器,可产生定时中断、PWM信号等 | |
- 适用于简单的定时任务,如延时、周期性触发等 | |
通用定时器 | - 具有多路独立通道,可用于输入捕获、输出比较等 |
- 16位或32位定时器,具有多种计数模式、多个通道 | |
- 可以产生PWM信号、捕获外部输入信号、触发输出比较等功能 | |
- 适用于复杂的定时任务,如PWM控制、编码器接口等 | |
高级定时器 | - 除具备通用定时器所有功能外,还具备带死区控制的互补信号输出、刹车输入等功能 |
- 提供高级功能、高精度的定时任务支持,如高速PWM、电机控制等 | |
- 适用于需要高性能和精准度的定时任务,如电机控制、数字电源设计等 |
二、基本定时器
2.1、基本定时器简介(F1为例)
基本定时器(TIM6/TIM7)是STM32中的两个简单的定时器,主要特性包括:
- 16位递增计数器: 可以计数从0到65535的整数,计数器在计数溢出时会重新从0开始计数。
- 16位预分频器: 可以将外部时钟源预分频,分频系数范围从1到65536,用于调节定时器的时钟频率。
- 可用于触发DAC: 可以利用基本定时器的更新事件(计数器溢出)来触发DAC转换,实现定时产生模拟输出信号。
- 中断/DMA请求: 基本定时器在更新事件发生时(即计数器溢出)会产生中断或DMA请求,用户可以在中断服务函数中执行相应的操作,或者通过DMA实现数据传输等功能。
2.2、基本定时器框图
影子寄存器
影子寄存器是指在某些微控制器中,特定功能模块的寄存器拥有两个版本:影子寄存器和实际寄存器。
-
实际寄存器: 是硬件内部用于存储功能模块配置的寄存器,由硬件直接访问和使用。
-
影子寄存器: 是由软件访问的虚拟寄存器,用于向硬件配置功能模块的参数。当软件写入影子寄存器时,其值会自动传送到对应的实际寄存器,从而配置了功能模块的相关参数。
使用影子寄存器的好处在于,软件可以在不直接访问硬件寄存器的情况下配置功能模块,从而避免了由于并发访问导致的冲突和错误。此外,使用影子寄存器也使得软件的编程更加灵活和可控。
UEV事件和UIF中断
U事件(Update Event)和UI中断(Update Interrupt)是定时器(TIM)功能中的两个重要概念:
-
U事件(Update Event): 当定时器的计数器溢出并重新开始计数时,或者在通过软件更新定时器的计数器值时,会触发U事件。这种更新事件常用于定时器的基本计时功能,例如生成精确的延时、定时采样等。
-
UI中断(Update Interrupt): UI中断是当定时器发生U事件时触发的中断请求。通过使能UI中断,可以让CPU在定时器发生更新事件时响应中断请求,并执行相应的中断服务程序。UI中断通常用于周期性的任务执行、定时采样、定时器管理等。
总的来说,U事件是定时器的一个基本触发事件,而UI中断则是由U事件触发的中断请求,通过处理UI中断,可以实现基于时间的任务调度和定时功能。
基本定时器的框图详解
基本定时器的框图主要包括三个部分:
-
时钟源(Clock Source): 定时器的时钟源通常来自于RCC(Reset and Clock Control)模块的TIMxCLK。这个时钟源经过内部时钟(CK_INT)后,传递给定时器的控制器。
-
控制器(Controller): 定时器的控制器负责控制定时器的各种功能,如复位、使能、计数等。它可以接收来自外部的触发信号(TRGO),并将计数器的计数结果发送到其他模块,比如DAC(Digital-to-Analog Converter)进行数模转换。
-
计数器(Counter,时基单元): 计数器是定时器的核心部分,用于实现计时功能。时钟信号经过PSC(Prescaler)预分频器进行预分频,然后输入到计数器(CNT)中进行计数。当计数器的值达到自动重装载寄存器(ARR)的值时,会产生溢出事件,即计数器溢出,产生一个更新事件(Update Event)。这个更新事件可以由硬件触发,也可以通过软件产生。UG位可产生软件更新事件,在更新事件时,计数器的值可以自动重装载为ARR的值,实现定时器的连续计时功能。
基本定时器ARPE位还可以配置自动重装载寄存器(ARR)是否具有缓冲,以及其他一些控制参数,以满足不同的定时需求。
2.3、定时器计数模式及溢出条件
递增计数模式实例说明
递增计数模式是指定时器的计数器在工作过程中逐步递增的模式。下面是一个递增计数模式的实例说明:
假设我们使用STM32的基本定时器TIM6来实现一个简单的定时功能,要求每隔一段时间执行特定的任务。
-
初始化定时器: 首先,我们需要对TIM6进行初始化。我们设置TIM6的时钟源为内部时钟,预分频器的分频系数为7999(即预分频系数为8000),这样TIM6的时钟频率就是内部时钟频率除以8000,即1 kHz。我们还设置TIM6的自动重装载寄存器(ARR)的值为999,这样计数器的计数范围就是0到999,计数到999时会产生一个更新事件。最后,使能TIM6并启动计数器。
-
等待更新事件: 在程序的主循环中,我们等待TIM6产生更新事件。一旦更新事件发生,说明计数器的值已经达到了999,即计数器溢出,这时我们执行特定的任务。
-
重置计数器: 在特定任务执行完毕后,我们重置计数器的值为0,重新开始计时。这样,定时器就能周期性地执行我们设定的任务了。
下面是一个伪代码示例:
// 初始化定时器 TIM6
TIM6_CLK_ENABLE(); // 使能时钟
TIM6->PSC = 7999; // 设置预分频系数
TIM6->ARR = 999; // 设置自动重装载寄存器的值
TIM6->CR1 |= TIM_CR1_CEN; // 使能定时器
while (1) {
// 等待 TIM6 产生更新事件
while (!(TIM6->SR & TIM_SR_UIF)); // 等待更新事件发生
TIM6->SR &= ~TIM_SR_UIF; // 清除更新事件标志位
// 执行特定任务
// 例如:LED翻转
LED_TOGGLE();
// 重置计数器
TIM6->CNT = 0;
}
在这个示例中,定时器TIM6的计数器每计满1000个时钟周期(即计数到999),就会产生一个更新事件。我们利用这个更新事件来执行特定的任务,例如LED翻转。然后,我们重置计数器的值为0,重新开始计时。
递减计数模式实例说明
递减计数模式是指定时器的计数器在工作过程中逐步递减的模式。下面是一个递减计数模式的实例说明:
假设我们使用STM32的基本定时器TIM6来实现一个简单的定时功能,要求每隔一段时间执行特定的任务。
-
初始化定时器: 首先,我们需要对TIM6进行初始化。我们设置TIM6的时钟源为内部时钟,预分频器的分频系数为7999(即预分频系数为8000),这样TIM6的时钟频率就是内部时钟频率除以8000,即1 kHz。我们还设置TIM6的自动重装载寄存器(ARR)的值为999,这样计数器的计数范围就是0到999,计数到0时会产生一个更新事件。最后,使能TIM6并启动计数器。
-
等待更新事件: 在程序的主循环中,我们等待TIM6产生更新事件。一旦更新事件发生,说明计数器的值已经递减到0,即计数器溢出,这时我们执行特定的任务。
-
重置计数器: 在特定任务执行完毕后,我们重置计数器的值为999,重新开始计时。这样,定时器就能周期性地执行我们设定的任务了。
下面是一个伪代码示例:
// 初始化定时器 TIM6
TIM6_CLK_ENABLE(); // 使能时钟
TIM6->PSC = 7999; // 设置预分频系数
TIM6->ARR = 999; // 设置自动重装载寄存器的值
TIM6->CR1 |= TIM_CR1_CEN; // 使能定时器
while (1) {
// 等待 TIM6 产生更新事件
while (!(TIM6->SR & TIM_SR_UIF)); // 等待更新事件发生
TIM6->SR &= ~TIM_SR_UIF; // 清除更新事件标志位
// 执行特定任务
// 例如:LED翻转
LED_TOGGLE();
// 重置计数器
TIM6->CNT = TIM6->ARR;
}
在这个示例中,定时器TIM6的计数器每计满1000个时钟周期(即计数到0),就会产生一个更新事件。我们利用这个更新事件来执行特定的任务,例如LED翻转。然后,我们重置计数器的值为999,重新开始计时。
中心对齐模式实例说明
中心对齐模式是STM32定时器的一种工作模式,它可以用于生成对称的 PWM(脉冲宽度调制)信号。在中心对齐模式下,计数器溢出时会重新加载自动重载寄存器的值,并在计数器值达到自动重载值的一半时翻转输出比较引脚的电平。
下面是一个中心对齐模式的实例说明,假设我们使用STM32的通用定时器TIM1来生成一个对称的PWM信号,用于控制LED的亮度:
-
初始化定时器: 首先,我们需要对TIM1进行初始化。我们设置TIM1的时钟源为内部时钟,预分频器的分频系数为7999(即预分频系数为8000),这样TIM1的时钟频率就是内部时钟频率除以8000。我们还设置TIM1的自动重载寄存器(ARR)的值为999,这样计数器的计数范围就是0到999。接着,我们将TIM1的工作模式设置为中心对齐模式(CMS = 01),并将计数模式设置为向上计数(DIR = 0)。最后,使能TIM1并启动计数器。
-
配置输出比较通道: 我们需要配置TIM1的输出比较通道,以控制LED的亮度。我们选择TIM1的通道1作为输出比较通道,将其配置为PWM输出模式,并设置比较值(CCR1)为499,即自动重载值的一半,这样就能实现对称的PWM信号。
-
等待PWM周期结束: 在程序的主循环中,我们可以根据需要等待一个PWM周期的结束,然后再进行下一次操作。
下面是一个伪代码示例:
// 初始化定时器 TIM1
TIM1_CLK_ENABLE(); // 使能时钟
TIM1->PSC = 7999; // 设置预分频系数
TIM1->ARR = 999; // 设置自动重装载寄存器的值
TIM1->CR1 |= TIM_CR1_CEN; // 使能定时器
TIM1->CR1 |= TIM_CR1_CMS_0; // 设置为中心对齐模式
TIM1->CR1 &= ~TIM_CR1_CMS_1;
TIM1->CR1 &= ~TIM_CR1_DIR; // 设置为向上计数模式
// 配置输出比较通道1
TIM1->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; // PWM模式1
TIM1->CCMR1 &= ~TIM_CCMR1_OC1M_0;
TIM1->CCR1 = 499; // 设置比较值为自动重载值的一半
while (1) {
// 等待PWM周期结束
// 根据需要执行其他操作
}
在这个示例中,定时器TIM1的计数器在每个PWM周期内都会递增到自动重载值999,然后重新开始计数。同时,当计数器的值达到自动重载值的一半499时,输出比较通道1的引脚电平会发生翻转,实现了对称的PWM信号。
2.4、定时器中断实验相关寄存器
2.5、定时器溢出时间计算方法
正确,定时器溢出时间可以通过以下公式计算:
T o u t = ( A R R + 1 ) × ( P S C + 1 ) F t Tout = \frac{(ARR+1) \times (PSC+1)}{Ft} Tout=Ft(ARR+1)×(PSC+1)
其中:
- T o u t Tout Tout 是定时器的溢出时间,单位为秒。
- A R R ARR ARR 是自动重装载寄存器的值。
- P S C PSC PSC 是预分频器寄存器的值。
- F t Ft Ft 是定时器的时钟源频率,单位为赫兹(Hz)。
(
P
S
C
+
1
)
F
t
\frac{(PSC+1)}{Ft}
Ft(PSC+1) 表示每个计数单元的持续时间,其中:
这个值表示了定时器的一个计数单元(即计数器增加1)所需的时间。
这个公式的计算方法是将定时器的计数范围( A R R + 1 ARR+1 ARR+1)与每个计数单元的持续时间( ( P S C + 1 ) / F t (PSC+1)/Ft (PSC+1)/Ft)相乘,得到总的溢出时间。
2.6、定时器中断实验配置步骤
定时器中断实验配置步骤基本流程。这些步骤包括:
-
配置定时器基础工作参数:使用HAL库的函数(如
HAL_TIM_Base_Init()
)配置定时器的基本工作参数,如预分频器、自动重装载寄存器等。 -
定时器基础MSP初始化:在
HAL_TIM_Base_MspInit()
函数中进行定时器的底层初始化工作,包括配置时钟、使能定时器的时钟等。 -
使能更新中断并启动计数器:调用HAL库函数(如
HAL_TIM_Base_Start_IT()
)使能定时器的更新中断,并启动定时器的计数器。 -
设置优先级,使能中断:使用HAL库函数(如
HAL_NVIC_SetPriority()
、HAL_NVIC_EnableIRQ()
)设置定时器中断的优先级并使能相应的中断。 -
编写中断服务函数:编写定时器中断的处理函数(如
TIMx_IRQHandler()
),在其中调用HAL库提供的函数(如HAL_TIM_IRQHandler()
)来处理中断事件。 -
编写定时器更新中断回调函数:实现定时器更新中断的回调函数(如
HAL_TIM_PeriodElapsedCallback()
),在其中执行定时器中断发生时需要执行的操作。
通过以上步骤,可以配置定时器并使其在每次计数器溢出时触发中断,从而实现定时器的定时功能。
HAL_TIM_Base_Init()
HAL_TIM_Base_Start_IT()
2.7、编程实战:定时器中断实验
首先,我们需要计算定时器的时钟频率。由于定时器6挂载在APB1总线上,其时钟频率是APB1总线频率的两倍。给定APB1时钟频率为36MHz,因此定时器6的时钟频率为36MHz * 2 = 72MHz。
接下来,根据给定的定时周期为500ms,我们可以使用定时器的时钟频率来计算自动重装载寄存器(ARR)的值。定时器溢出时间计算公式为:
T o u t = ( A R R + 1 ) × ( P S C + 1 ) F t Tout = \frac{{(ARR + 1) \times (PSC + 1)}}{{F_{t}}} Tout=Ft(ARR+1)×(PSC+1)
其中, T o u t Tout Tout 是定时器溢出时间, A R R ARR ARR 是自动重装载寄存器的值, P S C PSC PSC 是预分频器寄存器的值, F t F_{t} Ft 是定时器的时钟源频率。
我们已经知道了时钟源频率为72MHz,预分频器的值为7199(即 P S C = 7199 PSC = 7199 PSC=7199)。我们需要计算自动重装载寄存器(ARR)的值,使得定时器溢出时间为500ms。
因此,我们可以重排上述公式,解出( ARR )的值:
A R R = 500 m s × 72 M H z 7199 + 1 − 1 = 4999 ARR = \frac{{500ms \times 72MHz}}{{7199 + 1}} - 1 = 4999 ARR=7199+1500ms×72MHz−1=4999
现在我们可以将这些值代入公式进行计算,以确定自动重装载寄存器( A R R = 4999 ARR = 4999 ARR=4999)的值。然后,在定时器更新中断中,我们可以翻转LED0。
btim.c
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/btim.h"
TIM_HandleTypeDef g_timx_handle;
/* 定时器中断初始化函数 */
void btim_timx_int_init(uint16_t arr, uint16_t psc)
{
// 初始化定时器句柄
g_timx_handle.Instance = TIM6;
g_timx_handle.Init.Prescaler = psc;
g_timx_handle.Init.Period = arr;
HAL_TIM_Base_Init(&g_timx_handle);
// 启动定时器并使能更新中断
HAL_TIM_Base_Start_IT(&g_timx_handle);
}
/* 定时器基础MSP初始化函数 */
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
// 检查是否是TIM6
if (htim->Instance == TIM6)
{
// 使能TIM6时钟
__HAL_RCC_TIM6_CLK_ENABLE();
// 配置TIM6中断优先级和使能中断
HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 1, 3);
HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
}
}
/* 定时器6中断服务函数 */
void TIM6_DAC_IRQHandler(void)
{
// 调用HAL库的中断处理函数
HAL_TIM_IRQHandler(&g_timx_handle);
}
/* 定时器溢出中断中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
// 检查是否是TIM6的中断
if (htim->Instance == TIM6)
{
// 翻转LED0
LED0_TOGGLE();
}
}
btim.h
#ifndef __BTIM_H
#define __BTIM_H
#include "./SYSTEM/sys/sys.h"
void btim_timx_int_init(uint16_t arr, uint16_t psc);
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/btim.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟,72M */
delay_init(72); /* 初始化延时函数 */
led_init(); /* 初始化LED */
btim_timx_int_init(5000 - 1, 7200 - 1); /* 10Khz的计数频率,计数5K次为500ms */
while(1)
{
delay_ms(500);
}
}
三、通用定时器
3.1、通用定时器简介(F1为例)
通用定时器(TIM2/TIM3/TIM4/TIM5)在STM32F1系列中具有广泛的应用。以下是其主要特性:
-
计数器特性:
- 16位递增、递减、中心对齐计数器,计数范围为0到65535。
- 支持不同的计数模式,包括递增模式、递减模式和中心对齐模式。
-
时钟和预分频器:
- 具有16位预分频器,可以将外部时钟源分频为所需的计数频率。
- 预分频系数范围为1到65536。
-
应用场景:
- 可用于触发DAC和ADC,通过定时器的触发事件来启动ADC转换或DAC输出。
- 在更新事件、触发事件、输入捕获和输出比较时,可以产生中断或DMA请求,实现灵活的定时功能。
- 具有4个独立通道,可用于输入捕获、输出比较、输出PWM、单脉冲模式等不同应用场景。
-
外部信号控制和同步:
- 支持使用外部信号控制定时器的启动、停止和重装载功能,以及多个定时器之间的同步。
- 可以实现编码器接口和霍尔传感器等外部设备的连接和控制。
通用定时器在嵌入式系统中具有广泛的应用,可以用于实现各种定时、计数、PWM输出等功能。在STM32F1系列中,TIM2至TIM5是通用定时器,提供了丰富的功能和灵活的配置选项,适用于各种不同的应用场景。
3.2、通用定时器框图
当使用通用定时器(如TIM2、TIM3、TIM4、TIM5)时,以下是关于框图中所示的各个模块的详细说明:
-
时钟源(CLK):
- 时钟源是定时器的输入时钟,它可以是来自内部时钟源(如系统时钟)或外部时钟源(如外部触发输入或其他定时器)。
- 定时器的工作频率由时钟源决定,可以通过预分频器来对时钟源进行分频以得到所需的计数频率。
-
控制器(Control):
- 控制器模块用于配置和控制定时器的各种功能。它包括预分频器(Prescaler)、自动重装载寄存器(Auto-Reload Register)和其他控制寄存器。
- 预分频器用于将时钟源的频率分频,以获取更低的计数频率。
- 自动重装载寄存器确定定时器的溢出时间,即定时器计数器从0开始重新计数的值。
-
时基单元(Counter):
- 时基单元是定时器的核心部分,它实现了定时器的计数功能。
- 计数器可以递增或递减,根据配置可以选择不同的计数方向和计数模式(例如,向上计数、向下计数或中心对齐计数)。
-
输入捕获(Input Capture):
- 输入捕获模块用于捕获外部信号的时间戳。
- 它可以用于测量外部信号的脉冲宽度、周期或频率等特性。
-
捕获/比较(Capture/Compare):
- 捕获/比较模块可以用于捕获外部信号或执行比较操作。
- 可以配置该模块以实现输入捕获、输出比较或PWM输出等功能。
-
输出比较(Output Compare):
- 输出比较模块用于产生PWM输出信号,控制外部设备的电平或速度。
- 可以通过配置比较器来生成周期性的PWM信号,调节PWM信号的占空比和频率。
这些模块可以根据具体的应用需求进行灵活配置和组合,以实现各种定时和控制功能。通用定时器具有丰富的功能和灵活的配置选项,适用于各种应用场景。
ETR(External Trigger)和通道CH1/2/3/4引脚
在STM32通用定时器(TIM2/TIM3/TIM4/TIM5)中,ETR(External Trigger)和CH1/2/3/4引脚具有不同的作用和功能:
-
ETR(外部触发输入):
- ETR引脚用于接收外部触发信号,可以控制定时器的启动、停止和重装载等功能。
- 通过配置ETR源和极性,可以实现对定时器的外部触发控制。
- ETR可以配置为外部时钟源,以替代内部时钟源,从而实现外部时钟的同步和控制。
-
CH1/2/3/4引脚(通道输入/输出):
- CH1/2/3/4引脚用于定时器的通道输入和输出功能。
- 作为输入,这些通道可以用于输入捕获和PWM输入模式,用于测量外部信号的脉冲宽度或频率。
- 作为输出,这些通道可以用于PWM输出模式,产生周期性的PWM信号,控制外部设备的电平或速度。
总之,ETR用于接收外部触发信号,控制定时器的启动和停止,而CH1/2/3/4引脚用于定时器的通道输入和输出功能,支持输入捕获、PWM输入和PWM输出等不同的应用场景。这些引脚的灵活配置可以满足各种定时器应用的需求。
3.3、计数器时钟源
计数器时钟源是指定时器用于计数的时钟信号来源。在通用定时器中,计数器时钟源可以有以下几种选择:
-
内部时钟(CK_INT):
- 内部时钟源来自外设总线APB提供的时钟。
- 这种时钟源是定时器的默认时钟源,通常被用于大多数应用场景。
-
外部时钟模式1:外部输入引脚(TIx):
- 定时器的计数器时钟可以来自于定时器通道1或通道2引脚的外部信号。
- 通过将定时器设置为外部时钟模式1,并连接外部信号到相应的定时器通道引脚,可以实现基于外部信号的定时器计数。
-
外部时钟模式2:外部触发输入(ETR):
- 外部触发输入是指定时器的计数器时钟来自于可以复用为TIMx_ETR的IO引脚的外部信号。
- 这种模式下,定时器通过ETR引脚接收外部信号作为计数器的时钟源,从而实现基于外部触发的定时器计数。
-
内部触发输入(ITRx):
- 内部触发输入用于与芯片内部其他通用/高级定时器级联。
- 通过连接一个定时器的输出到另一个定时器的输入引脚,可以实现级联计数器的功能,即一个定时器的计数器溢出会触发另一个定时器的计数。
这些时钟源可以根据具体的应用需求进行配置和选择,以实现不同的定时功能和时钟来源。
gtim.c
#include "./BSP/TIMER/gtim.h"
#include "./BSP/LED/led.h"
TIM_HandleTypeDef g_timx_handle; /* 定时器x句柄 */
/**
* @brief 通用定时器TIMX定时中断初始化函数
* @note
* 通用定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
* 通用定时器的时钟为APB1时钟的2倍, 而APB1为36M, 所以定时器时钟 = 72Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param arr: 自动重装值。
* @param psc: 时钟预分频数
* @retval 无
*/
void gtim_timx_int_init(uint16_t arr, uint16_t psc)
{
GTIM_TIMX_INT_CLK_ENABLE(); /* 使能TIMx时钟 */
g_timx_handle.Instance = GTIM_TIMX_INT; /* 通用定时器x */
g_timx_handle.Init.Prescaler = psc; /* 预分频系数 */
g_timx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_handle.Init.Period = arr; /* 自动装载值 */
HAL_TIM_Base_Init(&g_timx_handle);
HAL_NVIC_SetPriority(GTIM_TIMX_INT_IRQn, 1, 3); /* 设置中断优先级,抢占优先级1,子优先级3 */
HAL_NVIC_EnableIRQ(GTIM_TIMX_INT_IRQn); /* 开启ITMx中断 */
HAL_TIM_Base_Start_IT(&g_timx_handle); /* 使能定时器x和定时器x更新中断 */
}
/**
* @brief 定时器中断服务函数
* @param 无
* @retval 无
*/
void GTIM_TIMX_INT_IRQHandler(void)
{
/* 以下代码没有使用定时器HAL库共用处理函数来处理,而是直接通过判断中断标志位的方式 */
if(__HAL_TIM_GET_FLAG(&g_timx_handle, TIM_FLAG_UPDATE) != RESET)
{
LED1_TOGGLE();
__HAL_TIM_CLEAR_IT(&g_timx_handle, TIM_IT_UPDATE); /* 清除定时器溢出中断标志位 */
}
}
gtim.h
#ifndef __GTIM_H
#define __GTIM_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 通用定时器 定义 */
/* TIMX 中断定义
* 默认是针对TIM2~TIM5.
* 注意: 通过修改这4个宏定义,可以支持TIM1~TIM8任意一个定时器.
*/
#define GTIM_TIMX_INT TIM3
#define GTIM_TIMX_INT_IRQn TIM3_IRQn
#define GTIM_TIMX_INT_IRQHandler TIM3_IRQHandler
#define GTIM_TIMX_INT_CLK_ENABLE() do{ __HAL_RCC_TIM3_CLK_ENABLE(); }while(0) /* TIM3 时钟使能 */
/******************************************************************************************/
void gtim_timx_int_init(uint16_t arr, uint16_t psc); /* 通用定时器 定时中断初始化函数 */
#endif
这个通用定时器中断实验的代码实现了一个简单的定时器中断功能。让我们来逐步解读这个实验的主要部分:
-
宏定义部分:
- 定义了通用定时器TIM3的相关宏,包括中断号、中断处理函数名以及时钟使能的宏。
-
函数声明部分:
gtim_timx_int_init(uint16_t arr, uint16_t psc)
: 这个函数声明了通用定时器的定时中断初始化函数。它需要传入自动重装载值(arr
)和时钟预分频数(psc
)作为参数。
-
定时器中断初始化函数 (
gtim_timx_int_init
):- 这个函数的作用是初始化通用定时器TIM3的定时中断功能。
- 首先,使用了
__HAL_RCC_TIM3_CLK_ENABLE()
宏来使能TIM3的时钟。 - 然后,设置了通用定时器句柄
g_timx_handle
的相关参数,包括预分频系数和自动重装载值。 - 接着,调用了
HAL_TIM_Base_Init()
函数进行通用定时器的初始化。 - 使用
HAL_NVIC_SetPriority()
和HAL_NVIC_EnableIRQ()
函数设置了定时器中断的优先级和使能。 - 最后,调用
HAL_TIM_Base_Start_IT()
函数使能定时器TIM3和更新中断。
-
定时器中断服务函数 (
GTIM_TIMX_INT_IRQHandler
):- 这个函数中实现了定时器中断的具体操作。
- 首先,通过
__HAL_TIM_GET_FLAG()
函数检查是否发生了定时器溢出事件(Update Event)。 - 如果发生了溢出事件,就执行LED的翻转操作,并清除中断标志位。
这个实验主要展示了如何使用通用定时器TIM3来实现定时中断功能,并在中断服务函数中控制LED的翻转。通过合理设置自动重装载值和预分频系数,可以达到精确的定时效果。
3.4、通用定时器PWM输出实验
3.4.1、通用定时器输出比较部分框图介绍
3.4.2、通用定时器输出PWM原理
通用定时器可以用于产生PWM(脉冲宽度调制)信号,其原理如下:
-
时钟源选择:
- 通用定时器的时钟源可以是内部时钟(如APB1时钟)或外部时钟(来自外部引脚的信号)。
-
预分频器设置:
- 预分频器用于将时钟源的频率分频,以得到所需的计数频率。通常情况下,PWM的频率是固定的,因此需要通过预分频器来调整定时器的计数频率。
-
自动重装载寄存器(ARR)设置:
- 自动重装载寄存器决定了定时器的溢出周期,也即PWM的周期。通过调整ARR的值,可以调节PWM的周期。
-
占空比控制:
- PWM的占空比由比较寄存器(CCR)的值决定。CCR的值表示了PWM的高电平持续时间,而ARR则表示了一个完整的PWM周期。因此,占空比可以通过调整CCR的值来控制。
-
工作模式配置:
- 通用定时器需要配置为PWM模式,以确保它以PWM方式工作。在PWM模式下,定时器的计数会从0开始,计数到ARR时会产生一个更新事件,并在达到CCR时产生比较匹配事件。
-
输出设置:
- PWM的输出通常连接到微控制器的输出引脚上,通过配置GPIO引脚的工作模式和输出类型,可以将PWM信号输出到外部设备或用于控制电机、LED等。
综上所述,通用定时器通过配置预分频器、自动重装载寄存器、比较寄存器和工作模式,可以产生具有可调节占空比的PWM信号。这种PWM信号可以用于控制各种应用,如电机速度控制、LED亮度调节、温度控制等。
通用定时器输出PWM的原理如下:
-
ARR(自动重装载寄存器):决定了PWM波的周期,也就是一个完整的PWM波从开始到结束所经历的时间。
-
CCRx(捕获/比较寄存器):决定了PWM波的占空比,即PWM波中高电平的持续时间。
当定时器的计数值(CNT)小于捕获/比较寄存器的值(CCRx)时,输出为逻辑0;当计数值大于等于捕获/比较寄存器的值时,输出为逻辑1。
通过调节ARR和CCRx的值,可以灵活控制PWM波的周期和占空比,从而实现对PWM信号的精确调节。这种方式非常适合用于各种应用中,如电机驱动、LED调光、温度控制等。
3.4.3、PWM模式
PWM模式1:
- 递增模式:当计数器CNT小于捕获/比较寄存器的值(CCRx)时,输出有效电平;当CNT大于等于CCRx时,输出无效电平。
- 递减模式:当CNT大于捕获/比较寄存器的值(CCRx)时,输出无效电平;当CNT小于等于CCRx时,输出有效电平。
PWM模式2:
- 递增模式:当CNT小于捕获/比较寄存器的值(CCRx)时,输出无效电平;当CNT大于等于CCRx时,输出有效电平。
- 递减模式:当CNT大于捕获/比较寄存器的值(CCRx)时,输出有效电平;当CNT小于等于CCRx时,输出无效电平。
在PWM模式中,输出信号的有效状态和无效状态取决于TIMx_CCER寄存器的设置:
- 若CCxP位为0,则表示输出为高电平有效,即当输出为有效时,输出为高电平;当输出为无效时,输出为低电平。
- 若CCxP位为1,则表示输出为低电平有效,即当输出为有效时,输出为低电平;当输出为无效时,输出为高电平。
这些模式提供了灵活的PWM信号输出方式,可根据具体的应用需求进行选择和配置。
有效电平和无效电平
在PWM输出中,有效电平和无效电平是指信号处于逻辑高电平或逻辑低电平的状态。
-
有效电平:指信号处于能够有效驱动外部电路的状态,即在此状态下,输出能够产生所需的电平变化,执行相应的功能。在PWM输出中,有效电平对应于所期望的信号状态,用于传输数据或控制外部设备。
-
无效电平:指信号处于不能有效驱动外部电路的状态,即在此状态下,输出不能产生所需的电平变化,不能执行相应的功能。在PWM输出中,无效电平通常是信号处于空闲状态或不进行传输数据的状态。
这些术语在描述信号的状态和行为时经常使用,特别是在数字电路和通信领域中。在PWM输出中,通过控制有效电平和无效电平的时序和持续时间,可以实现不同的信号传输和控制功能。
3.4.4、通用定时器PWM输出实验配置步骤
这些步骤是配置通用定时器以产生PWM输出的基本步骤。以下是这些步骤的详细解释:
-
配置定时器基础工作参数:
使用HAL库的HAL_TIM_PWM_Init()
函数初始化通用定时器,并配置其基本工作参数,如时钟源、预分频器、计数器模式等。 -
定时器PWM输出MSP初始化:
编写定时器PWM输出的MSP(MCU特定部分)初始化函数HAL_TIM_PWM_MspInit()
。在这个函数中,配置定时器相关的中断、时钟、GPIO等。 -
配置PWM模式/比较值等:
使用HAL_TIM_PWM_ConfigChannel()
函数配置PWM输出的通道,包括PWM模式、占空比、极性等参数。 -
使能输出并启动计数器:
调用HAL_TIM_PWM_Start()
函数使能PWM输出并启动定时器计数。 -
修改比较值控制占空比(可选):
可以通过修改比较值来控制PWM的占空比。使用__HAL_TIM_SET_COMPARE()
函数设置比较值。 -
使能通道预装载(可选):
可以通过使能通道预装载来提高PWM输出的稳定性。使用__HAL_TIM_ENABLE_OCxPRELOAD()
函数使能通道预装载。
通过以上步骤,您可以配置通用定时器以产生所需的PWM输出信号,并根据需要调整占空比和其他参数。
HAL_TIM_PWM_ConfigChannel()
__HAL_TIM_SET_COMPARE()
3.4.5、编程实战:通用定时器PWM输出实验
gtim.c
/*
* @brief 通用定时器PWM输出初始化函数
* @param arr: 自动重装载值。
* @param psc: 时钟预分频数
* @retval 无
*/
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc)
{
TIM_OC_InitTypeDef timx_oc_pwm_chy;
g_timx_pwm_chy_handle.Instance = TIM3; /* 定时器3 */
g_timx_pwm_chy_handle.Init.Prescaler = psc; /* 预分频系数 */
g_timx_pwm_chy_handle.Init.Period = arr; /* 自动装载值 */
g_timx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle); /* 初始化PWM */
timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1; /* PWM模式1 */
timx_oc_pwm_chy.Pulse = arr / 2; /* 占空比设置为50% */
timx_oc_pwm_chy.OCPolarity = TIM_OCPOLARITY_LOW; /* 输出低电平有效 */
HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle, &timx_oc_pwm_chy, TIM_CHANNEL_2); /* 配置通道2 */
HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, TIM_CHANNEL_2); /* 启动PWM输出 */
}
/*
* @brief 定时器输出PWM MSP初始化函数
* @param htim: 定时器句柄
* @retval 无
*/
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM3) /* 仅当定时器为TIM3时进行初始化 */
{
GPIO_InitTypeDef gpio_init_struct;
/* 使能GPIOB时钟 */
__HAL_RCC_GPIOB_CLK_ENABLE();
/* 使能TIM3时钟 */
__HAL_RCC_TIM3_CLK_ENABLE();
gpio_init_struct.Pin = GPIO_PIN_5; /* GPIOB Pin5 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 推挽复用 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(GPIOB, &gpio_init_struct); /* 初始化GPIOB */
/* 使能AFIO时钟 */
__HAL_RCC_AFIO_CLK_ENABLE();
/* 重新映射TIM3部分引脚 */
__HAL_AFIO_REMAP_TIM3_PARTIAL();
}
}
gtim.h
#ifndef __GTIM_H
#define __GTIM_H
#include "./SYSTEM/sys/sys.h"
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc);
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/gtim.h"
extern TIM_HandleTypeDef g_timx_pwm_chy_handle; /* 定时器x句柄 */
int main(void)
{
uint16_t ledrpwmval = 0;
uint8_t dir = 1;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟,72M */
delay_init(72); /* 初始化延时函数 */
led_init(); /* 初始化LED */
gtim_timx_pwm_chy_init(500 - 1, 72 - 1);
while(1)
{
delay_ms(10);
if (dir)ledrpwmval++; /* dir==1 ledrpwmval递增 */
else ledrpwmval--; /* dir==0 ledrpwmval递减 */
if (ledrpwmval > 300)dir = 0; /* ledrpwmval到达300后,方向为递减 */
if (ledrpwmval == 0)dir = 1; /* ledrpwmval递减到0后,方向改为递增 */
/* 修改比较值控制占空比 */
__HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle, TIM_CHANNEL_2, ledrpwmval);
}
}
3.5、通用定时器输入捕获实验
3.5.1、通用定时器输入捕获部分框图介绍
通用定时器的输入捕获功能主要由捕获/比较通道1负责。以下是其主要组成部分的介绍:
-
捕获/比较通道1的输入部分:
- 输入部分包括定时器引脚和输入捕获电路。
- 定时器引脚用于接收外部信号,可以配置为触发捕获事件的输入引脚(例如TIMx_CH1引脚)。
- 输入捕获电路负责检测定时器引脚上的信号边沿,以确定捕获事件的发生时间。
-
捕获/比较通道1的主电路:
- 主电路包括输入捕获单元、捕获/比较寄存器和捕获/比较控制逻辑。
- 输入捕获单元通过对定时器引脚的信号进行采样和比较,检测到信号的边沿(上升沿或下降沿)。
- 捕获/比较寄存器用于存储捕获到的时间戳或比较值。
- 捕获/比较控制逻辑根据输入捕获单元的输出和捕获/比较寄存器的设置,判断捕获事件的发生,并触发相应的中断或DMA请求。
通过这些组成部分,捕获/比较通道1能够准确地捕获外部信号的边沿,并将捕获的时间信息存储到捕获/比较寄存器中,以供后续处理或应用。
捕获/比较通道(通道1)的输入部分涉及到以下几个方面的设置和配置:
-
输入通道映射:
- 选择定时器的输入引脚,将其映射到捕获/比较通道1。这通常通过GPIO的复用功能实现,将定时器输入引脚的功能设置为捕获/比较通道1。
-
滤波方式选择:
- 可以选择是否启用输入滤波器以消除输入信号中的噪声。滤波器可以设置为不同的模式,例如不滤波、2个采样时钟周期、4个采样时钟周期等。
-
采样频率 CKD[1:0]:
- CKD[1:0]位用于配置捕获/比较通道的采样频率。这些位用于控制捕获/比较寄存器的更新方式。根据不同的设置,捕获/比较通道可以在每个或每隔一个输入信号的边沿进行采样。
-
选择边沿检测方式:
- 可以选择捕获/比较通道的边沿检测方式,例如上升沿、下降沿或两种边沿都检测。这决定了在输入信号的何种边沿触发捕获事件。
-
分频后的捕获信号:
- 可以选择是否对输入信号进行预分频。预分频可以将输入信号的频率降低,以适应定时器的工作频率范围。
-
捕获分频:
- 可以配置捕获信号的分频比例,以调整捕获事件的精度和分辨率。
-
使能捕获:
- 最后一步是使能捕获/比较通道,启动捕获功能,使定时器能够根据输入信号的边沿进行捕获,并将捕获的时间信息存储到相应的寄存器中。
通过以上设置和配置,可以有效地配置捕获/比较通道(通道1)的输入部分,实现对外部信号的准确捕获和处理。
捕获/比较通道1的主电路的输入部分涉及到一系列关键配置和功能,下面是对每个部分的解释:
-
CC1S配置为输入:
- CC1S位用于配置捕获/比较通道1的输入源。将其设置为输入意味着捕获/比较通道1将从外部引脚获取输入信号。
-
CC1E捕获使能:
- CC1E位用于使能捕获/比较通道1。启用此位后,捕获/比较通道1将开始捕获输入信号,并将捕获的时间信息存储到相应的寄存器中。
-
IC1PS捕获到信号:
- IC1PS位用于配置捕获输入的预分频比例。这可以帮助调整捕获事件的精度和分辨率,根据输入信号的频率和定时器的工作频率进行适当的调整。
-
CC1G软件产生捕获事件TIM1_EGR:
- CC1G位用于通过软件生成捕获事件。产生捕获事件后,捕获/比较通道1将进行捕获操作,捕获输入信号的状态,并将捕获的时间信息写入相关的寄存器中。
-
CCR1读操作read_in_progress:
- 在读取捕获/比较通道1的捕获值时,可能需要检查CCR1寄存器是否正在进行读操作。这个标志位指示着当前是否正在进行读取操作,以确保读取操作的正确性。
-
capture_transfer不可直接访问捕获/比较影子寄存器要访问捕获/比较预装载寄存器:
- 这是关于捕获/比较通道1捕获值的访问说明。通常,要访问捕获值,需要访问捕获/比较预装载寄存器而不是直接访问影子寄存器。这确保了在捕获值被更新之前,仍然可以访问到原始的捕获值。
通过这些配置和功能,可以有效地控制捕获/比较通道1的输入部分,实现对外部信号的准确捕获和处理。
3.5.2、通用定时器输入捕获脉宽测量原理
通用定时器的输入捕获功能可用于测量外部信号的脉冲宽度。以下是脉宽测量的基本原理:
-
配置捕获通道:
首先,需要配置通用定时器的一个捕获通道用于接收外部信号。捕获通道的输入源应该连接到要测量脉冲宽度的外部信号源。 -
捕获起始边沿:
当外部信号触发定时器的捕获通道时,定时器将记录捕获时间。通常,捕获操作在外部信号的上升沿或下降沿触发,具体取决于应用需求。 -
记录起始时间:
定时器记录捕获事件的时间戳,即捕获时间。这个时间戳表示了外部信号的起始边沿到达时定时器的计数器值。 -
等待结束边沿:
定时器继续计数,等待外部信号的结束边沿。通常,外部信号的结束边沿会触发另一个捕获事件。 -
记录结束时间:
当定时器捕获到外部信号的结束边沿时,再次记录捕获事件的时间戳。这个时间戳表示了外部信号的结束边沿到达时定时器的计数器值。 -
计算脉冲宽度:
脉冲宽度可以通过结束时间减去起始时间来计算得到。这个时间差乘以定时器的计数器时钟周期,即可得到以时间单位表示的脉冲宽度。 -
处理测量结果:
测量得到的脉冲宽度可以根据应用需求进行进一步处理,例如进行单位转换、进行平均值计算或进行其他相关操作。
通过这个流程,通用定时器的输入捕获功能可以有效地测量外部信号的脉冲宽度,从而实现精确的时间测量和信号分析。
捕获测量高电平脉宽的情况下
在捕获测量高电平脉宽的情况下,可以通过以下步骤进行计算:
-
计算高电平持续时间:
首先,需要计算捕获到的两个边沿之间的计数器计数次数,即 N × ( A R R + 1 ) + C C R x 2 N \times (ARR+1) + CCRx2 N×(ARR+1)+CCRx2。其中, N N N 表示捕获到的溢出次数, A R R ARR ARR 是自动重装载寄存器的值, C C R x 2 CCRx2 CCRx2 是捕获寄存器的值。这个计数代表了高电平信号持续的时间。 -
转换为时间单位:
将计数器的计数次数转换为时间单位。这需要考虑定时器的时钟频率和预分频值。具体地,可以使用下列公式将计数器的计数转换为时间:
脉冲宽度 = 计数 × 计数器时钟周期 定时器时钟频率 \text{脉冲宽度} = \frac{{\text{计数} \times \text{计数器时钟周期}}}{\text{定时器时钟频率}} 脉冲宽度=定时器时钟频率计数×计数器时钟周期
通过这个计算过程,可以得到捕获测量的高电平脉宽,从而实现对高电平持续时间的精确测量。
3.5.3、通用定时器输入捕获实验配置步骤
这是通用定时器输入捕获实验的基本配置步骤:
-
配置定时器基础工作参数:
使用 HAL 库提供的HAL_TIM_IC_Init()
函数初始化定时器。 -
定时器输入捕获MSP初始化:
编写定时器输入捕获的MSP初始化函数HAL_TIM_IC_MspInit()
,在其中配置 NVIC、时钟、GPIO等。 -
配置输入通道映射和捕获边沿:
使用HAL_TIM_IC_ConfigChannel()
函数配置输入通道映射和捕获边沿。这一步可以设置捕获边沿的触发方式,例如上升沿、下降沿或者双边沿触发。 -
设置优先级,使能中断:
使用HAL_NVIC_SetPriority()
和HAL_NVIC_EnableIRQ()
函数设置中断优先级并使能中断。在这个步骤中,需要为定时器的中断分配合适的优先级,并使能定时器的中断。 -
使能定时器更新中断:
使用__HAL_TIM_ENABLE_IT()
函数使能定时器的更新中断。这一步是为了确保在计数器溢出时能够触发中断,以便及时处理。 -
使能捕获、捕获中断及计数器:
使用HAL_TIM_IC_Start_IT()
函数使能捕获、捕获中断及计数器。这个函数会启动捕获功能,并使能捕获中断和计数器,以便开始捕获输入信号的边沿变化。 -
编写中断服务函数:
编写定时器中断服务函数TIMx_IRQHandler()
,在其中调用 HAL 库提供的HAL_TIM_IRQHandler()
函数。 -
编写更新中断和捕获回调函数:
编写定时器的更新中断和捕获回调函数,分别是HAL_TIM_PeriodElapsedCallback()
和HAL_TIM_IC_CaptureCallback()
。在这些回调函数中,可以实现相应的功能逻辑,例如处理定时器的更新事件或捕获到的信号。
通过以上步骤,可以完成通用定时器输入捕获实验的配置和初始化工作。
3.5.4、编程实战:通用定时器输入捕获实验
gtim.c
#include "./BSP/TIMER/gtim.h"
TIM_HandleTypeDef g_timx_cap_chy_handle; /* 定时器x句柄 */
/* 通用定时器通道y 输入捕获 初始化函数 */
void gtim_timx_cap_chy_init(uint16_t arr, uint16_t psc)
{
TIM_IC_InitTypeDef timx_ic_cap_chy = {0};
g_timx_cap_chy_handle.Instance = TIM5; /* 定时器5 */
g_timx_cap_chy_handle.Init.Prescaler = psc; /* 定时器分频 */
g_timx_cap_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_cap_chy_handle.Init.Period = arr; /* 自动重装载值 */
HAL_TIM_IC_Init(&g_timx_cap_chy_handle);
timx_ic_cap_chy.ICPolarity = TIM_ICPOLARITY_RISING; /* 上升沿捕获 */
timx_ic_cap_chy.ICSelection = TIM_ICSELECTION_DIRECTTI; /* 映射到TI1上 */
timx_ic_cap_chy.ICPrescaler = TIM_ICPSC_DIV1; /* 配置输入分频,不分频 */
timx_ic_cap_chy.ICFilter = 0; /* 配置输入滤波器,不滤波 */
HAL_TIM_IC_ConfigChannel(&g_timx_cap_chy_handle, &timx_ic_cap_chy, TIM_CHANNEL_1); /* 配置TIM5通道1 */
__HAL_TIM_ENABLE_IT(&g_timx_cap_chy_handle, TIM_IT_UPDATE); /* 使能更新中断 */
HAL_TIM_IC_Start_IT(&g_timx_cap_chy_handle, TIM_CHANNEL_1); /* 开始捕获TIM5的通道1 */
}
/* 定时器 输入捕获 MSP初始化函数 */
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM5) /*输入通道捕获*/
{
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_TIM5_CLK_ENABLE(); /* 使能TIM5时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启捕获IO的时钟 */
gpio_init_struct.Pin = GPIO_PIN_0;
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
HAL_NVIC_SetPriority(TIM5_IRQn, 1, 3); /* 抢占1,子优先级3 */
HAL_NVIC_EnableIRQ(TIM5_IRQn); /* 开启ITMx中断 */
}
}
/* 输入捕获状态(g_timxchy_cap_sta)
* [7] :0,没有成功的捕获;1,成功捕获到一次.
* [6] :0,还没捕获到高电平;1,已经捕获到高电平了.
* [5:0]:捕获高电平后溢出的次数,最多溢出63次,所以最长捕获值 = 63*65536 + 65535 = 4194303
* 注意:为了通用,我们默认ARR和CCRy都是16位寄存器,对于32位的定时器(如:TIM5),也只按16位使用
* 按1us的计数频率,最长溢出时间为:4194303 us, 约4.19秒
*
* (说明一下:正常32位定时器来说,1us计数器加1,溢出时间:4294秒)
*/
uint8_t g_timxchy_cap_sta = 0; /* 输入捕获状态 */
uint16_t g_timxchy_cap_val = 0; /* 输入捕获值 */
/* 定时器5中断服务函数 */
void TIM5_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_timx_cap_chy_handle); /* 定时器HAL库共用处理函数 */
}
/* 定时器输入捕获中断处理回调函数 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM5)
{
if ((g_timxchy_cap_sta & 0X80) == 0) /* 还没有成功捕获 */
{
if (g_timxchy_cap_sta & 0X40) /* 捕获到一个下降沿 */
{
g_timxchy_cap_sta |= 0X80; /* 标记成功捕获到一次高电平脉宽 */
g_timxchy_cap_val = HAL_TIM_ReadCapturedValue(&g_timx_cap_chy_handle, TIM_CHANNEL_1); /* 获取当前的捕获值 */
TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1); /* 一定要先清除原来的设置 */
TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING); /* 配置TIM5通道1上升沿捕获 */
}
else /* 还未开始,第一次捕获上升沿 */
{
g_timxchy_cap_sta = 0; /* 清空 */
g_timxchy_cap_val = 0;
g_timxchy_cap_sta |= 0X40; /* 标记捕获到了上升沿 */
__HAL_TIM_DISABLE(&g_timx_cap_chy_handle); /* 关闭定时器5 */
__HAL_TIM_SET_COUNTER(&g_timx_cap_chy_handle, 0); /* 定时器5计数器清零 */
TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1); /* 一定要先清除原来的设置!! */
TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING); /* 定时器5通道1设置为下降沿捕获 */
__HAL_TIM_ENABLE(&g_timx_cap_chy_handle); /* 使能定时器5 */
}
}
}
}
/* 定时器更新中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM5)
{
if ((g_timxchy_cap_sta & 0X80) == 0) /* 还未成功捕获 */
{
if (g_timxchy_cap_sta & 0X40) /* 已经捕获到高电平了 */
{
if ((g_timxchy_cap_sta & 0X3F) == 0X3F) /* 高电平太长了 */
{
TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1); /* 一定要先清除原来的设置 */
TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING);/* 配置TIM5通道1上升沿捕获 */
g_timxchy_cap_sta |= 0X80; /* 标记成功捕获了一次 */
g_timxchy_cap_val = 0XFFFF;
}
else /* 累计定时器溢出次数 */
{
g_timxchy_cap_sta++;
}
}
}
}
}
gtim.h
#ifndef __GTIM_H
#define __GTIM_H
#include "./SYSTEM/sys/sys.h"
void gtim_timx_cap_chy_init(uint16_t arr, uint16_t psc);
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/gtim.h"
extern uint8_t g_timxchy_cap_sta; /* 输入捕获状态 */
extern uint16_t g_timxchy_cap_val; /* 输入捕获值 */
int main(void)
{
uint32_t temp = 0;
uint8_t t = 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
gtim_timx_cap_chy_init(0XFFFF, 72 - 1); /* 以1Mhz的频率计数 捕获 */
while (1)
{
if (g_timxchy_cap_sta & 0X80) /* 成功捕获到了一次高电平 */
{
temp = g_timxchy_cap_sta & 0X3F;
temp *= 65536; /* 溢出时间总和 */
temp += g_timxchy_cap_val; /* 得到总的高电平时间 */
printf("HIGH:%d us\r\n", temp); /* 打印总的高点平时间 */
g_timxchy_cap_sta = 0; /* 开启下一次捕获*/
}
t++;
if (t > 20) /* 200ms进入一次 */
{
t = 0;
LED0_TOGGLE(); /* LED0闪烁 ,提示程序运行 */
}
delay_ms(10);
}
}
3.6、通用定时器脉冲计数实验
3.6.1、脉冲计数实验原理
脉冲计数实验是利用通用定时器的输入捕获功能来测量输入信号的脉冲数量或频率的实验。其原理如下:
-
输入信号:
通用定时器的输入捕获通道连接到外部信号源,该信号源产生脉冲信号,其脉冲数量或频率需要测量。 -
定时器配置:
- 配置定时器的输入捕获通道,选择合适的输入通道和捕获边沿。通常选择上升沿或下降沿触发。
- 配置定时器的计数模式和计数范围,以及预分频器的分频系数,以适应输入信号的频率范围。
-
捕获脉冲:
当输入信号产生一个脉冲时,定时器的捕获通道会检测到信号的边沿变化,并记录下捕获时刻的计数器的值。捕获的计数器值随后会被保存在捕获寄存器中。 -
计算脉冲数量或频率:
- 如果是测量脉冲数量,可以记录下多个捕获脉冲的计数器值,并计算相邻捕获值之间的差值,最终得到脉冲数量。
- 如果是测量脉冲频率,可以根据捕获的计数器值和捕获间隔时间计算脉冲的频率。
-
显示或输出结果:
将测量得到的脉冲数量或频率显示在LED、LCD或者通过串口输出等方式展示出来,以便观察和分析。
通过以上步骤,可以实现对输入信号脉冲数量或频率的准确测量,从而完成脉冲计数实验。
3.6.2、通用定时器脉冲计数实验配置步骤
配置通用定时器进行脉冲计数实验的步骤如下:
-
配置定时器基础工作参数:
使用HAL库的HAL_TIM_IC_Init()
函数初始化通用定时器的输入捕获功能。 -
定时器输入捕获MSP初始化:
编写HAL_TIM_IC_MspInit()
函数,配置定时器的中断优先级、时钟、GPIO等外设。 -
配置定时器从模式:
如果需要,使用HAL_TIM_SlaveConfigSynchro()
函数配置定时器从模式,使其与主定时器同步。 -
使能输入捕获并启动计数器:
使用HAL_TIM_IC_Start()
函数使能输入捕获功能,并启动定时器计数器。 -
获取计数器的值:
如果需要读取当前计数器的值,可以使用__HAL_TIM_GET_COUNTER()
宏。 -
设置计数器的值:
如果需要修改计数器的值,可以使用__HAL_TIM_SET_COUNTER()
宏。
通过以上步骤,可以完成通用定时器的脉冲计数实验的配置。在配置完成后,定时器将会记录输入信号的脉冲数量,并且可以通过相应的函数获取计数器的值进行后续处理。
HAL_TIM_SlaveConfigSynchro()
HAL_TIM_SlaveConfigSynchro()
函数用于配置定时器的从模式,使其与主定时器同步。在某些应用场景下,需要多个定时器之间保持同步,这时可以使用从模式将一个定时器设置为主定时器,其他定时器设置为从定时器,以保持它们的计数同步。
函数原型如下所示:
HAL_StatusTypeDef HAL_TIM_SlaveConfigSynchro(TIM_HandleTypeDef *htim, TIM_SlaveConfigTypeDef *sSlaveConfig)
其中,参数htim
是指向定时器句柄的指针,而sSlaveConfig
是一个结构体,用于配置从模式的各项参数,例如:
SlaveMode
: 选择从模式。InputTrigger
: 选择输入触发源。TriggerPolarity
: 设置输入触发源的极性。TriggerPrescaler
: 设置输入触发源的预分频系数。TriggerFilter
: 设置输入触发源的滤波器。
通过调用该函数,可以实现定时器之间的同步,确保它们在计数上保持一致。
HAL_StatusTypeDef HAL_TIM_SlaveConfigSynchro(TIM_HandleTypeDef *htim, TIM_SlaveConfigTypeDef *sSlaveConfig)
{
/* 检查参数的有效性 */
assert_param(IS_TIM_SLAVE_INSTANCE(htim->Instance));
assert_param(IS_TIM_SLAVE_MODE(sSlaveConfig->SlaveMode));
assert_param(IS_TIM_TRIGGER_SELECTION(sSlaveConfig->InputTrigger));
/* 锁定定时器资源 */
__HAL_LOCK(htim);
/* 设置定时器状态为忙碌 */
htim->State = HAL_TIM_STATE_BUSY;
/* 设置从模式配置 */
if (TIM_SlaveTimer_SetConfig(htim, sSlaveConfig) != HAL_OK)
{
/* 配置失败,恢复定时器状态并解锁 */
htim->State = HAL_TIM_STATE_READY;
__HAL_UNLOCK(htim);
return HAL_ERROR;
}
/* 关闭触发中断 */
__HAL_TIM_DISABLE_IT(htim, TIM_IT_TRIGGER);
/* 关闭触发DMA请求 */
__HAL_TIM_DISABLE_DMA(htim, TIM_DMA_TRIGGER);
/* 设置定时器状态为就绪 */
htim->State = HAL_TIM_STATE_READY;
/* 解锁定时器资源 */
__HAL_UNLOCK(htim);
return HAL_OK;
}
3.6.3、编程实战:通用定时器脉冲计数实验
gtim.c
TIM_HandleTypeDef g_timx_cnt_chy_handle; // 定时器x句柄
/* 通用定时器通道y 脉冲计数 初始化函数 */
void gtim_timx_cnt_chy_init(uint16_t psc)
{
// 配置定时器参数
g_timx_cnt_chy_handle.Instance = TIM2;
g_timx_cnt_chy_handle.Init.Prescaler = psc;
g_timx_cnt_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
g_timx_cnt_chy_handle.Init.Period = 65535; // 定时器周期设置为最大值,以便捕获脉冲计数
// 初始化定时器
HAL_TIM_IC_Init(&g_timx_cnt_chy_handle);
// 配置定时器从模式
TIM_SlaveConfigTypeDef tim_salve_config = {0};
tim_salve_config.SlaveMode = TIM_SLAVEMODE_EXTERNAL1; // 从模式为外部触发模式
tim_salve_config.InputTrigger = TIM_TS_TI1F_ED; // 外部触发源选择为通道1的边沿检测模式
tim_salve_config.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING; // 触发极性为上升沿触发
tim_salve_config.TriggerFilter = 0; // 触发滤波器配置为0,即不进行滤波
HAL_TIM_SlaveConfigSynchro(&g_timx_cnt_chy_handle, &tim_salve_config); // 配置定时器从模式
// 启动输入捕获
HAL_TIM_IC_Start(&g_timx_cnt_chy_handle, TIM_CHANNEL_1); // 启动通道1的输入捕获
}
/* 定时器 输入捕获 MSP初始化函数 */
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
// 如果是定时器2
if(htim->Instance == TIM2)
{
// 使能GPIOA时钟和定时器2时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_TIM2_CLK_ENABLE();
// 配置GPIOA引脚0为推挽输出模式,下拉
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.Pin = GPIO_PIN_0;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_PULLDOWN;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
}
}
gtim.h
#ifndef __GTIM_H
#define __GTIM_H
#include "./SYSTEM/sys/sys.h"
void gtim_timx_cnt_chy_init(uint16_t psc);
#endif
key.c
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
/**
* @brief 按键初始化函数
* @param 无
* @retval 无
*/
void key_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
KEY0_GPIO_CLK_ENABLE(); /* KEY0时钟使能 */
KEY1_GPIO_CLK_ENABLE(); /* KEY1时钟使能 */
KEY2_GPIO_CLK_ENABLE(); /* KEY2时钟使能 */
WKUP_GPIO_CLK_ENABLE(); /* WKUP时钟使能 */
gpio_init_struct.Pin = KEY0_GPIO_PIN; /* KEY0引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(KEY0_GPIO_PORT, &gpio_init_struct); /* KEY0引脚模式设置,上拉输入 */
gpio_init_struct.Pin = KEY1_GPIO_PIN; /* KEY1引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(KEY1_GPIO_PORT, &gpio_init_struct); /* KEY1引脚模式设置,上拉输入 */
gpio_init_struct.Pin = KEY2_GPIO_PIN; /* KEY2引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(KEY2_GPIO_PORT, &gpio_init_struct); /* KEY2引脚模式设置,上拉输入 */
gpio_init_struct.Pin = WKUP_GPIO_PIN; /* WKUP引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(WKUP_GPIO_PORT, &gpio_init_struct); /* WKUP引脚模式设置,下拉输入 */
}
/**
* @brief 按键扫描函数
* @note 该函数有响应优先级(同时按下多个按键): WK_UP > KEY2 > KEY1 > KEY0!!
* @param mode:0 / 1, 具体含义如下:
* @arg 0, 不支持连续按(当按键按下不放时, 只有第一次调用会返回键值,
* 必须松开以后, 再次按下才会返回其他键值)
* @arg 1, 支持连续按(当按键按下不放时, 每次调用该函数都会返回键值)
* @retval 键值, 定义如下:
* KEY0_PRES, 1, KEY0按下
* KEY1_PRES, 2, KEY1按下
* KEY2_PRES, 3, KEY2按下
* WKUP_PRES, 4, WKUP按下
*/
uint8_t key_scan(uint8_t mode)
{
static uint8_t key_up = 1; /* 按键按松开标志 */
uint8_t keyval = 0;
if (mode) key_up = 1; /* 支持连按 */
if (key_up && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0 || WK_UP == 1)) /* 按键松开标志为1, 且有任意一个按键按下了 */
{
delay_ms(10); /* 去抖动 */
key_up = 0;
if (KEY0 == 0) keyval = KEY0_PRES;
if (KEY1 == 0) keyval = KEY1_PRES;
if (KEY2 == 0) keyval = KEY2_PRES;
if (WK_UP == 1) keyval = WKUP_PRES;
}
else if (KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && WK_UP == 0) /* 没有任何按键按下, 标记按键松开 */
{
key_up = 1;
}
return keyval; /* 返回键值 */
}
key.h
#ifndef __KEY_H
#define __KEY_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 引脚 定义 */
#define KEY0_GPIO_PORT GPIOE
#define KEY0_GPIO_PIN GPIO_PIN_4
#define KEY0_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
#define KEY1_GPIO_PORT GPIOE
#define KEY1_GPIO_PIN GPIO_PIN_3
#define KEY1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
#define KEY2_GPIO_PORT GPIOE
#define KEY2_GPIO_PIN GPIO_PIN_2
#define KEY2_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
#define WKUP_GPIO_PORT GPIOA
#define WKUP_GPIO_PIN GPIO_PIN_0
#define WKUP_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
/******************************************************************************************/
#define KEY0 HAL_GPIO_ReadPin(KEY0_GPIO_PORT, KEY0_GPIO_PIN) /* 读取KEY0引脚 */
#define KEY1 HAL_GPIO_ReadPin(KEY1_GPIO_PORT, KEY1_GPIO_PIN) /* 读取KEY1引脚 */
#define KEY2 HAL_GPIO_ReadPin(KEY2_GPIO_PORT, KEY2_GPIO_PIN) /* 读取KEY2引脚 */
#define WK_UP HAL_GPIO_ReadPin(WKUP_GPIO_PORT, WKUP_GPIO_PIN) /* 读取WKUP引脚 */
#define KEY0_PRES 1 /* KEY0按下 */
#define KEY1_PRES 2 /* KEY1按下 */
#define KEY2_PRES 3 /* KEY2按下 */
#define WKUP_PRES 4 /* KEY_UP按下(即WK_UP) */
void key_init(void); /* 按键初始化函数 */
uint8_t key_scan(uint8_t mode); /* 按键扫描函数 */
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/TIMER/gtim.h"
extern TIM_HandleTypeDef g_timx_cnt_chy_handle; // 外部定义定时器x句柄
int main(void)
{
uint16_t curcnt; // 当前计数器值
uint16_t oldcnt; // 上一次计数器值
uint8_t key; // 按键状态
uint8_t t = 0; // 计时变量
HAL_Init(); // 初始化HAL库
sys_stm32_clock_init(RCC_PLL_MUL9); // 设置时钟为72Mhz
delay_init(72); // 初始化延时函数
usart_init(115200); // 初始化串口波特率为115200
led_init(); // 初始化LED
key_init(); // 初始化按键
gtim_timx_cnt_chy_init(0); // 初始化定时器作为脉冲计数器
while (1)
{
key = key_scan(0); // 检测按键状态
if (key == KEY0_PRES)
{
__HAL_TIM_SET_COUNTER(&g_timx_cnt_chy_handle, 0); // 按下 KEY0 时清零计数器
}
curcnt = __HAL_TIM_GET_COUNTER(&g_timx_cnt_chy_handle); // 获取当前计数器值
if (oldcnt != curcnt) // 如果计数器值发生变化
{
oldcnt = curcnt; // 更新上一次的计数器值
printf("CNT:%d\r\n", oldcnt); // 打印当前计数器值到串口
}
t++; // 计时加一
if (t > 20) // 如果计时达到一定值
{
t = 0; // 重置计时
LED0_TOGGLE(); // LED0翻转状态
}
delay_ms(10); // 延时10ms
}
}
四、高级定时器
4.1、高级定时器简介(F1为例)
高级定时器(如TIM1/TIM8)是STM32中功能强大的定时器模块之一,具有许多特性,包括:
- 计数器类型: 支持16位递增、递减、中心对齐计数器,计数范围为0~65535。
- 预分频器: 可配置的16位预分频器,分频系数范围为1~65536。
- 应用范围: 可用于触发DAC、ADC等模块,适用于各种应用场景。
- 中断/DMA请求: 在更新事件、触发事件、输入捕获、输出比较等情况下,会产生中断或DMA请求,提供了灵活的事件处理能力。
- 通道功能: 具有4个独立通道,可用于输入捕获、输出比较、输出PWM、单脉冲模式等多种功能。
- 同步电路: 支持外部信号控制定时器,并可实现多个定时器之间的同步。
- 编码器和霍尔传感器: 提供编码器和霍尔传感器电路支持,适用于位置检测和控制应用。
- 重复计数器: 支持重复计数器功能,用于特定应用场景下的计数控制。
- 死区时间: 带有可编程的死区时间,用于控制PWM输出的相位和占空比。
- 断路输入: 提供断路输入功能,可将定时器的输出信号置于用户可选的安全配置中,增强系统的稳定性和安全性。
综上所述,高级定时器在STM32微控制器中扮演着重要角色,广泛应用于各种实时控制、PWM调制、传感器接口等应用中,提供了丰富的功能和灵活的配置选项,满足了不同应用场景的需求。
4.2、高级定时器框图
高级定时器(如TIM1/TIM8)的框图包含了多个功能模块,其中主要包括重复计数器、输出比较和断路功能,下面对这些模块进行简要讲解:
-
重复计数器:
- 重复计数器模块用于控制定时器的计数范围,通常用于生成定时器的重复周期。
- 它与定时器的计数器直接相连,控制计数器的溢出周期。
- 可以通过配置重复计数器的预装载值来设定定时器的重复计数周期。
- 在定时器每次计数器溢出时,重复计数器会自动加载预装载值,重新开始计数,从而实现定时器的重复工作。
-
输出比较:
- 输出比较模块用于在定时器计数器与比较寄存器(CCR)的值相等时产生输出事件。
- 比较寄存器中存储了预先设定的比较值,当计数器的值与比较寄存器的值相等时,会触发输出比较事件。
- 输出比较事件可以用于控制定时器输出的PWM信号、触发中断等操作。
-
断路功能:
- 断路功能提供了一种保护机制,用于将定时器的输出信号与外部设备断开。
- 当触发某些特定事件(如捕获到特定信号、达到特定计数值等)时,断路功能会自动将定时器的输出信号断开,从而保护外部设备。
- 断路功能可以通过特定的配置来实现,通常包括设置断路信号的触发条件和动作。
综上所述,高级定时器的框图涵盖了重复计数器、输出比较和断路功能等多个模块,这些模块共同作用于定时器的工作过程中,为用户提供了丰富的功能和灵活的配置选项,满足了不同应用场景的需求。
4.3、高级定时器输出指定个数PWM实验
4.3.1、重复计数器特性
重复计数器(RCR)是高级定时器(如TIM1/TIM8)中的一个重要特性,它允许控制定时器的重复计数周期。下面是重复计数器的主要特性:
-
计数器每次溢出或下溢减1: 每当定时器的计数器发生上溢(overflow)或下溢(underflow)时,重复计数器的值会减1。这意味着重复计数器会随着定时器的计数而递减。
-
产生更新事件: 当重复计数器减到0时,再次发生一次溢出时,会产生更新事件。这个更新事件可以用于触发中断、DMA传输等操作。
-
设置重复计数周期: 通过设置重复计数器的值(RCR),可以控制定时器的重复计数周期。如果将RCR设置为N,那么更新事件将在经过N+1次溢出时发生。
重复计数器的特性使得高级定时器具有灵活的重复计数控制能力,可以满足不同应用场景下的定时需求。
4.3.2、高级定时器输出指定个数PWM实验原理
实现高级定时器输出指定个数的PWM的实验原理如下:
-
配置边沿对齐模式输出PWM: 首先,需要配置高级定时器的边沿对齐模式,以确保PWM的输出在计数器计数到自动重载值(ARR)时发生翻转。通过设置输出比较模式和通道的比较值,可以控制PWM的占空比。
-
设置重复计数器值: 将所需输出的PWM个数减1(假设为N),然后将结果写入重复计数器寄存器(RCR)。这样设置后,当定时器的计数器经过N次溢出时,会产生更新事件。
-
在更新中断内关闭计数器: 在更新事件中断处理函数中,首先关闭定时器,以停止计数器的计数。这样可以确保PWM输出的个数与预期一致。关闭定时器的方法是将MOE位(Main Output Enable)设置为0。
-
启动计数器和MOE: 在设置完成后,重新启动计数器,并确保MOE位置为1,以使得输出通道的PWM信号被使能。
通过以上步骤,可以实现高级定时器输出指定个数的PWM。这种方法可以用于需要精确控制PWM输出个数的应用场景,如需要发送固定数量脉冲的马达控制、通信协议模拟等。
4.3.3、高级定时器输出指定个数PWM实验配置步骤
实验配置步骤如下:
-
配置定时器基础工作参数: 使用 HAL 库中的
HAL_TIM_PWM_Init()
函数初始化定时器的 PWM 模式。 -
定时器 PWM 输出 MSP 初始化: 编写定时器 PWM 输出的 MSP 初始化函数
HAL_TIM_PWM_MspInit()
,在该函数中配置 NVIC、时钟、GPIO 等外设。 -
配置 PWM 模式/比较值等: 使用 HAL 库中的
HAL_TIM_PWM_ConfigChannel()
函数配置 PWM 模式和比较值等参数。 -
设置优先级,使能中断: 使用 HAL 库中的
HAL_NVIC_SetPriority()
和HAL_NVIC_EnableIRQ()
函数设置更新中断的优先级,并使能中断。 -
使能定时器更新中断: 使用
__HAL_TIM_ENABLE_IT()
函数使能定时器的更新中断。 -
使能输出、主输出、计数器: 使用
HAL_TIM_PWM_Start()
函数使能定时器的 PWM 输出,主输出和计数器。 -
编写中断服务函数: 编写定时器更新中断的中断服务函数
TIMx_IRQHandler()
,在该函数中调用 HAL 库中的HAL_TIM_IRQHandler()
函数。 -
编写更新中断回调函数: 编写更新中断回调函数
HAL_TIM_PeriodElapsedCallback()
,该函数在定时器的更新事件发生时被调用。
通过以上步骤,可以完成高级定时器输出指定个数 PWM 的实验配置。
HAL_TIM_GenerateEvent()
HAL_TIM_GenerateEvent()
函数用于生成定时器事件。它允许软件生成特定的事件,例如更新事件、触发事件等,而不需要等待实际的硬件触发条件发生。
在这个函数中,会根据传入的定时器句柄和事件源参数,生成指定的定时器事件。这个函数通常在特定的场景下使用,例如在PWM输出实验中,当需要改变PWM输出的个数时,可以通过生成更新事件来触发更新中断,从而实现动态调整PWM输出个数的功能。
在代码中的应用示例是在更新中断回调函数中使用HAL_TIM_GenerateEvent()
函数来生成更新事件,以便实现动态调整PWM输出个数的功能。
4.3.4、编程实战:高级定时器输出指定个数PWM实验
atim.c
#include "./BSP/TIMER/atim.h"
TIM_HandleTypeDef g_timx_npwm_chy_handle; /* 定时器x句柄 */
static uint8_t g_npwm_remain = 0; /* 剩余待输出 PWM 个数 */
/* 高级定时器TIMX 通道Y 输出指定个数PWM 初始化函数 */
void atim_timx_npwm_chy_init(uint16_t arr, uint16_t psc)
{
TIM_OC_InitTypeDef timx_oc_npwm_chy = {0};
// 初始化定时器
g_timx_npwm_chy_handle.Instance = TIM8;
g_timx_npwm_chy_handle.Init.Prescaler = psc;
g_timx_npwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
g_timx_npwm_chy_handle.Init.Period = arr;
g_timx_npwm_chy_handle.Init.RepetitionCounter = 0; // 重复计数器初始值为0,不重复计数
HAL_TIM_PWM_Init(&g_timx_npwm_chy_handle);
// 配置 PWM 输出通道
timx_oc_npwm_chy.OCMode = TIM_OCMODE_PWM1;
timx_oc_npwm_chy.Pulse = arr / 2; // 设置初始占空比为 50%
timx_oc_npwm_chy.OCPolarity = TIM_OCPOLARITY_HIGH; // 输出极性为高电平有效
HAL_TIM_PWM_ConfigChannel(&g_timx_npwm_chy_handle, &timx_oc_npwm_chy, TIM_CHANNEL_1);
// 使能更新中断和 PWM 输出
__HAL_TIM_ENABLE_IT(&g_timx_npwm_chy_handle, TIM_IT_UPDATE);
HAL_TIM_PWM_Start(&g_timx_npwm_chy_handle, TIM_CHANNEL_1);
}
/* 定时器 PWM输出 MSP初始化函数 */
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM8)
{
GPIO_InitTypeDef gpio_init_struct;
// 使能 GPIOC 和 TIM8 时钟
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_TIM8_CLK_ENABLE();
// 配置 GPIOC 引脚为复用推挽输出
gpio_init_struct.Pin = GPIO_PIN_6;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_PULLUP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOC, &gpio_init_struct);
// 配置 TIM8 更新中断优先级并使能中断
HAL_NVIC_SetPriority(TIM8_UP_IRQn, 1, 3);
HAL_NVIC_EnableIRQ(TIM8_UP_IRQn);
}
}
/* 高级定时器TIMX NPWM设置PWM个数函数 */
void atim_timx_npwm_chy_set(uint8_t npwm)
{
if(npwm == 0) return; // 如果待输出 PWM 个数为0,直接返回
g_npwm_remain = npwm; // 更新待输出 PWM 个数
HAL_TIM_GenerateEvent(&g_timx_npwm_chy_handle, TIM_EVENTSOURCE_UPDATE); // 生成更新事件
__HAL_TIM_ENABLE(&g_timx_npwm_chy_handle); // 使能定时器
}
/* 定时器8中断服务函数 */
void TIM8_UP_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_timx_npwm_chy_handle); // 调用 HAL 库中的定时器中断处理函数
}
/* 定时器更新中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM8)
{
if(g_npwm_remain) // 如果待输出 PWM 个数不为0
{
TIM8->RCR = g_npwm_remain - 1; // 设置重复计数器值为待输出 PWM 个数减1
HAL_TIM_GenerateEvent(&g_timx_npwm_chy_handle, TIM_EVENTSOURCE_UPDATE); // 生成更新事件
__HAL_TIM_ENABLE(&g_timx_npwm_chy_handle); // 使能定时器
g_npwm_remain = 0; // 清零待输出 PWM 个数
}
else
{
TIM8->CR1 &= ~(1 << 0); // 关闭定时器
}
}
}
atim.h
#ifndef __ATIM_H
#define __ATIM_H
#include "./SYSTEM/sys/sys.h"
void atim_timx_npwm_chy_init(uint16_t arr, uint16_t psc);
void atim_timx_npwm_chy_set(uint8_t npwm);
#endif
key.c
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
/**
* @brief 按键初始化函数
* @param 无
* @retval 无
*/
void key_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
KEY0_GPIO_CLK_ENABLE(); /* KEY0时钟使能 */
KEY1_GPIO_CLK_ENABLE(); /* KEY1时钟使能 */
KEY2_GPIO_CLK_ENABLE(); /* KEY2时钟使能 */
WKUP_GPIO_CLK_ENABLE(); /* WKUP时钟使能 */
gpio_init_struct.Pin = KEY0_GPIO_PIN; /* KEY0引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(KEY0_GPIO_PORT, &gpio_init_struct); /* KEY0引脚模式设置,上拉输入 */
gpio_init_struct.Pin = KEY1_GPIO_PIN; /* KEY1引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(KEY1_GPIO_PORT, &gpio_init_struct); /* KEY1引脚模式设置,上拉输入 */
gpio_init_struct.Pin = KEY2_GPIO_PIN; /* KEY2引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(KEY2_GPIO_PORT, &gpio_init_struct); /* KEY2引脚模式设置,上拉输入 */
gpio_init_struct.Pin = WKUP_GPIO_PIN; /* WKUP引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(WKUP_GPIO_PORT, &gpio_init_struct); /* WKUP引脚模式设置,下拉输入 */
}
/**
* @brief 按键扫描函数
* @note 该函数有响应优先级(同时按下多个按键): WK_UP > KEY2 > KEY1 > KEY0!!
* @param mode:0 / 1, 具体含义如下:
* @arg 0, 不支持连续按(当按键按下不放时, 只有第一次调用会返回键值,
* 必须松开以后, 再次按下才会返回其他键值)
* @arg 1, 支持连续按(当按键按下不放时, 每次调用该函数都会返回键值)
* @retval 键值, 定义如下:
* KEY0_PRES, 1, KEY0按下
* KEY1_PRES, 2, KEY1按下
* KEY2_PRES, 3, KEY2按下
* WKUP_PRES, 4, WKUP按下
*/
uint8_t key_scan(uint8_t mode)
{
static uint8_t key_up = 1; /* 按键按松开标志 */
uint8_t keyval = 0;
if (mode) key_up = 1; /* 支持连按 */
if (key_up && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0 || WK_UP == 1)) /* 按键松开标志为1, 且有任意一个按键按下了 */
{
delay_ms(10); /* 去抖动 */
key_up = 0;
if (KEY0 == 0) keyval = KEY0_PRES;
if (KEY1 == 0) keyval = KEY1_PRES;
if (KEY2 == 0) keyval = KEY2_PRES;
if (WK_UP == 1) keyval = WKUP_PRES;
}
else if (KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && WK_UP == 0) /* 没有任何按键按下, 标记按键松开 */
{
key_up = 1;
}
return keyval; /* 返回键值 */
}
key.h
#ifndef __KEY_H
#define __KEY_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 引脚 定义 */
#define KEY0_GPIO_PORT GPIOE
#define KEY0_GPIO_PIN GPIO_PIN_4
#define KEY0_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
#define KEY1_GPIO_PORT GPIOE
#define KEY1_GPIO_PIN GPIO_PIN_3
#define KEY1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
#define KEY2_GPIO_PORT GPIOE
#define KEY2_GPIO_PIN GPIO_PIN_2
#define KEY2_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
#define WKUP_GPIO_PORT GPIOA
#define WKUP_GPIO_PIN GPIO_PIN_0
#define WKUP_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
/******************************************************************************************/
#define KEY0 HAL_GPIO_ReadPin(KEY0_GPIO_PORT, KEY0_GPIO_PIN) /* 读取KEY0引脚 */
#define KEY1 HAL_GPIO_ReadPin(KEY1_GPIO_PORT, KEY1_GPIO_PIN) /* 读取KEY1引脚 */
#define KEY2 HAL_GPIO_ReadPin(KEY2_GPIO_PORT, KEY2_GPIO_PIN) /* 读取KEY2引脚 */
#define WK_UP HAL_GPIO_ReadPin(WKUP_GPIO_PORT, WKUP_GPIO_PIN) /* 读取WKUP引脚 */
#define KEY0_PRES 1 /* KEY0按下 */
#define KEY1_PRES 2 /* KEY1按下 */
#define KEY2_PRES 3 /* KEY2按下 */
#define WKUP_PRES 4 /* KEY_UP按下(即WK_UP) */
void key_init(void); /* 按键初始化函数 */
uint8_t key_scan(uint8_t mode); /* 按键扫描函数 */
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/TIMER/atim.h"
int main(void)
{
uint8_t key;
uint8_t t = 0;
GPIO_InitTypeDef gpio_init_struct;
HAL_Init(); /* 初始化 HAL 库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟为 72MHz */
delay_init(72); /* 初始化延时函数 */
usart_init(115200); /* 初始化串口波特率为 115200 */
led_init(); /* 初始化 LED */
key_init(); /* 初始化按键 */
/* 将 PE5 设置为输入,避免与 PC6 冲突 */
__HAL_RCC_GPIOE_CLK_ENABLE();
gpio_init_struct.Pin = GPIO_PIN_5;
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入模式 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(GPIOE, &gpio_init_struct);
/* 初始化高级定时器TIMx通道y输出指定个数PWM */
atim_timx_npwm_chy_init(5000 - 1, 7200 - 1);
/* 设置待输出 PWM 个数为 5 */
atim_timx_npwm_chy_set(5);
while (1)
{
key = key_scan(0);
if(key == KEY0_PRES)
{
/* 按键触发时,设置待输出 PWM 个数为 6 */
atim_timx_npwm_chy_set(6);
}
t++;
delay_ms(10);
if (t > 50) /* 控制 LED0 闪烁,提示程序运行状态 */
{
t = 0;
LED0_TOGGLE();
}
}
}
4.4、高级定时器输出比较模式实验
4.4.1、高级定时器输出比较模式实验原理
在高级定时器中,输出比较模式允许在计数器的值等于比较寄存器(CCRx)的值时触发某种动作。输出比较模式通常用于生成精确的脉冲或控制外部设备。在实验中,输出比较模式的原理可以描述如下:
-
配置输出比较模式:首先,配置定时器的工作模式为输出比较模式,并设置比较寄存器(CCRx)的值,确定触发输出翻转的计数器值。
-
输出电平翻转:当计数器的值等于比较寄存器(CCRx)的值时,输出比较模式会触发输出电平的翻转。如果输出极性为高电平有效,则输出电平会从高电平翻转为低电平;反之亦然。这样就实现了输出电平的翻转。
-
设置占空比:在这种模式下,占空比通常是固定的,因为比较寄存器(CCRx)的值确定了输出电平翻转的时机,而周期由自动重装载寄存器(ARR)的值决定。
-
相位调节:通过调节比较寄存器(CCRx)的值,可以调节输出电平翻转的相位,从而控制输出波形的相位关系。
通过这种方式,可以在高级定时器中实现精确控制的输出比较模式,用于生成各种精密的脉冲或控制外部设备的信号。
4.4.2、高级定时器输出比较模式实验配置步骤
配置高级定时器的输出比较模式实验的步骤如下:
-
配置定时器基础工作参数:
使用HAL_TIM_OC_Init()
函数初始化高级定时器的输出比较模式。 -
定时器输出比较MSP初始化:
实现HAL_TIM_OC_MspInit()
函数,配置所需的中断、时钟、GPIO 等外设,确保定时器的正常工作。 -
配置输出比较模式:
调用HAL_TIM_OC_ConfigChannel()
函数配置输出比较通道的工作模式、比较值等参数,确定输出比较模式的具体设置。 -
使能通道预装载:
使用__HAL_TIM_ENABLE_OCxPRELOAD()
宏使能通道预装载功能,确保比较寄存器的更新在下一个更新事件时生效。 -
使能输出、主输出、计数器:
调用HAL_TIM_OC_Start()
函数使能定时器的输出比较通道和计数器,启动定时器的工作。 -
修改捕获/比较寄存器的值:
在需要修改输出比较模式的比较值时,使用__HAL_TIM_SET_COMPARE()
宏来修改对应的比较寄存器的值。
通过以上步骤,可以配置高级定时器的输出比较模式实验,并根据需要调节比较值等参数,实现不同的输出比较模式。
相位
"相位"通常指的是输出信号与某个参考点之间的时间关系。在输出比较模式中,相位通常由比较寄存器的值确定,它决定了输出信号的相对位置。
具体来说,如果将定时器视为一个周期性的计数器,在周期性的计数过程中,相位表示了输出信号与周期起始点(通常是计数器归零点)之间的时间差。修改比较寄存器的值可以改变输出信号的相位,从而调整信号的触发时间。
在输出比较模式中,相位的调节可以用于同步多个定时器的输出,也可以用于实现复杂的波形控制和调节。
在定时器中,相位通常指的是相对于某个参考点或事件的时间延迟或提前量。在输出比较模式中,相位通常由比较寄存器(CCR)的值确定。
具体来说,在输出比较模式下,相位是指输出信号与某个参考事件(通常是定时器的更新事件)之间的时间差。如果将比较寄存器的值设置为较小的值,输出信号就会在更新事件之后的较短时间内发生,这样就可以实现提前相位;反之,如果将比较寄存器的值设置为较大的值,输出信号就会在更新事件之后的较长时间内发生,这样就可以实现延迟相位。
在配置定时器的输出比较模式时,通过调整比较寄存器的值,可以灵活地控制输出信号与参考事件之间的相位关系,从而满足不同的应用需求。
4.4.3、编程实战:高级定时器输出比较模式实验
atim.c
#include "./BSP/TIMER/atim.h"
TIM_HandleTypeDef g_timx_comp_pwm_handle; /* 定时器x句柄 */
/* 高级定时器 输出比较模式 初始化函数 */
void atim_timx_comp_pwm_init(uint16_t arr, uint16_t psc)
{
TIM_OC_InitTypeDef timx_oc_comp_pwm = {0};
g_timx_comp_pwm_handle.Instance = TIM8; /* 使用定时器8 */
g_timx_comp_pwm_handle.Init.Prescaler = psc; /* 设置定时器分频 */
g_timx_comp_pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 选择递增计数模式 */
g_timx_comp_pwm_handle.Init.Period = arr; /* 设置自动重装载值 */
HAL_TIM_OC_Init(&g_timx_comp_pwm_handle); /* 初始化输出比较模式 */
/* 配置输出比较模式的通道1、2、3、4 */
timx_oc_comp_pwm.OCMode = TIM_OCMODE_TOGGLE; /* 选择输出比较模式为翻转模式 */
timx_oc_comp_pwm.OCPolarity = TIM_OCPOLARITY_HIGH; /* 设置输出极性为高 */
HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_1);
HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_2);
HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_3);
HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_4);
/* 使能通道预装载功能 */
__HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_1);
__HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_2);
__HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_3);
__HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_4);
/* 启动通道1、2、3、4的输出比较模式 */
HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_1);
HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_2);
HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_3);
HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_4);
}
/* 定时器 输出比较 MSP初始化函数 */
void HAL_TIM_OC_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM8)
{
GPIO_InitTypeDef gpio_init_struct;
/* 使能定时器8和GPIOC的时钟 */
__HAL_RCC_TIM8_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
/* 配置PC6、PC7、PC8、PC9引脚为复用推挽模式,用于定时器8的输出比较模式 */
gpio_init_struct.Pin = GPIO_PIN_6;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_NOPULL;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOC, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_7;
HAL_GPIO_Init(GPIOC, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_8;
HAL_GPIO_Init(GPIOC, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_9;
HAL_GPIO_Init(GPIOC, &gpio_init_struct);
}
}
atim.h
#ifndef __ATIM_H
#define __ATIM_H
#include "./SYSTEM/sys/sys.h"
void atim_timx_comp_pwm_init(uint16_t arr, uint16_t psc);
#endif
led.c
#include "./BSP/LED/led.h"
/**
* @brief 初始化LED相关IO口, 并使能时钟
* @param 无
* @retval 无
*/
void led_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
LED0_GPIO_CLK_ENABLE(); /* LED0时钟使能 */
LED1_GPIO_CLK_ENABLE(); /* LED1时钟使能 */
gpio_init_struct.Pin = LED0_GPIO_PIN; /* LED0引脚 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(LED0_GPIO_PORT, &gpio_init_struct); /* 初始化LED0引脚 */
gpio_init_struct.Pin = LED1_GPIO_PIN; /* LED1引脚 */
HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct); /* 初始化LED1引脚 */
LED0(1); /* 关闭 LED0 */
LED1(1); /* 关闭 LED1 */
}
led.h
#ifndef _LED_H
#define _LED_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 引脚定义 */
#define LED0_GPIO_PORT GPIOB
#define LED0_GPIO_PIN GPIO_PIN_5
#define LED0_GPIO_CLK_ENABLE() do { __HAL_RCC_GPIOB_CLK_ENABLE(); } while (0) /* 使能LED0的GPIOB时钟 */
#define LED1_GPIO_PORT GPIOE
#define LED1_GPIO_PIN GPIO_PIN_5
#define LED1_GPIO_CLK_ENABLE() do { __HAL_RCC_GPIOE_CLK_ENABLE(); } while (0) /* 使能LED1的GPIOE时钟 */
/******************************************************************************************/
/* LED端口定义 */
#define LED0(x) do { x ? \
HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_RESET); \
} while (0) /* 控制LED0亮/灭 */
#define LED1(x) do { x ? \
HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET); \
} while (0) /* 控制LED1亮/灭 */
/* LED取反定义 */
#define LED0_TOGGLE() do { HAL_GPIO_TogglePin(LED0_GPIO_PORT, LED0_GPIO_PIN); } while (0) /* 翻转LED0状态 */
#define LED1_TOGGLE() do { HAL_GPIO_TogglePin(LED1_GPIO_PORT, LED1_GPIO_PIN); } while (0) /* 翻转LED1状态 */
/******************************************************************************************/
/* 外部接口函数 */
void led_init(void); /* LED初始化函数 */
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/atim.h"
extern TIM_HandleTypeDef g_timx_comp_pwm_handle; /* 定时器x句柄 */
int main(void)
{
uint8_t t = 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
atim_timx_comp_pwm_init(1000 - 1, 72 - 1); /* 初始化定时器输出比较模式 */
/* 设置各通道的比较值 */
__HAL_TIM_SET_COMPARE(&g_timx_comp_pwm_handle, TIM_CHANNEL_1, 250 - 1);
__HAL_TIM_SET_COMPARE(&g_timx_comp_pwm_handle, TIM_CHANNEL_2, 500 - 1);
__HAL_TIM_SET_COMPARE(&g_timx_comp_pwm_handle, TIM_CHANNEL_3, 750 - 1);
__HAL_TIM_SET_COMPARE(&g_timx_comp_pwm_handle, TIM_CHANNEL_4, 1000 - 1);
while (1)
{
delay_ms(10);
t++;
if (t >= 20)
{
LED0_TOGGLE(); /* LED0(RED)闪烁 */
t = 0;
}
}
}
4.5、高级定时器互补输出带死区控制实验
4.5.1、互补输出,还带死区控制,什么意思?
在高级定时器中,互补输出指的是同时控制两个通道,一个通道输出高电平时,另一个通道输出低电平,它们的电平是互补的。这种输出方式常用于驱动功率器件,如H桥驱动器。
死区控制是为了防止在H桥驱动器等场景下,同时开启上下桥臂而产生短路,从而损坏电路或器件。死区控制就是在切换两个互补输出信号之间,引入一个时间延迟,确保上下桥臂不会同时导通,从而避免短路。
因此,实验中的“高级定时器互补输出带死区控制”意味着使用高级定时器配置两个互补输出通道,并设置适当的死区,以确保在控制电机等装置时不会发生短路现象。
在电机驱动或其他需要精确控制的应用中,互补输出和死区控制是常见的功能。它们通常用于控制电机或其他设备的运动和停止,以确保安全和可靠的操作。
-
互补输出:在电机控制中,经常会使用两个输出信号来驱动电机的两个相。互补输出意味着这两个信号是互补的,一个信号的高电平时,另一个信号的低电平,在反之亦然。这种设计有助于减少电机的功耗和提高效率,同时还可以减少电机控制电路的复杂性。
-
死区控制:在电机控制中,由于电机的特性和控制系统的延迟,可能会出现同时开启两个输出信号而导致电机短路的情况。为了避免这种情况,通常会在两个输出信号之间设置一个时间间隔,称为死区。死区控制就是指控制这个时间间隔的技术,以确保两个输出信号之间的间隔足够大,从而避免电机短路或其他不良影响。
综合起来,互补输出和死区控制通常一起使用,用于控制电机或其他设备的运动,以提高系统的性能和可靠性。
4.5.2、带死区控制的互补输出应用之H桥
H桥是电机控制中常用的电路配置,用于实现电机的正反转控制。它通常由四个开关组成,可以将电机的两个端子连接到电源的正负极,实现电机的正转、反转和制动。带死区控制的互补输出常用于控制H桥,以实现电机的正反转功能。
具体步骤如下:
-
H桥的构成:H桥由四个电子开关组成,分别为上下两个开关。在正转时,上半桥的两个开关闭合,下半桥的两个开关断开,使电流从电源正极经过电机,流回电源负极。在反转时,上半桥的两个开关断开,下半桥的两个开关闭合,改变电流的方向,实现电机反转。
-
互补输出控制:使用互补输出的定时器控制H桥的四个开关。正转时,一个通道输出高电平,另一个通道输出低电平,使得对应的上下两个开关闭合。反转时,相反的通道输出高低电平,实现相反的开关闭合。
-
死区控制:在正转和反转过程中,需要确保两个上半桥或下半桥的开关不同时闭合,以防止短路。为此,在控制两个开关切换的时间间隔时,需要设置一个短暂的延迟,称为死区。这样可以确保在开启一个开关的同时关闭另一个开关,避免短路现象的发生。
通过以上步骤,可以实现带死区控制的互补输出应用于H桥控制电机的正反转功能。这种配置不仅能够实现电机的正反转,而且能够确保电机在切换过程中不会发生短路,提高了系统的稳定性和可靠性。
4.5.3、捕获/比较通道的输出部分(通道1至3)
下面是捕获/比较通道的输出部分的注释:
TIM_OC_InitTypeDef timx_oc_output = {0};
/* 输出比较模式选择 */
timx_oc_output.OCMode = TIM_OCMODE_ACTIVE; // 选择活动模式,即输出模式
/* 输出参考信号,高电平有效 */
timx_oc_output.OCPolarity = TIM_OCPOLARITY_HIGH; // 设置输出极性为高电平有效
/* 死区时间控制 */
timx_oc_output.OCNPolarity = TIM_OCNPOLARITY_HIGH; // 设置互补输出极性为高电平有效
timx_oc_output.OCIdleState = TIM_OCIDLESTATE_RESET; // 设置输出闲置状态为低电平
timx_oc_output.OCNIdleState = TIM_OCNIDLESTATE_RESET; // 设置互补输出闲置状态为低电平
/* 输出使能 */
timx_oc_output.OCFastMode = TIM_OCFAST_DISABLE; // 禁用快速模式
timx_oc_output.OCIdleState = TIM_OCIDLESTATE_RESET; // 设置输出闲置状态为低电平
timx_oc_output.OCNIdleState = TIM_OCNIDLESTATE_RESET; // 设置互补输出闲置状态为低电平
/* 输出极性选择 */
timx_oc_output.OCNPolarity = TIM_OCNPOLARITY_HIGH; // 设置互补输出极性为高电平有效
/* 输出必须置1 */
TIM_CCxChannelCmd(TIMx, TIM_CHANNEL_x, TIM_CCx_ENABLE); // 使能通道输出
/* OC1REF清零,由OC1CE位配置 */
TIM_OC1RefClear(TIMx); // 清零OC1REF,由OC1CE位配置
以上是捕获/比较通道的输出部分的注释,其中包括输出比较模式的选择、输出参考信号的设置、死区时间的控制、输出使能、输出极性的选择等内容。
4.5.4、死区时间计算
根据给定的参数,可以按照以下步骤计算死区时间:
-
确定 t D T S t_{DTS} tDTS 的值:
t D T S = 1 f D T S t_{DTS} = \frac{1}{f_{DTS}} tDTS=fDTS1
其中,
f D T S = F t 2 C K D [ 1 : 0 ] f_{DTS} = \frac{F_t}{2^{CKD[1:0]}} fDTS=2CKD[1:0]Ft
F t F_t Ft 是定时器的时钟频率, C K D [ 1 : 0 ] CKD[1:0] CKD[1:0] 是时钟分频因子。 -
判断 D T G [ 7 : 5 ] DTG[7:5] DTG[7:5],选择计算公式:
- 如果 D T G [ 7 : 5 ] = 0 x x DTG[7:5] = 0xx DTG[7:5]=0xx,则 D T = D T G [ 7 : 0 ] × T D T G DT = DTG[7:0] \times T_{DTG} DT=DTG[7:0]×TDTG,其中 T D T G = t D T S T_{DTG} = t_{DTS} TDTG=tDTS。
- 如果 D T G [ 7 : 5 ] = 10 x DTG[7:5] = 10x DTG[7:5]=10x,则 D T = ( 64 + D T G [ 5 : 0 ] ) × T D T G DT = (64 + DTG[5:0]) \times T_{DTG} DT=(64+DTG[5:0])×TDTG,其中 T D T G = 2 × t D T S T_{DTG} = 2 \times t_{DTS} TDTG=2×tDTS。
- 如果 D T G [ 7 : 5 ] = 110 DTG[7:5] = 110 DTG[7:5]=110,则 D T = ( 32 + D T G [ 4 : 0 ] ) × T D T G DT = (32 + DTG[4:0]) \times T_{DTG} DT=(32+DTG[4:0])×TDTG,其中 T D T G = 8 × t D T S T_{DTG} = 8 \times t_{DTS} TDTG=8×tDTS。
- 如果 D T G [ 7 : 5 ] = 111 DTG[7:5] = 111 DTG[7:5]=111,则 D T = ( 32 + D T G [ 4 : 0 ] ) × T D T G DT = (32 + DTG[4:0]) \times T_{DTG} DT=(32+DTG[4:0])×TDTG,其中 T D T G = 16 × t D T S T_{DTG} = 16 \times t_{DTS} TDTG=16×tDTS。
-
代入选择的公式计算 D T DT DT 的值。
根据以上步骤,可以计算出所需的死区时间 D T DT DT。
TIMx_CR1
TIMx_BDTR
根据给定的例子和计算步骤:
-
首先,根据时钟分频因子 C K D [ 1 : 0 ] CKD[1:0] CKD[1:0] 和定时器的时钟频率 F t F_t Ft ,计算 t D T S t_{DTS} tDTS 的值:
f D T S = F t 2 C K D [ 1 : 0 ] f_{DTS} = \frac{F_t}{2^{CKD[1:0]}} fDTS=2CKD[1:0]Ft
t D T S = 1 f D T S t_{DTS} = \frac{1}{f_{DTS}} tDTS=fDTS1 -
根据 D T G [ 7 : 0 ] DTG[7:0] DTG[7:0] 的值,选择计算公式,因为 D T G [ 7 : 5 ] = 111 DTG[7:5] = 111 DTG[7:5]=111,所以选择第四条公式:
D T = ( 32 + 26 ) × T D T G DT = (32 + 26) \times T_{DTG} DT=(32+26)×TDTG
其中, T D T G = 16 × t D T S T_{DTG} = 16 \times t_{DTS} TDTG=16×tDTS -
代入计算:
T D T G = 16 × t D T S T_{DTG} = 16 \times t_{DTS} TDTG=16×tDTS
D T = ( 32 + 26 ) × 16 × t D T S DT = (32 + 26) \times 16 \times t_{DTS} DT=(32+26)×16×tDTS
计算得到 D T DT DT 的值,根据给定的时钟频率 F t F_t Ft 和 C K D [ 1 : 0 ] CKD[1:0] CKD[1:0] ,以及 D T G [ 7 : 0 ] DTG[7:0] DTG[7:0] 的值,得到的结果为 D T = 51.55968 μ s DT = 51.55968 \mu s DT=51.55968μs。
4.5.5、刹车(断路)功能
刹车功能的实现需要通过配置定时器的相关寄存器来实现。以下是实现刹车功能的基本步骤:
-
使能刹车功能:设置 TIMx_BDTR 寄存器的 BKE 位为 1,这将启用刹车功能。同时,刹车输入信号的极性由 BKP 位设置。
-
配置输出状态控制:通过设置 TIMx_BDTR 的 MOE、OSSI、OSSR 位,TIMx_CR2 的 OISx、OISxN 位以及 TIMx_CCER 的 CCxE、CCxNE 位来控制 OCx 和 OCxN 输出状态。
-
避免 OCx 和 OCxN 同时处于有效电平:刹车功能要求无论何时,OCx 和 OCxN 输出不能同时处于有效电平。因此,在配置输出状态时,需要确保这两个输出不会同时处于有效状态,否则可能导致短路或其他不良后果。
通过以上步骤,可以实现刹车功能,并确保在需要刹车时,输出状态能够按预期进行调整,以满足系统要求。
发生刹车后,会出现以下情况:
-
MOE位被清零:MOE(Main Output Enable)位控制主输出使能状态。当发生刹车后,MOE位会被清零,导致主输出 OCx 和 OCxN 处于无效、空闲或复位状态,具体取决于 OSSI 位的设置。
-
OCx 和 OCxN 的状态:根据相关控制位的状态,OCx 和 OCxN 的输出状态会进行相应调整。如果使用了互补输出功能,输出电平会根据刹车控制逻辑自动调整。具体情况需要参考参考手册中的刹车(断路)功能部分。
-
刹车中断和 DMA 请求:刹车触发后,如果使能了 BIE 位,则会产生刹车中断,即 BIF 位会被置位。如果同时使能了 TDE 位,则会产生 DMA 请求,允许将相关事件传输到 DMA。
-
自动使能 MOE 位:如果 AOE 位被设置为 1,则在下一个更新事件 UEV(Update Event)时,MOE 位会被自动置为 1,主输出会重新被使能。
这些是刹车功能触发后可能发生的一些情况,具体的行为取决于定时器配置和控制寄存器的设置。
4.5.6、高级定时器互补输出带死区控制实验配置步骤
这里是高级定时器互补输出带死区控制的实验配置步骤:
-
配置定时器基础工作参数:
使用HAL_TIM_PWM_Init()
函数初始化定时器基础工作参数。 -
定时器 PWM 输出 MSP 初始化:
实现HAL_TIM_PWM_MspInit()
函数,在其中配置 NVIC、时钟、GPIO 等相关设置。 -
配置 PWM 模式/比较值等:
使用HAL_TIM_PWM_ConfigChannel()
函数配置 PWM 模式、比较值等参数。 -
配置刹车功能、死区时间等:
使用HAL_TIMEx_ConfigBreakDeadTime()
函数配置刹车功能和死区时间。在这一步中,需要设置刹车功能和死区时间,以实现互补输出并避免输出冲突。 -
使能输出、主输出、计数器:
使用HAL_TIM_PWM_Start()
函数使能输出、主输出和计数器。 -
使能互补输出、主输出、计数器:
使用HAL_TIMEx_PWMN_Start()
函数使能互补输出、主输出和计数器。这一步是针对互补输出的特殊设置,确保互补输出通道也被正确地启用。
以上步骤可以帮助配置高级定时器以实现带死区控制的互补输出功能。在具体实验中,根据实际需求和硬件配置,可能需要调整参数和其他设置以达到预期的功能。
4.5.7、编程实战:高级定时器互补输出带死区控制实验
atim.c
#include "./BSP/TIMER/atim.h"
TIM_HandleTypeDef g_timx_cplm_pwm_handle; /* 定时器x句柄 */
TIM_BreakDeadTimeConfigTypeDef g_sbreak_dead_time_config; /* 死区时间设置 */
/**
* @brief 高级定时器互补输出初始化函数(使用PWM模式1)
* @param arr: 自动重装载值
* @param psc: 预分频系数
* @retval 无
*/
void atim_timx_cplm_pwm_init(uint16_t arr, uint16_t psc)
{
TIM_OC_InitTypeDef tim_oc_cplm_pwm = {0};
// 配置定时器基本工作参数
g_timx_cplm_pwm_handle.Instance = TIM1; /* 定时器x */
g_timx_cplm_pwm_handle.Init.Prescaler = psc; /* 定时器预分频系数 */
g_timx_cplm_pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_cplm_pwm_handle.Init.Period = arr; /* 自动重装载值 */
g_timx_cplm_pwm_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4; /* CKD[1:0] = 10, tDTS = 4 * tCK_INT = Ft / 4 = 18Mhz */
HAL_TIM_PWM_Init(&g_timx_cplm_pwm_handle);
// 配置 PWM 输出模式和极性
tim_oc_cplm_pwm.OCMode = TIM_OCMODE_PWM1; /* PWM模式1 */
tim_oc_cplm_pwm.OCPolarity = TIM_OCPOLARITY_HIGH; /* OCy 高电平有效 */
tim_oc_cplm_pwm.OCNPolarity = TIM_OCNPOLARITY_HIGH; /* OCyN 高电平有效 */
tim_oc_cplm_pwm.OCIdleState = TIM_OCIDLESTATE_RESET; /* 当MOE=0,OCx=0 */
tim_oc_cplm_pwm.OCNIdleState = TIM_OCNIDLESTATE_RESET; /* 当MOE=0,OCxN=0 */
HAL_TIM_PWM_ConfigChannel(&g_timx_cplm_pwm_handle, &tim_oc_cplm_pwm, TIM_CHANNEL_1);
// 配置死区参数,开启死区中断
g_sbreak_dead_time_config.OffStateRunMode = TIM_OSSR_DISABLE; /* 运行模式的关闭输出状态 */
g_sbreak_dead_time_config.OffStateIDLEMode = TIM_OSSI_DISABLE; /* 空闲模式的关闭输出状态 */
g_sbreak_dead_time_config.LockLevel = TIM_LOCKLEVEL_OFF; /* 不用寄存器锁功能 */
g_sbreak_dead_time_config.BreakState = TIM_BREAK_ENABLE; /* 使能刹车输入 */
g_sbreak_dead_time_config.BreakPolarity = TIM_BREAKPOLARITY_HIGH; /* 刹车输入有效信号极性为高 */
g_sbreak_dead_time_config.AutomaticOutput = TIM_AUTOMATICOUTPUT_ENABLE; /* 使能AOE位,允许刹车结束后自动恢复输出 */
HAL_TIMEx_ConfigBreakDeadTime(&g_timx_cplm_pwm_handle, &g_sbreak_dead_time_config);
// 启动 PWM 输出和互补输出
HAL_TIM_PWM_Start(&g_timx_cplm_pwm_handle, TIM_CHANNEL_1); /* OCy 输出使能 */
HAL_TIMEx_PWMN_Start(&g_timx_cplm_pwm_handle, TIM_CHANNEL_1); /* OCyN 输出使能 */
}
/**
* @brief 定时器 PWM输出 MSP初始化函数
* @param htim: 定时器句柄
* @retval 无
*/
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM1)
{
GPIO_InitTypeDef gpio_init_struct = {0};
// 使能定时器时钟和 GPIO 时钟
__HAL_RCC_TIM1_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
// 配置 GPIO 引脚为 AF 推挽输出
gpio_init_struct.Pin = GPIO_PIN_9;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_PULLDOWN;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH ;
HAL_GPIO_Init(GPIOE, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_8;
HAL_GPIO_Init(GPIOE, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_15;
HAL_GPIO_Init(GPIOE, &gpio_init_struct);
// 使能定时器 1 的重映射功能
__HAL_RCC_AFIO_CLK_ENABLE();
__HAL_AFIO_REMAP_TIM1_ENABLE();
}
}
/**
* @brief 定时器TIMX 设置输出比较值 & 死区时间
* @param ccr: 输出比较值
* @param dtg: 死区时间
* @arg dtg[7:5]=0xx时, 死区时间 = dtg[7:0] * tDTS
* @arg dtg[7:5]=10x时, 死区时间 = (64 + dtg[6:0]) * 2 * tDTS
* @arg dtg[7:5]=110时, 死区时间 = (32 + dtg[5:0]) * 8 * tDTS
* @arg dtg[7:5]=111时, 死区时间 = (32 + dtg[5:0]) * 16 * tDTS
* @note tDTS = 1 / (Ft / CKD[1:0]) = 1 / 18M = 55.56ns
* @retval 无
*/
void atim_timx_cplm_pwm_set(uint16_t ccr, uint8_t dtg)
{
// 设置输出比较值
__HAL_TIM_SET_COMPARE(&g_timx_cplm_pwm_handle, TIM_CHANNEL_1, ccr);
// 设置死区时间
g_sbreak_dead_time_config.DeadTime = dtg;
HAL_TIMEx_ConfigBreakDeadTime(&g_timx_cplm_pwm_handle, &g_sbreak_dead_time_config);
}
atim.h
#ifndef __ATIM_H
#define __ATIM_H
#include "./SYSTEM/sys/sys.h"
void atim_timx_cplm_pwm_init(uint16_t arr, uint16_t psc);
void atim_timx_cplm_pwm_set(uint16_t ccr, uint8_t dtg);
#endif
led.c
#include "./BSP/LED/led.h"
/**
* @brief 初始化LED相关IO口, 并使能时钟
* @param 无
* @retval 无
*/
void led_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
LED0_GPIO_CLK_ENABLE(); /* LED0时钟使能 */
LED1_GPIO_CLK_ENABLE(); /* LED1时钟使能 */
gpio_init_struct.Pin = LED0_GPIO_PIN; /* LED0引脚 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(LED0_GPIO_PORT, &gpio_init_struct); /* 初始化LED0引脚 */
gpio_init_struct.Pin = LED1_GPIO_PIN; /* LED1引脚 */
HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct); /* 初始化LED1引脚 */
LED0(1); /* 关闭 LED0 */
LED1(1); /* 关闭 LED1 */
}
led.h
#ifndef _LED_H
#define _LED_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 引脚 定义 */
#define LED0_GPIO_PORT GPIOB
#define LED0_GPIO_PIN GPIO_PIN_5
#define LED0_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
#define LED1_GPIO_PORT GPIOE
#define LED1_GPIO_PIN GPIO_PIN_5
#define LED1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
/******************************************************************************************/
/* LED端口定义 */
#define LED0(x) do{ x ? \
HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* LED0翻转 */
#define LED1(x) do{ x ? \
HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* LED1翻转 */
/* LED取反定义 */
#define LED0_TOGGLE() do{ HAL_GPIO_TogglePin(LED0_GPIO_PORT, LED0_GPIO_PIN); }while(0) /* 翻转LED0 */
#define LED1_TOGGLE() do{ HAL_GPIO_TogglePin(LED1_GPIO_PORT, LED1_GPIO_PIN); }while(0) /* 翻转LED1 */
/******************************************************************************************/
/* 外部接口函数*/
void led_init(void); /* 初始化 */
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/TIMER/atim.h"
int main(void)
{
uint8_t t = 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
atim_timx_cplm_pwm_init(1000 - 1, 72 - 1);
atim_timx_cplm_pwm_set(700 - 1, 100);
while (1)
{
delay_ms(10);
t++;
if (t >= 20)
{
LED0_TOGGLE(); /* LED0(RED)闪烁 */
t = 0;
}
}
}
4.6、高级定时器PWM输入模式实验
4.6.1、PWM输入模式工作原理
PWM输入模式是通过高级定时器(TIM)的通道1或通道2实现的。以下是PWM输入模式的工作原理总结:
-
时钟源配置:时钟源来自RCC(Reset and Clock Control),必须首先配置RCC以确定TIM的时钟源及其频率。
-
预分频器设置:使用预分频器(PSC)对时钟源进行预分频,以获得适当的计数器时钟频率。
-
通道选择:PWM输入模式只能使用通道1或通道2。选择要使用的通道,并进行相应的GPIO引脚配置。
-
输入滤波器和边沿检测器配置:配置输入滤波器和边沿检测器以适应输入信号的特性。这些配置包括设置滤波器的带宽和边沿检测器的触发方式。
-
捕获比较寄存器映射:通道1和通道2的捕获比较寄存器(CCR1和CCR2)被映射到同一个TIx上,以便可以捕获输入信号的上升沿和下降沿。
-
触发输入选择:通过设置捕获比较寄存器和捕获比较控制寄存器,选择将输入信号与哪个TIx触发器关联。
-
模式选择:选择复位模式,以便在每个输入捕获事件后清除计数器。
-
捕获事件:在输入信号的上升沿或下降沿时触发捕获事件。捕获事件将捕获计数器的当前值,并将其存储在捕获/比较寄存器中。
-
计算:通过分析捕获的计数值,可以计算出PWM信号的周期、频率和占空比。周期由两个连续的捕获事件之间的计数差确定,高电平的持续时间由上升沿到下降沿之间的计数确定。
综上所述,PWM输入模式通过捕获输入信号的上升沿和下降沿来测量信号的周期、频率和占空比。这种模式适用于需要测量外部PWM信号的应用,例如电机控制或传感器数据采集。
#include "./BSP/TIMER/atim.h"
TIM_HandleTypeDef g_timx_pwm_input_handle; /* 定时器x句柄 */
/**
* @brief 高级定时器 PWM输入模式初始化函数
* @param arr: 自动重装载值
* @param psc: 预分频系数
* @retval 无
*/
void atim_timx_pwm_input_init(uint16_t arr, uint16_t psc)
{
TIM_IC_InitTypeDef tim_ic_init_struct = {0};
// 配置定时器基本工作参数
g_timx_pwm_input_handle.Instance = TIM1; /* 定时器x */
g_timx_pwm_input_handle.Init.Prescaler = psc; /* 定时器预分频系数 */
g_timx_pwm_input_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_pwm_input_handle.Init.Period = arr; /* 自动重装载值 */
HAL_TIM_IC_Init(&g_timx_pwm_input_handle);
// 配置输入捕获通道1
tim_ic_init_struct.ICPolarity = TIM_ICPOLARITY_RISING; /* 上升沿触发 */
tim_ic_init_struct.ICSelection = TIM_ICSELECTION_DIRECTTI; /* TI1映射到输入捕获通道1 */
tim_ic_init_struct.ICPrescaler = TIM_ICPSC_DIV1; /* 输入分频系数 */
tim_ic_init_struct.ICFilter = 0; /* 输入滤波器 */
HAL_TIM_IC_ConfigChannel(&g_timx_pwm_input_handle, &tim_ic_init_struct, TIM_CHANNEL_1);
// 配置输入捕获通道2
tim_ic_init_struct.ICPolarity = TIM_ICPOLARITY_FALLING; /* 下降沿触发 */
HAL_TIM_IC_ConfigChannel(&g_timx_pwm_input_handle, &tim_ic_init_struct, TIM_CHANNEL_2);
HAL_TIM_IC_Start(&g_timx_pwm_input_handle, TIM_CHANNEL_1); /* 启动输入捕获通道1 */
HAL_TIM_IC_Start(&g_timx_pwm_input_handle, TIM_CHANNEL_2); /* 启动输入捕获通道2 */
}
/**
* @brief 定时器 PWM输入模式 MSP初始化函数
* @param htim: 定时器句柄
* @retval 无
*/
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM1)
{
GPIO_InitTypeDef gpio_init_struct = {0};
// 使能定时器时钟和 GPIO 时钟
__HAL_RCC_TIM1_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
// 配置 GPIO 引脚为 AF 输入模式
gpio_init_struct.Pin = GPIO_PIN_9 | GPIO_PIN_11;
gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;
gpio_init_struct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOE, &gpio_init_struct);
// 使能定时器 1 的重映射功能
__HAL_RCC_AFIO_CLK_ENABLE();
__HAL_AFIO_REMAP_TIM1_ENABLE();
}
}
/**
* @brief 定时器TIMX 获取 PWM输入模式相关参数
* @param period: PWM周期
* @param pulse: 高电平计数值
* @retval 无
*/
void atim_timx_get_pwm_input_params(uint32_t *period, uint32_t *pulse)
{
uint32_t cnt_period = __HAL_TIM_GET_AUTORELOAD(&g_timx_pwm_input_handle); /* 获取周期计数值 */
uint32_t cnt_pulse = __HAL_TIM_GET_COMPARE(&g_timx_pwm_input_handle, TIM_CHANNEL_1); /* 获取高电平计数值 */
*period = cnt_period + 1; /* PWM周期 = 自动重装载值 + 1 */
*pulse = cnt_pulse; /* 高电平计数值 */
}
4.6.2、PWM输入模式时序
以下是PWM输入模式的时序说明:
-
初始化阶段:
- 配置TIM的基本参数,如时钟源、预分频器等。
- 配置GPIO引脚用于PWM输入信号的接收,确保通道1(或通道2)的输入引脚正确连接到外部PWM信号。
-
捕获设置:
- 配置TIM通道1(或通道2)为PWM输入模式,选择触发输入为PWMTI1。
- 配置TIM通道1和通道2的捕获/比较寄存器(TIMx_CCR1和TIMx_CCR2)用于存储捕获的计数值。
-
捕获过程:
- 当PWM信号的上升沿到达时,TIMx_CNT的当前值会被捕获并存储到TIMx_CCR1中,IC1上升沿捕获。
- 当PWM信号的下降沿到达时,TIMx_CNT的当前值会被捕获并存储到TIMx_CCR2中,IC2下降沿捕获。
-
计数器复位:
- 在每次上升沿捕获后,计数器(TIMx_CNT)会被复位并重新开始计数,以便进行下一次测量。
-
脉冲宽度测量:
- 当下降沿到来时,通过计算TIMx_CCR2和TIMx_CCR1之间的计数差来测量PWM信号的脉冲宽度,即PWM信号的高电平持续时间。
-
周期测量:
- 在下一个上升沿捕获事件之前,可以利用TIMx_CCR1和上一个TIMx_CCR1之间的计数差来测量PWM信号的周期。
-
重复:
- 上述步骤会在每个PWM周期内重复进行,以实时测量PWM信号的脉冲宽度和周期。
通过这种方式,PWM输入模式可以精确地测量外部PWM信号的脉冲宽度和周期,用于各种应用中,例如电机控制、编码器读取等。
4.6.3、高级定时器PWM输入模式实验配置步骤
配置高级定时器PWM输入模式实验的基本步骤:
-
配置定时器基础工作参数:
- 使用
HAL_TIM_IC_Init()
初始化定时器,设置基本工作参数,如时钟源、计数模式等。
- 使用
-
定时器捕获输入MSP初始化:
- 实现
HAL_TIM_IC_MspInit()
函数,配置定时器捕获输入所需的 NVIC、时钟、GPIO 等。
- 实现
-
配置IC1/2映射、捕获边沿等:
- 使用
HAL_TIM_IC_ConfigChannel()
配置捕获通道,选择输入映射、捕获边沿等参数。
- 使用
-
配置从模式,触发源等:
- 可以使用
HAL_TIM_SlaveConfigSynchro()
配置从模式和触发源,根据需要设置同步触发。
- 可以使用
-
设置优先级,使能中断:
- 使用
HAL_NVIC_SetPriority()
和HAL_NVIC_EnableIRQ()
设置中断优先级并使能中断,以便处理捕获事件。
- 使用
-
使能捕获、捕获中断及计数器:
- 使用
HAL_TIM_IC_Start_IT()
或HAL_TIM_IC_Start()
启动捕获模式,并选择是否使能捕获中断。
- 使用
-
编写中断服务函数:
- 实现中断服务函数,处理捕获中断事件。通常是在中断服务函数中调用
HAL_TIM_IRQHandler()
。
- 实现中断服务函数,处理捕获中断事件。通常是在中断服务函数中调用
-
编写输入捕获回调函数:
- 编写输入捕获的回调函数
HAL_TIM_IC_CaptureCallback()
,在该函数中处理捕获事件,包括获取捕获值、计算脉冲宽度、周期等。
- 编写输入捕获的回调函数
以上步骤是配置高级定时器PWM输入模式实验的基本流程,根据具体需求和硬件连接,可能需要进行适当调整和补充。
4.6.4、编程实战:高级定时器PWM输入模式实验
atim.c
#include "./BSP/TIMER/atim.h"
TIM_HandleTypeDef g_timx_pwmin_chy_handle; /* 定时器x句柄 */
/* PWM输入状态(g_timxchy_cap_sta)
* 0,没有成功捕获.
* 1,已经成功捕获了
*/
uint8_t g_timxchy_pwmin_sta = 0; /* PWM输入状态 */
uint16_t g_timxchy_pwmin_psc = 0; /* PWM输入分频系数 */
uint32_t g_timxchy_pwmin_hval = 0; /* PWM的高电平脉宽 */
uint32_t g_timxchy_pwmin_cval = 0; /* PWM的周期宽度 */
/* PWM输入模式 初始化函数,采样时钟频率为72Mhz,精度约13.8ns */
void atim_timx_pwmin_chy_init(void)
{
TIM_SlaveConfigTypeDef slave_config = {0};
TIM_IC_InitTypeDef tim_ic_pwmin_chy = {0};
// 初始化定时器配置
g_timx_pwmin_chy_handle.Instance = TIM8; /* 定时器8 */
g_timx_pwmin_chy_handle.Init.Prescaler = 0; /* 定时器预分频系数 */
g_timx_pwmin_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_pwmin_chy_handle.Init.Period = 65535; /* 自动重装载值 */
HAL_TIM_IC_Init(&g_timx_pwmin_chy_handle);
// 从模式配置
slave_config.SlaveMode = TIM_SLAVEMODE_RESET; /* 从模式:复位模式 */
slave_config.InputTrigger = TIM_TS_TI1FP1; /* 定时器输入触发源:TI1FP1 */
slave_config.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING; /* 上升沿检测 */
slave_config.TriggerFilter = 0; /* 不滤波 */
HAL_TIM_SlaveConfigSynchro(&g_timx_pwmin_chy_handle, &slave_config);
// 配置通道1和通道2的输入捕获参数
tim_ic_pwmin_chy.ICPolarity = TIM_ICPOLARITY_RISING; /* 上升沿检测 */
tim_ic_pwmin_chy.ICSelection = TIM_ICSELECTION_DIRECTTI; /* 选择输入端IC1映射到TI1 */
tim_ic_pwmin_chy.ICPrescaler = TIM_ICPSC_DIV1; /* 不分频 */
tim_ic_pwmin_chy.ICFilter = 0; /* 不滤波 */
HAL_TIM_IC_ConfigChannel(&g_timx_pwmin_chy_handle, &tim_ic_pwmin_chy, TIM_CHANNEL_1);
// 配置通道2的输入捕获参数
tim_ic_pwmin_chy.ICPolarity = TIM_ICPOLARITY_FALLING; /* 下降沿检测 */
tim_ic_pwmin_chy.ICSelection = TIM_ICSELECTION_INDIRECTTI; /* 选择输入端IC2映射到TI1 */
HAL_TIM_IC_ConfigChannel(&g_timx_pwmin_chy_handle, &tim_ic_pwmin_chy, TIM_CHANNEL_2);
// 启动捕获模式并开启中断
HAL_TIM_IC_Start_IT(&g_timx_pwmin_chy_handle, TIM_CHANNEL_1);
HAL_TIM_IC_Start(&g_timx_pwmin_chy_handle, TIM_CHANNEL_2);
}
/* 定时器 输入捕获 MSP初始化函数 */
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM8)
{
GPIO_InitTypeDef gpio_init_struct = {0};
// 使能时钟和GPIO引脚配置
__HAL_RCC_TIM8_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
// 配置GPIO引脚为复用推挽输出
gpio_init_struct.Pin = GPIO_PIN_6;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_PULLDOWN;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOC, &gpio_init_struct);
// 配置定时器8的输入捕获中断优先级和使能中断
HAL_NVIC_SetPriority(TIM8_CC_IRQn, 1, 3);
HAL_NVIC_EnableIRQ(TIM8_CC_IRQn);
}
}
/* 定时器8 输入捕获 中断服务函数,仅TIM1/TIM8有这个函数,其他普通定时器没有这个中断服务函数! */
void TIM8_CC_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_timx_pwmin_chy_handle); /* 调用HAL库提供的定时器中断处理函数 */
}
/* PWM输入模式 重新启动捕获 */
void atim_timx_pwmin_chy_restart(void)
{
sys_intx_disable(); /* 关闭中断 */
g_timxchy_pwmin_sta = 0; /* 清零状态,重新开始检测 */
g_timxchy_pwmin_hval=0;
g_timxchy_pwmin_cval=0;
sys_intx_enable(); /* 打开中断 */
}
/* 定时器输入捕获中断处理回调函数 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM8)
{
if(g_timxchy_pwmin_sta == 0)
{
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
// 读取捕获值,计算PWM的高电平脉宽和周期宽度
/* 周期计数值 */
g_timxchy_pwmin_hval = HAL_TIM_ReadCapturedValue(&g_timx_pwmin_chy_handle, TIM_CHANNEL_2) + 1 + 1;
/* 高电平计数值 */
g_timxchy_pwmin_cval = HAL_TIM_ReadCapturedValue(&g_timx_pwmin_chy_handle, TIM_CHANNEL_1) + 1 + 1;
/* 设置成功捕获标志位 */
g_timxchy_pwmin_sta = 1;
}
}
}
}
atim.h
#ifndef __ATIM_H
#define __ATIM_H
#include "./SYSTEM/sys/sys.h"
void atim_timx_pwmin_chy_init(void);
void atim_timx_pwmin_chy_restart(void);
#endif
gtim.c
#include "./BSP/TIMER/gtim.h"
TIM_HandleTypeDef g_timx_pwm_chy_handle; /* 定时器句柄 */
/* 通用定时器PWM输出初始化函数 */
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc)
{
TIM_OC_InitTypeDef timx_oc_pwm_chy; /* 定时器输出比较配置结构体 */
g_timx_pwm_chy_handle.Instance = TIM3; /* 定时器3 */
g_timx_pwm_chy_handle.Init.Prescaler = psc; /* 定时器预分频系数 */
g_timx_pwm_chy_handle.Init.Period = arr; /* 自动重装载值 */
g_timx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle); /* 初始化定时器PWM模式 */
timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1; /* PWM模式1 */
timx_oc_pwm_chy.Pulse = arr / 2; /* 设置占空比 */
timx_oc_pwm_chy.OCPolarity = TIM_OCPOLARITY_LOW; /* 输出比较极性 */
HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle, &timx_oc_pwm_chy, TIM_CHANNEL_2); /* 配置PWM通道2 */
HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, TIM_CHANNEL_2); /* 启动PWM输出 */
}
/* 定时器输出PWM MSP初始化函数 */
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM3) /* 判断是定时器3 */
{
GPIO_InitTypeDef gpio_init_struct; /* GPIO初始化结构体 */
__HAL_RCC_GPIOB_CLK_ENABLE(); /* 使能GPIOB时钟 */
__HAL_RCC_TIM3_CLK_ENABLE(); /* 使能定时器3时钟 */
gpio_init_struct.Pin = GPIO_PIN_5; /* GPIOB引脚5 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 推挽复用模式 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* GPIO速度为高速 */
HAL_GPIO_Init(GPIOB, &gpio_init_struct); /* 初始化GPIOB引脚 */
__HAL_RCC_AFIO_CLK_ENABLE(); /* 使能AFIO时钟 */
__HAL_AFIO_REMAP_TIM3_PARTIAL(); /* 部分重映射TIM3 */
}
}
gtim.h
#ifndef __GTIM_H
#define __GTIM_H
#include "./SYSTEM/sys/sys.h"
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc);
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/atim.h"
#include "./BSP/TIMER/gtim.h"
extern uint16_t g_timxchy_pwmin_sta; /* PWM输入状态 */
extern uint16_t g_timxchy_pwmin_psc; /* PWM输入分频系数 */
extern uint32_t g_timxchy_pwmin_hval; /* PWM的高电平脉宽 */
extern uint32_t g_timxchy_pwmin_cval; /* PWM的周期宽度 */
int main(void)
{
uint8_t t = 0;
double ht, ct, f, tpsc;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
gtim_timx_pwm_chy_init(10 - 1, 72 - 1); /* 初始化通用定时器PWM输出 */
TIM3->CCR2 = 3; /* 设置通道2的比较值,调整PWM输出占空比 */
atim_timx_pwmin_chy_init(); /* 初始化高级定时器PWM输入模式 */
while (1)
{
delay_ms(10);
t++;
if (t >= 20) /* 每200ms输出一次结果,并闪烁LED0,提示程序运行 */
{
if (g_timxchy_pwmin_sta) /* 捕获了一次数据 */
{
printf("\r\n"); /* 输出空,另起一行 */
printf("PWM PSC :%d\r\n", g_timxchy_pwmin_psc); /* 打印分频系数 */
printf("PWM Hight:%d\r\n", g_timxchy_pwmin_hval); /* 打印高电平脉宽 */
printf("PWM Cycle:%d\r\n", g_timxchy_pwmin_cval); /* 打印周期 */
tpsc = ((double)g_timxchy_pwmin_psc + 1) / 72; /* 得到PWM采样时钟周期时间 */
ht = g_timxchy_pwmin_hval * tpsc; /* 计算高电平时间 */
ct = g_timxchy_pwmin_cval * tpsc; /* 计算周期长度 */
f = (1 / ct) * 1000000; /* 计算频率 */
printf("PWM Hight time:%.3fus\r\n", ht); /* 打印高电平脉宽长度 */
printf("PWM Cycle time:%.3fus\r\n", ct); /* 打印周期时间长度 */
printf("PWM Frequency :%.3fHz\r\n", f); /* 打印频率 */
atim_timx_pwmin_chy_restart(); /* 重启PWM输入检测 */
}
LED1_TOGGLE(); /* LED1(GREEN)闪烁 */
t = 0;
}
}
}
测量的最长PWM周期为910.2us
五、总结