1. 개요

 

       이 문서는 시리얼 모듈을 루아에서 동작 시키기 위한 기능 중에서 

       시리얼 통신 기능을 지원하는 시리얼 COM PORT 객체에 대한

       구현 내용을 기록한 문서이다.


       이 문서에 대한 시험은 EZ-S3C2440 으로 하였다. 

       저번 시간에 이어서 실제 C 에서 어떻게 구현했는가에 대한 소스 설명을 다룬다.

 

10. 소스 코드

 

  10.1 시리얼 통신 객체 관리 구조체

 

      serial_com_t 구조체는 사용자 데이터 형으로 시리얼 통신을 구현하기 위한 구조체이다.

     
 typedef struct {
    char            port[128];
    int             baud;
    int             data;
    char            parity[16];
    int             fd;
    struct termios  old_tio;
    ssize_t         rw_size;
    unsigned char   r_data[32*1024];
} serial_com_t;


     이 구조체는 통신 디바이스 파일 정보, 설정 정보 및 프로그램 제어 정보와
     내부 수신 버퍼로 이루어 진다.
 
     디바이스 파일 정보는 다음 필드를 사용한다.
 

1. 개요

 

       이 문서는 시리얼 모듈을 루아에서 동작 시키기 위한 기능 중에서 

       시리얼 통신 기능을 지원하는 시리얼 COM PORT 객체에 대한

       구현 내용을 기록한 문서이다.


       이 문서에 대한 시험은 EZ-S3C2440 으로 하였다. 

       저번 시간에 이어서 실제 C 에서 어떻게 구현했는가에 대한 소스 설명을 다룬다.

 

10. 소스 코드

 

  10.1 시리얼 통신 객체 관리 구조체

 

      serial_com_t 구조체는 사용자 데이터 형으로 시리얼 통신을 구현하기 위한 구조체이다.

char            port[128];

 
     최대 128 자 까지 디바이스 파일명을 다룰 수 있도록 했다.
 
     통신 파라메터를 설정하기 위한 필드는 다음과 같다.
 
    
int     baud;           // 통신 속도
int     data;           // 데이터 비트 크기 
char    parity[16];     // 패리티

      baud 는 실제 통신 속도를 지정한다. 예를 들어 115200 이라면 baud = 115200 이다.
      data  는 통신 데이터 크기로 8비트라면 8을 지정하면 된다.
      parity 는 패리티 비트를 지정하는데 다음 값 중 하나로 설정된다.
 

          

"none"

: 패리티 비트를 사용하지 않는다.

"even"

: 짝수 패리티 비트를 사용 한다.

"char"

: 홀수 패리티 비트를 사용 한다.

 
      통신을 실제로 관리하기 위해서는 디바이스 파일 디스크립터 fd 를 사용하고
      사용 이전 설정 사항을 내부적으로 보관하기 위해서 old_tio 를 사용한다.
   
int             fd;         // 통신 처리 대상이 되는 디바이스의 파일 디스크립터 
struct termios  old_tio;    // 이전에 설정된 통신 디바이스의 설정 내용

       LUA 스크립트에서 수신 처리를 원활하게 하려면 내부적인 수신 버퍼가 필요하다.
 
       수신 최대 크기는 보통 32KByte 이상은 다룰 수 없으므로 최대 크기를 32 * 1024 로 잡았다.

ssize_t         rw_size;            // 읽거나 써 넣어진 크기 (현재는 읽은 크기만 저장함)
unsigned char   r_data[32*1024];    // 수신 버퍼

 

 

  10.2 제어 대상이 되는 시리얼 포트 객체 생성 지원 C 함수

 

     루아에서 시리얼 객체를 사용하려면 다음과 같이

     시리얼 통신 디바이스 제어용 객체를 생성해야 한다.


m   = require "serial"; -- 시리얼 모듈 생성
com = m.create();        -- 생성

 

     이 스크립트 문장은 다음과 같이 정의된 시리얼 객체 모듈 에서

     등록된 함수 serial_com_create() 를 호출 하게 된다.


