当前位置: 首页 > article >正文

51 单片机[11]:蜂鸣器播放提示音和音乐

1. 蜂鸣器

蜂鸣器是一种将电信号转换为声音信号的器件,常用来产生设备的按键音、报警音等提示信号。
蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器。

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

51开发板原理图上的蜂鸣器:

img

img

51单片机开发板上,蜂鸣器连接在ULN2003D上。这种连接方式属于集成电路驱动,比较少见,一般用三极管驱动。下图展示了用三极管驱动蜂鸣器的原理图。
img

ULN2003D 是单片集成的高耐压、大电流达林顿管阵列,电路内部包含 7 个独立的达林顿管驱动通道。电路内部设计有续流二极管,可用于驱动继
电器、步进电机等电感性负载。单个达林顿管集电极可输出 500mA 电流,将多个通道并联可实现更高的输出电流能力。该电路可广泛应用于继电器驱动、照明驱动、显示屏驱动(LED)、步进电机驱动和逻辑缓冲器。

达林顿管驱动电路图如下图所示。

img

ULN2003D数据手册中的逻辑图如下图所示。

img

由逻辑图和原理图可知,只要控制 U14 的引脚5,就可以控制蜂鸣器。

注意:无源蜂鸣器不能一直通电,容易被烧毁。无源蜂鸣器通常由一个线圈和一个振动膜组成,当给无源蜂鸣器施加变化的电压信号时,线圈中的电流会产生变化的磁场,这使得与线圈相连的振动膜振动,从而产生声音。

由开发板的原理图可知,蜂鸣器是默认被使用的。因为单片机上电后P2口默认是高电平,那么P25为1,BEEP就为0。这个开发板因为有限流电阻,所以没有烧。

2. 乐理

在让蜂鸣器播放声音之前,需要补充一些乐理知识:

img
img
img

3. 蜂鸣器播放提示音

3.1 独立按键控制蜂鸣器播放提示音

新建项目,把定时器时钟项目的Delay.c和Delay.h文件,按键控制LED流水灯模式项目的Key.c和Key.h和模块化编程项目的Digital.c和Digital.h复制过来,然后添加进项目。

img

在main.c中包含头文件:

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

先写一个检测独立按键键码的程序。按下K1,数码管显示1,按下K2,数码管显示2……

unsigned char KeyNum;

void main()
{
    digital(1, 0);//什么都不按,显示0
	while(1)
	{
		KeyNum = Key();//返回键码值
		if(KeyNum)
		{
			digital(1, KeyNum);//在数码管第一位显示KeyNum
		}
	}
}

下面实现显示键码的同时,蜂鸣器发声。定义一个Buzzer控制蜂鸣器,定义一个循环次数i控制蜂鸣器振动时长。

sbit Buzzer = P2^5;
unsigned char KeyNum;
unsigned int i;

void main()
{
	digital(1, 0);//什么都不按,显示0
	while(1)
	{
		KeyNum = Key();//返回键码值
		if(KeyNum)
		{
			for(i=0;i<100;i++)//以500Hz的频率响100毫秒
			{
				Buzzer = !Buzzer;//翻转100次
				Delay(1);
			}
			digital(1, KeyNum);//在数码管第一位显示KeyNum
		}
	}
}

为什么是500Hz?

高电平1ms、低电平1ms, 1 ÷ ( 0.001 s + 0.001 s ) = 500 Hz 1÷(0.001 \text{s} + 0.001 \text{s})=500\text{Hz} 1÷(0.001s+0.001s)=500Hz

3.2 程序模块化

新建Buzzer.c和Buzzer.h文件。

Buzzer.h:

#ifndef __BUZZER_H__
#define __BUZZER_H__

void Buzzer_Time(unsigned int ms);

#endif

在Buzzer.c文件中,定义Buzzer_Time函数,把刚刚的for循环封装到Buzzer_Time函数里

void Buzzer_Time(unsigned int ms)		//@12.000MHz
{
	
	for(i=0;i<ms;i++)//以500Hz的频率响ms毫秒
	{
		Buzzer = !Buzzer;//翻转ms*2次
		Delay(1);
	}
}

新定义一个函数,替换一下Delay(1);

在STC-ISP上的“软件延时计算器”中生成Delay500us()函数
img

给函数名加个蜂鸣器前缀,把这个函数作为蜂鸣器私有函数。

