linux下的串口編程入門

 1.簡介
    串口通信可以分爲同步通信和異步通信兩類。同步通信是按照軟件識別同步字符來實現數據的發送和接收,異步通信是一種利用字符的再同步技術的通信方式。
        1.1同步通信
        同步通信是一種連續串行傳送數據的通信方式,一次通信只傳送一幀信息。這裏的信息幀與異步通信中的字符幀不同,通常含有若干個數據字符。
        它們均由同步字符、數據字符和校驗字符(CRC)組成。其中同步字符位於幀開頭,用於確認數據字符的開始。數據字符在同步字符之後,個數沒有限制,由所需傳輸的數據塊長度來決定;校驗字符有1到2個,用於接收端對接收到的字符序列進行正確性的校驗。
        同步通信的缺點是要求發送時鐘和接收時鐘保持嚴格的同步。
        1.2異步通信
        異步通信中,數據通常以字符或者字節爲單位組成字符幀傳送。字符幀由發送端逐幀發送,通過傳輸線被接收設備逐幀接收。發送端和接收端可以由各自的時鐘來控制數據的發送和接收,這兩個時鐘源彼此獨立,互不同步。
        接收端檢測到傳輸線上發送過來的低電平邏輯"0"(即字符幀起始位)時,確定發送端已開始發送數據,每當接收端收到字符幀中的停止位時,就知道一幀字符已經發送完畢。
        在異步通行中有兩個比較重要的指標:字符幀格式和波特率。
        (1)字符幀,由起始位、數據位、奇偶校驗位和停止位組成。
        1.起始位:位於字符幀開頭,佔1位,始終爲邏輯0電平,用於向接收設備表示發送端開始發送一幀信息。
        2.數據位:緊跟在起始位之後,可以設置爲5位、6位、7位、8位,低位在前高位在後。
        3.奇偶校驗位:位於數據位之後,僅佔一位,用於表示串行通信中採用奇校驗還是偶校驗。
        (2)波特率,波特率是每秒鐘傳送二進制數碼的位數,單位是b/s。
        異步通信的優點是不需要傳送同步脈衝,字符幀長度也不受到限制。缺點是字符幀中因爲包含了起始位和停止位,因此降低了有效數據的傳輸速率。
        1.3什麼是RS-232
        RS-232-C 接口(又稱 EIA RS-232-C)它是在 1970 年由美國電子工業協會(EIA)聯合貝爾系統、調制解調器廠家及計算機終端生產廠家共同制定的用於串行通訊的標準。它的全名是"數據終端設備(DTE)和 數據通訊設備(DCE)之間串行二進制數據交換接口技術標準"該標準規定採用一個 25 個腳的 DB25 連接器,對連接器的每個引腳的信號內容加以規定,還對各種信號的電平加以規定。傳輸距離在碼元畸變小於 4% 的情況下,傳輸電纜長度應爲 50 英尺。
       1.4計算機串口引腳說明
        引出號     說明
1 接地
2 TXD輸出
3 RXD輸入
4 RTS請求發送
5 CTS請求接收
6 DSR數據序列就緒
7 GND邏輯地
8 DCD數據負載檢測
9 保留
10 保留
11 未定義
12 後備DCD
13 後備CTS
14 後備TXD
15 傳輸時鐘
16 後備RXD
17 接收時鐘
18 未定義
19 後備RTS
20 DTR數據終端就緒
21 信號質量檢測
22 鬧鐘檢測
23 數據速率選擇
24 傳輸時鐘
25 未定義
      
       1.5    3線接法
       A機      B機
       2----------3    接收
       3----------2    發送
       5----------5     地線

       1.6全雙工與半雙工
        1.全雙工,表示機器可以同時發送數據也可以接收數據,有兩個獨立的數據通道(一個用於發送,一個用於接收)
        2.半雙工,表示機器不能在發送數據的同時也接收數據。

        1.7流量控制
        1)使用軟件方法
        使用特殊的字符來標記數據流的開始和結束,比如XON,DC1,八進制021來標誌開始,用X0FF,DC3,八進制023來標誌結束。
        2)使用硬件方法
        使用RS232的CTS和RTS信號來代替特殊字符控制。當接收方準備接收更多數據時,設置CTS爲0,反之設置成1。對應的發送端準備發送數據時,設置RTS爲0。
         
2.串口訪問函數
    1)打開串口
    int   fd;
    fd = open("/dev/ttyS0",O_RDWR|NOCTTY|O_NDELAY);
    ttyS0是串口1,ttyS1是串口2,O_RDWR|NOCTTY|O_NDELAY分別表示可讀寫,非控制終端(防止任意的中斷信號(如鍵盤)影響程序的執行),不關注DCD信號線的狀態.詳細設置如下:
    O_RDONLY 以只讀方式打開文件
    O_WRONLY 以只寫方式打開文件
    O_RDWR 以讀寫方式打開文件
    O_APPEND 寫入數據時添加到文件末尾
    O_CREATE 如果文件不存在則產生該文件,使用該標誌需要設置訪問權限位mode_t
    O_EXCL 指定該標誌,並且指定了O_CREATE標誌,如果打開的文件存在則會產生一個錯誤
    O_TRUNC 如果文件存在並且成功以寫或者只寫方式打開,則清除文件所有內容,使得文件長度變爲0
    O_NOCTTY 如果打開的是一個終端設備,這個程序不會成爲對應這個端口的控制終端,如果沒有該標誌,任何一個輸入,例如鍵盤中止信號等,都將影響進程。
    O_NONBLOCK 該標誌與早期使用的O_NDELAY標誌作用差不多。程序不關心DCD信號線的狀態,如果指定該標誌,進程將一直在休眠狀態,直到DCD信號線爲0。
    O_SYNC 對I/O進行寫等待
    返回值:成功返回文件描述符,如果失敗返回-1
    2)關閉串口
    close(fd);  //返回值:成功返回0,否則返回-1
    3)向串口寫數據
    int   n;
    n = write(fd,buff,bufLen);
    buff是數據的緩衝區地址,bufLen是其長度,n 是實際發送的長度
    4)從串口讀數據
    n = read(fd,buff,bufLen);
    注意,在對串口進行讀取操作的時候,如果是使用的RAW模式,每個read系統調用將返回當前串行輸入緩衝區中存在的字節數。如果沒有數據,將會一致阻塞 到有字符達到或者間隔時鐘到期,或者發生錯誤。如果想使read函數在沒有數據的時候立即返回則可以使用fcntl函數來設置文件訪問屬性。例如:
    fcntl(fd, F_SETFL, FNDELAY);
    這樣設置後,當沒有可讀取的數據時,read函數立即返回0。
    通過fcntl(fd, F_SETFL, 0)可以設置回一般狀態。
    5)設置串口屬性
    控制結構爲:POSIX終端接口
    大多數系統都支持POSIX終端接口,POSIX終端通過一個termios結構來進行控制,該結構定義在termios.h文件中。
    termios結構
    struct termios
    {
                tcflag_t c_iflag; /* 輸入選項標誌 */
                tcflag_t c_oflag; /* 輸出選項標誌 */
                tcflag_t c_cflag; /* 控制選項標誌 */
                tcflag_t c_lflag; /* 本地選項標誌 */
                cc_t c_cc[NCCS]; /* 控制特性 */
    };
    (1)設置波特率
    struct termios Opt;
    tcgetattr(fd, &Opt);                   /*得到當前的串口屬性*/

    cfsetispeed(&Opt,B19200);      /*設置爲19200Bps*/
    cfsetospeed(&Opt,B19200);
    tcsetattr(fd,TCANOW,&Opt);  /*設置新的串口屬性*/
    (2)設置控制模式
          ->流控
          不使用:Opt.c_cflag &= ~CRTSCTS
          硬件:    Opt.c_cflag   |=  CRTSCTS
          軟件:    Opt.c_cflag    = IXON|IXOFF|IXANY
          ->設置字符的大小
         
