OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 52825|回复: 88

【教程】使用STM32测量频率和占空比的几种方法(申请置酷!)

  [复制链接]

3

主题

548

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1383
金钱
1383
注册时间
2015-2-3
在线时间
197 小时
发表于 2016-8-26 22:44:31 | 显示全部楼层 |阅读模式
本帖最后由 yyx112358 于 2016-8-27 22:28 编辑

这几天在论坛上面解答了好几个询问STM32测量频率的贴子,觉得这种需求还是存在的(示波器、电机控制等等)。而简单搜索了一下论坛,这方面的贴子有但是不全。正好今年参加比赛做过这方面的题目(最后是一等奖嘿嘿),所以把我们当时尝试过的各种方案都列出来,方便以后大家使用,也是作为一个长期在论坛的潜水党对论坛的回报。


PS:由于我们当时的题目除了测量频率之外,更麻烦的是测量占空比。而这两个的测量方法联系比较紧密,所以也一并把测量占空比的方法写出来。因为时间有限,所以并不能把所有思路都一一测试,只是写在下面作为参考,敬请谅解。


使用平台:官方STM32F429DISCOVERY开发板,180MHz的主频,定时器频率90MHz

相关题目:

1)测量脉冲信号频率f_O,频率范围为10Hz2MHz,测量误差的绝对值不大于0.1%。(15分)

2)测量脉冲信号占空比D,测量范围为10%~90%,测量误差的绝对值不大于2%。(15分)


思路一:外部中断

思路:这种方法是很容易想到的,而且对几乎所有MCU都适用(连51都可以)。方法也很简单,声明一个计数变量TIM_cnt,每次一个上升沿/下降沿就进入一次中断,对TIM_cnt++,然后定时统计即可。如果需要占空比,那么就另外用一个定时器统计上升沿、下降沿之间的时间即可。

缺点:缺陷显而易见,当频率提高,将会频繁进入中断,占用大量时间。而当频率超过100kHz时,中断程序时间甚至将超过脉冲周期,产生巨大误差。同时更重要的是,想要测量的占空比由于受到中断程序影响,误差将越来越大。

总结:我们当时第一时间就把这个方案PASS了,没有相关代码(这个代码也很简单)。不过,该方法在频率较低(10K以下)时,可以拿来测量频率。在频率更低的情况下,可以拿来测占空比。


思路二:PWM输入模式

思路:翻遍ST的参考手册,在定时器当中有这样一种模式:
5.png
2.png

简而言之,理论上,通过这种模式,可以用硬件直接测量出频率和占空比。当时我们发现这一模式时欢欣鼓舞,以为可以一步解决这一问题,代码如下:

初始化:
[mw_shl_code=c,true]void Tim2_PWMIC_Init(void)
{
        GPIO_InitTypeDef GPIO_InitStructure;
  NVIC_InitTypeDef NVIC_InitStructure;
        TIM_ICInitTypeDef  TIM_ICInitStructure;
  /* TIM4 clock enable */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

  /* GPIOB clock enable */
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
  
  /* TIM4 chennel2 configuration : PB.07 */
  GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_7;
  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP ;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
  
  /* Connect TIM pin to AF2 */
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_TIM4);
        
  /* Enable the TIM4 global Interrupt */
  NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
        
        TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
  TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
  TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
  TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
  TIM_ICInitStructure.TIM_ICFilter = 0x0;

  TIM_PWMIConfig(TIM4, &TIM_ICInitStructure);

  /* Select the TIM4 Input Trigger: TI2FP2 */
  TIM_SelectInputTrigger(TIM4, TIM_TS_TI2FP2);

  /* Select the slave Mode: Reset Mode */
  TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_Reset);
  TIM_SelectMasterSlaveMode(TIM4,TIM_MasterSlaveMode_Enable);

  /* TIM enable counter */
  TIM_Cmd(TIM4, ENABLE);

  /* Enable the CC2 Interrupt Request */
   TIM_ITConfig(TIM4, TIM_IT_CC2, ENABLE);

}
//中断程序:
void TIM4_IRQHandler(void)
{
  /* Clear TIM4 Capture compare interrupt pending bit */
  TIM_ClearITPendingBit(TIM4, TIM_IT_CC1|TIM_IT_CC2);

  /* Get the Input Capture value */
  IC2Value = TIM_GetCapture2(TIM4);//周期

  if (IC2Value != 0)
  {
                highval[filter_cnt]=TIM_GetCapture1(TIM4);//高电平周期
                waveval[filter_cnt]=IC2Value;
                filter_cnt++;
                if(filter_cnt>=FILTER_NUM)
                        filter_cnt=0;
  }
  else
  {
    DutyCycle = 0;
    Frequency = 0;
  }
}
//主循环:
  while (1)
  {
                uint32_t highsum=0,wavesum=0,dutysum=0,freqsum=0;
                LCD_Clear(0);
                for(i=0;i<FILTER_NUM;i++)
                {
                        highsum+=highval;
                        wavesum+=waveval;
                }
  
              delay_ms(1);
                DutyCycle=highsum*1000/wavesum;
                Frequency=(SystemCoreClock/2*1000/wavesum);
                freq=Frequency*2.2118-47.05;//线性补偿
                sprintf(str,"DUTY:%3d\nFREQ:%.3f KHZ\n",DutyCycle,freq/1000);
               
                LCD_ShowString(0,200,str);
                delay_ms(100);
  }
[/mw_shl_code]

但是,经过测量之后发现这种方法测试数据不稳定也不精确,数据不停跳动,且和实际值相差很大。ST的这些功能经常有这种问题,比如定时器的编码器模式,在0点处频繁正负跳变时有可能会卡死。这些方法虽然省事,稳定性却不是很好。

经过线性补偿可以一定程度上减少误差(参数在不同情况下不同):

freq=Frequency*2.2118-47.05;

这种方法无法实现要求。所以在这里我并不推荐这种方法。如果有谁能够有较好的程序,也欢迎发出来。


思路三:输入捕获

思路:一般来说,对STM32有一定了解的坛友们在测量频率的问题上往往都会想到利用输入捕获。首先设定为上升沿触发,当进入中断之后(rising)记录与上次中断(rising_last)之间的间隔(周期,其倒数就是频率)。再设定为下降沿,进入中断之后与上升沿时刻之差即为高电平时间(falling-rising_last),高电平时间除周期即为占空比。

