
简介这是一份基于51单片机实现8键电子琴的完整毕业设计资料适合电子信息、嵌入式相关专业学生用于课程设计、毕业设计或单片机入门实践。资源围绕电子琴的硬件搭建与软件编程展开覆盖按键扫描、音符识别、频率生成及时序控制等核心模块并囊括模拟音频处理、电源管理、PCB设计与抗干扰等知识要点。压缩包共57个文件大小2.12MB主要包含C语言源程序与工程文件.c、.uvproj、原理图.sch、.SchDoc、Proteus仿真文件.DSN、.DBK、参考论文与开题报告.doc/.pdf以及元件清单、实物照片和答辩技巧等辅助文档。已有4986人学习下载。通过这份资料读者可以对照源程序与仿真电路完成整个电子琴的设计验证理解51单片机从按键输入到音频输出的完整链路并借助论文与答辩材料梳理项目思路提升嵌入式系统综合开发能力。1. 8键电子琴的51单片机设计从按键到音符核心是频率把一个8键电子琴做成毕业设计难点通常不在按键而在于声音怎么来。按下按键只是一个电信号想要让蜂鸣器发出 do、re、mi本质是用51单片机生成特定频率的方波do 的 261.6Hz、re 的 293.7Hz每差一个音定时器初值就差几十微秒。这个设计适合刚完成单片机基础课程的学生也适合想补全“定时器中断、按键消抖、Proteus仿真、原理图绘制”整条链路的人。很多人的误区是先去抄一个完整源程序结果仿真不响连问题出在晶振频率还是驱动电路都分不清。这篇文章就把硬件原理、源程序、仿真验证拆开讲让你拿到任意一款 STC89C52RC 或 AT89S51 都能自己搭出来。2. 8键电子琴硬件设计晶振、蜂鸣器与按键组的原理图拆解2.1 音调频率与定时器初值的关系为什么用12MHz晶振51单片机的定时器工作在方式1时是16位计数器每计一个数的时间是单片机的机器周期。使用12MHz晶振时机器周期是1MHz定时器每隔1微秒加一这个换算关系让初值计算非常直观。要用定时器T0产生一个方波通常是让引脚在定时器溢出时取反溢出周期就是方波周期的一半。某个音对应方波频率为F则定时时间为 T 1/(2F)需要装入定时器的初值是 N 65536 - T * 1000000。比如中央C的频率是262HzT 约 1908 微秒初值就是 65536 - 1908 63628换算成十六进制是 F88C。12MHz晶振在原理图上不只是一个接在XTAL1和XTAL2之间的元件还要配合两颗20pF到30pF的贴片电容接地。电容大小影响起振稳定性和频率误差Proteus仿真时用默认值往往也能跑但做实物时建议不要省略。很多毕业设计原理图直接用“单片机最小系统”模块包含复位电路、晶振电路、电源去耦电容这个做法是对的只要保证EA引脚接高电平或VCCALE不驱动外部存储器时保持稳定即可。2.2 按键输入电路独立按键与矩阵键盘的选择8键电子琴常用两种接法独立按键和矩阵键盘。独立按键的电路最简单每个按键一端接P1口或P2口另一端接GND软件给端口写1按下后读到0。矩阵键盘用4根行线加两根列线节省IO但程序里要多一步扫描逻辑。对于纯电子琴设计我更推荐独立按键理由是消抖和扫描代码逻辑直观毕业答辩时更容易解释清楚。原理图里每个按键并联一个100nF电容也可以起到硬件消抖作用但对蜂鸣器发声影响不大通常不接。按键端口不要直接使用P0口因为P0是开漏结构内部没有上拉电阻。如果非要用P0必须在原理图里给每个按键加上10kΩ上拉电阻。STC89C52RC的P1、P2、P3都有内部上拉直接接按键不会有电平浮空问题。按键的另一端接地这个“低有效”设计是大多数51单片机独立按键的标准接法源程序里对应判断if(key 0)。2.3 发声电路有源蜂鸣器 vs 无源蜂鸣器与驱动管同样的8键电子琴发声器件选错了程序再好也白搭。有源蜂鸣器内部有振荡电路通电就响但只能发出固定频率不能通过定时器改变音调只能当报警器用。无源蜂鸣器需要外部提供方波信号才能发声频率由你给这才是电子琴该用的器件。原理图上无源蜂鸣器常用一个NPN三极管如S8050驱动单片机引脚通过1kΩ限流电阻连到三极管基极发射极接地集电极接蜂鸣器负极蜂鸣器正极接VCC同时并联一个续流二极管防止反向电动势。这段驱动电路经常被简化但实物和仿真有区别Proteus里信号源直接连蜂鸣器正极也能响因为理想电源和理想器件忽略驱动能力。实物的P1口驱动能力只有20mA左右带动无源蜂鸣器时声音很小或完全不响。原理图设计时把三极管电路画全仿真验证时就能直接验证程序逻辑之后做成PCB也不会返工。下面是一份8键电子琴常用的音符频率和12MHz晶振下的定时器初值表低音到高音按自然音阶排列。音符频率(Hz)定时周期(us)定时器初值(十进制)定时器初值(十六进制)do2621908636280xF88Cre2941701638350xF95Bmi3301515640210xFA15fa3491433641030xFA67sol3921276642600xFB04la4401136644000xFB90si4941012645240xFC0C高音do523956645800xFC44注意表格里的初值用的是取整后的定时周期误差控制在0.5%以内听感上完全能接受。如果你希望更精确可以用浮点计算后四舍五入但C51单片机做浮点运算会额外占用代码空间和运行时间非必要不推荐。整个硬件方案下来所用到的IO口是P1口的8个按键输入和一个P3.4或P2.0作为蜂鸣器输出完成一个最小系统加按键、无源蜂鸣器驱动原理图大约能控制在30个元件以内。3. 51单片机8键电子琴源程序实现查表、定时器中断与按键消抖3.1 代码结构与音符查表法8键电子琴的源程序最常用的结构是主函数初始化定时器和IO口然后在一个while循环里扫描按键检测到按下后把对应的音符频率初值装入定时器同时置位蜂鸣器输出标志。另一部分代码在定时器中断里翻转蜂鸣器引脚形成方波。这样把“按键”和“发声”分开好处是按键扫描过程中发生的中断不会影响发声的连续性。音符频率初值适合用查表法而不是用switch计算。表格可以定义成两个数组一个存8个音符的定时器初值高位一个存低位。这样主程序里只要拿到按键编号0到7就能直接取出高字节和低字节。下面是一份可以直接在Keil里编译的核心代码结构。#include reg52.h #define KEY_PORT P1 #define BEEP P3_4 // 12MHz晶振下音调对应的定时器初值高字节 unsigned char code tone_h[] {0xF8, 0xF9, 0xFA, 0xFA, 0xFB, 0xFB, 0xFC, 0xFC}; // 12MHz晶振下音调对应的定时器初值低字节 unsigned char code tone_l[] {0x8C, 0x5B, 0x15, 0x67, 0x04, 0x90, 0x0C, 0x44}; unsigned char key_state 0xFF; // 按键释放标记0x00表示按下 unsigned int beep_delay 0; // 发声时长计数 void timer0_init(void) { TMOD 0xF0; // 清零T0的模式位 TMOD | 0x01; // T0工作在方式116位定时 TH0 0xFC; // 默认装入“静音”初值 TL0 0x18; ET0 1; // 允许T0中断 EA 1; // 开总中断 TR0 1; // 启动T0 } void beep_on(unsigned char note) { TR0 0; // 先停止定时器避免临界区竞争 TH0 tone_h[note]; // 查表获得高字节 TL0 tone_l[note]; // 查表获得低字节 TR0 1; // 重新启动定时器 } void timer0_isr(void) interrupt 1 { BEEP ~BEEP; // 翻转蜂鸣器引脚产生方波 TH0 tone_h[key_state]; // 重新装载初值 TL0 tone_l[key_state]; if(beep_delay 0) beep_delay--; } void main(void) { unsigned char i; timer0_init(); while(1) { key_state 0xFF; // 按键扫描8个独立按键低电平有效 for(i 0; i 8; i) { if((KEY_PORT (0x01 i)) 0x00) { key_state i; // 记录当前按下的是第几个键 beep_on(i); // 更新定时器初值 beep_delay 4000; // 持续发声约4万次中断 while((KEY_PORT (0x01 i)) 0x00); // 等待释放 } } // 如果所有按键释放停止发声 if(key_state 0xFF) { TR0 0; BEEP 0; } } }这段代码的main函数里有一个短小的死循环等待按键释放在真实键盘输入场景中会造成后续按键无法识别这是很多人都踩过的坑。更合理的做法是去掉while等待只记录按键事件发声时长用beep_delay计数控制定时器中断里递减归零后关闭蜂鸣器。上面的代码保留了等待是为了让第一次点按的效果清晰实际做一个能连续弹奏的电子琴时请改成事件触发方式。3.2 定时器0中断产生方波的程序设计定时器中断是源程序里最需要理解的部分。初始化时TMOD等于0x01选择16位定时模式每溢出一次进入interrupt 1在中断服务函数里反转P3_4。由于音调频率不同每次中断时装入的初值不能写死在中断里而是从tone_h和tone_l数组中读取数组下标就是当前按下音符的编号。这里有一个关键点中断服务函数里不能再调用beep_on因为beep_on里写了TR00在中断执行期间关闭定时器会造成下一次翻转时间错乱只应该直接修改TH0和TL0。beep_delay这个变量控制了“一次按键发声多久”。当它大于0时中断里每进入一次就减1减到0后主循环检测到key_state0xFF才关闭蜂鸣器。你也可以不用计数变量改成按键按下时TR01按键松开时TR00这样更像真实的电子琴可以产生长音和短音效果。程序结构上建议把按键状态读取和发声控制分离主循环里只负责查键值定时器中断只负责翻转引脚两者通过一个全局变量通信。3.3 按键扫描与软件消抖响应速度和误触决策机械按键在按下和释放的瞬间会产生约5ms到10ms的电平抖动直接读取端口会声音断断续续。常见的软件消抖是延时10ms再读一次比如下面的代码片段unsigned char key_scan(void) { unsigned char i; for(i 0; i 8; i) { if((KEY_PORT (0x01 i)) 0x00) { delay_ms(10); if((KEY_PORT (0x01 i)) 0x00) { return i; } } } return 0xFF; }delay_ms(10)使用软件延时在12MHz晶振下通常是一个双层循环大约执行10毫秒。这个延时会造成弹奏时每个音符滞后但人耳基本感觉不到所以是可行的。如果你追求更流畅的弹奏体验可以把延时压缩到5ms或者完全去掉靠定时器中断里的计数来平滑抖动。事实上电子琴应用对误触不敏感因为手指按压是持续动作偶尔一次电平抖动只会让音色波形产生极短的毛刺很难被听到。需要特别注意的是按键判断使用“低有效”还是“高有效”必须和原理图对应。原理图按键接GND就使用(KEY_PORT (0x01 i)) 0x00判断按下。有的原理图为了画得方便把按键接到VCC程序里就要改成读1。源程序里如果写错了有效电平现象就是蜂鸣器一直响按下按键反而静音。这类问题只靠读代码很难发现必须对着原理图逐位核对。下面给出按键与音符的映射关系方便在Proteus仿真时用键盘上的数字键对点调试。单片机IO口按键编号音符十六进制初值P1.00do0xF88CP1.11re0xF95BP1.22mi0xFA15P1.33fa0xFA67P1.44sol0xFB04P1.55la0xFB90P1.66si0xFC0CP1.77高音do0xFC44在Keil里打开“Options for Target”时记得检查晶振频率设置。默认的10MHz会让定时器时间基准偏小仿真时声音频率整体偏高。把晶振设为12MHz后编译把生成的HEX文件加载到Proteus里的STC89C52RC元件上运行音高会和真实电子琴接近。4. Proteus仿真8键电子琴从原理图到仿真运行的完整流程4.1 Proteus电路搭建元器件与连线Proteus仿真8键电子琴时首先新建一个设计文件从元件库中添加AT89C51或STC89C52、RES、CAP、CRYSTAL、NOT等元件。由于Proteus不同版本对STC支持程度不同最稳妥的是使用AT89C51作为仿真芯片源程序在Keil中把Device选为Atmel AT89C51即可功能和STC89C52RC没有本质区别。电路由三个模块组成单片机最小系统、8个按键、蜂鸣器驱动。具体连线时P1口8个引脚分别接按键的一端按键另一端统一接GND。蜂鸣器驱动电路可以用一个NPN三极管也可以在Proteus里直接使用元件库中的BUZZER有源蜂鸣器但要注意有源蜂鸣器无法体现程序生成的频率变化所以仿真中应选择无源蜂鸣器常见的是SOUNDER元件。Proteus中无源蜂鸣器通常用交流电源驱动实际接法是蜂鸣器一端接VCC另一端接三极管C极通过P3.4控制。Proteus里如果点击仿真没有声音检查右下角是否启用了音频输出。4.2 Keil生成HEX与仿真联调在Keil里点击Output标签勾选Create HEX File编译工程后会在项目目录下生成.hex文件。然后在Proteus中双击单片机元件在Program File一栏选择该HEX文件晶振频率填12MHz点击运行。如果一切都正确按动P1口的按键蜂鸣器会发出对应音符用Proteus虚拟示波器可以看到P3.4脚输出方波频率值和第2章的表格一致。仿真联调最常见的问题是没有加载HEX文件或者是HEX文件路径包含中文和空格导致Proteus读取失败。解决方法是把Keil工程和Proteus设计文件放在同一个纯英文目录下比如D:\project\keyboard。还有一点Proteus运行仿真时如果单片机没有执行程序可以单步运行Keil里的调试器观察tone_h[tone]数组的取值大多数情况下会发现是数组下标越界因为按键扫描返回0到7之外的值。这个问题在清一色的“51单片机毕业设计参考程序”里尤其常见我的习惯是在key_scan里对返回值做一个范围限制超过7直接返回0xFF。4.3 仿真中的三个常见坑频率偏移、蜂鸣器不响、按键失灵第一个坑是频率偏移。使用Proteus的虚拟示波器观察P3.4的波形时如果频率偏了将近十分之一检查单片机属性里的晶振频率。Proteus中默认时钟频率有时是10MHz而代码里按12MHz计算初值频率会同步偏移。第二个坑是蜂鸣器不响。Proteus元件库中的BUZZER是有源蜂鸣器接到P3.4后高电平会直接响但不会随方波变化需要改用SOUNDER并且给它并联一个合适的电阻或电感的等效模型。如果你不想换元件可以先用LED观察P3.4是否以几毫秒周期闪烁能闪烁说明中断和定时器正常问题在蜂鸣器模型选错。第三个坑是按键失灵。仿真时单击鼠标按键有时会出现“按下一次却触发多次”的现象这是Proteus的按键模型本身带有抖动。解决方法是把源程序的消抖延时从10ms加到20ms或者在代码里加入按键释放检测。不要上来就怀疑程序死循环先在主循环里让某个LED跟着按键翻转如果LED正常而蜂鸣器不正常说明按键扫描没问题问题出在发声控制。下面是一个仿真实用的排错表。现象优先检查点常见处理动作有波形但没声音蜂鸣器有源/无源型号改用SOUNDER并检查三极管连接声音完全静音定时器是否启动TR0是否置1EA1是否写在初始化中音调偏低晶振频率设置单片机属性改为12MHz弹一个键触发两个音消抖延时不足延时加到15~20ms高音与低音混淆初值数组与按键顺序错位核对音符频率表在Proteus中运行仿真的速度通常比实物快因为单片机的指令周期在仿真中可能根据CPU占用率有所浮动所以听感会和真实电路略有差异这属于正常现象。验证逻辑正确性以后尽量在实物上再校准一次声音延迟。5. 示波器校准音准与低成本扩展玩法5.1 用Proteus虚拟示波器观察音符频率完成基本仿真后建议用虚拟示波器对8个按键逐一测量频率而不是靠耳朵判断。Proteus左侧工具栏选择Virtual Instruments添加OSCILLOSCOPE探头接到P3.4引脚和GND。运行时按住P1.0不放示波器上看到的应该是对称方波测量周期取倒数就是实际频率。do的实测值会在261Hz到264Hz之间浮动这个范围符合要求。如果频率偏高把定时器初值整体调大偏低就调小。每次修改只动一个音符的初值不要批量替换。比如do偏低到250Hz计算差值比例是5%那么初值需要增加约5%即63628乘以1.05等于66809超过65536无法装入说明这个方向错了——初值增大只能让定时时间更长、频率更低所以偏低应该减小初值。记住这个方向和直觉相反很多人在调音时把反方向错了。5.2 扩展数码管显示、录音回放与多音色毕业设计如果只做一个会响的电子琴答辩时亮点偏少。我见过很多学生用同一个硬件扩充到LCD1602显示当前音符或加一个四位LED数码管显示简谱编号。实现方式是在按键扫描返回音符编号i后把tone_h[i]和tone_l[i]之外再用一个数字表存储显示编码送七段数码管。这部分改动不需要改中断逻辑只需要在主循环里查表并调用显示函数适合时间不足的时候快速加分。录音回放也是一个常见的扩展玩法。定义一段数组保存按键序列和时间间隔比如按下按键时把音符下标存入数组同时记录10ms计的间隔。回放时用定时器1作为延时基准依次调用beep_on并保持相同时长。这个扩展刚好能把已经学过的两个定时器都用起来T0产生音调T1产生节拍代码量增加不大原理图不需要任何改动。多音色则比较麻烦需要把方波改成PWM波用T0做频率载波、T1做占空比调节模拟出类似三角波或梯形波的音色但51单片机性能有限效果只能说“有变化”不如直接使用DAC或语音芯片不属于本设计的核心。最后的实用技巧是调音时把8个按键做成一个循环扫描程序让单片机自动依次播放大调音阶用示波器记录每个音的周期比手动按键高效得多。具体做法是主循环里用for(i0;i8;i)依次调用beep_on(i)每个音持续200ms这样可以在几分钟内完成所有频率校验也方便录制一段演示视频作为答辩材料。本文还有配套的精品资源点击获取