stm32学习笔记---TIM输入捕获(代码部分)输入捕获模式测频率/PWMI模式测频率占空比

news2024/11/15 1:24:57

目录

第一个代码:输入捕获模式测频率

调整频率

PWM.c

PWM.h

输入捕获

IC.c

输入捕获初始化步骤

TIM.h库函数

TIM_ICInit

TIM_PWMIConfig

TIM_ICStructInit

TIM_SelectInputTrigger

TIM_SelectOutputTrigger

TIM_SelectSlaveMode

单独配置四个通道的分频器的函数

代码实现

第一步,RCC开启时钟

第二步,GPIO初始化

第三步,配置时基单元

第四步,配置输入捕获单元

第五步,选择从模式的触发源

第六步,选择触发之后执行的操作

第七步,调用TIM_Cmd函数开启定时器。

IC.h

main.c

第二个代码:PWMI模式测频率占空比

IC.c

IC.h

main.c

测频率的性能


声明:本专栏是本人跟着B站江科大的视频的学习过程中记录下来的笔记,我之所以记录下来是为了方便自己日后复习。如果你也是跟着江科大的视频学习的,可以配套本专栏食用,如有问题可以QQ交流群:963138186

本节我们来学习输入捕获的代码部分。

第一个代码:输入捕获模式测频率

接线图:

我们测量信号的输入引脚是PA6,信号从PA6进来。待测的PWM信号也是STIM32自己生成的,输出引脚是PA0。所以接线这里直接用一根线把PA6引到PA6就行了。如果你有信号发生器的话,也可以设置成方波信号,输出高电平3.3V,低电平0V,然后直接接到PA6,然后和GND共地。

复制PWM驱动LED呼吸灯那一节的工程并改名

然后我们将原本的代码改进一下。

调整频率

原本的代码的逻辑是初始化TIM2的通道,产生一个PWM波形。输出引脚是PA0。

目前PWM的频率是在初始化里写好,是固定的,运行的时候调节不太方便。所以我们在最后再加一个函数,用来便捷地调节PWM频率。

如何调节PWM频率?

通过公式,我们知道PWM频率=更新频率=72M/PSC+1/ARR+1。所以PSC和ARR都可以调节频率,但是占空比=CCR/ARR+1。所以通过调节ARR调节频率,还同时会影响到占空比。而通过PSC调节频率不会影响占空比,显然比较方便。

所以我们的计划是固定ARR100-1,通过调节PSC来改变PWM频率。

另外ARR100-1,CCR的数值直接就是占空比,用起来比较直观。

这里再总结一下调节PWM频率时,如何确定PSC和ARR以及CCR的值:

一般我们可以根据分辨率的要求先确定好ARR。比如分辨率1%就足够了,那ARR给100-1,这样PSC决定频率,CCR决定占空比,这就好算了。

如果我们想要更高的分辨率,比如0.1%,那ARR就先固定1000-1,这样频率就是72M/预分频/1000,

占空比就是CCR/1000,这样也好算。

接下来就开始单独封装一个函数来单独调整频率。

然后我们需要在TIM.h里找一个函数,调用这个函数就是单独写入PSC

因为这个函数还有一个重装模式的参数,所以它并不叫SetPrescaler,而叫PrescalerConfig,这是这个库的命名规范,但其实都是一个意思,就是写入PSC。

第一个参数TIMx,我们使用的是定时器二,所以给TIM2。

第二个Prescaler就是写入PSC的值, 我们直接把外层函数的这个参数传进去。

第三个参数TIM_PSCReloadMode就是重装模式,它的参数解释是指定定时器预分频器的重装模式。

这个参数可以是下面的其中一个值:

第一个TIM_PSCReloadMode_Update预分频器在更新事件重装。

第二个TIM_PSCReloadMode_Immediate预分频器立即重装。

其实还是影子计行器预装载这个问题,就是写入的值是立刻生效,还是在更新事件生效。立刻生效可能会在值改变时产生切断波形的现象。

比如PWM一个周期刚过去一半立刻生效了,那就立刻切断当前波形开始新的一个周期,在频率变化时,这里会出现一个不完整的周期。

