Cílem této práce je vytvořit hudební systém MPC, který bude schopný nahrávat zvuky a následně je reprodukovat. K realizaci tohoto systému byla využita vývojová deska STM32F4DISCOVERY, která obsahuje integrovaný mikrofon MP45DT02 MEMS.Rovněž potřebujeme sluchátka, USB flash disk a tlačítka popřípadě maticovou klávesnici.
Aplikace přechází mezi stavem nahrávání zvuků nebo přehrávání uložených zvuků podle toho, zda bylo zmáčknuto USER button na vývojové desce. Dojde-li ke k prvnímu stisku tlačítka, tak dochází ke zvolení režimu nahrávání („Audio recording“). Uživatel po stisknu tlačítka stiskne libovolnou klávesu na klávesnici a tím přiřadí nahrávaný zvuk k dané klávese.Po opětovném stisknutí tlačítka se audiostopa uloží na USB flash disk pod názvem podle toho jaká klávesa byla stisknuta a zařízení přechází do stavu přehrávání (Audio playback). Ve stavu přehrávání dochází k reprodukci uložených zvuků podle toho jakou klávesu stiskneme. Níže lze vidět vývojový diagram této aplikace. Mikrofon MP45DT02 je připojen přes sběrnici I2S pomocí jednoho datového a jednoho hodinového pinu. Hodiny I2S jsou nastaveny na 1024MHz podle výstupní vzokrovací frekvence audio souboru (16KHz) a zvoleného decimačního faktoru (64). I2S je nastavena tak, aby vyvolala přerušení pokaždé, když je přijato 16bit vzorků. Vzorky z mikrofonu jsou ve formátu PDM (Pulse density modulation) a jsou pomocí externí knihovny filtru PDMlib převáděny na 16-bit PCM a výsledná audio stopa „.wav“ je uložena na USB. Pro přehrávání je rovněž využito I2S sběrnice a DMA, kdy jsou jednotlivé bloky dat (1024bytes) přenášeny z USB do interní SRAM paměti. Při přehrávání jsou použity dva buffery. Jeden slouží k ukládání dat z USB a druhý k přehrávání vzorků pomocí externího kodeku DAC. Níže je přiložen vývojový diagram pro proces nahrávání.
Základem pro mojí aplikaci je Audio playback and recording example pro STM32F4DISCOVERY (AN3997-Aplication note) a program KEIL. Audio playback and recording example projekt obsahuje následující knihovny:
Naše aplikace je založena na výše zmíněných knihovnách a je tedy potřeba tyto knihovny upravit podle námi požadované funkce.Nejdříve jsem v souboru souboru main.h definoval nové názvy souborů, se kterými program pracuje při přehrávání a nahrávaní zvuků, tak aby ve výsledku bylo možno nahrát tři různé zvuky a následně je po stisku určité klávesy přehrát. Celá aplikace je tvořena pro možnost uložení tří zvuků. Při potřeba uložit více zvuků je postup obdobný.
/* Select the media where the Wave file is stored */ #if !defined (MEDIA_IntFLASH) && !defined (MEDIA_USB_KEY) //#define MEDIA_IntFLASH /* Wave file stored in internal flash */ //#define MEDIA_USB_KEY /* Wave file stored in USB flash */ #endif /* Uncomment this define to disable repeat option */ //#define PLAY_REPEAT_OFF #if defined MEDIA_USB_KEY /* You can change the Wave file name as you need, but do not exceed 11 characters */ #define WAVE_NAME "0:audio.wav" #define REC_WAVE_NAME "0:rec.wav" #define WAVE_NAME_1 "0:audi1.wav" #define REC_WAVE_NAME_1 "0:rec1.wav" #define WAVE_NAME_2 "0:audi2.wav" #define REC_WAVE_NAME_2 "0:rec2.wav"
V tomto souboru je nastavení časovače pro LED diody a obsluha hlavního kódu který je větven podle toho, zda pro vzorky využíváme interní nebo externí úložiště. V nekonečné smyčce je poté spouštěna funkce USBH_Process. Kód byl doplněn o volání funkce GPIO_Init(), která obsahuje nastavení pinů pro klávesnici (definováno v stm32f4_discovery.c).Knihovna obsahuje nastavení pinu PB12 jako výstupní a nastavení jeho hodnoty na logickou nulu. Dále jsem nastavil piny PB13, PB14 a PB15 jako vstupní s pull-up tranzistorem. Při stisku tlačítka se tedy na těchto pinech objeví logická nula.
/* Includes ------------------------------------------------------------------*/ #include "main.h" /** @addtogroup STM32F4-Discovery_Audio_Player_Recorder * @{ */ /* Private typedef -----------------------------------------------------------*/ /* Private define ------------------------------------------------------------*/ /* Private variables ---------------------------------------------------------*/ #if defined MEDIA_USB_KEY USB_OTG_CORE_HANDLE USB_OTG_Core; USBH_HOST USB_Host; #endif RCC_ClocksTypeDef RCC_Clocks; __IO uint8_t RepeatState = 0; __IO uint16_t CCR_Val = 16826; extern __IO uint8_t LED_Toggle; /* Private function prototypes -----------------------------------------------*/ static void TIM_LED_Config(void); /* Private functions ---------------------------------------------------------*/ /** * @brief Main program. * @param None * @retval None */ int main(void) { /* Initialize LEDS */ STM_EVAL_LEDInit(LED3); STM_EVAL_LEDInit(LED4); STM_EVAL_LEDInit(LED5); STM_EVAL_LEDInit(LED6); /* Green Led On: start of application */ STM_EVAL_LEDOn(LED4); /* SysTick end of count event each 10ms */ RCC_GetClocksFreq(&RCC_Clocks); SysTick_Config(RCC_Clocks.HCLK_Frequency / 100); /* Configure TIM4 Peripheral to manage LEDs lighting */ TIM_LED_Config(); /* Initialize the repeat status */ RepeatState = 0; LED_Toggle = 7; #if defined MEDIA_IntFLASH WavePlayBack(I2S_AudioFreq_48k); while (1); #elif defined MEDIA_USB_KEY /* Initialize User Button */ STM_EVAL_PBInit(BUTTON_USER, BUTTON_MODE_EXTI); GPIO_Init(); /* Init Host Library */ USBH_Init(&USB_OTG_Core, USB_OTG_FS_CORE_ID, &USB_Host, &USBH_MSC_cb, &USR_Callbacks); while (1) { /* Host Task handler */ USBH_Process(&USB_OTG_Core, &USB_Host); } #endif } /** * @brief Configures the TIM Peripheral for Led toggling. * @param None * @retval None */ static void TIM_LED_Config(void) { TIM_OCInitTypeDef TIM_OCInitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; uint16_t prescalervalue = 0; /* TIM4 clock enable */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); /* Enable the TIM3 gloabal Interrupt */ NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); /* Initialize Leds mounted on STM324F4-EVAL board */ STM_EVAL_LEDInit(LED3); STM_EVAL_LEDInit(LED4); STM_EVAL_LEDInit(LED6); /* Compute the prescaler value */ prescalervalue = (uint16_t) ((SystemCoreClock ) / 550000) - 1; /* Time base configuration */ TIM_TimeBaseStructure.TIM_Period = 65535; TIM_TimeBaseStructure.TIM_Prescaler = prescalervalue; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); /* Enable TIM4 Preload register on ARR */ TIM_ARRPreloadConfig(TIM4, ENABLE); /* TIM PWM1 Mode configuration: Channel */ TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = CCR_Val; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; /* Output Compare PWM1 Mode configuration: Channel2 */ TIM_OC1Init(TIM4, &TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Disable); /* TIM Interrupts enable */ TIM_ITConfig(TIM4, TIM_IT_CC1 , ENABLE); /* TIM4 enable counter */ TIM_Cmd(TIM4, ENABLE); } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t* file, uint32_t line) { /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* Infinite loop */ while (1) { } } #endif
V knihovně usbh_usr.c je funkce void COMMAND_AudioExecuteApplication(void), která se stará o přepínaní mezi stavy nahrávání a přehrávání podle hodnoty proměnné command_index, která se mění podle toho zda bylo nebo nebylo stisknuto USER button. V rámci této funkce se volají funkce WavePlayerStart a WaveRecorderUpdate. Tyto funkce byly přepsány tak, aby podle parametru klávesa (stisk klávesy 1-3) přehrávaly nebo nahrávaly určitý zvuk. Proměnná klávesa je definovaná v stm32f4xx_it.c jako volatile uint8_t klavesa a je přidána do stm32f4xx_it.h jako externí proměnná, tak aby mohla být použita i v rámci jiných knihoven.
void COMMAND_AudioExecuteApplication(void) { /* Execute the command switch the command index */ switch (Command_index) { /* Start Playing from USB Flash memory */ case CMD_PLAY: if (RepeatState == 0) WavePlayerStart(klavesa); break; /* Start Recording in USB Flash memory */ case CMD_RECORD: RepeatState = 0; WaveRecorderUpdate(klavesa); break; default: break; } }
Knihovna stm32f4xx.c obsahuje obsluhy přerušení pro tlačítko (User button) a pro časovač zajištující obsluhu LED diod. Obsluha přerušení pro časovač TIM4 byla upravena, aby při každé obsluze se kontroloval stav tlačítek 1-3. Pokud je některá klávesa stisknutá, tak dochází k přiřazení příslušné hodnoty do proměnné klávesa.
void TIM4_IRQHandler(void) { uint8_t clickreg = 0; /*************/ if (!GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13)) { klavesa=0; GPIO_SetBits(GPIOB, GPIO_Pin_13); } if (!GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14)) { klavesa=1; STM_EVAL_LEDOn(LED6); GPIO_SetBits(GPIOB, GPIO_Pin_14); } if (!GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_15)) { klavesa=2; STM_EVAL_LEDOn(LED6); GPIO_SetBits(GPIOB, GPIO_Pin_15); } /*************/ if (AudioPlayStart != 0x00) { /* Read click status register */ LIS302DL_Read(&clickreg, LIS302DL_CLICK_SRC_REG_ADDR, 1); LIS302DL_Read(Buffer, LIS302DL_STATUS_REG_ADDR, 6); } /* Checks whether the TIM interrupt has occurred */ if (TIM_GetITStatus(TIM4, TIM_IT_CC1) != RESET) { TIM_ClearITPendingBit(TIM4, TIM_IT_CC1); if( LED_Toggle == 3) { /* LED3 Orange toggling */ STM_EVAL_LEDToggle(LED3); STM_EVAL_LEDOff(LED6); STM_EVAL_LEDOff(LED4); } else if( LED_Toggle == 4) { /* LED4 Green toggling */ STM_EVAL_LEDToggle(LED4); STM_EVAL_LEDOff(LED6); STM_EVAL_LEDOff(LED3); } else if( LED_Toggle == 6) { /* LED6 Blue toggling */ STM_EVAL_LEDOff(LED3); STM_EVAL_LEDOff(LED4); STM_EVAL_LEDToggle(LED6); } else if (LED_Toggle ==0) { /* LED6 Blue On to signal Pause */ STM_EVAL_LEDOn(LED6); } else if (LED_Toggle == 7) { /* LED4 toggling with frequency = 439.4 Hz */ STM_EVAL_LEDOff(LED3); STM_EVAL_LEDOff(LED4); STM_EVAL_LEDOff(LED5); STM_EVAL_LEDOff(LED6); } capture = TIM_GetCapture1(TIM4); TIM_SetCompare1(TIM4, capture + CCR_Val); } }
Knihovna waverecorder.c obsahuje rutinu nahrávaní, filtrování vzorků pomocí externí knihovny pdm_filter.h a ukládání do souboru. Funkce WaveRecorderUpdate(uint8_t filename) byla rozšířena o podmínku pro hodnotu uloženou v proměné klávesa. Proměná klávesa může mít hodnotu 1-3 podle stisklé klávesy. Pro různou hodnotu proměná klávesa ukládáme do proměné recFile dříve definovaný název výstupního audio souboru. RecFile je definováno jako ukazatel static char *recFile. Pro různé tlačítko tak vytvoříme na USB jiný zvukový soubor.
void WaveRecorderUpdate(uint8_t filename) { WaveRecorderInit(32000,16, 1); WaveCounter = 0; LED_Toggle = 7; //while( !filename ||filename || (filename == 2)); if (!filename) recFile = REC_WAVE_NAME; if (filename) recFile = REC_WAVE_NAME_1; if (filename == 2) recFile = REC_WAVE_NAME_2; /* Remove Wave file if exist on flash disk */ f_unlink (recFile); /* Open the file to write on it */ if ((HCD_IsDeviceConnected(&USB_OTG_Core) != 1) || (f_open(&file, recFile, FA_CREATE_ALWAYS | FA_WRITE) != FR_OK)) { /* Set ON Red LED */ while(1) { STM_EVAL_LEDToggle(LED5); } } else { WaveRecStatus = 1; } /* Initialize the Header Wave */ WavaRecorderHeaderInit(RecBufHeader); /* Write the Header wave */ f_write (&file, RecBufHeader, 512, (void *)&bytesWritten); /* Increment tne wave counter */ WaveCounter += 512; /* Start the record */ WaveRecorderStart(RecBuf, PCM_OUT_SIZE); /* Reset the time base variable */ Time_Rec_Base = 0; Switch = 0; while(HCD_IsDeviceConnected(&USB_OTG_Core)) { /* Wait for the recording time */ if (Time_Rec_Base <= TIME_REC) { /* Wait for the data to be ready with PCM form */ while((Data_Status == 0)&& HCD_IsDeviceConnected(&USB_OTG_Core)); Data_Status =0; /* Switch the buffers*/ if (Switch ==1) { pAudioRecBuf = RecBuf; writebuffer = RecBuf1; WaveCounter += 32; Switch = 0; } else { pAudioRecBuf = RecBuf1; writebuffer = RecBuf; WaveCounter += 32; Switch = 1; } for (counter=0; counter<16; counter++) { LED_Toggle = 3; if (buf_idx< RAM_BUFFER_SIZE) { /* Store Data in RAM buffer */ RAM_Buf[buf_idx++]= *(writebuffer + counter); if (buf_idx1 == RAM_BUFFER_SIZE) { buf_idx1 = 0; /* Write the stored data in the RAm to the USB Key */ f_write (&file, (uint16_t*)RAM_Buf1, RAM_BUFFER_SIZE*2 , (void *)&bytesWritten); } } else if (buf_idx1< RAM_BUFFER_SIZE) { /* Store Data in RAM buffer */ RAM_Buf1[buf_idx1++]= *(writebuffer + counter); if (buf_idx == RAM_BUFFER_SIZE) { buf_idx = 0; /* Write the stored data in the RAM to the USB Key */ f_write (&file, (uint16_t*)RAM_Buf, RAM_BUFFER_SIZE*2 , (void *)&bytesWritten); } } } /* User button pressed */ if ( Command_index != 1) { /* Stop recording */ WaveRecorderStop(); Command_index = 0; LED_Toggle = 6; break; } } else /* End of Recording time */ { WaveRecorderStop(); LED_Toggle = 4; Command_index = 2; Data_Status =0; break; } } /* Update the data length in the header of the recorded wave */ f_lseek(&file, 0); RecBufHeader[4] = (uint8_t)(WaveCounter + 512) ; RecBufHeader[5] = (uint8_t)(((WaveCounter+512) >> 8) & 0xFF); RecBufHeader[6] = (uint8_t)(((WaveCounter+512) >> 16) & 0xFF); RecBufHeader[7] = (uint8_t)(((WaveCounter+512) >> 24) & 0xFF); RecBufHeader[40] = (uint8_t)(WaveCounter); RecBufHeader[41] = (uint8_t)((WaveCounter >> 8) & 0xFF); RecBufHeader[42] = (uint8_t)((WaveCounter >> 16) & 0xFF); RecBufHeader[43] = (uint8_t)((WaveCounter >> 24) & 0xFF); /* Write the updated header wave */ f_write (&file, RecBufHeader, 512, (void *)&bytesWritten); /* Close file and filesystem */ f_close (&file); f_mount(0, NULL); }
Knihovna waveplayer.c obsahuje rutinu pro přehrávání audio souboru. Obsahuje funkce WavePlayerStart(uint8_t filename), která byla upravena tak, aby mohly být přehráté různé zvuky podle stisknutí klávesy. Dochází ke kontrole předané hodnoty proměnné klávesa a podle její hodnoty se do proměnné WaveFileName přiřadí jiný název audiostopy.
void WavePlayerStart(uint8_t filename) { char path[] = "0:/"; buffer_switch = 1; /* Get the read out protection status */ if (f_opendir(&dir, path)!= FR_OK) { while(1) { STM_EVAL_LEDToggle(LED5); Delay(10); } } else { if (WaveRecStatus == 1) { if (!filename) WaveFileName = REC_WAVE_NAME; if (filename) WaveFileName = REC_WAVE_NAME_1; if (filename == 2) WaveFileName = REC_WAVE_NAME_2; // WaveFileName = REC_WAVE_NAME; } else { if (!filename) WaveFileName = WAVE_NAME; if (filename) WaveFileName = WAVE_NAME_1; if (filename == 2) WaveFileName = WAVE_NAME_2; // WaveFileName = WAVE_NAME; } /* Open the wave file to be played */ if (f_open(&fileR, WaveFileName , FA_READ) != FR_OK) { STM_EVAL_LEDOn(LED5); Command_index = 1; } else { /* Read data(_MAX_SS byte) from the selected file */ f_read (&fileR, buffer1, _MAX_SS, &BytesRead); WaveFileStatus = WavePlayer_WaveParsing(&wavelen); if (WaveFileStatus == Valid_WAVE_File) /* the .WAV file is valid */ { /* Set WaveDataLenght to the Speech wave length */ WaveDataLength = WAVE_Format.DataSize; } else /* Unvalid wave file */ { /* Led Red Toggles in infinite loop */ while(1) { STM_EVAL_LEDToggle(LED5); Delay(10); } } /* Play the wave */ WavePlayBack(WAVE_Format.SampleRate); } } }
Na prvním videu můžeme vidět proces nahrávání tří různých zvuku pro tři pozice na klávesnici. USB flash je poté připojena na PC a zvuky jsou přehrány. Na druhém videu je proces přehrávání. Bohužel z neznámého důvodu chybí na videu zvuk, tudíž je video nevypovídající. Jelikož jsem při tvorbě dokumentace neměl vývojový kit u sebe, tak jsem bohužel nebyl schopen natočit video nové. Můžeme ovšem vypozorovat indikaci přehrávání pomocí modré LED. Po zmáčknutí klávesy je přehrána odpovídající nahrávka, avšak až po dokončení přehrávání nahrávky předešlé.
Cílem této práce bylo vytvořit hudební systém MPC, který bude schopný nahrávat zvuky a následně je reprodukovat. Bylo dosaženo nahrávání zvuků do různých souboru na USB, podle toho jaká klávesa byla stisknuta. Zvuky jsou pak při přepnutí do režimu přehrávání reprodukovány, podle toho jaká klávesa je stisknuta. Bohužel se při práci na projektu nepodařilo dosáhnout efektu mixování zvuků tak, aby dva zvuky mohly být reprodukovány současně. Aplikace nyní funguje tak, že jsou zvuky přehrávány postupně a jednotlivý audiosoubor je vždy dohrán až do konce. Možným pokračováním na projektu by bylo ošetření zákmitů tlačítek, využití celé maticové klávesnice a celková změna v řešení projektu, kdy by bylo využito možnosti přerušení při stisku určité klávesy a část uživatelského kódu by byla napsána do obsluhy tohoto přerušení, což by nejspíše vyžadovalo větší změny v použitých knihovnách audio playback example.