STC89C52单片机学习——第26节: [11-2]蜂鸣器播放音乐

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做!
本文写于:2025.03.19

51单片机学习------第26节: [11-2]蜂鸣器播放音乐

前言

本次笔记是用来记录我的学习过程,同时把我需要的困难和思考记下来,有助于我的学习,同时也作为一种习惯,可以督促我学习,是一个激励自己的过程,让我们开始51单片机的学习之路。

欢迎大家给我提意见,能给我的嵌入式之旅提供方向和路线,现在作为小白,我就先学习51单片机了,就跟着B站上的江协科技开始学习了.

在这里会记录下江协科技51单片机开发板的配套视频教程所作的实验和学习笔记内容,因为我之前有一个开发板,我大概率会用我的板子模仿着来做.让我们一起加油!

另外为了增强我的学习效果:每次笔记把我不知道或者问题在后面提出来,再下一篇开头作为解答!

开发板说明

本人采用的是慧净的开发板,因为这个板子是我N年前就买的板子,索性就拿来用了。不再另外购买视频中的普中开发板了。

原理图如下

视频中的都用这个开发板来实现,如果有资源就利用起来。

仔细看了看:开发板的晶振为:11.0592Mhz;12Mhz晶振是用来给CH340G芯片外置晶振;

下图是实物图

引用

51单片机入门教程-2020版 程序全程纯手打 从零开始入门

还参考了下图中的书籍:

手把手教你学51单片机(C语言版)

STC89C52手册

解答和科普

一、LED播放音乐

1.1 播放音乐

弹钢琴时的两个要素是:1、弹哪一个钢琴按键 2、弹多长时间。弹哪一个按键就是选择音高,弹多长时间就是音长。在简谱中对应的就是写的数字是多少,还有就是拍子数。

由上一节可知,蜂鸣器的频率由计数频率,进而算出来计数周期,一个周期翻转2次,就是高电平持续半个周期,低电平持续半个周期,所以我们算出来半个周期,就是定时器定时的时间,就是半个周期是一种状态。也就是用定时器,定固定的周期,完成音高的产生。

c 复制代码
void Timer0_Routine()   interrupt  1	//1毫秒跳转到这里,T=2ms,触发中断 500hz
{											
	TL0 = FreqTable[FreqSelect]/256;							//重新设置定时初值,根据表格算的值,计算出对应的TL0和TH0,只有我们可以设置不同的频率
	TH0 =  FreqTable[FreqSelect]%256;							//又因为总共有36个音高,是固定的只需要取数组里的值。TH0=X/256,TL0=X%256;
	Buzzer=!Buzzer;
	
}

根据上节课的EXCEL表,我们可以创建一个数组:里面放的数据是用来计算重装载值,产生对应音高的重装载值。

c 复制代码
unsigned int code  FreqTable[]={0,
						  63628,63731,63835,63928,64021,64103,64185,64260,64331,64400,64463,64528,
						  64580,64633,64684,64732,64777,64820,64860,64898,64934,64968,65000,65030,
						  65058,65085,65110,65134,65157,65178,65198,65217,65235,65252,65268,65283,
							0xFF
	};

如何我们在设置一个频率选择变量:FreqSelect,用来选择需要的音高。

c 复制代码
while(1)
	{
	FreqSelect++;
	Delay(500);	//这个音高响500ms
	}

现在这个时候我们可以完成按着顺序弹奏给的音高。并且每个音高是500ms。

2、接下来要完成按照简谱的顺序演奏歌曲,我们就又创建了一个Music数组用来存放简谱音高的索引值,按照简谱顺序演唱的索引值。

c 复制代码
unsigned char code Music[]={12,16,12,36,19,20};			

并且创造一个MusicSelect变量用来选择,Music数组中的索引值;

c 复制代码
while(1)
	{
	FreqSelect=Music[MusicSelect];		//这个就是得到了简谱的音高的索引值,比如MusicSelect=0,去Music数组中找就是简谱第一个音高对应的索引值,如是13就是代表中音1,然后再把FreqSelect去FreqTable去找,就是64580,对应的事重装载值。
	MusicSelect++;
	Delay(500);	//这个音高响500ms
	}