Opt.c_cflag &    =~    CSIZE   //先屏蔽字符大小位
          Opt.c_cflag        |=      CS8      //選擇8位數據位
         
Opt.c_cflag        |=      CS7      //選擇7位數據位
         
Opt.c_cflag        |=      CS6      //選擇6位數據位
         
Opt.c_cflag        |=      CS5      //選擇5位數據位
          ->設置奇偶校驗
         
無校驗位:    Opt.c_cflag &=~PARENB
         
奇校驗:        Opt.c_cflag |=   PARENB;
         
                     Opt.c_cflag &=PARODD;
         
偶校驗:        Opt.c_cflag |=PARENB;
         
                     Opt.c_cflag &=~PARODD;
3.串口通信實例:
    1)mycom.h
/*本程序符合GPL條約
MyCom.h
一組操作串口的函數
*/

/*串口結構*/
typedef struct{
    char    prompt;        /*prompt after reciving data*/
    int     baudrate;        /*baudrate*/
    char    databit;        /*data bits, 5, 6, 7, 8*/
    char     debug;            /*debug mode, 0: none, 1: debug*/
    char     echo;            /*echo mode, 0: none, 1: echo*/
    char    fctl;            /*flow control, 0: none, 1: hardware, 2: software*/
    char     tty;            /*tty: 0, 1, 2, 3, 4, 5, 6, 7*/
    char    parity;        /*parity 0: none, 1: odd, 2: even*/
    char    stopbit;        /*stop bits, 1, 2*/
    const int reserved;        /*reserved, must be zero*/
}portinfo_t;
typedef portinfo_t *pportinfo_t;

/*
 *    打開串口,返回文件描述符
 *    pportinfo: 待設置的串口信息
*/
int PortOpen(pportinfo_t pportinfo);

/*
 *    設置串口
 *    fdcom: 串口文件描述符, pportinfo: 待設置的串口信息
*/
int PortSet(int fdcom, const pportinfo_t pportinfo);

/*
 *    關閉串口
 *    fdcom:串口文件描述符
*/
void PortClose(int fdcom);

/*
 *    發送數據
 *    fdcom:串口描述符, data:待發送數據, datalen:數據長度
 *    返回實際發送長度
*/
int PortSend(int fdcom, char *data, int datalen);

/*
 *    接收數據
 *    fdcom:串口描述符, data:接收緩衝區, datalen.:接收長度, baudrate:波特率
 *    返回實際讀入的長度
*/
int PortRecv(int fdcom, char *data, int datalen, int baudrate);

    2)mycom.c
/*本程序符合GPL條約
MyCom.c
*/
#include <stdio.h>              /*printf*/
#include <fcntl.h>              /* open  */
#include <string.h>             /* bzero */
#include <stdlib.h>             /*exit   */
#include <sys/times.h>          /* times*/
#include <sys/types.h>          /* pid_t*/
#include <termios.h>        /*termios, tcgetattr(), tcsetattr()*/
#include <unistd.h>
#include <sys/ioctl.h>          /* ioctl*/
#include "MyCom.h"
 
#define    TTY_DEV    "/dev/ttyS"    /*端口路徑*/
#define TIMEOUT_SEC(buflen,baud) (buflen*20/baud+2)  /*接收超時*/
#define TIMEOUT_USEC 0
/*******************************************
 *    獲得端口名稱
********************************************/
char *get_ptty(pportinfo_t pportinfo)
{
    char *ptty;
 
    switch(pportinfo->tty){
        case '0':{
            ptty = TTY_DEV"0";
        }break;
        case '1':{
            ptty = TTY_DEV"1";
        }break;
        case '2':{
            ptty = TTY_DEV"2";
        }break;
    }
    return(ptty);
}
 
/*******************************************
 *    波特率轉換函數(請確認是否正確)
********************************************/
int convbaud(unsigned long int baudrate)
{
    switch(baudrate){
        case 2400:
            return B2400;
        case 4800:
            return B4800;
        case 9600:
            return B9600;
        case 19200:
            return B19200;
        case 38400:
            return B38400;
        case 57600:
            return B57600;
        case 115200:
            return B115200;
        default:
            return B9600;
    }
}
 
