正点原子STM32F103战舰开发指南V1.2-音乐播放器实验摘要

1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html#

第五十四章 音乐播放器实验

正点原子战舰STM32F103板载了VS1053B这颗高性能音频编解码芯片,该芯片可以支持wav/mp3/wma/flac/ogg/midi/aac等音频格式的播放,并且支持录音(下一章介绍)。本章,我们将利用战舰STM32F103实现一个简单的音乐播放器(支持wav/mp3/wma/flac/ogg/midi/aac等格式)。本章分为如下几个部:
54.1 VS1053简介
54.2 硬件设计
54.3 软件设计
54.4 下载验证

54.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接口
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不停的输入音频数据,它就会自动帮我们解码了,然后从输出通道输出音乐,这时我们接上耳机就能听到所播放的歌曲了。
VS1053的SPI支持两种模式:1,VS1002有效模式(即新模式)。2,VS1001兼容模式。这里我们仅介绍VS1002有效模式(此模式也是VS1053的默认模式)。表48.1.2是在新模式下VS1053的SPI信号线功能描述:

表54.1.2 VS1053新模式下SPI口信号线功能
VS1053的SPI数据传送,分为SDI和SCI,分别用来传输数据/命令。SDI和前面介绍的SPI协议一样的,不过VS1053的数据传输是通过DREQ控制的,主机在判断DREQ有效(高电平)之后,直接发送即可(一次可以发送32个字节)。
这里我们重点介绍一下SCI。SCI串行总线命令接口包含了一个指令字节、一个地址字节和一个16位的数据字。读写操作可以读写单个寄存器,在SCK的上升沿读出数据位,所以主机必须在下降沿刷新数据。SCI的字节数据总是高位在前低位在后的。第一个字节指令字节,只有2个指令,也就是读和写,读为0X03,写为0X02。
一个典型的SCI读时序如图54.1.2所示:

图54.1.2 SCI读时序
从图54.1.2可以看出,向VS1053读取数据,通过先拉低XCS(VS_XCS),然后发送读指令(0X03),再发送一个地址,最后,我们在SO线(VS_MISO)上就可以读到输出的数据了。而同时SI(VS_MOSI)上的数据将被忽略。
看完了SCI的读,我们再来看看SCI的写时序,如图54.1.3所示:
图54.1.3 SCI写时序

图54.1.3中,其时序和图54.1.2基本类似,都是先发指令,再发地址。不过写时序中,我们的指令是写指令(0X02),并且数据是通过SI写入VS1053的,SO则一直维持低电平。细心的读者可能发现了,在这两个图中,DREQ信号上都产生了一个短暂的低脉冲,也就是执行时间。这个不难理解,我们在写入和读出VS1053的数据之后,它需要一些时间来处理内部的事情,这段时间,是不允许外部打断的,所以,我们在SCI操作之前,最好判断一下DREQ是否为高电平,如果不是,则等待DREQ变为高。
了解了VS1053的SPI读写,我们再来看看VS1053的SCI寄存器,VS1053的所有SCI寄存器如表54.1.3所示:

VS1053总共有16个SCI寄存器,这里我们不介绍全部的寄存器,仅仅介绍几个我们在本章需要用到的寄存器。
首先是MODE寄存器,该寄存器用于控制VS1053的操作,是最关键的寄存器之一,该寄存器的复位值为0x0800,其实就是默认设置为新模式。表54.1.4是MODE寄存器的各位描述:
表54.1.4 MODE寄存器各位描述
这个寄存器,我们这里只介绍一下第2和第11位,也就是SM_RESET和SM_SDINEW。其他位,我们用默认的即可。这里SM_RESET,可以提供一次软复位,建议在每播放一首歌曲之后,软复位一次。SM_SDINEW为模式设置位,这里我们选择的是VS1002新模式(本地模式),所以设置该位为1(默认的设置)。其他位的详细介绍,请参考VS1053的数据手册。
接着我们看看SCI_BASS寄存器,该寄存器可以用于设置VS1053的高低音效。该寄存器的各位描述如表54.1.5所示:
名称 位域 说明
ST_AMPLITUDE 15:12 高音控制,步长为1.5dB(-8…7,0=关闭
ST_FREQLIMIT 11:08 下限频率,步长为1000hz(1.15)
SB_AMPLITUDE 7:04 低音增强,步长为1dB(0…15,0=关闭
SB_FREQLIMIT 3:00 频率上限,步长为10hz(2.15)
表54.1.5 SCI_BASS寄存器各位描述
通过这个寄存器以上位的一些设置,我们可以随意配置自己喜欢的音效(其实就是高低音的调节)。VS1053的EarSpeaker效果则由MODE寄存器控制,请参考表54.1.4。
接下来,我们介绍一下CLOCKF寄存器,这个寄存器用来设置时钟频率、倍频等相关信息,该寄存器的各位描述如表54.1.6所示:
CLOCKF寄存器
位 15:13 12:11 10:00
名称 SC_MULT SC_ADD SC_FREQ
描述 时钟倍频数 允许倍速频 时钟频率
说明 CLKI=XTALI*(Scmult0.5+1) 倍频增量=SC_ADD0.5 当时钟频率不为12.288M时,外部时钟为12.288时,此部分设置为0即可
表54.1.6 CLOCKF寄存器各位描述
此寄存器,重点说明SC_FREQ,SC_FREQ是以4Khz为步进的一个时钟寄存器,当外部时钟不是12.288M的时候,其计算公式为:
SC_FREQ=(XTALI-8000000)/4000
式中为XTALI的单位为Hz。表54.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播放一首歌曲。最简单的步骤如下:
复位VS1053
这里包括了硬复位和软复位,是为了让VS1053的状态回到原始状态,准备解码下一首歌曲。这里建议大家在每首歌曲播放之前都执行一次硬件复位和软件复位,以便更好的播放音乐。
配置VS1053的相关寄存器
这里我们配置的寄存器包括VS1053的模式寄存器(MODE)、时钟寄存器(CLOCKF)、音调寄存器(BASS)、音量寄存器(VOL)等。
发送音频数据
当经过以上两步配置以后,我们剩下来要做的事情,就是往VS1053里面扔音频数据了,只要是VS1053支持的音频格式,直接往里面丢就可以了,VS1053会自动识别,并进行播放。不过发送数据要在DREQ信号的控制下有序的进行,不能乱发。这个规则很简单:只要DREQ变高,就向VS1053发送32个字节。然后继续等待DREQ变高,直到音频数据发送完。
经过以上三步,我们就可以播放音乐了。VS1053就先介绍到这里。
54.2 硬件设计

  1. 例程功能
    本章实验功能简介:开机后,先初始化各外设,然后检测字库是否存在,如果检测无问题, 则开始循环播放 SD 卡 MUSIC 文件夹里面的歌曲(必须在 SD 卡根目录建立一个 MUSIC 文件夹,并存放歌曲在里面),在 TFTLCD 上显示歌曲名字、播放时间、歌曲总时间、歌曲总数目、当前歌曲的编号等信息。KEY0 用于选择下一曲,KEY2 用于选择上一曲,KEY_UP 和 KEY1 用来调节音量。DS0 还是用于指示程序运行状态,DS1用于指示VS1053正在初始化。
  2. 硬件资源
    本实验,大家需要准备1个microSD/SD卡(在里面新建一个MUSIC文件夹,并存放一些歌曲在MUSIC文件夹下)和一个耳机(非必备),分别插入SD卡接口和耳机接口,然后下载本实验就可以通过耳机来听歌了。实验用到的硬件资源如下:
    1)LED灯
    LED0 – PB5
    LED1 – PE5
    2)独立按键
    KEY0 – PE4
    KEY1 – PE3
    KEY2 – PE2
    KEY_UP – PA0 (程序中的宏名:WK_UP)
    3)串口1 (PA9/PA10连接在板载USB转串口芯片CH340上面)
    4)正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
    5)SD卡:
    通过SDIO(SDIO_D0D4(PC8PC11),SDIO_SCK(PC12),SDIO_CMD(PD2))连接
    6)NOR FLASH(SPI FLASH芯片,连接在SPI2上)
    7)VS1053芯片,通过SPI1驱动
    8)功放芯片HT6872,用于放大1053的输出以支持扬声器
    正点原子战舰STM32开发板,自带了一颗VS1053音频编解码芯片,所以,我们直接可以通过开发板来播放各种音频格式,实现一个音乐播放器。战舰STM32开发板板载了VS1053解码芯片的驱动电路,原理图如图54.1.1所示:

