Skip to content

02. Interrupt#

GPIO Interrupt Example (GPIO Interrupt를 사용하여 Switch를 눌렀을 때 LED Toggle)#

  1. Swtich를 눌렀을 때 발생한 Interrupt가 LED를 Toggle시키는 예제이다.
  2. 새로운 예제를 위한 프로젝트를 생성한다.
  3. 원하는 동작을 위해 레지스터와 메모리에 직접 접근해 값을 써야한다.
  4. GPIO Interrupt를 사용하기 위해 Datasheet를 분석한다.
  5. 분석 결과를 활용해 임베디드 프로그래밍을 한다.

1. Datasheet 분석#

Easy Module Shield V1의 SW1을 사용하기 위해, PTD2를 사용한다.

1-1. MSCR#

PTDm을 제어하기 위한 MSCRn 번호는 \(\rm n=(m+96)\)로 구할 수 있다.
MSCRn의 주소는 \(\rm SIUL2\ base + MSCR\ offset+ (n \times 4)\)로 구할 수 있다.
MSCR98 주소: 4029_03C8h (4029_0000h + 240h + 188h)

1-2. IMCR#

eirq_sss

S32K312는 외부 인터럽트를 사용할 때 IMCRn의 SSS 비트를 EIRQ로 설정할 필요가 있다. PTD2의 경우 IMCR538의 SSS 비트를 0011b로 설정해야 한다.

imcr 계산법

하지만 IMCR538이라는 레지스터는 없다. 512의 offset이 적용된 상태이므로 실제로는 \(538-512=26\)이므로 IMCR26에 접근하면 된다.

imcr 주소

IMCRn의 주소는 \(\rm SIUL2\ base + IMCR\ offset+ (n \times 4)\)로 구할 수 있다.
IMCR26 주소: 4029_0AA8h (4029_0000h + A40h + 68h)

imcr 구조

IMCR26의 SSS 비트에 앞서 확인한 값(0011b)을 쓰면 된다.

1-3. 인터럽트 리퀘스트 번호#

exint diagram
req num1
req num2

위에서 IMCR 비트를 확인할 때 EIRQ[10]이라는 번호도 확인할 수 있는데, 이는 REQ[10]에 해당하므로 1번 채널에 해당한다.

1-4. 인터럽트 관련 SIUL2 레지스터#

exint init-1
exint init-2

  1. IFER (Interrupt Filter Enable Register) 를 set해 글리치(노이즈) 필터를 켠다.
  2. DIRER (DMA/Interrupt Request Enable Register) 를 clear해 인터럽트를 차단한다.
  3. IREER (Interrupt Rising-edge Event Enable Reigster) 또는 IFEER (Interrupt Falling-edge Event Enable Register) 을 set해 인터럽트 발생 조건을 설정한다. 실습 보드에서 스위치를 누르면 falling-edge가 발생하므로 본 예제에서는 IFEER0을 설정한다.
  4. MSCR의 OBE를 clear하고 IBE를 set해 입력으로 설정한다.
  5. DIRSR (DMA/Interrupt Request Select Register) 을 clear해 interrupt 모드로 설정한다. (set시 DMA 모드)
  6. 필터 관련 설정은 생략한다.
  7. DISR (DMA/Interrupt Status flag Register) 을 set해 인터럽트 플래그를 clear한다. (w1c 속성이므로 set해서 clear)
  8. DIRER을 set해 인터럽트를 활성화한다.

siul2 int reg 주소

설정에 필요한 레지스터 주소 오프셋은 위와 같다.

1-5. NVIC 레지스터#

  • NVIC 모듈에서 사용하고자 하는 interrupt의 우선순위를 정하고 enable해주어야 interrupt를 사용할 수 있다.
  • 각 모듈의 interrupt에 대한 설정 위치(메모리 주소)가 정해져 있기 때문에 사용하고자 하는 interrupt를 선택적으로 설정할 수 있다.

NVIC

  • NVIC 레지스터의 종류