void Buzzer_Delay500us()		//@12.000MHz
{
	unsigned char i;

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

注意,调用_nop_();函数,需要包含头文件INTRINS.H

现在可以修改一下Buzzer_Time函数

void Buzzer_Time(unsigned int ms)		//@12.000MHz
{
	
	for(i=0;i<ms*2;i++)//以1000Hz的频率响ms毫秒
	{
		Buzzer = !Buzzer;//翻转ms*2次
		Buzzer_Delay500us();//每次循环延时500us
	}
}

独立按键按一下响100毫秒。

4. 蜂鸣器播放音乐

本节用定时器中断来控制蜂鸣器的状态

需要定时器时钟项目中的Delay.h、Delay.c、Timer0.c和Timer0.h。

先做一下准备工作,在main.c中包含头文件

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

声明一下位端口,搭好主函数

sbit Buzzer = P2^5;

void main()
{
	Timer0Init();
	while(1)
	{
		
	}
}

其中Timer0Init()是1毫秒的定时器中断(在Timer0.c文件中)。

把Timer0.c中的定时器中断函数模板复制过来。定时器0溢出后触发中断。Timer0_Rountine()函数体内部是中断后执行的操作。

void Timer0_Rountine() interrupt 1
{
	static unsigned int T0Count;	//静态变量,退出函数后不丢失值
	T0Count++;
	TL0 = 0x18;		//重新赋初值,防止溢出后从0开始计数
	TH0 = 0xFC;
	if(T0Count>=1000)	//每隔一秒执行一次
	{
		T0Count = 0;
		
	}
}

把函数修改为中断后翻转蜂鸣器状态:

void Timer0_Rountine() interrupt 1
{
	TL0 = 0x18;		//重新赋初值,防止溢出后从0开始计数
	TH0 = 0xFC;
	Buzzer = !Buzzer;
}

编译一下,下载程序,给单片机上电,发现蜂鸣器一直响。

为什么蜂鸣器一直响?

因为主函数中Timer0Init()每隔1毫秒触发一次定时器中断,每次中断执行的任务都是翻转Buzzer的状态,这就相当于给蜂鸣器输入了一个频率为 500Hz 的方波,导致它持续发声。

8051单片机的机器周期是晶振频率的1/12,因此在12MHz晶振下,机器周期为

机器周期 = 1 12 × 1 0 6 = 1 μ s 机器周期=\frac{1}{12\times10^6}=1\mu \text{s} 机器周期=12×1061=1μs

0xFFFF − 0xFC18 = 0x3E7 = 999 \text{0xFFFF} - \text{0xFC18} = \text{0x3E7} = 999 0xFFFF0xFC18=0x3E7=999,即计数器需要从0xFC18计数999次才能溢出。

每次计数时间为1微秒, 999 × 1 μ s ≈ 1 ms 999\times 1\mu \text{s} \approx 1\text{ms} 999×1μs1ms,所以计数器每过1毫秒就会溢出一次,触发中断。

高电平 1 ms + 低电平 2 ms = 一个周期 2 ms 高电平1\text{ms}+低电平2\text{ms}=一个周期2\text{ms} 高电平1ms+低电平2ms=一个周期2ms

1 ÷ 0.002 s = 500 Hz 1÷0.002\text{s}=500\text{Hz} 1÷0.002s=500Hz

main() 中的 while(1) 循环是空的,没有任何条件来控制蜂鸣器的开关。
每次定时器中断都会反转蜂鸣器的状态,因此蜂鸣器持续工作。

如何调整蜂鸣器振动频率?

改变 TL0TH0 可以调整定时器溢出的速度,从而改变蜂鸣器的振荡频率。注意是改变Timer0_Rountine()中的 TL0TH0 ,而不是改变Timer0Init()中的 TL0TH0Timer0Init()中的 TL0TH0只决定了第一次中断的时间,以后的中断时间由Timer0_Rountine()中的 TL0TH0决定。

如果Timer0Init()中的 TL0TH0 变大,那溢出的时间就变短,周期就变短,频率就变高。

如果要播放中央C音符,它的重装载值是64580。

img

只需要给Timer0Init()中的 TL0TH0 赋新值即可

	TL0 = 64580%256;//取余,放在低位
	TH0 = 64580/256;//取整,放在高位

为了方便找音符对应的重装载值,把低、中、高音放在数组中

unsigned int FreqTable[] = {
	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直接写成FreqTable[0]即可:

	TL0 = FreqTable[1]%256;
	TH0 = FreqTable[1]/256;

试试好不好用,定义一个频率选择变量FreqSelect

unsigned char FreqSelect;

void main()
{
	Timer0Init();
	while(1)
	{
		FreqSelect++;
		Delay(500);
	}
}

void Timer0_Rountine() interrupt 1
{
	//重新给TL0和TH0赋初值,防止溢出后从0开始计数
	TL0 = FreqTable[FreqSelect]%256;
	TH0 = FreqTable[FreqSelect]/256;
	Buzzer = !Buzzer;
}

可以听到,音调越来越高。

如何指定音调,响出曲子《小星星》呢?定义一个数组Music[],根据索引表格把数填进去,再定义一个MusicSelect去选Music[]里的数。

unsigned char FreqSelect, MusicSelect;
unsigned char Music[] = {12,12,19,19,21,21,19,17,17,16,16,14,14,12};

void main()
{
	Timer0Init();
	while(1)
	{
		FreqSelect = Music[MusicSelect];
		MusicSelect++;
		Delay(500);
	}
}

void Timer0_Rountine() interrupt 1
{
	//重新给TL0和TH0赋初值,防止溢出后从0开始计数
	TL0 = FreqTable[FreqSelect]%256;
	TH0 = FreqTable[FreqSelect]/256;
	Buzzer = !Buzzer;
}

编译一下,发现这些音之间没有停顿,需要通过控制定时器TR0加一个停顿。

void main()
{
	Timer0Init();
	while(1)
	{
		FreqSelect = Music[MusicSelect];
		MusicSelect++;
		Delay(500);
		//断奏
		TR0 = 0;//关掉定时器
		Delay(5);
		TR0 = 1;//打开定时器
	}
}

虽然这下有了停顿,但是像“5 - ”这种带延时符号的,时长不对。可以再定义一个数组控制音符时长,也可以更改Music[],音符后面紧跟音符时长。这里选择后者。

unsigned char Music[] = {
	13,4,
	13,4,
	20,4,
	20,4,
	22,4,
	22,4,
	20,4+4,
	18,4,
	18,4,
	17,4,
	17,4,
	15,4,
	15,4,
	13,4+4,
};

音符的时长,可以以最短的时值作为1来写。如果最短的是四分音符,那么把四分音符当作1,二分音符就为2,全音符就为4。最后只需要用一个系数乘以这些1、2、4就能表示时长。在这里选用125(十六分音符的时长)作为这个系数。

void main()
{
	Timer0Init();
	while(1)
	{
		FreqSelect = Music[MusicSelect];//取的是音调
		MusicSelect++;//此时Music[MusicSelect]是时长
		Delay(125*Music[MusicSelect]);
		MusicSelect++;
		//断奏
		TR0 = 0;//关掉定时器
		Delay(5);
		TR0 = 1;//打开定时器
	}
}

为了方便更改系数,做一个宏定义

#define SPEED 500

Delay(125*Music[MusicSelect]);改为

Delay(SPEED/4*Music[MusicSelect]);

这样每次只需要调整SPEED既可以调整速度。

现在没有休止符0,需要定义休止符,只需要在FreqTable[]中加入0即可。注意更改Music[]中的索引值。

现在还有一个问题,播完设定的音乐后,蜂鸣器就乱响了,需要加一个终止判断。在Music[]的最后加一个0xFF,然后在主函数中加一个终止判断:

void main()
{
	Timer0Init();
	while(1)
	{
		if(Music[MusicSelect]!=0xFF)
		{
			FreqSelect = Music[MusicSelect];//音调
			MusicSelect++;//此时Music[MusicSelect]是时长
			Delay(SPEED/4*Music[MusicSelect]);
			MusicSelect++;
			//断奏
			TR0 = 0;//关掉定时器
			Delay(5);
			TR0 = 1;//打开定时器
		}
		else
		{
			TR0 = 0;
			while(1);
		}
	}
}

http://www.kler.cn/news/364088.html

相关文章:

  • u盘装win10系统提示“windows无法安装到这个磁盘,选中的磁盘采用GPT分区形式”解决方法
  • 算法训练(leetcode)二刷第五天 | 242. 有效的字母异位词、349. 两个数组的交集、202. 快乐数、1. 两数之和
  • Java项目-基于springboot框架的智慧外贸系统项目实战(附源码+文档)
  • PoissonRecon学习笔记
  • Java 多线程(七)—— 定时器
  • 实践OpenVINO™ GenAI
  • DNS 原理
  • 证明非平方整数阶射影平面关联矩阵的主对角线有t+1个1
  • Python 爬虫下载图片
  • 将 Docker 安装到指定目录
  • Spring Boot 中常见的注解,分类列出
  • 机房巡检机器人有哪些功能和作用
  • 【数据分析】Power BI的使用教程
  • asp.net core会话session设置滑动过期时间
  • Web3.0技术入门
  • YOLO11改进 | 主干网络 | 简单而优雅且有效的VanillaNet 【华为诺亚方舟】
  • 【rk3568】sg90舵机pwm控制
  • uniapp 获取签名证书 SHA1 自有证书签名打包
  • 什么是Kubernetes?K8s基础与工作原理
  • QMetaObject invokeMethod
  • 长城坦克正式公布全新越野架构Hi4-Z,开启越野新时代
  • STM32之EC800K 4G模块驱动
  • 网络文件系统搭建
  • k8s 外部的 Prometheus 监控 k8s 集群
  • RHCE 多IP访问多网站
  • 使用flask构建一个简单的文件同步系统