آموزش STM32 با توابع LL قسمت سی‌ و دوم: راه‌اندازی یک wave player

0
189
آموزش STM32 با توابع LL قسمت سی‌ و دوم: راه‌اندازی یک wave player
آموزش STM32 با توابع LL قسمت سی‌ و دوم: راه‌اندازی یک wave player

همان‌طور که می‌دانید فرمت wave (فایل‌های با پسوند.wav) یکی از فرمت‌های رایج برای فایل‌های صوتی است. در این بخش از آموزش STM32 قصد داریم یک wave player با استفاده از قابلیت‌های این میکرو بسازیم.

طرح کلی wave player
طرح کلی wave player به وسیله میکروکنترلر STM32.

قبل از این‌که وارد روند توسعه پروژه wave player شویم، فرمت wav را با جزییات بیشتری بررسی می‌کنیم. این فرمت از ساختار فرمت فایل RIFF (مخفف Resource Interchange File Format) استفاده می‌کند. همچنین معمولاً از این فرمت برای ذخیره اطلاعات صوتی بدون فشرده‌سازی و اتلاف استفاده می‌شود. هرچند امکان ذخیره اطلاعات به‌صورت فشرده نیز در فرمت wav وجود دارد.

 

فرمت  wav

هر فایل wav عموماً از سه بخش تشکیل می‌شود. قسمت RIFF، قسمت Format و قسمت Data در حالتی که فایل wav به‌صورت فشرده ساخته‌شده باشد، شامل بلاک‌های fact نیز می‌شود. ساختار معمول فرمت wav در شکل زیر قابل‌مشاهده است:

فرمت wav
بخش‌های مختلف یک فایل wav در استاندارد Canonical

هرکدام از بخش‌های هدر یک فایل 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

بنابراین برای خواندن صحیح یک فایل wav و طراحی wave player، باید بتوانیم این اطلاعات را به‌درستی از هدر فایل استخراج‌کنیم و براساس آن، پارامترهایی مثل اندازه فایل، سرعت پخش، اندازه هر نمونه را تنظیم کنیم. یک مثال از مشخصات فایل wav مربوط به یک موج سینوسی (تک فرکانس) و نمایش هگز اطلاعات آن را در تصاویر زیر می‌بینیم:

 مشخصات مربوط به فایل wav یک موج سینوسی.
مشخصات مربوط به فایل wav یک موج سینوسی.
 نمایش هگز اطلاعات ذخیره شده در فایل wav موج سینوسی.
نمایش هگز اطلاعات ذخیره شده در فایل wav موج سینوسی.
 شرح اطلاعات مروبط به هر بخش فایل موج سینوسی.
شرح اطلاعات مروبط به هر بخش فایل موج سینوسی.

همان‌طور که در تصویر بالا مشخص است، چهار بایت اول کد اسکی “RIFF” را مشخص می‌کند و به همین ترتیب اطلاعات مربوط به هدر قرارگرفته‌اند، تا جایی که به بلوک داده می‌رسیم. در این بلوک پس از قسمت‌های ID (که کد اسکی data است) و Subchunk2Size، داده‌های مربوط به فایل صوتی قرارگرفته‌اند. نمونه‌های این فایل صوتی 16 بیتی و دو کاناله (Stereo) هستند. یعنی اینکه هر نمونه 2 بایت فضا اشغال کرده است و داده‌های کانال چپ و کانال راست به‌صورت یکی در میان قرارگرفته‌اند.

حال که با ساختار فایل wav آشنا شده‌ایم، به سراغ طراحی wave player می‌رویم.

 

ایجاد پروژه

در این پروژه از میکروکنترلر STM32F103RET6 استفاده می‌کنیم. برای ایجاد پروژه wave player، مانند بخش قبلی، یعنی پروژه SD Card، بااینکه نیاز به واحد SPI داریم اما فعلاً آن را فعال نمی‌کنیم و توابع مربوط به این واحد و فایل سیستم را بعد به پروژه اضافه می‌کنیم. پس از فعال و تنظیم کردن بخش‌های دیباگ، کلاک و USART1، واحد DAC را نیز برای تولید خروجی فعال می‌کنیم و کانال DMA مربوط به خروجی 1 آن را در حالت Circular تنظیم می‌کنیم؛

