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

目录

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

调整频率

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模式1TIM_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_Hvoid 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;				//选择配置定时器通道1TIM_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);					//触发源选择TI1FP1TIM_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_Hvoid 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) / 100PWM_SetCompare1(50);						//PWM占空比Duty = CCR / 100while (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;				//选择配置定时器通道1TIM_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);					//触发源选择TI1FP1TIM_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_Hvoid 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:00000HzOLED_ShowString(2, 1, "Duty:00%");			//2行1列显示字符串Duty:00%/*使用PWM模块提供输入捕获的测试信号*/PWM_SetPrescaler(720 - 1);					//PWM频率Freq = 72M / (PSC + 1) / 100PWM_SetCompare1(50);						//PWM占空比Duty = CCR / 100while (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.mzph.cn/pingmian/36548.shtml

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

相关文章

聊一聊UDF/UDTF/UDAF是什么,开发要点及如何使用?

背景介绍 UDF来源于Hive,Hive可以允许用户编写自己定义的函数UDF,然后在查询中进行使用。星环Inceptor中的UDF开发规范与Hive相同,目前有3种UDF: A. UDF--以单个数据行为参数,输出单个数据行; UDF&#…

打破生态「孤岛」,Catizen将开启Telegram小游戏2.0时代?

Catizen:引领Telegram x TON生态的顶级猫咪链游 在区块链游戏领域,吸引玩家的首要因素往往是游戏的趣味性。然而,仅靠趣味性无法评估一个项目的长期价值和发展潜力。真正能在区块链游戏市场中取得长久成功的项目,无一例外都依靠扎…

【消息队列】RabbitMQ集群原理与搭建

目录 前言1、集群搭建1.1、安装RabbitMQ1.1.1、前置要求1.1.2、安装Erlang环境①创建yum库配置文件②加入配置内容③更新yum库④正式安装Erlang 1.1.3、安装RabbitMQ1.1.4、RabbitMQ基础配置1.1.5、收尾工作 1.2、克隆VMWare虚拟机1.2.1、目标1.2.2、克隆虚拟机1.2.3、给新机设…

智能充电桩网关,构建高效充电网络

近年来我国新能源汽车的增长速度出现明显的上升趋势,但是其充电桩的发展还比较缓慢。目前在充电桩系统设计期间仍存在一些问题,主要表现在充电设施短缺、充电难等问题,这些问题的发生均会在一定程度上限制新能源汽车的发展,这就需…

navicat Premium发布lite免费版本了

Navicat Premium发布lite免费版本了,下面是完整功能对比链接 Navicat Premium 功能列表 | Navicat 免费版本下载链接如下: Navicat | 免费下载 Navicat Premium Lite 开发功能完全够用,点赞。 dbeaver该如何应对。

振弦采集仪在大型工程安全监测中的应用探索

振弦采集仪在大型工程安全监测中的应用探索 振弦采集仪是一种用于监测结构振动和变形的设备,它通过采集振弦信号来分析结构的动态特性。在大型工程安全监测中,振弦采集仪具有重要的应用价值,可以帮助工程师和监测人员实时了解结构的状况&…

如何在线上快速定位bug(干货)

想必有许多人都想我刚进公司一样不会快速定位线上bug吧,不会快速定位bug会大大降低我们的开发效率,随之而来的就是工作质量下降、业绩下滑。 我总结了一些我常用的线上定位技巧,希望能帮助到大家! 我这里以使用阿里云日志分析作…

Attention步骤

一个典型的Attention思想包括三部分:Qquery、Kkey、Vvalue。 Q是query,是输入的信息;key和value成组出现,通常是原始文本等已有的信息;通过计算Q与K之间的相关性a,得出不同的K对输出的重要程度;…

2021年12月电子学会青少年软件编程 中小学生Python编程等级考试三级真题解析(选择题)

2021年12月Python编程等级考试三级真题解析 选择题(共25题,每题2分,共50分) 1、小明在学习计算机时,学习到了一个十六进制数101,这个十六进制数对应的十进制数的数值是 A、65 B、66 C、256 D、257 答案&#xff…

为什么javaer认为后台系统一定要用java开发?

