??本節原來是想講一講無源蜂鳴器發聲的原理,用于添加BGM功能。為了講原理,就寫了一些通俗的代碼,沒想到越寫越多,后來,干脆就形成了一個小小的項目吧——基于STM32與無源蜂鳴器的電子琴。
燈光效果
??首先想到的是做一個燈光的效果,按下哪個按鍵,哪個按鍵的燈要亮;松手后,燈滅掉。順帶,檢測一下帶松手檢測的按鍵功能好不好用。后續還可以做成通過亮燈提示需要按下那個按鍵,類似于節奏大師的功能——哪里要響點哪里。
??我去掉了無關的代碼,主函數里通過死循環,來確保按鍵按下的時候,燈是亮起來的 :
//main.c
while(1)
{
AllLED_OFF();
while(!SKEY1)
{
SLED1 = LED_ON;
}
while(!SKEY2)
{
SLED2 = LED_ON;
}
while(!SKEY3)
{
SLED3 = LED_ON;
}
while(!SKEY4)
{
SLED4 = LED_ON;
}
while(!SKEY5)
{
SLED5 = LED_ON;
}
while(!SKEY6)
{
SLED6 = LED_ON;
}
while(!SKEY7)
{
SLED7 = LED_ON;
}
while(!SKEY8)
{
SLED8 = LED_ON;
}
if(PAUSE_PRES == KEY_Scan(0))
{
LED1 = !LED1;
}
}
??下載程序并看看現象。
無源蜂鳴器的音調控制
??音調和頻率是息息相關的,可以在網上查找到頻率和音調對應的表格。本文的代碼參考了這篇文章,表示感謝
??根據圖片,可以做宏定義,中音的C調:
#define CM1 523
#define CM2 587
??為了講清楚原理,這里蜂鳴器先當做LED用。引腳給高電平,蜂鳴器就能響(只有一瞬間有聲音)。然而,只給高電平,無源蜂鳴器不能自己持續發出聲音;需要馬上給低電平,然后再給一個高電平。即在一個很短的周期內,無源蜂鳴器在高電平持續器件工作,在低電平持續器件休息。周期的倒數就是頻率。
??蜂鳴器的引腳是PB1,初始化跟LED一樣,我直接寫在了LED的初始化函數里。
??接下來先寫兩個按鍵的功能,用按鍵1和2來演奏C調的哆和唻。我定義了一個變量,是us為單位的時間,這是蜂鳴器的一個周期。它的值就是1000000us(1百萬us就是1s)除以頻率。頻率是查表得到的。在周期內,高電平持續的時間和低電平持續的時間各占一半。
//main.c
u32 F_us; //特定頻率對應的周期時間,單位us
while(!SKEY1)
{
SLED1 = LED_ON;
F_us = 1000000/CM1;
BEEP = 1;
delay_us(F_us/2);
BEEP = 0;
delay_us(F_us/2);
}
while(!SKEY2)
{
SLED2 = LED_ON;
F_us = 1000000/CM2;
BEEP = 1;
delay_us(F_us/2);
BEEP = 0;
delay_us(F_us/2);
}
??下載程序,按下按鍵1或2,就可以聽到不同的音調。原理就是這么簡單。
音量控制
??無源蜂鳴器可以用高電平持續的時間調整音量,在一個周期中,高電平持續的時間越長,蜂鳴器聲音越大;高電平持續的時間越短,蜂鳴器的聲音越小。這句話還有一個時髦的描述方法——脈寬調制。
??原理很簡單,實現起來也不復雜。在上一個案例的基礎上,我把高電平持續的時間由50%改成了通過變量volum來計算。如果volum=1,那么高電平持續的時間就是周期的一半(右移一位等于除以2);如果volum=5,那么高電平持續的時間就是周期的64分之1,(右移n位等于除以2的n次方)。為了方便比較,我先讓按鍵1和2的音調一樣,音量不一樣。
//main.c
u32 F_us; //特定頻率對應的周期時間,單位us
u32 time_ON; //蜂鳴器響的時間
u32 time_OFF; //蜂鳴器不響的時間
u8 volum; //音量
while(!SKEY1)
{
SLED1 = LED_ON;
F_us = 1000000/CM1;
volum = 1;
time_ON = F_us>>volum;
time_OFF = F_us - time_ON;
BEEP = 1;
delay_us(time_ON);
BEEP = 0;
delay_us(time_OFF);
}
while(!SKEY2)
{
SLED2 = LED_ON;
F_us = 1000000/CM1;
volum = 6;
time_ON = F_us>>volum;
time_OFF = F_us - time_ON;
BEEP = 1;
delay_us(time_ON);
BEEP = 0;
delay_us(time_OFF);
}
??按下兩個按鍵,可以聽出響度是不一樣。事實上我調整過比例,感覺,50%的占空比可能是最大的聲音了,,volum < 4之前都聽不大出來。
提取函數
??既然按鍵1和按鍵2都既能控制音調,用能控制音量了,別的按鍵把代碼復制粘貼就能實現功能了。只不過,復制粘貼是代碼不好的表現。
??所以,再次提取出一個函數,傳入音調和音量,就能發出聲音。
void play(u32 tone,u8 tvolum)
{
u32 F_us; //特定頻率對應的周期時間,單位us
u32 time_ON; //蜂鳴器響的時間
u32 time_OFF; //蜂鳴器不響的時間
F_us = 1000000/tone;
time_ON = F_us>>tvolum;
time_OFF = F_us - time_ON;
BEEP = 1;
delay_us(time_ON);
BEEP = 0;
delay_us(time_OFF);
}
??然后修改死循環。我有8個帶燈按鍵,但是音調只有7個,所以預留8和pause用于升降調,這兩個按鍵無需松手檢測。其它按鍵按下時,調用play函數。
while(1)
{
key = KEY_Scan(0);
if(KEY8_PRES == key)
{
LED2 = !LED2;
}
else if(PAUSE_PRES == key)
{
LED1 = !LED1;
}
else
{
AllLED_OFF();
}
while(!SKEY1)
{
SLED1 = LED_ON;
play(CM1,volum);
}
while(!SKEY2)
{
SLED2 = LED_ON;
play(CM2,volum);
}
while(!SKEY3)
{
SLED3 = LED_ON;
play(CM3,volum);
}
while(!SKEY4)
{
SLED4 = LED_ON;
play(CM4,volum);
}
while(!SKEY5)
{
SLED5 = LED_ON;
play(CM5,volum);
}
while(!SKEY6)
{
SLED6 = LED_ON;
play(CM6,volum);
}
while(!SKEY7)
{
SLED7 = LED_ON;
play(CM7,volum);
}
}
??至此,就已經實現了最簡單的電子琴的功能。
升調和降調功能
??默認情況下,我們演奏的都是C調中間那個音階。我定義按鍵8升調,按鍵PAUSE為降調(其實調整的不是音調而是音階)。然后定義個變量用于儲存當前是C調還是F調,也就是音階?
while(1)
{
key = KEY_Scan(0);
if(KEY8_PRES == key)
{
LED2 = !LED2;
tone_level++;
}
else if(PAUSE_PRES == key)
{
LED1 = !LED1;
tone_level--;
}
else
{
AllLED_OFF();
}
。。。
}
??修改play函數,根據音階與音調來計算周期。
void play(u32 tone,u8 tvolum)
{
u32 F_us; //特定頻率對應的周期時間,單位us
u32 time_ON; //蜂鳴器響的時間
u32 time_OFF; //蜂鳴器不響的時間
if(tone_level<1)
tone_level = 1;
else if(tone_level>12)
tone_level = 12;
if(1 == tone_level)
{
switch(tone)
{
case 1:F_us = 1000000/CL1;
case 2:F_us = 1000000/CL2;
case 3:F_us = 1000000/CL3;
case 4:F_us = 1000000/CL4;
case 5:F_us = 1000000/CL5;
case 6:F_us = 1000000/CL6;
case 7:F_us = 1000000/CL7;
}
}
else if(2 == tone_level)
{
switch(tone)
{
case 1:F_us = 1000000/CM1;
case 2:F_us = 1000000/CM2;
case 3:F_us = 1000000/CM3;
case 4:F_us = 1000000/CM4;
case 5:F_us = 1000000/CM5;
case 6:F_us = 1000000/CM6;
case 7:F_us = 1000000/CM7;
}
}
//F_us = 1000000/tone;
time_ON = F_us>>tvolum;
time_OFF = F_us - time_ON;
BEEP = 1;
delay_us(time_ON);
BEEP = 0;
delay_us(time_OFF);
}
??這段代碼太糟糕了,才寫了兩種音階我就受不了了。之前音調的信息都是宏定義,為了方便調用,我改成數組。
//beep.c
u16 CL[7]={262,294,330,349,392,440,494};
u16 CM[7]={523,587,659,698,784,880,988};
u16 CH[7]={1047,1175,1319,1397,1568,1760,1976};
u16 DL[7]={294,330,370,392,440,494,554};
u16 DM[7]={587,659,740,784,880,988,1109};
u16 DH[7]={1175,1319,1480,1568,1760,1976,2217};
u16 EL[7]={330,370,415,440,494,554,622};
u16 EM[7]={659,740,831,880,988,1109,1245};
u16 EH[7]={1319,1480,1661,1760,1976,0,0};
u16 FL[7]={349,392,440,466,523,587,659};
u16 FM[7]={698,784,880,932,1047,1175,1319};
u16 FH[7]={1397,1568,1760,1865,0,0,0};
??然后修改演奏函數。
void play(u32 tone,u8 tvolum)
{
u32 F_us; //特定頻率對應的周期時間,單位us
u32 time_ON; //蜂鳴器響的時間
u32 time_OFF; //蜂鳴器不響的時間
if(tone_level<1)
tone_level = 1;
else if(tone_level>12)
tone_level = 12;
switch(tone_level)
{
case 1: F_us = 1000000/CL[tone];break;
case 2: F_us = 1000000/CM[tone];break;
case 3: F_us = 1000000/CH[tone];break;
case 4: F_us = 1000000/DL[tone];break;
case 5: F_us = 1000000/DM[tone];break;
case 6: F_us = 1000000/DH[tone];break;
case 7: F_us = 1000000/EL[tone];break;
case 8: F_us = 1000000/EM[tone];break;
case 9: F_us = 1000000/EH[tone];break;
case 10:F_us = 1000000/FL[tone];break;
case 11:F_us = 1000000/FM[tone];break;
case 12:F_us = 1000000/FH[tone];break;
}
//F_us = 1000000/tone;
time_ON = F_us>>tvolum;
time_OFF = F_us - time_ON;
BEEP = 1;
delay_us(time_ON);
BEEP = 0;
delay_us(time_OFF);
}
??主函數調用的部分也修改了。注意,數組的索引是從零開始的。
while(1)
{
key = KEY_Scan(0);
if(KEY8_PRES == key)
{
LED2 = !LED2;
tone_level++;
}
else if(PAUSE_PRES == key)
{
LED1 = !LED1;
tone_level--;
}
else
{
AllLED_OFF();
}
while(!SKEY1)
{
SLED1 = LED_ON;
play(0,volum);//數組的第一個元素是0
}
while(!SKEY2)
{
SLED2 = LED_ON;
play(1,volum);
}
while(!SKEY3)
{
SLED3 = LED_ON;
play(2,volum);
}
while(!SKEY4)
{
SLED4 = LED_ON;
play(3,volum);
}
while(!SKEY5)
{
SLED5 = LED_ON;
play(4,volum);
}
while(!SKEY6)
{
SLED6 = LED_ON;
play(5,volum);
}
while(!SKEY7)
{
SLED7 = LED_ON;
play(6,volum);
}
}
??也可以把音階的信息作為一個變量傳入參數,避免使用全局的變量。
??實際演奏時,還發現了小小的BUG,E和F的高音,數組不夠7個,如果傳入的參數是0,那么F_us的時候分母是0,程序可能卡死,所以把0音調改成1了。當然也可以用判斷語句來避免這種情況。
??我還設想了很多功能,比如屏幕顯示個樂譜,屏幕顯示音調;按鍵亮起作為提示,然后按下對應的按鍵,發出聲音。想法越來越多,我只好趕緊收手了,畢竟,,,我原來的計劃是打地鼠掌機啊!電子琴只是為了講蜂鳴器的原理啊!
??放上兩只老虎的簡譜,來彈奏一曲吧。
上一篇:STM32掌機教程7,演奏音樂
下一篇:STM32掌機教程5,程序框架,隨機,加命與升級
推薦閱讀
史海拾趣
在發展過程中,Aces也面臨過各種挑戰和危機。例如,原材料價格波動、市場競爭激烈、貿易壁壘等因素都曾經給公司帶來不小的壓力。然而,Aces憑借敏銳的市場洞察力和強大的危機管理能力,成功應對了這些挑戰。公司及時調整采購策略、優化產品結構、拓展銷售渠道等措施,有效緩解了外部壓力,保持了穩定的發展態勢。
這些故事雖然基于假設和推測,但反映了一個電子企業在發展過程中可能遇到的各種情況。無論面對技術挑戰、市場競爭還是行業變革,Aces Electronics Co., Ltd.都展現了堅韌不拔、勇往直前的精神風貌。未來,公司將繼續致力于技術創新和市場拓展,為電子行業的發展貢獻更多力量。
在追求經濟效益的同時,Esterline Power Systems也積極履行社會責任,注重綠色環保和可持續發展。公司采用環保材料和生產工藝,降低生產過程中的能源消耗和廢棄物排放。此外,公司還積極參與環保公益活動,推動綠色能源的發展和應用。這些舉措不僅提升了公司的社會形象,也為公司贏得了更多客戶的信任和支持。
Esterline Power Systems公司在其早期階段就致力于研發創新技術,以滿足航空和國防行業對高性能電源系統的需求。一次關鍵的技術突破發生在公司對一種新型高效能電源控制器的研發上。面對市場上已有的產品性能瓶頸,公司的研發團隊經過數月的深入研究與實驗,成功開發出了具有更高效率和更穩定性能的電源控制器。這一創新不僅贏得了客戶的認可,也為公司贏得了市場份額,奠定了在電源系統領域的領先地位。
在實現了從貿易到制造的轉變后,依必安派特并沒有停止前進的步伐。在2012年,依必安派特亞太研發中心正式落戶上海,開始了本土化研發之路。這一舉措使得依必安派特能夠更好地了解中國市場的需求和趨勢,為中國客戶提供更加符合其需求的產品和解決方案。同時,本土化研發也加速了依必安派特在中國市場的創新步伐,推動了公司業務的快速發展。
Astec America, Inc的創立可以追溯到上世紀80年代,當時創始人看到了電源技術的巨大潛力,并決定在這一領域進行深耕。公司初創時期,團隊主要專注于電源技術的研發,推出了一系列具有創新性的電源產品。這些產品不僅性能穩定、效率高,而且價格合理,很快就在市場上獲得了良好的口碑。
面對數字化轉型的大趨勢,Astec也積極擁抱變革。公司加大了對信息技術和智能制造的投入,通過引入先進的生產管理系統和數據分析工具,提高了生產效率和產品質量。同時,Astec還積極探索新的商業模式和銷售渠道,以適應數字化時代的市場需求。展望未來,Astec將繼續堅持創新驅動的發展理念,不斷推動公司在電子行業中的持續發展。
這五個故事基于Astec America, Inc在電子行業發展的主要階段和事件進行編寫,旨在客觀描述公司的發展歷程和重要成就。請注意,這些故事可能無法涵蓋公司發展的所有細節,但能夠提供一個大致的框架。
CC2430+CC2591 模塊是在CC2430模塊上擴展了CC2591 CC2430+CC2591模塊是第一個符合ZigBee 的SOC芯片CC2430精心設計的加PA+LNA無線收發模塊;工作載頻為2.4GHZ,采用擴頻技術,另CC2430器件的性能已超過了IEEE802.15.4規范的要求;同時還具有優異的選擇 ...… 查看全部問答∨ |
要求是在傳送帶的2端分別放置光電傳感器,中間用4個“7劃數碼管”(就是電子顯示的那個8)顯示傳送帶的速度 需要自行安裝排布所有的電路并分析 我們已完成光電傳感器的電路 問題在于如何使光電傳感器的輸出電壓作為測量速度的開始和結束?(需自 ...… 查看全部問答∨ |
我們計算機組成原理考試,老師讓我們自己上網搜索具體運用了計算機組成原理知識的一些程序優化技術,比如沒優化時是怎樣的,運用了相關計算機組成知識優化后現在時間周期提高了多少, 望前輩們隨便舉個相關案例, 指個大方向也好, 我自己去查查, 急求, ...… 查看全部問答∨ |
關于Eboot添加USB下載功能 給假期疲勞的調劑 見者有分 硬件 友善 MINI2440 做了個BSP已經實現網絡下載 客戶要求實現DNW+USB下載 在網上down了個 三星原廠的CE5.0bsp%2C包括eboot,nboot,kernel,driver。實現了kitl,directdraw驅動+eboot有usb下載功能__2440b ...… 查看全部問答∨ |
我想打開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,需幾級?若用三臺處理機計算此表達式,則只需幾級? … 查看全部問答∨ |
標準CE5BSP+2440+128MB SDRAM SD卡無法顯示盤符問題 我以優龍的2440+標準ce5的開發板為基礎做了自己的板子,并采用你的方法升級SDRAM為128M,系統可以啟動,但是啟動后無法識別SD卡,在控制面板中的存儲器管理器中也看不到SD卡,不知可能是什么原因??SD卡部分的硬件電路與優龍開發板的一樣,驅動 ...… 查看全部問答∨ |