音频录制以及播放 象棋小子 1048272975 一般的音频应用中,往往需要支持音频的拾取输入以及音频的播放输出。LPC5411x具有I2S音频接口以及双通道PDM数字麦克风接口,其中数字麦克风接口支持芯片深度睡眠时的语音激活,非常适合于音频,尤其是低功耗音频的应用。 1. I2SLPC5411x内置了8个Flexcomm接口用于支持串行外设,其中Flexcomm 6和Flexcomm 7可以配置成I2S接口。每个I2S接口只能支持音频的半双工传输,即I2S数据口只能配置成输入或者输出。万利的LCP5411x 开发板板载了一颗WM8904音频编解码器,支持Line in输入以及耳机输出,通过Flexcomm 7连接音频输出流,Flexcomm 6连接音频输入流,从而构成I2S的全双工传输。 I2S的初始化过程如下: a. 在pin_mux.c中初始化I2S的功能引脚。 // I2S FC6 uint32_t i2s_config = { IOCON_FUNC1 | IOCON_MODE_INACT| IOCON_DIGITAL_EN| IOCON_INPFILT_OFF }; IOCON_PinMuxSet(IOCON, 0, 5,i2s_config); // PIO0_5 DATAO IOCON_PinMuxSet(IOCON, 0, 6,i2s_config); // PIO0_6 WSO IOCON_PinMuxSet(IOCON, 0, 7,i2s_config); // PIO0_7 BCKO // FC7 i2s_config = IOCON_FUNC4 | IOCON_MODE_INACT | IOCON_DIGITAL_EN | IOCON_INPFILT_OFF; IOCON_PinMuxSet(IOCON, 1, 12,i2s_config); // PIO1_12 BCKI IOCON_PinMuxSet(IOCON, 1, 13,i2s_config); // PIO1_13 DATAI IOCON_PinMuxSet(IOCON, 1, 14,i2s_config); // PIO1_14 WSI IOCON_PinMuxSet(IOCON, 1, 17,i2s_config); // PIO1_17 MCKL b. 设置I2S外设时钟,使用PLL时钟,24.576MHz。 const pll_setup_t pllSetup = { .syspllctrl =SYSCON_SYSPLLCTRL_BANDSEL_MASK | SYSCON_SYSPLLCTRL_SELP(0x1FU) |SYSCON_SYSPLLCTRL_SELI(0x8U), .syspllndec =SYSCON_SYSPLLNDEC_NDEC(0x2DU), .syspllpdec =SYSCON_SYSPLLPDEC_PDEC(0x42U), .syspllssctrl= {SYSCON_SYSPLLSSCTRL0_MDEC(0x34D3U) | SYSCON_SYSPLLSSCTRL0_SEL_EXT_MASK,0x00000000U}, .pllRate =24576000U, .flags =PLL_SETUPFLAG_WAITLOCK}; /* Ini tializePLL clock */ CLOCK_AttachClk(kFRO12M_to_SYS_PLL); CLOCK_SetPLLFreq(&pllSetup); /* I2S clocks*/ CLOCK_AttachClk(kSYS_PLL_to_FLEXCOMM6); CLOCK_AttachClk(kSYS_PLL_to_FLEXCOMM7); c. 设置I2S的主时钟MCLK输出,1分频,MCLK输出24.576MHz时钟。 /* Attach PLLclock to MCLK for I2S, no divider */ CLOCK_AttachClk(kSYS_PLL_to_MCLK); SYSCON->MCLKDIV= SYSCON_MCLKDIV_DIV(0U); SYSCON->MCLKIO= 1U; d. 设置I2S1为主机模式,相应的数据格式,并初始化。I2S1的寄存器基址对应Flexcomm 7的寄存器基址,在主机模式,由主机产生位时钟、帧时钟这些同步时钟,通过I2STxConfig.divider分频决定I2S的位时钟,在48分频时,位时钟为24.576MHz / 48 =512KHz,16位长,双通道对应的采样率为24.576MHz / 48 / 16 / 2 = 16K。 /* * masterSlave = kI2S_MasterSlaveNormalMaster; * mode = kI2S_ModeI2sClassic; * rightLow = false; * leftJust = false; * pdmData = false; * sckPol = false; * wsPol = false; * divider = 1; * oneChannel = false; * dataLength = 16; * frameLength = 32; * position = 0; * fifoLevel = 4; */ I2S_TxGetDefaultConfig(&I2STxConfig); I2STxConfig.divider = 48; //24576000/16/2/48=16k I2S_TxInit(I2S1, &I2STxConfig); e. 设置I2S0为从机模式,相应的数据格式,并初始化。I2S0的寄存器基址对应Flexcomm 6的寄存器基址,在从机模式,从机的位时钟、帧时钟同步到主机的位时钟、帧时钟。 /* * masterSlave = kI2S_MasterSlaveNormalSlave; * mode = kI2S_ModeI2sClassic; * rightLow = false; * leftJust = false; * pdmData = false; * sckPol = false; * wsPol = false; * divider = 1; * oneChannel = false; * dataLength = 16; * frameLength = 32; * position = 0; * watermark = 4; * txEmptyZero = false; * pack48 = true; */ I2S_RxGetDefaultConfig(&I2SRxConfig); I2S_RxInit(I2S0, &I2SRxConfig); f. 设置I2S的DMA通道传输,DMA传输可以减少CPU对音频流的参与处理,从而降低CPU的负载。通常音频数据的处理以帧为单位,方便音频编解码,打包传输等处理,当DMA完成一帧缓存传输时,通过中断通知CPU处理下一帧缓存的DMA传输。 DMA_EnableChannel(DMA0, I2S_TX_CHANNEL); DMA_EnableChannel(DMA0, I2S_RX_CHANNEL); DMA_SetChannelPriority(DMA0,I2S_TX_CHANNEL, kDMA_ChannelPriority3); DMA_SetChannelPriority(DMA0,I2S_RX_CHANNEL, kDMA_ChannelPriority2); DMA_CreateHandle(&I2STxDmaHandle,DMA0, I2S_TX_CHANNEL); DMA_CreateHandle(&I2SRxDmaHandle,DMA0, I2S_RX_CHANNEL); I2S_TxTransferCreateHandleDMA(I2S1,&I2STxHandle, &I2STxDmaHandle, I2STxCallback, (void*)&I2STxTransfer); I2S_RxTransferCreateHandleDMA(I2S0,&I2SRxHandle, &I2SRxDmaHandle, I2SRxCallback, (void*)&I2SRxTransfer); g. I2S准备就绪后,可以通过I2S_TxStart()函数启动输出帧缓存到音频输出流的DMA传输,通过I2S_RxStart()函数启动音频输入流到输入帧缓存的DMA传输。 void I2S_TxStart(void) { I2SState.TxReadIndex = 0; I2STxTransfer.data = (volatile uint8_t*)I2SState.TxBuffer[0]; I2STxTransfer.dataSize =sizeof(I2SState.TxBuffer[0]); I2S_TxTransferSendDMA(I2S1,&I2STxHandle, I2STxTransfer); I2SState.TxWriteIndex = 1; I2S_TxTransferSendDMA(I2S1,&I2STxHandle, I2STxTransfer); } void I2S_RxStart(void) { I2SState.RxWriteIndex = 0; I2SRxTransfer.data = (volatile uint8_t*)I2SState.RxBuffer[0]; I2SRxTransfer.dataSize = sizeof(I2SState.RxBuffer[0]); I2S_RxTransferReceiveDMA(I2S0,&I2SRxHandle, I2SRxTransfer); I2SState.RxWriteIndex = 1; I2SRxTransfer.data = (volatile uint8_t*)I2SState.RxBuffer[1]; I2S_RxTransferReceiveDMA(I2S0,&I2SRxHandle, I2SRxTransfer); } 2. 音频编解码器开发板板载了WM8904音频编解码器,通过I2C设置WM8904内部寄存器,设置为从机模式,相应的数据格式。 a. 在pin_mux.c中初始化I2C的功能引脚。 const uint32_t i2c_config = ( IOCON_FUNC5 | IOCON_DIGITAL_EN| IOCON_INPFILT_OFF| IOCON_OPENDRAIN_EN ); IOCON_PinMuxSet(IOCON,1, 1, i2c_config); // PIO1_1 SCL IOCON_PinMuxSet(IOCON,1, 2, i2c_config); // PIO1_2 SDA b. 设置I2C外设时钟,使用FRO时钟12MHz。 /* I2C clock */ CLOCK_AttachClk(kFRO12M_to_FLEXCOMM4); /* reset FLEXCOMM for I2C */ RESET_PeripheralReset(kFC4_RST_SHIFT_RSTn); c. 设置I2C为主机模式,相应的波特率,并初始化。 /* * enableMaster = true; * baudRate_Hz = 100000U; * enableTimeout = false; */ I2C_MasterGetDefaultConfig(&i2cConfig); i2cConfig.baudRate_Bps =WM8904_I2C_BITRATE; I2C_MasterInit(I2C4, &i2cConfig,12000000); d. 初始化WM8904音频编解码器。 WM8904_GetDefaultConfig(&codecConfig); codecHandle.i2c = I2C4; if (WM8904_Init(&codecHandle,&codecConfig) != kStatus_Success) { PRINTF("WM8904_Initfailed!rn"); return; } /* Adjust it to your needs, 0x0006 for-51 dB, 0x0039 for 0 dB etc. */ WM8904_SetVolume(&codecHandle,0x0030, 0x0030); 3. DMICDMIC (digital microphoneinterface)支持PDM输出的数字麦克风接口。开发板板载了SPH0641LM4H数字麦克风,从数字麦克风获取的PDM数据可以通过一系列的滤波器还原出相应的波形,再采样获得对应的PCM数据。PCM采样率由DMIC时钟以及过采样率(OSR)决定,采样率公式如下:
例如,DMIC的时钟800KHz,过采样率OSR为25,在2 FS模式,对应的PCM采样率为16K。 a. 在pin_mux.c中初始化DMIC的功能引脚。 const uint32_t dmic_config = ( IOCON_FUNC1| IOCON_MODE_INACT| IOCON_DIGITAL_EN| IOCON_INPFILT_OFF ); IOCON_PinMuxSet(IOCON,1, 15, dmic_config); // PIO1_15 PDM0_CLK IOCON_PinMuxSet(IOCON,1, 16, dmic_config); // PIO1_16 PDM0_DATA b. 设置DMIC外设时钟FRO时钟12MHz,相应的接口时钟800KHz。 /* DMIC uses 12MHz FRO clock */ CLOCK_AttachClk(kFRO12M_to_DMIC); /*12MHz divided by 15 = 800KHz PDM clock*/ CLOCK_SetClkDiv(kCLOCK_DivDmicClk, 15,false); c. 设置DMIC过采样率,相应的增益,16位数据,并初始化。 dmic_channel_cfg.divhfclk =kDMIC_PdmDiv1; dmic_channel_cfg.osr = 25U; dmic_channel_cfg.gainshft = 3U; dmic_channel_cfg.preac2coef =kDMIC_CompValueZero; dmic_channel_cfg.preac4coef =kDMIC_CompValueZero; dmic_channel_cfg.dc_cut_level =kDMIC_DcCut155; dmic_channel_cfg.post_dc_gain_reduce =1U; dmic_channel_cfg.saturate16bit = 1U; dmic_channel_cfg.sample_rate =kDMIC_PhyFullSpeed; DMIC_Init(DMIC0); DMIC_ConfigIO(DMIC0, kDMIC_PdmDual); DMIC_Use2fs(DMIC0, true); DMIC_SetOperationMode(DMIC0,kDMIC_OperationModeDma); DMIC_ConfigChannel(DMIC0,kDMIC_Channel0, kDMIC_Left, &dmic_channel_cfg); DMIC_FifoChannel(DMIC0, kDMIC_Channel0,FIFO_DEPTH, true, true); d. 设置DMIC的DMA传输通道。 DMA_EnableChannel(DMA0, DMAREQ_DMIC0); DMA_CreateHandle(&DmicDmaHandle,DMA0, DMAREQ_DMIC0); DMIC_TransferCreateHandleDMA(DMIC0,&DmicHandle, DMIC_Callback, &DmicTransfer, &DmicDmaHandle); DMA_SetChannelPriority(DMA0,DMAREQ_DMIC0, kDMA_ChannelPriority2); e. DMIC准备好后,可以通过Dmic_Start()函数启动DMIC音频输入流到帧输入缓存的DMA传输。 void Dmic_Start(void) { DmicTransfer.data = (uint16_t*)DmicState.Buffer; DmicTransfer.dataSize = sizeof(DmicState.Buffer[0]); DMIC_TransferReceiveDMA(DMIC0,&DmicHandle, &DmicTransfer, kDMIC_Channel0); DMIC_EnableChannnel(DMIC0,DMIC_CHANEN_EN_CH0(1)); } 4. 应用例程main函数例程实现把音频编解码器输入流DMA传输到音频输入缓存,音频输入缓存不经过任何处理,直接输出到音频输出缓存,DMA再把输出缓存传输到音频编解码器输出流中,实现音频编解码器Line in的回放。也可以把数字麦克分的输入流DMA传输到麦克风输入缓存,并拷贝到音频输出缓存,DMA传输到音频编解码器输出流中,实现数字麦克风的拾取回放。 int main(void) { int i; int Select; /* Board pin, clock, debug console init*/ CLOCK_EnableClock(kCLOCK_InputMux); CLOCK_EnableClock(kCLOCK_Gpio0); CLOCK_EnableClock(kCLOCK_Gpio1); /* USART0 clock */ CLOCK_AttachClk(BOARD_DEBUG_UART_CLK_ATTACH); BOARD_InitPins(); BOARD_BootClockFROHF96M(); BOARD_InitDebugConsole(); Gpio_Init(); DMA_Init(DMA0); I2S_Init(); Codec_Init(); Dmic_Init(); I2S_TxStart(); Select = 1; if (Select) { // codec linein I2S_RxStart(); while(1) { if(I2SState.RxEvent) { for(i=0; i I2SState.TxBuffer[I2SState.TxWriteIndex]= I2SState.RxBuffer[I2SState.RxReadIndex]; } if(I2SState.RxReadIndex >= AUDIO_NUM_BUFFERS-1) { I2SState.RxReadIndex= 0; }else { I2SState.RxReadIndex++; } if(I2SState.TxWriteIndex >= AUDIO_NUM_BUFFERS-1) { I2SState.TxWriteIndex= 0; }else { I2SState.TxWriteIndex++; } I2SState.RxEvent= 0; } } } else { //digital mic Dmic_Start(); while(1) { if(DmicState.Event) { for(i=0; i I2SState.TxBuffer[I2SState.TxWriteIndex]= DmicState.Buffer[DmicState.ReadIndex]; } if(DmicState.ReadIndex >= AUDIO_NUM_BUFFERS-1) { DmicState.ReadIndex= 0; }else { DmicState.ReadIndex++; } if(I2SState.TxWriteIndex >= AUDIO_NUM_BUFFERS-1) { I2SState.TxWriteIndex= 0; }else { I2SState.TxWriteIndex++; } DmicState.Event= 0; } } } } 5. 附录附件为音频录制以及播放的MDK工程,相应的文档。
|