51单片机:模拟钢琴之蜂鸣器(代码详解)

目录

前言

正文

乐理

程序

补充


前言

        最近心血来潮,想要用蜂鸣器播放音乐(全损音质),于是最初的想法诞生了,但是我总不能每次想听歌都敲一遍蜂鸣器的代码吧,有没有什么办法只需要敲一遍的代码便可以实现听歌自由呢(相对自由)?也就是每次写歌只需要将歌曲的谱子录入即可,于是我决定把蜂鸣器模拟成任何一种乐器,将乐器的每一个音都写到程序中,如此,只需要将所获得的谱子添加到程序中便可,极大地减少了工作量。

        既然如此,我为何选择模拟钢琴呢?由于鄙人缺乏乐理知识,只好向朋友或者网友请教,刚好有认识会钢琴的网友,于是确定了下来模拟钢琴。在此,感谢网友的帮助,让我的学习进展得十分顺利!

        本文以《起风了》这首歌来为大家讲解相关的知识和程序,先听听歌(全损音质)。

起风了(全损音质)

正文

乐理

温馨提示:此处为未懂得乐理的网友提供一点知识,懂得乐理的大神们可以跳过。本人也才疏学浅,有讲解不对的地方,请不吝赐教!

        首先我们来看一小段钢琴的简谱,如图

         谱子中有数字,有下划线,有双下划线,有弧,还有点,这些都代表什么呢?首先,数字表示这个音的音调,这是我们再熟悉不过的了,比如1对应的音就是do,2对应的音就是re,以此类推,接下来的3、4、5、6、7对应的音就是mi、fa、so、la、xi……

        对了,还有一个就是有的时候会碰到谱子上的有写着“#1”或者其他前面带有“#”的数字,这个表示的是这个音比1高但比2低,即介于1和2之间。而数字0表示的则是一个空拍,就是没有声音的意思就是了。

        接下来先讲下面的一点,比如7下面带着这样的一点“·”,表示的是这个带点的“7”这个音比不带点的“7”的音调低一轮。那如果是两个点呢?那就是音调低两轮。相反地,如果这个点是放在7的上头的,那么一个点就是音调高一轮,两个点就是音调高两轮。

        而下划线呢?一个下划线和两个下划线有什么不一样?还有右边的那一横杠是什么?我们应该知道,音乐在演奏的过程中,每个音都有对应的时间,也就是这个音要“响”多久,所以呢,下划线和右边一横杠便表示相应的音延时多久。它们的规律是这样的——单下划线表示这个音延时一个八分音符,双下划线表示这个音延时一个十六分音符(即八分音符时间的一半),以此类推,后面三十二分音符就是三个下划线。而横杠,则有一个横杠表示一个二分音符,两个横杠表示一个全音符。说了这么多,怎么不见四分音符?其实四分音符的表示方法就是一个音不带下划线也不带横杠。

        至于两个3之间的弧……那是钢琴的一种演奏方法,我们这里不作深究。毕竟我们的目标是蜂鸣器。

        还有一种符号,如图

         仔细看我们发现,这两个“2”后面跟了一个小点点,这个小点点又是什么意思呢?其实它也是延时的意思,表示前面的这个音再延长原来时间的一半。这么说多少有点抽象,就举个例子吧,就上图的这个例子,音调“2”下带了一个下划线,表示这个音延时了一个八分音符,然后我在这延时之后再延时一个十六分音符(八分音符的一半),也就是总共延时了(八分音符+十六分音符)。

        还有,细心的读者可能会发现,这幅图后面的两个音——“6”,下面带了两个点,这就是我上面所说的它的音调低了两轮。

        写蜂鸣器所需要的乐理大致如此,接下来开始讲解程序。

程序

先看看我书写的程序框架:

         图中主要的程序文件为main.c、music.c和wind.c文件music.c文件里存放着我上部分讲解的乐理部分,而wind.c文件则存放着《起风了》这首歌的谱子。

        废话少说,先看主程序:

#include<stc89c5xrc.h>
#include<music.h>
#include<moon.h>

sbit led = P2^7;

void main()
{
	const float time = 0.75;
	unsigned int num;
	musicInit(time);
	while(1)
	{
		num++;
		if(num >= 20000)
		{
			num = 0;
			led = !led;
		}
	}
}

        main函数中首先定义了两个变量,之后初始化musicInit函数,变量num想必读者们都懂,它是用于延时使灯led闪烁的,这里不作介绍。于是我们进入music.c此文件中看看musicInit等函数的定义。如下:

#include<music.h>

//音调(晶振频率:12M)(定时器的装载值)
code unsigned int tone[8][12] =
{
	{35233,36965,38509,39895,41146,42809,43797,45128,46305,47354,48295,49407},
	{50151,51043,51837,52715,53341,54042,54666,55332,55921,56445,56989,57406},
	{57902,58342,58733,59126,59475,59822,60131,60434,60728,60991,61244,61487},

	{61719,61926,62135,62321,62506,62671,62833,62985,63126,63263,63390,63512},
	{63624,63731,63832,63928,64019,64103,64185,64260,64333,64400,64463,64524},
	{64580,64634,64685,64733,64777,64820,64860,64898,64934,64968,65000,65030},

	{65058,65085,65110,65134,65157,65178,65198,65217,65235,65252,65268,65283},
	{65297,65311,65323,65335,65346,65357,65367,65377,65386,65394,65402,65409},
};