이름 설명
ISER(Interrupt Set-Enable Register) 레지스터의 각 비트는 32개의 인터럽트 중 하나에 해당하고, 특정 비트를 set하면 해당 인터럽트가 활성화된다.
ISER(Interrupt Clear-Enable Register) 레지스터의 각 비트는 32개의 인터럽트 중 하나에 해당하고, 특정 비트를 set하면 해당 인터럽트가 비활성화된다.
ISER(Interrupt Set-Pending Register) 레지스터의 각 비트는 32개의 인터럽트 중 하나에 해당하고, 특정 비트를 set하면 해당 인터럽트를 pending state로 만든다.
ISER(Interrupt Clear-Pending Register) 레지스터의 각 비트는 32개의 인터럽트 중 하나에 해당하고, 특정 비트를 set하면 해당 인터럽트를 pending state에서 벗어나게 한다.
IABR(Interrupt Active Bit Registers) 어떤 인터럽트가 활성화 상태인지 판별하기 위해 읽는 비트다. 인터럽트가 활성화되어 있으면 1, 아니면 0이다.
IPR(Interrupt Priority Registers) 레지스터의 각 바이트는 해당 인터럽트의 우선순위를 결정한다.
  • NVIC 레지스터 주소

arm cortex m7 nvic 이미지 출처

위의 표에서 각 레지스터의 시작 주소를 알 수 있고, 아래의 방법으로 m번째 인터럽트를 제어하는 레지스터 번호를 구할 수 있다.

IPR을 제외한 레지스터 번호 구하기
IxxRn: \(\rm n=floor(m\div32)\)
해당 레지스터에서 각 인터럽트를 제어하는 비트 번호 구하기
\(\rm bit=n\ mod\ 32\)
IPR 레지스터 번호 구하기
IPRn: \(\rm n=m\)

인터럽트 번호를 구하기 위해서는 프로젝트 폴더 내 Startup_Code 폴더에 있는 startup_ARMCM7.c 파일을 확인할 필요가 있다.

int start
siul handler

[1.1.3에서 설명한 내용]에서 1번 채널 인터럽트를 사용해야 하므로, SIUL_1_Handler를 사용해야 한다.
0번 인터럽트가 267행이고, 사용하고자 하는 인터럽트 핸들러 SIUL_1_Handler가 321행이므로 \(\rm 321-267 = 54\)번 인터럽트임을 알 수 있다. 따라서

NVIC_ISER1 주소: E000_E104h (E000_E100h + 4h)
NVIC_ICPR1 주소: E000_E284h (E000_E280h + 4h)
NVIC_IPR54 주소: E000_E436h (E000_E400h + 36h) (54=36h)

2. 프로그래밍#

2-1. 매크로 정의#

// SIUL2 essential Registers
#define SIUL2_BASE (0x40290000UL)
#define SIUL2_MSCR_BASE (SIUL2_BASE + 0x240)
#define SIUL2_MSCR74 (*(volatile unsigned *)(SIUL2_MSCR_BASE + 0x128))
#define SIUL2_MSCR98 (*(volatile unsigned *)(SIUL2_MSCR_BASE + 0x188))

#define SIUL2_IMCR_BASE (SIUL2_BASE + 0xA40)
#define SIUL2_IMCR26 (*(volatile unsigned *)(SIUL2_IMCR_BASE + 0x68))

#define SIUL2_GPDO_BASE (SIUL2_BASE + 0x1300)
#define SIUL2_GPDO74 (*(volatile unsigned char *)(SIUL2_GPDO_BASE + 0x49))

// MSCR & IMCR Bits
#define OBE_BIT 21 // Output Buffer Enable
#define IBE_BIT 19 // Input Buffer Enable
#define SSS_BITS 0 // Source Signal Select

// SIUL2 Interrupt Registers
#define SIUL2_DISR0 (*(volatile unsigned *)(SIUL2_BASE + 0x10))
#define SIUL2_DIRER0 (*(volatile unsigned *)(SIUL2_BASE + 0x18))
#define SIUL2_DIRSR0 (*(volatile unsigned *)(SIUL2_BASE + 0x20))
#define SIUL2_IREER0 (*(volatile unsigned *)(SIUL2_BASE + 0x28))
#define SIUL2_IFEER0 (*(volatile unsigned *)(SIUL2_BASE + 0x30))
#define SIUL2_IFER0 (*(volatile unsigned *)(SIUL2_BASE + 0x38))

