自学内容网 自学内容网

【51单片机】蜂鸣器演奏音乐——小星星&天空之城

学习使用的开发板:STC89C52RC/LE52RC
编程软件:Keil5
烧录软件:stc-isp

开发板实图:
在这里插入图片描述

蜂鸣器

蜂鸣器在开发板的位置如下:
在这里插入图片描述
蜂鸣器是一种将电信号转换为声音信号的器件,常用来产生设备的按键音、报警音等提示信号

蜂鸣器按驱动方式可分为有源蜂鸣器无源蜂鸣器

  • 有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发生,频率固定。可用于按键音,报警音
  • 无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音

在这里插入图片描述

蜂鸣器的原理图如下:

在这里插入图片描述

  • VCC:电源正极
  • BZ1:蜂鸣器
  • BEEP:蜂鸣器I/O口

本单片机使用五线四项步进电机 ULN2003D 驱动电路
在这里插入图片描述

可以看到 BEEP 连通 P25

常见的蜂鸣器驱动电路为三极管驱动

在这里插入图片描述

  • Buzzer:蜂鸣器
  • VCC:电源正极
  • GND:电源接地

三极管分为 NPNPNP,其中 N 为 N(Native)型半导体,P 为 P(Positive)型半导体。
三极管认识可参看B站“爱上半导体”《三极管是如何导电?超形象动画让你一看就懂!》、《三极管的饱和与放大》

此处了解,NPN型,R1处给高电平导通,低电平截止;PNP型,R1处给低电平导通,高电平截止

按键发声

蜂鸣器的使用很简单,因为只有一个I/O口——BEEP(P2^5)
控制蜂鸣器发声有两个因素——频率时长

频率控制蜂鸣器发声的音高,时长控制此次发声持续多久。代码如下:

#include <REGX52.h>
#include <INTRINS.h>
#include "Delay.h"

sbit Buzzer_BZ = P2^5;
/**
  * @brief软件延时500us
  * @parm无
  * @retval无
  */
void Delay500us()//@11.0592MHz
{
unsigned char i;

_nop_();
i = 227;
while (--i);
}
/**
  * @brief控制蜂鸣器发声时长
  * @parmmx:发声的时长
  * @retval无
  */
void Buzzer_Time(unsigned int mx)
{
unsigned int i;
//若传入mx:500,预期是响500ms
//但一次for循环耗时0.5ms,若要响500ms,for循环次数要加倍
for(i = 0; i < 2 * mx; ++i)
{
Buzzer_BZ = !Buzzer_BZ;
Delay500us();//每1ms响一次,1ms对应频率为1KHz
}
}

Buzzer_Time() 为发声逻辑,通过控制Buzzer_BZ(P2^5)变化的频率实现发声频率,500us变化一次,1 -> 0 -> 1为1ms,则频率为1KHz

完整代码如下:

蜂鸣器模块
Buzzer.h

#ifndef __BUZZER_H__
#define __BUZZER_H__

void Buzzer_Time(unsigned int mx);

#endif

Buzzer.c

#include <REGX52.h>
#include <INTRINS.h>
#include "Delay.h"

sbit Buzzer_BZ = P2^5;

/**
  * @brief软件延时500us
  * @parm无
  * @retval无
  */
void Delay500us()//@11.0592MHz
{
unsigned char i;

_nop_();
i = 227;
while (--i);
}


/**
  * @brief控制蜂鸣器发声时长
  * @parmmx:发声的时长
  * @retval无
  */
void Buzzer_Time(unsigned int mx)
{
unsigned int i;
//若传入mx:500,预期是响500ms
//但一次for循环耗时0.5ms,若要响500ms,for循环次数要加倍
for(i = 0; i < 2 * mx; ++i)
{
Buzzer_BZ = 1;
Delay500us();//每2ms响一次,1ms对应频率为1KHz,2ms对应频率为500Hz
}
}

按键模块

SoleKey.h

#ifndef __SOLEKEY_H__
#define __SOLEKEY_H__

unsigned char SoleKey();

#endif

SoleKey.c

#include <REGX52.h>
#include "Delay.h"
/**
  * @brief  获取独立按键的键码(哪个被按下)
  * @parm无
  * @retval独立按键的键码
  */
