STC学习:可同步显示歌词的ABC英文歌

本文详细介绍了如何通过按键控制音乐播放,包括《IcansayABC》的简谱转音乐代码,以及数码管同步显示歌词的过程。涉及无源蜂鸣器原理、编程代码和电路连接。

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

程序设计目标及程序运行效果说明
程序设计目标:通过按下按键key1来控制音乐播放以及数码管的歌词显示。
程序运行效果说明:按下按键key1,此时播放音乐《I can say ABC》并显示歌词;再次按下key1键可以暂停播放音乐。

程序相关电路及原理说明
1.原理说明
本实验板采用的是无源蜂鸣器,相比与有源蜂鸣器,无源蜂鸣器的优点在于价格便宜,可以通过控制其振动频率来改变发出的声音,因此,无源蜂鸣器可以用于音乐的播放。而有源蜂鸣器的优点在于使用简单,不需要编写“乐谱”。本实验板使用的无源蜂鸣器是电磁式蜂鸣器,电磁式蜂鸣器由振荡器、电磁线圈、磁铁、振动膜片及外壳等组成。接通电源后,接收到的音频信号电流通过电磁线圈,使电磁线圈产生磁场。振动膜片在电磁线圈和磁铁的相互作用下,周期性地振动发声。

本程序中,数组music[]即是要播放的音乐,格式为音符,节拍,音符,节拍,如此循环下去。音符为要发出的音调,而节拍则是声音的持续时间。如图,在数组music[]中,音符表示的格式为:十位代表是低八度,中八度还是高八度,1代表高八度,2代表中八度,3代表高八度,个位代表简谱的音符,例如,0x15代表低八度的S0,如图即是低5S0,0x21代表中八度的DO,如图即是中1 D0。音符中,0x00代表结束符,表示整首歌曲演唱完毕,而0xff代表休止符,表示要休止100ms。遇到这两种情况,都应该重新执行循环中的第一步。其余情况则是正常播放。程序烧入单片机后,需要按下按键key1才会进行演奏。相比基础篇则添加了歌词数码管同步显示功能。

注:因使用数码管显示歌词着实有些勉强,有些字母很难在数码管上面显示,如“M”等,这个实验主要的学习重点在于歌词的同步显示和歌曲的数字编排(即由简谱变成音乐代码)。 ABC英文歌字母数码管显示对应符号表(A-Z)已另外做成文档,请见文档“ABC英文歌字母数码管显示对应符号表(A-Z)”。

2.程序相关电路
(1)无源蜂鸣器电路原理图
在这里插入图片描述
(2)按键控制电路
在这里插入图片描述
芯片相关引脚:Beep——P3^4;

代码如下:

