输入捕获测频率
输入捕获测频率接线图

测量信号的输入信号是PA6,信号从PA6进入。
待测的PWM信号也是由STM32自己生成的,输出引脚是PA0。
所以接线这里,直接用一根线把PA0引到PA6就行了。
输入捕获测频率代码
输出PWM代码
PWM的频率和占空比都是可调的,我们知道占空比 = CCR/ARR,如果后续想要调节占空比,改变CCR或者ARR的值都是可以的,但是我们的ARR的值一般是1000-1或者100-1,这样只需要改变捕获比较寄存器CCR的值,就可以规律的改变占空比。
另外,我们也知道 PWM频率 = 时钟信号 / (PSC+1) / (ARR+1) ,既然我们需要规律可调的占空比,那么ARR的值是固定的,想要修改频率,则需要改变PSC的值,达到我们想要的频率。
所以我们固定ARR为100-1,通过调节PSC来改变PWM频率。另外ARR为100-1,CCR的数值直接就是占空比,用起来也比较直观。
通过之前的学习,我们也知道单独写入PSC的库函数是TIM_PrescalerConfig(),写入PSC。
所以输出PWM波的代码是这样的:
void PWM_Init()
{
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//选择时基单元时钟
TIM_InternalClockConfig(TIM2);
//配置时基单元,使得计数器溢出频率为1KHz,计数器的溢出频率 = PWM的频率 =1KHz
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruture;
TIM_TimeBaseStruture.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseStruture.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseStruture.TIM_Period=100-1;
TIM_TimeBaseStruture.TIM_Prescaler=720-1;//720-1
TIM_TimeBaseStruture.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStruture);
//初始化输出比较单元
TIM_OCInitTypeDef TIM_OCInitStruture;
TIM_OCInitStruture.TIM_OCMode=TIM_OCMode_PWM1;
TIM_OCInitStruture.TIM_OCPolarity=TIM_OCPolarity_High;
TIM_OCInitStruture.TIM_OutputState=TIM_OutputState_Enable;
TIM_OCInitStruture.TIM_Pulse=0;
TIM_OC1Init(TIM2,&TIM_OCInitStruture);
/*现在我们已经把通道初始化好了,在TIM2的OC1通道上就可以输出PWM波形了。*/
/*但最终,这个波形肯定要借用一下GPIO口才能输出,所以还需要配置一下GPIO*/
//开启时钟,配置GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStruture;
GPIO_InitStruture.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出
GPIO_InitStruture.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStruture.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruture);
//启动定时器
TIM_Cmd(TIM2,ENABLE);
}
//改变通道1的占空比
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2,Compare);
}
//改变通道1的频率
void PWM_SetPrescaler(uint16_t Prescaler)
{
TIM_PrescalerConfig(TIM2,Prescaler,TIM_PSCReloadMode_Immediate);
}
通过这三个函数,可以使得PA0引脚输出PWM,并且可以改变输出PWM的频率与占空比。
测量PWM代码

第一步,RCC开启时钟,把GPIO和TIM的时钟打开。
第二步,GPIO初始化,把GPIO配置成输入模式(一般选择上拉输入或者浮空输入模式)。
第三步,配置时基单元,让CNT计数器在内部时钟的驱动下自增运行。
第四步,配置输入捕获单元(包括滤波器、极性、直连通道还是交叉通道、分频器,结构体可以统一配置)。
第五步,选择从模式的触发源,触发源选择为TI1FP1。(调用一个库函数即可)
第六步,选择触发之后执行的操作。(执行Reset操作,调用一个库函数即可)
第七步,调用TIM_Cmd()函数,开启定时器。
这样就完成了,当我们需要读取最新一个周期的频率时,直接读取CCR寄存器,然后按照fc/N计算一下就行了。
接下来我们来学习一下这里需要用到的库函数。
TIM_ICinit()函数。
第一个参数是选择定时器,第二个参数就是包含各个配置的结构体。
另外注意,输入捕获和输出比较都有4个通道,OCinit是四个函数配置(配置输出比较模块),一个通道有一个函数,而ICInit是4个通道共用一个函数的,在结构体里会额外有个参数,可以用来选择具体是配置哪个通道。因为可能有交叉通道的配置,所以函数合在一起比较方便。
TIM_PWMIConfig()函数。
这个和TIM_ICinit()函数类似,都是用于初始化输入捕获单元的,但是上一个函数只是单一地配置一个通道,而这个函数可以快速配置两个通道,把外设电路结构配置成PWMI模式。
TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct);
TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);选择输入触发源TRGI

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

TIM_SelectOnePulseMode(TIM_TypeDef* TIMx, uint16_t TIM_OPMode);选择输出触发源TRGO。

TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode);
void TIM_SetIC1Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC2Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC3Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC4Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
这个参数结构体也可以配置,是一样的效果。
uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx);
这4个函数和SetCompare1、2、3、4是对应的,读写的都是CCR寄存器。
输出比较模式下,CCR是只写的,要用SetCompare写入。
输入捕获模式下,CCR是只读的,要用GetCapture读取。
现在思路有了,库函数也有了,可以开始写程序了。
void IC_Init()
{
//开启时钟,我们使用TIM3来捕获PWM波。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//配置GPIO,PA6改成上拉输入。
GPIO_InitTypeDef GPIO_InitStruture;
GPIO_InitStruture.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStruture.GPIO_Pin=GPIO_Pin_6;
GPIO_Init(GPIOA,&GPIO_InitStruture);
//选择时基单元时钟
TIM_InternalClockConfig(TIM3);
//配置时基单元,使得计数器溢出频率为1KHz,计数器的溢出频率 = PWM的频率 =1KHz
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruture;
TIM_TimeBaseStruture.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseStruture.TIM_CounterMode=TIM_CounterMode_Up;
//ARR的值最好设置的大一些,防止计数溢出,这里给最大值65536-1,满量程计数。
TIM_TimeBaseStruture.TIM_Period=65536 -1;
//这里的PSC决定了测周法的标准频率fc。我们给的标准频率是1MHz。
TIM_TimeBaseStruture.TIM_Prescaler=72-1;
TIM_TimeBaseStruture.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStruture);
//初始化输入捕获单元。
TIM_ICInitTypeDef TIM_ICInitStruture;
//选择通道,选择TIM3的通道1.
TIM_ICInitStruture.TIM_Channel=TIM_Channel_1;
//配置输入捕获的滤波器,范围是0X0~0XF。越大滤波效果越好。
TIM_ICInitStruture.TIM_ICFilter=0XF;
//边沿检测、极性选择。我们选择上升沿触发。
TIM_ICInitStruture.TIM_ICPolarity=TIM_ICPolarity_Rising;
//我们需要每次触发都有效,所以分频器选择不分频。
TIM_ICInitStruture.TIM_ICPrescaler=TIM_ICPSC_DIV1;
//选择触发信号从哪个引脚输入,可以选择直连通道或者是交叉通道。
//我们这里选择直连通道。
TIM_ICInitStruture.TIM_ICSelection=TIM_ICSelection_DirectTI;
TIM_ICInit(TIM3,&TIM_ICInitStruture);
//配置TRGI的触发源为TI1FP1。
TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1);
//配置从模式为Reset。
TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);
//使能定时器
TIM_Cmd(TIM3,ENABLE);
//当我们启动定时器后,CNT就会在内部不断自增,即使信号没有过来,他也会不断自增。
//CNT有信号来的时候,他就会在从模式的作用下自动清零。
}
//当我们想要查看频率,需要读取CCR进行计算。
uint32_t IC_GetFreq()
{
return 1000000 / TIM_GetCapture1(TIM3);
}
当我们实际测量的时候,测量1000Hz频率的PWM时,显示的是1001Hz,可能是计数器刚到1000Hz的那个数时,信号也发刚好跳变,因为电路结构或者其他什么原因,导致这一个数刚好没有计到。不过这个也属于正负1误差的范畴,目前这个误差也是符合要求的。如果觉得别扭,可以在TIM_GetCapture1(TIM3)的基础上加1,这样频率就正好是1000Hz了。
PWMI测频率占空比代码

