前言
学习是永无止境的,就算之前学过的东西再次学习一遍也能狗学习到很多东西,输入捕获很早之前就用过了,但是仅仅是照搬例程没有去进行理解。温故而知新!
定时器
定时器简介
定时器的分类
高级定时器 通用定时器 基本定时器,针对不同情况可以选择自己所需要的定时器,以下是这几种定时器的主要功能。
高级定时器:
- 计数器单元
- 输入捕获
- 重复计数器
- PWM模式
- 互补输出和死区插入
- 支持针对定位的增量(正交)编码器和霍尔传感器电路
- 刹车功能
- DMA功能
通用定时器:
- 计数器单元
- 输入捕获
- PWM模式
- 支持针对定位的增量(正交)编码器和霍尔传感器电路
- DMA功能
基本定时器:
- 计数器单元
- DMA功能
定时器的功能
我们使用定时器很多时候使用都是用做于计数作用,同样也可以作为PWM输出,输入捕获(脉宽测量,频率检测)等等,有的时候我们还会使用DMA+PWM输出这样的功能,总之定时器对于我们进行产品开发是必不可少的。
定时器的配置
主要寄存器
这里我使用的是航芯ACM32FP0X系列的单片机,不过其他单片机用法也是一样的。
这里我们主要关注以上六个寄存器,中断使能寄存器、状态寄存器、事件产生寄存器、预分频寄存器、自动加载寄存器、捕获/比较寄存器。其他的寄存器不是说不重要,只是在本次实验中主要理解这几个寄存器就好。
PWM配置
因为本次实验没有使用外置的脉冲发生器,所以使用单片机上另一个通用定时器来配置PWM,然后使用高级定时器去进行输入捕获。
TIM_Handler_PWM.Instance = TIM15;
TIM_Handler_PWM.Init.ARRPreLoadEn = TIM_ARR_PRELOAD_ENABLE;
TIM_Handler_PWM.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
TIM_Handler_PWM.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM_Handler_PWM.Init.RepetitionCounter = 0;
TIM_Handler_PWM.Init.Prescaler = (timer_clock/TIM_CLOCK_FREQ)*1 - 1;
if (timer_clock%TIM_CLOCK_FREQ > TIM_CLOCK_FREQ/2)
{
TIM_Handler_PWM.Init.Prescaler = TIM_Handler_PWM.Init.Prescaler + 1;
}
TIM_Handler_PWM.Init.Period = (TIM_CLOCK_FREQ/100000)*20 - 1; // period = 1ms
首先是常规的配置,这里我选择的是定时器15作为PWM口进行输出,然后不分频所以APB的时钟就是定时器的时钟。然后就是自动重装载值ARR,和预分配系数Prescaler的配置;这两个参数共同决定了配置的PWM输出极限频率是多少。
PWM输出频率 Fre = 。
这里来解析一下为什么要使用 TIM_CLOCK_FREQ这个参数:
TIM_CLOCK_FREQ 代表的是定时器经过分频之后能够达到的极限频率,我们可以来推导一下,timer_clock(也就是主时钟APBCLK)分频之后 就是 ,
简单约去公约数,得到的就是TIM_CLOCK_FREQ,所以当重装载值为1的时候TIM_CLOCK_FREQ就是最高频率。再来解释一下周期是怎么得出的,由于1s之内计数器计数了TIM_CLOCK_FREQ次,所以一次需要的时间是1/TIM_CLOCK_FREQ,所以周期和重装载值之间的关系就是T= ,这里除以1000的原因是将秒的单位换算成毫秒。OK,也不知道讲清楚了没有,之前也搞不懂这个关系式,后来推导出来的时候还感觉蛮巧妙的。
配置完这些再配置一下模式和占空比(CCR)和引脚就行了
输入捕获的配置
TIM_IC_InitTypeDef Tim_IC_Init_Para;
TIM_Handler.Instance = TIM1;
TIM_Handler.Init.ARRPreLoadEn = TIM_ARR_PRELOAD_ENABLE;
TIM_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
TIM_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM_Handler.Init.RepetitionCounter = 0;
TIM_Handler.Init.Prescaler = 0;
TIM_Handler.Init.Period = 0xFFFF; // max period
TIM1_MSP_Pre_Init(&TIM_Handler);
HAL_TIMER_Base_Init(&TIM_Handler);
Tim_IC_Init_Para.TIFilter = TIM_TI1_FILTER_LVL(0); // no filter
Tim_IC_Init_Para.ICPolarity = TIM_SLAVE_CAPTURE_ACTIVE_RISING_FALLING;
Tim_IC_Init_Para.ICPrescaler = TIM_IC1_PRESCALER_1;
Tim_IC_Init_Para.ICSelection = TIM_ICSELECTION_DIRECTTI; // TI1FP1
HAL_TIMER_Capture_Config(TIM_Handler.Instance, &Tim_IC_Init_Para, TIM_CHANNEL_1);
TIM1_MSP_Post_Init();
上面也是一些很常规的配置,配置了分频系数(为1意味着定时器的频率和APB时钟一致),重装载值为0xFFFF,这些数据传达了一个怎样的信息呢?说明了使用输入捕获的频率可高达64MHz(APB时钟为64MHz),说明捕获精度可以达到1/64000000 s,也就是15ns,最长捕获时间间隔为1023us(15ns * 0xFFFF)。然后下一个就是设置捕获触发模式,可能不同的单片机的库设置起来会有细微差别,但是大致意思是相同的。由于我们需要收集这些鞋数据然后在串口上打印出来所以我这里使用了中断触发,在中断里面收集数据。
HAL_TIMER_Clear_Capture_Flag(&TIM_Handler, TIM_CHANNEL_1);
NVIC_ClearPendingIRQ(TIM1_CC_IRQn);
NVIC_ClearPendingIRQ(TIM1_BRK_UP_TRG_COM_IRQn);
NVIC_EnableIRQ(TIM1_CC_IRQn);
NVIC_EnableIRQ(TIM1_BRK_UP_TRG_COM_IRQn);
HAL_TIMER_Base_Init(&TIM_Handler);
HAL_TIM_ENABLE_IT(&TIM_Handler, TIM_IT_CC1);
HAL_TIM_ENABLE_IT(&TIM_Handler, TIMER_INT_EN_UPD); //计数器向上溢出/向下溢出 用于记录更新次数
HAL_TIM_Capture_Start(TIM_Handler.Instance, TIM_CHANNEL_1);
当然这里还加了个溢出中断,用于辅助捕获计算超出最大捕获长度的电平时间,具体思路就是当计数器溢出的时候在事件更新中断里面设置一个变量进行加加。
可以大致看一下捕获中断里面的函数,主要是获取触发了捕获中断之后将数据写入事先定义好的数组,然后在定义一个变量进行计数。
void TIM1_CC_IRQHandler(void)
{
uint32_t status;
status = TIM1->SR;
if ( (status & TIMER_SR_CC1IF) && ((TIM1->CCMR1) & (BIT0|BIT1)) )
{
if(flag1 == 1)
flag2 = 1;
flag1 = 1;
Capture_data[0][capture_times] = TIM1->CCR1;
}
if ((status & TIMER_SR_CC2IF) && ((TIM1->CCMR1) & (BIT8|BIT9)) )
{
Capture_data[1][capture_times] = TIM1->CCR2;
}
capture_times++;
NVIC_ClearPendingIRQ(TIM1_CC_IRQn);
}
然后在主函数里面获取标志位之后打印显示就可以了
这样在主函数当中我们就可以是去使用到这些数据然后打印到串口。这里要注意一点我使用的是双边沿触发所以打印出来的不是一个周期,而是两段占空比的数值。
当然这里我还使用了DMA+PWM进行PWM输出,如果再讲下去就有点多了,这部分内容网上也有很多。
总结
输入捕获是高级定时器和通用定时器才有的功能,主要通过设置触发边沿和设置捕获频率来对脉宽进行一个测量,得出脉宽之后我们就可以很容易地计算出频率和周期。