s3c2440裸板实现播放音乐

        之前已经写了一篇文章《3线接口与wm8976声卡驱动》,但那不是裸板驱动,只是修改了声卡uda1314的硬件部分的代码,移植过来的,需要在内核下其他模块的支持,才把wm8976用起来了。为了更深入的理解声卡的工作过程,这次不止写了wm8976的裸板驱动,还有IIS和DMA的编程,并利用它们一起来实现裸板播放音乐的功能。也是说把存储在nand    flash上的一首歌(.wav格式)通过声卡wm8976播放出来,就是我这次实验的目的。

        要想实现这件事,只需要把wm8976配置好,然后把声音数据通过IIS发送给wm8976即可,作为声卡编解码芯片,  wm8976会自动把音频数据解码,把模拟信号作用在扬声器上,发出音乐来。
这里涉及两个问题:
1、配置wm8976就是要读写wm8976内部的寄存器,通过什么办法从wm8976的寄存器中读取数据或往其中写入数据?
2、wm8976的IIS数据线显然连接在开发板的IIS总线上,IIS控制器从哪里获得音频数据?
完全回答这两个问题,就达到了播放音乐的目的了。
        实际上这里wm8976硬件驱动的内容不多,更多的内容是使用wm8976播放音乐的过程中涉及的其他问题,具体就是  IIS和DMA的使用问题。
        首先,wm8976的控制数据和音频数据是分开传输的,wm8976的控制数据由3线接口来传输,而音频数据专门由IIS接口负责传输。

先看第一个问题:
JZ2440上的wm8976的3线接口是接着s3c2440的GPIO上的,用这个GPIO模拟3线接口的时序即可。

3线接口时序:

刚开始全部设为高电平:
set_csb(1);
set_dat(1);
set_clk(1);

然后第一个数据:
set_clk(0);
set_dat(1);
delay();
set_clk(1);

至于具体写入什么内容到wm8976内部寄存器来配置其工作方式,这里就不阅读其芯片手册了,直接把别人的东西拿来用:

初始化wm8976:

	/* software reset */
	wm8976_write_reg(0, 0);

	/* OUT2的左/右声道打开
	 * 左/右通道输出混音打开
	 * 左/右DAC打开
	 */
	wm8976_write_reg(0x3, 0x6f);
	
	wm8976_write_reg(0x1, 0x1f);//biasen,BUFIOEN.VMIDSEL=11b  
	wm8976_write_reg(0x2, 0x185);//ROUT1EN LOUT1EN, inpu PGA enable ,ADC enable

	wm8976_write_reg(0x6, 0x0);//SYSCLK=MCLK  
	wm8976_write_reg(0x4, 0x10);//16bit 		
	wm8976_write_reg(0x2B,0x10);//BTL OUTPUT  
	wm8976_write_reg(0x9, 0x50);//Jack detect enable  
	wm8976_write_reg(0xD, 0x21);//Jack detect  
	wm8976_write_reg(0x7, 0x01);//Jack detect 

这样就通过3线接口配置好wm8976了,这部分还是比较简单的。

看第二个问题:

        这里的实现方法是把声音从nand拷贝到SDRAM,再通过DAM把声音从SDRAM传输到IIS的发送FIFO寄存器。文件是如何放在nand中的呢?烧写,把wav文件像烧写内核或文件系统那样下载到nand中。这里把文件烧在nand的0x60000。

        代码中用到read_wav()函数:把wav文件从nand中读到SDRAM中,并获取文件头部信息。其中要用到nand_read()函数,这个就不具体说,不从零写了,nand等硬件的裸板驱动,  复制之前写BootLoader部分的代码来支持。
        nand_read()的调用:      nand_read(0x60000, (unsigned char *)wav_buf, 0x200000);
其中wav_buf是SDRAM的起始地址0x30000000。

现在文件就处在SDRAM 中了,下面通过DMA把它传输到IIS的FIFO寄存器。

dma初始化部分的代码:

#define DMA0_BASE_ADDR  0x4B000000
#define DMA1_BASE_ADDR  0x4B000040
#define DMA2_BASE_ADDR  0x4B000080
#define DMA3_BASE_ADDR  0x4B0000C0

struct s3c_dma_regs {
	unsigned long disrc;
	unsigned long disrcc;
	unsigned long didst;
	unsigned long didstc;
	unsigned long dcon;
	unsigned long dstat;
	unsigned long dcsrc;
	unsigned long dcdst;
	unsigned long dmasktrig;
};

struct s3c_dma_regs *dma_regs;

static int dma_src_rep;
static int dma_len_rep;
    
/* 数据传输: 源,目的,长度 */
void dma_init(unsigned int src, unsigned int len)
{
    dma_src_rep = src;//0x30000000
    dma_len_rep = len;
    
    dma_regs = (struct s3c_dma_regs *)DMA2_BASE_ADDR;
    
	/* 把源,目的,长度告诉DMA */
	dma_regs->disrc      = src;        /* 源的物理地址 */
	dma_regs->disrcc     = (0<<1) | (0<<0); /* 源位于AHB总线, 源地址递增 */
	dma_regs->didst      = 0x55000010;        /* 目的的物理地址,IIS FIFO寄存器地址 */
	dma_regs->didstc     = (0<<2) | (1<<1) | (1<<0); /* 目的位于APB总线, 目的地址不变 */
	dma_regs->dcon       = (1<<31)|(0<<30)|(1<<29)|(0<<28)|(0<<27)|(0<<24)|(1<<23)|(1<<20)|(len/2);  /* 使能中断,单个传输,硬件触发 */
}

