Linux UART 開發指南
1 概述
1.1 編寫目的
介紹 Linux 內核中 UART 驅動的接口及使用方法,為 UART 設備的使用者提供參考。
1.2 適用范圍
表 1-1: 適用產品列表
內核版本 | 驅動文件 |
---|---|
Linux-4.9 及以上 | sunxi-uart.c |
1.3 相關人員
UART 驅動、及應用層的開發/維護人員。
2 模塊介紹
2.1 模塊功能介紹
Linux 內核中,UART 驅動的結構圖 1 所示, 可以分為三個層次:
圖 2-1: Linux UART 體系結構圖
Sunxi UART Driver, 負責 SUNXI 平臺 UART 控制器的初始化、數據通信等, 也是我們要實現的部分。
UART Core, 為 UART 驅動提供了一套 API, 完成設備和驅動的注冊等。
TTY core, 實現了內核中所有 TTY 設備的注冊和管理。
2.2 相關術語介紹
表 2-1: UART 模塊相關術語介紹
術語 | 解釋說明 |
---|---|
Sunxi | 指 Allwinner 的一系列 SoC 硬件平臺 |
UART | Universal Asynchronous Receiver/Transmitter,通用異步收發傳輸器 |
Console | 控制臺,Linux 內核中用于輸出調試信息的 TTY 設備 |
TTY | TeleType/TeleTypewriters 的一個老縮寫,原來指的是電傳打字機,現在泛指和計算機串行端口連接的終端設備。TTY 設備還包括虛擬控制臺,串口以及偽終端設備 |
2.3 源碼結構介紹
linux4.9
|-- drivers
| |-- tty
| | |-- serial
| | |-- serial_core.c
| | |-- sunxi-uart.c
| | |-- sunxi-uart.h
3 模塊配置介紹
3.1 kernel menuconfig 配置
在 longan 頂層目錄,執行./build.sh menuconfig(需要先執行./build.sh config) 進入配置主界面,并按以下步驟操作:首先,選擇 Device Drivers 選項進入下一級配置,如下圖所示:
圖 3-1: 內核 menuconfig 根菜單
選擇 Character devices, 進入下級配置,如下圖所示:
圖 3-2: 內核 menuconfig device drivers 菜單
選擇 Serial drivers, 進入下級配置,如下圖所示:
圖 3-3: 內核 menuconfig Character drivers 菜單
選擇 SUNXI UART Controller 和 Console on SUNXI UART port 選項,如下圖:
圖 3-4: 內核 menuconfig sunxi uart 配置菜單
如果需要 UART 支持 DMA 傳輸,則可以打開 SUNXI UART USE DMA 選項。
3.2 device tree 源碼結構和路徑
? 設備樹文件的配置是該 SoC 所有方案的通用配置,對于 ARM64 CPU 而言,設備樹的路徑為內核目錄下:arch/arm64/boot/dts/sunxi/sun*.dtsi。
? 設備樹文件的配置是該 SoC 所有方案的通用配置,對于 ARM32 CPU 而言,設備樹的路徑為內核目錄下:arch/arm/boot/dts/sun*.dtsi。
? 板級設備樹 (board.dts) 路徑:/device/config/chips/{IC}/configs/{BOARD}/board.dts。device tree 的源碼結構關系如下:
device tree 的源碼結構關系如下:
linux-4.9
board.dts
|--------sun*.dtsi
|------sun*-pinctrl.dtsi
|------sun*-clk.dtsi
linux-5.4
board.dts
|-------sun*.dtsi
3.2.1 device tree 對 uart 控制器的通用配置
linux-4.9 的通用配置如下:
/ {
model = "sun*";
compatible = "arm,sun*";
interrupt-parent = <&wakeupgen>;
#address-cells = <2>;
#size-cells = <2>;
aliases {
serial0 = &uart0;
serial1 = &uart1;
serial2 = &uart2;
serial3 = &uart3;
serial4 = &uart4;
serial5 = &uart5;
...
};
?
uart0: uart@05000000 {
compatible = "allwinner,sun50i-uart"; /* 用于驅動和設備綁定 */
device_type = "uart0"; /* 設備類型*/
reg = <0x0 0x05000000 0x0 0x400>; /* 設備使用的寄存器基地址以及范圍*/
interrupts = ; /* 設備使用的硬件中斷號*/
clocks = <&clk_uart0>; /* 設備使用的時鐘*/
pinctrl-names = "default", "sleep";
pinctrl-0 = <&uart0_pins_a>; /* 設備正常狀態下使用的pin腳*/
pinctrl-1 = <&uart0_pins_b>; /* 設備休眠狀態下使用的pin腳*/
uart0_port = <0>; /* uart控制器對應的ttyS唯一端口號,不能與其他uart控制器重復*/
uart0_type = <2>; /* uart控制器線數,取值2/4/8*/
use_dma = <0>; /* 是否采用DMA 方式傳輸,0:不啟用,1:只啟用TX,2:只啟用RX,3:啟 用TX 與RX*/
status = "okay"; /* 是否使能該節點*/
};
linux-5.4 的通用配置如下:
uart0: uart@5000000 {
compatible = "allwinner,sun50i-uart";
device_type = "uart0";
reg = <0x0 0x05000000 0x0 0x400>;
interrupts = ;
sunxi,uart-fifosize = <64>;
clocks = <&ccu CLK_BUS_UART0>; /* 設備使用的時鐘 */
clock-names = "uart0";
resets = <&ccu RST_BUS_UART0>; /* 設備reset時鐘 */
pinctrl-names = "default", "sleep";
pinctrl-0 = <&uart0_pins_a>;
pinctrl-1 = <&uart0_pins_b>;
uart0_port = <0>;
uart0_type = <2>;
dmas = <&dma 14>; /* 14表示DRQ */
dma-names = "tx";
use_dma = <0>; /* 是否采用DMA 方式傳輸,0:不啟用,1:只啟用TX,2:只啟用RX,3:啟用TX 與RX */
};
在 Device Tree 中對每一個 UART 控制器進行配置, 一個 UART 控制器對應一個 UART 節點, 節點屬性的含義見注釋。為了在 UART 驅動代碼中區分每一個 UART 控制器,需要在 Device Tree 中的 aliases 節點中未每一個。UART 節點指定別名,如上 aliases 節點所示。別名形式為字符串 “serial” 加連續編號的數字,在 UART 驅動程序中可以通過 of_alias_get_id() 函數獲取對應的 UART 控制器的數字編號,從而區分每一個 UART 控制器。
3.2.2 board.dts 板級配置
board.dts 用于保存每個板級平臺的設備信息 (如 demo 板、demo2.0 板等等),board.dts 路徑如下:/device/config/chips/{IC}/configs/{BOARD}/board.dts。
在 board.dts 中的配置信息如果在 *.dtsi(如 sun50iw9p1.dtsi 等) 存在,則會存在以下覆蓋規則:
相同屬性和結點,board.dts 的配置信息會覆蓋 *.dtsi 中的配置信息。
新增加的屬性和結點,會添加到編譯生成的 dtb 文件中。
uart 在 board.dts 的簡單配置如下:
soc@03000000 {
...
&uart0 {
uart-supply = ; /* IO使用的電 */
status = "okay";
};
?
&uart1 {
status = "okay";
};
...
}
3.2.3 uart dma 模式配置
在內核配置菜單打開 CONFIG_SERIAL_SUNXI_DMA 配置,如下圖所示:
圖 3-5: 內核 menuconfig sunxi uart 配置菜單
在對應 dts 配置使用 dma,如下所示:
Linux-4.9 配置如下:
uart1: uart@05000400 {
...
use_dma = <3>; /* 是否采用DMA 方式傳輸,0:不啟用,1:只啟用TX,2:只啟用RX,3:啟用TX 與RX */
status = "okay";
};
linux-5.4 配置如下:
uart0: uart@5000000 {
...
dmas = <&dma 14>; /* 14表示DRQ, 查看dma spec */
dma-names = "tx";
use_dma = <0>; /* 是否采用DMA 方式傳輸,0:不啟用,1:只啟用TX,2:只啟用RX,3:啟用TX 與RX
*/
};
3.2.4 設置其他 uart 為打印 conole
從 dts 確保設置為 console 的 uart 已經使能,如 uart1。
uart1: uart@05000400 {
compatible = "allwinner,sun50i-uart";
device_type = "uart1";
reg = <0x0 0x05000400 0x0 0x400>;
interrupts = ;
clocks = <&clk_uart1>;
pinctrl-names = "default", "sleep";
pinctrl-0 = <&uart1_pins_a>;
pinctrl-1 = <&uart1_pins_b>;
uart1_port = <1>;
uart1_type = <4>;
status = "okay"; /* 確保該uart已經使能 */
};
修改方案使用的 env*.cfg 文件,如下所示:
console=ttyS1,115200 說明: ttyS0 <===> uart0 ttyS1 <===> uart1 ...
3.2.5 設置 uart 波特率
在不同的 Sunxi 硬件平臺中,UART 控制器的時鐘源選擇、配置略有不同,總體上的時鐘關系如下:
圖 3-6: 時鐘說明
UART 控制器會對輸入的時鐘源進行分頻,最終輸出一個頻率滿足(或近似)UART 波特率的時鐘信號。UART 常用的標準波特率有:
#define B4800 0000014 #define B9600 0000015 #define B19200 0000016 #define B38400 0000017 #define B57600 0010001 #define B115200 0010002 #define B230400 0010003 #define B460800 0010004 #define B500000 0010005 #define B576000 0010006 #define B921600 0010007 #define B1000000 0010010 #define B1152000 0010011 #define B1500000 0010012 #define B2000000 0010013 #define B2500000 0010014 #define B3000000 0010015 #define B3500000 0010016 #define B4000000 0010017
UART 時鐘的分頻比是 16 的整數倍,分頻難免會有誤差,所以輸出 UART Device 通信的波特率是否足夠精準,很大程度取決于輸入的時鐘源頻率。考慮到功耗,UART 驅動中一般默認使用 24M 時鐘源,但是根據應用場景我們有時候需要切換別的時鐘源,基于兩個原因:
24MHz/16=1.5MHz,這個最大頻率滿足不了 1.5M 以上的波特率應用;
24M 分頻后得到波特率誤差可能太大,也滿足不了某些 UART 外設的冗余要求(一般要求 2% 或 5% 以內,由外設決定)。UART 時鐘源來自 APB2,APB2 的時鐘源有兩個,分別是 24MHz(HOSC)和 PLL_PERIPH(即驅動中的 PLL_PERIPH_CLK),系統默認配置 APB2 的時鐘源是 24M,如果要提高UART 的時鐘就要將 APB2 的時鐘源設置為 PLL_PERIPH。同時要注意到 APB2 也是 TWI 的時鐘源,所以需要兼顧 TWI 時鐘。
各個 uart 波特率對應頻點關系如下:
圖 3-7: 波特率關系
例如需要配置 uart2 的波特率為 460800,在上述關系表中可以看出,對應的時鐘為 30M、37.5M、42.857M、46.153M 和 50M 等,所以需要在設備樹里修改 uart2 時鐘:
linux-4.9 修改波特率如下:
device_type = "uart2"; reg = <0x0 0x05000800 0x0 0x400>; interrupts = ; - clocks = <&clk_uart2>; + clocks = <&clk_uart2>, <&clk_apb2>, <&clk_psi>; + clock-frequency = <50000000>; pinctrl-names = "default", "sleep";
有的情況下,APB2 不一定能準確分出 30M 或者 37.5M 等時鐘,所以這里我們選擇配置為 50M 時鐘。
如果我們修改了 APB2 時鐘,可能會對 uart0 有影響,啟動串口會出現亂碼,那么我們最好也將 uart0 的時鐘配置為 50M 時鐘。
device_type = "uart0"; reg = <0x0 0x05000000 0x0 0x400>; interrupts = ; - clocks = <&clk_uart0>; + clocks = <&clk_uart0>, <&clk_apb2>, <&clk_psi>; + clock-frequency = <50000000>; pinctrl-names = "default", "sleep";
linux-5.4 修改波特率如下:
device_type = "uart2"; reg = <0x0 0x05000800 0x0 0x400>; interrupts = ; - clocks = <&clk_uart2>; + clocks = <&ccu CLK_BUS_UART0>, + <&ccu CLK_APB2>, + <&ccu CLK_PSI_AHB1_AHB2>; + clock-frequency = <50000000>; pinctrl-names = "default", "sleep";
說明
修改 APB2 總線時鐘,會影響到使用到 APB2 總線時鐘的相應模塊。
3.2.6 支持 cpus 域的 uart
在不同的 Sunxi 硬件平臺中,UART 控制器根據電源域劃分了 CPUX 域和 CPUS 域,系統默認對 CPUX 域的 uart 控制器都會默認配置上,但對 CPUS 域的 uart 控制器可能沒有支持上,當希望通過 CPUX 使用 CPUS 域的 uart 時,請參考以下步驟進行支持:
通過 datasheet 或者 spec,獲取 CPUS 域 uart 控制器的 pin 腳、clk、控制器地址等信息。
確保 cpus 運行的系統沒有使用到 CPUS 域的 uart 控制器,只有 CPUX 域在使用。
在 r_pio 域配置 pin 的 dtsi 文件 (*.-pinctrl.dtsi)。
/ { soc@03000000{ r_pio: pinctrl@07022000 { ... /* 配置CPUS域uart pin信息 */ s_uart0_pins_a: s_uart0@0 { allwinner,pins = "PL2", "PL3"; allwinner,function = "s_uart0"; allwinner,muxsel = <2>; allwinner,drive = <1>; allwinner,pull = <1>; }; ... }; ... };
在 clk dtsi 配置時鐘信息 (*-clk.dtsi)。
clk_suart0: suart0 { #clock-cells = <0>; compatible = "allwinner,periph-cpus-clock"; clock-output-names = "suart0"; };
在 dtsi 配置 uart 控制器信息 (*.dtsi)。
aliases { serial0 = &uart0; ... serial5 = &uart5; //添加uart5的別名,必須要添加 }; uart0:uart@05000000 { }; .... /* 添加uart控制器信息 ,必須要添加,具體屬性含義,見上文 */ uart5: uart@07080000 { compatible = "allwinner,sun8i-uart"; device_type = "uart5"; reg = <0x0 0x07080000 0x0 0x400>; interrupts = ; clocks = <&clk_suart0>; pinctrl-names = "default", "sleep"; pinctrl-1 = <&s_uart0_pins_a>; uart5_port = <5>; uart5_type = <2>; status = "okay"; };
檢查或添加 pin 描述符。
有可能 pinctrl 驅動里,沒有把 CPUS 域 uart 控制器的 pin 描述出來,因此要的 pinctrl 驅動里檢查或添加 pin 的描述信息,查看 pinctrl-*-r.c 文件。
static const struct sunxi_desc_pin *_r_pins[] = { SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 0), ... SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 2), ... SUNXI_FUNCTION(0x2, "s_uart0"), //沒有則要添加 ... SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 3), ... SUNXI_FUNCTION(0x2, "s_uart0"), //沒有則要添加 ... };
檢查 clk 的描述信息。
有可能 clk 驅動根據情況,也沒有把 CPUS 域的 uart 控制器的時鐘信息描述出來,因此要 clk 驅動里檢查或添加 clk 的描述信息,查看 clk-.c 和 clk-.h 文件。
/* clk-*.h */ #define CPUS_UART_GATE 0x018C /* clk-*.c */ static const char *suart_parents[] = {"cpurapbs2"}; SUNXI_CLK_PERIPH(suart0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CPUS_UART_GATE, CPUS_UART_GATE, 0, 0, 16, 0, 0, &clk_lock, NULL, 0); struct periph_init_data sunxi_periphs_cpus_init[] = { ... {"suart0", 0, suart_parents, ARRAY_SIZE(suart_parents), &sunxi_clk_periph_suart0 }, };
檢查 uart 驅動支持的 uart 數量是否足夠,查看 sunxi-uart.h 文件。
通過 SUNXI_UART_NUM 宏確認支持 uart 的數量。
修改完畢后,啟動小機,查看 ttySn 設備是否生成,通過該設備測試 uart 是否正常使用即可。
4 接口描述
UART 驅動會注冊生成串口設備/dev/ttySx,應用層的使用只需遵循 Linux 系統中的標準串口編程方法即可。
4.1 打開/關閉串口
使用標準的文件打開函數:
int open(const char *pathname, int flags); int close(int fd);
需要引用頭文件:
#include #include #include #include
4.2 讀/寫串口
同樣使用標準的文件讀寫函數:
ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count);
需要引用頭文件:
#include
4.3 設置串口屬性
串口屬性包括波特率、數據位、停止位、校驗位、流控等,這部分是串口設備特有的接口。串口屬性的數據結構 termios 定義如下:(terminos.h)。
#define NCCS 19 struct termios { tcflag_t c_iflag; /* input mode flags */ tcflag_t c_oflag; /* output mode flags */ tcflag_t c_cflag; /* control mode flags */ tcflag_t c_lflag; /* local mode flags */ cc_t c_line; /* line discipline */ cc_t c_cc[NCCS]; /* control characters */ };
其中,c_iflag 的標志常量定義如下:
標志 | 說明 |
---|---|
IGNBRK | 忽略輸入中的 BREAK 狀態。 |
BRKINT | 如果設置了 IGNBRK,將忽略 BREAK。如果沒有設置,但是設置了 BRKINT,那么 BREAK 將使得輸入和輸出隊列被刷新,如果終端是一個前臺進程組的控制終端,這個進程組中所有進程將收到 SIGINT 信號。如果既未設置 IGNBRK 也未設置 BRKINT,BREAK 將視為與 NUL 字符同義,除非設置了 PARMRK,這種情況下它被視為序列 377 ? ?。 |
IGNPAR | 忽略楨錯誤和奇偶校驗錯。 |
PARMRK | 如果沒有設置 IGNPAR,在有奇偶校驗錯或楨錯誤的字符前插入 377 ?。如果既沒有設置 IGNPAR 也沒有設置 PARMRK,將有奇偶校驗錯或楨錯誤的字符視為 ?。 |
INPCK | 啟用輸入奇偶檢測。 |
ISTRIP | 去掉第八位。 |
INLCR | 將輸入中的 NL 翻譯為 CR。 |
IGNCR | 忽略輸入中的回車。 |
ICRNL | 將輸入中的回車翻譯為新行 (除非設置了 IGNCR)。 |
IUCLC | (不屬于 POSIX) 將輸入中的大寫字母映射為小寫字母。 |
IXON | 啟用輸出的 XON/XOFF 流控制。 |
IXANY | (不屬于 POSIX.1;XSI) 允許任何字符來重新開始輸出。 |
IXOFF | 啟用輸入的 XON/XOFF 流控制。 |
IMAXBEL | (不屬于 POSIX) 當輸入隊列滿時響零。Linux 沒有實現這一位,總是將它視為已設置。 |
c_oflag 的標志常量定義如下:
標志 | 說明 |
---|---|
OLCUC | (不屬于 POSIX) 將輸出中的小寫字母映射為大寫字母。 |
ONLCR | (XSI) 將輸出中的新行符映射為回車-換行。 |
OCRNL | 將輸出中的回車映射為新行符。 |
ONOCR | 不在第 0 列輸出回車。 |
ONLRET | 不輸出回車。 |
OFILL | 發送填充字符作為延時,而不是使用定時來延時。 |
OFDEL | (不屬于 POSIX) 填充字符是 ASCII DEL (0177)。如果不設置,填充字符則是 ASCII NUL。 |
NLDLY | 新行延時掩碼。取值為 NL0 和 NL1。 |
CRDLY | 回車延時掩碼。取值為 CR0, CR1, CR2, 或 CR3。 |
TABDLY | 水平跳格延時掩碼。取值為 TAB0, TAB1, TAB2, TAB3 (或 XTABS)。取值為 TAB3,即 XTABS,將擴展跳格為空格 (每個跳格符填充 8 個空格)。 |
BSDLY | 回退延時掩碼。取值為 BS0 或 BS1。(從來沒有被實現過)。 |
VTDLY | 豎直跳格延時掩碼。取值為 VT0 或 VT1。 |
FFDLY | 進表延時掩碼。取值為 FF0 或 FF1。 |
c_cflag 的標志常量定義如下:
標志 | 說明 |
---|---|
CBAUD | (不屬于 POSIX) 波特率掩碼 (4+1 位)。 |
CBAUDEX | (不屬于 POSIX) 擴展的波特率掩碼 (1 位),包含在 CBAUD 中。(POSIX 規定波特率存儲在 termios 結構中,并未精確指定它的位置,而是提供了函數 cfgetispeed() 和 cfsetispeed() 來存取它。一些系統使用 c_cflag 中CBAUD 選擇的位,其他系統使用單獨的變量,例如 sg_ispeed 和sg_ospeed 。) |
CSIZE | 字符長度掩碼。取值為 CS5, CS6, CS7, 或 CS8。 |
CSTOPB | 設置兩個停止位,而不是一個。 |
CREAD | 打開接受者。 |
PARENB | 允許輸出產生奇偶信息以及輸入的奇偶校驗。 |
PARODD | 輸入和輸出是奇校驗。 |
HUPCL | 在最后一個進程關閉設備后,降低 modem 控制線 (掛斷)。 |
CLOCAL | 忽略 modem 控制線。 |
LOBLK | (不屬于 POSIX) 從非當前 shell 層阻塞輸出 (用于 shl )。 |
CIBAUD | (不屬于 POSIX) 輸入速度的掩碼。CIBAUD 各位的值與 CBAUD 各位相同,左移了 IBSHIFT 位。 |
CRTSCTS | (不屬于 POSIX) 啟用 RTS/CTS (硬件) 流控制 |
c_lflag 的標志常量定義如下:
標志 | 說明 |
---|---|
ISIG | 當接受到字符 INTR, QUIT, SUSP, 或 DSUSP 時,產生相應的信號。 |
ICANON | 啟用標準模式 (canonical mode)。允許使用特殊字符 EOF, EOL, EOL2, ERASE, KILL, LNEXT, REPRINT, STATUS, 和 WERASE,以及按行的緩沖。 |
XCASE | (不屬于 POSIX; Linux 下不被支持) 如果同時設置了 ICANON,終端只有大寫。輸入被轉換為小寫,除了以 前綴的字符。輸出時,大寫字符被前綴,小寫字符被轉換成大寫。 |
ECHO | 回顯輸入字符。 |
ECHOE | 如果同時設置了 ICANON,字符 ERASE 擦除前一個輸入字符,WERASE 擦除前一個詞。 |
ECHOK | 如果同時設置了 ICANON,字符 KILL 刪除當前行。 |
ECHONL | 如果同時設置了 ICANON,回顯字符 NL,即使沒有設置 ECHO。 |
ECHOCTL | (不屬于 POSIX) 如果同時設置了 ECHO,除了 TAB, NL, START, 和 STOP 之外的 ASCII 控制信號被回顯為 ?X, 這里 X 是比控制信號大 0x40 的 ASCII 碼。例如,字符 0x08 (BS) 被回顯為 ?H。 |
ECHOPRT | (不屬于 POSIX) 如果同時設置了 ICANON 和 IECHO,字符在刪除的同時被打印。 |
ECHOKE | (不屬于 POSIX) 如果同時設置了 ICANON,回顯 KILL 時將刪除一行中的每個字符,如同指定了 ECHOE 和 ECHOPRT 一樣。 |
DEFECHO | (不屬于 POSIX) 只在一個進程讀的時候回顯。 |
FLUSHO | (不屬于 POSIX; Linux 下不被支持) 輸出被刷新。這個標志可以通過鍵入字符 DISCARD 來開關。 |
NOFLSH | 禁止在產生 SIGINT, SIGQUIT 和 SIGSUSP 信號時刷新輸入和輸出隊列。 |
PENDIN | (不屬于 POSIX; Linux 下不被支持) 在讀入下一個字符時,輸入隊列中所有字符被重新輸出。(bash 用它來處理 typeahead) |
TOSTOP | 向試圖寫控制終端的后臺進程組發送 SIGTTOU 信號。 |
IEXTEN | 啟用實現自定義的輸入處理。這個標志必須與 ICANON 同時使用,才能解釋特殊字符 EOL2,LNEXT,REPRINT 和 WERASE,IUCLC 標志才有效。 |
c_cc 數組定義了特殊的控制字符。符號下標 (初始值) 和意義為:
標志 | 說明 |
---|---|
VINTR | (003, ETX, Ctrl-C, or also 0177, DEL, rubout) 中斷字符。發出 SIGINT 信號。當設置 ISIG 時可被識別,不再作為輸入傳遞。 |
VQUIT | (034, FS, Ctrl-) 退出字符。發出 SIGQUIT 信號。當設置 ISIG 時可被識別,不再作為輸入傳遞。 |
VERASE | (0177, DEL, rubout, or 010, BS, Ctrl-H, or also #) 刪除字符。刪除上一個還沒有刪掉的字符,但不刪除上一個 EOF 或行首。當設置 ICANON 時可被識別,不再作為輸入傳遞。 |
VKILL | (025, NAK, Ctrl-U, or Ctrl-X, or also @) 終止字符。刪除自上一個 EOF 或行首以來的輸入。當設置 ICANON 時可被識別,不再作為輸入傳遞。 |
VEOF | (004, EOT, Ctrl-D) 文件尾字符。更精確地說,這個字符使得 tty 緩沖中的內容被送到等待輸入的用戶程序中,而不必等到 EOL。如果它是一行的第一個字符,那么用戶程序的 read() 將返回 0,指示讀到了 EOF。當設置 ICANON時可被識別,不再作為輸入傳遞。 |
VMIN | 非 canonical 模式讀的最小字符數。 |
VEOL | (0, NUL) 附加的行尾字符。當設置 ICANON 時可被識別。 |
VTIME | 非 canonical 模式讀時的延時,以十分之一秒為單位。 |
VEOL2 | (not in POSIX; 0, NUL) 另一個行尾字符。當設置 ICANON 時可被識別。 |
VSTART | (021, DC1, Ctrl-Q) 開始字符。重新開始被 Stop 字符中止的輸出。當設置 IXON 時可被識別,不再作為輸入傳遞。 |
VSTOP | (023, DC3, Ctrl-S) 停止字符。停止輸出,直到鍵入 Start 字符。當設置 IXON 時可被識別,不再作為輸入傳遞。 |
VSUSP | (032, SUB, Ctrl-Z) 掛起字符。發送 SIGTSTP 信號。當設置 ISIG 時可被識別,不再作為輸入傳遞。 |
VLNEXT | (not in POSIX; 026, SYN, Ctrl-V) 字面上的下一個。引用下一個輸入字符,取消它的任何特殊含義。當設置 IEXTEN 時可被識別,不再作為輸入傳遞。 |
VSTART | (021, DC1, Ctrl-Q) 開始字符。重新開始被 Stop 字符中止的輸出。當設置 IXON 時可被識別,不再作為輸入傳遞。 |
VSTOP | (023, DC3, Ctrl-S) 停止字符。停止輸出,直到鍵入 Start 字符。當設置 IXON 時可被識別,不再作為輸入傳遞。 |
VSUSP | (032, SUB, Ctrl-Z) 掛起字符。發送 SIGTSTP 信號。當設置 ISIG 時可被識別,不再作為輸入傳遞。 |
VLNEXT | (not in POSIX; 026, SYN, Ctrl-V) 字面上的下一個。引用下一個輸入字符,取消它的任何特殊含義。當設置 IEXTEN 時可被識別,不再作為輸入傳遞。 |
VWERASE | (not in POSIX; 027, ETB, Ctrl-W) 刪除詞。當設置 ICANON 和 IEXTEN 時可被識別,不再作為輸入傳遞。 |
VREPRINT | (not in POSIX; 022, DC2, Ctrl-R) 重新輸出未讀的字符。當設置 ICANON和 IEXTEN 時可被識別,不再作為輸入傳遞。 |
4.3.1 tcgetattr
? 作用:獲取串口設備的屬性。
? 參數:
? fd,串口設備的文件描述符。
? termios_p,用于保存串口屬性。
? 返回:
? 成功,返回 0。
? 失敗,返回-1,errnor 給出具體錯誤碼。
4.3.2 tcsetattr
? 作用:設置串口設備的屬性。
? 參數:
? fd,串口設備的文件描述符。
? optional_actions,本次設置什么時候生效。
? termios_p,指向要設置的屬性結構。
? 返回:
? 成功,返回 0。
? 失敗,返回-1,errnor 給出具體錯誤碼
說明
其中,optional_actions的取值有:
TCSANOW:會立即生效。
TCSADRAIN:當前的輸出數據完成傳輸后生效,適用于修改了輸出相關的參數。
TCSAFLUSH:當前的輸出數據完成傳輸,如果輸入有數據可讀但沒有讀就會被丟棄。
4.3.3 cfgetispeed
? 作用:返回串口屬性中的輸入波特率。
? 參數:
? termios_p,指向保存有串口屬性的結構。
? 返回:
? 成功,返回波特率,取值是一組宏,定義在 terminos.h。
? 失敗,返回-1,errnor 給出具體錯誤碼。
說明
波特率定義如下所示:
#define B0 0000000
#define B50 0000001
#define B75 0000002
#define B110 0000003
#define B134 0000004
#define B150 0000005
#define B200 0000006
#define B300 0000007
#define B600 0000010
#define B1200 0000011
#define B1800 0000012
#define B2400 0000013
#define B4800 0000014
#define B9600 0000015
#define B19200 0000016
#define B38400 0000017
#define B57600 0010001
#define B115200 0010002
#define B230400 0010003
#define B460800 0010004
#define B500000 0010005
#define B576000 0010006
#define B921600 0010007
#define B1000000 0010010
#define B1152000 0010011
#define B1500000 0010012
#define B2000000 0010013
#define B2500000 0010014
#define B3000000 0010015
#define B3500000 0010016
#define B4000000 0010017
4.3.4 cfgetospeed
? 作用:返回串口屬性中的輸出波特率。
? 參數:
? termios_p,指向保存有串口屬性的結構。
? 返回:
? 成功,返回波特率,取值是一組宏,定義在 terminos.h,見 4.3.3
? 失敗,返回-1,errnor 給出具體錯誤碼。
4.3.5 cfsetispeed
? 作用:設置輸入波特率到屬性結構中。
? 參數:
? termios_p,指向保存有串口屬性的結構。
? speed,波特率,取值同 4.3.3。
? 返回:
? 成功,返回 0。
? 失敗,返回-1,errnor 給出具體錯誤碼
4.3.6 cfsetospeed
? 作用:設置輸出波特率到屬性結構中。
? 參數:
? termios_p,指向保存有串口屬性的結構。
? speed,波特率,取值同 4.3.3。
? 返回:
? 成功,返回 0。
? 失敗,返回-1,errnor 給出具體錯誤碼
4.3.7 cfsetspeed
? 作用:同時設置輸入和輸出波特率到屬性結構中。
? 參數:
? termios_p,指向保存有串口屬性的結構。
? speed,波特率,取值同 4.3.3。
? 返回:
? 成功,返回 0。
? 失敗,返回-1,errnor 給出具體錯誤碼
4.3.8 tcflush
? 作用:清空輸出緩沖區、或輸入緩沖區的數據,具體取決于參數 queue_selector。
? 參數:
? fd,串口設備的文件描述符。
? queue_selector,清空數據的操作。
? 返回:
? 成功,返回 0。
? 失敗,返回-1,errnor 給出具體錯誤碼。
說明
參數 queue_selector 的取值有三個:
TCIFLUSH:清空輸入緩沖區的數據。
TCOFLUSH:清空輸出緩沖區的數據。
TCIOFLUSH:同時清空輸入/輸出緩沖區的數據。
5 模塊使用范例
此 demo 程序是打開一個串口設備,然后偵聽這個設備,如果有數據可讀就讀出來并打印。設備名稱、偵聽的循環次數都可以由參數指定。
#include /*標準輸入輸出定義*/ #include /*標準函數庫定義*/ #include /*Unix 標準函數定義*/ #include #include #include /*文件控制定義*/ #include /*PPSIX 終端控制定義*/ #include /*錯誤號定義*/ #include enum parameter_type { PT_PROGRAM_NAME = 0, PT_DEV_NAME, PT_CYCLE, PT_NUM }; #define DBG(string, args...) do { printf("%s, %s()%u---", __FILE__, __FUNCTION__, __LINE__); printf(string, _00args); printf("n"); } while (0) void usage(void) { printf("You should input as: n"); printf("t select_test [/dev/name] [Cycle Cnt]n"); } int OpenDev(char *name) { int fd = open(name, O_RDWR ); //| O_NOCTTY | O_NDELAY if (-1 == fd) DBG("Can't Open(%s)!", name); return fd; } /** * @brief 設置串口通信速率 * @param fd 類型 int 打開串口的文件句柄 * @param speed 類型 int 串口速度 * @return void */ void set_speed(int fd, int speed) { int i; int status; struct termios Opt = {0}; int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300, B38400, B19200, B9600, B4800, B2400, B1200, B300, }; int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300, 38400, 19200, 9600, 4800, 2400, 1200, 300, }; tcgetattr(fd, &Opt); for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++) { ? ? ? ?if (speed == name_arr[i]) ? ? ? ?break; ? ?} ? ?tcflush(fd, TCIOFLUSH); ? ?cfsetispeed(&Opt, speed_arr[i]); ? ?cfsetospeed(&Opt, speed_arr[i]); ? ?Opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/ ? ?Opt.c_oflag &= ~OPOST; /*Output*/ ? ?status = tcsetattr(fd, TCSANOW, &Opt); ? ?if (status != 0) { ? ? ? ?DBG("tcsetattr fd"); ? ? ? ?return; ? ?} ? ?tcflush(fd, TCIOFLUSH); } /** *@brief 設置串口數據位,停止位和效驗位 *@param fd 類型 int 打開的串口文件句柄 *@param databits 類型 int 數據位 取值 為 7 或者8 *@param stopbits 類型 int 停止位 取值為 1 或者2 *@param parity 類型 int 效驗類型 取值為N,E,O,,S */ int set_Parity(int fd,int databits,int stopbits,int parity) { ? ?struct termios options; ? ?if ( tcgetattr(fd, &options) != 0) { ? ? ? ?perror("SetupSerial 1"); ? ? ? ?return -1; ? ?} ? ?options.c_cflag &= ~CSIZE; ? ?switch (databits) /*設置數據位數*/ ? ?{ ? ? ? ?case 7: ? ? ? ? ? ?options.c_cflag |= CS7; ? ? ? ? ? ?break; ? ? ? ?case 8: ? ? ? ? ? ?options.c_cflag |= CS8; ? ? ? ? ? ?break; ? ? ? ?default: ? ? ? ? ? ?fprintf(stderr,"Unsupported data sizen"); ? ? ? ? ? ?return -1; ? ?} ? ? ? ?switch (parity) ? ?{ ? ? ? ?case 'n': ? ? ? ?case 'N': ? ? ? ? ? ?options.c_cflag &= ~PARENB; /* Clear parity enable */ ? ? ? ? ? ?options.c_iflag &= ~INPCK; /* Enable parity checking */ ? ? ? ? ? ?break; ? ? ? ?case 'o': ? ? ? ?case 'O': ? ? ? ? ? ?options.c_cflag |= (PARODD | PARENB); /* 設置為奇效驗*/ ? ? ? ? ? ?options.c_iflag |= INPCK; /* Disnable parity checking */ ? ? ? ? ? ?break; ? ? ? ?case 'e': ? ? ? ?case 'E': ? ? ? ? ? ?options.c_cflag |= PARENB; /* Enable parity */ ? ? ? ? ? ?options.c_cflag &= ~PARODD; /* 轉換為偶效驗*/ ? ? ? ? ? ?options.c_iflag |= INPCK; /* Disnable parity checking */ ? ? ? ? ? ?break; ? ? ? ?case 'S': ? ? ? ?case 's': /*as no parity*/ ? ? ? ? ? ?options.c_cflag &= ~PARENB; ? ? ? ? ? ?options.c_cflag &= ~CSTOPB;break; ? ? ? ?default: ? ? ? ? ? ?fprintf(stderr,"Unsupported parityn"); ? ? ? ? ? ?return -1; ? ?} ? ? ? ?/* 設置停止位*/ ? ?switch (stopbits) ? ?{ ? ? ? ?case 1: ? ? ? ? ? ?options.c_cflag &= ~CSTOPB; ? ? ? ? ? ?break; ? ? ? ?case 2: ? ? ? ? ? ?options.c_cflag |= CSTOPB; ? ? ? ? ? ?break; ? ? ? ?default: ? ? ? ? ? ?fprintf(stderr,"Unsupported stop bitsn"); ? ? ? ? ? ?return -1; ? ?} ? ? ? ?/* Set input parity option */ ? ?if (parity != 'n') ? ? options.c_iflag |= INPCK; ? ?tcflush(fd,TCIFLUSH); ? ?options.c_cc[VTIME] = 150; /* 設置超時15 seconds*/ ? ?options.c_cc[VMIN] = 0; ? ?/* Update the options and do it NOW */ ? ?if (tcsetattr(fd,TCSANOW,&options) != 0) ? ?{ ? ? ? ?perror("SetupSerial 3"); ? ? ? ?return -1; ? ?} ? ?return 0; } void str_print(char *buf, int len) { ? ?int i; ? ?for (i=0; i;>
6 FAQ
6.1 UART 調試打印開關
6.1.1 通過 debugfs 使用命令打開調試開關
注:內核需打開 CONFIG_DYNAMIC_DEBUG 宏定義
1.掛載debugfs。 mount -t debugfs none /sys/kernel/debug 2.打開uart模塊所有打印。 echo "module sunxi_uart +p" > /mnt/dynamic_debug/control 3.打開指定文件的所有打印。 echo "file sunxi-uart.c +p" > /mnt/dynamic_debug/control 4.打開指定文件指定行的打印。 echo "file sunxi-uart.c line 615 +p" > /mnt/dynamic_debug/control 5.打開指定函數名的打印。 echo "func sw_uart_set_termios +p" > /mnt/dynamic_debug/control 6.關閉打印。 把上面相應命令中的+p 修改為-p 即可。 更多信息可參考linux 內核文檔:linux-3.10/Documentation/dynamic-debug-howto.txt。
6.1.2 代碼中打開調試開關
1.定義CONFIG_SERIAL_DEBUG宏。 linux-4.9 內核版本中默認沒有定義CONFIG_SERIAL_DEBUG , 需要自行在 drivers/tty/serial/Kconfig 中添加CONFIG_SERIAL_DEBUG 定義,然后drivers/tty/serial/Makefile文件中添加代碼ccflags-$(CONFIG_SERIAL_DEBUG) := -DDEBUG。 注:使用該宏,需要內核關閉CONFIG_DYNAMIC_DEBUG宏。
6.1.3 sysfs 調試接口
UART 驅動通過 sysfs 節點提供了幾個在線調試的接口
1./sys/devices/platform/soc/uart0/dev_info cupid-p2:/ # cat /sys/devices/platform/soc/uart0/dev_info id = 0 name = uart0 irq = 247 io_num = 2 port->mapbase = 0x0000000005000000 port->membase = 0xffffff800b005000 port->iobase = 0x00000000 pdata->regulator = 0x (null) pdata->regulator_id = 從該節點可以看到uart端口的一些硬件資源信息。 2./sys/devices/platform/soc/uart0/ctrl_info cupid-p2:/ # cat /sys/devices/platform/soc/uart0/ctrl_info ier : 0x05 lcr : 0x13 mcr : 0x03 fcr : 0xb1 dll : 0x0d dlh : 0x00 last baud : 115384 (dl = 13) TxRx Statistics: tx : 61123 rx : 351 parity : 0 frame : 0 overrun: 0 此節點可以打印出軟件中保存的一些控制信息,如當前UART 端口的寄存器值、收發數據的統計等。 3./sys/devices/platform/soc/uart0/status cupid-p2:/ # cat /sys/devices/platform/soc/uart0/status uartclk = 24000000 The Uart controller register[Base: 0xffffff800b005000]: [RTX] 0x00 = 0x0000000d, [IER] 0x04 = 0x00000005, [FCR] 0x08 = 0x000000c1 [LCR] 0x0c = 0x00000013, [MCR] 0x10 = 0x00000003, [LSR] 0x14 = 0x00000060 [MSR] 0x18 = 0x00000000, [SCH] 0x1c = 0x00000000, [USR] 0x7c = 0x00000006 [TFL] 0x80 = 0x00000000, [RFL] 0x84 = 0x00000000, [HALT] 0xa4 = 0x00000002 此節點可以打印出當前UART 端口的一些運行狀態信息,包括控制器的各寄存器值。
-
內核
+關注
關注
3文章
1363瀏覽量
40228 -
接口
+關注
關注
33文章
8497瀏覽量
150834 -
Linux
+關注
關注
87文章
11227瀏覽量
208924 -
uart
+關注
關注
22文章
1227瀏覽量
101172 -
開發指南
+關注
關注
0文章
34瀏覽量
7531
發布評論請先 登錄
相關推薦
評論