ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

51单片机电子琴课程设计:从原理图到代码烧录全解析

51单片机电子琴课程设计:从原理图到代码烧录全解析 简介面向51单片机学习者和电子DIY爱好者的电子琴完整项目基于STC51系列实现从原理图设计到C语言编程的全流程实践适合想将单片机基础应用到音频控制场景的读者。压缩包共26个文件约313KB其中原理图sch/ddb/pdf用于硬件接线参考C程序及hex/obj/lst等对应Keil工程源码与编译中间结果另有使用说明书doc提供焊接、烧录和调试指引bak/bkp为工程备份便于追溯修改过程。项目覆盖按键矩阵扫描、音符识别、定时器中断控制音乐节奏等核心知识点并提供Protel原理图、产品说明书等完整文档便于从零搭建一台声光电子琴。目前已有177人学习下载适合作为单片机课程设计或入门DIY项目的参考资料。1. 基于51单片机的电子琴拿到这套资源先看什么做课程设计或练手单片机避不开“电子琴”这个项目。它不复杂但把 GPIO、定时器、中断、按键消抖全串起来了。这套资源里既有 Protel 原理图也有 Keil 工程和编译好的 dzq.hex意味着不需要从零画板也能在实物上跑起来。适合两类人一类是准备交课程报告、需要理解原理的学生另一类是手里有 STC89 系列芯片想把现有工程改造成自己版本的在职工程师。下文按“原理图→软件→烧录→进阶”的顺序拆解每一步都给出可以对得上文件的操作方式而不是泛泛讲概念。实际用下来这个项目的难点并不在 C 语言本身而在“频率怎么算、按键怎么扫、烧录后为什么不响”这三个点解决它们整个项目就通了一半。2. 原理图与按键矩阵STC51引脚的电气连接和音符映射2.1 先看压缩包里到底有什么拿到 zip 后不要直接双击打开先用命令行列出文件能更快定位哪些是源文件、哪些是编译产物。在 Windows 的 PowerShell 或 Git Bash 里执行$ unzip -l 基于51单片机的电子琴.zip Archive: 基于51单片机的电子琴.zip Length Date Time Name --------- ---------- ----- ---- ... 原理图.ddb ... 原理图.pdf ... Backup of dianziqin.Sch ... c程序/dzq.c ... c程序/dzq.hex ... 使用说明书/声光电子琴产品使用说明书.doc这里unzip -l只列出内容不释放文件适合快速确认有没有 PDF 或 hex 这类可以直接用的文件。.ddb是 Protel 98/99 的工程数据库格式新版 Altium Designer 可以导入但如果只是看连接关系直接打开原理图.pdf最省事。.Sch和.Bkp是原理图源文件及自动备份改电路时才需要动它们。.uvproj是 Keil 工程.hex是编译后的固件烧录时只认 hex。.doc是说明书里面有焊接顺序和调试方法遇到硬件问题优先翻它。文件后缀用途什么时候需要.ddb / .Sch / .BkpProtel 原理图源文件修改电路、重新布线时.pdf原理图打印版快速查看引脚连接.c / .uvprojKeil 源码与工程修改程序逻辑时.hex编译后的固件烧录到单片机时.doc使用说明书焊接、调试、故障排查时2.2 矩阵键盘的引脚分配逻辑这套项目使用矩阵键盘常见做法是 4×2 结构8 个按键占 6 个 IO 口。原理图上一般把行线接到 P2 的低四位列线接到 P2 的高两位蜂鸣器接 P1.7。为什么用矩阵而不是 8 个独立按键因为 51 单片机引脚本身紧张独立按键要占 8 个 IO矩阵可以省两个而且为后续扩展到 16 键留了接口。行线作为输出逐行拉低列线作为输入平时靠内部准双向口上拉为高某一行被拉低后如果该行与某一列交点有按键按下对应列就会被拉低。判断哪一列变低就知道是哪个键被按下。在 Keil 里定义引脚时我会用这种写法#define MATRIX_PORT P2 // P2低4位接矩阵行P2高2位接矩阵列 sbit BEEP P1^7; // 蜂鸣器输出引脚这里MATRIX_PORT直接映射整个 P2 口扫描时同时操作 6 个引脚。BEEP单独定义因为定时器中断里要频繁翻转它用 sbit 编译出来的指令更短中断响应更快。注意 P2 高两位作为输入时初始化要让这两位置 151 的准双向口只有写了 1 之后才能正确读到外部电平否则读到的永远是 0。2.3 音频输出电路怎么接原理图上的音频输出通常不是直接用 IO 驱动蜂鸣器而是经过一个三极管放大。51 单片机的 IO 输出能力有限直接接无源蜂鸣器声音很小且容易拉低端口电平。常见接法是P1.7 输出方波经过 1kΩ 限流电阻接到 S8050 三极管基极集电极接蜂鸣器负极发射极接地蜂鸣器正极接 VCC。这样方波的高电平让三极管导通低电平截止蜂鸣器两端得到完整的 5V 方波。原理图上的 LED 和限流电阻并联在蜂鸣器两端起“声光指示”作用——蜂鸣器响时 LED 同步亮调试时不用贴耳朵听。说明书里“声光电子琴”的名称就是这么来的焊接时务必区分蜂鸣器正负极无源蜂鸣器接反不会发声。3. Keil C程序与定时器音符表、初值计算和扫描逻辑3.1 Keil 工程文件怎么对应打开dzq.uvproj需要 Keil老版本工程用 Keil4 打开后会自动生成dzq.uvopt和*.bak文件这些是窗口布局和用户配置的备份不影响编译结果。真正要改的只有dzq.c。.OBJ和.LST是编译中间文件.M51是存储器分配图里面可以看到每个变量落在哪个地址区间排查内存溢出时才会用到。从源码到 hex 的流程是编辑dzq.c→ 点击 Build → 生成新的dzq.hex。如果编译没报错但 hex 文件时间戳没更新多半是工程配置里没勾选生成 hex。3.2 音符频率与定时器初值电子琴的核心是把每个音符对应的频率转换为定时器初值。以中音 C 调音阶为例从低音 1 到高音 1 共 8 个音符频率分别为 262Hz、294Hz、330Hz、349Hz、392Hz、440Hz、494Hz、523Hz。定时器工作在模式 116 位计数器每溢出一次就翻转一次蜂鸣器引脚所以一个完整的方波周期需要两次溢出。如果晶振是 12MHz51 单片机默认 12 分频定时器计数频率是 1MHz那么一个半周期需要计数的数量是1000000 / (2 * 频率)。初值计算公式如下定时器初值 65536 - 1000000 / (2 * 频率)音符唱名频率 (Hz)定时器初值 (12MHz, 模式1)C4126263628D4229463835E4333064021F4434964103G4539264260A4644064400B4749464524C5高音152364580上表的初值是四舍五入结果实际听感没有明显差异。如果换用 11.0592MHz 晶振需要重新计算因为振荡频率直接影响定时器计数速度。在代码里这组初值最好放在只读存储区code unsigned int timer_reload[8] { 63628, 63835, 64021, 64103, 64260, 64400, 64524, 64580 };code关键字把数组固化到程序 Flash 里不占用宝贵的内部 RAM。8 个 int 只占 16 字节但换来的是查表速度比每次按键后现场计算快得多。这是 51 单片机程序里最常见的空间换时间做法。3.3 定时器中断产生方波方波产生逻辑可以独立函数拆开便于复用。先看中断服务函数和频率设置函数void Timer0_ISR(void) interrupt 1 { BEEP !BEEP; // 每次溢出翻转电平形成方波 } void set_freq(unsigned int reload) { TR0 0; // 先关定时器避免写入时触发中断 TH0 reload 8; // 初值高8位 TL0 reload 0xFF; // 初值低8位 TR0 1; // 重新启动 }interrupt 1是 8051 定时器 0 的中断号Keil 根据这个关键字自动生成中断向量。BEEP !BEEP用异或翻转编译后是一条单周期指令适合在中断里做高频切换。set_freq必须先关定时器再写 TH0 和 TL0因为 16 位定时器的初值分两次写入如果先写高 8 位后写低 8 位期间恰好溢出中断会使用一个不完整的初值产生爆音。reload 8和reload 0xFF分别是拆分高低字节的标准写法。3.4 矩阵扫描与按键消抖矩阵扫描函数返回按键索引没有按键时返回0xFF。用 4×2 矩阵行扫描从第 0 行到第 3 行列检查从第 0 列到第 1 列返回值为row * 2 col正好对应音符表下标 0~7。#define MATRIX_PORT P2 unsigned char scan_key(void) { unsigned char row, col; for (row 0; row 4; row) { // 当前行输出0其他行输出1高4位保持不变 MATRIX_PORT (MATRIX_PORT 0xF0) | (~(0x01 row) 0x0F); for (col 0; col 2; col) { // 检查对应列是否被拉低 if ((MATRIX_PORT (0x10 col)) 0) { return row * 2 col; } } } return 0xFF; }MATRIX_PORT 0xF0保留高两位列线的当前值避免修改行输出时把列线误设置为 0。~(0x01 row) 0x0F把当前行的低 4 位清零其他位置为一。列检查用0x10 col当 col0 时检查 P2.4col1 时检查 P2.5。整个扫描过程中每个按键按下时只有一行输出为低其他行输出为高所以不会出现多键短路的冲突。主循环里调用这个函数配合“等待松开”逻辑防止按键长按重复触发void main(void) { unsigned char key; TMOD 0x01; // 定时器0模式1 ET0 1; // 使能定时器0中断 EA 1; // 开总中断 while (1) { key scan_key(); if (key ! 0xFF) { set_freq(timer_reload[key]); while (scan_key() ! 0xFF); // 等待按键释放 } else { TR0 0; BEEP 0; } } }while (scan_key() ! 0xFF)是阻塞式等待按键不松手就不会执行后面的代码但定时器中断依然在工作所以音调持续输出。这种方式最简单代价是释放前无法响应其他按键对于 8 键单音电子琴完全够用。如果想要支持多音或连发需要换状态机方案这在后面单独讲。4. 程序烧录与实物调试从hex到发声的常见坑4.1 Keil 里重新编译并生成 hex拿到工程后第一件事不是打开源码而是确认 Output 选项卡里是否勾选了Create HEX File。如果不勾选点 Build 只会生成.OBJ不会更新 hex。命令行编译可以把这一步固化到批处理里适合反复修改后快速构建C:\Keil\UV4\UV4.exe -b dzq.uvproj -o build.log-b参数让 Keil 进入批处理编译模式编译完成后立即退出不打开图形界面-o build.log把编译信息写入日志文件。编译结束后打开 build.log看到0 Error(s), 0 Warning(s)说明构建通过。如果有警告重点看是不是某个变量未使用或者定时器初值溢出。初值超过 65535 会报溢出错误这时需要检查频率是否太低或者换用模式 0 的 13 位定时器。4.2 用 STC-ISP 烧录STC51 系列绝大多数用串口下载STC-ISP 软件操作步骤固定顺序颠倒会导致下载失败。先选择芯片型号再选择串口然后加载 dzq.hex最后点“下载/编程”按钮在提示出现后重新给板子上电。注意 STC 下载需要冷启动也就是先点下载再上电不是上电后再点下载。参数推荐值说明芯片型号STC89C51 或 STC89C52RC以原理图标注为准串口COM3 或 COM4设备管理器里确认波特率2400下载慢但稳定数据位8STC 默认校验位无STC 默认停止位1STC 默认串口识别不出来的情况九成是板载 USB 转串口芯片驱动没装好。设备管理器里看到带黄色感叹号的 COM 口先装 CH340 或 PL2303 驱动再插拔一次 USB 线。下载波特率不要调太高某些劣质 USB 线在 115200 下容易失败用 2400 最稳妥。4.3 上电不发声的排查顺序烧录成功但不发声一般不是程序问题而是硬件连接或触发条件不对。判断顺序是先确认单片机有没有跑起来再量蜂鸣器引脚波形最后查按键。现象可能原因检查方法烧录成功但无声蜂鸣器正负极接反对照原理图检查蜂鸣器引脚按任意键都没反应矩阵列线没有上拉万用表量 P2.4/P2.5 对地电压应为高声音刺耳或频率不对定时器初值算错用示波器看 P1.7 波形对比目标频率按下响松开还响等待释放逻辑没生效检查主循环里是否有while等待下载时报硬件无响应串口芯片驱动或冷启动问题先点下载再断电上电如果没有示波器可以用一个 LED 串联 1kΩ 电阻接到 P1.7LED 闪烁频率约等于音符频率的一半虽然人眼看不出具体频率但响与不响很容易分辨。这个方法也适合验证定时器中断是否在工作。4.4 声光部分怎么联动说明书写的是“声光电子琴”所以 dzq.c 里大概率有一个 LED 控制片段。常见实现是在set_freq里把 LED 引脚置高在关闭声音时置低。如果发现 LED 常亮不闪说明程序把它当成普通电平输出如果 LED 跟随蜂鸣器亮度变化说明引脚接在了蜂鸣器驱动端。调试时可以直接把 LED 限流电阻改到 1kΩ 以上避免额外负载影响音调。5. 用状态机扩展电子琴长按连发与任意按键数5.1 为什么需要状态机前面主循环里的while (scan_key() ! 0xFF)虽然简单但有几个问题按键按住时 CPU 一直被占住没法处理第二个按键长按同一个键会在蜂鸣器发声的同时反复执行扫描浪费功耗扩展多键时等待释放会造成整体卡顿。改用状态机可以把“按下”“保持”“释放”拆成独立状态长按发声本质上是按住期间每隔固定时间重新触发一次发声事件而不是把音符频率反复写入定时器。5.2 四状态按键处理代码状态机定义四个状态空闲、按下确认、发声、等待释放。#define IDLE 0 #define PRESSED 1 #define PLAYING 2 #define WAIT_REL 3 unsigned char state IDLE; unsigned char current_note 0xFF; void key_event(unsigned char key_index) { switch (state) { case IDLE: if (key_index ! 0xFF) { // 检测到按键 current_note key_index; set_freq(timer_reload[current_note]); state PRESSED; } break; case PRESSED: // 消抖确认 if (key_index current_note) { state PLAYING; } else { state IDLE; } break; case PLAYING: if (key_index 0xFF) { // 按键松开 TR0 0; BEEP 0; state IDLE; } // 这里可加入长按连发逻辑 break; default: state IDLE; break; } }key_event由主循环周期性调用比如每 10ms 读一次scan_key()并传入。第一次检测到按键从 IDLE 进入 PRESSED不会立即响应要等下一次扫描确认还是同一个键才进入 PLAYING这样天然完成软件消抖。PLAYING 状态下不做任何事直到扫描到无按键才停止发声并把状态重置回 IDLE。5.3 长按连发和 16 键扩展长按连发只需要在 PLAYING 分支里加一个计数器。主循环每次调用key_event时递增keep_ms当它超过 500ms 且当前键仍未释放就重新调用set_freq并把计数器清零。这样做出的效果更接近“颤音”而不是电子琴的持续音适合做课程设计里的附加功能。要扩展到 16 键把行扫描从 4 行改成 4 行 4 列返回索引改成row * 4 col音符表扩到 16 个元素。列线数量变多之后建议外部加上拉电阻否则寄生电容可能导致高电平读不准。矩阵变大后扫描周期变长主循环调key_event的间隔需要同步调整否则消抖时间会漂移。把这套状态机套进原有工程改动点只在scan_key和音符表长度主逻辑不变后续换旋律、加八度切换都更容易。本文还有配套的精品资源点击获取
返回列表