8位机也玩音频-Arduino+SD+DAC+运放打造的播放器
扫描二维码
随时随地手机看文章
前言
图 最终组装图之一
图 最终组装图之二
曾几何时,玩音频曾经是搞DIY中的高大上.不说DSP也得上ARM,至于DAC与运放更是一度成为土豪们展示经济实力的舞台.不过随着单片机等器件的进化,开源软件的蓬勃发展,玩音频的门槛早就下来了.比如本文要介绍的Adafruit Wave Shield,就能是专门为Arduino设计的音频模块.确切的说是为AVR这一类的8位机而设计的音频模块.下文将从硬件到软件详细介绍一下子此模块的设计细节与使用方法.
硬件介绍+焊接
熟悉Arduino的同学们都了解,这种模块一般都是以元器件形式提供给大家的,元器件也大多是插件形式的,为的是锻炼大家的动手能力.当然对作者这种习惯了贴片和钢网的人来讲,只能说怀怀旧了.
图 一包元器件
图 模块包含的所有材料
拿到手真有点眼泪汪汪的,前几天玩了个TI的MSP430的板子,那板子只有主控属于DIP,当时都感概了一番.如今拿到这板子除了实在没法插件的SD卡座之外,居然全都是插件的,插件电容,插件电阻,插件运放,插件DAC...结果是逼迫着又怀了一把旧.记忆中上次玩这种全插件的板子肯定是大学时代了,而且绝对是大三之前.好在这种板子焊接起来也快,三下五除二就焊接好了.
图 官方的焊接图
图 作者焊接好的图片
图 插在Arduino Uno板子上
图 带上耳机
也懒得去追求美观了,只求扎实与连接可靠.有几个件没有焊接的属于套件中没有包含的器件,是些指示,接口之类的, 并不影响主要功能.
下面看看原理图:
图 板子的原理图
原理同总共就这一页.不难看出,板子是通过DAC片(MCP4921)来播放音频的,这是个12bit的单通道,参数上与大多数有DAC的片如STM32的内置DAC类似. 但是分立的DAC肯定效果要好点. DAC过了之后加了两级运放进行放大, 运放电路也是简单粗暴, 仅仅进行电流加强, 这是因为DAC输出的幅度已经足够. 另外使用74125隔离后使用SPI来读取SD卡. 这个SD卡才是这板子较为重要的部分. 因为SD卡意味着能播放的数据源超级大了. 相比而言,12bit的DAC虽然性能还说的过去, 但不是那么必要, 因为Arduino板子几乎都有通过PWM+滤波器来播放音频的选项. 但是这板子不仅仅是个SD卡扩展kit,分立DAC+运放的组合起码在教育意义上较大. 很多人能够通过这板子来理解基本的音频播放电路的原理. Arduino是通过SPI接口来读取SD卡的.
安装Arduino库
Arduino的一大特点是能利用的库函数很丰富. 如何使用这个模块, 当然自己来写FAT读取,再进行DAC操作也是可以的. 但那样做之前不妨试试别人写好的库来评估一下子硬件也是很好的. 作者相信这也是Arduino这种开源硬件这么流行的原因: 方便快捷评估硬件.这一点效率在真正的开发中很重要.在DIY中也是如此,因为可以参考别人成果,激发你的创作灵感. 当没有库的时候也要发挥自己动手的开创精神, 有库的时候就要先去利用.
以下这个库函数封装了读取SD卡与通过MCP4921来播放wav文件的功能:
https://github.com/adafruit/WaveHC
通过git下载也好,直接下载zip也好. 总之下载好解压之后把WaveHC这个文件夹拷贝到你的Arduino的库文件夹中. 系统中Arduino的库文件夹这样查看:
图 Arduino的库文件夹
转换音频
首先找个SD卡, 用TF+卡套也可以. 本人使用的是16G的SDHC卡, 最好是先进行格式化成FAT32格式. 再到你电脑上找几个想听的歌曲要转成单通道44.1KHz或以下, 16bit或以下的wav文件. 这里面我们使用Audacity来进行:
http://www.audacityteam.org/
这软件本人在其他文章中写了不少, 这里从简, 不清楚的可以留言或翻之前的文章.
比如要转化某mp3文件,首先当然是打开该文件:[!--empirenews.page--]
图 打开某mp3文件
上面显示的是两通道, 首先合并为单通道. 因为板子上只有一路DAC, 不合并也可以, 只是要改库函数稍稍麻烦, 这里合并一下子以求简洁.
图 合并命令菜单
图 此时看起来是单通道
下一步要进行重采样到低一点, 这一步可选, 因为Arduio Uno的主控较弱,要播放的采样率太高了会失真,如果你转换的原文件如果高于44.1KHz则建议重采样为至多44.1KHz.
图 重采样命令
图 设定工程采样率也就是导出采样率
最后就是导出了,注意选择格式:
图 导出菜单命令
图 导出格式
把导出的文件放到你刚刚格式化的SD卡内,如果卡够大的话,不妨多放几首,可以循环播放.
播放程序
一个简单的循环播放程序,这也是WaveHC中的示例之一.简单走一下子这程序的流程:
//首先包含这两个头文件, 注意如果提示找不到这两个文件请退回去看如何安装库文件.
#include
#include
//定义几个全局对象: 卡,文件系统,音频播放对象. 以及其他几个全局变量
SdReader card; // This object holds the information for the card
FatVolume vol; // This holds the information for the partition on the card[!--empirenews.page--]
FatReader root; // This holds the information for the volumes root directory
WaveHC wave; // This is the only wave (audio) object, since we will only play one at a time
uint8_t dirLevel; // indent level for file/dir names (for prettyprinting)
dir_t dirBuf; // buffer for directory reads
/*
* Define macro to put error messages in flash memory
*/
#define error(msg) error_P(PSTR(msg))
// Function definitions (we define them here, but the code is below)
void play(FatReader &dir);
//Arduino程序的初始化函数,主要是加载文件系统
void setup() {
Serial.begin(9600); // set up Serial library at 9600 bps for debugging
putstring_nl("\nWave test!"); // say we woke up!
putstring("Free RAM: "); // This can help with debugging, running out of RAM is bad
Serial.println(FreeRam());
// if (!card.init(true)) { //play with 4 MHz spi if 8MHz isn't working for you
if (!card.init()) { //play with 8 MHz spi (default faster!)
error("Card init. failed!"); // Something went wrong, lets print out why
}
// enable optimize read - some cards may timeout. Disable if you're having problems
card.partialBlockRead(true);
// Now we will look for a FAT partition!
uint8_t part;
for (part = 0; part < 5; part++) { // we have up to 5 slots to look in
if (vol.init(card, part))
break; // we found one, lets bail
}
if (part == 5) { // if we ended up not finding one :(
error("No valid FAT partition!"); // Something went wrong, lets print out why
}
// Lets tell the user about what we found
putstring("Using partition ");
Serial.print(part, DEC);
putstring(", type is FAT");
Serial.println(vol.fatType(), DEC); // FAT16 or FAT32?
// Try to open the root directory
if (!root.openRoot(vol)) {
error("Can't open root dir!"); // Something went wrong,
}
// Whew! We got past the tough parts.
putstring_nl("Files found (* = fragmented):");
// Print out all of the files in all the directories.
root.ls(LS_R | LS_FLAG_FRAGMENTED);
}
//主循环就是播放,播放是通过中断来进行的,所以也可以同时做其他操作
void loop() {
root.rewind();
play(root);
}
//一些出错报告函数
/*
* print error message and halt
*/
void error_P(const char *str) {
PgmPrint("Error: ");
SerialPrint_P(str);
sdErrorCheck();
while(1);
}
/*
* print error message and halt if SD I/O error, great for debugging!
*/
void sdErrorCheck(void) {
if (!card.errorCode()) return;
PgmPrint("\r\nSD I/O error: ");
Serial.print(card.errorCode(), HEX);[!--empirenews.page--]
PgmPrint(", ");
Serial.println(card.errorData(), HEX);
while(1);
}
/*
* play recursively - possible stack overflow if subdirectories too nested
*/
void play(FatReader &dir) {
FatReader file;
while (dir.readDir(dirBuf) > 0) { // Read every file in the directory one at a time
// Skip it if not a subdirectory and not a .WAV file
if (!DIR_IS_SUBDIR(dirBuf)
&& strncmp_P((char *)&dirBuf.name[8], PSTR("WAV"), 3)) {
continue;
}
Serial.println(); // clear out a new line
for (uint8_t i = 0; i < dirLevel; i++) {
Serial.write(' '); // this is for prettyprinting, put spaces in front
}
if (!file.open(vol, dirBuf)) { // open the file in the directory
error("file.open failed"); // something went wrong
}
if (file.isDir()) { // check if we opened a new directory
putstring("Subdir: ");
printEntryName(dirBuf);
Serial.println();
dirLevel += 2; // add more spaces
// play files in subdirectory
play(file); // recursive!
dirLevel -= 2;
}
else {
// Aha! we found a file that isnt a directory
putstring("Playing ");
printEntryName(dirBuf); // print it out
if (!wave.create(file)) { // Figure out, is it a WAV proper?
putstring(" Not a valid WAV"); // ok skip it
} else {
Serial.println(); // Hooray it IS a WAV proper!
wave.play(); // make some noise!
uint8_t n = 0;
while (wave.isplaying) {// playing occurs in interrupts, so we print dots in realtime
putstring(".");
if (!(++n % 32))Serial.println();
delay(100);
}
sdErrorCheck(); // everything OK?
// if (wave.errors)Serial.println(wave.errors); // wave decoding errors
}
}
}
}
如果一切正常的话,下载此程序后即可开始播放.如果你一时没有合适的资源,请下载本文后面的附件中本人的资源进行播放.
总结与参考
这个Arduino的Shield模块具备了音频播放器的种种功能,因为配备了SD卡, 使得资源不成问题.受限于主控性能, 不能解码一些流行格式如MP3,AAC,但是转换也是个一次性的工作,也不显得非常麻烦.笔者自己转换了几十集的评书, 准备每天回来听一集. 总体来说,这个模块很有味道, 值得喜欢音频的同学买一块或者DIY一块.
此模块的官方连接: https://www.adafruit.com/product/94
本人的资源供参考使用: 链接: https://pan.baidu.com/s/1caYUvG 密码: enfw
(注意要放在SD卡的根目录)