资讯详情

STM32单片机-输入捕获、FFT测频

本内容介绍基于STM32F103VET6的一个实际工程中添加采集A相电压信号或B相电流信号频率的小功能,分别通过输入捕获与FFT实现,均可测试。紫色文字是超链接,点击自动跳转到相关博客。不断更新,原创不容易!
目录:
1)定时器配置 2)定期溢出和输入捕获中断处理
-----------------------------------------------------------------------------------------------------------------

图1.1.1
-----------------------------------------------------------
主控MCU:STM32F103ZET6,LM293输出连接在PB检测0上电压信号的频率,如图1所示.1.1与图1.2.1所示。

图1.2.1

图1.2.2
如图1.2.2所示,注意其中的TIM3_CH2N是PWM捕获比较输出,TIM3_CH三是输入捕获。

图1.2.3
-----------------------------------------------------------------------------------------------------------------
这里通过STM32输入捕获或FFT在实际工程中,实现了频率测量的转换。STM32输入捕获信号小于2V当单片机无法检测跳变沿时,需要硬件适当处理信号(如图1所示.1.1)。PB0/ADC8也可用ADC读信号电压值,ADC当值为0时记录,再次为0相当于周期。计算两次ADC以0的时差计算信号的频率,不受信号范围的限制。
--------------------------------
以下程序采集PB0口(图1.2.1)的电压信号,因频率较低,且要求继电器出口时间小于35mS,频率采用测周法计算。给出定时器配置和定时器中断程序的主要部分。由于上升沿示波器的测试并不陡峭(图1.1.1模拟图也可以看到),所以每周波下降两次。
请注意,后期处理程序必须捕获到两个下降边缘,才能进行相应的处理如果采集程序未完成,处理将出错。

图2.1.1
1)定时器配置
void adc_TIM_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定时器
GPIO_InitTypeDef GPIO_InitStructure; //端口
TIM_ICInitTypeDef TIM_ICInitStructure; ///输入捕获
   
   //初始化GPIO口
  GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; //浮空输入模式
   GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
   GPIO_Init(GPIOB,&GPIO_InitStructure);
   GPIO_SetBits(GPIOB,GPIO_Pin_0);
   
   //使能时钟
   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //TIM3
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE);
   
   //初始化TIM3定时
   TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
   TIM_TimeBaseStructure.TIM_Prescaler = 17; //1MHz计数脉冲 1uS
   TIM_TimeBaseStructure.TIM_Period = 65535;
   TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;
   TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
   TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
   TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
   
   //初始化TIM3 Channel3输入捕获IC(Input Capture)
   TIM_ICInitStructure.TIM_Channel=TIM_Channel_3;
   TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Falling; //下降沿捕获
   TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI; //管脚与寄存器一一对应
   TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1; //有下降沿就捕获,不分频
   TIM_ICInitStructure.TIM_ICFilter=0x00; //不打开输入捕获滤波器
   TIM_ICInit(TIM3,&TIM_ICInitStructure);
   TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许定时中断
   TIM_ITConfig(TIM3,TIM_IT_CC3,ENABLE); //允许CC3捕获中断
   TIM_Cmd(TIM3,ENABLE);
…………
}
--------------------------------
2)定时溢出和输入捕获中断处理
void TIM3_IRQHandler(void) //TIM3
{
   static u8 CapStatus=0; //捕获状态,CapStatus=0未捕获到第1个下降沿,CapStatus=1捕获到第1个下降沿
   static u8 TIM3_CH3_Capture=0; //总的计数次数
   u32 FrequencyTemp=0;
   
   if(TIM_GetITStatus(TIM3,TIM_IT_Update)) //TIM3定时溢出更新中断
   {
      TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
      if(CapStatus)
         TIM3_CH3_Capture++;
   }
   
   if(TIM_GetITStatus(TIM3,TIM_IT_CC3)) //RB0输入捕获中断
   {
      TIM_ClearITPendingBit(TIM3,TIM_IT_CC3); //清除中断标志位
      if(!CapStatus)
      {
         CapStatus=1;
         TIM_SetCounter(TIM3,0); //计数器清零
      }
      else if(CapStatus) //已经捕获到第1个下降沿
      {
         CapStatus=0;
         FrequencyTemp=TIM_GetCapture3(TIM3)+TIM3_CH3_Capture*65536; //计算两个下降沿总计数
         TIM3_CH3_Capture=0; //溢出次数清零
         TIM_SetCounter(TIM3,0); //计数器清零
         FrequencyValue=400000000/FrequencyTemp; //计算频率,比如5000,单位0.01Hz
      }
   }
}

