Programming/8051

8051( 중급 11부 - serial 통신 )

청솔1 2009. 11. 29. 16:08

8051( 중급 11부 - serial 통신 )


 그동안 일이 바쁘다는 핑게로 강좌에 소홀해서 죄송함다.
 그리고 순서가 뒤죽 박죽 되어 더욱 죄송함다.
 이번 강좌에서는 C언어에 의한 시리얼 통신에 대해 설명 해 드림다.
 시리얼 통신에 있어서 인터럽트 방식이 아닌 polling 방식도 있지만 polling 방식의
 효율성이 낮고 사용할 가능성이 적으므로 생략함다. 
  interrupt 방식은 시리얼 데이터가 수신 되었을때 다른 일을 하고 있다 하더라도 즉시
 처리 할수 있는 장점이 있어서 multi tasking을 구현할수 있슴다.
 하지만 polling 방식은 항상 데이터가 수신되는지를 감시 하고 있어야 하고 수신된 데이
 터를 SBUF상에서 읽어 들이지 않으면 다음 수신되는 데이터에 의해 망가지게 되겠지요.
  마이컴의 처리 속도와 시리얼 통신의 속도는 오토바이를 타고 가는 사람과 자전거를 타는
 사람과의 차이정도로 현저한 차이가 있슴다. 
  시리얼 데이터 한 바이트 받는데 기다리는 시간동안 마이컴은 많은 일을 처리 할수가 있어
 서 interrupt 방식으로 데이터를 수신하고 나머지 시간 동안에는 다른 일을 처리토록 하는
 게 효율적임다.
  이러한 이유로 polling 방식은 현장에서 거의 사용되지 아니함다. 
  궂이 필요 하다면 다른 책들을 보십시오.
  항상 강조 하지만 책에 투자 하지 않고 단순히 남의 프로그램만 엿보고 다니는 각오로는
 아무것도 얻을수 없슴다.
  자신이 흘린 땀만큼 얻는것은 비례 하는검다.
  일단 시리얼 통신을 하기 위해서는 초기화 과정이 필요함다.
  baudrate, data bits, stop bits, parity bit등을 설정해야 하기 때문임다.
     
타이머 1에 의한 baudrate 계산 수식 
  ( 일반적인 경우이며 80C320, W77E58의 경우 시리얼 포트2를 타이머1로 사용함.)
                K x Osc frequency
   TH1 = 256 -  -----------------
                384 x baudrate
   SMOD = 0이면 K = 1, SMOD = 1 이면 K = 2
타이머 2에 의한 baudrate 게산 수식
  ( 8052부터 사용 가능. 
  그리고 80C320, W77E58의 경우 시리얼 포트1은 타이머 2에 의해 사용함. )
                              Osc frequency
  RCAP2H, RCAP2L = 65536 - -----------------
                              32 x BaudRate
다음은 위 공식에 의해 필요한 baudrate를 설정하기 위해 시리얼 포트를 초기화 하는 루틴임다.
/* 타이머 1에 의한 시리얼 포트 초기화 */    
void Init_COM( void ) {
    /* 2400 baud rate */
    Head   = 0;
    Tail   = 0;
    SCON    = 0x52;
    TH1     = 244;
    TR1     = 1;
    TI      = 1;
    RI      = 0;
    }
/* 타이머 1을 8bit autoreload모드로 세팅을 해주어야 하므로 */
/* 타이머 초기화 루틴도 필요함다.                          */
/* 이때 타이머초기화를 먼저 시킨 다음 시리얼 포트 초기화를 */
/* 함다.                                                   */ 
void Timer_init( void ) {
    TMOD = 0x21;    /* timer 1 8bit auto reload timer */
                    /* timer 0 16 bit timer */
    TH0  = 0xee;
    TL0  = 0x00;
    TR0  = 1;
    ET0  = 1;
    }
/* 타이머 2에 의한 시리얼 포트 초기화 */
void Init_COM( void ) {
    /* 1200 baud rate  MODEM control */
    Head   = 0;
    Tail   = 0;
    SCON    = 0x52;
    T2CON   = 0x30;
    RCAP2H  = 0xFE;
    RCAP2L  = 0xE0;      /* 1200 bps */
    T2CON.2 = 1;
    TI     = 1;
    RI     = 0;
    }
