STM32通用定时器的输入捕获(实例:输入捕获)

2023-04-14 19:00   375   0  

通用定时器输入捕获概述


输入捕获的工作原理






3aa31c3c-ac51-11ed-bcd2-b8ca3a6cb5c4.webp






在通用定时器框图中,主要涉及到最顶上的一部分(计数时钟的选择)、中间部分(时基单元)、左下部分(输入捕获)这三个部分。这里主要讲解一下左下部分(输入捕获),其他两个部分可以参考文章:【STM32】通用定时器的基本原理(实例:定时器中断)。




输入捕获模式可以用来测量脉冲宽度或者测量频率。STM32的定时器,除了TIM6、TIM7,其他的定时器都有输入捕获的功能。下面以一个简单的脉冲输入为例,简单地讲述一下输入捕获用于测量脉冲宽度的工作原理:






3aa31c3d-ac51-11ed-bcd2-b8ca3a6cb5c4.jpg






先设置输入捕获为上升沿检测,记录发生上升沿时TIMx_CNT的值。然后配置捕获信号为下降沿捕获,当下降沿到来的时候发生捕获,并记录此时的TIMx_CNT的值。这样,前后两次TIMx_CNT的值之差就是高电平的脉宽。同时根据TIM的计数频率,我们就能知道高电平脉宽的准确时间。




输入捕获的通道概览


每一个捕获/比较通道都是围绕着一个捕获/比较寄存器(包含影子寄存器),包括捕获的输入部分(数字滤波、多路复用和预分频器),和输出部分(比较器和输出控制)。




捕获/比较模块由一个预装载寄存器和一个影子寄存器组成。读写过程仅操作预装载寄存器。




在捕获模式下,捕获发生在影子寄存器上,然后再复制到预装载寄存器中。


在比较模式下,预装载寄存器的内容被复制到影子寄存器中,然后影子寄存器的内容和计数器进行比较。






3aa31c3e-ac51-11ed-bcd2-b8ca3a6cb5c4.jpg






输入部分对相应的TIx输入信号采样,并产生一个滤波后的信号TIxF。然后,一个带极性选择的边缘检测器产生一个信号(TIxFPx),它可以作为从模式控制器的输入触发或者作为捕获控制。该信号通过预分频进入捕获寄存器(ICxPS)。




一句话总结工作过程:通过检测TIMx_CHx通道上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。同时,还可以配置捕获时是否触发中断/DMA等。






输入捕获的工作过程


将输入捕获的通道图进行分解,分解成四个部分,下面对这四个部分进行分析来了解输入捕获的工作过程:






3aa31c3f-ac51-11ed-bcd2-b8ca3a6cb5c4.jpg






设置输入捕获滤波器


输入捕获滤波器IC1F[3:0],这个用于设置采样频率和数字滤波器长度。其中:fCK_INT是定时器的输入频率,fDTS是根据TIMx_CR1的CKD[1:0]的设置来确定的。




这里滤波器的作用是什么意思呢?数字滤波器由一个事件计数器组成,它记录到N个事件后会产生一个输出的跳变。也就是说连续N次采样,如果都是高电平,则说明这是一个有效的触发,就会进入输入捕捉中断(如果设置了的话)。这样就可以滤除那些高电平脉宽低于8个采样周期的脉冲信号,从而达到滤波的作用。




设置输入捕捉极性


这里是设置捕捉事件是发生在上升沿还是下降沿。




设置输入捕获映射关系


由于我们只显示了一路通道的通道图,如果在几条通路的情况下:






3aa31c40-ac51-11ed-bcd2-b8ca3a6cb5c4.jpg






在TIMx_CH1和TIMx_CH2两条通道的情况下,我们可以看出除了TIMx_CH1捕捉到的信号可以连接到IC1,TIMx_CH2捕捉到的信号可以连接到IC2之外,TIMx_CH1捕捉到的信号也可以连接到IC2,TIMx_CH2捕捉到的信号也可以连接到IC1。




