STM32 HAL CubeMX 串口IDLE接收空閒中斷+DMA

關於DMA原理部分講解,及CubeMx配置部分,請參考該文章
【STM32】HAL庫 STM32CubeMX教程十一—DMA (串口DMA發送接收)
本篇文章我們僅針對例程進行詳解剖析

歷程詳解

詳解包括:

  • 中斷原理講解
  • 例程流程詳解
  • 庫函數分析詳解
  • 對應寄存器介紹
  • 對應函數介紹
  • 對應註釋詳解

本篇文章提供兩種方法:
一種是 :IDLE 接收空閒中斷+DMA
一種是: IDLE 接收空閒中斷+RXNE接收數據中斷

都可完成串口數據的收發

知識點介紹:

STM32 IDLE 接收空閒中斷

功能:
在使用串口接受字符串時,可以使用空閒中斷(IDLEIE置1,即可使能空閒中斷),這樣在接收完一個字符串,進入空閒狀態時(IDLE置1)便會激發一個空閒中斷。在中斷處理函數,我們可以解析這個字符串。

接受完一幀數據,觸發中斷

STM32的IDLE的中斷產生條件
在串口無數據接收的情況下,不會產生,當清除IDLE標誌位後,必須有接收到第一個數據後,纔開始觸發,一但接收的數據斷流,沒有接收到數據,即產生IDLE中斷

STM32 RXNE接收數據中斷

功能:
當串口接收到一個bit的數據時,(讀取到一個停止位) 便會觸發 RXNE接收數據中斷

接受到一個字節的數據,觸發中斷

比如給上位機給單片機一次性發送了8個字節,就會產生8次RXNE中斷,1次IDLE中斷。

串口CR1寄存器
在這裏插入圖片描述
對bit4寫1開啓IDLE接受空閒中斷
,對bit5寫1開啓RXNE接收數據中斷。

串口ISR寄存器
在這裏插入圖片描述
此寄存器爲串口狀態查詢寄存器

當串口接收到數據時,bit5 RXNE就會自動變成1,當接收完一幀數據後,bit4就會變成1.

清除RXNE中斷標誌位的方法爲:

只要把接收到的一個字節讀出來,就會清除這個中斷

在STM32F1 /STM32F4 系列中 清除IDLE中斷標誌位的方法爲:

  1. 先讀SR寄存器,
  2. 再讀DR寄存器。

在這裏插入圖片描述

memset()函數

extern void *memset(void *buffer, int c, int count)        
  • buffer:爲指針或是數組
  • c:是賦給buffer的值
  • count:是buffer的長度.

USART採用DMA接收時,如何讀取當前接收字節數?

   #define __HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNDTR);

DMA接收時該宏將返回當前接收空間剩餘字節

實際接受的字節= 預先定義的接收總字節 - __HAL_DMA_GET_COUNTER()

其本質就是讀取NTDR寄存器,DMA通道結構體中定義了NDTR寄存器,讀取該寄存器即可得到未傳輸的數據數呢,

NTDR寄存器
在這裏插入圖片描述

實現方法:

兩種利用串口IDLE空閒中斷的方式接收一幀數據,方法如下:

方法1:實現思路:採用STM32F103的串口1,並配置成空閒中斷IDLE模式且使能DMA接收,並同時設置接收緩衝區和初始化DMA。那麼初始化完成之後,當外部給單片機發送數據的時候,假設這次接受的數據長度是200個字節,那麼在單片機接收到一個字節的時候並不會產生串口中斷,而是DMA在後臺把數據默默地搬運到你指定的緩衝區數組裏面。當整幀數據發送完畢之後串口才會產生一次中斷,此時可以利用__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);函數計算出當前DMA接收存儲空間剩餘字節

本次的數據接受長度=預先定義的接收總字節-接收存儲空間剩餘字節

  • 比方說 本次串口接受200個字節,
