04 인터럽트 제어(Interrupt Control)

원문 :  http://kkamagui.springnote.com/pages/422069

 

들어가기 전에...

 

0.시작하면서

 이번에는 NDS의 인터럽트(Interrupt)에 대해서 알아보자. NDS은 꽤나 많은 주변장치를 가지고 있기 때문에 이 장비들에 대해서 효율적으로 데이터를 받고 또 처리해야 한다. 장비에서 데이터를 얻어오는 방식은 폴링(Polling)을 통한 방법이나 인터럽트(Interrupt)를 통한 방법, 두가지가 있을 수 있는데, 인터럽트를 통한 방법이 좀 더 효율적이다.

 

1.NDS의 인터럽트 레지스터(Interrupt Register) 설정

 NDS의 Interrupt에 대한 내용은 http://nocash.emubase.de/gbatek.htm#dsinterrupts에서 찾을 수 있다.

4000208h - IME - Interrupt Master Enable Register (R/W)

Bit   Expl.
  0     Disable all interrupts         (0=Disable All, 1=See IE register)
  1-15  Not used
인터럽트를 완전히 "가능" 하게 하거나 "불가능" 하게 하거나 하는 레지스터이다. 

 

4000210h - IE - 32bit - Interrupt Enable (R/W)
4000214h - IF - 32bit - Interrupt Request Flags (R/W)

Bit 0     LCD V-Blank                    (0=Disable)
  Bit 1     LCD H-Blank                    (etc.)
  Bit 2     LCD V-Counter Match            (etc.)
  Bit 3     Timer 0 Overflow               (etc.)
  Bit 4     Timer 1 Overflow               (etc.)
  Bit 5     Timer 2 Overflow               (etc.)
  Bit 6     Timer 3 Overflow               (etc.)
  Bit 7     NDS7 only: SIO/RCNT/RTC (Real Time Clock)
Bit 8     DMA 0                          (etc.)
  Bit 9     DMA 1                          (etc.)
  Bit 10    DMA 2                          (etc.)
  Bit 11    DMA 3                          (etc.)
  Bit 12    Keypad                         (etc.)
  Bit 13    Game Pak (external IRQ source) (etc.)
  Bit 14-15 Not used
  Bit 16    IPC Sync
  Bit 17    IPC Send FIFO Empty
  Bit 18    IPC Recv FIFO Not Empty
  Bit 19    Game Card Data Transfer Completion
  Bit 20    Game Card IREQ_MC
  Bit 21    NDS9 only: Geometry Command FIFO
  Bit 22    NDS7 only: Screens unfolding
  Bit 23    NDS7 only: SPI bus
  Bit 24    NDS7 only: Wifi
  Bit 25-31 Not used
Raw TCM-only IRQs can be processed even during DMA ?
For the "Same as GBA" Bits, see

Interrupt Control

 위에서 보면 온갖 디바이스가 다 달려있는 것을 알 수 있다. ARM9과 ARM9에서만 사용가능한 플래그도 있는데, 이는 ARM9과 ARM7에 연결된 주변장치가 다르기 때문에 그렇다. 인터럽트를 가능하게 하려면 아래와 같은 단계를 거쳐야 한다.

  • Interrupt Master Enable 레지스터를 1로 설정해서 인터럽트가 가능하도록 설정해야 한다.
  • Interrupt Enable 레지스터의 해당 비트를 1로 설정해서 해당 디바이스의 Interrupt가 발생가능하도록 설정해야 한다.
  • Interrupt가 발생된 후라면, Interrupt Request 레지스터에 해당 비트를 1로 설정해서 해당 디바이스의 인터럽트가 다시 발생하도록 설정해야 한다. 이 과정을 빼먹으면 다시는 해당 디바이스의 인터럽트가 발생하지 않는다.

 

