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

51单片机编程学习笔记——无源蜂鸣器演奏《祝你生日快乐》

大纲

  • 蜂鸣器分类
    • 有源蜂鸣器
    • 无源蜂鸣器
  • 电路图
  • 发声
  • 演奏《祝你生日快乐》
    • 模拟88键钢琴发声
    • 音符时值(Note Value)
    • 演奏
    • 完整代码

蜂鸣器是一种常用的电子发声器件,有源蜂鸣器和无源蜂鸣器在工作原理和特性上有明显区别。

蜂鸣器分类

有源蜂鸣器

  • 工作原理
    内部自带振荡电路,接通直流电源后,振荡电路能产生固定频率的信号,从而驱动蜂鸣片发声。这里的 “有源” 指的是它自身含有电源(振荡电路所需的能源)。
  • 特点
    • 发声较为简单,只需接入合适的直流电压即可发声,无需外部提供驱动信号,使用方便。
    • 通常有特定的发声频率,声音较为单一、稳定
    • 工作电压一般较低,常见的有 3V、5V 等。
  • 应用场景
    常用于各种电子设备的状态提示,如电脑、打印机、报警器等,当设备出现故障、完成操作或需要提醒用户时,有源蜂鸣器会发出特定的声音。

无源蜂鸣器

  • 工作原理
    内部没有振荡电路,需要外部输入一定频率的脉冲信号(如方波信号)才能发声。“无源” 意味着它自身不含振荡源,需要依赖外部的信号源来驱动。
  • 特点:
    • 需要搭配驱动电路来提供脉冲信号,使用相对复杂一些,但灵活性较高。
    • 通过改变输入信号的频率,可以发出不同音调的声音,能够实现更丰富的音效,如演奏简单的音乐等。
    • 工作电压范围相对较宽,可根据具体的应用需求进行选择。
  • 应用场景
    在一些需要多样化声音效果的电子设备中较为常见,如电子玩具、智能音箱、电子琴等,通过编程控制输入信号的频率和时长,无源蜂鸣器可以发出各种不同的声音,增加设备的趣味性和交互性。

电路图

在我买的电路板上的蜂鸣器是无源蜂鸣器,它的引脚信息如下图
在这里插入图片描述
可以看到它有一个Beep引脚,该引脚给无源蜂鸣器提供了脉冲信号。
该引脚又会连接到ULN2003D达林顿阵列的12号引脚上。
在这里插入图片描述
我们再看下达林顿阵列的电路图
在这里插入图片描述
达林顿阵列(Darlington Array)是一种集成化的功率晶体管阵列,由多个达林顿管组合而成。其核心特性使其成为驱动高功率负载(如步进电机、继电器、电磁阀等)的理想选择。
达林顿管是由两个三极管级联组成,第一级三极管的发射极连接到第二级三极管的基极,形成极高的电流增益(β 值可达数千)。这样我们只需极小的基极电流即可驱动大负载电流,适合与微控制器(如 Arduino、单片机)直接连接。
达林顿管的工作原理是:当输入引脚为高电平时,对应的内部达林顿管导通。导通后,会将输出引脚拉低至接近地电位,即输出低电平。所以我们将其看做一个逻辑非的电路。

发声

无源蜂鸣器发声是通过外部电路提供不同频率的方波信号,使蜂鸣器内部的压电陶瓷片周期性振动,从而发出不同音高的声音。所以我们只要让达林顿阵列的12号引脚输出一定频率的方波信号即可。

sbit beep = P2^5; // Buzzer pin
beep = !beep;

在这里插入图片描述

演奏《祝你生日快乐》

模拟88键钢琴发声

按键的顺序,其每个键的声音频率是