/*******************************************
 *    Setup comm attr
 *    fdcom: 串口文件描述符,pportinfo: 待設置的端口信息(請確認)
 *
********************************************/
int PortSet(int fdcom, const pportinfo_t pportinfo)
{
    struct termios termios_old, termios_new;
    int     baudrate, tmp;
    char    databit, stopbit, parity, fctl;
 
    bzero(&termios_old, sizeof(termios_old));
    bzero(&termios_new, sizeof(termios_new));
    cfmakeraw(&termios_new);
    tcgetattr(fdcom, &termios_old);            /*get the serial port attributions*/
    /*------------設置端口屬性----------------*/
    /*baudrates*/
    baudrate = convbaud(pportinfo -> baudrate);
    cfsetispeed(&termios_new, baudrate);        /*填入串口輸入端的波特率*/
    cfsetospeed(&termios_new, baudrate);        /*填入串口輸出端的波特率*/
    termios_new.c_cflag |= CLOCAL;            /*控制模式,保證程序不會成爲端口的佔有者*/
    termios_new.c_cflag |= CREAD;            /*控制模式,使能端口讀取輸入的數據*/
 
    /* 控制模式,flow control*/
    fctl = pportinfo-> fctl;
    switch(fctl){
        case '0':{
            termios_new.c_cflag &= ~CRTSCTS;        /*no flow control*/
        }break;
        case '1':{
            termios_new.c_cflag |= CRTSCTS;            /*hardware flow control*/
        }break;
        case '2':{
            termios_new.c_iflag |= IXON | IXOFF |IXANY;    /*software flow control*/
        }break;
    }
 
    /*控制模式,data bits*/
    termios_new.c_cflag &= ~CSIZE;        /*控制模式,屏蔽字符大小位*/
    databit = pportinfo -> databit;
    switch(databit){
        case '5':
            termios_new.c_cflag |= CS5;
        case '6':
            termios_new.c_cflag |= CS6;
        case '7':
            termios_new.c_cflag |= CS7;
        default:
            termios_new.c_cflag |= CS8;
    }
 
    /*控制模式 parity check*/
    parity = pportinfo -> parity;
    switch(parity){
        case '0':{
            termios_new.c_cflag &= ~PARENB;        /*no parity check*/
        }break;
        case '1':{
            termios_new.c_cflag |= PARENB;        /*odd check*/
            termios_new.c_cflag &= ~PARODD;
        }break;
        case '2':{
            termios_new.c_cflag |= PARENB;        /*even check*/
            termios_new.c_cflag |= PARODD;
        }break;
    }
 
    /*控制模式,stop bits*/
    stopbit = pportinfo -> stopbit;
    if(stopbit == '2'){
        termios_new.c_cflag |= CSTOPB;    /*2 stop bits*/
    }
    else{
        termios_new.c_cflag &= ~CSTOPB;    /*1 stop bits*/
    }
 
    /*other attributions default*/
    termios_new.c_oflag &= ~OPOST;            /*輸出模式,原始數據輸出*/
    termios_new.c_cc[VMIN]  = 1;            /*控制字符, 所要讀取字符的最小數量*/
    termios_new.c_cc[VTIME] = 1;        /*控制字符, 讀取第一個字符的等待時間    unit: (1/10)second*/
 
    tcflush(fdcom, TCIFLUSH);                /*溢出的數據可以接收,但不讀*/
    tmp = tcsetattr(fdcom, TCSANOW, &termios_new);    /*設置新屬性,TCSANOW:所有改變立即生效*/    tcgetattr(fdcom, &termios_old);
    return(tmp);
}
 
/*******************************************
 *    Open serial port
 *    tty: 端口號 ttyS0, ttyS1, ....
 *    返回值爲串口文件描述符
********************************************/
int PortOpen(pportinfo_t pportinfo)
{
    int fdcom;    /*串口文件描述符*/
    char *ptty;
 
    ptty = get_ptty(pportinfo);
    /*fdcom = open(ptty, O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY);*/
    fdcom = open(ptty, O_RDWR | O_NOCTTY | O_NONBLOCK);
 
    return (fdcom);
}
 
