查看: 3935|回复: 0

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

[复制链接]
  • TA的每日心情
    慵懒
    2015-5-29 12:01
  • 签到天数: 11 天

    连续签到: 1 天

    [LV.3]偶尔看看II

    发表于 2013-8-12 16:39:35 | 显示全部楼层 |阅读模式
    分享到:
        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_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音频,大家可以尝试一下音量可通过耳机来调,前提是你的耳机要能调节音量才行。
    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /2 下一条

    手机版|小黑屋|与非网

    GMT+8, 2024-4-20 14:21 , Processed in 0.137368 second(s), 18 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2020, Tencent Cloud.