单片机学习小组
直播中

小组店小二

9年用户 908经验值
擅长:可编程逻辑 电源/新能源 MEMS/传感技术 测量仪表 嵌入式技术 制造/封装 模拟技术 连接器 EMC/EMI设计 光电显示 存储技术 EDA/IC设计 处理器/DSP 接口/总线/驱动 控制/MCU RF/无线
私信 关注

STM32多路PWM信号频率如何检测?

频率检测的原理是什么?

STM32多路PWM信号频率如何检测?

回帖(1)

郭金

2022-2-14 11:55:02
这星期老师让给项目中添加一个检测输入信号频率的功能,用于矿下煤气浓度检测,于是搞了几天做成了一个样例,由于电路板的限制,用的是TIM3和TIM4。
这个程序最多支持8路不通频率信号的测量,由于有实际要求,我把测量的频率设定在1000~200Hz之间,当然测更高的频率也行,只是我没有测试过2000Hz以上的信号。
频率检测的原理

怎么编程需要看懂下面这张定时器输入捕获结构图,我只进行了最低限度的解释,要了解更多推荐看看野火的《零死角玩转STM32F103指南者》高级定时器那一章。

使用 STM32 一定要找到参考手册、数据手册或者其他参考书,不然寸步难行。
这里先说一下怎么进行信号捕获
0.TIMx一直在计数
1.信号通过 TIMx_CH1/2/3/4 流入
2.经过滤波器和边沿检测
3.信号进入捕获通道
4.经过预分频器
5.捕获寄存器在发生捕获时存储 CNT 的值,产生 CCxI 中断
首先借助于定时器的输入捕获的4个通道的边沿捕获功能,检测到上升沿后出发 CI 中断。
以 TIM3 的 CH1 为例,它检测到第一个上升沿后,我们用变量 cnt_val 记录一下 CNT(计数器) 的值。
当 CH1 遇到第二次遇到上升沿时,TIM3->CCR1 寄存器记录下此时 CNT 的值,我们用一个变量 ccr_val 记录一下。
然后退出中断, ( ccr_val - cnt_val ) 的值就是一个周期内 TIM3 计数的次数(忽略计数器更新),用变量 c_num 记录。接着用 TIM3 的计时频率 / c_num 可以得出频率的值。


从上面的图中可以看出,假如捕获信号的一个周期内定时器发生了更新,那这次采集就算失败,因为更新后CNT重新计数了。
还有一种情况是信号源突然掉线,所以需要定时进行在线检测。
目前我没想到其它的bug。


GPIO初始化
我用到了两个计时器,这里我只拿一个TIM3举例子,下面用到了很多宏定义,是为了方便我开关某些功能。


#define USE_TIM3                          1
#define USE_TIM3_CH1          1
#define USE_TIM3_CH2          1
#define USE_TIM3_CH3          1
#define USE_TIM3_CH4          1
#define TIM3_IT_CCx                                                /**/TIM_IT_CC1|TIM_IT_CC2|TIM_IT_CC3|TIM_IT_CC4/*注意要与上面的通道向对应*/
#define TIM3_GPIO_CLK         RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB


static void TIM3_GPIO_Config(void)
{
        GPIO_InitTypeDef GPIO_InitStructure;
  //GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE);//TIM3 端口重映射,我用不上
  //GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable , ENABLE);        //禁止JTAG功能,把PB3,PB4作为普通IO口使用,我也用不上
        RCC_APB2PeriphClockCmd(TIM3_GPIO_CLK,ENABLE);//开启对应GPIO的时钟,TIM3_GPIO_CLK是宏定义
#if USE_TIM3_CH1
        //配置CH1
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
        GPIO_InitStructure.GPIO_Pin = TIM3_CH1_PIN;
        GPIO_Init(TIM3_CH1_PORT,&GPIO_InitStructure);
#endif
       
#if USE_TIM3_CH2
        //配置CH2
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_InitStructure.GPIO_Pin = TIM3_CH2_PIN;
        GPIO_Init(TIM3_CH2_PORT,&GPIO_InitStructure);
#endif
       
#if USE_TIM3_CH3
        //配置CH3
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_InitStructure.GPIO_Pin = TIM3_CH3_PIN;
        GPIO_Init(TIM3_CH3_PORT,&GPIO_InitStructure);
#endif


#if USE_TIM3_CH4
        //配置CH4
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_InitStructure.GPIO_Pin = TIM3_CH4_PIN;
        GPIO_Init(TIM3_CH4_PORT,&GPIO_InitStructure);
#endif
}


