藍森林首頁 | 返回主頁 | 本站地圖 | 站內搜索 | 聯繫信箱 |
 您目前的位置:首頁 > 自由軟件 > 技術交流 > 應用編程


    

藍森林 http://www.lslnet.com 2006年6月6日 10:18


在SCO下開發串口通信程序

眾所周知,在UNIX環境下,所有的設備都以文件的形式出現。串口設備也不例外,它也可以在/dev目錄中找到相對應的文件。
一般在SCO OS安裝完成後,可以在/dev/目錄下找到以下一組文件:
crw-------   1 bin      terminal   5,  0 Jul 17 22:36 tty1a
crw-rw-rw-   1 bin      bin        5,128 Jul  9 01:49 tty1A
crw-------   1 bin      terminal   5,  8 Jul 17 21:55 tty2a
crw-rw-rw-   1 bin      bin        5,136 Jul  9 01:49 tty2A
這些文件分別對應了系統的兩個串口。其中tty1a和tty1A對應的是串口一,tty2a和tty2A對應的是串口二。如果未能在/dev目錄下找到以上文件,請檢查當前機器是否有串口設備。如果有,請聯繫SCO安裝人員重新進行串口設備的配置,此內容不在本文描述範圍內,在此不再贅述。
每個串口設備有兩個文件分別對應,其中tty?a對應的是普通串口設備,tty?A對應的是連接modem的串口設備。本文所述範圍只限於普通串口設備,對連接modem的串口設備不作討論。讀者若需要開發連接modem的串口設備程序,請查閱其它的相關資料。
以下列出在其它常用系統上的串口設備對應文件,以供參考。
串口設備文件
操作系統        串口一        串口二
IRIX&        /dev/ttyf1        /dev/ttyf2
HP-UX        /dev/tty1p0        /dev/tty2p0
Solaris&/SunOS&        /dev/ttya        /dev/ttyb
Linux&        /dev/ttyS0        /dev/ttyS1
Digital UNIX&        /dev/tty01        /dev/tty02
SCO OS5        /dev/tty1a        /dev/tty2a

可以看到,以上的設備文件對普通用戶不開放讀寫權限。所以以普通用戶登錄的用戶在運行對串口設備操作的程序時可能被拒絕。為了解決為個問題。一般來說有以下兩種方法。
一種方法是在程序中設置用戶標識,使用戶可以使用設備文件。
另一種方法是在程序運行之前設置設備文件的權限。
由於設備文件的權限放開並不會對系統的安全性造成太大的影響,所以一般我們都採用第二種方法來使普通用戶對設備文件有操作權限。
可以使用超級用戶運行以下命令來調整設備文件的權限:
chmod a+rw /dev/tty1a
chmod a+rw /dev/tty2a

要在程序中對串口設備操作,需要有相關頭文件的支持。一般需要以下的頭文件:
#include     <stdio.h>;      /* 標準輸入輸出定義         */
#include     <stdlib.h>;     /* 標準函數庫定義           */
#include     <unistd.h>;     /* Unix 標準函數定義        */
#include     <sys/types.h>;  
#include     <sys/stat.h>;   
#include     <fcntl.h>;      /* 文件控制定義                */
#include     <termios.h>;    /* POSIX 終端控制定義 */
#include     <errno.h>;      /* 錯誤號定義                */

在對串口操作之前,首先需要設置串口的各種屬性。最基本的串口設置包括設置串口的波特率,校驗位和停止位。

對串口屬性的操作通過以下結構體來設置:
struct termio
{        unsigned short  c_iflag;                /* 輸入模式標誌 */       
        unsigned short  c_oflag;                /* 輸出模式標誌 */       
        unsigned short  c_cflag;                /* 控制模式標誌*/       
        unsigned short  c_lflag;                /* local mode flags */       
        unsigned char  c_line;                    /* line discipline */       
        unsigned char  c_cc[NCC];            /* control characters */
};
串口的設置主要就是設置該結構體中各元素的值。

