TA的每日心情 | 慵懒 2015-5-29 12:01 |
---|
签到天数: 11 天 连续签到: 1 天 [LV.3]偶尔看看II
|
3、MP3(支持中英文、长短文件名)
3.1 实验描述及工程文件清单
实验描述:
将MicroSD卡(以文件系统FATFS访问)里面的mp3文件通过VS1003B解码,然后将解码后的数据送到功放TDA1308后通过耳机播放出来。注意:野火M3-V1的MP3模块是加了功放,野火M3-V3里面去掉了功放TDA1308,因为从VS1003出来的模拟信号足够驱动耳机。(这个文档是更新版本的,配套的例程已经可以支持长中文文件名、4G 的sd卡,可以播放mp3,wma,mid和部分的wav格式的音频文件)
硬件连接:
PB13-SPI2_SCK : VS1003B-SCLK
PB14-SPI2_MISO : VS1003B-SO
PB15-SPI2_MOSI : VS1003B-SI
PB12-SPI2_NSS : VS1003B-XCS
PB11 : VS1003B-XRET
PC6 : VS1003B-XDCS
PC7 : VS1003B-DREQ
用到的库文件:
startup/start_stm32f10x_hd.c
CMSIS/core_cm3.c
CMSIS/system_stm32f10x.c
FWlib/stm32f10x_gpio.c
FWlib/stm32f10x_rcc.c
FWlib/stm32f10x_usart.c
FWlib/stm32f10x_sdio.c
FWlib/stm32f10x_dma.c
FWlib/stm32f10x_spi.c
FWlib/misc.c
用户编写的文件:
USER/main.c
USER/stm32f10x_it.c
USER/sdio_sdcard.c
USER/ff.c
USER/usart1.c
USER/mp3play.c
USER/vs1003.c
USER/SysTick.c
文件系统文件:
ff9/diskio.c
ff9/ff.c
ff9/cc936.c
野火STM32开发板中MP3硬件接口图
MP3模块原理图(野火M3-V3没有板载MP3,需要另外选购)
解码部分采用 VS1003-MP3/WMA 音频解码器,然后将解码后的数据送TDA1308放大后由音频接口外播出来。注意:野火M3-V1的MP3模块是加了功放,野火M3-V3里面去掉了功放TDA1308,因为从VS1003出来的模拟信号足够驱动耳机。
3.2 VS1003 & TDA1308简介
3.2.1 VS1003
VS1003 是一个单片 MP3/WMA/MIDI 音频解码器和 ADPCM 编码器。它包含一个高性能,自主产权的低功耗 DSP 处理器核 VS_DSP 4 ,工作数据存储器,为用户应用提供 5KB 的指令 RAM 和 0.5KB 的数据 RAM。串行的控制和数据接口,4 个常规用途的 I/O 口,一个 UART,也有一个高品质可变采样率的 ADC 和立体声 DAC,还有一个耳机放大器和地线缓冲器。
VS1003 通过一个串行接口来接收输入的比特流,它可以作为一个系统的从机。输入的比特流被解码,然后通过一个数字音量控制器到达一个 18 位过采样多位ε-Δ DAC。通过串行总线控制解码器。除了基本的解码,在用户 RAM 中它还可以做其他特殊应用,例如 DSP 音效处理。
VS1003原理框图:
本实验中我们只用了红色圆圈中的那几个数据口,这些数据口是串行模式的,我们用到了开发板中的SPI2来控制。其中数据经SI接口进去,经解码后由L、R这两个左右声道引脚出来,因为VS1003内部集成了一个DA,所以出来的数据是模拟的,可直接驱动耳机,一般不需要另外加耳机功放。
3.2.2 TDA1308
TDA1308是一款双通道的立体耳机驱动器,是一款专门用于耳机驱动的功放。其原理框图如右:
有关VS1003B和TDA1308的详细应用,大家可参考官方的datasheet,野火就不在这里罗嗦。野火只是在这里介绍TDA1308下,让大家知道有这回事。野火M3-V3里面的MP3模块中采用的是VS1003的官方应用电路,没有加耳机功放,而是直接驱动耳机。
3.3 实验讲解
本实验是在 《2、FatFS(Rev-R0.09)》这个实验基础上进行的。 没做过这个实验的话可参考前面的教程,否则有些代码会让您犯糊涂。
首先需要将需要用到的库文件添加进来,有关库的配置可参考前面的教程,这里不再详述。在配置好库的环境之后我们从main函数开始分析:
int main(void)
{
SysTick_Init(); /* 配置SysTick 为10us中断一次 */
USART1_Config(); /* 配置串口1 115200 8-N-1 *
* Interrupt Config,配置sdio的中断优先级, */
NVIC_Configuration();
printf(" 这是一个MP3测试例程 ! " );
VS1003_SPI_Init(); /* MP3硬件I/O初始化 */
MP3_Start(); /* MP3就绪,准备播放,在vs1003.c实现 */
MP3_Play(); /* 播放SD卡(FATFS)里面的音频文件 *
* Infinite loop */
while (1)
{
}
}
这里没有调用库函数SystemInit();是因为在3.5的固件库中,在3.5版本的库中SystemInit()函数在启动文件startup_stm32f10x_hd.d中已用汇编语句调用了,设置的时钟为默认的72M。所以在main函数就不需要再调用啦,当然,再调用一次也是没问题的。
如果你使用的是其它版本的库,在所有工作之前首先要做的就是先设置系统时钟,这可千万别忘了。在ST3.0.0版本之后的库中,这部分工作都放在了启动文件中了,由汇编实现,只要用户代码一进入main函数就表示已经初始化好系统时钟了,完全不用用户考虑,用户不知道这点的话还以为不需要初始化系统时钟呢。至于ST3.0.0和之后高版本的库有什么区别,我想说的是没什么大的区别,代码的目录结构基本没有改变,只是在代码的功能增多了,支持更完善的外设。
SysTick 为10us中断一次用于SysTick 为10us中断一次,用于后面的延时函数。
USART1_Config(); 配置串口1波特率为 115200 ,8个数据位,1个停止位,无硬件流控制。
NVIC_Configuration(); 用于配置MicroSD卡的中断优先级。
VS1003_SPI_Init();用于初始化MP3解码芯片VS1003B需要用到的I/O口,包括数据口(SPI2)和控制I/O。VS1003_SPI_Init();由用户在vs1003.c中实现:
/*
* 函数名:VS1003_SPI_Init
* 描述 :VS1003所用I/O初始化
* 输入 :无
* 输出 :无
* 调用 :外部调用
*/
void VS1003_SPI_Init(void)
{
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能VS1003B所用I/O的时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC , ENABLE);
/* 使能SPI2 时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2 ,ENABLE);
/* 配置 SPI2 引脚: PB13-SCK, PB14-MISO 和 PB15-MOSI */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* PB12-XCS(片选) */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* PB11-XRST(复位) */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* PC6-XDCS(数据命令选择) */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* PC7-DREQ(数据中断) */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* SPI2 configuration */
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI2, &SPI_InitStructure);
/* Enable SPI2 */
SPI_Cmd(SPI2, ENABLE);
}
假如我们要将数据口换成SPI1或者改变其他控制I/O,只需改变这个函数即可,移植性非常强。关于STM32的SPI接口详细使用教程,请参照前面的FLASH或E2PROM的文档。
MP3_Start(); 使MP3进入就绪模式(standby),随时播放音乐。MP3_Start();在vs1003.c中实现:
* 函数名:MP3_Start
* 描述 :使MP3进入就绪模式,随时准备播放音乐。
* 输入 :无
* 输出 :无
* 调用 :外部调用
*/
void MP3_Start(void)
{
u8 BassEnhanceValue = 0x00; // 低音值先初始化为0
u8 TrebleEnhanceValue = 0x00; // 高音值先初始化为0
TRST_SET(0);
Delay_us( 1000 ); // 1000*10us = 10ms
VS1003_WriteByte(0xff); // 发送一个字节的无效数据,启动SPI传输
TXDCS_SET(1);
TCS_SET(1);
TRST_SET(1);
Delay_us( 1000 );
Mp3WriteRegister( SPI_MODE,0x08,0x00); // 进入VS1003的播放模式
Mp3WriteRegister(3, 0x98, 0x00); // 设置vs1003的时钟,3倍频
Mp3WriteRegister(5, 0xBB, 0x81); // 采样率48k,立体声
// 设置重低音
Mp3WriteRegister(SPI_BASS, TrebleEnhanceValue, BassEnhanceValue);
Mp3WriteRegister(0x0b,0x00,0x00); // VS1003 音量
Delay_us( 1000 );
while( DREQ == 0 ); // 等待DREQ为高 表示能够接受音乐数据输入
}
函数中涉及到的宏定义都在vs1003.h这个头文件中实现。关于函数中为什么要这样操作寄存器,或者为什么要按照这个顺序来操作寄存器,请大家查阅vs1003的pdf,里面讲得很详细,有e文跟中文资料。
MP3_Play(); 这个函数逐个扫描我们卡里面的音频文件,把根目录下的所有音频文件播放一次,若音频文件放在其它目录,可以通过修改代码中的文件路径来实现。以下是MP3_Play();在vs1003.c中实现:
/*
* 函数名:MP3_Play
* 描述 :读取SD卡里面的音频文件,并通过耳机播放出来
* 支持的格式:mp3,mid,wma, 部分的wav
* 输入 :无
* 输出 :无
* 说明 :已添加支持长中文文件名
*/
void MP3_Play(void)
{
FATFS fs; // Work area (file system object) for logical drive
FRESULT res;
UINT br; /*读取出的字节数,用于判断是否到达文件尾*/
FIL fsrc; // file objects
FILINFO finfo; /*文件信息*/
DIR dirs;
uint16_t count = 0;
char lfn[70]; /*为支持长文件的数组,[]最大支持255*/
char j = 0;
char path[100] = {""}; /* MicroSD卡根目录 */
char *result1, *result2, *result3, *result4;
BYTE buffer[512]; /* 存放读取出的文件数据 */
finfo.lfname = lfn; /*为长文件名分配空间*/
finfo.lfsize = sizeof(lfn); /*空间大小*/
f_mount(0, &fs); /* 挂载文件系统到0区 */
if (f_opendir(&dirs,path) == FR_OK) /* 打开根目录 */
{
while (f_readdir(&dirs, &finfo) == FR_OK) /* 依次读取文件名 */
{
if ( finfo.fattrib & AM_ARC ) /* 判断是否为存档型文档 */
{
if(finfo.lfname[0] == NULL && finfo.fname !=NULL) /*当长文件名称为空,短文件名非空时转换*/
finfo.lfname =finfo.fname;
if( !finfo.lfname[0] ) /* 文件名为空即到达了目录的末尾,退出 */
break;
printf( " 文件名为: %s ",finfo.lfname );
result1 = strstr( finfo.lfname, ".mp3" ); /* 判断是否为音频文件 */
result2 = strstr( finfo.lfname, ".mid" );
result3 = strstr( finfo.lfname, ".wav" );
result4 = strstr( finfo.lfname, ".wma" );
if ( result1!=NULL || result2!=NULL || result3!=NULL || result4!=NULL )
{
if(result1 != NULL)/*若是mp3文件则读取mp3的信息*/
{
res = f_open( &fsrc, finfo.lfname, FA_OPEN_EXISTING | FA_READ ); /* 以只读方式打开 *
* 获取歌曲信息(ID3V1 tag / ID3V2 tag) */
if ( Read_ID3V1(&fsrc, &id3v1) == TRUE )
{// ID3V1 tag
printf( " 曲目 :%s ", id3v1.title );
printf( " 艺术家 :%s ", id3v1.artist );
printf( " 专辑 :%s ", id3v1.album );
}
else
{// 有些MP3文件没有ID3V1 tag,只有ID3V2 tag
res = f_lseek(&fsrc, 0);
Read_ID3V2(&fsrc, &id3v2);
printf( " 曲目 :%s ", id3v2.title );
printf( " 艺术家 :%s ", id3v2.artist );
}
}
/* 使文件指针 fsrc 重新指向文件头,因为在调用Read_ID3V1/Read_ID3V2时,
fsrc 的位置改变了 */
res = f_open( &fsrc, finfo.lfname, FA_OPEN_EXISTING | FA_READ );
res = f_lseek(&fsrc, 0);
br = 1; /* br 为全局变量 */
TXDCS_SET( 0 ); /* 选择VS1003的数据接口 *
* ------------------- 一曲开始 --------------------*/
printf( " 开始播放 " );
for (;;)
{
res = f_read( &fsrc, buffer, sizeof(buffer), &br );
if ( res == 0 )
{
count = 0; /* 512字节完重新计数 */
Delay_us( 1000 ); /* 10ms 延时 */
while ( count < 512) /* SD卡读取一个sector,一个sector为512字节 */
{
if ( DREQ != 0 ) /* 等待DREQ为高,请求数据输入 */
{
for (j=0; j<32; j++ ) /* VS1003的FIFO只有32个字节的缓冲 */
{
VS1003_WriteByte( buffer[count] );
count++;
}
}
}
}
if (res || br == 0) break; /* 出错或者到了MP3文件尾 */
}
printf( " 播放结束 " );
/* ------------------- 一曲结束 --------------------*/
count = 0;
/* 根据VS1003的要求,在一曲结束后需发送2048个0来确保下一首的正常播放 */
while ( count < 2048 )
{
if ( DREQ != 0 )
{
for ( j=0; j<32; j++ )
{
VS1003_WriteByte( 0 );
count++;
}
}
}
count = 0;
TXDCS_SET( 1 ); /* 关闭VS1003数据端口 */
f_close(&fsrc); /* 关闭打开的文件 */
}
}
} /* while (f_readdir(&dirs, &finfo) == FR_OK) */
} /* if (f_opendir(&dirs, path) == FR_OK) */
} /* end of MP3_Play */
由于代码比较长,在格式编排上不是很好,野火建议大家还是配合源代码一起阅读^_^。
现在我们来大概分析下MP3_Play();这个函数,这里边涉及到一些文件系统操作的函数,关于这部分函数的操作大家可参考前面的教程或者阅读FATFS的官方文档,其实我的教程也不完全正确,阅读官方的文档才是最可靠的。
首先说一下为支持中文长文件名的文件系统配置。
要在ffconf.h文件中的Namespace configuration宏配置中设定如下:
/*---------------------------------------------------------------------------
Locale and Namespace Configurations
/----------------------------------------------------------------------------*/
#define _CODE_PAGE 936
/* The _CODE_PAGE specifies the OEM code page to be used on the target system.
/ Incorrect setting of the code page can cause a file open failure.
932 - Japanese Shift-JIS (DBCS, OEM, Windows)
/ 936 - Simplified Chinese GBK (DBCS, OEM, Windows)
/ 949 - Korean (DBCS, OEM, Windows)
/ 950 - Traditional Chinese Big5 (DBCS, OEM, Windows)
/ 1250 - Central Europe (Windows)
/ 1251 - Cyrillic (Windows)
/ 1252 - Latin 1 (Windows)
/ 1253 - Greek (Windows)
/ 1254 - Turkish (Windows)
/ 1255 - Hebrew (Windows)
/ 1256 - Arabic (Windows)
/ 1257 - Baltic (Windows)
/ 1258 - Vietnam (OEM, Windows)
/ 437 - U.S. (OEM)
/ 720 - Arabic (OEM)
/ 737 - Greek (OEM)
/ 775 - Baltic (OEM)
/ 850 - Multilingual Latin 1 (OEM)
/ 858 - Multilingual Latin 1 + Euro (OEM)
/ 852 - Latin 2 (OEM)
/ 855 - Cyrillic (OEM)
/ 866 - Russian (OEM)
/ 857 - Turkish (OEM)
/ 862 - Hebrew (OEM)
/ 874 - Thai (OEM, Windows)
/ 1 - ASCII only (Valid for non LFN cfg.)
*/
#define _USE_LFN 2 /* 0 to 3 */
#define _MAX_LFN 255 /* Maximum LFN length to handle (12 to 255) *
* The _USE_LFN option switches the LFN support.
0: Disable LFN feature. _MAX_LFN and _LFN_UNICODE have no effect.
/ 1: Enable LFN with static working buffer on the BSS. Always NOT reentrant.
/ 2: Enable LFN with dynamic working buffer on the STACK.
/ 3: Enable LFN with dynamic working buffer on the HEAP.
The LFN working buffer occupies (_MAX_LFN + 1) * 2 bytes. To enable LFN,
/ Unicode handling functions ff_convert() and ff_wtoupper() must be added
/ to the project. When enable to use heap, memory control functions
/ ff_memalloc() and ff_memfree() must be added to the project. */
#define _LFN_UNICODE 0 /* 0:ANSI/OEM or 1:Unicode *
* To switch the character code set on FatFs API to Unicode,
/ enable LFN feature and set _LFN_UNICODE to 1. */
#define _FS_RPATH 0 /* 0 to 2 *
* The _FS_RPATH option configures relative path feature.
0: Disable relative path feature and remove related functions.
/ 1: Enable relative path. f_chdrive() and f_chdir() are available.
/ 2: f_getcwd() is available in addition to 1.
Note that output of the f_readdir fnction is affected by this option. */
修改的第一个宏配置是_CODE_PAGE 改成简体中文的936.
Code page是什么?我们知道ASCII码的前7位定义的是我们常用的标准字符集,于是128位以下的用处达成了共识,而ASCII码中的第8位没有被使用,对于128位以上的可能有不同的解释,这些不同的解释就叫做code_page,我们使用936这个宏就是调用了简体中文的code_page。所以要支持中文,还要添加fatfs源文件中option目录下的cc936.c文件到工程中。
接下来还要修改_USE_LFN和MAX_LFN的宏,这两个是长文件名支持的配置。
MAX_LFN 定义了最大文件名长度,单位为Byte。
_USE_LFN >= 1则开启长文件支持。
=1表示长文件名的存储在 静态存储区。
=2表示长文件名的存储在 栈区。
=3表示长文件名的存储在 堆区。
这里涉及到变量的存储分布问题。
sram 内存变量分布图:
存储在全局变量的内存空间是不会被回收的,栈区是用来存放局部变量的,在子函数调用运行完成之后会释放内存,而且少用全局变量会让代码的移植性更好。所以这里的长文件名变量我们把它设置为2,把它放在栈区。
另外,因为我们的mp3_play()函数中定义了很多局部变量,占用的栈空间很大,所以我们要修改启动文件startup_stm32f10x_hd.s中的栈空间大小:
Stack_Size EQU 0x00000f00 ;Stack_Size,标号。EQU定义
AREA STACK, NOINIT, READWRITE, ALIGN=3 ;定义名称为stack的栈,noinitStack_Mem SPACE Stack_Size ;定义名称为Stack_Mem 大小为stack_size大小
__initial_sp
在这个文件中把原来的
Stack_size EQU 0x00000400
改成了
Stack_size EQU 0x0000f00 。
有时我们调试程序的时候会发现代码莫名奇妙地卡在harddefault 的硬中断里,这时可以检查一下是不是在启动文件中把栈大小设置得太保守了,可以根据实际需要把这个设置得大一点。
文件系统中的文件信息结构体:
/* File status structure (FILINFO) */
typedef struct {
DWORD fsize; /* File size */
WORD fdate; /* Last modified date */
WORD ftime; /* Last modified time */
BYTE fattrib; /* Attribute */
TCHAR fname[13]; /* Short file name (8.3 format) */
#if _USE_LFN
TCHAR* lfname; /* Pointer to the LFN buffer */
UINT lfsize; /* Size of LFN buffer in TCHAR */
#endif
} FILINFO;
关于长文件名(包管中英文)的支持,最后还要注意一点,在使用文件名信息时,不要再使用FILINFO->fname(短文件名数组) 。而应该使用FILINFO->lfname (长文件名指针)。而且长文件名在结构体中定义的是一个指针,在使用前我们要为这个指针分配内存空间,注意不要使用野指针。具体的使用方法可以参照mp3_play()函数中开头的变量定义和赋初值部分。
还要注意一下如果读取的文件名长度不超过FILINFO->fname(短文件名)的空间时,文件名的信息只会保存在短文件名数组中,而FILINFO->lfname (长文件名指针)的值将会是空的,所以我在代码中加了一个判断语句才可以进行正常的使用。
函数f_mount(0, &fs);为我们在文件系统中注册一个工作区,并初始化盘符的名为0。这个函数还调用了底层的disk_initialize (),进行sdio的初始化,所以在文件操作之前必须调用这个函数。不建议在main函数直接调用 disk_initialize ()来对sdio进行初始化,要尽量使用封装好的脱离硬件层的函数,这样会令代码移植性更好呀。
函数f_opendir(&dirs, path) 用于打开卡的根目录,并将这个根目录关联到dirs这个结构指针,然后我们就可以通过这个结构指针来操作这个目录了,其实这个结构指针就类似LINUX下系统编程中的文件描述符,不论是操作还是目录都得通过文件描述符才能操作。
f_readdir(&dirs, &finfo) 函数通过刚刚的dirs结构指针来读取目录里面的信息,并将目录的信息储存在finfo这个结构体变量中。这个结构体中包括了文件名,文件大小,文件类型,修改时间等信息。
紧接着判断文件的属性,如果是存档型文件的话就将文件名打印出来,然后比较文件的后缀名,查看是否为音频文件,支持的音频格式有 mp3、mid、wav、wma。
如果是音频文件的话则调用f_open( &fsrc, finfo.fname, FA_OPEN_EXISTING | FA_READ );打开这个音频文件。
如果是mp3类型的文件件,我们还可以调用Read_ID3V1()和Read_ID3V2()来读取mp3的文件信息,这些文件信息是属于mp3文件的内部数据,可以参照《mp3文件的存储格式》这个文档来理解这两个函数,实质就是把文件记录的数据,按格式把相应的信息整合到结构体里便于使用而已。
我们把读取到的音频数据直接通过SPI接口送入到vs1003就可以进行各种音频数据的解码了。
TXDCS_SET( 0 ); 用于选择vs1003的数据端口,准备往vs1003中输入数据。其中TXDCS_SET( 0 );是在vs1003.h中实现的一个宏:
#define XDCS (1<<6) // PC6-XDCS
#define TXDCS_SET(x) GPIOC->ODR=(GPIOC->ODR&~XDCS)|(x ? XDCS:0)
紧接着进入一个大循环中播放我们的mp3文件。
函数f_read( &fsrc, buffer, sizeof(buffer), &br );从文件中读取512个字节的数据到缓冲区中,至于为什么是512个字节,这是因为卡的一个sector是512个字节,一次只能读取一个sector,实际上也可以一次读取n 个 sector,但在这里没必要。
函数VS1003_WriteByte( buffer[count] );将缓冲区中的数据写入vs1003的数据缓冲区。注意,这里一次只能写入32个字节,这是因为vs1003的FIFO的大小为32个字节,写多了无效。
当文件出错或者一曲播放完毕时就跳出for 循环,并打印出“播放结束”的调试信息。
根据VS1003的要求,在一曲结束后需发送2048个0来确保下一首的正常播放。
一曲播放完毕我们关闭vs1003的数据端,关闭打开的文件,等待下一曲的播放,直到目录下的音频文件播放完为止。
这里面涉及到了vs1003操作的一些特性,需大家参考vs1003的datasheet来帮助理解。
3.4 实验现象
将野火STM32开发板供电(DC5V),插上JLINK,插上串口线(两头都是母的交叉线),插上MicroSD卡( 野火用的是1G,也可支持2G、4G,8G ),在卡的根目录下要有mp3文件,打开超级终端,配置超级终端为115200 8-N-1,将编译好的程序下载到开发板,即可看到超级终端打印出如下信息:
野火的卡的根目录下放了1个mp3文件,1个wma文件。可以看到,这个代码支持了超长的中文文件名;也支持了wma的格式,根据vs1003的datasheet说明,还可以支持mid和部分的wav音频,大家可以尝试一下音量可通过耳机来调,前提是你的耳机要能调节音量才行。 |
|