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数组中,这样就能演奏出音乐了。

相关推荐
#金毛7 分钟前
六、STM32 HAL库回调机制详解:从设计原理到实战应用
stm32·单片机·嵌入式硬件
Clockwiseee39 分钟前
文件上传总结
运维·服务器·学习·文件上传
苜柠1 小时前
Wpf学习片段
学习
欢乐熊嵌入式编程2 小时前
智能手表固件升级 OTA 策略文档初稿
嵌入式硬件·学习·智能手表
欢乐熊嵌入式编程2 小时前
智能手表 MCU 任务调度图
单片机·嵌入式硬件·智能手表
起床学FPGA2 小时前
异步FIFO的学习
学习·fpga开发
【云轩】2 小时前
电机密集型工厂环境下的无线通信技术选型与优化策略
经验分享·嵌入式硬件
依年南台2 小时前
搭建大数据学习的平台
大数据·学习
小虎卫远程打卡app3 小时前
视频编解码学习10之成像技术原理
学习·计算机视觉·视频编解码
sword devil9003 小时前
将arduino开发的Marlin部署到stm32(3D打印机驱动)
stm32·单片机·嵌入式硬件