電子琴項目詳解:定時器產(chǎn)生方波與Proteus仿真全攻略)
簡介這是基于51單片機(jī)的電子琴設(shè)計與Proteus仿真資源面向單片機(jī)初學(xué)者、課程設(shè)計及電子愛好者。資源包含完整的工程源碼與仿真項目涵蓋音符演奏、音色選擇、和弦處理等關(guān)鍵模塊重點(diǎn)演示了ADC按鍵讀取、定時器PWM波形生成、中斷服務(wù)程序設(shè)計等經(jīng)典技術(shù)。壓縮包共7個文件以C程序源碼、Proteus工程文件與Keil工程文件為主輔以備份文件和模板配置整體僅30KB結(jié)構(gòu)簡潔適合快速查看和學(xué)習(xí)。已有2336人學(xué)習(xí)使用。通過這套資料讀者不僅能直接運(yùn)行仿真觀察電子琴工作效果還能從源碼中理解單片機(jī)驅(qū)動音頻輸出的完整思路包括音階映射、多音符同步與仿真調(diào)試技巧為后續(xù)嵌入式音頻開發(fā)打下基礎(chǔ)。1. 51 單片機(jī)電子琴是什么一塊芯片、八個按鍵、一路方波51 單片機(jī)電子琴是入門階段“結(jié)果感”最強(qiáng)的項目之一一塊芯片、八個按鍵、一個蜂鳴器按下去就響出對應(yīng)的哆來咪而且整份工程能在 Proteus 仿真里直接跑不用先買硬件。這類壓縮包里裝的正是這樣一套東西——控制程序加上配套仿真電路核心原理只有一句用定時器產(chǎn)生指定頻率的方波。它適合兩類人剛學(xué)完 GPIO、定時器和中斷、想綜合練手的初學(xué)者課程設(shè)計需要做可演示作品的在校生。做完它按鍵掃描、消抖、查表法、中斷與主循環(huán)的分工就全串起來了。別小看這個“玩具”級項目它是后面做音樂播放器、頻率計、PWM 發(fā)聲這些進(jìn)階方向的共同底子。但多數(shù)人第一次會栽在同一個地方仿真搭好、程序編譯過卻不出聲或者音調(diào)奇怪。這些問題往往不是程序邏輯錯而是蜂鳴器選型、晶振頻率這類“看似不重要”的細(xì)節(jié)。下文按原理、程序、仿真、排錯、擴(kuò)展的順序把這條路完整走一遍。2. 發(fā)聲原理與定時器初值一個音符對應(yīng)一串精確的數(shù)字2.1 方波怎么變成哆來咪頻率、周期與定時器的換算關(guān)系51 芯片本身不會發(fā)音樂它只會按指令翻轉(zhuǎn) I/O 口的電平。把一根引腳的電平每隔半個周期翻轉(zhuǎn)一次輸出就是方波方波的頻率決定音高262Hz 的方波聽起來是中央 C523Hz 是比它高八度的 C。所以這個項目的本質(zhì)工程問題是怎么讓引腳每秒精確翻轉(zhuǎn) 2×f 次。答案是用定時器。51 的定時器在方式 1 下是 16 位計數(shù)器從初值開始加 1加到 65536 溢出產(chǎn)生中斷溢出周期由初值決定。在中斷服務(wù)里只做一件事翻轉(zhuǎn)引腳電平。中斷頻率如果是 2f引腳輸出的方波頻率就是 f。這里有個初學(xué)者最容易犯的錯想輸出 262Hz 就開 262Hz 的中斷結(jié)果出來的是低八度的音。一次中斷只翻轉(zhuǎn)半次周期一個完整方波周期需要兩次翻轉(zhuǎn)所以中斷頻率必須翻倍。再算具體數(shù)字。12MHz 晶振下標(biāo)準(zhǔn) 51 內(nèi)部 12 分頻定時器的計數(shù)脈沖是 1MHz也就是一個計數(shù) 1μs。中央 C 是 262Hz一個周期約 3817μs半周期約 1908μs所以只要讓定時器每 1908 個計數(shù)溢出一次即可。從 65536 往回倒數(shù)初值就是 65536 - 1908 63628。整理成通用公式就是reload 65536 - fosc / (12 × 2 × f)其中 fosc 是晶振頻率12 是標(biāo)準(zhǔn) 51 的 12T 分頻系數(shù)2 是半周期系數(shù)。公式里的每個數(shù)都能在數(shù)據(jù)手冊或芯片模型里找到依據(jù)不存在玄學(xué)成分。后面所有音符的初值都是這一條公式算出來的。2.2 查表法發(fā)聲為什么不用現(xiàn)場算初值而要用常數(shù)表既然有了公式能不能每次按鍵時現(xiàn)場算可以但不劃算。標(biāo)準(zhǔn) 51 沒有硬件除法器算一次 16 位除法要寫軟件除法或浮點(diǎn)庫主循環(huán)會變慢程序也臃腫。更關(guān)鍵的是按鍵掃描要求響應(yīng)快中斷里更不能做耗時計算。所以工程上幾乎清一色用查表法把音符對應(yīng)的初值預(yù)先算好按音符編號存在一片常量數(shù)組里按下第幾個鍵直接按下標(biāo)取數(shù)。查表法在 8 個音符的場景下只占 16 字節(jié)程序存儲空間換來的是幾次指令周期內(nèi)完成取值而且邏輯一眼能看懂。換晶振時只需要重新算一整張表程序結(jié)構(gòu)和查表語句一行都不用改。這就是“把計算留在開發(fā)時把查表留給運(yùn)行時”的典型做法也是 51 項目里非常通用的套路。需要說明的是51 的定時器計數(shù)只能取整數(shù)四舍五入后每個音會有一點(diǎn)微小偏差。以 12MHz 為例中央 C 初值取整后誤差約 0.4μs對應(yīng)頻率偏差不到 0.1Hz人耳完全聽不出來。真正影響音準(zhǔn)的是后面要講的晶振頻率設(shè)置和中斷重裝時序而不是這張表的取整。2.3 中央 C 到高音 C 的初值對照表與 12MHz 晶振的約定按公式把最常用的一個八度算出來做成一張可以直接抄的表。約定條件晶振 12MHz標(biāo)準(zhǔn) 51 的 12T 模式定時器方式 1。音名簡譜頻率(Hz)半周期(μs)初值(十進(jìn)制)初值(十六進(jìn)制)中央C12621908636280xF88CD22941701638350xF95BE33301515640210xFA15F43491433641030xFA67G53921275642610xFB05A64401136644000xFB90B74941012645240xFC0C高音C1·523956645800xFC44這張表只有 8 個音對應(yīng)一個完整自然音階。想做更低的音或更高的音把頻率代進(jìn)公式重算即可。注意低音區(qū)半周期長、初值接近 65536定時精度反而更好高音區(qū)半周期短中斷重裝和翻轉(zhuǎn)的開銷占比變大音頭容易發(fā)虛后面第 5 章會講這個坑。提示如果換成 11.0592MHz 晶振計數(shù)周期變成 12/11059200 ≈ 1.085μs初值公式改為 65536 - 921600/(2×f)。中央 C 的初值約 63777。選這個晶振通常是為了串口波特率做電子琴時 12MHz 最好算、最省事。3. 用 C51 寫出可運(yùn)行的電子琴程序掃描、消抖、中斷發(fā)聲3.1 主循環(huán)的動作讀按鍵、防抖、判斷鍵位變化程序結(jié)構(gòu)上要分清兩個部分各自干什么主循環(huán)負(fù)責(zé)識別按鍵定時器中斷負(fù)責(zé)讓引腳翻轉(zhuǎn)。中斷里的動作必須快、短、沒有阻塞按鍵檢測允許慢慢來放在主循環(huán)里。這個分工是這個項目最值得學(xué)的結(jié)構(gòu)設(shè)計。先定義引腳、音高表和狀態(tài)變量。按鍵接 P1 口P1 是準(zhǔn)雙向口、內(nèi)部自帶弱上拉按鍵另一端接地按下時讀回低電平。蜂鳴器驅(qū)動腳選 P3.5。#include REGX52.H // 頭文件按實(shí)際芯片型號選擇 #define KEY_NUM 8 sbit BEEP P3^5; // 發(fā)聲引腳經(jīng)三極管驅(qū)動無源蜂鳴器 // 按鍵接 P1 口默認(rèn)高電平按下為低 // 12MHz 晶振、12T 模式下中央C到高音C的定時器初值 unsigned int code tone_table[KEY_NUM] { 63628, // 中央C 262Hz 63835, // D 294Hz 64021, // E 330Hz 64103, // F 349Hz 64261, // G 392Hz 64400, // A 440Hz 64524, // B 494Hz 64580 // 高音C 523Hz }; unsigned char current_key 0xFF; // 當(dāng)前有效鍵0xFF 表示沒有鍵按下 unsigned char output_enable 0; // 1 才允許引腳翻轉(zhuǎn)發(fā)聲 unsigned int reload_value 0; // 查表得到的初值中斷里直接使用代碼里的code關(guān)鍵字把表放進(jìn)程序存儲區(qū)不占寶貴的內(nèi)部 RAM這是 51 項目里處理常量表的常用寫法。reload_value是主循環(huán)和中斷之間的“交接變量”主循環(huán)負(fù)責(zé)填中斷負(fù)責(zé)用。按鍵掃描函數(shù)本身很簡單循環(huán)讀 P1 口每一位哪一位是 0 就返回哪一位的編號。但這個簡單版本只適合仿真驗證實(shí)板上必須加消抖。// 返回 P1 口上按下的鍵號 0~7沒有按鍵返回 0xFF // 簡化版直接讀電平仿真夠用實(shí)板建議加 10~20ms 確認(rèn) unsigned char scan_keyboard(void) { unsigned char i; for (i 0; i KEY_NUM; i) { if ((P1 (1 i)) 0) // 該位被拉低說明按鍵按下 { return i; } } return 0xFF; }機(jī)械按鍵按下瞬間電平會抖動幾次每次抖動間隔幾毫秒。如果不處理一次按壓會被識別成“按下-松開-再按下”聲音會發(fā)毛。常見做法是檢測到按下后延時 10~20ms 再讀一次兩次都是低才算有效松開時同樣確認(rèn)。也可以用定時器做 1ms 時基跑狀態(tài)機(jī)效果更好但課程設(shè)計用延時消抖已經(jīng)足夠。3.2 定時器中斷里的發(fā)聲動作重裝初值并翻轉(zhuǎn) I/O 口定時器初始化放在獨(dú)立的函數(shù)里只做一件事把 T0 配置成方式 1 并開啟中斷。void timer0_init(void) { TMOD 0xF0; // 清零 T0 的模式位保留 T1 的原設(shè)置 TMOD | 0x01; // T0 方式 116 位定時器 TH0 0x00; // 先給任意初值啟動后由 reload_value 接管 TL0 0x00; ET0 1; // 允許 T0 中斷 EA 1; // 開啟總中斷 TR0 1; // 啟動 T0 } void timer0_isr(void) interrupt 1 { // 重裝初值必須放在中斷服務(wù)最前面 TH0 reload_value 8; // 高 8 位 TL0 reload_value 0xFF; // 低 8 位 if (output_enable) // 有鍵按下時才翻轉(zhuǎn) { BEEP ~BEEP; // 翻轉(zhuǎn)一次 半個周期 } }interrupt 1是 T0 的中斷向量號這是 51 中斷體系里寫死的規(guī)定。重裝初值放在服務(wù)函數(shù)最前面是有原因的如果先干活再重裝定時器在中斷返回前多走了幾個脈沖實(shí)際溢出周期變長音會偏低而且這個偏差不穩(wěn)定。先重裝等下一輪加滿溢出周期才準(zhǔn)確。主循環(huán)和中斷的配合在 main 里體現(xiàn)循環(huán)讀鍵鍵位變了才更新參數(shù)。void main(void) { unsigned char key; unsigned char last_key 0xFF; timer0_init(); while (1) { key scan_keyboard(); // 1. 讀按鍵 if (key ! last_key) // 2. 鍵位發(fā)生變化才處理 { last_key key; if (key 0xFF) // 3. 松開停止發(fā)聲 { output_enable 0; BEEP 0; // 拉低電平避免輸出直流 } else { reload_value tone_table[key]; // 4. 查表更新初值 output_enable 1; } } } }這段邏輯的關(guān)鍵是“鍵位變化才處理”。一直按住時last_key不變主循環(huán)重復(fù)掃描但不打擾中斷發(fā)音穩(wěn)定只有按下或松開那一刻才更新狀態(tài)。松開時把BEEP拉低很重要否則引腳停在最后一次翻轉(zhuǎn)的電平上部分無源響片會一直有輕微的直流哼聲。注意reload_value是 16 位變量51 對它賦值要拆成兩條 8 位指令中斷隨時可能插進(jìn)來。極端情況下中斷會讀到只更新了一半的初值產(chǎn)生瞬間雜音。想徹底杜絕更新時先EA 0關(guān)中斷賦值完再EA 1。這個細(xì)節(jié)做課程設(shè)計時可以忽略做音樂播放器時值得加上。3.3 三個可調(diào)參數(shù)晶振頻率、音域范圍、延音與中止方式把上面代碼看成模板最常改的就是三個參數(shù)。第一是晶振頻率。代碼按 12MHz 算表Proteus 芯片屬性里的 Clock Frequency 也必須設(shè)成 12MHz兩邊不一致是音不準(zhǔn)的頭號原因。換成 11.0592MHz 時重算整張表并同時改仿真屬性缺一不可。第二是音域范圍。8 個獨(dú)立按鍵占滿 P1 一個口想擴(kuò)音域要么換矩陣鍵盤要么加“八度切換鍵”。擴(kuò)表時注意低音區(qū)初值大、重裝誤差小高音區(qū)半周期短、中斷開銷占比大所以擴(kuò)到兩個八度時高音區(qū)的初值可以按實(shí)測微調(diào)幾個計數(shù)別直接照抄公式。第三是延音與中止。上面示例是“松手即?!毕耧L(fēng)琴不像鋼琴。想要鋼琴那種延音效果可以在松開時不清除output_enable而是讓一個計數(shù)器倒數(shù)比如倒計時 50ms 內(nèi)繼續(xù)翻轉(zhuǎn)時間到了再停。用這種方式還能順手實(shí)現(xiàn)簡單的力度效果按鍵時長越長延音越長。再往后就是預(yù)置歌曲播放了那是第 6 章的內(nèi)容。4. Proteus 仿真搭建從元件擺放到 HEX 加載的完整清單4.1 電路連接與元件清單按鍵、三極管、無源蜂鳴器怎么接仿真不是畫個芯片拖根線就行元件選錯是后面“不響”的最大來源。按下面這張清單擺放元件型號/取值數(shù)量作用單片機(jī)AT89C521主控也可以用 AT89C51按鍵BUTTON88 個音階鍵三極管2N22221放大引腳驅(qū)動電流電阻1kΩ1三極管基極限流無源蜂鳴器SOUNDER 或 SPEAKER 模型1發(fā)聲元件電源5V / GND-供電連線規(guī)則P1.0 到 P1.7 各接一個按鍵按鍵另一端接地。P1 口內(nèi)部有上拉不按為高按下拉低所以不需要外接上拉電阻。這里有個常見誤區(qū)是以為所有 I/O 口都有內(nèi)部上拉實(shí)際上 P0 口是開漏結(jié)構(gòu)、必須外接上拉電阻P1/P2/P3 才是準(zhǔn)雙向口。如果按教材把按鍵接到了 P0每個按鍵都要配一個 10kΩ 上拉。發(fā)聲部分用 NPN 三極管驅(qū)動P3.5 經(jīng) 1kΩ 電阻接到 2N2222 基極發(fā)射極接地集電極接蜂鳴器負(fù)極蜂鳴器正極接 5V。引腳輸出高電平時三極管導(dǎo)通、蜂鳴器通電輸出低電平時截止。這個接法比直接用引腳帶蜂鳴器音量好得多實(shí)板表現(xiàn)尤其明顯。提示Proteus 元件庫里叫 BUZZER 的模型很多是自帶振蕩源的有源蜂鳴器一接直流就自己響頻率固定。做這個項目必須選需要外部方波驅(qū)動的無源響片模型或者干脆用 SPEAKER。如何區(qū)分見 4.3。4.2 芯片屬性里最容易錯的三個設(shè)置時鐘頻率、HEX 路徑、電源仿真圖畫出后雙擊 AT89C52 打開屬性對話框有三個設(shè)置決定成敗。第一是 Program File也就是加載編譯好的 HEX 文件。用 C51 開發(fā)環(huán)境把上一章的工程編譯通過后把生成的 HEX 路徑填進(jìn)去。這一步是仿真的前提芯片沒有程序文件仿真跑起來只是一塊死芯片。填路徑時注意目錄里別帶中文和空格這是 Proteus 的老毛病路徑里有中文會加載失敗或者仿真時芯片完全不工作看不出任何報錯。第二是 Clock Frequency。很多默認(rèn)模板里這個值不是 12MHz可能是 4MHz 或者 1MHz。代碼里的初值按 12MHz 算這里卻是別的頻率結(jié)果是所有音按固定比例跑偏。改成 12MHz 后波形立刻回到正確位置。這個屬性在“編輯元件”對話框里就能看到。第三是電源和復(fù)位。Proteus 的 MCU 模型有時會自動處理電源腳但如果你在畫圖過程中刪改過連線要確認(rèn) VCC40 腳接 5V、GND20 腳接地。復(fù)位腳 RST 懸空一般也能跑但嚴(yán)謹(jǐn)做法是接一個 10μF 電容到 VCC、10kΩ 電阻到 GND形成上電復(fù)位電路這和實(shí)板保持一致。4.3 仿真交互操作按鍵盤按鍵觸發(fā)電路按鍵的發(fā)聲驗證設(shè)置完成后點(diǎn)運(yùn)行按鈕然后用鼠標(biāo)點(diǎn)擊仿真圖里的按鍵。Proteus 的 BUTTON 模型是瞬動開關(guān)點(diǎn)擊時相當(dāng)于按下點(diǎn)擊旁邊空白處再操作時會松開具體交互手感取決于用的按鍵模型種類習(xí)慣一下就好。每點(diǎn)一個鍵應(yīng)該聽到對應(yīng)的音高松開聲音停止。如果點(diǎn)了沒聲音先檢查兩件事一是蜂鳴器模型是不是選成了有源 BUZZER判斷方法很直接——把蜂鳴器單獨(dú)接 5V 和 GND一接直流就響的是有源不響的才是無源響應(yīng)片。程序輸出的是方波必須配無源響應(yīng)片。二是看仿真動畫選項里的音頻是否被關(guān)閉Proteus 的音頻輸出走系統(tǒng)聲卡系統(tǒng)靜音也會讓你誤以為程序錯了。比起耳朵更硬核的驗證是直接在 BEEP 引腳上掛一個示波器OSCILLOSCOPE。示波器顯示的方波頻率應(yīng)當(dāng)和音名頻率一致按下中央 C 鍵看到 262Hz按下高音 C 看到 523Hz。用示波器驗證有兩個好處不受電腦音量影響而且波形頻率是數(shù)字證據(jù)寫課程設(shè)計報告時直接截圖用。5. 電子琴項目常見問題與避坑仿真不響、音不準(zhǔn)、串鍵前面四章把路鋪好了真正讓人翻車的全集中在這一章。每條都按現(xiàn)象、原因、解決三個層次來寫建議照著一條條排查。5.1 仿真沒有任何聲音把有源蜂鳴器模型當(dāng)成了無源響片現(xiàn)象程序編譯通過、HEX 也加載了按鍵按下去芯片引腳電平有變化但聽不到音符最多是“嘀”一聲固定音。原因元件庫里的 BUZZER 模型很多自帶振蕩源給它直流電就自顧自振蕩頻率固定跟你程序產(chǎn)出的方波毫無關(guān)系有些型號內(nèi)部電路還會把外部方波吃掉。程序以為在播放哆來咪蜂鳴器實(shí)際在唱一個調(diào)。解決換成無源響片或喇叭模型也就是需要外部方波驅(qū)動才發(fā)聲的那類。簡單區(qū)分方法把元件直接接到 5V 和 GND 上一接直流就響的是有源接直流不響、給方波才響的是無源。這個項目必須選后者。5.2 音高整體偏高或偏低晶振頻率與代碼初值不一致現(xiàn)象能響但所有音都按同一個比例跑偏比如按下 C 聽起來像 B 甚至 C#整體偏高或偏低一個半音以上。原因代碼里按 12MHz 算初值Proteus 芯片屬性里的 Clock Frequency 卻是別的值。定時器計數(shù)脈沖頻率變了同樣的初值對應(yīng)的溢出頻率自然變了。解決雙擊芯片把 Clock Frequency 改成和代碼一致再用示波器量 BEEP 腳頻率核對。換晶振時必須兩邊同步改只改代碼或只改仿真屬性都會繼續(xù)跑偏。這個坑在實(shí)板上表現(xiàn)為“換了塊開發(fā)板音就變”因為板上晶振和你原來算表的頻率不一樣。5.3 按一下鍵連續(xù)響或者響兩個音缺少消抖與松手檢測現(xiàn)象單次按鍵發(fā)出“咔啦”般的快速重復(fù)音或者同時觸發(fā)兩個鍵的聲音音階明顯錯亂。原因機(jī)械按鍵按下和松開的瞬間電平在幾毫秒內(nèi)抖動多次主循環(huán)掃描速度遠(yuǎn)快于抖動結(jié)束時間一次按壓被識別成多次。矩陣鍵盤場景下兩個鍵同時按下時掃描到的電平組合會被誤讀成一個不存在的鍵位。解決按下后延時 10~20ms 再讀一次兩次都是低才算有效松開同樣要確認(rèn)避免抖動產(chǎn)生瞬間的“按下-松開”交替。矩陣鍵盤還要加“每次只接受一個新鍵、松開后清標(biāo)志”的邏輯不能把電平組合直接當(dāng)鍵號。5.4 長按穩(wěn)定但快速連按時聲音發(fā)毛初值更新時機(jī)不對現(xiàn)象按住一個鍵時聲音穩(wěn)定快速切換按鍵或連續(xù)敲擊時音頭出現(xiàn)刺耳的雜音段像破音。原因切換鍵時主循環(huán)更新reload_value的時機(jī)撞上了中斷中斷讀到了半新半舊的初值或者中斷服務(wù)里先做了別的操作再重裝初值多跑了幾個計數(shù)脈沖造成瞬間頻率突變。解決把重裝初值的兩行放在中斷服務(wù)最前面主循環(huán)更新 16 位變量前先關(guān)總中斷賦值完再打開。對課程設(shè)計來說這個 3 行改動就能消除絕大多數(shù)破音成本很低。5.5 仿真正常、實(shí)板不響或音量極小引腳驅(qū)動能力不夠現(xiàn)象仿真里聽得清清楚楚下載到開發(fā)板或自焊電路后音量像蚊子叫甚至完全無聲。原因51 的 I/O 口驅(qū)動能力有限直接帶無源響片功率不夠電平也被拉垮。有些開發(fā)板板上蜂鳴器是有源加三極管組合程序按無源方波去驅(qū)動自然發(fā)不出正確音。解決實(shí)板上按第 4 章的 NPN 三極管方案接基極串 1kΩ蜂鳴器接集電極與電源之間。先確認(rèn)蜂鳴器是無源的類型有源蜂鳴器內(nèi)置振蕩源會和外加方波沖突。排查時先用示波器確認(rèn)引腳上有正確頻率的方波再懷疑功率問題順序別反。6. 從“能響”到“能彈”預(yù)置歌曲、加一個八度與驗證自己的作品6.1 預(yù)置歌曲的數(shù)據(jù)結(jié)構(gòu)音符 時值電子琴能響之后最自然的擴(kuò)展是讓它自動播一首歌。數(shù)據(jù)結(jié)構(gòu)就兩樣音符在音高表里的下標(biāo)加上這個音的時值。比如“小星星”的頭兩句用 0~7 表示音高表下標(biāo)0xFF 表示休止// 自動播放示例1 1 5 5 6 6 5 - 4 4 3 3 2 2 1 - unsigned char code song[] { 0, 0, 4, 4, 5, 5, 4, 0xFF, 3, 3, 2, 2, 1, 1, 0, 0xFF };播放循環(huán)里用一個節(jié)拍計數(shù)器控制每個音的時長比如一拍固定 300ms用 T1 或軟件延時都行。每播一個下標(biāo)就執(zhí)行一次“查表更新初值 打開發(fā)聲”休止符則關(guān)閉發(fā)聲再等一拍。這個結(jié)構(gòu)可以說是 51 音樂播放器的標(biāo)準(zhǔn)骨架理解了它換任何歌曲都只是換數(shù)組內(nèi)容。6.2 用矩陣鍵盤擴(kuò)展音域和功能鍵8 個獨(dú)立按鍵占滿一個口想彈兩個八度就不夠用了。常見做法是改成 4×4 矩陣16 個鍵可以排 14 個音加一個八度切換鍵和一個播放鍵。矩陣掃描的原理是列線輪流輸出低電平、行線讀回狀態(tài)void scan_matrix(void) { unsigned char col, row_data; for (col 0; col 4; col) { P2 | 0xF0; // 4 根列線先全部恢復(fù)高 P2 ~(0x10 col); // 只把當(dāng)前列拉低 _nop_(); // 留一個機(jī)器周期讓電平穩(wěn)定 row_data P2 0x0F; // 讀 4 根行線 if (row_data ! 0x0F) // 有行被拉低說明該列上有鍵按下 { // 由 col 和行號算出鍵值再映射到音高表 break; } } }掃描結(jié)果是一維鍵號鍵號和音名之間放一張映射表這樣按鍵排布隨便怎么走線都能對上音。矩陣方案比獨(dú)立按鍵多一個“串鍵”風(fēng)險兩鍵同時按時掃描結(jié)果可能是第三個鍵所以一定要加上一次只認(rèn)一個新鍵的標(biāo)志位。6.3 驗證方法電子琴 app、示波器、逐條對照做完擴(kuò)展最該做的是系統(tǒng)驗證一遍。手機(jī)裝一個調(diào)音器類應(yīng)用或者用在線鋼琴頁面作參照按住某個鍵看調(diào)音器顯示的音名是否和鍵位一致。音高差一點(diǎn)就微調(diào)音高表里的初值一次只加一兩個計數(shù)調(diào)完再聽。Proteus 里則用示波器逐個鍵量頻率把 8 個音的實(shí)際頻率記成一張表和理論值對比誤差在 ±1Hz 內(nèi)就算合格。這個逐條對照的習(xí)慣我把每一次實(shí)驗記錄都留在工程附帶的說明里。說實(shí)話電子琴做到“能響能彈”剩下就是熟能生巧。我個人的習(xí)慣是每次改完參數(shù)先給示波器留一張截圖再上耳朵驗收這個小習(xí)慣幫我少走了很多彎路。希望這篇能把你在電子琴項目上的坑提前填平希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取