在調試裝置時需要一個穩定的聲音源,想到之前買的有不少stc的MCU芯片,可以用它來產生幾個聲音信號。于是寫了這個程序。 八音盒能產生七個不同的樂音,而音樂就是由這些基本樂音組合構成(有點臉紅)。所以本例程就是個基于MCU的樂音產生程序。 用MCU產生一個樂音本身很簡單,比如要產生一個110HZ的樂音,只要用MCU生成一個頻率為110HZ的PWM方波,然后輸出電路上加個一階低通濾波器,驅動喇叭就聽到聲音了。 但當我們需要產生多個樂音時,就遇到一個問題,不同樂音的頻率不同,需要的濾波器的參數就不一樣,只用一個濾波器時,頻率高的樂音會受到較大的衰減。結果低音很強,高音聽不見了。 當然可以用七個不同的低通濾波器,應對七個頻率,用一個cd4051進行選擇,mcu輸出不同頻度時,控制cd4051接通不同的濾波器。不過這樣硬件就多了幾樣。而且無法適應更多的頻率。 常用解決辦法是采用一個高頻率的載波,然后把需要產生的聲音頻率信號調制到載波上。這樣就節省了濾波器。這就是spwm波。它是一個脈沖寬度按正弦規律變化的PWM波。下面給出一個實現方法。 stc32g12k128具有硬件pwm功能。只要做好設置,它能自動產生符合設置頻率和脈寬的PWM波,不需要占用MCU時間。我們只要合理控制它的脈寬變化,就能得到需要的SPWM波了。所以程序簡單穩定。精度也很高。 我選擇系統主頻用24MHZ,載波頻率為55*512=28160HZ。55為參考頻率,512為對應該頻率時正弦波表的點數。因為人耳可聞聲頻率上限20KHZ,所以這個頻率不會對樂音產生干擾,同時它又能保證每個周期對應一個波點數據。最大限度的保證產生的聲音的精度。對其它頻率的樂音,載波頻率不變,只改變正弦波表的點數。始終保證系統在最大精度上運行。該載波頻率對應的周期設置值為:24000000/28161=852=0x0354 STC官方技術手冊上有現成的產生spwm波的例程。就直接套用了,包括pwm設置和pwm中斷服務。本例程主要是解決正統波表的生成,管理,使用。樂譜表的建立和使用。樂曲的播放管理。 對spwm波來講,每個頻率的正弦波都需要一組正統波表數據,建立正統波表時,我使用了軟件 Spwm_calc.exe。 它的界面如圖所示。中值采用420,幅值415,調制度0.98,在55HZ時的點數512.其它頻率的點數值通過計算獲得。產生了七組數據存放在頭文件sin_table1.h里,每組數據的第一個數放的是本組數據的個數(也就是使用的點數)。第二個開始才是正弦波表的數據。也就是說。讀波表數據要從第二個開始讀。這是為了編程序簡單方便。 系統設置了三個數組,一個是正弦波表,放在頭文件里。第二個是波表索引數組u16 *sin_table_index[],存放正弦波表的數據的地址,它是樂譜與正弦波表的過渡。第三個是樂譜 u8 music_score[33][3]。 樂譜表中一組數據有三個,第一個是音符名,程序根據它通過索引數組讀取對應正弦波表。第二個是音組名。1是最低音,2是低音,3是中音4是高音,5是最高音。這個數據決定從波表中讀取數據的數量,數據越少音頻越高。比如讀512個是最低音的6(la),那么讀256個就是低音的6(la),高了八度。頻率高一倍。以此類推。這樣七組波表數據可用以播放幾十個樂音。第三個是音長(樂音的持續時間)。本例程只設置了三個數值,1是全音,2是半音,4 是4分音。僅用來展示一下音長管理的方法。想多加幾個音長設置很容易。 例程使用了四個中斷服務: PWMA中斷,基本照抄官方例程,用來產生spwm波。不同的是這里輸出的正弦波頻率不是固定的。所以讀波表時不能用固定首地址。而是用了一個變量*p存放正弦波表首地址值,方便切換頻率。 外中斷int0控制程序運行,主要是把等待切換為運行。如果不用這個中斷。可以讓程序開機后就自動循環播放。在中斷服務程序中。控制變量CC的值,從而影響主程序里語句 While(cc);的運行。 在調試時發現中斷服務里改變cc值后,主程序里的while語句沒有響應。接上stc-link1d仿真器后觀察到,中斷發生后cc值確實發生了變化,但while語句不理會,沒有如預期的那樣跳出死循環。把這個現象放到群里咨詢時,有高人指出。這是由于keil編譯不合理所致,解決的辦法是聲名變量CC時加一個限制符volatile。試了一下,果然解決了問題。 中斷T1是避免按鍵振動產生干擾的延時。這兩個都不重要,可以不用。 中斷T0是核心,它控制一個樂音的播放時間,同時設定播放所需要的所有參數。各語句的作用在程序里做了注明。 void t0_sever() interrupt 1//確定一個音符的輸出參數,包括樂音的持續時間,音高數據的地址,數據量,讀取參數 { read_music_long(cnt);//讀當前音符的播放時間,并設定對應的延時 pp2=music_score[cnt][1];//讀取本音符的音組值,以確定讀波表數據時的偏移量 pp3=pow(2,pp2);//計算本音符數據偏移量,在式中給pp2加整數能成倍提高輸出頻率 pp0=*sin_table_index[music_score[cnt][0]-1];///pp2;//讀取本音符的數據量 pp1=(sin_table_index[music_score[cnt][0]-1]+1);//取本音符數據指針初值 p=pp1;//賦正弦數據指針初值 cnt++;//準備讀下一個數據 if(cnt>33)//樂譜播放完成,這里的33是根據樂譜數據的參數設定的。如果改變樂譜數據量,這里要做對應變化,用小了不能完整播放,用大了會出錯 { cc=0;//播放結束 ET0=0;//關中斷 PWMA_IER = 0x00; //關中斷 PWMA_ENO = 0x00;//關閉PWM輸出
} } 音長子程序里有三個軟件定時程序,是直接使用stc官方的軟件延時工具產生的。全音用了一秒,半音0.5秒,四分音用0.25秒。 樂音播放所需要的控制參數都是在這個中斷服務里確定的。PWM中斷服務則負責按參數進行輸出。 本例程的主程序很簡單,包括系統設置和播放管理兩項。直接列出來吧: void main(void) { mcu_initial();//mcu設置程序 //打開播放程序,播放完成后重新進入等待 while(1)//這個是重入語句 {
cc=1;//等待狀態,由中斷int0改變,如果屏蔽這個語句則程序自動重復播放樂曲 while(cc); //初始化播放指針并開始播放 cnt=0;//把播放計數復位到開始位置 ET0=1; TR0 = 1; //定時器0開始計時 PWMA_IER = 0x01; //使能中斷 PWMA_ENO |= 0x01; //使能輸出 PWMA_ENO |= 0x02; //使能輸出
cc=1; while(cc);//等待播放完成,由T0中斷服務程序控制這里的CC值 } } 為聽到產生的聲音,我使用了唯創的PWM功率放大模塊WT1312,它能把spwm信號變成推動喇叭的正弦信號并直接推動喇叭發聲。其電路如圖所示:把mcu的spwm輸出端直接連上功放芯片的PWM輸入端就行了。因電流較大。芯片電源單獨接了一個4.2/3.7V鋰電池。二者不需要共地。功放的輸入阻抗是100K,對MCU輸出要求很低。它的體積很小,又只用了一個電容,所以我直接用sop23-10/dip10轉接板當功放板了。接好的板子如圖。MCU部分電路沒有特殊要求,使用最小系統板就行,我在實驗中用了stc32g12k128的降龍棍系統板。音頻測試視頻體積太大沒法傳上來。因為測試電路和這個八音盒程序都沒使用晶振,所以會有些誤差。效果整體還是很不錯的。完整的程序見附件,歡迎大家批評指正。
|