본문 바로가기

stm32 레지스터 직접 접근 (5. Exception / Interrupt [설명 & 코드 Part 01])

반응형

관련글


잡동사니 세상 :: stm32 레지스터 직접 접근 (5. Exception / Interrupt [인터럽트 설정 Part 02]) (tistory.com)

잡동사니 세상 :: stm32 레지스터 직접 접근 (5. Exception / Interrupt [중요 레지스터들 Part 03]) (tistory.com)


잡동사니 세상 :: stm32 레지스터 직접 접근 (소개) (tistory.com)

잡동사니 세상 :: stm32 레지스터 직접 접근 (0. Microcontroller와 ARM ) (tistory.com)

잡동사니 세상 :: stm32 레지스터 직접 접근 (1. STM32F10x란) (tistory.com)

잡동사니 세상 :: stm32 레지스터 직접 접근 (2. 프로그램 설치 및 설정) (tistory.com)

잡동사니 세상 :: stm32 레지스터 직접 접근 (3. RCC) (tistory.com)

잡동사니 세상 :: stm32 레지스터 직접 접근 (4. GPIO [Output]) (tistory.com)

잡동사니 세상 :: stm32 레지스터 직접 접근 (4. GPIO [Input]) (tistory.com)

잡동사니 세상 :: stm32 레지스터 직접 접근 (5. Exception / Interrupt [인터럽트 설정 Part 02]) (tistory.com)

stm32 레지스터 직접 접근 (5. Exception / Interrupt [중요 레지스터들 Part 03]) (tistory.com)


서론


이번 포스트는 다음에 올릴 포스트와 같이 봐야합니다.

대부분의 MCU에는 Exception 또는 Interrupt라는 기능이 있습니다. Exception 또는 Interrupt (지금부터는 통칭 Interrupt라고 부르겠습니다.)는 특정한 상황에서 실행 되고 있는 메인 프로그램을 중단하고, Handler라고 부르는 함수를 호출하여 잠깐 이를 실행시키는 기능입니다. 예를 들어 여러분이 공부하고 있는 상황이 있습니다. 여기서 공부하다메인 프로그램입니다. 공부하는 중에 갑자기 벨이 울려 택배를 받아야하는 상황은 Interrupt가 발생 되었다라고 말 할 수 있고, 택배를 받는 행위는 Handler에서 처리하는 것입니다.

코드로 표현하면 다음과 같겠네요.

그림 01
그림 02

 

Interrupt가 발생되면, 메인 프로그램은 자동으로 중단되고 Interrupt Handler가 실행됩니다. Interrupt Handler가 끝나면 중단된 메인 프로그램으로 다시 돌아 옵니다.

 

STM32에는 일정 주기마다 발생되는 Interrupt, 리셋이 되면 발생되는 Interrupt, 설정된 핀의 상태에 따라 발생되는 Interrupt 등이 있습니다.

 

이번 포스트에서는 External interrupt 즉, 외부 인터럽트라는 것에 대해 알아볼 예정입니다.


Interrupt

External interrupt

  Registers

    RCC

    GPIOs

    AFIO

    EXTI


참고 자료


STM32F103ZET6 Reference Manual

Section 10.1.2 [Interrupt and exception vectors] (198 ~ 200 page)

Section 7.3.7 [APB2 peripheral clock enable register] (112 ~ 114 page)

Section 9.1 [GPIO functional description] (159 ~ 161 page)

Section 9.4.4 [External interrupt configuration register 2] (191 page)

Section 10.3.4 [Falling trigger selction register] (212 page)

Section 10.3.3 [Rising trigger selection register] (212 page)

Section 10.3.1 [Interrupt mask register] (211 page)

 

STM32F10xxx/20xxx/21xxx/L1xxxx Cortex®-M3 programming manual

Section 4.3.2 [Interrupt set-enable registers] (120 page)

 

Interrupt


STM32는 Interrupt vector table이라는 개념이 있습니다. Interrupt vector table은 Handler의 주소들을 담고 있는 테이블로 다음과 같습니다.

