UART,全稱Universal Asynchronous Receiver Transmitter,通用異步收發器,俗稱串口。作為最常用的通信接口之一,從8位單片機到64位SoC,一般都會提供UART接口。
UART的常規構成及特性
芯片內部的UART模塊,一般由波特率發生器、發送和接收FIFO、硬件流控、中斷源等組件構成。常見特性如下:
-
全雙工通信
-
硬件流控
-
可編程的字長(5/6/7/8比特)
-
可編程的停止位(1/1.5/2比特)
-
奇偶校驗
-
可編程的FIFO中斷觸發水位
-
可編程的波特率
UART硬件信號及應用
信號腳 |
方向 |
用途 |
TXD |
輸出 | 串行數據輸出 |
RXD | 輸入 |
串行數據輸入 |
CTSN | 輸入 |
流控腳,允許發送,由對端設備控制,控制己方UART是否可以發送數據。低電平時,UART可以發送數據出去,高電平時,UART停止發送數據。 |
RTSN | 輸出 |
流控腳,請求發送,連接到對端設備的CTSN腳上,通知對端設備是否可以發送數據。低電平時,通知對端設備可以發送,高電平時,通知對端設備停止發送。 |
圖1 帶硬件流控的UART連接
圖2 不帶硬件流控的UART連接
UART通信協議
UART通信時序圖如下
圖3 UART時序
數據線的空閒電平為邏輯“1”,要傳輸數據時:
起始位:先發出一個邏輯”0”的信號,表示傳輸數據的開始。
數據位:實際要傳輸的數據,數據位數可以是5、6、7、8,數據是從最低有效位(LSB)開始。
校驗位:數據位加上這一位後,使得“1”的位數應為偶數(偶校驗)或奇數(奇校驗),以此來校驗數據傳送的正確性。上圖中沒有特意標明。
停止位:數據的結束標志。可以是1/1.5/2位的空閒電平。Linux串口設備編程接口並不支持設置1.5位。
UART通信是異步的,並沒有單獨的時鍾來做同步,通信雙方需要約定好相同的波特率。UART中的波特率可以認為是比特率,即每秒傳輸的位數。一般波特率有9600,115200,460800等選項。
Linux中的UART驅動
Linux UART驅動框架如下圖所示,UART在用戶空間會生成名為/dev/ttyS*的設備(ttyS名稱是驅動給出的,可能因驅動而異),應用程序通過讀寫設備就可以進行UART通信。Linux內核實現了tty層和serial core,serial core會調用tty層的接口,注冊tty driver,同時提供了底層uart的抽象:
-
定義struct uart_driver、struct uart_port、struct uart_ops等結構來描述底層uart驅動;
-
提供相應接口 uart_register_driver、uart_add_one_port 等。
圖4 UART驅動框架
重點看一下struct uart_ops定義,如下圖所示,定義了UART硬件能完成的操作。
UART控制器驅動會定義uart_ops結構並實現對應的函數功能,當然並不是所有函數都需要實現,最為關鍵的幾個函數如下表所示。
函數 |
意義 |
tx_empty |
查詢TX FIFO是否為空,為空則返回1,否則返回0 |
stop_tx |
停止發送 |
start_tx |
啟動發送 |
stop_rx |
停止接收 |
startup |
開啟UART |
shutdown |
關閉UART |
set_termios |
設置UART屬性,比如波特率、數據位數、停止位數、奇偶校驗、流控等。 |
struct uart_driver指代一個UART驅動,struct uart_port指代一個具體UART端口,它們與struct uart_ops的關系大致如下圖。
下面以某廠商的UART控制器驅動為例,看看驅動實現的整體流程。
UART控制器驅動,以platform_driver的形式呈現。
關鍵的uart_ops結構定義如下,uart_ops會關聯到一個或多個uart_port上。
UART控制器驅動的probe函數,完成初始化uart_port,並添加到serial core中。
Linux上串口的常規操作工具
Linux上,除了一些串口工具比如minicom, cutecom可以操作串口外,也可以用如下命令行工具進行基本的操作。
目的 | 操作方法(以/dev/ttyS0為例) |
查詢串口 |
stty -F /dev/ttyS0 |
設置串口 |
stty -F /dev/ttyS0 speed 115200 cs8 -parenb -cstopb 115200波特率 8數據位 1停止位 無校驗 |
讀取數據 |
cat /dev/ttyS0 |
發送數據 |
echo "test data" > /dev/ttyS0 |
用戶空間的串口編程
打開/讀/寫串口,與普通字符設備一樣,open/read/write系統調用,不再贅述。如何設置串口屬性,Linux提供了專門的API。
struct termios options;
//獲取參數
tcgetattr(fd, &options);
//波特率
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
//8位數據位
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
//無校驗
options.c_cflag &= ~PARENB;
//1位停止位
options.c_cflag &= ~CSTOPB;
//設置參數
tcsetattr(fd, TCANOW, &options);
內核空間的UART外設編程?
從以上介紹可以看到,UART最終是以用戶空間的tty設備來呈現,應用程序可以操作tty設備完成UART通信。但是如果需要在內核中調用UART驅動呢,有沒有相關接口?
假設有一個UART接口的按鍵擴展芯片,既要通過UART獲取設備數據,又要將這些數據上報給input子系統。由於input子系統位於內核空間,那麼通過UART獲取數據的操作也應該在內核空間完成。那這個時候如何操作UART?如果最底層實現的UART操作函數有與tty和serial core解耦,那倒是可以考慮直接調用這些接口,但現實情況是一般都沒有完全解耦。如果是專門針對該應用場景重構UART底層驅動,那也是一種方法,也確實有人這樣做。
其它總線驅動,比如I2C驅動,針對掛在I2C總線上的設備,有i2c_driver框架實現設備驅動,有i2c_transfer函數接口來實現I2C通信。SPI驅動也是類似,有spi_driver框架和相應的SPI傳輸函數。所以在內核空間編寫I2C/SPI設備驅動很方便。
針對UART,如果是4.14之前的內核,那麼可以通過serio驅動來實現某設備與tty設備的掛鉤,進而可以調用tty層的讀寫函數,tty層的讀寫函數最後會調用到UART底層驅動。
serio是個抽象的總線,並不專指UART,而是Serial IO的統稱。serio驅動代碼用struct serio_bus表示serio總線,用struct serio表示serio控制器,用struct serio_drvier表示serio設備驅動。詳情可參考代碼:
drivers/input/serio/*
drivers/input/touchscreen/touchit213.c
如果是4.14版本以後的內核,已經新增了serail dev bus,並提供了相應的設備驅動注冊函數:
serdev_device_driver_register(struct serdev_device_driver *, struct module *);
使用方法可參考藍牙驅動:
drivers/bluetooth/hci_bcm.c
------ END ------
作者:bigfish99
部落格:https://www.cnblogs.com/bigfish0506/
公眾號:大魚嵌入式