那在更新事件生效就是会有个缓存器,延迟参数的写入时间,等一个周期结束了,在更新事件时,再统一改变参数,保证每个周期的完整。

那目前我们这个程序使用哪个参数?因为我们目前要求不高,哪个都行,就选择立刻生效。

这样我们这个单独调整频率值的函数就写好了

然后我们在主函数循环之前调用一下这两个函数就是这样的

这样调用之后,PA口就能输出一个频率1KHz,占空比50%的待测信号。

PWM.c

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:PWM初始化
  * 参    数:无
  * 返 回 值:无
  */
void PWM_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO重映射*/
//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);			//开启AFIO的时钟,重映射必须先开启AFIO的时钟
//	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);			//将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
//	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);		//将JTAG引脚失能,作为普通GPIO引脚使用
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;		//GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA0引脚初始化为复用推挽输出	
																	//受外设控制的引脚,均需要配置为复用模式		
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;					//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
	
	/*输出比较初始化*/
	TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量
	TIM_OCStructInit(&TIM_OCInitStructure);							//结构体初始化,若结构体没有完整赋值
																	//则最好执行此函数,给结构体所有成员都赋一个默认值
																	//避免结构体初值不确定的问题
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;				//输出比较模式,选择PWM模式1
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//输出极性,选择为高,若选择极性为低,则输出高低电平取反
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	//输出使能
	TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);						//将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/**
  * 函    数:PWM设置CCR
  * 参    数:Compare 要写入的CCR的值,范围:0~100
  * 返 回 值:无
  * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
  *           占空比Duty = CCR / (ARR + 1)
  */
void PWM_SetCompare1(uint16_t Compare)
{
	TIM_SetCompare1(TIM2, Compare);		//设置CCR1的值
}

/**
  * 函    数:PWM设置PSC
  * 参    数:Prescaler 要写入的PSC的值,范围:0~65535
  * 返 回 值:无
  * 注意事项:PSC和ARR共同决定频率,此函数仅设置PSC的值,并不直接是频率
  *           频率Freq = CK_PSC / (PSC + 1) / (ARR + 1)
  */
void PWM_SetPrescaler(uint16_t Prescaler)
{
	TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Immediate);		//设置PSC的值
}

PWM.h

#ifndef __PWM_H
#define __PWM_H

void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);
void PWM_SetPrescaler(uint16_t Prescaler);

#endif

输入捕获

接下来开始封装输入捕获的代码

IC.c

输入捕获初始化步骤

首先还是先初始函数,初始化的步骤就根据这个结构图来

第一步,RCC开启时钟,把GPIO和TIM的时钟打开。

第二步,GPIO初始化,把GPIO配置成输入模式,一般选择上拉输入或者浮空输入模式。

第三步,配置时基单元,让CNT计数器在内部时钟的驱动下自增运行。这一步和之前的代码是一样的。

第四步,配置输入捕获单元,包括滤波器、极性、直连通道还是交叉通道、分频器这些参数用一个结构体就可以统一进行配置了。

第五步,选择从模式的触发源,触发源选择为TI1FP1,这里调用一个库函数,给个参数就行了。

第六步,选择触发之后执行的操作,执行reset(复位)操作,这里也是调用个库函数就行了。

第七步,调用TIM_Cmd函数开启定时器。

这样所有的电路就能配合起来,按照我们的要求工作了。

当我们需要读取最新一个周期的频率时,直接读取CCR寄存器,然后按照fc/N计算一下就行了。

这就是整个程序的思路。

我们再来介绍一下TIM.h里面的一些库函数。

TIM.h库函数

TIM_ICInit

用结构体配置输入捕获单元的函数。第一个参数选择哪个定时器,第二个参数就是包含各个配置的结构体。

另外注意,输入捕获和输出比较都有四个通道,OCInit四个通道,它每个通道单独占一个函数。

而ICInit四个通道是共用一个函数的,在结构体里会额外有一个参数,可以用来选择具体是配置哪个通道。因为可能有交叉通道的配置,所以函数合在一起比较方便。

