原创 【原】基于STM32的MP3播放器设计与实现(一)(提供MDK完整源码)

2009-10-8 09:57 11333 8 49 分类: MCU/ 嵌入式

本文将介绍一个利用STM32处理器实现简易MP3 Player的设计实例,这个综合应用实例有助于读者了解STM32SPI接口、SD卡、TIMER、中断、FAT文件系统、USB等的应用。


这里提供了两种设计方案,第一种方案是简易声波播放器,仅使用STM103V100评估板,令计时器TIM4工作在PWM模式下,将wav格式的声波文件从SD卡中读出,由TIM4产生不同频率的方波通过低通滤波器和放大器送喇叭,如图1所示;第二种方案则是简易MP3播放器,还需要使用额外的解码芯片,将MP3格式的文件从SD卡读出,然后送解码芯片解码播放,如图2所示。本节将先介绍SD卡、FAT16文件格式、VS1003编解码器等关键部分,然后再分别给出两种设计方案的软件设计。


367E76F161D77A24A07715CE33D858DA_500.jpg


1 简易声波播放器方案


98B061DE2A2C0DC04AFD2137F9DAB087_500.jpg


2 简易MP3 Player方案


1 SD卡的结构及读写方法


STM103V100评估板有SD连接器,其使用SPI总线与STM32处理器连接,如图3所示。



9E29FCC40EDA4FB0BC4606EF6D1ABEA7_500.jpg


3 SD连接器与STM32处理器SPI连接图


SD卡(Secure Digital Memory Card)是一种为满足安全性、容量、性能和使用环境等各方面的需求而设计的一种新型存储器件,SD卡允许在两种模式下工作,即SD模式和SPI模式,本系统采用SPI模式。本小节仅简要介绍在SPI模式下,STM32处理器如何读写SD卡,如果读者如希望详细了解SD卡,可以参考相关资料。SD卡内部结构及引脚如图4所示。



78CD675A1DF1CE2D7D42ACB6BF66BF4D_500.jpg


4 SD卡内部结构及引脚


SD卡主要引脚和功能为:


n         CLK:时钟信号,每个时钟周期传输一个命令或数据位,频率可在025MHz之间变化,SD卡的总线管理器可以不受任何限制的自由产生025MHz的频率;


n         CMD:双向命令和回复线,命令是一次主机到从卡操作的开始,命令可以是从主机到单卡寻址,也可以是到所有卡;回复是对之前命令的回答,回复可以来自单卡或所有卡;


n         DAT03:数据线,数据可以从卡传向主机也可以从主机传向卡。


     SD卡以命令形式来控制SD卡的读写等操作。可根据命令对多块或单块进行读写操作。在SPI模式下其命令由6个字节构成,其中高位在前。SD卡命令的格式如表1所示,其中相关参数可以查阅SD卡规范。


1 SPI命令格式




Byte 1


Byte2-5


Byte 6


7


6


5        0


31         0


7


0


0


1


Command


Command Argument


CRC


1


下面分别给出读写SD卡的两个函数:


n         读取SD卡函数u8 MSD_ReadBlock(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead);


/*****************************************************************


* Function Name : MSD_ReadBlock


* Description    : Reads a block of data from the MSD.


* Input          : - pBuffer : pointer to the buffer that receives the data read


*                    from the MSD.


*                  - ReadAddr : MSD's internal address to read from.


*                  - NumByteToRead : number of bytes to read from the MSD.


* Output         : None


* Return         : The MSD Response:


- MSD_RESPONSE_FAILURE: Sequence failed


*                   - MSD_RESPONSE_NO_ERROR: Sequence succeed


*****************************************************************/


u8 MSD_ReadBlock(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)


{


  u32 i = 0;


  u8 rvalue = MSD_RESPONSE_FAILURE;


 


  /* MSD chip select low */


  MSD_CS_LOW();


  /* Send CMD17 (MSD_READ_SINGLE_BLOCK) to read one block */


  MSD_SendCmd(MSD_READ_SINGLE_BLOCK, ReadAddr, 0xFF);


 


  /* Check if the MSD acknowledged the read block command:


 R1 response (0x00: no errors) */


  if (!MSD_GetResponse(MSD_RESPONSE_NO_ERROR))


  {


    /* Now look for the data token to signify the start of the data */


    if (!MSD_GetResponse(MSD_START_DATA_SINGLE_BLOCK_READ))


    {


      /* Read the MSD block data : read NumByteToRead data */


      for (i = 0; i < NumByteToRead; i++)


      {


        /* Save the received data */


        *pBuffer = MSD_ReadByte();


        /* Point to the next location where the byte read will be saved */


        pBuffer++;


      }


      /* Get CRC bytes (not really needed by us, but required by MSD) */


      MSD_ReadByte();


      MSD_ReadByte();


      /* Set response value to success */


      rvalue = MSD_RESPONSE_NO_ERROR;


    }


  }


 


  /* MSD chip select high */


  MSD_CS_HIGH();


  /* Send dummy byte: 8 Clock pulses of delay */


  MSD_WriteByte(DUMMY);


  /* Returns the reponse */


  return rvalue;


}