在开始前刚好我有一些资料,是我根据网友给的问题精心整理了一份「java的资料从专业入门到高级教程」, 点个关注在评论区回复“666”之后私信回复“666”,全部无偿共享给大家!!!公司有两个开发团队&#xf…

4年突破20亿,今麦郎如何持续策划凉白开极致产品力?

范总在方便面市场拥有30年的丰富经验,并曾创造过奇迹。1994年,他从冰糖生意进入方便面行业,创立今麦郎的前身华龙集团。当时,方便面市场已经进入红海阶段,市场上有上千家企业,康师傅和统一占据了80%的市场份…

计算机视觉-期末复习-简答/名词解释/综合设计

目录 第一讲--计算机/机器视觉概述 名词解释 简答 第二讲--图像处理概述 名词解释 简答 第三讲没划重点习题 第四讲--特征提取与选择 名词解释 简答 综合题 第五讲--不变特征 名词解释 简答 第六讲--物体分类与检测 简答 综合题 第七讲--视觉注意机制 简答 …

三角洲行动卡顿严重?这样快速解决三角洲行动国服卡顿问题

三角洲行动官方精心设计的游戏地图和敌人布局,加上“曼德尔砖”等目标导向性道具的引入,更是为玩家之间的竞技和争夺增添了无数的变数。每一次的争夺都如同是一场智慧与勇气的较量,让人热血沸腾,无法自拔。在这个战场上&#xff0…

第六篇:精通Docker Compose:打造高效的多容器应用环境

精通Docker Compose:打造高效的多容器应用环境 1. 引言 1.1 目的与重要性 在现代软件开发中,随着应用程序的复杂性不断增加,传统的单一容器部署方式已无法满足需求。Docker Compose作为一种强大的工具,专门用于定义和运行多容器…

用户中心项目全流程

企业做项目流程 需求分析 > 设计(概要设计 、 详细设计) > 技术选型 >初始化项目 / 引入需要的技术 > 写个小demo > 写代码 (实现业务逻辑) > 测试(单元测试)> 代码提交 / 代码评审 …

ClickHouse-Keeper安装使用

1.rpm 安装 clickhouse-keeper rpm -ivh clickhouse-keeper-23.8.11.28.x86_64.rpm 2.修改keeper的配置文件 vi /etc/clickhouse-keeper/keeper_config.xml修改部分参数 1.可修改日志等存储路径 2.增加监听配置 <listen_host>0.0.0.0</listen_host> 3.server_id…

HarmonyOS Next开发学习手册——层叠布局 (Stack)

概述 层叠布局&#xff08;StackLayout&#xff09;用于在屏幕上预留一块区域来显示组件中的元素&#xff0c;提供元素可以重叠的布局。层叠布局通过 Stack 容器组件实现位置的固定定位与层叠&#xff0c;容器中的子元素依次入栈&#xff0c;后一个子元素覆盖前一个子元素&…

【Spring】SpringCloudAlibaba学习笔记

Nacos Nacos是一个更易于构建云原生应用的动态服务发现/服务配置和服务管理平台核心功能: 服务注册: Nacos Client会通过发送REST请求向Nacos Server注册自己的服务, 提供自己的元数据, 如ip地址/端口等信息; Nacos Server收到注册请求后, 就会把这些信息存储在Map中服务心跳:…

Java毕业设计 基于SSM vue药店管理系统小程序 微信小程序

Java毕业设计 基于SSM vue药店管理系统小程序 微信小程序 SSM 药店管理系统小程序 功能介绍 用户 登录 注册 首页 药品信息 药品详情 加入购物车 立即购买 收藏 购物车 立即下单 新增收货地址 我的收藏管理 用户充值 我的订单 留言板 管理员 登录 个人中心 修改密码 个人信息…

分布式并行最短路径

此前我 “自然而然” 做了两个小算法&#xff0c;最短路径 和 最小生成树&#xff0c;我喜欢大自然的第一性原理&#xff0c;最小作用量&#xff0c;梯度下降&#xff0c;爆炸&#xff0c;河水泛滥&#xff0c;本质上都是一回事。 大自然另一风格是分布式并行&#xff0c;没外…