Cortex-M3 (NXP LPC1788)之IIS控制器

I2S总线为数字音频应用提供了一个标准的通信接口。它是一条3线串行总线,包含串行数据SD、字选择WS、串行时钟SCK。LPC1788的I2S接口提供了彼此独立的发送和接收通道,每个通道可以作为主机或从机,还提供了可选的过采样主机时钟输出MCLK。因此发送和接收通道各有四个引脚,对于发送为I2S_TX_CLK、I2S_TX_WS、I2S_TX_SDA、I2S_TX_MCLK。I2S的时序如图:

在这里插入图片描述

从图中可以看出每个串行时钟SCK传送一位音频信号,因此SCK的频率=声道数 * 采样频率 * 采样位数。如程序中44.1KHZ采样的16位立体音,对应的SCK频率为 2 * 16 * 44100HZ。

WS是字段选择,也称为帧时钟,他的一个周期正好传输一帧数据。在WS改变后,数据字在发送时钟的下一个下降沿上开始。即图中MSB位在WS下降沿的下一个SCK时钟出现。在立体声模式下,当WS为低电平时发送左声道数据,当WS为高电平时发送右声道数据。如果是单声道模式,相同的数据会在WS的高低电平各发送一次。因此,要正确的通过I2S总线进行音频信号的传送,关键要正确配置传输速率。对于LCP1778,我们通过配置数字音频输出寄存器I2SDAO配置音频数据的宽度、声道数、和WS的周期。程序中的音频数据为44.1KHZ采样的16位立体音,因此I2SDAO中的WS_halfperiod字段配置成16-1,因为WS低电平和高电平各发送16位数据。

配置SCK要设置2个寄存器,首先配置发送时钟速率寄存器I2STXRATE,通过它配置分数速率发生器以分频CCLK得到I2S TX_REF=CCLK*(X/Y)/2;如果我们使能了MCLK输出时,RX_REF就发送到MCLK管脚。其次,配置位发送时钟位速率寄存器I2STXBITRATE,该寄存器的值加1用来分频TX_REF得到SCK频率。

对I2S控制器配置完成后,我们可以将要发送的音频数据写入发送FIFO,FIFO的大小为8个字。而且发送和接收通道仅接收32位对齐的数据,因此对较短的数据我们要进行扩展才能写入FIFO进行发送。

对于总线上的状态,将在状态反馈寄存器I2SSTATE中进行指示,主要有中断和DMA请求的指示以及FIFO的当前深度指示。对于中断我们可以配置中断请求控制寄存器I2SIRQ设置FIFO触发中断的深度。

IIS应用实例–UDA1380进行音频数据播放

LPC1788发送到I2S总线上的音频数据要通过音频解码芯片才能输出模拟音频信号。开发板上使用的是UDA1380,对它的寄存器的配置可以通过L3总线或者I2C总线进行,这里使用I2C总线进行控制,对于I2C总线的操作可以参考之前I2C的介绍。UDA1380的寄存器主要分成3类,系统控制、插值滤波(interpolation filter)、抽取滤波(decimator filter)。插值滤波和DAC转换有关,用于控制控制声音的输出参数。抽取滤波和ADC有关,用于控制对音频的采样。寄存器的地址和功能如图:

在这里插入图片描述
根据图1的红色标记中的内容,可以知道两个滤波器的正常使用需要一个128fs的clock,这个时钟可以通过SYSCLK引脚或者WSI的信号获得。在硬件连接上,通过将LPC1788的MCLK输出的时钟,连接到UDA1380的SYSCLK引脚。因此,我们需要配置I2S的发送模式控制寄存器I2STXMODE,使能TX_REF在MCLK输出,使UDA1380内部产生一个滤波器需要的时钟。

程序中我们通过I2S发送一段音频数据,该数据是我从WAV格式的文件中去掉WAV头格式后得到的一个纯音频数据数组。该WAV音频为16位双通道 采样频率为44.1KHZ。LPC1788将该数组发送到I2S总线,UDA1380读取该数据进行声音的输出。程序如下:

#include "i2c.h"
#include "audio.h"
 
#define rI2SDAO         (*(volatile unsigned *)(0x400A8000))
#define rI2STXFIFO      (*(volatile unsigned *)(0x400A8008))
#define rI2STXRATE      (*(volatile unsigned *)(0x400A8020))
#define rI2STXBITRATE   (*(volatile unsigned *)(0x400A8028))
#define rI2STXMODE      (*(volatile unsigned *)(0x400A8030))
 
#define rI2SDMA1        (*(volatile unsigned *)(0x400A8014))
#define rI2SDMA2        (*(volatile unsigned *)(0x400A8018))
#define rI2SSTATE       (*(volatile unsigned *)(0x400A8010))
#define rI2SIRQ         (*(volatile unsigned *)(0x400A801C))
 
#define rI2SDAI         (*(volatile unsigned *)(0x400A8004))
#define rI2SRXFIFO      (*(volatile unsigned *)(0x400A800C))
#define rI2SRXRATE      (*(volatile unsigned *)(0x400A8024))
#define rI2SRXBITRATE   (*(volatile unsigned *)(0x400A802C))
#define rI2SRXMODE      (*(volatile unsigned *)(0x400A8034))
 
