리눅스를 처음 접하시는 분은 C 언어도 어느 정도 익힌 것 같고, 개발환경도 썩 마음에 들거나 몸에 익지 않지만 작업을 할 수 있는 상태가 되면, 오히려 공허해질 때가 있습니다. 이제 뭘 작성해 보나? 하는 것이죠. 회사에서 프로젝트가 떨어지면 힘들기는 해도 정신없이 학습하면서 프로젝트를 구성하면서 실력을 쌓아 가실 수 있습니다.

그러나 혼자 공부하시는 경우에는 뭘해야 할지 난감할 때가 있습니다. 이럴 때, 저는 통신과 그래픽을 권해 드립니다. 물론 이와 경중을 따질 수는 없지만 파일 처리나 디바이 드라이버 등 중요하고 반드시해야될 내용도 많습니다만 처음부터 이런 것을 잡으면 힘이 너무 듭니다. 그리고 일단 재미가 없습니다.

학습은 그래픽으로

통신은 이제 약방의 감초격입니다. 통신은 반드시 해야 합니다. 이렇게 중요한 통신을 문자열만 주고받는 단순한 프로그램으로는 실력을 배양하기가 너무 힘이 들고 재미도 없습니다. 그래픽으로 가상의 시스템을 구성하듯이 만들어 보시면, 그리고 점차 키보드와 마우스, 특히 터치스크린을 추가하면서 작업하시다 보면 매우 높은 교육 효과를 보실 수 있습니다.

MS 윈도우 프로그램

테스트를 위해 MS 윈도우 프로그램을 첨부합니다. (1) 이 프로그램을 실행하신 후 (2) 통신 포트 선택하고 (3) 게이지 버튼을 끌기를 하면 각 게이지의 갑에 맞추어 시리얼 포트로 자료를 계속 전송해 줍니다.

마우스로 클릭하거나 끌기를 할 때 아래의 포맷에 맞추어 게이지 값이 DLE코드를 사용한 프로토콜에 따라 자료가 전송됩니다. 예제가 복잡해 지는 것을 피하기 위해 CRC를 뺐습니다.

DLE 프로토콜에 대한 내용 강좌 게시판의 DLE 프로토콜 소개를 참고하여 주십시오.

프로그램에서는 게이지 값을 아래와 같이 전송합니다.

DLE STX DATA DLE ETX
게이지 번호 게이지 값
0x10 0x02 data 1 data 2 0x10 0x03

물론 게이지 값이 DLE코드인 0x10 이라면 0x10 0x10으로 중복해서 전달해 줍니다. 예제가 너무 복잡해 지는 것을 피하기 위해 상대로부터 응답 처리는 하지 않았습니다. 또한 게이지 값을 연속으로 변경해도 계속 보내는 것이 아니라 최소 50msec 정도 후에 전송하도록 했습니다.

예제 프로그램

아래의 예제 프로그램은 동영상에서 보여 드린 프로그램입니다. 소스 파일도 첨부했습니다만, 위에 데이터 전송 내용에 따라 직접 작성해 보시는 것을 권해 드리고 싶습니다.

다운로드예제 프로그램 다운로드: study_01.tar
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/poll.h>
#include <termios.h>                   // B115200, CS8 등 상수 정의
#include <fcntl.h>                     // O_RDWR , O_NOCTTY 등의 상수 정의
#include <gx.h>                        // 기본 그래픽 라이브러리

#define CODE_STX        0x02
#define CODE_ETX        0x03
#define CODE_DLE        0x10

#define WAIT_DLE        0              // DLE 코드를 만날 때 까지 대기
#define WAIT_STX        1              // STX 코드를 만날 때 까지 대기
#define RCV_DATA        2              // swRCV_DATA 자료 수신을 계속
#define CHK_DLE         3              // 데이터 수신 중 DLE 코드를 받음, 다음이 DLE 인지를 확인

unsigned char  rxBuff[1024];           // 자료 수신을 위한 버퍼
int            rxBranch;               // 시리얼로 입력된 자료를 분석하기 위한 분리자
int            rxTag;                  // 입력된 자료
Tdc           *dcScreen;

