学习嵌入式入门(十)高级定时器简介及实验(下)

news2024/9/20 18:45:48

一、高级定时器互补输出带死区控制实验

       上图中,CH1 输出黄色的 PWM,它的互补通道 CH1N 输出绿色的 PWM。通过对比,可以 知道这两个 PWM 刚好是反过来的,CH1 的 PWM 为高电平期间,CH1N 的 PWM 则是低电平, 反之亦然,这就是互补输出。

        上图中,CH1 输出的 PWM 和 CH1N 输出的 PWM 在高低电平转换间,插入了一段时间才 实现互补输出。这段时间称为死区时间,可以通过 DTG[7:0]位配置控制死区时间的长度。

TIM1/TIM8 寄存器

控制寄存器 1(TIMx_CR1)

       CKD[1:0]位指示定时器时钟(CK_INT)频率与死区发生器以及数字滤波器(ETR、TIx) 所使用的死区及采样时钟(tDTS)之间的分频比。我们设置 CKD[1:0]位为 10,结合高级定时器 时钟源频率等于 APB2 总线时钟频率,即 72MHz,可以得到 tDTS=55.56ns。

捕获/比较模式寄存器 1/2(TIMx_CCMR1/2)

捕获/比较使能寄存器(TIMx_ CCER)

该寄存器控制着各个输入输出通道的开关和极性

捕获/比较寄存器 1/2/3/4(TIMx_CCR1/2/3/4)

本实验中可以通过改变该寄存器的值来改变 PWM 的占空比。

断路和死区寄存器(TIMx_ BDTR)

定时器的 HAL 库驱动

1. HAL_TIMEx_ConfigBreakDeadTime 函数

HAL_StatusTypeDef HAL_TIMEx_ConfigBreakDeadTime(TIM_HandleTypeDef *htim, 
 TIM_BreakDeadTimeConfigTypeDef *sBreakDeadTimeConfig);

函数描述: 用于初始化定时器的断路(即刹车)和死区时间。

函数形参: 形参 1 是 TIM_HandleTypeDef 结构体类型指针变量。

形参 2 是 TIM_BreakDeadTimeConfigTypeDef 结构体类型指针变量,用于配置断路和死区 参数,其定义如下:

typedef struct 
{ 
 uint32_t OffStateRunMode; /* 运行模式下的关闭状态选择 */ 
 uint32_t OffStateIDLEMode; /* 空闲模式下的关闭状态选择 */ 
 uint32_t LockLevel; /* 寄存器锁定配置 */ 
 uint32_t DeadTime; /* 死区时间设置 */ 
 uint32_t BreakState; /* 断路(即刹车)输入使能控制 */ 
 uint32_t BreakPolarity; /* 断路输入极性 */ 
 uint32_t BreakFilter; /* 断路输入滤波器 */ 
 uint32_t AutomaticOutput; /* 自动恢复输出使能控制 */ 
} TIM_BreakDeadTimeConfigTypeDef; 

2. HAL_TIMEx_PWMN_Start 函数

HAL_StatusTypeDef HAL_TIMEx_PWMN_Start(TIM_HandleTypeDef *htim, 
uint32_t Channel); 

函数描述: 该函数用于启动定时器的互补输出。

函数形参: 形参 1 是 TIM_HandleTypeDef 结构体类型指针变量,用于配置定时器基本参数。

形参 2 是定时器通道,范围:TIM_CHANNEL_1 到 TIM_CHANNEL_4。

定时器互补输出带死区控制配置步骤

1)开启 TIMx 和通道输出以及刹车输入的 GPIO 时钟,配置该 IO 口的复用功能输出。

__HAL_RCC_TIM1_CLK_ENABLE(); /* 使能定时器 1 */ 
__HAL_RCC_GPIOE_CLK_ENABLE(); /* 开启 GPIOE 时钟 */ 

2)初始化 TIMx,设置 TIMx 的 ARR 和 PSC 等参数

3)设置定时器为 PWM 模式,输出比较极性,互补输出极性等参数

4)设置死区参数

5)启动 Ocy 输出以及 OCyN 互补输出

程序解析

atim.h

/* 输出通道引脚 */ 
#define ATIM_TIMX_CPLM_CHY_GPIO_PORT GPIOE 
#define ATIM_TIMX_CPLM_CHY_GPIO_PIN GPIO_PIN_9 
#define ATIM_TIMX_CPLM_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); 
}while(0) /* PE 口时钟使能 */ 
/* 互补输出通道引脚 */ 
#define ATIM_TIMX_CPLM_CHYN_GPIO_PORT GPIOE 
#define ATIM_TIMX_CPLM_CHYN_GPIO_PIN GPIO_PIN_8 
#define ATIM_TIMX_CPLM_CHYN_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); 
 }while(0) /* PE 口时钟使能 */ 