// SIUL2 Interrupt Register Bits
#define EIRQ10 10
#define IFE10 10
#define EIRE10 10
#define IREE10 10
#define IFEE10 10
#define DIRS10 10
#define EIF10 10

// Arm Cortex-M7 NVIC Registers
#define NVIC_ISER1 (*(volatile unsigned *)(0xE000E100 + 0x4))
#define NVIC_ICPR1 (*(volatile unsigned *)(0xE000E280 + 0x4))
#define NVIC_IPR54 (*(volatile unsigned char *)(0xE000E400 + 0x36))

2-2. PORT_init()#

  • Switch, LED 포트 설정을 위해 PORT_init() 함수를 구현한다.
    void PORT_init(void) {
      SIUL2_MSCR74 &= ~(0b1111 << SSS_BITS); // GPIO
      SIUL2_MSCR74 |= (1 << OBE_BIT);        // output
    
      SIUL2_MSCR98 &= ~(0b1111 << SSS_BITS); // GPIO
      SIUL2_MSCR98 |= (1 << IBE_BIT);        // input
    
      SIUL2_IMCR26 &= ~(0b1111 << SSS_BITS);
      SIUL2_IMCR26 |= (0b0011 << SSS_BITS); // EIRQ10
    }
    
    PTC10(MSCR74)는 출력으로, PTD2(MSCR98 & IMCR26)은 입력 및 EIRQ로 설정한다.

2-3. SIUL2_init_exint()#

  • 인터럽트를 위한 SIUL2 레지스터 설정 함수를 구현한다.
    void SIUL2_init_exint(void) {
      // 1. Set IFER to enable glitch filter
      SIUL2_IFER0 &= ~(1 << IFE10);
      SIUL2_IFER0 |= (1 << IFE10);
    
      // 2. clear DIRER to disable interrupt
      SIUL2_DIRER0 &= ~(1 << EIRE10);
    
      // 3. write 1 to IFEER0 to set pin polarity
      SIUL2_IFEER0 |= (1 << IFEE10);
    
      // 5. write DIRSR0 to select DMA or interrupt
      SIUL2_DIRSR0 &= ~(1 << DIRS10); // 0: Interrupt
    
      // 7. write 1 to DISR0 to clear any flag
      SIUL2_DISR0 |= (1 << EIF10); // w1c
    
      // 8. set DIRER0 to enable interrupt
      SIUL2_DIRER0 |= (1 << EIRE10);
    }
    

2-4. NVIC_init_IRQs()#

  • 54번째 인터럽트에 대한 NVIC 설정 함수를 구현한다.
    void NVIC_init_IRQs(void) {
      NVIC_ICPR1 |= (1 << (54 % 32));
      NVIC_ISER1 |= (1 << (54 % 32));
      NVIC_IPR54 |= (10 << 4);
    }
    

2-5. main#

  • 위에서 작성한 함수들을 호출한다.
    int main(void) {
    
      SIUL2_init_exint();
      NVIC_init_IRQs();
    
      while (1);
    
      return 0;
    }
    

2-6. ISR#

  • 인터럽트 발생 시 호출될 인터럽트 핸들러를 작성한다.
    void SIUL_1_Handler(void) {
      SIUL2_GPDO74 = SIUL2_GPDO74 ? 0 : 1;
      SIUL2_DISR0 |= (1 << EIF10); // w1c
    }
    
    인터럽트가 발생할 때마다 PTC10을 토글하는 코드이다. ISR이 끝나기 전에 DISR(플래그 레지스터)에 1을 써 flag를 clear해주지 않으면 인터럽트를 다시 발생시킬 수 없으므로, 반드시 마지막에 flag를 클리어해 주어야 한다.

3. 동작 모습#

led on led off

빌드, 디버그 후 동작시켰을 때 SW1을 누를 떄마다 Red LED가 토글됨을 확인할 수 있다.