#define rIOCON_P0_07	(*(volatile unsigned *)(0x4002C01C))
#define rIOCON_P0_08	(*(volatile unsigned *)(0x4002C020))
#define rIOCON_P0_09	(*(volatile unsigned *)(0x4002C024))
#define rIOCON_P1_16	(*(volatile unsigned *)(0x4002C0C0))
 
#define UDA1380_ADDRESS 0x1A
 
void Uda1380_WriteData(unsigned char reg, unsigned short int data)
{
    unsigned char config[3];
    
    config[0] = reg;
    config[1] = (data >> 8) & 0xFF;    //MS
    config[2] = data&0xFF;             //LS
    
    
    I2C0_MasterTransfer(UDA1380_ADDRESS, config, sizeof(config), 0, 0);
    
    I2C0_MasterTransfer(UDA1380_ADDRESS, config, 1, &config[1], 2);     //校验写入的数据是否正确
    if((config[1]<<8|config[2]) != data)
    {
        while(1);   //写入和读出的数据不一致
    }
}
 
void Uda1380_config()
{
    I2C0_Init();
    
    Uda1380_WriteData(0x7F, 0x0);         //restore L3-default values
    Uda1380_WriteData(0x01, 0x0);         //数据格式为标准的I2S格式
    
    Uda1380_WriteData(0x13, 0x0);         //配置音频的输出
    Uda1380_WriteData(0x14, 0x0);           
 
    Uda1380_WriteData(0x00, 0x2|0x1<<8|0x1<<9);     //使能DAC的时钟,选择使用SYSCLK产生128fs的时钟
    Uda1380_WriteData(0x02,0x1<<15|0x1<<13|0x1<<10|0x1<<8); //使能DAC 电源
    
}
 
int main(void)
{   
    unsigned int count=0, i;
    unsigned char flag=1;
    
    rIOCON_P0_07 = (rIOCON_P0_07&(~0x3))|0x1;   //I2S_TX_SCK
    rIOCON_P0_08 = (rIOCON_P0_08&(~0x3))|0x1;   //I2S_TX_WS
    rIOCON_P0_09 = (rIOCON_P0_09&(~0x3))|0x1;   //I2S_TX_SDA
    rIOCON_P1_16 = (rIOCON_P1_16&(~0x3))|0x2;   //I2SMCLK
    rPCONP |= 0x1<<27;
    
    rI2SDAO = (16 - 1)<<6 | 0x1<<4 | 0x1<<3  |0x1;  //16位, 立体音, 禁止发送
 
    rI2STXMODE |= 0x1<<3;       //使能MCLK输出,使TX_REF输出到UDA1380的SYSCLK引脚
    
    rI2STXRATE = 0x1<<8|0x1;    //配置分数速率寄存器 得到TX_REF=CCLK/(1/1)/2
    rI2STXBITRATE = CCLK/2/(44100*2*16) - 1;  //44.1KHZ采样16位
    
    for(i = 0; i <0x1000000; i++);  //延时 等待UDA1380内部通过SYSCLK产生稳定的128fs提供插值滤波和抽取滤波使用
    
    Uda1380_config();
    
    rI2SDAO &= ~ (1<<4);
    rI2SDAO &= ~ (1<<3);
    rI2SDAO &= ~ (1<<15);   //启动I2S数据传输
    
    while(flag)
    {
        if(((rI2SSTATE>>16)&0xFF)<=4)       //如果发送FIFO中的数据小于或等于4个字
        {
            for(i=0; i<8-(((rI2SSTATE>>16)&0xFF)); i++)     //将FIFO填充到8个字
            {
                rI2STXFIFO = *(unsigned int *)(audio + count);  //转换成int类型的指针,从指针指向的位置读取32位数据
                
                count+=4;               //读取一个字,相当于读取char类型数组中的4个元素
               
                if(count>=sizeof(audio)) //数组中的数据发送完
                {
                    flag = 0;
                    break;
                }
            }
        }
    }
    
    rI2SDAO |= 0x1<<3|0x1<<4;   //停止I2S传输
    
    return 0;
}

下面对程序需要注意的进行说明

  • i2c.h中是上一篇介绍I2C总线中所用的函数,aduio.h中存放的是音频数据的数组,const unsigned char audio[]={0,0,0,0,0,…

  • I2C总线每次发送的数据为1个字节,而UDA1380的寄存器为16位,因此我们先发送高字节然后再发送低字节,具体的时序可以参考UDA1380的数据手册。

  • 程序中配置发送控制寄存器I2STXMODE使能了MCLK输出TX_REF的时钟到UDA1380的SYSCLK引脚,而UDA1380中配置成使用该时钟产生内部滤波器需要的128fs的时钟。在图中标志中说明running at …因此在程序中配置UDA1380之前,使用了一个for延时,用于等待UDA1380内部产生稳定的128fs时钟。只有这样才能正确的配置0x10之后的滤波器相关寄存器。否则对0x10之后的滤波器相关寄存器操作会失败。这点没有验证,但是在debug调试的时候可以正常的有声音输出,但是下载到板子上运行,则没有效果。如果去掉for循环延时效果也不正常发音。如果不使能MCLK输出,则写0x13寄存器的值不会成功,读取该寄存器的值永远都是其默认值。因此推测和UDA1380的SYSCLK产生内部滤波器使用的128fs时钟有关。

本文章转载自 Cortex-M3 (NXP LPC1788)之IIS控制器

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值