unsigned int fre = 0;	//定时器的装载值

unsigned int delay[7];	//延时:全音符,二分音符,四分音符,八分音符,十六分音符,三十二分音符


/*
	初始化
	参数t:设置一拍(四分音符)的时间(单位:s)
*/
void musicInit(float t)
{
	unsigned char x,y;

	TMOD = 0x11;	//定时器设置16位模式
	TH1 = 0xfc;
	TL1 = 0x18;		//定时器0每1ms中断一次

	TH0 = 0xff;
	TL0 = 0x00;
	
	ET1 = 1;
	ET0 = 1;
	EA  = 1;
	PT0 = 1;		//定时器0的优先级高
	PT1 = 0;	//定时器1的优先级低,可被定时器0中断打断
	
	t *= 4;		//全音符时长(单位:s)
	for(x = 0;x < 7;x++)
	{
		delay[x] = t *1000;		//t *1000;全音符时长(单位:ms)
		for(y = 0;y < x;y++)
		{
			delay[x] /= 2;	//全音符,二分音符,四分音符,八分音符,十六分音符,三十二分音符
		}
	}
	delay[6] /= 8;		//相同音延时,只需一瞬间(经我测试这个延时是最好的)
	
	TR1 = 1;
	TR0 = 1;
}


/*
	定时器0中断函数,主要负责蜂鸣器的振动频率
	中断优先级高于定时器1,防止定时器1的中断影响其振动频率而导致音调不对
*/
void rate() interrupt 1	
{
	if(fre != 0)
	{
		TH0 = (fre >> 8);
		TL0 = (fre & 0x00ff);
		beep = !beep;
	}
	else	//空拍
	{
		TH0 = 0xff;
		TL0 = 0xe0;
		beep = 0;
	}
}

/*
	定时器1中断,主要负责每个音的延时时间(音符),优先级低于定时器0中断
	每1ms进入一次中断
*/
void time() interrupt 3
{
	static unsigned int j,n;
	TH1 = 0xfc;
	TL1 = 0x18;			//定时器0每1ms中断一次
	if((++j) >= delay[MUSIC[n][2]])
	{
		n++;		//切换到下一个音
		j = 0;
	}
	
	if(n >= LENGTH)    //LENGTH为音乐的长度,该部分为防止n超出数组的范围
	{
		n = 0;
	}
	
	if(MUSIC[n][0] != NONE)		//判断是不是空拍
	{
		fre = tone[MUSIC[n][0]][MUSIC[n][1]];
	}
	else	//空拍
	{
		fre = 0;
	}
}

        首先看musicInit函数,里面我初始化了两个定时器,为什么要用到两个定时器呢?我们不妨思考一下,钢琴的音调是如何控制的,初中的物理知识告诉我们是通过频率控制它音调高低的,因此这里面一个重要因素就是频率,不妨看到定时器0中断函数,可以知道我利用了定时器在不同的装载值下,进入中断的时间(即频率)不同,那么蜂鸣器的振动频率也随之不同。所以,我们只需要控制定时器0的重装载值即可控制蜂鸣器的音调高低。

        至于定时器1,还记得我在乐理部分讲过每一个音都有一定的延时吗?定时器1就是为其延时而来的。而参数t和数组delay的处理我将在后面延时部分细讲,请读者留意。

        不妨先看到数组tone,顾名思义,tone所装载的内容十有八九跟音调高低有关,没错,它装的就是定时器0的重装载值(晶振为12MHz),每一行从左往右分别对应音调:1、#1、2、#2、3、4、#4、5、#5、6、#6、7。从上往下,上一行的音调要比下一行的音调低(这个其实就是我在乐理部分为大家讲解的数字上头或下方带小点点的部分,比如我选择“1”这个音在第一行,则“1”上面加一点这个音就在第二行,加两个点就在第三行)。

        因此,我们只需要将谱子上的每一音分解成两个数字,将这两个数字对应到数组中,即可使蜂鸣器发出的声音为我们所想要的音调。这两组数字我将在头文件部分定义,读者稍安勿躁。

        那么读者肯定好奇,我是如何知道它每个调的频率的,这还不简单,当然是知之为知之,不知“百度知”啦。如图:

         以上便是每个音调所对应的频率,请读者自行计算!

        定时器中断0我在前面有简单讲述,其主要负责蜂鸣器发出声音的音调,过于简单,我在此不作过多赘述。

        接下来看定时器中断函数1,我也有说过它主要用于声音的延时。由重装载值可知,每过1ms进入一次中断函数。讲到这里,先插入讲一下数组delay。delay数组里所存放的内容为延时时间,即全音符、二分音符、四分音符……说的比较直白点就是存放了需要进入多少次定时器1中断来延时(也就是需要延时多少个1ms)。不妨看定时器1中断函数的第一个条件判断语句if,每当一个音延长到相应的时间之后变通过变量n切换到下一个音。

        再看看我们刚刚在musicInit函数里落下的变量t和delay处理部分。此时读者对这段代码的理解是否更加深刻了呢?变量t为传入参数,表示一拍(即一个四分音符)所占的时长(单位:s),我们可以根据歌曲的节奏快慢改变t的数值,而数组delay由delay[0]到delay[6]则是对应全音符、二分音符 ……二百五十六分音符(为什么要一个二百五十六分音符我会在后续遇到问题上作讲解)的时长(单位:ms)。

        接着继续看定时器1中断函数的第三个条件判断语句if,其中fre为定时器0的重装载值,即它的大小决定着蜂鸣器的音调高低。而它的值则来自数组tone,由此我们知道,数组MUSIC的第0列和第1列肯定存放着确定音高低的数字,通过这两个数字对应到数组tone中便是了。

        下面我将带领读者瞧一瞧music.h头文件,如下

