×
嵌入式 > 技术百科 > 详情

零死角玩转stm32-高级篇之MP3(支持中英文、长短文件名)

发布时间:2020-06-08 发布时间:
|

    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(" \r\n 这是一个MP3测试例程 !\r\n " );

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(  " \r\n 文件名为: %s \r\n",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( "\r\n 曲目    :%s \r\n", id3v1.title );

printf( "\r\n 艺术家  :%s \r\n", id3v1.artist );

printf( "\r\n 专辑    :%s \r\n", id3v1.album );

}

else

{// 有些MP3文件没有ID3V1 tag,只有ID3V2 tag

res = f_lseek(&fsrc, 0);

Read_ID3V2(&fsrc, &id3v2);

printf( "\r\n 曲目    :%s \r\n", id3v2.title );

printf( "\r\n 艺术家  :%s \r\n", 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( " \r\n 开始播放 \r\n" );

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( " \r\n 播放结束 \r\n" );

/* ------------------- 一曲结束 --------------------*/

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_LFNMAX_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音频,大家可以尝试一下音量可通过耳机来调,前提是你的耳机要能调节音量才行。


『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
Semtech的LoRa技术携手Chipsafer将牧场连接至云端