Blackfin DSP(八):1D DMA與音頻處理模板

1.DMA產生的背景html

  在許多須要使用DSP 的場合,通常都須要大量的數據搬移工做,而若是每次數據搬移都由DSP 內核來參與完成,將大大佔用DSP 內核的處理時間,從而嚴重影響其信號處理能力。所以,Blackfin DSP 集成了直接訪問(DMA)控制器來完成數據搬移這種簡單卻耗時的工做。它能夠直接進行數據搬移而不須要內核的參與。編程

說說我對DMA的理解:其實我以爲DMA不算難,反而十分便利,將它想象成城市供水局,爲了給城市中不一樣的小區供水,最原始的辦法就是挨家挨戶的去送,這就耗費了大量的供水局(DSP內核)的人力物力,而當咱們建成了自來水管道(DMA通道),一端鏈接到自來水廠(數據源),一端鏈接到須要供水的小區(目的地),當須要供水時閥門一開(DMA_ENABLE),自來水(數據)就源源不斷的在管道中流動,不再須要其它人員的參與了,是否是很方便???下面就看看DSP中的這條管道是如何創建的。數組

2.BF533的DMA總線結構圖以下:ide

    

  從圖中能夠看出,DMA能夠在各類存儲器與外設之間直接進行數據傳輸。函數

3. DMA的種類oop

  DMA分爲基於寄存器的DMA和基於描述符的DMA:post

  1)基於寄存器的DMAui

    容許用戶直接對DMA寄存器進行編程,當DMA完成時,由配置寄存器中特定的位來決定接下來的動做:是重裝初始值,仍是自動中止。url

  2)基於描述符的DMAspa

    這種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變換結果:

相關文章
相關標籤/搜索