static const struct luaL_Reg sm[] =
{
                :
    { "create"      , serial_com_create  },
                :
};

      이 함수는 serial_com.c 에 다음과 같이 정의되어 있다.

   
int serial_com_create( lua_State *L )
{
    serial_com_t *com;
      
    com = sc_new_userdata(L);
  
    com->fd = -1;                       // 아직 열려 있지 않다.
  
    strcpy( com->port,"/dev/ttyS1" );   // 디폴트 디바이스 파일명은 "/dev/ttyS1"
    com->baud = 115200;                 // 디폴트 통신 속도는 115200
    com->data = 8;                      // 데이터 비트는 8 비트
    strcpy( com->parity,"none" );       // 패리티 비트는 사용하지 않는다.
   
    return 1;
}

     이 함수는 sc_new_userdata() 함수를 이용하여 사용자 데이터를 생성하고

     디폴트 값을 넣는다.

 

      sc_new_userdata() 함수는 다음과 같이 정의되어 있다.

   
static serial_com_t *sc_new_userdata(lua_State *L)
{
    serial_com_t *com = (serial_com_t *) lua_newuserdata(L, sizeof(serial_com_t));
                              
    luaL_getmetatable(L, SERIAL_COM);
    lua_setmetatable(L, -2);
    return com;
}

 

     lua_newuserdata() 함수를 사용하여 serial_com_t 구조체를 다룰 수 있는 사용자 데이터를 할당하고

      SERIAL_COM 메타테이블을 연결한다.

 

      SERIAL_COM 메타 테이블은 다음과 같은 구성을 갖는다.

   
static const luaL_reg serial_com_meta[] = {
    {"__newindex"   , sc_newindex   },  // com 객체의 멤버 변수 대입에 대한 처리 
    {"__index"      , sc_index      },  // com 객체의 멤버 변수 값 참조
    {"__tostring"   , sc_tostring   },  // com 객체의 문자열 반환
    {"__gc"         , sc_gc         },  // com 객체의 소멸시 처리해야 할 루틴
    {"open"         , sc_open       },  // com 객체의 실질적인 사용을 위한 오픈 함수 
    {"close"        , sc_close      },  // com 객체의 사용 종료를 위한 클로즈 함수 
    {"read"         , sc_read       },  // com 객체에서 시리얼 데이터 읽기에 대한 처리 함수
    {"write"        , sc_write      },  // com 객체에 시리얼 데이터 쓰기에 대한 처리 함수 
    {0, 0}
};

      이 메타 테이블은 serial_com_new_metatable() 함수에 의해서 루아 스테이지에 등록된다.

      serial_com_new_metatable() 함수는  시리얼 모듈이 등록될때 호출되는 luaopen_serial() 에서 호출한다.

 

 

  10.3 메타 테이블 함수가 호출될때 사용자 데이터를 얻어 오는 C 함수

 

      메타 테이블에 지정된 함수가 호출되면 다음과 같이 serial_com_t 구조체 데이터 주소를 얻어 와야 한다. 

   
serial_com_t *com;
com = sc_get_data( L, 1 );

       sc_get_data() 함수는 다음과 같이 정의 되어 있다. 
   
static serial_com_t * sc_get_data(lua_State *L, int index)
{
    serial_com_t *com = (serial_com_t *) lua_touserdata(L, index);
    if (com == NULL) luaL_typerror(L, index, SERIAL_COM);
    return com;
}


     객체 멤버 변수가 참조되거나 함수가 호출되면 사용자 데이터는 스택 처음에 놓이기 되는데 

     이것을 얻어와 형변환 하여 반환한다.

 

 

  10.4 객체 멤버 변수 값 설정 처리

 

       com 객체는 통신 설정 변수로 다음과 같은 변수를 관리한다.

 

        

port

: 통신 디바이스 파일명

baud

: 통신 속도

data

: 통신 데이터 크기

parity