就是通道1、2都进行一下配置,通道1上升沿触发,把整个周期的计数CNT返回给CCR1,通道2下降沿触发,把高电平的CNT计数返回给CCR2,这样CCR2/CCR2就是占空比了。
void IC_Init()
{
//开启时钟,我们使用TIM3来捕获PWM波。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//配置GPIO,PA6改成上拉输入。
GPIO_InitTypeDef GPIO_InitStruture;
GPIO_InitStruture.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStruture.GPIO_Pin=GPIO_Pin_6;
GPIO_Init(GPIOA,&GPIO_InitStruture);
//选择时基单元时钟
TIM_InternalClockConfig(TIM3);
//配置时基单元,使得计数器溢出频率为1KHz,计数器的溢出频率 = PWM的频率 =1KHz
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruture;
TIM_TimeBaseStruture.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseStruture.TIM_CounterMode=TIM_CounterMode_Up;
//ARR的值最好设置的大一些,防止计数溢出,这里给最大值65536-1,满量程计数。
TIM_TimeBaseStruture.TIM_Period=65536 -1;
//这里的PSC决定了测周法的标准频率fc。我们给的标准频率是1MHz。
TIM_TimeBaseStruture.TIM_Prescaler=72-1;
TIM_TimeBaseStruture.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStruture);
//初始化输入捕获单元。
TIM_ICInitTypeDef TIM_ICInitStruture;
//选择通道,选择TIM3的通道1.
TIM_ICInitStruture.TIM_Channel=TIM_Channel_1;
//配置输入捕获的滤波器,范围是0X0~0XF。越大滤波效果越好。
TIM_ICInitStruture.TIM_ICFilter=0XF;
//边沿检测、极性选择。我们选择上升沿触发。
TIM_ICInitStruture.TIM_ICPolarity=TIM_ICPolarity_Rising;
//我们需要每次触发都有效,所以分频器选择不分频。
TIM_ICInitStruture.TIM_ICPrescaler=TIM_ICPSC_DIV1;
//选择触发信号从哪个引脚输入,可以选择直连通道或者是交叉通道。
//我们这里选择直连通道。
TIM_ICInitStruture.TIM_ICSelection=TIM_ICSelection_DirectTI;
TIM_ICInit(TIM3,&TIM_ICInitStruture);
//选择通道,选择TIM3的通道2.
TIM_ICInitStruture.TIM_Channel=TIM_Channel_2;
//配置输入捕获的滤波器,范围是0X0~0XF。越大滤波效果越好。
TIM_ICInitStruture.TIM_ICFilter=0XF;
//边沿检测、极性选择。我们选择下升沿触发。
TIM_ICInitStruture.TIM_ICPolarity=TIM_ICPolarity_Falling;
//我们需要每次触发都有效,所以分频器选择不分频。
TIM_ICInitStruture.TIM_ICPrescaler=TIM_ICPSC_DIV1;
//选择触发信号从哪个引脚输入,可以选择直连通道或者是交叉通道。
//我们这里选择交叉通道。
TIM_ICInitStruture.TIM_ICSelection=TIM_ICSelection_IndirectTI;
TIM_ICInit(TIM3,&TIM_ICInitStruture);
//配置TRGI的触发源为TI1FP1。
TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1);
//配置从模式为Reset。
TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);
//使能定时器
TIM_Cmd(TIM3,ENABLE);
//当我们启动定时器后,CNT就会在内部不断自增,即使信号没有过来,他也会不断自增。
//CNT有信号来的时候,他就会在从模式的作用下自动清零。
}
没错,ST公司专门为PWMI模式封装了一个函数,就是我们之前在初始化输入捕获单元库函数说过的TIM_PWMIConfig()。
这样只需要配置一个通道,另一个通道自动会配置成相反的选项,这样便捷了代码(其实就是一堆if语句选择)。
比如传入的是通道1、直连、上升沿;那函数里面会顺带配置通道、交叉、下降沿。
void IC_Init()
{
//开启时钟,我们使用TIM3来捕获PWM波。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//配置GPIO,PA6改成上拉输入。
GPIO_InitTypeDef GPIO_InitStruture;
GPIO_InitStruture.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStruture.GPIO_Pin=GPIO_Pin_6;
GPIO_Init(GPIOA,&GPIO_InitStruture);
//选择时基单元时钟
TIM_InternalClockConfig(TIM3);
//配置时基单元,使得计数器溢出频率为1KHz,计数器的溢出频率 = PWM的频率 =1KHz
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruture;
TIM_TimeBaseStruture.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseStruture.TIM_CounterMode=TIM_CounterMode_Up;
//ARR的值最好设置的大一些,防止计数溢出,这里给最大值65536-1,满量程计数。
TIM_TimeBaseStruture.TIM_Period=65536 -1;
//这里的PSC决定了测周法的标准频率fc。我们给的标准频率是1MHz。
TIM_TimeBaseStruture.TIM_Prescaler=72-1;
TIM_TimeBaseStruture.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStruture);
//初始化输入捕获单元。
TIM_ICInitTypeDef TIM_ICInitStruture;
//选择通道,选择TIM3的通道1.
TIM_ICInitStruture.TIM_Channel=TIM_Channel_1;
//配置输入捕获的滤波器,范围是0X0~0XF。越大滤波效果越好。
TIM_ICInitStruture.TIM_ICFilter=0XF;
//边沿检测、极性选择。我们选择上升沿触发。
TIM_ICInitStruture.TIM_ICPolarity=TIM_ICPolarity_Rising;
//我们需要每次触发都有效,所以分频器选择不分频。
TIM_ICInitStruture.TIM_ICPrescaler=TIM_ICPSC_DIV1;
//选择触发信号从哪个引脚输入,可以选择直连通道或者是交叉通道。
//我们这里选择直连通道。
TIM_ICInitStruture.TIM_ICSelection=TIM_ICSelection_DirectTI;
TIM_PWMIConfig(TIM3,&TIM_ICInitStruture);
//配置TRGI的触发源为TI1FP1。
TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1);
//配置从模式为Reset。
TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);
//使能定时器
TIM_Cmd(TIM3,ENABLE);
//当我们启动定时器后,CNT就会在内部不断自增,即使信号没有过来,他也会不断自增。
//CNT有信号来的时候,他就会在从模式的作用下自动清零。
}
//当我们想要查看频率,需要读取CCR进行计算。
uint32_t IC_GetFreq()
{
return 1000000 / (TIM_GetCapture1(TIM3)+1);
}
//获取占空比的函数
uint32_t IC_GetDuty()
{
//经过实测CCR会少一个数,所以CCR各加一个数。
return (TIM_GetCapture2(TIM3)+1)*100/ (TIM_GetCapture1(TIM3)+1);
}
最后,我们来研究一下这个测频率的性能,首先是测频率的范围,目前我们给的标准频率是1MHz,计数器最大只能计到65535,所以所测量的最低频率是1M/65535,这个值算一下大概是15Hz。如果频率再低,计数器就要溢出了。如果想要测量的频率比15Hz还低,那就要把预分频器的1MHz再加大一些。
最大频率是1MHz,但实际不会那么大,因为越大误差也就越大。