TIM_PWMIConfig

这个函数和上一个函数类似,都是用于初始化输入捕获单元的。但是上一个函数只是单一的配置一个通道,而这个函数可以快速配置两个通道,把外设电路结构配置成PWMI模式:

这个结构图等会儿我们写第二个代码的时候会用到。

TIM_ICStructInit

可以给输物捕获结构体赋一个初始值。

TIM_SelectInputTrigger

选择输入触发源TRGI。这个函数就对应这里,从模式的触发源选择:

调用这个函数就能选择从模式的触发源了。我们本节要用的TI1FP1.

TIM_SelectOutputTrigger

选择输出触发源TRGO 。对应这里,选择主模式输出的触发源

TIM_SelectSlaveMode

选择从模式。这个函数对应这里的从模式选择的部分。

单独配置四个通道的分频器的函数

然后这四个函数分别单独配置通道一二三四的分频器

这个参数结构体里也可以配置,是一样的效果。

最后这四函数分别读取四个通道的CCR。

这四个函数和这四个函数是对应的

读写的都是CCR寄存器。

输出比较模式下,CCR是只写的,要用TIM_SetCompare1写入。

输入捕获模式下,CCR是只读的,要用TIM_GetCapture1读出

函数介绍就完成了,接下来我们就来开始写程序。

代码实现

前三步开启时钟、配置GPIO、配置时基单元直接复制之前的然后修改一下。

第一步,RCC开启时钟

这里开启的时钟是TIM2,我们这个代码还需要TIM2输出PWM。所以输入捕获的定时器要换一个,我们就换到TIM3。TIM3也是APB1的外设,所以函数还是APB1。

开启GPIO的时钟,这个就要查一下引脚定义表:

可以看到TIM3的通道1和通道2对应PA6和PA7。通道3和通道4对应PB0和PB1,所以引脚需要根据实际需求来,我们本次代码计划用TIM3的通道1引脚,所以引脚就是PA6。

如果你选择其他通道或者其他定时器,那这个引脚就需要变了。

我们计划用PA6的通道1,所以开启GPIOA的时钟。

第二步,GPIO初始化

然后初始化GPIO的PA6口,模式是上拉输入,那这样GPIO配置就完成了。

第三步,配置时基单元

接下来就是时基单元,选择内部时钟,定时器要换成TIM3。

然后是时基单元的参数,ARR自动重装值,根据上一节的分析,我们最好要设置大一些,防止计数溢出。这里我们就给最大值65536-1,也就是十六位的计数,可以满量程计数。

之后是PSC预分频器根据上节的分析这个值决定了测周法的标准频率fc72M/预分频PSC+1就是计数器自的频率就是计数标准频率标准频率不用输出,不需要ARR的处理这个需要根据你信号频率的分布范围来调整,我们暂时先给72-1,这样标准频率就是72M/72=1MHz,比较方便计算。

然后计数器还是采用向上计数的模式。

下面时基单元初始化改成TIM3,把这些参数配置到TIM3的时基单元,这样时基单元就配置好了。

第四步,配置输入捕获单元

我们进入第四步,输入捕获单元的初始化。我们要用这个函数初始化

第一个参数给TIM3,第二个参数是结构体,这些是这个结构体的成员。

第一个结构体成员TIM_Channel就是我们刚才说的选择通道的那个参数。因为ICInit的函数只有一个,所以要靠结构体的这个参数来指定是配置哪个通道。

需要配置哪个通道就选哪个参数。目前我们计划使用的是TIM3的通道1,所以选择第三个。

第二个成员TIM_ICFilter用来选择输入捕获的滤波器。

如果你信号有毛刺和噪声,就可以增大滤波器参数,可以有效避免干扰。

这个成员的取值可以是0x0到0xF之间的一个数。数越大,滤波效果越好,每个数值对应的采样频率和采样次数在参考手册里有。

这里我们就给0xF。

注意滤波器和分频器的区别。

虽然它俩都是计次的东西,但是滤波器计次并不会改变信号的原有频率。一般滤波器的采样频率都会远高于信号频率,所以它只会滤除高频噪声,使信号更平缓。1KHz滤波之后仍然是1KHz,信号频率不会变化。

