در قسمت قبل در مورد جزییات فرمت فایل صوتی wav و نحوه پیادهسازی یک wave player ساده صحبت کردیم. در این بخش میخواهیم یک لیست پخش به wave player اضافه کنیم، پس با سیسوگ همراه باشید.
لیست پخشی که در نظر داریم به این صورت است که میکرو تمامی فایلهای wav موجود روی کارت حافظه را پیدا کند و پس از پخش هرکدام به سراغ فایل بعدی برود. یک کلید برای رفتن به آهنگ بعدی نیز در این پروژه تعریف میکنیم که در صورت فشرده شدن، پخش فایل فعلی متوقفشده و برنامه فایل بعدی را پخش کند. پس برای شروع کار، مثل گذشته به سراغ ایجاد پروژه میرویم.
ایجاد پروژه
برای سهولت کار و عدم نیاز بهاضافه کردن همه فایلهای موردنیاز از ابتدا، فایلهای مربوط به پروژه قبلی را کپی میکنیم و سپس تغییرات موردنیاز را در آن اعمال میکنیم. پس از کپی کردن پروژه wave player، فایل IOC آن را باز میکنیم تا نرمافزار Cube MX اجرا شود (البته تغییر پروژه بدون این نرمافزار نیز امکانپذیر است و انجام این عمل تنها بهمنظور یکپارچه بودن روند آموزشهاست). تنها تغییر که میخواهیم ایجاد کنیم، تعریف یک پایه ورودی برای وصل کلید مربوط به آهنگ بعدی است. این کار را مانند تصویر زیر انجام میدهیم؛
برای کلید، پایه PC0 را در نظر گرفتیم و همانطور که در تصویر مشخص است، این پین را در حالت pull-up تنظیم کردیم. حالا تغییرات را اعمال میکنیم و به سراغ کد میرویم.
نوشتن کد پروژه
در این پروژه به تابعی نیاز داریم که فایلهای روی کارت حافظه را چک کند، اگر پسوند فایل wav بود آن را از طریق تابع wave play که قبلا نوشتیم پخش کند و سپس به سراغ فایل بعدی برود. برای نوشتن این تابع به فایل WAV_Handler.c میرویم. تابع scan_files را مشابه مثال خود elm chaN مینویسیم؛
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 | FRESULT scan_files ( char* path /* Start node to be scanned (***also used as work area***) */ ) { FRESULT res; DIR dir; UINT i; static FILINFO fno; static char workPath[128] = {0}; strncpy(workPath, path, sizeof(workPath) - 1); res = f_opendir(&dir, workPath); /* Open the directory */ if (res == FR_OK) { for (;;) { res = f_readdir(&dir, &fno); /* Read a directory item */ if (res != FR_OK || fno.fname[0] == 0) break; /* Break on error or end of dir */ if (fno.fattrib & AM_DIR) { /* It is a directory */ i = strlen(workPath); snprintf(&workPath[i],sizeof(workPath) - i - 1, "/%s", fno.fname); res = scan_files(workPath); /* Enter the directory */ if (res != FR_OK) break; workPath[i] = 0; } else { /* It is a file. */ if(strstr(&fno.fname[strlen(fno.fname) - 5], ".WAV")) { char *filePath = (char*) malloc(sizeof(char) * 128); snprintf(filePath, 127, "%s/%s", workPath, fno.fname); #if(Debug == 1) printf("we play: %s\n", filePath); #endif wave_play(filePath); free(filePath); } } } f_closedir(&dir); } return res; } |
طبیعتاً این تابع را در فایل WAV_Handler.h نیز اعلان میکنیم که بتوانیم از آن استفاده کنیم. در ادامه برای تعریف کردن کلید آهنگ بعدی و همچنین امکان پخش آهنگ بعد پس از اتمام یک آهنگ، دو متغیر بولین گلوبال زیر را تعریف میکنیم؛
1 | volatile bool stop,next = false; |
اکنون باید به ترتیب تغییرات زیر را در توابع wave-start، wave_EndofFile_Callback و wave_play اعمال کنیم؛
1 2 3 4 5 6 7 8 9 10 | void wave_start(void) { #if(Debug == 1) printf("playing the file... \r\n"); #endif stop = false; next = false; EndofFile = false; LL_TIM_EnableCounter(waveTimer); } |
1 2 3 4 5 6 7 8 9 | void wave_EndofFile_Callback(void) { if(EndofFile) { LL_DMA_DisableChannel(waveDMA1, waveDMA1_Channel); BufferAction = 0; stop = true; } } |
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 | if(isWAV){ wave_DACConf(&wavHeader1, wavBuffer); wave_start(); bool unpressedKey = false; while(!(stop || next)) { wave_IsEndofFile(&fil, wavBuffer, wavHeader1.signatureHeader.BitsPerSample); //polling for button if(!unpressedKey && !IsPressedKey()) { unpressedKey = true; } if(unpressedKey && IsPressedKey()) { #if(Debug == 1) printf("next song!"); #endif next = true; } } BufferAction = 0; LL_DMA_DisableChannel(waveDMA1, waveDMA1_Channel); // LL_DMA_DisableChannel(waveDMA2, waveDMA2_Channel); // for copy mono channel section LL_TIM_DisableCounter(waveTimer); LL_DAC_Disable(DAC, waveDAC_Channel1); // LL_DAC_Disable(DAC, waveDAC_Channel2); // for copy mono channel section } |
کار تقریباً تمام است، تنها بخش باقیمانده استفاده از تابع جدیدی است که تعریف کردیم. در فایل main.c کد زیر را مینویسیم؛
1 2 3 4 | wave_Init(); char Start_Path[128] = "/"; scan_files(Start_Path); |
در صورت درست انجام دادن همه مراحل، لیست پخش wave player باید بهدرستی کار کند و فایلهای wav رو کارت حافظه را حتی در پوشههای تودرتو پیدا و پخش کند.
کپی کردن کانال Mono
برای بهتر شدن عملکرد پروژه در پخش موسیقی تک کاناله یا Mono برای پخش با هندزفری، میتوانیم این نوع فایل را جوری پخشکنیم که روی هر دو کانال صدا داشته باشیم. برای کپی کردن کانال، لازم است تغییراتی در کد اعمال کنیم که در ادامه، این تغییرات را بررسی میکنیم.
در مرحله اول، مجدداً فایل IOC را باز میکنیم تا کانال DMA مربوط به خروجی دوم DAC را تنظیم کنیم؛
در کد پروژه نیز باید تابع تنظیم DMA را تغییر دهیم اما قبل از آن در فایل WAV_Handler.h اسم کانال جدید DMA را تعریف میکنیم؛
1 | #define waveDMA2_Channel LL_DMA_CHANNEL_4 |
تغییر مورد نظر در تابع wave_DMAConf :
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 | void wave_DMAConf(wave_header_t *wav1HeaderPtr, uint8_t *wavBuffer) { LL_DMA_DisableChannel(waveDMA1, waveDMA1_Channel); //Disable DMA before configuraton, for consecutive playing /******************* Determine DMA transfer data length, addrees of DAC register and * memory/peripheral data alignment, based on bits per sample & Number of channels */ volatile uint32_t *DACRegADS = &DAC1->DHR8R1; volatile uint32_t *DAC2RegADS = &DAC1->DHR8R2; uint32_t DMA_DataLength = WavebufferLength; uint32_t waveBitPerSampleM, waveBitPerSampleP; // bits per sample for setting Memory and Peripheral /* mono 8 bits per sample */ if((wav1HeaderPtr->signatureHeader.BitsPerSample == 8) && (wav1HeaderPtr->signatureHeader.NumChannels == 1)) { DACRegADS = &DAC1->DHR8R1; DAC2RegADS = &DAC1->DHR8R2; waveBitPerSampleM = LL_DMA_MDATAALIGN_BYTE; waveBitPerSampleP = LL_DMA_PDATAALIGN_BYTE; DMA_DataLength = WavebufferLength; } /* mono 16 bits per sample */ else if((wav1HeaderPtr->signatureHeader.BitsPerSample == 16) && (wav1HeaderPtr->signatureHeader.NumChannels == 1)) { DACRegADS = &DAC1->DHR12L1; DAC2RegADS = &DAC1->DHR12L2; waveBitPerSampleM = LL_DMA_MDATAALIGN_HALFWORD; waveBitPerSampleP = LL_DMA_PDATAALIGN_HALFWORD; DMA_DataLength = WavebufferLength / 2; } /* stereo 8 bits per sample */ else if((wav1HeaderPtr->signatureHeader.BitsPerSample == 8) && (wav1HeaderPtr->signatureHeader.NumChannels == 2)) { DACRegADS = &DAC1->DHR8RD; waveBitPerSampleM = LL_DMA_MDATAALIGN_HALFWORD; waveBitPerSampleP = LL_DMA_PDATAALIGN_HALFWORD; DMA_DataLength = WavebufferLength / 2; } /* stereo 16 bits per sample */ else if((wav1HeaderPtr->signatureHeader.BitsPerSample == 16) && (wav1HeaderPtr->signatureHeader.NumChannels == 2)) { DACRegADS = &DAC1->DHR12LD; waveBitPerSampleM = LL_DMA_MDATAALIGN_WORD; waveBitPerSampleP = LL_DMA_PDATAALIGN_WORD; DMA_DataLength = WavebufferLength / 4; } #if(Debug == 1) printf("DMA_DataLength: %d\r\n\n", DMA_DataLength); #endif //set DMA Data width for peripheral and memory according to Bits per Sample of the file LL_DMA_SetPeriphSize(waveDMA1, waveDMA1_Channel, waveBitPerSampleP); LL_DMA_SetMemorySize(waveDMA1, waveDMA1_Channel, waveBitPerSampleM); /********************** configuring DMA **********************/ LL_DMA_ConfigAddresses(waveDMA1, waveDMA1_Channel, (uint32_t)wavBuffer, (uint32_t) DACRegADS, LL_DMA_DIRECTION_MEMORY_TO_PERIPH); LL_DMA_SetDataLength(waveDMA1, waveDMA1_Channel, DMA_DataLength); LL_DMA_EnableIT_TC(waveDMA1, waveDMA1_Channel); LL_DMA_EnableIT_HT(waveDMA1, waveDMA1_Channel); LL_DMA_EnableIT_TE(waveDMA1, waveDMA1_Channel); LL_DMA_EnableChannel(waveDMA1, waveDMA1_Channel); if(wav1HeaderPtr->signatureHeader.NumChannels == 1) { LL_DMA_SetPeriphSize(waveDMA2, waveDMA2_Channel, waveBitPerSampleP); LL_DMA_SetMemorySize(waveDMA2, waveDMA2_Channel, waveBitPerSampleM); /********************** configuring DMA **********************/ LL_DMA_ConfigAddresses(waveDMA2, waveDMA2_Channel, (uint32_t)wavBuffer, (uint32_t) DAC2RegADS, LL_DMA_DIRECTION_MEMORY_TO_PERIPH); LL_DMA_SetDataLength(waveDMA2, waveDMA2_Channel, DMA_DataLength); LL_DMA_EnableChannel(waveDMA2, waveDMA2_Channel); } } |
همچنین تابع wave_DACConf را به صورت زیر تغییر میدهیم؛
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | void wave_DACConf(wave_header_t *wav1HeaderPtr, uint8_t *wavBuffer) { LL_DAC_Enable(DAC, waveDAC_Channel1); //Enable DAC channel 1 LL_DAC_Enable(DAC, waveDAC_Channel2); //Enable DAC channel 2 wave_DMAConf(wav1HeaderPtr, wavBuffer); wave_TimerConf(wav1HeaderPtr->signatureHeader.SampleRate); LL_DAC_EnableDMAReq(DAC, LL_DAC_CHANNEL_1); if(wav1HeaderPtr->signatureHeader.NumChannels == 1) { LL_DAC_EnableDMAReq(DAC, LL_DAC_CHANNEL_2); } } |
دو خط کد کامنت شده (با توضیح for copy mono channel section) در تابع wave_Play نیز از حالت کامنت خارج میکنیم.
اکنون هنگام پخش فایلهای mono، صدا از هر دو بلندگو پخش خواهد شد.
سلام وقت بخیر
ممنون از مطالب مفیدتون.
ممنون میشم برنامه ADC+DMA+timer رو هم بذارید.