键号   音名   频率 (Hz)    键号   音名   频率 (Hz)    键号   音名   频率 (Hz)    键号   音名   频率 (Hz)
---------------------------------------------------------------------------------------------------------------
1      A0     27.50        23     F#2    92.50        45     D4     293.66       67     B5     987.77
2      A#0    29.14        24     G2     97.99        46     D#4    311.13       68     C6     1046.50
3      B0     30.87        25     G#2    103.83       47     E4     329.63       69     C#6    1108.73
4      C1     32.70        26     A2     110.00       48     F4     349.23       70     D6     1174.66
5      C#1    34.65        27     A#2    116.54       49     F#4    369.99       71     D#6    1244.51
6      D1     36.71        28     B2     123.47       50     G4     392.00       72     E6     1318.51
7      D#1    38.89        29     C3     130.81       51     G#4    415.30       73     F6     1396.91
8      E1     41.20        30     C#3    138.59       52     A4     440.00       74     F#6    1479.98
9      F1     43.65        31     D3     146.83       53     A#4    466.16       75     G6     1567.98
10     F#1    46.25        32     D#3    155.56       54     B4     493.88       76     G#6    1661.22
11     G1     49.00        33     E3     164.81       55     C5     523.25       77     A6     1760.00
12     G#1    51.91        34     F3     174.61       56     C#5    554.37       78     A#6    1864.66
13     A1     55.00        35     F#3    185.00       57     D5     587.33       79     B6     1975.53
14     A#1    58.27        36     G3     196.00       58     D#5    622.25       80     C7     2093.00
15     B1     61.74        37     G#3    207.65       59     E5     659.25       81     C#7    2217.46
16     C2     65.41        38     A3     220.00       60     F5     698.46       82     D7     2349.32
17     C#2    69.30        39     A#3    233.08       61     F#5    739.99       83     D#7    2489.02
18     D2     73.42        40     B3     246.94       62     G5     783.99       84     E7     2637.02
19     D#2    77.78        41     C4     261.63       63     G#5    830.61       85     F7     2793.83
20     E2     82.41        42     C#4    277.18       64     A5     880.00       86     F#7    2959.96
21     F2     87.31        43     D4     293.66       65     A#5    932.33       87     G7     3135.96
22     F#2    92.50        44     D#4    311.13       66     B5     987.77       88     G#7    3322.44

我们不用在代码中硬编码这些频率,因为它们是有公式计算的

f = 440 × 2^((n-49)/12)

一个方波是由一个高电平和一个低电平组成的,所以我们每隔半个周期翻转一次电平

beep = !beep;
delay_us(half_period_us);

在这里插入图片描述

音符时值(Note Value)

在钢琴演奏中,每个琴键按下的时长在音乐理论中通常与音符时值(Note Value)相关。它指的是音符持续的时间长度,直接影响音乐的节奏和表现力。
如果我们知道音符时值,又知道每个音符的频率,则可以计算出该音符需要循环多少个周期以达到音符时值。

noteValueSeconds /(1 Second / freq)

G#7键的频率3322.44Hz为例,每个方波的周期是1000 * 1000 / 3322.44=300.98us。
如果G#7要持续0.5s,则需要位置该频率方波0.5 * 1000 * 1000 / 300.98=1661个周期。

在代码上,我们以ms为单位,表示音符持续时长,则计算公式是

ms * 1000 / (1000 * 1000 / freq)

由于单片机算力有限,我们要尽量简化计算过程,这样可以尽量减少计算对音符持续时长和频率的影响。于是上述可以简化成

ms * freq / 1000

演奏

下面play_key方法可以模拟一个音符(freq)持续的时长(ms)。

sbit beep = P2^5; // Buzzer pinvoid delay_us(unsigned long us) {while(us--) {_nop_();_nop_();_nop_();// 粗略1us,实际可根据晶振微调}
}double calculate_frequency(int n) {// 88键钢琴编号:n=1为A0(27.5Hz),n=49为A4(440Hz)// 公式:f = 440 × 2^((n-49)/12)return 440.0 * pow(2.0, (n - 49) / 12.0);
}void play_key(double freq, unsigned int ms) {unsigned long total_cycles = (unsigned long)(freq * ms / 1000); // 周期次数unsigned long half_period_us = (unsigned int)(500.0 * 1000 / freq ); // 半周期usunsigned long i;for (i = 0; i < total_cycles; i++) {beep = !beep;delay_us(half_period_us / 100);}beep = 0; 
}