/* 刹车输入引脚 */ 
#define ATIM_TIMX_CPLM_BKIN_GPIO_PORT GPIOE 
#define ATIM_TIMX_CPLM_BKIN_GPIO_PIN GPIO_PIN_15 
#define ATIM_TIMX_CPLM_BKIN_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); 
 }while(0) /* PE 口时钟使能 */ 
 
/* TIMX REMAP 设置 
 * 因为 PE8/PE9/PE15, 默认并不是 TIM1 的复用功能脚, 必须开启完全重映射, 才可以将: 
TIM1_CH1->PE9; TIM1_CH1N->PE8; TIM1_BKIN->PE15; 
 * 这样, PE8/PE9/PE15, 才能用作 TIM1 的 CH1N/CH1/BKIN 功能. 
 * 因此必须实现 ATIM_TIMX_CPLM_CHYN_GPIO_REMAP, 通过 sys_gpio_remap_set 函数设置重映射 
 * 如果我们使用默认的复用功能输出, 则不用设置重映射, 是可以不需要该函数的! 根据具体需要来实现. 
 */ 
#define ATIM_TIMX_CPLM_CHYN_GPIO_REMAP() do{__HAL_RCC_AFIO_CLK_ENABLE();\ 
 __HAL_AFIO_REMAP_TIM1_ENBLE();\}while(0) 
/* 互补输出使用的定时器 */ 
#define ATIM_TIMX_CPLM TIM1 
#define ATIM_TIMX_CPLM_CHY TIM_CHANNEL_1 
#define ATIM_TIMX_CPLM_CHY_CCRY ATIM_TIMX_CPLM->CCR1 
#define ATIM_TIMX_CPLM_CLK_ENABLE() do{ __HAL_RCC_TIM1_CLK_ENABLE(); }while(0)

atim.c

void atim_timx_cplm_pwm_init(uint16_t arr, uint16_t psc) 
{ 
 GPIO_InitTypeDef gpio_init_struct = {0}; 
 TIM_OC_InitTypeDef tim_oc_cplm_pwm = {0}; 
 
 ATIM_TIMX_CPLM_CLK_ENABLE(); /* TIMx 时钟使能 */ 
 ATIM_TIMX_CPLM_CHY_GPIO_CLK_ENABLE(); /* 通道 X 对应 IO 口时钟使能 */ 
 ATIM_TIMX_CPLM_CHYN_GPIO_CLK_ENABLE(); /* 通道 X 互补通道对应 IO 口时钟使能 */ 
 ATIM_TIMX_CPLM_BKIN_GPIO_CLK_ENABLE(); /* 通道 X 刹车输入对应 IO 口时钟使能 */ 
 
 gpio_init_struct.Pin = ATIM_TIMX_CPLM_CHY_GPIO_PIN; 
 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(ATIM_TIMX_CPLM_CHY_GPIO_PORT, &gpio_init_struct); 
 
 gpio_init_struct.Pin = ATIM_TIMX_CPLM_CHYN_GPIO_PIN; 
 HAL_GPIO_Init(ATIM_TIMX_CPLM_CHYN_GPIO_PORT, &gpio_init_struct); 
 
 gpio_init_struct.Pin = ATIM_TIMX_CPLM_BKIN_GPIO_PIN; 
 HAL_GPIO_Init(ATIM_TIMX_CPLM_BKIN_GPIO_PORT, &gpio_init_struct); 
 
 ATIM_TIMX_CPLM_CHYN_GPIO_REMAP(); /* 重映射定时器 IO */ 
 
 g_timx_cplm_pwm_handle.Instance = ATIM_TIMX_CPLM; /* 定时器 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; /* 自动重装载值 */ 
/* CKD[1:0] = 10, tDTS = 4 * tCK_INT = Ft / 4 = 18Mhz */ 
 g_timx_cplm_pwm_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4; 
g_timx_cplm_pwm_handle.Init.AutoReloadPreload = 
TIM_AUTORELOAD_PRELOAD_ENABLE; /* 使能影子寄存器 TIMx_ARR */ 
 HAL_TIM_PWM_Init(&g_timx_cplm_pwm_handle); 
 
 tim_oc_cplm_pwm.OCMode = TIM_OCMODE_PWM1; /* PWM 模式 1 */ 
 tim_oc_cplm_pwm.OCPolarity = TIM_OCPOLARITY_LOW; /* OCy 低电平有效 */ 
 tim_oc_cplm_pwm.OCNPolarity = TIM_OCNPOLARITY_LOW; /* OCyN 低电平有效 */ 
 tim_oc_cplm_pwm.OCIdleState = TIM_OCIDLESTATE_SET; /* 当 MOE=0,OCx=1 */ 
 tim_oc_cplm_pwm.OCNIdleState = TIM_OCNIDLESTATE_SET;/* 当 MOE=0,OCxN=1 */ 
HAL_TIM_PWM_ConfigChannel(&g_timx_cplm_pwm_handle, &tim_oc_cplm_pwm, 
ATIM_TIMX_CPLM_CHY); 
 
/* 设置死区参数,开启死区中断 */ 
/* 运行模式的关闭输出状态 */ 
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; 
/* 使能 AOE 位,允许刹车结束后自动恢复输出 */ 
 g_sbreak_dead_time_config.AutomaticOutput = TIM_AUTOMATICOUTPUT_ENABLE; 
HAL_TIMEx_ConfigBreakDeadTime(&g_timx_cplm_pwm_handle, 
&g_sbreak_dead_time_config); 
 
/* 使能 OCy 输出 */ 
HAL_TIM_PWM_Start(&g_timx_cplm_pwm_handle, ATIM_TIMX_CPLM_CHY); 
/* 使能 OCyN 输出 */ 
 HAL_TIMEx_PWMN_Start(&g_timx_cplm_pwm_handle, ATIM_TIMX_CPLM_CHY); 
} 

