DMA簡介
DMA全稱為Direct Memory Access,即直接存儲器訪問。在進行DMA傳輸前,CPU將總線控制權交給DMA,通過共享系統總線,實現無需CPU參與的快速數據傳輸,能夠直接將數據從一個地址空間復制到另一個地址空間,DMA在數據傳輸結束后將總線控制權交回CPU。
MM32F0140內置5路通用DMA,可以管理設備到存儲器、存儲器到設備與存儲器到存儲器的數據傳輸,每個通道都有專門的硬件DMA請求邏輯,也可以通過軟件配置來觸發每個通道。
MM32F0140的DMA模塊所支持的外設類型包括UART、I2C、SPI、ADC與通用、高級和基礎定時器,當DMA從外設產生的請求通過邏輯或輸入DMA控制器時,為避免沖突,在一個通道中,同時只能有一個外設DMA請求有效,詳細各通道DMA請求如圖1所示。
圖1.各通道DMA外設請求
DMA配置
DMA的配置涉及到傳輸模式、數據寬度、外設地址、存儲器地址、通道優先級、數據傳輸數量、中斷使能、自動重裝載及指針增量。
傳輸模式
存儲器到外設模式
配置DMA_CCRx寄存器(x由1 ~ 7)的DIR位選擇傳輸方向,該位置1,傳輸方向為從存儲器讀;DMA_CCRx寄存器的MEM2MEM位置0,關閉存儲器到存儲器模式。
外設到存儲器模式
配置DMA_CCRx寄存器的DIR位選擇傳輸方向,該位置0,傳輸方向為從外設讀;DMA_CCRx寄存器的MEM2MEM位置0,關閉存儲器到存儲器模式。
存儲器到存儲器模式
數據傳輸方向為外設地址到存儲器地址:DMA_CCRx寄存器的MEM2MEM位置1,使能存儲器到存儲器模式;DIR位置0,從外設讀。
數據傳輸方向為存儲器地址到外設地址:DMA_CCRx寄存器的MEM2MEM位置1,使能存儲器到存儲器模式;DIR位置1,從存儲器讀。
注意,存儲器到存儲器模式不能與循環模式同時使用。
循環模式
若需要循環讀寫緩沖區或進行連續的數據傳輸,則可以進入循環模式。配置DMA通道x配置寄存器(DMA_CCRx)中的CIRC位為1,使能循環模式。在循環模式下,若DMA傳輸數量遞減為0,則重新加載先前配置的數值,繼續進行DMA數據傳輸。
自動重新加載
DMA通道x配置寄存器(DMA_CCRx)的ARE位控制自動重裝載,若ARE位置1,則使能自動重裝載傳輸數量,當DMA通道x傳輸數量寄存器(DMA_CNDTRx)中數值為0時,會自動將DMA_CNDTRx寄存器中的值加載為之前配置的數值;若ARE位置0,則禁止自動重裝載傳輸數量。
數據寬度
DMA的數據寬度配置包含:存儲器數據寬度配置與外設數據寬度配置,可獨立配置為字節、半字、全字。
存儲器數據寬度由DMA通道x配置寄存器(DMA_CCRx)的MSIZE位控制,MSIZE[1:0]為00則數據寬度為8bit,MSIZE[1:0]為01則數據寬度為16bit,MSIZE[1:0]為10則數據寬度為32bit。
外設數據寬度配置由DMA通道x配置寄存器(DMA_CCRx)的PSIZE位控制,可配置為8bit, 16bit或32bit,MSIZE位與PSIZE位如圖2所示。
圖2.數據寬度對應位
存儲器/外設地址
DMA的地址配置包含:存儲器地址的配置與外設地址的配置。
對DMA通道x存儲器地址寄存器(DMA_CMARx)進行賦值,從而配置存儲器地址,存儲器地址可作為數據傳輸的源或目標。
對DMA通道x外設地址寄存器(DMA_CPARx)進行賦值,從而配置外設地址,外設地址是外設數據寄存器的基地址,作為數據傳輸的源或目標。
源和目標地址必須根據各自配置的數據傳輸寬度對齊。
指針增量
指針增量配置:每次傳輸后指針自動向后遞增或保持不變。
操作DMA通道x配置寄存器(DMA_CCRx)的MINC位,若MINC位置1,則DMA配置為存儲器地址遞增模式,存儲器的訪問地址可以按照步長累加,不需要每次都去設置訪問地址;若MINC位置0,則每次DMA傳輸固定訪問同一個地址。
設置DMA通道x配置寄存器(DMA_CCRx)的PINC位,若PINC位置1,則DMA配置為外設地址遞增模式,外設的訪問地址可以按照步長累加;若PINC位置0,則每次DMA傳輸固定訪問同一個地址。
外設或存儲器配置為增量模式時,下一個要傳輸的地址將是前一個地址加上步長,步長取決于所選的數據寬度,首個傳輸的地址存放在DMA通道x外設地址寄存器(DMA_CPARx)/DMA通道x存儲器地址寄存器(DMA_CMARx)中。
優先級
仲裁器根據通道請求的優先級來啟動外設或存儲器的訪問,優先處理軟件優先級高的請求,當軟件優先級相同時,默認編號更低的通道優先處理。每個通道的優先級可在DMA通道x配置寄存器(DMA_CCRx)中的PL位配置,優先級分為四種:最高優先級(PL[1:0]=11)、高優先級(PL[1:0]=10)、中等優先級(PL[1:0]=01)與低優先級(PL[1:0]=00)。
數據傳輸數量
數據傳輸數量的最大值為65535,將數據傳輸數量值賦值到DMA通道x傳輸數量寄存器(DMA_CNDTRx),每次傳輸后,DMA_CNDTRx遞減,表示剩余多少次DMA傳輸。
當數值遞減為0時,數據全部傳輸完畢。若對應通道配置為自動重加載模式時,DMA_CNDTRx寄存器中的內容將被自動重新加載為之前配置時的數值。
中斷
每個通道支持3種事件標志:DMA半傳輸(HTIF)、DMA傳輸完成(TCIF)和DMA傳輸出錯(TEIF),各通道單獨的中斷請求由這3種事件標志邏輯或起來。可通過配置DMA通道x配置寄存器(DMA_CCRx)的TCIE位為1使能傳輸完成中斷,配置HTIE位為1使能半傳輸中斷,配置TEIE位為1使能傳輸錯誤中斷。
實驗
本實驗演示DMA burst搬運模式的中斷行為,配置DMA的傳輸模式為存儲器到存儲器模式且傳輸方向為從存儲器讀,開啟自動重裝載,寬度配置為全字,指針遞增,優先級為低優先級,設置兩數組為DMA數據傳輸地址,配置傳輸數量為16。DMA配置完成后,進行DMA數據傳輸,使能DMA通道,當產生半傳輸中斷,設置半傳輸標志為true,當產生傳輸完成中斷,設置傳輸完成標志為true,清除中斷標志位。本實驗可通過串口調試工具觀察數據傳輸現象,當DMA傳輸已到一半時串口打印"half",當DMA傳輸完成時串口打印"done"。
啟用外設時鐘 enable_clock()
實驗使用DMA1,并通過UART串口顯示實驗結果,因此需要啟用DMA與UART的外設時鐘。
void enable_clock() { /* Enable UART1 clock. */ RCC->APB2ENR |= RCC_APB2_PERIPH_UART1; /* Enable GPIOA clock. */ RCC->AHB1ENR |= RCC_AHB1_PERIPH_GPIOA; /* Enable DMA1 clock. */ RCC->AHB1ENR |= RCC_AHB1_PERIPH_DMA1; }
配置引腳 pin_init()
實驗現象通過串口顯示,因此配置UART的TX(PA9)與RX(PA10)引腳。
void pin_init() { /* Setup PA9, PA10. */ GPIOA->CRH = ~GPIO_CRH_MODE9_MASK; GPIOA->CRH |= GPIO_PinMode_AF_PushPull; /* PA9 multiplexed push-pull output. */ GPIOA->AFRH = ~GPIO_AFRH_AFR_MASK; GPIOA->AFRH |= (GPIO_AF_1 << GPIO_CRH_MODE9_SHIFT); /* Use AF1. */ GPIOA->CRH = ~GPIO_CRH_MODE10_MASK; GPIOA->CRH |= GPIO_PinMode_In_Floating; /* PA10 floating input. */ GPIOA->AFRH |= (GPIO_AF_1 << GPIO_CRH_MODE10_SHIFT); /* Use AF1. */ }
UART初始化 uart_init()
初始化UART,配置時鐘頻率、波特率、數據長度、停止位、傳輸模式及是否使用校驗。
void uart_init() { /* Clear the corresponding bit to be used. */ UART1->CCR = ~( UART_CCR_PEN_MASK | UART_CCR_PSEL_MASK | UART_CCR_SPB0_MASK | UART_CCR_SPB1_MASK | UART_CCR_CHAR_MASK ); UART1->GCR = ~( UART_GCR_AUTOFLOWEN_MASK | UART_GCR_RXEN_MASK | UART_GCR_TXEN_MASK ); /* WordLength. */ UART1->CCR |= UART_CCR_CHAR_MASK; /* XferMode. */ UART1->GCR |= (UART_XferMode_RxTx << UART_GCR_RXEN_SHIFT); /* Setup baudrate, BOARD_DEBUG_UART_FREQ = 48000000u, BOARD_DEBUG_UART_BAUDRATE = 9600u. */ UART1->BRR = (BOARD_DEBUG_UART_FREQ / BOARD_DEBUG_UART_BAUDRATE) / 16u; UART1->FRA = (BOARD_DEBUG_UART_FREQ / BOARD_DEBUG_UART_BAUDRATE) % 16u; /* Enable UART1. */ UART1->GCR |= UART_GCR_UARTEN_MASK; }
DMA初始化 dma_init()
操作DMA_CCRx寄存器,配置輸出傳輸模式、傳輸方向、外設和存儲器的增量模式、外設和存儲器的數據寬度、通道優先級和指針增量;操作DMA_CNDTRx寄存器,配置DMA傳輸數量,DMA傳輸完成一次,該數值減1,且在DMA傳輸期間DMA_CNDTRx寄存器不可被寫入;操作DMA_CPARx寄存器,配置外設寄存器地址,由于本實驗配置DMA傳輸方向為從存儲器讀,因此該外設地址在DMA傳輸時作為目標地址;操作DMA_CMARx寄存器,配置數據存儲器的地址,DMA傳輸時從該存儲器地址加載數據。
void dma_init() { uint32_t ccr = 0u; ccr |= DMA_CCR_DIR_MASK; /* Data transmission direction is: read from memory. */ | DMA_CCR_MEM2MEM_MASK; /* Xfer mode: memory to memory. */ | DMA_CCR_ARE_MASK; /* Enable automatic reloading. */ | DMA_CCR_PINC(DMA_AddrIncMode_IncAfterXfer) /* DMA_AddrIncMode_IncAfterXfer=1u, peripheral increment mode. */ | DMA_CCR_MINC(DMA_AddrIncMode_IncAfterXfer) /* Memory increment mode. */ | DMA_CCR_PSIZE(DMA_XferWidth_32b) /* DMA_XferWidth_32b = 2u, peripheral size 32 bits. */ | DMA_CCR_MSIZE(DMA_XferWidth_32b) /* Memory size 32 bits. */ | DMA_CCR_PL(DMA_Priority_Low) /* DMA_Priority_Low = 0u, low priority. */ ; DMA1->CH[0].CCR = ccr; /* channel number = 0 refer to DMA1_CH1. */ DMA1->CH[0].CNDTR = DMA_BUFF_COUNT; /* DMA_BUFF_COUNT = 16u, data transmission quantity. */ DMA1->CH[0].CPAR = (uint32_t)app_dma_buff_to; /* Set arry app_dma_buff_to as peripheral address. */ DMA1->CH[0].CMAR = (uint32_t)app_dma_buff_from; /* Set arry app_dma_buff_from as memory address. */ }
使能DMA enable_dma()
操作DMA_CCRx寄存器的ENABLE位,使能通道1,在通道使能后可進行DMA傳輸。
void enable_dma() { DMA1->CH[0].CCR |= DMA_CCR_EN_MASK; /* Enable DMA1 channel 1. */ }
使能中斷 enable_interrupt()
操作DMA_CCRx寄存器的TCIE位置1,使能傳輸完成中斷;HTIE位置1,使能半傳輸中斷。
void enable_interrupt() { DMA1->CH[0].CCR |= (DMA_CHN_INT_XFER_HALF_DONE 0xEu); /* DMA half transfer interrupt. */ DMA1->CH[0].CCR |= (DMA_CHN_INT_XFER_DONE 0xEu); /* DMA end of transfer interrupt. */ }
配置NVIC NVIC_EnableIRQ()
使用Cortex-M0 core_cm0.h頭文件中的NVIC_EnableIRQ使能中斷,由于DMA初始化時配置使用通道1,因此使用DMA1通道1全局中斷DMA1_CH1_IRQn.
NVIC_EnableIRQ(DMA1_CH1_IRQn)
編寫中斷服務程序DMA1_CH1_IRQHandler()
讀DMA中斷狀態寄存器(DMA_ISR)獲取當前中斷狀態,當產生半傳輸中斷,令定義的半傳輸標志app_dma_xfer_half_done為true;當產生傳輸完成中斷,令定義的傳輸完成標志app_dma_xfer_done為true。操作DMA中斷標志清除寄存器(DMA_IFCR)對應位置1,清除對應中斷。
void DMA1_CH1_IRQHandler() { uint32_t flags = DMA1->ISR 0xFu; if (flags DMA_CHN_INT_XFER_HALF_DONE) /* DMA half transfer interrupt. */ { app_dma_xfer_half_done = true; } if (flags DMA_CHN_INT_XFER_DONE) /* DMA end of transfer interrupt. */ { app_dma_xfer_done = true; } DMA1->IFCR = (flag 0xFu); /* Clear interrupt. */ }
main()函數
main()函數結合上述操作,初始化DMA,設置變量app_dma_xfer_done為傳輸完成標志,設置變量app_dma_xfer_half_done為DMA已傳輸一半的標志,設置數組app_dma_buff_from[]為源,即DMA傳輸時從該存儲器地址加載數據,設置數組app_dma_buff_to[]為數據存儲地址,設置變量DMA_BUFF_COUNT為數據傳輸數量,傳輸數量為16,每次傳輸后,該數值遞減,當數值遞減為0時,數據傳輸完畢,配置自動重加載模式,數據傳輸數量會被自動重新加載為之前配置時的數值。當檢測到半傳輸中斷標志產生,串口打印"half",檢測到傳輸完成中斷標志產生,串口打印"done"。本實驗在串口輸入數據時進行DMA傳輸,將源地址app_dma_buff_from[]中的數據通過DMA傳輸到app_dma_buff_to[]地址中。實驗現象如圖3所示。
int main() { uint8_t c; enable_clock(); pin_init(); uart_init(); printf("rndma_burst_interrupt example.rn"); dma_init(); NVIC_EnableIRQ(DMA1_CH1_IRQn); enable_interrupt(); for (uint32_t i = 0u; i < DMA_BUFF_COUNT; i++) { app_dma_buff_from[i] = i; app_dma_buff_to[i] = 0u; } while (1) { c = getchar(); putchar(c); for (uint32_t i = 0u; i < DMA_BUFF_COUNT; i++) { app_dma_buff_to[i] = 0u; } app_dma_xfer_done = false; app_dma_xfer_half_done = false; enable_dma(); while (!app_dma_xfer_half_done) {} printf("half.rn"); app_dma_xfer_half_done = false; while (!app_dma_xfer_done) {} printf("done.rn"); app_dma_xfer_done = false; } }
圖3.實驗現象
審核編輯:彭菁
-
存儲器
+關注
關注
38文章
7452瀏覽量
163598 -
cpu
+關注
關注
68文章
10824瀏覽量
211136 -
dma
+關注
關注
3文章
559瀏覽量
100420
發布評論請先 登錄
相關推薦
評論