> 文档中心 > STM32之音乐播放器

STM32之音乐播放器


STM32之音乐播放器

1.硬件平台

  • CPU:STM32F103ZE
  • 屏幕:3.5寸TFTLCD屏
  • 音频解码器: VS1053
  • SD卡、外扩Sram

2.示例效果

在这里插入图片描述
在这里插入图片描述

3.软件设计

  VS1053b 是单片 Ogg Vorbis/MP3/AAC/WMA/MIDI 音频解码器,及 IMA ADPCM 编码器和用户加载的 OggVorbis 编码器。
  支持: MP3/WMA/OGG/WAV/FLAC/MIDI/AAC 等音频格式的解码,并支持: OGG/WAV 音频格式的录音,支持高低音调节设置, 功能十分强大。
  它包含了一个高性能、有专利的低功耗 DSP 处理器内核VS_DSP4、工作数据存储器、供用户应用程序和任何固化解码器一起运行的 16 KiB 指令 RAM 及 0.5KiB 多的数据 RAM、串行的控制和输入数据接口、最多 8 个可用的通用 I/O 引脚、一个 UART、并有一个优质的可变采样率立体声 ADC(“咪”、“线路”、“线路+咪”或“线路*2”) 和立体声 DAC、和跟随的一个耳机功放及一个公共电压缓冲器。

3.1 硬件接口

在这里插入图片描述

引脚 GPIO 说明
VS_MISO PA6 主机输入
VS_MOSI PA7 主机输出
VS_SCK PA5 时钟
VS_XCS PF7 命令片选(低电平有效)
VS_XDCS PF6 数据片选(低电平有效)
VS_DREQ PC13 数据请求线(高电平表示可以接收数据)
VS_RST PE6 复位脚(低电平复位)

3.2 VS1053驱动

