51单片机学习笔记-9蜂鸣器

news2024/11/24 17:32:19

9 蜂鸣器

[toc]

注:笔记主要参考B站江科大自化协教学视频“51单片机入门教程-2020版 程序全程纯手打 从零开始入门”。
注:工程及代码文件放在了本人的Github仓库。


9.1 蜂鸣器简介

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

有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定。
无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲(一般是PWM波)才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音。
注:两者外观几乎没有差别,购买的时候要注意。
再注:单片机上自带的蜂鸣器为无源蜂鸣器

图9-1 DS1302实物图

由于单片机引脚的驱动能力弱,所以一般不能直接用于驱动信号,而是作为开关信号来控制驱动能力更强的信号,从而驱动元器件正常工作。驱动蜂鸣器的方式主要有两种:三极管驱动(图8-2)、集成电路驱动(图8-3)。单片机采用集成电路芯片 达林顿晶体管阵列 ULN2003D 驱动蜂鸣器。

图9-2 蜂鸣器-三极管驱动方式
图9-3 蜂鸣器-集成电路驱动方式

达林顿管又称复合管。他将两个三极管串联,以组成一只等效的新的三极管。这只等效三极管的放大倍数是原二者之积,因此它的特点是 放大倍数非常高达林顿管的作用一般是在高灵敏的放大电路中放大非常微小的信号,如大功率开关电路。在电子学电路设计中,达林顿接法常用于功率放大器和稳压电源中。达林顿晶体管相当于非门,但是输出的“1”是高阻态,没有驱动能力;输出的“0”则有驱动能力。

图9-4 ULN2003逻辑框图

下面来分析乐理。无源蜂鸣器只有在由通路变成断路时才会发出声音,反之由断路变成通路并不发出声音。根据电路原理图可知,只要控制P2_5下降沿的频率就可以控制蜂鸣器发出不同的声音,而调节占空比则可以控制蜂鸣器的音量。下图8-5中,按照一组音调依次分成4组:大字组、小字组、小字1组、小字2组。组与组之间相差“8度”;相邻的两个键之间(包括白键与黑键)相差半音; 数字简谱左上标“#”表示升高半音,“b”表示降低半音,升降号在本小节有效。 下图8-6给出了“小字组、小字1组、小字2组”音符与频率的对应关系。也就是说,每一个音符都对应了一个特定的频率。
而为了按照正常的节奏演奏出整首音乐,还需要调整每个音符所占用的时间。下图8-7给出了不同的音符的时值比,可以看出在音符的右侧和下面加横线可以实现2的幂次的调整,而在右侧加点则表示乘以1.5倍。 一般设置四分音符的持续时间为500ms。
于是根据下面两张图,整体思路如下:钢琴五线谱→数字简谱→蜂鸣器频率。 通过不断改变定时器的复位值,进而控制蜂鸣器的频率变化。

图9-5 钢琴谱与数字简谱的转换关系
图9-6 数字简谱与蜂鸣器频率对应关系
图9-7 不同的音符的时值比

9.2 实验:蜂鸣器播放提示音

需求:对于四个独立按键开关,每当按键按下,蜂鸣器给出提示音,并且在数码管上显示按下的按键值。

本实验使用软件延时,完成蜂鸣器的频率调整和蜂鸣持续时间。代码如下:

- main.c

#include <REGX52.H>
#include "PushKey.h"
#include "NixieTube.h"
#include "Buzzer.h"

void main(){
  unsigned char key_num = 0;
  NixieTube(8,key_num); // 数码管显示初始化
  while(1){
    key_num = PushKey();
    if(key_num){
      NixieTube(8,key_num);
      Buzzer_Time(500);
    }
  }
}

- Buzzer.h

#ifndef __BUZZER_H__
#define __BUZZER_H__

void Buzzer_Time(unsigned int ms);//1000Hz,持续ms个毫秒

#endif

- Buzzer.c

#include <REGX52.H>

// 重新定义端口名
sbit Buzzer_con = P2^5;

/**
  * @brief :蜂鸣器专用延时函数,@11.0592MHz晶振,延时500us
  * @param :需要延时500us的个数
  * @retval :无
 */
