HAL_UART_Receive接收最容易丟數據了,STM32 HAL庫UART查詢方式實例
可以考慮用中斷來實現,但是HAL_UART_Receive_IT還不能直接用,容易數據丟失,實際工作中不會這樣用,STM32 HAL庫USART串口中斷編程:演示數據丟失,
需要在此基礎優化一下.?STM32F103 HAL庫USART串口中斷,利用環形緩沖區來防止數據丟失.
本文介紹STM32F103 HAL庫USART串口DMA IDLE中斷.
IDLE 中斷 在串口通信里,IDLE 代表空閑狀態,其定義為:總線在一個字節的傳輸時間內未再接收到新數據。
或許有人會有疑問:UART 的DMA RxD 引腳初始狀態就是空閑的,那 IDLE 中斷會一直觸發嗎?其實并非如此。當我們使能 IDLE 中斷后,它不會立即產生。只有在至少接收到 1 個數據后,若發現接下來一個字節的時間里都沒有新數據到來,才會觸發 IDLE 中斷。
使用 DMA 進行數據接收時,雖然能顯著提升 CPU 的使用效率,但也存在一個問題,即“無法預先知曉要接收的數據量”。然而,我們往往希望能盡快處理接收到的數據。舉個例子,假設我們打算讀取 150 字節的數據,但在接收到 50 字節后,對方停止了數據發送,這種情況下該如何判斷數據傳輸已經中止呢?此時,IDLE 中斷就起作用了。
坑在HAL_UARTEx_ReceiveToIdle_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)的DMA的半滿中斷-UART_DMARxHalfCplt,關閉 DMA 的半傳輸完成中斷? ? __HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);? ?
目錄
?一、開發環境
二、配置STM32CubeMX
三、代碼實現與部署
四、運行結果:
?五、注意事項
主要是5個函數的調用和實現.
HAL_UARTEx_ReceiveToIdle_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);? ?void circle_buf_init(p_circle_buf pCircleBuf, uint32_t len, uint8_t *buf);//初始化環形緩沖區int circle_buf_read(p_circle_buf pCircleBuf, uint8_t *pVal);//讀取環形緩沖區int circle_buf_write(p_circle_buf pCircleBuf, uint8_t val);//寫環形緩沖區
1.HAL_UARTEx_ReceiveToIdle_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);調用此函數后,務必關閉 DMA 的半傳輸完成中斷。若未關閉,當接收到超過數據總長度一半的數據時,系統不僅會觸發一次空閑中斷,還會觸發一次半傳輸完成中斷。而這兩個中斷均會調用一次HAL_UARTEx_RxEventCallback?回調函數,導致該回調函數總共被調用兩次。
2.__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT); 禁用與 USART1 接收相關的 DMA 通道的半傳輸完成中斷。
__HAL_DMA_DISABLE_IT?是一個 HAL 庫提供的宏,專門用于禁用 DMA 的特定中斷。
&
hdma_usart1_rx是指向 USART1 接收所用 DMA 句柄的指針,明確要操作的是哪個 DMA 通道。
DMA_IT_HT代表半傳輸完成中斷(Half Transfer Complete Interrupt),指定要禁用的具體中斷類型。
3.從源頭到調用回調函數的調用過程, ?HAL_UARTEx_ReceiveToIdle_DMA->UART_Start_Receive_DMA(huart, pData, Size)->huart->hdmarx->XferHalfCpltCallback = UART_DMARxHalfCplt或? huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt;
DMA1_Channel5_IRQHandler->HAL_DMA_IRQHandler(&hdma_usart1_rx)->? hdma->XferHalfCpltCallback(hdma)或hdma->XferCpltCallback(hdma);
?一、開發環境
硬件:正點原子精英版?V2?STM32F103開發板
單片機:STM32F103ZET6
Keil版本:5.32
STM32CubeMX版本:6.9.2
STM32Cube?MCU Packges版本:STM32F1xx_DFP.2.4.1
串口:USART1(PA9,PA10)
二、配置STM32CubeMX
1.啟動STM32CubeMX,新建STM32CubeMX項目:?
2.選擇MCU:在軟件中選擇你的STM32型號-STM32F103ZET6。?
3.選擇時鐘源:
4.配置時鐘:
?5.使能Debug功能:Serial Wire
?6.HAL庫時基選擇:SysTick
7.USART1配置:選擇異步模式,使能中斷。
8.配置工程參數:在Project標簽頁中,配置項目名稱和位置,選擇工具鏈MDK-ARM。? 9.生成代碼:在Code Generator標簽頁中,配置工程外設文件與HAL庫,勾選頭文件.c和.h文件分開,然后點擊Project > Generate Code生成代碼。?
三、代碼實現與部署
?main.c增加代碼:
/* USER CODE BEGIN Header */
/********************************************************************************* @file : main.c* @brief : Main program body******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <string.h>
#include"circle_buffer.h"/* USER CODE END Includes *//* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
extern UART_HandleTypeDef huart1;
extern DMA_HandleTypeDef hdma_usart1_rx;
static circle_buf g_CircleBuf;
uint32_t buf_len;
static uint8_t g_RecvChar;
static uint8_t g_RecvTempBuf[10];
static uint8_t g_RecvBuf[150];
int UART1_read(uint8_t *pVal)
{return circle_buf_read(&g_CircleBuf, pVal);
}/* USER CODE END PTD *//* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD *//* USER CODE END PD *//* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM *//* USER CODE END PM *//* Private variables ---------------------------------------------------------*//* USER CODE BEGIN PV *//* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
char *str= "hello\r\n";
char c;
/* USER CODE END PFP *//* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 *//* USER CODE END 0 *//*** @brief The application entry point.* @retval int*/
int main(void)
{/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_DMA_Init();MX_USART1_UART_Init();/* USER CODE BEGIN 2 */HAL_UART_Transmit(&huart1,str,strlen(str),1000);circle_buf_init(&g_CircleBuf, 150, g_RecvBuf);//初始化環形緩沖區HAL_UARTEx_ReceiveToIdle_DMA (&huart1,g_RecvTempBuf,10);//一開始就打開中斷__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT); //關閉DMA本身的半傳輸中斷/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */while (0 != UART1_read(&c)); HAL_UART_Transmit(&huart1, &c, 1, 1000);HAL_UART_Transmit(&huart1, "\r\n", 2, 1000);}/* USER CODE END 3 */
}/*** @brief System Clock Configuration* @retval None*/
void SystemClock_Config(void)
{RCC_OscInitTypeDef RCC_OscInitStruct = {0};RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};/** Initializes the RCC Oscillators according to the specified parameters* in the RCC_OscInitTypeDef structure.*/RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;RCC_OscInitStruct.HSEState = RCC_HSE_ON;RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;RCC_OscInitStruct.HSIState = RCC_HSI_ON;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){Error_Handler();}/** Initializes the CPU, AHB and APB buses clocks*/RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK){Error_Handler();}
}/* USER CODE BEGIN 4 */
static volatile int g_rx_cplt = 0;void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{for (int i = 0; i < Size; i++){circle_buf_write(&g_CircleBuf, g_RecvTempBuf[i]);}HAL_UARTEx_ReceiveToIdle_DMA(&huart1, g_RecvTempBuf, 10);//重新開中斷__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT); //關閉DMA本身的半傳輸中斷}
/* USER CODE END 4 *//*** @brief This function is executed in case of error occurrence.* @retval None*/
void Error_Handler(void)
{/* USER CODE BEGIN Error_Handler_Debug *//* User can add his own implementation to report the HAL error return state */__disable_irq();while (1){}/* USER CODE END Error_Handler_Debug */
}#ifdef USE_FULL_ASSERT
/*** @brief Reports the name of the source file and the source line number* where the assert_param error has occurred.* @param file: pointer to the source file name* @param line: assert_param error line source number* @retval None*/
void assert_failed(uint8_t *file, uint32_t line)
{/* USER CODE BEGIN 6 *//* User can add his own implementation to report the file name and line number,ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) *//* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
2.增加代碼circle_buffer.h,circle_buffer.c
#ifndef _CIRCLE_BUF_H
#define _CIRCLE_BUF_H#include <stdint.h>typedef struct circle_buf {uint32_t r;uint32_t w;uint32_t len;uint8_t *buf;
}circle_buf, *p_circle_buf;void circle_buf_init(p_circle_buf pCircleBuf, uint32_t len, uint8_t *buf);int circle_buf_read(p_circle_buf pCircleBuf, uint8_t *pVal);int circle_buf_write(p_circle_buf pCircleBuf, uint8_t val);#endif /* _CIRCLE_BUF_H */
#include <stdint.h>
#include "circle_buffer.h"void circle_buf_init(p_circle_buf pCircleBuf, uint32_t len, uint8_t *buf)
{pCircleBuf->r = pCircleBuf->w = 0;pCircleBuf->len = len;pCircleBuf->buf = buf;
}int circle_buf_read(p_circle_buf pCircleBuf, uint8_t *pVal)
{if (pCircleBuf->r != pCircleBuf->w){*pVal = pCircleBuf->buf[pCircleBuf->r];pCircleBuf->r++;if (pCircleBuf->r == pCircleBuf->len)pCircleBuf->r = 0;return 0;}else{return -1;}
}int circle_buf_write(p_circle_buf pCircleBuf, uint8_t val)
{uint32_t next_w;next_w = pCircleBuf->w + 1;if (next_w == pCircleBuf->len)next_w = 0;if (next_w != pCircleBuf->r){pCircleBuf->buf[pCircleBuf->w] = val;pCircleBuf->w = next_w;return 0;}else{return -1;}
}
??3.連接USART1:用USB轉TTL工具連接當前硬件USART1的PA9、PA10,GND。???
?4.打開串口助手:???
?5.編譯代碼:Keil編譯生成的代碼。
?6.燒錄程序:將編譯好的程序用ST-LINK燒錄到STM32微控制器中。
四、運行結果:
1. 程序燒錄完成并運行,復位打印hello,發送"abcdefghij"時候,打印"abcdefghij",數據沒有丟失.??2.可以把兩處關閉半滿中斷的程序屏蔽__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);?,觀察現象,編譯燒錄完成并運行,復位打印hello,發送"abcdefghij"時候,打印"abcdeabcdefghij","abcde"重復了,不正確.?
?五、注意事項
1.確保你的開發環境和工具已經正確安裝和配置。
2.如果沒有打印,按一下復位鍵,檢查連接和電源是否正確,注意根據你所用的硬件來接線,不要接錯線。
3.在串口打印數據時,要確保波特率等參數與串口助手設置一致。
通過上述步驟,講解STM32F103 HAL庫USART串口DMA IDLE中斷.坑在HAL_UARTEx_ReceiveToIdle_DMA()的DMA的半滿中斷-UART_DMARxHalfCplt.僅供參考,有任何問題,歡迎在評論區留言討論!