4. VS1053模块

4.1. 模块功能简介

野火提供的程序实现的功能有:显示歌词,显示歌曲列表,显示时间,快进/快退,上/下一曲,暂停/播放,下一曲,音量调节,播歌,录音。播歌,支持的格式有:MP3、FLAC、WAV。录音:录音文件以WAV格式保存在SD卡,支持咪头和LINE_IN输入。

更详细的VS1053模块资料请参考:https://baike.baidu.com/item/vs1053/2686409?fr=aladdin

4.2. 连接方式

VS1053音频模块可以直接与野火的指南者板子配套,直接插到板子上的 I2S/SPI2接口即可,也可使用杜邦线连接,其中VS1053引脚丝印可在模块背面看到。

VS1053模块

以野火的指南者开发板为例,下面是配套程序对应的IO连接方式。

VS1053接口

VS1053引脚

STM32 IO

VS1053引脚

STM32 IO

XCS

PB12

CLK

PB13

SO

PB14

SI

PB15

DREQ

PC6

RET

PC7

NC

不用连接

XDCS

PB9

NC

不用连接

GND

地

5V

电源

NC

不用连接

4.3. 实验现象

按照上面的连接方式将模块与开发板连接之后,在SD卡中放一个MP3文件,文件名字修改为TestFile_1,将SD卡插入开发板中,上电后即可通过耳机或者音响听歌, 同时,串口会打印如下信息。

vs_1053_1

4.4. 例程介绍

本例程采用的硬件SPI,硬件SPI在《SPI基础知识》一文已经介绍,不再赘述,这里主要分析代码框架。

在编写VS1053模块驱动时,也要考虑更改硬件环境的情况。我们把VS1053模块引脚相关的宏定义到”VS1053.h”文件中, 在更改或移植的时候只用改宏定义就可以。

程序中以SPI2作为VS1053的硬件接口在VS1053.h中定义,该头文件中还定义了一些数据结构和VS1053的寄存器,完整内容请参考VS1053的SD卡读歌例程。

VS1053.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 //... ...
 /*定义SPI2作为VS1053的硬件接口*/
 #define VS_XCS                                 GPIO_Pin_12           /*定义VS1053的片选管脚*/
 #define VS_SCLK                                GPIO_Pin_13           /*定义VS1053的时钟管脚*/
 #define VS_MISO                                GPIO_Pin_14           /*定义VS1053的MISO管脚*/
 #define VS_MOSI                                GPIO_Pin_15           /*定义VS1053的MOSI管脚*/
 #define VS_SPIGPIO_PORT                        GPIOB                 /* GPIO端口 */
 #define VS_SPIGPIO_CLK                         CC_APB2Periph_GPIOB   /* GPIO端口时钟 */
 #define VS_SPI                                 SPI2
 #define VS_SPI_CLK                             RCC_APB1Periph_SPI2

 #define VS_XDCS                                GPIO_Pin_6            /*定义VS1053的片选管脚*/
 #define VS_GPIO_XDCS_PORT                      GPIOE                 /* GPIO端口 */
 #define VS_GPIO_XDCS_CLK                       RCC_APB2Periph_GPIOE  /* GPIO端口时钟 */

 #define VS_GPIO_RST_PORT                       GPIOC                 /* GPIO端口 */
 #define VS_GPIO_RST_CLK                        RCC_APB2Periph_GPIOC  /* GPIO端口时钟 */
 #define VS_RST                                 GPIO_Pin_3            /*定义VS1053的RST管脚*/

 #define VS_GPIO_DREQ_PORT                      GPIOE                 /* GPIO端口 */
 #define VS_GPIO_DREQ_CLK                       RCC_APB2Periph_GPIOE  /* GPIO端口时钟 */
 #define VS_DREQ                                GPIO_Pin_3            /*定义VS1053的DREQ管脚*/

 //... ...