c 复制代码
oid Timer0_Routine()   interrupt  1	//1毫秒跳转到这里,T=2ms,触发中断 500hz
{											
	
	TL0 = FreqTable[FreqSelect]/256;							//重新设置定时初值,根据表格算的值,计算出对应的TL0和TH0,只有我们可以设置不同的频率
	TH0 =  FreqTable[FreqSelect]%256;							//又因为总共有36个音高,是固定的只需要取数组里的值。TH0=X/256,TL0=X%256;
	Buzzer=!Buzzer;
}

现在这个函数完成了对简谱的音高选择,并且每个音的时间是500us。
下面是音长的设置

在这里我们采用了用最短的音符为基准设为1,比如八分音符为1,四份音符就为2,2分音符为4,全音符就为8。在这里我没有再去设数组,和数组选择位,而是把这个音长放在了Music的频率选择位后面,就这样在Music数组中形成要播放的音高,音长这么一组,一组组的数据,分别代表频率和音长。

c 复制代码
FreqSelect=Music[MusicSelect];				//频率
		MusicSelect++;
		Delay(250*Music[MusicSelect]);				//时长=基准值乘以音符的系数,如基准是16音符是1,4分音符的系数是4
		MusicSelect++;								//取到了频率的索引值

然后完成抬手停顿的动作,每演奏一个音符把定时器关掉,这样就形成了抬手动作

c 复制代码
		
		TR0=0;									//每演奏一个音符,就把定时器关掉
		Delay(5);								//抬手的时间,每个音符都分开
		TR0=1;
		}

然后还有停止符,就是在频率的第一个填写了为0,这样在延迟来到蜂鸣器不发出声音,然后就是解释标志位,放在频率表后0XFF,为结束标志为。

c 复制代码
unsigned int code  FreqTable[]={**0,**
						  63628,63731,63835,63928,64021,64103,64185,64260,64331,64400,64463,64528,
						  64580,64633,64684,64732,64777,64820,64860,64898,64934,64968,65000,65030,
						  65058,65085,65110,65134,65157,65178,65198,65217,65235,65252,65268,65283,
							**==0xFF==**
						};
	

为0的话不进入中断,也就不发声音。

c 复制代码
void Timer0_Routine()   interrupt  1	//1毫秒跳转到这里,T=2ms,触发中断 500hz
{											
	if(FreqTable[FreqSelect])	
	{
	TL0 = FreqTable[FreqSelect]/256;							//重新设置定时初值,根据表格算的值,计算出对应的TL0和TH0,只有我们可以设置不同的频率
	TH0 =  FreqTable[FreqSelect]%256;							//又因为总共有36个音高,是固定的只需要取数组里的值。TH0=X/256,TL0=X%256;
	Buzzer=!Buzzer;
	}		
	
}

在这里如何乐谱选择的不是解释标准才执行,音高和音长的选择。

c 复制代码
while(1)
	{
		if(Music[MusicSelect]!=0xFF)
		{
		FreqSelect=Music[MusicSelect];				//频率
		MusicSelect++;
		Delay(SPEED/4*Music[MusicSelect]);				//时长=基准值乘以音符的系数,如基准是16音符是1,4分音符的系数是4
		MusicSelect++;								//取到了频率的索引值
		
		TR0=0;									//每演奏一个音符,就把定时器关掉
		Delay(5);								//抬手的时间,每个音符都分开
		TR0=1;
		}
		else
		{
			TR0=0;	
			while(1);
		}
		
	}

这样就完成了对简谱的描述,为了方便,我们给索引号定义类有含义的字母符号便于编写。

c 复制代码
#define SPEED 500
#define P 	 0
#define L1   1
#define L1_  2
#define L2   3
#define L2_  4
#define L3   5
#define L4   6
#define L4_  7
#define L5   8
#define L5_  9
#define L6   10
#define L6_  11
#define L7   12