unsigned char SoleKey()
{
unsigned char keyNum = 0;
if(P3_1 == 0){Delayms(20);while(P3_1 == 0);Delayms(20);keyNum = 1;}
if(P3_0 == 0){Delayms(20);while(P3_0 == 0);Delayms(20);keyNum = 2;}
if(P3_2 == 0){Delayms(20);while(P3_2 == 0);Delayms(20);keyNum = 3;}
if(P3_3 == 0){Delayms(20);while(P3_3 == 0);Delayms(20);keyNum = 4;}
return keyNum;
}

延时器模块

Delay.h

#ifndef __DELAY_H__
#define __DELAT_H__

void Delayms(unsigned int xms);//等待指定毫秒

#endif

Delay.c

/**
  * @brief  延迟一定时间
  * @parm延迟的时间,单位是毫秒,范围:0 ~ 65535
  * @retval无
  */
void Delayms(unsigned int xms)//@12.000MHz
{
while(xms--)
{
unsigned char i, j;

i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}

数码管模块

Nixie.h

#ifndef __NIXIE_H__
#define __NIXIE_H__

void LightNixieTube(unsigned char location, unsigned char num);

#endif

Nixie.c

#include <REGX52.H>
#include "Delay.h"

//指定的数字有哪些地方要亮,对应数值的数组
unsigned char NixieTubeNums[] = {0x3F, 0x06, 0x5B, 0x4F,0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};
/**
  * @brief 显示指定位置指定数字
  * @parm 第一个参数是从左往右要显示的位置 范围: 1~8
  * @parm 第二个参数是要显示的数字 范围: 0~9
  * @retval 无
  */
void LightNixieTube(unsigned char location, unsigned char num)
{
//第一个位置是LED8,对应38译码器111
switch(location)
{
case 1:P2_4 = 1;P2_3 = 1;P2_2 = 1;break;
case 2:P2_4 = 1;P2_3 = 1;P2_2 = 0;break;
case 3:P2_4 = 1;P2_3 = 0;P2_2 = 1;break;
case 4:P2_4 = 1;P2_3 = 0;P2_2 = 0;break;
case 5:P2_4 = 0;P2_3 = 1;P2_2 = 1;break;
case 6:P2_4 = 0;P2_3 = 1;P2_2 = 0;break;
case 7:P2_4 = 0;P2_3 = 0;P2_2 = 1;break;
case 8:P2_4 = 0;P2_3 = 0;P2_2 = 0;break;
}
//指定的数字有哪些地方要亮
P0 = NixieTubeNums[num];
}

主程序——获取按键键码,显示在数码管上,并发出按键提示音

main.c

#include <REGX52.h>
#include "Buzzer.h"
#include "Nixie.h"
#include "SoleKey.h"


void main()
{
unsigned char Key;
LightNixieTube(1, 0);
    while(1)
    {
        Key = SoleKey();
if(Key)
{
LightNixieTube(1, Key);
Buzzer_Time(100);
}
    }
}

项目链接:蜂鸣器——按键发声

无源蜂鸣器演奏音乐

简单乐理

在这里插入图片描述

c1为中央C,中音1,最中间的按键,以此划分区域
钢琴上每8个白键一组,算上黑键则12一组,例如从c ~ b
相邻的黑白键相差半音,升半音符号和降半音符号如下:
在这里插入图片描述
一组按键对应简谱如下:
在这里插入图片描述

相邻组的音高相差八度,如小字1组的 c 比小字组的 c 高八度。对应简谱如下:
在这里插入图片描述


无源蜂鸣器通过调整振荡脉冲的频率实现发出不同频率的声音。

我们通过定时器中断函数实现频率的变化,定时器使用参看【51单片机】定时器

定时器中断函数模版如下:

//定时器中断函数——实现蜂鸣器不同音高发声
void Timer0_Rountine(void) interrupt 1
{
TH0 = ??
TL0 = ??
}

定时器逻辑简单描述就是:

  1. 有两个8位计数器,TH0 和 TL0,TH0 存储高位数据。TL0 存储低位数据。两个计数器记录完整的数据,范围:0 ~ 65535
  2. 根据系统频率,如12MHz,12T模式,则 12 / 12 = 1MHz,即每 1us 对计数器加一
  3. 当计数器溢出时,发出定时器中断,执行上述定时器中断函数
  4. 通过设置 TH0 和 TL0 重载值,可以实现不同时间发出中断,如 TH0 和 TL0 组合为 64535,距离溢出还有 1000,则溢出还需 1000us,即1ms,如此就实现了每1ms发出一次中断

如何通过定时器实现不同频率的发声呢?—— 通过设置 TH0 和 TL0 重装值,并在中断函数中改变BEEP状态,就可以实现每隔一段时间改变BEEP状态,这就是频率。
不同的重装值,就会对应不同的频率,发出不同的音高

对此,我们需要获取相应音符对应的重装值

频率与C调音符对照表如下:

在这里插入图片描述

后续曲目只涉及小字组(低音) ~ 小字2组(高音)
对频率进行如下转变:如低音1
262Hz = 0.000262MHz = 3816.793893us(周期)

因为BEEP进行两次操作为一次发生,所以周期需要 / 2
3816.793893 / 2 = 1908.396947,取整为1908,即需要每1908us 对 BEEP 状态改变一次

重装值:65535 - 1908 = 63628
重装值如下:

在这里插入图片描述

有了重装值就可以进行编码了

小星星

小星星简谱如下:

在这里插入图片描述

重装值可以设置频率,音高,但还需要控制音符的演奏时长
对应时长,有二分音符,四分音符…
在这里插入图片描述
音符之间为两倍差值
定义四分音符——500ms为基准,二分音符则持续1000ms,八分音符持续250ms,十六分音符持续125ms

简谱的左侧4/4表示以四分音符为一拍,每小节四拍
第二小节6 6 5 -,每个音符都是四分音符,持续500ms,但-表示延续,即5 - ,表示中音5延迟一拍,持续两个四分音符,1000ms

小星星较为简单,到此就可以进行编码了


先定义每个音符对应的重装值

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

0表示休止符,不发声

再对音符进行编号,对应重装值下标,并设置基准四分音符时长为500ms

//播放速度,值为四分音符的时长(ms)
#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

其中,L1_ 表示 L1 升半音,也就是 c 右侧的黑键

接下来就可以扒谱了,将简谱抽象成一个数组,每两个元素为一组,分别为频率对应重装值的下标 和 持续时长,持续时长以125ms为单位
1 1 5 5 | 6 6 5 -,转化为数组为

M1,4,
M1,4,
M5,4,
M5,4,

M6,4,
M6,4,
M5,4+4

小星星简谱如下:

//小星星乐谱
//两个一组:频率索引,播放时长
unsigned char code Music[] = {
//S1
M1, 4,
M1, 4,
M5, 4,
M5, 4,
//S2
M6, 4,
M6, 4,
M5, 4+4,
//S3
M4, 4,
M4, 4,
M3, 4,
M3, 4,
//S4
M2, 4,
M2, 4,
M1, 4+4,
//S5
M5, 4,
M5, 4,
M4, 4,
M4, 4,
//S6
M3, 4,
M3, 4,
M2, 4+4,
//S7
M5, 4,
M5, 4,
M4, 4,
M4, 4,
//S8
M3, 4,
M3, 4,
M2, 4+4,
//S9
M1, 4,
M1, 4,
M5, 4,
M5, 4,
//S10
M6, 4,
M6, 4,
M5, 4+4,
//S11
M4, 4,
M4, 4,
M3, 4,
M3, 4,
//S12
M2, 4,
M2, 4,
M1, 4+4,
//终止符
0xFF
};

最后用终止符防止数组越界,在遇到终止符时关闭定时器

演奏音乐的逻辑如下:

//控制频率  遍历乐谱
unsigned char FrequencySelect, MusicSelect;

void main()
{
Timer0_Init();
    while(1)
    {
//终止符:音乐结束
        if(Music[MusicSelect] != 0xFF)
{
FrequencySelect = Music[MusicSelect];//选择音符对应频率
MusicSelect++;
Delayms(SPEED / 4 * Music[MusicSelect]);//选择播放时长
MusicSelect++;
//相邻音符停顿一下下
TR0 = 0;
Delayms(5);
TR0 = 1;
}
else//终止符,停止播放
{
TR0 = 0;//关闭定时器
while(1);
}
    }
}

//蜂鸣器按频率发声
void Timer0_Rountine(void) interrupt 1
{
//如果不是休止符
if(Frequency[FrequencySelect])
{
TH0 = Frequency[FrequencySelect] / 256;//高8位
TL0 = Frequency[FrequencySelect] % 256;//低8位
Buzzer_BZ = !Buzzer_BZ;
}
}
  • 在主函数中,遍历乐谱,选择对应频率的下标。
  • Delayms 实现发声的持续时长,在Delayms 前和 Delayms期间,通过定时器中断实现不同频率的发声
  • 遇到休止符则不改变Buzzer_ER,不发声
  • 遇到终止符则关闭定时器,停止演奏

天空之城

在这里插入图片描述

对一些音符进行说明

在这里插入图片描述半音,即四分音符的一半——八分音符,对应频率和时长如下:

M6, 2,
M7, 2,

在这里插入图片描述延音,延长原音符的一半,即四分音符+八分音符,对应频率和时长如下:

H1, 4+2,

在这里插入图片描述升半音,对应频率和时长如下:

M4_, 2,

在这里插入图片描述连音,连线的音符为同一音,对应频率和时长如下:

H1, 2,
M7, 2+2,
H1, 2+4

在这里插入图片描述循环符号,回到乐谱最开始循环

完整乐谱如下:

//天空之城乐谱
unsigned char code Music[] = {
//S1
P,4,
P,4,
P,4,
M6,2,
M7,2,
//S2
H1,4+2,
M7,2,
H1,4,
H3,4,
//S3
M7,4+4+4,
M3,2,
M3,2,
//S4
M6,4+2,
M5,2,
M6,4,
H1,4,
//S5
M5,4+4+4,
M3,4,
//S6
M4,4+2,
M3,2,
M4,4,
H1,4,
//S7
M3,4+4,
P,2,
H1,2,
H1,2,
H1,2,
//S8
M7,4+2,
M4_,2,
M4,4,
M7,4,
//S9
M7,4+4,
P,4,
M6,2,
M7,2,
//S10
H1,4+2,
M7,2,
H1,4,
H3,4,
//S11
M7,4+4+4,
M3,2,
M3,2,
//S12
M6,4+2,
M5,2,
M6,4,
H1,4,
//S13
M5,4+4+4,
M2,2,
M3,2,
//S14
M4,4,
H1,2,
M7,2+2,
H1,2+4,
//S15
H2,2,
H2,2,
H3,2,
H1,2+4+4,
//S16
H1,2,
M7,2,
M6,2,
M6,2,
M7,4,
M5_, 4,
//S17
M6,4+4+4,
H1,2,
H2,2,
//S18
H3,4+2,
H2,2,
H3,4,
H5,4,
//S19
H2,4+4+4,
M5,2,
M5,2,
//S20
H1,4+2,
M7,2,
H1,4,
H3,4,
//S21
H3,4+4+4+4,
//S22
M6,2,
M7,2,
H1,4,
M7,4,
H2,2,
H2,2,
//S23
H1,4+2,
M5,2+4+4,
//S24
H4,4,
H3,4,
H2,4,
H1,4,
//S25
H3,4+4+4,
H3,4,
//S26
H6,4+4,
H5,4,
H5,4,
//S27
H3,2,
H2,2,
H1,4+4,
P,2,
H1,2,
//S28
H2,4,
H1,2,
H2,2,
H2,4,
H5,4,
//S29
H3,4+4+4,
H3,4,
//S30
H6,4+4,
H5,4+4,
//S31
H3,2,
H2,2,
H1,4+4,
P,2,
H1,2,
//S32
H2,4,
H1,2,
H2,2+4,
M7,4,
//S33
M6,4+4+4,
P,4,
0xFF//终止标志
};

项目链接:蜂鸣器——播放音乐

效果如下:

51单片机蜂鸣器演奏天空之城


以上就是本篇博客的所有内容,感谢你的阅读
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。
在这里插入图片描述


原文地址:https://blog.csdn.net/m0_72563041/article/details/143606167

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!