void  DrawBox( int value)
// 설명: 사각형을 출력
{
   // 이전에 그렸던 사각형을 지움
   gxPenColor  ( dcScreen, CLR_BLACK);
   gxBrushColor( dcScreen, CLR_BLACK);
   gxRectangle  ( dcScreen, 0, 400-255, 100, 400);

   // 새로 입력 받은 값에 따라 새롭게 사각형을 그림
   gxPenColor  ( dcScreen, gxColor( dcScreen, 255, 255, 255));
   gxBrushColor( dcScreen, gxColor( dcScreen, value, 0, 255));
   gxRectangle  ( dcScreen, 0, 400-value, 100, 400);
}

void  DrawCircle( int value)
// 설명: 원을 출력
{
   int   xCenter;
   int   yCenter;

   xCenter = dcScreen->Width  / 2;
   yCenter = dcScreen->Height /2;

   // 이전에 그렸던 원을 지움
   gxPenColor  ( dcScreen, CLR_BLACK);
   gxBrushColor( dcScreen, CLR_BLACK);
   gxCircle     ( dcScreen, xCenter, yCenter, 255);

   // 새로 입력 받은 값에 따라 새롭게 원을 그림
   gxPenColor  ( dcScreen, gxColor( dcScreen, 255, 255, 255));
   gxBrushColor( dcScreen, gxColor( dcScreen, value, value, 255));
   gxCircle     ( dcScreen, xCenter, yCenter, value);
}

void  OnReceiveData( unsigned char *buff)  // 자료를 수신했음
{
   int   mode;                         // 출력할 그랙픽 모드
   int   value;                        // 수신된 값

   mode  = *buff++;                    // 첫번째 바이트는 모드
   value = (int)*buff;                 // 두번째 바이트는 값

   switch( mode)
   {
   case  0  :  DrawBox( value);    break;
   default  :  DrawCircle( value); break;
   }
}

void  OnReceive( char *buff, int count)  // 프로토콜에 맞추어 자료 분리
{
   char  datum;

   while( count--)
   {
      datum = *buff++;
      switch( rxBranch)
      {
      case WAIT_DLE  :
                     switch( datum)
                     {
                     case CODE_DLE     :
                                       rxBranch = WAIT_STX;
                                       break;
                     }
                     break;
      case WAIT_STX  :
                     switch( datum)
                     {
                       case CODE_STX   :
                                       rxTag      = 0;
                                       rxBranch   = RCV_DATA;
                                       break;
                       default         :
                                       rxBranch   = WAIT_DLE;
                                       break;
                     }
                     break;
      case RCV_DATA  :
                     if ( 1024 < rxTag)           // 버퍼가 모두 찾다면 지금까지의 자료를 모두 무시하고,
                     {                             // 처음부터 다시 받도록 한다.
                        rxTag         = 0;
                        rxBranch      = WAIT_DLE; // 처음 수신 부분으로 점프
                        break;
                     }

                     switch( datum)
                     {
                        case CODE_DLE  :
                                       rxBranch = CHK_DLE;
                                       break;
                        default        :
                                       rxBuff[rxTag++] = datum;
                                       break;
                     }
                     break;
      case CHK_DLE   :
                     switch( datum)
                     {
                     case CODE_DLE     :
                                       rxBuff[rxTag++] = CODE_DLE;
                                       rxBranch   = RCV_DATA;
                                       break;
                     case CODE_ETX     :
                                       OnReceiveData( rxBuff);

                                       rxTag      = 0;
                                       rxBranch   = WAIT_DLE;
                                       break;
                     default           :                       // dle 코드 다음에
                                       rxTag      = 0;        // dle, etx 가 아니면
                                       rxBranch   = WAIT_DLE; // 수신 에러
                                       break;
                     }
                     break;
      }
   }
}