而分频器就是对信号本身进行计次,来会改变频率,1KHz二分频之后就是500Hz,四分频就是250Hz。

下一个参数TIM_ICPolarity极性,这个对应的就是图里的这个边沿检测极性选择的部分

选择是上升沿触发还是下降沿触发

这里我们需要上升沿触发,所以复制第一个。

接着TIM_ICPrescaler分频器。

不分频就是每次触发都有效,二分频就是每隔一次有效一次,以此类推。

div1就是不分频,div2二分频。这个分频值并不能任意指定,只能选择这四种。我们目前需要每次触发都有效,所以选择div1不分频。

最后一个参数TIM_ICSelection选择触发信号

显然这个参数是配置这个数据选择器的,可以选择直连通道,或者是交叉通道。

对于这个代码来说,我们需要选择直连通的。

我们输入捕获的通道就配置完了。

也就是这部分配置完了

第五步,选择从模式的触发源

然后我们把主从模式的这两个东西配置好。

先是触发源选择,用这个这个函数

它有八个可选的触发源:

这八个触发源就对应这八个

具体的解释可以看看手册。

那我们要选的是TI1FP1

这样触发源就选择好了。

第六步,选择触发之后执行的操作

接下来是配置从模式为reset,需要这个函数

第一个给参数TIM3,第二个参数这里给出了这四种从模式对应这里下面四个从模式

上面三个encode从模式是给编码器接口用的,还会另外有函数进行配置。

在这里我们需要选择reset这个参数

这样从模式就配置好了。

最后一步,我们就要用TIM_cmd,参数给TIM3,enable启动定时器。

这样整个电路的配置就完成了。

第七步,调用TIM_Cmd函数开启定时器。

当我们启动定时器之后,CNT就会在内部时钟的驱动下不断自增。即使信号没有过来,它也会不断自增。不过这也没关系。因为有信号来的时候,它就会在从模式的作用下自动清零,并不会影响测量。

初始化之后,整个电路就能全自动测量了。

当我们想要查看频率时,需要读取CCR进行计算。

所以我们在下面再写个函数获取输入捕获的频率。在里面我们需要执行这个公式

fc=72M/PSC+1,上面说过PSC目前是72-1,所以fc等于1MHz值。N就是读取的CCR的值,需要到tim.h文件里找一下这个函数来读取CCR的值。

里面如果直接写return 1000000 / (TIM_GetCapture1(TIM3) ;那么最终我们在OLED上看到的频率值将是1001

这可能是由于计数刚到1KHz的那个数值,信号也刚好跳变,因为电路结构或者其他什么原因导致这一个数刚好没计到会有一点误差,不过也属于正负一误差的范畴,目前这个误差也是符合要求的。但是基于完美主义,给计次值加1,给它补一个数。return 1000000 / (TIM_GetCapture1(TIM3) + 1);

目前我们这个函数返回的是最新一个周期的频率值,单位是Hz。

IC.c

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:输入捕获初始化
  * 参    数:无
  * 返 回 值:无
  */
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,定时器开始运行
}

/**
  * 函    数:获取输入捕获的频率
  * 参    数:无
  * 返 回 值:捕获得到的频率
  */
uint32_t IC_GetFreq(void)
{
	return 1000000 / (TIM_GetCapture1(TIM3) + 1);		//测周法得到频率fx = fc / N,这里不执行+1的操作也可
	//N就是读取的CCR的值
}

IC.h

#ifndef __IC_H
#define __IC_H

void IC_Init(void);
uint32_t IC_GetFreq(void);