void Buzzer_Delay100us(unsigned int cycles){
	unsigned char i;
  do{
	i = 43;
	while (--i);
  }while(--cycles);
}


/**
  * @brief :蜂鸣器以默认频率1000Hz,持续响指定时间。
  * @param :cycles持续时间(响cycles个1ms)。
  * @retval :无
 */
void Buzzer_Time(unsigned int ms){
  unsigned int i;
  for(i=0;i<ms;i++){
    Buzzer_con = !Buzzer_con;//每500us翻转一次
    Buzzer_Delay100us(5);//控制蜂鸣器频率为1000Hz
  }
}

- PushKey.h

#ifndef __PUSHKEY_H__
#define __PUSHKEY_H__

// 延时cycles ms,晶振@11.0592MHz
void PushKey_Delay(unsigned char cycles){
  unsigned char i, j;
  do{
    i = 2;
    j = 199;
    do{
      while (--j);
    }while (--i);
  }while(--cycles);
}

/**
  * @brief :检测按下了哪个按键开关
  * @param :无
  * @retval :输出按键开关编号1~4,不按返回0,松开触发
 */
unsigned char PushKey(){
  unsigned char key = 0;
  if(!P3_1)     {PushKey_Delay(10);while(!P3_1);PushKey_Delay(10);key=1;}
  else if(!P3_0){PushKey_Delay(10);while(!P3_0);PushKey_Delay(10);key=2;}
  else if(!P3_2){PushKey_Delay(10);while(!P3_2);PushKey_Delay(10);key=3;}
  else if(!P3_3){PushKey_Delay(10);while(!P3_3);PushKey_Delay(10);key=4;}
  return key;
}

#endif

- NixieTube.h

#ifndef __NIXIETUBE_H__
#define __NIXIETUBE_H__

void NixieTube(unsigned char led, unsigned char num){
  // 给出数字0~9的定义(符合数组的索引)
  unsigned char number[10] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
  // 给出选择的LED1~LED8的定义(实际上只用P2的2/3/4引脚)
  unsigned char sel_led[8] = {0x00,0x04,0x08,0x0c,0x10,0x14,0x18,0x1c};
  P0 = 0x00; // 数码管不显示,消影
  P2 = sel_led[led-1]; // 选择数码管:LED1
  P0 = number[num];  // 数码管显示7
}

#endif

9.3 实验:蜂鸣器播放音乐

需求:使用蜂鸣器播放小星星、天空之城。

图9-8 《小星星》曲谱
图9-9 《天空之城》曲谱

由于单片机性能有限,实现播放音乐的功能之后几乎就没什么资源了,所以就不进行模块化了。为了便捷的控制蜂鸣器频率,使用定时器T0,通过对其溢出复位值得调整,以控制蜂鸣器周期。下面是代码的调用逻辑:

图9-10 “蜂鸣器播放音乐”的代码调用关系

代码展示:

- main.c

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

// 下面的乐谱可以替换
/***************************************************/
// 《小星星》乐谱定义
#define  music_len 42//指定乐谱长度
unsigned char code music_freq_sel[2][music_len] = {
{12,12,19,19, 21,21,19,    17,17,16,16, 14,14,12,    19,19,17,17, 16,16,14,   
 19,19,17,17, 16,16,14,    12,12,19,19, 21,21,19,    17,17,16,16, 14,14,12   },
{4,4,4,4, 4,4,8, 4,4,4,4, 4,4,8, 4,4,4,4, 4,4,8,
 4,4,4,4, 4,4,8, 4,4,4,4, 4,4,8, 4,4,4,4, 4,4,8}};
/***************************************************/

// 通用延时函数,1ms
void Delay1ms(unsigned int cycles){
  unsigned char i, j;
  do{
    i = 2;
    j = 199;
    do{
      while (--j);
    }while (--i);
  }while(--cycles);
}

// 重新定义端口名
sbit Buzzer_con = P2^5;

// 定义四分音符时长(500ms)
#define SPEED 500

//不同频率所对应的定时器重装载值
unsigned int freq_reload[37] =
{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};
//定义所需要选择的频率索引
unsigned char freq_sel = 0;
//定义音乐的索引变换,时长变换(时长以1/16音符为基准)
#define N0  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

