여기의 모든 예제는 miniterm.c에서 따왔다. Canonical 입력 처리에서
처리할 수 있는 최대 길이의 문자는 255개(<linux/limits.h>
혹은 <posix1_lim.h>에 정의됨) 로서 버퍼의 최대 길이는
255로 제한된다.
여러 입력 처리 모드의 사용법에 대한 설명을 원하면 코드 내의 comment를 참조하라. 코드가 이해하기 쉽기를 바란다. Canonical 입력 처리 모드의 예제는 comment를 가장 잘 해놓았다. 다른 예제는 canonical 모드 예제와 다른 부분에만 comment를 달았다.
설명이 완벽하진 않지만, 이 예제로 직접 테스트를 해보면 당신의 프로그램에 적용할 때 최적의 방법을 찾을 수 있을 것이다.
시리얼 포트 장치 파일의 선택을 제대로 했는가를 다시 한 번 확인하고, 파일 접근
허가는 제대로 되어 있는지 보기를 바란다.
(예: chmod a+rw /dev/ttyS1)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>
/* Baudrate 설정은 <asm/termbits.h>에 정의되어 있다.
/* <asm/termbits.h>는 <termios.h>에서 include된다. */
#define BAUDRATE B38400
/* 여기의 포트 장치 파일을 바꾼다. COM1="/dev/ttyS1, COM2="/dev/ttyS2 */
#define MODEMDEVICE "/dev/ttyS1"
#define _POSIX_SOURCE 1 /* POSIX 호환 소스 */
#define FALSE 0
#define TRUE 1
volatile int STOP=FALSE;
main()
{
int fd,c, res;
struct termios oldtio,newtio;
char buf[255];
/* 읽기/쓰기 모드로 모뎀 장치를 연다.(O_RDWR)
데이터 전송 시에 <CTRL>-C 문자가 오면 프로그램이 종료되지 않도록
하기 위해 controlling tty가 안되도록 한다.(O_NOCTTY)
*/
fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY );
if (fd <0) {perror(MODEMDEVICE); exit(-1); }
tcgetattr(fd,&oldtio); /* save current serial port settings */
bzero(&newtio, sizeof(newtio)); /* clear struct for new port settings */
/*
BAUDRATE: 전송 속도. cfsetispeed() 및 cfsetospeed() 함수로도 세팅 가능
CRTSCTS : 하드웨어 흐름 제어. (시리얼 케이블이 모든 핀에 연결되어 있는
경우만 사용하도록 한다. Serial-HOWTO의 7장을 참조할 것.)
CS8 : 8N1 (8bit, no parity, 1 stopbit)
CLOCAL : Local connection. 모뎀 제어를 하지 않는다.
CREAD : 문자 수신을 가능하게 한다.
*/
newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
/*
IGNPAR : Parity 에러가 있는 문자 바이트를 무시한다.
ICRNL : CR 문자를 NL 문자로 변환 처리한다. (이 설정을 안하면 다른
컴퓨터는 CR 문자를 한 줄의 종료문자로 인식하지 않을 수 있다.)
otherwise make device raw (no other input processing)
*/
newtio.c_iflag = IGNPAR | ICRNL;
/*
Raw output.
*/
newtio.c_oflag = 0;
ICANON : canonical 입력을 가능하게 한다.
disable all echo functionality, and don't send signals to calling program
*/
newtio.c_lflag = ICANON;
/*
모든 제어 문자들을 초기화한다.
디폴트 값은 <termios.h> 헤어 파일에서 찾을 수 있다. 여기 comment에도
추가로 달아놓았다.
*/
newtio.c_cc[VINTR] = 0; /* Ctrl-c */
newtio.c_cc[VQUIT] = 0; /* Ctrl-\ */
newtio.c_cc[VERASE] = 0; /* del */
newtio.c_cc[VKILL] = 0; /* @ */
newtio.c_cc[VEOF] = 4; /* Ctrl-d */
newtio.c_cc[VTIME] = 0; /* inter-character timer unused */
newtio.c_cc[VMIN] = 1; /* blocking read until 1 character arrives */
newtio.c_cc[VSWTC] = 0; /* '\0' */
newtio.c_cc[VSTART] = 0; /* Ctrl-q */
newtio.c_cc[VSTOP] = 0; /* Ctrl-s */
newtio.c_cc[VSUSP] = 0; /* Ctrl-z */
newtio.c_cc[VEOL] = 0; /* '\0' */
newtio.c_cc[VREPRINT] = 0; /* Ctrl-r */
newtio.c_cc[VDISCARD] = 0; /* Ctrl-u */
newtio.c_cc[VWERASE] = 0; /* Ctrl-w */
newtio.c_cc[VLNEXT] = 0; /* Ctrl-v */
newtio.c_cc[VEOL2] = 0; /* '\0' */
/*
이제 modem 라인을 초기화하고 포트 세팅을 마친다.
*/
tcflush(fd, TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio);
/*
터미널 세팅이 끝났고, 이제는 입력을 처리한다.
이 예제에서는 한 줄의 맨 첫 문자를 'z'로 했을 때 프로그램을
종료한다.
*/
while (STOP==FALSE) { /* 종료 조건(STOP==TRUE)가 될 때까지 루프 */
/* read()는 라인 종료 문자가 나올 때까지 255 문자를 넘어가더라도
block 된다. read 하고자 하는 문자 개수가 입력 가능한 문자 개수보다
적은 경우에는 또 한번의 read를 하여 나머지를 읽어낼 수 있다.
res는 read에 의해서 실제로 읽혀진 문자의 개수를 갖게 된다. */
res = read(fd,buf,255);
buf[res]=0; /* set end of string, so we can printf */
printf(":%s:%d\n", buf, res);
if (buf[0]=='z') STOP=TRUE;
}
/* restore the old port settings */
tcsetattr(fd,TCSANOW,&oldtio);
}
Non-Canonical 입력 처리 모드에서는 입력이 한 줄 단위로 처리되지 않는다.
erase, kill, delete 등의 입력 처리도 수행되지 않는다. 이 모드에서 설정하는
파라미터는 c_cc[VTIME]과 c_cc[VMIN] 두 가지이다.
c_cc[VTIME]은 타이머의 시간을 설정하고, c_cc[VMIN]은
read할 때 리턴되기 위한 최소의 문자 개수를 지정한다.
MIN은 read가 리턴되기 위한 최소한의 문자 개수.
TIME이 0이면 타이머는 사용되지 않는다.(무한대로
기다린다.)
TIME은 time-out 값으로 사용된다. Time-out 값은
TIME * 0.1 초이다. Time-out이 일어나기 전에
한 문자라도 들어오면 read는 리턴된다.
TIME은 time-out이 아닌 inter-character 타이머로 동작한다. 최소 MIN 개의 문자가 들어오거나 두 문자 사이의 시간이 TIME 값을 넘으면 리턴된다. 문자가 처음 들어올 때 타이머는 동작을 시작하고 이후 문자가 들어올 때마다 재시작된다.
read는 즉시 리턴된다. 현재 읽을 수 있는 문자의 개수나
요청한 문자 개수가 반환된다. Antonino씨에 의하면
read하기 전에 fcntl(fd, F_SETFL, FNDELAY); 를
호출하면 똑같은 결과를 얻을 수 있다.
newtio.c_cc[VTIME]과 newtio.c_cc[VMIN]을
수정하여 위 네 가지 방식을 테스트 할 수 있다.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>
#define BAUDRATE B38400
#define MODEMDEVICE "/dev/ttyS1"
#define _POSIX_SOURCE 1 /* POSIX compliant source */
#define FALSE 0
#define TRUE 1
volatile int STOP=FALSE;
main()
{
int fd,c, res;
struct termios oldtio,newtio;
char buf[255];
fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY );
if (fd <0) {perror(MODEMDEVICE); exit(-1); }
tcgetattr(fd,&oldtio); /* 현재 설정을 oldtio에 저장 */
bzero(&newtio, sizeof(newtio));
newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
newtio.c_iflag = IGNPAR;
newtio.c_oflag = 0;
/* set input mode (non-canonical, no echo,...) */
newtio.c_lflag = 0;
newtio.c_cc[VTIME] = 0; /* 문자 사이의 timer를 disable */
newtio.c_cc[VMIN] = 5; /* 최소 5 문자 받을 때까진 blocking */
tcflush(fd, TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio);
while (STOP==FALSE) { /* loop for input */
res = read(fd,buf,255); /* 최소 5 문자를 받으면 리턴 */
buf[res]=0; /* '\0' 종료 문자열(printf를 하기 위해) */
printf(":%s:%d\n", buf, res);
if (buf[0]=='z') STOP=TRUE;
}
tcsetattr(fd,TCSANOW,&oldtio);
}
#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <sys/types.h>
#define BAUDRATE B38400
#define MODEMDEVICE "/dev/ttyS1"
#define _POSIX_SOURCE 1 /* POSIX compliant source */
#define FALSE 0
#define TRUE 1
volatile int STOP=FALSE;
void signal_handler_IO (int status); /* signal handler 함수 정의 */
int wait_flag=TRUE; /* signal을 받지 않은 동안은 TRUE */
main()
{
int fd,c, res;
struct termios oldtio,newtio;
struct sigaction saio; /* signal action의 정의 */
char buf[255];
/* Non-blocking 모드로 시리얼 장치를 연다(read 함수 호출 후 즉각 리턴) */
fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd <0) {perror(MODEMDEVICE); exit(-1); }
/* install the signal handler before making the device asynchronous */
/* 장치를 비동기 모드로 만들기 전에 signal handler */
saio.sa_handler = signal_handler_IO;
saio.sa_mask = 0;
saio.sa_flags = 0;
saio.sa_restorer = NULL;
sigaction(SIGIO,&saio,NULL);
/* SIGIO signal을 받을 수 있도록 한다. */
fcntl(fd, F_SETOWN, getpid());
/* file descriptor를 비동기로 만든다. (manual page를 보면
O_APPEND 와 O_NONBLOCK만이 F_SETFL에 사용할 수 있다고 되어 있다.) */
fcntl(fd, F_SETFL, FASYNC);
tcgetattr(fd,&oldtio); /* save current port settings */
/* canonical 입력처리를 위한 포트 세팅 */
newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
newtio.c_iflag = IGNPAR | ICRNL;
newtio.c_oflag = 0;
newtio.c_lflag = ICANON;
newtio.c_cc[VMIN]=1;
newtio.c_cc[VTIME]=0;
tcflush(fd, TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio);
/* loop while waiting for input. normally we would do something
useful here */
while (STOP==FALSE) {
printf(".\n");usleep(100000);
/* after receiving SIGIO, wait_flag = FALSE, input is available
and can be read */
if (wait_flag==FALSE) {
res = read(fd,buf,255);
buf[res]=0;
printf(":%s:%d\n", buf, res);
if (res==1) STOP=TRUE; /* stop loop if only a CR was input */
wait_flag = TRUE; /* wait for new input */
}
}
/* restore old port settings */
tcsetattr(fd,TCSANOW,&oldtio);
}
/***************************************************************************
* signal handler. sets wait_flag to FALSE, to indicate above loop that *
* characters have been received. *
***************************************************************************/
void signal_handler_IO (int status)
{
printf("received SIGIO signal.\n");
wait_flag = FALSE;
}
이 섹션은 간략한 설명만을 하겠다. 어떻게 하는 지에 대한 간단한 힌트만을 주기 위한 것이므로 짧은 예제 코드만을 담았다. 이 방법은 시리얼 포트에만 적용되는 것이 아니라 file descriptor를 사용하는 모든 입출력에 사용할 수 있다.
select() 시스템 호출 함수와 해당하는 매크로 함수들은
fd_set을 사용한다. fd_set는 bit array로서
file descriptor의 bit entry로 작용한다.
select()는 해당하는 file descriptor의 bit들을 세팅한
fd_set을 입력 파라미터로 받아서 입력, 출력 혹은
예외 발생이 일어난 경우 리턴한다. fd_set은
FD_로 시작하는 매크로 함수들을 통해서 관리한다.
select(2)의 man page를 참조하라.
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
main()
{
int fd1, fd2; /* input sources 1 and 2 */
fd_set readfs; /* file descriptor set */
int maxfd; /* maximum file desciptor used */
int loop=1; /* loop while TRUE */
/* open_input_source opens a device, sets the port correctly, and
returns a file descriptor */
fd1 = open_input_source("/dev/ttyS1"); /* COM2 */
if (fd1<0) exit(0);
fd2 = open_input_source("/dev/ttyS2"); /* COM3 */
if (fd2<0) exit(0);
maxfd = MAX (fd1, fd2)+1; /* maximum bit entry (fd) to test */
/* loop for input */
while (loop) {
FD_SET(fd1, &readfs); /* set testing for source 1 */
FD_SET(fd2, &readfs); /* set testing for source 2 */
/* block until input becomes available */
select(maxfd, &readfs, NULL, NULL, NULL);
if (FD_ISSET(fd1, &readfs)) /* input from source 1 available */
handle_input_from_source1();
if (FD_ISSET(fd2, &readfs)) /* input from source 2 available */
handle_input_from_source2();
}
}
위의 예제 코드는 입력이 들어올 때까지 계속 block 되는 동작을 보여준다. Time-out이 필요하다면, 다음과 같이 바꾼다.
int res;
struct timeval Timeout;
/* set timeout value within input loop */
Timeout.tv_usec = 0; /* milliseconds */
Timeout.tv_sec = 1; /* seconds */
res = select(maxfd, &readfs, NULL, NULL, &Timeout);
if (res==0)
/* number of file descriptors with input = 0, timeout occurred. */
이 예제는 1초 후에 time-out이 되는 것을 보여준다. Time-out이 일어나면
select()는 0을 반환한다. 여기서 주의할 점은,
설정한 Timeout 값은 select()에 의해서 감소하기 때문에
다시 select()를 호출한다면 Timeout.tv_usec과
Timeout.tv_sec 값을 다시 설정해야 한다.
Timeout 값이 0이 되면 time-out이 발생하고
select()는 즉시 리턴된다.