图2.1.2

图2.1.3
-----------------------------------------------------------
下面是采集PC1口(图1.2.1)的小通道电流信号,计算频率,其固件具ST官方DSP库实现FFT,测试固件移步:FFT(具ST官方DSP库实现)。
--------------------------------

图2.2.1
-----------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------
网上找的资料,不保证正确性,没有实际测试过,仅供参考。
通过在一定时间内检测跳边沿的个数可计算出频率 频率=上升沿或下降沿个数/统计时间。
-----------------------------------------------------------
方法1:利用外部中断统计跳边沿个数,配置一个定时器每隔一定时间对频率进行计算。部分代码如下。
void exti_init()  //外部中断初始化函数
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
       
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&GPIO_InitStructure);
 
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource2);//选择GPIO引脚用作外部中段线路
//此处一定要记住给端口管脚加上中断外部线路
 
EXTI_InitStructure.EXTI_Line=EXTI_Line2;
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;  //下降沿进中断
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); 
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);  
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; //打开EXTI2的全局中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //设置优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;   
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   //使能
NVIC_Init(&NVIC_InitStructure);
}
外部中断中断函数
void EXTI2_IRQHandler()    
{
if(EXTI_GetITStatus(EXTI_Line2)==SET)
{
    EXTI_ClearITPendingBit(EXTI_Line0);//清中断
if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_2)==Bit_RESET)    //确定沿
{
cnt++;
}
}
定时器中断函数
void TIM3_IRQHandler()   
{
frequent=cnt; //定时器设置时间为1s时
cnt=0;  //清零计数cnt
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);    //清标志位
}
-----------------------------------------------------------
方法2:采用定时器外部计数的方法,另外一个定时器负责每隔一段时间计算频率,部分代码如下。
void time_init()
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM2_TimeBaseInitStructure;  
TIM_TimeBaseInitTypeDef TIM3_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除TIM2中断标志位
TIM2_TimeBaseInitStructure.TIM_Period = 0xFFFF;//设置自动重装载值
TIM2_TimeBaseInitStructure.TIM_Prescaler = 0;//设置分频
TIM2_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; 
TIM2_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
TIM_TimeBaseInit(TIM2,&TIM2_TimeBaseInitStructure);
  
TIM_ETRClockMode1Config(TIM2, TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted, 0x00);  //设置为采用外部时钟计数,可设定滤波参数消除信号干扰
TIM_Cmd(TIM2,ENABLE); 
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
TIM3_TimeBaseInitStructure.TIM_Period = 999;
TIM3_TimeBaseInitStructure.TIM_Prescaler = 3599;
TIM3_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; 
TIM3_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3,&TIM3_TimeBaseInitStructure);
TIM_Cmd(TIM3,ENABLE);
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE );
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; 
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;  
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
定时器中断函数
void TIM3_IRQHandler()  
{
static u8 i;
static u32 frequent_sum;
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);   //清中断
if(i<19)
{
cnt += TIM_GetCounter(TIM2);  //,获取计数器的值,累加减少误差
TIM_SetCounter(TIM2,0);    //计数器清零
i++;
}
else
{
cnt += TIM_GetCounter(TIM2);
TIM_SetCounter(TIM2,0);
cnt += cnt*0.000025; //根据实际情况修改系数线性补偿
frequent = cnt;
i = 0;
cnt = 0;
}
}
-----------------------------------------------------------------------------------------------------------------

标签: 单片机应如何正确驱动继电器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台