#include "STC15F2K60S2.H"
#define uint unsigned int				  //宏定义
#define uchar unsigned char
sbit led_sel=P2^3;						 //数码管与发光二极管选通引脚
sbit sel0=P2^0;							 //SEL0、SEL1、SEL2组合为位选信息,0-7
sbit sel1=P2^1;
sbit sel2=P2^2;
sbit beep=P3^4;			 				 //蜂鸣器引脚
sbit key1=P3^2;						     //定义按键1
sbit key2=P3^3;							 //定义按键2
uchar timeh,timel;		  //定义定时器的重装值
uchar flag;				  //所选择点亮的数码管0-7标志位	
bit zanting;		  	  //播放或暂停标志位
uchar jindu=0;    	 	  //music数组中指向的位置
uchar geci[]={0x77,0x7f,0x39,0x00,0x6f,0x7b};					 //显示开机画面的数码管段码 ABC ge
uchar geci1[]={0x77,0x7f,0x39,0x3f,0x79,0x71,0x3d,0x40};    	 //显示A-G的数码管段码 ABCDEFG-
uchar geci2[]={0x76,0x06,0x1e,0xf6,0x38,0x4f,0x37,0x40};    	//显示H-N的数码管段码  HIJKLMN-
uchar geci3[]={0x3f,0x73,0xbf,0x40,0xf7,0x6d,0x07,0x40};		//显示O-T的数码管段码  OPQ-RST-
uchar geci4[]={0x62,0x3e,0xf9,0x40,0xf6,0xe6,0xdb,0x40};		//显示U-Z的数码管段码  UVW-XYZ-
uchar geci5[]={0xf6,0xe6,0xdb,0x80,0x37,0x3f,0xf9,0x00}; 		 //XYZ,Now 	
uchar geci51[]={0xe6,0x3f,0x62,0x00,0x6d,0x79,0x79,0x00}; 	  	//you see
uchar geci6[]={0x0f,0x39,0x77,0x37,0x00,0x6d,0x77,0xe6}; 		 //I can say  
uchar geci61[]={0x77,0x7f,0x39,0x00,0x00,0x00,0x00,0x00};		 // ABC
uchar weixuan[]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07};		//位选码
uchar code music[] ={    //音乐代码,歌曲为《I can say ABC》,格式为: 音符, 节拍, 音符, 节拍,    
0x11,0x10,0x21,0x10,	 //音符的十位代表是低八度,中八度还是高八度,1代表低八度,2代表中八度,3代表高八度
0x25,0x10,0x25,0x10,	 //个位代表简谱的音符,例如0x15代表低八度的S0,0x21代表中八度的DO。
0x26,0x10,0x26,0x10,	 //节拍则是代表音长,例如:0x10代表一拍,0x20代表两拍,0x05代表1/2拍
0x25,0x10,0x25,0x10,  //16
0x24,0x10,0x14,0x10,
0x23,0x10,0x13,0x10,
0x22,0x10,0x22,0x10,
0x21,0x10,0x21,0x10,  //32
0x25,0x10,0x25,0x10,
0x24,0x10,0x24,0x20,
0x23,0x10,0x23,0x10,
0x22,0x10,0x22,0x10,  //48
0x25,0x10,0x25,0x10,
0x24,0x10,0x24,0x20,
0x23,0x10,0x23,0x10,  
0x22,0x10,0x22,0x10,  //64
0x21,0x10,0x21,0x10,
0x25,0x10,0x25,0x10,  //72
0x26,0x10,0x26,0x10,
0x25,0x10,0x25,0x10,  //80
0x24,0x10,0x24,0x10,
0x23,0x10,0x23,0x10,  //88
0x22,0x10,0x22,0x10,
0x21,0x10,0x21,0x20,  //96
0x00,0x00
};
uchar code quzi[] ={	  //此数组数据为各个音符在定时器中的重装值,第一列是高位,第二列是低位
0xf8,0x8c,	  //低八度,低1
0xf9,0x5b,	  
0xfa,0x15,	  //低3
0xfa,0x67,
0xfb,0x04,	  //低5
0xfb,0x90,
0xfc,0x0c,	  //低7	
0xfc,0x44,	  //中央C调
0xfc,0xac,	  //中2
0xfd,0x09,
0xfd,0x34,	  //中4
0xfd,0x82,					   
0xfd,0xc8,	  //中6
0xfe,0x06,
0xfe,0x22,	  //高八度,高1	
0xfe,0x56,
0xfe,0x6e,	  //高3
0xfe,0x9a,
0xfe,0xc1,	  //高5
0xfe,0xe4,
0xff,0x03	  //高7
};
void delay(unsigned int xms)
{
	uint i,j;						   
	 for(i=xms;i>0;i--)
	 	for(j=124;j>0;j--);
}
//功能描述:在quzi数组中,找到music数组定义的简谱音符的重装值,并返回其在quzi数组中的位置
//入口参数:tem:music数组中定义的简谱音符
//出口参数:返回的是tem音符在quzi数组中的位置值
uchar quyin(uchar tem)
{
	uchar qudiao,jp,weizhi;		  //定义曲调,音符和位置
	qudiao=tem/16;				  //高4位是曲调值
	jp=tem%16;					  //低4位是音符
	if(qudiao==1)				  //当曲调值为1时,即是低八度,低八度在quzi数组中基址为0
		qudiao=0;
	else if(qudiao==2)			  //当曲调值为2时,即是中八度,中八度在quzi数组中基址为14
		qudiao=14;
	else if(qudiao==3)			  //当曲调值为3时,即是高八度,高八度在quzi数组中,基址为28
		qudiao=28;
	weizhi=qudiao+(jp-1)*2;		  //通过基址加上音符作为偏移量,即可定位此音符在quzi数组中的位置
	return weizhi;				  //返回这一个位置值
}
void playmusic()//播放音乐
{
	uchar p,m,tem;   //m为节拍   
	while(1)   
	{   
		if(zanting==1)			//播放或暂停控制
		{
			p=music[jindu];
			if(p==0x00)			//如果碰到结束符,延时1秒,回到开始再来一遍 
			{
				jindu=0;
				delay(1000);
				break;
			}        
			else if(p==0xff)   //若碰到休止符,延时100ms,继续取下一音符
			{
				jindu++;			
				delay(100);
				TR0=0;			//关定时器0
				break;
			}      
			else			   //正常情况下取音符和节拍 
			{
				tem=quyin(music[jindu]);		//取出当前音符在quzi数组中的位置值
				timeh=quzi[tem];			//把音符相应的计时器重装载值赋予timeh和timel
				timel=quzi[tem+1];
				jindu++;
				TH0=timeh;					//把timeh和timel赋予计时器
				TL0=timel;
				m=music[jindu];					 //取得节拍
				jindu++;
			}  		   
	        TR0=1;                    //开定时器0    
			delay(m*180);             //等待节拍完成, 通过P3^4口输出音频    
			TR0=0;                    //关定时器0   
			beep=0;	 					 //将beep置0,以保护蜂鸣器		
		  }
	}
}
//系统初始化,功能是配置IO口
void init_sys()
{
	P0M0=0xff;			 		//设置推挽模式
	P0M1=0x00;
	P2M0=0x08;
	P2M1=0x00;
	P3M0=0x10;
	P3M1=0x00;
}
//定时器初始化
void init()						  
{
	TMOD=0x01;					 //设置定时器0,工作方式1,16位手动重装初值;设置定时器1,工作方式0,16位自动重装初值
	TH0=0xD8;					 //设置定时器0初值
	TL0=0xEF;
	TH1=(65536-1000)/256;		 //设置定时器1初值
	TL1=(65536-1000)%256;
	EA=1;						 //打开总中断
	ET0=1;						 //打开定时器0中断
	ET1=1;						 //打开定时器1中断
	TR0=0;						 //定时器0暂时不启动
	TR1=1;						 //启动定时器1	
	EX0=1;						 //启动外部中断0						 
	zanting=0;					 //播放或暂停标志位赋初值
	beep=0;	 					 //将beep置0,以保护蜂鸣器
}
void main()
{
	init_sys();							  //系统初始化
	init();
	P0=0x00;							  
	key1=1;
	flag=0;
	while(1)
		playmusic();			 //进入播放音乐函数
}
//定时器0中断处理,重新装值,并把beep值取反,产生方波
void tim1()interrupt 1						//计时器控制频率
{
	TH0=timeh;								//中断后重赋初值
	TL0=timel;
	beep=~beep;							//中断beep翻转产生方波,使得蜂鸣器发声
}
//按下按键1的外部中断,对标志位取反,功能是暂停和播放音乐
void exint0()interrupt 0
{
	if(key1==0)
	{		
		delay(10);
		if(key1==0)
			zanting=~zanting;
	}
}				
//定时器1中断处理,用作数码管显示歌词。
void timer1()interrupt 3				   //把显示程序提到定时器1中断服务程序
{
	flag++;
	if(flag==8)
		flag=0;
	P0=0;
	P2=weixuan[flag];
	if(jindu==0)						 //通过判断播放时的jindu进而控制此时的歌词显示
	{									 //此时为零时,显示“ABC ge”
		switch(flag)
		{
			case 0:P0=geci[0];break;
			case 1:P0=geci[1];break;
			case 2:P0=geci[2];break;
			case 4:P0=geci[4];break;
			case 5:P0=geci[5];break;
			default:P0=geci[3];break;
		}
	}
	else if(jindu<16)
	{					 //此时进度为0-15之间,显示“ABCDEFG-”
	   switch(flag)
	   {
		   case 0:P0=geci1[0];break;
	       case 1:P0=geci1[1];break;
		   case 2:P0=geci1[2];break;
		   case 3:P0=geci1[3];break;
		   case 4:P0=geci1[4];break;
		   case 5:P0=geci1[5];break;
		   case 6:P0=geci1[6];break;
		   default:P0=geci1[7];break;
	   }
	}
	else if(jindu<32)
	{					  //此时进度为16-31之间,显示“HIJKLMN-”
	   switch(flag)
	   {
		   case 0:P0=geci2[0];break; 
	       case 1:P0=geci2[1];break; 
		   case 2:P0=geci2[2];break; 
		   case 3:P0=geci2[3];break; 
		   case 4:P0=geci2[4];break; 
		   case 5:P0=geci2[5];break; 
		   case 6:P0=geci2[6];break; 
		   default:P0=geci2[7];break; 
	   }
	}
	else if(jindu<48)
	{					  //此时进度为32-47之间,显示“OPQ- RST-”
	   switch(flag)
	   {
		   case 0:P0=geci3[0];break; 
	       case 1:P0=geci3[1];break; 
		   case 2:P0=geci3[2];break;
		   case 3:P0=geci3[3];break;
		   case 4:P0=geci3[4];break;
		   case 5:P0=geci3[5];break;
		   case 6:P0=geci3[6];break;
		   default:P0=geci3[7];break;
	   }
	}
	else if(jindu<64)
	{					  //此时进度为48-63之间,显示“UVW- XYZ-”
	   switch(flag)
	   {
		   case 0:P0=geci4[0];break; 
	       case 1:P0=geci4[1];break; 
		   case 2:P0=geci4[2];break; 
		   case 3:P0=geci4[3];break; 
		   case 4:P0=geci4[4];break; 
		   case 5:P0=geci4[5];break; 
		   case 6:P0=geci4[6];break; 
		   default:P0=geci4[7];break; 
	   }
	}
	else if(jindu<72)
	{					 //此时进度为64-71之间,显示“XYZ- NOW”
	   switch(flag)
	   {
		   case 0:P0=geci5[0];break;
	       case 1:P0=geci5[1];break;
		   case 2:P0=geci5[2];break;
		   case 3:P0=geci5[3];break;
		   case 4:P0=geci5[4];break;
		   case 5:P0=geci5[5];break;
		   case 6:P0=geci5[6];break;
		   default:P0=geci5[7];break;
	   }
	}
	else if(jindu<80)
	{					//此时进度为72-79之间,显示“YOU SEE ”
	   switch(flag)
	   {
		   case 0:P0=geci51[0];break; 
	       case 1:P0=geci51[1];break; 
		   case 2:P0=geci51[2];break; 
		   case 3:P0=geci51[3];break; 
		   case 4:P0=geci51[4];break; 
		   case 5:P0=geci51[5];break; 
		   case 6:P0=geci51[6];break; 
		   default:P0=geci51[7];break; 
	   }
	}
	else if(jindu<88)
	{					//此时进度为80-87之间,显示“ICAN SAY”
	   switch(flag)
	   {
		   case 0:P0=geci6[0];break;
	       case 1:P0=geci6[1];break;
		   case 2:P0=geci6[2];break;
		   case 3:P0=geci6[3];break;
		   case 4:P0=geci6[4];break;
		   case 5:P0=geci6[5];break;
		   case 6:P0=geci6[6];break;
		   default:P0=geci6[7];break;
	   }
	}
	else if(jindu<96)
	{					//此时进度为88-95之间,显示“ABC”
	   switch(flag)
	   {
		   case 0:P0=geci61[0];break; 
	       case 1:P0=geci61[1];break; 
		   case 2:P0=geci61[2];break; 
		   case 3:P0=geci61[3];break; 
		   case 4:P0=geci61[4];break; 
		   case 5:P0=geci61[5];break; 
		   case 6:P0=geci61[6];break; 
		   default:P0=geci61[7];break; 
	   }
	}
}

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

布布要成为最强的人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值