画图如下(由于修改多次使得):

程序如下,注意由于为了减少程序复杂性使用了32位定时器5(计数周期如果是1us时可以计数4294s,否则如果是16位只能计数65ms),如果需要在F1上使用则需要自行处理:
[mw_shl_code=c,true]//定时器5通道1输入捕获配置
//arr:自动重装值(TIM2,TIM5是32位的!!)
//psc:时钟预分频数
void TIM5_CH1_Cap_Init(u32 arr,u16 psc)
{
        GPIO_InitTypeDef GPIO_InitStructure;
        TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
        NVIC_InitTypeDef NVIC_InitStructure;

        
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);          //TIM5时钟使能   
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);         //使能PORTA时钟        
        
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //GPIOA0
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;        //速度100MHz
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; //下拉
        GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA0

        GPIO_PinAFConfig(GPIOA,GPIO_PinSource0,GPIO_AF_TIM5); //PA0复用位定时器5
  
         
        TIM_TimeBaseStructure.TIM_Prescaler=psc;  //定时器分频
        TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
        TIM_TimeBaseStructure.TIM_Period=arr;   //自动重装载值
        TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
        
        TIM_TimeBaseInit(TIM5,&TIM_TimeBaseStructure);
        

        //初始化TIM5输入捕获参数
        TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; //CC1S=01         选择输入端 IC1映射到TI1上
  TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;        //上升沿捕获
  TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
  TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;         //配置输入分频,不分频
  TIM5_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置输入滤波器 不滤波
  TIM_ICInit(TIM5, &TIM5_ICInitStructure);
               
        TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断 ,允许CC1IE捕获中断        
        
  TIM_Cmd(TIM5,ENABLE );         //使能定时器5


  NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
        NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;                //子优先级
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                        //IRQ通道使能
        NVIC_Init(&NVIC_InitStructure);        //根据指定的参数初始化VIC寄存器、
        
        
}
//捕获状态(对于32位定时器来说,1us计数器加1,溢出时间:4294秒)
//定时器5中断服务程序         
void TIM5_IRQHandler(void)
{                     
                        
        if(TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)//捕获1发生捕获事件
        {
                if(edge==RESET)//上升沿
                {
                        rising=TIM5->CCR1-rising_last;
                        rising_last=TIM5->CCR1;
                        TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); //CC1P=0 设置为上升沿捕获
                        edge=SET;
                }
                else
                {
                        falling=TIM5->CCR1-rising_last;
                        TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); //CC1P=0 设置为上升沿捕获
                        edge=RESET;
                }
        }
        TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
}
主程序:
  while (1)
  {
                uint32_t highsum=0,wavesum=0,dutysum=0,freqsum=0;
                LCD_Clear(0);

                delay_ms(1);

                sprintf(str,"rise:%3d\nfall:%d\nfall-rise:%d",rising,falling,falling-rising);
                LCD_ShowString(0,100,str);
                sprintf(str,"Freq:%.2f Hz\nDuty:%.3f\n",90000000.0/rising,(float)falling/(float)rising);//频率、占空比
                LCD_ShowString(0,200,str);
                delay_ms(100);
  }
[/mw_shl_code]


注意的是,中断程序当中的变量rising,last因为多次修改的缘故,与名称本身含义有所区别,示意如下:

3.jpg
该方法尤其是在中低频(<100kHz)之下精度不错。
缺点:稍有经验的朋友们应该都能看出来,该方法仍然会带来极高的中断频率。在高频之下,首先是CPU时间被完全占用,此外,更重要的是,中断程序时间过长往往导致会错过一次或多次中断信号,表现就是测量值在实际值、实际值×2、实际值×3等之间跳动。实测中,最高频率可以测到约400kHz
总结:该方法在低频率(<100kHz)下有着很好的精度,在考虑到其它程序的情况下,建议在10kHz之下使用该方法。同时,可以参考以下的改进程序减少CPU负载。
改进:
前述问题,限制频率提高的主要因素是过长的中断时间(一般应用情景之下,还有其它程序部分的限制)。所以进行以下改进:
1.           使用2个通道,一个只测量上升沿,另一个只测量下降沿。这样可以减少切换触发边沿的延迟,缺点是多用了一个IO口。
2.           使用寄存器,简化程序
最终程序如下:
[mw_shl_code=c,true]//TIM2_CH1->PA5
//TIM2_CH2->PB3
void TIM2_CH1_Cap_Init(u32 arr,u16 psc)
{
        GPIO_InitTypeDef GPIO_InitStructure;
        TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
        TIM_ICInitTypeDef  TIM_ICInitStructure;
        
        TIM_DeInit(TIM2);
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);          //TIM2时钟使能   
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB, ENABLE);         //使能PORTA时钟        
        
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //GPIOA0
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;        //速度100MHz
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; //下拉
        GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA0
        
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //GPIOA0
        GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化PA0
        
        GPIO_PinAFConfig(GPIOA,GPIO_PinSource5,GPIO_AF_TIM2); //PA0复用位定时器5
        GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_TIM2); //PA0复用位定时器5
        
        TIM_TimeBaseStructure.TIM_Prescaler=psc;  //定时器分频
        TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
        TIM_TimeBaseStructure.TIM_Period=arr;   //自动重装载值
        TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
        
        TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);
        //初始化TIM2输入捕获参数
        TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //CC1S=01         选择输入端 IC1映射到TI1上
  TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;        //上升沿捕获
  TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
  TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;         //配置输入分频,不分频
  TIM_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置输入滤波器 不滤波
  TIM_ICInit(TIM2, &TIM_ICInitStructure);
        
        TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; //CC1S=01         选择输入端 IC1映射到TI1上
        TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;        //上升沿捕获
        
        TIM_ICInit(TIM2, &TIM_ICInitStructure);
               
        TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC1|TIM_IT_CC2,ENABLE);//允许更新中断 ,允许CC1IE捕获中断        
