历史上的今天
今天是:2025年03月17日(星期一)
2020年03月17日 | 51单片机播放音乐(一):蜂鸣器
2020-03-17 来源:eefocus
蜂鸣器
蜂鸣器分为有(震动)源的和无源的,有源的无法控制频率,所以用无源的才能播放音乐。无源蜂鸣器需要自己控制输入变化的信号才能发声,最简单的就是输入方波信号了,通过单片机控制方波的频率就能发出不同音调的声音
这是发出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)1/(晶振频率/12) 秒。还要知道for循环一次需要多少个机器周期,我是在keil仿真调试时开启定时器测出来的,是38个周期。一次循环时间就是 机器周期∗1000∗每次循环机器周期数 机器周期 * 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 #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。实际中可能单片机最大电流不够驱动蜂鸣器,这时要加个三极管来驱动
史海拾趣
|
电磁兼容原理设计和预测技术 作 者: 蔡仁钢 I S B N: 7810126733 页 数: 240 开 本: 16开 封面形式: 简裝本 出 版 社: 北京航空航天大学出版社 出版日期: 1997-11-1 定 ...… 查看全部问答> |
|
TCHAR *nTargetAddress m_SockAddrIn.sin_addr.S_un.S_addr=inet_addr(nTargetAddress); error C2664: \'inet_addr\' : cannot convert parameter 1 from \'unsigned short *\' to \'const char *\' 不知道这个怎么转?API里面是不是不能用CH ...… 查看全部问答> |
|
A0results[index] = ADC12MEM0; // Move A0 results, IFG is cleared A1results[index] = ADC12MEM1; // Move A1 results, IFG is cleared A2results[index] = ADC12MEM2; // Move A2 results, IFG is cleared A3results[index] = ADC12MEM3; ...… 查看全部问答> |
|
AT89C51单片机 6M晶振的时钟谁帮忙写下程序呗 最好C语言的 拜托了! 希望各位达人帮帮忙 帮忙解答AT89C51单片机 6M晶振的 时钟有电路图和相关说明 谁帮忙写下程序呗 最好C语言的 拜托了!还有就是那个 LED是怎么连接的啊 求解答要求和原理图在http://embed.chinaitlab.com/xfl/804366.html … 查看全部问答> |
|
【ZZ】ARM与TSMC完成首件20纳米Cortex-A15多核设计定案 ARM公司与TSMC日前共同宣布,已顺利完成首件采用20纳米工艺技术生产的ARM Cortex-A15 处理器设计定案(Tape Out)。藉由TSMC在开放创新平台上建构完成的20纳米设计生态环境,双方花费六个月的时间即完成从缓存器转换阶层(RTL)到产品设计定 ...… 查看全部问答> |
|
LM2594输入可高达60V,输出有3.3V,5V,12V等型号,电流可达500mA当输入电压越高时需要使用的二极管D1耐压就越大,此图为输入+24V,输出为5V,电流200mA左右,电感要用330uH,如果太小则输出电压不能保证,负载稍大则可能使输出电压被拉低,1N5819 ...… 查看全部问答> |
|
本帖最后由 dontium 于 2015-1-23 11:23 编辑 目录 00、前言 01、电路图及说明 02、仿真 03、准备材料、元件 04、元件布局 05、制作输入隔离岛 06、输入岛岛芯的制作 07、元件安装和焊接 08、反馈电容制作 09、安装完成 10、初步测试 ...… 查看全部问答> |




