9 I2S简介
I2S(inter-IC sound)总线是数字音频专用总线。它有四个引脚,两个数据引脚(DOUT和DIN),一个位率时钟引脚(BCLK)和一个左右通道选择引脚(LRCLK)。
另外,通过ING91682A的MCLK输出,它可用于给外部 DAC/ADC 芯片提供时钟(可选)。
9.1 功能描述
9.1.1 特点
遵从 I2S 协议标准,支持12S标准模式和左对齐模式
支持PCM(脉冲编码调制)时序
可编程的主从模式
可配置的LRCLK和BCLK极性
可配置数据位宽
独立发送和接收FIFO
TX和RX的FIFO深度分别为16*32bit
支持立体声和单声道模式
可配置的采样频率
TX和RX分别支持DMA搬运
9.1.4 串行数据
串行数据是以高位(MSB)在前,低位(LSB)在后的方式进行传送的。
如果音频codec发送的位数多于I2S控制器的接收位数,I2S控制器会将低位多余的位数忽略掉;
如果音频codec发送的位数小于I2S控制器接收位数,I2S控制器将后面的位补零。
9.1.5 时钟分频
916芯片可选用系统24MHz时钟或者PLL作为I2S时钟源。
位率时钟(BCLK)可以通过对功能时钟进行分频得到;
通道选择时钟(LRCLK)即音频数据的采样频率可以通过对BCLK进行分频得到。
音频 Codec 中对采样频率 LRCLK 要求精度比较高,我们在计算分频时应该首先根据不同的采样频率计算得到对应的 MCLK 和 BCLK。
9.1.5.1 时钟分频计算
计算示例:
假设当前codec采用16K采样频率,mic要求一帧64位(参考具体的mic使用手册)。
有以下关系:
f_bclk = clk/(2*b_div)
f_lrclk = f_bclk/(2*lr_div)
其中clk为codec时钟,f_bclk、f_lrclk分别为BCLK和LRCLK,b_div、lr_div分别为BCLK和LRCLK的分频系数。
BCLK和LRCLK之间的关系是可变的,但是BCLK必须大于等于LRCLK的48倍。即lr_div>=24。
支持lr_div = 32,DATA_LEN = 32位的配置,其他情况下lr_div - DATA_LEN > 3。
通过f_lrclk = 16000,lr_div = 32计算出f_bclk = 1.024MHz。也就是clk = 2.048*b_div。
clk通过时钟源分频得到必定是整数,b_div也同样是整数,通过计算得知在384MHz内只有当b_div = 125时clk = 256MHz为整数。
故需要将PLL时钟配置为256MHz,b_div = 125可以得到16K采样率。
9.2 使用方法
9.2.1 方法概述
I2S使用方法总结为:时钟配置,I2S配置(包括采样率)和数据处理。
数据发送:
1. I2S引脚GPIO配置
2. 配置外部 codec 芯片,使其处于工作模式
3. 写相应配置寄存器
4. 将数据写入TX_MEM
5. 使能I2S
6. 等待中断产生
7. 读取状态寄存器,将数据写入TX_MEM
8. 传输完毕,关闭 I2S
数据接收:
1. I2S引脚GPIO配置
2. 配置外部 codec 芯片,使其处于工作模式
3. 写相应配置寄存器
4. 使能I2S
5. 等待中断产生
6. 读取状态寄存器,读取 RX_MEM 中数据
7. 传输完毕,关闭 I2S
I2S控制器操作流程图如下:
如果需要用到DMA搬运则需要在使能I2S之前配置DMA并使能。
9.3 编程指南
9.3.1 驱动接口
I2S_ConfigClk:I2S时钟配置接口
I2S_Config:I2S配置接口
I2S_ConfigIRQ:I2S中断配置接口
I2S_DMAEnable:I2S DMA使能接口
I2S_Enable:I2S使能接口
I2S_PopRxFIFO、I2S_PushTxFIFO:I2S FIFO读写接口
I2S_ClearRxFIFO、I2S_ClearTxFIFO:I2S清FIFO接口
I2S_GetIntState、I2S_ClearIntState:I2S获取中断、清中断接口
I2S_GetRxFIFOCount、I2S_GetTxFIFOCount:I2S获取FIFO数据数量接口
I2S_DataFromPDM:I2S获取PDM数据接口
9.3.2 代码示例
下面将通过实际代码展示I2S的基本配置及使用代码。
9.3.2.1 I2S配置
#define I2S_PIN_BCLK 21
#define I2S_PIN_IN 22
#define I2S_PIN_LRCLK 35
void I2sSetup(void)
{
// pinctrl & GPIO mux
(I2S_PIN_BCLK, IO_SOURCE_I2S_BCLK_OUT);
PINCTRL_SetPadMux(I2S_PIN_IN, IO_SOURCE_I2S_DATA_IN);
PINCTRL_SetPadMux(IO_NOT_A_PIN, IO_NOT_A_PIN, I2S_PIN_IN);
PINCTRL_SelI2sIn(I2S_PIN_LRCLK, IO_SOURCE_I2S_LRCLK_OUT);
PINCTRL_SetPadMux(I2S_PIN_IN, PINCTRL_PULL_DOWN);
PINCTRL_Pull
// CLK & Register
(6, 128, 2); // sorce clk PLL = 256MHz
SYSCTRL_ConfigPLLClk(SYSCTRL_CLK_PLL_DIV_1 + 4); // I2s_Clk = 51.2MHz
SYSCTRL_SelectI2sClk(APB_I2S, 25, 32); // F_bclk = 1.024MHz, F_lrclk = 16K
I2S_ConfigClk(APB_I2S, 0, 1, 0, 10);
I2S_ConfigIRQ(APB_I2S, 0, 0);
I2S_DMAEnable(APB_I2S, I2S_ROLE_MASTER, I2S_MODE_STANDARD, 0, 0, 0, 1, 24);
I2S_Config
// I2s interrupt
(PLATFORM_CB_IRQ_I2S, cb_isr, 0);
platform_set_irq_callback}
9.3.2.2 I2S使能
I2S使能分3种情况:I2S发送、I2S接收、使用DMA搬运
接收:
void I2sStart(void)
{
(APB_I2S);
I2S_ClearRxFIFO(APB_I2S, 0, 1);
I2S_Enable}
发送:
uint8_t sendSize = 10;
uint32_t sendData[10];
void I2sStart(void)
{
int i;
(APB_I2S);
I2S_ClearTxFIFO// push data into TX_FIFO first
for (i = 0; i < sendSize; i++) {
(APB_I2S, sendData[i]);
I2S_PushTxFIFO}
(APB_I2S, 0, 1);
I2S_Enable}
使用DMA(接收):
#define CHANNEL_ID 0
((aligned (8)));
DMA_Descriptor test __attribute__void I2sStart(uint32_t data)
{
(CHANNEL_ID, &test);
DMA_EnableChannel(APB_I2S);
I2S_ClearRxFIFO(APB_I2S, 1, 1);
I2S_DMAEnable(APB_I2S, 0, 1);
I2S_Enable}
无论哪种情况都必须最后一步使能I2S,否则I2S工作异常。
9.3.2.3 I2S中断
接收:
uint32_t cb_isr(void *user_data)
{
uint32_t state = I2S_GetIntState(APB_I2S);
(APB_I2S, state);
I2S_ClearIntState
int i = I2S_GetRxFIFOCount(APB_I2S);
while (i) {
uint32_t data = I2S_PopRxFIFO(APB_I2S);
--;
i// do something with data
}
return 0;
}
发送:
uint8_t sendSize = 10;
uint32_t sendData[10];
uint32_t cb_isr(void *user_data)
{
uint32_t state = I2S_GetIntState(APB_I2S);
(APB_I2S, state);
I2S_ClearIntState
int i;
for (i = 0; i < sendSize; i++) {
(APB_I2S, sendData[i]);
I2S_PushTxFIFO}
return 0;
}
9.3.2.4 I2S & DMA乒乓搬运
下面以经典的DMA乒乓搬运I2S接收数据为例展示I2S实际使用方法。
这里我们采用16K采样率,单个数据帧固定64位,和DMA协商握手、burstSize=8、一次搬运80个数据。
#include "pingpong.h"
#define I2S_PIN_BCLK 21
#define I2S_PIN_IN 22
#define I2S_PIN_LRCLK 35
#define CHANNEL_ID 0
;
DMA_PingPong_t PingPong
static uint32_t DMA_cb_isr(void *user_data)
{
uint32_t state = DMA_GetChannelIntState(CHANNEL_ID);
(CHANNEL_ID, state);
DMA_ClearChannelIntState
uint32_t *rr = DMA_PingPongIntProc(&PingPong, CHANNEL_ID);
uint32_t i = 0;
uint32_t transSize = DMA_PingPongGetTransSize(&PingPong);
while (i < transSize) {
// do something with data 'rr[i]'
++;
i}
return 0;
}
void I2sSetup(void)
{
// pinctrl & GPIO mux
(I2S_PIN_BCLK, IO_SOURCE_I2S_BCLK_OUT);
PINCTRL_SetPadMux(I2S_PIN_IN, IO_SOURCE_I2S_DATA_IN);
PINCTRL_SetPadMux(IO_NOT_A_PIN, IO_NOT_A_PIN, I2S_PIN_IN);
PINCTRL_SelI2sIn(I2S_PIN_LRCLK, IO_SOURCE_I2S_LRCLK_OUT);
PINCTRL_SetPadMux(I2S_PIN_IN, PINCTRL_PULL_DOWN);
PINCTRL_Pull
// CLK & Register
(6, 128, 2); // sorce clk PLL = 256MHz
SYSCTRL_ConfigPLLClk(SYSCTRL_CLK_PLL_DIV_1 + 4); // I2s_Clk = 51.2MHz
SYSCTRL_SelectI2sClk(APB_I2S, 25, 32); // F_bclk = 1.024MHz, F_lrclk = 16K
I2S_ConfigClk(APB_I2S, 0, 1, 0, 8);
I2S_ConfigIRQ(APB_I2S, 0, 0);
I2S_DMAEnable(APB_I2S, I2S_ROLE_MASTER, I2S_MODE_STANDARD, 0, 0, 0, 1, 24);
I2S_Config
// setup DMA
(&PingPong, SYSCTRL_DMA_I2S_RX, 100, 8);
DMA_PingPongSetup(PLATFORM_CB_IRQ_DMA, DMA_cb_isr, 0);
platform_set_irq_callback
// start working
(&PingPong, CHANNEL_ID);
DMA_PingPongEnable(APB_I2S);
I2S_ClearRxFIFO(APB_I2S, 1, 1);
I2S_DMAEnable(APB_I2S, 0, 1);
I2S_Enable}
DMA(乒乓搬运)的具体用法请参见本手册DMA一节。
更加系统化的I2S代码请参考SDK中voice_remote_ctrl例程。