#ifndef _MUSIC_H
#define _MUSIC_H

#include<stc89c5xrc.h>
#include<wind.h>

sbit beep = P1^5;

//高低音,但谱子需要升降调时,可调节其数字来升降调,但此处只能使用C调
#define L 4
#define M 5
#define H 6

//音调,do,re,mi,fa,so,la,xi
#define T1_0 0		//C
#define T1_5 1		//C#
#define T2_0 2		//D
#define T2_5 3		//Eb
#define T3_0 4		//E
#define T4_0 5		//F
#define T4_5 6		//F#
#define T5_0 7		//G
#define T5_5 8		//G#
#define T6_0 9		//A
#define T6_5 10		//Bb
#define T7_0 11		//B
#define NONE 12		//空拍

//音符(延时)
#define D_1 	0	//全音符
#define D_2 	1	//二分音符
#define D_4 	2	//四分音符
#define D_8 	3	//八分音符
#define D_16 	4	//十六分音符
#define D_32 	5	//三十二分音符
#define D_256	6	//二百五十六分音符(用于两个相同的音按两次时中间的间断)


extern unsigned int delay[7];

void musicInit(float t);

#define MUSIC wind			//起风了
#define LENGTH windLength    //歌曲长度

#endif

        程序不长,我挑主要部分讲(其实我也在代码里面都注释好了doge)。可以看到我所定义的"L"、"M"、"H"即对应.c文件中数组tone的行数,而”T1_0“、”T1_5“、”T2_0“等等则是对应着数组tone的列数,通过这两组数组来确定音调在tone中的位置(当我们对歌曲的整体音调有要求时,只需改变定义的”L“、”M“、”H“的数值即可改变成我们所想要的音调,当然,数值的大小不可超过数组tone的范围)。而下面的”D_1“、”D_2“、”D_3“等则是对应到数组delay中去。有了这些定义之后,我们只需要在一个数组中,按着谱子的顺序放入它们,便可以将歌曲放出来了。如程序中定义的MUSIC,它其实就是一个数组,里面按照一定的规律放着谱的信息,而LENGTH则是数组MUSIC的长度。

        以上便是主要的程序部分,那么接下来我们来看看wind.c文件,也就是存放歌曲的谱的信息的文件

#include<wind.h>

