【STM32H7教程】第65章 STM32H7的低功耗串口LPUART基礎知識和HAL庫API
- 2020 年 3 月 6 日
- 筆記
第65章 STM32H7的低功耗串口LPUART基礎知識和HAL庫API
本章節為大家講解LPUART(Low power universal asynchronous receiver transmitter,低功耗通用非同步收發器)的基礎知識和對應的HAL庫API。相比第29章的通用串口,增加了低功耗特性。
65.1 初學者重要提示
65.2 低功耗串口基礎知識
65.3 低功耗串口的HAL庫用法
65.4 源文件stm32h7xx_hal_uart.c
65.5 總結
65.1 初學者重要提示
- 特別注意,LPUART沒有自己的HAL庫驅動文件,是跟通用串口公用的驅動文件。
- 學習串口外設推薦從硬體框圖開始了解基本的功能特性,然後逐步深入了解各種特性,這種方式方便記憶和以後查閱。而串口的通訊學習,推薦看時序圖。
65.2 低功耗串口基礎知識
LPUART的全稱是Low power universal synchronous asynchronous receiver transmitter,中文意思是低功耗通用非同步收發器,簡稱LPUART。
65.2.1 低功耗串口的硬體框圖
認識一個外設,最好的方式就是看它的框圖,方便我們快速的了解串口的基本功能,然後再看手冊了解細節。

通過這個框圖,我們可以得到如下資訊:
- IRQ Interface中斷介面
用於實現中斷方式的串口喚醒lpusart_wkup和串口的相關中斷lpusart_it。
- DMA Interface DMA介面
實現串口發送lpuart_tx_dma和接收lpuart_rx_dma的DMA方式。
- COM Contronller串口控制器
串口相關的暫存器基本都在這部分。
- TxFIFO和RxFIFO
串口的發送和接收都支援了硬體FIFO功能。
- TX和RX引腳的互換功能
發送偏移暫存器(TX Shift Reg)和接收偏移暫存器(RX Shift Reg)與TX引腳,RX引腳之間弄了個交叉連接,這裡的意思是支援了引腳互換功能,這樣大家在設計PCB的時候就可以比較隨性了,接反了也沒有關係。
- 發送過程經過的暫存器
依次是LPUART_TDR -> TxFIFO ->Tx Shift Reg偏移暫存器 –> TX或者RX引腳。
- 接收經過的暫存器
依次是TX或者RX引腳-> Rx Shift Reg偏移暫存器->RxFIFO –>LPUART_RDR。
- 兩個時鐘lpuart_pclk和lpuart_ker_ck
這兩個時鐘是獨立的,作用如下:
-
- lpuart_pclk
用於為外設匯流排提供時鐘。
-
- lpuart_ker_ck
串口外設的時鐘源。
65.2.2 低功耗串口的基本功能
STM32的串口功能很強大,支援太多的模式。我們只需關心我們最常用的特性即可。我們的串口驅動使用的串口中斷+FIFO結構,沒有使用DMA。因此我們只討論和串口中斷、串口常規參數有關的知識。
STM32串口的優越特性:(只列了舉常用的)
- 各種波特率。硬體採用分數波特率發生器系統,可以設置各種需要的波特率
- 可編程數據字長度,支援7bit,8bit和9bit。
- 可配置的停止位。支援1或2個停止位。
- 發送器和接收器可以單獨使能。比如GPS應用只需要串口接收,那麼發送的GPIO就可以節省出來用作其他功能。
- 檢測標誌和中斷:
- 接收緩衝器滿,可產生中斷。串口中斷服務程式據此判斷是否接收到數據。
- 發送緩衝器空,可產生中斷。串口中斷服務程式據此啟動發送下一個數據。
- 傳輸結束標誌,可產生中斷。用於RS485通訊,等最後一個位元組發送完畢後,需要控制RS485收發器晶片切換為接收模式。
其它中斷不常用,包括:CTS改變、LIN斷開符檢測、檢測到匯流排為空閑(在DMA不定長接收方式會用到)、溢出錯誤、幀錯誤、噪音錯誤、校驗錯誤。
65.2.3 低功耗串口的高級特性
H7系列的串口支援了一些高級特性,比如:
- 數據邏輯電平翻轉。
- 低功耗特性。
- RX和TX引腳交換。
- MSB位先發送。
- 外接485的PHY晶片時,硬體支援收發切換,無需用戶手動控制DE引腳。
相比第29章的通用串口,低功耗串口不支援超時接收和自適應波特率。
65.2.4 低功耗串口的數據幀格式
串口支援的幀格式如下(M和PCE都是LPUART_CR1暫存器的位,其中M位用於控制幀長度,PCE用於使能奇偶校驗位):

這裡特別注意奇偶校驗位,用戶在配置的時候可以選擇奇校驗和偶校驗,校驗位是佔據的最高位。比如選擇M=00,PCE=1,即7bit的數據位。
- 串口發送數據:
如果發送的7bit數據是111 0011,這個裡面有奇數個1,那麼選擇偶校驗的情況下,校驗位 = 1,湊夠偶數個1,而選擇奇校驗的情況下,校驗位 = 0,因為已經是奇數個1。校驗位不需要用戶去計算,是硬體自動生成的。
- 串口接收數據:
根據用戶設置的奇校驗或者偶校驗類型,串口硬體會對接收到的數據做校驗,如果失敗,LPUART_ISR暫存器的PE位會被置1。如果使能了對應的中斷PEIE,那麼失敗的時候還會產生中斷。
了解到幀格式後,再來看一下實際數據發送時,數據位的先後順序:

65.2.5 低功耗串口的支援的時鐘和波特率
低功耗定時器支援如下幾種時鐘:

這裡我們重點關注PCLK3(D3PCLK1),HSI和LSE。
- LPUART時鐘選擇LSE(32768Hz)
最高速度是10922bps,最低8bps(計算方法3x < 32768 < 4096x,x表示波特率)。
- LPUART時鐘選擇HSI(64MHz)
最高值是21MHz,最小值15625bps(計算方法3x < 64MHz < 4096x,x表示波特率)。
- LPUART時鐘選擇D3PCLK1(100MHz)
最大值33Mbps,最小值24414bps(計算方法3x < 100MHz < 4096x,x表示波特率)。
65.2.6 低功耗串口的支援喚醒方式
低功耗串口的喚醒主要是通過接收數據來喚醒,具體喚醒的方如下:
- 檢測到起始位喚醒。
- 檢測到RXNE標誌喚醒,即接收到數據。
- 檢測到匹配地址時喚醒。
匹配地址支援7bit和4bit匹配兩種方式,比如我們採用7bit匹配,設置地址是0x19,那麼用戶喚醒的時候要將最高bit設置為1,即發送地址0x99(0b1001 1001)才可以喚醒。
喚醒成功時的時序效果如下,特別注意喚醒訊號位置:

喚醒失敗時的時序效果:

65.2.7 低功耗串口發送時序圖
這個時序圖非常具有代表性,可以幫助大家很好的理解TC發送完成中斷和TXE空中斷。

