STM32F407ZET6音乐播放器
文章目录
- 一、主要功能
- 二、flash读写
- 1.读flash
- 2.写flash
- 3.获取wav格式音频数据
- 4.操作
- 三、播放音频
- 1.原理
- 2.播放“欢迎光临”提示音
- 3.播放SD卡内的音频文件
- 读取SD信息以及控制播放音乐
- 播放wav格式音频
- 四、红外遥控
- 1.原理
- 2.捕获红外遥控
- 五、源代码
一、主要功能
- SD 卡模块存储至少 5 首以上音乐文件(wav 格式);
- 片内 Flash 存储 1-2 句短提示音(5-6 秒长度),比如“xxx 的音乐播放器欢迎你!”、
“SD 文件找不到!”; - 最小系统板上电后自动查找读取 SD 卡上第 1 首音乐文件,然后依次循环播放;
- 最小系统板外接一个红外接收模块,接收红外遥控器发送的按键指令序列。红外遥
控器用来控制最小系统板的音乐播放,实现“暂停”、“播放”、“下一首”、 “上
一首”、“回到第 1 首”等功能
二、flash读写
这里选择扇区5进行flash的读写
1.读flash
/**
读取flash
address 读取起始地址
readBuf 读取内容存放位置
size 读取的大小
**/
void readFlash(uint32_t address,uint8_t *readBuf,uint16_t size)
{uint16_t i;uint16_t tmpBuf;printf("开始读flash\r\n");for(i=0;i<size;i+=2){tmpBuf=*(__IO uint16_t *)(address+i);//低八位readBuf[i]=tmpBuf&0xff;//高八位readBuf[i+1]=tmpBuf>>8;}printf("读flash完毕\r\n");
}
2.写flash
/**
写flash
address 起始地址
data 数据
size 数据大小**/
void writeFlash(u32 address,u8 *data,u16 size)
{FLASH_EraseInitTypeDef FlashSet;HAL_StatusTypeDef FlashStatus = HAL_OK;u16 tmpbuf;u16 i;u32 PageError = 0;//解锁FLASHHAL_FLASH_Unlock();//擦除FLASH//第一次写入之前调用擦除函数进行擦除,后续无需再调用,否则会吧之前写入的数据擦除掉//除非需要覆盖之前的flash //初始化FLASH_EraseInitTypeDef//擦除方式FlashSet.TypeErase = FLASH_TYPEERASE_SECTORS;//擦除起始页FlashSet.Sector = 5;//擦除结束页FlashSet.NbSectors = 6;FlashSet.VoltageRange = FLASH_VOLTAGE_RANGE_3; printf("擦除\r\n");//调用擦除函数HAL_FLASHEx_Erase(&FlashSet, &PageError);FlashStatus = FLASH_WaitForLastOperation(1000); //等待上次操作完成//对FLASH烧写printf("开始写flash\r\n");for( i= 0;i< size ;i+=2){//把8位转16位,得到半个字节//每两个8位,一个当低八位,一个当高八位tmpbuf = (*(data + i + 1) << 8) + (*(data + i));//写半个字节HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD , address + i , tmpbuf);}//锁住FLASHHAL_FLASH_Lock();
}
3.获取wav格式音频数据
参考前面的博客Python提取wav格式文件的data并转为十六进制格式重定向输出提取数据,再分批次进行flash写入。
4.操作
- 把“欢迎光临”的提示音转换成wav格式
- 通过python代码得到wav格式音频的数据块,以16进格式保存
- 把得到的16进制格式数据用数组进行存储
- main函数中调用writeFlash函数进行写flash操作
- 重复3~4,直到写完所有flash
三、播放音频
1.原理
通过I2S接口和MCU进行音频数据的传输 将得到的wav数据(PCM编码)丢给WM8978(codec)即可播放声音
2.播放“欢迎光临”提示音
/**
播放欢迎光临提示
从flash读取音频文件**/
void hello(){u8 res;//每次读取flash大小u32 eachSize=8192;//音频文件大小u32 fillnum=data_size;//flash起始地址u32 address=(u32)0x08020000;//分配内存audiodev.i2sbuf1=mymalloc(SRAMIN,eachSize);audiodev.i2sbuf2=mymalloc(SRAMIN,eachSize);audiodev.tbuf=mymalloc(SRAMIN,eachSize);WM8978_I2S_Cfg(2,0); //飞利浦标准,16位数据长度I2S2_Init(I2S_STANDARD_PHILIPS,I2S_MODE_MASTER_TX,I2S_CPOL_LOW,I2S_DATAFORMAT_16B_EXTENDED); //飞利浦标准,主机发送,时钟低电平有效,16位扩展帧长度I2S2_SampleRate_Set(8000);//设置采样率I2S2_TX_DMA_Init(audiodev.i2sbuf1,audiodev.i2sbuf2,eachSize/2); //配置TX DMAi2s_tx_callback=wav_i2s_dma_tx_callback; //回调函数指wav_i2s_dma_callbackaudio_start(); printf("开始播放\r\n");while(1) { //根据当前剩余数据大小和每次播放数据大小来确定每次读取buf的大小if(fillnum>=eachSize){fillnum-=eachSize;}else{eachSize=fillnum;}//读取flashreadFlash(address,audiodev.i2sbuf1,eachSize);readFlash(address,audiodev.i2sbuf2,eachSize);//根据当前剩余数据大小和每次播放数据大小来计算读取地址if(eachSize!=fillnum){address+=eachSize;}else{break;}while(wavtransferend==0);//等待wav传输完成; wavtransferend=0;//播放转换完毕的bufaudiodev.status=1;while(1){//判断是否播放完当前bufif((audiodev.status&0X01)==0)delay_ms(10);else break;}}//停止播放audio_stop();printf("停止播放\r\n"); myfree(SRAMIN,audiodev.tbuf); //释放内存myfree(SRAMIN,audiodev.i2sbuf1);//释放内存myfree(SRAMIN,audiodev.i2sbuf2);//释放内存
}
3.播放SD卡内的音频文件
基于正点原子的 音乐播放器实验-HAL库函数版代码进行修改。先扫描是否存在SD卡,如果存在则扫描指定文件里面的歌曲信息,歌曲格式为wav格式。如果存在歌曲,则把所有的歌曲信息进行保存,并且默认从第一首开始播放。播放原理就是通过读取每次读取指定大小的buf,然后通过I2S进行转换,转换为PCM编码,再通过PCM模块进行播放。这里只展示部分代码。最终代码见源代码链接。
读取SD信息以及控制播放音乐
//播放音乐
void audio_play(void)
{u8 res;DIR wavdir; //目录FILINFO *wavfileinfo; //文件信息 u8 *pname; //带路径的文件名u16 totwavnum; //音乐文件总数u16 curindex; //当前索引u8 key; //键值 u32 temp;u32 *wavoffsettbl; //音乐offset索引表WM8978_ADDA_Cfg(1,0); //开启DACWM8978_Input_Cfg(0,0,0);//关闭输入通道WM8978_Output_Cfg(1,0); //开启DAC输出 //播放预设音频hello();while(f_opendir(&wavdir,"0:/MUSIC"))//打开音乐文件夹{ //Show_Str(60,190,240,16,"MUSIC文件夹错误!",16,0);printf("MUSIC文件夹错误\r\n");delay_ms(200); //LCD_Fill(60,190,240,206,WHITE);//清除显示 delay_ms(200); } totwavnum=audio_get_tnum("0:/MUSIC"); //得到总有效文件数while(totwavnum==NULL)//音乐文件总数为0 { printf("没有音乐文件\r\n");//Show_Str(60,190,240,16,"没有音乐文件!",16,0);delay_ms(200); //LCD_Fill(60,190,240,146,WHITE);//清除显示 delay_ms(200); } wavfileinfo=(FILINFO*)mymalloc(SRAMIN,sizeof(FILINFO)); //申请内存pname=mymalloc(SRAMIN,_MAX_LFN*2+1); //为带路径的文件名分配内存wavoffsettbl=mymalloc(SRAMIN,4*totwavnum); //申请4*totwavnum个字节的内存,用于存放音乐文件off block索引while(!wavfileinfo||!pname||!wavoffsettbl)//内存分配出错{ printf("内存分配失败\r\n"); //Show_Str(60,190,240,16,"内存分配失败!",16,0);delay_ms(200); //LCD_Fill(60,190,240,146,WHITE);//清除显示 delay_ms(200); } //记录索引res=f_opendir(&wavdir,"0:/MUSIC"); //打开目录if(res==FR_OK){curindex=0;//当前索引为0while(1)//全部查询一遍{temp=wavdir.dptr; //记录当前index res=f_readdir(&wavdir,wavfileinfo); //读取目录下的一个文件if(res!=FR_OK||wavfileinfo->fname[0]==0)break; //错误了/到末尾了,退出 res=f_typetell((u8*)wavfileinfo->fname); if((res&0XF0)==0X40)//取高四位,看看是不是音乐文件 {wavoffsettbl[curindex]=temp;//记录索引curindex++;} } } curindex=0; //从0开始显示res=f_opendir(&wavdir,(const TCHAR*)"0:/MUSIC"); //打开目录while(res==FR_OK)//打开成功{ dir_sdi(&wavdir,wavoffsettbl[curindex]); //改变当前目录索引 res=f_readdir(&wavdir,wavfileinfo); //读取目录下的一个文件if(res!=FR_OK||wavfileinfo->fname[0]==0)break; //错误了/到末尾了,退出 strcpy((char*)pname,"0:/MUSIC/"); //复制路径(目录)strcat((char*)pname,(const char*)wavfileinfo->fname); //将文件名接在后面//LCD_Fill(60,190,lcddev.width-1,190+16,WHITE); //清除之前的显示//Show_Str(60,190,lcddev.width-60,16,(u8*)wavfileinfo->fname,16,0);//显示歌曲名字 //audio_index_show(curindex+1,totwavnum);key=audio_play_song(pname); //播放这个音频文件if(key==KEY2_PRES) //上一曲{if(curindex)curindex--;else curindex=totwavnum-1;}else if(key==KEY0_PRES)//下一曲{curindex++; if(curindex>=totwavnum)curindex=0;//到末尾的时候,自动从头开始}else break; //产生了错误 } myfree(SRAMIN,wavfileinfo); //释放内存 myfree(SRAMIN,pname); //释放内存 myfree(SRAMIN,wavoffsettbl); //释放内存
}
播放wav格式音频
//播放某个WAV文件
//fname:wav文件路径.
//返回值:
//KEY0_PRES:下一曲
//KEY2_PRES:上一曲
//其他:错误
u8 wav_play_song(u8* fname)
{u8 key;u8 key2;u8 t=0; u8 res; u32 fillnum; audiodev.file=(FIL*)mymalloc(SRAMIN,sizeof(FIL));audiodev.i2sbuf1=mymalloc(SRAMIN,WAV_I2S_TX_DMA_BUFSIZE);audiodev.i2sbuf2=mymalloc(SRAMIN,WAV_I2S_TX_DMA_BUFSIZE);audiodev.tbuf=mymalloc(SRAMIN,WAV_I2S_TX_DMA_BUFSIZE);if(audiodev.file&&audiodev.i2sbuf1&&audiodev.i2sbuf2&&audiodev.tbuf){ res=wav_decode_init(fname,&wavctrl);//得到文件的信息if(res==0)//解析文件成功{// printf("%s",fname);if(wavctrl.bps==16){WM8978_I2S_Cfg(2,0); //飞利浦标准,16位数据长度I2S2_Init(I2S_STANDARD_PHILIPS,I2S_MODE_MASTER_TX,I2S_CPOL_LOW,I2S_DATAFORMAT_16B_EXTENDED); //飞利浦标准,主机发送,时钟低电平有效,16位扩展帧长度}else if(wavctrl.bps==24){WM8978_I2S_Cfg(2,2); //飞利浦标准,24位数据长度I2S2_Init(I2S_STANDARD_PHILIPS,I2S_MODE_MASTER_TX,I2S_CPOL_LOW,I2S_DATAFORMAT_24B); //飞利浦标准,主机发送,时钟低电平有效,24位长度}I2S2_SampleRate_Set(wavctrl.samplerate);//设置采样率I2S2_TX_DMA_Init(audiodev.i2sbuf1,audiodev.i2sbuf2,WAV_I2S_TX_DMA_BUFSIZE/2); //配置TX DMAi2s_tx_callback=wav_i2s_dma_tx_callback; //回调函数指wav_i2s_dma_callbackaudio_stop();res=f_open(audiodev.file,(TCHAR*)fname,FA_READ); //打开文件if(res==0){f_lseek(audiodev.file, wavctrl.datastart); //跳过文件头fillnum=wav_buffill(audiodev.i2sbuf1,WAV_I2S_TX_DMA_BUFSIZE,wavctrl.bps);fillnum=wav_buffill(audiodev.i2sbuf2,WAV_I2S_TX_DMA_BUFSIZE,wavctrl.bps);audio_start(); while(res==0){ while(wavtransferend==0);//等待wav传输完成; wavtransferend=0;if(fillnum!=WAV_I2S_TX_DMA_BUFSIZE)//播放结束?{res=KEY0_PRES;break;} if(wavwitchbuf)fillnum=wav_buffill(audiodev.i2sbuf2,WAV_I2S_TX_DMA_BUFSIZE,wavctrl.bps);//填充buf2else fillnum=wav_buffill(audiodev.i2sbuf1,WAV_I2S_TX_DMA_BUFSIZE,wavctrl.bps);//填充buf1while(1){ //扫描红外遥控key2=Remote_Scan();//扫描按键key=KEY_Scan(0); if(key==WKUP_PRES || key2==56)//暂停{printf("暂停\r\n");if(audiodev.status&0X01)audiodev.status&=~(1<<0);else audiodev.status|=0X01; }if(key==KEY2_PRES||key2==24 )//上一曲{// printf("切换歌曲\r\n");
// key=key2;printf("切换上一首歌曲\r\n");res=KEY2_PRES; break; }if(key==KEY0_PRES||key2==74 )//下一曲{// printf("切换歌曲\r\n");
// key=key2;printf("切换下一首歌曲\r\n");res=KEY0_PRES; break; }wav_get_curtime(audiodev.file,&wavctrl);//得到总时间和当前播放的时间 //audio_msg_show(wavctrl.totsec,wavctrl.cursec,wavctrl.bitrate);t++;if(t==20){t=0;LED0=!LED0;}if((audiodev.status&0X01)==0)delay_ms(10);else break;}}audio_stop(); }else res=0XFF; }else {res=0XFF;}}else res=0XFF; myfree(SRAMIN,audiodev.tbuf); //释放内存myfree(SRAMIN,audiodev.i2sbuf1);//释放内存myfree(SRAMIN,audiodev.i2sbuf2);//释放内存 myfree(SRAMIN,audiodev.file); //释放内存 return res;
}
四、红外遥控
1.原理
- NEC协议特征
(1)8位地址和8位指令长度;
(2)地址和命令2次传输(确保可靠性)
(3)PWM脉冲宽度调制,以发射红外载波的占空比代表“0”和“1”;
(4)载波频率为38Khz;
(5)位时间为1.125ms或2.25ms。
- NEC码的位定义
一个脉冲对应560us的连续载波,一个逻辑1传输需要2.25ms(560us脉冲+1680us低电平),一个逻辑0的传输需要1.125ms(560us脉冲+560us低电平)。而遥控接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,我们在接收头端收到的信号为:逻辑1应该是560us低+1680us高,逻辑0应该是560us低+560us高。
具体详情可以参考博客https://blog.csdn.net/li_little7/article/details/89950161
2.捕获红外遥控
基于正点原子红外遥控解码驱动代码进行修改。使用定时器设置定时中断去扫描对面通道的值。根据扫描的值进行对应转换得到按键信息。
//定时器更新(溢出)中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if(htim->Instance==TIM1){if(RmtSta&0x80)//上次有数据被接收到了{ RmtSta&=~0X10; //取消上升沿已经被捕获标记if((RmtSta&0X0F)==0X00)RmtSta|=1<<6;//标记已经完成一次按键的键值信息采集if((RmtSta&0X0F)<14)RmtSta++;else{RmtSta&=~(1<<7);//清空引导标识RmtSta&=0XF0; //清空计数器 } } }
}//定时器输入捕获中断回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)//捕获中断发生时执行
{if(htim->Instance==TIM1)
{if(RDATA)//上升沿捕获{TIM_RESET_CAPTUREPOLARITY(&TIM1_Handler,TIM_CHANNEL_1); //一定要先清除原来的设置!!TIM_SET_CAPTUREPOLARITY(&TIM1_Handler,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING);//CC1P=1 设置为下降沿捕获__HAL_TIM_SET_COUNTER(&TIM1_Handler,0); //清空定时器值 RmtSta|=0X10; //标记上升沿已经被捕获}else //下降沿捕获{Dval=HAL_TIM_ReadCapturedValue(&TIM1_Handler,TIM_CHANNEL_1);//读取CCR1也可以清CC1IF标志位TIM_RESET_CAPTUREPOLARITY(&TIM1_Handler,TIM_CHANNEL_1); //一定要先清除原来的设置!!TIM_SET_CAPTUREPOLARITY(&TIM1_Handler,TIM_CHANNEL_1,TIM_ICPOLARITY_RISING);//配置TIM5通道1上升沿捕获if(RmtSta&0X10) //完成一次高电平捕获 {if(RmtSta&0X80)//接收到了引导码{if(Dval>300&&Dval<800) //560为标准值,560us{RmtRec<<=1; //左移一位.RmtRec|=0; //接收到0 }else if(Dval>1400&&Dval<1800) //1680为标准值,1680us{RmtRec<<=1; //左移一位.RmtRec|=1; //接收到1}else if(Dval>2200&&Dval<2600) //得到按键键值增加的信息 2500为标准值2.5ms{RmtCnt++; //按键次数增加1次RmtSta&=0XF0; //清空计时器 }}else if(Dval>4200&&Dval<4700) //4500为标准值4.5ms{RmtSta|=1<<7; //标记成功接收到了引导码RmtCnt=0; //清除按键次数计数器} }RmtSta&=~(1<<4);} }
}
//处理红外键盘
//返回值:
// 0,没有任何按键按下
//其他,按下的按键键值.
u8 Remote_Scan(void)
{ u8 sta=0; u8 t1,t2; if(RmtSta&(1<<6))//得到一个按键的所有信息了{ t1=RmtRec>>24; //得到地址码t2=(RmtRec>>16)&0xff; //得到地址反码 if((t1==(u8)~t2)&&t1==REMOTE_ID)//检验遥控识别码(ID)及地址 { t1=RmtRec>>8;t2=RmtRec; if(t1==(u8)~t2)sta=t1;//键值正确 } if((sta==0)||((RmtSta&0X80)==0))//按键数据错误/遥控已经没有按下了{RmtSta&=~(1<<6);//清除接收到有效按键标识RmtCnt=0; //清除按键次数计数器}} return sta;
}
五、源代码
https://github.com/TangtangSix/MusicPlayer
https://gitee.com/tangtangsix/MusicPlayer
STM32F407ZET6音乐播放器相关推荐
- 单片AT89C2051 + SD卡 + 3310LCD = 音乐播放器
http://www.amobbs.com/thread-4503884-1-1.html 这个小玩意,采用 ATMEL 的传统51MCU作主控制芯片,加上SD卡和显示屏,就可以作简单的音乐播放器了, ...
- android手机播放pc音乐播放器,最强手机音乐播放器?Foobar2K安卓版体验
说到最强大的PC音乐播放器,相信很多朋友,特别是HiFi发烧友,会把选票投给Foobar2000.的确,在PC平台上,Foobar2000的优势非常巨大.例如它能够自由定制界面,虽然原生界面很简陋,但 ...
- android 系统锁屏音乐播放器,Android实现音乐播放器锁屏页
本文实例为大家分享了Android音乐播放器锁屏页的具体代码,供大家参考,具体内容如下 首页我们先看一下效果图 下边来说一下实现逻辑,其主要思路就是新建一个activity使其覆盖在锁屏页上边. 一. ...
- 从零开始撸音乐播放器(源码可下载)
演示视频 哈哈,自己是真的弱.被生活狠狠打脸了. Java课设要搞音乐播放器.然而老师只讲了输入输出啥的,其他自学. 从零开始撸代码. 1 .从sound,到AppletPlayer又到Player. ...
- 编写音乐播放器的一些感想
编写音乐播放器的一些感想 当初是想着学习C#,就动手开始实现一个简单的播放器.在实现的工程中发现自己能够学到很多东西,就有了把播放器用c++重写的想法,在实现过程中,发现c++想实现c#同样的功能,真 ...
- Linux开发板怎么用madplay,Linux中madplay 音乐播放器移植步骤
madplay 音乐播放器移植步骤 madplay版本: madplay-0.15.2 交叉编译器版本: arm-linux-gcc 3.4.1 操作系统平台: Linux -- Red Hat 9. ...
- 小型音乐播放器插件APlayer.js的简单使用例子
本篇博客将会给出一个小型音乐播放器插件APlayer.js的使用例子.关于APlayer.js的具体介绍和Github地址,可以参考: https://github.com/MoePlayer/. ...
- Mac无损音乐播放器Audirvana plus
Audirvana plus for Mac(点击进入)一款适用于Mac系统的无损音乐播放器,有别于MP3的有损压缩编码,它不会破坏任何原有的音讯资讯,可以完整地还原音乐CD的音质.除此以外,Audi ...
- js插件---10个免费开源的JS音乐播放器插件
js插件---10个免费开源的JS音乐播放器插件 一.总结 一句话总结:各种插件都有很多,多去找. 二.js插件---10个免费开源的JS音乐播放器插件 亲测可用 音乐播放器在网页设计中有时候会用到, ...
- 其他发行版本安装深度音乐播放器
其他 Linux 发行版本,如 Ubuntu.Linux Mint 等都可以直接下载 Deb 格式安装包来安装 Linux Deepin 精心开发的深度音乐播放器. 下面,以 Ubuntu 为例,介绍 ...
最新文章
- PMP-【第9章 项目资源管理】-2021-2-15(200页-219页)
- FFmpeg转HTML5支持的视频格式
- 程序员初试和复试_程序员因肌肉发达面试被质疑能力,网友:这做程序员有啥关系呢?...
- trident原理及编程指南
- java jpa jar_JPA 开发所需的Jar包 (基于Hibernate)
- 信息学奥赛一本通 2072:【例2.15】歌手大奖赛
- 炒冷饭系列:设计模式 单例模式
- 阿里云Kubernetes SpringCloud 实践进行时(5): 弹性服务与容错处理
- extern作用详解
- linux 测试post接口
- Could not set the project description for 'hotelseqbid.ws' because the project d
- 红细胞识别matlab,图像处理—红细胞计数(Matlab).doc
- 图片云存储(腾讯云 七牛云)
- canvas实现5张图片合成一张图片
- 超简洁WIN10桌面分享
- 按钮按下时立体感效果
- 聚合数据+新闻头条+数据入库+数据展示
- org.elasticsearch.discovery.MasterNotDiscoveredException异常解决
- 虽然中国超大城市的施工成本可能上升,但仍处于世界最便宜之列
- 派大星python代码_一些简单的python例子
热门文章
- 如何扩展硬盘以及删除恢复分区?
- 列宽一字符等于多少厘米_Excel中行高多少等于1厘米?列宽多少等...
- 苹果手机收不到推送信息_苹果手机微信收不到信息怎么回事(超详细解决方案分享)...
- python色彩变换CMYK,RGB,HSI
- PS(Photoshop)去水印的方法
- 计算机部分应用显示模糊,win10系统打开部分软件字体总显示模糊的解决方法
- 计算机维护费入什么会计科目,金税盘技术维护费计入什么科目_增值税
- 【Excel】五种方法添加打勾方框(其他符号差不多)
- 图片(img)alt属性标签怎么写
- SEO优化之alt属性和title属性