HAL_UART_Receive_DMA(&huart1,rx_buffer,200);//打開DMA接收
  • 然後我發送了 Zxiaoxuan 9個字節的數據長度

  • 那麼此時 GET_COUNTER函數讀出來 接收存儲空間剩餘字節 就是191個字節

  • 實際接受的字節(9) = 預先定義的接收總字節(200) - __HAL_DMA_GET_COUNTER()(191
    9 = 200-191

應用對象:適用於各種串口相關的通信協議,如:MODBUS,PPI ;還有類似於GPS數據接收解析,串口WIFI的數據接收等,都是很好的應用對象。

方法2:實現思路:直接利用stm32的RXNE和IDLE中斷進行接收不定字節數據。 每次接收到一個字節的數據,觸發RXNE中斷 將該字節數據存放到數組裏,傳輸完成之後,觸發一次IDLE中斷,對已經獲取到的數據進行處理

例程1

本例程功能:

使用DMA+串口接受空閒中斷 實現將接收的數據完整發送到上位機的功能

在這裏插入圖片描述
接收數據的流程:

首先在初始化的時候打開DMA接收,當MCU通過USART接收外部發來的數據時,在進行第①②③步的時候,DMA直接將接收到的數據寫入緩存rx_buffer[100] //接收數據緩存數組,程序此時也不會進入接收中斷,在軟件上無需做任何事情,要在初始化配置的時候設置好配置就可以了。

數據接收完成的流程:

當數據接收完成之後產生接收空閒中斷④

在中斷服務函數中做這幾件事:

  • 判斷是否爲IDLE接受空閒中斷
  • 在中斷服務函數中將接收完成標誌位置1
  • 關閉DMA防止在處理數據時候接收數據,產生干擾。
  • 計算出接收緩存中的數據長度,清除中斷位,

while循環 主程序流程:

  • 主程序中檢測到接收完成標誌被置1
  • 進入數據處理程序,現將接收完成標誌位置0,
  • 將接收到的數據重新發送到上位機
  • 重新設置DMA下次要接收的數據字節數,使能DMA進入接收數據狀態。

例程代碼:

uart.c

volatile uint8_t rx_len = 0;  //接收一幀數據的長度
volatile uint8_t recv_end_flag = 0; //一幀數據接收完成標誌
uint8_t rx_buffer[100]={0};  //接收數據緩存數組
void MX_USART1_UART_Init(void)
{

  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
//下方爲自己添加的代碼
	__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE中斷

//DMA接收函數,此句一定要加,不加接收不到第一次傳進來的實數據,是空的,且此時接收到的數據長度爲緩存器的數據長度
	HAL_UART_Receive_DMA(&huart1,rx_buffer,BUFFER_SIZE);

	
}

uart.h

extern UART_HandleTypeDef huart1;
extern DMA_HandleTypeDef hdma_usart1_rx;
extern DMA_HandleTypeDef hdma_usart1_tx;
/* USER CODE BEGIN Private defines */
 
 
#define BUFFER_SIZE  100  
extern  volatile uint8_t rx_len ;  //接收一幀數據的長度
extern volatile uint8_t recv_end_flag; //一幀數據接收完成標誌
extern uint8_t rx_buffer[100];  //接收數據緩存數組

main.c

/*
*********************************************************************************************************
* 函 數 名: DMA_Usart_Send
* 功能說明: 串口發送功能函數
* 形  參: buf,len
* 返 回 值: 無
*********************************************************************************************************
*/
void DMA_Usart_Send(uint8_t *buf,uint8_t len)//串口發送封裝
{
 if(HAL_UART_Transmit_DMA(&huart1, buf,len)!= HAL_OK)   //判斷是否發送正常,如果出現異常則進入異常中斷函數
  {
   Error_Handler();
  }

}



/*
*********************************************************************************************************
* 函 數 名: DMA_Usart1_Read
* 功能說明: 串口接收功能函數
* 形  參: Data,len
* 返 回 值: 無
*********************************************************************************************************
*/
void DMA_Usart1_Read(uint8_t *Data,uint8_t len)//串口接收封裝
{
	HAL_UART_Receive_DMA(&huart1,Data,len);//重新打開DMA接收
}

while循環

 while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		 if(recv_end_flag == 1)  //接收完成標誌
		{
			
			
			DMA_Usart_Send(rx_buffer, rx_len);
			rx_len = 0;//清除計數
			recv_end_flag = 0;//清除接收結束標誌位
//			for(uint8_t i=0;i<rx_len;i++)
//				{
//					rx_buffer[i]=0;//清接收緩存
//				}
				memset(rx_buffer,0,rx_len);
  }
		HAL_UART_Receive_DMA(&huart1,rx_buffer,BUFFER_SIZE);//重新打開DMA接收
}

stm32f1xx_it.c中

#include "usart.h"

