[導讀] 單片機開發串口是應用最為廣泛的通信接口,也是最為簡單的通信接口之一,但是其中的一些要點你是否明了呢?來看看本人對串口的一些總結,當然這個總結并不能面面俱到,只是將個人認為具有共性以及相對比較重要的點做了些梳理。
啥是串口?首先這玩意兒分兩種:
通用異步收發器(UART)是用于異步串行通信的一種物理層標準,其中數據格式和傳輸速度是可配置的。
通用同步收發器(USART)是一種串行接口設備,可以對其進行編程以進行異步或同步通信。
數據格式
線上空閑、無數據狀態為常高電平,故邏輯低定義為起始位。
起始位:總是 1 位
數據位:常見的有 8 位或 9 位。
校驗位
奇校驗
偶校驗
無校驗
停止位:
1 位
2 位
波特率:bit rate 就是位/秒的概念,就是 1 秒傳送多少位的概念。常見的波特率有哪些呢?
這里須注意的要點:
一個有效字節的傳輸時間怎么算?
比如 9600 下,1 位起始位,8 位數據位,奇校驗,1 位停止位,則
為什么要理解清楚這個概念呢,因為在應用中需要計算數據吞吐率問題,就比如一個應用是數據采集串口傳輸問題,需要計算采集的位速率需要小于或等于傳輸波特率,否則數據就來不及傳。當然如果說你有足夠大的緩沖區可以臨時存儲,但是如果進來太快,而傳出速度跟不上,多大的緩沖都會滿!
校驗位有用嗎?當你的傳輸介質處于一個有干擾的場景下,校驗位就可以從物理層檢測出錯誤。
理解數據編碼方式有啥意義呢?比如在調試中你可以利用邏輯分析直接去解析收發線上的數據報文。
應用電路設計的時候 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 接收,制定協議時盡量考慮將幀長度固定,這樣處理會方便些。
總結一下單片機串口是一個需要好好掌握的內容,這里總結了一些個人經驗,盡量將一些個人共性的東西總結出來。至于實際實現而言,由于芯片體系差異較多,具體代碼各異。但個人認為處置的思路方法卻是基本一致。所以本文除了描述串口本身的細節而言,想表達的一個額外的觀點是:
對于一些技術點盡量學會將其共性的東西剝離總結出來。
總結、概括、剝離抽象是一個比較好的學習思路,不用對具體的硬件死記,萬變不離其宗。
如果本文有喜歡的朋友,后面陸續可以總結一下I2C/SPI等常用接口。
? ? ? ? 責任編輯:pj
評論
查看更多