可能是电路最简单的MP3播放器

您所在的位置:网站首页 mp3解码器板是双声吗还是单声 可能是电路最简单的MP3播放器

可能是电路最简单的MP3播放器

2024-07-14 19:06| 来源: 网络整理| 查看: 265

最近正在基于STM32F411+WM8978开发一个MP3播放器,支持WAV、FLAC和MP3格式,因此积累了好多音频方面的代码。于是,我脑中浮现了在制作一个播放器的念头,这个播放器最好能做到最简化,让所有拥有最小系统的人都可以体验到音乐的乐趣。

STM32F411的时钟频率有100MHz,那么能流畅播放MP3的最低频率能到多少呢?经过实测,30M!没想到流畅解码FLAC和MP3格式的音频居然这么轻松,突然觉得用F4做这个MP3有点浪费……于是脑洞大开,能不能用F1完成这个播放器呢?

网易云音乐下载得到的FLAC文件都是LV8的,解码需要90k左右的内存,F1自然是全系列都不能满足,但是LV2需要的内存和MP3差不多,需要30k多的内存,这样大部分大容量的F103就能满足了。由于懒得每首歌都转换,就没有移植FLAC的解码,其实是很好实现的,并且解码FLAC所需要的性能是比MP3小的。

由于使用F4芯片里面有独立的音频PLL和全功能的IIS,F1里面的IIS是残废的(除非使用外置时钟,否则时钟误差极大),因此就排除了用外置DAC的想法。突然,我想到大容量的芯片里面有内置的DAC!那么我们能不能就用这个DAC就输出音频呢!

首先查看选型表,我们常见的STM32F103RCT6就能满足我们的需求:STM32F103RC大容量系列,有48k的内存(据说是和ZET6一样的晶元,说不定能用上64K,但是这里48K已经足够),还有双路DAC,正好两个声道,用它做播放器,完全可以满足我们的要求。

等等…这个DAC是12位的。我们常见的音频文件都是16位的,这个DAC并不能接受我们常见的音频文件,需要进行一个转换,这也成了浪费我时间最多的步骤。有人可能会问,12位音质会不会很差啊?我可以负责的告诉你,音质绝对说的过去,至少木耳是听不出来和手机明显的差别的。

说完了DAC的位数,好多人可能还会怀疑这个内置DAC的性能问题。经过手册查询,这个DAC输出值从最低到最高需要4us,也就是250KHz,对于音频完全没问题,而且音频也不会出现幅度这么大的数据。并且带一个可配置的输出缓冲器,可以省掉外部的运放,直接驱动一定的负载,真的离极简设计越来越近了。

说干就干。首先将以前移植好的SD卡驱动简单移植,实现了文件的读取以后,就开始移植WAV播放程序。先移植它是因为WAV不需要解码,直接读文件送进去就好。这里需要提一下我程序框架的问题。由于以前已经有成熟的MP3,因此程序都是有一套框架的。基本的就是设置IIS等参数,然后读文件,等待当前缓冲区播放完再继续读取然后解码。因此我的程序中出现一些IIS字眼不要担心……只是直接移植,懒得改了。

音频播放是定时器+DAC+DMA,这个例程ST官方就有提供,我只是简单移植就让DAC双通道输出了正弦波。

定时器部分就是产生一个和音频文件采样率相同的时钟给DAC,让DAC按照这个速度去找DMA取数据,DMA有两个缓冲区,播放一个缓冲区的时候读取和解码另一个缓冲区,这样纯硬件的播放可以保证恒定并且正确的播放速度,并且不需要使用CPU进行数据的搬运。当一个缓冲区播放完以后,产生中断,切换缓冲区,继续读取。下面是DMA和定时器的初始化代码。

uint8_t AudioPlay_I2SConfig(uint8_t Bits,uint32_t SampleRate,uint16_t BufSize) { DMA_InitTypeDef DMA_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_DeInit(TIM2); TIM_TimeBaseStructInit(&TIM_TimeBaseStructure); TIM_TimeBaseStructure.TIM_Period = 72000000/SampleRate; TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update); TIM_Cmd(TIM2, ENABLE); DMA_DeInit(DMA2_Channel4); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(DAC->DHR12RD); DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&DualSine12bit; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize = BufSize; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA2_Channel4, &DMA_InitStructure); DMA_Cmd(DMA2_Channel4, ENABLE); DMA_ITConfig(DMA2_Channel4,DMA_IT_TC|DMA_IT_HT,ENABLE); return 0; }

