用Arduino单片机制作一个简单的音乐播放器

Arduino单片机上有多个数字IO针脚,可以输出数字信号,用于驱动发声器件,从而让它发出想要的声音。蜂鸣器是一种常见的发声器件,通电后可以发出声音。因此,单片机可以通过数字输出控制蜂鸣器发出指定的声音。另外,Arduino支持串口的通信方式,可以从电脑上接收数据,根据收到的数据确定所需发的声音。本文说明用上述方式如何通过Arduino单片机实现一个简单的音乐播放器。

一、项目实现的工具

(一)无源蜂鸣器

蜂鸣器有分有源蜂鸣器和无源蜂鸣器两种。其中,无源蜂鸣器内部不带震荡源,要通过震荡的信号来令其发出声音。音调随震荡频率的不同而不同[1]。

(二)Arduino单片机

Arduino单片机通过串口从电脑端获取音乐信息,然后根据音乐信息控制无源蜂鸣器的震荡频率,产生音乐效果。

(三)可调节电阻

用于控制无源蜂鸣器的电压大小,从而调节音量。

(四)树莓派电脑,运行基于Debian Linux的Raspberry Pi OS

一方面,编写Arduino程序并将其烧录进单片机;另一方面,将存储的音乐信息通过串口发送给Arduino。

(五)信号线

主要用于通电和传输信号

二、电路及程序设计

(一)电路

在该项目中,使用Arduino单片机的第7个IO针脚作为向蜂鸣器输出信号的针脚。注意Arduino单片机的输出是推挽输出,即高电平输出。

无源蜂鸣器有三个针脚,即电源(VCC),接地(GND)以及信号(I/O)。蜂鸣器在接通电源时,其随信号的高电平输入进行震荡,发出声音。而电源的电压决定了发声的响度。

电路大致如下

所以通过可调电阻,控制蜂鸣器的输入电压大小,从而调节音量大小。

(二)程序

在介绍程序之前,先简单介绍一些音乐的基本知识。我们通常的音乐是用八度音阶表示的。任何一个音符,和高八度的音相比,其音调(发声体震荡频率)相差两倍。每个八度区里有12个半音,所以每个半音的音调相差\sqrt[12]{2}倍。也就是说,如果简谱中的1的音调是xHz,那么{\stackrel{7}{\cdot}}的音调是x\over \sqrt[12]{2}Hz,而{}^\#1的音调是x*\sqrt[12]{2}2的音调是x*\sqrt[6]{2}。详细说明,见[2]。

本项目在Arduino程序中,用一个字节作为一个音符的信号。规定当该字节值为0x80时,代表的音调为1024Hz,可以理解为简谱中1的音调是1024Hz,然后字节值每相差1,就代表相差一个半音。因此,0x7F代表1024\over \sqrt[12]{2}Hz,即{\stackrel{7}{\cdot}};0x81代表1024*\sqrt[12]{2}Hz,即{}^\#1

另外,把0x00作为休止符。这里规定,每一个音符的时长为半秒。

在此,贴上Arduino里的C++程序