//        TIM2_CH1_Cap_DMAInit();
  TIM_Cmd(TIM2,ENABLE );         //使能定时器5

  NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//抢占优先级3
        NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;                //子优先级3
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                        //IRQ通道使能
        NVIC_Init(&NVIC_InitStructure);        //根据指定的参数初始化VIC寄存器、
        
}
//定时器2中断服务程序(对于32位定时器来说,1us计数器加1,溢出时间:4294秒)
void TIM2_IRQHandler(void)
{                     
        if(TIM2->SR&TIM_FLAG_CC1)//TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET)//捕获1发生捕获事件
        {
                rising=TIM2->CCR1-rising_last;
                rising_last=TIM2->CCR1;
                return;
        }
        if(TIM2->SR&TIM_FLAG_CC2)//TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)
        {
                falling=TIM2->CCR2-rising_last;
                return;
        }
        TIM2->SR=0;
}
[/mw_shl_code]

之所以改用TIM2是因为TIM5CH1(PA0)还是按键输入引脚。本来想来这应当也没什么,按键不按下不就是开路嘛。但是后来发现官方开发板上还有一个RC滤波……
所以,当使用别人的程序之前,请一定仔细查看电路图。
4.jpg


这样,最高频率能够达到约1.1MHz,是一个不小的进步。但是,其根本问题——中断太频繁——仍然存在。

解决思路也是存在的。本质上,我们实际上只需要读取CCR1CCR2寄存器。而在内存复制过程中,面对大数据量的转移时,我们会想到什么?显然,我们很容易想到——利用DMA。所以,我们使用输入捕获事件触发DMA来搬运寄存器而非触发中断即可,然后将这些数据存放在一个数组当中并循环刷新。这样,我们可以随时来查看数据并计算出频率。

这一方法我曾经尝试过,没有调出来,因为,有一个更好的方法存在。但是理论上这是没有问题的,以供参考我列出如下。

【注意:这段程序无法工作,仅供参考!!!】

[mw_shl_code=c,true]//TIM2_CH1->DMA1_CHANNEL3_STREAM5
u32        val[FILTER_NUM]={0};
void        TIM2_CH1_Cap_DMAInit(void)
{
        NVIC_InitTypeDef NVIC_InitStructure;
        DMA_InitTypeDef  DMA_InitStructure;
        
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1时钟使能
        
  DMA_DeInit(DMA1_Stream5);
        
        while (DMA_GetCmdStatus(DMA1_Stream5) != DISABLE){}//等待DMA可配置
        
  /* 配置 DMA Stream */
  DMA_InitStructure.DMA_Channel = DMA_Channel_3;  //通道选择
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(TIM5->CCR1);//DMA外设地址
  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)val;//DMA 存储器0地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//存储器到外设模式
  DMA_InitStructure.DMA_BufferSize = FILTER_NUM;//数据传输量
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;//外设数据长度:8位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;//存储器数据长度:8位
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用普通模式
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;//中等优先级
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;         
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发单次传输
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发单次传输
  DMA_Init(DMA1_Stream5, &DMA_InitStructure);//初始化DMA Stream

        TIM_DMAConfig(TIM5,TIM_DMABase_CCR1,TIM_DMABurstLength_16Bytes);
        TIM_DMACmd(TIM5,TIM_DMA_CC1,ENABLE);
        //如果需要DMA中断则如下面所示
                NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream5_IRQn;                                                                        //使能TIM中断
                NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;                //抢占优先级
                NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;                                                //子优先级
                NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                                                        //使能中断
        NVIC_Init(&NVIC_InitStructure);
        DMA_ITConfig(DMA1_Stream5,DMA_IT_TC,ENABLE);        
        //开启DMA传输
        DMA_Cmd(DMA1_Stream5, ENABLE);  
}
void        DMA1_Stream5_IRQHandler(void)
{
        DMA_ClearITPendingBit(DMA1_Stream5,DMA_IT_TCIF5);
}
[/mw_shl_code]


@xkwy大神在回复中提出了几个改进意见,列出如下:
1.可以设定仅有通道2进行下降沿捕获并触发中断,而通道1捕获上升沿不触发中断。在中断函数当中,一次读取CCR1CCR2。这样可以节省大量时间。
2.可以先进行一次测量,根据测量值改变预分频值PSC,从而提高精度
3.间隔采样。例如每100ms采样10ms.
这样的改进应当能够将最高采样频率增加到2M.但是频率的进一步提高仍然不可能。因为这时的主要矛盾是中断函数时间过长,导致CPU还在处理中断的时候这一次周期就结束了,使得最终测量到的频率为真实频率的整数倍左右。示意图如下:
QQ截图20160827222601.jpg


因此,高频时仍然推荐以下方法。



思路四:使用外部时钟计数器

这种方法是我这几天回答问题时推荐的方法。思路是配置两个定时器,定时器a设置为外部时钟计数器模式,定时器b设置为定时器(比如50ms溢出一次,也可以用软件定时器),然后定时器b中断函数中统计定时器a在这段时间内的增量,简单计算即可。

代码:
[mw_shl_code=c,true]//TIM7->100ms
//TIM2_CH2->PB3
void TIM_Cnt_Init(void)
{
        GPIO_InitTypeDef GPIO_InitStructure;
        TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
        
        TIM_DeInit(TIM2);
        TIM_DeInit(TIM7);
        
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2|RCC_APB1Periph_TIM7,ENABLE);          //TIM2时钟使能   
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);         //使能PORTA时钟        
//IO        
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //GPIOA0
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;        //速度100MHz
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //下拉
        GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化PA0
        
        GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_TIM2); //PA0复用位定时器5
//TIM2配置
        TIM_TimeBaseStructure.TIM_Prescaler=0;  //定时器分频
        TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
        TIM_TimeBaseStructure.TIM_Period=0xFFFFFFFF;   //自动重装载值
        TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
        TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);
        
        TIM_TIxExternalClockConfig(TIM2,TIM_TIxExternalCLK1Source_TI2,TIM_ICPolarity_Rising,0);//外部时钟源
//TIM7        100ms        
        TIM_TimeBaseStructure.TIM_Prescaler=18000-1;  //定时器分频
        TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
        TIM_TimeBaseStructure.TIM_Period=1000-1;   //自动重装载值
        TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
        TIM_TimeBaseInit(TIM7,&TIM_TimeBaseStructure);