#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	PWM_Init();			//PWM初始化
	IC_Init();			//输入捕获初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Freq:00000Hz");		//1行1列显示字符串Freq:00000Hz
	
	/*使用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);	//不断刷新显示输入捕获测得的频率
	}
}

这样现在测量1KHz

第二个代码:PWMI模式测频率占空比

接线图:

和上一个代码的接线图是一样的。

复制上一个工程并改名

接下来我们就按照这个结构图来配置

IC.c

开启时钟、GPIO和时基单元都不需要更改。

然后输入捕获初始化的部分,需要进行一下升级。配置成两个通道同时捕获同一个引脚的模式。

配置PWMI输入捕获单元

到TIM.h里找一下这个函数,就能完整PWMI输入捕获单元的配置

第一个参数TIM3,第二个参数取地址,把结构体变量放过来。

使用这个函数,只需要传入一个通道的参数就行了,在这个函数里会自动把剩下的一个通道初始化成相反的配置

比如这里传入通道1,直连,上升沿,那函数里面就会顺带配置通道2,交叉,下降沿。

如果传入通道2,直连,上升沿,函数就会顺带配置通道1,交叉,下降沿。

这个函数只支持通道1和通道2的配置不要传入通道3和通道4

执行完这个函数,我们的PWMI电路就配置好了。

接着配置主从模式,启动定时器,这些都不需要更改。

最后获取频率的函数不用改我们再写一个获取空比的函数

上节的分析高电平的计数值存在CCR2里,整个周期的计数值存在CCR1里,CCR2除CCR1就能得到占空比了。

这个数的范围是0到1,我们要显示整数的话,可以乘个100扩大一百倍。这样返回值的范围就是0到100,对应占空比0%~100%。

另外还有个问题,经过实测这个CCR总会少一个数,所以给它各加一个数补回来,看着舒服些。

IC.c

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:输入捕获初始化
  * 参    数:无
  * 返 回 值:无
  */
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的时基单元
	
	/*PWMI模式初始化*/
	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_PWMIConfig(TIM3, &TIM_ICInitStructure);						//将结构体变量交给TIM_PWMIConfig,配置TIM3的输入捕获通道
																	//此函数同时会把另一个通道配置为相反的配置,实现PWMI模式

	/*选择触发源及从模式*/
	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);					//触发源选择TI1FP1
	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);					//从模式选择复位
																	//即TI1产生上升沿时,会触发CNT归零
	
	/*TIM使能*/
	TIM_Cmd(TIM3, ENABLE);			//使能TIM3,定时器开始运行
}

/**
  * 函    数:获取输入捕获的频率
  * 参    数:无
  * 返 回 值:捕获得到的频率
  */
uint32_t IC_GetFreq(void)
{
	return 1000000 / (TIM_GetCapture1(TIM3) + 1);		//测周法得到频率fx = fc / N,这里不执行+1的操作也可
}

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

IC.h

#ifndef __IC_H
#define __IC_H

void IC_Init(void);
uint32_t IC_GetFreq(void);
uint32_t IC_GetDuty(void);

#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"

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);	//不断刷新显示输入捕获测得的占空比
	}
}

运行结果:

测频率的性能

我们第二个代码就写完了,最后我们来研究一下这个测频率的性能。

首先是测频率的范围,目前我们给的标准频率是1MHz计数器,最大只能计到65535,所测量的最低频率是1M/65535,这个值算一下,大概是15Hz。如果信号频率再低,计数器就要溢出了。

所以最低频率就是15Hz左右。

那如果想再降低一些最低频率的限制我们可以把这个预分频再加大点。这样标准频率就更低,所支持测量的最低频率也就更低。

这是测量频率的下限。

然后是测量频率的上限。这个最大频率并没有一个明显的界限,因为随着待测频率的增大,误差也会逐渐增大。如果非要找一个频率上限,那应该就是标准频率1MHz。

超过1MHz信号频率比标准频率还高,那肯定测不了。但是这个1MHz的上限并没有意义,因为信号频率接近1MHz时误差已经非常大了,所以最大频率要看你对误差的要求

上一节我们说到了正负一误差,计一百个数,误差一个,相对误差就是百分之一,计一千个数,误差一个相对误差就是千分之一,所以正负一误差可以认为是1/计数值

如果要求误差等于千分之一时,频率为上限,那这个上限就是1M/1000=1KHz。

如果要求误差可以到百分之一,那频率上限就是1M/100=10KHz。

这就是频率的上限。