/*
Let's define the hex of the tonedefine a do: 1024Hz
0x80: do3
0x81: #do3
0x82: re3
0x7F: si2and each byte is for a note of 0.5s. So if you need do3 for 1s, then need 2 0x80*/const int buzzerPin = 7;
byte baseNote = 0x80;
int baseTone = 1024;
byte note;
void setup() {// put your setup code here, to run once:pinMode(buzzerPin, OUTPUT);Serial.begin(9600);
}void loop() {// put your main code here, to run repeatedly:if ((Serial.available() > 0) && (note = Serial.read()) && (note != 0x00)){Serial.print('Sing:');Serial.print(note);Serial.print(' ');//Now convert it into frequency of the toneint toneFreq;if (baseNote > note){ //received note is under base noteint noteDiff = baseNote - note;toneFreq = (int)((float)baseTone / (pow(2.0, noteDiff/12.0))); //between do and #do is 2^(1/12)}else{//received note is above base noteint noteDiff = note - baseNote;toneFreq = (int)((float)baseTone * (pow(2.0, noteDiff/12.0)));}Serial.print(toneFreq);Serial.print(' ');tone(buzzerPin, toneFreq);}else{noTone(buzzerPin);Serial.print('0');}delay(500);}

程序中,tone(buzzerPin, toneFreq)表式在buzzerPin数字输出中产生频率为toneFreqHz的震荡;而noTone(buzzerPin)表示让数字输出停止震荡。

因此,程序通过串口接收音符字节信息,然后根据该信息激活数字输出,让无源蜂鸣器发出音乐。

另外,在树莓派电脑中,运行一个python程序,通过读取指定格式的乐谱,产生音符数据,并将其通过串口输入到Arduino中。这里,先对乐谱的格式做一个规定。

这里,令乐谱为一个txt文件,该txt文件里每一个音符用一个1-3个字符的字符串组成,而音符和音符之间用空格分开。对于每一个音符,第一个字符是1-7之间的数字,意义和简谱的数字部分一样;第二的字符表明该音符在哪一个八度上,标准的八度号为3,所以如果没有该字符就默认为3处理;第三个字符可以不存在,也可以是+或-,表明是升调\#还是降调\flat。所以,假定乐谱文件如下:

13 33 23 52 0 43+ 63-

则对应的简谱是

1323 {\stackrel{5}{\cdot}} 0^\sharp 4^\flat 6

所以,把乐谱txt文件转换为Arduino读取的音乐数据的python程序如下

"""
This file gives the function to get music from file
base 0x80 is do3(13)
so do2 is 12 semitones lower than do3, do3+ is 1 semitone higher than do3, do3- is 1 semitone lower than do3
"""def loadMusicFile(filename):with open(filename,'r') as file:content = file.read()return content.split(' ')def convertFromMusicToArduino(filename):notes = loadMusicFile(filename)print(notes)musicData = []for n in notes:if len(n) == 0:continue;dataThis = 0x80if n[0] == '1':dataThis += 0elif n[0] == '2':dataThis += 2elif n[0] == '3':dataThis += 4elif n[0] == '4':dataThis += 5elif n[0] == '5':dataThis += 7elif n[0] == '6':dataThis += 9elif n[0] == '7':dataThis += 11else:dataThis = 0x00if len(n) > 1 and dataThis != 0x00:dataThis += (int(n[1]) - 3) * 0x0cif len(n) > 2 and dataThis != 0x00:if n[2] == '+':dataThis += 0x01elif n[2] == '-':dataThis -= 0x01musicData.append(dataThis)return musicData

注意0x0c表明十进制里的12。

然后,运行以下python程序,把音频文件转成Arduino支持的格式后,通过串口传入Arduino。python的串口连接的方式见[3]。

import serial
import os
from time import sleep
from getmusicFromFile import convertFromMusicToArduinoserialportName = '/dev/ttyACM1'
bps = 9600ser = serial.Serial(serialportName, int(bps), timeout=0.5, parity=serial.PARITY_NONE, stopbits=1)data = convertFromMusicToArduino('song.txt')data_bytes = bytes(data)
print(data_bytes)if (ser.isOpen()):print("Serial opened")sleep(1.8)ser.write(data_bytes)sleep(10)print(ser.read(30))ser.close()
print("done")

注意,在ser.isOpen()后,在传输数据前有一个sleep(1.8)。这个等待,是要让串口准备好后再传输数据,否则会传输失败,Arduino有可能未能收到数据。

三、实验效果

该实验,用歌曲"友谊地久天长"的第一小段作为乐谱,文件song.txt如下

52 52 13 13 0 13 13 0 33 33 23 23 23 13 23 0 33 33 13 13 0 13 33 33 53 53 63 63 63 0 0 0 63 63 53 53 53 33 33 33 13 13 23 23 23 13 23 23 33 23 13 13 13 62 62 62 52 52 13 13 13

运行效果见视频,播放的正是"友谊地久天长"的第一段。注意当我转动可调电阻时音量的变化。

友谊地久天长

参考资料

[1]有源蜂鸣器与无源蜂鸣器的驱动方式详解(精华版)_有源蜂鸣器和无源蜂鸣器的电路图-CSDN博客

[2]八度音阶和频率的关系_音阶与频率的关系-CSDN博客

[3]​​​​​​​用 Python 玩转串口(基于 pySerial)_python打开串口-CSDN博客

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

马丁代尔药物大典数据库

马丁代尔药物大典是一本由Pharmaceutical Press出版的参考书,拥有全球使用的近 6000 种药物和药品,包括超过 125,000 种专有制剂的详细信息。其中还包括近 700 篇疾病治疗评论。 它于 1883 年首次出版,马丁代尔包含全球临床用药信息&#xff…

【qt】QQ仿真项目2

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 一览全局: QQ仿真项目 一.主窗口的创建二.主窗口的ui设计三.初始化状态,等级,app…

<Rust>iced库(0.13.1)学习之部件(三十一):picklist部件的使用及可变style设置

前言 本专栏是学习Rust的GUI库iced的合集,将介绍iced涉及的各个小部件分别介绍,最后会汇总为一个总的程序。 iced是RustGUI中比较强大的一个,目前处于发展中(即版本可能会改变),本专栏基于版本0.12.1. 注:新版本已更新为0.13 概述 这是本专栏的第三十一篇,主要说明下…

俗人,精气神,歌曲《错的人》

精气神,在人体中,精指构成人体生命活动的各层次的有形元素,常呈固体或液体状态。 哲学前提:世界上的一切,从微观上讲,都是由精微物质构成的,比如基本粒子。 关于有形与无形、与主观关注点相关…

DHCP安装

步骤 1:安装DHCP服务器 在系统上安装DHCP服务。以下是安装命令: # 安装DHCP软件包 yum install dhcp步骤 2:配置DHCP服务器 安装完成后,需要配置DHCP服务器来绑定MAC地址和IP地址。 # 备份原始的DHCP配置文件 cp /etc/dhcp/dh…

迁移学习案例-python代码

大白话 迁移学习就是用不太相同但又有一些联系的A和B数据,训练同一个网络。比如,先用A数据训练一下网络,然后再用B数据训练一下网络,那么就说最后的模型是从A迁移到B的。 迁移学习的具体形式是多种多样的,比如先用A训练…

HCIA综合实验

实验步骤 1.划分网段 内网部分---三个大块 2.先配交换机 左边:3个vlan ,3个access,1个trunk 右边:2个vlan ,2个access,1个trunk 3.再配路由 3.1 r5先配接口ipg/0/0/0 口配子接口 g0/0/0.1-0.3 g0/0/1 …

【YOLOv8实时产品缺陷检测】

YOLOv8应用于产品缺陷检测实例 项目概况项目实现YOLOv8安装及模型训练关键代码展示动态效果展示 项目概况 本项目是应用YOLOv8框架实现训练自定义模型实现单一零件的缺陷检测,软件界面由PyQt5实现。 功能已正式使用,识别效果达到预期。 项目实现 项目…

手机误删照片?试试这5款免费数据恢复神器!

大家好!今天咱们来聊聊一个大家都关心的话题——免费数据恢复工具。不论是误删照片、视频,还是丢失重要文件,数据恢复都是个让人头疼的问题。但好消息是,现在有众多免费的数据恢复工具能帮助我们找回失去的数据。今天我就来为大家…

力扣16~20题

题16&#xff08;中等&#xff09;&#xff1a; 思路&#xff1a; 双指针法&#xff0c;和15题差不多&#xff0c;就是要排除了&#xff0c;如果total<target则排除了更小的&#xff08;left右移&#xff09;&#xff0c;如果total>target则排除了更大的&#xff08;rig…

pycharm 远程ssh时,mujuco提示mujoco.FatalError: gladLoadGL error

在ubuntu系统运行时完全没问题&#xff0c;但是使用pycharm远程ssh登录时就会提示这个。 解决方法&#xff1a; 1. 可以修改环境变量 2. export LD_PRELOAD/usr/lib/x86_64-linux-gnu/libstdc.so.6 参考【Mujuco】WSL2安装Mujoco用于python,遇到FatalError,以及图形驱动架构…

【Git原理与使用】远程操作标签管理

远程操作&&标签管理 1.理解分布式版本控制系统2.新建远程仓库3.克隆远程仓库4.向远程仓库推送5.拉取远程仓库6.配置 Git7.配置命令别名8.标签管理8.1创建标签8.2操作标签 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496;…

RTOS系统移植

一、完成系统移植 系统移植上官网寻找合适的系统包&#xff0c;下载后将文件移植入工程文件 二、创建任务句柄、内核对象句柄&#xff08;信号量&#xff0c;消息队列&#xff0c;事件标志组&#xff0c;软件定时器&#xff09;、声明全局变量、声明函数 三、创建主函数&#…

Vue2电商项目(七)、订单与支付

文章目录 一、交易业务Trade1. 获取用户地址2. 获取订单信息 二、提交订单三、支付1. 获取支付信息2. 支付页面--ElementUI(1) 引入Element UI(2) 弹框支付的业务逻辑(这个逻辑其实没那么全)(3) 支付逻辑知识点小总结 四、个人中心1. 搭建二级路由2. 展示动态数据(1). 接口(2).…

【计算机网络 - 基础问题】每日 3 题(二十九)

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞…

【Docker】03-自制镜像

1. 自制镜像 2. Dockerfile # 基础镜像 FROM openjdk:11.0-jre-buster # 设定时区 ENV TZAsia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # 拷贝jar包 COPY docker-demo.jar /app.jar # 入口 ENTRYPOINT ["ja…

Redis:通用命令 数据类型

Redis&#xff1a;通用命令 & 数据类型 通用命令SETGETKEYSEXISTSDELEXPIRETTLTYPEFLUSHALL 数据类型 Redis的客户端提供了很多命令用于操控Redis&#xff0c;在Redis中&#xff0c;key的类型都是字符串&#xff0c;而value有多种类型&#xff0c;每种类型都有自己的操作命…

Redis篇(最佳实践)(持续更新迭代)

介绍一&#xff1a;键值设计 一、优雅的key结构 Redis 的 Key 虽然可以自定义&#xff0c;但最好遵循下面的几个最佳实践约定&#xff1a; 遵循基本格式&#xff1a;[业务名称]:[数据名]:[id]长度不超过 44 字节不包含特殊字符 例如&#xff1a; 我们的登录业务&#xff0…

Leetcode—76. 最小覆盖子串【困难】

2024每日刷题&#xff08;167&#xff09; Leetcode—76. 最小覆盖子串 C实现代码 class Solution { public:string minWindow(string s, string t) {int bestL -1;int l 0, r 0;vector<int> cnt(128);for(const char c: t) {cnt[c];}int require t.length();int m…