그림 03 (198 ~ 200 page)
그림 04 외부 인터럽트에 대한 Interrupt vector table

위의 테이블은 해석할 필요는 없으며 아래 내용 또한 프로그래머가 설정하는 것이 아니라 하드웨어가 자동으로 해주는 작업니다.

 

지금은 Interrupt가 발생되었을 때를 예시를 들어 잠깐 설명하겠습니다.

메인 프로그램이 실행되던 도중, Reset이라는 Interrupt가 발생되면 하드웨어(NVIC)가 자동으로 Interrupt vector table에서 Reset interrupt가 있는 0x000 0004에 저장된 값을 읽습니다. (위의 테이블에는 저장된 안 나와 있으며, 이는 프로그램마다 다릅니다.) 이 저장된 값은 Reset handler의 주소이며, 하드웨어는 자동으로 Reset handler가 있는 위치로 이동하여 Handler를 다 실행시킨 후 메인 프로그램으로 다시 돌아옵니다. (여기서는 초기화 된다가 맞는 표현이겠습니다.)

외부 인터럽트6이 발생되면 하드웨어는 자동으로 Interrupt vector table에서 EXTI9_4가 있는 0x000 009C에 저장된 값을 읽습니다. 이 저장된 값은 EXTI9_4 handler의 주소이며, 하드웨어는 자동으로 EXTI9_4 handler가 있는 위치로 이동하여 Handler를 다 실행시킨 후 메인 프로그램으로 다시 돌아옵니다.

여기서 제가 하드웨어(NVIC)가 자동으로 ~에서 저장된 값을 읽고, 해당 위치로 이동한다.라고 표현을 했습니다. 해당 동작은 사람이 느끼기에는 아주 짧은 시간이 소요됩니다.(대략 12 cycles) 그 아주 짧은 시간이 지나면 이제야 Handler가 실행되는 거죠. 이때 하드웨어(NVIC)가 자동으로 ~에서 저장된 값을 읽고, 해당 위치로 이동한다. 즉, Interrupt가 실행되고 NVIC가 Handler의 위치를 읽어 MCU에 알려줘 실행되기 전까지를 Pend 상태라고 합니다. 그리고 Handler가 실행되고 끝나기까지를 Active 상태라고 하죠. 그림으로 표현하면 다음과 같습니다.

그림 05

 

Interrupt handler(줄여서 Handler)가 실행되는 중 다시 Interrupt가 발생된다면 다음과 같이 실행됩니다.

그림 06

1. 첫 번째 Interrupt가 발생되면, 첫 번째 Interrupt의 상태는 Pend가 됩니다.

2. 짧은 시간이 지난 후, 첫 번째 Interrupt의 Handler가 실행되고, Interrupt의 상태는 Active가 됩니다. 

3. 첫 번째 Interrupt가 Active인 상태에서 두 번째 Interrupt가 발생되어 첫 번째 Interrupt는 중단됩니다.

4. 두 번째 Interrupt는 Pend 상태가 됩니다.]

5. 짧은 시간이 지난 후, 두 번재 Interrupt의 Handler가 실행되고, Interrupt의 상태는 Active가 됩니다.

6. 두 번째 Handler가 끝나면 중단된 부분부터 다시 첫 번째 Handler가 실행됩니다.

7. 모든 Handler가 끝나 메인 프로그램이 수행됩니다.

 

 

여기서 만약 여러 Interrupts가 Pend 상태가 된다면 어떻게 동작할까요? 이때는 Interrupt vector table (그림 03) 왼쪽에서 두 번째에 나와 있는 Priority가 높은 것부터 Active 됩니다. (*여기서 숫자가 작을 수록 Priority가 높습니다.)

예를 들어 EXTI0와 EXTI1가 동시에 발생되어 둘 모두 Pend상태가 된다면, Priority(우선순위)가 더 높은 EXTI0가 Active 되어 EXTI0 handler가 먼저 수행되고, 그 다음 EXTI1이 Active가 되어 EXTI1 handler가 수행되는 그런 것입니다.

 

External interrupt


External interrupt(줄여서 EXTI) 또는 외부 인터럽트는 바로 위에서 설명한 핀의 상태에 따라 발생되는 Interrupt입니다. 여기서 말하는 상태는 Rising, Falling, Change(Rising & Falling) 등이 있으며 Rising은 Low -> High로 신호가 변할 때, Falling은 High -> Low로 변할 때, Change는 Low -> High 또는 High -> Low로 변하는 것을 의미합니다.

외부 인터럽트는 버튼이 눌렸음을 빨리 감지해야할 때, 엔코더를 사용할 때, MPU6050같이 Interrupt 알림 기능이 있는 센서를 사용할 때 사용됩니다.

 

Registers


Interrupt는 앞에서 설명한 것처럼 갑자기 택배를 받는 등 언제 발생될 지는 모르지만, 발생되고 Active 상태가 되기 까지는 12 cycles정도가 걸립니다. 즉 실행되는 것은 클럭에 의존한다는 것이며, 이것을 우리는 클럭을 인가해야한다라고 말합니다.

잡동사니 세상 :: stm32 레지스터 직접 접근 (3. RCC) (tistory.com)에서 설명한 것처럼, STM32의 클럭 관련된 것들은 모두 RCC가 관장합니다. 따라서 외부 인터럽트를 사용할 때 이에 클럭을 인가해야하기 때문에 우리는 RCC 레지스터를 살펴봐야합니다.

 

RCC


 

APB2 peripheral clock enable register (RCC_APB2ENR)

그림 07 (112 ~ 114 page)

 

EXTI는 일반 PA0, PG9이런 핀을 외부 인터럽트 핀으로, 그 기능을 바꾸는 것이기 때문에 Alternate function의 한 종류이기도 합니다. 따라서 우리는 Alternate function이라는 기능을 사용해야하기 때문에 RCC의 AFIOEN을 1로 만들어 일반 핀 (PA0, PG9 등)에 EXTI 같은 다른 기능을 부여할 수 있는 준비를 합니다.

우리는 PG6에 EXTI 기능을 부여할 것이기 때문에 RCC의 APB2ENR의 AFIOEN과, PG핀을 사용하기 위한 IOPGEN 둘 모두를 1로 만들어야합니다. (추가로 PF6에 연결된 LED를 사용하기 위해 IOPFEN로 1로 만듭니다.)

RCC -> APB2ENR = (1 << IOPGEN) | (1 << IOPGEN) | (1 << AFIOEN);	
/*
  PF6 핀을 사용하기 위해 IOPFEN을 1로 만듭니다.
  PG6 핀을 사용하기 위해 IOPGEN을 1로 만듭니다.
  Alternate function을 사용하기 위해 AFIOEN을 1로 만듭니다.
*/

 

 

GPIOs


그 다음은 GPIO 설정입니다.

그림 08

우리는 PG6를 EXTI 즉, Alternate function중 값을 읽는 input모드로 사용할 것이기 때문에 CNF6[1:0], MODE6[1:0]을 붉은 네모 안의 어느 하나로 설정해야합니다. 여기서는 PG6를 Input pull-up모드로 사용할 것이기 때문에 CNF6[1:0]을 10, MODE6[1:0]을 00, 그리고 ODR6를 1로 만듭니다. (또한 PF6를 사용하기 위해 GPIOF도 설정합니다.)

  GPIOG -> CRL = (8 << MODE6);      // PG6를 pull-up/pull-down 모드로 설정합니다. 10 00 -> 8
  GPIOG -> ODR = (1 << 6);      // PG6을 pull-up 모드로 설정합니다. (Open103Z 보드에 Pull-up 모드를 사용하도록 설계 되어 있습니다.)
  GPIOF -> CRL = (1 << MODE6);  //PF6을 Push-pull 모드로 설정합니다.

 

 

AFIO


우리는 PG6를 EXTI 즉, Alternate function 중 input 모드로 사용할 것이니, STM32의 Alternate Function Input Output (AFIO)을 다뤄야합니다. 