//中断
  NVIC_InitStructure.NVIC_IRQChannel = TIM7_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//抢占优先级3
        NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;                //子优先级3
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                        //IRQ通道使能
        NVIC_Init(&NVIC_InitStructure);        //根据指定的参数初始化VIC寄存器、
        TIM_ITConfig(TIM7,TIM_IT_Update,ENABLE);//允许更新中断 ,允许CC1IE捕获中断        

        TIM_Cmd(TIM7,ENABLE );         //使能定时器5
  TIM_Cmd(TIM2,ENABLE );         //使能定时器5        
}
u32 TIM7_LastCnt;
//频率为TIM_ExtCntFreq
void TIM7_IRQHandler(void)
{
        char        str[32];
        TIM_ExtCntFreq=(TIM2->CNT-TIM7_LastCnt)*(1/SAMPLE_PERIOD);// SAMPLE_PERIOD为采样周期0.1s
        sprintf(str,"%3.3f",TIM_ExtCntFreq/1000.0);//必须加这一句,莫明其妙
        TIM7_LastCnt=TIM2->CNT;
        TIM_ClearITPendingBit(TIM7,TIM_IT_Update);
}
[/mw_shl_code]

缺点:

1.无法测量占空比,高频的占空比测量方法见下文。

2.在频率较低的情况下,测量精度不如思路3(因为测量周期为100ms,此时如果脉冲周期是200ms……)。

3.输入幅值必须超过3V 。如果不够或者超出,需要加入前置放大器。

总结:这种方法精度很高,实测在2MHz之下误差为30Hz也就是0.0015%(由中断服务程序引发,可以使用线性补偿修正),在25MHz之下也是误差30Hz左右(没法达到更高的原因是波形发生器的最大输出频率是25MHz^_^)。同时,从根本上解决了中断频率过高的问题。而由于低频的问题,建议:在低频时,或者加大采样间隔(更改TIM7的周期),或者采用思路3的输入捕获。

此外,还有一个莫名其妙的问题就是,中断当中如果不加入sprintf(str,"%3.3f",TIM_ExtCntFreq/1000.0)这一句,TIM_ExtCntFreq就始终为0 。我猜测是优化的问题,但是加入volatile也没有用,时间不够就没有理睬了。


思路五:ADC采样测量(概率测量法)

一般的高端示波器,测量频率即是这种方法。简而言之,高速采样一系列数据,然后通过频谱分析(例如快速傅里叶变换FFT),获得频率。F4有着FPUDSP指令,计算速度上可以接受。但是ADC的采样频率远远达不到。官方手册上声明,在三通道交替采样+DMA之下,最高可以达到8.4M的采样率。然而,根据香农采样定理,采样频率至少要达到信号的2倍。2M信号和8.4M的采样率,即使能够计算,误差也无法接受。所以,ADC采样是无法测量频率特别是高频频率的。

但是,无法测量频率,却可以测量占空比,乃至超调量和上升时间(信号从10%幅值上升到90%的时间)!原理也很简单,大学概率课上都说过这个概率基本原理:
QQ截图20160826223025.jpg

当采样数n趋于无穷时,事件A的概率即趋近于统计的频率。所以,当采样数越大,则采样到的高电平占样本总数的频率即趋近于概率——占空比!
QQ截图20160826224036.jpg
因此,基本思路即是等间隔(速度无所谓,但必须是保证等概率采样)采样,并将这些数据存入一个数组,反复刷新。这样,可以在任意时间对数组中数据进行统计,获得占空比数据。

以下是代码,使用了三通道8ADC+DMA。理论上,采用查询法也是可以的。
[mw_shl_code=c,true]//ADC1-CH13-PC3
//DMA2-CH0-STREAM0
  #define ADCx                     ADC1
  #define ADC_CHANNEL              ADC_Channel_13
  #define ADCx_CLK                 RCC_APB2Periph_ADC1
  #define ADCx_CHANNEL_GPIO_CLK    RCC_AHB1Periph_GPIOC
  #define GPIO_PIN                 GPIO_Pin_3
  #define GPIO_PORT                GPIOC
  #define DMA_CHANNELx             DMA_Channel_0
  #define DMA_STREAMx              DMA2_Stream0
  #define ADCx_DR_ADDRESS          ((uint32_t)&(ADCx->DR))//((uint32_t)0x4001224C)
void        ADC_DMAInit(void)
{
  ADC_InitTypeDef       ADC_InitStructure;
  ADC_CommonInitTypeDef ADC_CommonInitStructure;
  DMA_InitTypeDef       DMA_InitStructure;
  GPIO_InitTypeDef      GPIO_InitStructure;

  /* Enable ADCx, DMA and GPIO clocks ****************************************/
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
  RCC_AHB1PeriphClockCmd(ADCx_CHANNEL_GPIO_CLK, ENABLE);  
  RCC_APB2PeriphClockCmd(ADCx_CLK, ENABLE);
  

  /* DMA2 Stream0 channel2 configuration **************************************/
  DMA_InitStructure.DMA_Channel = DMA_CHANNELx;  
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADCx_DR_ADDRESS;
  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&(ADC_DATAPOOL[0]);
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
  DMA_InitStructure.DMA_BufferSize = ADC_POOLSIZE;
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;         
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  DMA_Init(DMA_STREAMx, &DMA_InitStructure);
  DMA_Cmd(DMA_STREAMx, ENABLE);

  /* Configure ADC3 Channel7 pin as analog input ******************************/
  GPIO_InitStructure.GPIO_Pin = GPIO_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
  GPIO_Init(GPIO_PORT, &GPIO_InitStructure);

  /* ADC Common Init **********************************************************/
  ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
  ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
  ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
  ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles;
  ADC_CommonInit(&ADC_CommonInitStructure);

  /* ADC3 Init ****************************************************************/
  ADC_InitStructure.ADC_Resolution = ADC_Resolution_8b;
  ADC_InitStructure.ADC_ScanConvMode = DISABLE;
  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  ADC_InitStructure.ADC_NbrOfConversion = 1;
  ADC_Init(ADCx, &ADC_InitStructure);

  /* ADC3 regular channel7 configuration **************************************/
  ADC_RegularChannelConfig(ADCx, ADC_CHANNEL, 1, ADC_SampleTime_480Cycles);

/* Enable DMA request after last transfer (Single-ADC mode) */
  ADC_DMARequestAfterLastTransferCmd(ADCx, ENABLE);

  /* Enable ADC3 DMA */
  ADC_DMACmd(ADCx, ENABLE);

  /* Enable ADC3 */
  ADC_Cmd(ADCx, ENABLE);
}
主程序:
                                        for(j=0;j<ADC_POOLSIZE;j++)
                                        {
                                                if(ADC_DATAPOOL[j]>0x01)
                                                        posicnt++;
                                        }
                                        duty=100*posicnt/(float)(ADC_POOLSIZE)+0.1f;//线性补偿
