(一)输入捕获
在之前学习了定时器的定时中断和输出比较功能,此外定时器还有一个功能就是输入捕获功能
输入捕获功能是用来测量外部方波信号的频率和周期以及相关值的,输入捕获功能在外部信号一个触发沿到来时开始计数,到下一个触发沿到来时停止计数,如果我们可以读取到这个时间段内的周期,那么我们就知道了待测信号的频率,我们只需要知道定时器的频率和计次的总数,就可以计算输入波形的频率了,如果我们知道上升沿到下一个上升沿的时间,那我们就知道其周期,如果我们还知道第一个上升沿到下降沿的时间,那么我们就知道其占空比了
输入捕获和之前的输出比较使用的是同一个时钟,输入捕获和输出比较一样一个定时器有四条通道,其中通道1和通道2可以交叉配置,我们可以选择一个输入端口,在接收到一个上升沿时初始时间清零,然后让1通道记录其下一个上升沿的时间,让通道2记录其下一个下降沿的时间,那么我们就可以获得该信号的频率和占空比了
这是输入捕获的流程图,这样只能获得输入信号的频率
如果我们要获得输入信号的频率和占空比,那么我们应该这样配置
我们要同时使用两个通道,通道1的边沿检测为上升沿,通道2的边沿检测为下降沿
我们在每个信号的上升沿来时计数器的值是要清零的,这样我们才能开始下一个信号的测量,我们可以手动清零,但是这里用到的是从模式清零,从模式清零:只要我们有从模式的触发信号,那么就会自动执行对应的任务,不需要CPU资源,直接由硬件电路执行;这里我们选择由上升沿触发的从模式自动清零,其会在上升沿到来时自动清零CNT的值,以便我们下次读取CNT的值时是一个信号周期内的计数值
从流程图中可以看到,我们的初始化有七步:(1)打开时钟,(2)打开GPIO口,(3)选择时钟,(4)初始化时基单元,(5)初始化输入捕获单元,(6)初始化从模式触发,(7)打开定时器
这里除了初始化输入捕获单元和初始化从模式触发前面没有写过,其他的和前面的初始化基本相同,先了解输入捕获单元和从模式触发需要的函数
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
//初始化输入捕获,只初始化一个通道,只能测输入波形的频率
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
//初始化输入捕获,初始化两个通道,分别为上升沿触发和下降沿触发,可以测量波形的频率和周期
void TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct);
//用于给输入捕获初始化结构体赋默认值
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
//设置输入触发信号,配合从模式初始化来配置从模式触发
void 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);
//分别读取四个通道的CCR的值,也就是定时器的计数值
(二)代码实现测量输入信号频率
(1)时钟打开、GPIO初始化、时钟源选择、时基单元初始化
这部分和前面的定时中断和输出比较的代码基本一样,这里我们选择时钟三来实现输入捕获,我们选择的是通道1,对应为PA6口
void ic_rcc_init()
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
}
void ic_gpio_init()
{
GPIO_InitTypeDef gpio_init;
gpio_init.GPIO_Mode = GPIO_Mode_IPU;
gpio_init.GPIO_Pin = GPIO_Pin_6;
gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_init);
}
void ic_time_channel_init()
{
TIM_InternalClockConfig(TIM3);
}
void ic_time_base_init()
{
TIM_TimeBaseInitTypeDef time_base_init;
time_base_init.TIM_ClockDivision = TIM_CKD_DIV1;
time_base_init.TIM_CounterMode = TIM_CounterMode_Up;
time_base_init.TIM_Period = 65535-1;
time_base_init.TIM_Prescaler = 720-1; //fn=72*10^6 / 720
time_base_init.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &time_base_init);
}
我们这里设置的预分频是720,而单片机内的时钟频率是72*10^6HZ,可以知道,经过我们分频后计数一次的频率为1*10^5HZ,记一个数的时间t=1/10^5,如果我们知道停止计数时的记了n个数,那么一个信号的周期就是T=n/10^5,则该信号的频率f=1/T=10^5/n
(2)输入捕获单元初始化
接下来我们配置的是输入捕获通道,我们可以跳转到输入捕获初始化结构体看下其定义
(1)TIM_Channel参数表示的是选择那个通道,之前我们说过,输入捕获和输出比较一样,是有四个通道的,且四个通道和IO口有对应的关系,我们的信号从PA6进来,应该选择通道1
(2)TIM_ICPolarity表示的是边沿检测方式(可以点击@ref后面的定义进行按Ctrl和F搜索),有上升沿触发、下降沿触发和上升下降沿都触发,这里我们选择的是上升沿触发赋值TIM_ICPolarity_Rising
(3)TIM_ICSelection有两个选择,一个是TIM_ICSelection_DirectTI,另一个是TIM_ICSelection_IndirectTI,分别表示从本通道和交叉通道捕获,这里只用测量频率,所以选择TIM_ICSelection_DirectTI
(4)TIM_ICPrescaler是输入检测的分频,这里我们要每个上升沿都进行捕获,我们不需要分频,参数赋值1分频(不分频)TIM_ICPSC_DIV1
(5)TIM_ICFilter是滤波器选择,范围为0x0~0xF,数值越大滤波越强,这个可以根据情况修改
这样就可以完成对输入捕获单元的初始化了
void ic_input_capture_init()
{
TIM_ICInitTypeDef ic_init;
ic_init.TIM_Channel = TIM_Channel_1;
ic_init.TIM_ICFilter = 0x5;
ic_init.TIM_ICPolarity = TIM_ICPolarity_Rising;
ic_init.TIM_ICPrescaler = TIM_ICPSC_DIV1;
ic_init.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInit(TIM3, &ic_init);
}
(3)从模式初始化
在我们捕获到对应的触发源后,输入捕获的寄存器CCR会记录此时的计数器值CNT,我们要做的就是保证CNT在被记录后清零,这样我们才可以知道一个信号的周期时间和频率,这里选择用从模式触发的方式清零,这种方式不需要软件执行,可以在检测到触发源时执行对应的操作,首先要通过TIM_SelectInputTrigger()函数来设置触发源,这个触发源和输入捕获一样,我们选择触发源为上升沿检测后的触发源TIM_TS_TI1FP1
选择从模式触发源
接着我们选择从模式的行为,这里我们选择自动清零,即TIM_SlaveMode_Reset
这样我们就配置好了从模式了,它会在我们上升沿的时候自动给CNT清零
(4)读取信号的频率
我们可以调取TIM_GetCapture1()函数来获取通道1CCR里的值,也就是信号一个周期的计数值,我们假设获取的计数值为N,我们之前讲过,我们对时钟的欲分频为720,那么我们计数一次的时间为t=1/10^5,我们信号的周期为T=N*t=N/10^5,那么我们信号的频率则是f=1/T=10^5/N,我们只要读取CCR的计数值,用100000除以这个值就获得了信号的频率
unsigned int ic_get_frequency() //T=freq=fn/n
{
return 100000 / TIM_GetCapture1(TIM3);
}
(5)打开定时器和封装
最后不要忘了把定时器的时钟打开
void ic_time_open()
{
TIM_Cmd(TIM3, ENABLE);
}
最后把所有的初始化函数封装成一个函数,和获取频率的函数设置为外部调用函数,输入捕获模块的代码就完成了
#include "stm32f10x.h" // Device header
void ic_rcc_init()
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
}
void ic_gpio_init()
{
GPIO_InitTypeDef gpio_init;
gpio_init.GPIO_Mode = GPIO_Mode_IPU;
gpio_init.GPIO_Pin = GPIO_Pin_6;
gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_init);
}
void ic_time_channel_init()
{
TIM_InternalClockConfig(TIM3);
}
void ic_time_base_init()
{
TIM_TimeBaseInitTypeDef time_base_init;
time_base_init.TIM_ClockDivision = TIM_CKD_DIV1;
time_base_init.TIM_CounterMode = TIM_CounterMode_Up;
time_base_init.TIM_Period = 65535-1;
time_base_init.TIM_Prescaler = 720-1; //fn=72*10^6 / 720
time_base_init.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &time_base_init);
}
void ic_input_capture_init()
{
TIM_ICInitTypeDef ic_init;
ic_init.TIM_Channel = TIM_Channel_1;
ic_init.TIM_ICFilter = 0x5;
ic_init.TIM_ICPolarity = TIM_ICPolarity_Rising;
ic_init.TIM_ICPrescaler = TIM_ICPSC_DIV1;
ic_init.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInit(TIM3, &ic_init);
}
void ic_slave_init()
{
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
}
void ic_time_open()
{
TIM_Cmd(TIM3, ENABLE);
}
void ic_init()
{
ic_rcc_init();
ic_gpio_init();
ic_time_channel_init();
ic_time_base_init();
ic_input_capture_init();
ic_slave_init();
ic_time_open();
}
unsigned int ic_get_frequency() //T=freq=fn/n
{
return 100000 / TIM_GetCapture1(TIM3);
}
头文件声明两个函数即可
#ifndef __IC_H__
#define __IC_H__
void ic_init(void);
unsigned int ic_get_frequency(void);
#endif
(6)主函数
主函数完成初始化后只要不断调用获取频率的函数即可
#include "stm32f10x.h" // Device header
#include "PWM.h"
#include "IC.h"
#include "OLED.h"
int main()
{
unsigned int freq = 0;
OLED_Init();
pwm_init();
ic_init();
OLED_ShowString(1, 1, "freq:");
while(1)
{
freq = ic_get_frequency();
OLED_ShowNum(1, 6, freq, 5);
}
return 0;
}
这样就可以在我们的OLED屏幕上输出信号的频率了
(三)用输入捕获来获取输入信号的频率和占空比
在上边中,我们只能够获取输入信号的频率,如果我们要获取一个信号的占空比,那我们还要一个通道来检测下降沿,有了到下降沿的计数我们可以用总共的计数来比上到下降沿的计数,这样就可以获取一个信号的占空比了
前面我们讲过使用TIM_PWMIConfig()函数来初始化输入捕获单元可以计算占空比,这个函数的两个参数和之前用的TIM_ICInit()完全相同,只不过它会根据第一个通道的初始化方式来自动以相反的方式初始化其交叉通道,例如通道1使用了上升沿触发,则通道2会自动使用下降沿触发,我们只需要把输入捕获单元的函数的最后一句修改调用函数即可完成这里的输入捕获单元初始化
void icfd_input_capture_init()
{
TIM_ICInitTypeDef ic_init;
ic_init.TIM_Channel = TIM_Channel_1;
ic_init.TIM_ICFilter = 0x05;
ic_init.TIM_ICPolarity = TIM_ICPolarity_Rising;
ic_init.TIM_ICPrescaler = TIM_ICPSC_DIV1;
ic_init.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_PWMIConfig(TIM3, &ic_init);
}
我们要计算其占空比,只要用通道2的计数值(从上升沿到下降沿的计数值),通道1的计数值(一个周期内的计数值)即可,这里转为百分比的形式,因此乘以100
unsigned int icfd_get_duty()
{
return TIM_GetCapture2(TIM3)*100 / TIM_GetCapture1(TIM3);
}
其余代码和之前测量频率的代码完全相同
#include "stm32f10x.h" // Device header
void icfd_rcc_init()
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
}
void icfd_gpio_init()
{
GPIO_InitTypeDef gpio_init;
gpio_init.GPIO_Mode = GPIO_Mode_IPU;
gpio_init.GPIO_Pin = GPIO_Pin_6;
gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_init);
}
void icfd_time_channel_init()
{
TIM_InternalClockConfig(TIM3);
}
void icfd_time_base_init()
{
TIM_TimeBaseInitTypeDef time_base_init;
time_base_init.TIM_ClockDivision = TIM_CKD_DIV1;
time_base_init.TIM_CounterMode = TIM_CounterMode_Up;
time_base_init.TIM_Period = 65525-1;
time_base_init.TIM_Prescaler = 720-1;
time_base_init.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &time_base_init);
}
void icfd_input_capture_init()
{
TIM_ICInitTypeDef ic_init;
ic_init.TIM_Channel = TIM_Channel_1;
ic_init.TIM_ICFilter = 0x05;
ic_init.TIM_ICPolarity = TIM_ICPolarity_Rising;
ic_init.TIM_ICPrescaler = TIM_ICPSC_DIV1;
ic_init.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_PWMIConfig(TIM3, &ic_init);
}
void icfd_slave_mode_init()
{
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
}
void icfd_time_open()
{
TIM_Cmd(TIM3, ENABLE);
}
void icfd_init()
{
icfd_rcc_init();
icfd_gpio_init();
icfd_time_channel_init();
icfd_time_base_init();
icfd_input_capture_init();
icfd_slave_mode_init();
icfd_time_open();
}
unsigned int icfd_get_freq() //f=72*10^6/720
{
return 100000 / TIM_GetCapture1(TIM3);
}
unsigned int icfd_get_duty()
{
return TIM_GetCapture2(TIM3)*100 / TIM_GetCapture1(TIM3);
}
头文件声明一下初始化函数、频率获取函数和占空比获取函数
#ifndef __ICFD_H__
#define __ICFD_H__
void icfd_init(void);
unsigned int icfd_get_freq(void);
unsigned int icfd_get_duty(void);
#endif
主函数调用,第一行显示信号频率,第二行显示信号占空比
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "ic_freq_and_duty.h"
#include "PWM.h"
int main()
{
unsigned int freq = 0;
unsigned int duty = 0;
OLED_Init();
pwm_init();
icfd_init();
OLED_ShowString(1, 1, "freq:");
OLED_ShowString(2, 1, "duty:");
while (1)
{
freq = icfd_get_freq();
duty = icfd_get_duty();
OLED_ShowNum(1, 6, freq, 5);
OLED_ShowNum(2, 6, duty, 3);
}
return 0;
}
(四)总结
通过使用定时器的输入捕获模式实现了信号的频率和占空比的测量,了解了输入捕获的原理和使用,还了解了主从触发模式的使用方法