1. 程式人生 > >DMA和UART的深刻認識--串列埠接收的3種工作方式(附STM32F4程式碼)

DMA和UART的深刻認識--串列埠接收的3種工作方式(附STM32F4程式碼)

可能會遇到的問題:

1.能實現接收但不傳送 注意是否是識別函數出錯

2.DMA單次傳輸模式要求再初始化,否者出現第二次中斷不執行。使用迴圈模式出現的問題是要結合配置公式:


3.DMA再次初始化不完全,會出現接收一次成功,再來一次不行。第三次能接收的問題

4.串列埠除錯連續點選的次數太快,會使的裡面的傳送程式出錯

一.串列埠uart中斷接收

 遇到的問題:

1、串列埠除錯接收引腳壞掉

2.接收資料識別,使用的庫函數出錯

串列埠設定的一般步驟可以總結為如下幾個步驟:1) 串列埠時鐘使能, GPIO 時鐘使能。2) 設定引腳複用器對映:呼叫 GPIO_PinAFConfig 函式。3) GPIO 初始化設定:要設定模式為複用功能。

4) 串列埠引數初始化:設定波特率,字長,奇偶校驗等引數。5) 開啟中斷並且初始化 NVIC,使能中斷(如果需要開啟中斷才需要這個步驟)。6) 使能串列埠。7) 編寫中斷處理函式:函式名格式為 USARTxIRQHandler(x 對應串列埠號)

其中串列埠中斷服務程式的解析(正點原子):

  void USART1_IRQHandler(void)                	//串列埠1中斷服務程式
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中斷(接收到的資料必須是0x0d 0x0a結尾)
	{
		Res =USART_ReceiveData(USART1);//(USART1->DR);	//讀取接收到的資料
		
		if((USART_RX_STA&0x8000)==0)//接收未完成 相當於一個迴圈 一開始肯定進入這裡 因為賦初值為0
		{
			if(USART_RX_STA&0x4000)//接收到了0x0d
			 {
				if(Res!=0x0a)USART_RX_STA=0;//接收錯誤,重新開始
				else USART_RX_STA|=0x8000;	//接收完成了 
			 }
			else //還沒收到0X0D
			{	
				if(Res==0x0d)USART_RX_STA|=0x4000;
				else
				{
					USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
					USART_RX_STA++;
					if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收資料錯誤,重新開始接收	  
				}	
			}
	}
}

上面的程式接收部分為正點原子編寫的一段程式,具體的思路是:

     定義了接收狀態暫存器USART_RX_STA總共16位,由於串列埠接收到資料開始產生中斷,在中斷函式裡啟動接收指令,一次讀取一個位元組,根據標誌位收到倒數第二個位元組0x0d 和最後一個位元組0x0a代表資料接收完。因此在上面的中斷處理函式中它做的判斷是:依據讀取的資料判斷接收是否完成,或者接收到資料了若接收到14位了表明之前已經判斷是接收到0x0d,並將該位置位。若這次沒有收到資料0x0a說明接收出錯。所以兩個變數的判斷很關鍵:USART_RX_STA和Res(接收的單個位元組);

如果接收的位元組還沒有到14位 而且當前的res不是0x0d,則接收緩衝區陣列遞增,通過USART_RX_STA++的方式。若是累加的數目要大於已知的資料長度,則說明多接收了,肯定出問題,但為什麼是減一,原因是USART_RX_STA是從0開始的,也就是累加到2時已經代表3個數據了(C語言的知識)同時將USART_RX_STA=0,重新開始接收。

但是問題是:中斷接收字串,使用Res =USART_ReceiveData(USART1);只是讀取一個位元組?難道是每個位元組的接收都會啟動一次中斷?

答:這和我們啟動的中斷方式有關,下圖為串列埠中斷的方式:

本次的實驗使用的是:USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);因此資料暫存器非空即來中斷。

因此每個位元組的接收都會來一次中斷。因此後面我們才會要使用DMA的方式。

在主函式內的操作:

1.中斷優先順序分組 NVIC_Configuration();

2.呼叫初始化函式 uart_init(9600);  

串列埠中斷完成,接收資料正常。

為了方便檢視正確的狀態,啟用串列埠傳送模式

串列埠傳送,在上面我們已經啟動了收發模式

為此傳送時判斷上次傳輸完成以後:

while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);  //等待上次傳輸完成。

啟動傳送命令:

USART_SendData(USART1,(uint8_t)USART1_TX__ins_BUF[j]);        //傳送資料到串列埠1

一次發一個位元組。

判斷髮送陣列的長度,並開啟迴圈傳送:如下所示:

void u3_printf(char* fmt,...) 
{  
	u16 i,j;
	va_list ap;
	va_start(ap,fmt);
	vsprintf((char*)USART1_TX__ins_BUF,fmt,ap);
	va_end(ap);//以上的語句可以對輸入的字串按照一定的格式儲存
	i=strlen((const char*)USART1_TX__ins_BUF);//此次傳送資料的長度
	for(j=0;j<i;j++)//迴圈傳送資料
	{
	  while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);  //等待上次傳輸完成 
		USART_SendData(USART1,(uint8_t)USART1_TX__ins_BUF[j]); 	 //傳送資料到串列埠1
	}
}

傳送部分完成,可以結合接收的資料傳送指定的字元;在這裡就會涉及到對接收字元的識別問題:1.可利用正則表示式 2.庫函式 3. 指定連續字元條件判斷

需要注意的是:strcmp庫函式實現的是兩個字串的比較,相等才為0,為此串列埠傳送的資料為帶有回車換行0x0d 和0x0a。

二、串列埠DMA中斷接收