[/mw_shl_code]

缺点:

1.精度低:实测2MHz下误差约1.3%,低频时无法统计(比如,频率10Hz,而ADC采样时间50ms。这时如果采样时间中刚好全是高电平,占空比为1……)。

2.内存占用大:数据池大小为65536,占用了64KB内存

3.有响应延迟:测量出来的是“平均占空比”而非“瞬时占空比”。由于我测试时使用的是波形发生器,输出波形相当稳定(1W+的价格毕竟是有它的道理的……),实际应用当中一般不能够达到这样的水平,势必带来响应延迟(准确说应该是采样系统积分惯性越大)。

4.幅值过低(0.3V)无法测量,过高则超过ADC允许最大值。所以必须视情况使用不同的前置放大器。

实际上使用时如何取舍,就需要看实际情况了。毕竟,这只是低成本下的解决方案而已。



综上,对这几种方法做一个总结:

外部中断:编写容易,通用性强。缺点是中断进入频繁,误差大。

PWM输入:全硬件完成,CPU负载小,编写容易。缺点是不稳定,误差大。

输入捕获:可达到约400kHz。低频精度高,10Hz可达到0.01%以下,400kHz也有3%。缺点是中断频繁,无法测量高频,幅值必须在3.3~5V之间。

外部时钟计数器(首选):可达到非常高的频率(理论上应当是90MHz)和非常低的误差(2MHz下为0.0015%且可线性补偿)。缺点是低频精度较低,同样幅值必须在3.3~5V之间。

ADC采样频率测量法:难以测量频率,高频下对占空比、上升时间有可以接受的测量精度(2MHz下约1.3%),低频下无法测量。幅值0.3~3.3V,加入前置放大则幅值随意。

ADC采样频谱分析:高端示波器专用,STM32弃疗。


我采用的方法是:首先ADC测量幅值并据此改变前置放大器放大倍数,调整幅值为3.3V ,同时测量得到参考占空比。而后使用外部时钟计数器测量得到频率,如果较高(>10000)则确认为频率数据,同时ADC测量占空比确认为占空比数据。否则再使用输入捕获方法测量得到频率、占空比数据。

对于各个方法存在的线性误差,使用了线性补偿来提高精度。一般情况下,使用存储在ROM中的数据作为参数,当需要校正时,采用如下校正思路:

波形发生器生成一些预设参数波形(例如10Hz10%100K50%2M90%……),在不同区间内多次测量得到数据,随后以原始数据为x,真实数据为y,去除异常数据之后,做y=f(x)的线性回归,并取相关系数最高的作为新的参数,同时存储在ROM当中。

我认为,我的这篇文章,应当是很全面了。当然,限于水平,存在着未完善和不正确的地方,也欢迎指正。

转载请注明作者——倾夜·陨灭星尘或openedv开源电子网的yyx112358

正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

3

主题

548

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1383
金钱
1383
注册时间
2015-2-3
在线时间
197 小时
 楼主| 发表于 2016-8-29 19:37:07 | 显示全部楼层
xkwy 发表于 2016-8-28 12:46
你理解错我的意思了。

我是说,某一时刻,启动捕捉,CCR1里存放上升沿,CCR2存放下降沿,捕捉到之后立 ...

一个上升,一个下降,这不是只能够获得一次高电平或者低电平时间吗?频率没法测啊
回复 支持 1 反对 1

使用道具 举报

5

主题

78

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
370
金钱
370
注册时间
2016-5-7
在线时间
63 小时
发表于 2016-8-27 18:28:27 | 显示全部楼层
xkwy 发表于 2016-8-27 17:28
高频不规则信号,有个通俗的名称:“噪声”,

如果不是噪声呢,我问的不是这个
回复 支持 1 反对 0

使用道具 举报

0

主题

4

帖子

0

精华

初级会员

Rank: 2

积分
60
金钱
60
注册时间
2017-5-23
在线时间
14 小时
发表于 2017-7-14 11:02:02 | 显示全部楼层
看完觉得有两个问题:
(1)进入中断时间太频繁导致不准(2)使用硬件测量不准
是否考虑直接轮询加通讯?
轮询就是简单判断IO高低值进而记录上升时间和下降时间记录数据。(轮训时间误差应该可以计算出来)
通讯是解决除了轮训任务外的任何时间影响,两个芯片解决这问题。
回复 支持 0 反对 1

使用道具 举报

3

主题

548

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1383
金钱
1383
注册时间
2015-2-3
在线时间
197 小时
 楼主| 发表于 2016-8-26 22:45:43 | 显示全部楼层
发上来格式有点乱了,发个PDF 使用STM32测量频率和占空比的几种方法.pdf (897.55 KB, 下载次数: 3484)
回复 支持 反对

使用道具 举报

3

主题

347

帖子

3

精华

金牌会员

Rank: 6Rank: 6

积分
2076
金钱
2076
注册时间
2014-12-19
在线时间
709 小时
发表于 2016-8-26 23:33:31 | 显示全部楼层
总结得不错!
回复 支持 反对

使用道具 举报

3

主题

548

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1383
金钱
1383
注册时间
2015-2-3
在线时间
197 小时
 楼主| 发表于 2016-8-26 23:42:39 | 显示全部楼层

谢谢,如果对我在文中提到的问题,比如PWM输入、DMA传输定时器寄存器值等问题有了解,希望不吝指教
回复 支持 反对

使用道具 举报

6