/*
	曲谱,3代表构成它的三个要素
	第一个为高低音,第二个为高低音中对应的音调,第三个为音调对应的音符(时长)
*/
code unsigned char wind[][3] = 
{
	{NONE,NONE,D_4},
//前奏
	{M,T7_0,D_16},
	{H,T1_0,D_16},
	{H,T2_0,D_16},
	{H,T3_0,D_16},
	{M,T3_0,D_8},
	{H,T5_0,D_16},
	{H,T3_0,D_16},
    {NONE,NONE,D_256},
	{H,T3_0,D_2},
	
	{M,T7_0,D_16},
	{H,T1_0,D_16},
	{H,T2_0,D_16},
	{H,T3_0,D_16},
	{M,T2_0,D_8},
	{H,T5_0,D_16},
	{H,T3_0,D_16},
	{H,T2_0,D_16},
	{H,T3_0,D_16},
	{H,T1_0,D_16},
	{H,T2_0,D_16},
	{M,T7_0,D_16},
	{H,T1_0,D_16},
	{M,T5_0,D_8},
	
	{M,T7_0,D_16},
	{H,T1_0,D_16},
	{H,T2_0,D_16},
	{H,T3_0,D_16},
	{M,T3_0,D_8},
	{H,T5_0,D_16},
	{H,T3_0,D_16},
    {NONE,NONE,D_512},
	{H,T3_0,D_2},
	
	{M,T7_0,D_16},
	{H,T1_0,D_16},
	{H,T2_0,D_16},
	{H,T3_0,D_16},
	{M,T2_0,D_8},
	{H,T5_0,D_16},
	{H,T3_0,D_16},
	{H,T2_0,D_16},
	{H,T3_0,D_16},
	{H,T1_0,D_16},
	{H,T2_0,D_16},
	{M,T7_0,D_16},
	{H,T1_0,D_16},
	{M,T5_0,D_4},
	{NONE,NONE,D_16},
	
	
//这一路上走走停停,顺着少年漂流的痕迹
	{M,T2_0,D_8},
	{M,T2_0,D_16},
	{M,T1_0,D_16},
	{M,T2_0,D_8},
	{M,T2_0,D_16},
	{M,T1_0,D_16},
	{M,T2_0,D_8},
	{M,T3_0,D_8},
	{M,T5_0,D_8},
	{M,T3_0,D_8},
	{M,T2_0,D_8},
	{M,T2_0,D_16},
	{M,T1_0,D_16},
	{M,T2_0,D_8},
	{M,T2_0,D_16},
	{M,T1_0,D_16},
	{M,T2_0,D_16},
	{M,T3_0,D_16},
	{M,T2_0,D_16},
	{M,T1_0,D_16},
	{L,T5_0,D_4},
	
//迈出车站的前一刻竟有些犹豫
	{M,T2_0,D_8},
	{M,T2_0,D_16},
	{M,T1_0,D_16},
	{M,T2_0,D_8},
	{M,T2_0,D_16},
	{M,T1_0,D_16},
	{M,T2_0,D_8},
	{M,T3_0,D_8},
	{M,T5_0,D_8},
	{M,T3_0,D_8},
	{M,T2_0,D_8},
	{M,T2_0,D_16},
	{M,T3_0,D_16},
	{M,T2_0,D_8},
	{M,T1_0,D_16},
	{M,T2_0,D_16},
	{NONE,NONE,D_256},
	{M,T2_0,D_4},
//	{NONE,NONE,D_4},	//此处照着谱子应该是四分音符,但个人认为在这里四分音符过长,所以改成八分音符
	{NONE,NONE,D_8},

//不禁笑这近乡情怯仍无可避免
	{M,T2_0,D_8},
	{M,T2_0,D_16},
	{M,T1_0,D_16},
	{M,T2_0,D_8},
	{M,T2_0,D_16},
	{M,T1_0,D_16},
	{M,T2_0,D_8},
	{M,T3_0,D_8},
	{M,T5_0,D_8},
	{M,T3_0,D_8},
	{M,T2_0,D_8},
	{M,T2_0,D_16},
	{M,T3_0,D_16},
	{M,T2_0,D_8},
	{M,T1_0,D_16},
	{L,T6_0,D_16},
	{NONE,NONE,D_256},
	{L,T6_0,D_4},

//而长野的天依旧那么暖,吹起了从前
	{M,T3_0,D_16},
	{M,T2_0,D_16},
	{M,T1_0,D_16},
	{M,T2_0,D_16},
	{M,T1_0,D_4},
	{M,T3_0,D_16},
	{M,T2_0,D_16},
	{M,T1_0,D_16},
	{M,T2_0,D_16},
	{M,T1_0,D_4},
	{M,T3_0,D_16},
	{M,T2_0,D_16},
	{M,T1_0,D_16},
	{M,T2_0,D_16},
	{M,T1_0,D_4},
//	{NONE,NONE,D_4},	//同上
	{NONE,NONE,D_8},

//从前初识这世间,万般流连,看着天边似在眼前
	{M,T1_0,D_8},
	{M,T2_0,D_8},
	{M,T3_0,D_8},
	{M,T1_0,D_8},
	{M,T6_0,D_8},
	{M,T5_0,D_16},
	{M,T6_0,D_16},
	{NONE,NONE,D_256},
	{M,T6_0,D_8},
	{M,T6_0,D_16},
	{M,T1_0,D_16},
	{M,T7_0,D_8},
	{M,T6_0,D_16},
	{M,T7_0,D_16},
	{NONE,NONE,D_256},
	{M,T7_0,D_4},
	{NONE,NONE,D_256},
	{M,T7_0,D_8},
	{M,T6_0,D_16},
	{M,T7_0,D_16},
	{NONE,NONE,D_256},
	{M,T7_0,D_16},
	{M,T3_0,D_16},
	{NONE,NONE,D_256},
	{M,T3_0,D_8},
	{H,T1_0,D_16},
	{H,T2_0,D_16},
	{H,T1_0,D_16},
	{M,T7_0,D_16},
	{M,T6_0,D_8},

//也甘愿赴汤蹈火去走它一遍
	{M,T5_0,D_8},
	{M,T6_0,D_8},
	{M,T5_0,D_16},
	{M,T6_0,D_16},
	{NONE,NONE,D_256},
	{M,T6_0,D_16},
	{M,T5_0,D_16},
	{M,T6_0,D_16},
	{M,T5_0,D_16},
	{M,T6_0,D_8},
	{M,T5_0,D_16},
	{M,T2_0,D_16},
	{NONE,NONE,D_256},
	{M,T2_0,D_16},
	{M,T5_0,D_8},
	{M,T5_0,D_16},
	{M,T3_0,D_4},
//	{NONE,NONE,D_4},	//同上
	{NONE,NONE,D_16},
	
//如今走过这世间,万般流连,翻过岁月不同侧脸
	{M,T1_0,D_8},
	{M,T2_0,D_8},
	{M,T3_0,D_8},
	{NONE,NONE,D_256},
	{M,T1_0,D_8},
	{M,T6_0,D_8},
	{M,T5_0,D_16},
	{M,T6_0,D_16},
	{NONE,NONE,D_256},
	{M,T6_0,D_8},
	{M,T6_0,D_16},
	{M,T1_0,D_16},
	{M,T7_0,D_8},
	{M,T6_0,D_16},
	{M,T7_0,D_16},
	{NONE,NONE,D_256},
	{M,T7_0,D_4},
	{NONE,NONE,D_256},
	{M,T7_0,D_8},
	{M,T6_0,D_16},
	{M,T7_0,D_16},
	{NONE,NONE,D_256},
	{M,T7_0,D_16},
	{M,T3_0,D_16},
	{NONE,NONE,D_256},
	{M,T3_0,D_8},
	{H,T1_0,D_16},
	{H,T2_0,D_16},
	{H,T1_0,D_16},
	{M,T7_0,D_16},
	{M,T6_0,D_8},

//措不及防闯入你的笑颜
	{M,T5_0,D_8},
	{M,T6_0,D_8},
	{H,T3_0,D_16},
	{NONE,NONE,D_256},
	{H,T3_0,D_16},
	{NONE,NONE,D_256},
	{H,T3_0,D_8},
	{M,T5_0,D_8},
	{M,T6_0,D_8},
	{H,T3_0,D_16},
	{NONE,NONE,D_256},
	{H,T3_0,D_16},
	{NONE,NONE,D_256},
	{H,T3_0,D_16},
	{M,T5_0,D_8},
	{M,T6_0,D_16},
	{NONE,NONE,D_256},
	{M,T6_0,D_2},
	{NONE,NONE,D_4},

//我曾难自拔于世界之大,也沉溺于其中梦话
	{H,T1_0,D_8},
	{H,T2_0,D_8},
	{H,T3_0,D_8},
	{H,T6_0,D_16},
	{H,T5_0,D_16},
	{NONE,NONE,D_256},
	{H,T5_0,D_8},
	{H,T6_0,D_16},
	{H,T5_0,D_16},
	{NONE,NONE,D_256},
	{H,T5_0,D_8},
	{H,T6_0,D_16},
	{H,T5_0,D_16},
	{NONE,NONE,D_256},
	{H,T5_0,D_8},
	{H,T2_0,D_16},
	{H,T3_0,D_16},
	{NONE,NONE,D_256},
	{H,T3_0,D_8},
	{H,T6_0,D_16},
	{H,T5_0,D_16},
	{NONE,NONE,D_256},
	{H,T5_0,D_8},
	{H,T6_0,D_16},
	{H,T5_0,D_16},
	{NONE,NONE,D_256},
	{H,T5_0,D_8},
	{H,T6_0,D_16},
	{H,T5_0,D_16},
	{NONE,NONE,D_256},
	{H,T5_0,D_16},
	{H,T3_0,D_8},
	{H,T3_0,D_16},
	
//不得真假不做挣扎不惧笑话
	{H,T2_0,D_8},
	{H,T1_0,D_16},
	{M,T6_0,D_16},
	{NONE,NONE,D_256},
	{M,T6_0,D_16},
	{H,T1_0,D_8},
	{NONE,NONE,D_256},
	{H,T1_0,D_16},
	{H,T2_0,D_8},
	{H,T1_0,D_16},
	{M,T6_0,D_16},
	{NONE,NONE,D_256},
	{M,T6_0,D_16},
	{H,T1_0,D_8},
	{H,T3_0,D_4},
	{NONE,NONE,D_256},
	{H,T3_0,D_16},
	{H,T4_0,D_16},
	{H,T3_0,D_16},
	{H,T2_0,D_16},
	{H,T3_0,D_16},
	{H,T2_0,D_8},
	{H,T2_0,D_16},

//我曾将青春翻涌成她,也曾指尖弹出盛夏
	{H,T1_0,D_8},
	{H,T2_0,D_8},
	{H,T3_0,D_8},
	{H,T6_0,D_16},
	{H,T5_0,D_16},
	{NONE,NONE,D_256},
	{H,T5_0,D_8},
	{H,T6_0,D_16},
	{H,T5_0,D_16},
	{NONE,NONE,D_256},
	{H,T5_0,D_8},
	{H,T6_0,D_16},
	{H,T5_0,D_16},
	{NONE,NONE,D_256},
	{H,T5_0,D_8},
	{H,T2_0,D_8},
	{H,T3_0,D_8},
	
	{H,T6_0,D_16},
	{H,T5_0,D_16},
	{NONE,NONE,D_256},
	{H,T5_0,D_8},
	{H,T6_0,D_16},
	{H,T5_0,D_16},
	{NONE,NONE,D_256},
	{H,T5_0,D_8},
	{H,T6_0,D_16},
	{H,T5_0,D_16},
	{NONE,NONE,D_256},
	{H,T5_0,D_16},

//心之所动且就随缘去吧
	{H,T3_0,D_8},
	{H,T3_0,D_16},
	{H,T2_0,D_8},
	{H,T1_0,D_16},
	{M,T6_0,D_16},
	{NONE,NONE,D_256},
	{M,T6_0,D_16},
	{H,T3_0,D_8},
	{H,T3_0,D_16},
	{H,T2_0,D_8},
	{H,T1_0,D_16},
	{M,T6_0,D_16},
	{NONE,NONE,D_256},
	{M,T6_0,D_16},
	{NONE,NONE,D_256},
	{M,T6_0,D_16},
	{H,T1_0,D_8},
	{NONE,NONE,D_256},
//	{H,T1_0,D_1},	//同上
	{H,T1_0,D_2},
	
//逆着光行走任风吹雨打
	{M,T6_0,D_16},
	{H,T3_0,D_8},
	{H,T3_0,D_16},
	{H,T2_0,D_8},
	{H,T1_0,D_16},
	{M,T6_0,D_16},
	{NONE,NONE,D_256},
	{M,T6_0,D_16},
	{H,T3_0,D_8},
	{H,T3_0,D_16},
	{H,T2_0,D_8},
	{H,T1_0,D_16},
	{M,T6_0,D_16},
	{NONE,NONE,D_256},
	{M,T6_0,D_16},
	{H,T1_0,D_8},
	{H,T1_0,D_16},
	{NONE,NONE,D_256},
//	{H,T1_0,D_1},	//同上
	{H,T1_0,D_2},
	
	{NONE,NONE,D_4},	//结束延时
};

//曲谱长度
code unsigned int windLength = sizeof(wind)/3;

        先不看其他的,总体浏览一下,知道前两列的变量决定着此时音调的高低,后一列的变量决定着这个音调需要的延时。而在着其中掺着不少的{NONE,NONE,…},对于{NONE,NONE,D_4}之类的,不难知道,这些就是对应着谱中的空拍(即0),我在这里不过多解释。对于{NONE,NONE,D_256}这个呢,为什么要插入这么短的一个空音?这是由于我在写歌的时候遇到了一个问题,就是如果前后两个音的音调是相同的,如果直接这样体现在蜂鸣器上,势必只能听到一次这个音,这于钢琴不同,钢琴如果遇到这两个音的弹奏过程是手指按下,然后起来,然后再按下,所以我在这里加多这一空音就是为了达到这种效果。

        等等,数组中为什么有的连续两个音相同但是它们中间却没有用{NONE,NONE,D_256}隔开呢?读者可否还记得谱子里有的音会在数字后面加一个小点,如图

         还记得这个是什么意思吗?不加一个短促的空音是因为这本来就是一个音,只是它被我分解成两个部分了。

补充

        以上部分是最最最主要的程序部分,接下来的是使这个模拟的钢琴更加完善。

        以上的代码部分只能演奏C调的歌曲,而我们所听的歌曲并不仅仅局限于C调,还有C#调、D调、Eb调、E调、F调、F#调、G调、G#调、A调、Bb调、B调等,那么该如何实现用不同的调来奏响曲子呢?其实方法有很多,比如在不同的调下使确定音调的变量发生适当的偏移,或者利用十二平均律来调节蜂鸣器振动的频率,或者……

        而由于鄙人比较懒惰,我使用了不需要动脑子的方法,但是相应的代码量就增加了(doge虽说是增加了,但是都是cv操作)。看看music.c文件的音调数组部分

