简介:基于AT89C51单片机的数字音乐盒课程设计报告,适合单片机原理与应用、嵌入式系统开发等课程的期末设计参考,也适合相关专业学生在做课程设计时借鉴。报告从任务书出发,依次完成总体方案论证、硬件电路设计、软件编码与系统调试。硬件部分包括单片机选型、液晶显示、按键控制、复位电路、时钟电路、输出显示和音频输出模块;软件部分给出主程序、外部中断、显示模块、系统初始化、音调节拍编码和流程图设计。资源为单个PDF文档,压缩包共1个文件,大小约1.5MB,已有412人浏览学习。通过阅读这份报告,读者可以理解如何用I/O口产生不同频率方波驱动蜂鸣器演奏乐曲,掌握多曲目切换、暂停、LCD显示播放序号与欢迎信息等功能实现方式,并能参考其中的电路图、程序清单和调试结果更高效地完成自己的设计。
1. 单片机课程设计里的数字音乐盒,先分清要做什么
单片机课程设计里,“基于单片机的数字音乐盒”是出现频率最高的题目之一。它的定位很明确:不是做音频解码,而是用 51 单片机(常见为 STC89C52)的定时器产生音符频率的方波,用无源蜂鸣器把简谱按节拍放出来。整个系统压缩到一颗芯片、一个蜂鸣器、两三个按键就能跑通。
难点集中在三处:音符频率到定时器初值的换算、歌曲数据怎么编码、有源与无源蜂鸣器的驱动方式区别,这三处恰恰是答辩时老师爱追问的点。对课程设计而言,这个题目把定时器中断、键盘扫描、状态切换这些单片机基础串成了一条完整链路。后面按发声原理、硬件电路、C51 程序、仿真验证的顺序展开,参数和代码都能直接套用。
2. 数字音乐盒发声原理:音符频率表与 51 单片机定时器初值计算
音乐盒的本质,是让蜂鸣器在时间轴上按顺序发出不同频率、不同时长的方波。频率由音符决定,时长由节拍决定。动手写代码之前,先把这两张表和一条公式定下来,后面所有程序都是围绕这张表展开的。
2.1 十二平均律下的常用音符频率表与 12MHz 初值
以 A4=440Hz 为基准,课程设计最常用的是从中音 C4 到高音 C6 这一段。下表是数字音乐盒里最常见的音符频率,以及 12MHz 晶振、12T 模式下定时器 0 方式 1(16 位)的初值。
| 音符 | 频率(Hz) | 计数个数 | 定时器初值 | TH0 | TL0 | 实际频率(Hz) |
|---|---|---|---|---|---|---|
| C4 | 262 | 1908 | 63628 | 0xF8 | 0x8C | 262.1 |
| D4 | 294 | 1700 | 63836 | 0xF9 | 0x5C | 294.1 |
| E4 | 330 | 1515 | 64021 | 0xFA | 0x15 | 330.0 |
| F4 | 349 | 1432 | 64104 | 0xFA | 0x68 | 349.2 |
| G4 | 392 | 1275 | 64261 | 0xFB | 0x05 | 392.2 |
| A4 | 440 | 1136 | 64400 | 0xFB | 0x90 | 440.1 |
| B4 | 494 | 1012 | 64524 | 0xFC | 0x0C | 494.1 |
| C5 | 523 | 956 | 64580 | 0xFC | 0x44 | 523.0 |
| D5 | 587 | 851 | 64685 | 0xFC | 0xAD | 587.5 |
| E5 | 659 | 758 | 64778 | 0xFD | 0x0A | 659.6 |
| F5 | 698 | 716 | 64820 | 0xFD | 0x34 | 698.3 |
| G5 | 784 | 638 | 64898 | 0xFD | 0x82 | 783.7 |
| A5 | 880 | 568 | 64968 | 0xFD | 0xC8 | 880.3 |
| B5 | 988 | 506 | 65030 | 0xFE | 0x06 | 988.1 |
| C6 | 1047 | 478 | 65058 | 0xFE | 0x22 | 1046.0 |
这张表的来源是一条公式:12MHz 晶振下 51 单片机是 12T 架构,机器周期固定为 1µs。要产生频率 f 的方波,定时器需要数一个半周期的时间,计数值 = 半周期时间 / 机器周期 = 500000/f。16 位定时器从初值数到 65536 溢出,所以初值 = 65536 - 计数值,TH0 存高位、TL0 存低位。以 C4 为例:500000/262 ≈ 1908,初值 63628,即 0xF88C。取整带来的误差不超过 0.1%,人耳对相对音高的分辨力约 0.5%~1%,整数运算足够。换 11.0592MHz 晶振时,公式要改成 460800/f;STC 芯片烧录时如果设成 6T 模式,机器周期减半,计数值也要跟着减半,否则整首歌会高一个八度。
2.2 有源蜂鸣器与无源蜂鸣器:驱动信号完全不同
数字音乐盒必须选无源蜂鸣器。有源蜂鸣器内部自带振荡电路,通电就响,频率固定不可控;无源蜂鸣器内部没有振荡源,必须由外部提供方波或 PWM 才发声,频率完全取决于驱动信号。这是“单片机控制蜂鸣器”最常见的区分点:
| 对比项 | 有源蜂鸣器 | 无源蜂鸣器 |
|---|---|---|
| 内部振荡电路 | 有 | 无 |
| 驱动信号 | 直流电平即可 | 方波/PWM |
| 发声频率 | 固定,通常 2~4kHz | 由方波频率决定 |
| 能否播放旋律 | 不能 | 能 |
外观上不好分辨时,用万用表电阻档测:有源蜂鸣器通常有固定阻值,无源蜂鸣器呈线圈特性。Proteus 仿真里也要注意:BUZZER 元件内部就是有源模型,上电即响;无源蜂鸣器对应的是 SOUNDER 元件,需要方波驱动。课程设计里很多人仿真半天不出旋律,就是在这里选错了元件。
2.3 歌曲编码:两张数组把简谱变成音符表
歌曲数据用两张 code 数组表示:一张存音高,一张存拍数。音高数组里放音符在频率表中的下标,拍数数组放对应音符的时值,0xFF 作为结束标志。这样播放逻辑和曲目数据完全分离,换歌只改数组,不用动任何驱动代码。
// 小星星:1 1 5 5 6 6 5 - 4 4 3 3 2 2 1 - // 下标约定:0=C4 1=D4 2=E4 3=F4 4=G4 5=A4 code unsigned char StarNote[] = {0,0,4,4,5,5,4, 3,3,2,2,1,1,0, 0xFF}; code unsigned char StarLen[] = {1,1,1,1,1,1,2, 1,1,1,1,1,1,2, 0xFF}; // 欢乐颂:3 3 4 5 5 4 3 2 | 1 1 2 3 3. 2 2 - code unsigned char OdeNote[] = {2,2,3,4,4,3,2,1, 0,0,1,2,2,1,1, 0xFF}; code unsigned char OdeLen[] = {1,1,1,1,1,1,1,1, 1,1,1,1,2,2,4, 0xFF};len 数组里的 1、2、4 是相对拍数,不是绝对时间。实际一拍多久由播放函数里的速度参数决定,这样同一首歌可以整体变速。音符下标对应频率表的索引,比直接存频率省 code 空间,也方便扩展休止符:约定下标值大于等于 0x80 时当作休止符处理。两张数组前后必须严格对齐,长度不一致是最容易犯的编码错误。
3. 数字音乐盒硬件搭建:STC89C52 最小系统与无源蜂鸣器驱动
3.1 STC89C52 最小系统:晶振、复位、电源三件套
常见板子以 STC89C52RC 为主,DIP40 封装,与 AT89C52 引脚完全兼容,Proteus 仿真时可以直接放 AT89C52。最小系统三部分缺一不可:
- 晶振:12MHz 无源晶振接 XTAL1 和 XTAL2,两只引脚各对地接一个 30pF 电容做负载。
- 复位:RST 引脚对 VCC 接 10µF 电解电容,对 GND 接 10kΩ 电阻,构成上电复位电路。
- 电源:5V 供电,VCC 与 GND 之间靠近芯片放一个 104 去耦电容。
其中晶振的取值直接决定第二章的频率表能否成立:表格里每个初值都按 12MHz 计算,板子换成 11.0592MHz 后整首歌会整体走音。这是“原理图照着画、上电音不准”的第一原因。
3.2 输出引脚接线与蜂鸣器驱动电路
| 信号 | STC89C52 引脚 | 接到哪里 |
|---|---|---|
| BEEP | P2.5(DIP40 第 26 脚) | 经 1kΩ 电阻到 S8050 基极 |
| KEY1 | P3.0(第 10 脚) | 按键到 GND,外接 10kΩ 上拉 |
| KEY2 | P3.1(第 11 脚) | 按键到 GND,外接 10kΩ 上拉 |
| XTAL1 | 第 19 脚 | 12MHz 晶振 + 30pF |
| RST | 第 9 脚 | 10µF + 10kΩ 复位网络 |
| VCC/GND | 第 40/20 脚 | 5V / GND + 104 去耦 |
51 单片机的 P1、P2、P3 是准双向口,高电平拉电流能力只有 1~2mA,直接驱动 20~30mA 的无源蜂鸣器,音量上不去,还会把引脚电平拉低。常见做法是加一个 NPN 三极管 S8050 做开关:蜂鸣器正极接 5V,负极接三极管集电极,发射极接地,基极经 1kΩ 电阻接 P2.5。基极电流约 (5-0.7)/1000 ≈ 4.3mA,三极管进入饱和,集电极压降不到 0.3V,蜂鸣器获得足够的驱动能量。如果改用 ULN2003 这类达林顿管,它的输出是低电平导通,方波逻辑与 NPN 三极管方案正好反相,代码里的翻转逻辑要跟着调整。
// Keil C51 里的引脚定义,与上面的接线表一一对应 sbit BEEP = P2^5; sbit KEY1 = P3^0; sbit KEY2 = P3^1;3.3 按键、上拉与可选显示
KEY1、KEY2 用独立按键接 GND,配合 10kΩ 外部上拉电阻,平时读到高电平,按下为低电平。51 的 P0 口比较特殊,内部没有上拉,如果后续加数码管显示曲号,P0 必须外接 10kΩ 上拉排阻,否则数码管亮度异常甚至不亮。每个按键的软件消抖放到下一章程序里,用定时器节拍来完成。
3.4 Proteus 仿真元件清单
| 元件 | 库中的名称 | 参数与注意点 |
|---|---|---|
| 单片机 | AT89C52 | 与 STC89C52 引脚兼容,可跑全部播放代码 |
| 无源蜂鸣器 | SOUNDER | 方波驱动,不要用 BUZZER 有源模型 |
| 按键 | BUTTON | 两个,分别接 P3.0、P3.1 |
| 电阻 | RES | 1kΩ、10kΩ,数码管方案加 10kΩ 排阻 |
| 电容 | CAP / CAP-ELEC | 30pF×2、10µF、104 |
| 晶振 | CRYSTAL | 12MHz,双击元件把频率改成 12M |
4. 基于 51 单片机的音乐盒程序实现:定时器中断、歌曲编码与按键换歌
4.1 两个定时器各干一件事
程序用两个定时器:定时器 0 产生音符方波,工作在方式 1(16 位定时),每次中断翻转一次 P2.5,中断间隔就是半个周期;定时器 1 产生 10ms 节拍基准,给音符时长数拍子。两个定时器分工明确,蜂鸣器发声的同时主循环还能扫描按键。
#include <STC89C5x.H> sbit BEEP = P2^5; sbit KEY1 = P3^0; sbit KEY2 = P3^1; // 音符频率表,下标与第 2 章表格一一对应 code unsigned int NoteFreq[] = { 262, 294, 330, 349, 392, 440, 494, 523, 587, 659, 698, 784, 880, 988 }; unsigned char reloadTH, reloadTL; // 当前音符的初值 volatile unsigned char tick10ms; // 定时器1 每 10ms 加 1 void StartTone(unsigned char idx) { unsigned long cnt; TR0 = 0; // 先停表再改初值 cnt = 500000UL / NoteFreq[idx]; // 12MHz/12T,机器周期 1us reloadTH = (unsigned char)((65536UL - cnt) >> 8); reloadTL = (unsigned char)(65536UL - cnt); TR0 = 1; }这里 500000UL 和 65536UL 是刻意写成 UL 的。Keil C51 的 int 只有 16 位,直接写 500000 和 65536 都会被截断,初值算出来完全不对,这是 Keil 工程里最典型的第一坑。StartTone 先关 TR0 再写初值,是为了避免中断刚好在改初值两个字节之间触发,重装出一组高低位不匹配的值,导致方波频率瞬间跳变。
4.2 两个中断服务函数
void Timer0_ISR(void) interrupt 1 { TH0 = reloadTH; // 手动重装 16 位初值 TL0 = reloadTL; BEEP = ~BEEP; // 翻转输出,产生方波 } void Timer1_ISR(void) interrupt 3 { TH1 = 0xD8; // 10ms 基准:65536-10000=0xD8F0 TL1 = 0xF0; tick10ms++; }定时器 0 的中断号是 1,定时器 1 的是 3,写错中断号会造成中断入口错乱。12MHz、12T 下定时器 1 数 10000 个机器周期就是 10ms,所以初值 0xD8F0。tick10ms 用 unsigned char 而不是 unsigned int:8 位 MCU 的定时中断里读写 16 位变量存在高低字节撕裂问题,8 位计数器不会有这个问题,代价是单音符时值上限 2550ms,对课程设计完全够用。
4.3 播放函数与主循环:按键换歌、播放
unsigned char beatMs = 40; // 一拍 400ms void PlaySong(unsigned char code *note, unsigned char code *len) { unsigned char i = 0; TR0 = 0; while (note[i] != 0xFF) { if (note[i] < 0x80) // 0x80 及以上当休止符 StartTone(note[i]); tick10ms = 0; while (tick10ms < (unsigned char)(len[i] * beatMs)); // 数拍 TR0 = 0; // 音符结束先静音 i++; } BEEP = 0; } void main(void) { unsigned char songNo = 0; unsigned char t; TMOD = 0x11; // T0、T1 都用方式 1 TH1 = 0xD8; TL1 = 0xF0; // T1 初值 10ms ET0 = 1; ET1 = 1; TR1 = 1; EA = 1; while (1) { if (KEY1 == 0) { // 第一次读到低电平 t = tick10ms; while (tick10ms == t); // 等 10ms if (KEY1 == 0) { // 二次确认,消除抖动 while (KEY1 == 0); // 松手检测 if (songNo == 0) PlaySong(StarNote, StarLen); else PlaySong(OdeNote, OdeLen); } } if (KEY2 == 0) { t = tick10ms; while (tick10ms == t); if (KEY2 == 0) { while (KEY2 == 0); songNo ^= 1; // 切换曲目 } } } }这段代码的逻辑是:KEY1 按下后等一个 10ms 节拍再确认一次,两次都读到低电平才认为是有效按键,等松手后开始播放;KEY2 同理,只切换 songNo,不打断当前播放。消抖用的是定时器 1 的 tick10ms,而不是空循环 delay,好处是消抖时间不随晶振偏差漂移。播放函数把 tick10ms 清零,再等待 len[i]*beatMs 个节拍,每拍默认 400ms,与歌曲数组里的相对拍数相乘就是绝对时长。整个播放是阻塞式的,按键响应要等当前音符结束,这是课程设计最常用的实现方式,简单、可解释、答辩不虚。
4.4 编译、仿真与烧录流程
- Keil 里新建工程,芯片选 AT89C52 或直接选 STC89C52RC,Output 选项卡勾上 Create HEX File,编译出 .hex 文件。
- Proteus 里放 AT89C52、SOUNDER、两个按键和晶振,双击单片机加载 .hex,把晶振频率改成 12MHz,运行仿真。
- 听不到声音不代表代码错:把示波器探头夹在 P2.5,看到周期正确的方波就说明音符频率对了。SOUNDER 发声依赖本机音频设备,仿真里用示波器验证比用耳朵可靠。
- 真机烧录用 STC-ISP 软件,型号选 STC89C52RC,串口选 USB 转 TTL 枚举出的 COM 口,点下载后给板子重新上电完成冷启动。烧录界面里的芯片模式必须和频率表一致:默认 12T,选成 6T 会整体高八度。
5. 数字音乐盒音准验证与答辩技巧:示波器、掉电记忆与变速
5.1 三分钟把音准量化
播放 A4 音时,把示波器探头接 P2.5,看到的方波周期应为 1/440 ≈ 2.27ms,误差在 ±2Hz 以内算正常。没有示波器就用手机定音器 App 对着蜂鸣器测,A4 应显示 440Hz。听感上,小星星第一句整体偏闷,先查音符数组下标映射;整首高一个八度,查 STC-ISP 里的 12T/6T 模式;只有个别音飘,查初值取整和晶振误差。
5.2 掉电记住当前曲目:STC 片内 EEPROM
STC89C52RC 内部带数据 Flash,常见例程把起始地址写成 0x2000,按 512 字节一个扇区擦除。换歌时把 songNo 写进去,上电先读出来,答辩时断电再开机回到上一首,是很加分的点。
// 寄存器名以 STC 头文件为准,89 系列常见 ISP_ 前缀 void EEPWrite(unsigned char dat) { ISP_CONTR = 0x83; // 使能 EEPROM,等待周期按 12MHz 选 ISP_CMD = 0x03; // 先擦除扇区 ISP_ADDRH = 0x20; ISP_ADDRL = 0x00; ISP_TRIG = 0x5A; ISP_TRIG = 0xA5; // 触发脉冲,连续写两次 ISP_CMD = 0x02; // 字节编程 ISP_DATA = dat; ISP_ADDRH = 0x20; ISP_ADDRL = 0x00; ISP_TRIG = 0x5A; ISP_TRIG = 0xA5; _nop_(); _nop_(); ISP_CONTR = 0x00; // 关闭,防止误操作 }写入前必须先擦除所在扇区,擦除粒度是 512 字节,误擦会把整个扇区清成 0xFF。不同批次 STC 芯片的 EEPROM 地址映射可能有差异,上电后先用读功能验证首字节,能读到预期值再决定是否写入。
5.3 让听感上一个台阶的小参数
音符之间留一点静音间隔:在 PlaySong 每个音符结束后、切下一个音符前,让 TR0 置 0 并等一个 10ms tick,能避免蜂鸣器余振把相邻音符连成浑浊一片。变速播放也很简单,把 beatMs 改成全局变量,KEY2 长按在 30/40/50 三档间循环即可。阻塞式播放下新速度只对下一首生效;要做到即时变速,就得把 PlaySong 改造成 10ms 状态机,这是播放器实现的标准做法,也是这个题目向上走一步的方向。
| 验证项 | 方法 | 通过标准 |
|---|---|---|
| 音准 | 示波器测 P2.5 方波周期 | A4 对应 2.27ms ±2% |
| 节拍 | 秒表记录 8 拍时长 | 与 beatMs 计算值误差 <0.3s |
| 按键 | 快速连按 20 次 | 无跳变、无卡死 |
| 掉电记忆 | 断电再上电 | 恢复上次曲目号 |
最后盯一眼 STC-ISP 烧录界面里的芯片模式:同一份初值表,12T 模式唱 C4,6T 模式唱出来就是 C5,音高整整升一个八度。先确认模式,再怀疑频率表。
本文还有配套的精品资源,点击获取