主题

1097

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
3571
金钱
3571
注册时间
2014-12-2
在线时间
365 小时
发表于 2016-8-27 07:32:08 | 显示全部楼层
1、你前期出问题,可能与输入滤波有关,
如果你待测信号不超过2MHz,则输入使用【0011: fSAMPLING=fCK_INT, N=8】滤波可能是一个不错的选择。

ST的这些功能经常有这种问题

我没遇到过,STM32的定时器各种模式我基本都使用过。

2、你输入捕捉效率太低了,
参考一下这个帖子:http://bbs.hpuedd.com/forum.php? ... &extra=page%3D1
另外,如果真的是高频的话,程序中应当动态根据测得的频率值来修改PSC,以实现动态精度适应
还有,输入捕捉真心不需要每次都进中断,1MHz的信号,你每秒捕捉100万次,值都是一样有意义吗?是否可以每秒捕捉10次取平均值?把细节处理好就行了。

坚决不用寄存器,拒绝重复造轮子。
回复 支持 反对

使用道具 举报

27

主题

711

帖子

0

精华

版主

Rank: 7Rank: 7Rank: 7

积分
11922
金钱
11922
注册时间
2015-11-5
在线时间
2086 小时
发表于 2016-8-27 10:12:08 | 显示全部楼层
本帖最后由 FreeRTOS 于 2016-8-27 10:13 编辑

感谢楼主无私的精神,对于高频脉冲信号的频率测量,当然是首选思路4的方案,在电子测量相关书籍里面提及的也是这种测量方法
以前做过一个项目测量多谐振荡电路的输出波形也是采用这种方法,不过这方法只能测量频率没法测量占空比

回复 支持 反对

使用道具 举报

5

主题

78

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
370
金钱
370
注册时间
2016-5-7
在线时间
63 小时
发表于 2016-8-27 10:50:50 | 显示全部楼层
xkwy 发表于 2016-8-27 07:32
1、你前期出问题,可能与输入滤波有关,
如果你待测信号不超过2MHz,则输入使用【0011: fSAMPLING=fCK_INT ...

第一点赞同,第二点每秒捕获10次取平均虽然很大程度上减少了上升沿和下降沿中断的处理时间,是不是又引入了新的误差?这相当于降低了采样率,每秒十次又需要另一个定时器产生时基,那么高的频率,采集瞬间也不能保证是高电平还是低电平吧?我自己也不太明白,想着有些懵圈,求指导!
回复 支持 反对

使用道具 举报

72

主题

2711

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3505
金钱
3505
注册时间
2014-8-4
在线时间
696 小时
发表于 2016-8-27 15:37:34 | 显示全部楼层
楼主用心了,mark一下,晚上抽空学一下
以我资质之鲁钝,当尽平心静气、循序渐进、稳扎稳打之力。
回复 支持 反对

使用道具 举报

6

主题

1097

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
3571
金钱
3571
注册时间
2014-12-2
在线时间
365 小时
发表于 2016-8-27 16:16:39 | 显示全部楼层
destiny、 发表于 2016-8-27 10:50
第一点赞同,第二点每秒捕获10次取平均虽然很大程度上减少了上升沿和下降沿中断的处理时间,是不是又引入 ...

我说的第二点是针对使用输入捕捉方式测脉宽和占空比的。

还有,输入捕捉为什么非得用中断?查询不行么?为什么每次捕捉到的值都要保存?

想想示波器面对MHz级的输入信号,难道它采集输入信号也是不间断的吗?


那么高的频率,采集瞬间也不能保证是高电平还是低电平吧?

不知所云。
坚决不用寄存器,拒绝重复造轮子。
回复 支持 反对

使用道具 举报

5

主题

78

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
370
金钱
370
注册时间
2016-5-7
在线时间
63 小时
发表于 2016-8-27 16:28:11 | 显示全部楼层
xkwy 发表于 2016-8-27 16:16
我说的第二点是针对使用输入捕捉方式测脉宽和占空比的。

还有,输入捕捉为什么非得用中断?查询不行么 ...

刚开始就想着信号频率在变化了,所以没想那么多,如果信号频率变化缓慢可以按照你说的方式去捕获。至于示波器,测量MHz的不规则信号不是不间断采样的?不了解示波器采样原理,求教
回复 支持 反对

使用道具 举报

6

主题

1097

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
3571
金钱
3571
注册时间
2014-12-2
在线时间
365 小时
发表于 2016-8-27 17:28:47 | 显示全部楼层
destiny、 发表于 2016-8-27 16:28
刚开始就想着信号频率在变化了,所以没想那么多,如果信号频率变化缓慢可以按照你说的方式去捕获。至于示 ...

高频不规则信号,有个通俗的名称:“噪声”,
坚决不用寄存器,拒绝重复造轮子。
回复 支持 反对

使用道具 举报

5

主题

78

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
370
金钱
370
注册时间
2016-5-7
在线时间
63 小时
发表于 2016-8-27 18:31:29 | 显示全部楼层
本帖最后由 destiny、 于 2016-8-27 18:35 编辑
xkwy 发表于 2016-8-27 17:28
高频不规则信号,有个通俗的名称:“噪声”,

那么上MHz的正弦,SPWM这样的周期的呢
回复 支持 反对

使用道具 举报

5

主题

78

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
370
金钱
370
注册时间
2016-5-7
在线时间
63 小时
发表于 2016-8-27 18:34:33 | 显示全部楼层
xkwy 发表于 2016-8-27 17:28
高频不规则信号,有个通俗的名称:“噪声”,

想做个简易示波器,大神求指导
回复 支持 反对

使用道具 举报

9

主题

538

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
3371
金钱
3371
注册时间
2015-1-7
在线时间
794 小时
发表于 2016-8-27 21:57:10 | 显示全部楼层
赞一个,感谢分享
机器生汇编,汇编生B,B生C,C生万物.... 经过长期对C语言的研究,目前只有两个方面不懂:这也不懂,那也不懂
https://github.com/ianhom
回复 支持 反对

使用道具 举报

3

主题

548

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1383
金钱
1383
注册时间
2015-2-3
在线时间
197 小时
 楼主| 发表于 2016-8-27 22:33:19 | 显示全部楼层