如果想提高频率的上限那我们在这里就要把PSC给降低一些。提高标准频率上限就会提高。除此之外,如果频率还要更高,那我们就要考虑一下测频法了。测频法适合高频,测周法适合低频。

我们这里是测周法,所以对于非常高的频率,还是交给测频法来解决。

然后,关于误差分析,除了我们之前说的正负一误差外,在实际测量的时候还会有晶振误差。比如我们STIM32的晶振不是那么准,在计次几百几万字之后,误差积累起来也会造成一些影响。数值可能就会有些抖动,后期可以再做一些滤波处理。

输入捕获的代码部分就写完了,下节继续。

QQ交流群:963138186

本篇就到这里,下篇继续!欢迎点击下方订阅本专栏↓↓↓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1870053.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

React Antd ProTable 如何设置类似于Excel的筛选框

React Antd ProTable 如何设置类似于Excel的筛选框 目标:在web页面的table表格中完成类似于EXCEL的Filter筛选功能。 示例图:点击标题列上方的漏斗状图标,即可对数据进行筛选。 ProTable 前景提要 ProTable API中有说明,是有…

一次关于k8s的node节点NotReady的故障排查

master现象 分析 kubectl get nodes -A 看了下pod的状态,好多CrashLoopBackOff kubectl get nodes -o wide 定位到那个具体node的IP地址,登录对应的IP去查看为什么会这样 node节点 journalctl -xe -f -u kubelet 查看此节点的 kubelet 服务&#xff…

Termius:现代化的SSH客户端,让服务器管理变得优雅简洁

Termius简介 是一款现代化的跨平台终端模拟器和SSH客户端。以下是对Terminus的介绍以及使用它的理由: 跨平台兼容性: Terminus支持Windows、macOS、Linux、IOS和Android,让用户在不同操作系统间保持一致的终端体验。优雅的用户界面&#xf…

何用Vue3和Plotly.js打造交互式3D图

本文由ScriptEcho平台提供技术支持 项目地址:传送门 利用 Plotly.js 创建交互式动画图表 应用场景 本代码适用于需要创建交互式动画图表的数据可视化项目。例如,可以用来展示时间序列数据或比较不同函数的行为。 基本功能 该代码使用 Plotly.js 库…

vite 创建vue3项目 集成 ESLint、Prettier、Sass等

在网上找了一大堆vue3脚手架的东西,无非就是vite或者vue-cli,在vue2时代,vue-cli用的人挺多的,也很好用,然而vue3大多是和vite搭配搭建的,而且个人感觉vite这个脚手架并没有那么的好用,搭建项目时只能做两个…

水上实用救生工具_救生拉杆_鼎跃安全

每年,由于水上事故而失去生命的人数不胜数,水上安全问题也成为公众关注的焦点。如何在关键时刻实施有效的救援,成为保障生命的重要课题。作为水上救援的重要工具,救生拉杆在紧急情况下发挥了无可替代的作用。 救生拉杆&#xff0c…

20210801pointer2

//#include<stdio.h> // //int main() //{ // float values[N_VALLUES]; // float *vp; // // 1.优先(priority) // for (vp &values[ N_VALLUES/* 5 */ ] /*第六个元素*/; vp > &values[0]/*指针是可以比较大小的*/ ; /*省略了*/ ) //…

【绝对有用】yolo系列目标检测 核心技术点 汇总

YOLO (You Only Look Once) 是一种高效的目标检测算法&#xff0c;它以速度和精度著称。YOLO 的工作原理是将目标检测视为一个回归问题&#xff0c;直接从图像的像素空间预测目标的类别和位置。YOLO 目标检测头包括以下几个关键部分&#xff1a; 输入图像处理&#xff1a; YOLO…

检信智能推出我国首款Allemotion OS基于AI生理心理参数服务开发者平台

检信Allemotion OS生理心理开发者平台是根据世界人工智能高速发展的特点,为实现脑机交互的行业需求&#xff0c;由检信智能推出我国首款检信Allemotion OS生理心理开发者平台。检信Allemotion OS生理心理开发者平台集成了振动影像心理情绪20项情绪参数、11项生理相关参数&#…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] LYA的登山之旅01(100分)- 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