2.인터럽트 핸들러(Interrupt Handler) 설정

 원래 정상적인 경우라면 인터럽트 벡터 테이블(Interrupt Vector Table)을 생성하여 원하는 인터럽트에 대해서 핸들러를 등록하고 이를 처리하는 과정을 거치게 된다. 하지만 NDS의 펌웨어(firmware)에 의해 부팅이 되고 나면 ARM7 및 ARM9의 BIOS 코드가 0xFFFF0000(ARM9), 0x00000000(ARM7)의 위치에 로딩되게 된다.

  • ARM9인터럽트 벡터 테이블은 ARM9 BIOS가 시작하는 0xFFFF0000 위치에 존재하게 되며, IRQ handler는 BIOS에 의해 DTCM+0x3FFC의 위치에 맵핑되게 된다.
  • ARM7 : 인터럽프 벡터 테이블은 ARM7 BIOS가 시작하는 0x00000000 위치에 존재하게 되며, IRQ handler는 BIOS에 의해 0x3FFFFFC나 0x380FFFC에 맵핑하게 된다.

 

DTCM+3FFCh - NDS9 IRQ Handler (hardcoded RAM address)
380FFFCh - NDS7 IRQ Handler (hardcoded RAM address)

Bit 0-31  Pointer to IRQ Handler

NDS7 Handler must use ARM code, NDS9 Handler can be ARM/THUMB (Bit0=Thumb).

 ARM9의 경우는 Firmware에 의해 0xFFFF0000 영역에 로딩된 BIOS가 있다. 인터럽터가 발생하면 일단 이 BIOS의 인터럽트 핸들러가 불리고 그후 DTCM+0x3FFC에 설정된 주소로 점프를 하는 것 같다. 따라서 DTCM+0x3FFC에 함수 주소를 넣어두면 인터럽트를 처리할 수 있다. ARM7의 경우도 마찬가지다.

 

3.인터럽트 요청 완료(Interrupt Request Complete) 설정

 일반적인 경우라면 인터럽트가 완료되고 나면 IR 레지스터에 해당 인터럽트의 비트를 1로 설정해 주는 것으로 끝난다. 하지만 NDS의 경우, firmware의 기능을 그대로 사용하려면 firmware에서 사용하는 별도의 IRQ Check 비트를 1로 설정해 줘야 한다.

DTCM+3FF8h - NDS9 IRQ Check Bits (hardcoded RAM address)
380FFF8h - NDS7 IRQ Check Bits (hardcoded RAM address)

Bit 0-31  IRQ Flags (same format as IE/IF registers)
When processing & acknowleding interrupts via IF register, the user interrupt handler should also set the corresponding bits of the IRQ Check value (required for BIOS IntrWait and VBlankIntrWait SWI functions).

 BIOS의 함수들을 함께 사용하려면 위의 영역에 값들도 1로 같이 설정해 줘야 한다. 그리 어려운 부분은 아니므로 꼭 설정해줘서 BIOS를 활용하도록 하자.

 

4.디버그(Debug) 관련 설정

 디버그 관련 핸들러와 스택설정이 있는데, 굳이 할 필요는 없을 것 같으므로 넘어간다.

--- Below for other (non-IRQ) exceptions ---

27FFD9Ch - RAM - NDS9 Debug Stacktop / Debug Vector (0=None)
380FFDCh - RAM - NDS7 Debug Stacktop / Debug Vector (0=None)
These addresses contain a 32bit pointer to the Debug Handler, and, memory below of the addresses is used as Debug Stack. The debug handler is called on undefined instruction exceptions, on data/prefetch aborts (caused by the protection unit), on FIQ (possibly caused by hardware debuggers). It is also called by accidental software-jumps to the reset vector, and by unused SWI numbers within range 0..1Fh.

 

5.구현

 이제 인터럽트를 직접 한번 처리해 보자. 우리가 해야 할 일은 아래와 같다.

  • IME의 값을 0으로 설정한다. 즉 모든 인터럽트를 비활성화 한다.
  • IR 레지스터에 비트를 1로 설정한다. 즉 원하는 디바이스의 인터럽트를 활성화 한다.
  • IRQ 핸들러 함수를 등록한다.
  • 해당 디바이스가 인터럽트 발생을 위해 설정을 필요로하면 디바이스 컨트롤을 설정해 준다.(ex 키패드, LCD Display 등등)
  • IR 레지스터의 모든 비트를 1로 설정하여 Clear 해준다.
  • IME의 값을 1로 설정한다. 즉 모든 인터럽트를 활성화 한다.
  • IRQ 핸들러에서 발생한 인터럽트를 검사하여 처리한다.
  • IR 레지스터 및 BIOS의 IRQ Check 영역에 해당 비트를 1로 설정해 준다.
  • 인터럽트의 처리를 반복한다.

 위의 내용만 순차적으로 처리해 준다면 인터럽트 처리를 문제없이 할 수 있다.

 

5.1 매크로 정의

 그럼 일단 인터럽트 관련 매크로를 한번 보자. devkitPro\libnds\include\nds 폴더에 가면 interrupt.h 파일이 있다. ARM7 및 ARM9의 공통적인 인터럽트 관련 정보를 가지고 있는데, 위의 IME/IE/IR 등등과 같은 매크로가 아래와 같이 정의되어 있다.

  1.  /*! \enum IRQ_MASKS
     \brief values allowed for REG_IE and REG_IF
  2. */
    enum IRQ_MASKS {
     IRQ_VBLANK   = BIT(0),  /*!< vertical blank interrupt mask */
     IRQ_HBLANK   = BIT(1),  /*!< horizontal blank interrupt mask */
     IRQ_VCOUNT   = BIT(2),  /*!< vcount match interrupt mask */
     IRQ_TIMER0   = BIT(3),  /*!< timer 0 interrupt mask */
     IRQ_TIMER1   = BIT(4),  /*!< timer 1 interrupt mask */
     IRQ_TIMER2   = BIT(5),  /*!< timer 2 interrupt mask */
     IRQ_TIMER3   = BIT(6),  /*!< timer 3 interrupt mask */
     IRQ_NETWORK   = BIT(7),  /*!< serial interrupt mask */
     IRQ_DMA0   = BIT(8),  /*!< DMA 0 interrupt mask */
     IRQ_DMA1   = BIT(9),  /*!< DMA 1 interrupt mask */
     IRQ_DMA2   = BIT(10), /*!< DMA 2 interrupt mask */
     IRQ_DMA3   = BIT(11), /*!< DMA 3 interrupt mask */
     IRQ_KEYS   = BIT(12), /*!< Keypad interrupt mask */
     IRQ_CART   = BIT(13), /*!< GBA cartridge interrupt mask */
     IRQ_IPC_SYNC  = BIT(16), /*!< IPC sync interrupt mask */
     IRQ_FIFO_EMPTY  = BIT(17), /*!< Send FIFO empty interrupt mask */
     IRQ_FIFO_NOT_EMPTY = BIT(18), /*!< Receive FIFO empty interrupt mask */
     IRQ_CARD   = BIT(19), /*!< interrupt mask */
     IRQ_CARD_LINE  = BIT(20), /*!< interrupt mask */
     IRQ_GEOMETRY_FIFO = BIT(21), /*!< geometry FIFO interrupt mask */
     IRQ_LID    = BIT(22), /*!< interrupt mask */
     IRQ_SPI    = BIT(23), /*!< SPI interrupt mask */
     IRQ_WIFI   = BIT(24), /*!< WIFI interrupt mask (ARM7)*/
     IRQ_ALL    = (~0)
    };
  3. /*! \def REG_IE
  4.     \brief Interrupt Enable Register.
  5.  This is the activation mask for the internal interrupts.  Unless
     the corresponding bit is set, the IRQ will be masked out.
    */
    #define REG_IE (*(vuint32*)0x04000210)
    /*! \def REG_IF
  6.     \brief Interrupt Flag Register.
  7.  Since there is only one hardware interrupt vector, the IF register
     contains flags to indicate when a particular of interrupt has occured.
     To acknowledge processing interrupts, set IF to the value of the
     interrupt handled.
  8. */
    #define REG_IF (*(vuint32*)0x04000214)
  9. /*! \def REG_IME
  10.     \brief Interrupt Master Enable Register.
  11.  When bit 0 is clear, all interrupts are masked.  When it is 1,
     interrupts will occur if not masked out in REG_IE.
  12. */
    #define REG_IME (*(vuint16*)0x04000208) 
  13. #define VBLANK_INTR_WAIT_FLAGS  *(__irq_flags)
    #define IRQ_HANDLER             *(__irq_vector) 

 좀 특이한 것은 마지막에 잇는 IRQ_HANDLERVBLANK_INTR_WAIT_FLAGS라는 부분인데, 특정 주소가 되어있는 것이 아니라 왠 변수 값으로 되어있다. 저 값은 링커 스크립트에서 찾을 수 있는데, \devkitPro\arm-eabi\lib 폴더에 ds_arm9.ld를 열면 아래와 같은 부분이 있다(자세한 내용은 07 링커 스크립트(Linker Script) 부분을 참고하자).

  1.  MEMORY {
  2.  rom : ORIGIN = 0x08000000, LENGTH = 32M
     ewram : ORIGIN = 0x02000000, LENGTH = 4M - 4k
     dtcm : ORIGIN = 0x0b000000, LENGTH = 16K
     itcm : ORIGIN = 0x01000000, LENGTH = 32K
    }
  3. __itcm_start = ORIGIN(itcm);
    __ewram_end = ORIGIN(ewram) + LENGTH(ewram);
    __eheap_end = ORIGIN(ewram) + LENGTH(ewram);
    __dtcm_start = ORIGIN(dtcm);
    __dtcm_top = ORIGIN(dtcm) + LENGTH(dtcm);
    __irq_flags = __dtcm_top - 0x08;
    __irq_vector = __dtcm_top - 0x04;

 위의 값을 계산하면 __irq_flags의 값은 dtcm + 0x4000(16Kbyte) - 0x8이고 __irq_vector의 값은 dtcm + 0x4000(16Kbyte) - 0x4의 주소를 가리키는 것을 알 수 있다.

 