xkwy 发表于 2016-8-27 07:32
1、你前期出问题,可能与输入滤波有关,
如果你待测信号不超过2MHz,则输入使用【0011: fSAMPLING=fCK_INT ...

谢谢大神指教,我已经在文中添加了有关内容。
不过还是想讨论一下,使用输入捕获在高频时出现的问题是测量到的频率为真实频率的整数倍分之一左右。原因应当主要是因为中断函数时间过长,导致CPU还在处理中断的时候这一次周期就结束了,从而测量出整数倍分之一的结果。换言之,改变PSC和每秒捕捉10次并不能从根本上解决高频时的误差问题
回复 支持 反对

使用道具 举报

3

主题

548

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1383
金钱
1383
注册时间
2015-2-3
在线时间
197 小时
 楼主| 发表于 2016-8-27 22:37:57 | 显示全部楼层
本帖最后由 yyx112358 于 2016-8-27 22:40 编辑
destiny、 发表于 2016-8-27 18:34
想做个简易示波器,大神求指导

示波器主要还是应当需要使用ADC测量的吧,去噪这些就需要我文中说的频谱分析、信号变换这些数学算法,还有就是硬件上的合理设计。至于上M的正弦我想STM32是无能为力了,我这里说的方法主要是针对脉冲信号的频率和占空比测量的。想要用几十块钱的芯片做出几千元示波器的功能毕竟是不现实的(高端示波器有的甚至是X86芯片+电脑主板+采集卡的设计)
安富莱有一个示波器教程,可以去这里看http://bbs.armfly.com/read.php?tid=3886
回复 支持 反对

使用道具 举报

5

主题

78

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
370
金钱
370
注册时间
2016-5-7
在线时间
63 小时
发表于 2016-8-27 22:49:29 | 显示全部楼层
yyx112358 发表于 2016-8-27 22:37
示波器主要还是应当需要使用ADC测量的吧,去噪这些就需要我文中说的频谱分析、信号变换这些数学算法,还 ...

LZ 的这个资料可以
回复 支持 反对

使用道具 举报

6

主题

1097

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
3571
金钱
3571
注册时间
2014-12-2
在线时间
365 小时
发表于 2016-8-28 12:46:54 | 显示全部楼层
yyx112358 发表于 2016-8-27 22:33
谢谢大神指教,我已经在文中添加了有关内容。
不过还是想讨论一下,使用输入捕获在高频时出现的问题是测 ...

你理解错我的意思了。

我是说,某一时刻,启动捕捉,CCR1里存放上升沿,CCR2存放下降沿,捕捉到之后立即关闭捕捉。

然后CCR1和CCR2就存放着波形信息。

主要是怕一次可能有偶然误差,多来几次。取平均值。


注意,如果处理好细节,这过程中捕捉是自动进行的,不需要中断干预。更不需要再中断里迅速切换捕捉边沿
坚决不用寄存器,拒绝重复造轮子。
回复 支持 反对

使用道具 举报

3

主题

25

帖子

0

精华

初级会员

Rank: 2

积分
135
金钱
135
注册时间
2016-5-22
在线时间
21 小时
发表于 2016-8-28 14:36:50 | 显示全部楼层
厉害!
回复 支持 反对

使用道具 举报

18

主题

238

帖子

3

精华

金牌会员

Rank: 6Rank: 6

积分
1823
金钱
1823
注册时间
2014-8-5
在线时间
211 小时
发表于 2016-8-28 17:49:16 | 显示全部楼层
谢谢大神的分享,讲的非常全,实在佩服
回复 支持 反对

使用道具 举报

6

主题

1097

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
3571
金钱
3571
注册时间
2014-12-2
在线时间
365 小时
发表于 2016-8-29 20:00:23 | 显示全部楼层
yyx112358 发表于 2016-8-29 19:37
一个上升,一个下降,这不是只能够获得一次高电平或者低电平时间吗?频率没法测啊

抱歉没有仔细考虑细节,我当初用的时候频率也不高20k以内的,为了快速完成任务也就直接用中断了。
坚决不用寄存器,拒绝重复造轮子。
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165309
金钱
165309
注册时间
2010-12-1
在线时间
2108 小时
发表于 2016-8-30 11:05:13 | 显示全部楼层
不错,cool
回复 支持 反对

使用道具 举报

2

主题

9

帖子

0

精华

新手上路

积分
47
金钱
47
注册时间
2015-5-21
在线时间
3 小时
发表于 2016-8-30 19:21:24 | 显示全部楼层
厉害,顶一个
回复 支持 反对

使用道具 举报

2

主题

394

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
2085
金钱
2085
注册时间
2012-5-8
在线时间
312 小时
发表于 2016-8-31 08:40:18 | 显示全部楼层
学习!
回复 支持 反对

使用道具 举报

3

主题

548

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1383
金钱
1383
注册时间
2015-2-3
在线时间
197 小时
 楼主| 发表于 2016-8-31 19:43:09 | 显示全部楼层

哈哈,谢谢原子哥。第一次发教程就精华了好激动!!
回复 支持 反对

使用道具 举报

0

主题

17

帖子

0

精华

新手上路

积分
47
金钱
47
注册时间
2014-3-20
在线时间
5 小时
发表于 2016-8-31 20:12:05 来自手机 | 显示全部楼层
很有研究精神啊
回复 支持 反对

使用道具 举报

1

主题

6

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
281
金钱
281
注册时间
2016-8-11
在线时间
29 小时
发表于 2016-9-1 15:43:59 | 显示全部楼层
我也是今年参加的电赛,这道题没做出来,现在认真看下楼主的帖子,好好学习下,谢谢楼主!!
回复 支持 反对

使用道具 举报

2

主题

27

帖子

0

精华

新手上路

积分
49
金钱
49
注册时间
2016-7-14
在线时间
19 小时
发表于 2016-9-19 18:14:27 | 显示全部楼层
ianhom 发表于 2016-8-27 21:57
赞一个,感谢分享

汇编生b的b是啥
回复 支持 反对

使用道具 举报

6

主题

29

帖子

0

精华

初级会员

Rank: 2

