티스토리 뷰
앞의 글에서 설명하였던 System timer처럼 ARM core를 내장한 SoC는 인터럽트를 처리하기 위하여 별도의 interrupt controller를 주변장치의 하나로써 제공하였다. 하지만 Cortex core가 들어가 있는 SoC의 경우 ARM에서 만든 interrupt controller가 내장되어 있다.
NVIC(Nested Vectored Interrupt Controller)라고하는 interrupt controller가 있는데, nested라는 말은 인터럽트가 발생하여 처리하고 있는 중에 더 높은 우선 순위의 인터럽트가 발생하면 현재 처리하고 있는 동작을 멈추고 높은 우선 순위의 인터럽트를 처리할 수 있게 하는 것이다.
Vectored라는 말은 인터럽트가 발생하면 각각의 인터럽트를 처리하는 ISR코드의 위치를 저장할 수 있는 벡터가 인터럽트마다 할당되어 있다는 말이다. 즉, 인터럽트가 발생하는 즉시 해당 ISR을 수행할 수 있으므로 그만큼 인터럽트 처리 시간이 짧아지는 이점이 있다.
기존에는 인터럽트가 발생하면 IRQ exception 벡터 코드가 실행되고, 그 코드 안에서 interrupt controller의 status register를 검사하여 어떤 인터럽트가 발생했는지를 알 수 있게 되므로 그만큼 인터럽트 처리하는 시간이 길어질 수 밖에 없는 구조였다.
물론, Cortex-M3에는 IPSR이라는 레지스터가 있어, 인터럽트가 발생하면 해당 인터럽트 번호가 IPSR에 기록되므로 예전과 같은 방식으로도 인터럽트를 처리할 수 있긴 하다.
이번 글에서는 Vectored 방식의 인터럽트 처리과정을 설명하도록 하겠다.
Cortex-M3는 기본적으로 ARM 자체에서 처리하기 위한 exception interrupt가 16개 예약되어 있다. SoC에서 사용하는 인터럽트는 예약된 exception 인터럽트 이후부터 할당되는 구조를 가지고 있다.
위의 그림이 STM32F103에서 사용하는 인터럽트를 표로 나타낸 것이다.
회색 바탕의 인터럽트가 ARM core에서 예약된 인터럽트이고, WWDG라는 인터럽트부터 나머지 인터럽트가 칩 제조사에서 할당하는 인터럽트 들이다.
WWDG의 poisition값이 0으로 표시되어 있는데, CPU에서는 16번 인터럽트에 할당되어 있다. 그리고 WWDG의 벡터주소가 0x0040으로 되어 있는데 이건 절대 주소가 아니고 offset 주소에 해당된다고 보면 된다. ARM core의 system control block 내에 있는 VTOR 레지스터에 vector table의 시작 주소를 등록해 주면 인터럽트가 발생하면 VTOR에 등록된 주소에서부터 해당 인터럽트의 offset 주소값을 더해서 ISR 코드를 실행하도록 되어 있다.
Start up에서는 CPU가 reset 상태에서 처음 프로그램이 시작될 때 어떤 절차로 시작되는지 설명하였는데 그때는 stack pointer와 reset handler에 대해서만 언급하였다. 사실은 reset handler이후에 계속 나머지 인터럽트에 대한 벡터 코드가 들어가야 하는데, 그렇게 되면 그런 코드들은 모두 ROM 영역에 존재하게 되므로 일단 프로그램이 시작되면 더이상 인터럽트 처리 방법을 변경할 수 없게 된다.
이번 글에서 설명하고자 하는 인터럽트 벡터는 ROM이 아닌 SRAM 영역에 벡터 테이블을 위치 시켜서 프로그램이 동작중이라도 필요에 따라 인터럽트 코드를 변경할 수 있는 방법을 이용하여 설명하고자 한다.
이를 위해서 링커 스크립트 파일에 다음과 같은 내용을 추가한다.
5번 줄에 보이는것처럼 data 섹션의 시작 위치에 intr_table이라는 섹션을 놓이게 한다.
다음으로는 STM32F103에서 제공하는 인터럽트에 대한 번호를 할당하는 코드를 만든다.
이 글에서는 SysTick 인터럽트와 UART 인터럽트에 대한 처리만을 할 예정이므로 위와 같이 필요한 내용만 정의하였다.
배열 irq_table은 intr_table섹션에 들어가게 되므로 최종적으로는 data 섹션의 맨 앞부분에 위치하게 된다. Map 파일을 열어서 실제로 data 섹션에 위치하는지 확인해 보기 란다.
인터럽트를 사용하기 위하여 interrupt_init() 함수를 사용한다.
5번 줄에 있는것은 VTOR레지스터에 벡터 테이블 주소를 알려주는 부분이다.
10번 줄은 irq_table을 초기화 해주는 부분으로써 dummy_isr() 함수를 모든 벡터에 할당해주게 된다. 사용하지 않는 인터럽트가 발생하면 dummy_isr()이 수행되도록 하는것이다.
12번 줄은 SysTick 타이머에서 인터럽트가 발생하는 경우 systick_isr()이 수행될 수 있도록 해당 ISR을 등록 해주고 있다.
dummy_isr()과 systick_isr()은 다음과 같다.
dummy_isr()은 IPSR 레지스터의 값을 읽어서 콘솔화면에 출력해주는 코드이다. 출력해 주는 번호에 해당하는 인터럽트가 발생했는데, 이를 위한 별도의 ISR이 등록되어 있지 않다는것을 알려주는 것이다.
systick_isr()은 단순히 LED만 점멸하는 코드를 넣어 주었다. Systick 인터럽트가 발생할때마다 LED가 토글되므로 LED를 보면 Systick 인터럽트가 발생한것을 알 수 있다.
Systick 인터럽트처럼 CPU core내에서 발생하는 인터럽트는 단순히 irq_table에만 해당 ISR 코드를 넣어 주면 되지만, 칩제조사에서 제공하는 인터럽트를 사용하기 위해서는 NVIC 레지스터에 인터럽트를 등록해주어야 한다.
각 주변장치에서 인터럽트를 사용하기 위하여 register_isr() 함수를 이용하면 된다.
예를 들어 UART1에서 인터럽트를 사용하기 위하여 다음과 같은 코드를 작성한다.
26번 줄에 RX 인터럽트를 발생시키기 위하여 UART_CR1_RXNEIE비트를 설정해 주었다.
28번 줄에 UART1에서 인터럽트가 발생하면 이를 처리하기 위하여 uart_rx_isr()을 등록해 주었다.
uart_rx_isr()은 단순히 어떤 data가 들어왔는지만 출력하도록 되어 있다.
실제로 보드에서 실행 시켜보면 LED가 일정한 주기(여기에서는 1초)로 점멸하는것을 볼 수 있고, 컴퓨터의 키보드에서 키를 누를때마다 콘솔창에 해당 글자의 ASCII 코드 값이 출력되는것을 볼 수 있을 것이다.
'ARM Cortex-M' 카테고리의 다른 글
heap 메모리 이해 (1) | 2017.03.17 |
---|---|
printf()를 만들어 보자 (3) | 2017.03.12 |
System Timer (0) | 2017.02.24 |
UART - STDIO (5) | 2017.02.18 |
UART (0) | 2017.02.18 |