高级定时器的框图和通用定时器框图很类似,只是添加了其它的一些功能,如:重复计数器、带死区控制的互补输出通道、断路输入等。
高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此高级定时器时钟 = 168Mhz。
在学习基本定时器和通用定时器的时候,我们知道定时器发生上溢或者下溢时,会直接生成更新事件。有重复计数器的定时器并不完全是这样的,定时器每次发生上溢或下溢时,重复计数器的值会减一,当重复计数器的值为 0 时,再发生一次上溢或者下溢才会生成定时器更新事件。
第②部分的 TIMx_CH1N、TIMx_CH2N 和 TIMx_CH3N 分别是定时器通道 1、通道 2 和通道 3的互补输出通道,通道4 是没有互补输出通道的。DTG 是死区发生器,死区时间由DTG[7:0]位来配置。如果不使用互补通道和死区时间控制,那么高级定时器 TIM1 和 TIM8 和通用定时器的输出比较部分使用方法基本一样,只是要注意 MOE 位得置1 定时器才能输出。
断路功能也称刹车功能,一般用于电机控制的刹车。F4 系列有一个断路通道,断路源可以是刹车输入引脚(TIMx_BKIN),也可以是一个时钟失败事件。时钟失败事件由复位时钟控制器中的时钟安全系统产生。系统复位后,断路功能默认被禁止,MOE 位为低。
使能断路功能的方法:将TIMx_BDTR 的位 BKE 置1。断路输入引脚 TIMx_BKIN 的输入有效电平可通过TIMx_BDTR 寄存器的位BKP 设置。
使能刹车功能后:由 TIMx_BDTR 的MOE、OSSI、OSSR 位,TIMx_CR2 的OISx、OISxN位,TIMx_CCER 的CCxE、CCxNE 位控制OCx 和OCxN 输出状态。无论何时,OCx 和OCxN输出都不能同时处在有效电平。
当发生断路输入后,会怎么样?
1,MOE 位被异步地清零,OCx 和OCxN 为无效、空闲或复位状态(由OSSI 位选择)。
2,OCx 和 OCxN 的状态:由相关控制位状态决定,当使用互补输出时:根据情况自动控制输出电平,参考《STM32F4xx 参考手册_V4(中文版).pdf》手册第382 页的表73 具有断路功能的互补通道Ocx 和 OcxN 的控制位。
3,BIF 位置1,如果使能了 BIE 位,还会产生刹车中断;如果使能了TDE 位,会产生DMA请求。
4,如果AOE 位置 1,在下一个更新事件 UEV 时,MOE 位被自动置1。
高级定时器框图部分就简单介绍到这里,下面通过实际的实验来学习高级定时器。
本次实验将PC6复用为TIM8_CH1,映射到同一个Tl1使用两个CCRx捕获比较器,将Tl1FP1配置为上升沿触发捕获并且使能中断,将Tl1FP2配置为下降沿触发捕获。另外将Tl1配置为上升沿触发的从模式的复位模式。测量的PWM波形由通用定时器TIM14_CH1产生,可通过TIM14的CCR1的值设置占空比。
(1)问题:上升沿是先触发清零动作还是CCR1(捕获比较器)的捕获动作?
答案是,硬件会先进行CCR1的捕获动作,再进行清零。
(2)本次实验为,PC9(TIM14_CH1)配置psc = 0,arr = 65535,精度为 1/168M = 6ns,最长测量时间约为390us。
(3)F4仅有一个捕获比较寄存器CCR1。如下图:
#include "./BSP/TIMER/atim.h"
TIM_HandleTypeDef a_timx_chy_ic_handle;
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的周期宽度 */
void a_timx_chy_ic_init(void)
{
/* 时钟使能,引脚初始化 */
__HAL_RCC_TIM8_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.Pin = GPIO_PIN_6; /* LED0引脚 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Alternate = GPIO_AF3_TIM8;
gpio_init_struct.Pull = GPIO_PULLDOWN; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(GPIOC, &gpio_init_struct); /* 初始化LED0引脚 */
a_timx_chy_ic_handle.Instance = TIM8;
a_timx_chy_ic_handle.Init.Prescaler = 0;
a_timx_chy_ic_handle.Init.Period = 65535;
a_timx_chy_ic_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
//a_timx_chy_ic_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
HAL_TIM_IC_Init(&a_timx_chy_ic_handle);
/* 从模式选择复位模式初始化*/
TIM_SlaveConfigTypeDef a_timx_chy_slave_config = {0};
a_timx_chy_slave_config.SlaveMode = TIM_SLAVEMODE_RESET; /* 选择复位模式,上升沿到来时,CNT变为0 */
a_timx_chy_slave_config.InputTrigger = TIM_TS_TI1FP1;
a_timx_chy_slave_config.TriggerPrescaler = TIM_TRIGGERPRESCALER_DIV1;
a_timx_chy_slave_config.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;
a_timx_chy_slave_config.TriggerFilter = 0;
HAL_TIM_SlaveConfigSynchro(&a_timx_chy_ic_handle, &a_timx_chy_slave_config);
/* 输入捕获初始化 */
/* TL1 */
TIM_IC_InitTypeDef a_timx_chy_ic_config = {0};
a_timx_chy_ic_config.ICSelection = TIM_ICSELECTION_DIRECTTI;
a_timx_chy_ic_config.ICPolarity = TIM_ICPOLARITY_RISING;
a_timx_chy_ic_config.ICPrescaler = TIM_ICPSC_DIV1;
a_timx_chy_ic_config.ICFilter = 0xff;
HAL_TIM_IC_ConfigChannel(&a_timx_chy_ic_handle, &a_timx_chy_ic_config, TIM_CHANNEL_1);
/* TL2 */
a_timx_chy_ic_config.ICSelection = TIM_ICSELECTION_INDIRECTTI; /* 注意TIM_CCMR1_CC2S_1 */
a_timx_chy_ic_config.ICPolarity = TIM_ICPOLARITY_FALLING;
a_timx_chy_ic_config.ICPrescaler = TIM_ICPSC_DIV1;
a_timx_chy_ic_config.ICFilter = 0xff;
HAL_TIM_IC_ConfigChannel(&a_timx_chy_ic_handle, &a_timx_chy_ic_config, TIM_CHANNEL_2); /* 注意这里是通道2 */
/* 配置定时器中断优先级,使能捕获中断 */
HAL_NVIC_SetPriority(TIM8_CC_IRQn, 1, 3);
HAL_NVIC_EnableIRQ(TIM8_CC_IRQn);
/* 使能通道,通道1设置为捕获中断 */
HAL_TIM_IC_Start_IT(&a_timx_chy_ic_handle,TIM_CHANNEL_1);
HAL_TIM_IC_Start(&a_timx_chy_ic_handle,TIM_CHANNEL_2);
}
/**
* @brief 定时器TIMX PWM输入模式 重新启动捕获
* @param 无
* @retval 无
*/
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(); /* 打开中断 */
}
/* 定时器8 输入捕获 中断服务函数,仅TIM1/TIM8有这个函数,其他普通定时器没有这个中断服务函数! */
void TIM8_CC_IRQHandler(void)
{
HAL_TIM_IRQHandler(&a_timx_chy_ic_handle); /* 定时器共用处理函数 */
}
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) /* 当前活动通道是否为通道1 */
{
g_timxchy_pwmin_cval = HAL_TIM_ReadCapturedValue(&a_timx_chy_ic_handle,TIM_CHANNEL_1) + 1 + 1; /* 第一个+1是因为从0开始计数 */
g_timxchy_pwmin_hval = HAL_TIM_ReadCapturedValue(&a_timx_chy_ic_handle, TIM_CHANNEL_2) + 1 + 1; /* 最后+1为手动修复误差 */
g_timxchy_pwmin_sta = 1;
}
}
}
}
注意误差问题
#include "./BSP/TIMER/gtim.h"
TIM_HandleTypeDef g_timx_pwm_chy_handle; /* 定时器x句柄 */
/**
* @brief 通用定时器TIMX 通道Y PWM输出 初始化函数(使用PWM模式1)
* @note
* 通用定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
* 通用定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param arr: 自动重装值。
* @param psc: 预分频系数
* @retval 无
*/
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc)
{
TIM_OC_InitTypeDef timx_oc_pwm_chy = {0}; /* 定时器输出句柄 */
g_timx_pwm_chy_handle.Instance = TIM14; /* 定时器x */
g_timx_pwm_chy_handle.Init.Prescaler = psc; /* 预分频系数 */
g_timx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_pwm_chy_handle.Init.Period = arr; /* 自动重装载值 */
HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle); /* 初始化PWM */
timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1; /* 模式选择PWM1 */
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_1); /* 配置TIMx通道y */
HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, TIM_CHANNEL_1); /* 开启对应PWM通道 */
}
/**
* @brief 定时器底层驱动,时钟使能,引脚配置
* 此函数会被HAL_TIM_PWM_Init()调用
* @param htim:定时器句柄
* @retval 无
*/
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM14)
{
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_TIM14_CLK_ENABLE();
__HAL_RCC_GPIOF_CLK_ENABLE();
gpio_init_struct.Pin = GPIO_PIN_9; /* 通道y的GPIO口 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推完输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
gpio_init_struct.Alternate = GPIO_AF9_TIM14; /* IO口REMAP设置, 是否必要查看头文件配置的说明! */
HAL_GPIO_Init(GPIOF, &gpio_init_struct);
}
}
3、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"
#include "./BSP/TIMER/gtim.h"
extern uint16_t g_timxchy_pwmin_psc; /* PWM输入状态 */
extern uint16_t g_timxchy_pwmin_sta; /* 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(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
//led_init(); /* 初始化LED */
gtim_timx_pwm_chy_init(10 - 1, 84 - 1); /* 1Mhz的计数频率, 100Khz PWM */
TIM14->CCR1 = 2; /* F4仅有一个捕获比较寄存器CCR1,设置占空比 */
a_timx_chy_ic_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) / 168; /* 得到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(); /* DS1闪烁 */
t = 0;
}
}
}
#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"
#include "./BSP/TIMER/gtim.h"
extern uint16_t g_timxchy_pwmin_psc; /* PWM输入状态 */
extern uint16_t g_timxchy_pwmin_sta; /* 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(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
//led_init(); /* 初始化LED */
gtim_timx_pwm_chy_init(10 - 1, 84 - 1); /* 1Mhz的计数频率, 100Khz PWM */
TIM14->CCR1 = 2; /* F4仅有一个捕获比较寄存器CCR1,设置占空比 */
a_timx_chy_ic_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) / 168; /* 得到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(); /* DS1闪烁 */
t = 0;
}
}
}
时钟使能不可放在中间,必须放到最前面。导致下面问题,串口打印出错误的结果。
TIM_ICFilter和TIM_ICPolarity不像TI1FP2那样在INDIRECT模式下使用(在channel1路径上)作为输入信号。因此,CCMR1必须是防止未初始化的过滤器和极性值。(来自有道翻译)
笔者的理解是必须把TI1FP2的filter以及polarity一起配置好。
占空比均设为80%,改变了分频系数。图二现象出现较大误差。