簡要說一下實驗目的:上位機給單片機發送數據,單片機使用串口接收中斷接收。在接收中斷中,串口向DMA控制器發送請求,把內存中的數據發送到串口的DR寄存器(發送到上位機)
1.串口的基本配置配置略過,需要注意的是打開串口的接收中斷,編寫接收中斷函數
串口接收中斷的NVIC配置
1 /* 配置USART為中斷源 */ 2 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; 3 /* 搶斷優先級*/ 4 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; 5 /* 子優先級 */ 6 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; 7 /* 使能中斷 */ 8 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 9 /* 初始化配置NVIC */ 10 NVIC_Init(&NVIC_InitStructure); 11 USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
串口中斷函數
1 void USART1_IRQHandler(void) 2 { 3 if(USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET)//串口接收中斷 4 { 5 USART_ClearITPendingBit(DEBUG_USARTx,USART_IT_RXNE); 6 printf("usart\n"); 7 8 DMA_Cmd(DMA1_Channel4,ENABLE);//串口向dma發送請求 9 DMA_ClearFlag(DMA1_FLAG_GL4); 10 11 //DMA1_Channel4->CNDTR = SENDBUFF_SIZE; 12 13 USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE); 14 } 15 }
2.DMA 配置
1 void USARTx_DMA_Config(void) 2 { 3 DMA_InitTypeDef DMA_InitStructure; 4 NVIC_InitTypeDef NVIC_InitStructure; /* Configure one bit for preemption priority */ 5 6 // 開啟DMA時鍾 7 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); 8 // 設置DMA源地址:串口數據寄存器地址*/ 9 DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS; 10 // 內存地址(要傳輸的變量的指針) 11 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff; 12 // 方向:從內存到外設 13 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; 14 // 傳輸大小 15 DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE; 16 // 外設地址不增 17 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; 18 // 內存地址自增 19 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 20 // 外設數據單位 21 DMA_InitStructure.DMA_PeripheralDataSize = 22 DMA_PeripheralDataSize_Byte; 23 // 內存數據單位 24 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; 25 // DMA模式,一次或者循環模式 26 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ; 27 //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; 28 // 優先級:中 29 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; 30 // 禁止內存到內存的傳輸 31 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; 32 // 配置DMA通道 33 DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure); 34 35 36 //DMA中斷的NVIC 37 // NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); 38 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn; 39 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; 40 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; 41 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 42 NVIC_Init(&NVIC_InitStructure); 43 DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); //配置DMA發送完成后產生中斷 44 DMA_ITConfig(DMA1_Channel4,DMA_IT_HT,ENABLE); //配置DMA發送完成后產生中斷 45 // USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE); 46 //DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); 47 // 使能DMA 48 DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE); 49 }
DMA中斷函數
1 void DMA1_Channel4_IRQHandler(void) 2 { 3 if(DMA_GetITStatus(DMA1_IT_TC4)==SET) 4 { 5 LED1(1); 6 printf("d"); 7 DMA_ClearITPendingBit(DMA1_IT_TC4); //清除全部中斷標志 8 DMA_ClearFlag(DMA1_FLAG_GL4); 9 DMA_Cmd(DMA1_Channel4,DISABLE); 10 // DMA_Cmd(DMA1_Channel4,DISABLE); 11 } 12 if(DMA_GetITStatus(DMA1_IT_HT4)==SET) 13 { 14 LED1(1); 15 printf("h"); 16 DMA_ClearITPendingBit(DMA1_IT_HT4); //清除全部中斷標志 17 } 18 }
3.結果
串口接收中斷中 向上位機發送“usart”,然后發送DMA請求,把內存中的數據“p”發送到串口(外設,由上位機接收)。DMA半傳輸完成中斷中向上位機發送“h”,DMA全部完成傳輸中斷中向上位機發送“d”。
下圖為實驗結果
經過多次調試,發現了幾個問題
問題1:如果DMA發送了N個數據,上位機接收過程中總會少2個數據。缺少的位置是h和d的位置。
問題2:在串口接收中斷中 使用USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE); 串口向DMA發送請求 發送數據時,只會在第一次進入接收中斷里發生DMA傳輸。第二次往后都只會進串口接收中斷,不會有DMA傳輸。DMA設置為normal模式,按照網上的說法,清中斷DMA_ClearITPendingBit(DMA1_IT_TC4),關DMA,以及在第二次傳輸前設置數據大小//DMA_SetCurrDataCounter(DMA1_Channel4,SENDBUFF_SIZE); (或者DMA1_Channel4->CNDTR = SENDBUFF_SIZE;)以及修改串口中斷和DMA中斷的NVIC優先級都不會有第二次傳輸。嘗試了這些方法都不能正常啟動DMA第二次傳輸。暫時先記錄一下,解決了再來寫。###:解決了,只用修改一下DMA_Mode即可。DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; 之前是DMA_InitStructure.DMA_Mode = DMA_Mode_Normal。感謝一位熱心網友。
其他問題和總結(自己總結的,理解的不到位的請大佬指正):
1.打開了串口接收和發送完成中斷。在串口接收中斷中 使用串口發送數據,有時候會出現接收到一個字符,多次進入接收中斷,查找資料,可能存在中斷嵌套,后來只在串口接收中斷中接收,添加了一個按鍵,用按鍵去輸出接收到的數據。這個沒有問題。(在使用printf時,一定要勾選microlib(MDK))
2.關於串口接收中斷和發送中斷的問題。經過查找啟動文件,發現在NVIC設置中斷時只能設置串口中斷(不區分時接收還是發送完成)NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;區分接收還是發送是在void USART1_IRQHandler(void) 中字節去判斷是哪個標志位。那可能串口的接收中斷和發送完成中斷的優先級是一樣的。
3.關於DMA的中斷問題。DMA不是不需要cpu參與嗎?為什么還要設置中斷。DMA的傳輸數據過程是不需要cpu的,但是DMA有3個中斷:半傳輸完成DMA1_IT_HT4、全部傳輸完成DMA1_IT_TC4、傳輸錯誤DMA1_IT_TE4。當DMA在傳輸數據完成一半、全部、出錯時,需要向cpu發送中斷。一樣的,這三個中斷也需要設置NVIC。和其他中斷是一樣的(比如:定時器中斷、串口中斷)。
打開DMA中的函數:
DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); //配置DMA發送完成后產生中斷 DMA_ITConfig(DMA1_Channel4,DMA_IT_HT,ENABLE); //配置DMA發送完成后產生中斷
4.DMA1有7個通道,DMA2有5個,每個通道都是固定的和某個外設連接,比如要使用存儲器到外設,外設到存儲器模式,像上面的串口。要使用串口外設,該怎么選DMA通道,要查中文參考手冊。存儲器到存儲器之間通道不固定,自選任選。
5. 關於設置順序,每個外設都有對應的通道,那到底是先設置外設呢,還是先設置通道。先設置通道,當需要開啟DMA時,用對應外設去發請求。設置通道的函數在DMA庫中,發送請求的函數在各個外設的庫中。也就是說當外設發送DMA請求時,該外設對應的DMA通道一定是配置好的。
__STM32F10x_DMA_H頭文件中:413行的函數 參數是 哪個通道的初始化和 通道初始化結構體
IIC頭文件中:537行的函數 參數是 哪個外設的DMA請求和ENABLE
串口頭文件中:372行的函數 參數是 哪個外設的DMA請求和ENABLE
6.每個通道都包括多個中斷比如:半傳輸完成DMA1_IT_HT4、全部傳輸完成DMA1_IT_TC4、傳輸錯誤DMA1_IT_TE4。那這3個中斷的優先級也不能單獨配置。因為每個通道的中函數名是void DMA1_Channel4_IRQHandler(void)這種類型的。從名字上能看出通道上的中斷是針對通道的,不針對具體中斷。就和串口中斷一樣,是接收中斷還是發送上完成中斷,自己在中斷函數中判斷去。
7.第6條是同一通道中的不同中斷,那DMA1有7個通道,不同通道中的中斷能設置不同優先級嗎?DMA也是一個外設,如果DMA1的7個通道(7個外設)同時要請求DMA,那么DMA先處理哪個通道呢?通過觀看通道Init函數,發現DMA_InitStructure結構體中有個變量DMA_InitStructure.DMA_Priority是設置優先級的,優先級可選項有4種:DMA_Priority_VeryHigh、DMA_Priority_High、DMA_Priority_Medium、DMA_Priority_Low。那么就是DMA中的不同通道是可以設置不同的優先級的。如果兩個通道優先級一樣。那通道號越小優先級越高(通道0>通道1)
8.在使用DMA時只要初始化DMA_InitStructure結構體中的所有變量和NVIC即可
1 DMA_InitTypeDef DMA_InitStructure; 2 NVIC_InitTypeDef NVIC_InitStructure; /* Configure one bit for preemption priority */ 3 4 // 開啟DMA時鍾 5 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); 6 // 設置DMA源地址:串口數據寄存器地址*/ 7 DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS; 8 // 內存地址(要傳輸的變量的指針) 9 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff; 10 // 方向:從內存到外設 11 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; 12 // 傳輸大小 13 DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE; 14 // 外設地址不增 15 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; 16 // 內存地址自增 17 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 18 // 外設數據單位 19 DMA_InitStructure.DMA_PeripheralDataSize = 20 DMA_PeripheralDataSize_Byte; 21 // 內存數據單位 22 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; 23 // DMA模式,一次或者循環模式 24 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ; 25 //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; 26 // 優先級:中 27 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; 28 // 禁止內存到內存的傳輸 29 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; 30 // 配置DMA通道 31 DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure); 32 33 34 35 // NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); 36 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn; 37 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; 38 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; 39 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 40 NVIC_Init(&NVIC_InitStructure); 41 DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); //配置DMA發送完成后產生中斷 42 DMA_ITConfig(DMA1_Channel4,DMA_IT_HT,ENABLE); //配置DMA發送完成后產生中斷 43 // USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE); 44 //DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); 45 // 使能DMA 46 DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE);