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引脚丝印可在模块背面看到。
以野火的指南者开发板为例,下面是配套程序对应的IO连接方式。
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卡插入开发板中,上电后即可通过耳机或者音响听歌, 同时,串口会打印如下信息。
4.4. 例程介绍¶
本例程采用的硬件SPI,硬件SPI在《SPI基础知识》一文已经介绍,不再赘述,这里主要分析代码框架。
在编写VS1053模块驱动时,也要考虑更改硬件环境的情况。我们把VS1053模块引脚相关的宏定义到”VS1053.h”文件中, 在更改或移植的时候只用改宏定义就可以。
程序中以SPI2作为VS1053的硬件接口在VS1053.h中定义,该头文件中还定义了一些数据结构和VS1053的寄存器,完整内容请参考VS1053的SD卡读歌例程。
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值计算的多项式;
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)中循环播放歌曲。
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,然后推出播放下一曲。
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。
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);
}
}
|