积分
83
金钱
83
注册时间
2013-9-7
在线时间
24 小时
发表于 2016-12-3 18:05:24 | 显示全部楼层
mark,回去认真看看!
回复 支持 反对

使用道具 举报

10

主题

147

帖子

0

精华

高级会员

Rank: 4

积分
602
金钱
602
注册时间
2015-7-11
在线时间
94 小时
发表于 2016-12-8 21:45:29 | 显示全部楼层
mark,学习一下!!!
回复 支持 反对

使用道具 举报

43

主题

137

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1299
金钱
1299
注册时间
2016-4-21
在线时间
262 小时
发表于 2016-12-9 20:48:18 | 显示全部楼层
如果用中断计数,他的定时统计怎么算
回复 支持 反对

使用道具 举报

20

主题

86

帖子

0

精华

初级会员

Rank: 2

积分
163
金钱
163
注册时间
2016-11-19
在线时间
44 小时
发表于 2016-12-9 21:52:31 | 显示全部楼层
谢谢楼主
回复 支持 反对

使用道具 举报

1

主题

18

帖子

0

精华

初级会员

Rank: 2

积分
171
金钱
171
注册时间
2016-5-22
在线时间
66 小时
发表于 2016-12-18 15:39:55 | 显示全部楼层
赞一个
回复 支持 反对

使用道具 举报

70

主题

6697

帖子

0

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
12687
金钱
12687
注册时间
2012-11-26
在线时间
3710 小时
发表于 2016-12-18 16:29:43 | 显示全部楼层
mark一下  有需求再详看
学无止境
回复 支持 反对

使用道具 举报

1

主题

7

帖子

0

精华

新手上路

积分
29
金钱
29
注册时间
2016-5-4
在线时间
5 小时
发表于 2016-12-19 09:31:33 | 显示全部楼层
做示波器有用
回复 支持 反对

使用道具 举报

42

主题

297

帖子

0

精华

高级会员

Rank: 4

积分
994
金钱
994
注册时间
2016-7-22
在线时间
318 小时
发表于 2017-1-16 13:13:31 | 显示全部楼层
感谢楼主 学习一下
回复 支持 反对

使用道具 举报

4

主题

346

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
3657
金钱
3657
注册时间
2016-2-21
在线时间
542 小时
发表于 2017-1-16 13:29:27 | 显示全部楼层
非常厉害,前排支持。
回复 支持 反对

使用道具 举报

9

主题

36

帖子

0

精华

初级会员

Rank: 2

积分
87
金钱
87
注册时间
2015-3-28
在线时间
13 小时
发表于 2017-1-16 19:18:13 | 显示全部楼层
请问按照你的程序能测到200K左右的占空比吗?比如我一个200K的信号 ,能把误差测到1K以内吗?
回复 支持 反对

使用道具 举报

0

主题

11

帖子

0

精华

初级会员

Rank: 2

积分
126
金钱
126
注册时间
2017-1-26
在线时间
16 小时
发表于 2017-1-26 17:07:00 | 显示全部楼层
yyx112358 发表于 2016-8-26 22:45
发上来格式有点乱了,发个PDF

基于stm32的频率计需要准备哪些东东?我现在手上有一块stm32F4的开发板
回复 支持 反对

使用道具 举报

0

主题

11

帖子

0

精华

初级会员

Rank: 2

积分
126
金钱
126
注册时间
2017-1-26
在线时间
16 小时
发表于 2017-2-13 20:39:14 | 显示全部楼层
请问怎样用AD采样,编写编码来分析判断信号源输入的波形种类(如正弦波、方波、三角波)?
回复 支持 反对

使用道具 举报

3

主题

548

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1383
金钱
1383
注册时间
2015-2-3
在线时间
197 小时
 楼主| 发表于 2017-2-16 16:36:45 | 显示全部楼层
白夔草 发表于 2017-2-13 20:39
请问怎样用AD采样,编写编码来分析判断信号源输入的波形种类(如正弦波、方波、三角波)?

做傅里叶变换,然后看频域分量符合哪种波形
回复 支持 反对

使用道具 举报

0

主题

11

帖子

0

精华

初级会员

Rank: 2

积分
126
金钱
126
注册时间
2017-1-26
在线时间
16 小时
发表于 2017-2-16 19:08:44 | 显示全部楼层
yyx112358 发表于 2017-2-16 16:36
做傅里叶变换,然后看频域分量符合哪种波形

请问有没有相关例子或资料?QQ748550631.我还是不太明白整个过程(Ps:初学者)
回复 支持 反对

使用道具 举报

1

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
58
金钱
58
注册时间
2017-1-22
在线时间
10 小时
发表于 2017-2-17 13:43:46 | 显示全部楼层
mark一下
回复 支持 反对

使用道具 举报

7

主题

15

帖子

0

精华

新手上路

积分
20
金钱
20
注册时间
2016-7-21
在线时间
15 小时
发表于 2017-3-12 20:07:12 | 显示全部楼层
嗯,好像那个sprintf是将测量的数据重映射到一个显示用的数组中,如果不用的话显示的数组是空数组默认为零。
回复 支持 反对

使用道具 举报

2

主题

6

帖子

0

精华

新手上路

积分
27
金钱
27
注册时间
2017-4-20
在线时间
5 小时
发表于 2017-4-20 16:54:44 | 显示全部楼层
yyx112358 发表于 2016-8-26 22:45
发上来格式有点乱了,发个PDF

能不能来一个频率测量的电路图?       chenkds@163.com   
回复 支持 反对

使用道具 举报

17

主题

329

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
2530
金钱
2530
注册时间
2016-3-6
在线时间
392 小时
发表于 2017-4-21 09:59:18 | 显示全部楼层
mark,学习了
路漫漫其修远兮,吾将上下而求索。
回复 支持 反对

使用道具 举报

1

主题

44

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
256
金钱
256
注册时间
2017-3-6
在线时间
35 小时
发表于 2017-4-21 10:53:34 | 显示全部楼层
最近正好有个测占空比的项目,学习了~
回复 支持 反对

使用道具 举报

0

主题

7

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
260
金钱
260
注册时间
2016-6-14
在线时间
78 小时
发表于 2017-4-21 17:26:55 | 显示全部楼层
不错不错
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-22 11:17

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表