void main(){
  unsigned int i;
  //定时器T0初始化
  Timer0_Init();
  freq_sel = 0;
  while(1){
    for(i=0;i<music_len;i++){
      freq_sel = music_freq_sel[0][i];
      Delay1ms(SPEED/4*music_freq_sel[1][i]);
      TR0 = 0;
      Delay1ms(5);
      TR0 = 1;
    }
    TR0 = 0;
    Delay1ms(5000); //暂停一段时间
    TR0 = 1;
  }
}

// 定义定时器T0中断后要执行的动作
void Timer0_Routine() interrupt 1{
  //当频率不是“0”,才进行相应的振荡
  if(freq_reload[freq_sel]){
    TH0 = freq_reload[freq_sel]/256;
    TL0 = freq_reload[freq_sel]%256;
    Buzzer_con = !Buzzer_con;
  }  
}

- Timer0.h

#ifndef __TIMER0_H__
#define __TIMER0_H__

#include <REGX52.H>
/**
  * @brief :对定时器0进行初始化,初始化完成后定时器0即可正常工作。
  * 注:对11.0592MHz进行12分频(脉冲周期1.0850694us)。
  * 注:配置过程中,由于掉电复位后中断都默认不开启,所以只需配置定时器0
  *     相关的寄存器即可,不要定义其他中断的寄存器,以保证程序的复用性。
 */
void Timer0_Init(){
  // 配置定时器T0的相关寄存器
  TMOD&=0xf0; TMOD|=0x01; // 选择T0的GATE=0/允许计数/模式1
  // 上面这个方法目的是不干扰高四位,对低四位先清零再加值。
  TF0 = 0; TR0 = 1; // 溢出标志位清空,运行控制位置1
  TH0 = 0xfc; TL0 = 0x66; // 离溢出近似1ms
  // 注:上面这个初值只在第一次溢出生效,后面都是从0开始计数。
  // 配置中断寄存器
  EA = 1; ET0 = 1; // 不屏蔽所有中断,允许T0溢出中断
  PT0 = 0; // T0优先级保持默认,不写这句话也可以
}

/*中断函数模板
// 定义定时器T0中断后要执行的动作
void Timer0_Routine() interrupt 1{
  static unsigned int count_T0; //中断次数
  count_T0++; //更新中断次数
  TH0 = 0xfc; TL0 = 0x66; // 恢复溢出周期,近似1ms
  if(count_T0>500){
    count_T0 = 0;
    
  }  
}
*/
#endif

编程感想:

  1. 关于位运算。在FPGA中经常使用位运算提升运算效率,即使位宽不同也可以很方便的指定位的索引;但是在单片机中,运算中通常不能指定位的索引,所以在进行不同位宽的位运算时,会出现一些奇怪的bug。即,在单片机中,除了一些特殊的场合(如寄存器赋值),应避免使用复杂的位运算。
  2. 愚蠢的错误:音乐播放完之后,不会继续播放,而是持续的嘈杂。原因:定义音乐的数组长度太长了,导致最后的音是空的。即数组的溢出错误,代码飘到哪里就不知道了。
  3. 下面是其他音乐的一些乐谱:
/***************************************************/
// 《天空之城》曲谱
#define  music_len 121//指定乐谱长度
unsigned char code music_freq_sel[2][music_len] = {
{ N0,N0,N0,M6,M7,  H1,M7,H1,H3,  M7,M3,M3,
  M6,M5,M6,H1,  M5,M3,  M4,M3,M4,H1,
  M3,N0,H1,H1,H1, M7,M4_,M4_,M7,  M7,N0,M6,M7,
  H1,M7,H1,H3,  M7,M3,M3,  M6,M5,M6,H1,
  M5,M2,M3,  M4,H1,M7,H1,  H2,H2,H3,H1,
  H1,M7,M6,M6,M7,M5_,  M6,H1,H2,  H3,H2,H3,H5,
  H2,M5,M5,  H1,M7,H1,H3,  H3,
  M6,M7,H1,M7,H2,H2,  H1,M5,  H4,H3,H2,H1,
  H3,H3,  H6,H5,H5,  H3,H2,H1,N0,H1,
  H2,H1,H2,H2,H5,  H3,H3,  H6,H5,
  H3,H2,H1,N0,H1,  H2,H1,H2,M7,  M6,M6,M7},
{ 4,4,4,2,2,  6,2,4,4,  12,2,2,
  6,2,4,4,  12,4,  6,2,4,4,
  8,2,2,2,2,  6,2,4,4,  8,4,2,2,
  6,2,4,4,  12,2,2,  6,2,4,4,
  12,2,2,  4,2,4,4, 2,2,2,8,
  2,2,2,2,4,4,  12,2,2,  6,2,4,4,
  12,2,2,  6,2,4,4,  16,
  2,2,4,4,2,2,  6,10,  4,4,4,4,
  12,4,  8,4,4,  2,2,8,2,2,
  4,2,2,4,4,  12,4,  8,8,
  2,2,8,2,2,  4,2,6,4,  12,2,2}
};
/***************************************************/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/185190.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hystrix工作步骤说明以及服务监控hystrixDashboard

