目录

江科大STM32TIM输入捕获模式PWMI模式测频率

【江科大STM32】TIM输入捕获模式&PWMI模式测频率

一、输入捕获测频率

接线图:

测信号的输入引脚为PA6,信号从PA6进来,待测的PWM信号也是STM32自己生成的,输出引脚是PA0,所以接线这里直接用一根线将PA0引到PA6就可以了。 如果有信号发生器的话,也可以设置成方波信号输出,高电平设置成3.3v,低电平0v,然后直接接到PA6,另一个就是共地。

https://i-blog.csdnimg.cn/direct/85d07576d7a341f9861c239ec26a5af7.jpeg

PWM初始化模块:

目前这里我们要借用一下之前写过的PWM模块代码,以便生成待测信号,所以这里程序直接复制,之前的PWM驱动LED呼吸灯工程,在这个工程基础上写。然后在这个PWM模块代码进行改进,目前这个代码逻辑是,初始化TIM2的通道1,产生一个PWM的波形,输出引脚是PA0,然后通过Set_Compare函数,可以调节CCR1的寄存器的值,从而控制PWM的占空比,但是目前这个PWM频率是在初始化已经写好的了,是固定的,操作起来不太方便。所以我们要在最后再加一个函数,用来便捷地调节PWM频率。

那这里如何调节PWM频率?

通过公式,我们知道PWM频率=更新频率=72M/(PSC+1/(ARR+1),所以PSC和ARR都可以调节频率,但是占空比=CCR/(ARR+1),所以通过ARR调节频率,同时还会影响到占空比,而通过PSC调节频率,不会影响占空比,显然比较方便。所以这里我们直接固定ARR为100-1,通过调节PSC来改变PWM频率,这里的ARR为100-1,CCR的数值直接就是占空比。

这里实际使用也是有技巧的,一般可以根据分辨率的要求,先确定好ARR值,比如分辨率[1/(ARR+1)],1%就足够了。那ARR就是给100-1,这样PSC决定频率,CCR决定占空比。如果想要更高的分辨率,比如0.1%,那ARR就先固定1000-1,这样频率就是72M/预分频/1000,占空比就是CCR/1000,这样也好算。

然后ARR我们固定给100-1,初始化操作的PSC就先不管,后面再写一个函数,在初始化之后单独修改PSC。

https://i-blog.csdnimg.cn/direct/7f104af97b4c472dadfb36ab9db99a3c.png

void PWM_SetPrescaler(uint16_t Prescaler) //配置TIMx预调度器。
{
	TIM_PrescalerConfig(TIM2,Prescaler,TIM_PSCReloadMode_Immediate);//Prescaler立即被加载
}

代码详解:

void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode)//配置TIMx预调度器

参数说明
TIMx其中x为1 ~ 17,选择TIM外设
Prescaler指定预Prescaler寄存器值
TIM_PSCReloadMode指定TIM预分频器的重装模式。该参数可以是以下值之一:TIM_PSCReloadMode_Update:在更新事件中加载precaler;TIM_PSCReloadMode_Immediate:立即加载precaler

TIM_PSCReloadMode的两个选择参数说白了还是影子寄存器的预装载问题,就是你写入的值是立刻生效还是在更新事件中生效。

  • 立刻生效:可能在值发生改变时产生切断波形的现象,比如PWM一个周期刚过去一半,立刻生效了,那就立刻切断当前波形,开始新的一个周期。在频率变化时,这里会出现一个不完整的周期。
  • 更新事件生效:就是会有一个缓存器,延迟参数的写入时间,等一个周期结束了,在更新事件时,再统一改变参数, 保证每个周期的完整。

在这里我们选择哪个参数都无所谓,我们要求不高,哪个都可以,那这里就选择立刻生效吧。

主函数这里调用之后就生成一个频率为 1KHz,占空比为50%的信号了。

int main(void)
{
	OLED_Init();
	PWM_Init();
	
	PWM_SetPrescaler(720 - 1);  //频率=72M/(PSC - 1)/(ARR - 1) 这里(ARR - 1)=100
	PWM_SetCompare1(50);		//Duty= CCR/(ARR - 1)
	
	while(1)
	{
		
	}
}

输入捕获初始化:

这里因为是自己测自己,所以还要建一个输入捕获模块代码,测一下PA0口的频率和占空比。

https://i-blog.csdnimg.cn/direct/881b1ad93dfc40fab467636a811627f6.png