在 atim_timx_cplm_pwm_init 函数中,没有使用 HAL 库的 MSP 回调,而是把相关的初始化 都写到该函数里面。

第一部分,使能定时器和相关通道对应的 GPIO 时钟,以及初始化相关 IO 引脚。

第二部分,通过 HAL_TIM_PWM_Init 函数初始化定时器的 ARR 和 PSC 等参数。

第三部分,通过 HAL_TIM_PWM_ConfigChannel 函数设置 PWM 模式 1、输出极性,以及 输出空闲状态等。

第四部分,通过 HAL_TIMEx_ConfigBreakDeadTime 函数配置断路功能。

最后一定记得要调用 HAL_TIM_PWM_Start 函数和 HAL_TIMEx_PWMN_Start 函数启动通 道输出和互补通道输出。 为了方便,我们还定义了设置输出比较值和死区时间的函数,其定义如下:

void atim_timx_cplm_pwm_set(uint16_t ccr, uint8_t dtg) 
{ 
 g_sbreak_dead_time_config.DeadTime = dtg; /* 死区时间设置 */ 
HAL_TIMEx_ConfigBreakDeadTime(&g_timx_cplm_pwm_handle, 
&g_sbreak_dead_time_config); /*重设死区时间*/ 
 __HAL_TIM_MOE_ENABLE(&g_timx_cplm_pwm_handle); /* MOE=1,使能主输出 */ 
 ATIM_TIMX_CPLM_CHY_CCRY = ccr; /* 设置比较寄存器 */ 
} 

 main.c

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); /* 1Mhz 的计数频率 1Khz 的周期. */ 
atim_timx_cplm_pwm_set(300, 100); /* 占空比:7:3, 死区时间 100*tDTS */ 
 
 while (1) 
 { 
 delay_ms(10); 
 t++; 
 
 if (t >= 20) 
 { 
 LED0_TOGGLE(); /* LED0(RED)闪烁 */ 
 t = 0; 
 } 
 } 
} 

二、高级定时器 PWM 输入模式实验 

第一,确定定时器时钟源。

第二,确定 PWM 输入的通道。

第三,确定 IC1 和 IC2 的捕获边沿。

第四,选择触发输入信号(TRGI)。

第五,从模式选择:复位模式。复位模式的作用是:在出现所选触发输入 (TRGI) 上升沿 时,重新初始化计数器并生成一个寄存器更新事件。

第六,读取一个 PWM 周期内计数器的计数个数,以及高电平期间的计数个数,再结合计 数器的计数周期(即计一个数的时间),最终通过计算得到输入的 PWM 周期和占空比等参数。

从模式控制寄存器(TIMx_SMCR)

捕获/比较模式寄存器 1/2(TIMx_CCMR1/2)

 

捕获/比较使能寄存器(TIMx_ CCER) 