#define M1   13
#define M1_  14
#define M2   15
#define M2_  16
#define M3   17
#define M4   18
#define M4_  19
#define M5   20
#define M5_  21
#define M6   22
#define M6_  23
#define M7   24

#define H1   25
#define H1_  26
#define H2   27
#define H2_  28
#define H3   29
#define H4   30
#define H4_  31
#define H5   32
#define H5_  33
#define H6   34
#define H6_  35
#define H7   36

这样就可以看着简谱,把频率和音长的数据编写在Music的数组中。最后结束防止结束标志位。

c 复制代码
else
		{
			TR0=0;	
			while(1);
		}

如果结束则关闭定时。结束演唱。

二、蜂鸣器播放音乐代码

1.代码

1.1main.c

c 复制代码
#include <REGX52.H>
#include <INTRINS.H>
#include "Delay.h"
#include "Key.h"
#include "Timer0.h"
#include "LCD1602.h" 
#include "Init.h"
#include "Nixie.h"

sbit  Buzzer = P2^3;		//蜂鸣器端口
#define SPEED 500
#define P 	 0
#define L1   1
#define L1_  2
#define L2   3
#define L2_  4
#define L3   5
#define L4   6
#define L4_  7
#define L5   8
#define L5_  9
#define L6   10
#define L6_  11
#define L7   12

#define M1   13
#define M1_  14
#define M2   15
#define M2_  16
#define M3   17
#define M4   18
#define M4_  19
#define M5   20
#define M5_  21
#define M6   22
#define M6_  23
#define M7   24

#define H1   25
#define H1_  26
#define H2   27
#define H2_  28
#define H3   29
#define H4   30
#define H4_  31
#define H5   32
#define H5_  33
#define H6   34
#define H6_  35
#define H7   36





unsigned int code  FreqTable[]={0,
						  63628,63731,63835,63928,64021,64103,64185,64260,64331,64400,64463,64528,
						  64580,64633,64684,64732,64777,64820,64860,64898,64934,64968,65000,65030,
						  65058,65085,65110,65134,65157,65178,65198,65217,65235,65252,65268,65283,
							0xFF
						};
unsigned char code Music[]={												//频率、时长
P,4,
P,4,
P,4,
M6,4,
M7,4,
	
H1,4+2,
M7,4+4,
H1,4,
H3,4,
	
M7,4+4+4,	
M3,2,
M3,2,	
	

0xFF	
};								//乐谱的音高顺序
unsigned char FreqSelect,MusicSelect;								//MusicSelect乐谱的音高的顺序

void main()
{
	Timer0_Init();			//第一次中断无关紧要
	DianZhengGuan();			//关闭点阵
	while(1)
	{
		if(Music[MusicSelect]!=0xFF)
		{
		FreqSelect=Music[MusicSelect];				//频率
		MusicSelect++;
		Delay(SPEED/4*Music[MusicSelect]);				//时长=基准值乘以音符的系数,如基准是16音符是1,4分音符的系数是4
		MusicSelect++;								//取到了频率的索引值
		
		TR0=0;									//每演奏一个音符,就把定时器关掉
		Delay(5);								//抬手的时间,每个音符都分开
		TR0=1;
		}
		else
		{
			TR0=0;	
			while(1);
		}
		
	}
}



void Timer0_Routine()   interrupt  1	//1毫秒跳转到这里,T=2ms,触发中断 500hz
{											
	if(FreqTable[FreqSelect])	
	{
	TL0 = FreqTable[FreqSelect]/256;							//重新设置定时初值,根据表格算的值,计算出对应的TL0和TH0,只有我们可以设置不同的频率
	TH0 =  FreqTable[FreqSelect]%256;							//又因为总共有36个音高,是固定的只需要取数组里的值。TH0=X/256,TL0=X%256;
	Buzzer=!Buzzer;
	}		
	
}

1.2Timer 0

c 复制代码
#include <REGX52.H>


