转载请注明出处: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倍。人耳理论能听到声音范围20HZ---20kHZ,我选择了500us定时,也就是2kHZ的採样频率。可是非常遗憾结果是失真的,可是失真的结果是能够接受的。
真实的情况:我曾最后拿着音频频谱和分析音频的软件相对照,发现你採样时2KHZ还是4KHZ结果差点儿是几乎相同的。我试着再次减小频率,发现还是几乎相同。
最后分析整个程序我才发现当中的问题。那就是因为单片机性能还是有限处理这样的FFT计算还是有些费力。所以造成的运算的时间比較长。这就使得不管你如何调节定时器。仅仅要超过了某个值。其结果都是一样的,这也是这个设计的一个缺陷之处。
void Timer0() interrupt 1 //我用的24M晶振,ADC採样频率即为採样周期 2KH 因为运行语句过长所以即使频率加大也没有仍会失真 { TH0 = 0xd1; TL0 = 0x20; fft_sign = 1; }
e.显示的BUG
显示的时候小伙伴可能会看到第一列的灯始终是亮着的,由于这个是结果产生的直流分量,当时调程序的时候。由于C语言能力有限,没可以弄掉,有点遗憾。