文章目录

  • 一、主要功能
  • 二、flash读写
    • 1.读flash
    • 2.写flash
    • 3.获取wav格式音频数据
    • 4.操作
  • 三、播放音频
    • 1.原理
    • 2.播放“欢迎光临”提示音
    • 3.播放SD卡内的音频文件
      • 读取SD信息以及控制播放音乐
      • 播放wav格式音频
  • 四、红外遥控
    • 1.原理
    • 2.捕获红外遥控
  • 五、源代码

一、主要功能

  1. SD 卡模块存储至少 5 首以上音乐文件(wav 格式);
  2. 片内 Flash 存储 1-2 句短提示音(5-6 秒长度),比如“xxx 的音乐播放器欢迎你!”、
    “SD 文件找不到!”;
  3. 最小系统板上电后自动查找读取 SD 卡上第 1 首音乐文件,然后依次循环播放;
  4. 最小系统板外接一个红外接收模块,接收红外遥控器发送的按键指令序列。红外遥
    控器用来控制最小系统板的音乐播放,实现“暂停”、“播放”、“下一首”、 “上
    一首”、“回到第 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.操作

  1. 把“欢迎光临”的提示音转换成wav格式
  2. 通过python代码得到wav格式音频的数据块,以16进格式保存


  1. 把得到的16进制格式数据用数组进行存储

  1. main函数中调用writeFlash函数进行写flash操作

  1. 重复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音乐播放器相关推荐

  1. 单片AT89C2051 + SD卡 + 3310LCD = 音乐播放器

    http://www.amobbs.com/thread-4503884-1-1.html 这个小玩意,采用 ATMEL 的传统51MCU作主控制芯片,加上SD卡和显示屏,就可以作简单的音乐播放器了, ...

  2. android手机播放pc音乐播放器,最强手机音乐播放器?Foobar2K安卓版体验

    说到最强大的PC音乐播放器,相信很多朋友,特别是HiFi发烧友,会把选票投给Foobar2000.的确,在PC平台上,Foobar2000的优势非常巨大.例如它能够自由定制界面,虽然原生界面很简陋,但 ...

  3. android 系统锁屏音乐播放器,Android实现音乐播放器锁屏页

    本文实例为大家分享了Android音乐播放器锁屏页的具体代码,供大家参考,具体内容如下 首页我们先看一下效果图 下边来说一下实现逻辑,其主要思路就是新建一个activity使其覆盖在锁屏页上边. 一. ...

  4. 从零开始撸音乐播放器(源码可下载)

    演示视频 哈哈,自己是真的弱.被生活狠狠打脸了. Java课设要搞音乐播放器.然而老师只讲了输入输出啥的,其他自学. 从零开始撸代码. 1 .从sound,到AppletPlayer又到Player. ...

  5. 编写音乐播放器的一些感想

    编写音乐播放器的一些感想 当初是想着学习C#,就动手开始实现一个简单的播放器.在实现的工程中发现自己能够学到很多东西,就有了把播放器用c++重写的想法,在实现过程中,发现c++想实现c#同样的功能,真 ...

  6. Linux开发板怎么用madplay,Linux中madplay 音乐播放器移植步骤

    madplay 音乐播放器移植步骤 madplay版本: madplay-0.15.2 交叉编译器版本: arm-linux-gcc 3.4.1 操作系统平台: Linux -- Red Hat 9. ...

  7. 小型音乐播放器插件APlayer.js的简单使用例子

      本篇博客将会给出一个小型音乐播放器插件APlayer.js的使用例子.关于APlayer.js的具体介绍和Github地址,可以参考: https://github.com/MoePlayer/. ...

  8. Mac无损音乐播放器Audirvana plus

    Audirvana plus for Mac(点击进入)一款适用于Mac系统的无损音乐播放器,有别于MP3的有损压缩编码,它不会破坏任何原有的音讯资讯,可以完整地还原音乐CD的音质.除此以外,Audi ...

  9. js插件---10个免费开源的JS音乐播放器插件

    js插件---10个免费开源的JS音乐播放器插件 一.总结 一句话总结:各种插件都有很多,多去找. 二.js插件---10个免费开源的JS音乐播放器插件 亲测可用 音乐播放器在网页设计中有时候会用到, ...

  10. 其他发行版本安装深度音乐播放器

    其他 Linux 发行版本,如 Ubuntu.Linux Mint 等都可以直接下载 Deb 格式安装包来安装 Linux Deepin 精心开发的深度音乐播放器. 下面,以 Ubuntu 为例,介绍 ...

最新文章

  1. PMP-【第9章 项目资源管理】-2021-2-15(200页-219页)
  2. FFmpeg转HTML5支持的视频格式
  3. 程序员初试和复试_程序员因肌肉发达面试被质疑能力,网友:这做程序员有啥关系呢?...
  4. trident原理及编程指南
  5. java jpa jar_JPA 开发所需的Jar包 (基于Hibernate)
  6. 信息学奥赛一本通 2072:【例2.15】歌手大奖赛
  7. 炒冷饭系列:设计模式 单例模式
  8. 阿里云Kubernetes SpringCloud 实践进行时(5): 弹性服务与容错处理
  9. extern作用详解
  10. linux 测试post接口
  11. Could not set the project description for 'hotelseqbid.ws' because the project d
  12. 红细胞识别matlab,图像处理—红细胞计数(Matlab).doc
  13. 图片云存储(腾讯云 七牛云)
  14. canvas实现5张图片合成一张图片
  15. 超简洁WIN10桌面分享
  16. 按钮按下时立体感效果
  17. 聚合数据+新闻头条+数据入库+数据展示
  18. org.elasticsearch.discovery.MasterNotDiscoveredException异常解决
  19. 虽然中国超大城市的施工成本可能上升,但仍处于世界最便宜之列
  20. 派大星python代码_一些简单的python例子

热门文章

  1. 如何扩展硬盘以及删除恢复分区?
  2. 列宽一字符等于多少厘米_Excel中行高多少等于1厘米?列宽多少等...
  3. 苹果手机收不到推送信息_苹果手机微信收不到信息怎么回事(超详细解决方案分享)...
  4. python色彩变换CMYK,RGB,HSI
  5. PS(Photoshop)去水印的方法
  6. 计算机部分应用显示模糊,win10系统打开部分软件字体总显示模糊的解决方法
  7. 计算机维护费入什么会计科目,金税盘技术维护费计入什么科目_增值税
  8. 【Excel】五种方法添加打勾方框(其他符号差不多)
  9. 图片(img)alt属性标签怎么写
  10. SEO优化之alt属性和title属性