只用STM32单片机+SD卡+耳机插座,实现播放MP3播放器!
看过很多STM32软解MP3的方案,即不通过类似VS1053之类的解码器芯片,直接用STM32和软件库解码MP3文件,通常使用了labmad或者Helix解码库实现,Helix相对labmad占用的RAM更少。但是大多数参考的方案还是用了外接IIS接口WM98xx之类的音频DAC芯片播放音频,稍显复杂繁琐。STM32F407Vx本身就自带了2路12位DAC输出,最高刷新速度333kHz,除了分辨率差点意思,速度上对于MP3通常44.1kHz采样率来说,用来播放音频绰绰有余了。本文给的方案和源码,直接用STM32软解码MP3并使用自带的2个DAC输出引脚输出音频左右声道。
原理:STM32从SD读取MP3文件原始数据,发送给Helix库解码,Helix解码后输出PCM数据流,将此数据进一步处理转换后,按照左右声道分别存入DAC输出1和2缓存,通过定时器以MP3文件的采样率的频率提供DAC触发节拍,通过DMA取缓存中高12位数据给DAC,在DAC1和2引脚产生音频波形,通过电容耦合到耳机的左右声道上。
MP3源文件是一种经过若干算法,将原始音频数据压缩得来的,软件解码的过程是逆过程,将压缩的音频反向转换为记录了左右声道、幅值的数据流,通常是PCM格式。
PCM:是模拟信号以固定的采样频率转换成数字信号后的表现形式。记录了音频采样的数据,双通道、16bit的PCM数据格式是以0轴为中心,范围为-32768~32767的数值,每个数据占用2字节,左声道和右声道交替存储,如图。
软解码得到的PCM数据到STM32的DAC缓存需要进一步处理。STM32的DAC是12位的,其输入范围04095,而双通道16位的PCM音频数据是左右声道交替存储,且数据范围-3276832767,因此PCM到STM32的DAC缓存要按照顺序一拆为二,分为左右声道,每个数据再加上32768,使其由short int的范围转换为unsigned short int,即0~65535。由于PCM数据是对音频的采样,因此调节音量(幅值)可以在此步骤一并处理,即音频数据 x 音量 /最大音量。至于DAC是12位,只需将DAC模式设置为左对齐12位,舍弃低4位即可。
到此,STM32的DAC输出引脚上应该已经有音频信号了,通常DAC引脚上串联一个1~10uF的电容用来耦合音频信号,电容越大音质越好,电容另一端接耳机插座的左声道/右声道,插上耳机就可以欣赏音乐啦!音质嘛,反正我是听不出来好不好,跟商品MP3播放器差不多。如果不串联电容,DAC引脚直连耳机插座左右声道也能听到声音,就是有些数字信号噪声也会传进来。如果希望噪声小一些,DAC引脚输出端加一个下图的低通滤波电路也是可以的。
Helix移植:
Helix源码的官网我没找到,直接用了野火的例程里面的代码,移植也很简单,不用改任何代码,只需要将Helix文件夹拷贝到工程目录里,然后在Keil中添加好文件,以及添加头文件途径,编译即可。工程目录如图。
源码:dac配置
dac.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 #include "dac.h" #define CNT_FREQ 84000000 #define DHR12R1_OFFSET ((uint32_t)0x00000008) #define DHR12R2_OFFSET ((uint32_t)0x00000014) #define DHR12RD_OFFSET ((uint32_t)0x00000020) uint32_t DAC_DHR12R1_ADDR = (uint32_t )DAC_BASE + DHR12R1_OFFSET + DAC_Align_12b_L;uint32_t DAC_DHR12R2_ADDR = (uint32_t )DAC_BASE + DHR12R2_OFFSET + DAC_Align_12b_L;uint16_t DAC_buff[2 ][DAC_BUF_LEN]; static void TIM6_Config (void ) ;void DAC_Config (void ) { GPIO_InitTypeDef GPIO_InitStructure; DAC_InitTypeDef DAC_InitStructure; RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd (RCC_APB1Periph_DAC, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_Init (GPIOA, &GPIO_InitStructure); DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None; DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO; DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable; DAC_Init (DAC_Channel_1, &DAC_InitStructure); DAC_Init (DAC_Channel_2, &DAC_InitStructure); DMA_InitTypeDef DMA_InitStruct; DMA_StructInit (&DMA_InitStruct); RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_DMA1, ENABLE); DMA_InitStruct.DMA_PeripheralBaseAddr = (u32)DAC_DHR12R1_ADDR; DMA_InitStruct.DMA_Memory0BaseAddr = (u32)&DAC_buff[0 ]; DMA_InitStruct.DMA_DIR = DMA_DIR_MemoryToPeripheral; DMA_InitStruct.DMA_BufferSize = DAC_BUF_LEN; DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; DMA_InitStruct.DMA_Priority = DMA_Priority_High; DMA_InitStruct.DMA_Channel = DMA_Channel_7; DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init (DMA1_Stream5, &DMA_InitStruct); DMA_InitStruct.DMA_PeripheralBaseAddr = (u32)DAC_DHR12R2_ADDR; DMA_InitStruct.DMA_Memory0BaseAddr = (u32)&DAC_buff[1 ]; DMA_Init (DMA1_Stream6, &DMA_InitStruct); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream6_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1 ; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init (&NVIC_InitStructure); DMA_ClearITPendingBit (DMA1_Stream6, DMA_IT_TCIF6); DMA_ClearITPendingBit (DMA1_Stream6, DMA_IT_HTIF6); DMA_ITConfig (DMA1_Stream6, DMA_IT_TC, ENABLE); DMA_ITConfig (DMA1_Stream6, DMA_IT_HT, ENABLE); DAC_Cmd (DAC_Channel_1, ENABLE); DAC_Cmd (DAC_Channel_2, ENABLE); DAC_DMACmd (DAC_Channel_1, ENABLE); DAC_DMACmd (DAC_Channel_2, ENABLE); TIM6_Config (); } void DAC_DMA_Start (uint32_t freq, uint16_t len) { DAC_DMA_Stop (); DMA_SetCurrDataCounter (DMA1_Stream5, len); DMA_SetCurrDataCounter (DMA1_Stream6, len); TIM_SetAutoreload (TIM6, (uint16_t )((CNT_FREQ)/freq)); DMA_Cmd (DMA1_Stream5, ENABLE); DMA_Cmd (DMA1_Stream6, ENABLE); } void DAC_DMA_Stop (void ) { DMA_Cmd (DMA1_Stream5, DISABLE); DMA_Cmd (DMA1_Stream6, DISABLE); } static void TIM6_Config (void ) { TIM_TimeBaseInitTypeDef TIM6_TimeBase; RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM6, ENABLE); TIM_TimeBaseStructInit (&TIM6_TimeBase); TIM6_TimeBase.TIM_Period = (uint16_t )((CNT_FREQ)/44100 ); TIM6_TimeBase.TIM_Prescaler = 0 ; TIM6_TimeBase.TIM_ClockDivision = 0 ; TIM6_TimeBase.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit (TIM6, &TIM6_TimeBase); TIM_SelectOutputTrigger (TIM6, TIM_TRGOSource_Update); TIM_Cmd (TIM6, ENABLE); } void DAC_Out1 (uint16_t dat) { DAC_SetChannel1Data (DAC_Align_12b_R, dat); DAC_SoftwareTriggerCmd (DAC_Channel_1, ENABLE); } void DAC_Out2 (uint16_t dat) { DAC_SetChannel2Data (DAC_Align_12b_R, dat); DAC_SoftwareTriggerCmd (DAC_Channel_2, ENABLE); }
源码:MP3播放流程 (原创野火,参考了野火的例程,本人进行整理和修改)
MP3player.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 #include #include #include "ff.h" #include "mp3Player.h" #include "mp3dec.h" #include "dac.h" #include "led.h" #define MP3BUFFER_SIZE 2304 #define INPUTBUF_SIZE 3000 static HMP3Decoder Mp3Decoder; static MP3FrameInfo Mp3FrameInfo; static MP3_TYPE mp3player; volatile uint8_t Isread = 0 ; volatile uint8_t dac_ht = 0 ; uint32_t led_delay = 0 ;uint8_t inputbuf[INPUTBUF_SIZE]={0 }; static short outbuffer[MP3BUFFER_SIZE]; static FIL file; static UINT bw; FRESULT result; int MP3DataDecoder (uint8_t **read_ptr, int *bytes_left) { int err = 0 , i = 0 , outputSamps = 0 ; err = MP3Decode (Mp3Decoder, read_ptr, bytes_left, outbuffer, 0 ); if (err != ERR_MP3_NONE) { switch (err) { case ERR_MP3_INDATA_UNDERFLOW: printf ("ERR_MP3_INDATA_UNDERFLOW\r\n" ); result = f_read (&file, inputbuf, INPUTBUF_SIZE, &bw); *read_ptr = inputbuf; *bytes_left = bw; break ; case ERR_MP3_MAINDATA_UNDERFLOW: printf ("ERR_MP3_MAINDATA_UNDERFLOW\r\n" ); break ; default : printf ("UNKNOWN ERROR:%d\r\n" , err); if (*bytes_left > 0 ) { (*bytes_left) --; read_ptr ++; } break ; } return 0 ; } else { MP3GetLastFrameInfo (Mp3Decoder, &Mp3FrameInfo); outputSamps = Mp3FrameInfo.outputSamps; if (outputSamps > 0 ) { if (Mp3FrameInfo.nChans == 1 ) { for (i = outputSamps - 1 ; i >= 0 ; i--) { outbuffer[i * 2 ] = outbuffer[i]; outbuffer[i * 2 + 1 ] = outbuffer[i]; } outputSamps *= 2 ; } } for (i = 0 ; i < outputSamps/2 ; i++) { if (dac_ht == 1 ) { DAC_buff[0 ][i] = outbuffer[2 *i] * mp3player.ucVolume /100 + 32768 ; DAC_buff[1 ][i] = outbuffer[2 *i+1 ] * mp3player.ucVolume /100 + 32768 ; } else { DAC_buff[0 ][i+outputSamps/2 ] = outbuffer[2 *i] * mp3player.ucVolume /100 + 32768 ; DAC_buff[1 ][i+outputSamps/2 ] = outbuffer[2 *i+1 ] * mp3player.ucVolume /100 + 32768 ; } } return 1 ; } } uint8_t read_file (const char *mp3file, uint8_t **read_ptr, int *bytes_left) { result = f_read (&file, inputbuf, INPUTBUF_SIZE, &bw); if (result != FR_OK) { printf ("读取%s失败 -> %d\r\n" , mp3file, result); return 0 ; } else { *read_ptr = inputbuf; *bytes_left = bw; return 1 ; } } void mp3PlayerDemo (const char *mp3file) { uint8_t *read_ptr = inputbuf; int read_offset = 0 ; int bytes_left = 0 ; mp3player.ucStatus = STA_IDLE; mp3player.ucVolume = 15 ; result = f_open (&file, mp3file, FA_READ); if (result != FR_OK) { printf ("Open mp3file :%s fail!!!->%d\r\n" , mp3file, result); result = f_close (&file); return ; } printf ("当前播放文件 -> %s\n" , mp3file); Mp3Decoder = MP3InitDecoder (); if (Mp3Decoder == 0 ) { printf ("初始化helix解码库设备失败!\r\n" ); return ; } else { printf ("初始化helix解码库完成\r\n" ); } if (!read_file (mp3file, &read_ptr, &bytes_left)) { MP3FreeDecoder (Mp3Decoder); return ; } if (MP3DataDecoder (&read_ptr, &bytes_left)) { printf (" \r\n Bitrate %dKbps" , Mp3FrameInfo.bitrate/1000 ); printf (" \r\n Samprate %dHz" , Mp3FrameInfo.samprate); printf (" \r\n BitsPerSample %db" , Mp3FrameInfo.bitsPerSample); printf (" \r\n nChans %d" , Mp3FrameInfo.nChans); printf (" \r\n Layer %d" , Mp3FrameInfo.layer); printf (" \r\n Version %d" , Mp3FrameInfo.version); printf (" \r\n OutputSamps %d" , Mp3FrameInfo.outputSamps); printf ("\r\n" ); if (Mp3FrameInfo.nChans == 1 ) { DAC_DMA_Start (Mp3FrameInfo.samprate, 2 * Mp3FrameInfo.outputSamps); } else { DAC_DMA_Start (Mp3FrameInfo.samprate, Mp3FrameInfo.outputSamps); } } else { MP3FreeDecoder (Mp3Decoder); return ; } mp3player.ucStatus = STA_PLAYING; while (mp3player.ucStatus == STA_PLAYING) { read_offset = MP3FindSyncWord (read_ptr, bytes_left); if (read_offset < 0 ) { if (!read_file (mp3file, &read_ptr, &bytes_left)) { continue ; } } else { read_ptr += read_offset; bytes_left -= read_offset; if (bytes_left < 1024 ) { u16 i = (uint32_t )(bytes_left)&3 ; if (i) i=4 -i; memcpy (inputbuf+i, read_ptr, bytes_left); read_ptr = inputbuf+i; result = f_read (&file, inputbuf+bytes_left+i, INPUTBUF_SIZE-bytes_left-i, &bw); if (result != FR_OK) { printf ("读取%s失败 -> %d\r\n" ,mp3file,result); break ; } bytes_left += bw; } } if (!MP3DataDecoder (&read_ptr, &bytes_left)) { Isread = 1 ; } if (file.fptr == file.fsize) { printf ("单曲播放完毕\r\n" ); break ; } while (Isread == 0 ) { led_delay++; if (led_delay == 0xffffff ) { led_delay=0 ; LED1_TROG; } } Isread = 0 ; } DAC_DMA_Stop (); mp3player.ucStatus = STA_IDLE; MP3FreeDecoder (Mp3Decoder); f_close (&file); } void DMA1_Stream6_IRQHandler (void ) { if (DMA_GetITStatus (DMA1_Stream6, DMA_IT_HTIF6) != RESET) { dac_ht = 1 ; Isread=1 ; DMA_ClearITPendingBit (DMA1_Stream6, DMA_IT_HTIF6); } if (DMA_GetITStatus (DMA1_Stream6, DMA_IT_TCIF6) != RESET) { dac_ht = 0 ; Isread=1 ; DMA_ClearITPendingBit (DMA1_Stream6, DMA_IT_TCIF6); } }
源码:main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 #include "main.h" #include "hw_includes.h" #include "ff.h" #include "exfuns.h" #include "mp3Player.h" u8 scan_files (u8 * path) { FRESULT res; char buf[512 ] = {0 }; char *fn; #if _USE_LFN fileinfo.lfsize = _MAX_LFN * 2 + 1 ; fileinfo.lfname = buf; #endif res = f_opendir (&dir,(const TCHAR*)path); if (res == FR_OK) { printf ("\r\n" ); while (1 ){ res = f_readdir (&dir, &fileinfo); if (res != FR_OK || fileinfo.fname[0 ] == 0 ) break ; #if _USE_LFN fn = *fileinfo.lfname ? fileinfo.lfname : fileinfo.fname; #else fn = fileinfo.fname; #endif printf ("%s/" , path); printf ("%s\r\n" , fn); } } return res; } int main (void ) { delay_init (168 ); usart1_Init (115200 ); LED_Init (); DAC_Config (); if (!SD_Init ()) { exfuns_init (); f_mount (fs[0 ],"0:" ,1 ); } scan_files ("0:" ); LED0_ON; while (1 ) { mp3PlayerDemo ("0:/断桥残雪.MP3" ); mp3PlayerDemo ("0:/张国荣-玻璃之情.MP3" ); delay_ms (50 ); } }
为方便调试测试,使用usart1打印数据。实测效果:
程序源码与原理图,测试音频:
链接:https://pan.baidu.com/s/10hYXkrqnuBQgs0DWKLUUOA?pwd=iatt 提取码:iatt
知道这里下载要积分登录什么的麻烦得很,所以程序放到百度网盘了,假如连接失效,记得在评论区喊我更新!
理论上STM32F1或者其他系列也能用这个方案,要自己改改测试喽,本文把思路分享出来抛砖引玉。
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!