捕获/比较寄存器 1/2/3/4(TIMx_CCR1/2/3/4)  

DMA/中断使能寄存器(TIMx_DIER) 

高级定时器 PWM 输入模式配置步骤  

1)开启 TIMx 和输入通道的 GPIO 时钟,配置该 IO 口的复用功能输入。 

2) 初始化 TIMx,设置 TIMx 的 ARR 和 PSC 等参数。

3)从模式配置,IT1 触发更新 

4)设置 IC1 捕获相关参数 

5)设置 IC2 捕获相关参数 

6)使能定时器更新中断,开启捕获功能,配置定时器中断优先级

atim.h

#define ATIM_TIMX_PWMIN_CHY_GPIO_PORT GPIOC 
#define ATIM_TIMX_PWMIN_CHY_GPIO_PIN GPIO_PIN_6 
#define ATIM_TIMX_PWMIN_CHY_GPIO_CLK_ENABLE() do{__HAL_RCC_GPIOC_CLK_ENABLE();\ 
}while(0) /* PC 口时钟使能 */ 
 
#define ATIM_TIMX_PWMIN TIM8 
#define ATIM_TIMX_PWMIN_IRQn TIM8_UP_IRQn 
#define ATIM_TIMX_PWMIN_IRQHandler TIM8_UP_IRQHandler 
#define ATIM_TIMX_PWMIN_CHY TIM_CHANNEL_1 /* 通道 Y, 1<= Y <=2*/ 
#define ATIM_TIMX_PWMIN_CHY_CLK_ENABLE() 
do{ __HAL_RCC_TIM8_CLK_ENABLE(); }while(0) /* TIM8 时钟使能 */ 
 
 /* TIM1 / TIM8 有独立的捕获中断服务函数,需要单独定义,对于 TIM2~5 等,则不需要以下定义 */ 
#define ATIM_TIMX_PWMIN_CC_IRQn TIM8_CC_IRQn 
#define ATIM_TIMX_PWMIN_CC_IRQHandler TIM8_CC_IRQHandler 

atim.c

void atim_timx_pwmin_chy_init(void) 
{ 
{ 
 GPIO_InitTypeDef gpio_init_struct = {0}; 
 TIM_SlaveConfigTypeDef slave_config = {0}; 
 TIM_IC_InitTypeDef tim_ic_pwmin_chy = {0}; 
 
 ATIM_TIMX_PWMIN_CHY_CLK_ENABLE(); 
 ATIM_TIMX_PWMIN_CHY_GPIO_CLK_ENABLE(); 
 __HAL_RCC_AFIO_CLK_ENABLE(); 
 AFIO_REMAP_PARTIAL(AFIO_EVCR_PORT_PC,AFIO_EVCR_PIN_PX6);/*复用 TIM8_CH1/PC6*/ 
 
 gpio_init_struct.Pin = ATIM_TIMX_PWMIN_CHY_GPIO_PIN; 
 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(ATIM_TIMX_PWMIN_CHY_GPIO_PORT, &gpio_init_struct); 
 
 g_timx_pwmin_chy_handle.Instance = ATIM_TIMX_PWMIN; /* 定时器 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); 
 
 /* 从模式配置,IT1 触发更新 */ 
 slave_config.SlaveMode = TIM_SLAVEMODE_RESET; /* 从模式:复位模式 */ 
 slave_config.InputTrigger = TIM_TS_TI1FP1; /* 定时器输入触发源:TI1FP1 */ 
 slave_config.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING;/*上升沿检测*/ 
 slave_config.TriggerFilter = 0; /* 不滤波 */ 
 HAL_TIM_SlaveConfigSynchro(&g_timx_pwmin_chy_handle, &slave_config); 
/* IC1 捕获:上升沿触发 TI1FP1 */ 
 tim_ic_pwmin_chy.ICPolarity = TIM_INPUTCHANNELPOLARITY_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 ); 
 
 /* IC2 捕获:上升沿触发 TI1FP2 */ 
 tim_ic_pwmin_chy.ICPolarity = TIM_INPUTCHANNELPOLARITY_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); 
 /* 设置中断优先级,抢占优先级 1,子优先级 3 */ 
 HAL_NVIC_SetPriority(ATIM_TIMX_PWMIN_IRQn, 1, 3); 
 HAL_NVIC_EnableIRQ( ATIM_TIMX_PWMIN_IRQn ); /* 开启 TIMx 中断 */ 
 
 /* TIM1/TIM8 有独立的输入捕获中断服务函数 */ 
 if ( ATIM_TIMX_PWMIN == TIM1 || ATIM_TIMX_PWMIN == TIM8) 
{ 
/* 设置中断优先级,抢占优先级 1,子优先级 3 */ 
 HAL_NVIC_SetPriority(ATIM_TIMX_PWMIN_CC_IRQn, 1, 3); 
 HAL_NVIC_EnableIRQ(ATIM_TIMX_PWMIN_CC_IRQn); /* 开启 TIMx 中断 */ 
 } 
 
 __HAL_TIM_ENABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_UPDATE); 
 HAL_TIM_IC_Start_IT(&g_timx_pwmin_chy_handle, TIM_CHANNEL_1); 
 HAL_TIM_IC_Start_IT(&g_timx_pwmin_chy_handle, TIM_CHANNEL_2); 
} 