一般情况下,我们设置成TIMx_CH1捕捉到的信号可以连接到IC1,TIMx_CH2捕捉到的信号可以连接到IC2。




设置输入捕获分频器


这里设置的是每N个事件触发一次捕捉。也就是说,我们可以设置成,每2次上升沿事件触发一次捕捉。






输入捕获相关配置寄存器


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






3aa31c41-ac51-11ed-bcd2-b8ca3a6cb5c4.webp






作用:在输入捕获模式下,确定数字滤波器、通道映射、预分频系数。




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






3aa31c42-ac51-11ed-bcd2-b8ca3a6cb5c4.webp






作用:在输入捕获模式下,确定捕捉极性和捕捉使能。




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






3aa31c43-ac51-11ed-bcd2-b8ca3a6cb5c4.webp






作用:在输入捕获模式下,确定上一次输入捕捉事件传输的计数值。






输入捕获相关配置库函数


1个输入初始化函数


void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);


作用:初始化捕获通道、滤波器、捕获极性、映射关系、分频系数等参数。




注意:由于输出初始化函数将所有的4个通道的函数分开各自定义了一个函数,而输入初始化函数并没有这么做。所以在输入初始化函数中,需要指定捕获通道。




1个参数获取函数


uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);


uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);


uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);


uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx);


作用:在四个通道中选择一个,确定上一次输入捕捉事件传输的计数值。




1个参数设置函数


void TIM_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);


void TIM_OC2PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);


void TIM_OC3PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);


void TIM_OC4PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);


作用:在四个通道中选择一个,设置通道极性。通常在初始化函数中已经设置了通道极性,此函数用于除初始化之外的修改。






输入捕获的一般步骤


实例要求:使用TIM5的通道1(PA0)来作为输入捕获,捕获PA0上高电平的脉宽(用WK_UP按键输入高电平),通过串口打印高电平脉冲时间。




初始化定时器和通道对应IO的时钟;


初始化IO口,模式为输入。调用函数:GPIO_Init();


初始化定时器ARR,PSC。调用函数:TIM_TimeBaseInit();


初始化输入捕获通道。调用函数:TIM_ICInit();


如果要开启捕获中断。调用函数:TIM_ITConfig();NVIC_Init();


使能定时器。调用函数:TIM_Cmd();


编写中断服务函数。调用函数:TIMx_IRQHandler()。


下面按照这个一般步骤来进行一个简单的输入捕获程序:




//定时器5通道1输入捕获配置


TIM_ICInitTypeDef TIM5_ICInitStructure;


void TIM5_Cap_Init(u16 arr,u16 psc)


{


GPIO_InitTypeDef GPIO_InitStructure;


TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;


NVIC_InitTypeDef NVIC_InitStructure;


RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //使能TIM5时钟


RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟




GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0 清除之前设置


GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 输入


GPIO_Init(GPIOA, &GPIO_InitStructure);


GPIO_ResetBits(GPIOA,GPIO_Pin_0); //PA0 下拉




//初始化定时器5 TIM5


TIM_TimeBaseStructure.TIM_Period = arr; //设定计数器自动重装值


TIM_TimeBaseStructure.TIM_Prescaler =psc; //预分频器


TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim


TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式


TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位


//初始化TIM5输入捕获参数


TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; //CC1S=01 选择输入端 IC1映射到TI1上


TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获


TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上


TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频


TIM5_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置输入滤波器 不滤波


TIM_ICInit(TIM5, &TIM5_ICInitStructure);




//中断分组初始化


NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //TIM3中断


NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级2级


NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级


NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能


NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器




TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断 ,允许CC1IE捕获中断




TIM_Cmd(TIM5,ENABLE ); //使能定时器5


}


u8 TIM5CH1_CAPTURE_STA=0; //输入捕获状态


u16 TIM5CH1_CAPTURE_VAL; //输入捕获值


//定时器5中断服务程序


void TIM5_IRQHandler(void)


