一般的音频应用中,往往需要支持音频的拾取输入以及音频的播放输出。LPC5411x具有I2S音频接口以及双通道PDM数字麦克风接口,其中数字麦克风接口支持芯片深度睡眠时的语音激活,非常适合于音频,尤其是低功耗音频的应用。

1. I2S

LPC5411x内置了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};



/* InitializePLL 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!\r\n");

return;

}

/* Adjust it to your needs, 0x0006 for-51 dB, 0x0039 for 0 dB etc. */

WM8904_SetVolume(&codecHandle,0x0030, 0x0030);

3. DMIC

DMIC (digital microphoneinterface)支持PDM输出的数字麦克风接口。开发板板载了SPH0641LM4H数字麦克风,从数字麦克风获取的PDM数据可以通过一系列的滤波器还原出相应的波形,再采样获得对应的PCM数据。PCM采样率由DMIC时钟以及过采样率(OSR)决定,采样率公式如下:

03_音频录制以及播放 - 图1

例如,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<AUDIO_FRAME_SIZE; i++) {
I2SState.TxBuffer[I2SState.TxWriteIndex][i]= I2SState.RxBuffer[I2SState.RxReadIndex][i];

       }

       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<AUDIO_FRAME_SIZE; i++) {
I2SState.TxBuffer[I2SState.TxWriteIndex][i]= DmicState.Buffer[DmicState.ReadIndex][i];

              }

              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工程,相应的文档。
源码:http://pan.baidu.com/s/1mh7Bbk8