【linux】系統呼叫版串列埠分析&原始碼實戰
阿新 • • 發佈:2020-11-27
[toc]
---
## 前言
* **目前不涉及驅動原始碼**
## 參考
* [linux手冊之termios](https://man7.org/linux/man-pages/man3/termios.3.html)
* [本文連結](https://www.cnblogs.com/lizhuming/p/14049897.html)
## 1. 實戰分析
### 1.1 開發步驟
1. 獲取串列埠裝置路徑
2. 開啟裝置檔案
3. 配置串列埠
4. 對該裝置檔案進行讀寫,相當於對該串列埠裝置進行讀寫,即通訊
5. 關閉裝置檔案
***以下程式碼段預設從 附件-最終串列埠測試原始碼 中摘取***
#### 1.1.1 獲取串列埠裝置路徑
* 使用陣列或者巨集定義在相關檔案前面定義預設串列埠路徑,方便修改,原始碼段如下:
```c
/* 不同的裝置,不同的路徑 */
const char def_uart_path[] = "/dev/ttymxc2" // 預設串列埠路徑(備用)
```
* 串列埠路徑優先從傳入引數中獲取,如果引數中沒有傳入,便使用 `def_uart_path` 預設路徑
```c
/* 終端裝置選擇 */
if(argc > 1)
path = argv[1];
else
path = (char *)def_uart_path;
```
#### 1.1.2 開啟裝置檔案
* 獲取裝置控制代碼,如果獲取失敗,便結束
```c
/* 開啟終端 */
fd = open(path, O_RDWR);
if(fd < 0){
printf("[%s] open err!", path);
return 0;
}
```
#### 1.1.3 配置串列埠
* 定義一個結構體 termios 用於獲取、設定終端裝置的引數
1. 包括波特率、資料位數、校驗位等
##### termios 結構體
* ***成員值作用,推薦先看官方手冊,看不懂再看本筆記中文表格***
* termios 結構體定義在編譯連結工具的標頭檔案預設路徑中的bits資料夾中
* 如如下原始碼來自 ***/usr/arm-linux-gnueabihf/include/bits/termios.h***
*
|成員|說明|
|:-:|:-:|
|c_iflag|輸入模式標誌|
|c_oflag|輸出模式標誌|
|c_cflag|控制模式標誌|
|c_lflag|本地模式標誌|
|c_line|行控制|
|c_cc[NCCS]|控制字元|
|c_ispeed|輸入波特率|
|c_ospeed|輸出波特率|
```c
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 */
speed_t c_ispeed; /* input speed */
speed_t c_ospeed; /* output speed */
#define _HAVE_STRUCT_TERMIOS_C_ISPEED 1
#define _HAVE_STRUCT_TERMIOS_C_OSPEED 1
};
```
##### 1. c_iflag 輸入模式標誌
* 用於控制如何對串列埠輸入的字元進行處理
*
|選項值|說明|
|:-:|:-:|
|IGNBRK|忽略輸入中的 BREAK 狀態。 (忽略命令列中的中斷)|
|BRKINT|(命令列出 現中斷時,可產生一插斷)如果設定了IGNBRK,中斷條件被忽略。如果沒有設定IGNBRK而設定了BRKINT,中斷條件清空輸入輸出佇列中所有的資料並且向tty的前 臺程序組中所有程序傳送一個SIGINT訊號。如果這兩個都沒有設定,中斷條件會被看作一個0字元。這時,如果設定了PARMRK,當檢測到一個幀誤差時 將會嚮應用程式傳送三個位元組'/377''/0''/0',而不是隻傳送一個'/0'|
|IGNPAR|忽略楨錯誤和奇偶校驗錯|
|PARMRK|如果設定了IGNPAR,則忽略接收到的資料的奇偶檢驗錯誤或幀錯誤(除了前面提到的中斷條件)。如果沒有設定IGNPAR而設定了PARMRK, 當接收到的位元組存在奇偶檢驗錯誤或幀錯誤的時候。將嚮應用程式傳送一個三位元組的'/377''/0''/n'錯誤報告。其中n表示所接收到的位元組。如果兩 者都沒有設定,除了接收到的位元組存在奇偶檢驗錯誤或幀誤差之外的中止條件都會嚮應用程式傳送一個單位元組('/0')的報告|
|INPCK|如果設定,則進行奇偶校驗。如果不進行奇偶檢驗,PARMRK和IGNPAR將對存在的奇偶校驗錯誤不產生任何的影響|
|ISTRIP|如果設定,所接收到的所有位元組的高位將會被去除,保證它們是一個7位的字元|
|INLCR|如果設定,所接收到的換行字元('/n')將會被轉換成回車符('/r')|
|IGNCR|如果設定,則會忽略所有接收的回車符('/r')|
|ICRNL|如果設定,但IGNCR沒有設定,接收到的回車符嚮應用程式傳送時會變換成換行符|
|IUCLC|如果IUCLC和IEXTEN都設定,接收到的所有大寫字母傳送給應程式時都被轉換成小寫字母。POSIX中沒有定義該標記|
|IXON|如果設定,接收到^S後會停止向這個tty裝置輸出,接收到^Q後會恢復輸出|
|IXANY|如果設定,則接到任何字元都會重新開始輸出,而不僅僅是^Q字元|
|IXOFF|如果設定,為避免tty裝置的輸入緩衝區溢位,tty裝置可以向終端傳送停止符^S和開始符^Q,要求終端停止或重新開始向計算機發送資料。通過停 止符和開始符來控制資料流的方式叫軟體流控制,軟體流控制方式較少用,我們主要還是用硬體流控制方式。硬體流控制在c_cflag標誌中設定|
|IMAXBEL|如果設定,當輸入緩衝區空間滿時,再接收到的任何字元就會發出警報符'/a'。POSIX中沒有定義該標記|
|IUTF8|(不屬於 POSIX)輸入 IUTF8 ,這是允許 character-erase 在 cooked 模式下被正確執行|
##### 2. c_oflag 輸出模式標誌
* 用於控制串列埠的輸出模式
*
|選項值|說明|
|:-:|:-:|
|OPOST|開啟該標記,後面的輸出標記才會生效。否則,不會對輸出資料進行處理|
|OLCUC|如果設定,大寫字母被轉換成小寫字母輸出|
|ONLCR|如果設定,在傳送換行符('/n')前先發送回車符('/r')|
|OCRNL|如果設定,回車符會被轉換成換行符。另外,如果設定了ONLRET,則current column會被設為0|
|ONOCR|如果設定,當current column為0時,回車符不會被髮送也不會被處理|
|ONLRET|如果設定,當一個換行符或回車符被髮送的時候,current column會被設定為0|
|OFILL|傳送填充字元作為延時,而不是使用定時來延時|
|OFDEL|(不屬於 POSIX) 填充字元是 ASCII DEL (0177)。如果不設定,填充字元則是 ASCII NUL|
|VTDLY|豎直跳格延時掩碼。取值為 VT0 或 VT1|
##### 3. c_cflag 控制模式標誌
* 用於控制串列埠的基本引數,如資料位、停止位等,常用配置見下表,特別地,c_cflag結構體成員還包含了波特率的引數
*
|選項值|說明|
|:-:|:-:|
|CLOCAL|如果設定,modem的控制線將會被忽略。如果沒有設定,則open()函式會阻塞直到載波檢測線宣告modem處於摘機狀態為止|
|CREAD|只有設定了才能接收字元,該標記是一定要設定的|
|CSIZE|設定傳輸字元的位數。CS5表示每個字元5位,CS6表示每個字元6位,CS7表示每個字元7位,CS8表示每個字元8位|
|CSTOPB|設定停止位的位數,如果設定,則會在每幀後產生兩個停止位,如果沒有設定,則產生一個停止位。一般都是使用一位停止位。需要兩位停止位的裝置已過時 了|
|HUPCL|如果設定,當裝置最後開啟的檔案描述符關閉時,串列埠上的DTR和RTS線會減弱訊號,通知Modem結束通話。也就是說,當一個使用者通過Modem拔號 登入系統,然後登出,這時Modem會自動結束通話|
|PARENB|允許輸出產生奇偶資訊以及輸入的奇偶校驗(啟用同位產生與偵測)|
|PARODD|輸入和輸出是奇校驗(使用奇同位而非偶同位)|
|CRTSCTS|使用硬體流控制。在高速(19200bps或更高)傳輸時,使用軟體流控制會使效率降低,這個時候必須使用硬體流控制|
##### 4. c_lflag 本地模式標誌
* 主要用於控制驅動程式與使用者的互動,在串列埠通訊中,實際上用不到該成員變數
*
|選項值|說明|
|:-:|:-:|
|ISIG|當接受到字元 INTR, QUIT, SUSP, 或 DSUSP 時,產生相應的訊號|
|ICANON|啟用標準模式 (canonical mode)。允許使用特殊字元EOF, EOL, EOL2, ERASE, KILL, LNEXT, REPRINT, STATUS, 和WERASE,以及按行的緩衝|
|ECHO|它可以讓你阻止鍵入字元的迴應|
|ECHOE|如果同時設定了 ICANON,字元 ERASE 擦除前一個輸入字元,WERASE 擦除前一個詞|
|ECHOK|如果同時設定了 ICANON,字元 KILL 刪除當前行|
|ECHONL|如果同時設定了 ICANON,回顯字元 NL,即使沒有設定 ECHO|
|NOFLSH|禁止在產生 SIGINT, SIGQUIT 和 SIGSUSP 訊號時重新整理輸入和輸出佇列,即關閉queue中的flush|
|TOSTOP|向試圖寫控制終端的後臺程序組傳送 SIGTTOU 訊號(傳送欲寫入的資訊到後臺 處理)|
|IEXTEN|啟用實現自定義的輸入處理。這個標誌必須與 ICANON同時使用,才能解釋特殊字元 EOL2,LNEXT,REPRINT 和WERASE,IUCLC 標誌才有效|
##### 5. c_cc[NCCS] 控制字元
* 該陣列包含了終端的所有特殊字元,可以修改特殊字元對應的鍵值(Ctrl+C產生的^C,ASCII碼為0x03)
* 僅列出常用的
*
|選項值|說明|
|:-:|:-:|
|VINTR|中斷字元。發出 SIGINT 訊號。當設定了c_lflag的ISIG標誌位時,該字母不再作為輸入傳遞|
|VERASE|刪除字元。刪除上一個還沒有刪掉的字元,但不刪除上一個EOF 或行首。當設定 ICANON 時可被識別,不再作為輸入傳遞|
|VIM|設定非標準模式讀取的最小位元組數|
|VTIM|設定非標準模式讀取時的延時值,單位為十分之一秒|
##### 6. c_ispeed和c_ospeed 波特率
* ***注意以 0 開頭的數字在是 C語言 的 8進位制 數字形式***
```c
#define B0 0000000 /* hang up */
#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 000001
#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
```
##### 分析
* 以上只是介紹了 termios 結構體,在編寫程式碼時,我們使用相關 api 去配置該結構體從而配置串列埠
* api 介面推薦先看本文推薦連結,不懂再看本文
```c
// api 如下
#include
#include
int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);
int tcsendbreak(int fd, int duration);
int tcdrain(int fd);
int tcflush(int fd, int queue_selector);
int tcflow(int fd, int action);
void cfmakeraw(struct termios *termios_p);
speed_t cfgetispeed(const struct termios *termios_p);
speed_t cfgetospeed(const struct termios *termios_p);
int cfsetispeed(struct termios *termios_p, speed_t speed);
int cfsetospeed(struct termios *termios_p, speed_t speed);
int cfsetspeed(struct termios *termios_p, speed_t speed);
```
* 清空接收緩衝區,獲取串列埠引數,配置,更新配置
```c
/* 定義串列埠結構體 */
struct termios opt;
/* 清空串列埠接收緩衝區 */
tcflush(fd, TCIOFLUSH);
/* 獲取串列埠引數 */
tcgetattr(fd, &opt);
/* 設定輸入、輸入波特率 */
cfsetospeed(&opt, B9600);
cfsetispeed(&opt, B9600);
/* 設定資料位數 */
opt.c_cflag &= ~CSIZE;
opt.c_cflag |= CS8;
/* 校驗位 */
opt.c_cflag &= ~PARENB;
opt.c_iflag &= ~INPCK;
/* 設定停止位 */
opt.c_cflag &= ~CSTOPB;
/* 更新配置 */
tcsetattr(fd, TCSANOW, &opt);
```
#### 1.1.4 串列埠收發測試
* 串列埠收發測試就是對該串列埠進行讀寫
```c
/* 傳送測試 */
write(fd, bufW, strlen(bufW));
/* 接收測試 */
res = read(fd, bufR, 512);
if(res > 0){
printf("receive data is %s", bufR);
}
```
#### 1.1.5 關閉裝置檔案
* 程式正常結束,不要忘記關閉裝置檔案
```c
/* 關閉檔案 */
close(fd);
```
## 附件
### 最終串列埠測試原始碼
```c
/** @file main.c
* @brief 串列埠測試檔案-系統呼叫版
* @details 詳細說明
* @author lzm
* @date 2020-11-23 19:18:20
* @version v1.0
* @copyright Copyright By lizhuming, All Rights Reserved
*
**********************************************************
* @LOG 修改日誌:
**********************************************************
*/
#include
#include
#include
#include
#include
#include
#include
#include
/* 不同的裝置,不同的路徑 */
const char def_uart_path[] = "/dev/ttymxc2"; // 預設串列埠路徑(備用)
/**
* @brief 主函式
* @param 無
* @retval 無
*/
int main(int argc, char *argv[])
{
int fd;
int res;
char *path;
char bufW[512] = "This is sys uart test!\n";
char bufR[512];
/* 終端裝置選擇 */
if(argc > 1)
path = argv[1];
else
path = (char *)def_uart_path;
/* 開啟終端 */
fd = open(path, O_RDWR);
if(fd < 0){
printf("[%s] open err!", path);
return 0;
}
/* 定義串列埠結構體 */
struct termios opt;
/* 清空串列埠接收緩衝區 */
tcflush(fd, TCIOFLUSH);
/* 獲取串列埠引數 */
tcgetattr(fd, &opt);
/* 設定輸入、輸入波特率 */
cfsetospeed(&opt, B9600);
cfsetispeed(&opt, B9600);
/* 設定資料位數 */
opt.c_cflag &= ~CSIZE;
opt.c_cflag |= CS8;
/* 校驗位 */
opt.c_cflag &= ~PARENB;
opt.c_iflag &= ~INPCK;
/* 設定停止位 */
opt.c_cflag &= ~CSTOPB;
/* 更新配置 */
tcsetattr(fd, TCSANOW, &opt);
do{
/* 傳送測試 */
write(fd, bufW, strlen(bufW));
/* 接收測試 */
res = read(fd, bufR, 512);
if(res > 0){
printf("receive data is %s", bufR);
}
}while(res >= 0);
/* 讀取錯誤 */
printf("read error, res = %d", res);
/* 關閉檔案 */
close(fd);
return 0;
}
```