void USART1_IRQHandler(void)
{
	uint32_t tmp_flag = 0;
	uint32_t temp;
	tmp_flag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //獲取IDLE標誌位
	if((tmp_flag != RESET))//idle標誌被置位
	{ 
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除標誌位
		//temp = huart1.Instance->SR;  //清除狀態寄存器SR,讀取SR寄存器可以實現清除SR寄存器的功能
		//temp = huart1.Instance->DR; //讀取數據寄存器中的數據
		//這兩句和上面那句等效
		HAL_UART_DMAStop(&huart1); //  停止DMA傳輸,防止
		temp  =  __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 獲取DMA中未傳輸的數據個數   
		//temp  = hdma_usart1_rx.Instance->NDTR;// 讀取NDTR寄存器,獲取DMA中未傳輸的數據個數,
		rx_len =  BUFFER_SIZE - temp; //總計數減去未傳輸的數據個數,得到已經接收的數據個數
		recv_end_flag = 1;	// 接受完成標誌位置1	
	 }
  HAL_UART_IRQHandler(&huart1);

}

註釋詳解:

註釋1:

temp = UartHandle.Instance->SR;  //清除狀態寄存器SR,讀取SR寄存器可以實現清除SR寄存器的功能
temp = UartHandle.Instance->DR; //讀取數據寄存器中的數據

這兩句被屏蔽的原因是它們實現的功能和這下面串口IDLE狀態寄存器SR標誌位清零的宏定義實現的功能是一樣的:

__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除標誌位

我們可以點擊這個宏定義進去看看,它實現的功能和上面兩句是一樣的:

#define __HAL_UART_CLEAR_IDLEFLAG(HANDLE) __HAL_UART_CLEAR_PEFLAG(HANDLE)

在這裏插入圖片描述
註釋2:

temp  = hdma_usart1_rx.Instance->NDTR;// 獲取DMA中未傳輸的數據個數,NDTR寄存器分析見下面  

同理, 這句被屏蔽的原因是因爲他和上面的__HAL_DMA_GET_COUNTER的作用也是一樣的,都可以獲取DMA中未傳輸的數據個數
在這裏插入圖片描述

測試正常:
在這裏插入圖片描述

  • 此程序可以進行接收不定長的數據幀,不需像RXNE每次接收到一個字節就進一次中斷。
  • 同時開啓DMA傳輸速率也會加快

完整例程下載:ZXiaoxuanSTM32-DMA-IDLE

例程2

UART中斷使能函數

__HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__)

功能: 該函數的作用是使能對應的UART中斷

在stm32f1xx_hal_uart.h中被宏定義
在這裏插入圖片描述

可以使能的中斷一共有這些:
在這裏插入圖片描述

我們現在所用到的爲:

  *  @arg UART_IT_RXNE: Receive Data register not empty interrupt
  *  @arg UART_IT_IDLE: Idle line detection interrupt

RXNE接收數據中斷
IDLE 接收空閒中斷

在這裏插入圖片描述

本例程功能:

使用RXNE接收數據中斷+IDLE串口接受空閒中斷 實現將接收的數據完整發送到上位機的功能

接收數據的流程:

首先在初始化的時候打開DMA接收,當MCU通過USART接收外部發來的數據時,在進行第①②③步的時候,DMA直接將接收到的數據寫入緩存rx_buffer[100] //接收數據緩存數組,同樣初始化配置的時候設置好配置就可以了。

接收到一個字節數據:
接收到一個字節的數據之後,便會進入RXNE接收數據中斷,

接受完一幀數據之後,便會進入IDLE 接收空閒中斷

uart.c中添加中斷函數

void MX_USART1_UART_Init(void)
{

  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }

	__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE); //使能IDLE中斷
	__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE中斷

//DMA接收函數,此句一定要加,不加接收不到第一次傳進來的實數據,是空的,且此時接收到的數據長度爲緩存器的數據長度
	HAL_UART_Receive_DMA(&huart1,rx_buffer,BUFFER_SIZE);

	
}

其核心代碼就是下面的兩句
在這裏插入圖片描述
第一條語句用來判斷是否接收到1個字節,第二條語句用來判斷是否接收到1幀數據


	if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)!= RESET)  //如果接收到了一個字節的數據
	{
			HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);    //反轉LED
	}
	
	if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)!= RESET)//如果接受到了一幀數據
	{ 
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除標誌位
		

其餘部分於上方例程1基本相同

  • 本次測試是檢測接收到一個字節就反轉LED

經測試,歷程正常:
在這裏插入圖片描述

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章