在 atim_timx_pwmin_chy_init 函数中,没有使用 HAL 库的 MSP 回调,而是把相关的初始 化都写到该函数里面。

第一部分,使能定时器和相关通道对应的 GPIO 时钟,以及初始化相关 IO 引脚。

第二部分,通过 HAL_TIM_IC_Init 函数初始化定时器的 ARR 和 PSC 等参数。

第三部分,通过 HAL_TIM_SlaveConfigSynchronization 函数配置从模式,复位模式等。

第四部分,通过 HAL_TIM_IC_ConfigChannel 函数分别配置 IC1 和 IC2。

第五部分,配置 NVIC,如使能定时器中断,配置抢占优先级和响应优先级。

最后,通过调用 HAL_TIM_IC_Start_IT 函数和__HAL_TIM_ENABLE_IT 函数宏使能捕获 中断和更新中断,并且使能定时器。 为了方便,我们定义了重新启动捕获函数,其定义如下:

void atim_timx_pwmin_chy_restart(void) 
{ 
 sys_intx_disable(); /* 关闭中断 */ 
 
 g_timxchy_pwmin_sta = 0; /* 清零状态,重新开始检测 */ 
 g_timxchy_pwmin_psc = 0; /* 分频系数清零 */ 
 
/* 以最大的计数频率采集,以得到最好的精度 */ 
 __HAL_TIM_SET_PRESCALER(&g_timx_pwmin_chy_handle, 0); 
 __HAL_TIM_SET_COUNTER(&g_timx_pwmin_chy_handle, 0); /* 计数器清零 */ 
 
 __HAL_TIM_ENABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_CC1);/* 使能捕获中断 */ 
 __HAL_TIM_ENABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_UPDATE);/*使能更新中断*/ 
 __HAL_TIM_ENABLE(&g_timx_pwmin_chy_handle); /* 使能定时器 TIMX */
ATIM_TIMX_PWMIN->SR = 0; /* 清除所有中断标志位 */ 
 
 sys_intx_enable(); /* 打开中断 */ 
} 

该函数首先关闭所有中断,然后把一些状态标志位清零、设置定时器预分频系数、计数器 值、使能相关中断、以及清除相关中断标志位,最后才允许被中断。 最后要介绍的是中断服务函数,在定时器1的输入捕获中断服务函数TIM1_CC_IRQHandler 和更新中断服务函数 TIM1_UP_IRQHandler 里面都是直接调用atim_timx_pwmin_chy_process函 数。输入捕获中断服务函数和更新中断服务函数都是用到宏定义的,这三个函数定义如下:

/** 
 * @brief 定时器 TIMX 更新/溢出 中断服务函数 
 * @note TIM1/TIM8 的这个函数仅用于更新/溢出中断服务,捕获在另外一个函数! 
 * 其他普通定时器则更新/溢出/捕获,都在这个函数里面处理! 
 * @param 无 
 * @retval 无 
 */ 
void ATIM_TIMX_PWMIN_IRQHandler(void) 
{ 
 atim_timx_pwmin_chy_process(); 
} 
 
/** 
 * @brief 定时器 TIMX 输入捕获 中断服务函数 
 * @note 仅 TIM1/TIM8 有这个函数,其他普通定时器没有这个中断服务函数! 
 * @param 无 
 * @retval 无 
 */ 
void ATIM_TIMX_PWMIN_CC_IRQHandler(void) 
{ 
 atim_timx_pwmin_chy_process(); 
} 
 
