Linux RS-232 程式設計

Linux RS-232 程式設計

6-1 終端機介面
終端機介面又稱為TTY介面,用來讓Linux系統透過RS-232串列埠連接數據機,通過電話線路與遠端的電腦系統相連接。終端機介面有二種模式:正規(canonical)模式和非正規(non-canonical)模式。
模式說明
正規模式又稱為cooked模式。在這種模式中,終端設備會處理特殊字元,且會以一次一列的方式將輸入傳給應用程式。例如Linux的shell指令。
非正規模式又稱為raw模式。在這種模式中,終端設備不會處理特殊字元,且會以一次一個字元的方式將輸入傳給應用程式。例如在Linux使用vim編輯程式。

串列埠檔案
在Linux中針對所有的周邊裝置都提供了[裝置檔案]供使用者存取。若要存取TTY串列埠只要開啟相關的[裝置檔案]即可。
在Linux中,每一個TTY串列埠都會對應到一個或多個[裝置檔案],[裝置檔案]放在[/dev]目錄中。相關的裝置檔案如下:
裝置檔案說明
/dev/ttyS0串列埠的COM1
/dev/typS1串列埠的COM2

開啟通訊埠
在Linux中將串列埠視為一個檔案,可以使用open()函數來開啟串列埠。底下的程式用來開啟PC的COM1串列通訊埠。


 
#include 
#include 
#include 
#include 
#include 
#include 
int open_port(void)
{
    int fd;
    fd=open("/dev/ttyS0",O_RDWR|O_NOCTTY|O_NDELAY);
    if(fd == -1){
        perror("open error");
    }
    retuen(fd);
}

其中
O_NOCTTY:告訴Linux這個程式不想控制TTY介面,如果不設定這個旗標,有些輸入(例如鍵盤的abort)信號可能影響程式。
O_NDELAY:告訴Linux這個程式不介意RS-232的DCD信號的狀態。如果不設定這個旗標,程式會處於speep狀態,直到RS-232有DCD信號進來。


6-2 Termios結構
在Linux中設定串列埠的參數,例如鮑率、字元長度等,可以透過POSIX標準終端介面,此介面稱為Termios,並定義於系統的標頭檔中。termios的結構如下:

#include
struct termios{
    tcflag_t c_iflag;         //輸入模式
    tcflag_t c_oflag;        //輸出模式
    tcflag_t c_cflag;        //控制模式
    tcflag_t c_lflag;         //局部模式
    cc_t c_cc[NCCS];   //特殊控制字元
}

輸入模式 c_iflag

IGNBRKIgnore BREAK condition on input.
BRKINTIf IGNBRK is set, a BREAK is ignored. If it is not set but BRKINT is set, then a BREAK causes the input and output queues to be flushed, and if the terminal is the controlling terminal of a foreground process group, it will cause a SIGINT to be sent to this foreground process group. When neither IGNBRK nor BRKINT are set, a BREAK reads as a NUL character, except when PARMRK is set, in which case it reads as the sequence \377 \0 \0.
IGNPARIgnore framing errors and parity errors. 忽略frame和同位錯誤
PARMRKIf IGNPAR is not set, prefix a character with a parity error or framing error with \377 \0. If neither IGNPAR nor PARMRK is set, read a character with a parity error or framing error as \0.
INPCKEnable input parity checking. 執行同位位元檢查
ISTRIPStrip off eighth bit. 去除第8個位元
INLCRTranslate NL to CR on input.
IGNCRIgnore carriage return on input.
ICRNLTranslate carriage return to newline on input (unless IGNCR is set).
IUCLC(not in POSIX) Map uppercase characters to lowercase on input.
IXONEnable XON/XOFF flow control on output.
IXANY(not in POSIX.1; XSI) Enable any character to restart output.
IXOFFEnable XON/XOFF flow control on input.
IMAXBEL(not in POSIX) Ring bell when input queue is full. Linux does not implement this bit, and acts as if it is always set.

