最新教程下載:http://www.armbbs.cn/forum.php?mod=viewthread&tid=93255
第23章 STM32F429的USART串口基礎知識和HAL庫API
本章節為大家講解USART(Universal synchronous asynchronous receiver transmitter,通用同步異步收發器)的基礎知識和對應的HAL庫API。
23.1 初學者重要提示
23.2 串口基礎知識
23.3 串口的HAL庫用法
23.4 源文件stm32f4xx_hal_uart.c
23.5 總結
23.1 初學者重要提示
- 學習串口外設推薦從硬件框圖開始了解基本的功能特性,然后逐步深入了解各種特性,這種方式方便記憶和以后查閱。而串口的通信學習,推薦看時序圖。
- 部分中斷標志是可以通過操作發送數據寄存器TDR或者接收數據寄存器RDR實現清除,這點要特別注意,詳情看本章23.3.4小節。
- 初次使用USART,還是有不少注意事項的,詳情看本章23.3.3小節和23.4.1小節。
23.2 串口基礎知識
USART的全稱是Universal synchronous asynchronous receiver transmitter,中文意思是通用同步異步收發器。我們經常使用串口是異步串口,簡稱UART。
23.2.1 串口的硬件框圖
認識一個外設,最好的方式就是看它的框圖,方便我們快速的了解串口的基本功能,然后再看手冊了解細節。
通過這個框圖,我們可以得到如下信息:
- TX和RX接口
分別用於數據的發送和接收。
- SW_RX接口
在智能卡模式下,此接口用於接收數據。
- IRAD_OUT和IRAD_IN接口
用於IRAD模式的數據發送和接收。
- RST和CTS接口
用於硬件流控制。
- USART_BRR
波特率生成單元。
- 發送過程經過的寄存器
依次是USART_TDR -> TxFIFO ->Tx Shift Reg偏移寄存器 –> TX或者RX引腳。
- 接收經過的寄存器
依次是TX或者RX引腳-> Rx Shift Reg偏移寄存器->RxFIFO –>USART_RDR。
23.2.2 串口的基本功能
STM32的串口功能很強大,支持太多的模式。我們只需關心我們最常用的特性即可。我們的串口驅動使用的串口中斷+FIFO結構,沒有使用DMA。因此我們只討論和串口中斷、串口常規參數有關的知識。
STM32串口的優越特性:(只列了舉常用的)
- 任意波特率。硬件采用分數波特率發生器系統,可以設置任意的波特率,最高達11.25Mbits/s。這一點很重要。比如ES8266串口WIFI芯片,上電時有個奇怪的波特率74880bps,當然STM32是可以支持的。
- 可編程數據字長度,支持8bit和9bit。
- 可配置的停止位。支持1或2個停止位。
- 發送器和接收器可以單獨使能。比如GPS應用只需要串口接收,那么發送的GPIO就可以節省出來用作其他功能。
- 檢測標志和中斷:
a. 接收緩沖器滿,可產生中斷。串口中斷服務程序據此判斷是否接收到數據。
b. 發送緩沖器空,可產生中斷。串口中斷服務程序據此啟動發送下一個數據。
c. 傳輸結束標志,可產生中斷。用於RS485通信,等最后一個字節發送完畢后,需要控制RS485收發器芯片切換為接收模式。
其它中斷不常用,包括:CTS改變、LIN斷開符檢測、檢測到總線為空閑(在DMA不定長接收方式會用到)、溢出錯誤、幀錯誤、噪音錯誤、校驗錯誤。
23.2.3 不同串口支持的特性異同
通過下面的表格,可以對串口1-8支持的功能有個全面的認識:
23.2.4 串口的數據幀格式
串口支持的幀格式如下(M和PCE都是USART_CR1寄存器的位,其中M位用於控制幀長度,PCE用於使能奇偶校驗位):
這里特別注意奇偶校驗位,用戶在配置的時候可以選擇奇校驗和偶校驗,校驗位是占據的最高位。比如選擇M=00,PCE=1,即7bit的數據位。
- 串口發送數據:
如果發送的7bit數據是111 0011,這個里面有奇數個1,那么選擇偶校驗的情況下,校驗位 = 1,湊夠偶數個1,而選擇奇校驗的情況下,校驗位 = 0,因為已經是奇數個1。校驗位不需要用戶去計算,是硬件自動生成的。
- 串口接收數據:
根據用戶設置的奇校驗或者偶校驗類型,串口硬件會對接收到的數據做校驗,如果失敗,USART_ISR寄存器的PE位會被置1。如果使能了對應的中斷PEIE,那么失敗的時候還會產生中斷。
了解到幀格式后,再來看一下實際數據發送時,數據位的先后順序:
23.2.5 串口發送時序圖
這個時序圖非常具有代表性,可以幫助大家很好的理解TC發送完成中斷和TXE空中斷。
23.2.6 同步串口和異步串口的區別
異步通信是按字符傳輸的。每傳輸一個字符就用起始位來進行收、發雙方的同步,不會因收發雙方的時鍾頻率的小的偏差導致錯誤。這種傳輸方式利用每一幀的起、止信號來建立發送與接收之間的同步。
異步的特點是:每幀內部各位均采用固定的時間間隔,而幀與幀之間的間隔是隨機的。接收機完全靠每一幀的起始位和停止位來識別字符是正在進行傳輸還是傳輸結束。
同步通信的發送和接收雙方要保持完全的同步,因此要求接收和發送設備必須使用同一時鍾。優點是可以實現高速度、大容量的數據傳送;缺點是要求發生時鍾和接收時鍾保持嚴格同步,同時硬件復雜。
可以這樣說,不管是異步通信還是同步通信都需要進行同步,只是異步通信通過傳送字符內的起始位來進行同步,而同步通信采用共用外部時鍾來進行同步。所以,可以說前者是自同步,后者是外同步。
23.2.7 單工,半雙工和全雙工通訊
單工:在一個單工的串行通訊系統中,一般至少有兩根線(信號線和地線),數據傳送只有一個方向,例如可以使用單工數據傳送將數據從一個簡單的數據監測系統傳送到PC上。
半雙工:在半雙工串行通信系統中,一般同樣要求至少有兩根線。這里的數據傳送是雙向的。然而,同一個時刻只能為一個方向。在上面的數據監測的例子中做了一些變化,可以使用半雙工通訊機制發送信息到嵌入式模塊(來設置參數,比如采樣率)。此外,在其他時候,可以使用這個種連接將嵌入式裝置上的數據下載到PC中。
全雙工:在一個全雙工的串行通信系統中,一般要求至少有三根線(信號線A,信號線B和地線)。信號線A將傳輸一個方向上的數據,同時信號線B傳送另一個方向上的數據。
23.3 串口的HAL庫用法
串口的HAL庫用法其實就是幾個結構體變量成員的配置和使用,然后配置GPIO、時鍾,並根據需要配置NVIC、中斷和DMA。下面我們逐一展開為大家做個說明。
23.3.1 串口寄存器結構體USART_TypeDef
USART相關的寄存器是通過HAL庫中的結構體USART_TypeDef定義的,在stm32h743xx.h中可以找到這個類型定義:
typedef struct { __IO uint32_t SR; /*!< USART Status register, Address offset: 0x00 */ __IO uint32_t DR; /*!< USART Data register, Address offset: 0x04 */ __IO uint32_t BRR; /*!< USART Baud rate register, Address offset: 0x08 */ __IO uint32_t CR1; /*!< USART Control register 1, Address offset: 0x0C */ __IO uint32_t CR2; /*!< USART Control register 2, Address offset: 0x10 */ __IO uint32_t CR3; /*!< USART Control register 3, Address offset: 0x14 */ __IO uint32_t GTPR; /*!< USART Guard time and prescaler register, Address offset: 0x18 */ } USART_TypeDef;
這個結構體的成員名稱和排列次序和CPU的USART寄存器是一 一對應的。
__IO表示volatile, 這是標准C語言中的一個修飾字,表示這個變量是非易失性的,編譯器不要將其優化掉。core_m4.h文件定義了這個宏:
#define __O volatile /*!< Defines 'write only' permissions */ #define __IO volatile /*!< Defines 'read / write' permissions */
下面我們看下USART1、USART2 ... UART8的定義,在stm32f429.h文件
#define PERIPH_BASE 0x40000000UL #define APB1PERIPH_BASE PERIPH_BASE #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000UL) #define USART1_BASE (APB2PERIPH_BASE + 0x1000UL) #define USART6_BASE (APB2PERIPH_BASE + 0x1400UL) #define USART2_BASE (APB1PERIPH_BASE + 0x4400UL) #define USART3_BASE (APB1PERIPH_BASE + 0x4800UL) #define UART4_BASE (APB1PERIPH_BASE + 0x4C00UL) #define UART5_BASE (APB1PERIPH_BASE + 0x5000UL) #define UART7_BASE (APB1PERIPH_BASE + 0x7800UL) #define UART8_BASE (APB1PERIPH_BASE + 0x7C00UL) #define USART1 ((USART_TypeDef *) USART1_BASE) <----- 展開這個宏,(USART_TypeDef *) 0x40001010 #define USART2 ((USART_TypeDef *) USART2_BASE) #define USART3 ((USART_TypeDef *) USART3_BASE) #define UART4 ((USART_TypeDef *) UART4_BASE) #define UART5 ((USART_TypeDef *) UART5_BASE) #define USART6 ((USART_TypeDef *) USART6_BASE) #define UART7 ((USART_TypeDef *) UART7_BASE) #define UART8 ((USART_TypeDef *) UART8_BASE)
我們訪問USART1的CR1寄存器可以采用這種形式:USART1->CR1 = 0。
23.3.2 串口句柄結構體UART_HandleTypeDef
HAL庫在USART_TypeDef的基礎上封裝了一個結構體UART_HandleTypeDef,定義如下:
typedef struct __UART_HandleTypeDef { USART_TypeDef *Instance; /*!< UART registers base address */ UART_InitTypeDef Init; /*!< UART communication parameters */ uint8_t *pTxBuffPtr; /*!< Pointer to UART Tx transfer Buffer */ uint16_t TxXferSize; /*!< UART Tx Transfer size */ __IO uint16_t TxXferCount; /*!< UART Tx Transfer Counter */ uint8_t *pRxBuffPtr; /*!< Pointer to UART Rx transfer Buffer */ uint16_t RxXferSize; /*!< UART Rx Transfer size */ __IO uint16_t RxXferCount; /*!< UART Rx Transfer Counter */ DMA_HandleTypeDef *hdmatx; /*!< UART Tx DMA Handle parameters */ DMA_HandleTypeDef *hdmarx; /*!< UART Rx DMA Handle parameters */ HAL_LockTypeDef Lock; /*!< Locking object */ __IO HAL_UART_StateTypeDef gState; /*!< UART state information related to global Handle management and also related to Tx operations. This parameter can be a value of @ref HAL_UART_StateTypeDef */ __IO HAL_UART_StateTypeDef RxState; /*!< UART state information related to Rx operations. This parameter can be a value of @ref HAL_UART_StateTypeDef */ __IO uint32_t ErrorCode; /*!< UART Error code */ #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 (* MspInitCallback)(struct __UART_HandleTypeDef *huart); void (* MspDeInitCallback)(struct __UART_HandleTypeDef *huart); #endif } UART_HandleTypeDef;
這里重點介紹前兩個參數,其它參數主要是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倍過采樣 */ } UART_InitTypeDef;
配置串口參數,其實就是配置結構體UART_HandleTypeDef的成員。比如下面配置為波特率115200,8個數據位,無奇偶校驗,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; if(HAL_UART_Init(&UartHandle) != HAL_OK) { Error_Handler(); }
- 條件編譯USE_HAL_UART_REGISTER_CALLBACKS
用於串口回調函數的設置。
如果要用到這種回調函數定義方式,可以在stm32f4xx_hal_conf.h文件里面使能。
23.3.3 串口的底層配置(GPIO、時鍾、中斷等)
串口外設的基本參數配置完畢后還不能使用,還需要配置GPIO、時鍾、中斷等參數,比如下面配置串口1,使用引腳PA9和PA10。
/* 串口1的GPIO PA9, PA10 */ #define USART1_CLK_ENABLE() __HAL_RCC_USART1_CLK_ENABLE() #define USART1_TX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() #define USART1_TX_GPIO_PORT GPIOA #define USART1_TX_PIN GPIO_PIN_9 #define USART1_TX_AF GPIO_AF7_USART1 /* ********************************************************************************************************* * 函 數 名: InitHardUart * 功能說明: 配置串口的硬件參數和底層 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void InitHardUart(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_PeriphCLKInitTypeDef RCC_PeriphClkInit; #if UART1_FIFO_EN == 1 /* 串口1 */ /* 使能 GPIO TX/RX 時鍾 */ USART1_TX_GPIO_CLK_ENABLE(); USART1_RX_GPIO_CLK_ENABLE(); /* 使能 USARTx 時鍾 */ USART1_CLK_ENABLE(); /* 配置TX引腳 */ GPIO_InitStruct.Pin = USART1_TX_PIN; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = USART1_TX_AF; HAL_GPIO_Init(USART1_TX_GPIO_PORT, &GPIO_InitStruct); /* 配置RX引腳 */ GPIO_InitStruct.Pin = USART1_RX_PIN; GPIO_InitStruct.Alternate = USART1_RX_AF; HAL_GPIO_Init(USART1_RX_GPIO_PORT, &GPIO_InitStruct); /* 配置NVIC the NVIC for UART */ HAL_NVIC_SetPriority(USART1_IRQn, 0, 1); HAL_NVIC_EnableIRQ(USART1_IRQn); /* 配置波特率、奇偶校驗 */ bsp_SetUartParam(USART1, UART1_BAUD, UART_PARITY_NONE, UART_MODE_TX_RX); CLEAR_BIT(USART1->SR, USART_SR_TC); /* 清除TC發送完成標志 */ CLEAR_BIT(USART1->SR, USART_SR_RXNE); /* 清除RXNE接收標志 */ // USART_CR1_PEIE | USART_CR1_RXNEIE SET_BIT(USART1->CR1, USART_CR1_RXNEIE); /* 使能PE. RX接受中斷 */ #endif }
總結下來就是以下幾點:
- 配置GPIO引腳時鍾。
- 配置USART時鍾。
- 配置USART的發送和接收引腳。
- 通過NVIC配置中斷。
- 配置波特率,奇偶校驗等,在上一小節有講。
- 清除TC和RXNE標志,使能接收中斷。
關於這個底層配置有以下幾點要着重說明下:
- 串口發送和接收引腳的復用模式選擇已經被HAL庫定義好,放在了stm32f4xx_hal_gpio_ex.h文件里面。比如串口1有兩個復用
#define GPIO_AF7_USART1 ((uint8_t)0x07) /* USART1 Alternate Function mapping */
為什么使用的AF7,這個是在數據手冊里定義好的:
那么使用GPIO_AF7_USART1即可。
- 根據情況要清除TC發送完成標志和RXNE接收數據標志,因為這兩個標志位在使能了串口后就已經置位,所以當用戶使用了TC或者RX中斷后,就會進入一次中斷服務程序,這點要特別注意。
- HAL庫有個自己的底層初始化回調函數HAL_UART_MspInit,是弱定義的,用戶可以在其它的C文件里面實現,並將相對的底層初始化在里面實現。當用戶調用HAL_UART_Init后,會在此函數里面調用HAL_UART_MspInit,對應的底層復位函數HAL_UART_MspDeInit是在函數HAL_UART_DeInit里面被調用的。當然,用戶也可以自己初始化,不限制必須在兩個函數里面實現。
- 上面舉的例子里面沒有用到DMA,如果用到了DMA,也是要初始化的。
23.3.4 串口的狀態標志清除問題
注,經常會有網友咨詢為什么串口中斷服務程序里面沒有做清除標志。
下面我們介紹__HAL_USART_GET_FLAG函數。這個函數用來檢查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有如下幾種取值:
請大家重點關注上表中紅字部分,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,直接調用USART1->CR1操作即可。
23.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步:串口初始化調用的函數HAL_UART_Init初始化。
23.4 源文件stm32f4xx_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
其實V6開發板設計的8個串口FIFO驅動文件bsp_uart_fifo.c僅用到了函數HAL_UART_Init,其它函數都沒有用到,不過這里也為大家做個說明。
23.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表示串口忙,正在使用中。
注意事項:
- 函數HAL_UART_MspInit用於初始化USART的底層時鍾、引腳等功能。需要用戶自己在此函數里面實現具體的功能。由於這個函數是弱定義的,允許用戶在工程其它源文件里面重新實現此函數。當然,不限制一定要在此函數里面實現,也可以像早期的標准庫那樣,用戶自己初始化即可,更靈活些。
- 如果形參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(); }
- 注意串口的中斷狀態寄存器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; if(HAL_UART_Init(&UartHandle) != HAL_OK) { Error_Handler(); }
23.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) { /* 省略 */ /* 上鎖 */ __HAL_LOCK(huart); while(huart->TxXferCount > 0U) { huart->TxXferCount--; /數據是9bit */ if (huart->Init.WordLength == UART_WORDLENGTH_9B) { /* 等待發送空標志 */ if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; } tmp = (uint16_t *) pData; huart->Instance->DR = (*tmp & (uint16_t)0x01FF); if (huart->Init.Parity == UART_PARITY_NONE) { pData += 2U; } else { pData += 1U; } } /* 其它數據寬度 */ else { /* 等待發送空標志 */ if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; } huart->Instance->DR = (*pData++ & (uint8_t)0xFF); } } /* 等待發送完成 */ if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; } /* 就緒 */ huart->gState = HAL_UART_STATE_READY; /* 解鎖 */ __HAL_UNLOCK(huart); 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; }
23.4.3 函數HAL_UART_Receive
函數原型:
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout) { uint16_t *tmp; uint32_t tickstart = 0U; /* 檢測RX接收狀態就緒 */ if (huart->RxState == HAL_UART_STATE_READY) { if ((pData == NULL) || (Size == 0U)) { return HAL_ERROR; } /* 上鎖 */ __HAL_LOCK(huart); huart->ErrorCode = HAL_UART_ERROR_NONE; huart->RxState = HAL_UART_STATE_BUSY_RX; /* 獲取當前的計數 */ tickstart = HAL_GetTick(); huart->RxXferSize = Size; huart->RxXferCount = Size; /* 阻塞式接收數據 */ while (huart->RxXferCount > 0U) { huart->RxXferCount--; /* 接收的數據是9bit的 */ if (huart->Init.WordLength == UART_WORDLENGTH_9B) { if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; } tmp = (uint16_t *) pData; if (huart->Init.Parity == UART_PARITY_NONE) { *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF); pData += 2U; } else { *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF); pData += 1U; } } /* 其它bit接收 */ else { if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; } if (huart->Init.Parity == UART_PARITY_NONE) { *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF); } else { *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F); } } } /* 接收就緒 At end of Rx process, restore huart->RxState to Ready */ huart->RxState = HAL_UART_STATE_READY; /* 解鎖 */ __HAL_UNLOCK(huart); 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; }
23.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); /* 使能發送空中斷 */ __HAL_UART_ENABLE_IT(huart, UART_IT_TXE); return HAL_OK; } else { return HAL_BUSY; } }
函數描述:
此函數以中斷的方式發送指定字節,可以選擇使能FIFO中斷方式或者發送空中斷方式。具體數據的發送是在中斷處理函數HAL_UART_IRQHandler里面實現。
函數參數:
- 第1個參數是UART_HandleTypeDef類型結構體指針變量。
- 第2個參數是要發送的數據地址。
- 第3個參數是要發送的數據大小,單位字節。
- 返回值,返回HAL_ERROR表示參數錯誤,HAL_OK表示發送成功,HAL_BUSY表示串口忙,正在使用中。
使用舉例:
使用中斷方式要使能串口中斷,此貼有完整例子:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=86245 。
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_K1\r\n", 13);
23.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; huart->ErrorCode = HAL_UART_ERROR_NONE; huart->RxState = HAL_UART_STATE_BUSY_RX; /* 解鎖 */ __HAL_UNLOCK(huart); /* 使能校驗錯誤中斷 */ __HAL_UART_ENABLE_IT(huart, UART_IT_PE); /* 使能幀錯誤,噪聲錯誤和溢出中斷 */ __HAL_UART_ENABLE_IT(huart, UART_IT_ERR); /* 使能接收非空中斷 ,即接收中斷 / __HAL_UART_ENABLE_IT(huart, UART_IT_RXNE); return HAL_OK; } else { return HAL_BUSY; } }
函數描述:
此函數以中斷的方式接收指定字節,使能了奇偶校驗中斷失敗和錯誤中斷。具體數據的接收是在中斷處理函數HAL_UART_IRQHandler里面實現。
函數參數:
- 第1個參數是UART_HandleTypeDef類型結構體指針變量。
- 第2個參數是要接收的數據地址。
- 第3個參數是要接收的數據大小,單位字節。
- 返回值,返回HAL_ERROR表示參數錯誤,HAL_OK表示發送成功,HAL_BUSY表示串口忙,正在使用中。
使用舉例:
使用中斷方式要使能串口中斷,此貼有完整例子:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=86245 。
UART_HandleTypeDef UartHandle; uint8_t s_ucBuf[5]; /* 數據接收*/ HAL_UART_Receive_IT(&UartHandle, s_ucBuf, 1);
23.4.6 函數HAL_UART_Transmit_DMA
函數原型:
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) { uint32_t *tmp; /* 檢查是否處於就緒態 */ 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; /* 設置DMA錯誤回調 */ huart->hdmatx->XferErrorCallback = UART_DMAError; /* 設置DMA終止回調 */ huart->hdmatx->XferAbortCallback = NULL; /* 使能串口DMA方式發送 */ tmp = (uint32_t *)&pData; HAL_DMA_Start_IT(huart->hdmatx, *(uint32_t *)tmp, (uint32_t)&huart->Instance->DR, Size); /* 清除TC標志 */ __HAL_UART_CLEAR_FLAG(huart, UART_FLAG_TC); /* 解鎖 */ __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方式,此貼有完整例子:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=86271 。
23.4.7 函數HAL_UART_Receive_DMA
函數原型:
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) { uint32_t *tmp; /* 檢查是否處於就緒態 */ 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; /* 設置DMA錯誤回調 */ huart->hdmarx->XferErrorCallback = UART_DMAError; /* 設置DMA終止傳輸回調 */ huart->hdmarx->XferAbortCallback = NULL; /* 使能串口DMA Enable the DMA stream */ tmp = (uint32_t *)&pData; HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, *(uint32_t *)tmp, Size); /*在啟用DMA Rx請求之前清除溢出標志:可以強制第2次傳輸 */ __HAL_UART_CLEAR_OREFLAG(huart); /* 上鎖 */ __HAL_UNLOCK(huart); /* 使能校驗錯誤中斷 */ SET_BIT(huart->Instance->CR1, USART_CR1_PEIE); /* 使能幀錯誤,噪聲錯誤和溢出中斷 */ 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方式,此貼有完整例子:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=86271 。
23.5 總結
本章節就為大家講解這么多,涉及到的知識點和API函數比較多,需要花點時間消化,后面用到的多了,就可以熟練掌握了。