void Timer0_Init(void)		//1毫秒@11.0592MHz
{
						//	TMOD不能复制,如果同时使用定时器1和0,它会覆盖淹没,
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	
	
	ET0=1;
	EA=1;
	PT0=0;
}





/*
void Timer0_Routine()   interrupt  1	//1毫秒跳转到这里,触发中断
{
	static unsigned int T0Count;		//Tcount为计数值  static只有本函数使用
	TL0 = 0x66;					//重新设置定时初值
	TH0 = 0xFC;					//重新设置定时初值
	T0Count++;
	if(T0Count>=1000)
	{
	T0Count=0;
	P1_0=~P1_0;
	
	}
	
}

*/
c 复制代码
#ifndef		__TIMER0_H
#define 	 __TIMER0_H

void Timer0_Init(void)	;

#endif

1.3Delay.c

c 复制代码
void Delay (unsigned int xms)		//@12.000MHz
{
	unsigned char i, j;
	while(xms--)
{
		i = 12;
		j = 169;
		do
		{
			while (--j);
		} while (--i);

}
}
c 复制代码
#ifndef __DELAY_H__
#define __DELAY_H__
void Delay (unsigned int xms);


#endif

成品展示

好运来 三段1,2两遍,3一遍

c 复制代码
#include <REGX52.H>
#include "Delay.h"
#include "Timer0.h"
#include "LCD1602.h"
//蜂鸣器端口定义
sbit Buzzer=P2^3         ;

//播放速度,值为四分音符的时长(ms)这个
#define SPEED	500

//音符与索引对应表,P:休止符,L:低音,M:中音,H:高音,下划线:升半音符号#
#define P	0
#define L1	1
#define L1_	2
#define L2	3
#define L2_	4
#define L3	5
#define L4	6
#define L4_	7
#define L5	8
#define L5_	9
#define L6	10
#define L6_	11
#define L7	12
#define M1	13
#define M1_	14
#define M2	15
#define M2_	16
#define M3	17
#define M4	18
#define M4_	19
#define M5	20
#define M5_	21
#define M6	22
#define M6_	23
#define M7	24
#define H1	25
#define H1_	26
#define H2	27
#define H2_	28
#define H3	29
#define H4	30
#define H4_	31
#define H5	32
#define H5_	33
#define H6	34
#define H6_	35
#define H7	36

//索引与频率对照表
unsigned int FreqTable[]={
	0,
	63628,63731,63835,63928,64021,64103,64185,64260,64331,64400,64463,64528,
	64580,64633,64684,64732,64777,64820,64860,64898,64934,64968,65000,65030,
	65058,65085,65110,65134,65157,65178,65198,65217,65235,65252,65268,65283,
};

//乐谱
unsigned char code Music[]={
	//音符,时值,
	
	//1A
	M6,	4,
	H3,	4,
	H2,	4,
	H1,	2,
	M6,	2,
	
	M5,	4,
	H1,	2,
	H2,	2,
	M6,	4+4,
	
	M6,	4,
	H2,	2,
	H3, 2,
	H2, 4,
	H1, 2,
	M6, 2,
	
	
	//2
	M2,	4,
	M5,	2,
	M6,	2,
	M3, 4+4,
	
	//3
	H1,2+1,
	H2,1,
	H3,2,
	H3,2,
	H3,2,
	H3,2,
	H2,2,
	H1,2,
	
	M5,4,
	H1,4,
	M6,4+4+16,
	
	
	//4B
	M6,2+1,
	M6,1,
	H1,2,
	H1,2,
	M6,4+2,
	M6,2,
	
	M5,2,
	M3,2,
	M5,2,
	H1,2,
	M6,8,
	
	M6,2,
	H1,2,
	H1,3,
	H1,1,
	M6,4,
	M5,4,
	
	//5
	M6,	2,
	M5,	2,
	M2,	2,
	M5,	2,
	M3, 6,
	M3, 2,
	
	M3,2,
	M2,2,
	M1,2,
	M3,2,
	M2,6,
	M3,2,
	
	M6,2,
	M5,2,
	M3,2,
	M6,2,
	M5,8,
	
	//6
	M6,2,
	H1,2,
	H1,3,
	M6,1,
	H2,2,
	H2,2,
	H2,2,
	H1,2,
	
	M6,4,
	M5,2,
	H1,2,
	
	M6,16,
	
	//1A2
	M6,	4,
	H3,	4,
	H2,	4,
	H1,	2,
	M6,	2,
	
	M5,	4,
	H1,	2,
	H2,	2,
	M6,	4+4,
	
	M6,	4,
	H2,	2,
	H3, 2,
	H2, 4,
	H1, 2,
	M6, 2,
	
	
	//2
	M2,	4,
	M5,	2,
	M6,	2,
	M3, 4+4,
	
	//3
	H1,2+1,
	H2,1,
	H3,2,
	H3,2,
	H3,2,
	H3,2,
	H2,2,
	H1,2,
	
	M5,4,
	H1,4,
	M6,4+4+16,
	
	//4B2
	M6,2+1,
	M6,1,
	H1,2,
	H1,2,
	M6,4+2,
	M6,2,
	
	M5,2,
	M3,2,
	M5,2,
	H1,2,
	M6,8,
	
	M6,2,
	H1,2,
	H1,3,
	H1,1,
	M6,4,
	M5,4,
	
	//5
	M6,	2,
	M5,	2,
	M2,	2,
	M5,	2,
	M3, 6,
	M3, 2,
	
	M3,2,
	M2,2,
	M1,2,
	M3,2,
	M2,6,
	M3,2,
	
	M6,2,
	M5,2,
	M3,2,
	M6,2,
	M5,8,
	
	//6
	M6,2,
	H1,2,
	H1,3,
	M6,1,
	H2,2,
	H2,2,
	H2,2,
	H1,2,
	
	M6,4,
	M5,2,
	H1,2,
	
	M6,16,
	//1A
	M6,	4,
	H3,	4,
	H2,	4,
	H1,	2,
	M6,	2,
	
	M5,	4,
	H1,	2,
	H2,	2,
	M6,	4+4,
	
	M6,	4,
	H2,	2,
	H3, 2,
	H2, 4,
	H1, 2,
	M6, 2,
	
	
	//2
	M2,	4,
	M5,	2,
	M6,	2,
	M3, 4+4,
	
	//3
	H1,2+1,
	H2,1,
	H3,2,
	H3,2,
	H3,2,
	H3,2,
	H2,2,
	H1,2,
	
	M5,4,
	H1,4,
	M6,4+4+16,
	
	//1A
	M6,	4,
	H3,	4,
	H2,	4,
	H1,	2,
	M6,	2,
	
	M5,	4,
	H1,	2,
	H2,	2,
	M6,	4+4,
	
	M6,	4,
	H2,	2,
	H3, 2,
	H2, 4,
	H1, 2,
	M6, 2,
	
	
	//2
	M2,	4,
	M5,	2,
	M6,	2,
	M3, 4+4,
	
	//3
	H1,2+1,
	H2,1,
	H3,2,
	H3,2,
	H3,2,
	H3,2,
	H2,2,
	H1,2,
	
	M5,4,
	H1,4,
	M6,4+4+16,
	
	//1A
	M6,	4,
	H3,	4,
	H2,	4,
	H1,	2,
	M6,	2,
	
	M5,	4,
	H1,	2,
	H2,	2,
	M6,	4+4,
	
	M6,	4,
	H2,	2,
	H3, 2,
	H2, 4,
	H1, 2,
	M6, 2,
	
	
	//2
	M2,	4,
	M5,	2,
	M6,	2,
	M3, 4+4,
	
	//3
	H1,2+1,
	H2,1,
	H3,2,
	H3,2,
	H3,2,
	H3,2,
	H2,2,
	H1,2,
	
	M5,4,
	H1,4,
	M6,4+4+16,
	
	//1A
	M6,	4,
	H3,	4,
	H2,	4,
	H1,	2,
	M6,	2,
	
	M5,	4,
	H1,	2,
	H2,	2,
	M6,	4+4,
	
	M6,	4,
	H2,	2,
	H3, 2,
	H2, 4,
	H1, 2,
	M6, 2,
	
	
	//2
	M2,	4,
	M5,	2,
	M6,	2,
	M3, 4+4,
	
	//3
	H1,2+1,
	H2,1,
	H3,2,
	H3,2,
	H3,2,
	H3,2,
	H2,2,
	H1,2,
	
	M5,4,
	H1,4,
	M6,4+4+16,
	
	

	//7C
	H2,16,
	H3,16,
	M6,32,
	
	P,4,
	M5,2,
	M5,2,
	M6,8,
	
	0xFF	//终止标志
};

