简介本资源是一套基于AT89C51单片机的多功能电子琴系统完整设计包面向嵌入式初学者、单片机课程实践者及电子设计竞赛备赛学生解决从硬件仿真到软件驱动的一体化开发问题。资源共23个文件包含4个C源码main.c、MatrixKey.c、LCD1602.c、Delay.c、3个头文件.h、1个Proteus工程.pdsprj、1个Keil项目.uvproj及配套hex、bat、md等文件涵盖键盘扫描、定时器音频生成、LCD1602动态显示、LED音符指示与独立按键音效切换等核心功能模块压缩包仅96KB轻量易解压学习。已有58人下载学习资源结构清晰C/H文件实现模块化编程Proteus工程支持即开即仿README.md提供快速上手指引Keil工程可直接编译调试完整呈现从频谱分析→定时器频率计算→矩阵键盘识别→声音与显示协同控制的全流程实现逻辑。 最近好几个读者在问单片机课程设计该怎么选方向我的建议一直是那个经典组合C语言写代码、Proteus做仿真、AT89C51当核心。这套组合下来无论你是学电子、自动化还是计算机都能把一个完整项目的流程跑通。今天这篇就把一个多功能电子琴系统的完整做法拆开讲从需求、原理图到代码、仿真调试全部覆盖附带的项目源码和文档说明我会逐段解释方便你直接改造复用。1. 这个电子琴系统到底要做什么需求拆解与技术选型1.1 课程设计里的“多功能”通常指什么先别急着打开Proteus画图第一步要把“多功能”三个字落到具体功能列表上。我和不少学生交流过发现很多人拿到题目就直接写代码结果写到一半发现按键不够用、蜂鸣器不出声、功能切换逻辑混乱全部推倒重来。所以这里先做一个标准的课程设计需求拆解。一个典型的“多功能电子琴”至少要覆盖以下模块手动弹奏模式按下按键蜂鸣器发出对应频率的声音松开停止。这是电子琴的基础功能对应真实电子琴的“琴键”。自动播放模式预存几首歌曲比如《小星星》《两只老虎》单片机按照歌谱自动扫描并播放数码管或LCD同步显示当前进度。音调切换功能同一组按键可以切换低音、中音、高音三个八度相当于电子琴上的八度键。显示功能用LCD1602或数码管显示当前音调、音符、模式让用户知道系统处于什么状态。这四个功能做完题目里“多功能”三个字就站得住脚了而且工作量适中——既不会因为太简单被老师质疑也不会因为太复杂做不完。我见过有些同学一口气加了录音回放、节拍器、音量调节结果一个学期都在调试中断优先级最后答辩时功能演示翻车。课程设计的核心是“把主干功能做扎实”扩展功能做1到2个即可。1.2 为什么选AT89C51而不是STM32这是个经常被问到的问题。现在STC8、STM32很流行为什么还要用十几年前的AT89C51原因有三点都很现实。第一Proteus对AT89C51的模型支持非常成熟。你打开元件库搜“AT89C51”直接就能用不需要额外配置而某些STM32芯片的Proteus模型不仅难找仿真速度还慢得让人抓狂。第二AT89C51的内部资源恰好够用定时器T0/T1用来产生音符频率P1口接按键P0P2接LCD资源刚好满负荷运转很能体现“合理分配单片机资源”的设计思路答辩时有得讲。第三51内核是绝大多数高校单片机课程的必修内容C语言写51对你来说负担最小能把注意力放在系统逻辑上而不是花大量时间学寄存器配置。还有一个重要原因是逻辑电平。电子琴系统的核心外设是蜂鸣器和LCD1602这两者都是5V器件和AT89C51完美匹配。如果换3.3V的STM32还得加电平转换电路徒增复杂度。1.3 系统模块划分和总体工作流程整个系统的硬件模块划分是这样的输入模块4x4矩阵按键或独立按键→ 按键扫描 控制模块AT89C51单片机 → 音调解析、模式切换、播放控制 执行模块LCD1602显示、蜂鸣器发声工作流程用大白话说就是单片机不断扫描键盘矩阵扫描到按下按键时判断当前处于哪个模式——如果是弹奏模式就根据按键编号和当前八度查表得到音符频率配置定时器的初值让定时器产生对应频率的方波驱动蜂鸣器如果是自动播放模式则读取预存歌谱数组逐个音符播放同时更新LCD显示。整个过程非常像一个人看谱弹琴眼睛看谱读数组、手指按琴键控制IO口、耳朵听声蜂鸣器发声。2. Proteus硬件搭建元件选型、接线与原理解析2.1 元件清单与Proteus里的对应名称打开Proteus 8 Professional新建工程后先别画线把元件全部找齐放好。这是我认为整个Proteus操作里最值得注意的一步——元件名称搞错了画半天全是红线。下面是我常用的元件清单直接对照搜元件功能Proteus关键字数量说明主控芯片AT89C51140引脚DIP封装晶振CRYSTAL112MHz电容CAP222pF配合晶振使用电解电容CAP-ELEC110uF复位电路电阻RES若干上拉电阻10k、限流电阻等按键BUTTON164x4矩阵键盘用蜂鸣器SOUNDER1重要无源蜂鸣器LCD显示LM016L1这是Proteus里的LCD1602模型三极管NPN如2N2222、BC5471实物中用Proteus仿真可以省略排阻RESPACK-81简化P0口上拉电阻画法这里提醒一下Proteus里蜂鸣器叫BUZZER的元件有两类一类是底下写着“Active”的有源蜂鸣器一类是“Passive”的无源蜂鸣器元件库里的SOUNDER默认就是无源蜂鸣器。这个区别直接决定你的电子琴能不能出声后面专门讲。2.2 蜂鸣器为什么要选无源型先说结论电子琴系统必须使用无源蜂鸣器。如果你用了有源蜂鸣器无论怎么配置定时器它都只会发出“滴——”的固定单音音调完全没法改变电子琴也就名存实亡了。原理其实很直观。有源蜂鸣器内部自带振荡电路只要通电就自己振荡发声频率是固定的你只能控制它响还是不响。而无源蜂鸣器内部没有振荡源需要外部给它一个特定频率的方波信号它才振动发声——方波的频率就是它发出的声音频率。这正是我们需要的单片机通过定时器输出不同频率的方波蜂鸣器就发出对应音调的声音和广播里说“音调由频率决定”完全是同一个逻辑。在Proteus里无源蜂鸣器的仿真模型特别简单模型内部是一个压电换能器元件你给它一个方波信号直接就能响。但也要注意Proteus的蜂鸣器模型有个参数叫“Operating Voltage”默认值如果是12V而你的单片机输出是5V声音可能会很小甚至不出声需要双击蜂鸣器把电压改为5V这个坑我后面细讲。2.3 晶振、复位电路与P0口上拉的接线规则单片机最小系统三件套电源、晶振、复位电路仿真里一个都不能少少了直接报错或者程序跑飞。晶振电路AT89C51的XTAL119脚和XTAL218脚之间接12MHz晶振两个引脚分别对地接22pF电容。我见过不少学生直接省掉电容仿真确实也能跑但这属于“靠运气的工作”因为实际电路里晶振起振需要负载电容而且Proteus在做某些时序分析时没有电容会导致系统时钟不稳定。养成好习惯电容必须加。复位电路RST9脚接一个10uF电解电容到VCC再接一个10k电阻到GND同时并联一个按键这是标准的“上电自动复位手动复位”结构。复位电路的作用是让程序从第一条指令开始执行仿真中是必须有这个复位的否则单片机可能处于未知状态。P0口上拉这是51单片机特有的事。P0口内部没有上拉电阻是“开漏”输出结构直接驱动外部器件时高电平上不去。所以凡是P0口接的器件比如LCD1602的8位数据线必须在每个引脚外接一个10k上拉电阻到VCC。用排阻最省事选RESPACK-8就行。这块忘记了LCD显示全是乱码什么都看不清。AT89C51的EA31脚必须接VCC其实这也是个容易忽略的地方。有些Proteus例程里EA悬空也能运行但严格来说EA接高电平表示从内部程序存储器取指令接低电平表示从外部取指令。我们用的是片内4KB Flash所以EA必须接VCC否则程序根本执行不了。2.4 矩阵按键与LCD1602的接线细节4x4矩阵键盘的接法是固定的套路把16个按键排列成四行四列每一行的四个按键一端连在一起成为行线每一列的四个按键另一端连在一起成为列线。连接到单片机时行线接P1.0到P1.3列线接P1.4到P1.7。扫描原理网上有很多说法我用自己的话翻译一遍单片机先把所有行线输出高电平然后依次将每一行输出低电平行扫描再读取列线电平。如果某一列被读到了低电平说明该行该列的按键被按下。比如当前扫描到第2行输出低电平同时P1.5读到低电平那就查坐标表得到按键编号。这个过程非常快人按一次键可以被扫描到上百次所以不会有漏键问题。LCD1602Proteus里叫LM016L的接线也是标准接口8位数据线D0-D7接P0口RS接P2.0RW接P2.1E使能接P2.2。Proteus里的LM016L模型比实物简单RW可以直接接地但为了代码通用性最好还是接上。VSS接地VDD接5VV0通过一个电位器接地用来调节对比度仿真里这个电位器不接也行显示不清楚时再接。3. 音符频率表与定时器初值电子琴发声的核心原理3.1 声音是怎么产生的频率、半周期和方波理解电子琴发声原理先要搞清楚一个概念频率决定音调。钢琴上的中央C也就是do频率是523Hz高八度的do是1046Hz翻一倍低八度就除以2。人耳感知到“音的升高”本质上是振动频率在翻倍。单片机产生特定频率的方波常用的方法是定时中断翻转电平每隔半个周期的时间把输出引脚的电平翻转一次。比如要产生523Hz的方波每个周期是1/523秒1912微秒半个周期是956微秒。定时器每隔956微秒溢出一次中断服务函数里把P3.5引脚取反就得到了523Hz方波。这就是为什么我强调必须用定时器而不是延时函数——延时函数要占用CPU死等单片机在延时期间没法做按键扫描和LCD显示体验会非常卡。用定时器中断则完全没有这个问题主程序循环处理按键和显示TIMER0在后台默默翻转电平互不干扰这才是真实产品该有的架构。3.2 定时器初值计算公式与常用音符对照表AT89C51的定时器T0工作在工作方式1时是一个16位计数器从初值开始计数计数到65536溢出。溢出后如果再配合软件重装初值就能循环产生稳定的溢出周期。单片机12MHz晶振下机器周期是1微秒12MHz除以12。也就是说计数器每加1耗时1微秒。所以定时器初值计算公式是初值 65536 - (半周期时间 / 机器周期) 65536 - (1000000 / (2 × 频率))用中央C的523Hz算一遍初值 65536 - 1000000/(2×523) ≈ 65536 - 956 ≈ 64580。转十六进制就是0xFC44所以TH00xFCTL00x44。常用音符的定时初值我整理成了一张表做电子琴时直接抄省得每次算一遍音符频率(Hz)半周期(us)TH0初值TL0初值低音113138170xED0x0F低音214734010xF00x17低音316530300xF20x0A低音417528570xF40x37低音519625510xF60x09低音622022730xF80x3F低音724720240xEA0x58中音126219080xFC0x44中音229417010xFD0x2B中音333015150xFE0x15中音434914330xFE0x67中音539212760xFE0x84中音644011360xFF0x10中音749410120xFF0x0C高音15239560xFF0x44高音25878520xFF0x6A高音36597590xFF0x09高音46987160xFF0xD4高音57846380xFF0x82高音68805680xFF0xDC高音79885060xFF0x06这里的初值已经是按方式1重装模式算好的。注意那个常见误区有些教材喜欢用“半周期 - 定时器溢出时间”的补码形式也就是直接用初值不要搞混。3.3 为什么优先用T0定时器而不是T151单片机有两个定时器T0和T1功能基本一致。但在这个项目里我会用T0做音符频率原因很实际T1还要承担波特率发生器功能虽然这个项目不涉及串口通信但万一你要扩展功能比如用蓝牙模块串口控制电子琴T1派得上用场。而且习惯养成很重要T0做定时、T1做串口波特率是51开发社区最通用的分工方式代码模板网上最多出问题好查。另外T0的工作方式116位定时器在这里非常合适自动播放模式中有些超低音音符的半周期接近4毫秒8位定时器最大只能定时256微秒要翻很多次计数器才够误差累积明显。16位定时器最大定时65毫秒完全够用。3.4 定时器中断服务程序的重装细节中断服务函数最常见的错误是只写TH0/TL0重装忘记中断标志位由硬件自动清除——这个倒是无所谓但有一个坑很多人不知道在重装初值之前定时器已经从上次溢出到这次进入中断之间又走了一小段计数如果不做补偿频率会偏高长时间播放会越来越不准。对课程设计来说这段误差人耳几乎听不出来而且Proteus仿真里也体现不明显所以大部分代码直接重装就行。但如果你想要更精确可以在重装前读一下当时的TL0值扣掉这个偏移量。这个属于优化思路知道有这回事就行答辩时提到这个细节绝对是加分项。4. C语言代码实现按键扫描、发音控制与自动演奏4.1 代码结构与工程组织写51单片机C语言程序我建议按照模块化思维组织哪怕是一个课程设计也把功能拆成独立的文件或函数段。这样做的好处是调试方便按键的问题不会牵扯到显示显示的问题不需要翻到发音函数里找。代码整体结构主函数 main.c ├── 系统初始化时钟、LCD、定时器、中断 ├── 主循环按键扫描→模式判断→功能分发 ├── 键盘处理函数扫描矩阵按键并消抖 ├── 音符控制函数查表、设置定时器初值 ├── LCD显示函数显示当前模式/音符/播放状态 ├── 音乐播放函数读取歌谱数组并逐音播放 └── 定时器中断服务函数电平翻转输出方波头文件里定义寄存器地址、音符频率表、歌曲数据结构、全局变量。如果老师要求用Keil C51那么在Project → Options for Target → Output里勾选“Create HEX File”这样才能生成Proteus要用的HEX文件这一步别漏。4.2 按键扫描与消抖的实现思路按键扫描函数我习惯返回两个值一个是按键编号1到16一个是当前是否有按键按下。这样主函数可以根据返回值决定是继续播放还是停止。核心代码思路unsigned char keyscan(void) { unsigned char row, col, keycode 0; P1 0xF0; // 行线输出低4位拉低列线输入 if ((P1 0xF0) ! 0xF0) // 检测是否有任一键按下 { delay_ms(10); // 消抖 if ((P1 0xF0) ! 0xF0) { // 行扫描 for (row 0; row 4; row) { P1 ~(0x01 row); // 当前行输出低电平 col (~P1) 0xF0; // 读取列线 if (col ! 0) { // 根据行列计算键值 keycode row * 4 ...; break; } } } } return keycode; }注意P1口初始值设为0xF0的含义低四位高电平等待输出低信号高四位高电平等待读入当按键按下时某一列被拉低读回来就是0。这个“先全置1再读”是51读端口的标准姿势因为P1口在输入模式下必须写1才能可靠读入外部电平。消抖代码里用了两次delay_ms(10)吗严格来说最好还要在松开时也加一次消抖防止按键弹起瞬间的抖动导致重复触发。不过电子琴这种应用重复触发不严重因为每次按键动作相当于弹一个音符重复一次也就多响一下无伤大雅。如果是做密码锁之类的系统就必须加“抬起检测”逻辑了。4.3 音符输出查表、定时器与中断的配合核心发音函数只需要做三件事查表得到初值关中断装初值开中断。用代码表达是这样的void play_note(unsigned char note_index) { if (note_index 0) // 休止符 { TR0 0; // 关闭定时器蜂鸣器静音 return; } TH0 freq_table[note_index].th; TL0 freq_table[note_index].tl; TR0 1; // 开启定时器 }这里的“休止符”很重要自动播放歌曲时音符之间有停顿就是靠插休止符实现的。如果用代码写死一个短延时来停止发声节奏容易不准而且不灵活。中断服务函数是整个电子琴能发出正确音调的幕后功臣void timer0_isr(void) interrupt 1 { TH0 current_th; // 重装初值 TL0 current_tl; beep_pin !beep_pin; // 翻转蜂鸣器引脚电平 }其中current_th和current_tl是全局变量在play_note函数里被更新。这样中断服务函数只做最少的动作保证翻转频率准确。4.4 自动播放歌曲的数据结构与播放流程自动播放模式的难点是歌谱怎么存。我的方案是定义两种数组音符数组和节拍数组。音符数组存放频率表索引或休止符0节拍数组存放该音符的持续时间比如4表示四分音符8表示八分音符。const unsigned char code song_star[] { 2, 2, 6, 6, 8, 8, 6, 0, 4, 4, 3, 3, 2, 0, ... }; const unsigned char code beat_star[] { 4, 4, 4, 4, 4, 4, 2, 0, 4, 4, 4, 4, 2, 0, ... };播放流程就是一个循环读取当前音符和节拍调用play_note让蜂鸣器响起来根据节拍值换算延时时间一个四分音符500ms八分音符250ms延时结束后调用play_note(0)静音一小段时间音与音之间的分隔索引加1继续下一音直到遇到结束标志这里有个细节是“音与音之间的分隔”。如果音符A结束立刻播放音符B听感上两个音是糊在一起的没有颗粒感。所以在每个音符之间加一个约30到50毫秒的静音间隔电子琴的听感会立刻上一个档次。这个静音间隔可以用节拍总和的一部分来扣减而不是额外增加播放时间否则整体节奏会拖慢。节拍延时我用的是T0定时器之外的另一套机制——因为T0已经被频率发生占用了延时只能选择软件延时或T1定时。软件延时写起来简单但播放期间无法响应按键——如果你要求按任意键停止播放需要在延时函数里不断扫描按键发现按下就break退出。这个属于交互设计小技巧代码里用变量标志位在延时的同时扫描按键即可。4.5 音调切换与LCD显示的状态管理多功能电子琴的模式切换可以通过一个“模式按键”实现按键每按一次模式值加1循环在“弹奏”“播放歌曲A”“播放歌曲B”之间切换。这样设计比多个按键分别控制更符合人的使用习惯——在电子琴上找一个功能键按一次切一个模式显示在LCD上直观明了。LCD1602的显示内容我建议这样安排第一行显示模式Mode 0 ManualMode 1 Song1Mode 2 Song2第二行显示当前手指按下的音符编号或音调代码里的状态变量至少要有这些unsigned char current_mode; // 0弹奏 1自动播放1 2自动播放2 unsigned char current_octave; // 当前八度低音0/中音1/高音2 unsigned char current_note; // 当前音符索引八度切换我用两个独立按键来实现一个“”一个“-”。在弹奏模式下按键按下输出哪个音符由当前八度和按键编号共同决定final_index base_index octave_offset。音符频率表里把低中高三个八度按顺序排列那么中音1的索引是14高音1的索引是21假设从低音1索引0开始。切换八度时只要对索引做加减就能访问对应频率逻辑非常简单。5. Proteus仿真与实物调试踩坑过程和解决办法5.1 先把Proteus跑起来从加载HEX到常见报错整个过程里最让新手崩溃的往往不是代码逻辑而是Proteus软件本身。踩过无数坑之后我把标准的操作流程固定成了下面这套照着走就不会错双击AT89C51芯片在Program File栏选择Keil生成的HEX文件。这一步文件路径不能有中文否则可能加载失败。设置晶振频率要和代码一致。双击芯片弹出的对话框里Clock Frequency默认是12MHz但如果你在电路里画了12MHz晶振这个参数还是要确认一下。Keil里仿真用的晶振频率和Proteus里芯片的Clock频率必须一致否则音符频率会整体偏移。点击左下角“运行”按钮等待程序执行。仿真中如果看到芯片引脚变红高电平或变蓝低电平表示IO口正在输出。如果程序没反应先检查芯片左下角是不是有个绿色的三角符号没有说明程序没进入运行状态。如果LCD1602只有背光没有字检查P0口上拉电阻如果LCD显示乱码检查RS、RW、E三根控制线接对不对顺序错一位数据就全乱了。5.2 仿真能跑但声音不对先确认蜂鸣器模型参数试过好多次代码和电路都正确但蜂鸣器声音不对或者声音很小。这里有几个Proteus特有的坑第一个是蜂鸣器的Operating Voltage参数。双击蜂鸣器把Operating Voltage设为5V。默认值我见过是12V如果单片机输出只有5V模型是按12V标称来驱动压电片的声音会明显偏小甚至只能看到波形没有声音输出。第二个是蜂鸣器类型选错。前面反复强调过这里再提一次——有源蜂鸣器BUZZER里带“Active”字样的不能用它们只能发固定频率的单音。选SOUNDER无源就对了。第三个是驱动方式。在Proteus仿真中无源蜂鸣器可以直接接单片机引脚因为仿真是理想环境输出电流足够。但你把同样的电路拿到实物上单片机引脚直接带无源蜂鸣器经常推不动。所以仿真通过之后实物需要加一个三极管驱动电路。第四个是检查输出波形。在Proteus里点右下角的“图表”工具把蜂鸣器正极引脚拖进去做数字分析就能看到方波波形。如果波形频率和计算值一致比如中音1是262Hz但耳朵听不到声音那是Proteus的声音模拟设置问题。检查菜单Debug → Digital Audio Settings是否打开以及电脑音量是否正常。5.3 从仿真到实物必须补的驱动电路和上拉电阻很多人仿真通过了就直接焊板子结果一上电发现声音小得可怜或干脆不响然后怀疑程序又写错了。其实程序没错错在硬件知识和仿真模型的差距。实物驱动蜂鸣器标准方案是用一个NPN三极管2N2222、S8050、BC547都可以做开关放大。接线方式单片机引脚通过一个1k基极电阻接到三极管基极三极管发射极接地集电极接蜂鸣器负极蜂鸣器正极接VCC。当单片机输出高电平时三极管导通蜂鸣器有电流流过发出声音输出低电平时三极管截止蜂鸣器停止。如果蜂鸣器是无源的这个电路加一个续流二极管1N4148反并联在蜂鸣器两端防止蜂鸣器电感产生反向电动势击穿三极管。实物上还要注意P0口上拉电阻是真的必须接的——仿真里不接可能也能显示因为Proteus模型对上拉不敏感但实物P0口是开漏结构不加上拉电阻LCD数据就是一堆乱码或空白。此外晶振的负载电容一定要接22pF左右复位电容用10uF这些在仿真里体现不明显在实物里都是家常便饭。5.4 定时中断里的常见隐患初值重装与全局标志位写定时器中断时有几个隐蔽问题值得单独拎出来讲。第一中断里用到的全局变量必须加volatile修饰。比如current_th、current_tl这两个变量它们在主程序里被赋值在中断里被读取如果编译器优化过了头可能直接把变量优化成寄存器副本导致主程序改了半天中断里还是旧值。加volatile是百利而无一害的但很多教材不提这个。第二修改定时器初值时要注意时序。主程序里设置TH0和TL0时如果恰好定时器正在跑两个字节不是原子操作可能出现“高字节新值、低字节旧值”的混合状态导致这一周期的频率异常。可靠做法是先关中断或先停定时器再修改初值最后重启定时器。play_note函数里我就是这么处理的。第三中断服务函数里不要再调用延时函数或重函数。51单片机中断里调用delay会导致主程序长时间被卡住LCD显示和按键扫描都会停顿。中断服务函数的黄金法则是能短则短只做必要的TC更新和引脚翻转。5.5 调试时的波形验证与听感判断最后一个经验分享——怎么判断你的电子琴系统到底“工作正确”我是这样做的在Proteus里用虚拟示波器在仪表工具栏选SIGNAL ANALYZER或者直接用DIGITAL ANALYZER观察蜂鸣器引脚波形按下不同按键时波形频率应该和音符频率表一致。中音1是262Hz波形周期约3.8ms高音1是523Hz波形周期约1.9ms直接看波形就能确认代码查表和定时器初值是否正确。然后关掉仿真在实物上调听感。同一个音符在实物和仿真里音调相同但音色略有差异这是蜂鸣器和扬声器的频响特性造成的。如果实物听起来比仿真“闷”可以试试换更大尺寸的蜂鸣器或者在蜂鸣器外壳开个孔——这些属于声学调教的范畴课程设计不用深入。如果自动播放的节拍听起来不对先检查延时函数的时间换算12MHz下一个简单的for循环延时1ms大致需要执行多少次循环这个我建议直接用逻辑分析仪测量而不是靠估算。用Keil的软件仿真也能看但实物上最好直接在示波器上量相邻两个音符间隔的时长如果偏差太大就去微调延时函数里的循环次数。这个项目后续怎么扩展整套系统做完你会发现它其实是一个“传感器按键输入处理器AT89C51执行器蜂鸣器/LCD的完整嵌入式应用”把电子琴换成别的题目代码框架还是那套扫描输入、查表映射、定时输出、显示反馈。这就是为什么我推荐大家认真做一个项目的原因——掌握的是方法论不是某个特定功能。最后再分享一个小技巧关于记谱和节拍手动弹奏模式和自动播放模式听起来像两码事但在代码里它们共用play_note这个函数只是触发方式不同——手动靠按键事件去调用自动靠播放循环去调用。如果以后想做录音回放功能就是在手动模式下把按键序列记录下来然后交给播放循环执行。理解了这套抽象关系很多花哨功能都能在一个月内加上去而且不会把代码写乱。本文还有配套的精品资源点击获取