单片机学习笔记---蜂鸣器播放提示音音乐(天空之城)
目录
蜂鸣器播放提示音
蜂鸣器播放音乐(天空之城)
准备工作
主程序
中断函数
上一节讲了蜂鸣器驱动原理和乐理基础知识,这一节开始代码演示!
蜂鸣器播放提示音
先创建工程:蜂鸣器播放提示音
把我们之前模块化的程序文件添加进来
但是这次我们 是要静态显示,所以要把Nixie.c文件中这里删掉
我们要实现的效果是我们按下按键之后,会出现按键提示音。
创建一个Buzzer.c和Buzzer.h文件
具体代码解释请看注释:
Buzzer.c
#include <REGX52.H>
#include <INTRINS.H>//_nop_函数的头文件
//蜂鸣器端口:普中A2的板子是P2^5,其他板子的可以试试P1^5
sbit Buzzer=P2^5;
//在博主以后的博客中,这个函数就作为蜂鸣器的私有延时函数
void Buzzer_Delay500us() //@12.000MHz
{
unsigned char i;
_nop_();//延时一微秒的函数
i = 247;
while (--i);
}
//蜂鸣器的发声时长函数
void Buzzer_Time(unsigned int ms)
{
unsigned int i;//蜂鸣器翻转的次数
for(i=0;i<ms*2;i++)//一个for循环500us=0.5ms,x*0.5ms*2延时xms
{
Buzzer=!Buzzer;//翻转蜂鸣器
//蜂鸣器的标准提示音是1000Hz
Buzzer_Delay500us();//每500us=0.5ms翻转一次,那蜂鸣响的周期就是1000Hz
}
}
Buzzer.h
#ifndef __BUZZER_H__
#define __BUZZER_H__
void Buzzer_Time(unsigned int ms);
#endif
main.c
#include <REGX52.H>
#include "Delay.h"
#include "Key.h"
#include "Nixie.h"
#include "Buzzer.h"
unsigned char KeyNum;
void main()
{
Nixie(1,0);//第一位数码管显示0
while(1)
{
KeyNum=Key();//按键按下后将键码赋值给keyNum
if(KeyNum)//如果按键按下
{
Buzzer_Time(100);//蜂鸣器响100ms
Nixie(1,KeyNum);//第1位数码管,显示键码
}
}
}
效果请看视频:
蜂鸣器播放提示音
以上就是蜂鸣器播放提示音的代码演示!
蜂鸣器播放音乐(天空之城)
接下来开始演示蜂鸣器播放音乐(天空之城)
开始之前,我们先接着上一篇解释一下这张表格(我们之后都叫它表1)
- 想要的频率在最左边,然后我们不能直接产生频率,我们只有周期,1除以频率就是周期。
- 我们需要半个周期给它翻转一次,翻转两次才是一个周期,所以翻转频率就是周期除以2,把这个频率取整就是我们的计数值(需要计这么多的数值然后就产生中断)。
- 那么它怎么计这么长呢?溢出才能产生中断对吧?我们就需要给它提前装好中间值(重装载值),在这个值开始计,计到65536溢出就产生中断了。
- 为了方便找到对应的音符,我们给这些音符弄个索引,相当于编号。
(PS:手机页面请把表格往左滑动就可看到重装载值)
表1:
音符 | 频率(Hz) | 周期(us) | 周期/2(us) | 取整 | 重装载值 | 索引 |
1 | 262 | 3816.793893 | 1908.396947 | 1908 | 63628 | 1 |
1# | 277 | 3610.108303 | 1805.054152 | 1805 | 63731 | 2 |
2 | 294 | 3401.360544 | 1700.680272 | 1701 | 63835 | 3 |
2# | 311 | 3215.434084 | 1607.717042 | 1608 | 63928 | 4 |
3 | 330 | 3030.30303 | 1515.151515 | 1515 | 64021 | 5 |
4 | 349 | 2865.329513 | 1432.664756 | 1433 | 64103 | 6 |
4# | 370 | 2702.702703 | 1351.351351 | 1351 | 64185 | 7 |
5 | 392 | 2551.020408 | 1275.510204 | 1276 | 64260 | 8 |
5# | 415 | 2409.638554 | 1204.819277 | 1205 | 64331 | 9 |
6 | 440 | 2272.727273 | 1136.363636 | 1136 | 64400 | 10 |
6# | 466 | 2145.922747 | 1072.961373 | 1073 | 64463 | 11 |
7 | 496 | 2016.129032 | 1008.064516 | 1008 | 64528 | 12 |
1 | 523 | 1912.045889 | 956.0229446 | 956 | 64580 | 13 |
1# | 554 | 1805.054152 | 902.5270758 | 903 | 64633 | 14 |
2 | 587 | 1703.577513 | 851.7887564 | 852 | 64684 | 15 |
2# | 622 | 1607.717042 | 803.8585209 | 804 | 64732 | 16 |
3 | 659 | 1517.450683 | 758.7253414 | 759 | 64777 | 17 |
4 | 698 | 1432.664756 | 716.3323782 | 716 | 64820 | 18 |
4# | 740 | 1351.351351 | 675.6756757 | 676 | 64860 | 19 |
5 | 784 | 1275.510204 | 637.755102 | 638 | 64898 | 20 |
5# | 831 | 1203.369434 | 601.6847172 | 602 | 64934 | 21 |
6 | 880 | 1136.363636 | 568.1818182 | 568 | 64968 | 22 |
6# | 932 | 1072.961373 | 536.4806867 | 536 | 65000 | 23 |
7 | 988 | 1012.145749 | 506.0728745 | 506 | 65030 | 24 |
1 | 1046 | 956.0229446 | 478.0114723 | 478 | 65058 | 25 |
1# | 1109 | 901.7132552 | 450.8566276 | 451 | 65085 | 26 |
2 | 1175 | 851.0638298 | 425.5319149 | 426 | 65110 | 27 |
2# | 1245 | 803.2128514 | 401.6064257 | 402 | 65134 | 28 |
3 | 1318 | 758.7253414 | 379.3626707 | 379 | 65157 | 29 |
4 | 1397 | 715.8196135 | 357.9098067 | 358 | 65178 | 30 |
4# | 1480 | 675.6756757 | 337.8378378 | 338 | 65198 | 31 |
5 | 1568 | 637.755102 | 318.877551 | 319 | 65217 | 32 |
5# | 1661 | 602.0469597 | 301.0234798 | 301 | 65235 | 33 |
6 | 1760 | 568.1818182 | 284.0909091 | 284 | 65252 | 34 |
6# | 1865 | 536.1930295 | 268.0965147 | 268 | 65268 | 35 |
7 | 1976 | 506.0728745 | 253.0364372 | 253 | 65283 | 36 |
接下来开始新创建一个工程:蜂鸣器播放音乐(天空之城)
把之前写好的定时器模块和Delay函数模块添加进来
接下来就对本节代码的每一个模块进行讲解,PS:讲解重点在代码的注释里。
准备工作
为了方便可以重定义端口号(如果最后整个程序写完之后蜂鸣器没有响,那么请看看自己的板子是不是普中的A2开发板,如是,则要把P1^5改成P2^5):
//蜂鸣器端口定义
sbit Buzzer=P1^5;
乐理zhonggu,一个四分音符所用时间是500ms,重定义四分音符的时间,把500ms重命名为Speed:
//播放速度,值为四分音符的时长(ms)
#define SPEED 500
然后我们把表1上的音符与索引重定义:
//音符与索引对应表,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
其中0在乐谱上表示休止符,表示不弹,这里是没有声音的
然后根据表1上的频率和索引弄成一个数组:
//索引与频率对照表
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,
};
接下来我们就把天空之城的乐谱中的每一个音符放进一个数组中
这是《天空之城》的乐谱:
这里我们定义十六分音符为1个时长(125ms),则八分音符就是2个时长,四分音符就是4个时长(500ms),二音符就是8个时长,全音符就是16个时长。时长即下面写的时值。
//乐谱
unsigned char code Music[]={
//音符,时值,
//1
P, 4,
P, 4,
P, 4,
M6, 2,
M7, 2,
H1, 4+2,
M7, 2,
H1, 4,
H3, 4,
M7, 4+4+4,
M3, 2,
M3, 2,
//2
M6, 4+2,
M5, 2,
M6, 4,
H1, 4,
M5, 4+4+4,
M3, 4,
M4, 4+2,
M3, 2,
M4, 4,
H1, 4,
//3
M3, 4+4,
P, 2,
H1, 2,
H1, 2,
H1, 2,
M7, 4+2,
M4_,2,
M4_,4,
M7, 4,
M7, 8,
P, 4,
M6, 2,
M7, 2,
//4
H1, 4+2,
M7, 2,
H1, 4,
H3, 4,
M7, 4+4+4,
M3, 2,
M3, 2,
M6, 4+2,
M5, 2,
M6, 4,
H1, 4,
//5
M5, 4+4+4,
M2, 2,
M3, 2,
M4, 4,
H1, 2,
M7, 2+2,
H1, 2+4,
H2, 2,
H2, 2,
H3, 2,
H1, 2+4+4,
//6
H1, 2,
M7, 2,
M6, 2,
M6, 2,
M7, 4,
M5_,4,
M6, 4+4+4,
H1, 2,
H2, 2,
H3, 4+2,
H2, 2,
H3, 4,
H5, 4,
//7
H2, 4+4+4,
M5, 2,
M5, 2,
H1, 4+2,
M7, 2,
H1, 4,
H3, 4,
H3, 4+4+4+4,
//8
M6, 2,
M7, 2,
H1, 4,
M7, 4,
H2, 2,
H2, 2,
H1, 4+2,
M5, 2+4+4,
H4, 4,
H3, 4,
H3, 4,
H1, 4,
//9
H3, 4+4+4,
H3, 4,
H6, 4+4,
H5, 4,
H5, 4,
H3, 2,
H2, 2,
H1, 4+4,
P, 2,
H1, 2,
//10
H2, 4,
H1, 2,
H2, 2,
H2, 4,
H5, 4,
H3, 4+4+4,
H3, 4,
H6, 4+4,
H5, 4+4,
//11
H3, 2,
H2, 2,
H1, 4+4,
P, 2,
H1, 2,
H2, 4,
H1, 2,
H2, 2+4,
M7, 4,
M6, 4+4+4,
P, 4,
0xFF //终止标志,防止数组越界之后乱音,用一个最大值来做一个终止标志,也可以设别的值
};
定义两个变量
//FreSelect就是FreqTable[]中的元素的下标,
//MusicSelect就是Music[]中的元素下标
unsigned char FreqSelect,MusicSelect;
主程序
void main()
{
Timer0Init();//定时器初始化,1ms后溢出调到中断函数中执行中断
//之后每次中断函数执行完之后返回主程序中进入while循环
while(1)
{
if(Music[MusicSelect]!=0xFF) //如果不是停止标志位
{
FreqSelect=Music[MusicSelect]; //选择音符对应的频率
//选择好Music[]里的音符(索引/编号的重定义)之后赋值给FreqTable[]的下标FreqSelect,
//然后在中断函数里就把FreqSelect这个下标对应的重装载值赋值给TL0和TH0,
//也就是让它产生对应的频率即可发出对应的音符
MusicSelect++;//Music[]的下标+1就是该音符的时长的下标
Delay(SPEED/4*Music[MusicSelect]); //选择音符对应的时值
//SPEED是500ms
//SPEED/4=一个十六分音符的时间125ms
//125ms*Music[MusicSelect]=125ms*时值=某个音符的时长
MusicSelect++;//Music[]的下标+1就是下一个音符的下标
//为了停顿后进入下一次while循环的MusicSelect的初值
TR0=0;//关闭定时器
Delay(5); //音符间短暂停顿
TR0=1;//开启定时器
}
else //如果是停止标志位0xFF
{
TR0=0;//关闭定时器
while(1);//程序就一直停在这里了
}
}
}
中断函数
//如果不是休止符,定时器初始化后第一次进来时是FreqTable[0]=0,没有出声,
//第二次进来中断函数才真正开始选(重装载值)
void Timer0_Routine() interrupt 1 //中断函数每1ms进来一次
{
if(FreqTable[FreqSelect])
{
/*取对应频率值的重装载值到定时器*/
TL0 = FreqTable[FreqSelect]%256; //设置定时初值
TH0 = FreqTable[FreqSelect]/256; //设置定时初值
Buzzer=!Buzzer; //翻转蜂鸣器IO口
}
}
效果请看视频:
蜂鸣器播放音乐
蜂鸣器播放音乐部分完成!
本节的源码放在评论区了,自取!
如有问题,可评论区留言!