/** 
 * @brief 定时器 TIMX 通道 Y PWM 输入模式 中断处理函数 
 * @note 
 * 因为 TIM1/TIM8 等有多个中断服务函数,而 TIM2~5/TIM12/TIM15 等普通定时器只有 1 个中断服务 
 * 函数,为了更好的兼容,我们对中断处理统一放到 atim_timx_pwin_chy_process 函数里面进行处理 
 * 
 * @param 无 
 * @retval 无 
 */ 
static void atim_timx_pwmin_chy_process(void) 
{ 
 static uint8_t sflag = 0; /* 启动 PWMIN 输入检测标志 */ 
 
 if (g_timxchy_pwmin_sta) 
 { 
 g_timxchy_pwmin_psc = 0; 
ATIM_TIMX_PWMIN->SR = 0; /* 清除所有中断标志位 */ 
 __HAL_TIM_SET_COUNTER(&g_timx_pwmin_chy_handle, 0); /* 计数器清零 */ 
 return ; 
} 
 
/* 如果发生了更新中断 */ 
 if (__HAL_TIM_GET_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_UPDATE)) 
{ 
/* 清除更新中断标记 */ 
 __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_UPDATE); 
/* 没有发生周期捕获中断,且捕获未完成 */ 
 if (__HAL_TIM_GET_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1) == 0) 
 {
   sflag = 0; 
 if (g_timxchy_pwmin_psc == 0) /* 从 0 到 1 */ 
 { 
 g_timxchy_pwmin_psc ++; 
 } 
 else 
 { 
 if (g_timxchy_pwmin_psc == 65535) /* 已经最大了,可能是无输入状态 */ 
 { 
 g_timxchy_pwmin_psc = 0; /* 重新恢复不分频 */ 
 } 
 else if (g_timxchy_pwmin_psc > 32767)/* 不能倍增了 */ 
 { 
 g_timxchy_pwmin_psc = 65535; /* 直接等于最大分频系数 */ 
 } 
 else 
 { 
 g_timxchy_pwmin_psc += g_timxchy_pwmin_psc; /* 倍增 */ 
 } 
 } 
 
 __HAL_TIM_SET_PRESCALER(&g_timx_pwmin_chy_handle, 
g_timxchy_pwmin_psc); /* 设置定时器预分频系数 */ 
 __HAL_TIM_SET_COUNTER(&g_timx_pwmin_chy_handle, 0); /* 计数器清零 */ 
ATIM_TIMX_PWMIN->SR = 0; /* 清除所有中断标志位 */ 
 return ; 
 } 
 } 
 
 if (sflag == 0) /* 第一次采集到捕获中断 */ 
{ 
/* 检测到了第一次周期捕获中断 */ 
 if (__HAL_TIM_GET_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1)) 
 { 
 sflag = 1; /* 标记第一次周期已经捕获, 第二次周期捕获可以开始了 */ 
 } 
ATIM_TIMX_PWMIN->SR = 0; /* 清除所有中断标志位 */ 
 return ; /* 完成此次操作 */ 
 } 
 
 if (g_timxchy_pwmin_sta == 0)/* 还没有成功捕获 */ 
{ 
 /* 检测到了周期捕获中断 */ 
 if (__HAL_TIM_GET_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1)) 
 { 
 g_timxchy_pwmin_hval = HAL_TIM_ReadCapturedValue( 
&g_timx_pwmin_chy_handle, TIM_CHANNEL_2) + 1; /* 高定平脉宽捕获值 */ 
 g_timxchy_pwmin_cval = HAL_TIM_ReadCapturedValue( 
&g_timx_pwmin_chy_handle, TIM_CHANNEL_1) + 1; /* 周期捕获值 */ 
 
 /* 高电平脉宽必定小于周期长度 */ 
 if (g_timxchy_pwmin_hval < g_timxchy_pwmin_cval) 
 { 
 g_timxchy_pwmin_sta = 1; /* 标记捕获成功 */ 
 
 g_timxchy_pwmin_psc = ATIM_TIMX_PWMIN->PSC;/* 获取 PWM 输入分频系数 */ 
 
 if (g_timxchy_pwmin_psc == 0) /* 分频系数为 0 的时候, 修正读取数据 */ 
 { 
 g_timxchy_pwmin_hval++; /* 修正系数为 1, 加 1 */ 
 g_timxchy_pwmin_cval++; /* 修正系数为 1, 加 1 */ 
  }
 sflag = 0; 
 /* 每次捕获 PWM 输入成功后, 停止捕获,避免频繁中断影响系统正常代码运行 */ 
 ATIM_TIMX_PWMIN->CR1 &= ~(1 << 0); /* 关闭定时器 TIMX */ 
 /* 关闭通道 1 捕获中断 */ 
 __HAL_TIM_DISABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_CC1); 
 /* 关闭通道 2 捕获中断 */ 
 __HAL_TIM_DISABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_CC2); 