5.2 코드

 매크로를 사용하면 아래와 같이 간단하게 코딩을 할 수 있다.

  1. #include <nds.h>
  2. /**
        IRQ를 설정한다.
    */
    void SetIrq( void )
    {
        // Master Disable
        REG_IME = 0;
       
        // All Flag Clear
        REG_IE = IRQ_VBLANK;//| IRQ_KEYS | IRQ_IPC_SYNC;
       
        // IRQ Handler를 등록한다.
        IRQ_HANDLER = IrqHandler;
       
        // Display에서 VBlank interrupt를 발생시키도록 한다.
        REG_DISPSTAT |= DISP_VBLANK_IRQ;
       
        // Key Interrupt를 발생하도록 한다.
        //REG_KEYCNT = 0x7FFF;
  3.     // 모든 IR의 값을 초기화
  4.     REG_IF = ~0;
  5.     VBLANK_INTR_WAIT_FLAGS = ~0;
  6.     // Master Enable
  7.     REG_IME = 1;
    }
  8. /**
        IRQ를 처리하는 Handler
    */
    void IrqHandler( void )
    {
        // VBlank 발생
        if(REG_IF & IRQ_VBLANK)
        {
            REG_IF |= IRQ_VBLANK;
            VBLANK_INTR_WAIT_FLAGS |= IRQ_VBLANK;
            g_ucVBlankCount++;
        }
       
        // IRQ_KEYS 발생
        if(REG_IF & IRQ_KEYS)
        {
            REG_IF |= IRQ_KEYS;
            VBLANK_INTR_WAIT_FLAGS |= IRQ_KEYS;
            g_ucKeysCount++;
        }
       
        // IRQ_IPC_SYNC 발생
        if(REG_IF & IRQ_IPC_SYNC)
        {
            REG_IF |= IRQ_IPC_SYNC;
            VBLANK_INTR_WAIT_FLAGS |= IRQ_IPC_SYNC;
            g_ucIPCCount++;
        }
    }

 주의할 점은 인터럽트 핸들러는 void XXX( void ) 형식이라는 것이다. 인터럽트 핸들러는 아무것도 돌려주지 않으며 아무것도 받지 않는다. 핸들러를 잘못 만들면 프로그램이 죽을 수도 있으니 주의한다.

 

6.마치며...

 지금까지 NDS의 인터럽트에 대해서 알아봤다. 인터럽트 처리에 대한 전체적은 구조를 알아 보았으니 이제 각 컨트롤러에 대해서만 알면 해당 컨트롤러 or 디바이스로 부터 데이터를 즉시 받아서 처리할 수 있다.

 또 다시 NDS의 세계로 빠져보자. @0@)/~

 

 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

+ Recent posts