51单片机之蜂鸣器模拟钢琴(代码详解)——起风了

2023-10-27

目录

前言

正文

乐理

程序

补充


前言

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

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

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

起风了(全损音质)

正文

乐理

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

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

         谱子中有数字,有下划线,有双下划线,有弧,还有点,这些都代表什么呢?首先,数字表示这个音的音调,这是我们再熟悉不过的了,比如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进入一次中断函数。讲到这里,先插入讲一下数组delaydelay数组里所存放的内容为延时时间,即全音符、二分音符、四分音符……说的比较直白点就是存放了需要进入多少次定时器1中断来延时(也就是需要延时多少个1ms)。不妨看定时器1中断函数的第一个条件判断语句if,每当一个音延长到相应的时间之后变通过变量n切换到下一个音。

        再看看我们刚刚在musicInit函数里落下的变量tdelay处理部分。此时读者对这段代码的理解是否更加深刻了呢?变量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_

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

51单片机之蜂鸣器模拟钢琴(代码详解)——起风了 的相关文章

  • 第七届cuit智能车比赛总结

    作为大一小白 第一次参加比赛 感受到了比赛的氛围 也收获了许多宝贵的经验 1 电磁杆 电磁杆的作用是感应漆包线的位置 给单片机传入数据 使车维持在一定范围内 他的感应部分应用RLC振荡电路 感应到的微弱电流经过一定倍数放大 和滤波电路 最后
  • 多线程详解(一)——创建多线程

    一 进程与线程 1 进程 进程是资源 CPU 内存等 分配的基本单位 它是程序执行时的一个实例 程序运行时系统就会创建一个进程 并为它分配资源 然后把该进程放入进程就绪队列 进程调度器选中它的时候就会为它分配CPU时间 程序开始真正运行 2
  • MyBatis-Plus(二)设置实体类对应的表名、字段名

    设置实体类对应的表名 字段名 一 设置关联的表名 1 默认情况下 如果数据库表是使用标准的下划线命名 并且能对应上实体类的类名 我们就不需要特别去手动匹配 比如有张 user info 表 那么会自动匹配下面这个实体类 Data publi
  • 【硬件】以太网PHY芯片有三个时钟说明

    硬件以太网PHY芯片有三个时钟 对此进行了梳理 PHY芯片时钟的选择 PHY芯片中有3个时钟 Gtx clk Rx clk Tx clk 1 GTX CLK仅使用在GMII模式下 时钟频率为125M 发送数据时的时钟 2 RX CLK在GM

随机推荐

  • Altium Desinger - PCB 3D模型创建导出

    打开绘制好的PCB版图 点击File gt Export gt STEP 3D 输出保存 就可以被solidworks 打开导入 整机文件
  • 上海某高校线上期末题-核酸检测系统C++实现

    上海某高校线上期末题 核酸检测系统C 实现 注意 本博客只是以学习交流和记录学习内容为主 分享自己的一些见解和思路 如有不当之处 请告知 试题内容 考核要求 项目文件地址 在github上开源 https github com hudisc
  • 如何在PADS中添加表面型测试点

    在PCB 设计中 我们经常需要对某些信号线增加一些测试点 以便在产品调试中对其信号进行测试 在PADS Layout POWERPCB 中添加测试点时 默认的是以标准过孔STANDARDVIA 作为测试点 但这是通孔方式的测试点 为了节省测
  • Spring更简单的使用方法

    前面介绍了一些最基本的存储和读取Bean对象的方法 但是在Spring中已经基本不这样使用了 因此这一篇博客就来介绍一下更简单的存储和读取Bean对象的方法而想要更简单的存储和读取最核心的就是使用注解 下面一起来看看吧 目录 一 存储Bea
  • Cherry-Pick

    版权声明 本公众号发布的所有文章 均属于原创 版权归本公众号所有 未经允许 不得转载 一 前言 在实际工作开发中 会遵循标准的 Git Work flow 对待不同的功能 会切出不同的分支进行 coding 所以 基于什么基准分支切出来的功
  • 过采样4:提高ADC分辨率实例(终篇)

    原文来自公众号 工程师看海 公众号后台回复 过采样 有更多资料 这应该是过采样系列的最后一篇文章 经常有同学在使用FPGA 单片机或者DSP进行过采样时没有正确设计代码 导致结果异常 有些结果看似正常 而实际却没有意义 这篇文章涉及到简单的
  • Unity 判断 鼠标/触摸 位置是否在指定Ui上,非射线检测方式。触屏移动物体。

    事出有因 1 项目的触摸点击事件 并且有手指缩放 移动功能 2 unity本身支持touch功能 这个不多说 3 当做手指缩放 移动的时候就要判断touch点位置了 但是如果不加任何判断则会乱套 如 本来想移动A 让A跟随 手指touch
  • 分布式事务中2PC与3PC的区别

    分布式事务中2PC与3PC的区别 2017 04 02 19 46 442人阅读 评论 0 收藏 举报 分类 Java知识 1 版权声明 本文为博主原创文章 未经博主允许不得转载 目录 协调者 在分布式系统中 每一个机器节点虽然都能明确的知
  • Pandas读取Excel文件XLRDError: Excel xlsx file; not supported

    问题背景 工作中大部使用Pandas分处理的数据都是以csv后缀结尾的文件 但是突然换成xlsx后缀的表格之后 出现的一些错误 问题现象 XLRDError Excel xlsx file not supported usr local l
  • pointer-event属性详解

    一 pointer event属性可以指定在什么情况下元素可以成为鼠标事件 二 取值 1 pointer event auto 默认值 对于svg元素 该值与visiblePainted效果相同 2 pointer event none 元
  • X的N次方求解——pow(x,n)实现

    最近看到这样的一个题目求X的N次方 自己想了一些解决办法 记录一下留作日后参考 求X的N次方 首先暴力求解 int exp int x int n int ret 1 for int i 0 i lt n i ret x return re
  • 阅读桑迪潘·戴伊的《Python图像处理实战》笔记十一

    十一 图像处理中的深度学习 图像检测等 1 检测目标的全卷积模型 YOLOv2 1 使用卷积神经网络检测目标 两个步骤 首先 使用小而紧密裁剪的图像训练卷积神经网络进行图像分类 其次 使用不同窗口大小的滑动窗口和事先学习该窗口内的测试图像进
  • 高性能的iocp网络设计思路

    IOCP是什么就不用介绍了 为什么要用IOCP就更不用提及 这里我们只简单讨论IOCP开发的一个思路 即能提高性能又能隆低开发复杂性 即能提高性能又能隆低开发复杂性 觉得我说的有矛盾吗 不是复杂的代码才能换来高效吗 其实不一定 我认为简单是
  • 用C语言编写程序,将多个字符串排序输出

    1 有三个字符串分别是 hello bit world 然后排序之后输出的顺序为 bit hello world int main char str1 100 char str2 100 char str3 100 char tmp 100
  • 【Oracle】事务的提交与回滚

    一 数据库事务 数据库事务 Database Transaction 是指作为单个逻辑工作单元执行的一系列操作 事务处理可以确保除非事务性单元内的所有操作都成功完成 否则不会永久更新面向数据的资源 通过将一组相关操作组合为一个要么全部成功要
  • 农村水利水电与水土保持

    授课 福师大张思鹏sunnyact 泉舟时代 主要内容 简介 做出一款能实时通报水利水电与水土相关的并能及时发送到指定相关人员手机短信 思路 通过水利局农村水利水电与水土保持处官网参考 百度脑图分享 https naotu baidu co
  • Docker容器如何连接网络+容器间网络互通+Docker网络模式+自定义网络+网络打通

    Docker网络 问题 Docker容器如何连接网络 Docker容器之间能否接通网络 Docker容器之间可以通过名称ping通吗 能否自定义一个网络 那么 我们带着几个问题来学习docker的网络 Docker容器是如何连接网络的 我们
  • 焉建伟:3.30黄金今日跌破1700关口如何操作,黄金原油实时操作建议

    国际黄金行情走势分析 从昨日盘整近2周的箱体破位之后黄金的行情就非常明朗了 今日就是一个反弹做空的基调不用多想 而对于长期的横盘整理之后出现破位方向明朗了 点位就并不是那么重要 今天这个行情很多人可能都在等着1716 17附近去参与空单 事
  • 在Java中以编程方式将PSB转换为PDF,JPG或PSD

    PSB Photoshop Big 文件扩展名用于存储与图形有关的大量信息 可以使用Java编程语言轻松地将PSB文件转换为PDF JPG或PSD格式 让我们学习以下各节以探讨PSB文件转换 使用Java以编程方式将PSB转换为PDF 使用
  • 51单片机之蜂鸣器模拟钢琴(代码详解)——起风了

    目录 前言 正文 乐理 程序 补充 前言 最近心血来潮 想要用蜂鸣器播放音乐 全损音质 于是最初的想法诞生了 但是我总不能每次想听歌都敲一遍蜂鸣器的代码吧 有没有什么办法只需要敲一遍的代码便可以实现听歌自由呢 相对自由 也就是每次写歌只需要