1. Serial 프로그램예제 1.1. 개요 타겟보드자체에메인콘솔용으로사용되는통신포트이외에두개의다른포트를지원하고있다. 일반적으로메인콘솔용으로사용되는포트는통신용으로부적합하다. 왜냐하면커널에서발생되는메세지가메인콘솔로출력되기때문에통신에방해를받을수있기때문이다. 이런이유로타겟보드에는통신시험이나응용통신에이용할수있도록 MCU 의통신포트에 MAX2421 레벨변환기를추가하였다. 이장은이두포트중한포트를이용하여리눅스에서통신프로그램을어떻게작성해야하는가에대한간단한예제를들고있다. 좀더자세한시리얼프로그램기법에대해서는리눅스프로그램관련서적이나 KLDP 에한글화된문서를참조하기바란다.
1.2. 준비조건 다음그림과같이시리얼케이블을 PC 와연결한다. 주의 ) FA-232C 케이블은 EZ-X5 보드에포함되어있는것이아닙니다. J8 점퍼는초기에제공된상태로두시면됩니다.
1.3. 포트세팅 리눅스에서는터미널을연결하기위하여장치파일을이용한다. 장치파일은 /dev/ttysac* 이다. 하지만 S3C2410 의시리얼장치파일은이름이다르게명명된다. /dev/ttysac0, /dev/ttysac1, /dev/ttysac2 로명명되어있다. 이중에서 /dev/ttysac2 는콘솔용으로사용되고있다. 이장에서설명하는것은 /dev/ttysac0 을이용할경우로한정지을것이다. /dev/ttysac1 을사용해도문제는없다. 시리얼장치파일은터미널장치로분류된다. 따라서초기설정이에코가되도록설정되어있다. 데이터전송을목적으로한다면바꾸어주어야한다. 장치파일의설정에관한것은 <asm/termbits.h> 에정의되어있는 termios 구조체에저장되어있다. 구조체의내용은아래와같다. #define NCCS 19 struct termios tcflag_t c_iflag; /* input mode flags */ tcflag_t c_oflag; /* output mode flags */ tcflag_t c_cflag; /* control mode flags */ tcflag_t c_lflag; /* local mode flags */ cc_t c_line; /* line discipline */ cc_t c_cc[nccs]; /* control characters */ ; c_iflag 모든입력처리를정의한다. 입력처리란 read() 함수에의해시리얼포트로들어온데이터를 read 에의해읽기전에데이터들을 c_iflag 에정의한대로처리하는것을의미한다. c_oflag 출력처리하는방법을정의한다. c_cflag baudrate, data bits, stop bits 등의포트세팅을정의한다. c_lflag echo 를할것인지등을결정한다. c_cc 배열 EOF, STOP 등의제어동작들을어떤문자로정의할것인가를결정한다. 제어문자의디폴트문자는 <asm/termios.h> 에정의되어있다.
1.4. 시리얼장치의입력방법 Canonical 입력처리 ( Canonical Input Processing) Canonical 입력처리는터미널의기본처리방법이다. 이방법은한줄단위로처리하는다른프로그램과통신하는데에사용할수있다. 한줄은디폴트로 NL(New Line, ASCII 는 LF) 문자, EOF(End of File) 문자, 혹은 EOL(End Of Line) 에의해종료되는문자열을의미한다. CR(Carriage Return, DOS/Windows 의디폴트 EOL 문자임 ) 문자는디폴트세팅에서한줄의종료문자로인식되지않는다. 또한 Canonical 입력처리모드에서는 ERASE, DELETE WORD, REPRINT CHARACTERS 문자들을처리할수있고, CR 문자를 NL 문자로변환처리를할수있다. Non-Canonical 입력처리 (Non-Canonical Input Processing) Non-Canonical 입력처리모드에서는한번읽을때마다정해진크기의문자만을읽어낼수있다. 또한타이머를두어서일정시간까지 read() 가리턴하지않는경우강제리턴을할수있다. 이모드는항상정해진크기의문자들만을읽어내거나대량의문자들을전송하고자할때사용한다. 비동기입력위의두가지모드는동기방식이나비동기방식으로사용될수있다. 동기방식은 read 의조건이만족될때까지 block 되는방식으로서디폴트로설정되어있다. 동기방식은 read 의조건이만족될때까지 block 되는방식으로서디폴트로설정되어있다. 비동기방식에서는 read() 함수가바로리턴되며, 호출한프로그램에게 signal 을보낸다. 이 signal handler( 시그널처리함수 ) 로보내진다.
1.5. 프로그램동작없이출력동작시험 하이퍼터미널에서통신에뮬레이터의 PC 쪽환경을간단하게다음과같이맞춘다. 리눅스에서는터미널장치의기본설정은 9600Baud 이다. 보드에서다음같이입력했을때하이퍼터미널에동일한메세지가나온다면연결이정상적으로된것이다.
1.6. 프로그램동작설명및소스이예제는간단하게데이타를전송하고수신된데이타를표출하는예제다. 우선 Makefile의내용을살펴보자 [Makefile] # # APP Makefile # CROSS_COMPILE = arm-linux- CC LD = $(CROSS_COMPILE)gcc = $(CROSS_COMPILE)ld CFLAGS += -O LDFLAGS += TARGET = sap_serial OBJS = sample_serial.o SRCS = $(OBJS:.o=.c) all : $(TARGET) cp -f $(TARGET) /nfs/sap $(TARGET) : $(OBJS) $(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) -o $@ %.o:%.c @echo "Compiling $<..." $(CC) -c $(CFLAGS) -o $@ $< dep : $(CC) -M $(SRCS) >.depend clean : rm -rf $(OBJS) $(TARGET) core
다음은어플리케이션소스이다. [sample_serial.c] // 파일 : sample_serial.c // 설명 : 시리얼포트를사용하여데이타를전송하는예제이다. // // 작성 : #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/signal.h> #include <sys/ioctl.h> #include <sys/poll.h> #include <termios.h> // 설명 : 시리얼포트를연다. // 주의 : RTS/CTS 를제어하지않는다. // 시리얼포트를열고이전의포트설정상태를저장하지않았다. int open_serial( char *dev_name, int baud, int vtime, int vmin ) int fd; struct termios newtio; // 시리얼포트를연다. fd = open( dev_name, O_RDWR O_NOCTTY ); if ( fd < 0 ) // 화일열기실패 printf( "Device OPEN FAIL %s\n", dev_name ); return -1; // 시리얼포트환경을설정한다. memset(&newtio, 0, sizeof(newtio)); newtio.c_iflag = IGNPAR; // non-parity newtio.c_oflag = 0; newtio.c_cflag = CS8 CLOCAL CREAD; // NO-rts/cts
switch( baud ) case 115200 : newtio.c_cflag = B115200; break; case 57600 : newtio.c_cflag = B57600; break; case 38400 : newtio.c_cflag = B38400; break; case 19200 : newtio.c_cflag = B19200; break; case 9600 : newtio.c_cflag = B9600; break; case 4800 : newtio.c_cflag = B4800; break; case 2400 : newtio.c_cflag = B2400; break; default : newtio.c_cflag = B115200; break; //set input mode (non-canonical, no echo,...) newtio.c_lflag = 0; newtio.c_cc[vtime] = vtime; // timeout 0.1초단위 newtio.c_cc[vmin] = vmin; // 최소 n 문자받을때까진대기 tcflush ( fd, TCIFLUSH ); tcsetattr( fd, TCSANOW, &newtio ); return fd; // 설명 : 시리얼포트를닫는다. // 주의 : void close_serial( int fd ) close( fd ); // 설명 : main // 32바이트의데이타를보낸후데이타가들어오는지를 1초동안감시한다. // 데이타가일정시간 (1초) 동안없으면다시데이타를전송한다. // 주의 : int main( int argc, char **argv ) int fd; // 시리얼포트파일핸들 int baud; // 전송속도 char dev_name[128]; // 시리얼포트노드파일이름 char cc, buf[128]; // 데이타버퍼 int rdcnt; if ( argc!= 3 ) printf( " sample_serial [device] [baud]\n" \ " device : /dev/ttysac0...\n" \ " baud : 2400... 115200\n" ); return -1; printf( " Serial test start... (%s)\n", DATE );
// 인자를얻어온다. strcpy( dev_name, argv[1] ); // 노드파일이름 baud = strtoul( argv[2], NULL, 10 ); // 전송속도 // 시리얼포트를연다 // 시리얼포트를 1 초동안대기하거나 32 바이트이상의데이타가들어오면 // 깨어나도록설정한다. fd = open_serial( dev_name, baud, 10, 32 ); if ( fd < 0 ) return -2; for ( cc='a'; cc<='z'; cc++ ) // 32 바이트데이타를전송한다. memset( buf, cc, 32 ); write( fd, buf, 32 ); // 데이타를읽어온다. rdcnt = read( fd, buf, sizeof(buf) ); if ( rdcnt > 0 ) buf[rdcnt] = '\0'; printf( "<%s rd=%2d> %s\n", dev_name, rdcnt, buf ); // 테스트를위한지연 sleep(1); // 시리얼포트를닫는다. close_serial( fd ); printf( " Serial test end\n" ); return 0;
1.7. 프로그램소스분석 int fd; struct termios newtio; 시리얼포트의헨들과 termios 구조체를선언한다. termios 구조체는위에서설명하였다. fd = open( dev_name, O_RDWR O_NOCTTY ); dev_name 은 /dev/ttysac0 또는 /dev/ttysac1 이다. 읽기 / 쓰기모드로장치를연다.(O_RDWR) 데이터전송시에 <ctrl>-c 문자가오면프로그램이종료되지않도록하기위해 controlling tty 가안되도록한다.(O_NOCTTY) newtio.c_cflag = B9600 CS8 CLOCAL CREAD ; B9600 전송속도. cfsetispeed() 및 cfsetospeed() 함수로도세팅가능 CS8 8N1 (8bit, no parity, 1 stopbit) CLOCAL Local connection. 모뎀제어를하지않는다. CREAD 문자수신을가능하게한다. CRTSCTS 하드웨어흐름제어.( 시리얼케이블이모든핀에연결되어있는경우만사용 ) newtio.c_iflag = IGNPAR; IGNPAR Parity 에러가있는문자바이트를무시한다. ICRL CR 문자를 NL 문자로변환처리한다. ( 이설정을하지않으면다른컴퓨터는 CR 문자를한줄의종료문자로인식하지않을수있다.) newtio.c_lflag = 0; 0 으로설정하면 Non-Canonical 입력처리하게된다. Non-Canonical 입력처리모드에서는입력이한줄단위로처리되지않는다. 따라서, 이모드에서설정하는파라미터는 c_cc[vtime] 과 c_cc[vmin] 두가지이다. 이것은아래다시설명하였다. ICANON canonical 입력을가능하게한다.
newtio.c_cc[vtime] = 10; 최소 1 초이상수신이없으면타임아웃이걸린다. 이때 read 함수는 0 을반환한다. newtio.c_cc[vmin] = 32; VTIME 값이 0 일경우 read문이리턴되기위한최소의수신문자개수를지정한다. MIN > 0, TIME = 0 MIN 은 read 가리턴되기위한최소한의문자개수. TIME 이 0 이면타이머는사용되지않는다.( 무한대로기다린다.) MIN = 0, TIME > 0 TIME 은 time-out 값으로사용된다. Time-out 값은 TIME * 0.1 초이다. Time-out 이일어나기전에한문자라도들어오면 read 는리턴된다. MIN > 0, TIME > 0 TIME 은 time-out 이아닌 inter-character 타이머로동작한다. 최소 MIN 개의문자가들어오거나두문자사이의시간이 TIME 값을넘으면리턴된다. 문자가처음들어올때타이머는동작을시작하고이후문자가들어올때마다재시작된다. MIN = 0, TIME = 0 read 는즉시리턴된다. 현재읽을수있는문자의개수나요청한문자개수가반환된다. Antonino 씨에의하면 read 하기전에 fcntl(fd, F_SETFL, FNDELAY); 를호출하면똑같은결과를얻을수있다. 다음은 termios.h에있는디폴트값을나열한것이다. 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' */
tcflush(fd, TCIFLUSH ); 통신을수행하기이전에이전에아직전송되지않았거나수신처리가되어있지않은데이타를모두비워버린다. tcsetattr(fd, TCSANOW, &newtio ); 모뎀라인을초기화하고포트 setting 을마친다. write( fd, buf, strlen(buf) ); A ~Z 까지의 32 개의문자가통신포트를통해전송한다. 하이퍼터미널에서는이값이표시되어야한다. rdcnt = read( fd, buf, sizeof(buf) ); 수신된문자를표출한다. close(fd ); 이부분은위의프로그램에서는수행되지않으나일반적으로프로그램이종료되기전에수행되어프로그램이실행되기이전의포트상태로되돌리고사용하던포트를닫는다. [ 주의사항 ] newtio.c_cc[vtime] = 10; newtio.c_cc[vmin] = 32; 의두값을잘설정하여야한다. 예제프로그램에서는이두가지모드를보여주기위해서위와같이설정을사용하였지만실제프로그램에서는자신의통신프로토콜에맞게수정을해주어야한다.
1.8. 프로그램수행화면 다음은수행화면을보여준다. [ 보드화면 ] [PC 화면 ] PC 화면에서 Enter 키를누를때마다수신을한다. 즉, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 32 개의문자를받고, Enter 키를누르면다음 32 개의문자를받는다.