unsigned char FreqSelect,MusicSelect;

void main()
{
	Timer0Init();
	 LCD_Init();
	LCD_ShowString(1,1,"START");
	LCD_ShowString(2,1,"HAPPY NEW YEAR");
	while(1)
	{
		if(Music[MusicSelect]!=0xFF)	//如果不是停止标志位
		{
			FreqSelect=Music[MusicSelect];	//选择音符对应的频率
			MusicSelect++;
			Delay(SPEED/4*Music[MusicSelect]);	//选择音符对应的时值
			MusicSelect++;
			TR0=0;
			Delay(5);	//音符间短暂停顿
			TR0=1;
		}
		else	//如果是停止标志位
		{
			TR0=0;
			
			LCD_ShowString(1,1,"END");
			while(1);
		}
	}
}

void Timer0_Routine() interrupt 1
{
	if(FreqTable[FreqSelect])	//如果不是休止符
	{
		/*取对应频率值的重装载值到定时器*/
		TL0 = FreqTable[FreqSelect]%256;		//设置定时初值
		TH0 = FreqTable[FreqSelect]/256;		//设置定时初值
		Buzzer=!Buzzer;                    	//翻转蜂鸣器IO口
	}
}

实验现象:

蜂鸣器播放好运来

问题

整体的编写难度很大!

总结

本节课主要学了如何用蜂鸣器来演唱音乐,主要有音高和音长着两个要素,音高的频率对应着计数器的计数频率,也就是对应的计数周期,每个音高对应一个定时器重装载值,然后进行Music数组对音高和音长一组组的存入,最后放入结束标志位,停止位不进行定时器计时(不发出声音),抬手的动作用关闭定时器5ms来表示。这样我们根据简谱把歌曲的音高和音长写入到Music数组中,这样就能演奏出音乐了。

相关推荐
虾球xz1 小时前
游戏引擎学习第171天
学习·游戏引擎
viperrrrrrrrrr71 小时前
大数据学习(78)-spark streaming与flink
大数据·学习·flink·spark
月落星还在4 小时前
AI学习——卷积神经网络(CNN)入门
人工智能·学习·cnn
zhongvv4 小时前
单片机flash存储也做磨损均衡
单片机·数据存储·磨损均衡·单片机开发·flash读写
电鱼智能的电小鱼4 小时前
eFish-SBC-RK3576 工业HMI硬件方案设计
大数据·人工智能·嵌入式硬件·智慧城市·边缘计算
lx学习6 小时前
嵌入式c学习六
c语言·数据结构·学习
myzzb9 小时前
python字符级差异分析并生成 Word 报告 自然语言处理断句
python·学习·自然语言处理·word·snownlp
IDIOT___IDIOT9 小时前
第一次烧录51单片机的烧录不了的问题
单片机·嵌入式硬件·51单片机
小麦嵌入式10 小时前
Linux驱动开发实战(六):设备树升级!插件设备树点灯!
linux·c语言·驱动开发·单片机·嵌入式硬件·mcu·ubuntu
梁山1号11 小时前
【QT】】qcustomplot的初步使用二
c++·单片机·qt