: 통신 피리티

 

      이 변수에 값을 설정하면 __newindex 메타 함수인 sc_newindex() 를 호출하게 된다.

      이 함수는 각각의 변수명을 구별하여 해당 변수에 따라서 루아 스택에서 전달된 값을

      가져와 serial_com_t 사용자 데이터에 설정한다.

 

      다음과 같이 sc_newindex()를 구현한다.

   
static int sc_newindex( lua_State *L )
{
    serial_com_t    *com;
    char            *var_str; 
 
    if( lua_type(L, 2) == LUA_TSTRING ) 
    {
    com  = sc_get_data ( L, 1 );
     var_str = (char * ) lua_tostring ( L, 2 ); 
    if( !strcmp( var_str, "port"    ) ) { return sc_set_port(L,com);    }
    if( !strcmp( var_str, "baud"    ) ) { return sc_set_baud(L,com);    }
    if( !strcmp( var_str, "data"    ) ) { return sc_set_data(L,com);    }
    if( !strcmp( var_str, "parity"  ) ) { return sc_set_parity(L,com);  }
    } 
    return 0;
}

 

  10.5 객체 멤버 변수 값 읽기와 함수 호출 사전 처리

 

      com 객체는 통신 설정 변수 값을 읽거나 함수를 호출하게 되면 __index 메타 함수인 sc_index() 를 호출하게 된다.

      sc_index() 함수는 설정 변수 이면 해당 값을 사용자 데이터에서 읽어와

      스택에 탑제하거나 함수 호출이면 사용자 데이터를 전역 변수 call_func_com 에 넣어

      이후에 메타 테이블에 정의된 함수가 호출되었을때 사용되도록 만든다.

 

      sc_index() 함수는 다음과 같이 구현되어 있다 .

   
static int sc_index( lua_State *L )
{
    serial_com_t    *com;
    char            *var_str; 
    if( lua_type(L, 2) == LUA_TSTRING ) 
    {
        com   = sc_get_data( L, 1 );
        var_str = (char *) lua_tostring( L, 2 );       
        if     ( !strcmp( var_str, "read"  ) ) { call_func_com = com; }
        else if( !strcmp( var_str, "write" ) ) { call_func_com = com; }
        else if( !strcmp( var_str, "open"  ) ) { call_func_com = com; }
        else if( !strcmp( var_str, "close" ) ) { call_func_com = com; }
        else
        { 
            if( !strcmp( var_str, "fd"      ) ) { lua_pushinteger( L, com->fd       ); return 1; }
            if( !strcmp( var_str, "port"    ) ) { lua_pushstring ( L, com->port     ); return 1; }
            if( !strcmp( var_str, "baud"    ) ) { lua_pushinteger( L, com->baud     ); return 1; }
            if( !strcmp( var_str, "data"    ) ) { lua_pushinteger( L, com->data     ); return 1; }
            if( !strcmp( var_str, "parity"  ) ) { lua_pushstring ( L, com->parity   ); return 1; }
        } 
        lua_getmetatable(L, 1);
        lua_replace(L, 1);
        lua_rawget(L, 1);
        return 1;
        }
    return 0;
}

 

10.6 open() 함수 구현

 

     com 객체는 환경 설정 변수가 설정된 상태에서 open() 함수를 호출하여 디바이스 드라이버를 실제로 연다.

 

     이 함수는 다음과 같이 각 변수에 따라서 디바이스 파일을 열고 시리얼 통신 제어용 파라메터를 설정한다.

 

  
