第四十九章 音乐播放器实验 前几年,MP3曾经风行一时,几乎人手一个。如果能自己做一个MP3,我想对于很多朋友来说,是十分骄傲的事情。ALIENTEK战舰STM32开发板自带了一颗非常强劲的MP3解码芯片:VS1053,利用该芯片,我们可以实现MP3/OGG/WMA/WAV/FLAC/AAC/MIDI等各种音频文件的播放。本章,我们将利用战舰STM32开发板实现一个简单的MP3播放器。本章分为如下几个部:
49.1 MP3简介
49.2 硬件设计
49.3 软件设计
49.4 下载验证
49.1 VS1053简介 VS1053是继VS1003后荷兰VLSI公司出品的又一款高性能解码芯片。该芯片可以实现对MP3/OGG/WMA/FLAC/WAV/AAC/MIDI等音频格式的解码,同时还可以支持ADPCM/OGG等格式的编码,性能相对以往的VS1003提升不少。VS1053拥有一个高性能的DSP处理器核VS_DSP,16K的指令RAM,0.5K的数据RAM,通过SPI控制,具有8个可用的通用IO口和一个串口,芯片内部还带了一个可变采样率的立体声ADC(支持咪头/咪头+线路/2线路)、一个高性能立体声DAC及音频耳机放大器。
VS1053的特性如下:
●支持众多音频格式解码,包括OGG/MP3/WMA/WAV/FLAC(需要加载patch)/MIDI/AAC等。
●对话筒输入或线路输入的音频信号进行OGG(需要加载patch)/IMA ADPCM编码
●高低音控制
●带有EarSpeaker空间效果(用耳机虚拟现场空间效果)
●单时钟操作12..13MHz
●内部PLL锁相环时钟倍频器
●低功耗
●内含高性能片上立体声DAC,两声道间无相位差
●过零交差侦测和平滑的音量调整
●内含能驱动30 欧负载的耳机驱动器
●模拟,数字,I/O 单独供电
●为用户代码和数据准备的16KB片上RAM
●可扩展外部DAC的I2S接口
●用于控制和数据的串行接口(SPI)
●可被用作微处理器的从机
●特殊应用的SPI Flash引导
●供调试用途的UART接口
●新功能可以通过软件和8 GPIO 添加
VS1053相对于它的前辈VS1003,增加了编解码格式的支持(比如支持OGG/FLAC,还支持OGG编码,VS1003不支持)、增加了GPIO数量到8个(VS1003只有4个)、增加了内部指令RAM容量到16KiB(VS1003只有5.5KiB)、增加了I2S接口(VS1003没有)、支持EarSpeaker空间效果(VS1003不支持)等。同时VS1053的DAC相对于VS1003有不少提高,同样的歌曲,用VS1053播放,听起来比1003效果好很多。
VS1053的封装引脚和VS1003完全兼容,所以如果你以前用的是VS1003,则只需要把VS1003换成VS1053,就可以实现硬件更新,电路板完全不用修改。不过需要注意的是VS1003的CVDD是2.5V,而VS1053的CVDD是1.8V,所以你还需要把稳压芯片也变一下,其他都可以照旧了。
VS1053通过SPI接口来接受输入的音频数据流,它可以是一个系统的从机,也可以作为独立的主机。这里我们只把它当成从机使用。我们通过SPI口向VS1053不停的输入音频数据,它就会自动帮我们解码了,然后从输出通道输出音乐,这时我们接上耳机就能听到所播放的歌曲了。
ALIENTEK战舰STM32开发板,自带了一颗VS1053音频编解码芯片,所以,我们直接可以通过开发板来播放各种音频格式,实现一个音乐播放器。战舰STM32开发板自带的VS1053解码芯片电路原理图,如图49.1.1所示:
图49.1.1 ALIENTEK 音频解码模块原理图
VS1053通过7根线同STM32连接,他们是:VS_MISO、VS_MOSI、VS_SCK、VS_XCS、VS_XDCS、VS_DREQ和VS_RST。这7根线同STM32的连接关系如表49.1.1所示:
表49.1.1 VS1053各信号线与STM32连接关系
其中VS_RST是VS1053的复位信号线,低电平有效。VS_DREQ是一个数据 请求信号,用来通知主机,VS1053可以接收数据与否。VS_MISO、VS_MOSI和VS_SCK则是VS1053的SPI接口他们在VS_XCS和VS_XDCS下面来执行不同的操作。从上表可以看出,VS1053的SPI是接在STM32的SPI1上面的。
VS1053的SPI支持两种模式:1,VS1002有效模式(即新模式)。2,VS1001兼容模式。这里我们仅介绍VS1002有效模式(此模式也是VS1053的默认模式)。表49.1.2是在新模式下VS1053的SPI信号线功能描述:
表49.1.2 VS1053新模式下SPI口信号线功能
VS1053的SPI数据传送,分为SDI和SCI,分别用来传输数据/命令。SDI和前面介绍的SPI协议一样的,不过VS1053的数据传输是通过DREQ控制的,主机在判断DREQ有效(高电平)之后,直接发送即可(一次可以发送32个字节)。
这里我们重点介绍一下SCI。SCI串行总线命令接口包含了一个指令字节、一个地址字节和一个16位的数据字。读写操作可以读写单个寄存器,在SCK的上升沿读出数据位,所以主机必须在下降沿刷新数据。SCI的字节数据总是高位在前低位在后的。第一个字节指令字节,只有2个指令,也就是读和写,读为0X03,写为0X02。
一个典型的SCI读时序如图49.1.2所示:
图49.1.2 SCI读时序
从图49.1.2可以看出,向VS1053读取数据,通过先拉低XCS(VS_XCS),然后发送读指令(0X03),再发送一个地址,最后,我们在SO线(VS_MISO)上就可以读到输出的数据了。而同时SI(VS_MOSI)上的数据将被忽略。
看完了SCI的读,我们再来看看SCI的写时序,如图49.1.3所示:
图49.1.3 SCI写时序
图49.1.3中,其时序和图49.1.2基本类似,都是先发指令,再发地址。不过写时序中,我们的指令是写指令(0X02),并且数据是通过SI写入VS1053的, SO则一直维持低电平。细心的读者可能发现了,在这两个图中,DREQ信号上都产生了一个短暂的低脉冲,也就是执行时间。这个不难理解,我们在写入和读出VS1053的数据之后,它需要一些时间来处理内部的事情,这段时间,是不允许外部打断的,所以,我们在SCI操作之前,最好判断一下DREQ是否为高电平,如果不是,则等待DREQ变为高。
了解了VS1053的SPI读写,我们再来看看VS1053的SCI寄存器,VS1053的所有SCI寄存器如表49.1.3所示:
表49.1.3 SCI寄存器
VS1053总共有16个SCI寄存器,这里我们不介绍全部的寄存器,仅仅介绍几个我们在本章需要用到的寄存器。
首先是MODE寄存器,该寄存器用于控制VS1053的操作,是最关键的寄存器之一,该寄存器的复位值为0x0800,其实就是默认设置为新模式。表49.1.4是MODE寄存器的各位描述:
表49.1.4 MODE寄存器各位描述
这个寄存器,我们这里只介绍一下第2和第11位,也就是SM_RESET和SM_SDINEW。其他位,我们用默认的即可。这里SM_RESET,可以提供一次软复位,建议在每播放一首歌曲之后,软复位一次。SM_SDINEW为模式设置位,这里我们选择的是VS1002新模式(本地模式),所以设置该位为1(默认的设置)。其他位的详细介绍,请参考VS1053的数据手册。
接着我们看看BASS寄存器,该寄存器可以用于设置VS1053的高低音效。该寄存器的各位描述如表49.1.5所示:
表49.1.5 BASS寄存器各位描述
通过这个寄存器以上位的一些设置,我们可以随意配置自己喜欢的音效(其实就是高低音的调节)。VS1053的EarSpeaker效果则由MODE寄存器控制,请参考表49.1.4。
接下来,我们介绍一下CLOCKF寄存器,这个寄存器用来设置时钟频率、倍频等相关信息,该寄存器的各位描述如表49.1.6所示:
表49.1.6 CLOCKF寄存器各位描述
此寄存器,重点说明SC_FREQ,SC_FREQ是以4Khz为步进的一个时钟寄存器,当外部时钟不是12.288M的时候,其计算公式为:
SC_FREQ=(XTALI-8000000)/4000
式中为XTALI的单位为Hz。表49.1.6中CLKI是内部时钟频率,XTALI是外部晶振的时钟频率。由于我们使用的是12.288M的晶振,在这里设置此寄存器的值为0X9800,也就是设置内部时钟频率为输入时钟频率的3倍,倍频增量为1.0倍。
接下来,我们看看DECODE_TIME这个寄存器。该寄存器是一个存放解码时间的寄存器,以秒钟为单位,我们通过读取该寄存器的值,就可以得到解码时间了。不过它是一个累计时间,所以我们需要在每首歌播放之前把它清空一下,以得到这首歌的准确解码时间。
HDAT0和HDTA1是两个数据流头寄存器,不同的音频文件,读出来的值意义不一样,我们可以通过这两个寄存器来获取音频文件的码率,从而可以计算音频文件的总长度。这两个寄存器的详细介绍,请参考VS1053的数据手册。
最后我们介绍一下VOL这个寄存器,该寄存器用于控制VS1053的输出音量,该寄存器可以分别控制左右声道的音量,每个声道的控制范围为0~254,每个增量代表0.5db的衰减,所以该值越小,代表音量越大。比如设置为0X0000则音量最大,而设置为0XFEFE则音量最小。注意:如果设置VOL的值为0XFFFF,将使芯片进入掉电模式!
关于VS1053的介绍,我们就介绍到这里,更详细的介绍请看VS1053的数据手册。接下来我们说说如何控制通过最简单的步骤,来控制VS1053播放一首歌曲。
1) 复位VS1053
这里包括了硬复位和软复位,是为了让VS1053的状态回到原始状态,准备解码下一首歌曲。这里建议大家在每首歌曲播放之前都执行一次硬件复位和软件复位,以便更好的播放音乐。
2) 配置VS1053的相关寄存器
这里我们配置的寄存器包括VS1053的模式寄存器(MODE)、时钟寄存器(CLOCKF)、音调寄存器(BASS)、音量寄存器(VOL)等。
3) 发送音频数据
当经过以上两步配置以后,我们剩下来要做的事情,就是往VS1053里面扔音频数据了,只要是VS1053支持的音频格式,直接往里面丢就可以了,VS1053会自动识别,并进行播放。不过发送数据要在DREQ信号的控制下有序的进行,不能乱发。这个规则很简单:只要DREQ变高,就向VS1053发送32个字节。然后继续等待DREQ变高,直到音频数据发送完。
经过以上三步,我们就可以播放音乐了。这一部分就先介绍到这里。
49.2 硬件设计本章实验功能简介:开机先检测字库是否存在,如果检测无问题,则对VS1053进行RAM测试和正弦测试,测试完后开始循环播放SD卡MUSIC文件夹里面的歌曲(必须在SD卡根目录建立一个MUSIC文件夹,并存放歌曲在里面),在TFTLCD上显示歌曲名字、播放时间、歌曲总时间、歌曲总数目、当前歌曲的编号等信息。KEY0用于选择下一曲,KEY2用于选择上一曲,WK_UP和KEY1用来调节音量。DS0还是用于指示程序运行状态,DS1用于指示VS1053正在初始化。
本实验用到的资源如下:
1) 指示灯DS0和DS1
2) 四个按键(WK_UP/KEY0/KEY1/KEY2)
3) 串口
4) TFTLCD模块
5) SD卡
6) SPI FLASH
7) VS1053
8) 74HC4052
9) TDA1308
这些硬件我们都已经介绍过了,其中74HC4052和TDA1308分别是用作音频选择和耳机驱动,在第四十章,我们已经介绍过。本章,我们使用的VS1053同STM32的连接关系详见表49.1.1。
本实验,大家需要准备1个SD卡(在里面新建一个MUSIC文件夹,并存放一些歌曲在MUSIC文件夹下)和一个耳机,分别插入SD卡接口和耳机接口,然后下载本实验就可以通过耳机来听歌了。
49.3 软件设计打开上一章的工程,首先在HARDWARE文件夹所在的文件夹下新建一个APP的文件夹。在该文件夹里面新建mp3player.c和mp3player.h两个文件。并将APP文件夹加入头文件包含路径。
然后在HARDWARE文件夹下新建一个VS10XX的文件夹,在该文件夹里面新建VS10XX.c、VS10XX.h和flac.h等三个文件。
首先打开VS10XX.c,里面的代码我们不一一贴出了,这里挑几个重要的函数给大家介绍一下,首先要介绍的是VS_Soft_Reset,该函数用于软复位VS1003,其代码如下:
//软复位VS10XX
void VS_Soft_Reset(void)
{
u8 retry=0;
while(VS_DQ==0); //等待软件复位结束
VS_SPI_ReadWriteByte(0Xff);//启动传输
retry=0;
while(VS_RD_Reg(SPI_MODE)!=0x0800)// 软件复位,新模式
{
VS_WR_Cmd(SPI_MODE,0x0804);// 软件复位,新模式
delay_ms(2);//等待至少1.35ms
if(retry++>100)break;
}
while(VS_DQ==0);//等待软件复位结束
retry=0;
while(VS_RD_Reg(SPI_CLOCKF)!=0X9800)//设置VS10XX的时钟,3倍频 ,1.5xADD
{
VS_WR_Cmd(SPI_CLOCKF,0X9800);//设置VS10XX的时钟,3倍频 ,1.5xADD
if(retry++>100)break;
}
delay_ms(20);
}
该函数比较简单,先配置一下VS1053的模式顺便执行软复位操作,在软复位结束之后,再设置好时钟,完成一次软复位。接下来,我们介绍一下VS_WR_Cmd函数,该函数用于向VS1003写命令,代码如下:
//向VS10XX写命令
//address:命令地址
//data:命令数据
void VS_WR_Cmd(u8 address,u16 data)
{
while(VS_DQ==0);//等待空闲
VS_SPI_SpeedLow();//低速
VS_XDCS=1; ;
VS_XCS=0;
VS_SPI_ReadWriteByte(VS_WRITE_COMMAND);//发送VS10XX的写命令
VS_SPI_ReadWriteByte(address); //地址
VS_SPI_ReadWriteByte(data>>8); //发送高八位
VS_SPI_ReadWriteByte(data); //第八位
VS_XCS=1;
VS_SPI_SpeedHigh(); //高速
}
该函数用于向VS1053发送命令,这里要注意VS1053的写操作比读操作快(写1/4 CLKI,读1/7 CLKI),虽然说写寄存器最快可以到1/4CLKI,但是经实测在1/4CLKI的时候会出错,所以在写寄存器的时候最好把SPI速度调慢点,然后在发送音频数据的时候,就可以1/4CLKI的速度了。有写命令的函数,当然也有读命令的函数了。VS_RD_Reg用于读取VS1053的寄存器的内容。该函数代码如下:
//读VS10XX的寄存器
//address:寄存器地址
//返回值:读到的值
//注意不要用倍速读取,会出错
u16 VS_RD_Reg(u8 address)
{
u16 temp=0;
while(VS_DQ==0);//非等待空闲状态
VS_SPI_SpeedLow();//低速
VS_XDCS=1;
VS_XCS=0;
VS_SPI_ReadWriteByte(VS_READ_COMMAND); //发送VS10XX的读命令
VS_SPI_ReadWriteByte(address); //地址
temp=VS_SPI_ReadWriteByte(0xff); //读取高字节
temp=temp<<8;
temp+=VS_SPI_ReadWriteByte(0xff); //读取低字节
VS_XCS=1;
VS_SPI_SpeedHigh();//高速
return temp;
}
该函数的作用和VS_WR_Cmd的作用基本相反,用于读取寄存器的值。VS10XX.c的剩余代码、VS10XX.h以及flac.h的代码,这里就不贴出来了,其中flac.h仅仅用来存储播放flac格式所需要的patch文件,以支持flac解码。大家可以去光盘查看他们的详细源码。保存VS10XX.c、VS10XX.h和flac.h三个文件。把VS10XX.c加入到HARDWARE组下。然后我们打开mp3player.c,该文件我们仅介绍一个函数,其他代码请看光盘的源码。这里要介绍的是mp3_play_song函数,该函数代码如下:
//播放一曲指定的歌曲
//返回值:0,正常播放完成;1,下一曲;2,上一曲;0XFF,出现错误了;
u8 mp3_play_song(u8 *pname)
{
FIL* fmp3;
u16 br;
u8 res,rval;
u8 *databuf;
u16 i=0;
u8 key;
rval=0;
fmp3=(FIL*)mymalloc(SRAMIN,sizeof(FIL)); //申请内存
databuf=(u8*)mymalloc(SRAMIN,4096); //开辟4096字节的内存区域
if(databuf==NULL||fmp3==NULL)rval=0XFF ; //内存申请失败.
if(rval==0)
{
VS_Restart_Play(); //重启播放
VS_Set_All(); //设置音量等信息
VS_Reset_DecodeTime(); //复位解码时间
res=f_typetell(pname); //得到文件后缀
if(res==0x4c)//如果是flac,加载patch
{
VS_Load_Patch((u16*)vs1053b_patch,VS1053B_PATCHLEN);
}
res=f_open(fmp3,(const TCHAR*)pname,FA_READ);//打开文件
if(res==0)//打开成功.
{
VS_SPI_SpeedHigh(); //高速
while(rval==0)
{
res=f_read(fmp3,databuf,4096,(UINT*)&br);//读出4096个字节
i=0;
do//主播放循环
{
if(VS_Send_MusicData(databuf+i)==0)//给VS10XX发送音频数据
{
i+=32;
}else
{
key=KEY_Scan(0);
switch(key)
{
case KEY_RIGHT: rval=1; break; //下一曲
case KEY_LEFT: rval=2; break; //上一曲
case KEY_UP: //音量增加
if(vsset.mvol<250)
{
vsset.mvol+=5;
VS_Set_Vol(vsset.mvol);
}else vsset.mvol=250;
mp3_vol_show((vsset.mvol-100)/5);//音量限制在: 100~
//250,显示时,按照公式(vol-100)/5,显示,也就是0~30
break;
case KEY_DOWN: //音量减
if(vsset.mvol>100)
{
vsset.mvol-=5;
VS_Set_Vol(vsset.mvol);
}else vsset.mvol=100;
mp3_vol_show((vsset.mvol-100)/5);
break;
}
mp3_msg_show(fmp3->fsize);//显示信息
}
}while(i<4096);//循环发送4096个字节
if(br!=4096||res!=0) {rval=0; break;}//读完了.
}
f_close(fmp3);
}else rval=0XFF;//出现错误
}
myfree(SRAMIN,databuf);
myfree(SRAMIN,fmp3);
return rval;
}
该函数,就是我们解码MP3的核心函数了,该函数在初始化VS1053后,根据文件格式选择是否加载patch(如果是flac格式,则需要加载patch),最后在死循环里面等待DREQ信号的到来,每次VS_DQ变高,就通过VS_Send_MusicData函数向VS1053发送32个字节,直到整个文件读完。此段代码还包含了对按键的处理(音量调节、上一首、下一首)及当前播放的歌曲的一些状态(码率、播放时间、总时间)显示。
mp3player.c的其他代码和mp3player.h在这里就不详细介绍了,请大家直接参考光盘源码。最后,我们在test.c里面修改main函数如下:
int main(void)
{
Stm32_Clock_Init(9); //系统时钟设置
delay_init(72); //延时初始化
uart_init(72,9600); //串口1初始化
LCD_Init(); //初始化液晶
LED_Init(); //LED初始化
KEY_Init(); //按键初始化
Audiosel_Init(); //初始化音源选择
usmart_dev.init(72); //usmart初始化
mem_init(SRAMIN); //初始化内部内存池
VS_Init(); //初始化VS1053
exfuns_init(); //为fatfs相关变量申请内存
f_mount(0,fs[0]); //挂载SD卡
f_mount(1,fs[1]); //挂载FLASH.
POINT_COLOR=RED;
while(font_init()) //检查字库
{
LCD_ShowString(60,50,200,16,16,\"Font Error!\");
delay_ms(200);
LCD_Fill(60,50,240,66,WHITE);//清除显示
}
Show_Str(60,50,200,16,\"战舰 STM32开发板\",16,0);
Show_Str(60,70,200,16,\"音乐播放器实验\",16,0);
Show_Str(60,90,200,16,\"广州星翼电子\",16,0);
Show_Str(60,110,200,16,\"2012年9月20日\",16,0);
Show_Str(60,130,200,16,\"KEY0:NEXT KEY2:PREV\",16,0);
Show_Str(60,150,200,16,\"KEY_UP:VOL+ KEY1:VOL-\",16,0);
while(1)
{
Audiosel_Set(0); //音频通道选择MP3音源
LED1=0;
Show_Str(60,170,200,16,\"存储器测试...\",16,0);
printf(\"Ram Test:0X%04X\\r\\n\",VS_Ram_Test());//打印RAM测试结果
Show_Str(60,170,200,16,\"正弦波测试...\",16,0);
VS_Sine_Test();
Show_Str(60,170,200,16,\"<<音乐播放器>>\",16,0);
LED1=1;
mp3_play();
}
}
该函数先检测外部flash是否存在字库,然后选择音频通道为MP3音源,之后执行VS1053的RAM测试和正弦测试,这两个测试结束后,调用mp3_play函数开始播放SD卡MUSIC文件夹里面的音乐。软件部分就介绍到这里。
49.4 下载验证在代码编译成功之后,我们下载代码到ALIENTEK战舰STM32开发板上,程序先执行字库监测,然后对VS1053进行RAM测试和正弦测试。
当检测到SD卡根目录的MUSIC文件夹有有效音频文件(VS1053所支持的格式)的时候,就开始自动播放歌曲了,如图49.4.1所示:
图49.4.1 MP3播放中
从上图可以看出,当前正在播放第4首歌曲,总共4首歌曲,歌曲名、播放时间、总时长、码率、音量等信息等也都有显示。此时DS0会随着音乐的播放而闪烁,2秒闪烁一次。
只要我们在开发板的PHONE端子插入耳机,就能听到歌曲的声音了。同时,我们可以通过按KEY0和KEY2来切换下一曲和上一曲,通过WK_UP按键来控制音量增加,通过KEY1控制音量减小。
本实验,我们还可以通过USMART来测试VS1053的其他功能,通过将VS10XX.c里面的部分函数加入USMART管理,我们可以很方便的设置/获取VS1053各种参数,达到验证测试的目的。有兴趣的朋友,可以实验测试一下。
至此,我们就完成了一个简单的MP3播放器了,在此基础上进一步完善,就可以做出一个比较实用的MP3了。大家可以自己发挥想象,做出一个你心仪的MP3。