stm32之DDS及FFT

来源:互联网 发布:java static volatile 编辑:程序博客网 时间:2024/05/21 17:33

DDS说明




  • DDS整个过程大概如图所示,简单来说就是写做好一个正弦表存入ROM中,然后根据定时器依次输出。需要注意的是频率和累加器。
  • 正弦表我用python写了一个(4096点,3.3V电压,16位自制DA,如用STM32的12位DA只需要改一下系数)
import mathfor i in range(0,4096):        print int(19680*(1.7+1.5*(math.sin(2*math.pi*i/4095)))),raw_input('Enter a word')
  • 对于其他如方波及锯齿波,我是先用STM32根据占空比等参数产生数据放入一个变量中,然后再依次输出。

FFT说明

  • DSP2.0的库中就有FFT的库,有64、256和1024点的FFT。点越多精度越高。
  • 根据香农采样定理,如果需要得出1KHz的波形,就至少要2KHzd的采样频率,最好过4倍。所以采样频率觉得了分辨的最大频率。
  • 精度与最大频率之间的关系:FFT之后会得到一个数组,包括实部和虚部。其中模值就是幅值的大小。而数组下标就代表了频率。0是直流分量。1就是采样频率/点数的频率。比如1024Hz采样,1024点FFT,那么精度就是1Hz。1就代表1Hz的幅值。这样一来分辨最大的只有512Hz。所以要精度就得降低最大频率,除非增加FFT的点数,但需要自己写了。
  • 还有一个问题:只有精度值的整数倍的频率经过FFT之后幅值才是正确的。也就是说如果4096Hz采样,1024点FFT,也就是频率是4的倍数才能正确计算出幅值来。
  • DSP库的使用很简单,采样数据放入数组,FFT之后得到输出数组。
  • DSP库的下载请点击。
  • 2012山东省电子设计竞赛源程序下载请点击。DDS输出然后FFT得到结果。Ps.以前的程序结构写的有点乱

出现的问题

  • 串口有很多的中断,不用的需要把它关掉,不然会总是进入中断出不来。
  • 关于DDS累加器其实可以单独采用一个32位的变量,这样精度可以更高,然后只要移位到需要位数的累加器中就能控制频率了。而且这个32位的变量不会以为数据的点数而改变。
u32 a1,b1;//const u32 fmin=429497; //精度设为1HZ,此时定时器频率为10KHz,算出1Hz的fmin=2^32/10000,这个与数据的点数无关const u32 fmin=42950;void TIM2_IRQHandler(void){if(TIM_GetFlagStatus(TIM2,TIM_FLAG_Update)==SET){TIM_ClearFlag(TIM2,TIM_FLAG_Update);  /*清楚中断标志位*/a1 +=F1*fmin;b1 +=F2*fmin;rate1=a1>>20;rate2=b1>>20;DAC_SetDualChannelData(DAC_Align_12b_R,SINWAVE[rate1]*A1,SINWAVE[rate2]*A2);}  }


  • 对于芯片的资料一定要先认真看一遍,要注意一下一些特别的地方。这样才不会浪费时间。LM358运放的最大输出电压为供电电压VCC-1.5v,还有单电源与双电源供电的情况也不一样。
  • 滤波电路其实在高频下才是必须的,不过应该能使波形更平滑。
  • TI公司ST公司的DSP库中FFT算法的结果不必像Matlab一样除以N或N/2,它的结果就代表了实际的值。但如果是AD采样进来的数据,还要注意一下转换成电压值。还有那个例程中那个算幅值的函数,正如网上所说一样,对于直流分量是多乘了一个2,还有就是对于AD采进来的数由于没有负数,那个函数也能简化一下直接求模就行。
