再用一個定時器
??在上一節,我們使用了一個定時器來計算頻率。需要某個音符持續一定的時間的話,仍然使用的是延時函數delay_ms,這會導致CPU阻塞,程序運行到這里,CPU只會去數數字,你按下按鍵,他也檢測不到——忙著數數字呢。接下來把這個延時也改成定時器,讓定時器像個鬧鐘一樣工作,讓CPU該干什么干什么,時間到了以后,讓定時器來提醒CPU。換句話說,播放的是背景音樂。
//改進此函數中的延時
void musicPlay(int length,unsigned char volume_level)
{
u8 i=0;
while(i buzzerSound(AllBGM[i].mName,volume_level); delay_ms(AllBGM[i].mTime); i++; } } ??定時器5的應用比定時器3還簡單點——不需要輸出PWM,只做計時用途。 //beep.c void TIM5_Int_Init(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //時鐘使能 //定時器TIM5初始化 TIM_TimeBaseStructure.TIM_Period = arr; //設置在下一個更新事件裝入活動的自動重裝載寄存器周期的值 TIM_TimeBaseStructure.TIM_Prescaler =psc; //設置用來作為TIMx時鐘頻率除數的預分頻值 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //設置時鐘分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式 TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根據指定的參數初始化TIMx的時間基數單位 TIM_ClearITPendingBit(TIM5, TIM_IT_Update ); //清除TIMx更新中斷標志 TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE ); //使能指定的TIMx中斷,允許更新中斷 //中斷優先級NVIC設置 NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //TIMx中斷 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; //先占優先級3級 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //從優先級2級 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能 NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器 TIM_Cmd(TIM5, ENABLE); //使能TIMx } ??我們把定時器5的初始化放在主函數的最前邊,并且把音量也初始化,讓掌機一上電就唱歌。死循環暫時什么都不寫。 int main(void) { BGM_volum = 6;//音量 TIM5_Int_Init(9,7199); //上電先播放背景音樂 ... } 定時器5的中斷服務 ??定時器的中斷服務函數無需調用,只要我們設定的時間到了以后,就會自動跳轉到此函數中來執行。在分頻系數為7199時,(自動重裝值+1)/10=ms,所以,如果知道了需要延時的時間ms以后,ms * 10-1=自動重裝值。 ??我們可以把每一個包含了音調與時間的音符都看作一個珍珠,整個樂曲就像是珍珠項鏈。前一個音符根據頻率與音量算出定時器3需要的自動重裝值與比較值(定時器3的分頻系數是確定的,9),根據延時的時間設置好定時器5的自動重裝值(定時器5的分頻系數也是確定的,7200,且無須比較值),然后開啟定時器。 ??CPU該干嘛干嘛。 ??等到定時器5的時間到了,播放下一個音符,周而復始。思路與之前一樣。顯而易見,我們需要一個變量來記錄當前播放到哪一個音符。我用了一個局部的靜態變量,即便中斷服務函數執行完了,靜態變量的值也不會丟失。只要沒有把樂譜播放完,就播放下一個音符。 //beep.c //定時器5中斷服務程序 void TIM5_IRQHandler(void) { static u16 i = 0; if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET) //檢查TIM5更新中斷發生與否 { TIM_ClearITPendingBit(TIM5, TIM_IT_Update ); //清除TIMx更新中斷標志 if(i buzzerSound(AllBGM[i].mName,BGM_volum); TIM_SetAutoreload(TIM5,AllBGM[i].mTime*10-1); TIM_SetCounter(TIM5,0); i++; } else i = 0; } } ??完成這些代碼以后,即便主函數的死循環為空,歌曲也能后臺播放了。 更多的BGM ??考慮到我要用的音效有好幾個,所以我需要更多的BGM。其中,打中地鼠與生成地鼠的音樂還是我原創的,哈哈哈。這是我的樂譜: //beep.c const tNote AllBGM[]= { //擊中 5 BAD_BGM {CM1,TT1/4},{CL4,TT1/4},{CL3,TT1/8},{CL1,TT1/8},{0,TT1/8}, // 生成 4 GOOD_BGM {CH1,TT1/4},{CH2,TT1/4},{CH3,TT1/4},{0,TT1/8}, //兩只老虎 36 BEGIN_BGM {CM1,TT/4},{CM2,TT/4},{CM3,TT/4},{CM1,TT/4}, {CM1,TT/4},{CM2,TT/4},{CM3,TT/4},{CM1,TT/4}, {CM3,TT/4},{CM4,TT/4},{CM5,TT/4},{0,TT/4}, {CM3,TT/4},{CM4,TT/4},{CM5,TT/4},{0,TT/4}, {CM5,TT/8},{CM6,TT/8},{CM5,TT/8},{CM4,TT/8},{CM3,TT/4},{CM1,TT/4}, {CM5,TT/8},{CM6,TT/8},{CM5,TT/8},{CM4,TT/8},{CM3,TT/4},{CM1,TT/4}, {CM1,TT/4},{CL5,TT/4},{CM1,TT/4},{0,TT/4}, {CM1,TT/4},{CL5,TT/4},{CM1,TT/4},{0,TT/4}, //小豬佩奇 12 LIFE_BGM {CH5,TTS/4},{CH3,TTS/8},{CH1,TTS/8},{CH2,TTS/4},{CM5,TTS/4}, {CM5,TTS/8},{CM7,TTS/8},{CH2,TTS/8},{CH4,TTS/8},{CH3,TTS/4},{CH1,TTS/4},{0,TT/4}, //坦克大戰 33 LEVEL_BGM {CM1,TT1/4},{CM2,TT1/4},{0,TT1/4},{CM3,TTS/4}, {CM1,TT1/4},{CM2,TT1/4},{0,TT1/4},{CM3,TTS/4}, {CM3,TT1/4},{CM4,TT1/4},{0,TT1/4},{CM5,TTS/4}, {CM3,TT1/4},{CM4,TT1/4},{0,TT1/4},{CM5,TTS/4}, {CM4,TT1/4},{CM5,TT1/4},{0,TT1/4},{CM6,TTS/4}, {CM4,TT1/4},{CM5,TT1/4},{0,TT1/4},{CM6,TTS/4}, {CM5,TT1/4},{CM7,TT1/4},{0,TT1/4},{CH1,TTS/4}, {CM5,TT1/4},{CM7,TT1/4},{0,TT1/4},{CH1,TTS/4},{0,TTS/4}, }; ??如何切換歌曲呢?可以每首歌都是不同的結構體數組,然后用判斷語句切換歌曲。我選擇把所有的歌都放在一個結構體數組內,記錄歌曲的開頭與結尾的位置,并且為每首歌都按順序編號,所以我還需要一些變量: //beep.c u8 BGM = 0; u8 BGM_LENGTH[6] = {0,5,4,36,12,33}; u8 BGM_change_flg = 0; u8 BGM_volum; ??通過遍歷BGM_LENGTH的數組,就可以找到每一首歌的開頭和結尾的坐標了。而BGM_change_flg可用于標記背景音樂有沒有改變。 按鍵切換BGM ??首先要修改定時器5的中斷服務,來判斷有沒有換歌,如果沒有,接著播放下一個音符;如果換了,遍歷LENGTH數組來尋找新的數組的開頭與結尾。 ??當歌曲播放完成以后,我通過把BGM_change_flg與old_BGM清零,來實現這個需求:按下按鍵1可以播放音樂1,音樂1 播放完以后,再次按下按鍵1,仍然可以播放音樂1。 //beep.c //定時器5中斷服務程序 void TIM5_IRQHandler(void) //TIM3中斷 { static u16 i = 0; static u8 old_BGM = 0; static u16 END = 0; if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET) //檢查TIM3更新中斷發生與否 { TIM_ClearITPendingBit(TIM5, TIM_IT_Update ); //清除TIMx更新中斷標志 if(old_BGM != BGM)//改變了BGM { u16 temp; i = 0; for(temp = 0;temp i += BGM_LENGTH[temp]; } END = i+BGM_LENGTH[temp];//計算音樂的開頭 old_BGM = BGM; BGM_change_flg = 0;//此處為1可以設為單曲循環。 } if(i buzzerSound(AllBGM[i].mName,BGM_volum); TIM_SetAutoreload(TIM5,AllBGM[i].mTime*10-1); TIM_SetCounter(TIM5,0); i++; } else { buzzerSound(0,BGM_volum);//停止。 if(BGM_change_flg) { BGM_change_flg = 0; old_BGM = 0; } } } } ??主函數先寫一段測試代碼,按下不同的按鍵,可以切換不同的BGM。 //main.c int main(void) { BGM_volum = 6;//音量 TIM5_Int_Init(9,7199); //上電先播放背景音樂 LED_Init(); KEY_Init(); delay_init(); initIIC(); initOLED(); TIM3_PWM_Init(0xfffe,8); //蜂鳴器頻率定時器初始化 while(1) { key = KEY_Scan(0); if(key) //如果按下按鍵 { BGM_change_flg = 1; if(key == KEY1_PRES)//正確打中地鼠 加分,生成下一個地鼠 { BGM = BAD_BGM; } else if(key == KEY2_PRES) { BGM = GOOD_BGM; } else if(key == KEY3_PRES) { BGM = BEGIN_BGM; } else if(key == KEY4_PRES) { BGM = LIFE_BGM; } else if(key == KEY5_PRES) { BGM = LEVEL_BGM; } else { BGM = 0; } } } } ??到目前為止,背景音樂功能都寫好了,最起碼,掌機可以實現點唱機的功能了。應付下本科生的畢設,應該是沒問題了。
上一篇:STM32掌機教程9,完成掌機
下一篇:STM32掌機教程7,演奏音樂
推薦閱讀
史海拾趣
背景:為了滿足全球客戶的需求,GardTec開始實施全球化戰略,在亞洲和美國等地設立全球制造廠和庫存地。
發展:通過在全球范圍內的戰略布局,GardTec不僅提升了產品的供應效率,還更好地服務了世界各地的客戶。公司的全球化布局進一步鞏固了其在風扇配件市場的領先地位。
影響:全球化戰略的實施,使得GardTec的產品能夠迅速響應市場變化,滿足不同地區客戶的多樣化需求,從而增強了公司的市場競爭力。
1920年,一位年輕而聰明的科學家山謬·魯本(Samuel Ruben)和另一位年輕而富有的鎢絲電線制造商菲立普·馬洛里(Philip Rogers Mallory)在一次偶然的機會中相遇。魯本到馬洛里公司尋找實驗設備,兩人在交談中共同意識到將發明天賦和制造力量結合起來的巨大機會。這個巧合開啟了他們的合作之旅,最終導致了金霸電池的誕生。魯本的發明為當時的電池科技帶來了革命性的改變,奠定了DURACELL公司堅實的基礎。
在開拓重慶市場時,DURACELL公司面臨了巨大的挑戰。當地電池市場競爭激烈,價格參差不齊。然而,DURACELL公司憑借其獨特的營銷策略和高質量的產品,迅速打開了市場。他們選擇了與當地貿易代理商合作的方式進入市場,并通過“三步走式”的營銷策略迅速占領了市場份額。這一成功案例展示了DURACELL公司靈活的市場策略和對品質的堅持。
品質是aconno公司發展的基石。公司從一開始就注重產品質量和用戶體驗,從原材料采購到生產工藝,再到產品測試,每一個環節都嚴格把關。這種對品質的堅持,使得aconno的產品在市場上贏得了良好的聲譽。同時,公司還建立了完善的售后服務體系,為用戶提供及時、專業的技術支持,進一步增強了用戶的信任度和忠誠度。
隨著技術的不斷成熟和產品線的不斷豐富,Analytic Instruments Corp開始積極拓展市場。公司通過與行業內的知名企業和研究機構建立合作關系,成功地將產品打入多個重要的應用領域。同時,公司還加大了品牌宣傳力度,通過參加行業展會、舉辦技術研討會等方式,提升了品牌知名度和影響力。
要求是在傳送帶的2端分別放置光電傳感器,中間用4個“7劃數碼管”(就是電子顯示的那個8)顯示傳送帶的速度 需要自行安裝排布所有的電路并分析 我們已完成光電傳感器的電路 問題在于如何使光電傳感器的輸出電壓作為測量速度的開始和結束?(需自 ...… 查看全部問答∨ |
|
我最近在做一塊ARM板上的藍牙底層初始化驅動,環境如下 板子:ARM11板 Linux內核版本:2.6.18 藍牙模塊:采用CSR的Bluecore5核心,和CPU通過UART相連 藍牙驅動:1.自己根據藍牙的datasheet在系統啟動時給藍牙模塊發送了一條Reset信號,并初始化 ...… 查看全部問答∨ |
求購windows CE下智能輸入法,最好是T9輸入法,至少含拼音和筆畫,windows CE操作系統,鍵盤4X5。有意聯系xiao615@126.com.… 查看全部問答∨ |
我想打開png圖片,上網查到要用SHLoadImageFile函數可以,但是使用SHLoadImageFile函數需要aygshell.lib,我在網上找不到aygshell.lib,卻下載了AYGSHELL.DLL,可是光有AYGSHELL.DLL卻不知道應該怎么用它。 希望高人指點一下怎么用AYGSHELL.DLL來 ...… 查看全部問答∨ |
|
用單臺處理機順序計算表達式:f=a+be+ce^2+de^3,需幾級?若用三臺處理機計算此表達式,則只需幾級? 用單臺處理機順序計算表達式:f=a+be+ce^2+de^3,需幾級?若用三臺處理機計算此表達式,則只需幾級? … 查看全部問答∨ |
|
在做AD采樣時,初始化AD時,如果沒有這段代碼,發現采樣結果不對,加上后,檢測結果正常: AdcRegs.ADCTRL1.bit.RESET=1; //復位整個ADC模塊 DSP28x_usDelay(1); ...… 查看全部問答∨ |