程式:
使用RS-232接收字元,執行同位位元檢查,c_iflag設定如下:
options.c_iflag |= (INPCK | ISTRIP);
串列埠忽略同位錯誤,接收傳入的字元
options.c_iflag |= IGNPAR;

輸出模式 c_oflag
c_oflag包含輸出過濾功能,負責控制輸出字元的處理方式。輸出字元在傳送到序列埠或螢幕之前是如何被程式處理。c_oflag的旗標如下:
OPOSTEnable implementation-defined output processing.
OLCUC(not in POSIX) Map lowercase characters to uppercase on output.
ONLCR(XSI) Map NL to CR-NL on output.
OCRNLMap CR to NL on output.
ONOCRDon't output CR at column 0.
ONLRETDon't output CR.
OFILLSend fill characters for a delay, rather than using a timed delay.
OFDEL(not in POSIX) Fill character is ASCII DEL (0177). If unset, fill character is ASCII NUL.
NLDLYNewline delay mask. Values are NL0 and NL1.
CRDLYCarriage return delay mask. Values are CR0, CR1, CR2, or CR3.
TABDLYHorizontal tab delay mask. Values are TAB0, TAB1, TAB2, TAB3 (or XTABS). A value of TAB3, that is, XTABS, expands tabs to spaces (with tab stops every eight columns).
BSDLYBackspace delay mask. Values are BS0 or BS1. (Has never been implemented.)
VTDLYVertical tab delay mask. Values are VT0 or VT1.
FFDLYForm feed delay mask. Values are FF0 or FF1.

程式
若要啟動輸出處理,必須加入OPOST選項,程式碼如下:
options.c_oflag |= OPOST;
將換列字元轉換成[CR][LF]
options.c_oflag |= OPOST | ONLCR;
若要啟動非正規模式,將OPOST選項設為disable,設定如下:
options.c_oflag &= ~OPOST;

控制模式 c_cflag
termios結構的c_cflag成員用來控制串列埠的鮑率、同位元、停止位元等。c_cflag的選項如下:
CBAUD(not in POSIX) Baud speed mask (4+1 bits).
CBAUDEX(not in POSIX) Extra baud speed mask (1 bit), included in CBAUD.
CSIZECharacter size mask. Values are CS5, CS6, CS7, or CS8.
CSTOPBSet two stop bits, rather than one.
CREADEnable receiver. 允許串列埠讀取傳入的資料。
PARENBEnable parity generation on output and parity checking for input.
PARODDParity for input and output is odd.
HUPCLLower modem control lines after last process closes the device (hang up).
CLOCALIgnore modem control lines. 忽略數據機控制線的信號
LOBLK(not in POSIX) Block output from a noncurrent shell layer. (For use by shl.)
CIBAUD(not in POSIX) Mask for input speeds. The values for the CIBAUD bits are the same as the values for the CBAUD bits, shifted left IBSHIFT bits.
CRTSCTS(not in POSIX) Enable RTS/CTS (hardware) flow control.

程式
將鮑率設定為9600bps。
struct termios options;
options.c_cflag |= (B9600 | CLOCAL | CREAD);

設定傳輸的資料長度為8 bits:
options.c_cflag |= CS8;

設定(8N1)傳輸資料長度8位元、無同位元檢查、1停止位元:
options.c_cflag |= ~PARENB;     //不允許同位元檢查
options.c_cflag |= ~CSTOPB;     //不是2停止位元
options.c_cflag |= CS8;             //8 bits

設定(7E1)傳輸資料長度7位元、偶同位元檢查、1停止位元:
options.c_cflag |= PARENB;     //允許同位元檢查
options.c_cflag |= ~PARODD;    //不是奇同位元檢查
options.c_cflag |= ~CSTOPB;     //不是2停止位元
options.c_cflag |= CS7;             //7 bits