【MySQL备份】lvm-snapshot篇

目录 1.简介 1.1.如何工作 1.2.应用场景 1.3.注意事项 1.4.优缺点 2.为什么选择lvm快照备份&#xff1f; 3.创建LVM 3.1.操作流程 3.2.正常安装MySQL后进行备份 3.3.MySQL运行一段时间后进行备份 3.3.1.准备lvm及文件系统//先添加一块磁盘 3.3.2.将数据迁移到LVM …

python API自动化(基于Flask搭建MockServer)

接口Mock的理念与实战场景: 什么是Mock: 在接口中&#xff0c;"mock"通常是指创建一个模拟对象来代替实际的依赖项&#xff0c;以便进行单元测试。当一个类或方法依赖于其他类或组件时&#xff0c;为了测试这个类或方法的功能&#xff0c;我们可以使用模拟对象来替代…

java基于ssm+jsp 大学生校园兼职系统

1前台首页功能模块 大学生校园兼职系统&#xff0c;在大学生校园兼职系统可以查看首页、企业信息、招聘信息、论坛信息、留言反馈、我的、跳转到后台等内容&#xff0c;如图1所示。 图1系统首页界面图 学生登录&#xff0c;通过学生登录填写账号、密码等信息进行登录操作&…

鸿蒙期末项目(完结)

两天仅睡3个小时的努力奋斗之下&#xff0c;终于写完了这个无比拉跨的项目&#xff0c;最后一篇博客总体展示一下本项目运行效果兼测试&#xff0c;随后就是答辩被同学乱沙&#xff08;悲 刚打开软件&#xff0c;会看到如下欢迎界面&#xff0c;介绍本app的功能和优点 随后我们…

服务器部署—虚拟机安装nginx并部署web网页

该篇博客用于讲解Linux的Centos7发行版中如何通过Linux安装Nginx&#xff0c;然后将静态页面部署到Nginx中&#xff0c;通过浏览器访问。 非常适用于新手小白学习项目部署相关的知识。建议收藏&#xff01;&#xff01;&#xff01; 需要大家提前准备好虚拟机和CentOS7操作系统…

智慧公厕系统在办公楼卫生管理中的作用,高效、便捷、智能

在现代化的办公楼中&#xff0c;卫生管理是营造舒适、高效工作环境的重要环节。而智慧公厕系统的引入&#xff0c;正以其高效、便捷、智能的特点&#xff0c;为办公楼的卫生管理带来了革命性的变革。 一、智慧公厕系统首先展现出了令人瞩目的高效性。 传统的公厕管理往往依赖人…

2024年6月17日~2024年6月26日周报

一、前言 在上周主要完成了可变形卷积的学习的部署。 本周&#xff0c;结合前段时间的工作与闵老师的讨论&#xff0c;思考了接下来的一些尝试方向。本周重新在之前的网络上尝试添加可变形卷积v4&#xff0c;或者将可变形卷积v2修改为可变形卷积v4。另外&#xff0c;继续学习了…

LCR 068. 搜索插入位置

给定一个排序的整数数组 nums 和一个整数目标值 target &#xff0c;请在数组中找到 target &#xff0c;并返回其下标。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 思路&#xff1a; 常规的二分查找&am…

充电桩小程序:引领未来,携手共创绿色充电新纪元

着新能源汽车市场的迅猛增长&#xff0c;充电桩行业正迎来前所未有的发展机遇。然而&#xff0c;在这个充满竞争和机遇并存的时代&#xff0c;如何快速、高效地满足用户需求&#xff0c;成为充电桩行业老板们关注的焦点。为此&#xff0c;我们推出了全新的充电桩小程序&#xf…

记录跨度3年的SqlServer数据同步项目分析

目录 技术选型决策阶段 发布订阅 自定义开发 Datax Datax废除主外键关系和自增ID ER模型分组 废掉库表主外键 维度划分Datax任务 基于ID同步 基于TIME时间同步 基于全表ALL同步 废掉自增ID DataX废除主外键关系手动拷贝 手动拷贝 Datax任务分组触发器 Datax全表…