图54.1.1 正点原子音频解码模块原理图
VS1053通过7根线同STM32连接,他们是:VS_MISO、VS_MOSI、VS_SCK、VS_XCS、VS_XDCS、VS_DREQ和VS_RST。这7根线同STM32的连接关系如表54.1.1所示:
芯片 信号线
VS1053 VS_MISO VS_MOSI VS_SCK VS_XCS VS_XDCS VS_DREQ VS_RST
STM32F103ZET6 PA6 PA7 PA5 PF7 PF6 PC13 PE6
表54.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上面的。
MP3_RIGHT和MP3_LEFT是来自VS1053的音频左右声道输出,由于我们用的1053B可以直接驱动一个30欧的负载,所以我们直接输出到耳机PHONE接口即可。PWM_AUDIO,则连接多功能接口的AIN,可用于外接音频输入或者PWM音频,注意PWM_AUDIO仅仅输入了TDA1308的一个声道,所以插上耳机的时候,听到也是只有一边有声音。SPK_IN则连接到了HT6872,用于HT6872的输入。
HT6872是一颗单声道、高功率(最大可达4.7W)D类功放IC,驱动板载的2W喇叭(在板子背面),HT6872原理图如图48.2.2所示:

图54.2.2 HT6872原理图
图中SPK_IN就是HT6872的音频输入,来自图48.2.1,然后SP+和SP-则分别连接喇叭的正负极。重点看看SPK_CTRL,这个信号控制着HT6872的工作模式,该信号由VS1053的36脚(GPIO4)控制(见图48.1.1),当SPK_CTRL脚为低电平时HT6872进入关断模式,也就是功放不工作了,当SPK_CTRL脚为高电平的时候,HT6872进入正常工作模式,此时喇叭可以播放SPK_IN输入的音频信号。这样,我们通过SPK_CTRL就可以控制喇叭的开关了。
关于如何控制VS1053的GPIO,详见VS1053中文数据手册的低10.7节(67页),这里我们就不作介绍了。本例程播放歌曲的时候,喇叭输出是默认开启的,方便大家测试。
54.3 程序设计
54.3.2 程序流程图

图54.3.1.1 音乐播放器实验程序流程图
音乐播放我们从SD卡的指定目前读取音乐文件,解析格式正确后,通过SPI不断向VS1053发送文件数据至播放完成,VS1053解码后通过选择扬声器或直接从耳机输出音乐。为了交互性,我们设置板载的按键用于控制播放的歌曲切换及音量调节。
54.3.2 程序解析
音乐文件我们要通过SD/SD卡来传给单片机,那我们自然要用到文件系统。LCD、按键交互这些我们也需要实现,同样我们为了快速建立工程,复制之前的《实验38 FATFS实验》来修改成我们音乐播放器实验。
由于播放功能涉及到多个外设的配合使用,用文件系统读音频文件,做播放控制等,所以我们把1053的硬件驱动放到BSP目录下,播放功能作为APP放到USER目录下。

  1. vs1053驱动代码
    这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码,VS1053的驱动主要包括两个文件:vs1053.c和vs1053.h。
    除去SPI的管脚,我们需要初始其它IO的模式,我们在头文件中定义VS1053的引脚,方便如果IO变更之后作修改:
/* VS10XX RST/XCS/XDCS/DQ 引脚 定义 */
/* RST口配置 */
#define VS10XX_RST_GPIO_PORT            GPIOE
#define VS10XX_RST_GPIO_PIN             GPIO_PIN_6
#define VS10XX_RST_GPIO_CLK_ENABLE()  do{ __HAL_RCC_GPIOE_CLK_ENABLE();}while(0) 
/* XCS口配置 */
#define VS10XX_XCS_GPIO_PORT            GPIOF
#define VS10XX_XCS_GPIO_PIN             GPIO_PIN_7
#define VS10XX_XCS_GPIO_CLK_ENABLE()  do{ __HAL_RCC_GPIOF_CLK_ENABLE();}while(0)   
/* XDCS口配置 */
#define VS10XX_XDCS_GPIO_PORT           GPIOF
#define VS10XX_XDCS_GPIO_PIN            GPIO_PIN_6
#define VS10XX_XDCS_GPIO_CLK_ENABLE() do{__HAL_RCC_GPIOF_CLK_ENABLE();}while(0)
/* DQ口配置 */
#define VS10XX_DQ_GPIO_PORT             GPIOC
#define VS10XX_DQ_GPIO_PIN              GPIO_PIN_13
#define VS10XX_DQ_GPIO_CLK_ENABLE()    do{__HAL_RCC_GPIOC_CLK_ENABLE();}while(0)
接下来就是用根据1053手册配置这些控制IO的功能,我们编写1053的初始化函数:
/**
 * @brief       VS10XX 初始化
 * @param       无
 * @retval      无
 */
void vs10xx_init(void)
{
    VS10XX_RST_GPIO_CLK_ENABLE();	/* VS10XX_RST  脚 时钟使能 */
    VS10XX_XCS_GPIO_CLK_ENABLE();  	/* VS10XX_XCS  脚 时钟使能 */
    VS10XX_XDCS_GPIO_CLK_ENABLE();	/* VS10XX_XDCS 脚 时钟使能 */
    VS10XX_DQ_GPIO_CLK_ENABLE();   	/* VS10XX_DQ   脚 时钟使能 */
    GPIO_InitTypeDef GPIO_Initure;
    /* RST  引脚模式设置,输出 */
    GPIO_Initure.Pin = VS10XX_RST_GPIO_PIN;
    GPIO_Initure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Initure.Pull = GPIO_PULLUP;
    GPIO_Initure.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(VS10XX_RST_GPIO_PORT, &GPIO_Initure);
    /* XCS  引脚模式设置,输出 */
    GPIO_Initure.Pin = VS10XX_XCS_GPIO_PIN;
    HAL_GPIO_Init(VS10XX_XCS_GPIO_PORT, &GPIO_Initure);
    /* XDCS 引脚模式设置,输出 */
    GPIO_Initure.Pin = VS10XX_XDCS_GPIO_PIN;
    HAL_GPIO_Init(VS10XX_XDCS_GPIO_PORT, &GPIO_Initure);
    /* DQ   引脚模式设置,输入 */
    GPIO_Initure.Pin = VS10XX_DQ_GPIO_PIN;
    GPIO_Initure.Mode = GPIO_MODE_INPUT;
    GPIO_Initure.Pull = GPIO_PULLUP;
    GPIO_Initure.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(VS10XX_DQ_GPIO_PORT, &GPIO_Initure);
    spi1_init();
}
由于VS1053需要符合它自定的SCI时序来配置:发送指令字节,读写操作和16位的数据字,在SCK下降沿更新数据,先发送每个字节的MSP,所以我们需要根据1053的这种时序,编写对应的配置的函数,这里我们重新封装一下SPI读写函数为vs10xx_spi_read_write_byte(),当我们修改SPI的接口时也方便修改;根据手册中的时序图我们编写1053的控制接口写命令函数如下:
/**
 * @brief       VS10XX 写命令
 * @param       address : 命令地址
 * @param       data    : 命令数据
 * @retval      无
 */
void vs10xx_write_cmd(uint8_t address, uint16_t data)
{
    while (VS10XX_DQ == 0); 							/* 等待空闲 */
    vs10xx_spi_speed_low(); 							/* 低速 */
    VS10XX_XDCS(1);
    VS10XX_XCS(0);
    vs10xx_spi_read_write_byte(VS_WRITE_COMMAND);	/* 发送VS10XX的写命令 */
    vs10xx_spi_read_write_byte(address);            	/* 地址 */
    vs10xx_spi_read_write_byte(data >> 8);        	/* 发送高八位 */
    vs10xx_spi_read_write_byte(data);                	/* 低八位 */
    VS10XX_XCS(1);
    vs10xx_spi_speed_high(); 						/* 高速 */
}