/* 关闭更新中断 */ 
 __HAL_TIM_DISABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_UPDATE); 
 ATIM_TIMX_PWMIN->SR = 0; /* 清除所有中断标志位 */ 
 } 
 else 
 { 
 atim_timx_pwmin_chy_restart(); 
 } 
 } 
 } 
 ATIM_TIMX_PWMIN->SR = 0; /* 清除所有中断标志位 */ 
}

main.c

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); /* 1Mhz 的计数频率, 100Khz PWM */ 
 atim_timx_pwmin_chy_init(); /* 初始化 PWM 输入捕获 */ 
 GTIM_TIMX_PWM_CHY_CCRX = 2; /* 低电平宽度 2,高电平宽度 8 */ 
 
 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);/* 打印周期 */ 
/* 得到 PWM 采样时钟周期时间 */ 
 tpsc = ((double)g_timxchy_pwmin_psc + 1)/72; 
 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 输入检测 */ 
 } 
 LED0_TOGGLE(); /* LED0(RED)闪烁 */ 
 t = 0; 
 } 
 } 
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2037674.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux下 vim的用法

目录 前言 一、初始Vim 二、使用Vim 1.1命令模式 2.1底行模式 3.1插入模式 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 本篇文章会介绍vim的基本用法和为什么我们要学习vim。 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供…

计算机网络——HTTP协议详解(上)

一、HTTP协议简单介绍 1.1 什么是HTTP协议 HTTP&#xff08;超文本传输协议&#xff09;是一种用于在Web浏览器和Web服务器之间传输数据的应用层协议。它是一种无状态协议&#xff0c;即服务器不会保留与客户端的任何连接状态信息&#xff0c;每个请求都被视为一个独立的事务。…

Echarts饼图7.0:图例自定义+取消高亮时放大的状态

1、源代码&#xff1a; let seriseData [{ value: 1048, name: Search Engine },{ value: 735, name: Direct } ] option {color: [#5D9AF1, #D6D6D6],tooltip: {trigger: item,backgroundColor: rgba(0,0,0,0.4),borderColor: transparent,formatter: (item) > {consol…

使用Arduino IDE生成带有bootloader的烧录文件

使用Arduino IDE生成bin&#xff08;烧录&#xff09;文件 1、在“项目”中&#xff0c;选择“导出已编译的二进制文件” 2、在工程目录中&#xff0c;会出现“build”文件夹 3、在build文件夹中&#xff0c;有hex文件&#xff0c;以及包含bootloader的bin和hex文件 bin和h…

ArkUI---Swiper、Grid、List组件简单介绍

前言&#xff1a;ForEach ForEach语法如下&#xff1a; ForEach(arr: Array,itemGenerator: (item: Array, index?: number) > void,keyGenerator?: (item: Array, index?: number) : string > string ) 参数1&#xff1a;数据源&#xff0c;为Array的数组 参数2&am…

【51单片机】让AI识别电路图,帮你进行编码(以51单片机为例)

让AI识别电路图,帮你进行编码&#xff08;以51单片机为例&#xff09; ​ 这里使用的AI大模型使用的是 Copilot。&#xff08;两个前提&#xff1a;1. 科学上网、2. 有微软账号&#xff09; 今天测试了一下Copilot识别图片的能力&#xff0c;能力还是可圈可点的。 首先准备一…

react-antive 項目報錯 [CXX1429] error when building with cmake using

react-antive 項目報錯 [CXX1429] error when building with cmake using修复 错误现场分析原因解决方案举一反三技巧引用参考&#xff08;感谢作者提供思路&#xff09; 错误现场 [CXX1429] error when building with cmake using /Users/sebastiangarcia/Desktop/work/flm/…

基于spring boot的校园商铺管理系统

TOC springboot188基于spring boot的校园商铺管理系统 第1章 绪论 1.1 研究背景 互联网概念的产生到如今的蓬勃发展&#xff0c;用了短短的几十年时间就风靡全球&#xff0c;使得全球各个行业都进行了互联网的改造升级&#xff0c;标志着互联网浪潮的来临。在这个新的时代&…

springboot颐养居家养老管理系统---附源码19707

