DDS说明
- DDS整个过程大概如图所示,简单来说就是写做好一个正弦表存入ROM中,然后根据定时器依次输出。需要注意的是频率和累加器。
- 正弦表我用python写了一个(4096点,3.3V电压,16位自制DA,如用STM32的12位DA只需要改一下系数)
import math
for 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/26MHz
RCC_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; //连续模式,但此模式 disable
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //由软件触发而不是外部触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //通道数目为1
ADC_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,这是是说明这个变量也可能在其他文件改变值,请编译器不要优化,这对于底层编程很有必要。
- 最后一点我也觉得是最重要一点就是,少用全局变量。