65.2.8 單工,半雙工和全雙工通訊
單工:在一個單工的串列通訊系統中,一般至少有兩根線(訊號線和地線),數據傳送只有一個方向,例如可以使用單工數據傳送將數據從一個簡單的數據監測系統傳送到PC上。
半雙工:在半雙工串列通訊系統中,一般同樣要求至少有兩根線。這裡的數據傳送是雙向的。然而,同一個時刻只能為一個方向。在上面的數據監測的例子中做了一些變化,可以使用半雙工通訊機制發送資訊到嵌入式模組(來設置參數,比如取樣率)。此外,在其他時候,可以使用這個種連接將嵌入式裝置上的數據下載到PC中。
全雙工:在一個全雙工的串列通訊系統中,一般要求至少有三根線(訊號線A,訊號線B和地線)。訊號線A將傳輸一個方向上的數據,同時訊號線B傳送另一個方向上的數據。
65.3 低功耗串口的HAL庫用法
串口的HAL庫用法其實就是幾個結構體變數成員的配置和使用,然後配置GPIO、時鐘,並根據需要配置NVIC、中斷和DMA。下面我們逐一展開為大家做個說明。
65.3.1 低功耗串口暫存器結構體USART_TypeDef
USART相關的暫存器是通過HAL庫中的結構體USART_TypeDef定義的,在stm32h743xx.h中可以找到這個類型定義:
typedef struct { __IO uint32_t CR1; /*!< USART Control register 1, Address offset: 0x00 */ __IO uint32_t CR2; /*!< USART Control register 2, Address offset: 0x04 */ __IO uint32_t CR3; /*!< USART Control register 3, Address offset: 0x08 */ __IO uint32_t BRR; /*!< USART Baud rate register, Address offset: 0x0C */ __IO uint16_t GTPR; /*!< USART Guard time and prescaler register, Address offset: 0x10 */ uint16_t RESERVED2; /*!< Reserved, 0x12 */ __IO uint32_t RTOR; /*!< USART Receiver Time Out register, Address offset: 0x14 */ __IO uint16_t RQR; /*!< USART Request register, Address offset: 0x18 */ uint16_t RESERVED3; /*!< Reserved, 0x1A */ __IO uint32_t ISR; /*!< USART Interrupt and status register, Address offset: 0x1C */ __IO uint32_t ICR; /*!< USART Interrupt flag Clear register, Address offset: 0x20 */ __IO uint16_t RDR; /*!< USART Receive Data register, Address offset: 0x24 */ uint16_t RESERVED4; /*!< Reserved, 0x26 */ __IO uint16_t TDR; /*!< USART Transmit Data register, Address offset: 0x28 */ uint16_t RESERVED5; /*!< Reserved, 0x2A */ __IO uint32_t PRESC; /*!< USART clock Prescaler register, Address offset: 0x2C */ } USART_TypeDef;
這個結構體的成員名稱和排列次序和CPU的USART暫存器是一 一對應的。
__IO表示volatile, 這是標準C語言中的一個修飾字,表示這個變數是非易失性的,編譯器不要將其優化掉。core_m7.h 文件定義了這個宏:
#define __O volatile /*!< Defines 'write only' permissions */ #define __IO volatile /*!< Defines 'read / write' permissions */
下面我們看下LPUART的定義,在stm32h743xx.h文件。
#define PERIPH_BASE (0x40000000UL) #define D3_APB1PERIPH_BASE (PERIPH_BASE + 0x18000000UL) #define LPUART1_BASE (D3_APB1PERIPH_BASE + 0x0C00UL) #define LPUART1 ((USART_TypeDef *) LPUART1_BASE) <----- 展開這個宏,(USART_TypeDef *) 0x58000C00
我們訪問LPUART1的CR1暫存器可以採用這種形式:LPUART1->CR1 = 0。
65.3.2 低功耗串口句柄結構體UART_HandleTypeDef
HAL庫在USART_TypeDef的基礎上封裝了一個結構體UART_HandleTypeDef,定義如下:
typedef struct __UART_HandleTypeDef { USART_TypeDef *Instance; UART_InitTypeDef Init; UART_AdvFeatureInitTypeDef AdvancedInit; uint8_t *pTxBuffPtr; uint16_t TxXferSize; __IO uint16_t TxXferCount; uint8_t *pRxBuffPtr; uint16_t RxXferSize; __IO uint16_t RxXferCount; uint16_t Mask; uint32_t FifoMode; uint16_t NbRxDataToProcess; uint16_t NbTxDataToProcess; void (*RxISR)(struct __UART_HandleTypeDef *huart); void (*TxISR)(struct __UART_HandleTypeDef *huart); DMA_HandleTypeDef *hdmatx; DMA_HandleTypeDef *hdmarx; HAL_LockTypeDef Lock; / __IO HAL_UART_StateTypeDef gState; __IO HAL_UART_StateTypeDef RxState; __IO uint32_t ErrorCode; #if (USE_HAL_UART_REGISTER_CALLBACKS == 1) void (* TxHalfCpltCallback)(struct __UART_HandleTypeDef *huart); void (* TxCpltCallback)(struct __UART_HandleTypeDef *huart); void (* RxHalfCpltCallback)(struct __UART_HandleTypeDef *huart); void (* RxCpltCallback)(struct __UART_HandleTypeDef *huart); void (* ErrorCallback)(struct __UART_HandleTypeDef *huart); void (* AbortCpltCallback)(struct __UART_HandleTypeDef *huart); void (* AbortTransmitCpltCallback)(struct __UART_HandleTypeDef *huart); void (* AbortReceiveCpltCallback)(struct __UART_HandleTypeDef *huart); void (* WakeupCallback)(struct __UART_HandleTypeDef *huart); void (* RxFifoFullCallback)(struct __UART_HandleTypeDef *huart); void (* TxFifoEmptyCallback)(struct __UART_HandleTypeDef *huart); void (* MspInitCallback)(struct __UART_HandleTypeDef *huart); void (* MspDeInitCallback)(struct __UART_HandleTypeDef *huart); #endif } UART_HandleTypeDef;
注意事項:
條件編譯USE_HAL_HRTIM_REGISTER_CALLBACKS用來設置使用自定義回調還是使用默認回調,此定義一般放在stm32h7xx_hal_conf.h文件裡面設置:
#define USE_HAL_UAR_REGISTER_CALLBACKS 1
通過函數HAL_UART_RegisterCallback註冊回調,取消註冊使用函數HAL_UART_UnRegisterCallback。
這裡重點介紹前三個參數,其它參數主要是HAL庫內部使用和自定義回調函數。
- USART_TypeDef *Instance
這個參數是暫存器的例化,方便操作暫存器,比如使能串口的發送空中斷。
SET_BIT(huart->Instance->CR1, USART_CR1_TXEIE)。
- UART_InitTypeDef Init
這個參數是用戶接觸最多的,用於配置串口的基本參數,像波特率、奇偶校驗、停止位等。UART_InitTypeDef結構體的定義如下:
typedef struct { uint32_t BaudRate; /* 波特率 */ uint32_t WordLength; /* 數據位長度 */ uint32_t StopBits; /* 停止位 */ uint32_t Parity; /* 奇偶校驗位 */ uint32_t Mode; /* 發送模式和接收模式使能 */ uint32_t HwFlowCtl; /* 硬體流控制 */ uint32_t OverSampling; /* 過取樣,可以選擇8倍和16倍過取樣 */ uint32_t Prescaler; /* 串口分頻 */ uint32_t FIFOMode; /* 串口FIFO使能 */ uint32_t TXFIFOThreshold; /* 發送FIFO的閥值 */ uint32_t RXFIFOThreshold; /* 接收FIFO的閥值 */ }UART_InitTypeDef;
- UART_AdvFeatureInitTypeDef AdvancedInit
這個參數用於配置串口的高級特性。具體支援的功能參數如下:
typedef struct { uint32_t AdvFeatureInit; /* 初始化的高級特性類別 */ uint32_t TxPinLevelInvert; /* Tx引腳電平翻轉 */ uint32_t RxPinLevelInvert; /* Rx引腳電平翻轉 */ uint32_t DataInvert; /* 數據邏輯電平翻轉 */ uint32_t Swap; /* Tx和Rx引腳交換 */ uint32_t OverrunDisable; /* 接收超時檢測禁止 */ uint32_t DMADisableonRxError; /* 接收出錯,禁止DMA */ uint32_t AutoBaudRateEnable; /* 自適應波特率使能 */ uint32_t AutoBaudRateMode; /* 自適應波特率的四種檢測模式選擇 */ uint32_t MSBFirst; /* 發送或者接收數據時,高位在前 */ } UART_AdvFeatureInitTypeDef;
配置串口參數,其實就是配置結構體UART_HandleTypeDef的成員。比如下面配置為波特率115200,8個數據位,無奇偶校驗,1個停止位。
UART_HandleTypeDef UartHandle; /* 配置如下: - 數據位 = 8 Bits - 停止位 = 1 bit - 奇偶校驗位 = 無 - 波特率 = 115200bsp - 硬體流控制 (RTS 和 CTS 訊號) */ UartHandle.Instance = LPUART1; UartHandle.Init.BaudRate = 115200; UartHandle.Init.WordLength = UART_WORDLENGTH_8B; UartHandle.Init.StopBits = UART_STOPBITS_1; UartHandle.Init.Parity = UART_PARITY_NONE; UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; UartHandle.Init.Mode = UART_MODE_TX_RX; UartHandle.Init.OverSampling = UART_OVERSAMPLING_16; UartHandle.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; if(HAL_UART_Init(&UartHandle) != HAL_OK) { Error_Handler(); }
65.3.3 低功耗串口的底層配置(GPIO、時鐘、中斷等)
串口外設的基本參數配置完畢後還不能使用,還需要配置GPIO、時鐘、中斷等參數,比如下面配置使用引腳PA9和PA10。
/* LPUART1的GPIO PA9, PA10 */ #define LPUART1_CLK_ENABLE() __HAL_RCC_LPUART1_CLK_ENABLE() #define LPUART1_TX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() #define LPUART1_TX_GPIO_PORT GPIOA #define LPUART1_TX_PIN GPIO_PIN_9 #define LPUART1_TX_AF GPIO_AF3_LPUART #define LPUART1_RX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() #define LPUART1_RX_GPIO_PORT GPIOA #define LPUART1_RX_PIN GPIO_PIN_10 #define LPUART1_RX_AF GPIO_AF3_LPUART /* ********************************************************************************************************* * 函 數 名: InitHardUart * 功能說明: 配置串口的硬體參數和底層 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void InitHardUart(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_PeriphCLKInitTypeDef RCC_PeriphClkInit; #if LPUART1_FIFO_EN == 1 /* 使能 GPIO TX/RX 時鐘 */ LPUART1_TX_GPIO_CLK_ENABLE(); LPUART1_RX_GPIO_CLK_ENABLE(); /* 使能 USARTx 時鐘 */ LPUART1_CLK_ENABLE(); /* 配置TX引腳 */ GPIO_InitStruct.Pin = LPUART1_TX_PIN; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = LPUART1_TX_AF; HAL_GPIO_Init(LPUART1_TX_GPIO_PORT, &GPIO_InitStruct); /* 配置RX引腳 */ GPIO_InitStruct.Pin = LPUART1_RX_PIN; GPIO_InitStruct.Alternate = LPUART1_RX_AF; HAL_GPIO_Init(LPUART1_RX_GPIO_PORT, &GPIO_InitStruct); /* 配置NVIC the NVIC for UART */ HAL_NVIC_SetPriority(LPUART1_IRQn, 0, 1); HAL_NVIC_EnableIRQ(LPUART1_IRQn); /* 配置波特率、奇偶校驗 */ bsp_SetLPUartParam(LPUART1, LPUART1_BAUD, UART_PARITY_NONE, UART_MODE_TX_RX); SET_BIT(LPUART1->ICR, USART_ICR_TCCF); /* 清除TC發送完成標誌 */ SET_BIT(LPUART1->RQR, USART_RQR_RXFRQ); /* 清除RXNE接收標誌 */ SET_BIT(LPUART1->CR1, USART_CR1_RXNEIE); /* 使能PE. RX接受中斷 */ #endif }
總結下來就是以下幾點:
- 配置GPIO引腳時鐘。
- 配置LPUART時鐘。
- 配置LPUART的發送和接收引腳。
- 通過NVIC配置中斷。
- 配置波特率,奇偶校驗等,在上一小節有講。
- 清除TC和RXNE標誌,使能接收中斷。
關於這個底層配置有以下幾點要著重說明下:
- 串口發送和接收引腳的復用模式選擇已經被HAL庫定義好,放在了stm32h7xx_hal_gpio_ex.h文件裡面。比如串口1有兩個復用
#define GPIO_AF3_LPUART ((uint8_t)0x03) /* LPUART Alternate Function mapping */ #define GPIO_AF8_LPUART ((uint8_t)0x08) /* LPUART Alternate Function mapping */
具體使用那個,要看數據手冊,比如我們這裡使用引腳PA9和PA10,對應的復用如下:

那麼使用GPIO_AF3_LPUART1即可。
- 根據情況要清除TC發送完成標誌和RXNE接收數據標誌,因為這兩個標誌位在使能了串口後就已經置位,所以當用戶使用了TC或者RX中斷後,就會進入一次中斷服務程式,這點要特別注意。
- HAL庫有個自己的底層初始化回調函數HAL_UART_MspInit,是弱定義的,用戶可以在其它的C文件裡面實現,並將相對的底層初始化在裡面實現。當用戶調用HAL_UART_Init後,會在此函數裡面調用HAL_UART_MspInit,對應的底層複位函數HAL_UART_MspDeInit是在函數HAL_UART_DeInit裡面被調用的。
當然,用戶也可以自己初始化,不限制必須在兩個函數裡面實現。
- 上面舉的例子裡面沒有用到DMA,如果用到了DMA,也是要初始化的。
65.3.4 低功耗串口的狀態標誌清除問題
注,早前使用F1和F4時候,經常會有網友諮詢為什麼串口中斷服務程式裡面沒有做清除標誌。
下面我們介紹__HAL_USART_GET_FLAG函數。這個函數用來檢查LPUART/USART標誌位是否被設置。
/** @brief Check whether the specified USART flag is set or not. * @param __HANDLE__: specifies the USART Handle * @param __FLAG__: specifies the flag to check. * This parameter can be one of the following values: * @arg USART_FLAG_TXFT: TXFIFO threshold flag * @arg USART_FLAG_RXFT: RXFIFO threshold flag * @arg USART_FLAG_RXFF: RXFIFO Full flag * @arg USART_FLAG_TXFE: TXFIFO Empty flag * @arg USART_FLAG_REACK: Receive enable ackowledge flag * @arg USART_FLAG_TEACK: Transmit enable ackowledge flag * @arg USART_FLAG_BUSY: Busy flag * @arg USART_FLAG_TXE: Transmit data register empty flag * @arg USART_FLAG_TC: Transmission Complete flag * @arg USART_FLAG_RXNE: Receive data register not empty flag * @arg USART_FLAG_IDLE: Idle Line detection flag * @arg USART_FLAG_ORE: OverRun Error flag * @arg USART_FLAG_UDR: UnderRun Error flag * @arg USART_FLAG_NE: Noise Error flag * @arg USART_FLAG_FE: Framing Error flag * @arg USART_FLAG_PE: Parity Error flag * @retval The new state of __FLAG__ (TRUE or FALSE). */ #define __HAL_USART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->ISR & (__FLAG__)) == (__FLAG__))
USART_FLAG有如下幾種取值:


請大家重點關註上表中紅字部分,LPUART/USART標誌是需要軟體主動清零的。清零有兩種方式:一種是調用__HAL_USART_CLEAR_FLAG函數,另一種是操作相關暫存器後自動清零。
/** @brief Clear the specified USART pending flag. * @param __HANDLE__: specifies the USART Handle. * @param __FLAG__: specifies the flag to check. * This parameter can be any combination of the following values: * @arg USART_FLAG_TXFT: TXFIFO threshold flag * @arg USART_FLAG_RXFT: RXFIFO threshold flag * @arg USART_FLAG_RXFF: RXFIFO Full flag * @arg USART_FLAG_TXFE: TXFIFO Empty flag * @arg USART_FLAG_REACK: Receive enable ackowledge flag * @arg USART_FLAG_TEACK: Transmit enable ackowledge flag * @arg USART_FLAG_WUF: Wake up from stop mode flag * @arg USART_FLAG_RWU: Receiver wake up flag (is the USART in mute mode) * @arg USART_FLAG_SBKF: Send Break flag * @arg USART_FLAG_CMF: Character match flag * @arg USART_FLAG_BUSY: Busy flag * @arg USART_FLAG_ABRF: Auto Baud rate detection flag * @arg USART_FLAG_ABRE: Auto Baud rate detection error flag * @arg USART_FLAG_RTOF: Receiver timeout flag * @arg USART_FLAG_LBD: LIN Break detection flag * @arg USART_FLAG_TXE: Transmit data register empty flag * @arg USART_FLAG_TC: Transmission Complete flag * @arg USART_FLAG_RXNE: Receive data register not empty flag * @arg USART_FLAG_IDLE: Idle Line detection flag * @arg USART_FLAG_ORE: OverRun Error flag * @arg USART_FLAG_NE: Noise Error flag * @arg USART_FLAG_FE: Framing Error flag * @arg USART_FLAG_PE: Parity Error flag * @retval The new state of __FLAG__ (TRUE or FALSE). */ #define __HAL_USART_CLEAR_FLAG(__HANDLE__, __FLAG__) ((__HANDLE__)->Instance->ICR = (__FLAG__))
上面介紹的USART標誌大部分能夠設置為產生中斷,也就是有對應的USART中斷標誌。我們只介紹幾個串口驅動要用到的中斷標誌:
USART_IT_TXE:TXE:發送數據暫存器空(此時數據可能正在發送)。
USART_IT_TC:發送完成 。
USART_IT_RXNE:接收數據暫存器非空。
中斷預設都是關閉的,通過__HAL_USART_ENABLE_IT函數可以使能相應的中斷標誌。函數定義如下:
/** @brief Enable the specified USART interrupt. * @param __HANDLE__: specifies the USART Handle. * @param __INTERRUPT__: specifies the USART interrupt source to enable. * This parameter can be one of the following values: * @arg USART_IT_RXFF: RXFIFO Full interrupt * @arg USART_IT_TXFE: TXFIFO Empty interrupt * @arg USART_IT_RXFT: RXFIFO threshold interrupt * @arg USART_IT_TXFT: TXFIFO threshold interrupt * @arg USART_IT_TXE : Transmit Data Register empty interrupt * @arg USART_IT_TC : Transmission complete interrupt * @arg USART_IT_RXNE: Receive Data register not empty interrupt * @arg USART_IT_IDLE: Idle line detection interrupt * @arg USART_IT_PE : Parity Error interrupt * @arg USART_IT_ERR : Error interrupt(Frame error, noise error, overrun error) * @retval None */ #define __HAL_USART_ENABLE_IT(__HANDLE__, __INTERRUPT__) (((((uint8_t)(__INTERRUPT__)) >> 5U) == 1)? ((__HANDLE__)->Instance->CR1 |= (1U << ((__INTERRUPT__) & USART_IT_MASK))): ((((uint8_t)(__INTERRUPT__)) >> 5U) == 2)? ((__HANDLE__)->Instance->CR2 |= (1U << ((__INTERRUPT__) & USART_IT_MASK))): ((__HANDLE__)->Instance->CR3 |= (1U << ((__INTERRUPT__) & USART_IT_MASK))))
STM32一個串口的中斷服務程式入口地址只有一個,進入中斷服務程式後,我們需要判斷是什麼原因進入的中斷,因此需要調用一個函數來檢測中斷標誌。函數原型如下:
#define __HAL_USART_GET_IT(__HANDLE__, __IT__) ((__HANDLE__)->Instance->ISR & ((uint32_t)1 << ((__IT__)>> 0x08)))
中斷處理完畢後,必須軟體清除中斷標誌,否則中斷返回後,會重入中斷。清中斷標誌位的函數為:
#define __HAL_USART_CLEAR_IT(__HANDLE__, __IT_CLEAR__) ((__HANDLE__)->Instance->ICR = (uint32_t)(__IT_CLEAR__))
正如前面介紹的,不是所有的標誌都需要用這個函數清零。
注意:操作串口的暫存器不限制必須要用HAL庫提供的API,比如要操作暫存器CR1,直接調用LPUART1->CR1操作即可。
65.3.5 低功耗串口初始化流程總結
使用方法由HAL庫提供:
第1步:定義UART_HandleTypeDef類型串口結構體變數,比如UART_HandleTypeDef huart。
第2步:使用函數HAL_UART_MspInit初始化串口底層,不限制一定要用此函數裡面初始化,用戶也可以自己實現。
- 使能串口時鐘。
- 引腳配置。
a、使能串口所使用的GPIO時鐘。
b、配置GPIO的復用模式。
- 如果使用中斷方式函數HAL_UART_Transmit_IT和HAL_UART_Receive_IT需要做如下配置。
a、配置串口中斷優先順序。
b、使能串口中斷。
- 串口中斷的開關是通過函數__HAL_UART_ENABLE_IT() 和 __HAL_UART_DISABLE_IT()來實現,這兩個函數被嵌套到串口的發送和接收函數中調用。
- 如果使用DMA方式函數HAL_UART_Transmit_DMA和HAL_UART_Receive_DMA需要做如下配置。
a、聲明串口的發送和接收DMA結構體變數,注意發送和接收是獨立的,如果都使用,那就都需要配置。
b、使能DMA介面時鐘。
c、配置串口的發送和接收DMA結構體變數。
d、配置DMA發送和接收通道。
e、關聯DMA和串口的句柄。
f、配置發送DMA和接收DMA的傳輸完成中斷和中斷優先順序。
第3步:配置串口的波特率,位長,停止位,奇偶校驗位,流控制和發送接收模式。
第4步:如果需要,可以編程高級特性,比如TX/RX交換引腳,自動波特率檢測。通過第1步串口結構體變數huart的結構體成員AdvancedInit來設置。
第5步:串口初始化調用的函數HAL_UART_Init初始化。
第6步:根據需要可以做動態註冊回調。
首先使能宏定義USE_HAL_UART_REGISTER_CALLBACKS。
然後調用函數HAL_UART_RegisterCallback() 就可以註冊如下回調函數:
(+) TxHalfCpltCallback
(+) TxCpltCallback
(+) RxHalfCpltCallback
(+) RxCpltCallback
(+) ErrorCallback
(+) AbortCpltCallback
(+) AbortTransmitCpltCallback
(+) AbortReceiveCpltCallback
(+) WakeupCallback
(+) RxFifoFullCallback
(+) TxFifoEmptyCallback
(+) MspInitCallback
(+) MspDeInitCallback
函數HAL_UART_UnRegisterCallback允許取消註冊的回調函數如下:
(+) TxHalfCpltCallback
(+) TxCpltCallback
(+) RxHalfCpltCallback
(+) RxCpltCallback
(+) ErrorCallback
(+) AbortCpltCallback
(+) AbortTransmitCpltCallback
(+) AbortReceiveCpltCallback
(+) WakeupCallback
(+) RxFifoFullCallback
(+) TxFifoEmptyCallback
(+) MspInitCallback
(+) MspDeInitCallback
關於動態註冊回調函數注意以下幾點:
- 默認情況下,HAL_UART_Init調用後將使用默認的弱定義回調,如果用戶註冊了回調,將使用用戶設置的。
- 回調函數只能在HAL_UART_STATE_READY狀態下才可以註冊/註銷。
- 回調函數MspInit和MspDeInit除外,這兩個函數可以在HAL_HRTIM_STATE_READY 或 HAL_HRTIM_STATE_RESET狀態下註冊,這樣的話,用戶調用函數HAL_HRTIM_DeInit()或者HAL_HRTIM_Init()時,就可以在其函數內運行MspInit/DeInit。
- 用戶可以在調用HAL_HRTIM_DeInit()或者HAL_HRTIM_Init()之前調用HAL_HRTIM_RegisterCallback()為MspInit/MspDeInit註冊回調。
65.4 低功耗源文件stm32h7xx_hal_uart.c
此文件涉及到的函數較多,這裡把幾個常用的函數做個說明:
- HAL_UART_Init
- HAL_UART_Transmit
- HAL_UART_Receive
- HAL_UART_Transmit_IT
- HAL_UART_Receive_IT
- HAL_UART_Transmit_DMA
- HAL_UART_Receive_DMA
其實V7開發板設計的低功耗串口FIFO驅動文件bsp_lpuart_fifo.c僅用到了函數HAL_UART_Init,其它函數都沒有用到,不過這裡也為大家做個說明。
65.4.1 函數HAL_UART_Init
函數原型:
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart) { /* 省略 */ if(huart->gState == HAL_UART_STATE_RESET) { huart->Lock = HAL_UNLOCKED; /* 初始化硬體: GPIO, CLOCK */ HAL_UART_MspInit(huart); } huart->gState = HAL_UART_STATE_BUSY; /* 禁止串口 */ __HAL_UART_DISABLE(huart); /* 配置串口參數 */ if (UART_SetConfig(huart) == HAL_ERROR) { return HAL_ERROR; } /* 配置串口高級特性 */ if (huart->AdvancedInit.AdvFeatureInit != UART_ADVFEATURE_NO_INIT) { UART_AdvFeatureConfig(huart); } /* 清暫存器的一些標誌位 */ CLEAR_BIT(huart->Instance->CR2, (USART_CR2_LINEN | USART_CR2_CLKEN)); CLEAR_BIT(huart->Instance->CR3, (USART_CR3_SCEN | USART_CR3_HDSEL | USART_CR3_IREN)); /* 使能串口 */ __HAL_UART_ENABLE(huart); return (UART_CheckIdleState(huart)); }
函數描述:
此函數用於初始化串口的基礎特性和高級特性。
函數參數:
- 第1個參數是UART_HandleTypeDef類型結構體指針變數,用於配置要初始化的參數。
- 返回值,返回HAL_TIMEOUT表示超時,HAL_ERROR表示參數錯誤,HAL_OK表示發送成功,HAL_BUSY表示串口忙,正在使用中。
注意事項:
1、函數HAL_UART_MspInit用於初始化USART的底層時鐘、引腳等功能。需要用戶自己在此函數裡面實現具體的功能。由於這個函數是弱定義的,允許用戶在工程其它源文件裡面重新實現此函數。當然,不限制一定要在此函數裡面實現,也可以像早期的標準庫那樣,用戶自己初始化即可,更靈活些。
2、如果形參huart的結構體成員gState沒有做初始狀態,這個地方就是個坑。特別是用戶搞了一個局部變數UART_HandleTypeDef UartHandle。
對於局部變數來說,這個參數就是一個隨機值,如果是全局變數還好,一般MDK和IAR都會將全部變數初始化為0,而恰好這個 HAL_UART_STATE_RESET = 0x00U。
解決辦法有三
方法1:用戶自己初始串口和涉及到的GPIO等。
方法2:定義UART_HandleTypeDef UartHandle為全局變數。
方法3:下面的方法
if(HAL_UART_DeInit(&UartHandle) != HAL_OK) { Error_Handler(); } if(HAL_UART_Init(&UartHandle) != HAL_OK) { Error_Handler(); }
3、注意串口的中斷狀態暫存器USART_ISR複位後,TC發送完成狀態和RXNE接收狀態都被置1,如果用戶使能這兩個中斷前,最好優先清除中斷標誌。
使用舉例:
UART_HandleTypeDef UartHandle; /* USART3工作在UART模式 */ /* 配置如下: - 數據位 = 8 Bits - 停止位 = 1 bit - 奇偶校驗位 = 無 - 波特率 = 115200bsp - 硬體流控制 (RTS 和 CTS 訊號) */ UartHandle.Instance = USART3; UartHandle.Init.BaudRate = 115200; UartHandle.Init.WordLength = UART_WORDLENGTH_8B; UartHandle.Init.StopBits = UART_STOPBITS_1; UartHandle.Init.Parity = UART_PARITY_NONE; UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; UartHandle.Init.Mode = UART_MODE_TX_RX; UartHandle.Init.OverSampling = UART_OVERSAMPLING_16; UartHandle.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; if(HAL_UART_Init(&UartHandle) != HAL_OK) { Error_Handler(); }
65.4.2 函數HAL_UART_Transmit
函數原型:
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout) { /* 省略 */ if(huart->gState == HAL_UART_STATE_READY) { /* 省略 */ while(huart->TxXferCount > 0U) { huart->TxXferCount--; /* 等待發送空中斷標誌 */ if(UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; } } /* 等待發送完成中斷 */ if(UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; } /* 省略 */ return HAL_OK; } else { return HAL_BUSY; } }
函數描述:
此函數以查詢的方式發送指定位元組。看源碼的話,程式裡面最重要的就是上面程式碼中置紅的兩個標誌,發送空標誌和發送完成標誌。發送空標誌表示發送數據暫存器為空,數據還在移位暫存器裡面,而發送完成標誌表示數據已經從移位暫存器發送出去。
函數參數:
- 第1個參數是UART_HandleTypeDef類型結構體指針變數。
- 第2個參數是要發送的數據地址。
- 第3個參數是要發送的數據大小,單位位元組。
- 第4個參數是溢出時間,單位ms。
- 返回值,返回HAL_TIMEOUT表示超時,HAL_ERROR表示參數錯誤,HAL_OK表示發送成功,HAL_BUSY表示串口忙,正在使用中。
使用舉例:
/* ********************************************************************************************************* * 函 數 名: fputc * 功能說明: 重定義putc函數,這樣可以使用printf函數從串口1列印輸出 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ int fputc(int ch, FILE *f) { HAL_UART_Transmit(&UartHandle, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; }
65.4.3 函數HAL_UART_Receive
函數原型:
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout) { /* 省略 */ if(huart->RxState == HAL_UART_STATE_READY) { /* 省略 */ while(huart->RxXferCount > 0U) { huart->RxXferCount--; if(UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; } } /* 省略 */ return HAL_OK; } else { /* 省略 */ return HAL_BUSY; } }
函數描述:
此函數以查詢的方式接收指定位元組。這個函數相對比較好理解,就是等待上面程式中的RXNE標誌,置位了表示接收數據暫存器已經存入數據。
函數參數:
第1個參數是UART_HandleTypeDef類型結構體指針變數。
第2個參數是要接收的數據地址。
第3個參數是要接收的數據大小,單位位元組。
第4個參數是溢出時間,單位ms。
返回值,返回HAL_TIMEOUT表示超時,HAL_ERROR表示參數錯誤,HAL_OK表示發送成功,HAL_BUSY表示串口忙,正在使用中。
使用舉例:
/* ********************************************************************************************************* * 函 數 名: fgetc * 功能說明: 重定義getc函數,這樣可以使用scanff函數從串口1輸入數據 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ int fgetc(FILE *f) { int ret; HAL_UART_Receive(&UartHandle, (uint8_t *)&ret, 1, HAL_MAX_DELAY); return ret; }
65.4.4 函數HAL_UART_Transmit_IT
函數原型:
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) { if(huart->gState == HAL_UART_STATE_READY) { if((pData == NULL ) || (Size == 0U)) { return HAL_ERROR; } __HAL_LOCK(huart); huart->pTxBuffPtr = pData; huart->TxXferSize = Size; huart->TxXferCount = Size; huart->ErrorCode = HAL_UART_ERROR_NONE; huart->gState = HAL_UART_STATE_BUSY_TX; __HAL_UNLOCK(huart); if (READ_BIT(huart->Instance->CR1, USART_CR1_FIFOEN) != RESET) { /* 使能FIFO發送中斷 */ SET_BIT(huart->Instance->CR3, USART_CR3_TXFTIE); } else { /* 使能發空中斷 */ SET_BIT(huart->Instance->CR1, USART_CR1_TXEIE); } return HAL_OK; } else { return HAL_BUSY; } }
函數描述:
此函數以中斷的方式發送指定位元組,可以選擇使能FIFO中斷方式或者發送空中斷方式。具體數據的發送是在中斷處理函數HAL_UART_IRQHandler裡面實現。
函數參數:
- 第1個參數是UART_HandleTypeDef類型結構體指針變數。
- 第2個參數是要發送的數據地址。
- 第3個參數是要發送的數據大小,單位位元組。
- 返回值,返回HAL_ERROR表示參數錯誤,HAL_OK表示發送成功,HAL_BUSY表示串口忙,正在使用中。
使用舉例:
使用中斷方式要使能串口中斷,
UART_HandleTypeDef UartHandle; uint8_t s_ucBuf[5]; /* 數據發送 */ HAL_UART_Transmit_IT(&UartHandle, s_ucBuf, 1); HAL_UART_Transmit_IT(&UartHandle, (uint8_t*)"KEY_DOWN_K1rn", 13);
65.4.5 函數HAL_UART_Receive_IT
函數原型:
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) { if(huart->RxState == HAL_UART_STATE_READY) { if((pData == NULL ) || (Size == 0U)) { return HAL_ERROR; } __HAL_LOCK(huart); huart->pRxBuffPtr = pData; huart->RxXferSize = Size; huart->RxXferCount = Size; UART_MASK_COMPUTATION(huart); huart->ErrorCode = HAL_UART_ERROR_NONE; huart->RxState = HAL_UART_STATE_BUSY_RX; __HAL_UNLOCK(huart); /* 使能錯誤中斷: (Frame error, noise error, overrun error) */ SET_BIT(huart->Instance->CR3, USART_CR3_EIE); if (READ_BIT(huart->Instance->CR1, USART_CR1_FIFOEN) != RESET) { /* 使能奇偶校驗失敗中斷 */ SET_BIT(huart->Instance->CR1, USART_CR1_PEIE); /* 使能FIFO接收中斷 */ SET_BIT(huart->Instance->CR3, USART_CR3_RXFTIE); } else { /* 使能奇偶校驗失敗中斷和接收中斷 */ SET_BIT(huart->Instance->CR1, USART_CR1_PEIE | USART_CR1_RXNEIE); } return HAL_OK; } else { return HAL_BUSY; } }
函數描述:
此函數以中斷的方式接收指定位元組,可以選擇使能FIFO中斷方式或者普通中斷方式,兩種方式使能了奇偶校驗中斷失敗和錯誤中斷。具體數據的接收是在中斷處理函數HAL_UART_IRQHandler裡面實現。
函數參數:
- 第1個參數是UART_HandleTypeDef類型結構體指針變數。
- 第2個參數是要接收的數據地址。
- 第3個參數是要接收的數據大小,單位位元組。
- 返回值,返回HAL_ERROR表示參數錯誤,HAL_OK表示發送成功,HAL_BUSY表示串口忙,正在使用中。
使用舉例:
使用中斷方式要使能串口中斷
UART_HandleTypeDef UartHandle; uint8_t s_ucBuf[5]; /* 數據接收*/ HAL_UART_Receive_IT(&UartHandle, s_ucBuf, 1);
65.4.6 函數HAL_UART_Transmit_DMA
函數原型:
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) { if(huart->gState == HAL_UART_STATE_READY) { if((pData == NULL ) || (Size == 0U)) { return HAL_ERROR; } __HAL_LOCK(huart); huart->pTxBuffPtr = pData; huart->TxXferSize = Size; huart->TxXferCount = Size; huart->ErrorCode = HAL_UART_ERROR_NONE; huart->gState = HAL_UART_STATE_BUSY_TX; /* 註冊各種DMA回調函數 */ huart->hdmatx->XferCpltCallback = UART_DMATransmitCplt; huart->hdmatx->XferHalfCpltCallback = UART_DMATxHalfCplt; huart->hdmatx->XferErrorCallback = UART_DMAError; huart->hdmatx->XferAbortCallback = NULL; /* 使能串口發送DMA通道 */ HAL_DMA_Start_IT(huart->hdmatx, (uint32_t)huart->pTxBuffPtr, (uint32_t)&huart->Instance->TDR, Size); /* 清除傳輸TC完成標誌 */ __HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_TCF); __HAL_UNLOCK(huart); /* 使能串口發送DMA傳輸 */ SET_BIT(huart->Instance->CR3, USART_CR3_DMAT); return HAL_OK; } else { return HAL_BUSY; } }
函數描述:
此函數以DMA的方式發送指定位元組。這裡是用的DMA中斷方式HAL_DMA_Start_IT進行的發送。所以使用此函數的話,不要忘了寫DMA中斷服務程式。而且DMA的配置也是需要用戶實現的,可以直接在函數HAL_UART_MspInit裡面實現,也可以放在其它位置。
函數參數:
- 第1個參數是UART_HandleTypeDef類型結構體指針變數。
- 第2個參數是要發送的數據地址。
- 第3個參數是要發送的數據大小,單位位元組。
- 返回值,返回HAL_ERROR表示參數錯誤,HAL_OK表示發送成功,HAL_BUSY表示串口忙,正在使用中。
使用舉例:
使用DMA方式,
65.4.7 函數HAL_UART_Receive_DMA
函數原型:
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) { if(huart->RxState == HAL_UART_STATE_READY) { if((pData == NULL ) || (Size == 0U)) { return HAL_ERROR; } __HAL_LOCK(huart); huart->pRxBuffPtr = pData; huart->RxXferSize = Size; huart->ErrorCode = HAL_UART_ERROR_NONE; huart->RxState = HAL_UART_STATE_BUSY_RX; /* 註冊各種DMA回調函數 */ huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt; huart->hdmarx->XferHalfCpltCallback = UART_DMARxHalfCplt; huart->hdmarx->XferErrorCallback = UART_DMAError; huart->hdmarx->XferAbortCallback = NULL; /* 使能串口接收DMA通道 */ HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->RDR, (uint32_t)huart->pRxBuffPtr, Size); __HAL_UNLOCK(huart); /* 使能串口校驗錯誤中斷 */ SET_BIT(huart->Instance->CR1, USART_CR1_PEIE); /* 使能串口錯誤中斷:(Frame error, noise error, overrun error) */ SET_BIT(huart->Instance->CR3, USART_CR3_EIE); /* 使能串口接收DMA傳輸 */ SET_BIT(huart->Instance->CR3, USART_CR3_DMAR); return HAL_OK; } else { return HAL_BUSY; } }
函數描述:
此函數以DMA的方式接收指定位元組。這裡是用的DMA中斷方式HAL_DMA_Start_IT進行的接收。所以使用此函數的話,不要忘了寫DMA中斷服務程式。而且DMA的配置也是需要用戶實現的,可以直接在函數HAL_UART_MspInit裡面實現,也可以放在其它位置。
函數參數:
- 第1個參數是UART_HandleTypeDef類型結構體指針變數。
- 第2個參數是要接收的數據地址。
- 第3個參數是要接收的數據大小,單位位元組。
- 返回值,返回HAL_ERROR表示參數錯誤,HAL_OK表示發送成功,HAL_BUSY表示串口忙,正在使用中。
使用舉例:
使用DMA方式
65.5 總結
本章節就為大家講解這麼多,涉及到的知識點和API函數比較多,需要花點時間消化,後面用到的多了,就可以熟練掌握了。