下面是VS1053的IO口/初始化,这里用到了SPI2,所以需要开启SPI的时钟和相应管脚的时钟。初始化VS1053的DREQ管脚,设置为上拉输入,GPIO时钟为50MHZ, 初始化VS1053的RST管脚,设置为推挽输出,GPIO_Speed如果没有修改的话,任然为上次设置的50MHZ。

VS_SCLK、VS_MISO、VS_MOSI均设置为复用推挽输出,VS_XCS、VS_XDCS设置为推挽输出。

SPI_DirectionSPI设置单向或者双向的数据模式,这里选择SPI设置为双线双向全双工;SPI_Mode是设置SPI工作模式,设置为主SPI;SPI_DataSize是设置数据传输的大小,设置为SPI发送接收8位帧; 设置串行同步时钟的空闲状态为高电平,串行同步时钟的第二个跳变沿(上升或下降)数据被采样;SPI_NSS是NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理,设置为内部NSS信号由SSI控制, SPI_BaudRatePrescaler为定义预分频,波特率预分频值为256;SPI_FirstBit是指定数据传输从MSB位还是LSB位开始,设置为数据传输从MSB位开始,SPI_CRCPolynomial为CRC值计算的多项式;

VS1053.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
 //初始化VS1053的IO口
 void VS_Init(void)
 {
     SPI_InitTypeDef  SPI_InitStructure;
     GPIO_InitTypeDef GPIO_InitStructure;

     /* 初始化STM32 SPI2接口 */
     RCC_APB1PeriphClockCmd(VS_SPI_CLK, ENABLE);

     /* 初始化SPI时钟及相应管脚时钟 */
     RCC_APB2PeriphClockCmd(VS_SPIGPIO_CLK|VS_GPIO_DREQ_CLK|VS_GPIO_RST_CLK|VS_GPIO_XDCS_CLK, ENABLE);

     /* 初始化VS1053的DREQ管脚 */
     GPIO_InitStructure.GPIO_Pin = VS_DREQ;                     //DREQ
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;              //上拉输入
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;          //GPIO时钟为50MHZ
     GPIO_Init(VS_GPIO_DREQ_PORT, &GPIO_InitStructure);

     /* 定义VS1053的RST管脚 */
     GPIO_InitStructure.GPIO_Pin = VS_RST;                      //PB9
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;           //推挽输出
     GPIO_Init(VS_GPIO_RST_PORT, &GPIO_InitStructure);

     GPIO_InitStructure.GPIO_Pin = VS_SCLK | VS_MISO | VS_MOSI;
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;            // 复用输出
     GPIO_Init(VS_SPIGPIO_PORT, &GPIO_InitStructure);

     GPIO_InitStructure.GPIO_Pin = VS_XCS;
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;           // 推免输出
     GPIO_Init(VS_SPIGPIO_PORT, &GPIO_InitStructure);

     GPIO_InitStructure.GPIO_Pin = VS_XDCS;
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;           // 推免输出
     GPIO_Init(VS_GPIO_XDCS_PORT, &GPIO_InitStructure);

     /* SPI2 配置 */
     SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;   //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
     SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                        //设置SPI工作模式:设置为主SPI
     SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                    //设置SPI的数据大小:SPI发送接收8位帧
     SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;                          //串行同步时钟的空闲状态为高电平
     SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;                         //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
     SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                            //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号由SSI控制
     SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定义波特率预分频的值:波特率预分频值为256
     SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                   //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
     SPI_InitStructure.SPI_CRCPolynomial = 7;                             //CRC值计算的多项式
     SPI_Init(VS_SPI, &SPI_InitStructure);                                //根据SPI_InitStruct中指定的参数初始化外设SPI寄存器

     /* 使能 SPI2 */
     SPI_Cmd(VS_SPI, ENABLE);
     SPI2_ReadWriteByte(0xff);//启动传输
 }