摘 要 随着社会的快速发展和人口老龄化趋势的加剧&#xff0c;居家养老已成为越来越多老年人的选择。然而&#xff0c;传统的居家养老方式面临着诸多问题&#xff0c;如服务质量不稳定、信息不对称等。为了解决这些问题&#xff0c;提高居家养老的服务质量和效率&#xff0c;我…

[mysql] 一行变多行

数据表 CREATE TABLE table_main (ID char(36) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,zb_list_str text CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci ,kf_list_str text CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci,PRIMARY KEY (ID) USI…

在宝塔面板下安装WordPress

宝塔面板是服务器管理好助手&#xff0c;尤其在Linux系统下&#xff0c;提高了管理的可视化&#xff0c;降低了Linux服务器的使用门槛。 WordPress是个非常好的博客系统&#xff0c;由于支持海量主题模板、各种类型的插件&#xff0c;因此已经成为建设各类网站的首选框架。 今…

java 获取request中的json请求体

Java 获取request中的json请求体 简介 在Java开发中&#xff0c;有时我们需要从HTTP请求中获取JSON格式的数据。本文将详细介绍如何在Java中获取request中的json请求体。 流程概览 以下是获取request中的json请求体的整体流程&#xff1a; 步骤 描述 1 获取HttpServletReque…

代码规范 —— 并发编程规范

优质博文&#xff1a;IT-BLOG-CN 【1】【强制】获取单例对象需要保证线程安全&#xff0c;其中的方法也要保证线程安全。 说明&#xff1a; 资源驱动类、工具类、单例工厂类都需要注意。 【2】【强制】创建线程或线程池时请指定有意义的线程名称&#xff0c;方便出错时回溯。…

Adobe Illustrator 2023 for Mac/Win:创意设计的强大引擎

Adobe Illustrator 2023&#xff08;简称AI 2023&#xff09;是一款专为设计师打造的矢量图形编辑软件&#xff0c;无论是Mac还是Windows平台&#xff0c;它都以其卓越的性能和丰富的功能赢得了业界的广泛赞誉。这款软件在设计领域具有举足轻重的地位&#xff0c;为设计师们提供…

算法的学习笔记—删除链表中重复的结点(牛客JZ76)

&#x1f600;前言 在链表操作中&#xff0c;删除重复节点是一个常见的问题。特别是在排序链表中&#xff0c;连续的重复节点不仅会影响链表的结构&#xff0c;还会带来额外的复杂度。本文将介绍一种高效的算法&#xff0c;用于删除链表中所有重复的节点&#xff0c;并保留链表…

GPT-4o mini发布,轻量级大模型如何颠覆AI的未来?

从巨无霸到小巨人&#xff1a;GPT-4o Mini的创新之路 ©作者|潇潇 来源|神州问学 引言 随着人工智能技术的飞速进步&#xff0c;AI领域的竞争日益激烈&#xff0c;大型模型的发布几乎成为常态。然而&#xff0c;这些庞大的模型通常需要大量的计算资源和存储空间&#xff…

如何使用Zabbix API批量修正主机名称

作者 乐维社区&#xff08;forum.lwops.cn&#xff09; 许远 先说为什么要修正&#xff1f; 这其实源自于Ansible安装zabbix agent的一个小Bug&#xff1a;有小伙伴发现&#xff0c;利用ansible批量安装zabbix agent后&#xff0c;zabbix系统上显示的主机名出错了&#xff0c;主…

疫苗发布和接种预约系统

TOC springboot173疫苗发布和接种预约系统 第一章 绪论** 1.1 研究背景 在现在社会&#xff0c;对于信息处理方面&#xff0c;是有很高的要求的&#xff0c;因为信息的产生是无时无刻的&#xff0c;并且信息产生的数量是呈几何形式的增加&#xff0c;而增加的信息如何存储以…

【Next】初识 Next

概述 在Reactr中创建SSR应用&#xff0c;需要调用 ReactDOM.hydrateRoot 函数&#xff0c;而不是 ReactDOM.createRoot。 createRoot:创建一个Root,接着调用其 render 函数将 App 直接加载到页面上hydrateRoot:创建水合 Root, 是在激活的模式下渲染 App 服务端可用 ReactDOM…

如何在 Odoo 16 中修改现有网页

在 Odoo 中&#xff0c;网页是指在 Odoo 网站上可访问的特定页面或 URL。Odoo 中的网页是通过内置网站模块创建和管理的&#xff0c;该模块允许您设计和自定义网页的内容、布局和功能。 Odoo 中的网页是您网站的构建块&#xff0c;可用于呈现信息、展示产品或服务、通过表单收…