#include "audio.h" #include "i2c.h" #include "stm32f4xx_conf.h" #include "stm32f4xx.h" #include static void WriteRegister(uint8_t address, uint8_t value); static void StartAudioDMAAndRequestBuffers(); static void StopAudioDMA(); static AudioCallbackFunction *CallbackFunction; static void *CallbackContext; static int16_t * volatile NextBufferSamples; static volatile int NextBufferLength; static volatile int BufferNumber; static volatile bool DMARunning; void InitializeAudio(int plln, int pllr, int i2sdiv, int i2sodd) { GPIO_InitTypeDef GPIO_InitStructure; // Intitialize state. CallbackFunction = NULL; CallbackContext = NULL; NextBufferSamples = NULL; NextBufferLength = 0; BufferNumber = 0; DMARunning = false; // Turn on peripherals. // Assume GPIOA,B,C,D already on RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE); // Assume I2C is set up // Configure reset pin. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOD, &GPIO_InitStructure); // Configure I2S MCK, SCK, SD pins. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_10 | GPIO_Pin_12; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOC, &GPIO_InitStructure); GPIO_PinAFConfig(GPIOC, GPIO_PinSource7, GPIO_AF_SPI3); GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_SPI3); GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_SPI3); // Configure I2S WS pin. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_SPI3); // Reset the codec. GPIOD ->BSRRH = 1 << 4; for (volatile int i = 0; i < 0x4fff; i++) { __asm__ volatile("nop"); } GPIOD ->BSRRL = 1 << 4; // Configure codec. WriteRegister(0x02, 0x01); // Keep codec powered off. WriteRegister(0x04, 0xaf); // SPK always off and HP always on. WriteRegister(0x05, 0x81); // Clock configuration: Auto detection. WriteRegister(0x06, 0x04); // Set slave mode and Philips audio standard. SetAudioVolume(0xff); // Power on the codec. WriteRegister(0x02, 0x9e); // Configure codec for fast shutdown. WriteRegister(0x0a, 0x00); // Disable the analog soft ramp. WriteRegister(0x0e, 0x04); // Disable the digital soft ramp. WriteRegister(0x27, 0x00); // Disable the limiter attack level. WriteRegister(0x1f, 0x0f); // Adjust bass and treble levels. WriteRegister(0x1a, 0x0a); // Adjust PCM volume level. WriteRegister(0x1b, 0x0a); // Disable I2S. SPI3 ->I2SCFGR = 0; // I2S clock configuration RCC ->CFGR &= ~RCC_CFGR_I2SSRC; // PLLI2S clock used as I2S clock source. RCC ->PLLI2SCFGR = (pllr << 28) | (plln << 6); // Enable PLLI2S and wait until it is ready. RCC ->CR |= RCC_CR_PLLI2SON; while (!(RCC ->CR & RCC_CR_PLLI2SRDY )) ; // Configure I2S. SPI3 ->I2SPR = i2sdiv | (i2sodd << 8) | SPI_I2SPR_MCKOE; SPI3 ->I2SCFGR = SPI_I2SCFGR_I2SMOD | SPI_I2SCFGR_I2SCFG_1 | SPI_I2SCFGR_I2SE; // Master transmitter, Phillips mode, 16 bit values, clock polarity low, enable. } void AudioOn() { WriteRegister(0x02, 0x9e); SPI3 ->I2SCFGR = SPI_I2SCFGR_I2SMOD | SPI_I2SCFGR_I2SCFG_1 | SPI_I2SCFGR_I2SE; // Master transmitter, Phillips mode, 16 bit values, clock polarity low, enable. } void AudioOff() { WriteRegister(0x02, 0x01); SPI3 ->I2SCFGR = 0; } void SetAudioVolume(int volume) { WriteRegister(0x20, (volume + 0x19) & 0xff); WriteRegister(0x21, (volume + 0x19) & 0xff); } void OutputAudioSample(int16_t sample) { while (!(SPI3 ->SR & SPI_SR_TXE )) ; SPI3 ->DR = sample; } void OutputAudioSampleWithoutBlocking(int16_t sample) { SPI3 ->DR = sample; } void PlayAudioWithCallback(AudioCallbackFunction *callback, void *context) { StopAudioDMA(); NVIC_EnableIRQ(DMA1_Stream7_IRQn); NVIC_SetPriority(DMA1_Stream7_IRQn, 5); SPI3 ->CR2 |= SPI_CR2_TXDMAEN; // Enable I2S TX DMA request. CallbackFunction = callback; CallbackContext = context; BufferNumber = 0; if (CallbackFunction) CallbackFunction(CallbackContext, BufferNumber); } void StopAudio() { StopAudioDMA(); SPI3 ->CR2 &= ~SPI_CR2_TXDMAEN; // Disable I2S TX DMA request. NVIC_DisableIRQ(DMA1_Stream7_IRQn); CallbackFunction = NULL; } void ProvideAudioBuffer(void *samples, int numsamples) { while (!ProvideAudioBufferWithoutBlocking(samples, numsamples)) __asm__ volatile ("wfi"); } bool ProvideAudioBufferWithoutBlocking(void *samples, int numsamples) { if (NextBufferSamples) return false; NVIC_DisableIRQ(DMA1_Stream7_IRQn); NextBufferSamples = samples; NextBufferLength = numsamples; if (!DMARunning) StartAudioDMAAndRequestBuffers(); NVIC_EnableIRQ(DMA1_Stream7_IRQn); return true; } static void StartAudioDMAAndRequestBuffers() { // Configure DMA stream. DMA1_Stream7 ->CR = (0 * DMA_SxCR_CHSEL_0 ) | // Channel 0 (1 * DMA_SxCR_PL_0 ) | // Priority 1 (1 * DMA_SxCR_PSIZE_0 ) | // PSIZE = 16 bit (1 * DMA_SxCR_MSIZE_0 ) | // MSIZE = 16 bit DMA_SxCR_MINC | // Increase memory address (1 * DMA_SxCR_DIR_0 ) | // Memory to peripheral DMA_SxCR_TCIE; // Transfer complete interrupt DMA1_Stream7 ->NDTR = NextBufferLength; DMA1_Stream7 ->PAR = (uint32_t) &SPI3 ->DR; DMA1_Stream7 ->M0AR = (uint32_t) NextBufferSamples; DMA1_Stream7 ->FCR = DMA_SxFCR_DMDIS; DMA1_Stream7 ->CR |= DMA_SxCR_EN; // Update state. NextBufferSamples = NULL; BufferNumber ^= 1; DMARunning = true; // Invoke callback if it exists to queue up another buffer. if (CallbackFunction) CallbackFunction(CallbackContext, BufferNumber); } static void StopAudioDMA() { DMA1_Stream7 ->CR &= ~DMA_SxCR_EN; // Disable DMA stream. while (DMA1_Stream7 ->CR & DMA_SxCR_EN ) ; // Wait for DMA stream to stop. DMARunning = false; } void DMA1_Stream7_IRQHandler() { DMA1 ->HIFCR |= DMA_HIFCR_CTCIF7; // Clear interrupt flag. if (NextBufferSamples) { StartAudioDMAAndRequestBuffers(); } else { DMARunning = false; } } // Warning: don't i2c_write call from IRQ handler ! static void WriteRegister(uint8_t address, uint8_t value) { const uint8_t device = 0x4a; const uint8_t data[2] = {address, value}; i2c_transaction_start(); i2c_write(device, data, 2); i2c_transaction_end(); }