티스토리 뷰

입문

인터럽트 - UART

Just4Fun 2016. 4. 6. 21:44

이번에는 UART를 이용하여 AVR에서 인터럽트를 발생 시키는 방법과 인터럽트가 발생하여 현재 수행중인 프로그램을 멈추고 인터럽트 벡터 주소로 점프하여 인터럽트를 처리한 후, 다시 인터럽트로 인하여 멈추어진 프로그램을 다시 실행 시키는 절차에 대하여 설명하도록 하겠다.

이전에 설명하였던 UART 글들을 참조하여 다음과 같은 메시지가 출력되도록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include "uart.h"
 
void setup(void)
{
    uart_init();
    printf("UART RX Interrupt Test Program. [%s %s]\n",__DATE__,__TIME__);
}
 
void loop(void)
{
}
 
int main(void)
{
    setup();
 
    while (1)
    {
        loop();
    }
}

위의 코드를 작성하여 컴파일하면 그 결과들이 Debug폴더에 만들어진다.  그 중에서 .lss 파일을 열어보면 다음과 같은 내용을 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
00000000 <__vectors>:
   0:   0c 94 34 00     jmp 0x68    ; 0x68 <__ctors_end>
   4:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
   8:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
   c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  10:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  14:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  18:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  1c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  20:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  24:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  28:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  2c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  30:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  34:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  38:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  3c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  40:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  44:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  48:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  4c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  50:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  54:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  58:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  5c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  60:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  64:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
 
00000068 <__ctors_end>:
  68:   11 24           eor r1, r1

첫번째 줄에 "00000000 <__vectors>:"라는 것을 볼 수 있다.  __vectors라는 이름의 심볼 주소가 0번지임을 알려주는 것이다.  여기서 보이는 vectors라는것이 인터럽트 벡터를 말하는 것이다.  같은 방법으로 29번 라인에 "00000068 <__ctors_end>:"라고 되어 있는 다른 이름의 심볼과 그 시작주소를 볼 수 있다.

두 심볼 사이에 있는 각각의 줄에 있는 명령어가 인터럽트가 발생하였을때 해당 인터럽트를 수행하기 위한 첫번째 명령어이다.  라인수를 세어보면 26개인것을 알 수 있다.  인터럽트 - 개념글에 있는 표를 보면 왜 26개인지 알 수 있다.  표에서 첫번째 인터럽트는 CPU가 reset상태에서 살아날 때 가장 처음으로 수행하기 위한 reset 인터럽트이다.  이 얘기는 AVR CPU는 무조건 0번지에서 프로그램이 시작된다는 의미이다.  심화 과정에서 reset 벡터 주소를 다른 주소로 변경하는 방법에 대해서 설명하겠지만 지금은 그냥 프로그램은 0번지에서 시작된다고 가정하고 넘어가도록 하겠다.

Reset 벡터에 있는 명령어는 "jmp 0x68"이라고 나와있다.  0x68번지로 점프하라는 명령이다.  그 위치에는 __ctors_end라는 심볼이 있으므로 __ctors_end가 실행된다.   Reset 벡터를 제외하고 나머지 인터럽트 벡터들은 모두 0xa2번지로 점프하도록 되어 있다.  그 주소의 심볼은 __bad_interrupt이다.  0xa2번지에 있는 __bad_interrupt를 찾아보면 0번지로 점프하라는 명령어가 있다.  프로그램을 Reset이 걸렸을 때와 똑같이 다시 처음부터 실행하라는 것이다.

왜 이런 코드가 들어있을까?  계속해서 반복되는 얘기이지만 인터럽트가 발생하면 무조건 특정 주소에서 프로그램이 시작되도록 되어 있다.  그런데 그 인터럽트가 발생할거라고 전혀 예상을 못한 상태에서 인터럽트가 발생하면, 그 인터럽트를 어떻게 처리를 할 것인지 전혀 준비가 되지 않은 상태이므로 엉뚱한 동작을 할 가능성이 생기게 된다.  임베디드 시스템이 오동작해도 전혀 문제 없는 상황이라면 관계없겠지만 사람의 생명이나 중요한 기계를 망가뜨릴수도 있는 상태라면 차라리 프로그램을 처음부터 다시 시작하는 것이 안전하기 때문에 이런 코드를 만들어두는 것이다.

이제 AVR의 UART로 데이터가 수신되었을 때 인터럽트를 발생시켜 보도록 하겠다.

첫번째로 해야 할일은 UART 블럭에서 데이터가 수신되었을 때 이러한 사실을 CPU에게 알려주기 위하여 "RX Complete Interrupt Enable" 비트를 1로 만들어 주어야 한다.

Datasheet 문서에서 UCSRB레지스터의7번비트가 RXCIEn비트이다.  uart.c 파일에 다음과 같은 함수를 하나 추가한다.

1
2
3
4
void uart_rx_intr_enable(void)
{
    uart->ucsr_b |= RXCIE;
}

그리고 setup() 함수에 이 함수를 부르는 코드를 추가한다.

1
2
3
4
5
6
void setup(void)
{
    uart_init();
    uart_rx_intr_enable();
    printf("UART RX Interrupt Test Program. [%s %s]\n",__DATE__,__TIME__);
}

다음으로는 인터럽트가 발생하면 이 인터럽트를 처리하도록 하는 함수를 만들어야 한다.  이 함수를 ISR(Interrupt Service Routine)이라고 부르거나 인터럽트 핸들러(interrupt handler)라고도 한다.

AVR에서는 ISR을 등록하기 위하여 ISR()을 사용하면 된다.  ISR()을 사용하기 위하여 <avr/interrupt.h>를 include 하여야 한다.

UART RX 인터럽트 ISR은 다음과 같은 코드를 이용하여 등록한다.

1
2
3
ISR(USART_RX_vect)
{
}

일단 여기까지만 코딩을 완료한 다음 컴파일하여 .lss파일이 어떻게 변경되었는지 확인해 보기로 하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
00000000 <__vectors>:
   0:   0c 94 34 00     jmp 0x68    ; 0x68 <__ctors_end>
   4:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
   8:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
   c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  10:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  14:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  18:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  1c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  20:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  24:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  28:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  2c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  30:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  34:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  38:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  3c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  40:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  44:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  48:   0c 94 53 00     jmp 0xa6    ; 0xa6 <__vector_18>
  4c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  50:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  54:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  58:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  5c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  60:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
  64:   0c 94 51 00     jmp 0xa2    ; 0xa2 <__bad_interrupt>
 
00000068 <__ctors_end>:
  68:   11 24           eor r1, r1

20번 라인을 자세히 보면 이전과 달라져 있는것을 볼수있다.  명령어 주소가 0x48번지이고 0xa6번지에 있는 __vector_18이라는 심볼로 점프하는 명령으로 바뀌어져 있다.  인터럽트 벡터 표를 보면 UART RX인터럽트의 벡터번호 19번에 주소는 0x24워드주소(0x48바이트주소)를 가지는 것이라고 설명되어 있다.  문서가 조금 불편하게 작성되었는데, reset벡터 번호가 1부터 시작되는 것이 아니고 0부터 시작된다고 보면 UART RX 인터럽트는 18번 인터럽트가 된다.  그래서 UART RX ISR의 심볼이 __vector_18이 된 것이다.

__vector_18이라고 되어 있는 심볼을 찾아보면 다음과 같이 나올것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
000000a6 <__vector_18>:
 
ISR(USART_RX_vect)
{
  a6:   1f 92           push    r1
  a8:   0f 92           push    r0
  aa:   0f b6           in  r0, 0x3f    ; 63
  ac:   0f 92           push    r0
  ae:   11 24           eor r1, r1
}
  b0:   0f 90           pop r0
  b2:   0f be           out 0x3f, r0    ; 63
  b4:   0f 90           pop r0
  b6:   1f 90           pop r1
  b8:   18 95           reti

ISR함수는 항상 reti라는 명령어로 끝나게 되어있다.  따라서 15번 라인이 ISR의 끝이라는 것을 알 수 있다.  현재는 UART RX의 ISR에 아무런 코드가 들어있지 않으므로 별다른 작업없이 그냥 빠져나오는 것을 알수 있다.

이제 ISR() 안에서 실제 UART블럭의 UDR레지스터에서 데이터를 읽어서 어떤 데이터가 들어 왔는지 출력해 보는 코드를 만들어 보겠다.

1
2
3
4
5
6
ISR(USART_RX_vect)
{
    struct uart *uart = (void*)UART_REG;
    uint8_t data = uart->udr;
    printf("RX Data = %c[%02X,%d]\n",data,data,data);
}

인터럽트를 사용하지 않았을 때는 UCSRA레지스터에서 RXC 비트값을 보고 데이터가 들어 와 있는지 확인하였는데, 인터럽트 방식에서는 그럴 필요가 없다.  이미 데이터가 수신되었기 때문에 인터럽트가 발생한것이므로 그냥 UDR레지스터에서 값을 읽어 오면된다.  읽어온 데이터를 ASCII코드로도 출력하고, 16진수와 10진수로도 출력하게 만들었다.

이렇게 한다음 컴파일 하여 보드에서 실행시켜 본다.   컴퓨터의 키보드를 아무거나 눌러서 해당 키에 대한 데이터가 출력되는지 확인해 본다.

아무런 반응이 없어야 정상이다.  앞의 글에서도 언급했지만 개별적은 주변장치에서도 인터럽트를 발생할 것인지 말것인지를 선택할 수 있지만, CPU에서도 인터럽트를 발생시킬것인지 말것인지 통합하여 설정하는 레지스터가 있다.  AVR에는 SREG라는 레지스터가 있다.

위의 그림에서 7번째 비트에 있는 I비트가 전체 인터럽트를 발생할것인지 여부를 결정하는 비트이다.  이 비트를 1로 설정하여야지만 주변장치에서 보낸 인터럽트 신호를 CPU에서 받아 들이게 된다.  이 비트를 1로 설정하기 위하여 직접 0x3F번지에 접근하여도 되지만 AVR에서는 sei()라는 명령어를 사용하여 I비트를 설정할 수 있다.  I 비트를 해제 하기위해서는 cli() 명령을 이용하면 된다. Global 인터럽트를 enable하기 위하여 setup() 함수의 마지막에 sei();를 넣은 후 다시 프로그램을 컴파일 하여 어떻게 동작 되는지 확인해 보자.

컴퓨터 키보드에서 a,b,c,d,e,f를 각각 눌렀을 때 위의 그림과 같은 결과를 볼 수 있었다.



main.c


uart.c


uart.h


입문 과정 목차

'입문' 카테고리의 다른 글

타이머/카운터 - TCNT  (1) 2016.04.09
인터럽트 - 시작과 끝  (0) 2016.04.07
인터럽트 - 개념  (1) 2016.04.04
UART 온도계  (0) 2016.04.03
UART - "hello, world"  (11) 2016.04.03
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
글 보관함