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

[導讀] 單片機開發串口是應用最爲普遍的通訊接口,也是最爲簡單的通訊接口之一,可是其中的一些要點你是否明瞭呢?來看看本人對串口的一些總結,固然這個總結並不能面面俱到,只是將我的認爲具備共性以及相對比較重要的點作了些梳理。算法

啥是串口?

首先這玩意兒分兩種:編程

  • 通用異步收發器(UART)是用於異步串行通訊的一種物理層標準,其中數據格式和傳輸速度是可配置的。
  • 通用同步收發器(USART)是一種串行接口設備,能夠對其進行編程以進行異步同步通訊。

數據格式

線上空閒、無數據狀態爲常高電平,故邏輯低定義爲起始位。微信

  • 起始位:老是1位異步

  • 數據位:常見的有8位或9位。函數

  • 校驗位學習

    • 奇校驗
    • 偶校驗
    • 無校驗
  • 中止位:ui

    • 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接收,制定協議時儘可能考慮將幀長度固定,這樣處理會方便些。

總結一下

單片機串口是一個須要好好掌握的內容,這裏總結了一些我的經驗,儘可能將一些我的共性的東西總結出來。至於實際實現而言,因爲芯片體系差別較多,具體代碼各異。但我的認爲處置的思路方法倒是基本一致。因此本文除了描述串口自己的細節而言,想表達的一個額外的觀點是:

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