一、简介
一个基于8051微控制器的计数器系统,该系统能够通过按键输入递增计数,并且能够在达到100时归零。该系统将使用计数器中断和外部中断来实现其功能。
51单片机因其简单易用和成本效益高,成为电子爱好者和学生的首选平台。通过编程单片机播放音乐,不仅可以锻炼编程技能,还能增加对电子项目的兴趣。本文将通过Proteus仿真,指导你如何使用51单片机播放三首经典歌曲。
设计一个基于8051微控制器的100以内按键计数器,涉及到硬件选择、软件编程、以及系统集成等多个方面。
二、环境与工具
Proteus 8.9 SP2 或更高版本:用于电路设计和仿真。
Keil uVision:用于编写和编译51单片机的C语言程序。
51单片机开发板:如STC89C52。
蜂鸣器:用于发声。
电阻:用于限流保护。
面包板及跳线:用于搭建电路。
三、项目目标与设计思路
1.利用51单片机通过蜂鸣器播放三首歌曲:《挥着翅膀的女孩》、《同一首歌》、《两只蝴蝶 》
2.设计一个基于8051微控制器的100以内按键计数器,涉及到硬件选择、软件编程、以及系统集成等多个方面。
3.1微控制器选择:
使用AT89C51微控制器,因为它是8051系列中广泛使用的一种,具有足够的I/O端口和内存来实现基本的计数器功能。
3.2显示设计:
使用七段数码管来显示计数值。数码管通过微控制器的P0和P2端口连接。
P0端口用于显示十位数字,P2端口用于显示个位数字。
3.3按键输入:
通过外部中断INT0(P3.2)来实现按键输入功能。当按键被按下时,触发外部中断,增加计数值。
3.4晶振配置:
使用XTAL1和XTAL2作为系统时钟的晶振输入,提供稳定的时钟信号,以确保定时器的准确性。
3.5定时器中断:
利用定时器0的溢出中断来实现定时清零功能,通过设置定时器0的初值(TH0和TL0)来控制溢出的时间。
3.6复位电路:
RST引脚连接复位电路,确保系统在上电或需要时能够正确复位。
3.7电源和去耦:
为微控制器和外围电路提供稳定的电源,并在关键位置放置去耦电容(如22pF),以减少电源噪声。
3.8串行通信:
P3.0/RXD和P3.1/TXD引脚用于串行通信,可用于与PC或其他设备进行数据交换。
3.9外部存储器接口:
ALE(地址锁存允许)引脚、PSEN(外部存储器允许)引脚和地址线(如P2.0/A8到P2.7/A15)用于连接外部存储器,如EPROM或SRAM。
3.10其他控制引脚:
P3.3/INT1和P3.4/T0等引脚预留为可能的扩展功能,如第二个外部中断或额外的定时器输入。
3.11调试和测试:
K1和K2可能是用于调试目的的开关,如K1用于计数,K2用于清零。
四、硬件设计
1. 电路搭建
将蜂鸣器的正极连接到51单片机的P1.0端口。
将蜂鸣器的负极连接到单片机的GND端口。
使用10KΩ电阻串联在蜂鸣器与P1.0端口之间,以限制电流。
2. Proteus仿真设置_**_
在Proteus中创建新的项目,并将51单片机和蜂鸣器添加到设计中。
1、X1 和 X2:这些通常表示晶振,用于为单片机提供时钟信号。晶振频率为12MHz,这是单片机运行的时钟频率。
2、XTAL1 和 XTAL2:这两个标识通常用于表示单片机的晶振输入和输出引脚。
3、P0.0 至 P0.7:这些标识代表单片机的端口0(Port 0)的8个I/O(输入/输出)引脚。在51单片机中,P0端口的引脚可以被配置为通用I/O或者特殊的功能,如外部数据存储器的地址或数据引脚。
4、P1.0 至 P1.7:这些是端口1(Port 1)的引脚,通常用于通用I/O,但在某些配置下也可能用于外部存储器的地址线。
5、P2.0 至 P2.7:端口2(Port 2)的引脚,同样可以作为通用I/O使用,或者在某些51单片机中作为第二组地址线。
6、P3.0 至 P3.7:端口3(Port 3)的引脚,这些引脚在51单片机中通常有特定的功能,如串行通信(RXD/TXD)、外部中断(INT0/INT1)、定时器(T0/T1)、写(WR)、读(RD)等。
6、RST:复位引脚,用于将单片机重置到初始状态。
7、ALE:地址锁存器使能(Address Latch Enable),用于锁存外部存储器的地址。
8、EA:外部访问允许(External Access Enable),通常用于启用或禁用外部存储器。
9、PSEN:编程存储器使能(Program Store Enable),用于访问外部程序存储器。
10、LS1:逻辑选择1,可能用于选择不同的存储器配置。
11、SOUNDER:这可能是一个自定义的标识,用于表示与蜂鸣器或扬声器连接的引脚。
12、P1.3:根据标识,这个引脚可能用于外部中断(INT1)或定时器(T1)的功能。
13、P3.0/RXD 和 P3.1/TXD:这些引脚用于单片机的串行通信,RXD是接收数据,TXD是发送数据。
整体电路图展示:
对应功能展示:
7.1计数:
7.2清零:
7.3到一百清零:
五、软件设计
1. 程序框架
编写C语言程序,控制蜂鸣器播放音乐。
包含头文件
#include <REG52.H>
#include "SoundPlay.h"
延时函数
void Delay1ms(unsigned int count)
{
unsigned int i,j;
for(i=0;i<count;i++)
for(j=0;j<120;j++);
}
2. 音乐播放逻辑
为每首歌曲创建一个音符数组,包含音符的频率和持续时间。
编写播放函数,根据音符数组控制蜂鸣器发声。
曲谱存贮格式 unsigned char code MusicName{音高,音长,音高,音长…., 0,0}; 末尾:0,0 表示结束(Important)
音高由三位数字组成: 音高由三位数字组成:
个位是表示 1~7 这七个音符
十位是表示音符所在的音区:1-低音,2-中音,3-高音;
百位表示这个音符是否要升半音: 0-不升,1-升半音。
音长最多由三位数字组成: 音长最多由三位数字组成:
个位表示音符的时值,其对应关系是:
数值(n): |0 |1 |2 |3 | 4 | 5 | 6
几分音符: |1 |2 |4 |8 |16 |32 |64 音符=2^n
十位表示音符的演奏效果(0-2): 0-普通,1-连音,2-顿音
百位是符点位: 0-无符点,1-有符点
调用演奏子程序的格式
Play(乐曲名,调号,升降八度,演奏速度);
乐曲名 : 要播放的乐曲指针,结尾以(0,0)结束;
调号(0-11) : 是指乐曲升多少个半音演奏;
升降八度(1-3) : 1:降八度, 2:不升不降, 3:升八度;
演奏速度(1-12000): 值越大速度越快;
3. 编译与仿真
使用Keil编译器将程序编译成HEX文件。
在Proteus中加载HEX文件,进行仿真测试。
六、实现步骤
- 启动:系统上电后,通过初始化代码配置微控制器,并清零计数器。
- 按键按下:用户按下按键,触发外部中断,计数器递增。
- 显示更新:定时器中断服务程序被触发,更新显示设备上的计数值。
- 计数器溢出:当计数器达到100时,自动归零。
5.播放音乐1. 音符频率数据
为三首歌曲创建音符频率数据表。
《挥着翅膀的女孩》
《同一首歌》unsigned char code Music_Girl[]={ 0x17,0x02, 0x17,0x03, 0x18,0x03, 0x19,0x02, 0x15,0x03, 0x16,0x03, 0x17,0x03, 0x17,0x03, 0x17,0x03, 0x18,0x03, 0x19,0x02, 0x16,0x03, 0x17,0x03, 0x18,0x02, 0x18,0x03, 0x17,0x03, 0x15,0x02, 0x18,0x03, 0x17,0x03, 0x18,0x02, 0x10,0x03, 0x15,0x03, 0x16,0x02, 0x15,0x03, 0x16,0x03, 0x17,0x02, 0x17,0x03, 0x18,0x03, 0x19,0x02, 0x1A,0x03, 0x1B,0x03, 0x1F,0x03, 0x1F,0x03, 0x17,0x03, 0x18,0x03, 0x19,0x02, 0x16,0x03, 0x17,0x03, 0x18,0x03, 0x17,0x03, 0x18,0x03, 0x1F,0x03, 0x1F,0x02, 0x16,0x03, 0x17,0x03, 0x18,0x03, 0x17,0x03, 0x18,0x03, 0x20,0x03, 0x20,0x02, 0x1F,0x03, 0x1B,0x03, 0x1F,0x66, 0x20,0x03, 0x21,0x03, 0x20,0x03, 0x1F,0x03, 0x1B,0x03, 0x1F,0x66, 0x1F,0x03, 0x1B,0x03, 0x19,0x03, 0x19,0x03, 0x15,0x03, 0x1A,0x66, 0x1A,0x03, 0x19,0x03, 0x15,0x03, 0x15,0x03, 0x17,0x03, 0x16,0x66, 0x17,0x04, 0x18,0x04, 0x18,0x03, 0x19,0x03, 0x1F,0x03, 0x1B,0x03, 0x1F,0x66, 0x20,0x03, 0x21,0x03, 0x20,0x03, 0x1F,0x03, 0x1B,0x03, 0x1F,0x66, 0x1F,0x03, 0x1B,0x03, 0x19,0x03, 0x19,0x03, 0x15,0x03, 0x1A,0x66, 0x1A,0x03, 0x19,0x03, 0x19,0x03, 0x1F,0x03, 0x1B,0x03, 0x1F,0x00, 0x1A,0x03, 0x1A,0x03, 0x1A,0x03, 0x1B,0x03, 0x1B,0x03, 0x1A,0x03, 0x19,0x03, 0x19,0x02, 0x17,0x03, 0x15,0x17, 0x15,0x03, 0x16,0x03, 0x17,0x03, 0x18,0x03, 0x17,0x04, 0x18,0x0E, 0x18,0x03, 0x17,0x04, 0x18,0x0E, 0x18,0x66, 0x17,0x03, 0x18,0x03, 0x17,0x03, 0x18,0x03, 0x20,0x03, 0x20,0x02, 0x1F,0x03, 0x1B,0x03, 0x1F,0x66, 0x20,0x03, 0x21,0x03, 0x20,0x03, 0x1F,0x03, 0x1B,0x03, 0x1F,0x66, 0x1F,0x04, 0x1B,0x0E, 0x1B,0x03, 0x19,0x03, 0x19,0x03, 0x15,0x03, 0x1A,0x66, 0x1A,0x03, 0x19,0x03, 0x15,0x03, 0x15,0x03, 0x17,0x03, 0x16,0x66, 0x17,0x04, 0x18,0x04, 0x18,0x03, 0x19,0x03, 0x1F,0x03, 0x1B,0x03, 0x1F,0x66, 0x20,0x03, 0x21,0x03, 0x20,0x03, 0x1F,0x03, 0x1B,0x03, 0x1F,0x66, 0x1F,0x03, 0x1B,0x03, 0x19,0x03, 0x19,0x03, 0x15,0x03, 0x1A,0x66, 0x1A,0x03, 0x19,0x03, 0x19,0x03, 0x1F,0x03, 0x1B,0x03, 0x1F,0x00, 0x18,0x02, 0x18,0x03, 0x1A,0x03, 0x19,0x0D, 0x15,0x03, 0x15,0x02, 0x18,0x66, 0x16,0x02, 0x17,0x02, 0x15,0x00, 0x00,0x00};
unsigned char code Music_Same[]={ 0x0F,0x01, 0x15,0x02, 0x16,0x02, 0x17,0x66, 0x18,0x03, 0x17,0x02, 0x15,0x02, 0x16,0x01, 0x15,0x02, 0x10,0x02, 0x15,0x00, 0x0F,0x01, 0x15,0x02, 0x16,0x02, 0x17,0x02, 0x17,0x03, 0x18,0x03, 0x19,0x02, 0x15,0x02, 0x18,0x66, 0x17,0x03, 0x19,0x02, 0x16,0x03, 0x17,0x03, 0x16,0x00, 0x17,0x01, 0x19,0x02, 0x1B,0x02, 0x1B,0x70, 0x1A,0x03, 0x1A,0x01, 0x19,0x02, 0x19,0x03, 0x1A,0x03, 0x1B,0x02, 0x1A,0x0D, 0x19,0x03, 0x17,0x00, 0x18,0x66, 0x18,0x03, 0x19,0x02, 0x1A,0x02, 0x19,0x0C, 0x18,0x0D, 0x17,0x03, 0x16,0x01, 0x11,0x02, 0x11,0x03, 0x10,0x03, 0x0F,0x0C, 0x10,0x02, 0x15,0x00, 0x1F,0x01, 0x1A,0x01, 0x18,0x66, 0x19,0x03, 0x1A,0x01, 0x1B,0x02, 0x1B,0x03, 0x1B,0x03, 0x1B,0x0C, 0x1A,0x0D, 0x19,0x03, 0x17,0x00, 0x1F,0x01, 0x1A,0x01, 0x18,0x66, 0x19,0x03, 0x1A,0x01, 0x10,0x02, 0x10,0x03, 0x10,0x03, 0x1A,0x0C, 0x18,0x0D, 0x17,0x03,```c **《两只蝴蝶 》** unsigned char code Music_Two[] ={ 0x17,0x03, 0x16,0x03, 0x17,0x01, 0x16,0x03, 0x17,0x03, 0x16,0x03, 0x15,0x01, 0x10,0x03, 0x15,0x03, 0x16,0x02, 0x16,0x0D, 0x17,0x03, 0x16,0x03, 0x15,0x03, 0x10,0x03, 0x10,0x0E, 0x15,0x04, 0x0F,0x01, 0x17,0x03, 0x16,0x03, 0x17,0x01, 0x16,0x03, 0x17,0x03, 0x16,0x03, 0x15,0x01, 0x10,0x03, 0x15,0x03, 0x16,0x02, 0x16,0x0D, 0x17,0x03, 0x16,0x03, 0x15,0x03, 0x10,0x03, 0x15,0x03, 0x16,0x01, 0x17,0x03, 0x16,0x03, 0x17,0x01, 0x16,0x03, 0x17,0x03, 0x16,0x03, 0x15,0x01, 0x10,0x03, 0x15,0x03, 0x16,0x02, 0x16,0x0D, 0x17,0x03, 0x16,0x03, 0x15,0x03, 0x10,0x03, 0x10,0x0E, 0x15,0x04, 0x0F,0x01, 0x17,0x03, 0x19,0x03, 0x19,0x01, 0x19,0x03, 0x1A,0x03, 0x19,0x03, 0x17,0x01, 0x16,0x03, 0x16,0x03, 0x16,0x02, 0x16,0x0D, 0x17,0x03, 0x16,0x03, 0x15,0x03, 0x10,0x03, 0x10,0x0D, 0x15,0x00, 0x19,0x03, 0x19,0x03, 0x1A,0x03, 0x1F,0x03, 0x1B,0x03, 0x1B,0x03, 0x1A,0x03, 0x17,0x0D, 0x16,0x03, 0x16,0x03, 0x16,0x0D, 0x17,0x01, 0x17,0x03, 0x17,0x03, 0x19,0x03, 0x1A,0x02, 0x1A,0x02, 0x10,0x03, 0x17,0x0D, 0x16,0x03, 0x16,0x01, 0x17,0x03, 0x19,0x03, 0x19,0x03, 0x17,0x03, 0x19,0x02, 0x1F,0x02, 0x1B,0x03, 0x1A,0x03, 0x1A,0x0E, 0x1B,0x04, 0x17,0x02, 0x1A,0x03, 0x1A,0x03, 0x1A,0x0E, 0x1B,0x04, 0x1A,0x03, 0x19,0x03, 0x17,0x03, 0x16,0x03, 0x17,0x0D, 0x16,0x03, 0x17,0x03, 0x19,0x01, 0x19,0x03, 0x19,0x03, 0x1A,0x03, 0x1F,0x03, 0x1B,0x03, 0x1B,0x03, 0x1A,0x03, 0x17,0x0D, 0x16,0x03, 0x16,0x03, 0x16,0x03, 0x17,0x01, 0x17,0x03, 0x17,0x03, 0x19,0x03, 0x1A,0x02, 0x1A,0x02, 0x10,0x03, 0x17,0x0D, 0x16,0x03, 0x16,0x01, 0x17,0x03, 0x19,0x03, 0x19,0x03, 0x17,0x03, 0x19,0x03, 0x1F,0x02, 0x1B,0x03, 0x1A,0x03, 0x1A,0x0E, 0x1B,0x04, 0x17,0x02, 0x1A,0x03, 0x1A,0x03, 0x1A,0x0E, 0x1B,0x04, 0x17,0x16, 0x1A,0x03, 0x1A,0x03, 0x1A,0x0E, 0x1B,0x04, 0x1A,0x03, 0x19,0x03, 0x17,0x03, 0x16,0x03, 0x0F,0x02, 0x10,0x03, 0x15,0x00, 0x00,0x00 }; 0x16,0x00, 0x0F,0x01, 0x15,0x02, 0x16,0x02, 0x17,0x70, 0x18,0x03, 0x17,0x02, 0x15,0x03, 0x15,0x03, 0x16,0x66, 0x16,0x03, 0x16,0x02, 0x16,0x03, 0x15,0x03, 0x10,0x02, 0x10,0x01, 0x11,0x01, 0x11,0x66, 0x10,0x03, 0x0F,0x0C, 0x1A,0x02, 0x19,0x02, 0x16,0x03, 0x16,0x03, 0x18,0x66, 0x18,0x03, 0x18,0x02, 0x17,0x03, 0x16,0x03, 0x19,0x00, 0x00,0x00 };
2. 播放控制
编写控制函数,根据音符频率和持续时间控制蜂鸣器。
#ifndef __SOUNDPLAY_H_REVISION_FIRST__
#define __SOUNDPLAY_H_REVISION_FIRST__
//**************************************************************************
#define SYSTEM_OSC 12000000 //定义晶振频率12000000HZ
#define SOUND_SPACE 4/5 //定义普通音符演奏的长度分率,//每4分音符间隔
sbit BeepIO = P3^7; //定义输出管脚
unsigned int code FreTab[12] = { 262,277,294,311,330,349,369,392,415,440,466,494 }; //原始频率表
unsigned char code SignTab[7] = { 0,2,4,5,7,9,11 }; //1~7在频率表中的位置
unsigned char code LengthTab[7]= { 1,2,4,8,16,32,64 };
unsigned char Sound_Temp_TH0,Sound_Temp_TL0; //音符定时器初值暂存
unsigned char Sound_Temp_TH1,Sound_Temp_TL1; //音长定时器初值暂存
//**************************************************************************
void InitialSound(void)
{
BeepIO = 0;
Sound_Temp_TH1 = (65535-(1/1200)*SYSTEM_OSC)/256; // 计算TL1应装入的初值 (10ms的初装值)
Sound_Temp_TL1 = (65535-(1/1200)*SYSTEM_OSC)%256; // 计算TH1应装入的初值
TH1 = Sound_Temp_TH1;
TL1 = Sound_Temp_TL1;
TMOD |= 0x11;
ET0 = 1;
ET1 = 0;
TR0 = 0;
TR1 = 0;
EA = 1;
}
void BeepTimer0(void) interrupt 1 //音符发生中断
{
BeepIO = !BeepIO;
TH0 = Sound_Temp_TH0;
TL0 = Sound_Temp_TL0;
}
//**************************************************************************
void Play(unsigned char *Sound,unsigned char Signature,unsigned Octachord,unsigned int Speed)
{
unsigned int NewFreTab[12]; //新的频率表
unsigned char i,j;
unsigned int Point,LDiv,LDiv0,LDiv1,LDiv2,LDiv4,CurrentFre,Temp_T,SoundLength;
unsigned char Tone,Length,SL,SH,SM,SLen,XG,FD;
for(i=0;i<12;i++) // 根据调号及升降八度来生成新的频率表
{
j = i + Signature;
if(j > 11)
{
j = j-12;
NewFreTab[i] = FreTab[j]*2;
}
else
NewFreTab[i] = FreTab[j];
if(Octachord == 1)
NewFreTab[i]>>=2;
else if(Octachord == 3)
NewFreTab[i]<<=2;
}
SoundLength = 0;
while(Sound[SoundLength] != 0x00) //计算歌曲长度
{
SoundLength+=2;
}
Point = 0;
Tone = Sound[Point];
Length = Sound[Point+1]; // 读出第一个音符和它时时值
LDiv0 = 12000/Speed; // 算出1分音符的长度(几个10ms)
LDiv4 = LDiv0/4; // 算出4分音符的长度
LDiv4 = LDiv4-LDiv4*SOUND_SPACE; // 普通音最长间隔标准
TR0 = 0;
TR1 = 1;
while(Point < SoundLength)
{
SL=Tone%10; //计算出音符
SM=Tone/10%10; //计算出高低音
SH=Tone/100; //计算出是否升半
CurrentFre = NewFreTab[SignTab[SL-1]+SH]; //查出对应音符的频率
if(SL!=0)
{
if (SM==1) CurrentFre >>= 2; //低音
if (SM==3) CurrentFre <<= 2; //高音
Temp_T = 65536-(50000/CurrentFre)*10/(12000000/SYSTEM_OSC);//计算计数器初值
Sound_Temp_TH0 = Temp_T/256;
Sound_Temp_TL0 = Temp_T%256;
TH0 = Sound_Temp_TH0;
TL0 = Sound_Temp_TL0 + 12; //加12是对中断延时的补偿
}
SLen=LengthTab[Length%10]; //算出是几分音符
XG=Length/10%10; //算出音符类型(0普通1连音2顿音)
FD=Length/100;
LDiv=LDiv0/SLen; //算出连音音符演奏的长度(多少个10ms)
if (FD==1)
LDiv=LDiv+LDiv/2;
if(XG!=1)
if(XG==0) //算出普通音符的演奏长度
if (SLen<=4)
LDiv1=LDiv-LDiv4;
else
LDiv1=LDiv*SOUND_SPACE;
else
LDiv1=LDiv/2; //算出顿音的演奏长度
else
LDiv1=LDiv;
if(SL==0) LDiv1=0;
LDiv2=LDiv-LDiv1; //算出不发音的长度
if (SL!=0)
{
TR0=1;
for(i=LDiv1;i>0;i--) //发规定长度的音
{
while(TF1==0);
TH1 = Sound_Temp_TH1;
TL1 = Sound_Temp_TL1;
TF1=0;
}
}
if(LDiv2!=0)
{
TR0=0; BeepIO=0;
for(i=LDiv2;i>0;i--) //音符间的间隔
{
while(TF1==0);
TH1 = Sound_Temp_TH1;
TL1 = Sound_Temp_TL1;
TF1=0;
}
}
Point+=2;
Tone=Sound[Point];
Length=Sound[Point+1];
}
BeepIO = 0;
}
3. 主函数
在主函数中调用播放控制函数,循环播放三首歌曲。
main()
{
InitialSound();
while(1)
{
Play(Music_Girl,0,3,360);
Delay1ms(500);
Play(Music_Same,0,3,360);
Delay1ms(500);
Play(Music_Two,0,3,360);
Delay1ms(500);
}
}
七、结果与验证
通过Proteus仿真,验证51单片机是否能够正确播放三首歌曲的旋律。
控制蜂鸣器的开关状态,与音乐播放的逻辑有关
提供时钟信号:晶体振荡器(X1)提供稳定的时钟信号给单片机(U1),这是单片机运行的基础。
稳定电源:电容器(C2)通常用于电源电路中,帮助稳定电压和减少噪声。
单片机运行:单片机(U1)使用提供的时钟信号来控制其内部操作的节奏。
八、常见问题与解决方案
问题:蜂鸣器声音不稳定或不发声。
解决:检查蜂鸣器与单片机的连接是否牢固,确认电阻是否正确串联。
问题:播放的旋律与预期不符。
解决:核对音符频率数据表是否准确,调整延时函数以匹配正确的音符持续时间。
九、总结
通过本项目,你将学会如何使用Proteus软件和51单片机来实现音乐播放功能,这不仅能够提升你的编程技能,还能加深对电子音乐制作的理解。通过编写C语言程序,加深了对8051微控制器编程的理解。
学习了如何使用中断服务程序来处理外部事件,如按键输入和定时器溢出。
评论(0)
您还未登录,请登录后发表或查看评论