void PowerMag(u16 nfill){  s16 lX,lY;  for (i=0; i < nfill; i++)  {    lX= (lBUFOUT[i]<<16)>>16; /* sine_cosine --> cos */    lY= (lBUFOUT[i] >> 16);   /* sine_cosine --> sin *///lBUFMAG[i]=sqrt(lX*lX+lY*lY);    //全是正数的时候也可以简化    {         float X=  nfill*((float)lX)/32768;      float Y = nfill*((float)lY)/32768;      float Mag = sqrt(X*X+ Y*Y)/nfill;  if(i==0)  lBUFMAG[i] = (uint32_t)(Mag*32768);  else      lBUFMAG[i] = (uint32_t)(Mag*65536);    }      }}
  • 这个函数是求相位的,但是注意会有个“初始相位”,也就是0相位时的结果不是0,但如果计算同频是的相位差结果是正确的。
void PowerPhase(u16 nfill){int32_t lX,lY;for (i=0; i < nfill/2; i++){        lX= (lBUFOUT[i]<<16)>>16; /* 取低16bit,sine_cosine --> cos */        lY= (lBUFOUT[i] >> 16);   /* 取高16bit,sine_cosine --> sin */            {            float X=  NPT*((float)lX)/32768;            float Y = NPT*((float)lY)/32768;            float phase = atan(Y/X);             if (Y>=0)             {                if (X>=0)                  ;                else                  phase+=PI;               }             else             {                             if (X>=0)                  phase+=PI2;                                  else                   phase+=PI;                                 }                                           lBUFPHASE[i] = phase*180.0/PI;        }        }}


  • 还有一个非常非常重要的一点,注意各种变量的位数以及是否有符号。
  • AD与DA的输出是0~3.3V与0~4095(12位的情况下),要记得变换一下。还有DA的输出直接连到AD的输入,会把3.3V降到3.1V、0V升到0.2V。也就是上下各有0.2V左右的偏差,与Buffer是否使能有关,如果除去这个偏差,带负载能力会下降。
/* DAC channel1 Configuration */DAC_InitStructure.DAC_Trigger = DAC_Trigger_None ;DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;//DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_TriangleAmplitude_4095;/*如果是Disable上下会有0.2V的线性变化,可能这样带负载能力强 */DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;DAC_Init(DAC_Channel_1, &DAC_InitStructure);


  • ADC采样频率问题:如果不是定时器采样,频率的计算与分频(RCC_ADCCLKConfig)和采样周期(ADC_RegularChannelConfig)有关。
ADC_InitTypeDef  ADC_InitStructure;RCC_ADCCLKConfig(RCC_PCLK2_Div4);    //PCLK2 4分频=18MHz-----13.5个周期+12.5=26// 如果不是定时器控制采样频率为18/26MHzRCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1|RCC_APB2Periph_GPIOC,ENABLE);/* ADC1 Configuration ------------------------------------------------------*/ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;  //工作在独立模式ADC_InitStructure.ADC_ScanConvMode = DISABLE;      //单通道和多通道模式disable==》单ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;  //连续模式,但此模式 disableADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //由软件触发而不是外部触发ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //右对齐ADC_InitStructure.ADC_NbrOfChannel = 1; //通道数目为1ADC_Init(ADC1, &ADC_InitStructure);/* ADC1 regular channel14 configuration */ ADC_RegularChannelConfig(ADC1, adc_channle, 1, ADC_SampleTime_13Cycles5); //设置采样顺序和采样时间13.5个周期


  • 函数体写不写在头文件里,主要看会不会多次引用!(这句话是以前写的,请最好不要这么做!!!)
  • 关于多文件我想再说一些,以前对于多文件的编译都不是很理解。相关联的一些函数变量放在一个C文件,头文件里只声明而在C文件里定义。
  • 特别需要注意:变量的声明与定义int i这个其实是定义,它已经分配了内存空间,如果你又在其他地方多次引用这个头文件就会造成重复定义的错误。所以在头文件里用extern int i声明。
  • 对于那些不希望其他文件使用的函数与变量定义成static,同时只在C文件声明就好了。对于static局部变量在每次函数调用时也只初始化一次,且不随函数结束而销毁空间,其实就相当于全局变量。
  • 还有一个关键词是volatile,这是是说明这个变量也可能在其他文件改变值,请编译器不要优化,这对于底层编程很有必要。
  • 最后一点我也觉得是最重要一点就是,少用全局变量。