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);
     }
 }