/硬件接口*VS_MISO -- PA6 主机输入VS_MOSI -- PA7 主机输出VS_SCK  -- PA5 时钟VS_XCS  -- PF7 命令片选(低电平有效)VS_XDCS -- PF6 数据片选(低电平有效)VS_DREQ -- PC13 数据请求线(高电平表示可以接收数据)VS_RST  -- PE6 复位脚(低电平复位)*/void VS1053_Init(void){/*1. 开时钟*/RCC->APB2ENR|=1<<2;//PARCC->APB2ENR|=1<<4;//PCRCC->APB2ENR|=1<<6;//PERCC->APB2ENR|=1<<7;//PFGPIOA->CRL&=0x000FFFFF;GPIOA->CRL|=0x38300000;GPIOF->CRL&=0x00FFFFFF;GPIOF->CRL|=0x33000000;GPIOC->CRH&=0xFF0FFFFF;GPIOC->CRH|=0x00800000;GPIOE->CRL&=0xF0FFFFFF;GPIOE->CRL|=0x03000000;VS_XCS=1;VS_XDCS=1;VS1053_RST();VS1053_SetVoice(255,255);/*2.配置时钟寄存器*/VS1053_WriteRegDat(VS1053_CLOCKF,0x9800);}/*SPI收发一个字节*/u8 VS1053_SPI_ReadWriteData(u8 data_tx){u8 data_rx=0;u8 i=0;for(i=0;i<8;i++){VS_SCK=0;if(data_tx&0x80)VS_MOSI=1;else VS_MOSI=0;VS_SCK=1;data_tx<<=1;data_rx<<=1;if(VS_MISO)data_rx|=0x01;}return data_rx;}/往寄存器中写入数据形参:u8 addr --地址u16 data  -- 写入的数据/void VS1053_WriteRegDat(u8 addr,u16 data){while(VS_DREQ==0){}//等待数据线空闲VS_XDCS=1;//数据片选拉高VS_XCS=0;//命令片选拉低VS1053_SPI_ReadWriteData(0x02);//写指令VS1053_SPI_ReadWriteData(addr);//寄存器地址VS1053_SPI_ReadWriteData(data>>8);VS1053_SPI_ReadWriteData(data>>0);//写入数据VS_XCS=1;}/*从寄存器中读取数据*/u16 VS1053_ReadRegDat(u8 addr){u16 data=0;while(VS_DREQ==0){}//等待数据线空闲VS_XDCS=1;//数据片选拉高VS_XCS=0;//命令片选拉低VS1053_SPI_ReadWriteData(0x03);//读指令VS1053_SPI_ReadWriteData(addr);//寄存器地址data=VS1053_SPI_ReadWriteData(0xff)<<8;data|=VS1053_SPI_ReadWriteData(0xff);VS_XCS=1;return data;}/音量调节*形参:u8 vol_l -- 左声道 0~254      u8 vol_r -- 右声道 0~254每个增量表示0.5db的衰减,值越大,音量越小注意:如果设置 VOL 的值为 0xFFFF,将使芯片进入掉电模式。右声道是高 8 位 左声道是低 8 位*/void VS1053_SetVoice(u8 vol_l,u8 vol_r){u16 temp=vol_r<<8|vol_l;VS1053_WriteRegDat(VS1053_VOL,temp);}/*VS1053硬件复位/void VS1053_RST(void){//硬件复位VS_RST=0;Delay_Ms(20);VS_XDCS=1;//取消数据传输VS_XCS=1;//取消命令传输VS_RST=1;//完成复位//软件复位while(VS_DREQ==0){}//等待数据线空闲VS1053_WriteRegDat(VS1053_MODE,0x0804);//设置为新模式,进行软件复位Delay_Ms(2);while(VS_DREQ==0){}//等待数据线空闲,复位完成}/获取解码时间/u16 VS1053_Get_Time(void){u16 time=0;time=VS1053_ReadRegDat(VS1053_DECODE_TIME);return time;}/清除解码时间/void VS1053_Clear_Time(void){VS1053_WriteRegDat(VS1053_DECODE_TIME,0);}

3.3 播放音乐,歌词同步,音乐切换

static unsigned char music_lrc[4096];//存放从文件中读取出来的歌词static unsigned char music_lrc_str[100][50];//存放筛选过后的歌词static u16 music_time[200];//保存每句歌词时间u8 buff_music[4096];/播放音乐/u8 VS1053_PlayOneMusic(const char *music_file,u8 display_lrc){u16 i=0,time1,time2;u32 k=0;u16 y=32;   u8 vol_l=50,vol_r=50;FIL fp;FRESULT res;UINT br;u8 key=0;res=f_open(&fp,music_file,FA_READ);//只读if(res!=FR_OK){//printf("%s文件打开失败err:%d\r\n",music_file,res);return 1;}   //printf("VS1053复位成功\r\n");LCD_ShowStr2(0,16,(u8 *)music_file,WHITE);//显示歌名   LCD_Refresh();//更新显示VS1053_Clear_Time();//清除解码时间   /*3.设置音量*/VS1053_SetVoice(50,50);while(!f_eof(&fp))//判断是否到文件尾{key=Key_Scan();if(key==1){break;//切换下一首}      else if(key==2)//声音加      {  if(vol_l<250)  {     vol_l+=50;     vol_r+=50;  }  VS1053_SetVoice(vol_l,vol_r);      }      else if(key==3)//声音减      {  if(vol_l>0)  {     vol_l-=50;     vol_r-=50;  }  VS1053_SetVoice(vol_l,vol_r);      }if(f_read(&fp,buff_music,sizeof(buff_music),&br)!=FR_OK)//读取音频数据{//printf("读取文件失败");f_close(&fp);return 2;}//printf("读取数据成功\r\n");for(i=0;i<br;i++){while(VS_DREQ==0){}//等待数据线空闲VS_XDCS=0;//片选拉低,开始发送音乐数据VS1053_SPI_ReadWriteData(buff_music[i]);VS_XDCS=1;}time1=VS1053_Get_Time();//获取解码时间if(time1!=time2){time2=time1;if(display_lrc==1)//是否显示歌词{if(time2>=music_time[k])//通过时间判断显示对应歌词{if(y>=(LCD_HIGHT-48))//换页显示歌词{y=32;LCD_ReflashBack();//重画背景LCD_ShowStr2(0,16,(u8 *)music_file,WHITE);//显示歌名} LCD_ShowStr2(y,16,music_lrc_str[k],WHITE);//显示当前行 if(k>=1 && y>=48){LCD_ShowStr2(y-16,16,music_lrc_str[k-1],GRAY);//将上一行清为底色} LCD_Refresh();//更新显示 y+=16; k++;}}}}return 0;}

3.4 歌词解析

/歌词解析/u8 Vs1053_GetLrc_Music(const char *musiclrc){FIL fil;FRESULT res;UINT br;FILINFO fno;char *p=NULL;char buff[10];u32 time=0,i=0,j=0,k=0,cnt=0,count=0;//记录每句歌词的播放起始时间/*1.打开文件*/res=f_open(&fil,musiclrc,FA_READ);if(res!=FR_OK){printf("%s文件打开失败\r\n",musiclrc);return 1;}memset(music_lrc,0,sizeof(music_lrc));memset(music_lrc_str,0,sizeof(music_lrc_str));/*2.读取歌词*/res=f_stat (musiclrc, &fno);//获取文件状态if(res!=FR_OK){printf("文件状态获取失败\r\n");return 2;}//printf("size=%d\r\n",fno.fsize);res=f_read(&fil,music_lrc,fno.fsize,&br);//读取歌词if(res!=FR_OK || br!=fno.fsize){printf("读取文件失败\r\n");return 3;}/*歌词解析*/j=0;k=0;p=strstr((char *)music_lrc,"[0");//找到歌词的起始位置p++;while(p[i]!='\0'){buff[j]=p[i];j++;i++;if(p[i]==']'){//[00:00.65]李荣浩 - 年少有为time=0;if(buff[6]>=7)time+=1;//最后两位时间大于5,总秒数+1time+=((buff[0]-'0')*10+(buff[1]-'0'))*60+(buff[3]-'0')*10+(buff[4]-'0');//歌词起始秒数j=0;music_time[cnt++]=time;//printf("time:%d\r\n",music_time[cnt]);/*获取歌词*/i++;  //[00:00.65]李荣浩 - 年少有为while(p[i]!='[')//保存一句歌词{music_lrc_str[count][k++]=p[i++];if(p[i]=='\0')break;}  music_lrc_str[count][k++]='\0';//保存一行歌词i++;  k=0;  count++;//记录第几行}}memset(music_lrc,0,sizeof(music_lrc));   k=32;   for(i=0;i<25;i++)   {      strcpy((char *)music_lrc,(char *)music_lrc_str[i]);      LCD_ShowStr2(k,16,music_lrc,GRAY);//显示一屏幕歌词      k+=16;   }   LCD_Refresh();//更新显示return 0;}

3.5 读取音乐文件,查找歌词,播放音乐

/*音乐播放*/u8 Vs1053_play_Music(const char *music_file){u32 i=0;u8 stat=0;DIR dp;char *p=NULL;FILINFO fno;FRESULT res;char buff1[50];char buff2[50];res=f_opendir(&dp,music_file);if(res!=FR_OK){printf("目录打开失败err:%d\r\n",res);return 1;}printf("目录打卡成功\r\n");while(1){res=f_readdir(&dp,&fno);if(res!=FR_OK || fno.fname[0]==0){break;}//printf("%s\r\n",fno.fname);p=strstr(fno.fname,".mp3");//查找文件中的音频文件if(p){i=0;//G.E.M. 邓紫棋 - 我的秘密while(1){buff1[i]=fno.fname[i];if((fno.fname[i]=='.') && (fno.fname[i+1]=='m') &&  (fno.fname[i+2]=='p') &&  (fno.fname[i+3]=='3'))break;i++;}buff1[i]='\0';//显示歌名LCD_ReflashBack();//重画背景  LCD_Refresh();//更新显示//printf("歌名:%s\r\n",buff1);snprintf((char *)buff2,sizeof(buff2),"%s/%s.lrc",music_file,buff1);//printf("buff2:%s\r\n",buff2);stat=Vs1053_GetLrc_Music(buff2);//歌词解析//if(stat==0)printf("获取歌词成功\r\n");snprintf((char *)buff2,sizeof(buff2),"%s/%s.mp3",music_file,buff1);stat=VS1053_PlayOneMusic(buff2,!stat);//播放音乐if(stat==0){printf("音乐播放完成\r\n");}else{printf("stat=%d\r\n",stat);}}}f_closedir(&dp);return 0;}

3.6 主函数:LCD初始化、SD卡挂载

int main(){   FRESULT ret;   FATFS fs;Beep_Init();Led_Init();Key_Init();Usartx_Init(USART1,115200,72);TIMx_Init(TIM2,72,20*1000);W25Q64_Init();//W25Q64初始化IIC_Init();//IIC初始化NT35310_Init();//LCD初始化SRAM_Init();AA:   /*挂载磁盘*/   ret=f_mount(&fs,"",1);   if(ret==FR_OK)   {      printf("磁盘挂载成功\n");   }   else    {      printf("请检查SD卡是否插入!!\r\n");      Delay_Ms(1000);      goto AA;   }   VS1053_Init();while(1){Vs1053_play_Music("0:/music");}}

4.工程示例:

KTV音响网