定时器初始化
同样,只拿一个TIM3举例


static void GENERAL_TIM3_Config(void)
{
        //配置引脚,就是上面的那个函数
        TIM3_GPIO_Config();
        //配置时基
        TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
        //初始化TIM3时钟72MHz
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
       
        TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1;        //分频 72M/72 = 1MHz
        TIM_TimeBaseInitStructure.TIM_Period = 0xffff-1;  //计时周期65535  计时器的一个周期是(1s/1M)*65535 =  0.0656s即65ms
        TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;        //配置为1分频
        TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//配置为向上计数模式
        TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,TIM3没有
       
        TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);  //配置TIM3
        TIM_ClearFlag(TIM3, TIM_FLAG_Update);               //清除中断标志位
}


配置输入捕获
这里需要注意 TIM_ICInitStructure.TIM_Channel 不能像配置GPIO一样多个值相或,如 GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8,TIM_Channel只能一次配置一个,我踩过的坑希望其他人别去踩。


// 测量的起始边沿
#define GENERAL_TIM_STRAT_ICPolarity         TIM_ICPolarity_Rising


static void TIM3_IC_Config(void)
{
        TIM_ICInitTypeDef TIM_ICInitStructure;
#if USE_TIM3_CH1
        TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;                                //配置捕获通道,!!!!"这个参数不支持多参数相或"!!!!!
        TIM_ICInitStructure.TIM_ICFilter = 0x0f;                                                                //设置滤波,参考手册p215
        TIM_ICInitStructure.TIM_ICPolarity = GENERAL_TIM_STRAT_ICPolarity;//选择IC触发边沿,上升沿还是下降沿,这里用的宏定义
        TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;                        //预分频数1
        TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//直接输入
        TIM_ICInit(TIM3, &TIM_ICInitStructure);                                                                        //配置TIM3的输入捕获功能
#endif
#if USE_TIM3_CH2
        TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;       
        TIM_ICInitStructure.TIM_ICFilter = 0x0f;
        TIM_ICInitStructure.TIM_ICPolarity = GENERAL_TIM_STRAT_ICPolarity;
        TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
        TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
        TIM_ICInit(TIM3, &TIM_ICInitStructure);
#endif
#if USE_TIM3_CH3
        TIM_ICInitStructure.TIM_Channel = TIM_Channel_3;
        TIM_ICInitStructure.TIM_ICFilter = 0x0f;
        TIM_ICInitStructure.TIM_ICPolarity = GENERAL_TIM_STRAT_ICPolarity;
        TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
        TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
        TIM_ICInit(TIM3, &TIM_ICInitStructure);
#endif
#if USE_TIM3_CH4
        TIM_ICInitStructure.TIM_Channel = TIM_Channel_4;
        TIM_ICInitStructure.TIM_ICFilter = 0x0f;
        TIM_ICInitStructure.TIM_ICPolarity = GENERAL_TIM_STRAT_ICPolarity;
        TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
        TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
        TIM_ICInit(TIM3, &TIM_ICInitStructure);
#endif
// 开启更新和捕获中断
        TIM_ITConfig (TIM3, TIM_IT_Update | TIM3_IT_CCx, ENABLE );
        TIM_ClearFlag(TIM3, TIM_FLAG_Update|TIM3_IT_CCx);//清中断标志
// 使能计数器
        TIM_Cmd(TIM3, ENABLE);
}


配置 NVIC
这里还是只贴出了TIM3的


static void GENERAL_TIM_NVIC_Config(void)
{
        NVIC_InitTypeDef NVIC_InitStructure;
#if USE_TIM3
        // 设置中断组为 1
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
        // 设置中断来源
        NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn ;
        // 设置主优先级为 1
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
        // 设置抢占优先级为 3
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                //中断通道使能
        NVIC_Init(&NVIC_InitStructure);
#endif
}