/*******************************************
 *    Close serial port
********************************************/
void PortClose(int fdcom)
{
    close(fdcom);
}
 
/********************************************
 *    send data
 *    fdcom: 串口描述符,data: 待發送數據,datalen: 數據長度
 *    返回實際發送長度
*********************************************/
int PortSend(int fdcom, char *data, int datalen)
{
    int len = 0;
 
    len = write(fdcom, data, datalen);    /*實際寫入的長度*/
    if(len == datalen){
        return (len);
    }
    else{
        tcflush(fdcom, TCOFLUSH);
        return -1;
    }
}
 
/*******************************************
 *    receive data
 *    返回實際讀入的字節數
 *
********************************************/
int PortRecv(int fdcom, char *data, int datalen, int baudrate)
{
    int readlen, fs_sel;
    fd_set    fs_read;
    struct timeval tv_timeout;
 
    FD_ZERO(&fs_read);
    FD_SET(fdcom, &fs_read);
    tv_timeout.tv_sec = TIMEOUT_SEC(datalen, baudrate);
    tv_timeout.tv_usec = TIMEOUT_USEC;
 
    fs_sel = select(fdcom+1, &fs_read, NULL, NULL, &tv_timeout);
    if(fs_sel){
        readlen = read(fdcom, data, datalen);
        return(readlen);
    }
    else{
        return(-1);
    }
 
    return (readlen);
}
 
/**************************Test*********************************/
int main(int argc, char *argv[])
{
    int fdcom, i, SendLen, RecvLen;
    struct termios termios_cur;
    char RecvBuf[10];
    portinfo_t portinfo ={
        '0',                              /* print prompt after receiving*/
         115200,                          /* baudrate: 9600*/
         '8',                              /* databit: 8*/
         '0',                              /* debug: off*/
         '0',                              /* echo: off*/
         '2',                              /* flow control: software*/
         '0',                              /* default tty: COM1*/
         '0',                              /* parity: none*/
         '1',                              /* stopbit: 1*/
          0                              /* reserved*/
    };
 
    if(argc != 2){
        printf("Usage: <type 0 -- send 1 -- receive>/n");
        printf("   eg:");
        printf("        MyPort 0");
        exit(-1);
    }
 
    fdcom = PortOpen(&portinfo);
    if(fdcom<0){
        printf("Error: open serial port error./n");
        exit(1);
    }
 
    PortSet(fdcom, &portinfo);
 
    if(atoi(argv[1]) == 0){
        /*send data*/
        for(i=0; i<100; i++){
            SendLen = PortSend(fdcom, "1234567890", 10);
            if(SendLen>0){
                printf("No %d send %d data 1234567890./n", i, SendLen);
            }
            else{
                printf("Error: send failed./n");
            }
            sleep(1);
        }
        PortClose(fdcom);
    }
    else{
        for(;;){
            RecvLen = PortRecv(fdcom, RecvBuf, 10, portinfo.baudrate);
            if(RecvLen>0){
                for(i=0; i<RecvLen; i++){
                    printf("Receive data No %d is %x./n", i, RecvBuf[i]);
                }
                printf("Total frame length is %d./n", RecvLen);
            }
            else{
                printf("Error: receive error./n");
            }
            sleep(2);
        }
    }
    return 0;
}

4.串口設置詳解
      
更詳細的解釋在:
http://tech.ccidnet.com/art/321/20060414/506801_1.html
       或者參看:
   
參考資料

1.《Serial Programming Guide for POSIX Operating Systems》5th Edition Michael R.Sweet

2.《Linux 下串口編程入門》左錦

3.《Advanced Programming in the UNIX Environment》 W.Richard Stevens

4.《Linux Serial Programming HOWTO》

5.《Unix Systems Programming》Kay A.Robbins & Steven Robbins

6.《Linux Programming by Example》Arnold Robbins

        7.《Linux Programmer's Manual》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章