設定(7O1)傳輸資料長度7位元、奇同位元檢查、1停止位元:
options.c_cflag |= PARENB;     //允許同位元檢查
options.c_cflag |= PARODD;    //不是奇同位元檢查
options.c_cflag |= ~CSTOPB;     //不是2停止位元
options.c_cflag |= CS7;             //7 bits

局部模式 c_lflag
c_lflag用來控制串列埠如何處理輸入字元。透過c_lflag設定串列埠為正規模式或非正規模式,c_lflag的旗標值如下:
ISIGWhen any of the characters INTR, QUIT, SUSP, or DSUSP are received, generate the corresponding signal.
ICANONEnable canonical mode. This enables the special characters EOF, EOL, EOL2, ERASE, KILL, LNEXT, REPRINT, STATUS, and WERASE, and buffers by lines.
XCASE(not in POSIX; not supported under Linux) If ICANON is also set, terminal is uppercase only. Input is converted to lowercase, except for characters preceded by \. On output, uppercase characters are preceded by \ and lowercase characters are converted to uppercase.
ECHOEcho input characters.
ECHOEIf ICANON is also set, the ERASE character erases the preceding input character, and WERASE erases the preceding word.
ECHOKIf ICANON is also set, the KILL character erases the current line.
ECHONLIf ICANON is also set, echo the NL character even if ECHO is not set.
ECHOCTL(not in POSIX) If ECHO is also set, ASCII control signals other than TAB, NL, START, and STOP are echoed as ^X, where X is the character with ASCII code 0x40 greater than the control signal. For example, character 0x08 (BS) is echoed as ^H.
ECHOPRT(not in POSIX) If ICANON and IECHO are also set, characters are printed as they are being erased.
ECHOKE(not in POSIX) If ICANON is also set, KILL is echoed by erasing each character on the line, as specified by ECHOE and ECHOPRT.
DEFECHO(not in POSIX) Echo only when a process is reading.
FLUSHO(not in POSIX; not supported under Linux) Output is being flushed. This flag is toggled by typing the DISCARD character.
NOFLSHDisable flushing the input and output queues when generating the SIGINT, SIGQUIT and SIGSUSP signals.
TOSTOPSend the SIGTTOU signal to the process group of a background process which tries to write to its controlling terminal.
PENDIN(not in POSIX; not supported under Linux) All characters in the input queue are reprinted when the next character is read. (bash handles typeahead this way.)
IEXTENEnable implementation-defined input processing. This flag, as well as ICANON must be enabled for the special characters EOL2, LNEXT, REPRINT, WERASE to be interpreted, and for the IUCLC flag to be effective.

程式
將串列埠設定為正規模式
options.c_lflag |= (ICANON | ECHO | ECHOE);

將串列埠設定為非正規模式
options.c_lflag |= ~(ICANON | ECHO | ECHOE | ISIG);

特殊控制字元 c_cc[NCCS]
c_cc 陣列成員用來定義支援的特殊控制字元,及一些timeout參數。
對正規模式,c_cc陣列的包括
NCCS特殊控制字元
VEOFEOF字元
VEOLEOL字元
VERASEERASE字元
VINTRINTR字元
VKILLKILL字元
VQUITQUIT字元
VSUSPSUSP字元
VSTARTSTART字元
VSTOPSTOP字元

