51单片机播放音乐(一):蜂鸣器

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xfgryujk/article/details/84309970

本文完整源码

原理

蜂鸣器

蜂鸣器分为有(震动)源的和无源的,有源的无法控制频率,所以用无源的才能播放音乐。无源蜂鸣器需要自己控制输入变化的信号才能发声,最简单的就是输入方波信号了,通过单片机控制方波的频率就能发出不同音调的声音

这是发出50%占空比方波的代码:

int i;
while (1) {
	for (i = 0; i < 10; ++i); // 改变循环次数可以改变方波频率
	P1_0 = 1;
	for (i = 0; i < 10; ++i);
	P1_0 = 0;
}

乐谱转成循环次数

首先要有蜂鸣器乐谱,就是用频率和持续时间表示一个音符的乐谱,至于如何获取蜂鸣器乐谱可以看我上一篇文章。由于单片机的运行速度很慢,如果在单片机里计算循环次数会浪费很多时间,导致输出的音乐断断续续的,所以我尽量在电脑上完成计算,单片机直接读取循环次数就行了

已知单片机使用的晶振频率(我用的11059200Hz),机器主频是晶振频率的12分频,所以一个机器周期是 1/(/12)1 / (晶振频率 / 12) 秒。还要知道for循环一次需要多少个机器周期,我是在keil仿真调试时开启定时器测出来的,是38个周期。一次循环时间就是 1000机器周期 * 1000 * 每次循环机器周期数 毫秒,然后可以算出发出相应频率的方波需要几次循环

下面是把蜂鸣器乐谱转成循环次数的Python脚本:

import json

# 晶振频率(Hz)
CRYSTAL_FREQUENCY = 11059200
# 计数周期(机器周期)(s)
COUNT_PERIOD = 1 / (CRYSTAL_FREQUENCY / 12)
# 一次循环几个机器周期,通过定时器实验得到
COUNT_PER_LOOP = 38
# 一次循环时间(ms)
MS_PER_LOOP = COUNT_PERIOD * 1000 * COUNT_PER_LOOP


def tone_to_loop_count(notes, output_path):
    res = []
    for frequency, duration in notes:
        if frequency == 0:
            # 延时
            loop_count = 65535
            period_count = round(duration / MS_PER_LOOP)
        else:
            period = 1000 / frequency
            loop_count = round(period / 2 / MS_PER_LOOP)
            period_count = round(duration / period)
        # 最低频率0.185,loop_count = 65534
        assert 0 <= loop_count <= 65535, f'frequency = {frequency}, loop_count = {loop_count},不在unsigned int范围内'
        # 把一个时间过长的音符拆成多个音符
        while period_count > 65535:
            res.append((loop_count, 65535))
            period_count -= 65535
        res.append((loop_count, period_count))

    with open(output_path + '.h', 'w') as f:
        f.write(f"""#define DELAY_COUNT 65535
#define NOTES_LEN {len(res)}
extern const unsigned int code notes[][2];
""")

    with open(output_path + '.c', 'w') as f:
        f.write('const unsigned int code notes[][2] = {\n')
        for i in range(0, len(res), 6):
            f.write('\t')
            for loop_count, period_count in res[i: i + 6]:
                f.write(f'{{{loop_count}, {period_count}}}, ')
            f.write('\n')
        f.write('};\n')


def main():
    with open('beep.json') as f:
        notes = json.load(f)
    tone_to_loop_count(notes, '../beep/music_data')


if __name__ == '__main__':
    main()

单片机代码

单片机代码就很简单了,读取循环次数然后循环发出方波

#include <AT89X51.H>

#include "music_data.h"

// 引脚定义
#define beepOut P1_0

int main() {
	beepOut = 0;
	while (1) {
		unsigned int i, j, k;
		for (i = 0; i < NOTES_LEN; ++i) {
			if (notes[i][0] == DELAY_COUNT) // 延时
				for (j = 0; j < notes[i][1]; ++j);
			else {
				for (j = 0; j < notes[i][1]; ++j) {
					for (k = 0; k < notes[i][0]; ++k);
					beepOut = 1;
					for (k = 0; k < notes[i][0]; ++k);
					beepOut = 0;
				}
			}
		}
	}
	
	return 0;
}

仿真电路图

其实就是单片机最小系统把P1.0口接到蜂鸣器。顺带一提Proteus中buzzer是有源蜂鸣器,sounder是发声器,只接受数字信号,speaker是扬声器,接受模拟信号,这里用的是sounder。实际中可能单片机最大电流不够驱动蜂鸣器,这时要加个三极管来驱动

电路图

展开阅读全文

没有更多推荐了,返回首页