위에서 설명을 안한 부분은 Head와 Tail임다. 이는 시리얼 수신 데이터를 소프트 웨어로 구성한 환상형 Queue를 만들기 위함임다. 이를 위해서 다음과 같이 외부 메모리 상에 Queue를 선언해 줌다. ( 내부 메모리로 구성할수도 있슴다. )
 
  #define BUF_SIZE 256;
  int Head, Tail;
  xdata unsigned char Queue[ BUF_SIZE ];
Queue에 대한 설명은 이미 ASM 강좌에서 언급 했슴다. 어셈블리 공부는 하지 않고 바로 C를 배웠기 때문에 어셈블리를 이해 할수 없다고 하시는 분들이 많슴다. 이는 단시간에 마이컴을 교육시키려 하는 학부 강의가 잘못 되었 기 때문임다. 지금이라도 제대로 마이컴 펌웨어 엔지니어로 활동 하고 싶으시면 어셈블리 를 공부 하십시오. 환상형 Queue에 대한 자세한것을 알고 싶으시면 "자료 구조"에 관한 서적을 참고 하십 시오. 아래 자료는 http://www.helloec.net에서 퍼온검다.

Queue : 큐

큐 (Queue)는  한 쪽에서 입력(삽입)만하고 다른 한 쪽에서는 출력(삭제)하는 리스트의 운영방식을 말한다. 선 입선출 (FIFO), 또는 후입후출 방법으로 접근되는 리스트

  • 선입 선출법(FIFO : First-In-First-Out)
  • 후입 후출법(LILO : Last-In-Lst-Out) 구조

큐는 여러 개의 데이타 항목들이 일정한 순서로 나열된 자료 구조이다. 스택(Stack)과는 달리 한쪽 끝에서는 삽입만 할 수 있고, 삭제는 반대쪽 끝에서만 할 수 있도록 되어 있다.

  • 양끝(fromt, rear)에서 각각 자료의 삽입/삭제가 일어나는 정보의 일차원적 나열
  • 스택의 정보 : 원소 갯수 n과 원소 리스트 (a0, ......, an-1)
  • 큐에서의 원소의 삽입/삭제
  • 첫번째 아이디어 : 항상 배열 선두로부터 저장
  • 두번째 아이디어 : 지속적으로 증가하는 위치에 저장
  • 세번째 아이디어 : 환형으로 증가하는 위치에 저장
  • 큐의 성질 : FIFO(First-In-First-Out, 선입선출)
  • 큐의 사용예 : 작업 스케쥴링

큐의 원리

큐는 매표소에서 표를 사기 위해 기다리는 대기자 열과 같은 원리를 가진다. 대기자 열에는 먼저 온 사람부터 차례로 대기자들이 늘어서 있다. 앞쪽 끝에서는 기다리던 사람이 표를 사서 빠져나가고 (삭제), 뒤쪽 끝에서는 새로운 사람들이 대기자 열로 들어온다(삽입).

이런 방식 역시 실생활에서 많이 찾아볼 수 있는데 이 방식의 저장체는 대부분 입구와 출구의 역할이 구분되어 있다는 특성을 갖는다. 예를 들어 식당에서 밥을 타기 위하여 줄을 설 때 새로 온 사람은 줄의 맨 끝에 가서 붙고 밥이 나오면 줄의 맨 앞에 선 사람이 이를 받아 식탁으로 간다. (물론 새치기하기 없기다.)

날클립을 사용하는 사람이 있다면 날클립 심을 뒤에서 넣으면 클립을 찍을 때는 반대편 맨 앞의 심이 항상 찍힌다는 것을 알고 있을 것이다. 이처럼 큐는 대부분 줄의 의미를 갖고 순서적인 서비스를 위하여 대기하는 장소의 개념을 갖는다. 따라서 줄의 선두를 front(앞), 줄의 후미를 rear(꽁무니)로 생각하면 원소의 삽입이 rear에서, 삭제가 front에서 일어난다는 점을 이해하기 쉬울 것이다.

큐의 성질

큐에 저장된 데이타 항목들 중에 먼저 삽입된 것은 먼저 삭제되고, 나중에 삽입된 것은 나중에 삭제된다. 그래서 큐를 선입 선출 리스트 (First-In-First-Out:FIFO) 라 부른다. 후입 선출법을 사용하는 스택 과는 상반된 성질을 가진다.

큐의 종류

  • 선형 큐(linear queue)
  • 환형 큐(circular queue)

큐에는 한 방향으로 데이타 항목들이 삽입/삭제되는 선형 큐와 시작점과 끝점이 서로 연결되어 있는 환형 큐가 있다.

 