對非正規模式,c_cc陣列的包括
NCCS特殊控制字元
VINTRKINTR字元
VMINMIN字元
VQUITQUIT字元
VSUSPSTART字元
VTIMETIME字元
VSTARTSTART字元
VSTOPSTOP字元
特殊控制字元:
VINTR(003, ETX, Ctrl-C, or also 0177, DEL, rubout) Interrupt character. Send a SIGINT signal. Recognized when ISIG is set, and then not passed as input.
VQUIT(034, FS, Ctrl-\) Quit character. Send SIGQUIT signal. Recognized when ISIG is set, and then not passed as input.
VERASE(0177, DEL, rubout, or 010, BS, Ctrl-H, or also #) Erase character. This erases the previous not-yet-erased character, but does not erase past EOF or beginning-of-line. Recognized when ICANON is set, and then not passed as input.
VKILL(025, NAK, Ctrl-U, or Ctrl-X, or also @) Kill character. This erases the input since the last EOF or beginning-of-line. Recognized when ICANON is set, and then not passed as input.
VEOF(004, EOT, Ctrl-D) End-of-file character. More precisely: this character causes the pending tty buffer to be sent to the waiting user program without waiting for end-of-line. If it is the first character of the line, the read() in the user program returns 0, which signifies end-of-file. Recognized when ICANON is set, and then not passed as input.
VMINMinimum number of characters for non-canonical read.
VEOL(0, NUL) Additional end-of-line character. Recognized when ICANON is set.
VTIMETimeout in deciseconds for non-canonical read.
VEOL2(not in POSIX; 0, NUL) Yet another end-of-line character. Recognized when ICANON is set.
VSWTCH(not in POSIX; not supported under Linux; 0, NUL) Switch character. (Used by shl only.)
VSTART(021, DC1, Ctrl-Q) Start character. Restarts output stopped by the Stop character. Recognized when IXON is set, and then not passed as input.
VSTOP(023, DC3, Ctrl-S) Stop character. Stop output until Start character typed. Recognized when IXON is set, and then not passed as input.
VSUSP(032, SUB, Ctrl-Z) Suspend character. Send SIGTSTP signal. Recognized when ISIG is set, and then not passed as input.
VDSUSP(not in POSIX; not supported under Linux; 031, EM, Ctrl-Y) Delayed suspend character: send SIGTSTP signal when the character is read by the user program. Recognized when IEXTEN and ISIG are set, and the system supports job control, and then not passed as input.
VLNEXT(not in POSIX; 026, SYN, Ctrl-V) Literal next. Quotes the next input character, depriving it of a possible special meaning. Recognized when IEXTEN is set, and then not passed as input.
VWERASE(not in POSIX; 027, ETB, Ctrl-W) Word erase. Recognized when ICANON and IEXTEN are set, and then not passed as input.
VREPRINT(not in POSIX; 022, DC2, Ctrl-R) Reprint unread characters. Recognized when ICANON and IEXTEN are set, and then not passed as input.
VDISCARD(not in POSIX; not supported under Linux; 017, SI, Ctrl-O) Toggle: start/stop discarding pending output. Recognized when IEXTEN is set, and then not passed as input.
VSTATUS(not in POSIX; not supported under Linux; status request: 024, DC4, Ctrl-T).

非正規模式的特殊字元TIME和MIN對於輸入字元的處理非常重要,有下列4種組合:
組合說明
MIN=0, TIME = 0以read()函數讀取串列埠後立即返回,若讀取到字元則傳回字元,否則傳回0。
MIN=0, TIME > 0以read()函數讀取串列埠後,會在TIME時間內等待第一個字元。若有字元傳入或時間到,立即返回。若讀取到字元則傳回字元,否則傳回0。
MIN > 0, TIME = 0以read()函數讀取串列埠後會等待資料傳入,若有MIN個字元可讀取,傳回讀取的字元數。
MIN > 0, TIME > 0以read()函數讀取串列埠後,會等待資料的傳入。若有MIN個字元可讀取時,傳回讀取到的字元數。若TIME的時間到,則read()傳回0。

程式
將串列埠設為非正規模式,讀取時間timeout設為1秒。
struct termios options;
options.c_cflag |= (CLOCAL | CREAD);
options.c_lflag |= ~(ICANON | ECHO | ECHOE | ISIG);
options.c_oflag |= ~OPOST;
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 10;

6-3 終端機相關函數
tcgetattr()
用來取得目前的串列埠參數值。
格式:
#include
int tcgetattr(int fd, struct termios *fp);
說明:
tcgetattr()取得檔案描述子fd後,將其存入tp所指向的termios資料結構。
傳回值:
成功:0
失敗:-1

tcsetattr()
用來設定串列埠參數值。
格式:
#include
int tcsetattr(int fd, int action, const struct termios *tp);
說明:
tcsetattr()執行後使用fp指向的termios資料結構,重新設定檔案描述子fd,其中引數action可以是下列的值
action值說明
TCSANOW立即將值改變
TCSADRAIN當目前輸出完成時,將值改變
TCSAFLUSH當目前輸出完成時,將值改變;並捨棄目前所有的輸入。

cfgetispeed()
傳回串列埠的輸入速率:
格式:
#include
int cfgetispeed(struct termios *tp);
其中tp為被處理的termios結構。

cgsetispeed()
設定串列埠的輸入速度。
格式:
#include
int cfsetispeed(struct termios *tp, speed_t speed);
其中tp為被處理的termios結構,speed為鮑率,可以是以下的一個值。
speed值speed值
B0B1800
B50B2400
B75B4800
B110B9600
B134B19200
B150B38400
B200B57600
B300B115200
B600B230400

cfgetospeed()
傳回串列埠的輸出速度。
格式:
#include
int cfgetospeed(struct *tp);
其中tp為被處理的termios結構。

cfsetospeed()
設定串列埠的輸出速度。
格式:
#include
int cfsetospeed(struct termios *tp, speed_t speed)
其中tp為被處理的termios結構,speed為鮑率。

tcdrain()
等待所有輸出寫到串列埠後,才返回呼叫的程式。
格式:
#include
tcdrain(int fd);
其中fd為被處理的串列埠。

tcflush()
清除所有佇列在串列埠的輸入和輸出。
格式:
#include
int tcflush(int fd, int queue);
其中fd為被處理的串列埠,引數queue為下列值。
queue值說明
TCIFLUSH清除輸入
TCOFLUSH清除輸出
TCIOFLUSH清除輸入和輸出

tcflow()
啟動或停止串列埠的資料傳送或接收。
格式:
#include
int tcflow(int fd, int action);
其中fd為被處理的串列埠,引數action為下列值。
action值說明
TCOON啟動輸出
TCOOFF停止輸出
TCION啟動輸入
TCIOFF停止輸入

6-4 程式:RS-232通訊
目的:
以正規模式撰寫RS-232通訊程式

要求:
透過COM1和COM2,藉由RS-232通訊來傳送資料。COM1為傳送端,COM2為接收端。RS-232的通訊格式為38400,n,8,1。

接收端程式
讀取COM2傳入的資料,並將其顯示在螢幕,當收到@字元表示傳送結束。


 
/* rs232_recv.c */
#include 
#include 
#include 
#include 
#include 

#define BAUDRATE B38400
#define MODEMDEVICE "/dev/ttyS1"
#define _POSIX_SOURCE 1

int main()
{
    int fd, c=0, res;
    struct termios oldtio, newtio;
    char buf[256];

    printf("Start...\n");
    fd = open(MODEMDEVICE, O_RDWR|O_NOCTTY);
    if (fd < 0) {
      perror(MODEMDEVICE);
      exit(1);
    }

    printf("Open...\n");
    tcgetattr(fd, &oldtio);
    bzero(&newtio, sizeof(newtio));

    newtio.c_cflag = BAUDRATE|CS8|CLOCAL|CREAD;
    newtio.c_iflag = IGNPAR;
    newtio.c_oflag = 0;
    newtio.c_lflag = ICANON;

    tcflush(fd, TCIFLUSH);
    tcsetattr(fd, TCSANOW, &newtio);
    printf("Reading...\n");
    while(1) {
        res = read(fd, buf, 255);
        buf[res]=0;
        printf("res=%d buf=%s\n", res, buf);
        if (buf[0] == '@') break;
    }
    
    printf("Close...\n");
    close(fd);
    tcsetattr(fd, TCSANOW, &oldtio);
    return 0;
}


傳送端程式
將資料從COM1傳送出去,每當使用者按下enter鍵,就傳送出去。


 
/* rs232_send.c */
#include 
#include 
#include 
#include 
#include 

#define BAUDRATE B38400
#define MODEMDEVICE "/dev/ttyS0"
#define _POSIX_SOURCE 1
#define STOP '@'

int main()
{
    int fd, c=0, res;
    struct termios oldtio, newtio;
    char ch;
    static char s1[20];

    printf("Start...\n");
    fd = open(MODEMDEVICE, O_RDWR|O_NOCTTY);
    if (fd < 0) {
      perror(MODEMDEVICE);
      exit(1);
    }

    printf("Open...\n");
    tcgetattr(fd, &oldtio);
    bzero(&newtio, sizeof(newtio));

    newtio.c_cflag = BAUDRATE|CS8|CLOCAL|CREAD;
    newtio.c_iflag = IGNPAR;
    newtio.c_oflag = 0;
    newtio.c_lflag = ICANON;

    tcflush(fd, TCIFLUSH);
    tcsetattr(fd, TCSANOW, &newtio);

    printf("Writing...\n");

    while(1) {
      while((ch=getchar()) != STOP) {
        s1[0]=ch;
        res=write(fd, s1, 1); 
      }
      s1[0]=ch;
      s1[1]='\n';
      res = write(fd, s1, 2); 
      break;
    }
    printf("Close...\n");
    close(fd);
    tcsetattr(fd, TCSANOW, &oldtio);
    return 0;
}



6-5 程式:刷卡機應用
刷卡機套件
1. 讀取單軌ISO7811/2規格的磁卡,並將號碼顯示在LCD上。
2.有讀取錯誤的文字和聲音警告功能。
3.讀取到的資料可傳送到8051。
4.可與PC溝通,支援COM1和COM2。

目的:
以Linux C設計一個可以讀取讀卡機資料的程式。
使用非正規模式來設計Linux RS-232通序程式。

要求:
讀卡機與PC的COM1連接,通訊規格為9600,n,8,1。

程式:


 
/* rs232_card.c */
#include 
#include 
#include 
#include 
#include 

#define BAUDRATE B9600
#define MODEMDEVICE "/dev/ttyS0"
#define _POSIX_SOURCE 1

int main()
{
    int fd, c=0, res;
    struct termios oldtio, newtio;
    char buf[256];

    printf("Start...\n");
    fd = open(MODEMDEVICE, O_RDWR|O_NOCTTY);
    if (fd < 0) {
      perror(MODEMDEVICE);
      exit(1);
    }

    printf("Open...\n");
    tcgetattr(fd, &oldtio);
    bzero(&newtio, sizeof(newtio));

    newtio.c_cflag = BAUDRATE|CS8|CLOCAL|CREAD;
    newtio.c_iflag = IGNPAR;
    newtio.c_oflag = 0;
    newtio.c_lflag = 0;
    newtio.c_cc[VTIME]=0;
    newtio.c_cc[VMIN]=10;

    tcflush(fd, TCIFLUSH);
    tcsetattr(fd, TCSANOW, &newtio);
    printf("Reading...\n");
    while(1) {
      res = read(fd, buf, 10);
      buf[res]=0;
      printf("res=%d buf=%s\n", res, buf);
      break;
    }
    printf("Close...\n");
    close(fd);
    tcsetattr(fd, TCSANOW, &oldtio);
    return 0;
}


留言

  1. options.c_cflag |= PARODD;//不是奇同位元檢查
    這行應該是//是奇同位元檢查

    回覆刪除
  2. 請問什麼要close(fd); 之後才設回oldtio,而不是之前?
    麻煩解惑了

    回覆刪除

張貼留言

這個網誌中的熱門文章

NMEA標準格式 -- GPS

網路 Transformer 的用途

cut,sed,awk 字串處理