目录 一、步骤说明和流程解析 二、服务监控hystrixDashboard 官网&#xff1a;How it Works Netflix/Hystrix Wiki GitHub 一、步骤说明和流程解析 1 创建 HystrixCommand&#xff08;用在依赖的服务返回单个操作结果的时候&#xff09; 或 HystrixObserableCommand&am…

threadLocal的分享

问题描述&#xff1a;1、书城首页的书明明是广告解锁的&#xff0c;但是没有free标识&#xff0c;经过多次接口请求得出结论&#xff0c;相同的请求参数&#xff0c;有时会展示出free标识&#xff0c;有时不会展示free标识问题分析&#xff1a;1、长时间分析也没有得出结论&…

Linux用户及用户组知识总结

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java案例分…

SAP 供应商创建默认字段(屏幕格式)后台配置总结

供应商创建默认字段&#xff08;屏幕格式&#xff09;后台配置总结 供应商创建时默认字段&#xff08;屏幕格式的配置在好几个地方的配置来控制。 一 、定义与采购组织特定的屏幕格式 &#xff08;事务代码&#xff1a; OMFK&#xff09; IMG路径为如下图&#xff1a; 在这里…

commons-beanutils的三种利用原理构造与POC

写在前面 commons-beanutils 是 Apache 提供的一个用于操作 JAVA bean 的工具包。里面提供了各种各样的工具类&#xff0c;让我们可以很方便的对bean对象的属性进行各种操作。其中比较常使用的有 MethodUtils/ConstructorUtils/PropertyUtils/BeanUtils/ConvertUtils等。 分析…

Python-文件、eval函数

1.文件的概念和作用1.1文件的概念和作用计算机的文件&#xff0c;就是存储在某种长期储存设备上的一段数据长期存储设备包括&#xff1a;硬盘、U盘、移动硬盘、光盘......文件的作用将数据长期保存下来&#xff0c;在需要的时候使用CPU内存 硬盘1.2文件的存储方式在计算机中&am…

机器视觉_HALCON_HDevelop用户指南_3.用HDevelop采集图像

文章目录三、用HDevelop采集/获取图像3.1. 从文件中读取图像3.2. 查看图像3.3. 图像采集助手3.3.1. 从文件或文件夹中获取图像3.3.2. 通过图像获取接口获取图像3.3.3. 修改生成的代码三、用HDevelop采集/获取图像 本章节标题的英文是Image Acquisition&#xff0c;直译是图像采…

微信小程序跳转页面三种方式

wx.navigateTo 保留当前页面&#xff0c;跳转到应用内的某个页面。但是不能跳到 tabbar 页面。使用 wx.navigateBack 可以返回到原页面。小程序中页面栈最多十层。 wx.navigateBack 关闭当前页面&#xff0c;返回上一页面或多级页面。可通过 getCurrentPages 获取当前的页面…

搜索入门技能树

搜索地址 以下哪个网址不能用来搜索&#xff1a; https://so.csdn.net/so/searchhttps://so.csdn.net/waphttps://devbit.csdn.net/searchhttps://dev.csdn.net/ 搜索频道 以下哪个不属于搜索频道&#xff1a; 全站博客下载图片 技能树 在搜索结果页展示的技能树模块中&am…

