说明
随着电子技术的发展,电子技术正在逐渐改善着人们的学习、生活、工作,因此开发本系统希望能够给人们多带来一点生活上的乐趣,电子技术与音乐的结合不断加深。
由此而产生的电子琴在这种形势下,因其体积小,易于携带,经济适用,对初学者,尤其对识谱的人来说是很容易弹奏的,一首简单的曲子灵感好的人甚至不用很多的练习和教师的指导就能很快的弹奏出来。是一般家庭都能承受得了的经济投入,而且电子琴键盘操作直观易于掌握。
这样就强烈地激发了学习者的学习兴趣,迅速地提高了电子琴的普及率。电子琴使用简单。深受广大音乐爱好者推崇。
设计电子琴包含的知识基本上覆盖了模拟电子技术基础、数字电子技术基础、EDA技术、电子线路、单片机基础以及接口技术课程的重要章节。
采用集成电路设计,基于stc89c52单片机设计一款简易的电子琴,采用4*4距阵键盘,鉴于传统电子琴可以用键盘上的“k0”到“k16”键演奏从低So到高DO等16个音,从而可以用来弹奏喜欢的乐曲。
硬件设计
系统组成及总体框图
由单片机控制电子琴,单片机工作于约11.0592MHZ时钟频率,使用其定时/计数器T0,工作模式为1,改变计数值TH0和TL0可以产生不同频率的脉冲信号有七首内置音乐。
该设计具有16个音节的键盘节拍300ms,用户可以根据乐谱在键盘上进行演奏,音乐发生器会根据使用者的弹奏,通过扬声器将音乐播放出来。
由于本例实现的音乐发生器是由使用者通过键盘输入弹奏乐曲的,所以节拍由使用者掌握,不由程序控制。用单片机产生的音频脉冲直接驱动扬声器并不能产生所要实现的音乐,因此采用蜂鸣器来构成发声。
STC89C52
功能特性: STC89C52是一种低功耗、高性能CMOS8位微控制器,具有 8K 在系统可编程Flash 存储器。在单芯片上,拥有灵巧的8 位CPU 和在系统可编程Flash,使得STC89C52为众多嵌入式控制应用系统提供高灵活、超有效的解决方案。
具有以下标准功能: 8k字节Flash,512字节RAM, 32 位I/O 口线,看门狗定时器,内置4KB EEPROM,MAX810复位电路,2个16 位定时器/计数器,一个6向量2级中断结构,全双工串行口。另外 STC89X52 可降至0Hz 静态逻辑操作,支持2种软件可选择节电模式。
空闲模式下,CPU 停止工作,允许RAM、定时器/计数器、串口、中断继续工作。掉电保护方式下,RAM内容被保存,振荡器被冻结,单片机一切工作停止,直到下一个中断或硬件复位为止。最高运作频率35MHz,6T/12T可选。
硬件各模块原理
STC89S52最小系统
单的上电复位电路由电容和电阻串联构成,如图所示。 上电瞬间,由于电容两端电压不能突变,则相当于电容短路即RST 引脚电压端为VR 为VCC,随着 对电容的充电, RST 引脚的电压呈指数规律下降。
经过时间t1 后,VR 降为高电平所需电压的下限3.6V,随着对电容充电的进行,VR 最后将接 近0V。为了确保单片机复位,t1 必须大于两个机器周期的时间,机器周期取决 于单片机系统采用的晶振频率,图(a)中,R 不能取得太小,典型值10kΩ;t1 与RC 电路的时间常数有关,由晶振频率和R 可以算出C 的取值。
STC89S52模块电路原理图
单片机主程序模块通过对键盘扫描程序信号的读取,在通过对应的表,取出定时器初始值以产生不同的声音信号。在这一过程中,对声音信号是通过中断程序进行控制。
键盘扫描模块电路原理图
对键盘扫描电路的扫描方式有行扫描法和线反转法,在此次程序编写中,采用行扫描法,通过在p1_0,p1_2,p1_4,p1_6上循环送出0扫描信号,p1_1,p1_3,p1_5,p1_7输入按键上的高低电平信息给单片机,经处理程序,判断出是哪个开关按下,并送主程序以实现功能。
蜂鸣器电路原理图
发声部分采用有源蜂鸣器来实现“多来米发索拉西”的效果,实现音乐的播放。而且蜂鸣器的程序控制方便。
整体电路图
软件设计
本软件设计关键是要实现一种由单片机控制的简单音乐发生器,它由16个音节组成的的键盘,用户可以根据乐谱在键盘上进行演奏,音乐发生器会根据用户的弹奏,将音乐播放出来。
音乐相关知识
乐音听起来有的高,有的低,这就叫音高,音高是由发音物体振动频率的高低决定的,频率高声音就高,频率低声音就低,不同音商的乐音是用C、D、E、F、G、A、B表示的,这7个字母就是乐音的音名,它们一般依次唱成DO、RE、MI、FA、SO、LA、SI,这是唱曲时乐音的发音,所以叫唱名。
音持续时间的长短即时值,一般用拍数表示,休止符表示暂停发音。
一首音乐是由许多不同的音符组成的,而每个音符对应着不同的频率,这样就可以利用不同频率的组合,加以与拍数对应的延时,构成音乐。
如何用单片机实现音乐的节拍
除了音符以外,节拍也是音乐的关键组成部分。
节拍实际上就是音持续时间的长短,在单片机系统中可以用延时来实现,如果1/4拍的延时是0.4秒,则1拍的延时是1.6秒,只要知道1/4拍的延时时间,其余的节拍延时时间就是它的陪数。如果单片机要自己播放音乐,那么必须在程序设计中考虑到节拍的设置,由于本例实现的音乐发生器是由用户通过键盘输入弹奏乐曲的,所以节拍由用户掌握,不由程序控制。对于不同的曲调我们也可以用单片机的另外一个定时/计数器来完成。音乐的音拍,一个节拍为单位(C调)具体如下表:
如何用单片机产生音频脉冲
了解音乐的一些基本知识后可知,产生不同频率的音频脉冲即能产生音乐,对于单片机而言,产生不同频率有脉冲非常方便,可以利用它的定时/计数器来产生这样的方波频率信号,因此,需要弄清楚音乐中的音符和对应的频率,以及单片机定时计数的关系。
在本实验中,单片机工作于12MHZ时钟频率,使用其定时/计数器T0,工作模式为1,改变计数值TH0和TL0可以产生不同频率的脉冲信号,在此情况下,C调的各音符频率与计数值T的对照如下表:
系统总体功能流程图
该程序设计思路比较清晰既从开始到声明变量与函数再到读取按钮开关,判断是否按下,然后就是一个一个按钮的动作。其主程序如下:
按键子程序流程图如下:
系统调试
电路调试是整个系统功能否实现的关键步骤,我们将整个调试过程分为三大部分:硬件调试、软件调试和综合调试。
硬件调试
硬件调试主要是针对单片机部分进行调试。
在上电前,先确保电路中不在断路或短路情况,这一工作是整个调试工作的第一步,也是非常重要的一个步骤。在这部分调试中主要使用的工具是万用表,用来完成检测电路中是否存在断路或者短路情况等。注意焊点之间,确保焊点没有短接在一起,同时注意焊点的美观,确保没有开路以及短路的现象出现。
在确保硬件电路正常,无异常情况(断路或短路)方可上电调试,上电调试的目的是检验电路是否接错,同时还要检验原理是否正确,在本次设计中,上电调试主要键盘单片机控制部分、和音频转换电路硬件调试。
键盘单片机控制部分调试:上电后,随机按动键盘可以发现各个按键对应的音正确。
软件调试
整个程序是一个主程序调用各个子程序实现功能的过程,要使主程序和整个程序都能平稳运行,各个模块的子程序的正确与平稳运行必不可少,所以在软件调试的最初阶段就是把各个子程序模块进行分别调试。
程序清单
#include"reg52.h"
#include<intrins.h>
#define uchar unsigned char
#define uint unsigned int
sbit SPK = P2^3; //蜂鸣器
sbit led1=P2^0; //绿灯
sbit led2=P2^1; //红灯
sbit K2=P3^2; //内部歌曲选择
sbit K1=P3^3; //播放和停止键
uchar Song_Index=0,Tone_Index=0; //当前音乐段索引,音符索引
uchar code HI_LIST[]={208,211,216,220,224,226,229,232,233,236,238,239,241,242,244,244}; //存放计时器高八位
uchar code LO_LIST[]={21,8,5,16,12,4,13,10,20,3,8,28,2,23,5,27}; //存放计时器低五位节
//三段音乐的音符,表示每个音符位于哪个度,一共七首音乐
uchar code Song[][100]=
{
{1,2,3,1,1,2,3,1,3,4,5,3,4,5,5,6,5,3,5,6,5,3,5,3,2,1,2,1,-1},
{3,3,3,4,5,5,5,5,6,5,3,5,3,2,1,5,6,5,3,2,1,1,-1},
{3,2,1,3,2,1,1,2,3,1,1,2,3,1,3,4,5,3,4,5,5,6,5,3,5,3,2,1,3,2,1,1,-1},
{3,3,4,3,6,5,3,3,4,3,7,6,3,3,10,8,6,5,4,9,9,8,6,7,6,-1},
{9,9,9,9,7,9,5,10,9,9,9,9,9,7,6,5,7,6,6,7,9,9,9,7,7,6,5,5,6,7,5,5,3,2,3,2,9,9,9,9,7,6,5,10,9,9,4,5,6,5,6,7,6,5,5,-1},
{5,5,5,7,9,9,9,9,10,10,11,9,8,8,10,10,7,7,7,7,6,6,6,6,9,9,5,5,5,7,9,9,9,9,10,10,11,9,8,8,8,10,7,7,7,7,6,6,6,7,5,-1},
{9,10,10,9,9,7,9,10,10,9,9,10,9,7,6,6,7,9,7,6,7,5,10,9,10,9,7,10,9,9,9,7,6,5,5,5,6,7,5,-1}
};
//三段音乐的节拍,表示每个音符的时间
uchar code Len[][100]=
{
{1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,1,2,1,2,-1},
{1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,2,2,-1},
{1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,1,2,1,1,2,2,-1},
{1,1,2,2,2,4,1,1,2,2,2,4,1,1,2,2,2,2,3,1,1,2,2,3,4,-1},
{1,1,1,1,1,2,1,1,2,1,1,1,1,1,2,1,1,2,1,1,1,1,2,1,1,1,1,2,1,1,2,1,1,1,1,1,3,1,1,1,1,2,1,1,1,2,1,2,1,1,1,2,1,1,2,2,-1},
{1,1,1,1,1,1,1,1,1,2,1,3,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,3,1,1,1,1,1,1,1,1,1,1,1,1,2,-1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,-1}
};
uint FreqTemp;
unsigned int code Freqtab[] = { //定时半周期的初始值
64021,64103,64260,64400, //低音3 4 5 6
64524,64580,64633,64723, //低音7,中音1 2 3
64820,64898,64968,65030, //中音4 5 6 7
65058,65110,65157,65178}; //高音1 2 3 4
uchar code DSY_CODE[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x80,0xc6,0xc0,0x86,0x8e}; //七段数码管显示码
void DelayMS(uint ms)//毫秒延时
{
uchar t;
while(ms--) for(t=0;t<120;t++);
}
//外部中断0用于切换歌曲和停止播放
void EX0_INT() interrupt 0
{
SPK=1;
TR1=0; //播放结束或者播放中途切换歌曲时停止播放
DelayMS(20);
if(K2==0)
{
Song_Index=(Song_Index+1)%7; //跳到下一首的开头
Tone_Index=0;
P0=DSY_CODE[Song_Index+1]; //数码管显示当前音乐段号
}
}
//外部中断1用于控制播放内置音乐
void EX1_INT() interrupt 2
{
P0=DSY_CODE[Song_Index+1];
led1=1; //green off
TR1=1; //开始播放
led2=0; //red on
Tone_Index=0; //从第0个音符开始
//若切换音乐段会触发外部中断,导致TR1=0,播放也会停止
while(Song[Song_Index][Tone_Index]!=-1&&TR1==1)
{
DelayMS(300*Len[Song_Index][Tone_Index]); //播放延时(节拍)
Tone_Index++; //当前音乐段的下一音符索引
}
TR1=0;
SPK=1;
led2=1; //停止播放 red off
}
//定时器1中断函数 播放内置音乐
void T1_INT(void) interrupt 3
{
TL1=LO_LIST[Song[Song_Index][Tone_Index]];
TH1=HI_LIST[Song[Song_Index][Tone_Index]];
SPK=~SPK;
}
//定时器0中断函数 按键音
void T0_INT(void) interrupt 1
{
TL0 = FreqTemp; //载入定时半周期的初始值
TH0 = FreqTemp >> 8;
SPK = ~SPK; //发音
}
uchar Keyscan(void)
{
uchar i, j, temp, Buffer[4] = {0xfe, 0xfd, 0xfb, 0xf7};
for(j = 0; j < 4; j++) { //循环四次,扫描四行
P1 = Buffer[j]; //在低四位分别输出一个低电平
_nop_();
temp = 0x80; //计划先读出P1.7位
for(i = 0; i < 4; i++) { //循环四次,检查四列
if(!(P1 & temp)) { //从高四位,截取1位
return (i + j * 4); //返回取得的按键值
}
temp >>= 1; //换右边一位
} }
return 16; //没有键按下就返回16
}
void Display(uchar i)
{ //数码管显示当前按键以及当前播放音乐序号
P0= DSY_CODE[i];
}
void main()
{
uchar Key_Value = 16, Key_Temp1, Key_Temp2;//读出的键值
TMOD = 0x01; //T0定时方式1 T1方式0
ET0 = 1; //允许T0中断
ET1 = 1; //允许T1中断
EX0 = 1; //允许X0中断
EX1=1; //允许X1中断
EA = 1; //开总中断
IT0=1; //下降沿触发
IT1 = 1; //下降沿触发
IP=0x09; //中断优先级,定时器1高优先,外部中断1高优先
TR1=0; //定时器1初始关闭
while(1)
{
TR0 = 0; //暂不发音
SPK=1; //蜂鸣器置高
if(TR1==0)
{
led1=0; //green on
Key_Temp1 = Keyscan(); //读入按键
if(Key_Temp1 != 16&&TR1==0) { //有键按下
Display(Key_Value); //显示键值、延时消抖
Key_Temp2 = Keyscan(); //再读一次
if (Key_Temp1 == Key_Temp2) {//两次相等
Key_Value = Key_Temp1; //就确认下来
FreqTemp = Freqtab[Key_Value]; //根据键值,取出定时半周期的初始值
Display(Key_Value); //显示
TR0 = 1; //启动定时器,发音
while (Keyscan() < 16); //等待释放
SPK = 1; //停止发音
}
}
}
}
}