在上一講,我們講過CKS32F4xx系列的6個串口都支持DMA傳輸。因此本節我們對CKS32F4xx系列的DMA進行介紹,同時利用DMA對串口數據進行傳輸。
DMA介紹
DMA,全稱為:Direct Memory Access,即直接存儲器訪問。DMA傳輸方式無需CPU直接控制傳輸,也沒有中斷處理方式那樣保留現場和恢復現場的過程,通過硬件為RAM與I/O設備開辟一條直接傳送數據的通路,能使CPU的效率大為提高。
CKS32F4xx系列最多有2個DMA控制器(DMA1和DMA2),共16個數據流(每個控制器8個),每個數據流總共可以有多達8個通道(或稱請求)。CKS32F4xx系列的DMA支持外設到存儲器傳輸、存儲器到外設傳輸和存儲器到存儲器傳輸三種傳輸模式。,存儲器一般是指片內SRAM、外部存儲器、片內Flash等等。
外設到存儲器:把外設數據寄存器內容轉移到指定的內存空間。比如進行ADC采集時我們可以利用DMA傳輸把AD轉換的數據轉移到我們定義的存儲區中。
存儲器到外設:把特定存儲區內容轉移至外設的數據寄存器中,這種多用于外設的發送通信,比如SPI、I2C和串口等。
存儲器到存儲器:就是把一個指定的存儲區內容拷貝到另一個存儲區空間。功能類似于C語言內存拷貝函數memcpy,不過利用DMA傳輸可以達到更高的傳輸效率。
注意:只有DMA2控制器支持存儲器到存儲器的傳輸,DMA1不支持。
DMA功能框圖
標號1處是外設通道,每一個數據流(標號2)對應著8個外設通道,外設通道選擇要解決的主要問題是決定哪一個外設作為該數據流的源地址或者目標地址。DMA1各個通道的請求映像如下表所示:
DMA2各個通道的請求映像如下表所示:
比如我們利用DMA將存儲器數據傳輸到串口3,然后發送出去,那么根據上表可知,我們可以選擇DMA1的數據流3通道4,或者DMA1的數據流4通道7。而將串口3的數據傳輸到存儲器則可以選擇DMA1的數據流1通道4。
標號2處是數據流和仲裁器,一個DMA控制器對應8個數據流,數據流包含要傳輸數據的源地址、目標地址、數據等等信息。如果我們需要同時使用同一個DMA控制器 (DMA1 或DMA2) 多個外設請求時,那必然需要同時使用多個數據流,這時就需要仲裁器對每一個數據流的優先級進行劃分。數據流的優先級可以通過配置DMA_SxCR寄存器 PL[1:0]位,可以設置為非常高、高、中和 低四個級別。如果兩個或以上數據流軟件設置優先級一樣,則他們優先級取決于數據流編號,編號越低越具有優先權,比如數據流2優先級高于數據流 3。
標號3處是FIFO,用于在源數據傳輸到目標地址之前臨時存放數據用的。可以通過DMA數據流xFIFO控制寄存器DMA_SxFCR的FTH[1:0]位來控制FIFO的閾值,分別為1/4、1/2、3/4和滿。如果數據存儲量達到閾值級別時,FIFO內容將傳輸到目標中。
標號4和5處是存儲器端口、外設端口。DMA控制器通過存儲器端口和外設端口與存儲器和外設進行數據傳輸。DMA2(DMA控制器2)的存儲器端口和外設端口都是連接到AHB總線矩陣,可以使用AHB總線矩陣功能。而DMA1的存儲區端口相比DMA2的要減少AHB2外設的訪問權,同時DMA1外設端口是沒有連接至總線矩陣的,只有連接到APB1外設,所以 DMA1不能實現存儲器到存儲器傳輸。
標號6處是編程端口,AHB從器件編程端口是連接至AHB2外設的,AHB2外設在使用 DMA傳輸時需要相關控制信號。
DMA軟件配置
在標準庫函數中對DMA進行初始化主要是對結構體DMA_InitTypeDef內的各類參數進行初始化。DMA_InitTypeDef結構體參數及介紹如下:
typedef struct { uint32_t DMA_Channel; uint32_t DMA_PeripheralBaseAddr; uint32_t DMA_Memory0BaseAddr; uint32_t DMA_DIR; uint32_t DMA_BufferSize; uint32_t DMA_PeripheralInc; uint32_t DMA_MemoryInc; uint32_t DMA_PeripheralDataSize; uint32_t DMA_MemoryDataSize; uint32_t DMA_Mode; uint32_t DMA_Priority; uint32_t DMA_FIFOMode; uint32_t DMA_FIFOThreshold; uint32_t DMA_MemoryBurst; uint32_t DMA_PeripheralBurst; }DMA_InitTypeDef;
1)DMA_Channel:DMA請求通道的選擇,每個外設對應固定的通道,具體設置值需要查25.2節中的DMA1和DMA2各個通道的請求映像表。該值在標準庫中可供選擇的參數值如下:
#define DMA_Channel_0 ((uint32_t)0x00000000) #define DMA_Channel_1 ((uint32_t)0x02000000) #define DMA_Channel_2 ((uint32_t)0x04000000) #define DMA_Channel_3 ((uint32_t)0x06000000) #define DMA_Channel_4 ((uint32_t)0x08000000) #define DMA_Channel_5 ((uint32_t)0x0A000000) #define DMA_Channel_6 ((uint32_t)0x0C000000) #defineDMA_Channel_7((uint32_t)0x0E000000)
2)DMA_PeripheralBaseAddr:外設地址,設定DMA_SxPAR寄存器的值;一般設置為外設的數據寄存器地址,如果是存儲器到存儲器模式則設置為其中一個存儲區地址。比如要進行串口DMA傳輸,那么外設基地址為串口接收發送數據存儲器USART1->DR的地址,表示方法為&USART1->DR。
3)DMA_Memory0BaseAddr:存儲器0地址,設定DMA_SxM0AR寄存器值;一般設置為我們自定義存儲區的首地址。比如我們程序中自己定義的用來存放數據的數組,此時這個值設置為數組名即可。
4) DMA_DIR:傳輸方向選擇,可選外設到存儲器、存儲器到外設以及存儲器到存儲器。該值在標準庫中可供選擇的參數值如下:
DMA_DIR_PeripheralToMemory //外設到存儲器 DMA_DIR_MemoryToPeripheral //存儲器到外設 DMA_DIR_MemoryToMemory //存儲器到存儲器
5)DMA_Bu?erSize:設定待傳輸數據數目,初始化設定DMA_SxNDTR寄存器的值。
6)DMA_PeripheralInc:是否使能外設地址自動遞增功能,它設定DMA_SxCR寄存器的PINC位的值;一般外設都是只有一個數據寄存器,所以一般不會使能該位。該值在標準庫中可供選擇的參數值如下:
DMA_PeripheralInc_Enable //使能 DMA_PeripheralInc_Disable //不使能
7)DMA_MemoryInc:是否使能存儲器地址自動遞增功能,它設定DMA_SxCR寄存器的MINC位的值;我們自定義的存儲區一般都是存放多個數據的,所以一般是使能存儲器地址自動遞增功能。該值在標準庫中可供選擇的參數值如下:
DMA_MemoryInc_Enable //使能 DMA_MemoryInc_Disable //不使能
8)DMA_PeripheralDataSize:外設數據寬度,可選字節(8位)、半字(16位)和字(32位),根據外設數據長度進行選擇。該值在標準庫中可供選擇的參數值如下:
DMA_PeripheralDataSize_Byte //字節 DMA_PeripheralDataSize_HalfWord //半字 DMA_PeripheralDataSize_Word //字
9) DMA_MemoryDataSize:存儲器數據寬度,可選字節(8位)、半字(16位)和字(32位),和外設數據寬度相對應。該值在標準庫中可供選擇的參數值如下:
DMA_MemoryDataSize_Byte //字節 DMA_MemoryDataSize_HalfWord //半字 DMA_MemoryDataSize_Word //字
10) DMA_Mode:DMA傳輸模式選擇,可選一次傳輸或者循環傳輸。該值在標準庫中可供選擇的參數值如下:
DMA_Mode_Normal //一次傳輸 DMA_Mode_Circular //循環傳輸
11) DMA_Priority:軟件設置數據流的優先級,有4個可選優先級分別為非常高、高、中和低。DMA優先級只有在多個DMA數據流同時使用時才有意義,如果只有一個數據流的話,設置成非常高優先級就可以了。該值在標準庫中可供選擇的參數值如下:
DMA_Priority_Low //低 DMA_Priority_Medium //中 DMA_Priority_High //高 DMA_Priority_VeryHigh //非常高
12) DMA_FIFOMode:FIFO模式使能,如果設置為DMA_FIFOMode_Enable 表示使能FIFO模式功能;如果采用直接傳輸模式,則不需要使用FIFO模式。直接模式下,DMA直接進行數據從源地址到目的地址的傳輸。而FIFO模式下,可以將要傳輸的多個數據(或字節)累計存儲在FIFO緩沖器中,然后在FIFO緩沖器中設置存儲閾值,當到達閾值時,FIFO會自動把所有存儲的數據一次性的發送到目標地址。該值在標準庫中可供選擇的參數值如下:
DMA_FIFOMode_Disable //不使能 DMA_FIFOMode_Enable //使能
13) DMA_FIFOThreshold:FIFO閾值選擇,可選4種狀態分別為FIFO容量的1/4、1/2、3/4和滿;不使用FIFO模式時,該設置改值無效。該值在標準庫中可供選擇的參數值如下:
DMA_FIFOThreshold_1QuarterFull //1/4 DMA_FIFOThreshold_HalfFull //1/2 DMA_FIFOThreshold_3QuartersFull //3/4 DMA_FIFOThreshold_Full //滿
14) DMA_MemoryBurst:存儲器突發模式選擇,可選單次模式、4節拍的增量突發模式、8節拍的增量突發模式或16節拍的增量突發模式。單次傳輸模式下,一次操作(軟件)只能傳輸一次,突發傳輸模式下,一次操作可以傳輸多次,如4次,8次,16次。該值在標準庫中可供選擇的參數值如下:
DMA_MemoryBurst_Single //單次 DMA_MemoryBurst_INC4 //4節拍 DMA_MemoryBurst_INC8 //8節拍 DMA_MemoryBurst_INC16 //16節拍
15) DMA_PeripheralBurst:外設突發模式選擇,可選單次模式、4節拍的增量突發模式、8節拍的增量突發模式或16節拍的增量突發模式。該值在標準庫中可供選擇的參數值如下:
DMA_PeripheralBurst_Single //單次 DMA_PeripheralBurst_INC4 //4節拍 DMA_PeripheralBurst_INC8 //8節拍 DMA_PeripheralBurst_INC16 //16節拍
串口DMA接發通信實驗
串口的DMA接發通信實驗是存儲器到外設和外設到存儲器的數據傳輸。在第24課串口通信的基礎上編寫而成。
1.編程要點
1)配置USART通信功能;
2)配置DMA通信功能,存儲器到外設和外設到存儲器兩種模式的配置。
3)使能指定的DMA數據流中斷;
4)使能USART3的DMA發送和接收請求;
5)開始一次DMA傳輸。
2.代碼分析
代碼中有關串口的配置的程序在第24課已經詳細講過了,這里就不再講述。主要是對DMA相關的代碼進行分析,相關程序在dma.c文件里。
代碼清單1:DMA初始化配置
void MYDMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE); while (DMA_GetCmdStatus(DMA1_Stream3) != DISABLE){} //存儲器到外設的DMA配置 DMA_DeInit(DMA1_Stream3); DMA_InitStructure.DMA_Channel = DMA_Channel_4; //通道選擇 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART3->DR);//DMA外設地址 DMA_InitStructure.DMA_Memory0BaseAddr =(uint32_t)USART3_TX_BUF;//DMA 存儲器0地址 DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;//存儲器到外設模式 DMA_InitStructure.DMA_BufferSize = USART3_MAX_TX_LEN;//數據傳輸量 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外設非增量模式 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存儲器增量模式 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外設數據長度:8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存儲器數據長度:8位 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式 DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高優先級 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存儲器突發單次傳輸 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外設突發單次傳輸 DMA_Init(DMA1_Stream3, &DMA_InitStructure);//初始化DMA Stream DMA_ClearITPendingBit(DMA1_Stream3,DMA_IT_TCIF3); //清除標志位 DMA_Cmd(DMA1_Stream3,DISABLE);//關閉DMA DMA_ITConfig(DMA1_Stream3,DMA_IT_TC,ENABLE); NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); USART_DMACmd(USART3,USART_DMAReq_Tx,ENABLE); //外設到存儲器的DMA配置 DMA_DeInit(DMA1_Stream1); while (DMA_GetCmdStatus(DMA1_Stream1) != DISABLE){}//等待DMA可配置 DMA_InitStructure.DMA_Channel = DMA_Channel_4; //通道選擇 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART3->DR);;//DMA外設地址 DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)USART3_RX_BUF;//DMA 存儲器0地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//存儲器到外設模式 DMA_InitStructure.DMA_BufferSize = USART3_MAX_RX_LEN;//數據傳輸量 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外設非增量模式 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存儲器增量模式 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外設數據長度:8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存儲器數據長度:8位 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式 DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;//最高優先級 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存儲器突發單次傳輸 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外設突發單次傳輸 DMA_Init(DMA1_Stream1, &DMA_InitStructure);//初始化DMA Stream DMA_ClearITPendingBit(DMA1_Stream1,DMA_IT_TCIF1); //清除標志位 DMA_Cmd(DMA1_Stream1,ENABLE);//關閉DMA DMA_ITConfig(DMA1_Stream1,DMA_IT_TC,ENABLE);//使能DMA1數據流1的傳輸完成中斷 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); USART_DMACmd(USART3,USART_DMAReq_Rx,ENABLE); }
這段代碼主要是根據第25.3節中講解的內容對DMA進行配置的。串口發送數據我們利用的是DMA數據流3通道4,存儲器是我們定義的一個數組USART3_TX_BUF,外設是串口的數據寄存器USART_DR,表示方法為&USART3->DR。串口接收數據我們利用的是DMA數據流1通道4,存儲器是我們定義的一個數組USART3_RX_BUF,外設是串口的數據寄存器USART_DR,表示方法為&USART3->DR。
代碼清單2:DMA1_Stream3_IRQHandler函數
void DMA1_Stream3_IRQHandler(void) //DMA發送數據流中斷服務函數 { if(DMA_GetITStatus(DMA1_Stream3,DMA_IT_TCIF3) != RESET) { DMA_ClearITPendingBit(DMA1_Stream3,DMA_IT_TCIF3); DMA_Cmd(DMA1_Stream3,DISABLE); //關閉DMA USART3_DMA_Tx_flag = 0; //發送完成 } }
該函數是DMA1數據流3中斷服務函數,在函數里我們通過對DMA_IT_TCIF3標志位的判斷,可以知道數據流3的數據是否傳輸完成,然后清除相應的標志位和關閉DMA傳輸。
代碼清單3:DMA1_Stream1_IRQHandler函數
void DMA1_Stream1_IRQHandler(void) //DMA接收數據流中斷服務函數 { if(DMA_GetITStatus(DMA1_Stream1,DMA_IT_TCIF1) != RESET) { DMA_ClearITPendingBit(DMA1_Stream1,DMA_IT_TCIF1); //清除標志位 DMA_Cmd(DMA1_Stream1,DISABLE); //關閉DMA } }
該函數的功能和DMA1數據流3中斷服務函數的功能是一樣的。
代碼清單4:USART3_IRQHandler函數
void USART3_IRQHandler(void) { if(USART_GetITStatus(USART3 , USART_IT_IDLE) != RESET) { USART_ReceiveData(USART3);//清除空閑中斷標志 DMA_Cmd(DMA1_Stream1,DISABLE);//關閉DMA ,防止干擾 USART3_RX_STA = USART3_MAX_RX_LEN - DMA_GetCurrDataCounter(DMA1_Stream1);//獲得接收到的字節數 DMA_SetCurrDataCounter(DMA1_Stream1,USART3_MAX_RX_LEN); DMA_ClearITPendingBit(DMA1_Stream1,DMA_IT_TCIF1);//比f103多的一句 DMA_Cmd(DMA1_Stream1,ENABLE);// DMA 開啟,等待數據。 USART3_RX_BUF[USART3_RX_STA&0X7FFF]='?'; USART3_RX_STA|=0x8000; //最高位置1,標記接收完成了 } if(USART_GetFlagStatus(USART3,USART_FLAG_ORE) == SET) // 檢查 ORE 標志,防止開關總中斷死機,放在接收中斷前面 { USART_ClearFlag(USART3,USART_FLAG_ORE); USART_ReceiveData(USART3); } }
該函數是串口3的中斷服務函數。在函數里判斷接收完成是通過串口空閑中斷的方式實現,即當串口數據流停止后,就會產生IDLE中斷。然后在中斷里依次做了以下事情:處理接收buff的數據;關閉串口接收DMA通道,防止后面接收到數據產生串擾;計算接收到的字節數存儲在USART3_RX_STA變量中;重新設置DMA下次要接收的數據字節數;清除DMA1數據流1接收完成標志位;開啟DMA通道,等待下一次的數據接收;置位標志位USART3_RX_STA,表示接收完成了。同時為了防止串口溢出錯誤,程序卡死在串口中斷里,在中斷服務函數里增加了溢出錯誤的處理。
代碼清單5:主函數
int main(void) { GPIO_Configuration(); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//設置系統中斷優先級分組2 USART_Configuration(); printf("start "); while (1) { if(USART3_RX_STA&0x8000) { //printf("USART3_Read:%s",USART3_RX_BUF); printf("USART3_Read:%s",USART3_RX_BUF); USART3_RX_STA=0; memset(USART3_RX_BUF,0,sizeof(USART3_RX_BUF)); } } }
主函數的編寫邏輯比較簡單,首先是各類外設的初始化,包括GPIO初始化、NVIC中斷初始化、串口初始化。然后在while循環里等待接收完成標志置位,將接收到的數據再發送到串口調試助手,并利用memset函數將存儲數據的數組USART3_RX_BUF清零。
-
控制器
+關注
關注
112文章
15896瀏覽量
175427 -
存儲器
+關注
關注
38文章
7366瀏覽量
163109 -
adc
+關注
關注
97文章
6302瀏覽量
542488 -
串口
+關注
關注
14文章
1534瀏覽量
75478 -
dma
+關注
關注
3文章
552瀏覽量
99954
原文標題:MCU微課堂 | CKS32F4xx系列產品串口DMA傳輸
文章出處:【微信號:中科芯MCU,微信公眾號:中科芯MCU】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論