[導讀] 微控制器開發串列埠是應用最為廣泛的通訊介面,也是最為簡單的通訊介面之一,但是其中的一些要點你是否明瞭呢?來看看本人對串列埠的一些總結,當然這個總結並不能面面俱到,只是將個人認為具有共性以及相對比較重要的點做了些梳理。
啥是串列埠?
首先這玩意兒分兩種:
- 通用非同步收發器(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接收,制定協議時儘量考慮將幀長度固定,這樣處理會方便些。
總結一下
微控制器串列埠是一個需要好好掌握的內容,這裡總結了一些個人經驗,儘量將一些個人共性的東西總結出來。至於實際實現而言,由於晶片體系差異較多,具體程式碼各異。但個人認為處置的思路方法卻是基本一致。所以本文除了描述串列埠本身的細節而言,想表達的一個額外的觀點是:
- 對於一些技術點儘量學會將其共性的東西剝離總結出來。
- 總結、概括、剝離抽象是一個比較好的學習思路,不用對具體的硬體死記,萬變不離其宗。
文章出自微信公眾號:嵌入式客棧,更多內容,請關注本人公眾號