知識
串口是一種通訊協議,存在於 設備-設備 之間。在介紹串口協議之前,我們先來看看通信網絡中的分層。如果參考OSI模型, 網絡OSI七層模型及各層作用 那么它屬於數據鏈路層。
串行數據通信的方向性結構有三種,即單工、半雙工和全雙工。
串口通信的概念非常簡單,串口按位(bit)發送和接收字節。盡管比按字節(byte)的並行通信慢,但是串口可以在使用一根線發送數據的同時用另一根線接收數據。它很簡單並且能夠實現遠距離通信。比如IEEE488定義並行通行狀態時,規定設備線總常不得超過20米,並且任意兩個設備間的長度不得超過2米;而對於串口而言,長度可達1200米。
憑借着其改善的信號完整性和傳播速度,串行通信總線正在變得越來越普遍,甚至在短程距離的應用中,其優越性已經開始超越並行總線不需要串行化元件(serializer),並解決了諸如時鍾偏移(Clock skew)、互聯密度等缺點。PCI到PCI Express的升級就一個例子。
USART(Universal Synchronous/Asynchronous Receiver/Transmitter, 通用同步/異步串行接收/發送器)是一個全雙工通用同步/異步串行收發模塊,該接口是一個高度靈活的串行通信設備。
uart和usart的區別
UART:universal asynchronous receiver and transmitter通用異步收/發器
USART:universal synchronous asynchronous receiver and transmitter通用同步/異步收/發器
從名字上可以看出,USART在UART基礎上增加了同步功能。
- 當我們使用USART在異步通信的時候,它與UART沒有什么區別
- 在同步通信時,USART能夠提供主動時鍾。如STM32的USART可以提供時鍾支持ISO7816的智能卡接口。
串口的有關參數
典型地,串口用於ASCII碼字符的傳輸。通信使用3根線完成:地線(GND),發送(Tx),接收(Rx)。由於串口通信是異步的,端口能夠在一根線上發送數據同時在另一根線上接收數據。其他線用於握手,但是不是必須的。串口通信最重要的參數是波特率、數據位、停止位和奇偶校驗。對於兩個進行通行的端口,這些參數必須匹配:
波特率 (Baud rate)
同步通訊需要時鍾信號來進行同步;而異步通信由於沒有時鍾信號,因此兩個通訊設備之間需要約定好波特率,即每個碼元的長度,以便對信號進行解碼。常見的波特率為4800、9600、115200等。
這是一個衡量通信速度的參數。它表示每秒鍾傳送的bit的個數。例如300波特表示每秒鍾發送300個bit。當我們提到時鍾周期時,我們就是指波特率例如如果協議需要4800波特率,那么時鍾是4800Hz。這意味着串口通信在數據線上的采樣率為4800Hz。通常電話線的波特率為14400,28800和36600。波特率可以遠遠大於這些值,但是波特率和距離成反比。高波特率常常用於放置的很近的儀器間的通信,典型的例子就是GPIB設備的通信。
數據位 (Data bit)
這是衡量通信中實際數據位的參數。當計算機發送一個信息包,實際的數據不會是8位的,標准的值是5、7和8位。如何設置取決於你想傳送的信息。比如,標准的ASCII碼是0~127(7位)。擴展的ASCII碼是0~255(8位)。如果數據使用簡單的文本(標准 ASCII碼),那么每個數據包使用7位數據。每個包是指一個字節,包括開始/停止位,數據位和奇偶校驗位。由於實際數據位取決於通信協議的選取,術語“包”指任何通信的情況。
停止位 (Stop bit)
用於表示單個包的最后一位。典型的值為1,1.5和2位。由於數據是在傳輸線上定時的,並且每一個設備有其自己的時鍾,很可能在通信中兩台設備間出現了小小的不同步。因此停止位不僅僅是表示傳輸的結束,並且提供計算機校正時鍾同步的機會。適用於停止位的位數越多,不同時鍾同步的容忍程度越大,但是數據傳輸率同時也越慢。
奇偶校驗位 (Parity bit)
在串口通信中一種簡單的檢錯方式。有四種檢錯方式:偶、奇、高和低。當然沒有校驗位也是可以的。對於偶和奇校驗的情況,串口會設置校驗位(數據位后面的一位),用一個值確保傳輸的數據有偶個或者奇個邏輯高位。例如,如果數據是011,那么對於偶校驗,校驗位為0,保證邏輯高的位數是偶數個。如果是奇校驗,校驗位位1,這樣就有3個邏輯高位。高位和低位不真正的檢查數據,簡單置位邏輯高或者邏輯低校驗。這樣使得接收設備能夠知道一個位的狀態,有機會判斷是否有噪聲干擾了通信或者是否傳輸和接收數據是否不同步。
CubeMX 配置 USART (以 USART2 為例)
關於串口的時候有3種模式。
HOST-OS : Windows-10
STM32 Cube :v5.6
MCU : STM32F429
LIB : stm32cube_fw_f4_v1250
輪詢模式
CPU不斷查詢IO設備,如設備有請求則加以處理。例如CPU不斷查詢串口是否傳輸完成,如傳輸超過則返回超時錯誤。輪詢方式會占用CPU處理時間,效率較低。
CubeMx 配置
1)在Pinout & Configuration中,選擇一個 UART/USART
2)Mode
: Asynchronous
(異步); Hardware Flow Control
(硬件流控) 選擇 Disable
3)Configuration
- Parameter Settings
中 (任意設置都可以,但通訊雙方要匹配)
- Baud Rate : 波特率,一般使用
115200
- Word Length : 字長 8
- Parity: 校驗
- Stop Bits : 停止位
4)填寫有關的項目屬性
5)右上角,GENERATE CODE
添加代碼
實現:簡單地發送有關的數據。
/* USER CODE BEGIN 1 */
uint8_t aTxBuffer[] = "Hello CubeMx\r\n";
int len = strlen(aTxBuffer);
/* USER CODE END 1 */
/* USER CODE BEGIN 2 */
HAL_UART_Transmit(&huart2, aTxBuffer, len, 0xFFFF); // 表示通過串口發送len個字符。ch為字符的存儲地址,0xFFFF為超時時間。
/* USER CODE END 2 */
編譯,運行;在串口助手中應該可以看到打印的消息。
中斷模式
當I/O操作完成時,輸入輸出設備控制器通過中斷請求線向處理器發出中斷信號,處理器收到中斷信號之后,轉到中斷處理程序,對數據傳送工作進行相應的處理。
配置
1)在Pinout & Configuration頁中的Connectivity
,選擇一個 UART/USART
Mode
:Asynchronous
(異步);Hardware Flow Control
(硬件流控) 選擇Disable
Configuration
-Parameter Settings
中 (任意設置都可以,但通訊雙方要匹配)- Baud Rate : 波特率,一般使用
115200
- Word Length : 字長 8
- Parity: 校驗
- Stop Bits : 停止位
- Baud Rate : 波特率,一般使用
Configuration
-NVIC Settings
中 : 勾選Enabled
(開啟中斷)
2)在Pinout & Configuration中,System Core
,選擇NVIC
Configuration
-Parameter Settings
中 ,確認UsartX global interrupt
的Enable
是勾選的Configuration
-Code generation
中,確認UsartX global interrupt
的Select for init sequence ordering
是勾選的。
3)填寫有關的項目屬性
4)右上角,GENERATE CODE
添加代碼
實現:接收數據並重新發回(echo)。
例子只是作為演示,在實際工程中不要這么用。
/* USER CODE BEGIN PV */
// 為了方便,這里使用到了全局變量
uint8_t aTxBuffer[] = "Hello CubeMx\r\n";
uint8_t aRxBuffer[20];/* Buffer used for recv */
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
HAL_UART_Transmit_IT(&huart2, (uint8_t *)aTxBuffer, sizeof(aTxBuffer) - 1);
HAL_UART_Receive_IT(&huart2, (uint8_t *)aRxBuffer, 1);
/* USER CODE END 2 */
在main.c文件后面重寫HAL_UART_RxCpltCallback
中斷接收完成回調函數;每次收完數據以后,
/* USER CODE BEGIN 4 */
/**
* @brief Rx Transfer completed callbacks
* @param huart: uart handle
* @retval None
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(huart);
/* NOTE : This function should not be modified, when the callback is needed,
the HAL_UART_RxCpltCallback can be implemented in the user file
*/
if(huart == &huart2)
{
HAL_UART_Transmit(huart, (uint8_t *)aRxBuffer, 10,0xFFFF);
HAL_UART_Receive_IT(huart, (uint8_t *)aRxBuffer, 1);
}
}
/* USER CODE END 4 */
注意事項
有關資料:關於HAL UART 發送接收死鎖問題、慎用HAL_UART_RxCpltCallback中調用HAL_UART_Receive_IT
HAL_UART_RxCpltCallback
中使用HAL_UART_Transmit
的問題分析:
由於HAL_UART_RxCpltCallback()
函數是在中斷里被調用的;
此時,如果在HAL_UART_RxCpltCallback
使用了HAL_UART_Receive_IT()
,最好不用HAL_UART_Transmit()
因為發送過程會鎖定串口,這時來了讀取中斷,其中的下一次HAL_UART_Receive_IT()
會因為獲得不了設備而失敗,因此中斷的鏈條就打斷了。
HAL_UART_RxCpltCallback
里面也不能用HAL_UART_Receive_IT
,因為會把ErrorCode
覆蓋掉,HAL_UART_Receive_IT
只是開啟中斷函數, 可以在對應的IRQHandler
(例如USART2_IRQHandler
)最后執行。
解決方案:收發盡量一致(要么都是中斷,要么都是阻塞式)
- 要么:選擇
HAL_UART_Receive_IT
放在主程序的while(1)
循環中 - 要么:在
HAL_UART_RxCpltCallback
中使用HAL_UART_Transmit
之前關閉中斷,調用完成以后再次打開,將此后再使用HAL_UART_Receive_IT
接收中斷。 - 要么:
HAL_UART_RxCpltCallback
中只使用HAL_UART_Receive_IT
重新開啟中斷,不使用HAL_UART_Transmit
。如果一定要發,維護一條任務隊列,在中斷回調函數中添加需要發送的數據,讓其在正常的調度環境中發送。
DMA模式
DMA(直接內存存取技術),直接傳送。即在內存與IO設備間傳送一個數據塊的過程中,不需要CPU的任何中間干涉,只需要CPU在過程開始時向設備發出“傳送塊數據”的命令,然后通過中斷來得知過程是否結束和下次操作是否准備就緒。
我們留在下一講進行講解。
附錄:使用printf串口打印
以 USART2 為例,在USART的初始化文件中添加如下代碼:
printf
的函數putc
不要用中斷,要用直接發送的HAL_UART_Transmit
。否則中斷無法發送。
/* USER CODE BEGIN 0 */
#include "stdio.h"
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
/* 重定向printf*/
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart2, (uint8_t*)&ch,1,HAL_MAX_DELAY);
return ch;
}
/* 重定向scanf */
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart2, &ch, 1, 0xffff);
return ch;
}
/* USER CODE END 0 */
附錄:Hal 庫 串口 收發 有關函數
輪詢模式
輪詢模式 | 功能 |
---|---|
HAL_UART_Transmit | 串口輪詢模式發送,使用超時管理機制。 |
HAL_UART_Receive | 串口輪詢模式接收,使用超時管理機制。 |
中斷模式
中斷模式 | 功能 |
---|---|
HAL_UART_Transmit_IT | 串口中斷模式發送 |
HAL_UART_Receive_IT | 串口中斷模式接收(當接受滿指定數量的字符打開中斷) |
DMA模式
DMA模式 | |
---|---|
HAL_UART_Transmit_DMA | 串口DMA模式發送 |
HAL_UART_Receive_DMA | 串口DMA模式接收 |
中斷回調函數
輪詢模式沒有中斷回調函數,中斷模式以及DMA模式的傳輸具備此功能。
串口相關的中斷回調函數 | |
---|---|
HAL_UART_TxHalfCpltCallback | 一半數據(half transfer)發送完成后,中斷處理函數調用此函數。 |
HAL_UART_RxHalfCpltCallback | 一半數據(half transfer)接收完成后,中斷處理函數調用此函數。 |
HAL_UART_TxCpltCallback | 發送完成后,中斷處理函數調用此函數。 |
HAL_UART_RxCpltCallback | 接收完成后,中斷處理函數調用此函數。 |
HAL_UART_ErrorCallback | 傳輸過程中出現錯誤時,中斷處理函數調用此函數。 |
UART串口接收數據異常導致卡死
有一個項目要用到串口通訊,異常數據會使串口直接卡死,而且不會恢復,只能重新上電才能恢復。
仿真查詢,會一直進中斷死在這邊:
void USART3_IRQHandler(void)
{
/* USER CODE BEGIN USART3_IRQn 0 */
/* USER CODE END USART3_IRQn 0 */
HAL_UART_IRQHandler(&huart3);
/* USER CODE BEGIN USART3_IRQn 1 */
/* USER CODE END USART3_IRQn 1 */
}
網上查詢了有很多類似的情況,錯誤由ORE導致,這個錯誤置起來后一直清不掉
原因找到了,但是不知道怎么解決,但是網上也找不到解決方法,只能自己解決了
1、先查手冊,查看錯誤標志有哪些,要怎么清掉,不會上傳圖片所以手冊截圖就不發了
2、怎么調用HAL庫文件清錯誤標志
#define __HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->SR & (__FLAG__)) == (__FLAG__)) // 獲取錯誤標志
#define __HAL_UART_CLEAR_FLAG(__HANDLE__, __FLAG__) ((__HANDLE__)->Instance->SR = ~(__FLAG__)) //清標志
3、建一個錯誤回調函數,下面這個我試過可以用,
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
uint32_t isrflags = READ_REG(huart->Instance->SR);//手冊上有講,清錯誤都要先讀SR
if((__HAL_UART_GET_FLAG(huart, UART_FLAG_PE))!=RESET)
{
READ_REG(huart->Instance->DR);//PE清標志,第二步讀DR
__HAL_UART_CLEAR_FLAG(huart, UART_FLAG_PE);//清標志
}
if((__HAL_UART_GET_FLAG(huart, UART_FLAG_FE))!=RESET)
{
READ_REG(huart->Instance->DR);//FE清標志,第二步讀DR
__HAL_UART_CLEAR_FLAG(huart, UART_FLAG_FE);
}
if((__HAL_UART_GET_FLAG(huart, UART_FLAG_NE))!=RESET)
{
READ_REG(huart->Instance->DR);//NE清標志,第二步讀DR
__HAL_UART_CLEAR_FLAG(huart, UART_FLAG_NE);
}
if((__HAL_UART_GET_FLAG(huart, UART_FLAG_ORE))!=RESET)
{
READ_REG(huart->Instance->CR1);//ORE清標志,第二步讀CR
__HAL_UART_CLEAR_FLAG(huart, UART_FLAG_ORE);
}
}