需要注意的是,delay_us并没有传递half_period_us ,而是传递了half_period_us / 100。这是因为在51单片机上,每条 nop() 指令加上循环和函数调用的开销,实际延时会比1微秒长很多(可能是几十甚至上百微秒)。如果直接用 delay_us(half_period_us);,实际延时会远大于应有的半周期,导致频率大大降低,音调变得很低。除以100是为了补偿 delay_us 的“虚假”延时,让实际输出的方波频率接近正确的频率。

完整代码

#include <REG52.H>
#include <intrins.h>
#include <math.h>sbit beep = P2^5; // Buzzer pinvoid delay_us(unsigned long us) {while(us--) {_nop_();_nop_();_nop_();// 粗略1us,实际可根据晶振微调}
}double calculate_frequency(int n) {// 88键钢琴编号:n=1为A0(27.5Hz),n=49为A4(440Hz)// 公式:f = 440 × 2^((n-49)/12)return 440.0 * pow(2.0, (n - 49) / 12.0);
}void play_key(double freq, unsigned int ms) {unsigned long total_cycles = (unsigned long)(freq * ms / 1000); // 周期次数unsigned long half_period_us = (unsigned int)(500.0 * 1000 / freq ); // 半周期usunsigned long i;for (i = 0; i < total_cycles; i++) {beep = !beep;delay_us(half_period_us / 100);}beep = 0; 
}// 88键钢琴编号:n=1为A0(27.5Hz),n=40为C4,n=42为D4,n=44为E4,n=45为F4,n=47为G4,n=49为A4,n=51为B4,n=52为C5
// 《祝你生日快乐》C调主旋律
static const int code melody[] = {40, 40, 42, 40, 45, 44,      // C4 C4 D4 C4 F4 E440, 40, 42, 40, 47, 45,      // C4 C4 D4 C4 G4 F440, 40, 52, 49, 45, 44, 42,  // C4 C4 C5 A4 F4 E4 D451, 51, 49, 45, 47, 45       // B4 B4 A4 F4 G4 F4
};
static const int code length[] = {300, 300, 600, 600, 600, 1200,300, 300, 600, 600, 600, 1200,300, 300, 600, 600, 600, 600, 1200,300, 300, 600, 600, 600, 1200
};void main() {int i;int notes = sizeof(melody) / sizeof(melody[0]);while (1) {for (i = 0; i < notes; i++) {play_key(calculate_frequency(melody[i]), length[i]*5);}}
}

相关文章:

  • 大模型服务如何实现高并发与低延迟
  • SAR ADC 比较器寄生电容对性能的影响
  • OSError: [WinError 193] %1 不是有效的 Win32 应用程序。
  • [特殊字符] jQuery 响应式瀑布流布局插件推荐!
  • 王树森推荐系统公开课 排序04:视频播放建模
  • Mybatis面向接口编程
  • Conda环境管理:确保Python项目精准复现
  • 基于Qwen3-7B FP8与基石智算打造高性能本地智能体解决方案
  • 【Java高阶面经:微服务篇】1.微服务架构核心:服务注册与发现之AP vs CP选型全攻略
  • C++:STL
  • 2025华为OD机试真题+全流程解析+备考攻略+经验分享+Java/python/JavaScript/C++/C/GO六种语言最佳实现
  • lasticsearch 报错 Document contains at least one immense term 的解决方案
  • 大模型预训练、微调、强化学习、评估指导实践
  • Token的组成详解:解密数字身份凭证的构造艺术
  • ragas precision计算的坑
  • JavaScript计时器详解:setTimeout与setInterval的使用与注意事项
  • 初步认识HarmonyOS NEXT端云一体化开发
  • 活到老学到老-Spring参数校验注解Validated /Valid
  • 工单派单应用:5 大核心功能提升协作效率
  • Git 克隆子分支
  • 上海觉群书画院成立十年,苏州河畔新展百幅精品
  • 凤阳文旅局回应鼓楼瓦片脱落:鼓楼楼宇系仿古建筑,动工时已履行报批手续
  • 为小龙虾洗清这些“黑锅”,这份科学吃虾指南请收好
  • 外交部:国际调解院着眼以调解定分止争,更好维护国际公平正义
  • 多所院校高规格召开考研动员会,有学院考研报名率达84%
  • 周慧芳任上海交通大学医学院附属上海儿童医学中心党委书记