步骤:

①RCC开启时钟(把要用的TIM外设和 外设时钟都打开)

②GPIO初始化,把GPIO配置为输入模式 ,一般选择上拉或者浮空输入模式

③配置时基单元,让CNT计数器在内部时钟驱动下自增运行

④配置输入捕获单元,包括滤波器、极性选择、直连通道还是交叉通道、分频器这些参数,用一个结构体就可以配置

⑤选择从模式的触发源触发源为TF1FP1,这里调用一个库函数给一个参数即可

⑥选择触发之后执行的操作

⑦开启定时器

代码:

这里的 时钟选择 ,要选择TIM3,也是APB1外设时钟,因为PWM模块还需要TIM2输出PWM,所以要开启TIM3时钟。GPIO初始化需要查看引脚定义表, TIM3通道1对应的是PA6引脚 ,如果选择其它定时器或者其它通道,那这个引脚就需要根据引脚定义表改变

https://i-blog.csdnimg.cn/direct/5d81e87c19e04b5a8705f4aa981148bb.png

这里的 TIM_Prescaler值 决定了测周法的标准频率Fc,72M/预分频就是就计数器自增的频率,就是计数器标准频率,这个需要根据你信号频率的分布范围来调整, 这里暂时先给72-1,这样标准频率就是72M/72=1MHz,这样方便计算。

void TIM_ICInit(TIM_TypeDef TIMx, TIM_ICInitTypeDef TIM_ICInitStruct)//根据指定初始化TIM外设TIM_ICInitStruct中的参数**

输入捕获初始化结构定义
参数说明
TIM_Channel指定TIM通道(1~4通道)
TIM_ICPolarity极性选择,指定输入信号的活动边缘(上升、下降、双边沿触发)
TIM_ICSelection指定输入(直连或者交叉输入)
TIM_ICPrescaler指定输入捕获预分频(1、2、4、8分频,1分频就是不分频)
TIM_ICFilter指定输入捕获过滤器,取值范围为0x0 ~ 0xF之间的数字,数值越大,滤波效果越好
滤波器和分频器的区别:

虽然两个都是计次的东西,但是滤波器计次,并不会改变信号的原有频率,一般滤波器的采样频率都会远高于信号频率,所以它只会滤除高频噪声,使信号更加平滑,1KHz滤波之后仍然是1KHz,信号频率不会发生变化。而分频器就是对信号本身进行计次,会改变频率,1KHz,二分频之后就是500Hz,四分频之后就是250Hz。

对应步骤⑤ :

void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource)//选择输入触发器源

参数说明
TIMx其中x可以是1、2、3、4、5、8、9、12或15来选择TIM外设。
TIM_InputTriggerSource输入触发器源。

TIM_InputTriggerSource参数选择:下面两图对应

https://i-blog.csdnimg.cn/direct/99f4050a42014e278013674db60ac211.png

https://i-blog.csdnimg.cn/direct/57f7f5ece8204567aedc4b203196a57c.png

对应步骤⑥

void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode)//选择TIMx从模式

参数说明
TIMx其中x可以是1、2、3、4、5、8、9、12或15来选择TIM外设。
TIM_SlaveMode指定定时器从模式。

TIM_SlaveMode参数选择:

https://i-blog.csdnimg.cn/direct/13a53a0afac5478db65980f0df01844c.png

https://i-blog.csdnimg.cn/direct/02eb561c8d424b6bbce80580c62fe1b9.png

最后,启动定时器后,CNT就会在内部时钟的驱动下不断自增,即使信号没有过来它也会自增。一直自增也没有关系,因为在有信号过来的时候,它就会在从模式下自动清零,不会影响测量。初始化完成后,整个电路就能实现全自动测量了。

void IC_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);			//开启TIM3的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	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);							//将PA6引脚初始化为上拉输入
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM3);		//选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	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的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元
	
	/*输入捕获初始化*/
	TIM_ICInitTypeDef TIM_ICInitStructure;							//定义结构体变量
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;				//选择配置定时器通道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_ICInit(TIM3, &TIM_ICInitStructure);							//将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
	
	/*选择触发源及从模式*/
	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);					//触发源选择TI1FP1
	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);					//从模式选择复位
																	//即TI1产生上升沿时,会触发CNT归零
	
	/*TIM使能*/
	TIM_Cmd(TIM3, ENABLE);			//使能TIM3,定时器开始运行
}