AFIO 중 EXTICR이라는 레지스터가 본격적으로 핀에 EXTI 기능을 넣는 역할을 수행합니다. EXTICR은 1 ~ 4까지 총 4개의 레지스터가 있으며, 설명은 다음과 같습니다.

레지스터 기능 예시 주의
EXTICR1 PA ~ PG의 0 ~ 3핀 중 최대 4개까지
EXTI0 ~ 3에 연결합니다.
PA1을 EXTI1에 연결,
PG2를 EXTI2에 연결,
PG3를 EXTI3에 연결
0번 핀은 EXTI0
1번 핀은 EXTI1
2번 핀은 EXTI2
3번 핀은 EXTI3에 연결됩니다.
EXTICR2 PA ~ PG의 4 ~ 7핀 중 최대 4개까지
EXTI4 ~ 7에 연결합니다.
- -
EXTICR3 PA ~ PG의 8 ~ 11핀 중 최대 4개까지
EXTI8 ~ 11에 연결합니다.
- -
EXTICR4 PA ~ PG의 12 ~ 15핀 중 최대 4개까지
EXTI12 ~ 15에 연결합니다.
- -

 

각각의 핀들을 해당되는 EXTI에 연결하기 위해서는 EXTICR를 다음과 같이 설정해야합니다.

(다음은 PG6를 EXTI6에 연결하기 위하여 EXTICR2의 설정 방법을 나타낸 것 입니다.)

그림 09 (191 page)

AFIO -> EXTICR2 = (6 << 8);   
/*
  EXTI6에 PG6를 연결합니다.
  
  EXTI6는 8 ~ 11번에 위치해 있기 때문에 8이라는 숫자를 사용한 것입니다.
  PG핀은 0110 즉, 십진수로 6이기 때문에 6이라는 숫자를 사용한 것입니다.
*/

 

 

EXTI


우리가 지금까지한 것을 정리하면 다음과 같습니다.

1. PG6을 EXTI로 사용하기 위해 Alternate function input에 RCC를 이용하여 Clock을 인가했습니다.

2. PG6에 EXTI6를 연결하기 위해 AFIO의 EXTICR2를 이용했습니다.

 

그 다음으로 할 일은 EXTI를 언제 발생시킬지 즉, Rising, Falling, Change 중 어느 상태에서 발생시킬지를 결정하고, EXTI를 받아들일 준비를 하는 것입니다.

 

STM32에는 EXTI의 FTSR, RTSR 두 개의 레지스터로 Falling, Rising을 결정합니다. 만약 EXTI6을 Falling에서만 동작시키고 싶다면 FTSR의 6번째 비트를 1로 설정하면 됩니다.

EXTI -> FTSR |= (1 << 6);
// EXTI6를 사용할 것이기 때문에 6이라는 숫자를 사용합니다.

EXTI -> RTSR &= ~(1 << 6);
// Rising 모드는 off합니다.

 EXTI6을 Rising에서만 동작시키고 싶다면 RTSR의 6번째 비트를 1로 설정하면 됩니다.

EXTI -> RTSR |= (1 << 6);
// EXTI6를 사용할 것이기 때문에 6이라는 숫자를 사용합니다.

EXTI -> FTSR &= ~(1 << 6);
// Falling 모드는 off합니다.

 

Change모드로 사용하고 싶다면 RTSR, FRSR 둘 다를 바꾸면 됩니다.

 

 

 

그 다음으로 할 일은 EXTI6을 받아들일 준비를 하는 작업입니다. (활성화와 다릅니다.)

간단하게 EXTI의 IMR 레지스터 중 6번째 비트를 1로 설정하면 됩니다.

EXTI -> IMR |= (1 << 6);

 

 

 

NVIC


마지막 작업은 설정이 끝난 EXTI6를 인터럽트를 관리하는 NVIC라는 장치에 알려 최종 활성화하는 작업입니다.

NVIC는 STM32만의 특성이 아닌, STM32의 Architecture인 Cortex M3의 특성이기 때문에 관련된 자료는 Reference manual가 아닌 Programming manual에 있습니다.

NVIC에서 Interrupt를 설정하기 위해선 Reference manual의 Interrupt vector table (그림 03 198 ~ 200 page) 또는 그림 04를 다시 볼 필요가 있습니다.

 

그림 04

가장 왼쪽에 있는 Position을 보시면 숫자들이 있는 것을 알 수 있습니다. Priority와는 다른 개념으로 해당 Interrupt의 고유 번호입니다. 

우리가 사용하는 EXTI6 자체에 대한 건 없지만, EXTI9_5에 EXTI6가 포함됩니다. 즉, EXTI9 ~ 5 중 하나가 발생되면 EXTI9_5 interrupt가 발생되는 거죠. EXTI6의 고유 번호는 23입니다. 이를 잘 기억해 두세요.

 

 

NVIC에 ISER0을 이용하여 EXTI6를 활성화할 수 있습니다.

그림 10 (Programming manual 120 page)

EXTI6의 고유 번호는 23입니다. EXTI6를 활성화하기 위해서는 NVIC ISER0의 23번째 비트를 1로 설정하면 됩니다. 

 

NVIC_ISER0 |= (1 << 23);
/*

  EXTI6를 활성화하기 위해 EXTI6를 다룰 수 있는 인터럽트인 EXTI9_5의 고유 번호 23을 이용합니다.
  
  다른 레지스터와 다른게 '->'를 사용하지 않는 이유는 레지스터의 주소가 일관되지 않기 때문에,
  하나하나 다음과 같이 지정해준 것입니다.


(cortex_m3 -> nvic.h에서)

#define NVIC_ICER0       *(volatile uint32_t *) 0xE000E180
#define NVIC_ICER1       *(volatile uint32_t *) 0xE000E184

*/

 

 

여기가지가 인터럽트 설정에 관한 것이었습니다.

설정 과정은 다음과 같습니다.

1. PG6을 EXTI로 사용하기 위해 Alternate function input에 RCC를 이용하여 Clock을 인가했습니다.

2. PG6에 EXTI6를 연결하기 위해 AFIO의 EXTICR2를 이용했습니다.

3. EXTI6를 설정을 하고, 받아들이기 위해 EXTI의 FTSR, RTSR, IMR을 이용했습니다.

4. EXTI6를 활성화하기 위해 NVIC ISER0의 23번째 비트를 1로 설정했습니다.

#include "inc.h"


int main(void) {
  
  RCC -> APB2ENR |= (1 << IOPGEN) | (1 << IOPFEN) | (1 << AFIOEN);        // GPIOG, F에 클럭을 인가합니다. AFIO에 클럭을 인가합니다.
  
  GPIOG -> CRL |= (8 << MODE6);      // PG6를 pull-up/pull-down 모드로 설정합니다.
  GPIOG -> ODR |= (1 << 6);      // PG6을 pull-up 모드로 설정합니다. (Open103Z 보드에 Pull-up 모드를 사용하도록 설계 되어 있습니다.)
  GPIOF -> CRL |= (1 << MODE6);  //PF6을 Push-pull 모드로 설정합니다.
  
  AFIO -> EXTICR2 |= (6 << 8);   // EXTI6에 PG6를 연결합니다.
  
  EXTI -> FTSR |= (1 << 6);   // Falling edge에서 EXTI6을 발생시킵니다.
  EXTI -> RTSR &= ~(1 << 6);            // 그 어떤 것도 Raising edge에서 인터럽트를 발생시키지 않습니다.
  EXTI -> IMR |= (1 << 6);    // EXTI6을 활성화 시킵니다.
  
  NVIC_ISER0 |= (1 << 23);    //NVIC에 EXTI6을 연결합니다.
  
  
  
  while(true);          //인터럽트는 동작해야하기 때문에 프로그램을 종료하지 않습니다.
  
  return 0;
}

 

여기가지 했다면 90% 완료된 것입니다.

나머지는 Handler 코드를 작성하고, 필요한 파일을 다운 받아 설정하는 10%입니다.

 

 


Continue

 


 

 

 

반응형