文章目录
前言
1、STM32F407ZGT6
2、STM32CubeMx软件
3、keil5
内容简述:
通篇文章将涉及以下内容,如有错误,欢迎指出:
定时器有关输入捕获的HAL库驱动程序
(1)CubeMx配置
(2)TIM驱动程序
(3)输入捕获检测方波占空比
(4) 输入捕获检测PWM频率和占空比
(5)用定时器ETR方式计算PWM脉冲数
- 有关于定时器 输出PWM功能不了解的可以看这篇文章 :HAL库STM32常用外设教程(一)—— 定时器 输出PWM
- 有关于定时器 定时功能+基础定时器特性不了解的可以看这篇文章 :HAL库STM32常用外设教程(四)—— 定时器 基本定时
- 有关于定时器输出比较+通用定时器特性不了解的可以看这篇文章 :HAL库STM32常用外设教程(五)—— 定时器 输出比较
一、输入捕获原理及相关驱动
1.1 输入捕获原理
输入捕获(intput capture)就是检测输入通道输入方波信号的跳变沿,并将发生跳变沿时的计数器的值锁存到CCR。使用输入捕获功能可以检测方波信号周期,从而计算方波信号的频率,也可以检测方波的占空比。其基本原理可以看成下面的几个步骤:
1、输入捕获通道配置:首先,需要选择一个特定的计时器作为输入捕获的计时源,并将其中的一个或多个通道配置为输入捕获模式。
2、捕获触发条件设置:根据需要,在输入捕获通道上设置触发条件,例如上升沿、下降沿或两者都有的边缘触发。
3、 外部信号检测:当外部信号满足触发条件(如上升沿)时,输入捕获通道会记录当前计时器的计数值并生成相应的中断或标志。
4、 捕获值获取:在中断服务程序或适当的时间点,可以读取输入捕获通道的捕获寄存器,获取记录的捕获值。
5、计算参数:通过对捕获值进行处理和计算,可以得到所需的参数,如脉冲宽度、频率、周期等。
下面将通过用输入捕获功能计算按键按下时高电平的占空比、输入捕获计算PWM的周期和频率两个例子讲解定时器输入捕获的用法。
1.2 输入捕获相关的HAL驱动
输入捕获相关的HAL函数如表2.1 所示。这里仅列出了相关函数名,简要说明其功能相关函数在stm32f4xx_hal_tim.h中。
表2.1输入捕获相关HAL库函数
函数名 | 功能描述 |
---|---|
HAL_TIM_IC_Init() | 输入捕获初始化,需先执行HAL_TIM_Base_Init()进行定时器初始化 |
HAL_TIM_IC_ConfigChannel() | 输入通道配置 |
HAL_TIM_IC_Start() | 启动输入捕获,需要先执行HAL_TIM_Base_start()启动定时器 |
HAL_TIM_IC_Stop() | 停止输入捕获 |
HAL_TIM_IC_Start_IT() | 以中断方式启动输入捕获,需要先执行HAL_TIM_Base_start_IT()启动定时器 |
HAL_TIM_IC_Stop_IT() | 停止输入捕获 |
HAL_TIM_IC_GetState() | 返回定时器状态,与HAL_TIM_Base_GetState()功能相同 |
__HAL_TIM_SET_CAPTUREPOLARITY() | 设置捕获输入极性,上跳沿、下降沿或双边捕获 |
__HAL_TIM_SET_COMPARE() | 设置比较寄存器CCR的值 |
__HAL_TIM_GET_COMPARE() | 读取比较寄存器CCR的值 |
HAL_TIM_IC_CaptureCallback() | 产生捕获事件时的回调函数 |
二、 输入捕获检测方波占空比
2.1 原理
如图2.1所示,如果两个上跳沿的捕获发生在同一个计数周期内,两个计数值分别为CCR1和CCR2,则方波的周期为CCR2-CCR1个计数周期,根据定时器的周期就可以计算出方波周期和频率。如果方波周期超过定时器的计数周期,或两次输入捕获发生在相邻的两个定时周期内,如图2.2中CCR2和CCR3,则只需将计数器的计数周期和UEV事件发生次数考虑进去即可,根据CCR2和CCR3计算的脉冲周期应该是 ARR - CCR1 + CCR2。
输入捕获还可以对输入设置滤波,滤波系数0 ~ 15,用于输入抖动时的处理。输入捕获还可以设置与分频系数 N,数值N的取值为1、2、4或8,表示发生了N个时间才执行一次的捕获。
图2.1 捕获PWM高电平示意图(1)
图2.2 捕获PWM高电平示意图(2)
本次示例通过按键去提供一次高电平,测试按键按下时的高电平时间,所以需要将TIM输入捕获的引脚接到按键的引脚上,可以在CubeMx里面将按键引脚配置成定时器输入捕获模式(如图2.3)
通过观察开发板原理图,如图2.3 到 图2.5所示,可以发现KEY_UP按键的引脚可以实现配成TIM2_CH1的输入捕获通道。
图2.3 CubeMx里按键引脚配置
图2.4 按键原理图(1)
图2.5 按键原理图(2)
2.2 STM32CubeMx设置
1、TIM配置
如图所示,按照图2.6 中的步骤将TIM2的通道1配置输入捕获模式,还需要使能TIM2中断(在中断里面计数高电平脉冲)。
图2.6 CubeMx里TIIM2输入捕获设置
图2.7 使能TIM2中断(1)
图2.8 使能TIM2中断(2)
因为需要通过串口将高电平时间计算出来,所以实验也需要配置好串口,本次用到的是串口1
图2.9 使能TIM2中断(2)
配置完成后生成工程。
2.3 程序设计
STM32CubeMx配置结束后,生成工程。先进行初步分析STM32CubeMx生成的工程,以便更好的理解STM32CubeMx里面的配置。
图2.10 Counter Settings
如图所示,逐行解释上图片中标记的代码
- htim2.Instance = TIM2;
将定时器设置为TIM2。 - htim2.Init.Prescaler = 84-1;
设置预分频器的值为83(实际值减去1)。预分频器用于将输入时钟频率分频,从而得到更低的计数频率。在这里,输入时钟频率将被除以84,获得的计数频率为84Mhz /(84-1 +1) = 1MHz。 - htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
将计数模式设置为向上计数模式。在向上计数模式下,计数器从0递增到自动重载值。 - htim2.Init.Period = 0xFFFFFFFF;
设置自动重载寄存器的值为0xFFFFFFFF(32位最大值)。这意味着计数器将从0递增到0xFFFFFFFF,然后重新从0开始循环。 - htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
设置时钟分频系数为不分频(TIM_CLOCKDIVISION_DIV1)。时钟分频用于进一步分频计数器的时钟频率。 - htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
启用自动重载预装载功能。当该功能启用时,新的自动重载值在下一个更新事件时生效,以避免在计数器工作时意外更改自动重载值。
图2.10 Counter Settings设置
- sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
这段代码的作用是将定时器的时钟源设置为内部时钟。sClockSourceConfig是一个结构体,其中的ClockSource成员用于配置定时器的时钟源。通过将ClockSource成员设置为TIM_CLOCKSOURCE_INTERNAL,代码指定定时器使用内部时钟作为计数时钟源。
图2.11 TRGO Parameters和Input Capture Channel设置
-
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
配置定时器的主输出触发源为 TIM_TRGO_RESET,表示在每次定时器重置(复位)时触发主输出触发事件。具体说,当定时器的计数值达到最大值并发生溢出时,定时器将自动复位到初始值。此时,如果配置了主输出触发源为 TIM_TRGO_RESET,就会产生一个触发事件,可以用来同步其他外设或执行相关的操作 -
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
禁用主从模式,即该定时器不会作为其他定时器的主定时器。 -
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
设置输入捕获通道的触发极性为上升沿触发。 -
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
配置输入捕获通道的输入源选择为直接触发模式。捕获选择器设置为 TIM_ICSELECTION_DIRECTTI 时,定时器会直接读取输入端的电平变化,并进行相应的计数和捕获操作。这通常适用于需要测量输入信号的上升沿和下降沿的情况。
当将输入捕获选择器设置为 TIM_ICSELECTION_INDIRECTTI 时,定时器会先通过一个互补触发器(Complementary Trigger)来产生一个间接触发信号。然后,根据该触发信号的边沿变化来触发输入捕获操作。这种设置通常适用于需要测量输入信号的互补边沿(例如正边沿和负边沿)或需要使用输入信号的互补版本进行计数的情况。 -
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
设置输入捕获通道的输入分频器预分频系数为1,TIM_ICPSC_DIV1指定输入信号的频率不进行预分频,直接用于输入捕获。 -
sConfigIC.ICFilter = 0;
将输入捕获通道的滤波器配置为无滤波器。ICFilter参数表示滤波器的设置值,0表示禁用滤波器,即不对输入信号应用任何滤波。通过将ICFilter设置为0,可以确保输入捕获模式下的输入信号不受滤波器的影响,即直接获取输入信号的原始状态。这适用于需要实时响应输入信号变化或对输入信号精确测量的情况。
了解了上面的 tim.c 里面的基础配置后,开始在生成的工程上编写用户代码 实现输入捕获功能。
(1)首先,需要对实现“串口重定向”,printf可以将指定字符打印到电脑的显示器上,但是要想使用printf在单片机中通过串口打印出来,就需要重定向到串口配置上。
注:如果用到的是串口2,将“串口重定向中的”USART1换成USART2即可。
/* 串口重定向 */
int fputc(int ch,FILE *f)
{
while(!(USART1->SR &(1<<7)));
USART1->DR = ch;
return ch;
}
(2)进行相关的变量定义。
uint32_t tim_value=0; /* 储存计数器的记录值 */
uint32_t tim_over=0; /* 计数器溢出的个数 */
uint32_t time; /* 高电平持续时间 */
/* 捕获状态 */
typedef enum{
idle = 0, /* 空闲 */
runing, /* 运行*/
end /* 结束*/
}IC_STATE;
IC_STATE ic_state; /* 定义结构体变量 */
(3)启动定时器TIM2的中断模式,并开始输入捕获功能。
HAL_TIM_Base_Start_IT(&htim2); /* 启动TIM2定时器的中断模式 */
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); /* 以中断方式开启TIM2的输入捕获模式 */
(4) HAL_TIM_PeriodElapsedCallback() 函数是定时器计数溢出回调函数。当定时器的计数达到最大值并发生溢出时,该回调函数会被调用。在这段代码中,首先判断定时器是否为TIM2,然后检查状态是否为正在运行状态(ic_state == running)。如果满足条件,则将定时器溢出值(tim_over)自增一次。
HAL_TIM_IC_CaptureCallback() 函数是定时器输入捕获回调函数。它在定时器TIM2的输入捕获事件发生时被调用。在这段代码中,同样首先判断定时器是否为TIM2。然后根据当前状态判断执行不同的操作:
- 如果状态为idle(空闲),则表示开始进行输入捕获。将状态设置为running(正在运行),计数值清零,将通道1的捕获极性设置为下降沿捕获,并将定时器的计数值也设置为0。
- 如果状态不为idle,表示之前已经开始了输入捕获过程,此时需要停止输入捕获功能,将状态设置为end(结束),获取此时的计数值,然后将通道1的捕获极性设置为上升沿捕获。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim2){ /* 判断是否为定时器TIM2 */
if(ic_state == runing){ /* 判断是否为是 正在运行状态 */
tim_over ++; /* 定时器溢出值计数 */
}
}
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim2){ /* 判断是否为定时器TIM2 */
if(ic_state == idle){ /* 判断状态是不是 空闲 */
ic_state = runing; /* 将状态设置为 正在运行 */
tim_value = 0; /* 计数值清零 */
__HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_FALLING); /* 将TIM2的通道1捕获极性设置为下降沿捕获 */
__HAL_TIM_SET_COUNTER(&htim2,0); /* 将TIM2的计数值设置为0 */
}else{ /* 此时不是空闲状态 */
HAL_TIM_IC_Stop_IT(&htim2,TIM_CHANNEL_1);/* 停止定时器2输入捕获功能 */
ic_state = end; /* 将状态设置为 结束 */
tim_value = HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_1); /* 获取此时计数值 */
__HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING);/* 将TIM2的通道1捕获极性设置为上升沿捕获 */
}
}
}
2.4 示例结果
接上串口后观察打印结果,如图2.12所示,其显示的高电平持续时间和图2.13中示波器测出来的相同。
图2.12 通过串口打印出的高电平时间
图2.13 通过示波器捕获的按键高电平时间
三、 输入捕获检测PWM频率和占空比
3.1 原理
如图3.1所示,想要测出PWM的周期和频率,必须知道PWM的占空比和周期(周期的倒数 = 频率),从这个角度去考虑,要进行三次捕获才能实现要求。如果三个上跳沿的捕获发生在同一个计数周期内,如图3.1,发生三次捕获的计数值分别为CCR0,CCR1和CCR2,则方波的周期为CCR2-CCR1个计数周期,方波的高电平周期是CCR2-CCR1个计数周期,根据定时器的周期就可以计算出方波占空比和频率。如果方波周期超过定时器的计数周期,或两次输入捕获发生在相邻的两个定时周期内,如图3.2,发生3次捕获的计数值是CC0、CCR1和CCR2,则只需将计数器的计数周期和UEV事件发生次数N考虑进去即可,计算的脉冲周期应该是 ARR * N + CCR2 - CCR1。
注:在程序中为了更方便的进行计数和计算,在发生第一次上升沿捕获时,通过__HAL_TIM_SET_COUNTER()函数将定时器的值设置为0,相当于CCR0 = 0,这点在理解程序时需要注意。
图3.1 定时器输入捕获测量PWM(1)
图3.2 定时器输入捕获测量PWM(2)
3.2 STM32CubeMx设置
因为此处需要产生一个PWM,此处用TIM2产生一个频率为1K的PWM方波,如何产生PWM请参考这篇文章: HAL库STM32常用外设教程(一)—— 定时器 输出PWM
将定时器TIM3的通道1配置成输入捕获通道,如图3.3。
图3.3 定时器输入捕获配置
使能定时器TIM3中断,如图3.4和3.5。
图3.4 定时器中断使能(1)
图3.5 定时器中断使能(2)
3.3 程序设计
(1)参考 2.3节 先进行 串口重定向,完成后再进行下面相关变量的定义。
uint16_t ccr1_cnt = 0; /* 发生第一次下升沿捕获时CCR1的值 */
uint16_t ccr2_cnt = 0; /* 发生第二次上升沿捕获时CCR1的值 */
uint16_t Period_cnt = 0; /* 发生计数器溢出事件的次数(过渡用) */
uint16_t Period_cnt1 =0; /* 发生计数器溢出事件的次数--计数1 */
uint16_t Period_cnt2 = 0; /* 发生计数器溢出事件的次数--计数2 */
uint16_t ic_flag = 0; /* 输入捕获标志 */
uint16_t end_flag = 0; /* 捕获结束标志 */
float frequency = 0; /* 频率 */
float duty_cycle = 0; /* 占空比 */
(2)
① 启动定时器TIM2通道1的PWM输出功能以及定时器TIM3的输入捕获功能。
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1); /* 启动定时器2通道1的PWM输出功能 */
HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1); /* 启动定时器3的输入捕获功能 */
② 这段代码的主要作用是获取定时器输入捕获的PWM信号的频率和占空比,并将其输出到串口进行显示。
首先,通过 HAL_Delay(500) 函数进行延时,等待一段时间(这里是500毫秒)。然后,通过判断 end_flag 是否为1来确定是否进行了输入捕获,即判断是否完成了三次输入捕获过程、如果 end_flag 为1,表示完成了三次输入捕获,可以进行频率和占空比的计算。
其中,Period_cnt1表示定时器TIM3第一次捕获到下降沿时溢出次数, ccr1_cnt 表示第一次捕获到下降沿时的CCR寄存器值。 Period_cnt2 表示定时器TIM3第二次捕获到上升沿时溢出次数, ccr2_cnt 表示第二次捕获到上升沿时的CCR寄存器值。
计算占空比的公式为:
(Period_cnt1 * 65536 + ccr1_cnt + 1) / (Period_cnt2 * 65536 + ccr2_cnt + 1) * 100
(Period_cnt1 * 65536 + ccr1_cnt + 1) : 高电平时间
(Period_cnt2 * 65536 + ccr2_cnt + 1): 一个周期时间
注:
Ⅰ、65535是CubeMx里面设置的自动重装载(ARR)的值 ;
Ⅱ、涉及到的 “+1” 是因为计数时时从0开始计数的。
计算频率的公式为:
1000000 / (Period_cnt2 * 65536 + ccr2_cnt + 1)
其中1000000 = 1MHz是因为总线时钟频率为84MHz,预分频系数(PSC)为84 - 1,故得出此时TIM3的时钟频率为
84MHz /(84-1 +1)=84000 000 /(84-1 +1) = 1000 000
最后,通过printf语句打印输出计算得到的频率和占空比。完成打印后,将end_flag置为0,准备进行下一次输入捕获。
注:在printf函数的格式字符串中,%%表示一个百分号字符。
HAL_Delay(500); /* 500ms打印一次 */
if(end_flag){ /* 判断是否结束 */
duty_cycle=(float)(Period_cnt1 * 65536 + ccr1_cnt + 1) * 100 /(Period_cnt2 * 65536 + ccr2_cnt + 1); /* 计算占空比 */
frequency=1000000 / (float)(Period_cnt2 * 65536 + ccr2_cnt + 1); /* 计算频率 */
printf("\r\n 频率 = %.2f Hz,占空比 = %.2f %%",frequency,duty_cycle);
end_flag = 0; /* 将捕获结束标志置0(准备下一次捕获) */
}
(3)
① HAL_TIM_PeriodElapsedCallback() 函数是定时器计数溢出回调函数。当定时器的计数达到最大值并发生溢出时,该回调函数会被调用。在这段代码中,Period_cnt变量用于记录定时器计数溢出的次数。每次回调函数被调用时,Period_cnt会自增一次,以表示又经过了一段时间。
② HAL_TIM_IC_CaptureCallback() 函数是定时器输入捕获回调函数。它在定时器的输入捕获事件发生时被调用。在这段代码中,主要针对定时器TIM3进行处理。根据ic_flag的不同值,可以确定当前处于输入捕获的哪个阶段。
- 阶段一:第一次捕获到上升沿时,将相关参数清零,并将输入捕获设置为等待第二个阶段。
- 阶段二:第一次捕获到下降沿时,获取存放在CCR寄存器的捕获值(CCR1),并记录计时器溢出次数1。然后将输入捕获设置为等待第三个阶段。
- 阶段三:第二次捕获到上升沿时,获取存放在CCR寄存器的捕获值(CCR2),并记录计时器溢出次数2。完成两次输入捕获后,将输入捕获设置为等待第一个阶段,并将end_flag置为1,表示完成一次PWM的输入捕获。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){ /* 定时器计数溢出回调函数 */
Period_cnt ++; /* 定时器计数溢出时间次数 */
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){ /* 定时器输入捕获回调函数 */
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1){ /* 判断是不是定时器TIM1 */
if(end_flag == 0){ /* 判断结束标志是不是0,在打印期间不进行捕获,防止打印时相关数值改变 */
switch(ic_flag){ /* 判断此时处于捕获的第几个阶段 */
case 0: /* 阶段一:第一次捕获到上升沿 */
{
__HAL_TIM_SET_COUNTER(&htim3,0); /* 将定时器3计数设置为0 */
ccr1_cnt = 0; /* 将相关参数清0 */
ccr2_cnt =0;
Period_cnt = 0;
Period_cnt1 = 0;
Period_cnt2 = 0;
__HAL_TIM_SET_CAPTUREPOLARITY(&htim3,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_FALLING);/* 设置成下降沿捕获 */
ic_flag = 1; /* 捕获设置为等待第二个阶段 */
break;
}
case 1: /* 阶段二:第一次捕获到下降沿 */
{
ccr1_cnt = __HAL_TIM_GET_COMPARE(&htim3,TIM_CHANNEL_1);/* 获取存放在CCR寄存器的值(捕获值CCR1) */
Period_cnt1 = Period_cnt; /* 获取计时器溢出次数1 */
__HAL_TIM_SET_CAPTUREPOLARITY(&htim3,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING); /* 设置成上升沿捕获 */
ic_flag = 2; /* 捕获设置等待为第三个阶段 */
break;
}
case 2: /* 阶段三:第二次捕获到上升沿 */
{
ccr2_cnt = __HAL_TIM_GET_COMPARE(&htim3,TIM_CHANNEL_1);/* 取存放在CCR寄存器的值(捕获值CCR2) */
Period_cnt2 = Period_cnt; /* 获取计时器溢出次数2 */
ic_flag = 0; /* 捕获设置等待为第一个阶段 */
end_flag = 1; /* 完成一次捕获,将标志置1 */
break;
}
}
}
}
}
3.3 示例结果
接上串口后观察打印结果,如图3.7和图3.8 所示,其显示的PWM频率域PWM占空比和图2.13中示波器测出来的相同。
图3.6 接线
图3.7 串口结果显示
图3.8 示波器显示
四、用定时器ETR方式计算PWM脉冲数
4.1 ETR计算脉冲数原理
通过示例2能够计算PWM的占空比和频率,如果单纯的计算PWM数,就不得不提到定时器的另一个模式:ETR(External Trigger)模式。在这种模式下,定时器的计数器会在PWM信号发生时启动计数。计数器会开始累积时钟脉冲的数量,这个数量与PWM信号的脉冲数成正比。
图4.1 定时器ETR模式框图
4.2 STM32CubeMx设置
(1)因为此处需要产生一个PWM,此处用TIM2产生一个频率为1K的PWM方波,如何产生PWM请参考这篇文章:HAL库STM32常用外设教程(一)—— 定时器 输出PWM ,TIM2的配置如图4.2所示。
图4.2 定时器TIM2 STM32CubeMx配置
(2)TIM3设置成ETR模式,如图4.3所示。然后使能TIM3中断,如图4.4所示
图4.3 定时器TIM3 STM32CubeMx配置
图4.4 使能定时器TIM3 中断
(2)通过TIM6设置成1s的计时,如图4.6所示,之所以设置成1s进行计时是因为PWM频率的定义为:
在单位时间内,PWM信号中的脉冲从起始到结束再到下一个脉冲的周期性重复次数。PWM频率通常以赫兹(Hz)为单位。
通俗来讲,1s内产生 x 个脉冲,其频率就为 x 赫兹。
但是有一点需要注意,ETR模式是用来测量脉冲个数的,本次示例通过定时1s去测量ETR的数值(脉冲的个数)来计算脉冲的频率,此方法是不严谨的,因为脉冲的频率可能是不断变化的,举例来说:
在图4.5中,一定时间内ETR测到的PWM1和PWM2的脉冲个数都是3个,如果按照本次示例中的测量方法得出的结论是PWM1和PWM2的频率是一样的(一定时间内脉冲个数是一样的),所以这种方式只能计算频率不变的脉冲频率。
图4.5 两种PWM
图4.6 定时器TIM6 STM32CubeMx配置
然后使能定时器6中断,如图4.7所示。需要在TIM6的溢出中断里面进行计数,在“NVIC”里再次检测中断TIM3和TIM6的中断是否开启。如图4.8所示。
图4.7 使能定时器TIM6 中断
图4.8 STM32Cube"NVIC"设置
4.3 程序设计
(1)参考 2.3节 先进行 串口重定向,完成后再进行下面相关变量的定义。
uint32_t count_over = 0; /* 定时器计数溢出次数 */
uint32_t count_cnt = 0; /* 定时器计数值 */
uint32_t count = 0; /* 1s 内脉冲总数 */
uint8_t printf_flag = 0;
(2)启动TIM2定时器的PWM输出,以中断方式启动TIM3定时器和TIM6定时器。
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1); /* 启动定时器TIM2的PWM输出 */
HAL_TIM_Base_Start_IT(&htim3); /* 以中断方式启动定时器TIM3 */
HAL_TIM_Base_Start_IT(&htim6); /* 以中断方式启动定时器TIM6 */
(3)在定时器TIM3的中断服务例程里面进行溢出计数,在定时器6的中断服务例程里面通过函数 __HAL_TIM_GET_COUNTER()进行读出此时的CNT值,将打印标志置1,此时主循环检测到该标志置1后就可以进行打印,并将溢出值清0,定时器3的计数值清零,为下一秒计算脉冲频率做准备。
void TIM3_IRQHandler(void)
{
/* USER CODE BEGIN TIM3_IRQn 0 */
/* USER CODE END TIM3_IRQn 0 */
HAL_TIM_IRQHandler(&htim3);
/* USER CODE BEGIN TIM3_IRQn 1 */
count_over ++;
/* USER CODE END TIM3_IRQn 1 */
}
void TIM6_DAC_IRQHandler(void)
{
/* USER CODE BEGIN TIM6_DAC_IRQn 0 */
count_cnt = __HAL_TIM_GET_COUNTER(&htim3); /* 获取此时的CNT值 */
printf_flag = 1; /* 将打印标志置1 */
count_over = 0; /* 溢出计数清0 */
__HAL_TIM_SET_COUNTER(&htim3,0); /* 将定时器3的计数值清0 */
/* USER CODE END TIM6_DAC_IRQn 0 */
HAL_TIM_IRQHandler(&htim6);
/* USER CODE BEGIN TIM6_DAC_IRQn 1 */
/* USER CODE END TIM6_DAC_IRQn 1 */
}
(4)在主循环里面通过判断 printf_flag标志将PWM频率打印出来,之所以放到主循环里面,是因为中断里面遵循“快进快出”的原则,在中断里面打印会占用过多的时间。
count = count_over * 65535 + count_cnt;
上述公式是计算的PWM的脉冲数,因为本次示例中自动重装载值(ARR)的数值设置的是65535,所以每溢出一次计数65535个,也是公式中count_over * 65535的由来。
if(printf_flag){ /* 判断中断标志是否被置1 */
count = count_over * 65535 + count_cnt; /* 计算1s内产生的脉冲数,即脉冲频率 */
printf("频率为 %d Hz \n\r",count);
printf_flag = 0; /* 将中断标志清0,为下一次打印做准备 */
}
4.4 示例结果
通过杜邦线将PA0引脚和PD2引脚相连,就可以在串口工具里面看到输出的频率,本次示例为验证输出的是否正确,在PA0引脚上又接了一个示波器,观察输出的波形,从图4.10和4.11可以看出,产生PWM的频率是1KHz,捕获到的频率也是1KHz。
图4.9 接线
图4.10 串口输出频率
图4.11 示波器显示
4.5 问题反思
(1)在做该实验时,搞错了一个函数,将 __HAL_TIM_GET_COUNTER()
函数错用成了 __HAL_TIM_GET_COMPARE()函数,下面补充一下这两个函数的作用
① __HAL_TIM_GET_COUNTER()函数
- 作用:用于获取定时器的当前计数器值。
- 示例:
uint32_t counterValue = __HAL_TIM_GET_COUNTER(&htim);
- 这个宏返回当前定时器的计数器寄存器(CNT)的值。在定时器工作时,计数器值不断增加,通过这个宏可以读取当前的计数值,用于查看定时器整体的计数状态。
② __HAL_TIM_GET_COUNTER()函数
- 作用:用于获取定时器的比较寄存器值。。
- 示例:
uint32_t compareValue = __HAL_TIM_GET_COMPARE(&htim, TIM_CHANNEL_1);
- 这个宏返回指定通道(channel)的比较寄存器(Capture/Compare Register,通常缩写为CCR的值。比较寄存器通常用于与计数器值进行比较,从而确定何时触发中断或改变输出状态,用于查看与特定通道相关联的比较状态。这通常与定时器的PWM生成、捕获比较等操作有关。
(2)在进行 “ printf ” 打印时,第一次‘’将其放在了TIM6定时器的中断服务例程里面,结果发现打印出来的频率都是996Hz,然后考虑到了中断里面“快进快出”原则 ,通过置标志的方式将打印内容放在了while循环里面,打印出的结果是1000Hz。在单片机中,这一原则有助于确保中断服务程序的及时响应和高效执行,该原则一般包括两个方面:
① 尽量减小中断服务程序的执行时间:
- 中断服务程序(ISR - Interrupt Service Routine)应该尽量简洁而高效。在中断服务程序中,应避免长时间的计算、复杂的数据处理等操作。
- 长时间执行的中断服务程序会导致其他中断被阻塞,这可能影响系统的实时性和响应性。
- 应该专注于在中断服务程序中完成必要的最小工作量,将复杂和耗时的任务移到主循环中或其他地方处理。
②尽量减小中断响应时间:
- 中断响应时间是指从中断发生到中断服务程序开始执行的时间。较短的中断响应时间有助于提高系统的实时性。
- 在设计中,可以通过适当配置中断控制器、合理选择中断优先级、以及优化硬件和软件的交互,来缩短中断响应时间。
- 另外,及时清除中断标志位也是减小中断响应时间的一部分,确保中断服务程序能够迅速响应新的中断。
五、总结
上述主要讲述了定时器输入捕获的原理及其三个应用示例,包括每个示例的HAL库配置、程序编写以及测试结果。在学习输入捕获时,应该多去琢磨其捕获的原理,可以通过画图去研究其过程,例如图3.1和图3.2,明白该在哪一个跳变沿进行捕获等。
参考书籍和文章:
1、《STM32Cube高效开发教程(基础篇)》王维波
2、《STM32F4xx中文参考手册》
3、《STM32F407 探索者开发指南》
4、【STM32】标准库与HAL库对照学习教程十–输入捕获实验
5、[018] [STM32] 定时器 基本定时/输出比较/输入捕获功能详解与HAL库编程
6、STM32频率测量
7、第五节:STM32输入捕获(用CubeMx学习STM32)
8、cube配置定时器ETR2模式测频实验
9、STM32的ETR引脚计数功能
10、基于STM32定时器ETR信号的应用示例
生活总是这样,不能叫人处处都满意。但我们还要热情的活下去。人活一生,值得爱的东西很多,不要因为一个不满意,就灰心。
——《人生》