下面来看一下main函数,main函数中首先需要初始化一下滴答定时器,方便调用系统延时函数,然后初始化串口、LED、按键,最后再初始化VS1053的IO口, 外设初始化完成之后进行ram 测试并打印结果,再进行正弦测试,测试完成再复位一下模块,在while(1)中循环播放歌曲。

main.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int main(void)
 {
     /* 启动系统滴答定时器 SysTick */
     SysTick_Init();

     /* 初始化串口 */
     USART1_Config();
     f_mount(&fs,"0:",1);

     /* 初始化LED */
     LED_GPIO_Config();

     /* 初始化按键 */
     EXTI_Key_Config();

     /* 初始化VS1053的IO口 */
     VS_Init();

     /* 打印测试结果 */
     printf("vs1053:%4X\n",VS_Ram_Test());

     /* 延时100毫秒 */
     Delay_ms(100);

     /* 正弦测试  */
     VS_Sine_Test();

     /* 硬复位MP3 */
     VS_HD_Reset();

     /* 软复位VS10XX */
     VS_Soft_Reset();

     /* 计算歌曲数量 */
     song_list_init();
     while(1)
     {
         // 测试歌曲放在SD卡根目录下
         vs1053_player_song(song_list[song_pt]);
         printf("MusicPlay End\n");
     }
 }

再来看一下vs1053_player_song中做了些什么,首先用VS_Restart_Play函数进行一次切歌的操作,是为了去掉杂音,然后设置音量,音效、重设解码时间,对音乐文件进行不区分大小写处理, 打开文件成功后将SPI设置到高速模式,将音乐文件不断的读出并进行播放,Restart_Play_flag为按键切歌标志位,按键按下后会在中断中置1,然后推出播放下一曲。

VS1053.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
 void vs1053_player_song(uint8_t *filepath)
 {
     uint16_t i=0;

     /* 切歌 */
     VS_Restart_Play();

     /* 设置音量,音效等 */
     VS_Set_All();

     /* 重设解码时间 */
     VS_Reset_DecodeTime();

     /* 对音乐文件进行不区分大小写处理 */
     if(strstr((const char*)filepath,".flac")||strstr((const char*)filepath,".FLAC"))
         VS_Load_Patch((u16*)vs1053b_patch,VS1053B_PATCHLEN);

     /* 打开文件 */
     result=f_open(&file,(const TCHAR*)filepath,FA_READ);

     /* 打开文件成功 */
     if(result==0)
     {
         /* 将SPI设置到高速模式 */
         VS_SPI_SpeedHigh();
         while(1)
         {
             i=0;

             /* 将文件中的数据读到buffer中,每次读BUFSIZE个数据 */
             result=f_read(&file,buffer,BUFSIZE,(UINT*)&bw);
             do
             {
                 /* 发送一次音频数据 */
                 if(VS_Send_MusicData(buffer+i)==0)
                 {
                     i+=32;

                     /* 判断切歌按键是否按下 */
                     if(Restart_Play_flag)
                     {
                          /* 切歌 */
                         VS_Restart_Play();

                         /* 清除标志位 */
                         Restart_Play_flag = 0;
                         goto exit;
                     }
                 }
             }while(i<bw);

             //读取的字节数与BUFSIZE不同,f_read返回不为0
             if(bw!=BUFSIZE||result!=0)
             {
                 break;
             }
             LED2_TOGGLE;
         }
         exit:
         f_close(&file);

         /* 播放一下曲 */
         song_pt++;

         /* 歌曲的最大数量自动计算 */
         if(song_pt == song_number_max)
         {
             song_pt = 0;
         }
     }
 }

下面是按键中断处理程序KEY1_IRQHandler,如果检测到有按键按下,就将切歌标志位置1。

stm32f103x_it.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 extern char Restart_Play_flag;
 void KEY1_IRQHandler(void)
 {
     /*确保是否产生了EXTI Line中断 */
     if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET)
     {
         /* 切歌标志位置1 */
         Restart_Play_flag = 1;

         /* 清除中断标志位 */
         EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);
     }
 }