接下来就是把缓冲区改成文件了。一到这儿我傻眼了,STM32F4的DMA有双缓冲模式但是F1没有。正在我一筹莫展的时候,我发现F1的DMA有一个半传输中断,这玩意儿不就和双缓冲一样的东西嘛。只需要取两倍的缓冲区大小,然后使能半传输和传输完成中断,就能实现类似双缓冲的无缝播放的效果。

void DMA2_Channel4_5_IRQHandler(void) { if(DMA_GetITStatus(DMA2_IT_TC4)) DataRequestFlag = 1; else if(DMA_GetITStatus(DMA2_IT_HT4)) DataRequestFlag = 2; DMA_ClearITPendingBit(DMA2_IT_TC4 | DMA2_IT_HT4); } void* AudioPlay_GetCurrentBuff(void) { if(DataRequestFlag == 1) { return (void*)((uint32_t)DualSine12bit + AudioPlayInfo.BufferSize); } else if(DataRequestFlag == 2) { return DualSine12bit; } else { return NULL; } }

注意这里的DataRequestFlag一定要加__IO也就是volatile修饰,要不会被优化掉,卡死在while里。 这里的DualSine12Bit就是缓冲区了,根据中断类型返回是缓冲区起始地址还是加上一个缓冲区(也就是后一半)的地址。 有了数据,声音是出来的。但是杂音非常非常大,只能听到一点点音乐。我想起来我是直接把立体声16位的数据送给DAC的双通道左对齐寄存器DAC->DHR12LD,我原以为这样就能一次传输两个通道的数据并且舍弃掉低4位,但是我错了。查阅资料发现,IIS的16位数据是signed short,也就是有符号位的,并且负数是用补码保存的,然而我们这里的DAC应该工作在中值为2048(一半参考电压,加耦合电容),因此音频数据我们需要稍微处理一下,如下面程序所示,除以16再加2048转换成无符号数。

void AudioPlay_DataProc(uint16_t* buff,uint16_t num) { uint16_t i; for(i = 0;i https://img.yuanze.wang/posts/mp3-dac/sch.png 小板子简易原理图

为了换歌曲方便,我加了两个按键,分别是上一曲和下一曲,还加了一个10K的双联音频电位器调整音量,自此一个WAV播放器就搞定了。

https://img.yuanze.wang/posts/mp3-dac/photo1.jpg 硬件整体照

硬件连接如上图所示,整个硬件主要由一个最小系统、一个SD卡模块以及一块自己焊接的小板子组成,小板子上总共有两个电阻、两个电容、一个电位器、两个按键和一个耳机座。连接单片机的端口可以在程序里修改,SD卡使用SPI2,CS=PB12,CLK=PB13,MISO=PB14,MOSI=PB15。 整个播放器只用了8个IO口!要是只用一个下一曲还可以再省一个,最小系统周围一圈IO口显得空空荡荡的。

https://img.yuanze.wang/posts/mp3-dac/photo2.jpg 小板子正面

https://img.yuanze.wang/posts/mp3-dac/photo3.jpg 小板子背面

https://img.yuanze.wang/posts/mp3-dac/photo4.jpg 最小系统的IO看起来空荡荡的

https://img.yuanze.wang/posts/mp3-dac/photo5.jpg SD卡模块

接下来的MP3解码也就轻而易举了。移植了Helix库,声音也很轻松的出来了。

最后,我还将MP3解码器移植了一份直接从ROM读取数据的版本,这样就可以方便的做开机语音和语音提示之类的了。程序只占用了40k,ROM还剩下了200K左右,放44.1KHz,16Bit,64KBps的单声道音频可以放半分钟多,足够了。有兴趣的可以用J-Link烧一个不带任何ID3信息的MP3文件到0x08020000也就是128k的地方感受一下开机语音。

 STM32工程



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3