该函数用于向VS1053发送命令,这里要注意VS1053的写操作比读操作快(写1/4CLKI,读1/7 CLKI),虽然说写寄存器最快可以到1/4 CLKI,但是经实测在1/4 CLKI的时候会出错,所以在写寄存器的时候最好把SPI速度调慢点,然后在发送音频数据的时候,就可以1/4 CLKI的速度了。
有了上面的写寄存器操作,我们还需要访问1053的工作状态、数据标头等信息,还需要编写一个读函数,同样参考VS1053的读时序,我们编写如下接口:

/**
 * @brief       VS10XX 读寄存器
 * @param       address : 寄存器地址
 * @retval      读取到的数据
 */
uint16_t vs10xx_read_reg(uint8_t address)
{
    uint16_t temp = 0;
    while (VS10XX_DQ == 0); 							/* 非等待空闲状态 */
    vs10xx_spi_speed_low(); 							/* 低速 */
    VS10XX_XDCS(1);
    VS10XX_XCS(0);
    vs10xx_spi_read_write_byte(VS_READ_COMMAND); 	/* 发送VS10XX的读命令 */
    vs10xx_spi_read_write_byte(address);         	/* 地址 */
    temp = vs10xx_spi_read_write_byte(0xff);     	/* 读取高字节 */
    temp = temp << 8;
    temp += vs10xx_spi_read_write_byte(0xff);    	/* 读取低字节 */
    VS10XX_XCS(1);
    vs10xx_spi_speed_high(); 						/* 高速 */
    return temp;
}

对于1053的一些需要特殊配置的参数,如播放速度、硬件版本、比特率等,需要借助RAM和RAM地址扩展,所以对这部分我们也单独封装了函数用于操作这些特殊地址,我们定义为vs10xx_read_ram()和vs10xx_write_ram(),代码如下:

/**
 * @brief       VS10XX 读RAM
 * @param       address : RAM地址
 * @retval      读取到的数据
 */
static uint16_t vs10xx_read_ram(uint16_t address)
{
    uint16_t res;
    vs10xx_write_cmd(SPI_WRAMADDR, address);
    res = vs10xx_read_reg(SPI_WRAM);
    return res;
}

/**
 * @brief       VS10XX 写RAM
 * @param       address : RAM地址
 * @param       data    : 要写入的值
 * @retval      无
 */
static void vs10xx_write_ram(uint16_t address, uint16_t data)
{
    vs10xx_write_cmd(SPI_WRAMADDR, address); 	/* 写RAM地址 */
    while (VS10XX_DQ == 0);                  		/* 等待空闲 */
    vs10xx_write_cmd(SPI_WRAM, data);        	/* 写RAM值 */
}

有了以上代码,我们就可以根据自己的需要配置1053工作在我们需要的模式下了,大家可以用这些读写函数测试相应的寄存器看看,其它的功能函数比较多,我们也实现了一些常用的函数,大家根据1053的手册和我们光盘提供的源码,对照着去实现。
2. audioplayer代码
这部分我们需要根据V1053的手册推荐的初始化顺序时行配置。我们需要借助SD卡和文件系统把我们需要播放的歌曲传给VS1053播放。我们在User目录下新建一个《APP》文件夹,同时在该目录下新建audioplayer.c和audioplayer.h并加入到工程。
同样地,需要判断音乐文件类型,符合条件的再把相应的文件数据发送给1053,同样需要用到文件类型判断的函数,我们在FATFS的扩展文件中已经实现了这个功能,在图片显示实验也演示了这部分代码的使用,我们把这个功能封装成了audio_get_tnum()函数,这部分参考我们光盘源码即可。接下来我们来分析一下audio_play_song ()函数,它用于实现播放单个歌曲的功能,由于1053在解码的FLAC时有已知Bug,我们在解码FLAC时需要根据官方给的patches文件利用vs10xx_load_patch()刷入1053的RAM区,结合播放控制,我们的歌曲播放函数实现如下:

/**
 * @brief       播放一曲指定的歌曲
 * @param       pname   : 带路径的文件名
 * @retval      播放结果
 * @arg         KEY0_PRES , 下一曲
 * @arg         KEY2_PRES , 上一曲
 * @arg         其他      , 错误
 */
uint8_t audio_play_song(uint8_t *pname)
{
    FIL *fmp3;
    uint16_t br;
    uint8_t res, rval;
    uint8_t *databuf;
    uint16_t i = 0;
    uint8_t key;
    rval = 0;
    fmp3 = (FIL *)mymalloc(SRAMIN, sizeof(FIL));    /* 申请内存 */
    databuf = (uint8_t *)mymalloc(SRAMIN, 4096);    /* 开辟4096字节的内存区域 */
    if (databuf == NULL || fmp3 == NULL)rval = 0XFF ;	/* 内存申请失败 */
    if (rval == 0)
    {
        vs10xx_restart_play();          	/* 重启播放 */
        vs10xx_set_all();              	/* 设置音量等信息 */
        vs10xx_reset_decode_time();     	/* 复位解码时间 */
        res = exfuns_file_type(pname); 	/* 得到文件后缀 */
        if (res == T_FLAC)              	/* 如果是flac,加载patch */
        {
            vs10xx_load_patch((uint16_t *)vs1053b_patch, VS1053B_PATCHLEN);
        }
        res = f_open(fmp3, (const TCHAR *)pname, FA_READ); /* 打开文件 */
        if (res == 0)   					/* 打开成功 */
        {
            vs10xx_spi_speed_high();    	/* 高速 */
            while (rval == 0)
            {
                res = f_read(fmp3, databuf, 4096, (UINT *)&br);/* 读出4096个字节 */
                i = 0;
                do      /* 主播放循环 */
                {
                    if (vs10xx_send_music_data(databuf + i) == 0)   
                    {/* 给VS10XX发送音频数据 */
                        i += 32;
                    }
                    else
                    {
                        key = key_scan(0);
                        switch (key)
                        {
                            case KEY0_PRES: /* 下一曲 */
                            case KEY2_PRES: /* 上一曲 */
                                rval = key;
                                break;
                            case WKUP_PRES: /* 音量增加 */
                                if (vsset.mvol < 250)
                                {
                                    vsset.mvol += 5;
                                    vs10xx_set_volume(vsset.mvol);
                                }
                                else
                                {
                                    vsset.mvol = 250;
                                } 
/* 音量限制在:100~250,显示的时候,按照公式(vol-100)/5,显示,也就是0~30 */
                                audio_vol_show((vsset.mvol - 100) / 5);
                                break;
                            case KEY1_PRES: /* 音量减 */
                                if (vsset.mvol > 100)
                                {
                                    vsset.mvol -= 5;
                                    vs10xx_set_volume(vsset.mvol);
                                }
                                else
                                {
                                    vsset.mvol = 100;
                                }
/* 音量限制在:100~250,显示的时候,按照公式(vol-100)/5,显示,也就是0~30 */
                                audio_vol_show((vsset.mvol - 100) / 5); 
                                break;
                        }
                        audio_msg_show(fmp3->obj.objsize);  /* 显示信息 */
                    }
                } while (i < 4096); /* 循环发送4096个字节 */
                if (br != 4096 || res != 0)
                {
                    rval = KEY0_PRES;
                    break;  	/* 读完了 */
                }
            }
            f_close(fmp3);
        }
        else
        {
            rval = 0XFF;    	/* 出现错误 */
        }
    }
    myfree(SRAMIN, databuf);
    myfree(SRAMIN, fmp3);
    return rval;
}

测试通过单曲播放的功能后,我们可以根据需要,再封装一个目录循环播放的函数,利用VS1053解码音乐数据的间隔我们显示一些跟音乐文件相关的信息,来完成类似音乐播放器的功能,我们封装为了audio_play()函数,这部分代码实现起来就相对容易一些了,大家自行实现或者参考我们光盘的源代码即可。
3. main.c代码
解决了音乐播放的问题,main.c函数实现起来就比较简单了,我们可以按照流程图的设计思路进行编写即可:

int main(void)
{
    HAL_Init();                              	/* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);   	/* 设置时钟, 72Mhz */
    delay_init(72);                           	/* 延时初始化 */
    usart_init(115200);                     	/* 串口初始化为115200 */
    usmart_dev.init(72);                    	/* 初始化USMART */
    led_init();                              	/* 初始化LED */
    lcd_init();                              	/* 初始化LCD */
    key_init();                              	/* 初始化按键 */
    sram_init();                             	/* SRAM初始化 */
    norflash_init();                            /* 初始化NORFLASH */
    vs10xx_init();                           	/* VS10XX初始化 */
    my_mem_init(SRAMIN);                    	/* 初始化内部SRAM内存池 */
    my_mem_init(SRAMEX);                    	/* 初始化外部SRAM内存池 */
    exfuns_init();                           	/* 为fatfs相关变量申请内存 */
    f_mount(fs[0], "0:", 1);                 	/* 挂载SD卡 */
    f_mount(fs[1], "1:", 1);                 	/* 挂载FLASH */
    while (fonts_init())                     	/* 检查字库 */
    {
        lcd_show_string(30, 50, 200, 16, 16, "Font Error!", RED);
        delay_ms(200);
        lcd_fill(30, 50, 240, 66, WHITE);  	/* 清除显示 */
        delay_ms(200);
    }
    text_show_string(30, 50, 200, 16, "正点原子STM32开发板", 16, 0, RED);
    text_show_string(30, 70, 200, 16, "音乐播放器 实验", 16, 0, RED);
    text_show_string(30, 110, 200, 16, "KEY0:NEXT   KEY2:PREV", 16, 0, RED);
    text_show_string(30, 130, 200, 16, "KEY_UP:VOL+ KEY1:VOL-", 16, 0, RED);
    while (1)
    {
        LED1(0);
        text_show_string(30, 150, 200, 16, "存储器测试...", 16, 0, RED);
        printf("Ram Test:0X%04X\r\n", vs10xx_ram_test());   /* 打印RAM测试结果 */
        text_show_string(30, 150, 200, 16, "正弦波测试...", 16, 0, RED);
        vs10xx_sine_test();
        text_show_string(30, 150, 200, 16, "<<音乐播放器>>", 16, 0, RED);
        LED1(1);
        audio_play();
    }
}

到这里本实验的代码基本就编写完成了,我们准备好音乐文件放到SD卡根目录下的《MUSIC》夹下测试本实验的代码,可以把VS1053的配置函数加到USMART下,这样就能用USMART来测试和调试VS1053了。
54.4 下载验证
在代码编译成功之后,我们下载代码到正点原子战舰STM32开发板上,程序先执行字库监测,然后对VS1053进行RAM测试和正弦测试。
当检测到SD卡根目录的MUSIC文件夹有有效音频文件(VS1053所支持的格式)的时候,就开始自动播放歌曲了,如图54.4.1所示:

图54.4.1音乐播放中
从上图可以看出,总共3首歌曲,当前正在播放第3首歌曲,歌曲名、播放时间、总时长、码率、音量等信息等也都有显示。此时DS0会随着音乐的播放而闪烁,2秒闪烁一次。
此时我们便可以听到开发板板载喇叭播放出来的音乐了,也可以在开发板的PHONE端子插入耳机来听歌。同时,我们可以通过按KEY0和KEY2来切换下一曲和上一曲,通过KEY_UP按键来控制音量增加,通过KEY1控制音量减小。
本实验,我们还可以通过USMART来测试VS1053的其他功能,通过将vs10xx.c里面的部分函数加入USMART管理,我们可以很方便的设置/获取VS1053各种参数,达到验证测试的目的。有兴趣的朋友,可以实验测试一下。
至此,我们就完成了一个简单的MP3播放器了,在此基础上进一步完善,就可以做出一个比较实用的MP3了。大家可以自己发挥想象,做出一个你心仪的MP3。

物联沃分享整理
物联沃-IOTWORD物联网 » 正点原子STM32F103战舰开发指南V1.2-音乐播放器实验摘要

发表评论