注意:

void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

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

这里输出比较和输入捕获都有四个通道,OCInit四个通道,每个通道占用一个函数,而ICInit是四个通道共用一个函数的,在结构体会有额外一个参数选择哪个通道。

  1. void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
  2. void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
  3. void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
  4. void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);
  5. uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);//获取TIMx输入捕获1值。
  6. uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);//获取TIMx输入捕获2值。
  7. uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);//获取TIMx输入捕获3值。
  8. uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx); //获取TIMx输入捕获4值。

这8个函数都是读取各个通道的CCR值,是相对应的,输出比较模式下,CCR是只写的,用TIM_SetCompare写入;输入捕获模式下,CCR是只读的,用TIM_GetCapture读出。

返回值:各Capture Compare 寄存器值。

查看频率读取CCR(频率)

这里需要执行公式Fx=Fc/N,之前说Fc=72M/(PSC+1),PSC=72-1,所以72M/72=1MHz,然后除以N,N就是读取CCR的值,用uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx)函数读取

uint32_t IC_GetFreq(void)
{
	return 1000000 / (TIM_GetCapture1(TIM3) + 1);		//测周法得到频率fx = fc / N,这里不执行+1的操作也可
}

主函数:

int main(void)
{
	OLED_Init();
	PWM_Init();
	IC_Init();
	
	OLED_ShowString(1,1,"Freq:00000Hz");
	
	/*使用PWM模块提供输入捕获的测试信号:将待测信号传给PA0,PA0又通过导线输入到PA6,PA6是
	TIM3的通道1,通道1通过输入捕获模块,测得频率*/
	PWM_SetPrescaler(720 - 1);  //频率=72M/(PSC - 1)/(ARR - 1) 这里(ARR - 1)=100
	PWM_SetCompare1(50);		//Duty= CCR/(ARR - 1)
	
	while(1)
	{
		OLED_ShowNum(1,6,IC_GetFreq(),5);//不断刷新显示输入捕获测得的频率
	}
}

二、PWMI模式测频率&占空比

这里输入捕获代码部分直接对上一个代码进行升级就可以了。配置成两个通道同时捕获一个引脚

https://i-blog.csdnimg.cn/direct/591533b532b847c6a66815f6f814f28c.png

这里输入捕获有两种配置方法:效果一样

1、直接复制多一份通道初始化

https://i-blog.csdnimg.cn/direct/2226454160d44e31ad1a606a77a7a7ad.png

2、 使用ST公司封装好的函数

https://i-blog.csdnimg.cn/direct/230d7de69cf345598d72f953f09bd1c3.png

void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct)//根据指定配置TIM外设TIM_ICInitStruct中的参数来测量外部PWM信号

作用:这个函数你只要传入一个通道的参数,在函数里它会自动把剩下一个通道初始化成相反的配置,比如这里传入通道1,直连,上升沿,函数就会顺带配置通道2,交叉,下降;传入通道2,直连,上升沿,函数就会顺带配置通道1,交叉,下降沿。这个函数只能传入通道1和通道2,不能传通道3和通道4。

获取占空比函数:

根据上节课 PWMI 分析 ,高电平的计数值存在CCR2里,整个周期的计数值存在CCR1里,用CCR2/CCR1就可以得到占空比了。

/**
  * 函    数:获取输入捕获的占空比
  * 参    数:无
  * 返 回 值:捕获得到的占空比
  */
uint32_t IC_GetDuty(void)
{
	return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);	//占空比Duty = CCR2 / CCR1 * 100,这里不执行+1的操作也可
}

主函数:

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	PWM_Init();			//PWM初始化
	IC_Init();			//输入捕获初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Freq:00000Hz");		//1行1列显示字符串Freq:00000Hz
	OLED_ShowString(2, 1, "Duty:00%");			//2行1列显示字符串Duty:00%
	
	/*使用PWM模块提供输入捕获的测试信号*/
	PWM_SetPrescaler(720 - 1);					//PWM频率Freq = 72M / (PSC + 1) / 100
	PWM_SetCompare1(50);						//PWM占空比Duty = CCR / 100
	
	while (1)
	{
		OLED_ShowNum(1, 6, IC_GetFreq(), 5);	//不断刷新显示输入捕获测得的频率
		OLED_ShowNum(2, 6, IC_GetDuty(), 2);	//不断刷新显示输入捕获测得的占空比
	}
}

测频率性能:

误差分析: