Skip to content

예제 uart_echo_interrupt

irmusy edited this page Sep 30, 2013 · 1 revision

개요

uart_echo_interrupt 예제는 UART 장치에 대한 읽기/쓰기를 테스트 해 볼 수 있는 간단한 예제입니다. [uart_echo 예제](예제 uart_echo)와의 차이점은 UART 수신 이벤트에 interrupt를 사용하고 있는 점입니다.

본 예제에서는 UART로 부터 데이터가 수신되면 인터럽트가 발생하고, 이 인터럽트 핸들러 함수에서 수신된 데이터를 즉시 송신 하는 형태로 구성되어 있습니다. UART 설정은 아래와 같습니다.

  • 115200bps
  • No parity bit
  • 8 data bit
  • 1 stop bit

관련 Peripheral

  • SysCtl
  • GPIO
  • UART
  • interrupt

소스 살펴보기

27 line:

#include "hw_types.h"
#include "hw_memmap.h"

#include "sysctl.h"
#include "gpio.h"
#include "uart.h"
#include "led.h"

필요한 헤더 파일들을 include 하고 있습니다. Driver Library를 사용할 때에는 이처럼 각 장치 별로 헤더파일들을 직접 include 해 줘야 합니다. 대표 헤더 파일 하나 만들어 두고 그것만 include해서 쓰면 편할건데 왜 이렇게 하는 걸까요? 여러가지 의견이 많이 있습니다만, 처음 배우는 사람을 위해서는 이처럼 필요한 것들을 직접 include해서 사용하는 방식이 더 효과적입니다. 버그도 줄일 수 있구요.

36 line:

#ifdef DEBUG
void
__error__(char *pcFilename, unsigned long ulLine)
{
}
#endif

__error__() 함수를 정의하고 있습니다. 이 함수는 Driver Library내에서 Asserition fail시 호출되는 함수 입니다. 자세한 내용은 assert를 참조하시기 바랍니다.

45 line:

static void UART_IntHandler(void);

UART 수신 인터럽트 발생 시 호출 될 인터럽트 핸들러 함수 원형을 선언합니다.

48 line:

int main(void)
{
    SysCtlClockSet(SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN |
                   SYSCTL_XTAL_8MHZ);

main() 함수를 시작한 직후 SysCtlClockSet() 함수를 사용하여 클럭 설정을 하고 있습니다. 이 설정값은 보드에 내장된 8MHz 크리스털을 사용하고( SYSCTL_OSC_MAIN), PLL을 enbale시켜( SYSCTL_USE_PLL) 200MHz 클럭을 만든 다음, 이를 다시 4분주( SYSCTL_SYSDIV_4) 하여 최종적으로 50MHz 클럭을 사용하겠다는 뜻입니다.

54 line:

    //
    // Configure UART
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
    GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
    UARTConfigSetExpClk(UART0_BASE, SysCtlClockGet(), 115200,
                        (UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE |
                         UART_CONFIG_PAR_NONE));

UART 장치를 활성화 하고 초기화합니다. LM3S8962에 내장된 UART 중 UART0 장치를 사용합니다. UART0는 GPIO port A의 PA0, PA1 핀에 연결되어 있습니다. **GPIOPinTypeUART()**함수를 사용하여 해당 핀을 UART 목적으로 사용하게끔 설정합니다.

UART0의 baudrate, data length, parity bit, stop bit 등을 원하는 형태로 설정하면 UART 설정이 끝납니다.

64 line:

    // FIFO 사이즈 = 16바이트(TX, RX 각각)
    // TX fifo는 4바이트 이하 사용중일때,
    // RX fifo는 12바이트 이상 사용중일때 인터럽트 발생.
    // 즉 TX는 쌓아두고 보낼 데이타가 4개밖에 남지 않았음을 알려주고
    // RX는 새로 들어오는 데이타를 쌓아둘 공간이 4개 밖에 남지 않았음을 알려준다.
    UARTFIFOLevelSet(UART0_BASE, UART_FIFO_TX2_8, UART_FIFO_RX6_8);

UART 장치의 FIFO 설정을 하고 있습니다. UART에는 수신 16 word, 송신 16 word의 FIFO가 있습니다. UART를 통해 수신된 데이터는 우선 UART 수신 FIFO에 저장됩니다. 수신 FIFO에 저장된 데이터가 일정 수 이상으로 쌓이면 그때 수신 인터럽트가 발생합니다.

이처럼 만들어진 이유는 매 글자가 수신될 때 마다 인터럽트가 발생하는 경우의 단점을 줄이기 위해서입니다. 인터럽트가 발생하면 기존 작업을 일시 중지하고 인터럽트 핸들러를 수행하게 됩니다. 이처럼 구문 전환이 일어날 때에는 필수적으로 오버헤드가 생길 수 밖에 없습니다. UART를 통해 10글자를 수신받을 때 매 글자마나 인터럽트가 발생한다면 10번의 오버헤드가 발생하는 샘입니다. 이를 해소하기 위해 수신된 데이터는 일단 FIFO에 쌓아두고 일정 수 이상 쌓이면 그때 한번만 인터럽트를 발생시켜 주는 구조를 가지고 있습니다. 본 예제에서는 수신 FIFO의 6/8, 즉 전체 16 word 중 12 word만큼 쌓이면 인터럽트를 발생시키도록 설정하였습니다. 11 글자가 수신될 때 까지는 FIFO에 쌓아 두기만 하다가 12 글자가 수신되었을 때 인터럽트가 발생하고, 그때 인터럽트 핸들러에서는 그동안 수신된 모든 데이터(12 글자)를 다 FIFO에서 읽어 와 한꺼번에 처리하게 됩니다.

참고로 송신 인터럽트는 사용하지 않습니다만, **UARTFIFOLevelSet()**함수 구조 상 함께 설정하고 있습니다.

72 line:

    // UART 수신 인터럽트만 활성화시킨다.
    UARTIntEnable(UART0_BASE, UART_INT_RX | UART_INT_RT);
    UARTIntRegister(UART0_BASE, UART_IntHandler);
    UARTEnable(UART0_BASE);

    IntEnable(INT_UART0);

UART 인터럽트를 설정하고 enable 시킵니다. UART 인터럽트에는 RX 인터럽트와 RT 인터럽트를 활성화 시켰습니다. RX 인터럽트는 위에서 설명한 FIFO 인터럽트입니다. 수신 FIFO에 저장된 데이터가 12 byte 이상이 되면 RX 인터럽트가 발생됩니다. RT 인터럽트는 receive timeout 인터럽트입니다. 이것은 일정 시간(약 3 바이트가 수신될 시간) 이상 수신된 데이터가 없으면, 즉 idle 이면 인터럽트를 발생시킵니다. FIFO를 이용해 수신 인터럽트의 발생 횟수를 줄이고 오버헤드를 줄이기 위해서는 이처럼 timeout 인터럽트가 필요해 지게 됩니다.

RT 인터럽트가 필요한 경우를 예를 들어 살펴보겠습니다. UART를 통해 3 글자만 수신되었다고 가정해 보겠습니다. FIFO에는 3 글자만 저장되어 있으니 수신 인터럽트가 발생하지 않을 것입니다. 이 상태에서 더이상 수신되는 데이터가 없다면 FIFO에 저장된 3 글자의 데이터는 영원히 잊혀지게 됩니다. 인터럽트가 발생하지 않으니 수신되었다는 것을 알 수 없기 때문입니다. 이런 경우를 위해 일정 시간동안 더이상 수신되는 데이터가 없다면 FIFO에 얼마나 쌓여 있는지와 무관하게 인터럽트를 발생시켜 수신된 데이터를 처리할 수 있게끔 해야 합니다.

80 line:

    //
    // Configure GPIO to drive LED
    //
    LED_INIT();

GPIO에 연결된 LED를 초기화 하고 있습니다. UART echo의 동작 상태를 눈으로 확인할 수 있도록 1 바이트가 수신될 때 마다 LED 상태를 토글하기 위해서 입니다.

87 line:

    //
    // Loop forever.
    //
    while(1)
    {
    }

무한 루프. [uart_echo 예제](예제 uart_echo)와 달리 데이터 수신과 송신 작업은 UART 인터럽트 핸들러 함수에서 처리되기 때문에 main()의 무한 루프에는 아무런 로직도 들어가지 않습니다.

95 line:

static void UART_IntHandler(void)
{
    unsigned long ulStatus;

    IntDisable(INT_UART0);
    ulStatus = UARTIntStatus(UART0_BASE, true);
    UARTIntClear(UART0_BASE, ulStatus);

    if (((ulStatus & UART_INT_RX) == UART_INT_RX) || ((ulStatus & UART_INT_RT) == UART_INT_RT))
    {
        while(UARTCharsAvail(UART0_BASE))
        {
            LED_TOGGLE();
            UARTCharPutNonBlocking(UART0_BASE, UARTCharGet(UART0_BASE));
        }
    }

    IntEnable(INT_UART0);
}

UART 인터럽트 핸들러입니다. 핸들러 시작 시점에 더이상의 인터럽트가 중복 발생하는 것을 막기 위해 **IntDisable()**로 UART0 인터럽트만 disable 시켜줍니다. 인터럽트 핸들러를 꼭 이처럼 작성해야 하는 것은 아니며, **IntDisable()**과 같은 함수는 상황에 맞게 주의해서 사용해야 합니다.

다음으로는 UART의 인터럽트 상태를 확인(UARTIntStatus)하여 해당 상태를 clear 시켜 줌으로써 해당 인터럽트가 잘 처리되었다고 알려줘야 합니다. 이렇게 하지 않으면 인터럽트 핸들러 무한 반복 상황에 빠지게 됩니다.

앞서 코드에서 UART 인터럽트 중 RX와 RT 인터럽트를 활성화 시켜 두었습니다. 본 핸들러에서는 발생한 인터럽트가 RX이거나 RT인 경우에만 동작하도록 if문이 추가되어 있습니다. if 구문 내부에는 while 구문이 있습니다. FIFO에 데이터가 남아 있는 동안(UARTCharsAvail) 그 데이터를 가져 와서(UARTCharGet) UART로 송신(UARTCharPutNonBlocking)하는 로직 입니다. 더불어 LED도 깜박이고 있습니다.

[uart_echo 예제](예제 uart_echo)에서는 UARTCharPutNonBlocking() 대신 UARTCharPut() 함수가 쓰였습니다. 이 두 함수의 차이는 송신 FIFO가 가득 차 있을 때 동작 방식이 다릅니다.

  • UARTCharPut() : 송신 FIFO가 가득 차 있을 경우에는 FIFO에 빈 자리가 생길 때 까지 기다렸다가 그 빈 자리에 송신할 데이터를 집어 넣고 리턴합니다.
  • UARTCharPutNonBlocking() : 송신 FIFO가 가득 차 있는 경우 새 데이터를 FIFO에 집어 넣는 대신 바로 리턴 해 버립니다. 이 경우 새 데이터는 UART로 송신되지 않습니다.

DriverLib의 uart.c 파일을 참조하셔서 그 차이점을 자세히 이해하실 것을 추천드립니다.

마지막으로 disable 시켜 뒀었던 UART0 인터럽트를 다시 enable 시켜 두고 인터럽트 핸들러 동작을 종료합니다.

실행 방법 안내

  1. 펌웨어 다운로드 후 보드상의 리셋 버튼을 1회 누릅니다.
  2. PC에서 ComPortMaster를 실행하고 Stellaris-JTAG의 가상 시리얼 포트를 open 합니다. 이때 설정은 115200bps로 합니다.
  3. ComPortMaster에서 아무 데이터나 send하면 동일한 데이터가 수신창에 돌아오는 것을 확인할 수 있습니다.