{


if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获


{


if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)


{


if(TIM5CH1_CAPTURE_STA&0X40)//已经捕获到高电平了


{


if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了


{


TIM5CH1_CAPTURE_STA|=0X80;//标记成功捕获了一次


TIM5CH1_CAPTURE_VAL=0XFFFF;


}else TIM5CH1_CAPTURE_STA++;


}


}


if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)//捕获1发生捕获事件


{


if(TIM5CH1_CAPTURE_STA&0X40) //捕获到一个下降沿


{


TIM5CH1_CAPTURE_STA|=0X80; //标记成功捕获到一次高电平脉宽


TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5);


TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); //CC1P=0 设置为上升沿捕获


}else //还未开始,第一次捕获上升沿


{


TIM5CH1_CAPTURE_STA=0; //清空


TIM5CH1_CAPTURE_VAL=0;


TIM_SetCounter(TIM5,0);


TIM5CH1_CAPTURE_STA|=0X40; //标记捕获到了上升沿


TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); //CC


TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); //CC1P=1 设置为下降沿捕获


}


}


}


TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位


}


extern u8 TIM5CH1_CAPTURE_STA; //输入捕获状态


extern u16 TIM5CH1_CAPTURE_VAL; //输入捕获值


int main(void)


{


u32 temp=0;


delay_init(); //延时函数初始化


NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级


uart_init(115200); //串口初始化为115200


TIM5_Cap_Init(0XFFFF,72-1); //以1Mhz的频率计数


while(1)


{


delay_ms(10);


if(TIM5CH1_CAPTURE_STA&0X80)//成功捕获到了一次上升沿


{


temp=TIM5CH1_CAPTURE_STA&0X3F;


temp*=65536;//溢出时间总和


temp+=TIM5CH1_CAPTURE_VAL;//得到总的高电平时间


printf("HIGH:%d usrn",temp);//打印总的高点平时间


TIM5CH1_CAPTURE_STA=0;//开启下一次捕获


}


}


}


代码逻辑


这里关于输入捕获的初始化部分比较简单,对照着一般步骤来就行了。但是在中断处理函数TIM5_IRQHandler()部分就有所难度了,为什么会比较复杂呢?




由于我们进行输入捕获,一旦捕捉到了上升沿,就设置计数器当前值为0,让它从0开始重新计数:




TIM_SetCounter(TIM5,0);


但是如果脉冲的长度过于宽了,也就是说,从0开始计数到自动重加载值一个循环结束了,脉冲还是没有结束。这个情况下,显而易见不能只记录一下最后的计数器当前值。




解决这个问题的办法:






3aa31c44-ac51-11ed-bcd2-b8ca3a6cb5c4.png




设置一个变量TIM5CH1_CAPTURE_STA,bit5-0为捕捉高电平后定时器溢出的次数,bit6为捕捉到高电平标志,bit7为捕获完场标志。




同时设置两个中断(更新中断和捕获中断):




TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断 ,允许CC1IE捕获中断


在中断处理函数中,先判断是否捕获成功,如果捕获成功了,说明是在脉冲低电平的阶段,什么都不需要做;如果捕获没有成功,说明是在脉冲高电平的阶段,就需要继续判断中断类型,然后再分别进行处理。在更新中断中,表示此时脉冲长度过长,TIM5CH1_CAPTURE_STA加1。在捕获中断中,判断捕捉到的是否为上升沿,如果是,计数器当前值清零,TIM5CH1_CAPTURE_STA清零,同时标记标志,设置极性下降沿捕捉;如果不是,标记捕获完成,保存当前计数器的值,设置极性上升沿捕获。




extern关键字


C语言中,extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此类变量和函数时在其他模块中寻找其定义。




注意:对于extern申明变量可以多次,但是定义只有一次。




登录icspec成功后,会自动跳转查看全文
博客评论
还没有人评论,赶紧抢个沙发~
发表评论
说明:请文明发言,共建和谐网络,您的个人信息不会被公开显示。