音乐频谱显示小玩具——FFT在STM32中的实现与应用
0、前言
音樂(lè)頻譜顯示說(shuō)白了就是“兒童版”頻譜儀。筆者平時(shí)比較喜歡聽(tīng)音樂(lè),閑暇之余聽(tīng)音樂(lè)的時(shí)候如果有個(gè)頻譜顯示的小玩具在旁邊跳來(lái)跳去的也挺有意思的,所以筆者去萬(wàn)能的某寶上搜索了一下,發(fā)現(xiàn)便宜的都很小,大一點(diǎn)的都很貴,而且都需要音頻接頭輸入,很麻煩,所以筆者就自制了這個(gè)小玩具。效果圖如下圖1所示。
效果視頻:自制音樂(lè)頻率響應(yīng)小玩具_(dá)嗶哩嗶哩_bilibili
圖1:實(shí)物展示其功能有一下3個(gè):
1、實(shí)時(shí)顯示音樂(lè)頻譜
2、將小玩具倒立,顯示當(dāng)前時(shí)間和溫度。
3、在顯示時(shí)間和溫度時(shí),一段時(shí)間后屏幕自動(dòng)熄滅。觸動(dòng)小玩具即可再次點(diǎn)亮
1、FFT(Fast Fourier transform)
快速傅里葉變換是一種利用計(jì)算機(jī)進(jìn)行高速快捷的離散傅里葉變換的一種方法,而什么是傅里葉變換?傅里葉變換的物理意義是什么呢?
當(dāng)年,著名的數(shù)學(xué)家傅里葉表示:任何連續(xù)測(cè)量的時(shí)序或信號(hào),都可以表示為不同頻率的正弦波信號(hào)的無(wú)限疊加。而通過(guò)傅里葉變換就可以將疊加的波形以正弦波為基本單位將其拆分開(kāi)。
圖2:傅里葉變換而快速傅里葉變換為什么可以快速呢?首先先看一下離散傅里葉變換的公式:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
通過(guò)公式不難看出如果我們讓計(jì)算機(jī)去計(jì)算時(shí),我們需要計(jì)算N^2次運(yùn)算。但是通過(guò)觀察可以發(fā)現(xiàn),在計(jì)算中有想當(dāng)一般的數(shù)據(jù)都是冗余的,并且不會(huì)顯示在頻譜中。所以我們可以將這些數(shù)據(jù)保存下來(lái)計(jì)算的時(shí)候直接調(diào)用或在此數(shù)據(jù)基礎(chǔ)上做簡(jiǎn)單的運(yùn)算。通過(guò)這樣的方法我們就可以將DFT的N^2次運(yùn)算減少到Nlog10(N)次運(yùn)算了。
例如如果做1000采樣點(diǎn)的傅里葉變換使用DFT的運(yùn)算次數(shù)就是1000000次,而使用FFT就可以減少到3000次運(yùn)算。
2、STM32實(shí)現(xiàn)FFT
因?yàn)楣P者做的這個(gè)玩具使用的芯片是STM32單片機(jī)F10系列。所以在做FFT的時(shí)候沒(méi)有官方庫(kù)的支持(以前是有,但是F4系列上線后就取消了,真實(shí)萬(wàn)惡的資本主義\dogs)雖然網(wǎng)上有很多教程是直接移植或者找之前的DSP庫(kù),但是筆者覺(jué)得著實(shí)有些麻煩,F10系列沒(méi)有DSP運(yùn)算核心,這個(gè)DSP庫(kù)就猶如花瓶。
這里主要借鑒了這位大神的文章:《C》C語(yǔ)言實(shí)現(xiàn)FFT算法_楊貴安的博客-CSDN博客_c語(yǔ)言fft
在單片機(jī)上做FFT時(shí)需要注意堆棧的大小,以免發(fā)生硬件錯(cuò)誤。代碼如下:
double * FFT(double *pr,int n) {static double amp_data[FFT_max*2];//定義輸出幅值static double fr_data[FFT_max*2];//定義輸出實(shí)部static double fi_data[FFT_max*2];//定義輸出虛部static double pr_data[FFT_max];//緩存輸入實(shí)部static double pi_data[FFT_max];//緩存輸入虛部unsigned int k;//n=2^k;int i,j;int it,m,is,nv;double p,q,s,vr,vi,poddr,poddi; //===============初始化================================//k = log10(n)/log10(2);//n=2^k;for(i=0;i<n;i++){pr_data[i] = *(pr+i);//讀入緩存pi_data[i] = 0;//讀入緩存} //========將pr[0]和pi[0]循環(huán)賦值給fr_data[]和fi_data[]==========//for (it=0; it<=n-1; it++) { m=it; is=0;for(i=0; i<=k-1; i++){ j=m/2; is=2*is+(m-2*j); m=j;}fr_data[it]=pr_data[is]; fi_data[it]=pi_data[is];} //==================================================//pr_data[0]=1.0; pi_data[0]=0.0;p=6.283185306/(1.0*n);pr_data[1]=cos(p); //將w=e^-j2pi_data/n用歐拉公式表示pi_data[1]=-sin(p); //================計(jì)算pr_data和pi_data========================//for (i=2; i<=n-1; i++) { p=pr_data[i-1]*pr_data[1]; q=pi_data[i-1]*pi_data[1];s=(pr_data[i-1]+pi_data[i-1])*(pr_data[1]+pi_data[1]);pr_data[i]=p-q; pi_data[i]=s-p-q;} //===================計(jì)算fr_data和fi_data=====================//for (it=0; it<=n-2; it=it+2) { vr=fr_data[it]; vi=fi_data[it];fr_data[it]=vr+fr_data[it+1]; fi_data[it]=vi+fi_data[it+1];fr_data[it+1]=vr-fr_data[it+1]; fi_data[it+1]=vi-fi_data[it+1];} //=================蝴蝶操作=========================//m=n/2; nv=2;for (i=k-2; i>=0; i--){ m=m/2; nv=2*nv;for (it=0; it<=(m-1)*nv; it=it+nv){for (j=0; j<=(nv/2)-1; j++){ p=pr_data[m*j]*fr_data[it+j+nv/2];q=pi_data[m*j]*fi_data[it+j+nv/2];s=pr_data[m*j]+pi_data[m*j];s=s*(fr_data[it+j+nv/2]+fi_data[it+j+nv/2]);poddr=p-q; poddi=s-p-q;fr_data[it+j+nv/2]=fr_data[it+j]-poddr;fi_data[it+j+nv/2]=fi_data[it+j]-poddi;fr_data[it+j]=fr_data[it+j]+poddr;fi_data[it+j]=fi_data[it+j]+poddi;}}} //=================計(jì)算幅值輸出==========================//for (i=0; i<=n-1; i++){amp_data[i]=sqrt(fr_data[i]*fr_data[i]+fi_data[i]*fi_data[i]); //幅值計(jì)算} //====================返回?cái)?shù)據(jù)=========================//return amp_data;//返回幅值 }直接通過(guò)KEIL仿真可以觀察出FFT的輸出波形與MATLAB對(duì)比圖如下圖3,圖4
圖3:Keil仿真波形 圖4:MATLAB波形方波的FFT測(cè)試如下圖5所示:
圖5:方波測(cè)試3、顯示屏選用與點(diǎn)陣驅(qū)動(dòng)
筆者使用了6個(gè)8*8點(diǎn)陣組成的24*16點(diǎn)陣作為顯示屏。每個(gè)8*8點(diǎn)陣使用MAX7219驅(qū)動(dòng)。MAX7219之間級(jí)聯(lián)保證占用的I/O口最少。其電路原理圖如下圖6所示
圖6:MAX7219驅(qū)動(dòng)電路因?yàn)镸AX7219這款I(lǐng)C輸入電壓范圍在4-5.5V故一般需要5V供電,信號(hào)端也需要5-3.3V的轉(zhuǎn)換。雖然大部分STM32的I/O口都是可以容忍5V輸入與輸出的。但筆者為了保險(xiǎn)起見(jiàn)加了3路的3.3V-5V電路。其電路原理圖如下。
4、STM32與音頻輸入電路
筆者將整個(gè)小玩具分成了兩個(gè)塊電路板,其中一塊全是點(diǎn)陣和MAX7219一塊是STM32、鋰電池充放電電路、MPU6050外圍電路和一路的音頻采集電路,引出剩下部分引腳。音頻采集電路主要使用咪頭,經(jīng)MAX4466做前置放大,放大后信號(hào)直接接入STM32內(nèi)置ADC引腳。電路圖如下圖7所示
圖7:MAX4466驅(qū)動(dòng)電路5、點(diǎn)陣屏?xí)r間顯示
時(shí)間的計(jì)算采用STM32內(nèi)置的RTC時(shí)鐘即可,首先需要顯示數(shù)字的字模,根據(jù)電路和字體大小(5*3),計(jì)算出數(shù)字0-9字模如下:
unsigned char disp1[11][3]={ {0x1F,0x11,0x1F},//0 {0x00,0x1F,0x08},//1 {0x1D,0x15,0x17},//2 {0x1F,0x15,0x15},//3 {0x1F,0x04,0x1C},//4 {0x17,0x15,0x1D},//5 {0x17,0x15,0x1F},//6 {0x18,0x17,0x10},//7 {0x1F,0x15,0x1F},//8 {0x1F,0x15,0x1D},//9 {0x00,0x0A,0x00} //: };根據(jù)電路結(jié)構(gòu)和掃描順序,寫(xiě)出底層顯示驅(qū)動(dòng)程序,程序如下:
//參數(shù)從左到右依次為:字體數(shù)據(jù),字體長(zhǎng)度,字體寬度,起始位置的x坐標(biāo)(自右向左),起始位置y坐標(biāo)(自下而上) void MAX7219_N_display(unsigned char *cData,unsigned char Length,unsigned char Width,unsigned char x,unsigned char y) {static u16 iSbufWidth[24];//縱向顯示緩存u16 iSbuf;//用戶數(shù)據(jù)緩存u16 iSbuf_Middle;//緩存中間變量unsigned int i;for(i=0;i<Length;i++){iSbuf = 0;//緩存清零iSbuf |= *(cData+i)<<y;//只有有效位存在"1"的可能,非有效位全為0iSbufWidth[i+x] |= iSbuf;//向顯示緩存寫(xiě)入有效位的“1”iSbuf = ~*(cData+i);//按位取反iSbuf <<= y;//左移到顯示位置iSbuf = ~iSbuf;//相當(dāng)于左移補(bǔ)1iSbuf_Middle = 0xffff;//置位iSbuf_Middle <<=(y+Width);iSbuf |= iSbuf_Middle;//補(bǔ)全高位使得只有有效位存在"0"的可能,非有效位全為1iSbufWidth[i+x] &= iSbuf;//向緩存寫(xiě)入有效位的“0”}for(i=0;i<8;i++){Write_Max7219_N(i+1,iSbufWidth[i],iSbufWidth[i+8],iSbufWidth[i+16],iSbufWidth[i+16]>>8,iSbufWidth[i+8]>>8,iSbufWidth[i]>>8);//顯示} }6、總結(jié)
這個(gè)裝置整體來(lái)說(shuō)比較簡(jiǎn)單,因?yàn)橛凶藨B(tài)傳感器和一個(gè)點(diǎn)陣屏,所以開(kāi)發(fā)的自由度也比較高。本文附資源,資源包含兩個(gè)電路板的PCB源文件和開(kāi)發(fā)的源代碼。如有好玩的想法或者改進(jìn)歡迎留言討論。
資源下載:音樂(lè)頻譜顯示資料.zip-硬件開(kāi)發(fā)其他資源-CSDN下載本資源內(nèi)含STM32源程序和PCB文件。可直接DIY制作。聲明:本資料僅供娛樂(lè)參考。如轉(zhuǎn)發(fā)或在其更多下載資源、學(xué)習(xí)資料請(qǐng)?jiān)L問(wèn)CSDN下載頻道.https://download.csdn.net/download/qq_24025329/12331286?spm=1001.2014.3001.5503
總結(jié)
以上是生活随笔為你收集整理的音乐频谱显示小玩具——FFT在STM32中的实现与应用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Python背单词记单词小程序源代码,背
- 下一篇: excel保存宏到person.xlsb