萬變不離其宗之UART要點總結

逸珺發表於2020-07-08

[導讀] 微控制器開發串列埠是應用最為廣泛的通訊介面,也是最為簡單的通訊介面之一,但是其中的一些要點你是否明瞭呢?來看看本人對串列埠的一些總結,當然這個總結並不能面面俱到,只是將個人認為具有共性以及相對比較重要的點做了些梳理。

啥是串列埠?

首先這玩意兒分兩種:

  • 通用非同步收發器(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接收,制定協議時儘量考慮將幀長度固定,這樣處理會方便些。

總結一下

微控制器串列埠是一個需要好好掌握的內容,這裡總結了一些個人經驗,儘量將一些個人共性的東西總結出來。至於實際實現而言,由於晶片體系差異較多,具體程式碼各異。但個人認為處置的思路方法卻是基本一致。所以本文除了描述串列埠本身的細節而言,想表達的一個額外的觀點是:

  • 對於一些技術點儘量學會將其共性的東西剝離總結出來。
  • 總結、概括、剝離抽象是一個比較好的學習思路,不用對具體的硬體死記,萬變不離其宗。
    文章出自微信公眾號:嵌入式客棧,更多內容,請關注本人公眾號

相關文章