单片机 PWM输入捕获【学习记录】

前言

学习是永无止境的,就算之前学过的东西再次学习一遍也能狗学习到很多东西,输入捕获很早之前就用过了,但是仅仅是照搬例程没有去进行理解。温故而知新!

定时器

定时器简介

定时器的分类

高级定时器 通用定时器 基本定时器 ,针对不同情况可以选择自己所需要的定时器,以下是这几种定时器的主要功能。

高级定时器:

  1. 计数器单元
  2. 输入捕获
  3. 重复计数器
  4. PWM模式
  5. 互补输出和死区插入
  6. 支持针对定位的增量(正交)编码器和霍尔传感器电路
  7. 刹车功能
  8. DMA功能

通用定时器:

  1. 计数器单元
  2. 输入捕获
  3. PWM模式
  4. 支持针对定位的增量(正交)编码器和霍尔传感器电路
  5. DMA功能

基本定时器:

  1. 计数器单元
  2. 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输出,如果再讲下去就有点多了,这部分内容网上也有很多。

总结

输入捕获是高级定时器和通用定时器才有的功能,主要通过设置触发边沿和设置捕获频率来对脉宽进行一个测量,得出脉宽之后我们就可以很容易地计算出频率和周期。

相关推荐
是孑然呀1 小时前
【小记】word批量生成准考证
笔记·学习·excel
ll7788113 小时前
C++学习之路,从0到精通的征途:继承
开发语言·数据结构·c++·学习·算法
LuckyLay4 小时前
React百日学习计划——Deepseek版
前端·学习·react.js
安和昂5 小时前
【iOS】SDWebImage源码学习
学习·ios
菜一头包5 小时前
c++ std库中的文件操作学习笔记
c++·笔记·学习
猴子请来的逗比4895 小时前
tomcat搭建内网论坛
学习·tomcat
belldeep5 小时前
如何阅读、学习 Git 核心源代码 ?
git·学习·源代码
Kazefuku5 小时前
python文件打包成exe文件
python·学习
threelab6 小时前
08.webgl_buffergeometry_attributes_none ,three官方示例+编辑器+AI快速学习
学习
嵌入式仿真实验教学平台6 小时前
「国产嵌入式仿真平台:高精度虚实融合如何终结Proteus时代?」——从教学实验到低空经济,揭秘新一代AI赋能的产业级教学工具
人工智能·学习·proteus·无人机·低空经济·嵌入式仿真·实验教学