【STM32学习】——定时器输入捕获(IC)&PWMI模式&频率测量方法&主从触发模式&IC模式测频&/PWMI模式测频率及占空比

文章目录

  • 前言
  • 一、输入捕获(含PWMI模式)
  • 二、频率测量
  • 三、主从触发模式
  • 1.主模式
  • 2.从模式
  • 3.触发源选择
  • 四、实操案例
  • 1.输入捕获模式测频率
  • 2.PWMI模式测频率和占空比
  • 总结

  • 声明:学习笔记根据b站江科大自化协stm32入门教程编辑,仅供学习交流使用!
    注意:读完大约需要15分钟,但会收获满满!!

    前言

    输入捕获部分一共两个实操案例程序:输入捕获模式测频率、PWMI模式测频率和占空比。
    测频法利用之前的外设就可实现,如对射式红外传感器计次、定时器外部时钟等稍加改进就可!可自行实验,本次实操用的是测周法
    注:考虑到初学者可能没有信号发生器,我们借用了上一小节定时器输出比较产生PWM的代码在PA0产生信号波形!!如果有示波器的话,也可以测试验证下程序的结果是否正确。

    一、输入捕获(含PWMI模式)

    芯片手册14.3.5和14.3.6
    1、IC(Input Capture)输入捕获——下图蓝框部分
    2、输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存到CCR中,可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数
    3、每个高级定时器和通用定时器都拥有4个输入捕获通道。
    4、可配置为PWMI模式,同时测量频率和占空比
    5、可配合主从触发模式,实现硬件全自动测量

    》最左边四个通道的引脚,引脚进来为一个三输入(接在通道1、2、3的端口)的异或门,执行逻辑是三个引脚任何一个有电平翻转输出引脚就会产生一次电平翻转,输出通过数据选择器(梯形符号)到达输入捕获通道1。
    》数据选择器如果选择上面一个,输入捕获通道1的输入就是3个引脚的异或值,如果选择下面一个那么异或门没用,四个通道各用各的引脚对应相应输入捕获通道。(下图通道1详细图)CC1S位可对数据选择器进行选择,
    》输入信号到达输入滤波器和边沿检测器。滤波可避免高频毛刺信号误触发;边沿检测和外部中断一样,可以选择高电平触发或者低电平触发,当出现指定电平时边沿检测电路会触发后续电路执行动作。注意这里其实内部有两套滤波和边沿检测(极性选择)电路,一路得到TI1FP1(TI1 Filter Polarity 1),输出给通道1后续电路,另一路给通道2,其他同理(如图),即两个通道之间可以选择各走各的,也可选择交叉连接,可灵活切换后续捕获电路的输入,并可借此实现把一个引脚的输入同时映射到两个捕获单元,这也是PWMI模式的经典结构。
    》**PWMI模式:**第一个捕获通道使用上升沿触发,用来捕获周期,第二个通道使用下降沿触发用来捕获占空比,两个通道同时对一个引脚进行捕获就可同时测量频率和占空比。
    》另外还有一个TRC信号,也可以选择作为捕获部分的输入,它来自上面橙框的中心那个TRC,这样设计也是为了无刷电机的驱动。
    》信号来到预分频器可选择对信号进行分频,分频之后的触发信号就可触发捕获电力进行工作,每来一个触发信号CNT的值就会向CCR转运一次。转运的同时会发生一个捕获事件CC1I,这个事件会在状态寄存器置标志位,同时也可以产生中断,如果需要在捕获的瞬间处理一些事件得话,就可以开启这个捕获中断。(下图通道1详细图)ICPS位可配置分频器,可选择不分频(1)、2、4、8分频,最后CC1E位控制输出使能或失能。
    》每捕获一次CNT的值都需要把CNT清零一下以便下次捕获,硬件电路可自动完成此操作:(下图)TI1FP1信号和TI1_ED(TI1的边沿信号)都可以通向从模式控制器,比如TI1FP1信号的上升沿触发捕获,它同时触发从模式,从模式里的电路自动完成对CNT的清零。主从触发模式详细见本博客第三部分。
    》拓:设计异或门的目的主要是为三相无刷电机服务,无刷电机有3个霍尔传感器检测转子的位置,可以根据转子的位置进行换相,有了异或门就可在前三个通道接三个霍尔传感器,然后这个定时器就作为无刷电机的接口定时器去驱动换相电路的工作。

    二、频率测量


    1、这里的信号都是只有高低电平变化的数字信号,STM32测频率也只能测数字信号,高3.3v,低0v。
    2、如果需要测量正弦波,那么还需要搭建信号预处理电路,最简单的是利用运放搭一个比较器把正弦波转换为数字信号。
    3、如果测量的信号电压特别高还需要考虑一下隔离的问题,比如运用一些隔离放大器、电压互感器等元件。
    5、测量方法:(2种)

    正如信号图所示,测频法适合频率较高的场合,闸门时间内出现的变化较多误差小;测周法适合频率较低的场合,低频信号周期长,一个周期内以标准频率计的次较多,有助于减少误差!(计次N越大,相对误差越小,因为计次只能是整数会出现正负1的误差,只能通过增大N来缩小这种影响)
    测频法测量的结果更新慢,数值相对稳定,测频法测量的是在闸门时间内的多个周期,所以自带均值滤波,如果在闸门时间内波形频率有变化,那么得到的其实是这段时间的平均频率;测周法更新的快数据跳变也快,出结果的速度决定于待测信号的周期(频率),一般为几百几千Hz,更新较快,因为其只测一个周期所以结果值受噪声影响,波动大
    6、中界频率
    多高算高频率,多低算低频率?它给了我们一个判断标准!
    当有一个频率,测频法和测周法计次N会相同,这个就是中界频率!

    三、主从触发模式

    芯片手册14.3.15——14.4.3
    该模式为我们自己起的名字,为主模式、从模式、触发选择源选择三个功能的简称。在库函数里非常简单,三个函数即可实现这三个功能!

    1.主模式

    可以将定时器内部的信号,映射到TRGO引脚,用于触发别的外设。芯片手册第14章14.4.2附近就有各种主模式的解释,比如想实现定时器的级联,就可以选择一个定时器主模式输出更新信号到TRGO,另一个定时器选择上一个定时器触发从模式,从模式选择执行外部时钟1的操作。

    不需要记忆,知道有这些功能用的时候会查就可!!

    2.从模式

    接收其他外设或自身外设的一些信号,用于控制自身定时器的运行,也就是被别的信号控制。芯片手册第14章14.4.3:

    3.触发源选择

    选择从模式的触发信号源,可以认为时从模式的一部分。选择指定的一个信号得到TRGI,TRGI去触发从模式,从模式可在绿色列表里选择一项操作自动执行。比如想要完成上面所说的想让TI1FP1信号自动触发CNT清零,那么触发源选择就可选TI1FP1,从模式里选择Reset的操作,实现硬件全自动。
    》只有TI1FP1和TI2FP2,没有TI3和TI4的信号,所以通过从模式实现硬件自动化清零CNT只能用通道1和2,对于3和4只能开启捕获中断,在中断里手动(软件)清零,这样程序会处于频繁中断,比较消耗软件资源。

    四、实操案例

    1.输入捕获模式测频率


    //参照上面的输入捕获基本结构图:
    //1、RCC开启时钟,把GPIO和TIM的时钟打开
    //2、GPIO初始化,把GPIO配置成输入模式,一般选择上拉或浮空
    //3、配置时基单元,让CNT计数器在内部时钟的驱动下自增运行(与之前代码一样)
    //4、配置输入捕获单元,包括滤波器、极性、直连通道还是交叉通道、分频器这些参数(结构体统一配置)
    //5、选择从模式的触发源,触发源选择为TI1FP1(一个库函数给个参数即可)
    //6、选择触发之后执行的操作,执行Reset操作(一个库函数即可)
    //7、配置完成,TIM_Cmd函数开启定时器

    //IC.c
    #include "stm32f10x.h"                  // Device header
    void IC_Init(){
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE );
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	
    	GPIO_InitTypeDef GPIO_InitStructure;
     	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
     	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	
    	TIM_InternalClockConfig(TIM3);
    	
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_ClockDivision= TIM_CKD_DIV1;
    	TIM_TimeBaseInitStructure.TIM_CounterMode= TIM_CounterMode_Up;
    	TIM_TimeBaseInitStructure.TIM_Period= 65536-1;//ARR大一些,防止计数溢出
    	TIM_TimeBaseInitStructure.TIM_Prescaler= 72-1;//PSC决定了测周法的标准频率fc = 72MHz/PSC = 1M
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter= 0 ;
    	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
    	
    	TIM_ICInitTypeDef TIM_ICInitStructure;
    	TIM_ICInitStructure.TIM_Channel= TIM_Channel_1;
    	TIM_ICInitStructure.TIM_ICFilter= 0xF;//0x0~0xF数越大滤波效果越好,每个数对应采样频率和次数见手册
    	TIM_ICInitStructure.TIM_ICPolarity= TIM_ICPolarity_Rising;//边沿检测和极性选择,上升沿触发
    	TIM_ICInitStructure.TIM_ICPrescaler= TIM_ICPSC_DIV1;//不分频
    	TIM_ICInitStructure.TIM_ICSelection= TIM_ICSelection_DirectTI;//数据选择器直连or交叉通道
    	TIM_ICInit(TIM3,&TIM_ICInitStructure);
    	
    	TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1);
    	TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);
    	
    	TIM_Cmd(TIM3,ENABLE);
    }
    //初始化后,整个电路就能进行全自动测量,当需要查看频率时需要读取CCR进行计算,所以还需要一个函数:
    
    uint32_t IC_GetFreq(void){
    	return 1000000/TIM_GetCapture1(TIM3);//测周法fx=fc/N,fc为1M,N即为CCR的值
    }
    
    //PWM.c
    //在PWM控制呼吸灯基础上微改
    #include "stm32f10x.h"                  
    void PWM_Init(void){
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE );
    	TIM_InternalClockConfig(TIM2);
    	
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_ClockDivision= TIM_CKD_DIV1;
    	TIM_TimeBaseInitStructure.TIM_CounterMode= TIM_CounterMode_Up;
    	TIM_TimeBaseInitStructure.TIM_Period= 10000-1;//ARR
    	TIM_TimeBaseInitStructure.TIM_Prescaler= 7200-1;//PSC
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter= 0 ;
    	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
    	
    	TIM_OCInitTypeDef TIM_OCInitStructure;
    	TIM_OCStructInit(&TIM_OCInitStructure);
    	TIM_OCInitStructure.TIM_OCMode= TIM_OCMode_PWM1;
    	TIM_OCInitStructure.TIM_OCNPolarity= TIM_OCNPolarity_High;
    	TIM_OCInitStructure.TIM_OutputState= TIM_OutputState_Enable;
    	TIM_OCInitStructure.TIM_Pulse= 0;//CCR
    	TIM_OC1Init(TIM2,&TIM_OCInitStructure);
    	
    	
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	GPIO_InitTypeDef GPIO_InitStructure;
     	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
     	GPIO_Init(GPIOB, &GPIO_InitStructure);
    	
    	TIM_Cmd(TIM2,ENABLE);
    }
    
    void PWM_SetCompare1(uint16_t Compare){//改变CCR,以改变占空比
    	TIM_SetCompare1(TIM2,Compare);
    }
    
    void PWM_SetPrescaler(uint16_t Prescaler){//不同点,单独修改PSC达到修改频率的作用
    	TIM_PrescalerConfig(TIM2,Prescaler,TIM_PSCReloadMode_Immediate);//单独写入PSC的函数,之所以不叫TIM_SetPrescaler是因为它还有个重装模式的参数
    	//immediate立刻生效:可能会在值改变时产生切断波形的现象,在频率变化时会出现不完整的周期
    	//update在更新事件生效:会有一个缓存器,延迟参数写入的时间,等一个周期结束了在更新事件时,再统一改变参数,保证每个周期的完整
    	//此实操案例要求不高,哪个都行
    }
    
    
    //main.c
    #include "stm32f10x.h"   // Device header
    #include "Delay.h"   
    #include "OLED.h"
    #include "PWM.h"
    #include "IC.h"
    
    int main(void){
    	OLED_Init();
    	PWM_Init();
    	IC_Init();
    	
    	OLED_ShowString(1,1,"Freq:00000Hz");
    	
    	PWM_SetPrescaler(720-1);   //Freq = 72M/(PSC+1)/(ARR+1),其中ARR+1为100,所以Fre=1KHz
    	PWM_SetCompare1(50);       //Duty = CCR/(ARR+1),其中ARR+1为100,所以Duty=50%
    	
    	while(1){
    		OLED_ShowNum(1,6,IC_GetFreq(),5);//从第6列开始覆盖showString
    	}
    }
    

    2.PWMI模式测频率和占空比

    //IC.c
    //与输入捕获案例上微改,不同处已标注
    #include "stm32f10x.h"                 
    void IC_Init(){
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE );
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	
    	GPIO_InitTypeDef GPIO_InitStructure;
     	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
     	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	
    	TIM_InternalClockConfig(TIM3);
    	
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_ClockDivision= TIM_CKD_DIV1;
    	TIM_TimeBaseInitStructure.TIM_CounterMode= TIM_CounterMode_Up;
    	TIM_TimeBaseInitStructure.TIM_Period= 65536-1;
    	TIM_TimeBaseInitStructure.TIM_Prescaler= 72-1;
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter= 0 ;
    	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
    	
    	TIM_ICInitTypeDef TIM_ICInitStructure;
    	TIM_ICInitStructure.TIM_Channel= TIM_Channel_1;
    	TIM_ICInitStructure.TIM_ICFilter= 0xF;
    	TIM_ICInitStructure.TIM_ICPolarity= TIM_ICPolarity_Rising;
    	TIM_ICInitStructure.TIM_ICPrescaler= TIM_ICPSC_DIV1;
    	TIM_ICInitStructure.TIM_ICSelection= TIM_ICSelection_DirectTI;
    	TIM_PWMIConfig(TIM3,&TIM_ICInitStructure);//不同点,PWMI模式(交叉通道)
    	/*会自动把剩下的一个通道初始化相反的配置
    	比如上面配置通道1为直连、上升沿;那么这个函数就会把通道2配置为交叉、下降沿,反之同理
    	这个函数只支持通道1、2,不要传入通道3、4*/
    	
    	
    	TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1);
    	TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);
    	
    	TIM_Cmd(TIM3,ENABLE);
    }
    
    
    uint32_t IC_GetFreq(void){
    	return 1000000/TIM_GetCapture1(TIM3);
    }
    
    uint32_t IC_GetDuty(void){//不同点,还需要一个获取占空比的函数
    	return TIM_GetCapture2(TIM3)*100/TIM_GetCapture1(TIM3);//高电平计数值存在CCR2,整个周期的计数值存在CCR1,故占空比=CCR2/CCR1,*100是为了显示成百分比形式
    }
    
    //PWM.c
    //在PWM控制呼吸灯基础上微改,不同处已标注
    #include "stm32f10x.h"                  
    void PWM_Init(void){
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE );
    	TIM_InternalClockConfig(TIM2);
    	
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_ClockDivision= TIM_CKD_DIV1;
    	TIM_TimeBaseInitStructure.TIM_CounterMode= TIM_CounterMode_Up;
    	TIM_TimeBaseInitStructure.TIM_Period= 10000-1;//ARR
    	TIM_TimeBaseInitStructure.TIM_Prescaler= 7200-1;//PSC
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter= 0 ;
    	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
    	
    	TIM_OCInitTypeDef TIM_OCInitStructure;
    	TIM_OCStructInit(&TIM_OCInitStructure);
    	TIM_OCInitStructure.TIM_OCMode= TIM_OCMode_PWM1;
    	TIM_OCInitStructure.TIM_OCNPolarity= TIM_OCNPolarity_High;
    	TIM_OCInitStructure.TIM_OutputState= TIM_OutputState_Enable;
    	TIM_OCInitStructure.TIM_Pulse= 0;//CCR
    	TIM_OC1Init(TIM2,&TIM_OCInitStructure);
    	
    	
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	GPIO_InitTypeDef GPIO_InitStructure;
     	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
     	GPIO_Init(GPIOB, &GPIO_InitStructure);
    	
    	TIM_Cmd(TIM2,ENABLE);
    }
    
    void PWM_SetCompare1(uint16_t Compare){//改变CCR,以改变占空比
    	TIM_SetCompare1(TIM2,Compare);
    }
    
    void PWM_SetPrescaler(uint16_t Prescaler){//不同点,单独修改PSC达到修改频率的作用
    	TIM_PrescalerConfig(TIM2,Prescaler,TIM_PSCReloadMode_Immediate);//单独写入PSC的函数,之所以不叫TIM_SetPrescaler是因为它还有个重装模式的参数
    	//immediate立刻生效:可能会在值改变时产生切断波形的现象,在频率变化时会出现不完整的周期
    	//update在更新事件生效:会有一个缓存器,延迟参数写入的时间,等一个周期结束了在更新事件时,再统一改变参数,保证每个周期的完整
    	//此实操案例要求不高,哪个都行
    }
    
    
    //main.c
    #include "stm32f10x.h"   // Device header
    #include "Delay.h"   
    #include "OLED.h"
    #include "PWM.h"
    #include "IC.h"
    
    int main(void){
    	OLED_Init();
    	PWM_Init();
    	IC_Init();
    	
    	OLED_ShowString(1,1,"Freq:00000Hz");
    	OLED_ShowString(2,1,"Duty:00%");//不同点
    	
    	PWM_SetPrescaler(720-1);   //Freq = 72M/(PSC+1)/(ARR+1),其中ARR+1为100,所以Fre=1KHz
    	PWM_SetCompare1(50);       //Duty = CCR/(ARR+1),其中ARR+1为100,所以Duty=50%
    	
    	while(1){
    		OLED_ShowNum(1,6,IC_GetFreq(),5);
    		OLED_ShowNum(2,6,IC_GetDuty(),2);//不同点
    	}
    }
    

    总结

    输出比较与输入捕获对比:
    1、输出比较引脚是输出端口,输入捕获引脚是输入端口。
    2、输出比较是根据CNT和CCR的大小关系来执行输出动作;输入捕获是接收到输入信号,执行CNT锁存到CCR的动作。
    遇到挫折,要有勇往直前的信念,马上行动,坚持到底,决不放弃,成功者决不放弃,放弃者绝不会成功。成功的道路上,肯定会有失败;对于失败,我们要正确地看待和对待,不怕失败者,则必成功;怕失败者,则一无是处,会更失败。
    今天的学习分享到此就结束了,我们下次再见!!

    往期精彩:
    STM32定时器输出比较功能(PWM)
    STM32定时中断
    STM32外部中断
    STM32GPIO精讲

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【STM32学习】——定时器输入捕获(IC)&PWMI模式&频率测量方法&主从触发模式&IC模式测频&/PWMI模式测频率及占空比

    发表评论