c_cflag用於設置控制選項,可以通過它來設置波特率,字長,校驗位以及其它內容。在POSIX標準中,對c_cflag可以有以下取值。
注意:以下取值的絕大部分都可以在SCO OS上使用。
Table 4 - Constants for the c_cflag Member
Constant        Description
CBAUD        Bit mask for baud rate
B0        0 baud (drop DTR)
B50        50 baud
B75        75 baud
B110        110 baud
B134        134.5 baud
B150        150 baud
B200        200 baud
B300        300 baud
B600        600 baud
B1200        1200 baud
B1800        1800 baud
B2400        2400 baud
B4800        4800 baud
B9600        9600 baud
B19200        19200 baud
B38400        38400 baud
B57600        57,600 baud
B76800        76,800 baud
B115200        115,200 baud
EXTA        External rate clock
EXTB        External rate clock
CSIZE        Bit mask for data bits
CS5        5 data bits
CS6        6 data bits
CS7        7 data bits
CS8        8 data bits
CSTOPB        2 stop bits (1 otherwise)
CREAD        Enable receiver
PARENB        Enable parity bit
PARODD        Use odd parity instead of even
HUPCL        Hangup (drop DTR) on last close
CLOCAL        Local line - do not change "owner" of port
LOBLK        Block job control output
CNEW_RTSCTS
CRTSCTS        Enable hardware flow control (not supported on all platforms)
注意:
1.        對於c_cflag的取值,其中的CLOCAL和CREAD永遠有效
2.        其中波特率的取值是為了與早期沒有c_ispeed和c_ospeed的接口兼容。在SCO OS 5.0.5的操作系統上,沒有c_ispeed和c_ospeed這兩個元素,對串口波特率的設置需要通過c_cflag來進行
3.        對c_cflag的賦值,可以使用按位運算邏輯運算符

設置波特率一般通過以下的代碼段來進行,由於比較簡單,所以就不作過多解釋了。
struct termios options;

/*
* Get the current options for the serial port...
*/

tcgetattr(fd, &options);

/*
* Set the baud rates to 19200...
*/

cfsetispeed(&options, B19200);
cfsetospeed(&options, B19200);

/*
* Enable the receiver and set local mode...
*/

options.c_cflag |= (CLOCAL | CREAD);

/*
* Set the new options for the port...
*/

tcsetattr(fd, TCSANOW, &options);
        對於tcsetattr()函數的第二個參數,有以下取值:
Constants for tcsetattr
Constant        Description
TCSANOW        Make changes now without waiting for data to complete
TCSADRAIN        Wait until everything has been transmitted
TCSAFLUSH        Flush input and output buffers and make the change


設置字長也是通過c_cflag來完成的。方法如下:
options.c_cflag &= ~CSIZE; /* Mask the character size bits */
options.c_cflag |= CS8;    /* Select 8 data bits */

校驗位的設置與字長的設置通常是一起實現的,以下是各種校驗位的設置方法:
No parity (8N1):
options.c_cflag &= ~PARENB
options.c_cflag &= ~CSTOPB
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
Even parity (7E1):
options.c_cflag |= PARENB
options.c_cflag &= ~PARODD
options.c_cflag &= ~CSTOPB
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS7;
Odd parity (7O1):
options.c_cflag |= PARENB
options.c_cflag |= PARODD
options.c_cflag &= ~CSTOPB
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS7;
Space parity is setup the same as no parity (7S1):
options.c_cflag &= ~PARENB
options.c_cflag &= ~CSTOPB
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;

c_lflag用於控制輸入的字符如何進行解析。通常我們可以使用兩種方法對輸入字符進行解析,規範的或原始的。
Constants for the c_lflag Member
Constant        Description
ISIG        Enable SIGINTR, SIGSUSP, SIGDSUSP, and SIGQUIT signals
ICANON        Enable canonical input (else raw)
XCASE        Map uppercase \lowercase (obsolete)
ECHO        Enable echoing of input characters
ECHOE        Echo erase character as BS-SP-BS
ECHOK        Echo NL after kill character
ECHONL        Echo NL
NOFLSH        Disable flushing of input buffers after interrupt or quit characters
IEXTEN        Enable extended functions
ECHOCTL        Echo control characters as ^char and delete as ~?
ECHOPRT        Echo erased character as character erased
ECHOKE        BS-SP-BS entire line on line kill
FLUSHO        Output being flushed
PENDIN        Retype pending input at next read or input char
TOSTOP        Send SIGTTOU for background output

在規範字符輸入方式下,輸入的字符被緩存在緩衝區內,用戶可對其進行編輯,直到輸入回車或換行後,才發送輸入的字符。通過以下方法將串口設置為規範字符輸入狀態:
options.c_lflag |= (ICANON | ECHO | ECHOE);


原始字符輸入方式直接發送收到的每個字符。通過以下方法將串口設置為原始字符輸入狀態:
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

不要在串口與modem或其它計算機連接的時候設置以上兩個屬性,將會出現一個字符回顯的無限循環。

在任何對串口屬性的設置之前,都必須要使用該函數來首先獲得串口的屬性,再在此基礎上進行設置。
注意:除了使用該函數外,還可以使用ioctl和fcntl函數來完成同樣的工作。而在POSIX標準中已經規定了tcgetattr來專門完成取串口屬性的功能,所以我們在此就不再使用ioctl和fcntl來做了。