Queue에 아무런 데이터가 없을때 Head와 Tail는 같은 값을 같슴다. 그러므로 프로그램 시작시에는 반드시 같은 값이 되도록 맞추어 주어야 함다. 수신 데이터가 발생하여 인터럽트 서비스 루틴으로 점프 하게 되면 SBUF의 내용을 읽어 서 인덱스 Head가 가르키고 있는 Queue에 데이터를 넣어 줌다. 그리고 Head는 1을 증가 시 켜줌다. 증가시킨 Head값이 BUF_SIZE 값보다 넘지못하도록 modular 연산을 취하는것도 필요 함다. 다음은 인터럽트 서비스 루틴의 내용임다.
 
 void SCON_int( void ) {
    if( RI == 1 ) {           /* 수신 인터럽트 발생시 */
        RI = 0;
        Queue[ Head ] = SBUF;
        Head ++;              /* 인덱스 증가  */
        Head %= BUF_SIZE;     /* modular 연산 */
        }
    else if( TI == 1 ) {      /* 송신 인터럽트 발생시 */
        TI = 0;
        Tx_ready = 1;         /* bit 변수 */
        }
    }
인터럽트 서비스 루틴에서는 Queue에 차곡 차곡 저장을 해 줌다. 사용자는 현재의 Head와 Tail값이 틀린지 여부를 조회하면 데이터가 Queue에 있는지 여부 를 알아 볼수 있슴다. define 함수로 간단하게 맹글었슴다.
 
 #define COM_ready()     ( Tail  == Head ) ? 0 : -1
COM_ready() 함수를 호출하여 -1값을 리턴 받으면 수신 데이터가 있다는것임다. 그 수신 데이터는 다음과 같은 함수로 읽어 낼수 있슴다.
unsigned char COM_read( void ) {
    unsigned char in_data;
    in_data = Queue[ Tail ];
    Tail ++;					/* 인덱스 Tail 증가 */
    Tail %= BUF_SIZE;           /* modular 연산     */
    return in_data;
    } 
시리얼 데이터를 인터럽트 서비스 루틴으로 수신할수 있는 방법을 설명했슴다. 다음의 함수 들은 송신 하는 함수 임다. 시리얼 인터럽트에는 수신 인터럽트와 송신 인터 럽트가 함께 처리 되도록 되어 있슴다. 하지만 송신에서는 인터럽트 방식을 활용하는게 무 리가 따름다. 그래서 인터럽트 서비스 루틴에서는 단지 사용자가 define한 Tx_ready만을 세팅 시켜주고 송신 자체는 polling 방식을 사용함다.
bit Tx_ready;
/* 한 문자를 송신 하는 함수 */
void COM_putc( xdata unsigned char ch ) {
    while( Tx_ready == 0 );        /* 송신 가능한지를 검사 */
    Tx_ready = 0;                  /* 송신 완료를 확인 하기 위해 0으로 클리어 */
    SBUF = ch;                     /* 송신이 되면 인터럽트 루틴에서 1로 세트 */
    }
/* 문자열을 송신 하는 함수 */
void COM_puts( xdata unsigned char *str ) {
    int i;
    for(i=0;str[ i ] != 0;i++) {   /* 문자열 끝인  null을 확인 할때 까지 반복 */
        while( Tx_ready == 0 );
        Tx_ready = 0;
        SBUF = str[ i ];
        }
    }
이와 같이 송신 루틴을 맹글수 있슴다. 여기에서 단점은 송신시에는 polling을 사용했기 때문에 송신 히는 동안에는 다른 일을 할수 없다는 검다. 이방법을 해결하기 위해서는 송신에서도 Queue를 만들어 주고 송신 데이터를 Queue에 저장해 놓고서 Tx_ready가 1일때는 그 Queue의 값을 끄집어 내어 송신 하는 방법을 만들수 있슴다. 이 방법은 다음 기회에 설명해 드리겠슴다. 이제 까지 설명한 함수들을 이용해서 main() 함수를 작성하면 다음과 같슴다.
 void main( void ) {
 	unsigned char temp;
     /*---- 각종 변수 초기화 ----*/
    Head = Tail = 0;
    Tx_ready = 1;
    /*---- 인터럽트 초기화 ----*/
    Timer_init();
    Init_COM();
    ES      = 1;
    PS      = 1;
    EA      = 1;
    /*---- 메인 처리 루틴 ----*/
    for(;;)  {
        if( COM_ready() ) {
            temp = COM_read();
            COM_putc( temp );
            }
        }        
    }