در قسمت قبلی راهاندازی و استفاده از کارت حافظه SD را آموختیم و در این قسمت می خواهیم راهاندازی یک wave player را به شما همراهان سیسوگ آموزش دهیم.
همانطور که میدانید فرمت wave (فایلهای با پسوند.wav) یکی از فرمتهای رایج برای فایلهای صوتی است. در این بخش از آموزش STM32 قصد داریم یک wave player با استفاده از قابلیتهای این میکرو بسازیم. با سیسوگ همراه باشید.
قبل از اینکه وارد روند توسعه پروژه wave player شویم، فرمت wav را با جزییات بیشتری بررسی میکنیم. این فرمت از ساختار فرمت فایل RIFF (مخفف Resource Interchange File Format) استفاده میکند. همچنین معمولاً از این فرمت برای ذخیره اطلاعات صوتی بدون فشردهسازی و اتلاف استفاده میشود. هرچند امکان ذخیره اطلاعات بهصورت فشرده نیز در فرمت wav وجود دارد.
فرمت wav
هر فایل wav عموماً از سه بخش تشکیل میشود. قسمت RIFF، قسمت Format و قسمت Data در حالتی که فایل wav بهصورت فشرده ساختهشده باشد، شامل بلاکهای fact نیز میشود. ساختار معمول فرمت wav در شکل زیر قابلمشاهده است:
هرکدام از بخشهای هدر یک فایل wav، اطلاعاتی راجع بهویژگیهای فایل میدهد. بخش Chuck ID شامل کد اسکی مربوط به حروف “RIFF” است. Chunksize اندازه فایل را براساس تعداد بایت نشان میدهد(اندازه فایل منهای 8 بایتی که تا اینجای هدر برای ذخیره این دو بخش بهکاررفته است). Format کد اسکی مربوط به حروف “WAVE” را شامل میشود. در بلوک fmt، جزییات مربوط به فرمت فایل قرار دارد. همانطور که احتمالاً انتظار دارید فیلد Subchunk1ID شامل کاراکترهای ” fmt” میشود. فیلد بعدی یعنی Subchunk1Size اندازه ادامه این بلوک را مشخص میکنم که درصورتیکه فایل در قالب PCM باشد، عدد 16 خواهد بود. فیلد AudioFormat نیز مشخص میکند که فایل PCM است یا اینکه نوعی فشردهسازی در آن بهکاررفته است. NumChannels نیز همانطور که اسم آن مشخص است، تعداد کانالها را مشخص میکند که برای Mono، 1 و برای Stereo، 2 خواهد بود. SampleRate سرعت پخش نمونهها را مشخص میکند و ByteRate سرعت پخش براساس بایت را بیان میکند. حوزه بعدی BlockAlign است که برابر است با تعداد کانالها ضربدر تعداد بیتهای هر نمونه تقسیمبر 8. فیلد BitsPerSample که در حالت PCM آخرین فیلد این بلاک است نیز تعداد بیتهای هر نمونه را مشخص میکند. بلوک بعدی بلوک داده است که شامل سه فیلد میشود. فیلد اول که Subchunk2ID است کد اسکی حروف “data” را در خود نگه میدارد. فیلد Subchunk2Size تعداد بایتهای دادههای صوتی را مشخص میکند و بالاخره آخرین فیلد خود داده صوتی است. توضیحات کامل مربوط به بخشهای مختلف هدر در جدول زیر آورده شده است (برای توضیح بیشتر به این لینک مراجعه کنید).
بنابراین برای خواندن صحیح یک فایل wav و طراحی wave player، باید بتوانیم این اطلاعات را بهدرستی از هدر فایل استخراجکنیم و براساس آن، پارامترهایی مثل اندازه فایل، سرعت پخش، اندازه هر نمونه را تنظیم کنیم. یک مثال از مشخصات فایل wav مربوط به یک موج سینوسی (تک فرکانس) و نمایش هگز اطلاعات آن را در تصاویر زیر میبینیم:
همانطور که در تصویر بالا مشخص است، چهار بایت اول کد اسکی “RIFF” را مشخص میکند و به همین ترتیب اطلاعات مربوط به هدر قرارگرفتهاند، تا جایی که به بلوک داده میرسیم. در این بلوک پس از قسمتهای ID (که کد اسکی data است) و Subchunk2Size، دادههای مربوط به فایل صوتی قرارگرفتهاند. نمونههای این فایل صوتی 16 بیتی و دو کاناله (Stereo) هستند. یعنی اینکه هر نمونه 2 بایت فضا اشغال کرده است و دادههای کانال چپ و کانال راست بهصورت یکی در میان قرارگرفتهاند.
حال که با ساختار فایل wav آشنا شدهایم، به سراغ طراحی wave player میرویم.
ایجاد پروژه
در این پروژه از میکروکنترلر STM32F103RET6 استفاده میکنیم. برای ایجاد پروژه wave player، مانند بخش قبلی، یعنی پروژه SD Card، بااینکه نیاز به واحد SPI داریم اما فعلاً آن را فعال نمیکنیم و توابع مربوط به این واحد و فایل سیستم را بعد به پروژه اضافه میکنیم. پس از فعال و تنظیم کردن بخشهای دیباگ، کلاک و USART1، واحد DAC را نیز برای تولید خروجی فعال میکنیم و کانال DMA مربوط به خروجی 1 آن را در حالت Circular تنظیم میکنیم؛
همانطور که در تصویر مربوط به تنظیم DAC مشخص است، ترگیر این واحد را بهوسیله تایمر 2 تنظیم کردهایم. درواقع سرعت پخش نمونهها توسط DAC بهوسیله تریگر تایمر 2 تنظیم میشود. پس فرکانس پخش را باید با فرکانس تایمر 2 تنظیم کنیم.
اکنون به سراغ تنظیم تایمر 2 میرویم؛
تنها بخشهای مهم در تنظیم تایمر، انتخاب منبع کلاک (Internal Clock) و Trigger Event Selection هستند. بقیه بخشها را بعداً در کد برنامه تنظیم میکنیم.
اکنون کلاک میکرو را روی 72MHz تنظیم میکنیم و توابع LL را برای بخشهای فعالشده انتخاب میکنیم و سپس پروژه را ایجاد میکنیم.
نوشتن کد پروژه
در این پروژه، مانند پروژه SD Card، فایلهای کتابخانهای fatfs و mmc_stm32f1_spi.c و دیگر فایلهای مورد نیاز آنها که در آن پروژه استفاده کردیم را به مسیر پروژه wave player اضافه میکنیم. برای منظم شدن پروژه و جلوگیری از شلوغ شدن فایل اصلی برنامه، دو فایل جدید WAV_Handler.c و WAV_Handler.h را به ترتیب در قسمتهای src و inc پروژه ایجاد میکنیم.
قبل از نوشتن فایلهای جدید، به سراغ فایل mmc_stm32f1_spi.c میرویم تا واحد spi مورد استفاده را از spi1 به spi2 تغییر دهیم. زیرا در این پروژه برای فایلهای stereo به خروجی دوم DAC نیاز پیدا میکنیم که با پینهای مورد استفاده توسط spi1 تداخل دارد؛
در فایل WAV_Handler.h کتابخوانهها و ثابتهای مورد نیاز را تعریف میکنیم، همچنین تایپ استراکچر مربوط به اطلاعات هدر را در این فایل تعریف میکنیم؛
1 2 3 4 5 6 7 8 9 10 11 | #include <string.h> #include <stdio.h> #include <stdint.h> #include <stdbool.h> #include <stdlib.h> #include "SD_Utility.h" #include "stm32f1xx_ll_dac.h" #include "stm32f1xx_ll_cortex.h" #include "stm32f1xx_ll_dma.h" #include "stm32f1xx_ll_tim.h" #include "stdio_usart1.h" |
1 2 3 4 5 6 7 8 9 10 | #define Debug 1 //for Debug mode: 1, for normal run: 0 #define SystemCoreClock 72000000 #define waveTimer TIM2 #define WavebufferLength 2048 //DMA will transfer buffer of size 1KB to DAC #define waveDAC_Channel1 LL_DAC_CHANNEL_1 #define waveDAC_Channel2 LL_DAC_CHANNEL_2 #define waveDMA DMA2 #define waveDMA_Channel LL_DMA_CHANNEL_3 #define waveFile "sin.wav" |
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 | typedef struct __attribute__((packed)){ //The "RIFF" chunk descriptor uint8_t ChunkID[4]; uint32_t ChunkSize; uint8_t Format[4]; //The "fmt" sub-chunk uint8_t Subchunk1ID[4]; uint32_t Subchunk1Size; uint16_t AudioFormat; uint16_t NumChannels; uint32_t SampleRate; uint32_t ByteRate; uint16_t BlockAlign; uint16_t BitsPerSample; }wave_header_t1; typedef struct __attribute__((packed)){ //The "data" sub-chunk uint8_t Subchunk2ID[4]; uint32_t Subchunk2Size; }wave_header_t2; typedef struct { wave_header_t1 signatureHeader; wave_header_t2 dataHeader; }wave_header_t; |
دلیل تعریف استراکچر بهصورت بالا، این است که ممکن است میان این بخش از اطلاعات فاصله وجود داشته باشد و بین آنها اطلاعات دیگری قرار بگیرد. با این تعریف مطمئن میشویم که اطلاعات بهدرستی خوانده میشوند. همچنین در بین فایلهای اضافهشده، SD_Utility.h مربوط به توابع تعریفشده در پروژه قبل هستند که محتویات آن را در ادامه بررسی میکنیم.
در آخر، تابعهایی که قرار است در فایل WAV_Handler.c تعریف کنیم را اعلان میکنیم.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // Initialization void wave_Init(void); //Handling wave file bool wave_open(FIL *, char *); bool wave_readHeader(FIL *, wave_header_t *); void wave_DMAConf(wave_header_t *, uint8_t *); void wave_TimerConf(uint32_t); void wave_DACConf(wave_header_t *, uint8_t *); void wave_start(void); void wave_IsEndofFile(FIL *, uint8_t *, uint16_t); void wave_play(char *); void wave_EndofFile_Callback(void); void wave_DMA_TC_Callback(void); void wave_DMA_HT_Callback(void); |
محتویات فایلهای SD_Utility.h و SD_Utility.c را به ترتیب بهصورت زیر تعریف میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /** ****************************************************************************** * @file SD_Utility.h ****************************************************************************** */ #include <stdio.h> #include "stm32f1xx_ll_cortex.h" #include "ff.h" #include "diskio.h" #define Debug_SDInit 0 //for Debug mode: 1, for normal run: 0 /********************/ /********///Functions: void SD_Card_Init(void); void Print_SD_Card_space(void); |
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 | /** ****************************************************************************** * @file SD_Utility.c ****************************************************************************** */ #include "SD_Utility.h" /********************/ /********///variables: /* file handling variables */ FATFS FatFs; /* File system */ FATFS *pfs; /********************/ /********///Functions: /*********************** Initialization ***********************/ void SD_Card_Init(void) { SysTick_Config(SystemCoreClock / 1000); LL_SYSTICK_SetClkSource(LL_SYSTICK_CLKSOURCE_HCLK); LL_SYSTICK_EnableIT(); int stat = f_mount(&FatFs, "", 0); #if(Debug_SDInit == 1) if(stat) printf("failed to mount the SD Card \r\n"); else printf("successfully mounted the SD Card\r\n"); #endif stat = disk_initialize(0); #if(Debug_SDInit == 1) printf("initialization status: %d\r\n", stat); #endif } /*********************** Card capacity details ***********************/ void Print_SD_Card_space(void) { /* Check storage size and free space */ float total, free_space; DWORD fre_clust; f_getfree("0:", &fre_clust, &pfs); total = (float)((pfs->n_fatent - 2) * pfs->csize * 0.5 / (1024)); printf("SD CARD Total Size: \t%.2f Mega Bytes\r\n", total); free_space = (float)(fre_clust * pfs->csize * 0.5 / (1024)); printf("SD CARD Free SPACE: \t%.2f Mega Bytes\r\n", free_space); } |
حالا باید به سراغ فایل WAV_Handler.c برویم و توابع اعلانشده برای خواندن و پخش فایل wav را در اینجا تعریف کنیم. قبل از هر چیز WAV_Handler.h را به این فایل اضافه میکنیم و متغیرهای گلوبال موردنیاز را تعریف میکنیم؛
1 2 3 4 5 6 7 8 9 10 | #include "WAV_Handler.h" /********************/ /********///variables: uint8_t headerBuffer[60]; // to read the header uint8_t wavBuffer[WavebufferLength]; // DMA buffer volatile uint8_t BufferAction = 0; // indicator to what buffer action is needed volatile bool EndofFile = false; // indicator of the end of the file |
اکنون به سراغ توابع میرویم. تابع اول، برای فعال سازی SD Card و واحد DAC است؛
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /*********************** Initialization ***********************/ void wave_Init() { #if(Debug == 1) printf("Initializing wave player\n\r"); #endif SD_Card_Init(); LL_DAC_Enable(DAC, waveDAC_Channel1); //Enable DAC channel 1 LL_DAC_Enable(DAC, waveDAC_Channel2); //Enable DAC channel 2 #if(Debug == 1) Print_SD_Card_space(); #endif } |
تابع بعدی را به منظور باز کردن فایل و برگرداندن خطا در صورت بروز مشکل تعریف میکنیم؛
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //opening the file bool wave_open(FIL *filptr, char *fileName) { FRESULT fr; /* FatFs return code */ fr = f_open(filptr, fileName, FA_READ); if(fr) { #if(Debug == 1) printf("Can not open the file\r\n -> %d \r\n", fr); #endif return false; } else { #if(Debug == 1) printf("Successfully opened the file\r\n"); #endif return 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 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 | //reading and parsing the Header of the WAV file bool wave_readHeader(FIL *filptr, wave_header_t *wav1HeaderPtr) { FRESULT fr; /* FatFs return code */ UINT br; //file read fr = f_read(filptr, headerBuffer, sizeof(headerBuffer), &br); if(fr) { #if(Debug == 1) printf("Can not read header of the file\r\n -> %d \r\n", fr); #endif return false; } else { #if(Debug == 1) printf("reading the file..\r\n"); #endif //setting the wav1's Header variables: //The "RIFF" chunk and The "fmt" sub-chunk descriptors memcpy(&wav1HeaderPtr->signatureHeader, headerBuffer, sizeof(wave_header_t1)); //The "data" sub-chunk memcpy(&wav1HeaderPtr->dataHeader, &headerBuffer[wav1HeaderPtr->signatureHeader.Subchunk1Size + 16 + 4], sizeof(wave_header_t2)); if( (strncmp(wav1HeaderPtr->signatureHeader.ChunkID, "RIFF", 4) != 0) || (strncmp(wav1HeaderPtr->signatureHeader.Format , "WAVE", 4) != 0) ){ return false; } else { // set read/write pointer after the header information to read data uint16_t headerEnd = 16 + wav1HeaderPtr->signatureHeader.Subchunk1Size + 12; // pointer to the end of header of wav file fr = f_lseek(filptr, headerEnd); fr = f_read(filptr, wavBuffer, WavebufferLength, &br); /**** print properties of the wav file, extracted from the header ****/ #if(Debug == 1) // printf("file ID: %s\r\n", wav1ptr->ChunkID); // printf("file format: %s\r\n", wav1ptr->Format); printf("number of bytes in the file(header excluded): %x\r\n", wav1HeaderPtr->dataHeader.Subchunk2Size); printf("number of channels: %d\r\n", wav1HeaderPtr->signatureHeader.NumChannels); printf("sample rate: %d\r\n", wav1HeaderPtr->signatureHeader.SampleRate); printf("byte rate: %d\r\n", wav1HeaderPtr->signatureHeader.ByteRate); printf("bits per sample: %d\r\n", wav1HeaderPtr->signatureHeader.BitsPerSample); printf("data ID: %c%c%c%c\r\n", wav1HeaderPtr->dataHeader.Subchunk2ID[0], wav1HeaderPtr->dataHeader.Subchunk2ID[1], wav1HeaderPtr->dataHeader.Subchunk2ID[2], wav1HeaderPtr->dataHeader.Subchunk2ID[3]); #endif return true; } } } |
همانطور که میبینید، بخش دوم Structure، یعنی بخش Data، بعد از خواندن بخش اول و با توجه به مقدار Subchunk1size پرشده است. زیرا همانطور که پیشتر اشاره شد ممکن است میان این دو بخش فاصله وجود داشته باشد. اکنون با توجه به مقدار Subchunk2Size میدانیم که چند بایت را باید برای پخش کامل فایل صوتی بخوانیم. همچنین با توجه به تعداد کانالها Mono یا Stereo بودن تنظیم میشود و سرعت پخش را از SampleRate میفهمیم.
بنابراین اکنون باید به سراغ تنظیم واحدهای DMA و Timer با توجه به مقدارهای خواندهشده برویم؛
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 | void wave_DMAConf(wave_header_t *wav1HeaderPtr, uint8_t *wavBuffer) { /******************* 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; uint32_t DMA_DataLength = WavebufferLength; uint32_t waveBitPerSampleM, waveBitPerSampleP; // bits per sample for setting Memory and Peripheral if((wav1HeaderPtr->signatureHeader.BitsPerSample == 8) && (wav1HeaderPtr->signatureHeader.NumChannels == 1)) { DACRegADS = &DAC1->DHR8R1; waveBitPerSampleM = LL_DMA_MDATAALIGN_BYTE; waveBitPerSampleP = LL_DMA_PDATAALIGN_BYTE; DMA_DataLength = WavebufferLength; } else if((wav1HeaderPtr->signatureHeader.BitsPerSample == 16) && (wav1HeaderPtr->signatureHeader.NumChannels == 1)) { DACRegADS = &DAC1->DHR12L1; waveBitPerSampleM = LL_DMA_MDATAALIGN_HALFWORD; waveBitPerSampleP = LL_DMA_PDATAALIGN_HALFWORD; DMA_DataLength = WavebufferLength / 2; } 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; } 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(waveDMA, waveDMA_Channel, waveBitPerSampleP); LL_DMA_SetMemorySize(waveDMA, waveDMA_Channel, waveBitPerSampleM); /********************** configuring DMA **********************/ LL_DMA_ConfigAddresses(waveDMA, waveDMA_Channel, (uint32_t)wavBuffer, (uint32_t) DACRegADS, LL_DMA_DIRECTION_MEMORY_TO_PERIPH); LL_DMA_SetDataLength(waveDMA, waveDMA_Channel, DMA_DataLength); LL_DMA_EnableIT_TC(waveDMA, waveDMA_Channel); LL_DMA_EnableIT_HT(waveDMA, waveDMA_Channel); LL_DMA_EnableIT_TE(waveDMA, waveDMA_Channel); LL_DMA_EnableChannel(waveDMA, waveDMA_Channel); } |
1 2 3 4 5 6 7 8 9 10 | void wave_TimerConf(uint32_t SampleRate) //set Timer update frequency according to SampleRate of the file { LL_TIM_InitTypeDef TIM_InitStruct = {0}; TIM_InitStruct.Prescaler = (SystemCoreClock/1000000) - 1; TIM_InitStruct.Autoreload = (SystemCoreClock / ((TIM_InitStruct.Prescaler + 1) * SampleRate) ) - 1; LL_TIM_Init(waveTimer, &TIM_InitStruct); LL_TIM_EnableUpdateEvent(waveTimer); } |
در ادامه تابع جداگانهای برای فعالسازی درخواست DAC از DMA و صدا زدن دو تابع قبلی تعریف میکنیم؛
1 2 3 4 5 6 | void wave_DACConf(wave_header_t *wav1HeaderPtr, uint8_t *wavBuffer) { wave_DMAConf(wav1HeaderPtr, wavBuffer); wave_TimerConf(wav1HeaderPtr->signatureHeader.SampleRate); LL_DAC_EnableDMAReq(DAC, LL_DAC_CHANNEL_1); } |
یک تابع نیز برای شروع به پخش، یعنی فعال کردن شمارنده تایمر مینویسیم؛
1 2 3 4 5 6 7 | void wave_start(void) { #if(Debug == 1) printf("playing the file... \r\n"); #endif LL_TIM_EnableCounter(waveTimer); } |
حالا باید تابعی تعریف کنیم که از تابعهای تنظیم و شروع به خواندن که نوشتیم، استفاده کند، اسم فایل مورد نظر را دریافت کند و آن را پخش کند؛
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 | void wave_play(char *fileName) { FIL fil; /* File object */ /* wave handling variables */ wave_header_t wavHeader1; //struct a new header bool isWAV = false; if(wave_open(&fil, fileName)){ isWAV = wave_readHeader(&fil, &wavHeader1); if(isWAV){ wave_DACConf(&wavHeader1, wavBuffer); wave_start(); while(1) { wave_IsEndofFile(&fil, wavBuffer, wavHeader1.signatureHeader.BitsPerSample); } } else{ #if(Debug == 1) printf("file is not in WAV format or can not read header of the file!\r\n"); #endif } } } |
تابعی که در حلقه while(1) بهکاررفته است را هنوز تعریف نکردهایم. کاربرد این تابع، بهروزرسانی بافر DMA از اطلاعات فایل صوتی موجود در SD Card و همچنین فعال کردن پرچم انتهای فایل است. این تابع را بهصورت زیر مینویسیم؛
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 | void wave_IsEndofFile(FIL *filptr, uint8_t *wavBuffer, uint16_t BitsPerSample) { UINT br; //file read // if(BufferAction == 0) if(BufferAction==3) //if both of DMA HT and TC buffer action flags have risen before one of them is cleared { #if(Debug == 1) printf("Low reading speed!\r\n"); #endif } if(BufferAction&0x1) //in case of DMA HT buffer action flag have risen { br = 0; f_read(filptr, wavBuffer, WavebufferLength / 2, &br); //first half of the wavbuffer must be updated if(BitsPerSample == 16) //if bits per sample is 16 (else it's 8) { for(uint16_t i = 1; i<WavebufferLength/2; i += 2) { wavBuffer[i] ^= 0x80; //required for 16 bits per sample mode } } BufferAction &=~0x1; //Clear the buffer action flag if(br<WavebufferLength/2) { EndofFile = true; } } if(BufferAction&0x2) //in case of DMA TC buffer action flag have risen { br = 0; f_read(filptr, wavBuffer + (WavebufferLength / 2), WavebufferLength / 2, &br); //second half of the wavbuffer must be updated if(BitsPerSample == 16) //if bits per sample is 16 (else it's 8) { for(uint16_t i = WavebufferLength/2 + 1; i<WavebufferLength; i += 2) { wavBuffer[i] ^= 0x80; //required for 16 bits per sample mode } } BufferAction &=~0x2; //Clear the buffer action flag if(br<WavebufferLength/2) //to indicate end of the file { EndofFile = true; } } } |
تقریبا به انتهای پروژه رسیدهایم. فقط باید تابعهای موردنیاز برای تغییر متغیر BufferAction که عملیات موردنیاز برای تغییر بافر را مشخص میکند بنویسیم و در وقفههای مناسب آنها را صدا بزنیم. ابتدا تابعها را مینویسیم؛
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void wave_EndofFile_Callback(void) { if(EndofFile) { LL_DMA_DisableChannel(waveDMA, waveDMA_Channel); BufferAction = 0; } } void wave_DMA_TC_Callback(void) { BufferAction |= 0x2; } void wave_DMA_HT_Callback(void) { BufferAction |= 0x1; } |
حالا باید از فایل stm32f1xx_it.c تابع مربوط به روال وقفه DMA را به فایل WAV_Handler.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 | /** * @brief This function handles DMA2 channel3 global interrupt. */ void DMA2_Channel3_IRQHandler(void) { /* USER CODE BEGIN DMA2_Channel3_IRQn 0 */ if(LL_DMA_IsActiveFlag_TC3(DMA2) == 1) { /* Clear flag DMA transfer complete */ LL_DMA_ClearFlag_TC3(DMA2); wave_DMA_TC_Callback(); } ////// if transfer fails : else if(LL_DMA_IsActiveFlag_TE3(DMA2) == 1) { LL_DMA_ClearFlag_TE3(DMA2); LL_DMA_DisableChannel(DMA2,LL_DMA_CHANNEL_3); } //////Half transfer complete if(LL_DMA_IsActiveFlag_HT3(DMA2) == 1) { /* Clear flag DMA half transfer */ LL_DMA_ClearFlag_HT3(DMA2); wave_DMA_HT_Callback(); } wave_EndofFile_Callback(); /* USER CODE END DMA2_Channel3_IRQn 0 */ /* USER CODE BEGIN DMA2_Channel3_IRQn 1 */ /* USER CODE END DMA2_Channel3_IRQn 1 */ } |
اکنون فایلهای کتابخانه ما تکمیلشدهاند. تنها کاری که برای استفاده از wave player نیاز است، قرار دادن یک فایل با فرمت wav در کارت حافظه، اتصال آن به میکرو و صدازدن توابع wave_init و wave_play، در فایل main.c است. این توابع را در int main فراخوانی میکنیم؛
1 2 | wave_Init(); wave_play("Colors.wav"); |
فراموش نشود که باید WAV_Handler.h را نیز به این فایل اضافه کنیم؛
1 | #include "WAV_Handler.h" |
در صورتی که همه کارها به درستی انجام شده باشند، میتوانیم فایل صوتی را به وسیله یک بلندگو با مقاومت حدود 25ohm پخش کنیم. یا اینکه شکل موج فایل در حال پخش را روی یک اسیلوسکوپ مشاهده کنیم. اگر مقدار Debug در فایل هدر را 1 قرار داده باشیم، مشخصات فایلی که برای پخش قرار دادیم، در ترمینال سریال چاپ میشود:
لینک فایل مربوط به این پروژه در گیتهاب
سلام
از آموزش خوبتون ممنونم
امکانش هست آموزش و راهنمای نحوه پخش فایل صوتی با فرمت WAV توسط DMA رو هم بزارین
چون اکثر میکرو های سطح پایین و حتی متوسط بازار هم DAC ندارن…
ممنونم