27. 定时器的PWM脉宽测量
一、PWM脉宽测量原理
PWM 脉宽测量是输入捕获模式的一个特例。PWM 输入模式经常被应用于测量 PWM 脉宽和频率。
第一,确定定时器时钟源。计数器的计数频率确定了测量的精度。
第二,确定 PWM 输入的通道。PWM 输入模式下测量 PWM,PWM 信号输入只能从通道 1(CH1)或者通道 2(CH2)输入。
第三,确定 IC1 和 IC2 的捕获边沿。这里以通道 1(CH1)输入 PWM 为例,一般我们习惯设置 IC1 捕获边沿为上升沿捕获,IC2 捕获边沿为下降沿捕获。
第四,选择触发输入信号(TRGI)。这里也是以通道 1(CH1)输入 PWM 为例,那么我们就应该选择 TI1FP1 为触发输入信号。如果是通道 2(CH2)输入 PWM,那就选择 TI2FP2 为触发输入信号。可以看到这里并没有对应通道 3(CH3)或者通道 4(CH4)的触发输入信号,所以我们只选择通道 1 或者通道 2 作为 PWM 输入的通道。
第五,从模式选择:复位模式。复位模式的作用是:在出现所选触发输入 (TRGI) 上升沿时,重新初始化计数器并生成一个寄存器更新事件。
第六,读取一个 PWM 周期内计数器的计数个数,以及高电平期间的计数个数,再结合计数器的计数周期(即计一个数的时间),最终通过计算得到输入的 PWM 周期和占空比等参数。以通道 1(CH1)输入 PWM,设置 IC1 捕获边沿为上升沿捕获,IC2 捕获边沿为下降沿捕获为例,那么 CCR1 寄存器的值+1 就是 PWM 周期内计数器的计数个数,CCR2 寄存器的值+1 就是PWM 高电平期间计数器的计数个数。通过这两个值就可以计算出 PWM 的周期或者占空比等参数。
这里以通道 1(CH1)输入 PWM,设置 IC1 捕获边沿为下降沿捕获,IC2 捕获边沿为上升沿捕获为例,那么 CCR1 寄存器的值+1 依然是 PWM 周期内计数器的计数个数,但是CCR2 寄存器的值+1 就是 PWM 低电平期间计数器的计数个数。通过这两个得到的参数依然可以计算出 PWM 的其它参数。
从时序图可以看出,计数器的计数模式是递增计数模式。从左边开始看,当 TI1 来了上升沿时,计数器的值被复位为 0(原因是从模式选择为复位模式),IC1 和 IC2 都发生捕获事件。然后计数器的值计数到 2 的时候,IC2 发生了下降沿捕获,捕获事件会导致这时候的计数器的值被锁存到 CCR2 寄存器中,该值 +1 就是高电平期间计数器的计数个数。最后计数器的值计数到 4 的时候,IC1 发生了上升沿捕获,捕获事件会导致这时候的计数器的值被锁存到 CCR1 寄存器中,该值 +1 就是 PWM 周期内计数器的计数个数。
二、常用的寄存器
2.1、TIMx控制寄存器
TIMx_CR1 寄存器位 7(ARPE)用于控制自动重载寄存器是否进行缓冲,如果 ARPE 位置 1,ARR 起缓冲作用,即只有在更新事件发生时才会把 ARR 的值写入其影子寄存器里;如果 ARPE 位置 0,那么修改自动重载寄存器的值时,该值会马上被写入其影子寄存器中,从而立即生效。
TIMx_CR1 寄存器 CMS[6:5] 位,用于设置边沿对齐模式还是中心对齐模式。当 CMS[1:0] 位设置为 00 时,为边沿对齐模式,其它值为中心对齐模式。
TIMx_CR1 寄存器位 4 DIR 位,用于控制定时器的计数方向。设置 DIR 位为 0 时,为递增计数。设置 DIR 位为 1 时,为递减计数。当定时器配置为中心对齐模式或编码器模式时,该位为只读状态。
TIMx_CR1 寄存器位 0 CEN 位,用于使能计数器的工作,必须要设置该位为 1,计数器才会开始计数。
2.2、TIMx从模式控制寄存器
如果要让外部引脚脉冲信号作为定时器的时钟源,所以位 [2:0] 设置的值是 111,即外部时钟模式 1。位 [6:4] 是触发选择设置,TIMx_CH1 对应 TI1FP1,TIMx_CH2 则对应 TI2FP2。ETF[3:0] 和 ETPS[1:0] 分别是外部触发滤波器和外部触发预分频器,
2.3、TIMx DMA中断/使能寄存器
该寄存器用于使能/失能触发 DMA 请求、捕获/比较中断以及更新中断。我们需要用到中断来处理捕获数据,所以必须开启通道 1 的捕获比较中断,即 CC1IE 设置为 1。同时我们还需要在定时器溢出中断中累计定时器溢出的次数,所以还需要使能定时器的更新中断,即 UIE 置 1。
2.4、TIMx捕获/比较模式寄存器
该寄存器的有些位在不同模式下,功能不一样。TIMx_CCMR1 寄存器对应于通道 1 和通道 2 的设置,CCMR2 寄存器对应通道 3 和通道 4。如:TIMx_CCMR1 寄存器位 [7:0] 用于捕获/比较通道 1 的控制,而位 [15:8] 则用于捕获/比较通道 2 的控制。
其中 CC1S[1:0],这两个位用于 CCR1 的通道配置,这里我们设置 IC1S[1:0]=01,也就是配置 IC1 映射在 TI1 上。
输入捕获 1 预分频器 IC1PSC[1:0] 设置每来几次事件执行一次捕获。这里是 1 次高电平脉冲就触发 1 次计数,所以不用分频选择 00 即可。
输入捕获 1 滤波器 IC1F[3:0],这个用来设置输入采样频率和数字滤波器长度。其中,\(f_{CK\_INT}\) 是定时器时钟源频率。
2.5、TIMx捕获/比较使能寄存器
该寄存器控制着各个输入输出通道的开关和极性。要使能输入捕获,必须设置 CC1E=1,而 CC1P 则根据自己的需要来配置。
2.6、TIMx计数器
TIM2/TIM5 的计数寄存器是 32 位的,TIM3/TIM4 的计数寄存器都是 16 位有效的,计数模式可以是递增计数模式、递减计数模式和中心对齐计数模式。其他定时器和基本定时器一样,可以直接写该寄存器设置计数的初始值,也可以读取该寄存器获取计数器值。
2.7、TIMx预分频器
定时器的预分频寄存器都是 16 位的,即写入该寄存器的数值范围是 0 到 65535,表示 1 到 65536 分频。比如我们要 8400 分频,就往该寄存器写入 8399。
2.8、TIMx自动重载寄存器
在 F4 系列中,TIM2 和 TIM5 的自动重装载寄存器是 32 位的,其他通用定时器自动重载寄存器是低 16 位有效。该寄存器可以由 APRE 位设置是否进行缓冲。计数器的值会和自动重装寄存器影子寄存器进行比较,当两者相等,定时器就会溢出,从而发生更新事件,如果打开了更新中断,还会发生更新中断。
2.9、TIMx捕获比较寄存器
该寄存器用来存储发生捕获事件时,TIMx_CNT 的值,我们从 TIMx_CCR1 就可以读出通道 1 捕获事件发生时刻的 TIMx_CNT 值,通过两次捕获(一次上升沿捕获,一次下降沿捕获)的差值,就可以计算出高电平脉冲的宽度(注意,对于高电平脉宽太长的情况,还要计算定时器溢出的次数)。
三、定时器对应通道引脚
【1】、TIM2 对应通道引脚及其重映射
通道名 | 通道引脚 | 重映射通道引脚 |
---|---|---|
Channel 1 | PA0 | PA5/PA15 |
Channel 2 | PA1 | PB3 |
Channel 3 | PA2 | PB10 |
Channel 4 | PA3 | PB11 |
【2】、TIM3 对应通道引脚及其重映射
通道名 | 通道引脚 | 重映射通道引脚 |
---|---|---|
Channel 1 | PA6 | PC6/PB4 |
Channel 2 | PA7 | PC7/PB5 |
Channel 3 | PB0 | PC8 |
Channel 4 | PB1 | PC9 |
【3】、TIM4 对应通道引脚及其重映射
通道名 | 通道引脚 | 重映射通道引脚 |
---|---|---|
Channel 1 | PD12 | PB6 |
Channel 2 | PD13 | PB7 |
Channel 3 | PD14 | PB8 |
Channel 4 | PD15 | PB9 |
【4】、TIM5 对应通道引脚及其重映射
通道名 | 通道引脚 | 重映射通道引脚 |
---|---|---|
Channel 1 | PA0 | |
Channel 2 | PA1 | |
Channel 3 | PA2 | |
Channel 4 | PA3 |
【5】、TIM9 对应通道引脚及其重映射
通道名 | 通道引脚 | 重映射通道引脚 |
---|---|---|
Channel 1 | PE5 | PA2 |
Channel 2 | PE6 | PA3 |
【6】、TIM10 对应通道引脚及其重映射
通道名 | 通道引脚 | 重映射通道引脚 |
---|---|---|
Channel 1 | PF6 | PB8 |
【7】、TIM11 对应通道引脚及其重映射
通道名 | 通道引脚 | 重映射通道引脚 |
---|---|---|
Channel 1 | PF7 | PB9 |
【8】、TIM12 对应通道引脚及其重映射
通道名 | 通道引脚 | 重映射通道引脚 |
---|---|---|
Channel 1 | PB14 | |
Channel 2 | PB15 |
【9】、TIM13 对应通道引脚及其重映射
通道名 | 通道引脚 | 重映射通道引脚 |
---|---|---|
Channel 1 | PF8 | PA6 |
【10】、TIM14 对应通道引脚及其重映射
通道名 | 通道引脚 | 重映射通道引脚 |
---|---|---|
Channel 1 | PF9 | PA7 |
【11】、TIM1 对应通道引脚及其重映射
通道名 | 通道引脚 | 重映射通道引脚 | 互补通道名 | 互补通道引脚 | 互补重映射通道引脚 |
---|---|---|---|---|---|
Channel 1 | PE9 | PA8 | Channel 1N | PA7 | PE8/PB13 |
Channel 2 | PE11 | PA9 | Channel 2N | PB0 | PE10/PB14 |
Channel 3 | PE13 | PA10 | Channel 3N | PB1 | PE12/PB15 |
Channel 4 | PE14 | PA11 | Channel 4N |
【12】、TIM8 对应通道引脚及其重映射
通道名 | 通道引脚 | 重映射通道引脚 | 互补通道名 | 互补通道引脚 | 互补重映射通道引脚 |
---|---|---|---|---|---|
Channel 1 | PC6 | Channel 1N | PA5 | PA7 | |
Channel 2 | PC7 | Channel 2N | PB0 | PB14 | |
Channel 3 | PC8 | Channel 3N | PB1 | PB15 | |
Channel 4 | PC9 | Channel 4N |
四、PWM输入配置步骤
4.1、使能定时器时钟和对应通道的GPIO时钟
使能高级定时器的时钟。
#define __HAL_RCC_TIM1_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM1EN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM1EN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_TIM8_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM8EN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM8EN);\
UNUSED(tmpreg); \
} while(0U)
使能通用定时器的时钟。
#define __HAL_RCC_TIM5_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM5EN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM5EN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_TIM9_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM9EN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM9EN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_TIM11_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM11EN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM11EN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_TIM12_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM12EN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM12EN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_TIM13_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM13EN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM13EN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_TIM14_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM14EN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM14EN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_TIM2_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM2EN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM2EN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_TIM3_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM3EN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM3EN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_TIM4_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM4EN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM4EN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_TIM10_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM10EN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM10EN);\
UNUSED(tmpreg); \
} while(0U)
使能定时器通道引脚对应的 GPIO 的时钟。
#define __HAL_RCC_GPIOA_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_GPIOB_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOBEN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOBEN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_GPIOC_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOCEN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOCEN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_GPIOD_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIODEN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIODEN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_GPIOE_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOEEN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOEEN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_GPIOF_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOFEN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOFEN);\
UNUSED(tmpreg); \
} while(0U)
4.2、配置定时器基本工作参数
HAL 库提供定时器输入捕获的初始化函数,它的说明如下:
HAL_StatusTypeDef HAL_TIM_IC_Init(TIM_HandleTypeDef *htim);
其中,htim 是 TIM_HandleTypeDef 结构体类型指针变量(亦称定时器句柄),结构体定义如下:
typedef struct
{
TIM_TypeDef *Instance; // 寄存器基地址
TIM_Base_InitTypeDef Init; // 定时器初始化结构体
HAL_TIM_ActiveChannel Channel; // 定时器通道
DMA_HandleTypeDef *hdma[7]; // DMA管理结构体
HAL_LockTypeDef Lock; // 锁对象
__IO HAL_TIM_StateTypeDef State; // 定时器状态
__IO HAL_TIM_ChannelStateTypeDef ChannelState[4]; // 定时器通道状态
__IO HAL_TIM_ChannelStateTypeDef ChannelNState[4]; // 定时器互补通道状态
__IO HAL_TIM_DMABurstStateTypeDef DMABurstState; // DMA溢出状态
} TIM_HandleTypeDef;
Instance:指向定时器寄存器基地址,可选值如下:
#define TIM1 ((TIM_TypeDef *) TIM1_BASE)
#define TIM8 ((TIM_TypeDef *) TIM8_BASE)
#define TIM2 ((TIM_TypeDef *) TIM2_BASE)
#define TIM3 ((TIM_TypeDef *) TIM3_BASE)
#define TIM4 ((TIM_TypeDef *) TIM4_BASE)
#define TIM5 ((TIM_TypeDef *) TIM5_BASE)
#define TIM9 ((TIM_TypeDef *) TIM9_BASE)
#define TIM10 ((TIM_TypeDef *) TIM10_BASE)
#define TIM11 ((TIM_TypeDef *) TIM11_BASE)
#define TIM12 ((TIM_TypeDef *) TIM12_BASE)
#define TIM13 ((TIM_TypeDef *) TIM13_BASE)
#define TIM14 ((TIM_TypeDef *) TIM14_BASE)
Init:定时器初始化结构体,用于配置定时器的相关参数,它的定义如下:
typedef struct
{
uint32_t Prescaler; // 预分频系数
uint32_t CounterMode; // 计数模式
uint32_t Period; // 自动重装载值
uint32_t ClockDivision; // 时钟分频因子
uint32_t RepetitionCounter; // 重复计数器值
uint32_t AutoReloadPreload; // 自动重装载值预载入功能
} TIM_Base_InitTypeDef;
Prescaler:预分频系数,即写入预分频寄存器的值,范围 0 到 65535。
CounterMode:计数器计数模式,。
#define TIM_COUNTERMODE_UP 0x00000000U /*!< Counter used as up-counter */
#define TIM_COUNTERMODE_DOWN TIM_CR1_DIR /*!< Counter used as down-counter */
#define TIM_COUNTERMODE_CENTERALIGNED1 TIM_CR1_CMS_0 /*!< Center-aligned mode 1 */
#define TIM_COUNTERMODE_CENTERALIGNED2 TIM_CR1_CMS_1 /*!< Center-aligned mode 2 */
#define TIM_COUNTERMODE_CENTERALIGNED3 TIM_CR1_CMS /*!< Center-aligned mode 3 */
Period:自动重载值,即写入自动重载寄存器的值,范围 0 到 65535。
AutoReloadPreload:自动重载预装载使能,即控制寄存器 1(TIMx_CR1)的 ARPE 位。
#define TIM_AUTORELOAD_PRELOAD_DISABLE 0x00000000U /*!< TIMx_ARR register is not buffered */
#define TIM_AUTORELOAD_PRELOAD_ENABLE TIM_CR1_ARPE /*!< TIMx_ARR register is buffered */
该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功,HAL_ERROR 表示 错误,HAL_BUSY 表示 忙碌,HAL_TIMEOUT 表示 超时。
typedef enum
{
HAL_OK = 0x00U, // 成功
HAL_ERROR = 0x01U, // 错误
HAL_BUSY = 0x02U, // 忙碌
HAL_TIMEOUT = 0x03U // 超时
} HAL_StatusTypeDef;
4.3、设置对应通道引脚的工作模式
HAL 库中,提供 HAL_GPIO_Init() 函数用于配置 GPIO 功能模式,初始化 GPIO。该函数的声明如下:
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
该函数的第一个形参 GPIOx 用来 指定端口号,可选值如下:
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
第二个参数是 GPIO_InitTypeDef 类型的结构体变量,用来 设置 GPIO 的工作模式,其定义如下:
typedef struct
{
uint32_t Pin; // 引脚号
uint32_t Mode; // 模式设置
uint32_t Pull; // 上下拉设置
uint32_t Speed; // 速度设置
uint32_t Alternate; // 复用功能设置
}GPIO_InitTypeDef;
成员 Pin 表示 引脚号,范围:GPIO_PIN_0 到 GPIO_PIN_15。
#define GPIO_PIN_0 ((uint16_t)0x0001) /* Pin 0 selected */
#define GPIO_PIN_1 ((uint16_t)0x0002) /* Pin 1 selected */
#define GPIO_PIN_2 ((uint16_t)0x0004) /* Pin 2 selected */
#define GPIO_PIN_3 ((uint16_t)0x0008) /* Pin 3 selected */
#define GPIO_PIN_4 ((uint16_t)0x0010) /* Pin 4 selected */
#define GPIO_PIN_5 ((uint16_t)0x0020) /* Pin 5 selected */
#define GPIO_PIN_6 ((uint16_t)0x0040) /* Pin 6 selected */
#define GPIO_PIN_7 ((uint16_t)0x0080) /* Pin 7 selected */
#define GPIO_PIN_8 ((uint16_t)0x0100) /* Pin 8 selected */
#define GPIO_PIN_9 ((uint16_t)0x0200) /* Pin 9 selected */
#define GPIO_PIN_10 ((uint16_t)0x0400) /* Pin 10 selected */
#define GPIO_PIN_11 ((uint16_t)0x0800) /* Pin 11 selected */
#define GPIO_PIN_12 ((uint16_t)0x1000) /* Pin 12 selected */
#define GPIO_PIN_13 ((uint16_t)0x2000) /* Pin 13 selected */
#define GPIO_PIN_14 ((uint16_t)0x4000) /* Pin 14 selected */
#define GPIO_PIN_15 ((uint16_t)0x8000) /* Pin 15 selected */
成员 Mode 是 GPIO 的 模式选择,有以下选择项:
#define GPIO_MODE_AF_PP 0x00000002U // 推挽式复用
成员 Pull 用于 配置上下拉电阻,有以下选择项:
#define GPIO_NOPULL 0x00000000U // 无上下拉
#define GPIO_PULLUP 0x00000001U // 上拉
#define GPIO_PULLDOWN 0x00000002U // 下拉
成员 Alternate 用于 配置具体的复用功能,不同的 GPIO 口可以复用的功能不同,具体可参考数据手册。
#define GPIO_AF1_TIM1 ((uint8_t)0x01) /* TIM1 Alternate Function mapping */
#define GPIO_AF1_TIM2 ((uint8_t)0x01) /* TIM2 Alternate Function mapping */
#define GPIO_AF2_TIM3 ((uint8_t)0x02) /* TIM3 Alternate Function mapping */
#define GPIO_AF2_TIM4 ((uint8_t)0x02) /* TIM4 Alternate Function mapping */
#define GPIO_AF2_TIM5 ((uint8_t)0x02) /* TIM5 Alternate Function mapping */
#define GPIO_AF3_TIM8 ((uint8_t)0x03) /* TIM8 Alternate Function mapping */
#define GPIO_AF3_TIM9 ((uint8_t)0x03) /* TIM9 Alternate Function mapping */
#define GPIO_AF3_TIM10 ((uint8_t)0x03) /* TIM10 Alternate Function mapping */
#define GPIO_AF3_TIM11 ((uint8_t)0x03) /* TIM11 Alternate Function mapping */
#define GPIO_AF9_TIM12 ((uint8_t)0x09) /* TIM12 Alternate Function mapping */
#define GPIO_AF9_TIM13 ((uint8_t)0x09) /* TIM13 Alternate Function mapping */
#define GPIO_AF9_TIM14 ((uint8_t)0x09) /* TIM14 Alternate Function mapping */
4.4、配置输入通道映射和捕获边沿
定时器的输入捕获通道设置初始化函数。其声明如下:
HAL_StatusTypeDef HAL_TIM_IC_ConfigChannel(TIM_HandleTypeDef *htim, TIM_IC_InitTypeDef *sConfig, uint32_t Channel);
其中,htim 是 TIM_HandleTypeDef 结构体类型指针变量(亦称定时器句柄)。
sConfig 是 TIM_IC_InitTypeDef 结构体类型指针变量,用于配置定时器的输入捕获参数。
typedef struct
{
uint32_t ICPolarity; // 输入捕获触发方式选择
uint32_t ICSelection; // 输入捕获选择,用于设置映射关系
uint32_t ICPrescaler; // 输入捕获分频系数
uint32_t ICFilter; // 输入捕获滤波器设置
} TIM_IC_InitTypeDef;
成员变量 ICPolarity 用来 来设置输入信号的有效捕获极性,可选值如下:
#define TIM_ICPOLARITY_RISING TIM_INPUTCHANNELPOLARITY_RISING // 上升沿捕获
#define TIM_ICPOLARITY_FALLING TIM_INPUTCHANNELPOLARITY_FALLING // 下降沿捕获
#define TIM_ICPOLARITY_BOTHEDGE TIM_INPUTCHANNELPOLARITY_BOTHEDGE // 双边沿捕获
成员变量 ICSelection 用来 输入捕获选择,用于设置映射关系,可选值如下:
#define TIM_ICSELECTION_DIRECTTI TIM_CCMR1_CC1S_0 // IC1直接映射在TI1
#define TIM_ICSELECTION_INDIRECTTI TIM_CCMR1_CC1S_1 // IC1映射在TI
#define TIM_ICSELECTION_TRC TIM_CCMR1_CC1S // IC1映射在TRC
成员变量 ICPrescaler 用来 输入捕获分频系数,可选值如下:
#define TIM_ICPSC_DIV1 0x00000000U /*!< Capture performed each time an edge is detected on the capture input */
#define TIM_ICPSC_DIV2 TIM_CCMR1_IC1PSC_0 /*!< Capture performed once every 2 events */
#define TIM_ICPSC_DIV4 TIM_CCMR1_IC1PSC_1 /*!< Capture performed once every 4 events */
#define TIM_ICPSC_DIV8 TIM_CCMR1_IC1PSC /*!< Capture performed once every 8 events */
成员变量 ICFilter 用来 输入捕获滤波器设置,可选范围:0x00 ~ 0x0F
Channel 是定时器通道,范围:TIM_CHANNEL_1 到 TIM_CHANNEL_4。
#define TIM_CHANNEL_1 0x00000000U /*!< Capture/compare channel 1 identifier */
#define TIM_CHANNEL_2 0x00000004U /*!< Capture/compare channel 2 identifier */
#define TIM_CHANNEL_3 0x00000008U /*!< Capture/compare channel 3 identifier */
#define TIM_CHANNEL_4 0x0000000CU /*!< Capture/compare channel 4 identifier */
#define TIM_CHANNEL_ALL 0x0000003CU /*!< Global Capture/compare channel identifier */
该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功,HAL_ERROR 表示 错误,HAL_BUSY 表示 忙碌,HAL_TIMEOUT 表示 超时。
4.5、配置定时器的从模式
配置定时器的从模式。
HAL_StatusTypeDef HAL_TIM_SlaveConfigSynchro(TIM_HandleTypeDef *htim, const TIM_SlaveConfigTypeDef *sSlaveConfig);
其中,htim 是 TIM_HandleTypeDef 结构体类型指针变量(亦称定时器句柄)。
sSlaveConfig 是 TIM_SlaveConfigTypeDef 结构体类型指针变量,用于配置定时器的从模式。
typedef struct
{
uint32_t SlaveMode; // 从模式选择
uint32_t InputTrigger; // 输入触发源选择
uint32_t TriggerPolarity; // 输入触发极性
uint32_t TriggerPrescaler; // 输入触发预分频
uint32_t TriggerFilter; // 输入滤波器设置
} TIM_SlaveConfigTypeDef;
成员 SlaveMode 用来 从模式选择,可选值如下:
#define TIM_SLAVEMODE_TRIGGER (TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1) /*!< Trigger Mode */
#define TIM_SLAVEMODE_EXTERNAL1 (TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1 | TIM_SMCR_SMS_0) /*!< External Clock Mode 1 */
成员 InputTrigger 用来 输入触发源选择,可选值如下:
#define TIM_TS_TI1F_ED TIM_SMCR_TS_2 /*!< TI1 Edge Detector (TI1F_ED) */
#define TIM_TS_TI1FP1 (TIM_SMCR_TS_0 | TIM_SMCR_TS_2) /*!< Filtered Timer Input 1 (TI1FP1) */
#define TIM_TS_TI2FP2 (TIM_SMCR_TS_1 | TIM_SMCR_TS_2) /*!< Filtered Timer Input 2 (TI2FP2) */
成员 TriggerPolarity 用来 输入触发极性,可选值如下:
#define TIM_TRIGGERPOLARITY_RISING TIM_INPUTCHANNELPOLARITY_RISING /*!< Polarity for TIxFPx or TI1_ED trigger sources */
#define TIM_TRIGGERPOLARITY_FALLING TIM_INPUTCHANNELPOLARITY_FALLING /*!< Polarity for TIxFPx or TI1_ED trigger sources */
#define TIM_TRIGGERPOLARITY_BOTHEDGE TIM_INPUTCHANNELPOLARITY_BOTHEDGE /*!< Polarity for TIxFPx or TI1_ED trigger sources */
成员 InputTrigger 用来 输入触发预分频,可选值如下:
#define TIM_TRIGGERPRESCALER_DIV1 TIM_ETRPRESCALER_DIV1 /*!< No prescaler is used */
#define TIM_TRIGGERPRESCALER_DIV2 TIM_ETRPRESCALER_DIV2 /*!< Prescaler for External ETR Trigger: Capture performed once every 2 events. */
#define TIM_TRIGGERPRESCALER_DIV4 TIM_ETRPRESCALER_DIV4 /*!< Prescaler for External ETR Trigger: Capture performed once every 4 events. */
#define TIM_TRIGGERPRESCALER_DIV8 TIM_ETRPRESCALER_DIV8 /*!< Prescaler for External ETR Trigger: Capture performed once every 8 events. */
该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功,HAL_ERROR 表示 错误,HAL_BUSY 表示 忙碌,HAL_TIMEOUT 表示 超时。
4.6、使能中断
4.6.1、设置中断优先级分组
HAL_NVIC_SetPriorityGrouping() 函数是设置中断优先级分组函数。其声明如下:
void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup);
其中,参数 PriorityGroup 是 中断优先级分组号,可以选择范围如下:
#define NVIC_PRIORITYGROUP_0 0x00000007U /*!< 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PRIORITYGROUP_1 0x00000006U /*!< 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PRIORITYGROUP_2 0x00000005U /*!< 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PRIORITYGROUP_3 0x00000004U /*!< 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PRIORITYGROUP_4 0x00000003U /*!< 4 bits for pre-emption priority
0 bits for subpriority */
这个函数在一个工程里基本只调用一次,而且是在程序 HAL 库初始化函数里面已经被调用,后续就不会再调用了。因为当后续调用设置成不同的中断优先级分组时,有可能造成前面设置好的抢占优先级和响应优先级不匹配。如果调用了多次,则以最后一次为准。
4.6.2、设置中断优先级
HAL_NVIC_SetPriority() 函数是设置中断优先级函数。其声明如下:
void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority);
其中,参数 IRQn 是 中断号,可以选择范围:IRQn_Type 定义的枚举类型,定义在 stm32f407xx.h。
typedef enum
{
TIM1_BRK_TIM9_IRQn = 24, /*!< TIM1 Break interrupt and TIM9 global interrupt */
TIM1_UP_TIM10_IRQn = 25, /*!< TIM1 Update Interrupt and TIM10 global interrupt */
TIM1_TRG_COM_TIM11_IRQn = 26, /*!< TIM1 Trigger and Commutation Interrupt and TIM11 global interrupt */
TIM1_CC_IRQn = 27, /*!< TIM1 Capture Compare Interrupt */
TIM2_IRQn = 28, /*!< TIM2 global Interrupt */
TIM3_IRQn = 29, /*!< TIM3 global Interrupt */
TIM4_IRQn = 30, /*!< TIM4 global Interrupt */
TIM8_BRK_TIM12_IRQn = 43, /*!< TIM8 Break Interrupt and TIM12 global interrupt */
TIM8_UP_TIM13_IRQn = 44, /*!< TIM8 Update Interrupt and TIM13 global interrupt */
TIM8_TRG_COM_TIM14_IRQn = 45, /*!< TIM8 Trigger and Commutation Interrupt and TIM14 global interrupt */
TIM8_CC_IRQn = 46, /*!< TIM8 Capture Compare global interrupt */
TIM5_IRQn = 50, /*!< TIM5 global Interrupt */
} IRQn_Type;
参数 PreemptPriority 是 抢占优先级,可以选择范围:0 到 15,具体根据中断优先级分组决定。
参数 SubPriority 是 响应优先级,可以选择范围:0 到 15,具体根据中断优先级分组决定。
4.6.3、使能中断
HAL_NVIC_EnableIRQ() 函数是中断使能函数。其声明如下:
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
其中,参数 IRQn 是 中断号,可以选择范围:IRQn_Type 定义的枚举类型,定义在 stm32f407xx.h。
4.7、使能定时器更新中断
#define __HAL_TIM_ENABLE_IT(__HANDLE__, __INTERRUPT__) ((__HANDLE__)->Instance->DIER |= (__INTERRUPT__))
4.8、使能捕获中断并启动计数器
HAL_StatusTypeDef HAL_TIM_IC_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel);
其中,htim 是 TIM_HandleTypeDef 结构体类型指针变量(亦称定时器句柄)。
Channel 是 定时器通道,范围:TIM_CHANNEL_1 到 TIM_CHANNEL_4。
该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功,HAL_ERROR 表示 错误,HAL_BUSY 表示 忙碌,HAL_TIMEOUT 表示 超时。
4.9、编写中断服务函数
定时器中断服务函数为:TIMx_IRQHandler 等,当发生中断的时候,程序就会执行中断服务函数。
void TIM1_BRK_TIM9_IRQHandler(void);
void TIM1_BRK_TIM9_IRQHandler(void);
void TIM1_UP_TIM10_IRQHandler(void);
void TIM1_TRG_COM_TIM11_IRQHandler(void);
void TIM1_CC_IRQHandler(void);
void TIM2_IRQHandler(void);
void TIM3_IRQHandler(void);
void TIM4_IRQHandler(void);
void TIM5_IRQHandler(void);
void TIM8_BRK_TIM12_IRQHandler(void);
void TIM8_UP_TIM13_IRQHandler(void);
void TIM8_TRG_COM_TIM14_IRQHandler(void);
void TIM8_CC_IRQHandler(void);
HAL 库提供了一个定时器中断公共处理函数 HAL_TIM_IRQHandler()
,该函数又会调用 HAL_TIM_PeriodElapsedCallback()
等一些回调函数,需要用户根据中断类型选择重定义对应的中断回调函数来处理中断程序。
HAL 库的更新中断回调函数:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
HAL 库的输入捕获回调函数:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);
五、程序源码
这里,我们使用定时器 14 的通道 1(PF9)输出 PWM 波,用定时器 5 的通道 1(PA0)测量 PWM 的频率/周期、占空比等信息。这里,我们使用杜邦线连接 PF9 和 PA0。
定时器 PWM 测量初始化函数:
TIM_HandleTypeDef g_tim5_handle;
/**
* @brief 定时器PWM功能初始化函数
*
* @param htim 定时器句柄
* @param TIMx 定时器寄存器基地址,可选值: TIMx, x可选范围: 1 ~ 5, 8 ~ 14
* @param prescaler 预分频系数,可选值: 0 ~ 65535
* @param period 自动重装载值,可选值: 0 ~ 65535
* @param trigger 触发源,可选值: [TIM_TS_TI1FP1, TIM_TS_TI2FP2]
*/
void TIM_PWM_IC_Init(TIM_HandleTypeDef *htim, TIM_TypeDef *TIMx, uint16_t prescaler, uint16_t period, uint32_t trigger)
{
TIM_SlaveConfigTypeDef TIM_SlaveConfigStruct = {0};
TIM_IC_InitTypeDef TIM_IC_InitStruct = {0};
htim->Instance = TIMx; // 定时器寄存器基地址
htim->Init.CounterMode = TIM_COUNTERMODE_UP; // 计数模式
htim->Init.Prescaler = prescaler; // 预分频系数
htim->Init.Period = period; // 自动重装载值
HAL_TIM_IC_Init(htim);
// 在PWM的高电平时,触发源触发通道,记录PWM的周期
TIM_SlaveConfigStruct.SlaveMode = TIM_SLAVEMODE_RESET; // 复位模式
TIM_SlaveConfigStruct.InputTrigger = trigger; // 输入触发源
TIM_SlaveConfigStruct.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING; // 输入触发极性
TIM_SlaveConfigStruct.TriggerPrescaler = TIM_ICPSC_DIV1; // 输入触发预分频
TIM_SlaveConfigStruct.TriggerFilter = 0; // 输入滤波器设置
HAL_TIM_SlaveConfigSynchro(htim, &TIM_SlaveConfigStruct);
// 通道1上升沿捕获,记录PWM的周期
TIM_IC_InitStruct.ICPolarity = TIM_ICPOLARITY_RISING; // 上升沿捕获
TIM_IC_InitStruct.ICPrescaler = TIM_ICPSC_DIV1; // 1分频
TIM_IC_InitStruct.ICFilter = 0; // 不滤波
if (trigger == TIM_TS_TI1FP1)
{
TIM_IC_InitStruct.ICSelection = TIM_ICSELECTION_DIRECTTI; // 直接映射
}
else if (trigger == TIM_TS_TI2FP2)
{
TIM_IC_InitStruct.ICSelection = TIM_ICSELECTION_INDIRECTTI; // 间接映射
}
HAL_TIM_IC_ConfigChannel(htim, &TIM_IC_InitStruct, TIM_CHANNEL_1);
// 通道2下降捕获,记录PWM高电平的时间
TIM_IC_InitStruct.ICPolarity = TIM_ICPOLARITY_FALLING; // 下降沿捕获
if (trigger == TIM_TS_TI1FP1)
{
TIM_IC_InitStruct.ICSelection = TIM_ICSELECTION_INDIRECTTI; // 间接映射
}
else if (trigger == TIM_TS_TI2FP2)
{
TIM_IC_InitStruct.ICSelection = TIM_ICSELECTION_DIRECTTI; // 直接映射
}
HAL_TIM_IC_ConfigChannel(htim, &TIM_IC_InitStruct, TIM_CHANNEL_2);
}
通用定时器 TIM5 的时钟来自 APB1,当 PPRE1 ≥ 2 分频的时候,通用定时器的时钟为 APB1 时钟的 2 倍, 而 APB1 为 42M, 所以定时器时钟 = 84Mhz。这里,我们设置定时器的分频因子为 0,自动重装载值设置为 65535。在未溢出的情况下,最多可以计数 65536 次,测量的最长 PWM 周期为 780.19us。
定时器对应通道的输入捕获底层初始化函数:
/**
* @brief 定时器输入捕获底层初始化函数
*
* @param htim 定时器句柄
*/
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (htim->Instance == TIM5)
{
__HAL_RCC_TIM5_CLK_ENABLE(); // 使能TIM5的时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能TIM5的Channel 1对应的GPIO时钟
GPIO_InitStruct.Pin = GPIO_PIN_0; // TIM5的Channel 1对应的GPIO引脚
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 复用功能
GPIO_InitStruct.Pull = GPIO_NOPULL; // 不使用上下拉
GPIO_InitStruct.Alternate = GPIO_AF2_TIM5; // 复用功能选择
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_NVIC_EnableIRQ(TIM5_IRQn); // 使能定时器5中断
HAL_NVIC_SetPriority(TIM5_IRQn, 4, 0); // 设置中断优先级
}
}
定时器 5 中断服务函数:
/**
* @brief 定时器5中断服务函数
*
*/
void TIM5_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_tim5_handle); // 调用HAL库公共处理函数
}
定时器输入捕获回调函数:
uint16_t g_timxchy_pwm_in_status = 0; // 0: 未检测到PWM信号; 1: 检测到PWM信号
uint16_t g_timxchy_pwm_in_period = 0; // PWM周期
uint8_t g_timxchy_pwm_in_high_level_duration = 0; // 高电平持续时间
/**
* @brief 定时器输入捕获回调函数
*
* @param htim 定时器句柄
*/
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM5)
{
if (g_timxchy_pwm_in_status == 0)
{
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) // 当前通道为通道1
{
// 获取高电平的计数值
g_timxchy_pwm_in_high_level_duration = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2) + 1;
// 获取PWM周期的计数值
g_timxchy_pwm_in_period = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1) + 1;
g_timxchy_pwm_in_status = 1; // 成功获取
}
}
}
}
定时器 5 PWM输入模式状态清零函数:
/**
* @brief 定时器5PWM输入模式状态清零函数
*
*/
void TIM_PWM_IC_Restart(void)
{
__ASM volatile("cpsid i"); // 关闭所有中断
// 清零状态,重新开始检测
g_timxchy_pwm_in_status = 0;
g_timxchy_pwm_in_high_level_duration = 0;
g_timxchy_pwm_in_period = 0;
__ASM volatile("cpsie i"); // 开启所有中断
}
main() 函数内容如下:
int main(void)
{
uint8_t prescaler = 83;
double tPwmPrescaler = 0, tPwmHighLevelDuration = 0, tPwmPeriod = 0, pwmFrequence = 0;
HAL_Init();
System_Clock_Init(8, 336, 2, 7);
Delay_Init(168);
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
UART_Init(&g_usart1_handle, USART1, 115200);
TIM_PWM_Init(&g_tim14_handle, TIM14, prescaler, 9, TIM_CHANNEL_1, TIM_OCPOLARITY_HIGH, 5);
HAL_TIM_PWM_Start(&g_tim14_handle, TIM_CHANNEL_1); // 使能输出并启动计数器
TIM_PWM_IC_Init(&g_tim5_handle, TIM5, prescaler, 65535, TIM_TS_TI1FP1);
HAL_TIM_IC_Start_IT(&g_tim5_handle, TIM_CHANNEL_1); // 使能通道输入以及使能捕获中断
HAL_TIM_IC_Start(&g_tim5_handle, TIM_CHANNEL_2); // 使能通道输入
printf("定时器的PWM输入实验\r\n");
while (1)
{
if (g_timxchy_pwm_in_status) // 捕获了一次数据
{
printf("\r\n");
printf("PWM PSC :%d\r\n", prescaler); // 打印分频系数
printf("PWM Hight:%d\r\n", g_timxchy_pwm_in_high_level_duration); // 打印高电平脉宽
printf("PWM Cycle:%d\r\n", g_timxchy_pwm_in_period); // 打印周期
tPwmPrescaler = ((double)(prescaler + 1)) / 84; // 得到PWM采样时钟周期时间
tPwmHighLevelDuration = g_timxchy_pwm_in_high_level_duration * tPwmPrescaler; // 计算高电平时间
tPwmPeriod = g_timxchy_pwm_in_period * tPwmPrescaler; // 计算周期长度
pwmFrequence = (1 / tPwmPeriod) * 1000000; // 计算频率
printf("PWM Hight time: %.3f us\r\n", tPwmHighLevelDuration); // 打印高电平脉宽长度
printf("PWM Cycle time: %.3f us\r\n", tPwmPeriod); // 打印周期时间长度
printf("PWM Frequency : %.3f Hz\r\n", pwmFrequence); // 打印频率
TIM_PWM_IC_Restart(); // 重启PWM输入检测
}
HAL_Delay(1000);
}
}