static int sc_open(lua_State *L)
{
    serial_com_t    *com;
    int             baud;
    struct termios  new_tio;
    com = call_func_com;
    if( com != NULL )
    { 
        if( com->fd >= 0 ) 
        {
             tcdrain(com->fd);
             close( com->fd );
        }
        com->fd = open( com->port, O_RDWR | O_NOCTTY | O_NONBLOCK );
        if( com->fd >= 0 )
        {
            tcgetattr( com->fd, &(com->old_tio) );
            memset( &new_tio, 0, sizeof(new_tio) );
            switch( com->baud )
            {
            case    1200 : baud = B1200     ; break;
            case    2400 :  baud = B2400    ; break;
            case    4800 :  baud = B4800    ; break;
            case    9600 :  baud = B9600    ; break;  
            case   19200 :  baud = B19200   ; break;
            case   38400 :  baud = B38400   ; break;
            case   57600 :  baud = B57600   ; break;
            case  115200 :  baud = B115200  ; break;
            case  230400 :  baud = B230400  ; break;
            case  460800 :  baud = B460800  ; break;
            case  500000 :  baud = B500000  ; break;
            case  576000 :  baud = B576000  ; break;
            case  921600 :  baud = B921600  ; break;
            case 1000000 :  baud = B1000000 ; break;
            default      :  baud = B115200  ; break; 
            }
    
            cfsetispeed(&new_tio, baud);
            cfsetospeed(&new_tio, baud);
            new_tio.c_cflag = baud;
            
            switch( com->data )
            {
            case 5 :  new_tio.c_cflag |= CS5 ; break;
            case 6 :  new_tio.c_cflag |= CS6 ; break;
            case 7 :  new_tio.c_cflag |= CS7 ; break;
            case 8 :  
            default :  new_tio.c_cflag |= CS8 ; break;
            } 
            if      ( !strcmp( com->parity,"even" ) ) new_tio.c_cflag  |= ( PARENB );
            else if ( !strcmp( com->parity,"odd"  ) ) new_tio.c_cflag  |= ( PARENB |PARODD ); 
            else
            {
             // none parity
            }  
    
            new_tio.c_cflag |= CLOCAL;   // 외부 모뎀을 사용하지 않고 내부 통신 포트 사용 
            new_tio.c_cflag |= CREAD;    // 쓰기는 기본, 읽기도 가능하게 
            new_tio.c_iflag = 0;
            new_tio.c_oflag = 0;
            new_tio.c_lflag = 0;
            new_tio.c_cc[VTIME] = 0; 
            new_tio.c_cc[VMIN]  = 1; 
            
            memset( &new_tio, 0, sizeof(new_tio) );
            
            new_tio.c_cflag = B115200;   // 통신 속도 115200 
            new_tio.c_cflag |= CS8;      // 데이터 비트가 8bit 
            new_tio.c_cflag |= CLOCAL;   // 외부 모뎀을 사용하지 않고 내부 통신 포트 사용 
            new_tio.c_cflag |= CREAD;    // 쓰기는 기본, 읽기도 가능하게 
            new_tio.c_iflag = 0;         // parity 비트는 없음
            new_tio.c_oflag = 0;
            new_tio.c_lflag = 0;
            new_tio.c_cc[VTIME] = 0; 
            new_tio.c_cc[VMIN] = 1; 
            
            tcflush (com->fd, TCIFLUSH );
            tcsetattr(com->fd, TCSANOW, &new_tio );
        } 
    }
    call_func_com = NULL;
    return 0; 
}

     가장 먼저 __index 메타 함수인 sc_index()에서 call_func_com에 저장된

 

     사용자 데이터 구조체 주소를 가져온다.

 


com = call_func_com;

 
     그리고 이전에 열려 있는 포트가 있으면 다음과 같이 닫는다. tcdrain() 함수는 시리얼 포트에 남아 있는

     덜 쓰여진 데이터를 모두 써 놓도록 하여 디바이스 드라이버의 내부 버퍼를 비우게 한다.

 


if( com->fd >= 0 ) 
{
    tcdrain(com->fd);
    close(  com->fd);
}


     open() 함수를 이용하여 시리얼 포트 디바이스를 다음과 같이 연다.
   
com->fd = open( com->port, O_RDWR | O_NOCTTY | O_NONBLOCK );

      tcgetattr() 함수를 이용하여 이전에 설정된 상태를 얻어와 old_tio 에 저장한다.

 