پروژه- تنظیم واحد DAC.
تنظیم واحد DAC.
 تنظیم کانال DMA مربوط به خروجی1 DAC.
تنظیم کانال DMA مربوط به خروجی1 DAC.

همان‌طور که در تصویر مربوط به تنظیم DAC مشخص است، ترگیر این واحد را به‌وسیله تایمر 2 تنظیم کرده‌ایم. درواقع سرعت پخش نمونه‌ها توسط DAC به‌وسیله تریگر تایمر 2 تنظیم می‌شود. پس فرکانس پخش را باید با فرکانس تایمر 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 کتابخوانه‌ها و ثابت‌های مورد نیاز را تعریف می‌کنیم، همچنین تایپ استراکچر مربوط به اطلاعات هدر را در این فایل تعریف می‌کنیم؛

 #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"
#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"
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 تعریف کنیم را اعلان می‌کنیم.

// 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 را به ترتیب به‌صورت زیر تعریف می‌کنیم:

/**
******************************************************************************
* @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);
/**
******************************************************************************
* @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 را به این فایل اضافه می‌کنیم و متغیرهای گلوبال موردنیاز را تعریف می‌کنیم؛

#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 است؛

 /*********************** 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
}

 

تابع بعدی را به منظور باز کردن فایل و برگرداندن خطا در صورت بروز مشکل تعریف می‌کنیم؛

 //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;
}
}

 

حالا باید مهم‌ترین تابع این پروژه، یعنی خواندن هدر فایل و برداشتن اطلاعات موردنیاز جهت تنظیم بخش‌های مختلف را بنویسیم؛

//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 با توجه به مقدارهای خوانده‌شده برویم؛

 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); 
}
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 و صدا زدن دو تابع قبلی تعریف می‌کنیم؛

 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);
}

یک تابع نیز برای شروع به پخش، یعنی فعال کردن شمارنده تایمر می‌نویسیم؛

 void wave_start(void)
{
#if(Debug == 1)
printf("playing the file... \r\n"); 
#endif
LL_TIM_EnableCounter(waveTimer);
}

حالا باید تابعی تعریف کنیم که از تابع‌های تنظیم و شروع به خواندن که نوشتیم، استفاده کند، اسم فایل مورد نظر را دریافت کند و آن را پخش کند؛

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 و همچنین فعال کردن پرچم انتهای فایل است. این تابع را به‌صورت زیر می‌نویسیم؛

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 که عملیات موردنیاز برای تغییر بافر را مشخص می‌کند بنویسیم و در وقفه‌های مناسب آن‌ها را صدا بزنیم. ابتدا تابع‌ها را می‌نویسیم؛

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 منتقل کنیم و تابع‌های بالا در آن صدا بزنیم؛

/**
* @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 فراخوانی می‌کنیم؛

 wave_Init();
wave_play("Colors.wav");

در صورتی که همه کارها به درستی انجام شده باشند، می‌توانیم فایل صوتی را به وسیله یک بلندگو با مقاومت حدود 25ohm پخش کنیم. یا اینکه شکل موج فایل در حال پخش را روی یک اسیلوسکوپ مشاهده کنیم. اگر مقدار Debug در فایل هدر را 1 قرار داده باشیم، مشخصات فایلی که برای پخش قرار دادیم، در ترمینال سریال چاپ می‌شود:

ترمینال سریال

 

 

    لینک فایل مربوط به این پروژه در گیت‌هاب

 

 

 

منبع:سیسوگ

مطلب قبلیکتابخانه LVGL برای نمایش‌گرهای TFT LCD (قسمت دوم)
مطلب بعدیکدام سری های ESP32 برای پروژه من مناسب است

پاسخ دهید

لطفا نظر خود را وارد کنید!
لطفا نام خود را در اینجا وارد کنید