转载请注明出处:http://blog.csdn.net/ruoyunliufeng/article/details/38023431
通过前面的介绍我们知道,声音信号要通过AD转换,变成我们能够处理的数字信号,然后再交给FFT进行处理。
一.ADC转换
1.设置引脚
void GPIO_Init() // GPIO口的初始化 { P1M1 = B(00000011); //设置P1口模式 P1M0 = B(00000000); //设置P1口模式 只有1.0和1.1为开漏,用于AD P1 = B(00000011); P1ASF = B(00000011); //将P10,P11的IO设置为模拟输入功能; }
2.ADC初始化
void ADC_Init() // 集成ADC的初始化(官方函数) { ADC_RES = 0; //Clear previous result ADC_CONTR = ADC_POWER | ADC_SPEEDLL; //ADC_SPEEDLL,每次转换需要420个时钟周期。420*0.04=16.8us Delay(2); //ADC power-on and delay }
3.ADC转换
uchar GetADCResult(uchar ch) // 进行AD转换(官方函数) { ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ch | ADC_START; _nop_(); //Must wait before inquiry _nop_(); _nop_(); _nop_(); while (!(ADC_CONTR & ADC_FLAG));//Wait complete flag ADC_CONTR &= ~ADC_FLAG; //Close ADC return ADC_RES; //Return ADC result }
二.FFT
FFT(Fast Fourier Transformation),即为快速傅氏变换,是离散傅氏变换的快速算法,它是根据离散傅氏变换的奇、偶、虚、实等特性,对离散傅立叶变换的算法进行改进获得的。
1.实现的根本:时域到频域
从这张图我们清晰的看到,左面的就是我们输入的信号,右面就是输出到点阵上面的信号。中间的转换过程由FFT实现。
2.算法实现
float code iw[32]= { 1.000,0.000, /* 0.9952,-0.0980,*/ 0.9808,-0.1951, /*0.9569,-0.2903,*/ 0.9239,-0.3827, /*0.8819,-0.4714,*/ 0.8315,-0.5556, /*0.7730,-0.6344,*/ 0.7071,-0.7071, /* 0.6344,-0.7730,*/ 0.5556,-0.8315, /* 0.4714,-0.8819,*/ 0.3827,-0.9239, /*0.2903,-0.9569, */ 0.1951,-0.9808, /*0.0980,-0.9952,*/ 0.0,-1.0000, /*-0.0980,-0.9952,*/ -0.1951,-0.9808, /* -0.2903,0.9569,*/ -0.3827,-0.9239, /*-0.4714,-0.8819, */ -0.5556,-0.8315, /* -0.6344,-0.7730, */ -0.7071,-0.7071, /* -0.7730,-0.6344, */ -0.8315,-0.5556, /* -0.8819,-0.4714, */ -0.9239,-0.3827, /* -0.9569,-0.2903,*/ -0.9808,-0.1951, /*-0.9952,-0.0980 */ }; //偶数cos值 奇数sin值。 复常数码表 。历经几个小时终于尼玛明白了。。原因 就是计算器还要调弧度和角度模式。。好了这这就是将1的sin和cos值按照角度平均分然后得到的sin和cos值即为上表。目的是为了节约计算量。第二个cos(π/32)记住计算器弧度和角度的设置 void ee(struct compx b1,uchar b2) //复数乘法 { temp.real=b1.real*iw[2*b2]-b1.imag*iw[2*b2+1]; temp.imag=b1.real*iw[2*b2+1]+b1.imag*iw[2*b2]; } uint mypow(uchar nbottom,uchar ntop) //乘方函数 { uint result=1; uchar t; for(t=0;t<ntop;t++)result*=nbottom; return result; } void fft(struct compx *xin,uchar data N) //快速傅立叶变换 { uchar data fftnum,i,j,k,l,m,n,disbuff,dispos,dissec; data struct compx t; fftnum=N; //傅立叶变换点数 for(m=1;(fftnum=fftnum/2)!=1;m++);//求得M的值 N=64 所以M=6 也就是分解为6级 for(k=0;k<=N-1;k++) //码位倒置 { n=k; j=0; for(i=m;i>0;i--) //倒置 例如001变为100 { j=j+((n%2)<<(i-1)); //二进制除二取余的反过程。就得到了倒置十进制数 n=n/2; //倒置过程中处位和末位都不变即0和63都不变。此例中1对应32 } if(k<j){t=xin[1+j];xin[1+j]=xin[1+k];xin[1+k]=t;}//交换数据 , 因为从0开始,所以进行加1处理。例如xin[2]和xin[33]交换。即1与32交换 } //到此FFT码位倒置结束 for(l=1;l<=m;l++) //FFT运算 l为级数 { disbuff=mypow(2,l); //求得碟间距离 求得碟间距离 (乘方函数 pow(X,y)就是计算X的Y次方) dispos=disbuff/2; //求得碟形两点之间的距离 for(j=1;j<=dispos;j++) //每个蝶形群运算的次数 当N等于64时每次进行蝶形运算次数l=1时为1*32 l=2时为 2* 16 总之每次进行蝶形运算数不变都为N/2 只不过相乘的两个数的间隔每级都加大 for(i=j;i<N;i=i+disbuff) //遍历M级所有的碟形 { dissec=i+dispos; //求得第二点的位置 ee(xin[dissec],(uint)(j-1)*(uint)N/disbuff);//复数乘法 t=temp; xin[dissec].real=xin[i].real-t.real; xin[dissec].imag=xin[i].imag-t.imag; xin[i].real=xin[i].real+t.real; xin[i].imag=xin[i].imag+t.imag; } } }
虽然注释的比较完善,但我还需要进行几点说明:
a.FFT的产生
我们想通过单片机处理信号,信号必须是离散的和有限长度的数据才能被处理。所以我们只能用DFT(离散傅里叶变换),DFT虽然能够处理信号,但是运算比较复杂,所以根据它的一些性质提出了FFT。
b.蝶形算法
在这里希望读者感兴趣的话找一本信号分析与处理的数来对照着分析,我写的注释比较完善,相信你能够理解,其中的注释部分,是我在用64点采样的时候做的,32点的原理是一样的。我在这里简单说下思路。
1.码位倒置
2.基32的FFT蝶形算法
c.采样点数的选取
这里网上大家通常看到的用单片机一般都采用64个点的。高级一点的STM32的程序用128个点的,而我选择了32个点。这里其实并没有明确要求大家采样用多少个点。但是通常都是2的N次方,因为计算会方便。再来说说我做的,由于由于FFT结果的对称性,通常只使用前N/2个采样点的结果。所以我用32个点采样,刚刚好能满足。采样频率我选择的是1KHZ,由于我一共有16列所以,每个列的频率其实已经固定了。所以加大采样的点数只是增加了计算而已,还有就是增加了我这16个点的选择机会。我测试过采用64个点,发现会有闪屏的现象,就是偶尔你会发现点阵上没有灯亮和刚开机的时候一样,后来分析原因就是运算的时间过长,使得画面无法连续显示。
d.FFT的采样时间
理论的情况:根据香农采样定力我们知道:为了不失真地恢复模拟信号,采样频率应该不小于模拟信号频谱中最高频率的2倍。人耳理论能听到声音范围20---2kHZ,但是实际上超过1kHZ人耳已经很难分辨了,所以我选择了500us定时,也就是2kHZ的采样频率。但是很遗憾结果是失真的,但是失真的结果是可以接受的。
真实的情况:我曾最后拿着音频频谱和分析音频的软件相对比,发现你采样时2KHZ还是4KHZ结果几乎是差不多的。我试着再次减小频率,发现还是差不多。最后分析整个程序我才发现其中的问题。那就是由于单片机性能还是有限处理这种FFT计算还是有些费力。所以造成的运算的时间比较长。这就使得无论你怎样调节定时器,只要超过了某个值,其结果都是一样的,这也是这个设计的一个缺陷之处。
void Timer0() interrupt 1 //我用的24M晶振,ADC采样频率即为采样周期 2KH 由于执行语句过长所以即使频率加大也没有仍会失真 { TH0 = 0xd1; TL0 = 0x20; fft_sign = 1; }
e.显示的BUG
显示的时候小伙伴可能会看到第一列的灯始终是亮着的,因为这个是结果产生的直流分量,当时调程序的时候,由于C语言能力有限,没能够弄掉,有点遗憾。