函數原型:
int tcgetattr(int fd, struct termios* options);
參數說明:
        fd : 調用open()後得到的文件描述符
        options : 用於存放串口屬性的結構體,必須已經分配空間
返回值:
        成功 : 0
        失敗 : -1

對串口屬性結構體進行了設置之後,還需要調用tcsetattr來應用這些設置。
函數原型:
        int        tcsetattr(int fd, int actions, struct termios* options);
參數說明:
        fd : 調用open()後得到的文件描述符
        actions : 參見2.2.2 設置波特率一節
        options : 用於存放串口屬性的結構體,必須已經分配空間
返回值:
        成功 : 0
        失敗 : -1

獲取輸出波特率:
speed_t cfgetospeed(const struct termios *termios_p);
        設置輸出波特率:
int cfsetospeed(struct termios *termios_p, speed_t speed);
        獲取輸入波特率:
speed_t cfgetispeed(const struct termios *termios_p);
        設置輸入波特率:
int cfsetispeed(struct termios *termios_p, speed_t speed);

以上函數不再作詳細描述。在使用時可查看系統中的相關幫助。另外可以參見2.2.2 設置波特率一節。

串口的打開和關閉就是打開串口對應的設備文件,首先確定串口對應的設備,然後使用open()來打開串口,close()來關閉串口。

經過以上的配置之後,對於串口的讀寫就非常簡單了。在使用時,完全可以將串口當作一個普通文件來對待。
對於讀串口,可以使用read()系統調用來完成。相應的,對於寫串口,可以調用write()系統調用來完成。
1        應用舉例
下面的程序用於操作連接在串口上的密碼小鍵盤和刷卡器。在SCO OS5.0.5操作系統上編譯通過並且調試通過。
#include <stdio.h>;
#include <stdlib.h>;
#include <string.h>;
#include <unistd.h>;
#include <sys/types.h>;
#include <sys/stat.h>;
#include <fcntl.h>;
#include <termios.h>;
#include <errno.h>;
#include <curses.h>;

#define LIGHT                 "\x1b]"
#define SNDHOST                "\x1bG"
#define SELK                   "\x1b%K"
#define ALL                 "\x80"        /*二燈亮*/
#define RED                 "\x81"        /*紅燈亮*/
#define GREEN                 "\x82"        /*綠燈亮*/
#define DARK                 "\x83"        /*二燈滅*/

#define GETSTAT        "\x1bj"
#define READ_2        "\x1b]"
#define READ_3        "\x1bT]"
#define READ_B        "\x1b\x42]"
#define BMODE3        "\x1bS"
#define OPEN        "\x1b%B"
#define CLOSE        "\x1b%R"
#define QUITKEY        0x7f

/* 根據串口設備的序號打開對應的串口設備 */
static        int        OpenSerialPort(int serial)
{
        int        fd;
        char        serialPort[100];

        /* 注意 : 以下內容只能用於SCO OS5
        **        其它操作系統中的串口設備名稱不盡相同
        **        請查看相關的其它資料
        **/
        sprintf(serialPort, "/dev/tty%da", serial);
        fprintf(stderr, "讀串口密碼小鍵盤時串口設備名稱[%s]", serialPort);

        fd = open(serialPort, O_RDWR | O_NOCTTY | O_NDELAY);
        if (fd == -1) {
                fprintf(stderr, "打開串口設備[%s]出錯:[%d][%s]", serialPort,
                        errno, strerror(errno));
                return        -1;
        }

        /* 設置該端口為讀堵塞狀態 */
        fcntl(fd, F_SETFL, 0);

        return        fd;
}

/* 設置 JKP512 密碼鍵盤的屬性
**        該密碼鍵盤屬性如下:
**        1. 數據位        8 bit
**        2. 校驗位        無
**        3. 停止位        1 bit
**        4. 波特率        1200bps
**/
static        int        SetSerialPinpadJKP512(int fd)
{
        int        ret;
        struct        termios        options;
       
        /* 獲取當前串口屬性 */
        memset(&options, 0, sizeof(options));
        ret = tcgetattr(fd, &options);
        if (ret != 0) {
                fprintf(stderr, "獲取當前串口屬性出錯[%d][%s]", errno,
                        strerror(errno));
                return        -1;
        }

        cfsetispeed(&options, B1200);
        cfsetospeed(&options, B1200);

        /* 設置為接收狀態 */
        options.c_cflag |= (CLOCAL | CREAD);

        /* 設置為8N1狀態 */
        options.c_cflag &= ~PARENB;
        options.c_cflag &= ~CSTOPB;
        options.c_cflag &= ~CSIZE;
        options.c_cflag |= CS8;

        options.c_cc[VMIN]  = 1;
        options.c_cc[VTIME] = 0;
        options.c_cc[VQUIT] = QUITKEY;
       
        /* 應用設置 */
        ret = tcsetattr(fd, TCSANOW, &options);
        if (ret != 0) {
                fprintf(stderr, "設置當前串口屬性出錯[%d][%s]", errno,
                        strerror(errno));
                return        -1;
        }

        return        0;
}