int main( void)
{
   int    fd;                       // 시리얼 포트를 위한 파일 디스크립터
   int    cnt;                      // 수신 자료 길이
   char   buf[1024];                // 수신 버퍼
   struct termios    NewTIO;        // 시리얼 통신 환경 설정
   struct pollfd     PollEvents;   // 체크할 event 정보를 갖는 struct
   int    PollState;                // poll 상태 값

   fd = open( "/dev/ttyS0", O_RDWR ¦ O_NOCTTY ¦ O_NONBLOCK );  // rs 포트 open
   if ( 0 > fd)
   {
      printf("open errorn");
      return -1;
   }

   memset( &NewTIO, 0, sizeof(NewTIO) );  // 시리얼 포트 통신 환경 설정
   NewTIO.c_cflag       = B115200 ¦ CS8 ¦ CLOCAL ¦ CREAD;
   NewTIO.c_iflag       = 0;              // NO_PARITY
   NewTIO.c_oflag       = 0;
   NewTIO.c_lflag       = 0;
   NewTIO.c_cc[VTIME]   = 0;
   NewTIO.c_cc[VMIN]    = 1;

   tcflush  (fd, TCIFLUSH );
   tcsetattr(fd, TCSANOW, &NewTIO );
   fcntl(fd, F_SETFL, FNDELAY);

   // poll 사용을 위한 준비

   PollEvents.fd       = fd;
   PollEvents.events   = POLLIN ¦ POLLERR;           // 수신된 자료가 있는지, 에러가 있는지
   PollEvents.revents  = 0;

   rxBranch            = WAIT_DLE;
   rxTag               = 0;

   // 그래픽 출력을 준비

   switch( gxInit( "/dev/fb") )
   {
      case  GXERR_NO_DEVICE      :  printf( "프레임 버퍼 장치가 없습니다.n"              ); return -1;
      case  GXERR_ACCESS_DEVICE  :  printf( "프레임 버퍼 장치 접근 권한이 없습니다.n"    ); return -1;
      case  GXERR_VSCREEN_INFO   :  printf( "FBIOGET_VSCREENINFO를 실행하지 못했습니다.n"); return -1;
      case  GXERR_FSCREEN_INFO   :  printf( "FBIOGET_FSCREENINFO를 실행하지 못했습니다.n"); return -1;
      case  GXERR_MEMORY_MAPPING :  printf( "메모리 메핑을 못했습니다.n"                 ); return -1;
   }

   if ( NULL == ( dcScreen  = gxScreenDc()))
   {
      printf( "스크린 dc를 얻지 못했습니다.n");
      return -1;
   }

   gxClear( dcScreen, CLR_BLACK);
   gxPenColor( dcScreen, gxColor( dcScreen, 255, 255, 255));

   while ( 1)
   {
      PollState = poll(                               // poll()을 호출하여 event 발생 여부 확인
                         (struct pollfd*)&PollEvents, // event 등록 변수
                                                   1,  // 체크할 pollfd 개수
                                                1000   // time out 시간
                       );

      if ( 0 < PollState)                             // 발생한 event 가 있음
      {
         if ( PollEvents.revents & POLLIN)            // event 가 자료 수신?
         {
            cnt = read( fd, buf, 1024);
            OnReceive( buf, cnt);
         }
         else if ( PollEvents.revents & POLLERR)      // event 가 에러?
         {
            printf( "통신 라인에 에러가 발생, 프로그램 종료");
            break;
         }
      }
   }
   gxReleaseDc( dcScreen);
   gxClose();
   close( fd);
   return 0;
}

예제 파일을 실행하시려면

  1. ]$ make dep    // make dep 를 실행하여 파일 연관 관계를 갱신
  2. ]$ make        // 실행 파일 생성
  3. ]$ ./sample    // 실행 파일 실행
  4. 리눅스 PC의 /dev/ttyS0 와 MS 윈도우의 PC를 서로 시리얼 케이블로 연결
  5. MS 윈도우에서 rsTester.exe 를 실행
  6. rsTester.exe에서 시리얼 포트를 선택
  7. 좌우 트랙바를 상/하로 이동하여 리눅스의 도형이 변경되는지 확인