//音调(晶振频率:12M)(定时器的装载值)
#ifdef C		//C调
code unsigned int tone[8][12] =
{
	{35233,36965,38509,39895,41146,42809,43797,45128,46305,47354,48295,49407},
	{50151,51043,51837,52715,53341,54042,54666,55332,55921,56445,56989,57406},
	{57902,58342,58733,59126,59475,59822,60131,60434,60728,60991,61244,61487},

	{61719,61926,62135,62321,62506,62671,62833,62985,63126,63263,63390,63512},
	{63624,63731,63832,63928,64019,64103,64185,64260,64333,64400,64463,64524},
	{64580,64634,64685,64733,64777,64820,64860,64898,64934,64968,65000,65030},

	{65058,65085,65110,65134,65157,65178,65198,65217,65235,65252,65268,65283},
	{65297,65311,65323,65335,65346,65357,65367,65377,65386,65394,65402,65409},
};
#endif
#ifdef C_		//C#调
code unsigned int tone[8][12] =
{
	{36965,38509,39895,41146,42809,43797,45128,46305,47354,48295,49407,50151},
	{51043,51837,52715,53341,54042,54666,55332,55921,56445,56989,57406,57902},
	{58342,58733,59126,59475,59822,60131,60434,60728,60991,61244,61487,61719},

	{61926,62135,62321,62506,62671,62833,62985,63126,63263,63390,63512,63624},
	{63731,63832,63928,64019,64103,64185,64260,64333,64400,64463,64524,64580},
	{64634,64685,64733,64777,64820,64860,64898,64934,64968,65000,65030,65058},

	{65085,65110,65134,65157,65178,65198,65217,65235,65252,65268,65283,65297},
//	{65311,65323,65335,65346,65357,65367,65377,65386,65394,65402,65409,00000},
};
#endif
#ifdef D		//D调
code unsigned int tone[8][12] =
{
	{38509,39895,41146,42809,43797,45128,46305,47354,48295,49407,50151,51043},
	{51837,52715,53341,54042,54666,55332,55921,56445,56989,57406,57902,58342},
	{58733,59126,59475,59822,60131,60434,60728,60991,61244,61487,61719,61926},

	{62135,62321,62506,62671,62833,62985,63126,63263,63390,63512,63624,63731},
	{63832,63928,64019,64103,64185,64260,64333,64400,64463,64524,64580,64634},
	{64685,64733,64777,64820,64860,64898,64934,64968,65000,65030,65058,65085},

	{65110,65134,65157,65178,65198,65217,65235,65252,65268,65283,65297,65311},
//	{65323,65335,65346,65357,65367,65377,65386,65394,65402,65409,00000,00000},
};
#endif
#ifdef Eb		//Eb调
code unsigned int tone[8][12] =
{
	{39895,41146,42809,43797,45128,46305,47354,48295,49407,50151,51043,51837},
	{52715,53341,54042,54666,55332,55921,56445,56989,57406,57902,58342,58733},
	{59126,59475,59822,60131,60434,60728,60991,61244,61487,61719,61926,62135},

	{62321,62506,62671,62833,62985,63126,63263,63390,63512,63624,63731,63832},
	{63928,64019,64103,64185,64260,64333,64400,64463,64524,64580,64634,64685},
	{64733,64777,64820,64860,64898,64934,64968,65000,65030,65058,65085,65110},

	{65134,65157,65178,65198,65217,65235,65252,65268,65283,65297,65311,65323},
//	{65335,65346,65357,65367,65377,65386,65394,65402,65409,00000,00000,00000},
};
#endif
#ifdef E		//E调
code unsigned int tone[8][12] =
{
	{41146,42809,43797,45128,46305,47354,48295,49407,50151,51043,51837,52715},
	{53341,54042,54666,55332,55921,56445,56989,57406,57902,58342,58733,59126},
	{59475,59822,60131,60434,60728,60991,61244,61487,61719,61926,62135,62321},

	{62506,62671,62833,62985,63126,63263,63390,63512,63624,63731,63832,63928},
	{64019,64103,64185,64260,64333,64400,64463,64524,64580,64634,64685,64733},
	{64777,64820,64860,64898,64934,64968,65000,65030,65058,65085,65110,65134},

	{65157,65178,65198,65217,65235,65252,65268,65283,65297,65311,65323,65335},
//	{65346,65357,65367,65377,65386,65394,65402,65409,00000,00000,00000,00000},
};
#endif
#ifdef F		//F调
code unsigned int tone[8][12] =
{
	{42809,43797,45128,46305,47354,48295,49407,50151,51043,51837,52715,53341},
	{54042,54666,55332,55921,56445,56989,57406,57902,58342,58733,59126,59475},
	{59822,60131,60434,60728,60991,61244,61487,61719,61926,62135,62321,62506},

	{62671,62833,62985,63126,63263,63390,63512,63624,63731,63832,63928,64019},
	{64103,64185,64260,64333,64400,64463,64524,64580,64634,64685,64733,64777},
	{64820,64860,64898,64934,64968,65000,65030,65058,65085,65110,65134,65157},

	{65178,65198,65217,65235,65252,65268,65283,65297,65311,65323,65335,65346},
//	{65357,65367,65377,65386,65394,65402,65409,00000,00000,00000,00000,00000},
};
#endif
#ifdef F_		//F#调
code unsigned int tone[8][12] =
{
	{43797,45128,46305,47354,48295,49407,50151,51043,51837,52715,53341,54042},
	{54666,55332,55921,56445,56989,57406,57902,58342,58733,59126,59475,59822},
	{60131,60434,60728,60991,61244,61487,61719,61926,62135,62321,62506,62671},

	{62833,62985,63126,63263,63390,63512,63624,63731,63832,63928,64019,64103},
	{64185,64260,64333,64400,64463,64524,64580,64634,64685,64733,64777,64820},
	{64860,64898,64934,64968,65000,65030,65058,65085,65110,65134,65157,65178},

	{65198,65217,65235,65252,65268,65283,65297,65311,65323,65335,65346,65357},
//	{65367,65377,65386,65394,65402,65409,00000,00000,00000,00000,00000,00000},
};
#endif
#ifdef G		//G调
code unsigned int tone[8][12] =
{
	{45128,46305,47354,48295,49407,50151,51043,51837,52715,53341,54042,54666},
	{55332,55921,56445,56989,57406,57902,58342,58733,59126,59475,59822,60131},
	{60434,60728,60991,61244,61487,61719,61926,62135,62321,62506,62671,62833},

	{62985,63126,63263,63390,63512,63624,63731,63832,63928,64019,64103,64185},
	{64260,64333,64400,64463,64524,64580,64634,64685,64733,64777,64820,64860},
	{64898,64934,64968,65000,65030,65058,65085,65110,65134,65157,65178,65198},

	{65217,65235,65252,65268,65283,65297,65311,65323,65335,65346,65357,65367},
//	{65377,65386,65394,65402,65409,00000,00000,00000,00000,00000,00000,00000},
};
#endif
#ifdef G_		//G#调
code unsigned int tone[8][12] =
{
	{46305,47354,48295,49407,50151,51043,51837,52715,53341,54042,54666,55332},
	{55921,56445,56989,57406,57902,58342,58733,59126,59475,59822,60131,60434},
	{60728,60991,61244,61487,61719,61926,62135,62321,62506,62671,62833,62985},

	{63126,63263,63390,63512,63624,63731,63832,63928,64019,64103,64185,64260},
	{64333,64400,64463,64524,64580,64634,64685,64733,64777,64820,64860,64898},
	{64934,64968,65000,65030,65058,65085,65110,65134,65157,65178,65198,65217},

	{65235,65252,65268,65283,65297,65311,65323,65335,65346,65357,65367,65377},
//	{65386,65394,65402,65409,00000,00000,00000,00000,00000,00000,00000,00000},
};
#endif
#ifdef A		//A调
code unsigned int tone[8][12] =
{
	{47354,48295,49407,50151,51043,51837,52715,53341,54042,54666,55332,55921},
	{56445,56989,57406,57902,58342,58733,59126,59475,59822,60131,60434,60728},
	{60991,61244,61487,61719,61926,62135,62321,62506,62671,62833,62985,63126},

	{63263,63390,63512,63624,63731,63832,63928,64019,64103,64185,64260,64333},
	{64400,64463,64524,64580,64634,64685,64733,64777,64820,64860,64898,64934},
	{64968,65000,65030,65058,65085,65110,65134,65157,65178,65198,65217,65235},

	{65252,65268,65283,65297,65311,65323,65335,65346,65357,65367,65377,65386},
//	{65394,65402,65409,00000,00000,00000,00000,00000,00000,00000,00000,00000},
};
#endif
#ifdef Bb		//Bb调
code unsigned int tone[8][12] =
{
	{48295,49407,50151,51043,51837,52715,53341,54042,54666,55332,55921,56445},
	{56989,57406,57902,58342,58733,59126,59475,59822,60131,60434,60728,60991},
	{61244,61487,61719,61926,62135,62321,62506,62671,62833,62985,63126,63263},

	{63390,63512,63624,63731,63832,63928,64019,64103,64185,64260,64333,64400},
	{64463,64524,64580,64634,64685,64733,64777,64820,64860,64898,64934,64968},
	{65000,65030,65058,65085,65110,65134,65157,65178,65198,65217,65235,65252},

	{65268,65283,65297,65311,65323,65335,65346,65357,65367,65377,65386,65394},
//	{65402,65409,00000,00000,00000,00000,00000,00000,00000,00000,00000,00000},
};
#endif
#ifdef B		//B调
code unsigned int tone[8][12] =
{
	{49407,50151,51043,51837,52715,53341,54042,54666,55332,55921,56445,56989},
	{57406,57902,58342,58733,59126,59475,59822,60131,60434,60728,60991,61244},
	{61487,61719,61926,62135,62321,62506,62671,62833,62985,63126,63263,63390},

	{63512,63624,63731,63832,63928,64019,64103,64185,64260,64333,64400,64463},
	{64524,64580,64634,64685,64733,64777,64820,64860,64898,64934,64968,65000},
	{65030,65058,65085,65110,65134,65157,65178,65198,65217,65235,65252,65268},

	{65283,65297,65311,65323,65335,65346,65357,65367,65377,65386,65394,65402},
//	{65409,00000,00000,00000,00000,00000,00000,00000,00000,00000,00000,00000},
};
#endif

        额……应该都能看出来吧,我用最直接的方法就是不同的调定义内容不同的tone数组,也就是在C调数组的基础上不断向前挤,从而改变歌曲的音调。相应的我们在头文件里也应添加一部分代码

//选择音调,可选C调,C#调(C_),D调,Eb调,E调,F调,F#调(F_),G调,G#调(G_),A调,Bb调,B调
#define F_

        由此我们实现了用不同的调奏出我们所想要的曲子!

物联沃分享整理
物联沃-IOTWORD物联网 » 51单片机:模拟钢琴之蜂鸣器(代码详解)

发表评论