STM32之音乐播放器 1.硬件平台
2.示例效果
- CPU:STM32F103ZE
- 屏幕:3.5寸TFTLCD屏
- 音频解码器: VS1053
- SD卡、外扩Sram
文章图片
文章图片
3.软件设计
??VS1053b 是单片 Ogg Vorbis/MP3/AAC/WMA/MIDI 音频解码器,及 IMA ADPCM 编码器和用户加载的 OggVorbis 编码器。3.1 硬件接口
??支持: 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、和跟随的一个耳机功放及一个公共电压缓冲器。
文章图片
引脚 | GPIO | 说明 |
---|---|---|
VS_MISO | PA6 | 主机输入 |
VS_MOSI | PA7 | 主机输出 |
VS_SCK | PA5 | 时钟 |
VS_XCS | PF7 | 命令片选(低电平有效) |
VS_XDCS | PF6 | 数据片选(低电平有效) |
VS_DREQ | PC13 | 数据请求线(高电平表示可以接收数据) |
VS_RST | PE6 | 复位脚(低电平复位) |
/**************硬件接口*****************
**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;
//PA
RCC->APB2ENR|=1<<4;
//PC
RCC->APB2ENR|=1<<6;
//PE
RCC->APB2ENR|=1<<7;
//PF
GPIOA->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=https://www.it610.com/article/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
=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,总秒数+1
time+=((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.工程示例 【STM32|STM32之音乐播放器】工程示例:https://download.csdn.net/download/weixin_44453694/85117817
推荐阅读
- STM32|STM32概述
- 单片机|电机控制开发
- 嵌入式开发|使用stm32配置自定义的HID设备
- 嵌入式开发|STM32HAL库串口接收中断HAL_UART_Receive_IT()配置失效
- fpga开发|博客更新计划的说明
- 设备驱动|设备驱动开篇
- STM32F1【标准库开发】|STM32F103ZET6【标准库函数开发】------05.通用定时器TIM4四个通道输出占空比、频率可调的PWM信号
- 单片机|基于单片机的通用定时器调度器SmartTimer
- STM32笔记|(十二)STM32——NVIC中断优先级管理