DMA相关知识:
DMA传输:包含源,目的,传输总大小,每次传输的大小(字节、半字、字)、传输计数等,源/目的地址增、减、不变。
DMA方向:外设到内存,内存到外设。
        DMA和那些片上接口(IIS,SPI等)都是片上的,通过配置寄存器操作,IIS外设,SPI外设接在这些接口上,通过这些接口控制器通信。DMA与那些接口的控制器也是有联系的,他们之间的传输也有一定的协议,把数据拷到那些控制器,再由控制器组织数据发送给外设。协议的东西,硬件上已经做好,我们要做的只是配置寄存器,让他们自动执行。

        需要注意的是平时写驱动,写spi,iis,uart等驱动,是CPU与这些接口的控制器交互数据,现在是dma与那些控制器交互数据,dma 和那些控制器都是需要CPU来配置好,然后让他们自动拷贝,拷完了就告诉CPU,作相应处理。由于与控制器的交互的对象由CPU改为dma,所以对那些接口的控制器(IIS、SPI控制器等)的配置也是不一样的。具体的配置内容不做分析了,看芯片手册好了。

IIS初始化部分代码:

void iis_init(int bits_per_sample, int fs)
{
    int tmp_fs;
    int i;
    int min = 0xffff;
    int pre = 0;

    /* 配置GPIO用于IIS */
    GPECON &= ~((3<<0) | (3<<2) | (3<<4) | (3<<6) | (3<<8));
    GPECON |= ((2<<0) | (2<<2) | (2<<4) | (2<<6) | (2<<8));
    
    
    /* bit[9] : Master clock select, 0-PCLK
     * bit[8] : 0 = Master mode
     * bit[7:6] : 10 = Transmit mode
     * bit[4] : 0-IIS compatible format
     * bit[2] : 384fs, 确定了MASTER CLOCK之后, fs = MASTER CLOCK/384
     * bit[1:0] : Serial bit clock frequency select, 32fs
     */
     
    if (bits_per_sample == 16)
        IISMOD = (2<<6) | (0<<4) | (1<<3) | (1<<2) | (1);
    else
        IISMOD = (2<<6) | (0<<4) | (0<<3) | (1<<2) | (1);

    /* Master clock = PCLK/(n+1)
     * fs = Master clock / 384
     * fs = PCLK / (n+1) / 384
     */
    for (i = 0; i <= 31; i++)
    {
        tmp_fs = PCLK/384/(i+1);
        if (ABS(tmp_fs, fs) < min)
        {
            min = ABS(tmp_fs, fs);
            pre = i;
        }
    }
    IISPSR = (pre << 5) | (pre);

    /*
     * bit15 : Transmit FIFO access mode select, 1-DMA
     * bit13 : Transmit FIFO, 1-enable
     */
    IISFCON = (1<<15) | (1<<13);
    
    /*
     * bit[5] : Transmit DMA service request, 1-enable
     * bit[1] : IIS prescaler, 1-enable
     */
    IISCON = (1<<5) | (1<<1) ;
}

下面是s3c2440的IIS控制器的一些相关知识:
64字节 FIFO (TxFIFO 和 和 RxFIFO ):发送数据传输中,数据被写入到 TxFIFO;接收数据传输中,从 RxFIFO读取数据。
16位移位寄存器(SFTR ):发送模式中并行数据被移位到串行数据输出,并且接收模式中串行数据输入被移位到并行数据。

传输模式:
IISMOD[7:6] : 
01-接收模式 
10-发送模式 
11-发送和接收模式,In this mode, IIS bus interface can transmit and receive data simultaneously.

发送FIFO/接收FIFO访问模式选择位:IISFCON[15]/IISFCON[14]
1、正常模式:0,IIS的FIFO与CPU通信。
    IIS 控制寄存器包含 发送FIFO和接收 FIFO 的FIFO就绪标志位,当FIFO准备传输数据时,如果发射的FIFO不是空的,则FIFO就绪的标志设置为“1”。如果传送的FIFO是空的,则FIFO就绪标志设置为“0”。接收中的 FIFO 未满,接收 FIFO 的FIFO 就绪标志设置为'1';它表明 FIFO 是否准备好了接收数据。CPU可以根据这些标志来决定什么时候来读写FIFO。
2、dma模式: 1,IIS的FIFO与dma通信。
    In this mode, transmit or receive FIFO is accessible by the DMA controller. DMA service request in transmit or receive mode is made by the FIFO ready flag automatically.

本例使用的是发送FIFO,DMA模式:IISFCON[15]=1。dma传输中的目的地址是0x55000010,是发送FIFO入口地址。

        好,现在准备好了音乐文件,即从nand上复制到SDRAM了,又初始化好了dma和iis,我们再用3线接口读写wm8976的内部寄存器来初始化它。然后我们启动dma和iis,dma就会自动的从SDRAM中拷贝音乐到iis的发送FIFO,这些数据会被传到IIS的16位移位寄存器,iis控制器把它一位一位的出现在IISDO上,wm8976接收数据解码,转为模拟信号播放,这样就可以听到音乐了。这些都是自动执行,不需CPU参与。

裸板中的main()函数的大概流程:

   read_wav(wav_buf, &fs, &channels, &bits_per_sample, &wav_size);
   /* soc init 
    * 初始化IIS之后它才会给codec芯片发送时钟
    * codec芯片才能被使用
    */
   iis_init(bits_per_sample, fs);
   dma_init(wav_buf, wav_size);
  
   /* machine init */
   jz2440_init();	//配置为3线
                
   /* codec init */
   wm8976_init();
   
   set_volume(volume);
        
   dma_start();
   iis_start();
   
   iis_stop();
   dma_stop();

猜你喜欢

转载自blog.csdn.net/qq_22863733/article/details/81285998