STM32 DMA串口接收不定長數據

串口通信(UART)在通信當中尤其是在低速率佔用很重要的地位 通信 速度雖然比不上SPI通信但是由於其簡單,對通信雙方的時鐘要求不是很高,受到很廣泛的使用,很多嵌入式程序猿(媛) 都傾向於串口通信。

1. 串口發送

串口發送函數非常簡單,直接調用串口的API函數
void USART_SendData(USART_TypeDef USARTx, uint16_t Data);*
即可發送出去,舉個簡單的實例:

void Usart1_SendData(u8 *Str)     //Str存儲發送的數據
{
  u8 i=0;
  while(Str[i]!=0)
  {
    USART_SendData(USART1,Str[i]);
    while(!USART_GetFlagStatus(USART1,USART_FLAG_TXE));
    i++;
  }
}

  上面的函數存在一個問題,當數據當中有0x00字節 時,就會跳出while循環,停止發送數據,所以存在bug,需要進行改良。

  很多朋友會問無緣無故爲啥會發0x00字節呢?這是因爲在項目中,尤其是一些協議通訊,經常會遇到0x00 ,代表數據正常 或者一定的含義 ,如果還是調用上面的函數,就會出現發送不完整的情況。

  所以要發送完全,就要加上數據的長度dateLength ,這樣就萬無一失了。

void Usart3_SendData(u8 *Str,u8 Datalength)
{
  u8 i=0;
  while(i<Datalength)
  {
    USART_SendData(USART3,Str[i]);
    while(!USART_GetFlagStatus(USART3,USART_FLAG_TXE));
    i++;
  }
}

  當然如果爲了減少CPU的壓力,可以使用DMA去發送數據。
  思路是:首先設置DMA的Counter值=數據長度,然後啓動DMA流,就開始發送了。可以設置DMA發送完成中斷,也可以無需設置,最後判斷一下串口是否發送完成即可。

void UART1_SentMsgL(unsigned char *data, u16 cnt)
{
  u16 i;
  USART_ClearFlag(USART1, USART_FLAG_TC);       //清除UART1發送完成標誌位
  for(i=0;i<cnt;i++)
    USART_TX_BUF[i]=data[i];
  DMA_SetCurrDataCounter(DMA2_Stream7, cnt);
  DMA_ITConfig(DMA2_Stream7 , DMA_IT_TC , ENABLE);
  DMA_Cmd(DMA2_Stream7, ENABLE);
  while(USART_GetFlagStatus(USART1, USART_FLAG_TC)!=1);  //發送完成退出
}

2. 串口接收

  串口接收,個人覺得比發送要難,因爲涉及到中斷,還要判斷什麼時候接收完成,相當於處於被動的地位,無法掌控對數據的操作。

  最基本的接收方式就是:串口接收中斷置位,USART_GetITStatus(USART1, USART_IT_RXNE)=SET
  則利用串口接收函數接收字符,
Res =USART_ReceiveData(USART1);
  然後就可以存儲到字符串當中,關鍵的方式就是,如何判斷接收完成。然後進行處理。

  最常見的串口接收函數應該就是正點原子的接收函數,很經典,也很方便,直接拿來就用,是根據最後的結束標誌位(0x0D 0x0A) 來判斷一幀數據接收完成的。

void USART1_IRQHandler(void)                	//串口1中斷服務程序
{
	u8 Res;
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中斷(接收到的數據必須是0x0d 0x0a結尾)
	{
		Res =USART_ReceiveData(USART1);//(USART1->DR);	//讀取接收到的數據
		
		if((USART_RX_STA&0x8000)==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;//接收數據錯誤,重新開始接收	  
				}		 
			}
		}   		 
  } 

  我自己也寫過一個簡單版的,附在下面,也比較清晰一點。(使用0x0A做結尾或者接收達到上限

void USART2_IRQHandler()
{
	u8 TempData;
	if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET)
	{
		TempData = USART_ReceiveData(USART2);
		if((TempData == 0X0A)||(RX_Index == RX_Num))
		{
			ReceiveFlag = 1;
		}
		else
		{
			RX_Buffer[RX_Index++] = TempData;
		}
		USART_ClearITPendingBit(USART2,USART_IT_RXNE);
	}
}

  上面的函數存在一個問題是(正點原子例程),通信的幀數據必須以0x0D 0x0A爲結尾,否則會出現無法判斷接收完成的情況。

  所以這樣就引出一個問題,如何接收任意的數據呢?即不定長而且不以任何特定的字符爲結尾的數據。

  在數據通信完成之後,串口就會出現空閒的狀態是否可以根據這樣的狀態來判斷數據接收完成呢,答案當然是可以的。

  串口中提供了一個可以獲取串口是否空閒的函數:
