目录
第一个代码:输入捕获模式测频率
调整频率
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调节频率不会影响占空比,显然比较方便。
所以我们的计划是固定ARR为100-1,通过调节PSC来改变PWM频率。
另外ARR为100-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预分频器,根据上节的分析,这个值决定了测周法的标准频率fc,72M/预分频(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
本篇就到这里,下篇继续!欢迎点击下方订阅本专栏↓↓↓