自定义一个保存信息的结构体
typedef struct {
        uint8_t Time_OutFlag;                                //捕获超时标志
        uint8_t Capture_FinishFlag; // 捕获结束标志位
        uint8_t Capture_StartFlag; // 捕获开始标志位
        uint16_t Capture_CNTValue; // 第一次捕获边沿时计数器的值
        uint16_t Capture_CcrValue; // 捕获寄存器的值
}TIM_ICUserValueType;


中断处理函数
extern TIM_ICUserValueType TIM_ICUserValue[8];
void TIM3_IRQHandler(void)
{
         if ( TIM_GetITStatus ( TIM3, TIM_IT_Update) != RESET )
        {//假定超时
                TIM_ICUserValue[0].Time_OutFlag = 1;
                TIM_ICUserValue[1].Time_OutFlag = 1;
                TIM_ICUserValue[2].Time_OutFlag = 1;
                TIM_ICUserValue[3].Time_OutFlag = 1;
                // 采集波形的一个周期内发生了寄存器溢出,放弃本次采集
                TIM_ICUserValue[0].Capture_StartFlag = 0;
                TIM_ICUserValue[0].Capture_FinishFlag = 0;
                TIM_ICUserValue[1].Capture_StartFlag = 0;
                TIM_ICUserValue[1].Capture_FinishFlag = 0;
                TIM_ICUserValue[2].Capture_StartFlag = 0;
                TIM_ICUserValue[2].Capture_FinishFlag = 0;
                TIM_ICUserValue[3].Capture_StartFlag = 0;
                TIM_ICUserValue[3].Capture_FinishFlag = 0;
                //清除中断标志
                TIM_ClearITPendingBit ( TIM3, TIM_FLAG_Update );
        }
#if USE_TIM3_CH1
        // TIM3_CH1上升沿捕获中断
        if ( TIM_GetITStatus (TIM3, TIM_IT_CC1 ) != RESET)
                {
                        // 第一次捕获
                        if ( TIM_ICUserValue[0].Capture_StartFlag == 0 )
                                {
                                        //记录首次捕获时计数器的值
                                        TIM_ICUserValue[0].Capture_CNTValue = TIM3->CNT;                                                       
                                        // 存捕获比较寄存器的值的变量的值清 0
                                        TIM_ICUserValue[0].Capture_CcrValue = 0;
                                        // 开始捕获标准置 1
                                        TIM_ICUserValue[0].Capture_StartFlag = 1;
                                }
                        // 下降沿捕获中断
                        else { // 第二次捕获
                                        // 获取捕获比较寄存器的值减去上一次计数器的值,这个值就是捕获到的高电平的时间的值
                                        TIM_ICUserValue[0].Capture_CcrValue =        TIM_GetCapture1(TIM3)-TIM_ICUserValue[0].Capture_CNTValue;
                                        // 开始捕获标志清 0
                                        TIM_ICUserValue[0].Capture_StartFlag = 0;
                                        // 捕获完成标志置 1
                                        TIM_ICUserValue[0].Capture_FinishFlag = 1;
                                        //超时标志清除
                                        TIM_ICUserValue[0].Time_OutFlag = 0;
                                        }
                         TIM_ClearITPendingBit (TIM3,TIM_IT_CC1);
                }
#endif


#if USE_TIM3_CH2
        // TIM3_CH2上升沿捕获中断
        if ( TIM_GetITStatus (TIM3, TIM_IT_CC2 ) != RESET)
                {
                        if ( TIM_ICUserValue[1].Capture_StartFlag == 0 )
                                {
                                        TIM_ICUserValue[1].Capture_CNTValue = TIM3->CNT;
                                        TIM_ICUserValue[1].Capture_CcrValue = 0;
                                        TIM_ICUserValue[1].Capture_StartFlag = 1;
                                }
                        else {
                                        TIM_ICUserValue[1].Capture_CcrValue =        TIM_GetCapture2(TIM3)-TIM_ICUserValue[1].Capture_CNTValue;               
                                        TIM_ICUserValue[1].Capture_StartFlag = 0;
                                        TIM_ICUserValue[1].Capture_FinishFlag = 1;
                                        TIM_ICUserValue[1].Time_OutFlag = 0;
                                }
                         TIM_ClearITPendingBit (TIM3,TIM_IT_CC2);
                }
#endif


#if USE_TIM3_CH3
        // TIM3_CH3上升沿捕获中断
          if ( TIM_GetITStatus (TIM3, TIM_IT_CC3 ) != RESET)
                {
                        if ( TIM_ICUserValue[2].Capture_StartFlag == 0 )
                                {
                                        TIM_ICUserValue[2].Capture_CNTValue = TIM3->CNT;
                                        TIM_ICUserValue[2].Capture_CcrValue = 0;
                                        TIM_ICUserValue[2].Capture_StartFlag = 1;
                                }
                        else {
                                        TIM_ICUserValue[2].Capture_CcrValue =        TIM3->CCR3-TIM_ICUserValue[2].Capture_CNTValue;               
                                        TIM_ICUserValue[2].Capture_StartFlag = 0;
                                        TIM_ICUserValue[2].Capture_FinishFlag = 1;
                                        TIM_ICUserValue[2].Time_OutFlag = 0;
                        }
                         TIM_ClearITPendingBit (TIM3,TIM_IT_CC3);
                }
#endif
               
#if USE_TIM3_CH4
        // TIM3_CH4上升沿捕获中断
        if ( TIM_GetITStatus (TIM3, TIM_IT_CC4 ) != RESET)
                {
                        if ( TIM_ICUserValue[3].Capture_StartFlag == 0 )
                                {
                                        TIM_ICUserValue[3].Capture_CNTValue = TIM3->CNT;
                                        TIM_ICUserValue[3].Capture_CcrValue = 0;
                                        TIM_ICUserValue[3].Capture_StartFlag = 1;
                                }
                        else {
                                        TIM_ICUserValue[3].Capture_CcrValue =        TIM_GetCapture4(TIM3)-TIM_ICUserValue[3].Capture_CNTValue;               
                                        TIM_ICUserValue[3].Capture_StartFlag = 0;
                                        TIM_ICUserValue[3].Capture_FinishFlag = 1;
                                        TIM_ICUserValue[3].Time_OutFlag = 0;
                        }
                         TIM_ClearITPendingBit (TIM3,TIM_IT_CC4);
                }
#endif
}