51单片机学习笔记-2数码管显示

2 数码管显示 [toc] 注&#xff1a;笔记主要参考B站江科大自化协教学视频“51单片机入门教程-2020版 程序全程纯手打 从零开始入门”。 注&#xff1a;工程及代码文件放在了本人的Github仓库。 2.1 静态数码管显示 2.1.1 原理介绍   LED数码管&#xff1a;数码管是一种简单…

办公技巧:分享7个非常实用的PPT技巧

❤️作者主页&#xff1a;IT技术分享社区 ❤️作者简介&#xff1a;大家好,我是IT技术分享社区的博主&#xff0c;从事C#、Java开发九年&#xff0c;对数据库、C#、Java、前端、运维、电脑技巧等经验丰富。 ❤️荣誉&#xff1a; CSDN博客专家、数据库优质创作者&#x1f3c6;&…

分享14个你可能还未用上但又实用的CSS属性

大家好&#xff0c;今天分享 14 个实用的CSS属性&#xff0c;你可能还未用上&#xff0c;这些 CSS 属性将帮助你提高开发的效率&#xff0c;本篇文章将介绍上半部分&#xff0c;废话不多说&#xff0c;我们快来了解下吧。一、:in-range 和 :out-of-range 伪类:in-range 和 :out…

域内权限维持:DSRM后门

01、简介 每个域控制器都有一个目录还原模式(DSRM)帐户&#xff0c;它的密码是在安装域控时设置的&#xff0c;实际上它对应的就是sam文件里的本地管理员“administrator”&#xff0c;基本很少会被重置&#xff0c;因此有着极强的隐蔽性。攻击者通过获取域控的DSRM密码&#x…

分布式事务| 使用 dotnetcore/CAP 的本地消息表模式

本地消息表模式本地消息表模式&#xff0c;其作为柔性事务的一种&#xff0c;核心是将一个分布式事务拆分为多个本地事务&#xff0c;事务之间通过事件消息衔接&#xff0c;事件消息和上个事务共用一个本地事务存储到本地消息表&#xff0c;再通过定时任务轮询本地消息表进行消…

运放电路中输入失调电压Vos及温漂-运算放大器

实际运放与理想运放具有很多差别&#xff0c;要理解这些差别&#xff0c;就必须认识实际运放的参数。下图是用于描述实际运放几个关键参数的等效模型。模型中&#xff0c;第一个黄色运放是一个近似的理想运放&#xff0c;只有Auo不是无穷大&#xff0c;其余都是理想的。第二个运…

【GD32F427开发板试用】 CAN总线收发测试

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;meijing 这篇测试下CAN通信的收发测试&#xff0c;代码使用库例程中修改。 硬件部分 测试用到了CAN0、串口0和定时器1。 1> CAN0使用的接…

ccflow代码

ccflow代码目录概述需求&#xff1a;设计思路实现思路分析1.什么是流程版本管理&#xff1f;流程讲义&#xff1a;参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better r…

企业如何利用制造业ERP管理系统做好仓库管理?

仓库管理&#xff0c;对于生产制造型企业来说是重中之重&#xff0c;很多制造企业的大部分”身家“&#xff0c;都在仓库里了。众多的原材料和堆积如山的成品、半成品&#xff0c;往往占用了企业大部分的流动资金。来料是否及时&#xff0c;物料是否齐备&#xff0c;库存是否安…

流程引擎与应用系统分布式部署架构

一、为什么应用系统和流程引擎需要分开部署 有句话讲&#xff1a;存在即合理。在实际的企业应用需求里有如下几种场景&#xff0c;需要把业务系统和流程引擎分开部署。 企业流程治理需求。即整个企业只部署一套流程平台BPM&#xff0c;也叫企业级流程中心BPM、或者跨系统端到…

canal数据同步安装、使用

canal源码仓库&#xff1a;https://github.com/alibaba/canal博主使用的是canal 1.5.5版本 MySQL 5.7.32 JDK:1.8 canal各个版本&#xff1a;https://github.com/alibaba/canal/releasescanal-adapter下载 canal-admin 下载 canal-deployer 下载上传到目标服务器对应目录下解压…