一般的音频应用中,往往需要支持音频的拾取输入以及音频的播放输出。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)决定,采样率公式如下:
例如,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