IIS ( Inter-IC Sound )由飞利浦公司开发,是一种常用的音频设备接口,主要用于 CD 、 MD 、 MP3 等设备。
s3c2440 一共有 5 个引脚用于 IIS : IISDO 、 IISDI 、 IISSCLK 、 IISLRCK 和 CDCLK 。前两个引脚用于数字音频信号的输出和输入,另外三个引脚都与音频信号的频率有关,可见要用好 IIS ,就要把信号频率设置正确。 IISSCLK 为串行时钟,每一个时钟信号传送一位音频信号,因此 IISSCLK 的频率=声道数×采样频率×采样位数,如采样频率 fs 为 44.1kHz ,采样的位数为 16 位,声道数 2 个(左、右两个声道),则 IISSCLK 的频率= 32fs = 1411.2kHz 。 IISLRCK 为帧时钟,用于切换左、右声道,如 IISLRCK 为高电平表示正在传输的是左声道数据,为低电平表示正在传输的是右声道数据,因此 IISLRCK 的频率应该正好等于采样频率。由于 IIS 只负责数字音频信号的传输,而要真正实现音频信号的放、录,还需要额外的处理芯片(在这里,我们使用的是 UDA1341 ), CDCLK 为该芯片提供系统同步时钟,即编解码时钟,主要用于音频的 A/D 、 D/A 采样时的采样时钟,一般 CDCLK 为 256fs 或 384fs 。
通过以上分析可以发现,采样频率 fs 对频率的设置至关重要。而 fs 不是任意设置的,一般基于不同的应用场合和听觉效果,而设置不同的几个固定的值,如 8kHz 、 16kHz 、 22.05kHz 、 44.1kHz 、 48kHz 、 96kHz 等。为了使系统得到以 fs 为基数的各类时钟信号,就要重新调整系统时钟。 s3c2440 用于 IIS 的时钟源有 PCLK 和 MPLLin ,我们这里选择 PCLK 作为 IIS 的时钟源。 PCLK 经过两个预分频器处理后分别得到 IISSCLK 、 IISLRCK 和 CDCLK (预分频器 A 得到 IISSCLK 、 IISLRCK ,预分频器 B 得到 CDCLK )。寄存器 IISPSR 是 IIS 预分频器寄存器, 5~9 位是预分频器 A , 0~4 位是预分频器 B ,一般来说,这两个预分频器的值 N 相等,即只要知道一个,另一个也就知道,而这里我们是通过 CDCLK 来计算预分频器 B 的值 N 的,即 CDCLK = PCLK / (N + 1) 。 PCLK 与 FCLK 有一定的比例关系,而 FCLK 又是由输入频率 Fin 得到。在这里,我们为了简化计算,不改变 PCLK 与 FCLK 的比例关系(即维持在启动代码中定义的 1:8 的关系),那么由 Fin 而得到 CDCLK 一共涉及到四个参数: MDIV 、 PDIV 、 SDIV 和前面公式中的 N ,涉及到的寄存器有 MPLLCON 和 IISPSR 。因此要得到这四个参数值,就需要一点耐心地计算,原则是误差最小,其中需要注意的是,计算的结果(包括中间过程的结果)不要溢出,即不要超过 32 位。例如 Fin 为 12MHz ,我们设置采样频率 fs = 44.1kHz ,而 CDCLK = 384fs = 16.9344MHz ,那么经过计算,最终得到 N = 3 , MDIV = 150 , PDIV = 5 , SDIV = 0 ,即 IISPSR = (3<<5) | 3; , MPLLCON = (150<<12) | (5<<4) | 0; 。
s3c2440 有关 IIS 的寄存器除了 IISPSR 外,还包括 IIS 控制寄存器 IISCON ,主要用于控制数据传输的方式、预分频器和 IIS 接口是否开启; IIS 模式寄存器 IISMOD ,主要用于设置 IIS 的时钟源、主从方式、接收发送方式、串行接口方式、每个声道串行数据位数和各种频率值; IIS 的 FIFO 接口寄存器 IISFCON 用于设置和判断数据传输的 FIFO 状态;而寄存器 IISFIFO 则用于音频数据的传输。
由于 s3c2440 要实现 IIS 的录、放音,还需要 UDA1341 芯片,因此我们再简要介绍一下这个芯片的使用。 s3c2440 与 UDA1341 之间除了我们前面介绍过的 IIS 接口相连接外,还有一个称之为 L3 总线的连接,用于 s3c2440 配置 UDA1341 内部的寄存器。由于 s3c2440 不具备 L3 总线接口,因此我们是用三个通用 IO 口来模拟 L3 ,从而实现 L3 总线的传输。 UDA1341 有两种模式:地址模式和数据传输模式。地址模式表示传输的是地址信息,它的高 6 位永远是 000101 ,低两位表示的是传输的模式,是状态模式、数据 0 模式还是数据 1 模式,其中状态模式主要用于配置 UDA1341 的各类初始状态,数据模式主要用于改善音频输入、输出的效果。
下面我们就给出具体的程序,在这里我们使用的是正常模式来实现数据的输入和输出的,即不使用 DMA 模式。首先是实现 s3c2440 对某一音频信号数据的输出,即放音。我们事先已知道该音频信号的各类特性,如采样频率、声道数、采样信号的位数等。
…… ……
//L3 接口
#define L3C (1<<4) //GPB4 = L3CLOCK
#define L3D (1<<3) //GPB3 = L3DATA
#define L3M (1<<2) //GPB2 = L3MODE
// 纯音频信号数据数组
unsigned char music[ ] = {
0xB8, 0xFF, 0xBA, 0xFF, 0xBF, 0xFF, 0xC0, 0xFF, 0xD4, 0xFF, 0xD3, 0xFF, 0xF2, 0xFF, 0xED, 0xFF,
0x0E, 0x00, 0x05, 0x00, 0x1C, 0x00, 0x0F, 0x00, 0x15, 0x00, 0x06, 0x00, 0xFC, 0xFF, 0xEC, 0xFF,
…… ……
}
//L3 总线接口的写函数
// 输入参数 data 为要写入的数据
// 输入参数 address ,为 1 表示地址模式,为 0 表示数据传输模式
static void WriteL3(U8 data,U8 address)
{
int i,j;
if(address == 1)
rGPBDAT = rGPBDAT & ~(L3D | L3M | L3C) | L3C; //L3D=L, L3M=L( 地址模式 ), L3C=H
else
rGPBDAT = rGPBDAT & ~(L3D | L3M | L3C) | (L3C | L3M); //L3M=H( 数据传输模式 )
for(i=0;i<10;i++)
; // 等待一段时间
// 并行数据转串行数据输出,以低位在前、高位在后的顺序
for(i=0;i<8;i++)
{
if(data & 0x1) // H
{
rGPBDAT &= ~L3C; //L3C=L
rGPBDAT |= L3D; //L3D=H
for(j=0;j<5;j++)
; // 等待一段时间
rGPBDAT |= L3C; //L3C=H
rGPBDAT |= L3D; //L3D=H
for(j=0;j<5;j++)
; // 等待一段时间
}
else // L
{
rGPBDAT &= ~L3C; //L3C=L
rGPBDAT &= ~L3D; //L3D=L
for(j=0;j<5;j++)
; // 等待一段时间
rGPBDAT |= L3C; //L3C=H
rGPBDAT &= ~L3D; //L3D=L
for(j=0;j<5;j++)
; // 等待一段时间
}
data >>= 1;
}
rGPBDAT = rGPBDAT & ~(L3D | L3M | L3C) | (L3C | L3M); //L3M=H,L3C=H
}
// 放音
void playsound(unsigned char *buffer, int length)
{
int count,i;
char flag;
rGPBDAT = rGPBDAT & ~(L3M|L3C|L3D) |(L3M|L3C); //L3 开始传输: L3M=H, L3C=H
// 配置 UDA1341
WriteL3(0x14 + 2,1); // 状态模式 (000101xx+10)
WriteL3(0x60,0); //0,1,10, 000,0 : 状态 0, 复位
WriteL3(0x14 + 2,1); // 状态模式 (000101xx+10)
WriteL3(0x10,0); //0,0,01, 000,0 : 状态 0, 384fs,IIS,no DC-filtering
WriteL3(0x14 + 2,1); // 状态模式 (000101xx+10)
WriteL3(0xc1,0); //1,0,0,0, 0,0,01: 状态 1,
//Gain of DAC 6 dB,Gain of ADC 0dB,ADC non-inverting,
//DAC non-inverting,Single speed playback,ADC-Off DAC-On
// 配置 s3c2440 的 IIS 寄存器
// 预分频器为 3 ,所以 CDCLK=PCLK/(3+1)=16.928kHz
rIISPSR = 3<<5|3;
// 无效 DMA ,输入空闲,预分频器有效
rIISCON = (0<<5)|(0<<4)|(0<<3)|(1<<2)|(1<<1);
//PCLK 为时钟源,输出模式, IIS 模式,每个声道 16 位, CODECLK=384fs , SCLK=32fs
rIISMOD = (0<<9)|(0<<8)|(2<<6)|(0<<5)|(0<<4)|(1<<3)|(1<<2)|(1<<0);
rIISFCON = (0<<15)|(1<<13); // 输出 FIFO 正常模式,输出 FIFO 使能
flag=1;
count=0;
// 开启 IIS
rIISCON |= 0x1;
while(flag)
{
if((rIISCON & (1<<7))==0) // 检查输出 FIFO 是否为空
{
//FIFO 中的数据为 16 位,深度为 32
// 当输出 FIFO 为空时,一次性向 FIFO 写入 32 个 16 位数据
for(i=0;i<32;i++)
{
rIISFIFO=(buffer[2*i+count])+(buffer[2*i+1+count]<<8);
}
count+=64;
if(count>length)
flag=0; // 音频数据传输完,则退出
}
}
rIISCON = 0x0; // 关闭 IIS
}
void Main(void)
{
// 配置 MPLL
//fs=44.1kHz,CODECLK=384fs=16.9344MHz
// 不改变 CLKDIVN ,所以 PCLK=FCLK/8
//MPLLCON:MDIV=150,PDIV=5,SDIV=0 ,所以 FCLK=541.7143MHz,PCLK=67.714MHz
rMPLLCON = (150<<12) | (5<<4) | 0;
// 配置 L3 接口总线, GPB2:L3MODE, GPB3:L3DATA, GPB4:L3CLOCK
rGPBCON = 0x015550; // 输出
rGPBUP = 0x7ff; // 上拉无效
rGPBDAT = 0x1e4;
// 配置 IIS 接口
rGPEUP = rGPEUP & ~(0x1f) | 0x1f; // 上拉无效, GPE[4:0] 1 1111
rGPECON = rGPECON & ~(0x3ff) | 0x2aa;
playsound(music,sizeof(music));
while(1)
{
;
}
}
上面的程序可以实现简单的播放内存中固有音频数据的功能,下面的程序实现了录制一段音频数据,然后再播出的功能。我们用 UART 来控制录、放音:当 s3c2440 接收到 0x51 时录音,接收到 0x55 时停止录音,接收到 0x66 时放音。
…… ……
#define L3C (1<<4) //GPB4 = L3CLOCK
#define L3D (1<<3) //GPB3 = L3DATA
#define L3M (1<<2) //GPB2 = L3MODE
unsigned char record_buffer[1000000]; // 用于存放录制的音频数据
char stop,cmd;
//UART 中断
void __irq uartISR(void)
{
char ch;
rSUBSRCPND |= 0x1;
rSRCPND |= 0x1<<28;
rINTPND |= 0x1<<28;
ch=rURXH0;
switch(ch)
{
case 0x51: // 开始录音
cmd=0x01;
break;
case 0x55: // 停止录音
stop=1; // 置退出录音标志
break;
case 0x66: // 放音
cmd=0x03;
break;
}
rUTXH0=ch;
}
…… ……
// 录音
// 输入参数为数组,输出参数为所录制数据的字节长度
int record(unsigned char * buffer)
{
int count,i;
unsigned short temp;
rGPBDAT = rGPBDAT & ~(L3M|L3C|L3D) |(L3M|L3C); //L3 开始传输 : L3M=H, L3C=H
// 配置 UDA1341
WriteL3(0x14 + 2,1); // 状态模式 (000101xx+10)
WriteL3(0x60,0); //0,1,10, 000,0 : 状态 0, 复位
WriteL3(0x14 + 2,1); // 状态模式 (000101xx+10)
WriteL3(0x10,0); //0,0,01, 000,0 : 状态 0, 384fs,IIS,no DC-filtering
WriteL3(0x14 + 2,1); // 状态模式 (000101xx+10)
WriteL3(0xa2,0); //1,0,1,0, 0,0,10 状态 1
//Gain of DAC 0 dB,Gain of ADC 6dB,ADC non-inverting,
//DAC non-inverting,Single speed playback,ADC-On DAC-Off
WriteL3(0x14 + 0,1); //DATA0 (000101xx+00)
WriteL3(0x7b,0); //01,11 10,11 : Data0, Bass Boost 18~24dB, Treble 6dB
WriteL3(0xc4,0); //1100 0,100 : Extended addr(3bits), 100
WriteL3(0xf0,0); //111,1 00,00 : DATA0, Enable AGC, 00, input amplifier gain channel 2 (2bits)
WriteL3(0xc0,0); //1100 0,000 : Extended addr(3bits), 000
WriteL3(0xe0,0); //111, 00000 : MA = 0dB
WriteL3(0xc1,0); //1100 0,001 : Extended addr(3bits), 001
WriteL3(0xe0,0); //111, 00000 : MB = 0dB
WriteL3(0xc2,0); //1100 0,010 : Extended addr(3bits), 010
WriteL3(0xf9,0); //111,1 10,11 : DATA0, MIC Amplifier Gain 27dB, input 1 X MA + input 2 X MB
// 配置 s3c2440 的 IIS 寄存器
// 预分频器为 3 ,所以 CDCLK=PCLK/(3+1)=16.928kHz
rIISPSR = 3<<5|3;
// 无效 DMA ,输出空闲,预分频器有效
rIISCON = (0<<5)|(0<<4)|(1<<3)|(0<<2)|(1<<1);
//PCLK 为时钟源,输入模式, IIS 模式,每个声道 16 位, CODECLK=384fs , SCLK=32fs
rIISMOD = (0<<9)|(0<<8)|(1<<6)|(0<<5)|(0<<4)|(1<<3)|(1<<2)|(1<<0);
rIISFCON = (0<<14)|(1<<12); // 输入 FIFO 正常模式,输入 FIFO 使能
count=0;
// 开启 IIS
rIISCON |= 0x1;
while(stop==0)
{
if((rIISCON & (1<<6))==0) // 检查输入 FIFO 是否为满
{
//FIFO 中的数据为 16 位,深度为 32
// 当输入 FIFO 为满时,一次性读取 FIFO 中的 32 个 16 位数据
for(i=0;i<32;i++)
{
temp=rIISFIFO;
record_buffer[count+2*i]=(unsigned char)temp;
record_buffer[count+2*i+1]=(unsigned char)(temp>>8);
}
count+=64;
if(count>1000000)
stop=1; // 当录制的数据超过数组长度时,退出
}
}
rIISCON=0; // 关闭 IIS
return count; // 返回录制数据长度
}
void Main(void)
{
char play;
int bufferlength;
…… ……
// 由于改变了 PCLK ,所以需要重新计算 UART 波特率因子
rUBRDIV0 = 36;
…… ……
stop=0;
cmd=0;
play=0;
while(1)
{
switch(cmd)
{
case 0x01: // 录音
bufferlength=record(record_buffer);
play=1; // 置录音标志
cmd=0;
break;
case 0x03: // 放音
if(play)
playsound(record_buffer,bufferlength);
else // 还没有录制音频数据
{
while(!(rUTRSTAT0 & 0x2));
rUTXH0 = 0xff;
}
cmd=0;
break;
}
}
}
补充:
应大家的要求,我把 UDA1341 的 L3 通信协议详细介绍一下。
顾名思义, L3 就是 line 3 ( 3 条线)的意思,它只有 L3DATA (数据线:用于传输数据)、 L3MODE (模式线:用于选择模式)、 L3CLOCK (时钟线:用于传输时钟)。 L3 一共有两个模式:地址模式和数据传输模式,先传输地址模式数据,再传输数据模式数据。 L3MODE 为低时是地址模式, L3MODE 为高时是数据传输模式。 L3DATA 和 L3CLOCK 相互作用,完成 8 位数据的传输,传输的顺序是先低位数据,再高位数据。
地址模式是用于选择设备和定义目标寄存器,在这种模式下, 8 位数据的含义是:高 6 位是设备地址( UDA1341 的地址为 000101 ),低两位是后面数据模式下寄存器的类型( 00 : DATA0 , 01 : DATA1 , 10 : STATUS )。只要没有再改变地址模式下的数据,则数据模式下的数据始终是传输到上一个地址模式所定义的寄存器内。
在传输数据模式下, STATUS 是用于设置复位,系统时钟频率、数据输入模式、 DC 滤波等内容。 DATA0 分为直接寻址模式和扩展寻址模式,直接寻址模式是直接进行模式的控制,包括音量、静音等等,而扩展寻址模式是在直接寻址模式下先设置 3 位扩展地址,再在直接寻址模式下设置 5 位扩展数据。在 DATA1 下,可以读取到被检测峰值。至于具体的 DATA0 、 DATA1 、 STATUS 下,每一位数据具体的含义,还请自己查阅手册。