tcgetattr( com->fd, &(com->old_tio) );

 

     이후 각 설정 변수값을 이용하여 통신 환경을 new_tio 에 설정하고 tcsetattr() 를

 

     이용하여 디바이스에 적용한다.

 


tcflush  (com->fd, TCIFLUSH );
tcsetattr(com->fd, TCSANOW, &new_tio );

     마지막으로 call_func_com 변수에 NULL값을 적용하여 호출이 끝났음을 알린다.

 


call_func_com = NULL;

     여기서 에러가 생긴것에 대한 처리는 하지 않았다.

     사용자는 fd 값을 얻어 이 값이 0 보다 작으면 에러가 발생했음을 확인해야 한다.

 

 

  10.7 close() 함수 구현

 

     닫기 함수는 다음과 같이 매우 단순하다.

 


static int sc_close(lua_State *L)
{
    serial_com_t *com;
    com = call_func_com;
    if( com != NULL )
    { 
        if( com->fd >= 0 )
        {
            tcdrain(com->fd);
            tcsetattr(com->fd, TCSANOW, &(com->old_tio) );
            close( com->fd );
            com->fd = -1;
         }
    }
    call_func_com = NULL;
    return 0; 
}

 
      tcdrain() 함수를 이용하여 아직 적용되지 않은 써 넣어진 데이터를 모두 처리하게 하고

      tcsetattr()함수를 이용하여 open() 함수에 적용된 이전값으로 디바이스 설정을 바꾼다.

      그리고 close() 함수를 이용하여 디바이스 파일을 닫은후 fd 값을 -1 로 설정하여

      더이상 열려져 있지 않음을 설정한다.

 

 

  10.8 read() 함수 구현

 

      read() 함수는 sc_read() 에서 구현하는데 다음과 같다.

 


static int sc_read(lua_State *L)
{
    serial_com_t    *com;
    ssize_t         ret;
    ret = -1;
    com = call_func_com;
   
    if( com != NULL )
    { 
        if( com->fd >= 0 )
        {
            ret = read( com->fd, com->r_data, sizeof( com->r_data ) );
        }
    }
    call_func_com = NULL;
    lua_pushinteger( L, ret );
    if( ret > 0 )
    {
        lua_pushlstring( L, (const char *) com->r_data, ret );
    }
    else
    { 
        lua_pushnil( L );
    } 
    return 2; 
}

        read() 함수를 이용하여 r_data 버퍼에 데이터를 읽고 읽은 크기 또는 에러 상태를  

 

lua_pushinteger( L, ret );

       를 이용하여 스택에 넣은 후 에러가 아니면 


lua_pushlstring( L, (const char *) com->r_data, ret );

       를 이용하여 문자열로 버퍼 내용을 스택에 넣는다.


       만약 에러라면 


lua_pushnil( L );

       를 이용하여 데이터가 없음으로 해서 스택에 넣는다. 

 
       루아에서는 다음과 같은 형태로 두개의 반환 인자를 받아야 한다.

   
rxsize, rxdata = com.read();

 

 

 10.9 write() 함수 구현

 

     write() 함수는 sc_write()에서 구현하는데 다음과 같다.


static int sc_write(lua_State *L)
{
    serial_com_t    *com;
    ssize_t         ret;
    unsigned char   *var_str; 
    ssize_t         w_size;
 
    ret = -1;
    com = call_func_com;
    
    if( com != NULL )
    { 
        if( com->fd >= 0 )
        {
          var_str = (unsigned char *) lua_tolstring( L, 1 , &w_size );
            if( var_str != NULL )
            { 
                ret = write( com->fd, var_str, w_size );
            } 
        }
    }
    call_func_com = NULL;
    lua_pushinteger( L, ret );
 
    return 1;
}

      sc_read() 함수와 유사하지만 써 넣어진 데이터 크기를 스택에 써 넣어 

      호출 루아 스크립트가 실제로 써 넣어진 데이터 크기나 에러 상태를 
      체크 할 수 있도록 한다.

 

  

 C000_lua.gif