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接口之flash和Blackfin 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變換結果: