• Blackfin DSP(八):1D DMA与音频处理模板


    1.DMA产生的背景

      在许多需要使用DSP 的场合,一般都需要大量的数据搬移工作,而如果每次数据搬移都由DSP 内核来参与完成,将大大占用DSP 内核的处理时间,从而严重影响其信号处理能力。因此,Blackfin DSP 集成了直接访问(DMA)控制器来完成数据搬移这种简单却耗时的工作。它可以直接进行数据搬移而不需要内核的参与。

    说说我对DMA的理解:其实我觉得DMA不算难,反而十分便利,将它想象成城市供水局,为了给城市中不同的小区供水,最原始的办法就是挨家挨户的去送,这就耗费了大量的供水局(DSP内核)的人力物力,而当我们建成了自来水管道(DMA通道),一端连接到自来水厂(数据源),一端连接到需要供水的小区(目的地),当需要供水时阀门一开(DMA_ENABLE),自来水(数据)就源源不断的在管道中流动,再也不需要其它人员的参与了,是不是很方便???下面就看看DSP中的这条管道是如何建立的。

    2.BF533的DMA总线结构图如下:

        

      从图中可以看出,DMA可以在各种存储器与外设之间直接进行数据传输。

    3. DMA的种类

      DMA分为基于寄存器的DMA和基于描述符的DMA:

      1)基于寄存器的DMA

        允许用户直接对DMA寄存器进行编程,当DMA完成时,由配置寄存器中特定的位来决定接下来的动作:是重装初始值,还是自动停止。

      2)基于描述符的DMA

        这种DMA要求我们先将所要设置的寄存器值存储在内存单元中,这组参数就叫做描述符,然后配置Current Descriptor Pointer寄存器和NEXT_DESC_PTR寄存器指向这组描述符,当DMA开始时,内核会自动控制将这组描述符按顺序载入到DMA控制寄存器中,从而完成对DMA的初始化。

        当然,描述符的排列顺序是有要求的,根据不同的排列顺序就衍生出了以下几种描述符的排列方式:Array mode,small mode和Large model,直接从图就可以看出他们的区别,就不再用语言描述了。

        

        在本音频处理模板中,采用的是直接配置寄存器的方法。

    4.如何配置寄存器

      对于不同的传输模式,需要配置的寄存器也不同。每种模式至少需要配置以下寄存器:

        

     以下例程将DMA与SPORT相连,接收SPORT传递来的数据,同时也通过SPORT将数据发送出去:

    //--------------------------------------------------------------------------//
    // Function:    Init_DMA                                                    //
    //                                                                            //
    // Description:    Initialize DMA1 in autobuffer mode to receive and DMA2 in    //
    //                autobuffer mode to transmit                                    //
    //--------------------------------------------------------------------------//
    void Init_DMA(void)
    {
        // Set up DMA1 to receive
        // Map DMA1 to Sport0 RX
        *pDMA1_PERIPHERAL_MAP = 0x1000;
    
        // Configure DMA1
        // 32-bit transfers, Interrupt on completion, Autobuffer mode
        *pDMA1_CONFIG = WNR | WDSIZE_32 | DI_EN | 0x1000;
        // Start address of data buffer
        *pDMA1_START_ADDR = (void *)iRxBuffer1;
        // DMA inner loop count
        *pDMA1_X_COUNT = 4;
        // Inner loop address increment
        *pDMA1_X_MODIFY = 4;
    
    
        // Set up DMA2 to transmit
        // Map DMA2 to Sport0 TX
        *pDMA2_PERIPHERAL_MAP = 0x2000;
    
        // Configure DMA2
        // 32-bit transfers, Autobuffer mode
        *pDMA2_CONFIG = WDSIZE_32 | 0x1000;
        // Start address of data buffer
        *pDMA2_START_ADDR = (void *)iTxBuffer1;
        // DMA inner loop count
        *pDMA2_X_COUNT = 4;
        // Inner loop address increment
        *pDMA2_X_MODIFY = 4;
    }

     这里最需要说明的就是COUNT和MODIFY的值。COUNT指定了每次要读取/发送的element的数量,这个element就是CONFIG寄存器中的WDSIZE_32,也就是说,当COUNT=4时,我们共要传输的数据为4个32位字。因此需要的缓冲的大小为4×32 = 4×sizeof(int),由此可以推断程序中iTxBuffer1的定义为:

    int iTxBuffer1[4];

    而MODIFY的值指出,当每次COUNT值减1时,也就是每次传输完一个elements时,指针移动的字节数,注意,是字节数!此时MODIFY的值至少为4,否则就会发生覆盖现象。示意图如下:

        

    当传输完毕后,由于设置的是Autobuffer 模式,当这4个elements传输结束后,参数寄存器会自动重载当前寄存器的值,0延时的重新开始下一次传输。因此DMA的开启和停止都需要手动来完成:

    //--------------------------------------------------------------------------//
    // Function:    Enable_DMA_Sport                                            //
    //                                                                            //
    // Description:    Enable DMA1, DMA2, Sport0 TX and Sport0 RX                    //
    //--------------------------------------------------------------------------//
    void Enable_DMA_Sport0(void)
    {
        // enable DMAs
        *pDMA2_CONFIG    = (*pDMA2_CONFIG | DMAEN);
        *pDMA1_CONFIG    = (*pDMA1_CONFIG | DMAEN);
    
        // enable Sport0 TX and RX
        *pSPORT0_TCR1     = (*pSPORT0_TCR1 | TSPEN);
        *pSPORT0_RCR1     = (*pSPORT0_RCR1 | RSPEN);
    }

    以上就是DMA的设置过程,下面趁热打铁,利用上述设置,介绍音频处理模板。

    <<<<<<<<<<<<<<<<<<<<<<<<<   分隔符  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    本模板使用ezkit-533,通过SPI接口将AD1836配置为I2S模式,然后利用SPORT0口接收数据的采样值,并通过DMA传输给DSP,DSP处理后,再通过SPORT0口传回DAC,输出波形。配置的过程如下:

      1)EBIU初始化,见Blackfin DSP(三):BF533 的EBIU接口之flashBlackfin DSP(四):BF533 EBIU之SDRAM
      2)SPI初始化,见Blackfin DSP(五):BF533的SPI接口

      3)SPORT0初始化,见Blackfin DSP(六):BF533的SPORT接口

      4)DMA setup;

      5)配置系统中断;

    void main(void)
    {
    
        Set_PLL(10,2);          //初始化PLL
        Init_EBIU();            //初始化EBIU,与flash接口
        Init_SDRAM();           //初始化SDRAM
        ezConfigureFlashA();    //配置板载flash的IO口方向,主要为了引脚复位AD1836
    
        Init1836();
        Init_Sport0();
        Init_DMA();
        Init_Interrupts();
        Enable_DMA_Sport0();
    
        while(1);
    
    }

    对于AD1836的配置如下:

    // names for codec registers, used for sCodec1836TxRegs[]
    #define DAC_CONTROL_1        0x0000
    #define DAC_CONTROL_2        0x1000
    #define DAC_VOLUME_0        0x2000
    #define DAC_VOLUME_1        0x3000
    #define DAC_VOLUME_2        0x4000
    #define DAC_VOLUME_3        0x5000
    #define DAC_VOLUME_4        0x6000
    #define DAC_VOLUME_5        0x7000
    #define ADC_0_PEAK_LEVEL    0x8000
    #define ADC_1_PEAK_LEVEL    0x9000
    #define ADC_2_PEAK_LEVEL    0xA000
    #define ADC_3_PEAK_LEVEL    0xB000
    #define ADC_CONTROL_1        0xC000
    #define ADC_CONTROL_2        0xD000
    #define ADC_CONTROL_3        0xE000
    
    // names for slots in ad1836 audio frame
    #define INTERNAL_ADC_L0            0
    #define INTERNAL_ADC_R0            2
    #define INTERNAL_DAC_L0            0
    #define INTERNAL_DAC_R0            2
    #define INTERNAL_ADC_L1            1
    #define INTERNAL_ADC_R1            3
    #define INTERNAL_DAC_L1            1
    #define INTERNAL_DAC_R1            3
    
    volatile short sCodec1836TxRegs[CODEC_1836_REGS_LENGTH] =
    {
                        DAC_CONTROL_1    | 0x000,
                        DAC_CONTROL_2    | 0x000,
                        DAC_VOLUME_0    | 0x3ff,
                        DAC_VOLUME_1    | 0x3ff,
                        DAC_VOLUME_2    | 0x3ff,
                        DAC_VOLUME_3    | 0x3ff,
                        DAC_VOLUME_4    | 0x000,
                        DAC_VOLUME_5    | 0x000,
                        ADC_CONTROL_1    | 0x000,
                        ADC_CONTROL_2    | 0x000,
                        ADC_CONTROL_3    | 0x000
    
    };
    
    void Init1836(void)
    {
        int i;
        int j;
        static unsigned char ucActive_LED = 0x01;
    
        // write to Port A to reset AD1836
        *pFlashA_PortA_Out = 0x00;
    
        // write to Port A to enable AD1836
        *pFlashA_PortA_Out = ucActive_LED;
    
        // wait to recover from reset
        for (i=0; i<0xf0000; i++) asm("nop;");
    
        // Enable PF4
        *pSPI_FLG = FLS4;
        // Set baud rate SCK = HCLK/(2*SPIBAUD) SCK = 2MHz
        *pSPI_BAUD = 16;
        // configure spi port
        // SPI DMA write, 16-bit data, MSB first, SPI Master
        *pSPI_CTL = 0x0003 | SIZE | MSTR;
    
        // Set up DMA5 to transmit
        // Map DMA5 to SPI
        *pDMA5_PERIPHERAL_MAP    = 0x5000;
    
        // Configure DMA5
        // 16-bit transfers
        *pDMA5_CONFIG = WDSIZE_16;
        // Start address of data buffer
        *pDMA5_START_ADDR = (void *)sCodec1836TxRegs;
        // DMA inner loop count
        *pDMA5_X_COUNT = CODEC_1836_REGS_LENGTH;
        // Inner loop address increment
        *pDMA5_X_MODIFY = 2;
    
        // enable DMAs
        *pDMA5_CONFIG = (*pDMA5_CONFIG | DMAEN);
        // enable spi
        *pSPI_CTL = (*pSPI_CTL | SPE);
    
        // wait until dma transfers for spi are finished
        for (j=0; j<0xaff0; j++) asm("nop;");
    
        // disable spi
        *pSPI_CTL = 0x0000;
    }

    SPORT0的初始化:

    //--------------------------------------------------------------------------//
    // Function:    Init_Sport0                                                    //
    //                                                                            //
    // Description:    Configure Sport0 for I2S mode, to transmit/receive data     //
    //                to/from the AD1836. Configure Sport for external clocks and //
    //                frame syncs.                                                //
    //--------------------------------------------------------------------------//
    void Init_Sport0(void)
    {
        // Sport0 receive configuration
        // External CLK, External Frame sync, MSB first, Active Low
        // 24-bit data, Stereo frame sync enable
        *pSPORT0_RCR1 = RFSR | RCKFE;
        *pSPORT0_RCR2 = 0x0017 | RXSE | RSFSE;
    
        // Sport0 transmit configuration
        // External CLK, External Frame sync, MSB first, Active Low
        // 24-bit data, Secondary side enable, Stereo frame sync enable
        *pSPORT0_TCR1 = TFSR | TCKFE;
        *pSPORT0_TCR2 = 0x0017 | TXSE | TSFSE;
    }

    每一次DMA传输完毕后,都会进入中断函数,调用Process_data()进行数据处理,系统中断配置:

    //--------------------------------------------------------------------------//
    // Function:    Init_Interrupts                                                //
    //                                                                            //
    // Description:    Initialize Interrupt for Sport0 RX                            //
    //--------------------------------------------------------------------------//
    void Init_Interrupts(void)
    {
        // Set Sport0 RX (DMA1) interrupt priority to 2 = IVG9
        *pSIC_IAR0 = 0xffffffff;
        *pSIC_IAR1 = 0xffffff2f;
        *pSIC_IAR2 = 0xffffffff;
    
        // assign ISRs to interrupt vectors
        // Sport0 RX ISR -> IVG 9
        register_handler(ik_ivg9, Sport0_RX_ISR);
    
        // enable Sport0 RX interrupt
        *pSIC_IMASK = 0x00000200;
    }
    
    //--------------------------------------------------------------------------//
    // Function:    Sport0_RX_ISR                                                //
    //                                                                            //
    // Description: This ISR is executed after a complete frame of input data     //
    //                has been received. The new samples are stored in             //
    //                iChannel0LeftIn, iChannel0RightIn, iChannel1LeftIn and         //
    //                iChannel1RightIn respectively.  Then the function             //
    //                Process_Data() is called in which user code can be executed.//
    //                After that the processed values are copied from the         //
    //                variables iChannel0LeftOut, iChannel0RightOut,                 //
    //                iChannel1LeftOut and iChannel1RightOut into the dma         //
    //                transmit buffer.                                            //
    //--------------------------------------------------------------------------//
    EX_INTERRUPT_HANDLER(Sport0_RX_ISR)
    {
        // confirm interrupt handling
        *pDMA1_IRQ_STATUS = 0x0001;
    
        // copy input data from dma input buffer into variables
        iChannel0LeftIn = iRxBuffer1[INTERNAL_ADC_L0];
        iChannel0RightIn = iRxBuffer1[INTERNAL_ADC_R0];
        iChannel1LeftIn = iRxBuffer1[INTERNAL_ADC_L1];
        iChannel1RightIn = iRxBuffer1[INTERNAL_ADC_R1];
    
        // call function that contains user code
        Process_Data();
    
        // copy processed data from variables into dma output buffer
        iTxBuffer1[INTERNAL_DAC_L0] = iChannel0LeftOut;
        iTxBuffer1[INTERNAL_DAC_R0] = iChannel0RightOut;
        iTxBuffer1[INTERNAL_DAC_L1] = iChannel1LeftOut;
        iTxBuffer1[INTERNAL_DAC_R1] = iChannel1RightOut;
    }

    这里的Process_Data()可以按照自己的需求添加信号处理函数,下面代码只是将连续的256个int32_t类型的音频采样点数据保存到数组中,输入和输出都只取了一个通道,即通道0的右声道。

    int32_t audio_data[256];
    uint16_t cnt=0;
    
    void Process_Data(void)
    {
        cnt %= 256;
        if(cnt<256)
        {
            audio_data[cnt] = (iChannel0RightIn<<8);  
            cnt ++ ;
        }
    
        //iChannel0LeftOut = iChannel0LeftIn;
        iChannel0RightOut = iChannel0RightIn; //原样输出
        //iChannel1LeftOut = iChannel1LeftIn;
        //iChannel1RightOut = iChannel1RightIn;
    }

    注意这里的 (iChannel0RightIn<<8);  这是因为AD1836的数据是24位的,符号位在第24位上,而我们的数组是int32的,所以需要左移8位将符号位放置到最高位上。

    用IDE的plot功能将audio_data[256]显示出来,结果如下:

      

    如果继续相对数据进行其他处理比如FFT,做一个“if(cnt==256)  FFT_func(audio_data[256]);” 运算即可;

    FFT变换结果:

  • 相关阅读:
    MSMQ简例
    C#观察者模式简例
    C#常见算法题目(面试准备)
    HttpWebRequest
    自定义Attribute简例
    .Net下的 ORM框架介紹
    for xml path的应用
    .net中日至框架log4net.dll如何使用
    动态载入.ascx用户控件
    wap 2.0 编写规范
  • 原文地址:https://www.cnblogs.com/BitArt/p/3305220.html
Copyright © 2020-2023  润新知