结果处理
这个函数你可以放到 main 函数里,我只是把它的结果用串口打印出来(串口部分请自行解决,网上的例子一大堆)


void PWM_IC_Print(void)
{
        double fre;
        // TIM 计数器的驱动时钟
        uint32_t TIM_PscCLK = 72000000 / (71+1);
        int i = 0;
        for(i = 0;i<8;i++)
        {
                //超时判断
                if(TIM_ICUserValue.Time_OutFlag == 1)
                        continue;
                //判断是否捕获完成
                if(TIM_ICUserValue.Capture_FinishFlag == 1)
                {               
                        // 计算一个周期的计数器的值
                        fre = (TIM_ICUserValue.Capture_CcrValue+1);
                        //滤波器,我这里只留下了我需要的频段,具体值可以任意修改
                        //下面的值的计算方法是 (f(TIM3)/目标频率)
                        if(fre>10000||fre<500)
                                continue;
                        // 打印频率,你想怎么处理,请自便
                        printf ( "rn %d号输入测得频率: %f hzrn",i,1/(fre/TIM_PscCLK));
                        //清除标志
                        TIM_ICUserValue.Capture_FinishFlag = 0;
                }
        }
}


下面是实际的测试结果
我是用TIM2产生了一个200hz的方波,测了一下,还可以



然后连起来,PA6(TIM3 CH1) 接 PA2(待测信号),棕色那根是GND

打开串口调试助手
测试一下多路输入,1个200hz,2个1000hz
举报

更多回帖

发帖
×
20
完善资料,
赚取积分