萬變不離其宗之UART要點總結
[導讀] 單片機開發串口是應用最為廣泛的通訊介面,也是最為簡單的通訊介面之一,但是其中的一些要點你是否明了呢?來看看本人對串口的一些總結,當然這個總結並不能面面俱到,只是將個人認為具有共性以及相對比較重要的點做了些梳理。
啥是串口?
首先這玩意兒分兩種:
- 通用非同步收發器(UART)是用於非同步串列通訊的一種物理層標準,其中數據格式和傳輸速度是可配置的。
- 通用同步收發器(USART)是一種串列介面設備,可以對其進行編程以進行非同步或同步通訊。
數據格式
線上空閑、無數據狀態為常高電平,故邏輯低定義為起始位。
-
起始位:總是1位
-
數據位:常見的有8位或9位。
-
校驗位
- 奇校驗
- 偶校驗
- 無校驗
-
停止位:
- 1位
- 2位
-
波特率:bit rate 就是位/秒的概念,就是1秒傳多少位的概念。常見的波特率有哪些呢?
這裡須注意的要點:
-
一個有效位元組的傳輸時間怎麼算?
\[T=位數*\frac{1}{波特率}
\]比如9600下,1位起始位,8位數據位,奇校驗,1位停止位,則
\[T=(1+8+1+1)*\frac{1}{9600}=0.00114583秒
\]為什麼要理解清楚這個概念呢,因為在應用中需要計算數據吞吐率問題,就比如一個應用是數據採集串口傳輸問題,需要計算採集的位速率需要小於或等於傳輸波特率,否則數據就來不及傳。當然如果說你有足夠大的緩衝區可以臨時存儲,但是如果進來太快,而傳出速度跟不上,多大的緩衝都會滿!
-
校驗位有用嗎?當你的傳輸介質處於一個有干擾的場景下,校驗位就可以從物理層檢測出錯誤。
-
理解數據編碼方式有啥意義呢?比如在調試中你可以利用邏輯分析直接去解析收發線上的數據報文。
-
應用電路設計的時候RX-TX相連,很多初學者容易在這裡踩坑!
-
常見的傳輸位序為低有效位在前。
-
對於波特率而言需要注意波特率發生器有可能帶來誤碼問題
啥是UART?
兩邊分別代表兩個通訊的設備,單從UART編程的角度講收發不需要物理同步握手,想發就發。圖中箭頭代表數據資訊流向。RX表示接收數據,TX表示發送數據。數據總是從發送端傳遞到接收端,這就是為啥RX連接TX,TX連RX的原因。
啥是USART?
同步簡單說,收發不可自如,不可以想發就發,收發需要利用硬體IO口進行握手,RTS/CTS就是用於同步的握手訊號:
- RTS:Ready to send,請求發送,用於在當前傳輸結束時阻止數據發送。
- CTS:clear to send,清除發送,用於指示 USART 已準備好接收數據。
這個對於普通應用而言並不常見,這裡不做詳細展開,需要用到的時候只需要對應收發時控制握手訊號即可。
編程策略
對於不同的單片機,其硬體體系各異,暫存器也差異很大,但是從收發編程策略角度而言,常見有下面三種方式:
- 查詢發送/中斷接收模式
- 收發中斷模式
- DMA模式
查詢發送/中斷接收模式
這裡以偽程式碼方式描述一下:
/*查詢發送位元組*/
void uart_send_byte( uint8 ch )
{
/*如果當前串口狀態暫存器非空閑,則一直等待*/
/*注意while循環後的分號,表示循環體為空操作*/
while( !UART_IS_IDLE() );
/*此時將發送位元組寫入發送暫存器*/
UART_TX_REG = ch;
}
/*發送一個緩衝區*/
void uart_send_buffer( uint8 *pBuf,uint8 size )
{
uint8 i = 0;
/* 異常參數處理*/
if( pBuf == NULL )
return;
for( i=0; i<size;i++ )
{
send_byte( pBuf[i] );
}
}
對於接收而言,如採用查詢模式則幾乎是沒有任何應用價值,因為外部數據不知道什麼時候會到來,所以查詢接受就不描述了,這裡描述一下中斷接收。
static uint8 rx_index = 0;
void uart_rx_isr( void )
{
/* 接收報文處理 */
rx_buffer[rx_index++] = UART_RX_REG;
}
中斷接收需要考慮的幾個要點:
- 斷幀:這就取決於協議怎麼制定了,比如應用協議定義的是ASCII碼方式,就可以定義同步頭、同步尾,比如AT指令的解析,做邏輯判斷幀頭、幀尾即可。但是如果傳輸的是16進位數據,比如MODBUS-RTU其斷幀採用的是3.5個位元組時間沒有新的位元組接收到,則認為收到完整的幀了。
- 如何保證幀的完整性,一般會在報文尾部加校驗,比較常用的校驗模式有CRC校驗演算法。
- 不同的單片機開發環境對於中斷向量的處理方式略有不同,需要根據各自晶片的特點進行處理。比如51單片機,其發送/接收都共享一個中斷向量號。
收發中斷模式
#define FRAME_SIZE (128u)
static uint8 tx_buffer[FRAME_SIZE];
static uint8 tx_index = 0;
static uint8 tx_length = 0;
static uint8 rx_buffer[FRAME_SIZE];
static uint8 rx_index = 0;
static bool rx_frame_done = false;
void prepare_frame( uint8 * pBuf, uint8 size )
{
/*將待傳的報文按照協議封裝*/
/*可能需要處理的事情,比如幀頭、幀尾、校驗等*/
}
bool uart_start_sending( uint8 * pBuf, uint8 size )
{
if( pBuf == NULL )
return false;
memcpy( tx_buffer,pBuf,size );
tx_index = 0;
tx_length = size;
/*使能發送中斷,向發送暫存器寫入一個位元組,進入連續發送模式*/
ENABLE_TX_INT = 1;
UART_TX_REG = tx_buffer[tx_index++];
}
void uart_tx_isr( void )
{
if( tx_index<tx_length )
{
UART_TX_REG = tx_buffer[tx_index++];
}
else
{
/*發送完畢,關閉發送中斷*/
DISABLE_TX_INT = 1;
}
}
void uart_rx_isr( void )
{
/*處理接收,待接收到完整的幀就設置幀完成標記*/
/*由於應用各有不同,這裡就無法描述實現了*/
}
還需要考慮的是,對於UART硬體層面的出錯處置,以STM32為例,就可能有下面的錯誤可能發生:
- 溢出錯誤
- 雜訊檢測
- 幀錯誤
- 奇偶校驗錯誤
另外不同的單片機其底層硬體實現差異也不較大,比如有的硬體發送緩衝是單位元組的緩衝,有的則具有FIFO,這些在選型編程時都需要綜合考慮。
DMA模式
DMA發送模式而言,大致分這樣幾步:
- 初始化UART為DMA發送模式,開啟DMA結束中斷,並寫好DMA傳輸結束中斷處理函數
- 準備待發送報文,幀頭、幀尾、校驗處理
- 將待發送報文緩衝區首地址賦值給DMA源地址,DMA目標地址設置為UART發送暫存器,設置好發送長度。
- 啟動DMA傳輸,剩下傳輸完成就會進入傳輸結束中斷處理函數。
DMA接收模式而言,大致分這樣幾步:
- 初始化UART為DMA接收模式,開啟DMA結束中斷,並寫好DMA傳輸結束中斷處理函數
- 中斷處理函數中標記接收到幀,對於使用RTOS而言,還可以使用的機制是利用RTOS的事件機制、消息機制進行通知有新的幀接收到了。
- 對於DMA接收模式而言,對於變長幀的處理較為不利,所以如果想使用DMA接收,制定協議時盡量考慮將幀長度固定,這樣處理會方便些。
總結一下
單片機串口是一個需要好好掌握的內容,這裡總結了一些個人經驗,盡量將一些個人共性的東西總結出來。至於實際實現而言,由於晶片體系差異較多,具體程式碼各異。但個人認為處置的思路方法卻是基本一致。所以本文除了描述串口本身的細節而言,想表達的一個額外的觀點是:
- 對於一些技術點盡量學會將其共性的東西剝離總結出來。
- 總結、概括、剝離抽象是一個比較好的學習思路,不用對具體的硬體死記,萬變不離其宗。
文章出自微信公眾號:嵌入式客棧,更多內容,請關注本人公眾號