写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做!
本文写于: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数组中,这样就能演奏出音乐了。