配置成uart+DMA的方式,那麼uart的收模式不配置中斷,同時配置DMA中斷,固定位元組接收。(即是搬運資料滿了才DMA中斷)

1.uart的初始化去掉NVIC的配置

2.DMA配置

  void MYDMA_Config_Rx(DMA_Stream_TypeDef*DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr) 

  (1)使能DMA的時鐘 並等待資料流可配置(這裡注意你要使用的DMA時鐘為哪個1或2)

  (2).設定外設地址

  (3).設定儲存器地址

  (4).設定傳輸資料量

  (5).設定DMA資料流的配置資訊

  (6)使能DMA資料流,啟動傳輸

  (7)中斷配置

 注意的是根據是傳送和接收選擇:外設到記憶體 or記憶體到外設方向

 正常傳輸模式和迴圈模式要注意,正常傳輸則傳輸完一次後DMA結束,下次要重啟DMA的配置資訊才能再次使用。可以選用迴圈模式,但是有一個問題是當接收的資料超過指定的接收長度時會出錯。

中斷的選擇為:

DMA_ITConfig(DMA2_Stream2,DMA_IT_TC,ENABLE);//使能DMA2流2的傳輸完成中斷

3.DMA的中斷服務函式

void DMA2_Stream2_IRQHandler(void)
{
	uint16_t pro=0;
	Blue_receive_Flag = 1;
if(Blue_receive_Flag == 1)      //
{
 Receive_data_process();//接收的資料判斷 DMA接收 下一步通過串列埠
 Blue_receive_Flag = 0;
//傳送應答 同上面的DMA 傳送
}	 
	if(DMA_GetITStatus(DMA2_Stream2,DMA_IT_TCIF2)!= RESET)     //DMA傳輸完成標誌
    {
//        DMA_Cmd(DMA2_Stream2, DISABLE);     				   //關閉USART1 RX DMA2 所指示的通道  
//        pro =  DMA_GetCurrDataCounter(DMA2_Stream2);	       //獲取DMA通道的DMA快取的大小
        DMA_Cmd(DMA2_Stream2, ENABLE);     					   //使能USART1 RX DMA2 所指示的通道        
        DMA_ClearITPendingBit(DMA2_Stream2,DMA_IT_TCIF2);      //清除中斷標誌  
			  USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);  //使能串列埠1的DMA接收    
      	MYDMA_Enable(DMA2_Stream2,USART_REC_LEN);     //開始一次DMA傳輸!	
    }
}

4.在主函式中需要初始化uart  MYDMA_Config_Rx

uart(9600);
MYDMA_Config_Rx(DMA2_Stream2,DMA_Channel_4,(u32)&USART1->DR,(u32)USART_RX_BUF,USART_REC_LEN);

5.啟動uart 和DMA 

DMA_Cmd(DMA_Streamx, ENABLE);
 USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);  //使能串列埠1的DMA接收

6.關閉DMA才可以設定

DMA_Cmd(DMA_Streamx, DISABLE);                      //關閉DMA傳輸 
	
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}	//確保DMA可以被設定  
		
DMA_SetCurrDataCounter(DMA_Streamx,ndtr);          //資料傳輸量  
 

//設定完成再次啟動即可

DMA_Cmd(DMA_Streamx, ENABLE); //開啟DMA傳輸

直至完成了串列埠DMA接收的配置

而且在上面中指定DMA搬送的快取區是USART_RX_BUF,位元組長度USART_REC_LEN。因此在處理函式中可以直接操作此陣列USART_RX_BUF例如識別陣列中含有的字串,並作出相應的判斷比如傳送接收的內容或者其它指定的字元。

三、不定長度資料的DMA接收

採用UART中斷+DMA搬運

1.uart初始化

 加上uart的中斷配置同時開啟空閒中斷

USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//開啟空閒中斷 修改使用的是空閒中斷

Usart1NVIC 配置

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串列埠1中斷通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x00;//搶佔優先順序3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =0x02;		//子優先順序3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
NVIC_Init(&NVIC_InitStructure);	//根據指定的引數初始化VIC暫存器、

2.DMA配置去掉中斷

 開啟DMA_Cmd(DMA2_Stream2, ENABLE);  //正式驅動DMA傳輸

3.中斷服務函式void USART1_IRQHandler(void) 

判斷當uart產生空閒的中斷時,從uart讀值以清除中斷標誌

 (1)關鍵的一點是:

Usart1_Rec_Cnt = DMA_REC_LEN-DMA_GetCurrDataCounter(DMA2_Stream2);	//算出接本幀資料長度

其中的DMA_GetCurrDataCounter獲得了當前剩餘緩衝區的大小

(2)並將接收的資料再次傳送出去:

len為Usart1_Rec_Cnt buf[t]為緩衝區DMA_Rece_Buf

for(t=0;t<len;t++)		//迴圈傳送資料
{		   
 while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);	  
 USART_SendData(USART1,buf[t]);
}	

(3)由於是單次傳輸,因此傳輸完成一次,要再次初始化DMA並清除中斷標誌

USART_ClearITPendingBit(USART1, USART_IT_IDLE);         //清除中斷標誌
MYDMA_Enable(DMA2_Stream2,DMA_REC_LEN);//可以實現實時調節資料傳輸量
MYDMA_Config_Rx(DMA2_Stream2,DMA_Channel_4,(u32)&USART1->DR,(u32)DMA_Rece_Buf, DMA_REC_LEN);

4.在主函式中初始化DMA和uart即可

完成

結果:其中可看到接收到22位元組 和29位元組的,原因是先使用串列埠除錯助手傳送給MCU,MCU依據接收的值再發送回串列埠。亂碼為本程式的其它操作可不比在意。