n         写读取SD卡函数u8 MSD_WriteBlock(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)


/*****************************************************************


* Function Name : MSD_WriteBlock


* Description    : Writes a block on the MSD


* Input          : - pBuffer : pointer to the buffer containing the data to be


*                    written on the MSD.


*                  - WriteAddr : address to write on.


*                  - NumByteToWrite: number of data to write


* Output         : None


* Return         : The MSD Response:


- MSD_RESPONSE_FAILURE: Sequence failed


*                   - MSD_RESPONSE_NO_ERROR: Sequence succeed


*****************************************************************/


u8 MSD_WriteBlock(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)


{


  u32 i = 0;


  u8 rvalue = MSD_RESPONSE_FAILURE;


 


  /* MSD chip select low */


  MSD_CS_LOW();


  /* Send CMD24 (MSD_WRITE_BLOCK) to write multiple block */


  MSD_SendCmd(MSD_WRITE_BLOCK, WriteAddr, 0xFF);


 


  /* Check if the MSD acknowledged the write block command:


R1 response (0x00: no errors) */


  if (!MSD_GetResponse(MSD_RESPONSE_NO_ERROR))


  {


    /* Send a dummy byte */


    MSD_WriteByte(DUMMY);


    /* Send the data token to signify the start of the data */


    MSD_WriteByte(0xFE);


    /* Write the block data to MSD : write count data by block */


    for (i = 0; i < NumByteToWrite; i++)


    {


      /* Send the pointed byte */


      MSD_WriteByte(*pBuffer);


      /* Point to the next location where the byte read will be saved */


      pBuffer++;


    }


    /* Put CRC bytes (not really needed by us, but required by MSD) */


    MSD_ReadByte();


    MSD_ReadByte();


    /* Read data response */


    if (MSD_GetDataResponse() == MSD_DATA_OK)


    {


      rvalue = MSD_RESPONSE_NO_ERROR;


    }


  }


 


  /* MSD chip select high */


  MSD_CS_HIGH();


  /* Send dummy byte: 8 Clock pulses of delay */


  MSD_WriteByte(DUMMY);


  /* Returns the reponse */


  return rvalue;


}


2 FAT16文件系统简介


     SD卡如果采用FAT16文件格式,按照其不同的特点和作用大致可分为5 部分:MBR区、DBR区、FAT区、FDT区和DATA区。由于SD卡一般不做引导盘,一般也不分区,因此通常无MBR区,直接从DBR区开始。下面对后面四个区分别作简介:


n         DBR


    内容为系统引导记录,它包括一个引导程序和一个被称为BPBBios Parameter Block)的本分区参数记录表。引导程序的主要任务是当MBR将系统控制权交给它时,判断本分区根目录是否有操作系统引导文件,如果有则将其读入内存,并把控制权交给该文件。BPB参数块记录着本分区的起始扇区、结束扇区、文件存储格式、根目录大小、FAT个数,分配单元大小等重要参数。本系统采用的DBR结构为:


typedef __packed struct


{/* 由于Cortex-M3内核默认以对齐方式访问,因此可能导致结构体元素之间有“空隙”,读出的结构体元素有误,因此需要加上关键字__packed,强制其以压缩方式存储结构体。这样该结构体在内存空间上是一片连续的空间,不存在“空隙”情况。其它地方同理 */


     u8         BS_jmpBoot[3];     //ofs:0.典型的如:0xEB,0x3E,0x90


     u8         BS_OEMName[8];  //ofs:3.典型的如:“MSWIN4.1


     u16        BPB_BytesPerSec;  //ofs:11.每扇区字节数


     u8         BPB_SecPerClus;          //ofs:13.每簇扇区数


     u16        BPB_RsvdSecCnt;         //ofs:14.保留扇区数,从DBR FAT 的扇区数


     u8         BPB_NumFATs;     //ofs:16.FAT 的个数,通常为2


     u16        BPB_RootEntCnt;         //ofs:17.根目录项数


     u16        BPB_TotSec16;            //ofs:19.分区总扇区数(<32M时用)


     u8         BPB_Media;        //ofs:21.分区介质标识,SD卡一般用0xF8


     u16        BPB_FATSz16;             //ofs:22.每个FAT 占的扇区数


     u16        BPB_SecPerTrk;           //ofs:24.每道扇区数,对于SD卡无意义


     u16        BPB_NumHeads;    //ofs:26.磁头数,对于SD卡无意义


     u32        BPB_HiddSec;       //ofs:28.隐藏扇区数,从MBRDBR的扇区数


     u32        BPB_TotSec32;      //ofs:32.分区总扇区数(≥32M时用)


     u8         BS_DrvNum;         //ofs:36.软盘:0x00,硬盘:0x80SD卡无意义


     u8         BS_Reservedl;       //ofs:37.保留


     u8         BS_BootSig;         //ofs:38.扩展引导标记:0x29,通常对于SD卡无意义


     u32        BS_VolID;          //ofs:39.盘序列号


     u8         BS_VolLab[11];    //ofs:43.如“Msdos


     u8         BS_FilSysType[8];  //ofs:54.FAT16


     u8         ExecutableCode[448];         //ofs:62.引导代码


     u8         ExecutableMarker[2];    //ofs:510.结束标识:0xAA55


} FAT_BPB;


n         FAT


    该区内容为文件分配表,FAT16文件系统进行空间分配的最基本单位是簇。文件分配表反映了SD卡所有簇的使用情况,通过查文件分配表可以得知任一簇的使用情况。对于FAT16来说,FAT表每项占用两个字节。FAT表的第一项通常为FFF8H。对于其它项,若其值为0000H表示可用;FFF7H表示为坏簇;FFF8H-FFFFH之间表示该簇为某文件或目录的最后一个簇,FFF0H-FFF6H之间为保留值;其它值则指示下一个簇的簇号。


n         FDT


   该区的内容为文件目录表,FAT文件系统的一个重要思想是把目录(文件夹)当作一个特殊的文件来处理,FAT32甚至将根目录当作文件处理。FAT分区中所有目录文件,实际上可以看作是一个存放其它文件(文件夹)入口参数的数据表。因此,目录占用空间的大小并不等同于其下所有数据的大小,但也不等于0,通常是占很小的空间。其具体的存储原理是:不管目录文件所占空间为多少簇,一簇为多少扇区、多少字节;系统都会以32个字节为单位,进行目录文件所占簇的分配。本系统目录项使用的结构体如下:


typedef __packed struct


{


u8            FileName[8];            //ofs:0.文件名


u8            ExtName[3];            //ofs:8.扩展名


u8              Attribute;                 //ofs:11.文件属性。典型值:存档(0x20)、卷标(0x08)


u8             Reserved[10];          //ofs:21.保留


u16            Time;                     //ofs:22.时间


u16            Data;                    //ofs:24.日期


u16            StartClus;                      //ofs:26.开始簇号


u32            FileLength;               //ofs:28.文件长度


} DIR;


   由于篇幅关系,这里不能对上面各字段进行一一介绍。


本系统支持长文件名和汉字显示,如果使用短文件名,则以ASCII编码;当文件名超过8个字节,扩展名超过3字节时,就以长文件名的形式存储,长文件名中的字符采用UNICODE形式编码,每个字符占据2个字节的空间,其目录项中偏移为11的字节即Attribute字段为0FH。在存储时将长文件名以13个字符为单位进行切割,每一组占据一个目录项,所以可能一个文件需要多个目录项,这时长文件名的各个目录项按倒序排列在目录表中,以防与其他文件名混淆。


本系统采用串口作为输入输出接口,通过在PC的超级终端中输入命令来对其进行控制,并在超级终端中显示输出的结果,如路径、目录列表、文件名等相关信息。超级终端输入输出的汉字以GB2312编码、输入输出英文字符以ASCII编码,而长文件名以UNICODE编码。因此需要将UNICODE编码转化为GB2312ASCII编码来和用户交互。这里使用一张排序二维表,里面有7000多个汉字UNICODE编码对应的GB2312编码。这里查找算法采用二分查找算法,大大提高了查找效率,这对于嵌入式应用很关键。


n         DATA


    该数据区存放文件的内容,SD卡所占用的空间绝大部分为此部分。如果文件长度大于一个簇的大小,需要多个簇存放该文件,这些放通过FAT链表串连起来。


3 VS1003-MP3/WMA音频编解码器简介


     VS1003是由芬兰VLSI Solution Oy公司所设计的一款单片MP3/WMA/MIDI音频解码器和ADPCM编码器,它包含一个高性能、自主产权的低功耗DSP处理器核VS_DSP4。该芯片内部结构如图5所示,其特点如下:



712806317E8A998D91E265C2A1637BD3_500.jpg


5 VS1003内部结构及引脚


 


n         可解码MPEG 1MPEG 2音频层IIICBR+VBR+ABR)、WMA 4.0/4.1/7/8/9 5-384kbps 所有流文件;WAV(PCM+IMA AD-PCM),可产生MIDI/SP-MIDI 文件;


n         可对话筒输入或线路输入的音频信号进行IMA ADPCM编码;


n         支持MP3WAV流;


n         高音、低音控制;


n         单时钟操作(12..13MHz);


n         内部PLL锁相环时钟倍频器;


n         低功耗


n         内含高性能片上立体声数模转换器,两声道间无相位差;


n         内含能驱动30欧负载的耳机驱动器;


n         模拟、数字、I/O单独供电;


n         为用户代码和数据准备了5.5KB片上RAM


n         串行的控制、数据接口;


n         可被用作微处理器的从机;


n         特殊应用的SPI Flash引导;


n         供调试用途的UART接口;


n         新功能可以通过软件和4GPIO添加。


    限于篇幅,这里不对VS1003芯片作详细介绍,读者有兴趣可查阅相关资料及数据手册。


注意,VS1003可以作为一个微控制器的从机,通过串行SPI接口来接收输入的比特流,输入的比特流被解码后,可以通过一个数字音量控制器到达一个18 位过采样多位DAC。这样利用一个VS1003芯片与STM32F103x处理器配合,STM32处理器读取SD卡中的MP3文件,将其通过SPI接口送往VS1003芯片播放,然后再利用STM32F10X处理器的一些GPIO口来控制VS1003即可以实现一个MP3 Player的原形设计。


 


PARTNER CONTENT

文章评论41条评论)

登录后参与讨论

用户377235 2012-5-22 18:04

使用STM32还要用外部MP3解码芯片?糟蹋了STM32!如果只是从SD卡读数据再送给MP3解码芯片,普通8位单片机就够了,十年我就用MOTOROLA的8位单片机做了这种方案的MP3播放器,当时用的是NAND FLASH。

用户1358696 2010-1-20 21:36

谢谢楼主!!!正在研究这个呢!

用户1422179 2009-12-7 21:48

东西呢

用户1593293 2009-12-7 11:10

正在研读ing,3ks,楼主!

用户183849 2009-11-30 03:04

不错的东西。

用户549590 2009-11-27 18:33

用户154520 2009-9-23 19:41

【原】基于STM32的MP3播放器设计与实现(二)(提供MDK完整源码) 后面的附件里有源码

用户192841 2009-9-20 18:50

怎么下代码啊?

用户247726 2009-8-22 16:39

路过看看

用户1102935 2009-7-23 12:15

谢谢分享
相关推荐阅读
用户154520 2010-03-01 14:49
【原】工作汇总
    作为应届生,一直把第一份工作看得很重,也一直在努力着,今天突然接到百度的电话offer,出乎意料,也感觉百度的速度确实太慢了,本来已经安心去腾讯的,现在又得折腾一下子。找工作的时间主要集中在9...
用户154520 2009-10-08 10:16
【原】基于STM32的MP3播放器设计与实现(二)(提供MDK完整源码)
4 简易声波播放器的设计与实现4.1 软件程序设计    由“基于STM32的MP3播放器设计与实现(一)”所述内容及图1可以得到简易声音播放器的软件工作过程:通过SPI1从SD卡中读取声波文件,将所...
用户154520 2009-10-08 10:05
汉字显示及字库
https://static.assets-stash.eet-china.com/album/old-resources/2009/6/26/0f00e9de-3982-460e-b215-81b8...
用户154520 2009-10-08 10:04
【原】VB,Eclipse,CSharp对Oracle之XML操作的例子
https://static.assets-stash.eet-china.com/album/old-resources/2009/6/26/1afca68f-1ec1-490d-a51d-cb70...
用户154520 2009-10-08 10:04
linux下备份还原MBR
备份:dd   if="/dev/hda"   of="mbr".bak   bs="512"   count="1"   恢复:dd   if="mbr".bak   of="/dev/hda"  ...
用户154520 2009-10-08 10:04
【原】硬盘安装SUSE-Linux-10.1-GM-DVD-i386
硬盘安装SUSE-Linux-10.1-GM-DVD-i386                            (安装文件可在http://www.chinaunix.com下载)我电脑有四个主...
EE直播间
更多
我要评论
41
8
关闭 站长推荐上一条 /1 下一条