여기의 모든 예제는 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()
는 즉시 리턴된다.