以下内容来自网蜂科技A8教程
初识 I2S 总线
I2S 简述
I2S 总线是近年来出现的一种面向多媒体计算机的音频总线,专门用于音频设备之间的数据传输,为数字立体声提供一个连接至标准编码解码器的总线。S5PV210 内置一个 I2S 总
线控制器,实现了到一个外部立体声音频 CODEC IC的接口,支持 I2S 总线数据格式和 MSB-justified、LSB-justified 数据格式。该接口对 FIFO 的访问采用 DMA 模式,它可以在
同一时间接收和发送数据。
I2S 关键特性
- 每个通道有 8/16/24 位的串行数据传输
- 支持 I2S,MSB-justified 和 LSB-justified 数据格式
- 支持主/从模式
I2S 总线数据传输
正常传输
对于发送和接收 FIFO,I2S 控制器有一个 FIFO 准备标志位。当 FIFO 准备发送数据时,如果 FIFO 非空,FIFO 准备标志位置为 1,如果 FIFO 为空,则 FIFO准备标志位置为 0。当 FIFO 准备接收数据时,如果 FIFO 非满,对应 FIFO 准备标志位置为 1,可以接收数据,如果 FIFO 为满,FIFO 准备标志位置为 0.通过这些标志们可以决定 CPU 读写 FIFO 的时间。采用这种方法,当 CPU 在访问发送接收 FIFO 的同时,串行数据也能被发送和接收。
DMA 传输
在 DMA 传输模式下,发送或接收 FIFO 对 DMA 控制器是可以访问的,在发送或接收模式下的 DMA 服务请求是由 FIFO 准备标志自动执行的。
音频串行接口格式
I2S 总线包括四条线:串行数据输入(I2SDI)、串行数据输出(I2SDO)、左右通道选择(I2SLRCK)和串行位时钟(I2SCLK)。产生 I2SLRCK 和 I2SCLK 的设备是主设备。串行数据以二进制的补码形式发送,MSB(高位)先发。高位先发的设计是考虑到发送器和接收器的字长可能不同,这样,发送器就不必知道接收器能接收多少位,接收器也不用知道有多少位正在被发送。
左右通道的选择指出了正在传输的通道。I2SLRCK 可以在串行时钟的下降沿或上升沿被改变,而且不需要进行同步。对于从设备而言,信号在串行时钟的下降沿或上升沿被锁存。因此,I2SLRCK 线在 MSB 发送前的一个时钟周期改变。这样就使得从设备的发送器能够为即将传输的串行数据设立同步时间。此外,接收器也能及时存储上一个字,然后清空输入,为下一个字的接收做准备。
I2S 实例(wm8960 音频实例)
上面 5 个引脚的一般功能用途:
LRCK ---- 左右声道控制信号
SCLK ---- 串行时钟
SDI ---- 数据输入
SDO ---- 数据输出
CDCLK ---- 为编解码芯片提供系统同步时钟
.global _start
.global IRQ_handle
_start:
ldr sp, =0x40000000 @设置栈,以便调用c函数
mov r0, #0x53 @进入SVC模式,开中断(把I位设为0)
msr CPSR_cxsf, r0
bl main @调用main函数
IRQ_handle:
ldr sp, =0xD0037F80
sub lr, lr, #4 @计算返回地址
stmfd sp!, {r0-r12, lr} @保存现场
bl do_i2c_irq @跳转到中断处理函数
ldmfd sp!, {r0-r12, pc}^ @恢复现场
CPU 从_start:标号开始跑,首先设置栈、接着进入 SVC 模式,并打开中断,然后就进入 main 函数了。那么 IRQ_handle:标号什么时候得以执行呢?发生中断的时候,就会执行!它的作用就是保存现场、跳转到中断处理函数、最后恢复现场。
int main(void)
{
int offset = 0x2E; //wav文件头部的大小
short *p = (short *)0x23000000; //下载音乐文件到这个地址,CPU从0x23000000取得音频数据
led_init(); //初始化LED
sys_clock_init(); //初始化时钟
uart_init(); //初始化串口
printf("\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n");
printf("\n This is a wm8960-->I2S test.\n");
printf("\n Write by WebeeA8 member: %d \n",2013);
printf("\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n");
irq_init(); //初始化中断
i2c_init(); //初始化I2C
wm8960_init(); // 初始化wm8960
i2s_init(); // 初始化I2S
while(1)
{
// polling Primary Tx FIFO0 full status indication.
while((IISCON & (1<<8)) == (1<<8));
IISTXD = *(p+offset); // 每次发送2byte
offset++;
if (offset > (882046-0x2e) /2) // 有多少个2byte = (文件大小-偏移)/2 ,882046(十进制)是文件的大小
{
offset = 0x2E; // replay from wav data offset
printf("\nOnce again...... ^_^\n");
led_flash();
}
}
return 0;
}
void irq_init(void)
{
/**这里主要是初始化 I2C0 中断,为什么会用到 I2C0 呢,第 17、18 引脚2C 的功能主要是用来控制 CPU
与 wm8960 芯片之间的传输信息,比如调节音量的大小、是否静音、如何优化音
质等,I2S 的作用就是负责传输声音数据,wm8960 芯片的作用是编解码音频*/
pExceptionIRQ = (unsigned long)IRQ_handle;
/* 设置为IRQ中断 */
VIC1INTSELECT &= ~(1<<14); // I2C0
/* 使能中断(中断控制器里面的) */
VIC1INTENABLE |= 1<<14; // I2C0
/* 设置中断向量 */
VIC1VECTADDR14 = (int)IRQ_handle; // I2C0
}
void i2s_init(void)
{
/* 设置对应GPIO用于I2S */
GPICON = 0x22222222;
/* 设置锁相环
* SDIV [2:0] : SDIV = 0x3
* PDIV [13:8] : PDIV = 0x3
* MDIV [24:16]: MDIV = 0x43
* LOCKED [29]: 1 = 使能锁
* ENABLE [31]: 1 = 使能锁相环
*
* Fout = (0x43+0.7)*24M / (3 * 2^3) = 67.7*24M/24 = 67.7Mhz
*/
EPLL_CON0 = 0xa8430303; //MPLL_FOUT = 67.7Mhz
EPLL_CON1 = 0xbcee; // K = 0xbcee
/* 时钟源的设置
* APLL_SEL[0] :1 = FOUTAPLL
* MPLL_SEL[4] :1 = FOUTMPLL
* EPLL_SEL[8] :1 = FOUTEPLL
* VPLL_SEL[12]:1 = FOUTVPLL
* MUX_MSYS_SEL[16]:0 = SCLKAPLL
* MUX_DSYS_SEL[20]:0 = SCLKMPLL
* MUX_PSYS_SEL[24]:0 = SCLKMPLL
* ONENAND_SEL [28]:1 = HCLK_DSYS
*/
CLK_SRC0 = 0x10001111;
/* 时钟源的进一步设置(AUDIO SUBSYSTEMCLK SRC)
* bit[3:2]: 00 = MUXi2s_a_out来源于Main CLK
* bit[0] : 1 = Main CLK来源于FOUT_EPLL
*/
CLK_CON = 0x1;
/* 由于AUDIO SUBSYSTEMCLK DIV寄存器使用的是默认值,故分频系数为1 */
// IISCDCLK 11.289Mhz = 44.1K * 256fs
// IISSCLK 1.4112Mhz = 44.1K * 32fs
// IISLRCLK 44.1Khz
/* 预分频值
* bit[13:8] : N = 5
* bit[15] : 使能预分频
*/
IISPSR = 1<<15 | 5<<8;
/* 设置IIS控制器
* bit[0]: 1 = 使能IIS
*/
IISCON |= 1<<0 | (unsigned)1<<31;
/* 设置各个时钟输出
* bit[2:1]:IISSCLK(位时钟) 44.1K * 32fs = 1.4112Mhz
* bit[3:4]:IISCDCLK(系统时钟) 44.1K * 256fs = 11.289Mhz
* bit[9:8]:10 = 既可以发送又可以接收
* bit[10] :0 = PCLK is internal source clock for IIS
*/
IISMOD = 1<<9 | 0<<8 | 1<<10;
}
#define WM8960_DEVICE_ADDR 0x34 //wm8960的设备地址
void wm8960_init(void)
{
/* 复位,让其他所有的寄存器恢复到默认值 */
wm8960_write(WM8960_DEVICE_ADDR, 0xf, 0x0);
/* 打开电源,使用fast start-up模式 */
wm8960_write(WM8960_DEVICE_ADDR, 0x19, 1<<8 | 1<<7 | 1<<6);
/* 任然是打开电源 */
wm8960_write(WM8960_DEVICE_ADDR, 0x1a, 1<<8 | 1<<7 | 1<<6 | 1<<5 | 1<<4 | 1<<3);
/* 左右声道输出使能 */
wm8960_write(WM8960_DEVICE_ADDR, 0x2F, 1<<3 | 1<<2);
/* 设置时钟,使用的都是默认值 */
wm8960_write(WM8960_DEVICE_ADDR, 0x4, 0x0);
/* 关键是将R5寄存器的bit[3]清零,关闭静音功能 */
wm8960_write(WM8960_DEVICE_ADDR, 0x5, 0x0);
/* 设置通信协议方式:如数据是24位,即IIS,左右声道时钟电平是否反转 */
wm8960_write(WM8960_DEVICE_ADDR, 0x7, 0x2);
/* 设置左右声道输出的音量 */
wm8960_write(WM8960_DEVICE_ADDR, 0x2, 0xFF | 0x100);/* 控制左声道的 */
wm8960_write(WM8960_DEVICE_ADDR, 0x3, 0xFF | 0x100);/* 控制右声道的 */
wm8960_write(WM8960_DEVICE_ADDR, 0xa, 0xFF | 0x100);/* 控制左声道的 */
wm8960_write(WM8960_DEVICE_ADDR, 0xb, 0xFF | 0x100);/* 控制右声道的 */
/* 使能通道,否则会静音 */
wm8960_write(WM8960_DEVICE_ADDR, 0x22, 1<<8 | 1<<7);/* 控制左声道的 */
wm8960_write(WM8960_DEVICE_ADDR, 0x25, 1<<8 | 1<<7);/* 控制右声道的 */
return;
}