/* 設置 MSRE 刷卡器的屬性
**        該刷卡器屬性如下:
**        1. 波特率        9600bps 4800bps 2400bps 1200bps
**/
static        int        SetSerialCardMSRE(int fd)
{
        int        ret;
        struct        termios        options;
       
        /* 獲取當前串口屬性 */
        memset(&options, 0, sizeof(options));
        ret = tcgetattr(fd, &options);
        if (ret != 0) {
                fprintf(stderr, "獲取當前串口屬性出錯[%d][%s]", errno,
                        strerror(errno));
                return        -1;
        }

        cfsetispeed(&options, B9600);
        cfsetospeed(&options, B9600);

        /* 設置為接收狀態 */
        options.c_cflag |= (CLOCAL | CREAD);

        /* 設置為8N1狀態 */
        options.c_cflag &= ~PARENB;
        options.c_cflag &= ~CSTOPB;
        options.c_cflag &= ~CSIZE;
        options.c_cflag |= CS8;

        /* 設置為原始字符輸入方式 */
        options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

        options.c_cc[VMIN]  = 1;
        options.c_cc[VTIME] = 0;
        options.c_cc[VQUIT] = QUITKEY;
       
        /* 應用設置 */
        ret = tcsetattr(fd, TCSANOW, &options);
        if (ret != 0) {
                fprintf(stderr, "設置當前串口屬性出錯[%d][%s]", errno,
                        strerror(errno));
                return        -1;
        }

        return        0;
}

static        void        sendcmd(int        fd, char*        s)
{
        write(fd, s,            strlen(s));
}

/* 讀取連接在串口上的密碼小鍵盤 */
int        ReadSerialPinpad(int        serial)
{
        int        i;
        int        fd;
        int        ret;
        char        temp[100];

        /* 打開輔口 */
        fd = OpenSerialPort(serial);
        if (fd == -1) {
                return        fd;
        }

        /* 設置輔口屬性 */
        ret = SetSerialPinpadJKP512(fd);
        if (ret != 0) {
                return        ret;
        }

        sendcmd(fd, GREEN);

        /* 接收按鍵 */
        i = 0;
        while (read(fd, temp, 1) == 1) {
                fprintf(stderr, "[%3d]ch = [%02x]", ++i, *temp);
                if ((unsigned char)(*temp) == 0x0d) {
                        break;
                }
        }

        sendcmd(fd, RED);

        i = 0;
        while (read(fd, temp, 1) == 1) {
                fprintf(stderr, "[%3d]ch = [%02x]", ++i, *temp);
                if ((unsigned char)(*temp) == 0x0d) {
                        break;
                }
        }

        sendcmd(fd, DARK);

        /* 關閉串口 */
        close(fd);

        return        0;
}

/* 打開刷卡器,準備接受刷卡輸入 */
int        ReadSerialCard(int serial)
{
        int        i;
        int        fd;
        int        ret;
        char        temp[100];

        /* 打開輔口 */
        fd = OpenSerialPort(serial);
        if (fd == -1) {
                return        fd;
        }

        /* 設置輔口屬性 */
        ret = SetSerialCardMSRE(fd);
        if (ret != 0) {
                return        ret;
        }

        sendcmd(fd, OPEN);
        sendcmd(fd, READ_B);

        /* 讀卡內容 */
        while (read(fd, temp, 1) == 1) {
                fprintf(stderr, "%c", *temp);
               
                if (*temp == '\x1c') {
                        break;
                }
        }

        /* 關閉串口 */
        close(fd);

        return        0;
}


int        main(void)
{
        /* 操作連接在串口一的密碼小鍵盤 */
        ReadSerialPinpad(1);

        /* 操作連接在串口二的刷卡器 */
        ReadSerialCard(2);

        return        0;
}

/* End of this File */



Copyright © 1999-2000 LSLNET.COM. All rights reserved. 藍森林網站 版權所有。 E-mail : webmaster@lslnet.com