USART_GetITStatus(USART3,USART_IT_IDLE);
根據這樣的一個函數就可以判斷串口是否空閒,從而來判斷數據幀是否結束。

  當然如果直接採用中斷的話,對CPU的佔用率比較大,採用DMA接收串口的數據的話,可以有效緩解CPU的壓力。

  設計思路是:先開啓串口接收中斷,當接收開始時,這是開啓DMA接收,當接收完成之後,進入空閒中斷,此時判斷幀結束,數據接收完成。

  所以這樣一個串口接收函數就新鮮出爐了:

void USART3_IRQHandler(void)                	//串口3中斷服務程序
{
	if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)  //通過接收中斷來開啓DMA接收
	{
      USART_ClearITPendingBit(USART3,USART_IT_RXNE);
      USART_ITConfig(USART3,USART_IT_RXNE,DISABLE);
	  USART_ITConfig(USART3,USART_IT_IDLE,ENABLE);
	  DMA_Cmd(DMA1_Stream1,ENABLE);
  }
  else if(USART_GetITStatus(USART3,USART_IT_IDLE)!=RESET)//再通過空閒中斷來判斷數據幀結束。
  {
       USART_ClearITPendingBit(USART3,USART_IT_IDLE);
	   Usart3_DataLength=RXNum-DMA_GetCurrDataCounter(DMA1_Stream1);   //DMA  Counter可以判斷接收到多少個字節。
	   DMA_Cmd(DMA1_Stream1,DISABLE);
	   USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);
	   USART_ITConfig(USART3,USART_IT_IDLE,DISABLE);
       Usart_RecFlag=1;                     //接收完成標誌位
  }
} 

  還有一種思路是,不使用串口空閒來判斷,而是通過定時器來實現,如果一定時間內沒有數據接收,則認爲接收完成,通常時間設置的是5ms。

  同樣使用DMA來接收,DMA是循環模式通過DMACounter的值來判斷是否開始接收和接收完成。
  DMAcounter值指示的待發送或者接收到的數據量,如果完成,則Counter值變爲0。

oid UART1Poll()
{
  unsigned short dmacnt;
  dmacnt=DMA_GetCurrDataCounter(DMA2_Stream5);
  if(USARTRecStartFlag==0)
  {
    if(dmacnt==USART_LEN);   //如果發生變化,則數據通信開始,
    else 
    {
      USARTRecStartFlag=1;
      TIM_Cmd(TIM5,ENABLE);
      USARTDataInTime = Systemtime;     // 記下時間
    }
  }
  else                           //開始接收數據,
  {
    if(dmacnt==DMALastCnt)
    {
      USARTCurrTime = Systemtime;   
      if(USARTCurrTime-USARTDataInTime>5)//時間片5*1ms   超過一定時間,則認爲接收完成
      {
        USART_FrameFlag=1;
        DMALastCnt=USART_LEN;
        USART_Len = USART_LEN - DMA_GetCurrDataCounter(DMA2_Stream5);
        USARTRecStartFlag=0;  
        DMA_Cmd(DMA2_Stream5,DISABLE); //停止使能 才能修改計數器 
        DMA_SetCurrDataCounter(DMA2_Stream5, USART_LEN);
        DMA_Cmd(DMA2_Stream5,ENABLE); //停止使能 才能修改計數器 
        TIM_Cmd(TIM5,DISABLE);
        TIM_SetCounter(TIM5,0); 
      }    
    }
    else 
    {
       DMALastCnt=dmacnt;
      USARTDataInTime = Systemtime;     //GetSystemtime();        // Systemtime;
    }   
  } 
}

void TIM5_IRQHandler(void)  //1000HZ     定時器計時。
{
  if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)
  {
    Systemtime++;
    // led output
    if(Systemtime == 1000)
    {  
       Systemtime = 0;  
    }
  }
  TIM_ClearITPendingBit(TIM5, TIM_IT_Update);
}
如有雷同,純屬我抄你,有問題可以直接聯繫郵箱,在個人資料裏面。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章