**单片机设计介绍
本文设计了一款基于STC89C52单片机的音乐盒,首先根据流程图总体介绍了本次设计所需要的模块电路,再分开一个一个介绍,第一个介绍了我们的软件部分分为我们的音乐程序以及LCD显示屏的初始化的一个设计,第二就要介绍硬件部分分为时钟振荡电路,复位电路,蜂鸣器电路,LED闪烁模块和LCD显示模块。最后我们会利用Proteus软件对本次的设计进行仿真。首先功能上实现读取乐谱上的信息并转化为单片机可用数据,通过驱动蜂鸣器实现音乐的播放。同时在液晶屏上显示音乐的相关信息。
关键词:数字音乐盒;单片机;蜂鸣器;LCD。
如图1和图2所示为音乐盒的设计框图和流程图。它是以STC89C52单片机为核心,有时钟振荡电路,复位电路,蜂鸣器电路以及LCD显示屏所组成的。
图1音乐盒设计框图
图2音乐盒设流程图
已知不同的音调有着不一样的频率。每个音调都有着不同的时间周期常数。用定时器T0对单片机进行定时,通过输出不同周期的方波给蜂鸣器来控制,发出需要的音调,从低音到高音每一个音调都有定时器T0对应的一个初值。由此当晶振为12MHz时可以列出音调与定时器T0初值的表3:
通过表4.1音调,频率和初值的关系表我们可以读出歌曲简谱上的每一个音符对应的音调以及定时器T0的初值,当从初值计数到二的十六次方也就是65526这个最大值的时候让定时器T0溢出,从而可以得到音调频率通过在单片机某个管脚信号上产生的,这时候我们可以再通过单片机所连接的蜂鸣器内部金属片的碰合断开来实现发声。任何一段音乐需要音调也同样所需要的是节拍,我们想要不同的节拍是可以根据通过延时的方法来产生的,如果一拍为0.16s,那么四分之一拍为0.4s,我们想要节拍时间就设定延时时间。如下表4就是四分之一拍和八分之一拍的一个设定。
表4 1/4和1/8节拍设定
我们要提前把音符找出来在我们在编写程序之前,然后再去找到音符所对应的简谱码和对应的初值建立节拍关系每一个字节都会被音符使用。在本设计中利用到延时函数用来控制每一个音符的时长,每一个延时函数都可以用十六分的音符的时长来表示,并且以此来做为一个标准去衡量其他音符时长,因此十六音符就是调用一次的时长,八分音符就是去循环去调用该函数两次,四分音符就是调用四次,二分音符就是循环调用该函数八次。
音符频率计算: T = 65536 - 1/Fr/2/MC
T:要算得的定时器初值
Fr: 各音阶对应的频率
MC: 一个机器周期所需的时间 ,11.0592MHz的晶振对应的机器周期为1.085μsEX:低音Do对应的频率为262,则T = 65536 - 1/2/1.085/262*(10^6)=63777,对应十六进制数为0xF921,分别写进TH0和TL0。音调和节拍关系处理:从简谱可以读出每个音符的音调和节拍存在数组中,每三个表示一个音符,每一个数字表示音符名1 2 3 4 5 6 7;第二个数字表示音调所在音区:0是重音,1是中音,2是高音;第三个数字表示延时函数的调用次数:1是16分音符调用次数,2是8分音符调用次数,4是4分音符调用次数,8是2分音符调用次数,16是全音符调用次数。而简谱到音乐的编码我们可以这样来表示数组第一个数字6表示第一个音符6,第二个数字是表示音符的音高,简谱上不带•的是2,表示第二个八度,简谱上面带•的是3,是第三个八度,简谱下面带•的是1,是第一个八度。第三个数字表示他的时长,通过这样就可以构成一个音乐的数组。
如图8为LCD显示程序初始化过程,它需要在工作显示之前完成初始化,对数字汉字的一个完整确认输入,之后再通过单片机对他进行写指令或者数据。
本设计中LCD初始化过程会利用8位数据并行方式,向LCD在当DL为高电平,RE为低电平时候写入0x30的指令,0x30写入功能设定中。向LCD在DBC指令都为高电平的时候写入0x0f指令,等待时间大于100us,这是我们的显示开关控制指令。当全部清除指令之后,把0x01指令写入LCD,等待时间大于10ms,把0x06指令写入LCD,这时就进入了模式设置。完成初始化设置,根据控制端使用单片机向LCD写入指令或者数据。
#include
#include
#define uchar unsigned char
#define uint unsigned int
sbit beep=P1^0; //定义蜂鸣器输出端口
uchar tick,tl,th; //定义节拍和初值
uchar TABLE[]={ //音符节拍表
0x82,0x01,0x81,0x94,0x84,0xB4,0xA4,0x04,
0x82,0x01,0x81,0x94,0x84,0xC4,0xB4,0x04,
0x82,0x01,0x81,0xF4,0xD4,0xB4,0xA4,0x94,
0xE2,0x01,0xE1,0xD4,0xB4,0xC4,0xB4,0x04,
0x82,0x01,0x81,0x94,0x84,0xB4,0xA4,0x04,
0x82,0x01,0x81,0x94,0x84,0xC4,0xB4,0x04,
0x82,0x01,0x81,0xF4,0xD4,0xB4,0xA4,0x94,
0xE2,0x01,0xE1,0xD4,0xB4,0xC4,0xB4,0x04,
0x00};
uchar TABLE[]={ //音符对应的定时器初值表
//64260,64400,64521,64580,
0xfb,0x04,0xfb,0x90,0xfc,0x09,0xfc,0x44,
//64684,64777,64820,64898,
0xfc,0xac,0xfd,0x09,0xfd,0x34,0xfd,0x82,
//64968,65030,65058,65110,
0xfd,0xc8,0xfe,0x06,0xfe,0x22,0xfe,0x56,
//65157,65178,65217
0xfe,0x85,0xfe,0x9a,0xfe,0xc1
};
/******************T0中断服务函数**********************/
timer0() interrupt 1 using 1{
TL0=tl;TH0=th; //重装定时初值
BEEP=~BEEP; //喇叭输出端口取反
}
/*******************基本单位延时函数*******************/
void delay1(){
unit i;
for(i=0;i<20000;i++);
}
/*********************节拍延时函数********************/
void delay(tt){
uchar i;
for(i=0;i<=tt;i++)delay();
}
/**********************主函数************************/
void main(){
uchar t,t1,k=0; //定义临时变量
while(1){
TMOD=0x01;IE=0x82; //定义T0工作方式,开中断
while(TABLE[k]!=0){ //判断取得的简谱码是否为结束码
Tick=(TABLE[k])&0x0f; //不是则取节拍码
t=(_crol_(TABLE[k],4))&0x0f; //取音符码
if(t!=0){ //判断取得的音符码是否为0
t1=--t*2+1; //不是,根据取得的音符码计算T0初值
t=t*2;
tl=TL0=TABLE1[t1];
Th=TH0=TABLE1[t];
TR0=1; //启动T0
}
else TR0=0; //取得的音符码为0,则停止T0
delay(tick); //根据则选的节拍码延时
k++;
}
TR=0; //取得的简谱码为结束码,则停止T0
}
#include
#include
#define uchar unsigned char
#define uint unsigned int
#define screen_on 0x3f //设置屏幕开关显示,0x3f表示开显示?
#define screen_off 0x3e //0x3e表示关显示?
#define line 0xC0 //首行地址为0XC0?
#define page 0xb8 //首页地址为0XB8?
#define col 0x40 //首列地址为0X40??
sbit CS1=P2^0; /*片选1*/
sbit CS2=P2^1; /*片选2*/
sbit RS=P2^2; /*数据/指令选择*/
sbit RW=P2^3; /*读/写选择*/
sbit EN=P2^4; /*读/写使能*/
void delay(uint t)
{
while(--t);
}
uchar code sheng[]=
{
/*-- 文字: 生 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
0x80,0x40,0x30,0x1E,0x10,0x10,0x10,0xFF,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
0x40,0x40,0x42,0x42,0x42,0x42,0x42,0x7F,0x42,0x42,0x42,0x42,0x42,0x40,0x40,0x00
};
uchar code ri[]=
{
/*-- 文字: 日 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
0x00,0x00,0x00,0xFE,0x82,0x82,0x82,0x82,0x82,0x82,0x82,0xFE,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0xFF,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0xFF,0x00,0x00,0x00,0x00
};
uchar code kuai[]=
{
/*-- 文字: 快 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
0x00,0xE0,0x00,0xFF,0x10,0x20,0x08,0x08,0x08,0xFF,0x08,0x08,0xF8,0x00,0x00,0x00,
0x01,0x00,0x00,0xFF,0x00,0x81,0x41,0x31,0x0D,0x03,0x0D,0x31,0x41,0x81,0x81,0x00
};
uchar code le[]=
{
/*-- 文字: 乐 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
0x00,0x00,0xE0,0x9C,0x84,0x84,0x84,0xF4,0x82,0x82,0x83,0x82,0x80,0x80,0x00,0x00,
0x00,0x20,0x10,0x08,0x06,0x40,0x80,0x7F,0x00,0x00,0x02,0x04,0x08,0x30,0x00,0x00
};
void busy() //状态检查,LCD是否忙
{
P0=0X00;
RS=0;
RW=1;
EN=1;
while(P0&0X80);
EN=0;
}
void wcmd(uchar cmd) //写命令函数
{
busy();//检测LCD是否忙
RS=0;
RW=0;
P0=cmd;
EN=1;
_nop_();
_nop_();
EN=0;
}
void wdata(uchar dat)//写数据函数与写命令函数只在RS=1或RS=0上不同,其余都相同
{
busy();//检测LCD是否忙
RS=1;
RW=0;
P0=dat;
EN=1;
_nop_();
_nop_();
EN=0;
}
void init() //初始化LCD
{
delay(100);
CS1=1;//刚开始关闭两屏
CS2=1;
delay(100);
wcmd(screen_off);//关屏幕显示,0X3E表示关显示,0X3F表示开显示
wcmd(page);//设置页地址,首页地址为0XB8
wcmd(line);//设置行地址,共有64行,首行地址为0XC0
wcmd(col);//设置列地址,半屏共有64列,首列地址为0X40
wcmd(screen_on);//设置屏幕开显示
}
void clr() //清除LCD内存程序
{
uchar j,i;
CS1=0; //左、右屏均开显示
CS2=0;
for(i=0;i<8;i++)//控制页数0-7,共8页
{
wcmd(page+i);//每页每页进行写
wcmd(col);//控制列数0-63,共64列,列地址会自动加1
for(j=0;j<64;j++)//整屏最多可写32个中文文字或64个ASCII字符
wdata(0x00);
}
}
/*函数功能:指定位置显示汉字16*16程序?p代表页,colum表示列,*hzk表示汉字点阵数据,是一维数组*/
void show(uchar p,uchar column, uchar code *hzk)
{
uchar i,j;
for(i=0;i<2;i++)//写一个汉字需要2页
{
wcmd(page+p+i); //首页地址为0xb8
wcmd(col+column);//首列地址为0X40,列地址会自动加1,两页对应相同的列
for(j=0;j<16;j++)
wdata(hzk[16*i+j]);//j=0表示第0行的数据,j=1表示第1行的数据
}
}
void main()
{
init();//初始化LCD
clr(); //清除LCD内存程序
CS1=0; //左屏开显示
CS2=1; //右屏关显示
show(0,1*16,sheng);//显示“东”,从第0页,第16列(即左屏第2个汉字位置)开始显示
show(0,2*16,ri); //显示“华"
show(0,3*16,kuai); //显示”理“
CS1=1; //左屏关显示
CS2=0; //右屏开显示
show(0,0*16,le);//显示”工“,从第0页,第0列(即右屏第1个汉字位置)开始显示
while(1);
}
}
目 录
摘 要 I
引言 1
第1章 音乐盒设计框图及流程 2
第2章 系统硬件设计 3
2.1单片机内部结构 3
2.2时钟振荡电路 4
2.3复位电路 5
2.4蜂鸣器电路 6
2.5LCD显示电路 7
第3章 电路设计总图 8
第4章 系统软件设计 9
4.1音乐设计程序 9
4.2 LCD显示程序设计 11
第5章 结语 12
参考文献 12
附录 12
致谢 19