10 타이머(Timer) 제어

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

 

들어가기 전에...

 

0.시작하면서...

 타이머는 일정 시간이 지나면 그것을 알려주는 역할을 하는 아주 간단한 장치이다. 아주 간단하지만 OS에서, 특히 시분할 스케줄링을 하는 OS에서는 스케줄링의 시작점이자 프로세스 퍼포먼스 분석의 기초자료가 되는 아주 중요한 역할을 한다. 또한 주기적으로 해야하는 작업의 경우 타이머를 사용해서 하면 편리하게 할 수 있다.

NDS의 타이머에 대해서 자세히 알아보자.

 

1.타이머 레지스터(Timer Register)

 NDS의 타이머에 대한 내용은 http://nocash.emubase.de/gbatek.htm#dstimers에서 자세히 볼 수 있다. NDS에서는 ARM9 4개, ARM7 4개 총 8개의 타이머를 가지고 있으며 타이머의 클럭은 F = 33.514 MHz 이다.

 GBA와 클럭만 다르고 나머지는 동일하므로 GBA의 타이머에 대한 설명을 보도록 하자.

The GBA includes four incrementing 16bit timers.
Timer 0 and 1 can be used to supply the sample rate for DMA sound channel A and/or B.


4000100h - TM0CNT_L - Timer 0 Counter/Reload (R/W)
4000104h - TM1CNT_L - Timer 1 Counter/Reload (R/W)
4000108h - TM2CNT_L - Timer 2 Counter/Reload (R/W)
400010Ch - TM3CNT_L - Timer 3 Counter/Reload (R/W)
Writing to these registers initializes the <reload> value (but does not directly affect the current counter value). Reading returns the current <counter> value (or the recent/frozen counter value if the timer has been stopped).
The reload value is copied into the counter only upon following two situations: Automatically upon timer overflows, or when the timer start bit becomes changed from 0 to 1.
Note: When simultaneously changing the start bit from 0 to 1, and setting the reload value at the same time (by a single 32bit I/O operation), then the newly written reload value is recognized as new counter value.

 Counter/Reload 레지스터는 16bit 크기를 가지는 레지스터로써, 읽을 때는 현재 카운터의 값을 리턴하고 쓸 때는 타이머 만료 카운트를 설정하는데 사용된다. 주의할 점은 쓸 때(Reload) 만료 카운트의 값이 바로 적용되는 것이 아니라 아래의 타이머 컨트롤(Timer Control) 레지스터의 7Bit의 값이 0 -> 1로 바뀌거나 타이머가 만료되었을 때 내부적으로 복사되어서 사용되는 점이다. 만료 카운트를 즉시 적용시키려면 값을 쓴 다음 타이머를 중지했다가 다시 시작하면 된다.

 만료 카운터를 설정할때 값을 양수로 넣으면 안되고 음수로 넣어야 한다. 즉 타이머 카운팅이 100회되면 만료된다는 것을 설정하고 싶다면 -100을 넣어야 한다. 이 점을 특히 주의해야 한다.

 

 만약 직접 값을 설정하고 싶다면 매크로를 사용하지 않고 직접 쓸 수 있다. 만약 타이머가 10회 카운트 되면 인터럽트가 발생하도록 설정하려면, 타이머의 값이 16bit이므로 Max 값은 0xFFFF가 된다. 이 값이 0x10000으로 되는 순간 overflow가 발생해서 인터럽트가 발생하므로 따라서 0x10000 - 10의 값을 설정하면 차례로 카운팅 되어 10회 카운트 된 다음 인터럽트가 발생하게 된다. 

 

4000102h - TM0CNT_H - Timer 0 Control (R/W)
4000106h - TM1CNT_H - Timer 1 Control (R/W)
400010Ah - TM2CNT_H - Timer 2 Control (R/W)
400010Eh - TM3CNT_H - Timer 3 Control (R/W)

Bit   Expl.
  0-1   Prescaler Selection (0=F/1, 1=F/64, 2=F/256, 3=F/1024)  2     Count-up Timing   (0=Normal, 1=See below)
  3-5   Not used
  6     Timer IRQ Enable  (0=Disable, 1=IRQ on Timer overflow)
  7     Timer Start/Stop  (0=Stop, 1=Operate)
  8-15  Not used
When Count-up Timing is enabled, the prescaler value is ignored, instead the time is incremented each time when the previous counter overflows. This function cannot be used for Timer 0 (as it is the first timer).
F = System Clock (16.78MHz). <== GBA의 클럭

 위의 타이머 컨트롤러의 플래그를 보면 만료가 되었을 때 인터럽트를 발생하게 하여 알려주는 옵션이 있다. 이것을 이용하여 인터럽트 발생 시 즉시 어떤 일을 수행하게 하면 타이밍에 민감한 프로그램도 작성할 수 있다.

 프리스케일러(Prescaler)의 경우 타이머 클럭을 특정 값으로 나누어 타이머의 분해능을 선택하는 역할을 한다. 0을 선택한 경우는 1초에 33.514M 번 타이머가 증가하지만 1을 선택한 경우는 1초에 33.514/64번 발생하는 것이다. 자신의 용도에 맞게 잘 사용하면 된다.

 Count-up Timing 옵션이 있는데 이 옵션을 사용하면 프리스케일러 값은 무시되고 바로 이전의 카운터(타이머 1의 경우는 타이머 0, 타이머 2의 경우는 타이머 1...)의 overflow가 발생할 때마다 카운트가 증가하게 된다. 단 타이머 0의 경우는 이전 타이머가 없기 때문에 해당되지 않는다.

 

2.구현

 타이머에 대한 구현은 devkitPro\libnds\source\include\nds 폴더에 timers.h 파일에서 찾을 수 있다.

  1. // Max frequency is: 33554432Hz\n
    // Min frequency is: 512Hz\n
  2. // 0x2000000는 33554432와 같다.
  3. #define TIMER_FREQ(n)    (-0x2000000/(n)) 
  4. #define TIMER_FREQ_64(n)  (-(0x2000000>>6)/(n))
  5. #define TIMER_FREQ_256(n) (-(0x2000000>>8)/(n))
  6. #define TIMER_FREQ_1024(n) (-(0x2000000>>10)/(n))

  7. //! Same as %TIMER_DATA(0).
    #define TIMER0_DATA    (*(vuint16*)0x04000100)
    #define TIMER1_DATA    (*(vuint16*)0x04000104)
    #define TIMER2_DATA    (*(vuint16*)0x04000108)
    #define TIMER3_DATA    (*(vuint16*)0x0400010C)
  8. #define TIMER_DATA(n)  (*(vuint16*)(0x04000100+((n)<<2)))
  9. // Timer control registers
    //! Same as %TIMER_CR(0).
    #define TIMER0_CR   (*(vuint16*)0x04000102)
    #define TIMER1_CR   (*(vuint16*)0x04000106)
    #define TIMER2_CR   (*(vuint16*)0x0400010A)
    #define TIMER3_CR   (*(vuint16*)0x0400010E)
  10. #define TIMER_CR(n) (*(vuint16*)(0x04000102+((n)<<2)))
  11. #define TIMER_ENABLE    (1<<7)
  12. //! Causes the timer to request an Interupt on overflow.
    #define TIMER_IRQ_REQ   (1<<6)
  13. //! When set will cause the timer to count when the timer below overflows (unavailable for timer 0).
    #define TIMER_CASCADE   (1<<2)
  14. //! Causes the timer to count at 33.514Mhz.
    #define TIMER_DIV_1     (0)
    //! Causes the timer to count at (33.514 / 64) Mhz.
    #define TIMER_DIV_64    (1)
    //! Causes the timer to count at (33.514 / 256) Mhz.
    #define TIMER_DIV_256   (2)
    //! Causes the timer to count at (33.514 / 1024)Mhz.
    #define TIMER_DIV_1024  (3)

 타이머의 주소와 비트 값에 대한 간단한 매크로로 되어있는 것을 알 수 있다.

 

 그럼 이제 실제로 사용하는 예제를 보자. 아주 간단한데 타이머를 1/1000초 즉 ms 단위로 튀게 하려면 아래와 같이 하면된다. ㅡ,.ㅡ;;;

  1. TIMER0_DATA = TIMER_FREQ( 1000 );
  2. // 타이머의 시작
  3. TIMER0_CRTIMER_ENABLE | TIMER_IRQ_REQ | TIMER_DIV_1;
  4. ...... 생략 ......
  5. // 타이머의 종료
  6. TIMER0_CR &= ~TIMER_ENABLE;

 

 

3.마치면서...

 이상으로 타이머에 대해 알아보았다. 워낙 간단해서 따로 설명할 것도 없었는데... 사용법 정도만 알아놓자.

 

 

 

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

09 NDS 홈브루(Homebrew) - KKAMAGUI Defence Tower

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

 

들어가기 전에...

 

0.버전관리

  • 2007/11/09 01:26:03 : NDS에서 테스트 결과 세로 16칸은 너무 넓어서 12칸으로 수정함

 

0.시작하면서...

 Defence Tower 스타일의 게임은 인터넷에서 흔히 구할 수 있다. 특히 플레쉬 게임으로 많이 나와있는데, http://www.xgenstudios.com/game.php?keyword=xenotactic 와 같은 게임이 대표적인 Defence Tower 스타일의 게임이라 할 수 있다.

 NDS의 홈브루 게임 중에서도 Defence Tower 류의 게임을 몇개 구할 수 있는데, 게임을 하다 흥미가 생겨서 하나 만들어 보기로 했다.

 

1.지도(Map) 디자인

 NDS의 화면은 256 * 192 Pixel의 듀얼로 구성되어있다. 위쪽 화면은 상태를 표시하는 화면으로 사용하고, 아래쪽 화면은 게임 진행 및 타워 설치 등등으로 사용하도록 하자. 그럼 이제 화면을 적절히 나누어서 최소 타일로 설정해야 하는데, 하나의 타일 크기가 적의 크기가 되도록 하자.

 일단 최소 단위 블럭은 12 pixel * 12 pixel로 정했고 이것을 NDS의 한 화면으로 나누면 대충 아래와 같은 21 개 * 16 개의 Block이 나온다. 아래는 최외각을 블럭으로 쌓고 그 외의 공간을 중립 공간으로 설정한 맵의 구성이다.

 Defence1.PNG

<Map Design>

 

 이제 최소 블럭 하나씩을 할당해서 적(Enemy)와 타워(Tower)를 할당한 후, 각 타워의 공격 범위(Sight)를 표시하면 아래와 같다.

 Defence2.PNG

<적과 타워 설치>

 

 위의 그림은 공격범위가 3인 타워와 공격범위가 2인 타워를 나타낸다. 각 타워는 해당 범위내에 있는 적을 검색해서 적절한 타이밍에 총을 발사하면 될 것이다.

 

2.전체 디자인

 게임의 전체적인 구성은 아래와 같이 되어있다.

 클래스다이어그램2.PNG

<클래스 다이어그램>

 게임의 주체는 Tower, Bullet, Enemy의 3가지로 구성되어있다.

  • Tower : 타워. 유저가 설치하여 적(Enemy)를 막도록 하는 구조물
  • Bullet : 타워가 적을 향해 발사하는 총알
  • Enemy : 유저를 괴롭히는 적

 

 매 Tick 마다 각각의 주체는 적을 검색하고 총알을 쏘고 타워를 피하는 등등의 각자 맡은 일을 한다. 전략의 경우는 여러가지가 있을 수 있기 때문에 Strategy 패턴을 이용하여 쉽게 교체가 가능하도록 했다(전략부분에 대해서는 아래의 알고리즘 항목을 참고하도록 하자).

 MapUtil 클래스를 통해서 Map에 접근함으로써 여기저기 중복될 수 있는 맵 관련 함수를 한 곳에 모았다. 게임의 전체적인 진행은 GameMain 클래스가 맡고 있으며 외부에서 GameMain 클래스를 통해서 게임을 진행하고 현재 진행 상태를 판단하게 된다.

 

3.알고리즘(Algorithm)

3.1 타워 알고리즘(Tower Algorithm)

 타워의 알고리즘은 아주 간단하다. 타워의 발사 속도 및 범위를 이용해서 주변의 적을 검색하고 주변에 적이 있을 경우 총을 발사하면 된다. 발사 한 후에는 일정시간 이상 대기한 후 다시 발사하면 된다.

 타워의 종류 또한 여러가지 있을 수 있으나, 일단 지금은 단발식 타워만 고려하고 업그레이드가 가능하도록 구현하였다.

 

3.2 적 알고리즘(Enemy Algorithm)

 적의 알고리즘은 약간 까다롭다. 적의 레벨에 따라서 여러 알고리즘이 존재할 수 있는데, 아래와 같은 요건을 고려하였다.

  • 무조건 시작점에서 출구점 방향(왼쪽에서 오른쪽)으로 이동해야 한다.
  • 벽 및 타워는 통과할 수 없다.
  • 적은 타워의 영향권을 가끔 벗어날 수 있다.

    • 저 레벨의 적은 타워의 발사 범위 영향을 거의 고려하지 않는다.
    • 고 레벨의 적은 타워의 발사 범위 영향을 많이 고려한다.

 글로 쓸려니 약간 추상적인데, 코드로 보면 간단하다. 테스트 방법 및 코드는 아래를 참고하자.

 

3.3 적 알고리즘 테스트

 MFC를 이용해서 간단히 알고리즘을 테스트하는 프로그램을 제작했다. 직접 눈으로 보면서 프로그래밍하면 더 나을 것 같아서 테스트 프로그램을 만들었다.

SmileWar1.png

<알고리즘 테스트 화면>

 각 ASCII 값이 나타내는 의미는 아래와 같다.

  • "1" : 타워. 실제 NDS로 포팅된 후에는 "T"로 바뀜
  • "X" : 적
  • "@" : 벽
  • 붉은색 점 : 타워에서 발사된 총알

 

 프로그램의 흰 바탕 위에 클릭을 하면 적절히 타워("1")이 설치되고 시작을 누르면 시뮬레이션이 시작된다. 더블 버퍼링을 하지 않았기 때문에 눈이 아프다는 큰 문제가... ㅜ_ㅜ

 테스트 프로그램은 알고리즘을 테스트하기 위한 프로그램이고, 추후 알고리즘을 추가하고 싶을 때 먼저 테스트 한 후 NDS로 포팅하면 된다. 테스트 프로그램은 아래 첨부 파일에 올려 놓았다.

 

3.4 알고리즘 구성 방법

 적(Enemy)는 다양한 알고리즘으로 구성되어야 하므로 Strategy 패턴을 이용하여 구성하였다. 아래는 전체 클래스 다이어그램에서 전략 알고리즘 부분을 표시한 부분이다.

클래스다이어그램1.PNG

<클래스다이어그램-Strategy 패턴>

 현재 3가지 타입의 전략이 있는데, 목표를 향해 직선으로 이동하고 중간에 장애물이 있을 때, 아래 위로 살짝 피하는 MoveStraightStrategy를 필두로 하여, 그것을 상속받아서 조금 수정한 AvoidTowerStrategy, 그리고 멍청하게 행동하는 FoolishStrategy가 있다.

 전략은 적이 생성될 때 랜덤하게 선택된다.

 

3.5 알고리즘 추가 및 테스트

 알고리즘은 아래의 CStrategy를 상속받아서 DeterminDirection() 함수를 구현해 주기만 하면 끝난다.

  1. #define DIRECTION_UP    1
    #define DIRECTION_DOWN  2
    #define DIRECTION_LEFT  3
    #define DIRECTION_RIGHT 4
  2.  

  3. /**
        움직임을 결정하는 전략
    */
    class CStrategy
    {
    protected:
        bool IsCanGo( int iX, int iY );
  4. public:
        CStrategy( void );
        virtual ~CStrategy( void );
       
        virtual int DetermineDirection( int iX, int iY, int iLastDirection ) = 0;
    };

 리턴값으로 DIRECTION_UP 과 같은 방향 매크로를 넘겨주면 그 방향으로 이동한다. iLastDirection에 이전에 움직였던 방향값이 넘어옴으로 이를 잘 활용하면 여러가지 패턴을 만들 수 있다. 실제 구현 예제는 위를 상속받은 구체 Strategy 클래스를 참고하도록 하자.

 

3.NDS 포팅

3.1 NDS 개발 환경 및 포팅

 NDS에 윈도우 라이브러리가 구현되어있으므로(물론 내가 만들었다. 자세한 내용은 02 NDS 윈도우 시스템(Windows System)을 참고하자) 위에서 설명한 알고리즘 테스트 프로그램의 소스를 크게 수정없이 사용할 수 있다. 차이라면 개인적인 취향 때문에 함수 몇개가 덜 구현되거나 스타일이 조금 다르다는 정도...?

 아무것도 없는 허허벌판(??)에서 윈도우 라이브러리를 사용하기가 쉽지 않은데, 그래서 템플릿으로 사용할 수 있는 프로젝트를 올려놓았다. 26 윈도우 라이브러리(Window Library) 사용을 위한 프로젝트(Project) 만들기에서 프로젝트 파일을 찾을 수 있다. libfat도 같이 사용하도록 되어있으니 필요없는 사람은 makefile을 수정해서 빼도록하자(libfat를 빼니 홈브루 크기가 120K 정도 줄어든것 같다. @0@ 이럴수가!!! ).

 포팅에 대한 내용은 크게 다루지 않을 것이며 궁금한 사람은 Diff 프로그램으로 위의 테스트 프로그램과 NDS로 포팅된 소스의 내용을 비교해 보면 알 수 있을 것이다. 메인 소스는 거의 바뀌지 않았음을 알 수 있다. 이 얼마나 행복한 일인가... ㅜ_ㅜ

 

3.2 실행 화면 및 게임 방법

 게임 방법은 아주 간단하다. 타워를 클릭하면 설치하는 데, 일단 설치된 타워에 인접해서 설치는 불가능하다는 조건만 명심하면 된다(이 조건으로 인해 게임 난이도가 살짝 올라갔다. ㅜ_ㅜ). 즉 대충 놓으면 결과적으로 화면에 놓을 수 있는 타워의 개수도 적어지고 게임이 일찍 끝날 확률이 높다.

  • Select 버튼 : 게임을 처음부터 다시 시작
  • Start 버튼 : 펌웨어 화면으로 이동
  • 빈곳에 터치 스크린 클릭 :  Money가 5 이상 있을 때 타워 설치. 단 인접한 곳에 타워가 없어야 함
  • 타워에 터치스크린 클릭 : Money가 20이상 있을 때 타워 업그레이드. 타워는 3단까지 업그레이드 가능

 

SmileWar3.PNG SmileWar2.png

<게임을 실행한 화면>

 

4.마치면서...

 디펜스 타워에 빠져서 개발하게 된 홈브루 치고는 좀 허술한데... 실제로 플레이 해보니 나름 할만해서 만족하고 있다. 추후 업그레이드는 별로 고려하지 않고 있지만, 적의 알고리즘이나 타워 설치 부분은 아쉬운 부분이 많아서 업그레이드 할지도...

 매번 프로그램을 작성하면서 느끼는 것이지만 정말 UI에 소질이 없는 것 같다. 프로그램 작성하는 시간보다 그림 그리는데 시간이 더 많이드니... ㅡ_ㅡa... 이거 원 산출이 안맞아서... ㅜ_ㅜ..

 심심하다면 한번쯤 다운 받아서 해보자~ @0@)/~

 

5.첨부

 

 

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

08 사운드(Sound) 제어

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

 

들어가기 전에...

 

1.채널 컨트롤 레지스터(Channel Control Register)

 NDS에는 16개의 사운드 채널이 존재하며 각 채널들은 독립적인 소리를 낼 수 있다. 스피커로 출력되는 소리는 각 채널에서 나는 소리의 합이다.

 IO 블럭은 첫번째 사운드 레지스터들이 0x4000400에서 0x400040F까지 존재하며 16번째 블럭은 0x40004F0에서 0x40004FF까지 존재한다. 사운드 레지스터에 대해서는 http://nocash.emubase.de/gbatek.htm#dssound 에서 자세히 볼 수 있다.

 위의 링크에 문서를 읽어보면 스피커가 2개이기 때문에 스테레오 사운드를 제공할 수 있다고 되어있다. 그런데 아래에 레지스터를 보면 스테레오 모드라는 것이 없다. 어떻게 스테레오를 구현하는 걸까? 곰곰히 생각해보니 스테레오로 Play 하려면 2개의 좌/우 스피커용 파일을 만들고 Panning을 이용해서 각각 넣는게 아닌가 하는 생각이 들었다. 실제로 그렇게 하는지는 의문이지만 가능은 하다는거 @0@)/~!!

 

40004x0h - ARM7 - SOUNDxCNT - Sound Channel X Control Register (R/W)

Bit0-6    Volume       (0..127=silent..loud)
  Bit7      Not used     (always zero)
  Bit8-9    Data Shift   (0=Normal, 1=Div2, 2=Div4, 3=Div16)
  Bit10-14  Not used     (always zero)
  Bit15     Hold         (0=Nothing, 1=Hold)               (?)
  Bit16-22  Panning      (0..127=left..right) (64=half volume on both speakers)
  Bit23     Not used     (always zero)
  Bit24-26  Wave Duty    (0..7) ;HIGH=(N+1)*12.5%, LOW=(7-N)*12.5% (PSG only)
  Bit27-28  Repeat Mode  (0=Manual, 1=Loop Infinite, 2=One-Shot, 3=Prohibited)
  Bit29-30  Format       (0=PCM8, 1=PCM16, 2=IMA-ADPCM, 3=PSG/Noise)
  Bit31     Start/Status (0=Stop, 1=Start/Busy)
All channels support ADPCM/PCM formats, PSG rectangular wave can be used only on channels 8..13, and white noise only on channels 14..15.

 위에서 보면 몇가지 재미있는 옵션들을 볼 수 있다. Panning 모드를 사용하면 스피커 한쪽에서 소리를 내는 것이 가능하며, Repeat 모드를 사용하면 계속해서 소리를 출력할 수 있다. Format 같은 경우 좀 특이하게 PCM/ADPCM/PGS-Noise 모드를 지원한다. 소리를 출력하기위한 포맷이 굳이 PCM이 아니어도 가능하다는 말이다.

 PSG라는 말이 나오는데, 아무래도 Pulse Sound Generator의 의미인것 같고 사인파 같은 파형을 출력하는 것 같다.

 

40004x4h - ARM7 - SOUNDxSAD - Sound Channel X Data Source Register (W)

Bit0-26  Source Address
  Bit27-31 Not used

 사운드 소스 레지스터로써 출력할 사운드가 어디에 위치하는지 주소를 넣어준다.

 

40004x8h - ARM7 - SOUNDxTMR - Sound Channel X Timer Register (W)

  Bit0-15  Timer Value, Sample frequency, timerval=-(16777216 / freq)

 The PSG Duty Cycles are composed of eight "samples", and so, the frequency for Rectangular Wave is 1/8th of the selected sample frequency.
For PSG Noise, the noise frequency is equal to the sample frequency.

 16777216의 값은 0x1000000와 같고 타이머 레지스터에 설정되어야 하는 값은 저 값을 -0x1000000의 값을 Frequency로 나눈 값으로 설정해야 한다. Frequency의 값은 11025와 같은 실제 Hz의 값을 넣어줘야 한다.

 

40004xAh - ARM7 - SOUNDxPNT - Sound Channel X Loopstart Register (W)

Bit0-15  Loop Start, Sample loop start position

 Loop로 반복할 때 시작 위치를 정해줄 수 있는 것 같은데... 자세한 건 테스트를 해봐야 겠다.

 

40004xCh - ARM7 - SOUNDxLEN - Sound Channel X Length Register (W)
The number of samples for N words is 4*N PCM8 samples, 2*N PCM16 samples, or 8*(N-1) ADPCM samples (the first word containing the ADPCM header). The Sound Length is not used in PSG mode.

Bit0-21  Sound length (counted in words, ie. N*4 bytes)
  Bit22-31 Not used
Minimum length is 4 words (16 bytes), smaller values (0..3 words) are interpreted as zero length (no sound output).

 Sound Length 레지스터에 입력되는 값은 Word 단위이다. ARM에서 Word 단위는 4Byte이므로 8bit Sample 같은 경우는 전체 길이를 4로 나누어줘야 하고 16bit의 Sample 같은 경우는 2로 나누어줘야 한다.

 

2.사운드 컨트롤 레지스터(Sound Control Register)

 각 채널별 설정이 아닌 전체에 적용되는 마스터의 성격을 가지는 레지스터이다.

4000500h - SOUNDCNT - ARM7 - Sound Control Register (R/W)

Bit0-6   Master Volume          (0..127=silent..loud)
  Bit7     Not used               (always zero)
  Bit8-9   Left Out      (probably selects Mixer or "Bypassed" channels?)
  Bit10-11 Right Out     (probably selects Mixer or "Bypassed" channels?)
  Bit12    Output Sound Channel 1 (0=To Mixer, 1=Bypass Mixer)
  Bit13    Output Sound Channel 3 (0=To Mixer, 1=Bypass Mixer)
  Bit14    Not used               (always zero)
  Bit15    Master Enable          (0=Disable, 1=Enable)

4000504h - SOUNDBIAS - ARM7 - Sound Bias Register (R/W)
Bit0-9   Sound Bias    (0..3FFh, usually 200h)
  Bit10-31 Not used      (always zero)
After applying the master volume, the signed left/right audio signals are in range -200h..+1FFh (with medium level zero), the Bias value is then added to convert the signed numbers into unsigned values (with medium level 200h).

The sampling frequency of the mixer is 1.04876 MHz with an amplitude resolution of 24 bits, but the sampling frequency after mixing with PWM modulation is 32.768 kHz with an amplitude resolution of 10 bits.

  그냥 살짝 볼 부분은 Master Enable과 Master Volume 부분인 것 같다. 나머지 부분은 그리 중요하지 않은 듯 하므로 패스~

 

3.구현

 사운드 부분은 ARM7에만 연결되어있으므로, 테스트를 위해 ARM7 코드를 손보거나 아니면 라이브러리를 통해 간접적으로 테스트 해야 한다. 소스가 간단하니 굳이 만들 필요는 없을 것 같고 libnds의 소스를 보도록 하자. 사운드 레지스터 관련 헤더파일은 \devkitPro\libnds\source\include\nds\arm7 폴더에 audio.h 파일을 보면 된다.


  1. #define SOUND_VOL(n) (n)
    #define SOUND_FREQ(n) ((-0x1000000 / (n)))
    #define SOUND_ENABLE BIT(15)
    #define SOUND_REPEAT    BIT(27)
    #define SOUND_ONE_SHOT  BIT(28)
    #define SOUND_FORMAT_16BIT (1<<29)
    #define SOUND_FORMAT_8BIT (0<<29)
    #define SOUND_FORMAT_PSG    (3<<29)
    #define SOUND_FORMAT_ADPCM  (2<<29)
    #define SOUND_16BIT      (1<<29)
    #define SOUND_8BIT       (0)
  2. #define SOUND_PAN(n) ((n) << 16)
  3. #define SCHANNEL_ENABLE BIT(31)
  4. //---------------------------------------------------------------------------------
    // registers
    //---------------------------------------------------------------------------------
    #define SCHANNEL_CR(n)    (*(vuint32*)(0x04000400 + ((n)<<4)))
    #define SCHANNEL_VOL(n)    (*(vuint8*)(0x04000400 + ((n)<<4)))
    #define SCHANNEL_PAN(n)    (*(vuint8*)(0x04000402 + ((n)<<4)))
    #define SCHANNEL_SOURCE(n)   (*(vuint32*)(0x04000404 + ((n)<<4)))
    #define SCHANNEL_TIMER(n)   (*(vint16*)(0x04000408 + ((n)<<4)))
    #define SCHANNEL_REPEAT_POINT(n) (*(vuint16*)(0x0400040A + ((n)<<4)))
    #define SCHANNEL_LENGTH(n)   (*(vuint32*)(0x0400040C + ((n)<<4)))
  5. #define SOUND_CR          (*(vuint16*)0x04000500)
    #define SOUND_MASTER_VOL  (*(vuint8*)0x04000500)
  6. //---------------------------------------------------------------------------------
    // not sure on the following
    //---------------------------------------------------------------------------------
    #define SOUND_BIAS        (*(vuint16*)0x04000504)
    #define SOUND508          (*(vuint16*)0x04000508)
    #define SOUND510          (*(vuint16*)0x04000510)
    #define SOUND514    (*(vuint16*)0x04000514)
    #define SOUND518          (*(vuint16*)0x04000518)
    #define SOUND51C          (*(vuint16*)0x0400051C)

 앞부분에서 설명했던 레지스터의 주소를 매크로로 정의해 놓은 것을 알 수 있다.

 실제 사용된 코드 부분은 \devkitPro\libnds\source\basicARM7\source 폴더의 defaultARM7.c 파일에서 볼 수 있다.

  1. //---------------------------------------------------------------------------------
    void startSound(int sampleRate, const void* data, u32 bytes, u8 channel, u8 vol,  u8 pan, u8 format) {
    //---------------------------------------------------------------------------------
     SCHANNEL_TIMER(channel)  = SOUND_FREQ(sampleRate);
     SCHANNEL_SOURCE(channel) = (u32)data;
     SCHANNEL_LENGTH(channel) = bytes >> 2 ; <== 4byte 즉 word 단위 값으로 넣기위해 4로 나누는 부분
     SCHANNEL_CR(channel)     = SCHANNEL_ENABLE | SOUND_ONE_SHOT | SOUND_VOL(vol) | SOUND_PAN(pan) | (format==1?SOUND_8BIT:SOUND_16BIT);
    }

  2. //---------------------------------------------------------------------------------
    s32 getFreeSoundChannel() {
    //---------------------------------------------------------------------------------
     int i;
     for (i=0; i<16; i++) {
      if ( (SCHANNEL_CR(i) & SCHANNEL_ENABLE) == 0 ) return i;
     }
     return -1;
    }
  3. //---------------------------------------------------------------------------------
    void VblankHandler(void) {
    //---------------------------------------------------------------------------------
  4.  u32 i;

  5.  //sound code  :)
     TransferSound *snd = IPC->soundData;
     IPC->soundData = 0;
  6.  if (0 != snd) {
  7.   for (i=0; i<snd->count; i++) {
       s32 chan = getFreeSoundChannel();
  8.    if (chan >= 0) {
        startSound(snd->data[i].rate, snd->data[i].data, snd->data[i].len, chan, snd->data[i].vol, snd->data[i].pan, snd->data[i].format);
       }
      }
     }
  9. }

 위에서 보면 채널 레지스터 설정 시에 타이밍, 소스, 길이 모두 설정한 뒤에 채널 설정 레지스터를 설정하는 것을 볼 수 있다. 타이밍, 소스, 길이 레지스터를 설정하는 순서는 별 의미 없어보이나 채널 설정 레지스터를 설정하는 것은 가장 마지막에 해야 한다.

 

4.마치며...

 이제 사운드도 출력할 수 있게 되었다. 사운드 정보를 PCM으로 바꾸면 간단히 Play할 수 있으므로 examples 폴더에 있는 사운드 예제를 한번 돌려보자.

 

 

 

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

08 NDS 홈브루(Homebrew) - KKAMAGUI NDS Shell

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


들어가기 전에...

0.시작하면서...

 NDS 사용자라면 문쉘(MoonShell)을 모르는 사람이 없을 것이다. 스킨 기능과 동영상 재생기능, MP3 Play기능 및 파일 보기 기능 등등~ 상당히 많은 기능들을 포함하고 있는 무시무시한 쉘이다. 시간 날때마다 문쉘의 소스를 뒤져보고 있지만, 어떻게 이렇게 복잡한 소스를 관리하는지 의문이 들 정도로 소스가 빼곡히 들어 차있다.

 KKAMAGUI NDS Shell은 문쉘의 기능에서 필요한 기능만 추려내고 내가 필요한 기능을 넣어서 사용하기위한 간단한 NDS용 쉘(Shell) 프로그램이다. 문쉘이 비하면 아직 기능이 많이 부족하지만, 시간 날때마다 짬짬히 기능을 추가하고 있으니 언젠가는 문쉘처럼 될 수 있을 것이라 생각한다. ^^;;;

 그럼 이제부터 KKAMAGUI NDS Shell에 대해서 알아보자.


1.프로그램 기능

 오늘(2007/11/01 05:38:07)까지 구현된 기능은 아래와 같다.

  • 듀얼 스크린 기능 : R 키를 통해 상하 스크린을 플립할 수 있는 기능. A키를 이용하여 Program Manager를 이동시켜 두 화면 모두 프로그램을 실행할 수 있음
  • 파일 브라우징 기능 및 텍스트 뷰어 기능: File & Text Viewer를 이용하여 파일 리스트를 표시하고 L 키와 파일 리스트의 항목을 클릭함으로써 파일을 볼 수 있는 기능
  • 한글 출력 기능 : 윈도우 타이틀 및 텍스트 뷰어에 한글을 출력하는 기능. 유니코드는 아직 미지원
  • 달력 기능 : 현재 날짜와 시간을 표시하는 달력 기능
  • 리셋 기능 : 홈브루를 종료하고 Firmware로 이동하는 기능. 자세한 내용은 06 소프트웨어 리셋 라이브러리(Software Reset Library)의 문서 참고

2.프로그램 컴파일 및 링크 방법

 KKAMAGUI NDS Shell은 FAT Library, Window Library, Reset Library를 사용한다. 각각의 library에 대한 내용은 아래를 참고하여 설치하면 된다.

  • FAT Library : libfat는 devkitPro를 사용하면 기본으로 깔려있기 때문에 굳이 손댈 필요는 없음. 다만 링크 후에 실행하기위해서는DLDI 패치를 수행해야 함. 새로 libfat를 컴파일해서 DLDI를 하는 불편함을 줄이고 싶은 사람은 01 libfat 업그레이드 문서를 참고
  • Window Library : 자작한 윈도우 라이브러리. NDS에서 MFC와 같은 스타일의 윈도우 프로그래밍을 하기위해서 만듬. 02 NDS 윈도우 시스템(Windows System) 문서를 참고
  • Reset Library : 문쉘(MoonShell)의 Reset.MSE 플러그인을 분석해서 만든 소프트웨어 리셋 라이브러리. 06 소프트웨어 리셋 라이브러리(Software Reset Library) 문서를 참고

 위의 라이브러리중 하나라도 빠지면 컴파일 또는 링크가 되지 않으므로 빠짐없이 설치하도록 하자.


3.소스 구성

 KKAMAGUI NDS Shell의 대부분의 소스는 윈도우 라이브러리(Window Library)의 기본 윈도우와 리스트 윈도우, 스킨 윈도우를 상속받아서 커스터마이징(Customizing)하는 형태로 만들어졌다. 거의 윈도우 라이브러리를 테스트하기위한 프로젝트라고 해도 과언이 아닌데, 소스를 전부 설명하기는 어려우므로 첨부에 있는 소스 파일을 참고하도록 하자.

 윈도우 라이브러리 사용에 대한 자세한 내용은 추후에 별도의 문서로 올릴 예정이니, 구조만 참고하면 될 듯하다. ^^;;;


4.사용방법 및 실행화면

 버튼별 역할은 아래와 같다.

  • R 버튼:

    • 스크린 전환 기능. 상하 LCD를 바꿔서 모든 스크린을 터치로 제어 가능하게 만듬
  • L 버튼:

    • File & Text Viewer에서 파일 or 디렉토리 선택 모드로 전환
    • 선택 모드 사용 시 리스트의 항목 중에 터치 스크린으로 클릭된 부분이 붉은 색으로 변하고 터치를 때면 해당 항목의 파일이 Text Viewer로 표시되거나 해당 디렉토리로 이동
  • 화면 터치:

    • 윈도우 타이틀을 터치하는 경우 윈도우 이동
    • 닫기 버튼인 경우 윈도우 닫기. 리스트 윈도우의 경우 스크롤 또는 항목 선택
    • Text Viewer의 경우 스크롤
  • A 버튼:

    • Program Manager 표시
    • 화면 전환이 일어나서 Program Manager를 표시해야 하거나 Program Manager를 닫은 경우 다시 화면 가운데 표시

 아래는 실행화면이다.

 KNS1.PNGKNS2.pngKNS3.png

<실행화면>


5.마치면서...

 몇가지 기능밖에 없는 허접한 쉘(Shell)이지만 NDS에서 쉘을 만들려고 고민하는 사람들에게는 좋은 예제라 생각한다. 앞으로 MP3 Play 정도는 추가할 예정인데, 어떻게 될지 모르겠다. ^^;;;;


6.첨부




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

07 문쉘(Moon shell)의 터치스크린(Touch Screen) 소스

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

 

들어가기 전에...

 

0.시작하면서...

 libnds의 터치스크린 소스는 값이 튀는 문제가 있다. 물론 소프트웨어 적으로 처리하는 방법이 있지만 완전하지 못해서 가장 널리 쓰이는 문쉘(Moon shell)의 소스를 받아서 터치스크린쪽 소스를 봤다. http://mdxonline.dyndns.org/archives/nds/에 가면 NDS의 문쉘소스를 받을 수 있다.

 

 실제로 이 소스를 이용해서 완전하게 터치스크린의 문제를 해결한 소스는 참고. 터치스크린(Touch Screen)의 튐 현상 해결방안 문서를 참고하면 된다.

 

1.libnds의 touch.c

 \devkitPro\libnds\source\source\arm7 폴더의 touch.c 파일을 열어보면 굉장히 복잡한 소스들이 널려있다. 그중에서 터치 스크린에서 값을 읽는 핵심적인 소스만 추리면 아래와 같다.

  1. static bool touchInit = false;
    static s32 xscale, yscale;
    static s32 xoffset, yoffset;
  2. //---------------------------------------------------------------------------------
    int16 readTouchValue(uint32 command, int16 *dist_max, u8 *err){
    //---------------------------------------------------------------------------------
     int16 values[5];
     int32 aux1, aux2, aux3, dist, dist2, result = 0;
     u8 i, j, k;
  3.  *err = 1;
  4.  SerialWaitBusy();
  5.  REG_SPICNT = SPI_ENABLE | SPI_BAUD_2MHz | SPI_DEVICE_TOUCH | SPI_CONTINUOUS;
     REG_SPIDATA = command;
  6.  SerialWaitBusy();
  7.  for(i=0; i<5; i++){
      REG_SPIDATA = 0;
      SerialWaitBusy();
  8.   aux1 = REG_SPIDATA;
      aux1 = aux1 & 0xFF;
      aux1 = aux1 << 16;
      aux1 = aux1 >> 8;
  9.   values[4-i] = aux1;
  10.   REG_SPIDATA = command;
      SerialWaitBusy();
  11.   aux1 = REG_SPIDATA;
      aux1 = aux1 & 0xFF;
      aux1 = aux1 << 16;
  12.   aux1 = values[4-i] | (aux1 >> 16);
      values[4-i] = ((aux1 & 0x7FF8) >> 3);
     }
  13.  REG_SPICNT = SPI_ENABLE | SPI_BAUD_2MHz | SPI_DEVICE_TOUCH;
     REG_SPIDATA = 0;
     SerialWaitBusy();
  14.  dist = 0;
     for(i=0; i<4; i++){
      aux1 = values[i];
  15.   for(j=i+1; j<5; j++){
       aux2 = values[j];
       aux2 = abs(aux1 - aux2);
       if(aux2>dist) dist = aux2;
      }
     }
  16.  *dist_max = dist;
  17.  for(i=0; i<3; i++){
      aux1 = values[i];
  18.   for(j=i+1; j<4; j++){
       aux2 = values[j];
       dist = abs(aux1 - aux2);
  19.    if( dist <= range ){
        for(k=j+1; k<5; k++){
         aux3 = values[k];
         dist2 = abs(aux1 - aux3);
  20.      if( dist2 <= range ){
          result = aux2 + (aux1 << 1);
          result = result + aux3;
          result = result >> 2;
          result = result & (~7);
  21.       *err = 0;
  22.       break;
         }
        }
       }
      }
     }
  23.  if((*err) == 1){
      result = values[0] + values[4];
      result = result >> 1;
      result = result & (~7);
     }
  24.  return (result & 0xFFF);
    }
  25. //---------------------------------------------------------------------------------
    // reading pixel position:
    //---------------------------------------------------------------------------------
    touchPosition touchReadXY() {
    //---------------------------------------------------------------------------------
  26.  int16 dist_max_y, dist_max_x, dist_max;
     u8 error, error_where, first_check, i;
  27.  touchPosition touchPos = { 0, 0, 0, 0, 0, 0 };
  28.  if ( !touchInit ) {
  29.   xscale = ((PersonalData->calX2px - PersonalData->calX1px) << 19) / ((PersonalData->calX2) - (PersonalData->calX1));
      yscale = ((PersonalData->calY2px - PersonalData->calY1px) << 19) / ((PersonalData->calY2) - (PersonalData->calY1));
  30.   xoffset = ((PersonalData->calX1 + PersonalData->calX2) * xscale  - ((PersonalData->calX1px + PersonalData->calX2px) << 19) ) / 2;
      yoffset = ((PersonalData->calY1 + PersonalData->calY2) * yscale  - ((PersonalData->calY1px + PersonalData->calY2px) << 19) ) / 2;
      touchInit = true;
     }
  31.  uint32 oldIME = REG_IME;
  32.  REG_IME = 0;
  33.  first_check = CheckStylus();
     if(first_check != 0){
      error_where = 0;
  34.   touchPos.z1 =  readTouchValue(TSC_MEASURE_Z1 | 1, &dist_max, &error);
      touchPos.z2 =  readTouchValue(TSC_MEASURE_Z2 | 1, &dist_max, &error);
  35.   touchPos.x = readTouchValue(TSC_MEASURE_X | 1, &dist_max_x, &error);
      if(error==1) error_where += 1;
  36.   touchPos.y = readTouchValue(TSC_MEASURE_Y | 1, &dist_max_y, &error);
      if(error==1) error_where += 2;
  37.   REG_SPICNT = SPI_ENABLE | SPI_BAUD_2MHz | SPI_DEVICE_TOUCH | SPI_CONTINUOUS;
      for(i=0; i<12; i++){
       REG_SPIDATA = 0;
  38.    SerialWaitBusy();
      }
  39.   REG_SPICNT = SPI_ENABLE | SPI_BAUD_2MHz | SPI_DEVICE_TOUCH;
      REG_SPIDATA = 0;
  40.   SerialWaitBusy();
  41.   if(first_check == 2) error_where = 3;
  42.   switch( CheckStylus() ){
      case 0:
       last_time_touched = 0;
       break;
      case 1:
       last_time_touched = 1;
  43.    if(dist_max_x > dist_max_y)
        dist_max = dist_max_x;
       else
        dist_max = dist_max_y;
  44.    break;
      case 2:
       last_time_touched = 0;
       error_where = 3;
  45.    break;
      }
  46.   s16 px = ( touchPos.x * xscale - xoffset + xscale/2 ) >>19;
      s16 py = ( touchPos.y * yscale - yoffset + yscale/2 ) >>19;
  47.   if ( px < 0) px = 0;
      if ( py < 0) py = 0;
      if ( px > (SCREEN_WIDTH -1)) px = SCREEN_WIDTH -1;
      if ( py > (SCREEN_HEIGHT -1)) py = SCREEN_HEIGHT -1;
  48.   touchPos.px = px;
      touchPos.py = py;

  49.  }else{
      error_where = 3;
      touchPos.x = 0;
      touchPos.y = 0;
      last_time_touched = 0;
     }
  50.  UpdateRange(&range, dist_max, error_where, last_time_touched);
  51.  REG_IME = oldIME;

  52.  return touchPos;
  53. }

 음.. 일단 소스가 좀 복잡하고 뭔가 잘하려고 노력한것 같은데, 문제가 좀 있어보인다. readTouchValue() 소스를 일단 보면 값을 여러번 읽어서 그 값을 평균내는 것 비슷하게 동작하는 것 같은데...  더 문제는 값을 다 읽고 난 다시 CheckStylus() 함수를 이용해서 터치스크린의 상태를 얻어온다는 점이다.

 만약 위에서 값을 읽었을 때 정상적인 터치스크린의 값이 들어왔는데, 뒤에 CheckStylus() 함수에서 체크할 때는 터치가 떨어진 상태라면...? 약간의 시간차가 있는데 이 시간차 때문에 문제가 있어 보인다(실제로도 문제가 있다. ㅡ,.ㅡ;;;)

 

2.문쉘(Moon Shell)의 _touch.c

 문쉘의 터치스크린 소스는 의외로 간단하다. 전체 소스는 아래와 같다.

  1. #include <nds.h>
    #include <nds/jtypes.h>
    #include <nds/system.h>
    #include <nds/arm7/touch.h>
    #include <nds/registers_alt.h>
  2. #include <stdlib.h>
  3. #include "_touch.h"
  4. //---------------------------------------------------------------------------------
    __attribute__((noinline)) static uint16 _touchRead(uint32 command) {
    //---------------------------------------------------------------------------------
     uint16 result;
     SerialWaitBusy();
  5.  // Write the command and wait for it to complete
     REG_SPICNT = SPI_ENABLE | SPI_BAUD_2MHz | SPI_DEVICE_TOUCH | SPI_CONTINUOUS; //0x0A01;
     REG_SPIDATA = command;
     SerialWaitBusy();
  6.  // Write the second command and clock in part of the data
     REG_SPIDATA = 0;
     SerialWaitBusy();
     result = REG_SPIDATA;
  7.  // Clock in the rest of the data (last transfer)
     REG_SPICNT = SPI_ENABLE | 0x201;
     REG_SPIDATA = 0;
     SerialWaitBusy();
  8.  // Return the result
     return ((result & 0x7F) << 5) | (REG_SPIDATA >> 3);
    }

  9. //---------------------------------------------------------------------------------
    uint32 _touchReadTemperature(int * t1, int * t2) {
    //---------------------------------------------------------------------------------
     *t1 = _touchRead(TSC_MEASURE_TEMP1);
     *t2 = _touchRead(TSC_MEASURE_TEMP2);
     return 8490 * (*t2 - *t1) - 273*4096;
    }

  10. static bool touchInit = false;
    static s32 xscale, yscale;
    static s32 xoffset, yoffset;

  11. //---------------------------------------------------------------------------------
    __attribute__((noinline)) static s32 readTouchValue(int measure, int retry , int range) {
    //---------------------------------------------------------------------------------
     int i;
     s32 this_value=0, this_range;
  12.  s32 last_value = _touchRead(measure | 1);
  13.  for ( i=0; i < retry; i++) {
      this_value = _touchRead(measure | 1);
      this_range = abs(last_value - this_value);
      if (this_range <= range) break;
     }
     
     if ( i == range) this_value = 0;
     return this_value;
  14. }
  15. static int _MaxRetry = 5;
    static int _MaxRange = 30;
  16. void _touchReadXY_AutoDetect(void)
    {
      xscale = ((PersonalData->calX2px - PersonalData->calX1px) << 19) / ((PersonalData->calX2) - (PersonalData->calX1));
      yscale = ((PersonalData->calY2px - PersonalData->calY1px) << 19) / ((PersonalData->calY2) - (PersonalData->calY1));
     
      xoffset = ((PersonalData->calX1 + PersonalData->calX2) * xscale  - ((PersonalData->calX1px + PersonalData->calX2px) << 19) ) / 2;
      yoffset = ((PersonalData->calY1 + PersonalData->calY2) * yscale  - ((PersonalData->calY1px + PersonalData->calY2px) << 19) ) / 2;
     
      touchInit = true;
    }
  17. // reading pixel position:
    //---------------------------------------------------------------------------------
    touchPosition _touchReadXY() {
    //---------------------------------------------------------------------------------
  18.  if(touchInit==false){
       REG_IME=0;
       while(1);
     }
  19.  touchPosition touchPos;
  20. /*
      if((xscale<128)||(yscale<128)||(xoffset<128)||(yoffset<128)){
     touchPos.px = 0;
     touchPos.py = 0;
  21.  return touchPos;
      }
    */
     
     touchPos.x = readTouchValue(TSC_MEASURE_X, _MaxRetry, _MaxRange);
     touchPos.y = readTouchValue(TSC_MEASURE_Y, _MaxRetry, _MaxRange);
  22.  s16 px = ( touchPos.x * xscale - xoffset + xscale/2 ) >>19;
     s16 py = ( touchPos.y * yscale - yoffset + yscale/2 ) >>19;
  23.  if ( px < 0) px = 0;
     if ( py < 0) py = 0;
     if ( px > (SCREEN_WIDTH -1)) px = SCREEN_WIDTH -1;
     if ( py > (SCREEN_HEIGHT -1)) py = SCREEN_HEIGHT -1;
  24.  touchPos.px = px;
     touchPos.py = py;
  25.  return touchPos;
  26. }

 libnds에서 봤던 평균을 낸다던지 하는 소스는 여기서 보이지 않는다. 아주 간단하게 처음 터치 스크린의 값을 읽고 그값과 다음 읽은 값과 비교하여 차이가 적당한 범위 내이면 읽은 값이 정확하다고 판단하여 리턴하는 형식이다. 참으로 간단하면서도 명확한 방법이 아닐 수 없다. 만약 첫번째 읽은 값이 잘못된 값이면 touchPos.x와 touchPos.y의 값이 문제가 있을 것이므로 이것을 이용해서 판단하면 될 듯 하다.

 실제 libnds를 이용해서 touch.x, touch.y의 값을 같이 사용해서 판단하고 있으나 제대로 동작하지 않는데, 문쉘의 방법은 괜찮을 듯 하다(실제로도 문쉘은 잘 동작한다.)

 

3.마치며...

 간단한 방법이 제일 좋은 듯하다. libnds의 경우 여러가지 경우를 너무 생각해서 소스가 복잡하고 제대로 동작하지 않는 것 같다. 나중에 소스를 손봐서 새로 테스트 해봐야 겠다.

 

 

 

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

07 Serial Peripheral Interface(SPI)

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

 

들어가기 전에...

 

 Serial Pheripheral Interface(SPI)는 FULL Duplex로 동작하는 Mototola에서 개발된 버스이다. 자세한 내용은 wikipedia http://en.wikipedia.org/wiki/Serial_peripheral_interface에서 찾을 수 있으며 하나의 Master와 하나 이상의 Slave의 컨트롤러로 이루어진다.

 

1.구성 및 설명

350px-SPI_single_slave.svg.png350px-SPI_three_slaves.svg.png

<Master-Slave SPI>

  좌측과 같이 Master/Slave가 1:1로 연결될 수 있고, 우측과 같이 1:n으로 연결하는 것도 가능하다. NDS의 SPI 컨트롤에서 Chip Select가 있는 것을 보아 다수의 컨트롤러가 묶여있는 것 같다.

 

Interface

The SPI bus specifies four logic signals.

  • SCLK — Serial Clock (output from master)
  • MOSI/SIMO — Master Output, Slave Input (output from master)
  • MISO/SOMI — Master Input, Slave Output (output from slave)
  • SS — Slave Select (active low; output from master)

Alternative naming conventions are also widely used:

  • SCK — Serial Clock (output from master)
  • SDI, DI, SI — Serial Data In
  • SDO, DO, SO — Serial Data Out
  • nCS, CS, nSS, STE — Chip Select, Slave Transmit Enable (active low; output from master)

The SDI/SDO (DI/DO, SI/SO) convention requires that SDO on the master be connected to SDI on the slave, and vice-versa. That's confusing, so the MOSI/MISO convention is preferred.

SPI port pin names for particular IC products may differ from those depicted in these illustrations.

 

2.동작방법

The SPI bus can operate with a single master device and with one or more slave devices.


If a single slave device is used, the SS pin may be fixed to logic low if the slave permits it. Some slaves require the falling edge (high->low transition) of the slave select to initiate an action such as the MAX1242 by Maxim IC, an ADC, that starts conversion on said transition. With multiple slave devices, an independent SS signal is required from the master for each slave device.

Most devices have tri-state outputs that become high impedance ("disconnected") when the device is not selected. Devices without tristate outputs can't share SPI bus segments with other devices; only one such slave may talk to the master, and only its chipselect may be activated.

Data Transmission

A typical hardware setup using two shift registers to form an inter-chip circular buffer
A typical hardware setup using two shift registers to form an inter-chip circular buffer

To begin a communication, the master first configures the clock, using a frequency less than or equal to the maximum frequency the slave device supports. Such frequencies are commonly in the range of 1-70 MHz.

The master then pulls the slave select low for the desired chip. If a waiting period is required (such as for analog-to-digital conversion) then the master must wait for at least that period of time before starting to issue clock cycles.

During each SPI clock cycle, a full duplex data transmission occurs:

  • the master sends a bit on the MOSI line; the slave reads it from that same line
  • the slave sends a bit on the MISO line; the master reads it from that same line

Not all transmissions require all four of these operations to be meaningful but they do happen.

The transmissions normally involve two shift registers of some given word size, such as eight bits, one in the master and one in the slave; they are connected in a ring. Data is usually shifted out with the most significant bit first, while shifting a new least significant bit into the same register. After that register has been shifted out, the master and slave have exchanged register values. Then each device takes that value and does something with it, such as writing it to memory. If there is more data to exchange, the shift registers are loaded with new data and the process repeats.

Transmissions may involve any number of clock cycles. When there are no more data to be transmitted, the master stops toggling its clock. Normally, it then deselects the slave.

Transmissions often use single 8-bit bytes, and a master can initiate multiple such transmissions if it wishes/needs. However, other word sizes are also common, such as 16-bit words for touchscreen controllers or audio codecs, like the TSC2101 from Texas Instruments; or 12-bit words for many digital-to-analog or analog-to-digital converters.

Every slave on the bus that hasn't been activated using its slave select line must disregard the input clock and MOSI signals, and may not drive MISO. The master selects only one slave at a time.

 

 

3.인터럽트(Interrupt)

Interrupts

SPI devices sometimes use another signal line to send an interrupt signal to a host CPU. Examples include pen-down interrupts from touchscreen sensors, thermal limit alerts from temperature sensors, alarms issued by real time clock chips, and headset jack insertions from the sound codec in a cell phone.

 위에서 NDS의 Pen Down 예제를 들어서 설명해 놓은 것이 있다.

 

4.NDS의 SPI

4.1 SPI 레지스터

 NDS에 연결되어있는 SPI의 구성은 http://nocash.emubase.de/gbatek.htm#dsserialperipheralinterfacebusspi 에서 찾아볼 수 있다.

Serial Peripheral Interface Bus
SPI Bus is a 4-wire (Data In, Data Out, Clock, and Chipselect) serial bus.
The NDS supports the following SPI devices (each with its own chipselect).
DS Firmware Serial Flash Memory
DS Touch Screen Controller (TSC)
DS Power Management

40001C0h - SPICNT - NDS7 - SPI Bus Control/Status Register

0-1   Baudrate (0=4MHz/Firmware, 1=2MHz/Touchscr, 2=1MHz/Powerman., 3=512KHz)
  2-6   Not used            (Zero)
  7     Busy Flag           (0=Ready, 1=Busy) (presumably Read-only)
  8-9   Device Select       (0=Powerman., 1=Firmware, 2=Touchscr, 3=Reserved)
  10    Transfer Size       (0=8bit/Normal, 1=16bit/Bugged)
  11    Chipselect Hold     (0=Deselect after transfer, 1=Keep selected)
  12-13 Not used            (Zero)
  14    Interrupt Request   (0=Disable, 1=Enable)
  15    SPI Bus Enable      (0=Disable, 1=Enable)
The "Hold" flag should be cleared BEFORE transferring the LAST data unit, the chipselect will be then automatically cleared after the transfer, the program should issue a WaitByLoop(3) manually AFTER the LAST transfer.

 SPI Control 레지스터를 설정할 때 주의할 점은 마지막 Command or Data를 송신할때는 Chipselect Hold를 미리 0으로 설정해 놓아야 한다는 것이다. 이것을 하지 않으면 Chipselect가 자동으로 해제되지 않으므로 해당 칩을 계속 물고 있게 된다.

  • 1Byte의 명령만 송신할 때 : chipselect를 0으로 설정해서 명령을 송신한 다음 데이터를 수신받음
  • 2Byte 이상의 명령 + 데이터의 형태를 송신할 때 : chipselect를 1로 설정하여 데이터를 송신한 다음 마지막 바이트에서는 chipselect를 0으로 한다음 송신

 

40001C2h - SPIDATA - NDS7 - SPI Bus Data/Strobe Register (R/W)

The SPI transfer is started on writing to this register, so one must <write> a dummy value (should be zero) even when intending to <read> from SPI bus.

0-7   Data
  8-15  Not used (always zero, even in bugged-16bit mode)
During transfer, the Busy flag in SPICNT is set, and the written SPIDATA value is transferred to the device (via output line), simultaneously data is received (via input line). Upon transfer completion, the Busy flag goes off (with optional IRQ), and the received value can be then read from SPIDATA, if desired.

 데이터를 송/수신 할때 주의할 점은 SPI Control 레지스터의 Busy bit가 0일때 보내야 한다는 것이다. Busy bit가 1일때는 데이터를 송신 중이거나 수신중이기 때문에 보내거나 받으면 안된다. 

 

Notes/Glitches
SPICNT Bits 12,13 appear to be unused (always zero), although the BIOS (attempts to) set Bit13=1, and Bit12=Bit11 when accessing the firmware.
The SPIDATA register is restricted to 8bit, so that only each second byte will appear in the register when attemting to use the bugged 16bit mode.

Cartridge Backup Auxiliar SPI Bus
The NDS Cartridge Slot uses a separate SPI bus (with other I/O Ports), see
DS Cartridge Backup

 위는 참고적인 사항이니 그냥 넘어가자.

 

4.2 SPI 사용 순서

4.2.1 명령 송신

 SPI를 통해 해당 Device로 명령과 데이터를 송신할때 아래와 같은 순서로 하면 된다.

  1. Busy 상태인가 확인 후 NON BUSY 상태 체크 
  2. SPI Control 레지스터에 SPI 사용 가능 및 클럭, Continous 모드 설정
  3. SPI Data 레지스터에 명령 or 데이터 송신 
  4. Busy 상태인가 확인후 NON BUSY 상태 체크
  5. 3~4를 마지막 데이터 or 명령까지 반복. 마지막 데이터의 경우 SPI Control 레지스터에 Continous 모드를 제거하고 송신

 

4.2.2 데이터 수신

 SPI를 통한 데이터 수신의 경우에는 명령을 보내고 수신 요청(0x00을 SPI Data 레지스터에 씀)을 함으로써 바로 받을 수 있다.

  1. Busy 상태인가 확인 후 NON BUSY 상태 체크 
  2. SPI Control 레지스터에 SPI 사용 가능 및 클럭, Continous 모드 설정
  3. SPI Data 레지스터에 명령 or 데이터 송신 
  4. Busy 상태인가 확인후 NON BUSY 상태 체크
  5. SPI Data 레지스터에 0x00 패킷 송신 
  6. Busy 상태인가 확인후 NON BUSY 상태 체크
  7. SPI Data 레지스터에서 값을 읽음 
  8. 4~7을 마지막 데이터까지 반복. 마지막 데이터의 경우 SPI Control 레지스터에 Continous 모드를 제거하고 Dummy Data 송신

 

 

5.구현

 SPI는 ARM7에만 있으므로 \devkitPro\libnds\srouce\include\nds\arm7 폴더에서 serial.h 파일을 찾아야 한다.

// SPI chain registers
#define REG_SPICNT      (*(vuint16*)0x040001C0)
#define REG_SPIDATA     (*(vuint16*)0x040001C2)

#define SPI_ENABLE  BIT(15)
#define SPI_IRQ     BIT(14)
#define SPI_BUSY    BIT(7)

 

// Pick the SPI clock speed
#define SPI_BAUD_4MHZ    0
#define SPI_BAUD_2MHZ    1
#define SPI_BAUD_1MHZ    2
#define SPI_BAUD_512KHZ  3

 

// meh
#define SPI_BAUD_4MHz    0
#define SPI_BAUD_2MHz    1
#define SPI_BAUD_1MHz    2
#define SPI_BAUD_512KHz  3

 

// Pick the SPI transfer length
#define SPI_BYTE_MODE   (0<<10)
#define SPI_HWORD_MODE  (1<<10)

 

// Pick the SPI device
#define SPI_DEVICE_POWER      (0 << 8)
#define SPI_DEVICE_FIRMWARE   (1 << 8)
#define SPI_DEVICE_NVRAM      (1 << 8)
#define SPI_DEVICE_TOUCH      (2 << 8)
#define SPI_DEVICE_MICROPHONE (2 << 8)

 

// When used, the /CS line will stay low after the transfer ends
// i.e. when we're part of a continuous transfer
#define SPI_CONTINUOUS       BIT(11)

 위에서 설명했던 각 Register의 값과 비트를 설정해 놓았다는 것을 알 수 있다.

 

 이 헤더 파일을 사용해서 직접 Power Control을 한 예제를 보자. \devkitPro\libnds\source\source\arm7 폴더에서 spi.c 파일을 찾으면 된다.

  1. //---------------------------------------------------------------------------------
    int writePowerManagement(int reg, int command) {
    //---------------------------------------------------------------------------------
      // Write the register / access mode (bit 7 sets access mode)
  2.   while (REG_SPICNT & SPI_BUSY);
      REG_SPICNT = SPI_ENABLE | SPI_BAUD_1MHZ | SPI_BYTE_MODE | SPI_CONTINUOUS | SPI_DEVICE_POWER;
      REG_SPIDATA = reg;
  3.   // Write the command / start a read
      while (REG_SPICNT & SPI_BUSY);
      REG_SPICNT = SPI_ENABLE | SPI_BAUD_1MHZ | SPI_BYTE_MODE | SPI_DEVICE_POWER;
      REG_SPIDATA = command;
  4.   // Read the result
      while (REG_SPICNT & SPI_BUSY);
      return REG_SPIDATA & 0xFF;
    }

 위에서 보면 2Byte의 Command를 날려야 하는데, 첫번째 송신때는 SPI_CONTINUOUS를 사용했지만 두번째는 SPI_CONTINOUS를 사용하지 않은 것을 알 수 있다.

 

6.마치면서...

 SPI의 동작 및 NDS에 적용된 부분을 살펴보았다. 이제 ARM7 코드만 손보면 우리가 원하는 작업을 할 수 있게 되었다. Power Control 같은 경우도 Memory Map Register를 사용해서 할 수 없었던 부분을 할 수 있다.

 이것 저것 많이 테스트 해보도록 하자 >ㅁ<)/~

 

 

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

07 NDS 홈브루(Homebrew) - KNG(KKAMAGUI NDS Graphic) File 변환 툴

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

 

들어가기 전에...

 

0.버전관리

  • 2007/11/07 23:42:37 : 이미지 변환 시, 마지막 한줄이 빠지는 버그 수정

 

 

0.시작하면서...

 NDS에서 그림을 출력하려면 어떻게 해야할까? 방법은 크게 2가지가 있다.

  1. 이미지를 BYTE 배열 형태로 롬 파일에 삽입하는 방법
  2. libfat를 이용해서 특정 경로에서 직접 읽어 표시하는 방법

 어느 방법을 사용하던지 중요한 점은 NDS에서 편하게 불러쓰고 화면에 뿌릴 수 있어야 한다는 점이다. 그래서 KNG(KKAMAGUI NDS Graphic) 포맷을 개발하게 되었고, RGB555 포맷으로 그대로 변환하여 저장하게 하여 별도의 처리없이 화면에 표시가능하도록 구성했다.

 이제 KNG에 대해서 하나하나 알아보자.

 

1.KNG 구성

 KNG는 RGB555 포맷을 기본으로 사용하도록 만들어졌고 크게 헤더 부분과 데이터 부분, 두부분으로 구성되어있다. 헤더를 굳이 만들 필요가 있는가 하는 사람도 있겠지만, PSP와 같은 기기로 확장을 생각하고 있기 때문에 나름 범용으로 쓰기위해 헤더와 데이터로 구분했다.

 아래는 헤더의 구성이다.

  1. // 매크로 정의
    #define VERSION_NDS_0_0 0x00
  2. #pragma pack( push, 1 )
  3. // 헤더 구조체
    typedef struct kkamaguiNdsGraphicStruct
    {
        char vcSignature[ 3 ]; // KNG 설정
        BYTE bVersion;         // 이미지 포맷 버전, 0x00 설정
        int iWidth;            // 이미지 넓이
        int iHeight;           // 이미지 높이
    } KNG, * PKNG;
  4. #pragma pack( pop )

 

 각 부분의 역할은 아래와 같다.

  • vcSignature : 파일 포맷의 유효성을 검증하기위한 방편
  • bVersion : 다양한 데이터 양식을 지원하기위해 넣은 버전 번호. 매크로에 정의되어있듯 NDS RGB555 포맷은 0x00으로 정의되어있음
  • iWidth : 이미지의 가로 넓이
  • iHeight : 이미지의 세로 넓이

 

 데이터는 헤더의 바로 뒤에 연달아서 나오게 되어있으므로 헤더를 뛰어넘고 데이터를 접근하면 Raw Data에 바로 접근할 수 있다. 데이터 양을 줄이기 위해서는 Run-Length Encoding과 같은 방법을 사용해서 처리하면 사이즈를 줄일 수 있지만, 현재 버전에서는 굳이 넣지 않았다.

 NDS Firmware 문서를 뒤져보면 Firmware에서 Run-Length Decoding과 Huffman Decoding 루틴을 제공하는 것을 알 수 있는데, 사이즈가 부담스러우면 두가지 방법을 사용하는 것도 괜찮은듯 하다.

 

2.KNG 파일 포맷으로 변환

 KNG로 변환하는 것은 아주 간단하다. 헤더를 생성한 다음, RGB 포맷을 RGB555 포맷으로 수정하여 저장하면 끝이다. 첨부에 포함된 변환 프로그램 소스를 보면 쉽게 알 수 있는데, 핵심 부분은 CMyImage 클래스 이다. FreeImage 라이브러리를 사용해서 이미지를 읽고 헤더를 생성한후 RGB555 포맷으로 저장한다. 학교 수업때 사용한 프로젝트를 약간 변경한 소스여서 지저분한데, 마음에 안들면 새로 만들도록 하자.

 아래는 CMyImage 클래스의 간단한 사용법이다.

  1. CMyImage clImage;
  2. ... 생략 ...
  3. clImage.LoadImage( "a.jpg" );
  4. clImage.SaveImageWithConvert();
  5. clImage.UnloadImage();

 위를 실행하고 나면 a.jpg.kng 라는 파일이 a.jpg 파일이 있는 같은 폴더에 생성된다.

 

 변환하는 과정을 좀더 상세히 살펴보자. 어떻게 변경하는 것일까? 아래는 실제 변환하는 소스이다.

  1. /**
        이미지를 변환과 함께 저장한다.
    */
    BOOL CMyImage::SaveImageWithConvert( void )
    {
        CSize clSize;
        int i;
        int j;
        RGBQUAD stRgb;
        char vcNewFileName[ 256 ];
        KNG stHeader;
        int iFd;
        WORD wValue;
        WORD wR;
        WORD wG;
        WORD wB;
       
        // 파일 이름을 설정한다.
        vcNewFileName[ 0 ] = '\0';
        _snprintf( vcNewFileName, sizeof( vcNewFileName ), "%s.kng", m_vcFileName );
  2.     iFd = _open( vcNewFileName, _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY,
            _S_IREAD | _S_IWRITE );
        if( iFd == -1 )
        {
            return FALSE;
        }
  3.     // 헤더를 설정하여 파일에 쓴다.
        SetHeaderInfo( &stHeader );
        _write( iFd, &stHeader, sizeof( stHeader ) );
  4.     // 이미지를 변환해서 파일에 쓴다.
        clSize = GetImageSize();

  5.     // 위 아래가 바뀌어 있어서 뒤집는다.
        for( j = clSize.cy - 1 ; j >= 0 ; j-- )
        {
            for( i = 0 ; i < clSize.cx ; i++ )
            {
                // RGB 값을 얻어서 555 Format으로 맞춘다.
                stRgb = GetPixel( i, j );
               
                wR = ( WORD )( stRgb.rgbRed * ( ( double ) 31 / 255 ) );
                wG = ( WORD )( stRgb.rgbGreen * ( ( double ) 31 / 255 ) );
                wB = ( WORD )( stRgb.rgbBlue * ( ( double ) 31 / 255 ) );
               
                // MSB는 Alpha Bit이다. 이것을 0으로 설정하면 NDS에서 그려지지 않는다.
                wValue = 0x8000 | ( wB << 10 ) | ( wG << 5 ) | ( wR );
                _write( iFd, &wValue, 2 );
                //TRACE( "[%d %d] [%d %d %d] %X\n", i, j, wR, wG, wB, wValue );
            }
        }
        _close( iFd );
  6.     return TRUE;
    }

 파일을 관리하는 부분을 다 제외하고 파란색 부분만 보면, 헤더를 생성하고 이미지의 각 부분의 Pixel 값을 읽은 후 RGB555 포맷에 맞게 곱셈/나눗셈을 해서 처리하는 것을 알 수 있다. 간단한 소스니 별다른 부연 설명은 필요 없을 듯...

 한가지 유의할 점은 원 이미지는 위아래가 뒤집혀서 저장되므로 그것을 바로잡기위해 루프의 인덱스를 조작한 부분이다. 혹시나 특정 파일 포맷을 변환했는데, 위아래가 뒤집혀서 NDS에 출력될 경우는 저 부분을 손보면 된다.

 

3.KNG 이미지 출력

 KNG 이미지를 출력하는 방법은 아주 간단하다. 일단 헤더에서 이미지에 대한 정보를 추출한 다음 루프를 돌면서 화면에 뿌려주면 된다. RGB555 모드로 NDS를 설정하는 방법이 궁금하면 02 NDS 윈도우 시스템(Windows System) 문서를 참고하도록 하자.

 아래는 KNG 파일을 출력하는 소스의 일부분이다.

  1.     // 복사한다.
     for( i = 0 ; i < iHeight; i++ )
     {
         iTemp1 = ( i + iDstY ) * iDstWidth + iDstX;
         iTemp2 = ( i + iSrcY ) * iSrcWidth + iSrcX;
        
         memcpy( pwDstAddr + iTemp1, pwSrcAddr + iTemp2, iWidth * 2 );
     }

 pwDstAddr이 RGB555로 설정된 비디오 메모리의 주소이고, pwSrcAddr이 KNG 파일 내의 Raw Data의 주소이다. 간단히 메모리간의 복사로 해결할 수 있다.

 

4.변환 툴 사용법

 이미지 변환툴은 jpg, png, bmp와 같은 그래픽 파일 포맷을 지원한다. 단 사이즈 조작 기능은 없으니 사이즈를 조작하려면 그림판이나 포토샵을 이용해서 이미 조작해야 할 것이다. 원활한 사용을 위해서는 이미지 크기가 256 X 192 이하로 하는게 좋다. 이 이상된다면 출력 루틴을 수정하고 그래픽 모드 또한 수정해야 할 것이므로, 정신 건강에 매우 해롭다. ㅡ_ㅡa...

 아래는 변환툴을 실행한 화면이다.

KNG1.PNG

<실행화면>

 메뉴의 파일->이미지 파일 선택을 클릭하여 변환할 파일을 선택하거나, 툴바의 열기 버튼을 클릭하여(위의 붉은색) 변환할 파일을 선택하면 이미지 변환이 수행되고 아래와 같은 화면이 표시되면 정상적으로 변환 된 것이다.

KNG2.PNG

<이미지 변환 성공>

 해당 이미지가 있는 폴더에 보면 .kng 확장자가 붙은 파일이 추가로 생성되었음을 알 수 있다. 이 파일을 롬파일에 추가하거나 libfat를 이용해서 불러 사용하면 된다. 롬파일에 이미지 또는 파일을 추가하는 방법은 참고. 롬 파일에 데이터(사운드, 이미지 등등) 파일 포함 방법 문서를 참고하도록 하자.

 

5.마치면서...

 NDS용 파일 포맷을 생성하고 변환하는 방법에 대해서 간단히 알아보았다. 이것으로 홈브루를 더욱 예쁘게 만들 수 있게 되었다. 더욱 홈브루 개발에 정진하자. @0@)/~!!!

 

6.첨부

 

 

 

 

 

 

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

06 키패드(KeyPad) 및 터치스크린(Touch Screen) 제어

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

 

들어가기 전에...

 

0.시작하면서...

 키패드는 4개의 방향키와 4개의 일반 버튼, 그리고 2개의 Start/Select 버튼, 2개의 어깨버튼으로 되어있다. 이중에서 Gameboy에서 사용했던 2개의 일반 버튼 X/Y는 ARM7에만 연결되어있으며, Touch Screen 또한 ARM7에만 연결되어있다.

 

1.ARM9/ARM7 키패드(Keypad)

 ARM9과 ARM7에서 다 접근할 수 있는 키는 http://nocash.emubase.de/gbatek.htm#keypadinput 에서 정보를 찾아볼 수 있다.

The built-in GBA gamepad has 4 direction keys, and 6 buttons.

4000130h - KEYINPUT - Key Status (R)

Bit   Expl.
  0     Button A        (0=Pressed, 1=Released)
  1     Button B        (etc.)
  2     Select          (etc.)
  3     Start           (etc.)
  4     Right           (etc.)
  5     Left            (etc.)
  6     Up              (etc.)
  7     Down            (etc.)
  8     Button R        (etc.)
  9     Button L        (etc.)
  10-15 Not used
It'd be usually recommended to read-out this register only once per frame, and to store the current state in memory. As a side effect, this method avoids problems caused by switch bounce when a key is newly released or pressed.

 위에서 보면 각 비트별로 키가 눌러졌으면 0, 눌러지지 않았으면 1로 설정되고 주의할 점은 한 프레임별로 한번만 읽어서 저장하라고 되어있다. 이렇게 하지 않으면 스위치가 튀는 현상이 생긴단다. 주의하도록 하자.

 

4000132h - KEYCNT - Key Interrupt Control (R/W)

The keypad IRQ function is intended to terminate the very-low-power Stop mode, it is not suitable for processing normal user input, to do this, most programs are invoking their keypad handlers from within VBlank IRQ.

Bit   Expl.
  0     Button A        (0=Ignore, 1=Select)
  1     Button B        (etc.)
  2     Select          (etc.)
  3     Start           (etc.)
  4     Right           (etc.)
  5     Left            (etc.)
  6     Up              (etc.)
  7     Down            (etc.)
  8     Button R        (etc.)
  9     Button L        (etc.)
  10-13 Not used
  14    IRQ Enable Flag (0=Disable, 1=Enable)
  15    IRQ Condition   (0=Logical OR, 1=Logical AND)
In logical OR mode, an interrupt is requested when at least one of the selected buttons is pressed.
In logical AND mode, an interrupt is requested when ALL of the selected buttons are pressed.

Notes
In 8bit gameboy compatibility mode, L and R Buttons are used to toggle the screen size between normal 160x144 pixels and stretched 240x144 pixels.
The GBA SP is additionally having a * Button used to toggle the backlight on and off, as far as I know there's no way to detect the current button or backlight state by software.

 인터럽트 컨트롤 레지스터는 키가 눌리면 인터럽트가 바로 발생하여 어떤 처리를 할 수 있도록 해준다. Logical AND와 Logical OR는 위에서 보는 것과 같이 인터럽트 플래그가 설정된 키가 모두 눌러지는가 or 하나만 눌러지는가에 따라서 인터럽트를 발생시키는 것이다.

 위의 설명에 따르면 일반적으로 User Input을 처리하는 방식은 Frame 별로 한번 읽어서 처리하는 방식이라 인터럽트를 사용해서 처리하는 방식은 Low Power Mode에서 깨어나게 하는 용도로 사용된다고 되어있다. 나중에 참고하도록 하자.

 

2.ARM7 키패드(Keypad)

4000136h - ARM7 - EXTKEYIN - Key X/Y Input (R)

0      Button X     (0=Pressed, 1=Released)
  1      Button Y     (0=Pressed, 1=Released)
  3      DEBUG button (0=Pressed, 1=Released/None such)
  6      Pen down     (0=Pressed, 1=Released/Disabled)
  7      Hinge/folded (0=Open, 1=Closed)
  2,4,5  Unknown / set
  8..15  Unknown / zero
The Hinge stuff is a magnetic sensor somewhere underneath of the Start/Select buttons, it will be triggered by the magnet field from the right speaker when the console is closed. The hinge generates an interrupt request (there seems to be no way to disable this, unlike as for all other IRQ sources), however, the interrupt execution can be disabled in IE register (as for other IRQ sources).
The Pen Down is the /PENIRQ signal from the Touch Screen Controller (TSC), if it is enabled in the TSC control register, then it will notify the program when the screen pressed, the program should then read data from the TSC (if there's no /PENIRQ then doing unneccassary TSC reads would just waste CPU power). However, the user may release the screen before the program performs the TSC read, so treat the screen as not pressed if you get invalid TSC values (even if /PENIRQ was LOW).
Not sure if the TSC /PENIRQ is actually triggering an IRQ in the NDS?
The Debug Button should be connected to R03 and GND (R03 is the large soldering point between the SL1 jumper and the VR1 potentiometer).
Interrupts are reportedly not supported for X,Y buttons.

 X/Y 버튼은 ARM7에 연결되어있으므로 ARM9에서는 바로 읽을 수 없고 이것을 ARM7에서 읽어 ARM9으로 넘겨주는 식으로 해야 한다. Hinge 같은 경우는 NDS가 접혔을때 1로 설정되고, Pen down은 Touch Screen이 눌러졌을 때 0으로 설정된다. 하지만 위에 설명에서 나왔듯이 저 값이 1로 설정되었을 때 CPU가 SPI를 통해 Touch Screen에서 값을 읽게 되는데, 읽기 전에 Touch Screen에서 Release되면 잘 못된 값을 읽을 수 있다. 즉 완전하지는 못한 것 같다. Debug 레지스터는 크게 중요하지 않으므로 넘어간다.

 

3.터치 스크린(Touch Screen)

 NDS의 터치스크린에 대한 문서는 http://nocash.emubase.de/gbatek.htm#dstouchscreencontrollertsc에서 찾아볼 수 있다.

 

Pin-Outs

________
  VCC  1|o       |16 DCLK
  X+   2|        |15 /CS
  Y+   3|  TSC   |14 DIN
  X-   4|  2046  |13 BUSY
  Y-   5|        |12 DOUT
  GND  6|        |11 /PENIRQ
  VBAT 7|        |10 IOVDD
  AUX  8|________|9  VREF

 

 현재 ( 2007/08/18 14:24:40 )까지 테스트한 결과로 아직까지 Touch Screen의 튀는 현상이 완전히 없어지지는 않고 있는데, 그 이유가 여기서 잠깐 나온다. ARM7에서 SPI를 통해서 값을 읽어야 하는데, 위에서 잠깐 언급했듯이 값을 읽는 동안에 release가 되면 어떻게 할 수 없다는 것이다. 결국 반응속도를 높여서 1로 설정되었을 때 빨리 읽어줘야 한다는 것인데.... 애매하기 짝이없다.

 튀는 현상을 줄이기 위해서는 이 부분에 대해서 libnds 라이브러리를 수정하여 튜닝을 좀 해야 할 것 같다.

Texas Instruments TSC2046
The Touch Screen Controller is accessed via SPI bus,
DS Serial Peripheral Interface Bus (SPI)

 SPI에 대한 내용은  07 Serial Peripheral Interface(SPI) 를 참조하도록 하자.

 

Control Byte (transferred MSB first)

  0-1  Power Down Mode Select
  2    Reference Select (0=Differential, 1=Single-Ended)
  3    Conversion Mode  (0=12bit, max CLK=2MHz, 1=8bit, max CLK=3MHz)
  4-6  Channel Select   (0-7, see below)
  7    Start Bit (Must be set to access Control Byte)

 Control Byte는 위와 같이 갖가지 모드를 사용할 수 있다. 채널은 아래에서 나오는데, 컨트롤러로부터 원하는 값을 읽을때 사용된다.

 

Channel
0 Temperature 0 (requires calibration, step 2.1mV per 1'C accuracy)
  1 Touchscreen Y-Position  (somewhat 0B0h..F20h, or FFFh=released)
  2 Battery Voltage         (not used, connected to GND in NDS, always 000h)
  3 Touchscreen Z1-Position (diagonal position for pressure measurement)
  4 Touchscreen Z2-Position (diagonal position for pressure measurement)
  5 Touchscreen X-Position  (somewhat 100h..ED0h, or 000h=released)
  6 AUX Input               (connected to Microphone in the NDS)
  7 Temperature 1 (difference to Temp 0, without calibration, 2'C accuracy)

All channels can be accessed in Single-Ended mode.
In differential mode, only channel 1,3,4,5 (X,Z1,Z2,Y) can be accessed.

 채널은 위와 같이 구성되며 각 채널은 해당 채널의 값을 리턴한다. 터치패드의 X값을 읽고 싶으면 Control Byte에 5를 설정하면 읽을 수 있다.

 오늘( 2007/08/21 03:27:23 ) 테스트 결과 NDS의 Z축 데이터는 정상적으로 수신되지 않는 것을 발견했다.

 

Power Down Mode

Mode /PENIRQ   VREF  ADC   Recommended use
  0    Enabled   Auto  Auto  Differential Mode (Touchscreen, Penirq)
  1    Disabled  Off   On    Single-Ended Mode (Temperature, Microphone)
  2    Enabled   On    Off   Don't use
  3    Disabled  On    On    Don't use
Allows to enable/disable the /PENIRQ output, the internal reference voltage (VREF), and the Analogue-Digital Converter.

 Power Down Mode는  데이터를 어떤식으로 받을 것인가 설정하는 부분인데, 아래에 설명이 나온다.

 

Reference Voltage (VREF)
VREF is used as reference voltage in single ended mode, at 12bit resolution one ADC step equals to VREF/4096. The TSC generates an internal VREF of 2.5V (+/-0.05V), however, the NDS uses as external VREF of 3.33V (sinks to 3.31V at low battery charge), the external VREF is always enabled, no matter if internal VREF is on or off. Power Down Mode 1 disables the internal VREF, which may reduce power consumption in single ended mode. After conversion, Power Down Mode 0 should be restored to re-enable the Penirq signal.

Sending the first Command after Chip-Select
Switch chipselect low, then output the command byte (MSB first).

Reply Data
The following reply data is received (via Input line) after the Command byte has been transferred: One dummy bit (zero), followed by the 8bit or 12bit conversion result (MSB first), followed by endless padding (zero).
Note: The returned ADC value may become unreliable if there are longer delays between sending the command, and receiving the reply byte(s).

Sending further Commands during/after receiving Reply Data
In general, the Output line should be LOW during the reply period, however, once when Data bit6 has been received (or anytime later), a new Command can be invoked (started by sending the HIGH-startbit, ie. Command bit7), simultanously, the remaining reply-data bits (bit5..0) can be received.
In other words, the new command can be output after receiving 3 bits in 8bit mode (the dummy bit, and data bits 7..6), or after receiving 7 bits in 12bit mode (the dummy bit, and data bits 11..6).
In practice, the NDS SPI register always transfers 8 bits at once, so that one would usually receive 8 bits (rather than above 3 or 7 bits), before outputting a new command.

 명령을 보내고 난뒤 응답을 수신하는 주기에서 바로 다시 새로운 커맨드를 보내면 데이터를 수신할 수 있다고 한다. 보내는 즉시 6bit를 수신할 수 있으므로 명령을 보내고 Ready가 됬을 때 명령을 날리는 것도 괜찮은 것 같다. 실제 NDS의 터치스크린 코드를 보면 그렇게 구현되어 있다.

 데이터 2Byte로 잘라보내며, ADC가 12bit의 값을 가지는데 6bit씩 잘라서 보내는 것 같다. 데이터를 보낼때 첫비트는 더미 비트로 보내고 나머지 8bit or 12bit가 데이터 그리고 남은 비트는 모두 0으로 체워지므로 2byte를 수신했을 때 아래와 같은 모습이 될 것이다.

  1. bit   76543210 76543210
  2. data  01111111 11111000

 이 값을 정상적인 12Bit로 만들려면 첫번째 byte를 << 5를 하고 두번째 Byte를 >>3 하면 된다.

 

Touchscreen Position
Read the X and Y positions in 12bit differential mode, then convert the touchscreen values (adc) to screen/pixel positions (scr), as such:

scr.x = (adc.x-adc.x1) * (scr.x2-scr.x1) / (adc.x2-adc.x1) + (scr.x1-1)
  scr.y = (adc.y-adc.y1) * (scr.y2-scr.y1) / (adc.y2-adc.y1) + (scr.y1-1)

The X1,Y1 and X2,Y2 calibration points are found in Firmware User Settings,
DS Firmware User Settings
scr.x1,y1,x2,y2 are originated at 1,1 (converted to 0,0 by above formula).

Touchscreen Pressure
To calculate the pressure resistance, in respect to X/Y/Z positions and X/Y plate resistances, either of below formulas can be used,

Rtouch = (Rx_plate*Xpos*(Z2pos/Z1pos-1))/4096
  Rtouch = (Rx_plate*Xpos*(4096/Z1pos-1)-Ry_plate*(1-Ypos))/4096

The second formula requires less CPU load (as it doesn't require to measure Z2), the downside is that one must know both X and Y plate resistance (or at least their ratio). The first formula doesn't require that ratio, and so Rx_plate can be set to any value, setting it to 4096 results in

touchval = Xpos*(Z2pos/Z1pos-1)

Of course, in that case, touchval is just a number, not a resistance in Ohms.

Touchscreen Notes
It may be impossible to press locations close to the screen borders.
When pressing two or more locations the TSC values will be somewhere in the middle of these locations.
The TSC values may be garbage if the screen becomes newly pressed or released, to avoid invalid inputs: read TSC values at least two times, and ignore BOTH positions if ONE position was invalid.

  터치 스크린 컨트롤러 같은 경우, 값이 쓰레기가 될 수 있기 때문에, 여러번 읽으라고 되어있다. 그래서 하나의 포지션이라도 유효하지 않으면 둘다 무시하면 된다고 한다. 쓰레기 값이 어떤 값인지 값을 한번 체크해 봐야겠다.

 

Microphone / AUX Channel
Observe that the microphone amplifier is switched off after power up, see:
DS Power Management

Temperature Calculation
TP0 decreases by circa 2.1mV per degree Kelvin. The voltage difference between TP1 minus TP0 increases by circa 0.39mV (1/2573 V) per degree Kelvin. At VREF=3.33V, one 12bit ADC step equals to circa 0.8mV (VREF/4096).
Temperature can be calculated at best resolution when using the current TP0 value, and two calibration values (an ADC value, and the corresponding temperature in degrees kelvin):

K = (CAL.TP0-ADC.TP0) * 0.4 + CAL.KELVIN
Alternately, temperature can be calculated at rather bad resolution, but without calibration, by using the difference between TP1 and TP0:
K = (ADC.TP1-ADC.TP0) * 8568 / 4096
To convert Kelvin to other formats,
Celsius:     C = (K-273.15)
  Fahrenheit:  F = (K-273.15)*9/5+32
  Reaumur:     R = (K-273.15)*4/5
  Rankine:     X = (K)*9/5
The Temperature Range for the TSC chip is -40'C .. +85'C. According to Nintendo, the DS should not be exposed to "extreme" heat or cold, the optimal battery charging temperature is specified as +10'C..+40'C.
The original firmware does not support temperature calibration, calibration is supported by nocash firmware (if present). See Extended Settings,
DS Firmware Extended Settings

 위의 내용은 온도 센서와 마이크에 관한 내용이므로 그냥 넘어가자.

 

4.구현

4.1 키 패드(Keypad) 제어

 키 패드 레지스터를 통해 접근가능한 키들은 아래와 같이 비교적 같단하게 읽어올 수 있다. 각 매크로들은 system.h에 있다.

 ........

    // Key Interrupt를 발생하도록 한다.
    REG_KEYCNT = 0x7FFF;
........

........

    // IRQ_KEYS 발생했는지 체크
    if(REG_IF & IRQ_KEYS)
    {
        REG_IF |= IRQ_KEYS;
        g_ucKeysCount++;
    }

......

......

    usButton = ~( REG_KEYINPUT );

    if( usButton & KEY_A)

    { ..... }

 위 처리 방식은 Interrupt를 이용한 방식이므로 04 인터럽트 제어(Interrupt Control) 문서를 참조하면 추가적인 사용법을 알 수 있다.

 ARM7에서만 접근 가능한 X/Y 버튼과 터치스크린은 libnds에서 IPC라는 구조체를 사용해서 넘기도록 되어있다. 실제 읽어 들이는 부분은 ARM7에서 처리하고 저장을 IPC 영역에 하여 ARM9에서 읽어 들일 수 있게한다. 자세한 구현은 실제 libnds를 살펴보도록 하자.

 

4.2 SPI를 통한 터치 스크린(Touch Screen) 제어

 SPI를 이용한 터치스크린의 값을 읽는 코드는 \devitPro\libnds\source\source\arm7 폴더에 touch.c 파일에서 찾을 수 있다. 파일을 열면 touchRead()라는 함수가 있는데, 이것이 터치스크린에서 값을 읽어들이는 함수이다.

  1. //---------------------------------------------------------------------------------
    uint16 touchRead(uint32 command) {
    //---------------------------------------------------------------------------------
     uint16 result, result2;
  2.  uint32 oldIME = REG_IME;
  3.  REG_IME = 0;
     
     SerialWaitBusy();
  4.  // Write the command and wait for it to complete
     REG_SPICNT = SPI_ENABLE | SPI_BAUD_2MHz | SPI_DEVICE_TOUCH | SPI_CONTINUOUS; //0x8A01;
     REG_SPIDATA = command;
     SerialWaitBusy();
  5.  // Write the second command and clock in part of the data
     REG_SPIDATA = 0; <== 데이터를 즉시 받기 위해 Dummy Command를 날림
     SerialWaitBusy();
     result = REG_SPIDATA;
  6.  // Clock in the rest of the data (last transfer)
     REG_SPICNT = SPI_ENABLE | 0x201; <== 마지막 데이터 보내는 부분. SPI_CONTINUOUS가 없음
     REG_SPIDATA = 0; <== 데이터를 즉시 받기 위해 Dummy Command를 날림
  7.  SerialWaitBusy();
  8.  result2 = REG_SPIDATA >>3;
  9.  REG_IME = oldIME;
  10.  // Return the result
     return ((result & 0x7F) << 5) | result2; <== 위에서 설명했던 2Byte를 12bit Value로 변환하는 부분
    }

 SPI 같은 경우 버스를 사용하는 통신이므로 상태란 것이 존재한다. 만약 위의 과정에서 중간에 인터럽트가 발생하여 다른 곳에서 다시 SPI 버스를 사용하게 된다면 낭패가 될 것이다. 그래서 SPI를 사용하는 부분은 인터럽트 불가를 설정하여 그런 일을 방지한다.

 

5.마치며...

 아직 터치스크린쪽은 devkitPro의 기본 Library를 이용했을 때, 튀는 문제가 많은 편이다. 여러가지가 맞물려 있어서 그런 것이겠지만 추후 수정을 좀 해야 할 부분 같다.

 

 

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

06 NDS 홈브루(Homebrew) - 소프트웨어 리셋 라이브러리(Software Reset Library)

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

 

들어가기 전에...

 

0.수정사항

  • 2007/11/01 05:21:27  수정

    • resetlib의 main.c 파일과 main.cpp 파일에 포함된 함수(SoftReset)에 대한 링크 문제로 main.c를 main.cpp로 수정함
    • resetlib.h 파일에 extern "C"를 추가하여 링크 문제 해결

 

 

0.시작하면서...

 소프트웨어 리셋 라이브러리의 모체는 23 Soft Reset 분석 에 나와있는 문쉘(moonshell)의 Reset.mse 플러그인 프로그램이다. 문쉘 플러그인으로 동작하게 만든 것을 소스를 변경하여 libfat만 있으면 동작 가능하도록 수정했다.

 모든 소스에 대한 권리는 문쉘의 권리를 따르며 이 소스를 사용하거나 임의 수정하여 얻는 불이익에 대해서는 책임지지 않는다. ^^;;;

 뭐 원래 오픈 소스이기 때문에 큰일이야 나겠냐 만은... ㅡ_ㅡa... 그래도 혹시나 모르니까 미리 알려둔다.

 

1.주된 변경 사항

1.1 GBA FS -> libfat 

 앞서 말했듯이 모체는 문쉘의 소스이다. 문쉘 소스는 과거의 FAT 라이브러리인 GBA FS를 사용하기 때문에 일반적으로 사용하는 libfat와는 맞지 않다. 그렇다고 리셋 기능 때문에 GBA FS를 그대로 사용하기에는 쓸모없는 코드가 너무 많이 들어가므로 이 부분을 libfat를 사용하도록 수정했다. 이 부분에 대한 자세한 내용은 23 Soft Reset 분석 문서를 참고하면 자세하게 알 수 있다.

 

1.2 IPCEX -> IPC 

 소프트웨어적인 리셋을 위해서는 ARM9과 ARM7 모두 리셋해야 하는데, ARM7의 리셋 신호를 위해 문쉘은 IPCEX라는 별도의 구조체를 사용하고 있다. 이것을 수정하여 libnds의 기본 정보는 IPC 구조체를 그대로 사용하도록 하고, mail 관련 정보를 사용하도록 했다. 자세한 내용은 아래의 사용 예제를 보면 쉽게 알 수 있다. 

 

2.라이브러리 설치 

2.1 소스 및 라이브러리 다운로드 

 resetlib를 사용하기위해서는 헤더파일과 라이브러리 파일만 libnds 폴더에 복사해 주면 된다. 아래 첨부에 있는 resetlib.zip을 다운받아서 압축을 해제하면 아래와 같이 소스파일과 헤더파일 그리고 라이브러리 파일이 생긴다.

 resetlib1.PNG

resetlib2.PNG

<헤더 및 라이브러리>

 

2.2 라이브러리 파일 복사 

 lib 아래에 있는 라이브러리 파일을 devkitPro가 설치된 폴더에 있는 libnds\lib 폴더 아래로 복사한다.

resetlib3.PNG

<라이브러리 파일 복사>

2.3 헤더 파일 복사 

  resetlib.h 파일을  devkitPro가 설치된 폴더에 있는 libnds\include 폴더 아래로 복사한다.

resetlib4.PNG

<헤더 파일 복사>

 

 이로써 라이브러리 설치가 완료되었다. 정말 간단하지 않은가? @0@)/~!!!

 

3.사용 예제 

 resetlib를 사용하기 위해서는 makefile을 수정해서 resetlib를 추가해주는 과정과 ARM9과 ARM7 소스에서 각각 Sync를 맞춰서 SoftReset() 함수를 호출해 주는 과정이 필요하다. 즉 ARM9만 사용하게 되어있는 프로젝트는 소프트웨어 리셋을 사용할 수 없다는 말이다. 

 ARM9 프로젝트를 ARM7과 ARM9을 사용하는 프로젝트로 변경하는 것은 그렇게 어려운 일이 아니니 devkitPro 예제에 templete 폴더에 있는 combined 템플릿을 이용하여 수정하도록 하자. 

 이제 makefile 수정방법부터 하나하나 알아보자. 

 

3.1 makefile 수정 

 resetlib는 ARM9용과 ARM7 용이 따로 제작되어있다. 따라서 각각에 맞게 수정해 줘야 한다. 

 

3.1.1 ARM9 수정 

 LIBS 부분을 아래와 같이 수정하여 libfatlibreset9을 사용하도록 한다.

  1. #---------------------------------------------------------------------------------
    # any extra libraries we wish to link with the project
    #---------------------------------------------------------------------------------
    LIBS := -lfat -lnds9 -lreset9 

 

3.1.2 ARM7 수정 

 LIBS 부분을 아래와 같이 수정하여 libfatlibreset7을 사용하도록 한다. 

  1. LIBS := -lnds7 -lreset7

 

3.2 ARM9 및 ARM7 코드 

3.2.1 ARM9 코드 

 간단히 헤더를 추가하고 libfat의  fatInitDefault() 함수를 호출해서 초기화를 수행한 다음 SoftReset() 함수를 호출해 주면 된다. 아주 간단하다.

  1. // 추가해야 하는 부분
    #include <fat.h>
  2. #include <resetlib.h>
  3. //---------------------------------------------------------------------------------
    int main(void) {
    //---------------------------------------------------------------------------------
        REG_IME=0;
       
        POWER_CR = POWER_ALL_2D;
        POWER_CR |= POWER_SWAP_LCDS;
       
        irqInit();
        {
            InitVRAM();
               
            // reset library는 libfat를 사용하므로 반드시 해줘야 한다.
            fatInitDefault();
            
            // Soft Reset 실행
            SoftReset();
        }
       
        while(1);

 

3.2.2 ARM7 코드 

 아래의 코드처럼 IPC의 mailData 부분을 통해서 RESET 관련 메시지를 ARM9이 보내도록 되어있는데, 그 값을 이용해서 Sync를 맞추어 SoftReset() 을 호출하면 된다. 

  1. #include <resetlib.h>
  2. ... 생략 ... 
  3. //---------------------------------------------------------------------------------
    int main(int argc, char ** argv) {
    //---------------------------------------------------------------------------------
        irqInit();
       
        // 반드시 초기화 해야 한다.
        IPC->mailData = RESET_NULL; //====== IPC RESET Clear by Rudolph (2007/06/13)
        irqSet(IRQ_VBLANK, VblankHandler);
       
        irqEnable(IRQ_VBLANK);
       
        while (1)
        {
            swiIntrWait(1,IRQ_VBLANK); // vblank
           
            // mail IPC를 감시하면서 값이 NULL 이 아니면 리셋 하도록 한다.
            if( IPC->mailData != RESET_NULL )
            {
                SoftReset( IPC->mailData );
                while(1);
            }
        }

 좀 더 상세한 내용은 첨부에 있는 resetlibTest.zip 를 보면 된다(사실 봐도 별 소용 없다. 위의 코드가 전부이다. ㅡ_ㅡa..)

 

4.마치면서... 

 이것으로 홈브루에도 리셋 기능을 넣을 수 있게 되었다. 홈브루 실행 후 파워 버튼을 만질 일도 이젠 없다. ㅜ_ㅜ... 이 얼마나 좋은 일인가.. ㅜ_ㅜ... 라이브러리 사용법 또한 간단하니 이 얼마나 감동인가... ㅜ_ㅜ

 이제 다시 홈브루 개발의 세계로 빠져보자. @0@)/~!!! 

 

5.첨부

 

 

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

05 파워 컨트롤(Power Control)

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

 

들어가기 전에...

 

0. 시작하면서...

 NDS는 LCD, 사운드, Graphic 엔진 등 여러가지 장비를 가지고 있다. 이 장비는 파워 컨트롤 레지스터에 특정 값을 입력함으로써 조절할 수 있으며 ARM9과 ARM7 별로 컨트롤 할 수 있는 장비가 다르다.

http://nocash.emubase.de/gbatek.htm#dspowermanagement 를 참조하면 Power Control에 대한 자세한 정보를 얻을 수 있는데, 아래와 같다.

 

1. Port I/O

1.1 Port And Bit

 먼저 살펴볼 부분은 Port I/O를 통해서 접근할 수 있는 장비에 대한 부분이다.

The DS contains several Power Managment functions, some accessed via I/O ports, some accessed via SPI bus (described later on below).

4000304h - POWCNT1 - NDS9 - Graphics Power Control Register (R/W)

0     Enable Flag for both LCDs (0=Disable) (Prohibited, see notes)
  1     2D Graphics Engine A      (0=Disable) (Ports 008h-05Fh, Pal 5000000h)
  2     3D Rendering Engine       (0=Disable) (Ports 320h-3FFh)
  3     3D Geometry Engine        (0=Disable) (Ports 400h-6FFh)
  4-8   Not used
  9     2D Graphics Engine B      (0=Disable) (Ports 1008h-105Fh, Pal 5000400h)
  10-14 Not used
  15    Display Swap (0=Send Display A to Lower Screen, 1=To Upper Screen)
Use SwapBuffers command once after enabling Rendering/Geometry Engine.
Improper use of Bit0 may damage the hardware?
When disabled, corresponding Ports become Read-only, corresponding (palette-) memory becomes read-only-zero-filled.

4000304h - POWCNT2 - NDS7 - Sound/Wifi Power Control Register (R/W)
Bit   Expl.
  0     Sound Speakers (0=Disable, 1=Enable) (Initial setting = 1)
  1     Wifi           (0=Disable, 1=Enable) (Initial setting = 0)
  2-31  Not used
Note: Bit0 disables the internal Speaker only, headphones are not disabled.
Bit1 disables Port 4000206h, and Ports 4800000h-480FFFFh.

4000206h - NDS7 - WIFIWAITCNT - Wifi Waitstate Control
Bit   Expl.
  0-2   Wifi WS0 Control (0-7) (Ports 4800000h-4807FFFh)
  3-5   Wifi WS1 Control (0-7) (Ports 4808000h-480FFFFh)
  4-15  Not used (zero)
This register is initialized by firmware on power-up, don't change.
Note: WIFIWAITCNT can be accessed only when enabled in POWCNT2.

4000301h - NDS7 - HALTCNT - Low Power Mode Control (R/W)
In Halt mode, the CPU is paused as long as (IE AND IF)=0.
In Sleep mode, most of the hardware including sound and video are paused, this very-low-power mode could be used much like a screensaver.
Bit   Expl.
  0-5   Not used (zero)
  6-7   Power Down Mode  (0=No function, 1=Enter GBA Mode, 2=Halt, 3=Sleep)
The HALTCNT register should not be accessed directly. Instead, the BIOS Halt, Sleep, CustomHalt, IntrWait, or VBlankIntrWait SWI functions should be used.
BIOS Halt Functions
ARM CP15 System Control Coprocessor
The NDS9 does not have a HALTCNT register, instead, the Halt function uses the co-processor opcode "mcr p15,0,r0,c7,c0,4" - this opcode locks up if interrupts are disabled via IME=0 (unlike NDS7 HALTCNT method which doesn't check IME).

4000300h - NDS7/NDS9 - POSTFLG - BYTE - Post Boot Flag (R/W)
The NDS7 and NDS9 post boot flags are usually set upon BIOS/Firmware boot completion, once when set the reset vector is redirected to the debug handler of Nintendo's hardware debugger. That allows the NDS7 debugger to capture accidental jumps to address 0, that appears to be a common problem with HLL-programmers, asm-coders know that (and why) they should not jump to 0.
Bit   Expl.
  0     Post Boot Flag (0=Boot in progress, 1=Boot completed)
  1     NDS7: Not used (always zero), NDS9: Bit1 is read-writeable
  2-7   Not used (always zero)
There are some write-restrictions: The NDS7 register can be written to only from code executed in BIOS. Bit0 of both NDS7 and NDS9 registers cannot be cleared (except by Reset) once when it is set.

 ARM9과 ARM7에서 접근할 수 있는 부분이 위와 같이 다르고 해당 비트만 0/1을 바꿔주면 간단히 제어할 수 있다는 것을 알 수 있다.

1.2 libnds 분석

 Devkit Pro의 libnds 및의 Source 폴더에 가면 실제 구현한 소스와 헤더를 볼 수 있다. Port I/O 관련 파워 관리 부분은 system.h 파일에 있으므로  한번 살펴보자.

 Port I/O 이므로 파워를 컨트롤 할 수 있는 포트 번호를 알아야 하는데 system.h 파일에 아래와 같이 정의 되어있고 02 NDS Spec 에서도 찾아볼 수 있다.

  1. #define REG_POWERCNT *(vu16*)0x4000304

  위의 정의를 보니 16bit의 크기를 가지는 것 같다. ARM9과 ARM7에 대해서 정의해 놓은 값도 같이 보자.

  1. #define POWER_LCD   BIT(0)
    #define POWER_2D_A   BIT(1)
    #define POWER_MATRIX  BIT(2)
    #define POWER_3D_CORE  BIT(3)
    #define POWER_2D_B   BIT(9)
    #define POWER_SWAP_LCDS  BIT(15)
  2. //! Enables power to all hardware required for 2D video.
    #define POWER_ALL_2D     (POWER_LCD |POWER_2D_A |POWER_2D_B)
  3. //! Enables power to all hardware required for 3D video.
    #define POWER_ALL   (POWER_ALL_2D | POWER_3D_CORE | POWER_MATRIX)

 위에서 파워 조작 레지스터의 비트값을 그대로 매크로로 만들었음을 알 수 있다. BIT()는 해당 비트를 1로 설정해 주는 역할을 하는 간단한 매크로이다.

 

1.3 사용 예제

 매크로를 사용하여 LCD를 키고 그래픽을 표시하려면 아래와 같이 사용하면 된다.

  1. REG_POWERCNT |= POWER_ALL_2D;
  2. powerON( POWER_ALL_2D ); <== 위의 코드와 비슷한 역할을 하는 매크로다.

 만약 위처럼 Engine을 Enable 하지 않고 그냥 LCD만 ON(bit0)만 하면 어떻게 될까? 실제로 해보면 상단 LCD에는 흰색이, 하단 LCD에는 검은색만 표시되고 화면에 아무것도 그려지지 않는다.

 엔진 A를 Enable 시키면? 하단 LCD에 그림이 표시되며, 엔진 B를 Enable 시키면? 상단 LCD가 표시된다. 이때 Swap을 Enable 시키면 상/하단이 바뀌어서 나오게 된다.

  I/O 포트를 이용해서 제어하는 방식은 위와 같이 하면 끝이다.

 

2. SPI

2.1 Index And Registers

 아래는 SPI를 사용해서 접근해야 하는 부분이다.

Power Management Device
The Power Management Device is accessed via SPI bus,
DS Serial Peripheral Interface Bus (SPI)
To access the device, write the Index Register, then read or write the data register, and release the chipselect line when finished.

Index Register

Bit0-1 Register Select          (0..3)
  Bit2-6 Not used
  Bit7   Register Direction       (0=Write, 1=Read)
Register 0 - Powermanagement Control (R/W)
Bit0   Sound Amplifier          (0=Disable, 1=Enable)
         (When disabled, sound becomes very silent, but it is still audible)
  Bit1   Sound related?           (0=Disable, 1=Enable)
  Bit2   Lower Backlight          (0=Disable, 1=Enable)
  Bit3   Upper Backlight          (0=Disable, 1=Enable)
  Bit4   Power LED Blink Enable   (0=Always ON, 1=Blinking OFF/ON)
  Bit5   Power LED Blink Speed    (0=Slow, 1=Fast) (only if Blink enabled)
  Bit6   DS System Power          (0=Normal, 1=Shut Down)
  Bit7   Not used
Register 1 - Battery Status (R)
Bit0   Battery Power LED Status (0=Power Good/Green, 1=Power Low/Red)
  Bit1-7 Not used
Register 2 - Microphone Amplifier Control (R/W)
Bit0   Amplifier                (0=Disable, 1=Enable)
  Bit1-7 Not used
Register 3 - Microphone Amplifier Gain Control (R/W)
Bit0-1 Gain                     (0..3=Gain 20, 40, 80, 160)
  Bit2-7 Not used

Backlight Dimming / Backlight caused Shut-Down(s)
The above bits are essentially used to switch Backlights on or off. However, there a number of strange effects. Backlight dimming is possible by pulse width modulation, ie. by using a timer interrupt to issue pulse widths of N% ON, and 100-N% OFF. Too long pulses are certainly resulting in flickering. Too short pulses are ignored, the backlights will remain OFF, even if the ON and OFF pulses are having the same length. Much too short pulses cause the power supply to shut-down; after changing the backlight state, further changes must not occur within the next (circa) 2500 clock cycles. The mainboard can be operated without screens & backlights connected, however, if so, the power supply will shut-down as soon as backlights are enabled.

Memory Power Down Functions
DS Main Memory Control
DS Firmware Serial Flash Memory

 

2.2 SPI(Serial Peripheral Interface Bus)

 SPI는 아래의 I/O 포트로 제어해야 한다.

Serial Peripheral Interface Bus
SPI Bus is a 4-wire (Data In, Data Out, Clock, and Chipselect) serial bus.
The NDS supports the following SPI devices (each with its own chipselect).
DS Firmware Serial Flash Memory
DS Touch Screen Controller (TSC)
DS Power Management

40001C0h - SPICNT - NDS7 - SPI Bus Control/Status Register

0-1   Baudrate (0=4MHz/Firmware, 1=2MHz/Touchscr, 2=1MHz/Powerman., 3=512KHz)
  2-6   Not used            (Zero)
  7     Busy Flag           (0=Ready, 1=Busy) (presumably Read-only)
  8-9   Device Select       (0=Powerman., 1=Firmware, 2=Touchscr, 3=Reserved)
  10    Transfer Size       (0=8bit/Normal, 1=16bit/Bugged)
  11    Chipselect Hold     (0=Deselect after transfer, 1=Keep selected)
  12-13 Not used            (Zero)
  14    Interrupt Request   (0=Disable, 1=Enable)
  15    SPI Bus Enable      (0=Disable, 1=Enable)
The "Hold" flag should be cleared BEFORE transferring the LAST data unit, the chipselect will be then automatically cleared after the transfer, the program should issue a WaitByLoop(3) manually AFTER the LAST transfer.

40001C2h - SPIDATA - NDS7 - SPI Bus Data/Strobe Register (R/W)
The SPI transfer is started on writing to this register, so one must <write> a dummy value (should be zero) even when intending to <read> from SPI bus.
0-7   Data
  8-15  Not used (always zero, even in bugged-16bit mode)
During transfer, the Busy flag in SPICNT is set, and the written SPIDATA value is transferred to the device (via output line), simultaneously data is received (via input line). Upon transfer completion, the Busy flag goes off (with optional IRQ), and the received value can be then read from SPIDATA, if desired.

Notes/Glitches
SPICNT Bits 12,13 appear to be unused (always zero), although the BIOS (attempts to) set Bit13=1, and Bit12=Bit11 when accessing the firmware.
The SPIDATA register is restricted to 8bit, so that only each second byte will appear in the register when attemting to use the bugged 16bit mode.

Cartridge Backup Auxiliar SPI Bus
The NDS Cartridge Slot uses a separate SPI bus (with other I/O Ports), see
DS Cartridge Backup

 Port I/O를 쓰지않고 SPI를 통해서 접근해야하는 부분들은 위와 같다. SPI는 ARM7에만 연결되어있으므로 ARM7을 통해서 접근해야 한다.

 

Power Management Device
The Power Management Device is accessed via SPI bus,
DS Serial Peripheral Interface Bus (SPI)
To access the device, write the Index Register, then read or write the data register, and release the chipselect line when finished.

Index Register

Bit0-1 Register Select          (0..3)
  Bit2-6 Not used
  Bit7   Register Direction       (0=Write, 1=Read)
Register 0 - Powermanagement Control (R/W)
Bit0   Sound Amplifier          (0=Disable, 1=Enable)
         (When disabled, sound becomes very silent, but it is still audible)
  Bit1   Sound related?           (0=Disable, 1=Enable)
  Bit2   Lower Backlight          (0=Disable, 1=Enable)
  Bit3   Upper Backlight          (0=Disable, 1=Enable)

Bit4 Power LED Blink Enable (0=Always ON, 1=Blinking OFF/ON) Bit5 Power LED Blink Speed (0=Slow, 1=Fast) (only if Blink enabled) Bit6 DS System Power (0=Normal, 1=Shut Down) Bit7 Not used
Register 1 - Battery Status (R)
Bit0   Battery Power LED Status (0=Power Good/Green, 1=Power Low/Red)
  Bit1-7 Not used
Register 2 - Microphone Amplifier Control (R/W)
Bit0   Amplifier                (0=Disable, 1=Enable)
  Bit1-7 Not used
Register 3 - Microphone Amplifier Gain Control (R/W)
Bit0-1 Gain                     (0..3=Gain 20, 40, 80, 160)
  Bit2-7 Not used

Backlight Dimming / Backlight caused Shut-Down(s)
The above bits are essentially used to switch Backlights on or off. However, there a number of strange effects. Backlight dimming is possible by pulse width modulation, ie. by using a timer interrupt to issue pulse widths of N% ON, and 100-N% OFF. Too long pulses are certainly resulting in flickering. Too short pulses are ignored, the backlights will remain OFF, even if the ON and OFF pulses are having the same length. Much too short pulses cause the power supply to shut-down; after changing the backlight state, further changes must not occur within the next (circa) 2500 clock cycles. The mainboard can be operated without screens & backlights connected, however, if so, the power supply will shut-down as soon as backlights are enabled.

 Power Management Register는 재미있는 항목들을 가지고 있다. DS를 닫았을 때, 파워 LED가 깜빡이게 하는 기능을 포함해서 전체 파워를 끄는기능, 깜빡이는 속도를 조절하는 기능 등등을 가지고 있다. 잘 활용하면 쉽게 사용할 수 있을 것 같다. SPI를 통해 사용해야 하므로 07 Serial Peripheral Interface(SPI) 부분을 참고하도록 하자.

 

2.3 libnds 분석

 여기다 분석해서 글 적기

 

2.4 사용 예제

 아래의 예제는 SPI를 사용하는 예제로써 Register 0의 값을 읽어 전원 관련 부분 LED를 Blink하는 소스이다.

  1. #include <nds.h>
  2. /**
        LED를 Slow or Fast로 깜빡이게 하거나 계속 켜져있도록 한다.
            SPI를 통해 보낸다.
    */
    void SetLEDBlinkMode( bool bBlinkEnable, bool bSlow )
    {
        unsigned char ucData;
       
        ucData = 0;
       
        SerialWaitBusy();
        // SPI를 설정한다.
        REG_SPICNT = SPI_ENABLE | SPI_BAUD_1MHz | SPI_DEVICE_POWER | SPI_CONTINUOUS;
        // Read Mode로 설정하고 Register 0을 선택한다.
        REG_SPIDATA = 0x80;
  3.     SerialWaitBusy();
        // 마지막 더미 데이터를 보내서 데이터를 바로 읽도록 한다.
        REG_SPICNT = SPI_ENABLE | SPI_BAUD_1MHz | SPI_DEVICE_POWER;
        REG_SPIDATA = 0x00;
       
        // Register 0에서 데이터를 읽는다.
        SerialWaitBusy();
        ucData = REG_SPIDATA;
  4.     // 만약 깜빡임 모드가 아니면 계속 켜져있도록 설정한다.
        if( bBlinkEnable == false )
        {
            ucData &= ~( BIT( 4 ) );
        }
        // 깜빡임 모드이면 Slow/Fast에 따라서
        else
        {
            ucData |= BIT( 4 );
            if( bSlow == true )
            {
                ucData &= ~( BIT( 5 ) );
            }
            else
            {
                ucData |= BIT( 5 );
            }
        }
  5.     SerialWaitBusy();
        // SPI를 설정한다.
        REG_SPICNT = SPI_ENABLE | SPI_BAUD_1MHz | SPI_DEVICE_POWER | SPI_CONTINUOUS;
       // Write Mode로 설정하고 Register 0을 선택한다.
        REG_SPIDATA = 0x00;
       
        // Register 0에 데이터를 보낸다.
        SerialWaitBusy();
        REG_SPICNT = SPI_ENABLE | SPI_BAUD_1MHz | SPI_DEVICE_POWER;
        REG_SPIDATA = ucData;
    }

 위의 소스를 보면 상단의 파란색 블럭은 Register 0에서 값을 읽는 부분이고 하단의 파란색 블럭은 Register 0에 값을 쓰는 부분임을 알 수 있다. 소스에서 나타난 흐름 같은 부분은 07 Serial Peripheral Interface(SPI) 문서를 보면 된다.

 

3. 마치며...

 여기다 마무리 하기.

 

 

 

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

05 NDS 홈브루(Homebrew) - MP3 Player 만들기

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

 

들어가기 전에...

 

0.시작하면서...

 NDS를 사용하는 사람이라면 문쉘에 대해서 모르는 사람이 없을 것이다. MP3 Player는 물론 Text Viewer, Binary Viewer, 동영상 재생까지 모든 기능을 갖추고 있는 만능 쉘이다.  뭔지 모르는 사람들은... 끄응... 구글링이라도 한번... ㅡ_ㅡa...

 나도 간단한 쉘을 만들고 있기에, MP3 Player에 과감히 도전해 보았다. 

 

1.MP3 Decoder Library 선택 

 문쉘이 쓰고 있는 MP3 Decoder는 libmad로써 나름 괜찮은 성능을 자랑한다. 하지만 단점이라면 큰 크기랄까... ㅡ_ㅡ;;; 실제 libmad를 다운받아서 ARM7에 올렸는데, Huffman 테이블의 크기도 크고 코드 량이 많아서 NDS의 ARM7쪽 코드 및 데이터 영역을 넘어서 버렸다. 컴파일 다 하고 링크할 때 오류가 나서 빌드가 안되는 말도안되는 상황이... ㅡ_ㅡ;;;; 

 문쉘은 필요없는 부분을 추려내서 사이즈를 줄인 것 같은데, 자세하게 분석해 보지 않아서 정확하게는 모르겠다. 

 마구 추려내기 꺼림직하여 좀더 작은 크기의 library를 찾아보니 Helix library가 있었다. https://helixcommunity.org/ 에서 다운로드 받을 수 있는데, 절차가 아주 까다롭고 불편했다. 뭐 여러가지 검색을 하다보니 겨우 소스를 구할 수 있었는데, helix.zip 를 클릭하면 된다(첨부에도 넣어놨다).

 Helix Community에서도 볼 수 있지만 장점이라면 fixed point에 최적화 되었고 작은 크기와 적은 CPU 사용량이랄까... 진짜 그런지는 모르겠지만 일단 크기가 작다니 믿고 쓰기로 했다. 

 

2.Helix Decoder 컴파일 

 일단 소스를 ARM9 이나 ARM7에 부은 다음 makefile을 적절히 수정해야 한다. 일단 Helix 소스가 MyLibrary 아래에 있다고 가정하고 makefile을 수정하는 예이다.

  1. ... 생략 ... 
  2. SOURCES  := source MyLibrary MyLibrary/real MyLibrary/real/arm 
  3. INCLUDES := include build MyLibrary/pub
  4. ... 생략 ...
  5. ARCH := -mthumb-interwork  <== 반드시 -mthumb 을 제거해야 한다. -mthumb를 제거하지 않으면 library 빌드 시에 ARM 모드 명령을 처리할 수 없어서 에러가 발생한다.
  6. CFLAGS := -g -Wall -O1 -DARM \ <== 다양한 플랫폼을 제공하기 때문에 arm 용이라는 매크로를 정의해 준다.
        -march=armv5te -mtune=arm946e-s -fomit-frame-pointer\
       -ffast-math \
       $(ARCH)
    ... 생략 ... 

 위에서 보듯 -mthumb를 제거해야 한다. mthumb는 thumb 모드로 컴파일하라는 옵션인데, 이것을 지우지 않아서 빌드가 안되 한참 고생했다. 위의 예제는 ARM9 에서 빌드하여 사용한다고 가정했다.

 

3.Helix 사용 예제 

 아래 코드는 직접 파일에서 MP3 데이터를 읽어서 디코딩하는 과정을 나타낸 소스코드이다.

  1. #include "nds.h"

    #include <nds/arm9/console.h> //basic print funcionality

    #include <stdio.h>

    #include <fat.h>

    #include "mp3dec.h"

  2.  

    /**
        버퍼를 파일에서 읽어서 다시 체운다.
    */
    void RefillBuffer( FILE* fp, char* pcDst, int iDstSize, char* pcRemain,
        int iRemainSize )
    {
        if( pcRemain != NULL )
        {
            memcpy( pcDst, pcRemain, iRemainSize );
        }
        
        // 버퍼를 채우고 다시 찾는다.
        fread( pcDst + iRemainSize, iDstSize - iRemainSize, 1, fp );

  3.  

  4. //---------------------------------------------------------------------------------

    int main(void) {

    //---------------------------------------------------------------------------------

    touchPosition touchXY;

    HMP3Decoder hMp3;

    unsigned char* pcBuffer;

    FILE* fp;

    int iSync;

    int iRet;

    int iByteLeft;

    int iFrameIndex;

     

    videoSetMode(0); //not using the main screen

    videoSetModeSub(MODE_0_2D | DISPLAY_BG0_ACTIVE); //sub bg 0 will be used to print text

    vramSetBankC(VRAM_C_SUB_BG);

     

    SUB_BG0_CR = BG_MAP_BASE(31);

     

    BG_PALETTE_SUB[255] = RGB15(31,31,31); //by default font will be rendered with color 255

     

    //consoleInit() is a lot more flexible but this gets you up and running quick

    consoleInitDefault((u16*)SCREEN_BASE_BLOCK_SUB(31), (u16*)CHAR_BASE_BLOCK_SUB(0), 16);

     

    iMyPrintf("\n\n\tHello World!\n");

     

    // IRQ 설정

    irqInit();

    irqSet(IRQ_VBLANK, NULL);

    SetYtrigger(80);

    irqEnable(IRQ_VBLANK | IRQ_VCOUNT);

     

    // FAT를 초기화 한다.

    fatInitDefault();

     

    hMp3 = MP3InitDecoder();

    iMyPrintf( "init result = %X\n", hMp3 );

     

    fp = fopen( "/a.mp3", "r" );

    if( fp == NULL )

    {

    iMyPrintf( "File is null\n" );

    }

    else

    {

    iMyPrintf( "File is not null\n" );

    }

     

    // 버퍼를 체운다.

    RefillBuffer( fp, g_vcBuffer, sizeof( g_vcBuffer ), NULL, 0 );

     

    pcBuffer = g_vcBuffer;

    iByteLeft = sizeof( g_vcBuffer );

    iFrameIndex = 0;

    while( 1 )

    {

    iSync = MP3FindSyncWord( pcBuffer, iByteLeft );

    //iMyPrintf( "[%d] sync word = %d, Left = %d\n", iFrameIndex, iSync, iByteLeft );

    if( ( iSync < 0 ) || ( iByteLeft < 1024 ) )

    {

    //iMyPrintf( "[%d] Refill Buffer\n", iFrameIndex );

    RefillBuffer( fp, g_vcBuffer, sizeof( g_vcBuffer ), pcBuffer, iByteLeft );

     

    pcBuffer = g_vcBuffer;

    iByteLeft = sizeof( g_vcBuffer );

    iSync = 0;

    }

    iFrameIndex++;

     

    iByteLeft -= iSync;

    pcBuffer = pcBuffer + iSync;

     

    // 만약 다음 프레임이 유효하지 않으면 안을 체운다.

    if( MP3GetNextFrameInfo( hMp3, &g_stInfo, pcBuffer ) != 0 )

    {

    iMyPrintf( "[%d] Get Next Frame Info Error\n", iFrameIndex );

    while( 1 );

    }

     

  5.  

     // iRet가 0이 아니면 오류가 발생한 것이다.

     

    iRet = MP3Decode( hMp3, &pcBuffer, &iByteLeft, sOutBuffer, 0 );

    iMyPrintf( "[%d] Decode Result = %d, Left = %d\n", iFrameIndex, iRet,

    iByteLeft );

     

    MP3GetLastFrameInfo( hMp3, &g_stInfo );

    iMyPrintf( "%d %d %d %d %d %d\n", g_stInfo.bitrate, g_stInfo.nChans,

    g_stInfo.samprate, g_stInfo.outputSamps, g_stInfo.layer, g_stInfo.version );

    if( g_stInfo.outputSamps <= 0 )

    {

    break;

    }

     

    // 출력 셈플을 L/R로 바꾼후 Play 한다.

    SplitAndPlay( sOutBuffer, g_stInfo.outputSamps );

    }

     

    while(1) {

     

    touchXY=touchReadXY();

    //iMyPrintf("\x1b[20;0HTouch x = %04X, %04X\n", touchXY.x, touchXY.px);

    //iMyPrintf("Touch y = %04X, %04X\n", touchXY.y, touchXY.py);

    }

    return 0;

    }

 

4.실제 적용 

 Sound 출력 부분은 ARM7 만이 접근할 수 있다. 따라서 ARM9으로 디코딩한 후에 ARM7쪽에 동기를 맞추어 버퍼를 넘겨주던지, 아니면 ARM9에서 파일을 읽어서 ARM7에 넘겨주고 ARM7이 디코딩하고 결과를 출력하던지... 2가지 방법이 있다. 

 ARM7에서 파일을 읽고 디코딩하고 출력하는 방법은 안될까? 안타깝지만 테스트 결과 libfat가 ARM7에서 동작하지 않았다. 고로 우리의 선택은 두가지 중에 하나를 해야 하는데, 현재(2007/10/13 21:24:43) 테스트 프로그램은 ARM9에서 읽어서 약 10초간 디코딩한 후에 디코딩 버퍼를 ARM7에 넘겨주고 ARM7은 마치 자기가 디코딩해서 출력하는 양 버퍼를 잘라서 Timer에 맞추어 더블 버퍼링 비슷(?)하게 동작한다.

 즉 목표는 ARM9에서는 파일만 읽어서 데이터를 ARM7에 넘겨주고 ARM7이 디코딩 + 출력까지 다하는 것이다. 

 

 ARM9 이던 ARM7 이던 소리를 제대로 출력하기 위해서는 MP3 디코더에 의해서 디코딩된 프레임이 나왔을때 이를 적절한 타이밍에 잘 출력해 줘야 한다. 즉 타이밍이라는 문제에 봉착하는 것이다. 실제 Helix로 MP3 파일을 디코딩하면 하나의 프레임당 2304 개의 sample이 나온다. 스테레오라고 가정할 때 1152개의 L/R Sample이 나오는데, 44100Hz로 Sampling된 경우 겨우 몇 ms 정도 출력하는 양이다.

 우리가 제대로 된 소리를 들을려면 이 프레임을 계속 디코딩하여 sample을 얻고 sample을 정확한 시간에 출력해 주는 것이 관건인데, 이 문제로 거의 3일을 고민했다. 수많은 테스트를 거쳐서(삽질도 포함해서... ㅡ_ㅜ... 사운드 출력함수를 잘못쓰다니... 젠장... ㅜ_ㅜ...) 겨우 제대로된 소리를 출력할 수 있었다.

 중요한 테크닉은 타이머 및 인터럽트를 사용해서 sample이 Play 완료 되는 시점을 정확하게 구하고 이 시점에서 다음 버퍼를 다른 채널로 Play 시키는 것이다. 같은 채널로 Play 시키면 소리가 정지됬다가 나오므로 약간 잡음이 들린다.

 

 이 타이밍 문제에 대한 자세한 내용은 아래의 6.테스트 및 진행과정 을 참고하도록 하자.

 첨부에 포함된 테스트 프로그램은 Root Directory에 있는 a.mp3 파일을 읽어서 10초 분량을 디코딩한 뒤에 ARM7에 넘겨서 Play한 예제이다. 아직 갈길이 멀지만 기념으로 올린다.

5.첨부 

 

 

6.테스트 및 진행과정 

6.1 테스트 

  • Helix를 포팅하여 MP3를 디코딩하는데 성공
  • ARM9에서 돌렸을 때 디코딩되는 속도가 한 44100Hz로 인코딩된 프레임이 사운드로 출력되는 시간보다 빠른 것을 확인

    • 디코딩 후 바로 Play 했을 때 소리가 겹치고 귀로 들었을 때 프레임이 겹쳐서 소리가 1.5배속 정도로 빨리 플레이 됨
  • 하드에 있는 MP3 파일을 디코딩 했을 때 417Byte 정도당 2304 Sample이 나오는 것을 발견.

    • 2 Channel이면 LRLRLR과 같이 디코딩 되어있으므로 실제로 L과 R에 출력해야 하는 Sample의 수는 1152 Sample이 됨 (16bit) 
    • NDS의 사운드 출력시 4Byte로 Align 되어야 하는데 잘리는 Sample의 수 없이 잘 잘림 

MP3_디코딩중.PNG

<디코딩 정보>

  • ARM9에서 디코딩 후 SoundPlay() 함수로 ARM7에 넘겼을 때, 각종 지연 때문에 음이 끊어지고 정상적으로 Play되지 않음

    • ARM9에서 10초 정도 버퍼를 쌓은 다음 ARM7에 넘기고 ARM7에서는 디코딩된 크기별로 버퍼를 잘라서 더블 버퍼를 이용해 Play하도록 테스트를 해봐야 겠음
    • 위와 같이 테스트 하여 거의 노이즈가 없으면 ARM7으로 디코더를 옮겨서 다시 테스트함 
    • 파일 읽기는 ARM9에서만 가능하므로 ARM9에서 버퍼를 읽어서 넣어줘야 겠음 

 

6.2.Timing 문제 

2007/10/13 20:18:13 해결 

  • 두개의 Timer를 사용하여 Timing 문제를 해결 

    • Timer 0는 원래 Sound의 Sampling Rate로 맞춤 
    • Timer 1은 Cascade 모드로 설정하여 Timer 0가 Overflow 될때 마다(Sampling Rate 만큼 Overflow가 발생) 카운트 되도록 설정 
    • Timer 1의 Overflow는 Frame에 포함된 Sample 수만큼 지나면 발생하도록 하여 Overflow 발생 시 버퍼를 교체해 주는 방식으로 처리 
    • 타이머는 16 bit 크기이고 16bit의 값을 넘어서면 Overflow가 되면서 인터럽트가 발생하는 구조임 
  • 나름대로 소리 깨끗하게 나옴 
  • 아래는 타이밍 설정의 핵심부분 

    1. /**
          Sample Count와 Frequency로 Timer 주기를 설정한다.
      */
      void SetTimer( int iFrequency, int iSampleCount )
      {
          // Sound에 대한 설정
          SCHANNEL_TIMER( 0 )  = SOUND_FREQ( iFrequency );
          SCHANNEL_LENGTH( 0 ) = iSampleCount >> 1 ;
          SCHANNEL_REPEAT_POINT(0) = 0;
          SCHANNEL_TIMER( 1 )  = SOUND_FREQ( iFrequency );
          SCHANNEL_LENGTH( 1 ) = iSampleCount >> 1 ;
          SCHANNEL_REPEAT_POINT( 1 ) = 0;
    2.     // Sound Frequency와 동일하게 Timer를 설정하고 Timer 1을 Cascade로 맞춰서
          // 사운드의 동일한 hz에 맞춰서 동작하도록 한다.
          TIMER0_CR = 0;
          TIMER0_DATA = SOUND_FREQ( iFrequency );
          TIMER0_CR = TIMER_DIV_1 | TIMER_ENABLE;
    3.     // 여기서 2를 곱하는 이유는 Sound_Freq는 Timer_Freq의 2배 속도기 때문이다.
          // 자세한건 메크로를 확인하자.
          TIMER1_DATA = 0x10000 - ( iSampleCount * 2 );
          TIMER1_CR = TIMER_CASCADE | TIMER_IRQ_REQ | TIMER_ENABLE;
    4.     irqSet( IRQ_TIMER1, isrTimer1 );
          irqEnable( IRQ_TIMER1 );
  • 아래는 Timer Interrupt의 Buffer Switching 부분이다. 0 번 채널과 1번 채널을 번갈아 가면서 사용하여 소리를 출력하고 있음을 알 수 있다.

    1. short* psSoundBuffer;
      int iChannel = 0;
      short sOutputBuffer[ 1152 ];
    2. void isrTimer1( void )
      {
          startSound( 44100, psSoundBuffer, 1152 * 2, iChannel, 127, 0, 2 );
          iTimer = 0;
          iChannel = 1 - iChannel;
          psSoundBuffer += 1152;
  • 아래는 Sound 출력 함수이다. 원래 함수에서 변하지 않는 부분은 그대로 놔두도록 변경했다.

    1. //---------------------------------------------------------------------------------
      void startSound(int sampleRate, const void* data, u32 bytes, u8 channel, u8 vol,  u8 pan, u8 format) {
      //---------------------------------------------------------------------------------
       //SCHANNEL_TIMER(channel)  = SOUND_FREQ(sampleRate);
       SCHANNEL_CR(channel) = 0;
       SCHANNEL_SOURCE(channel) = (u32)data;
       //SCHANNEL_LENGTH(channel) = bytes >> 2 ;
       //SCHANNEL_REPEAT_POINT(channel) = 0;
       SCHANNEL_CR(channel) = SCHANNEL_ENABLE | SOUND_ONE_SHOT | SOUND_VOL(vol) | SOUND_PAN(pan) | (format==1?SOUND_8BIT:SOUND_16BIT);

 

6.3 2007/10/13 20:18:55 이전 테스트... 

  • 정확하게 Sample이 Play되는 시간을 찾아서 그 시간에 다시 Sound FIFO에 데이터를 넣어줘야 함. 그렇지 못할 경우 White Noise가 생김.
  • ARM7의 Channel 설정 부분도 손볼 필요가 있음. 
  • Contol Register의 Hold Flag를 사용하면 한 Sample 정도 소리를 유지할 수 있음(자세한건 스펙 참조)

  • 그냥 사운드 컨트롤을 사용해서 넣을 경우 레지스터에 설정하는 크기는 Word 단위(4Byte) 이므로 혹시나 짝이 안맞아서 절삭되는 Sample 때문에 White Noise가 생기는 것은 아닌가 의문 
  • DMA를 사용해서 버퍼를 넣는 방법을 생각해 보기

    • NDS에서는 Sound가 자체 DMA를 통해 날아감 
    • Sound Control 부분만 잘 설정하면 됨

 

7.마치면서... 

 간단하게나마 NDS에 MP3 Player를 포팅(??)하는 방법에 대해서 알아보았다. 아직 갈길이 멀지만 MP3 Decoder를 포팅했다는 것만으로도 의의가 있다고 생각한다. 이제 좀더 발전시켜서 완전한 MP3 Player를 만들어 보자.

 

8.TODO 

  • ARM9에서 ARM7으로 Helix 코드 이동하기
  • ARM9에서 파일을 밀어주고 ARM7에서 디코딩 가능하도록 소스 수정하기 
  • 내 쉘에 올리기 

 

 

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

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@)/~

 

 

 

 

 

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

04 NDS 홈브루(Homebrew) - NDS 커널(Kernel) 만들기

들어가기 전에...

0.시작하면서...

NDS 커널 플레이 동영상

libnds를 이용해서 프로그램을 작성하는 예제는 충분히 많이 있다. 하지만 이것만 가지고는 무엇인가 부족하다. NDS에서 멀티 태스킹을 지원할 수 는 없을까? NDS는 타이머를 4개나 가지고 있기 때문에 시분할 멀티 태스킹을 하는데 전혀 문제가 없다.

커널이 되기위해서는 빠질 수 없는 기능이 멀티 태스킹이고 하니, NDS에 태스크 스위칭(Task Swithcing) 기능을 넣어보도록 하자. 컨텍스트 저장에 사용되는 곳은 OS 프레임워크와 동일하게 각 태스크 스택의 가장 아래부분(0 쪽에 가까운 부분)에 저장되고 아래의 순서로 총 16개의 값이 저장된다.

스택에 저장되는 레지스터 정보

위의 그림은 아래에서 설명할 소스코드를 이해하는데 도움이 되므로 꼭 봐두도록 하자

1.System/User 모드에서 태스크 스위칭(Task Switching)

유저모드에서 간단히 태스크 스위칭 하는 방법은 스택에다가 레지스터를 전부 넣고 스택을 스위칭 한 뒤에 전부 빼면된다. 저장하는 방식은 스택의 Bottom에서 레지스터의 갯수만큼을 저장하고 다시 복원하면 간단히 해결할 수 있다.

    태스크 스위치를 수행하는 함수  
    SwitchTask: // 0x0000 -> 0xFFFF로 올라가니까 넣고 증가시켜야 한다.  
    STMIA R0, { R0-R14 }  
    LDMIA R1, { R0-R13, pc}

2.타이머 인터럽트(Timer Interrupt)를 통한 태스크 스위칭(Task Swithcing)

2.1 인터럽트 핸들러(Interrupt Handler) 코드와 스택(Stack)

타이머 인터럽트를 통해 태스트스위칭을 할때 User/System 모드와 다른 점은 모드가 User/System 모드에서 IRQ 모드로 바뀐다는 것이다. 프로세서의 모드에 대한 내용은 참고. ARM Processor Overview의 내용을 참고하도록 하고 실질적인 문제에 대해서 알아보자.

가장 큰 문제는 레지스터가 뱅크되어 IRQ 모드의 R13(sp)R14(lr), 그리고 SPSR을 가진다는 것이다. 따라서 정상적인 태스트 스위칭을 위해서는 현재 태스크를 저장할 때 User/System 모드의 레지스터 및 SPSR을 저장해야 하며 복원할 태스크 또한 유저모드의 R13R14를 잘 복원해주고 유저모드의 CPSR에SPSR에 넣어준 다음 리턴해야 한다.

문제는 이것만이 아니다. 더 큰 문제는 NDS BIOS 라이브러리에 있는 인터럽트 처리 루틴을 통과한 후에 내가 제어를 이어받는 다는 것인데, NDS BIOS에서 인터럽트 선행 처리를 위해 무슨 일을 하는지 모르면 컨텍스트(Context)를 정확하게 저장할 수 없다. 상당히 충격적인 내용인데... BIOS와 동작 호환을 위해서는 이 부분에 대한 처리를 해줘야.. ㅜ_ㅜ...

일단 NDS의 인터럽트 디스패처(Interrupt Dispatcher) 소스는 \devkitPro\libnds\source\source\common 폴더의 interruptDispatcher.s 파일에서 찾을 수 있고 아래와 같다.

    #ifdef ARM7  
    .text  
    #endif

    #ifdef ARM9  
    .section .itcm,"ax",%progbits  
    #endif

    .extern irqTable  
    .code 32

    .global IntrMain

    @---------------------------------------------------------------------------------  
    IntrMain:
    @---------------------------------------------------------------------------------  
    mov r3, #0x4000000 @ REG_BASE

    ldr r1, [r3, #0x208] @ r1 = IME  
    str r3, [r3, #0x208] @ disable IME  
    mrs r0, spsr  
    stmfd sp!, {r0-r1,r3} @ {spsr, IME, REG_BASE}

    ldr r1, [r3,#0x210] @ REG_IE  
    ldr r2, [r3,#0x214] @ REG_IF  
    and r1,r1,r2

    ldr r0,=__irq_flags @ defined by linker script

    ldr r2,[r0]  
    orr r2,r2,r1  
    str r2,[r0]

    ldr r2,=irqTable

    @---------------------------------------------------------------------------------  
    findIRQ:  
    @---------------------------------------------------------------------------------  
    ldr r0, [r2, #4]  
    cmp r0,#0  
    beq no_handler  
    ands r0, r0, r1  
    bne jump_intr  
    add r2, r2, #8  
    b findIRQ

    @---------------------------------------------------------------------------------  
    no_handler:  
    @---------------------------------------------------------------------------------  
    str r1, [r3, #0x0214] @ IF Clear  
    ldmfd sp!, {r0-r1,r3} @ {spsr, IME, REG_BASE}  
    str r1, [r3, #0x208] @ restore REG_IME  
    mov pc,lr

    @---------------------------------------------------------------------------------  
    jump_intr:  
    @---------------------------------------------------------------------------------  
    ldr r1, [r2] @ user IRQ handler address  
    cmp r1, #0  
    bne got_handler  
    mov r1, r0  
    b no_handler

    @---------------------------------------------------------------------------------  
    got_handler:  
    @---------------------------------------------------------------------------------

    mrs r2, cpsr  
    bic r2, r2, #0xdf @ __  
    orr r2, r2, #0x1f @ / --> Enable IRQ & FIQ. Set CPU mode to System.  
    msr cpsr,r2

    str r0, [r3, #0x0214] @ IF Clear 
    push {lr}

    adr lr, IntrRet  
    bx r1

    @---------------------------------------------------------------------------------  
    IntrRet:  
    @---------------------------------------------------------------------------------  
    pop {lr}  
    mov r3, #0x4000000 @ REG_BASE  
    str r3, [r3, #0x208] @ disable IME

    mrs r3, cpsr  
    bic r3, r3, #0xdf @ __  
    orr r3, r3, #0x92 @ / --> Disable IRQ. Enable FIQ. Set CPU mode to IRQ.  
    msr cpsr, r3**

    ldmfd sp!, {r0-r1,r3} @ {spsr, IME, REG_BASE}

    str r1, [r3, #0x208] @ restore REG_IME  
    msr spsr, r0 @ restore spsr  
    mov pc,lr

    .pool  
    .end

코드의 중요부분은 IRQ Table에서 해당 처리 루틴을 찾아서 IRQ 모드에서 System 모드로 변경한 다음 함수를 호출하는 부분이다. 즉 타이머 인터럽트가 발생하면 내가 만든 타미어 핸들러를 호출하고 이 타이머 핸들러에서 리턴을 하면 정상적인 루트를 통해 다시 복구하도록 되어있다.

그렇다면 IntrMain 이라는 코드는 NDS의 BIOS가 불러준다는 이야긴데... 어떻게 찾아서 저 함수를 불러주는 것일까?

답은 아래는 \devkitPro\libnds\source\source\common 폴더에 있는 Interrupts.c 소스에서 찾을 수 있다. irqInit() 함수에서 위의 IntrMain 함수를 BIOS에서 호출해 줄 핸들러로 등록한다.

     ........ 생략 ........

    //---------------------------------------------------------------------------------  
    void irqInit() {  
    //---------------------------------------------------------------------------------  
    int i;

    // Set all interrupts to dummy functions.  
    for(i = 0; i < MAX_INTERRUPTS; i ++)  
    {  
    irqTable[i].handler = irqDummy;  
    irqTable[i].mask = 0;  
    }

    **IRQ_HANDLER = IntrMain;**

    REG_IE = 0; // disable all interrupts  
    REG_IF = IRQ_ALL; // clear all pending interrupts  
    REG_IME = 1; // enable global interrupt

    }

    ........ 생략 ........

여기서 잠깐.... 뭔가 이상한 점이 느껴지지 않는가? 보통 인터럽트가 발생하면 컨텍스트(Context)를 다 저장해 주고 인터럽트 처리 함수를 호출하여 인터럽트에 대한 처리를 한다음 다시 컨텍스트를 복구하는 절차를 거친다. 그런데 위의 InterruptDispatcher.s 소스에서는 그런 루틴이 보이지 않는다.

ARM 같은 경우라면 ldmfd sp!, {r0-r13} 등과 같은 코드가 있어야 할터인데... 이로보아 IntrMain 코드는 NDS의 BIOS에 의해 불려지는 코드임을 추측할 수 있다.

결국 BIOS의 인터럽트 핸들러 함수(인터럽트 처리의 최고 앞단)에 대해서 알아야 태스크 스위칭같은 문제를 해결할 수 있다는 것인데... ARM9 BIOS의 인터럽트 처리 코드는 어떤 형태일까? 한참을 뒤적이다가 http://www.bottledlight.com/ds/index.php/Main/Interrupts 에서 그 내용을 찾았다.

ARM7 Interrupt handler:

  1. stmdb sp!, {r0-r3, r12, lr}mov r0, #0x04000000
    add lr, pc, #0x0
    ldr pc, [r0, #-0x4]

    ldmia sp!, {r0-r3, r12, lr}

    subs pc, lr, #0x4

The user ARM7 interrupt vector is thus 0x03FFFFFC (mirrors down into ARM7 work RAM)

ARM9 Interrupt handler:

  • stmdb sp!, {r0-r3, r12, lr}
    mrc p15, 0, r0, c9, c1 @ r0 = DTCM_BaseAddress + 0x4000
    mov r0, r0, lsr #12
    mov r0, r0, lsl #12
    add r0, r0, #0x4000
    add lr, pc, #0x0
    ldr pc, [r0, #-0x4] @ bl [DTCM_BaseAddress + 0x3FFC]
    ldmia sp!, {r0-r3, r12, lr}
    subs pc, lr, #0x4

The user ARM9 interrupt vector is thus at DTCM+0x3FFC

In both cases, the BIOS flag word used in swi 0x4 and 0x5 is 4 bytes before the interrupt vector.

위에서 보면 알 수 있듯이 R0, R1, R2, R3, R12, lr을 저장하고 핸들러를 부른다음 IntrMain(붉은 색으로 표시된 부분)을 부르는 것을 알 수 있다. 그 뒤 다시 스택에서 레지스터를 다 복원한 다음 리턴한다. 모드 전환과 같은 코드가 없는 걸 봐서 libnds의 interruptDispatcher.s 파일에 있는 IntrMain 함수를 호출할때는 IRQ 스택을 사용하는 상태이다.

System 모드의 스택은 태스크마다 분리되어있기 때문에 저장해도 별 문제가 없지만 IRQ 레벨의 스택은 공통적으로 사용하기 때문에 만약 타이머 IRQ에서 다시 다른 IRQ가 발생하여 IRQ 스택에 데이터를 PUSH나 POP하면 문제가 발생한다.

일단 위험에 대해서는 위에서 많이 설명했으니 Interrupt가 발생하여 BIOS -> libnds의 IntrMain -> MyTimerHandler 순서로 호출되었을 때 System모드의 스택과 IRQ 모드의 스택 내용을 한번 보자.


<사용자 인터럽트 처리 함수까지 왔을 때의 스택의 모습>

위에서 IRQ 스택과 System 스택 2개로 나누어 진 것은 libndsHandler함수(IntrMain)에 의해서 System 모드로 전환되기 때문이다.

사용자 핸들러 함수(My Handler)가 불린 시점은 이미 System 모드로 전환된 상태이며 스택에 System 모드의 LR(R14) 레지스터의 값이 스택에 저장되어있다. 태스크 스위칭을 이 상황에서 수행하려면 저장하는 컨택스트는 IRQ 스택에서 SPSR과 R0~R3, R12, IRQ_LR을 빼서 저장해야 하고 System 스택에서는 SYSEM_LR을 빼서 저장해야 한다.

2.2 ARM 모드와 THUMB 모드

그런데 이것이 끝이 아니었다. 근 3일 정도를 스택을 계속 보면서 고민했는데, 그냥 리턴을 해서는 정상적으로 동작하지 않았다.

뭐가 문제일까? 그렇게 3일을 고민한 끝에 ARM 모드와 THUMB 모드의 전환... 이라는 내용이 머리를 스쳤다. 컴파일 옵션을 보면 우리가 C로 만든 태스크는 컴파일 옵션에 의해 THUMB 모드로 동작하게 되어있다. 그리고 ARM 모드로 컴파일된 코드를 호출할 수 있도록 Interwork 라는 옵션도 같이 들어있다. 이 말은 ARM 모드와 THUMB 모드를 넘나들며 함수를 호출할 때 중간에 Proxy 함수를 이용해서 호출해서 자동으로 처리해 준다는 이야긴데.... 일반적인 함수 호출에는 큰 문제가 없는 것 같았다. 하지만... 컨텍스트 스위칭의 경우에는 다르다. 레지스터 하나라도 복구가 잘못되면 그냥 크래쉬.. ㅜ_ㅜ...

주의할 점을 하나하나 집어보도록 하자.

2.2.1 CPSR의 설정

C 코드로 태스크 함수를 작성할 경우는 THUMB 모드 코드가 나오므로 태스크를 실행할때 당연히 CPU가 THUMB 모드로 설정되어야 한다. 따라서 복원할 CPSR의 상태를 THUMB Bit1을 키고 SYSTEM 모드(0x1F)로 설정해야 한다. 그런데 만약 어셈블리어 코드로 태스크 함수를 작성하는 경우는 어셈블리어 코드의 생성 옵션(.ARM or .THUMB) 같은 옵션에 맞게 설정해 주어야 한다.

2.2.2 ARM 모드 코드와 THUMB 모드 코드

C 코드(THUMB 코드)에서 어셈블리어 코드(ARM 코드)를 호출하면 어떻게 될까? 컴파일러가 자동적으로 Proxy 함수를 생성하여 C 함수 코드(THUMB 코드) -> THUMB/ARM 변경 코드 -> 어셈블리어 코드(ARM 코드)의 순서로 호출되게 된다. 여기서 잠깐 생각해 볼 것이 Proxy 함수를 거치면 상태 값이 살짝 바뀐다는 것이다. 여기에 대한 자세한 내용은 참고. THUMB 코드와 ARM 코드 및 상호 호출(Interwork) 부분을 참고 하도록 하자. 따라서 이 부분에 대한 처리도 해줘야 하므로 상당히 복잡해 진다.

어셈블리어 코드는 .ARM 모드에서 작성되어 모든 레지스터를 다 저장하고 복구하도록 되어있다. 하지만 타이머 인터럽트에 의해 불리어지는 C 함수는 THUMB 모드로 컴파일 되었고, 이 C 함수를 불러주는 인터럽트 함수는 ARM 모드에서 컴파일 된 핸들러 루틴이다.

이쯤되면 골치가 지끈 아파지고 쉽지 않을꺼라는 생각이 들텐데... 이 문제 때문에 3일을 고생했다. 결국은 요령으로 해결하긴 했지만... ARM 모드와 THUMB 모드를 번갈아가며 사용할 때 태스크 스위칭이 이렇게 힘든 줄 몰랐다.

2.2.3 태스크 스위칭(Task Switching) 코드

한참을 고생한 뒤에 나온 소스가 아래의 태스크 스위칭 코드이다.

@---------------------------------------------------------------------------------

.section ".text"  
.global SwitchTask  
.global SwitchTask2  
.global isrTimerInAsm  
.extern isrTimerInC  
.global g_dwCurTask  
.global g_dwNextTask  

@---------------------------------------------------------------------------------

.align  4  
.arm

@ Timer Interrupt에서 바로 호출해 주는 함수  
isrTimerInAsm:

PUSH { LR }  
BL isrTimerInC      

 LDR R0, g_dwCurTask

LDR R1, g_dwNextTask

 CMP R1, #0

BEQ TIMEREND  
    ADD R2, SP, #0x04  
    bl SwitchTask2

 TIMEREND:

    POP { LR }  
    BX LR   

/*

Switch Task  
    SYSTEM_R0-R14(LR), IRQ_R14, SPSR 순서로 저장한다.  
    IME가 불가된 상태에서 호출하면 안된다.  
    이 함수가 끝나면 가능상태로 바뀌기 때문이다.    

*/  
SwitchTask2:

// 인터럽트 불가 설정  
MOV R3, #0x4000000  
STR R3, [ R3, #0x208 ]    

MOV SP, R2  
POP { LR }  

// System 모드 레지스터를 저장한다.  
// R4-R14 저장  
ADD R3, R0, #16  
STMIA R3, { R4-R14 }^

MRS R3, CPSR  
BIC R3, R3, #0xDF  
ORR R3, R3, #0x92 // ISR 모드로 변경  
MSR CPSR, R3

LDMFD SP!, { R5-R6, R7 }  
//STR R6, [ R7, #0x208 ]  
// SPSR 저장  
//MSR SPSR, R0  
STR R5, [ R0, #64 ]

// R0-R3, R12, IRQ_LR 저장  
LDMFD SP!, { R5-R8, R12, LR }  
STMIA R0, { R5-R8 }  
STR R12, [ R0, #48 ]  
SUB LR, LR, #0x04  
STR LR, [ R0, #60 ]  
// 여기까지 오면 저장 끝...

// 여기서 부터는 복원 시작..

// SPSR 복원  
LDR R5, [ R1, #64 ]  
MSR SPSR, R5  
LDR LR, [ R1, #60 ]

// 인터럽트 가능 설정  
MOV R3, #0x4000001  
STR R3, [ R3, #0x207 ]

 LDMIA R1, { R0-R14 }^

//SUBS PC, LR, #0x00  
 MOVS PC, LR

/*  
Task Switch용 데이터를 저장하는 공간  
*/  
g_dwCurTask: nop  
nop  
g_dwNextTask:  
nop  
nop

간단한 아이디어는 아래와 같다.

  • libnds에 의해 호출되는 타이머 인터럽트 핸들러ARM 코드로 작성한다.
  • 스케줄링을 통해 스위칭할 태스크의 변수를 설정하고 기타 처리를하는 함수는 C로 작성한다.(THUMB 코드가 된다.)
  • 스케줄링 함수에서는 전역 변수 g_dwCurTask, g_dwNextTask 변수에 태스크 스위칭할 태스크 구조체를 넣어서 리턴한다.
  • 타이머 핸들러 함수(ARM 코드)에서에 전역 변수 g_dwCurTask, g_dwNextTask를 이용하여** 태스크 스위칭 함수(ARM 코드)**를 호출한다.
    • 태스크 스위칭 함수로 들어오면 스택 구조는 위 그림에서 보는 것과 같은 형태로 되어있으므로 IRQ 모드 및 SYSTEM 모드를 오가면서 태스크를 저장하고 복원한다.
    • THUMB 모드에서 이 함수를 호출 할 경우에는 Proxy 함수(THUMB에서 ARM 모드로 변경하는 함수)를 통해 호출되므로 모드가 변경되어 호출된다. 따라서 이 THUMB 모드 함수에서 태스크 스위칭 함수(ARM 코드)를 부르게 되면 모드 전환같은 곤란한 문제가 생기므로 약간 곤란하다. 이를 피하기 위해 내가 만든 타이머 인터럽트 핸들러 함수(ARM 코드)에서 태스크 스위칭 함수를 호출하여준다.
  • 만약 스케줄러가 NULL 값을 전역 변수 g_dwNextTask로 설정하면 태스크 스위칭을 호출하지 않고 정상적인 루트로 리턴을 하여 돌아간다. THUMB 모드에서 ARM 모드로 이동했을때 Proxy를 사용한다는 것은 알고 있었지만... 그게 이렇게 큰 문제를 일으킬줄은 몰랐다. 단순한 함수의 호출같은 문제는 크게 관계 없지만... 컨텍스트 스위칭의 경우는 세세한 부분까지 신경을 써줘야 하는 부분인데... 이렇게 되니 완전 눈물이 나는... ㅜ_ㅜ

3.스케줄러(Scheduler) 및 태스크(Task) 함수들...

스케줄러의 기본형태는 OS 프레임워크의 소스를 그대로 따르고 있다. 스케줄링 알고리즘은 라운드 로빈의 형태를 띄고 있고 지금 최대 5개까지 생성 가능하도록 되어있다. 스케줄러에 대한 자세한 내용은 Part14. Tutorial2-멀티 태스킹(Multi Tasking) 기능을 추가해 보자의 내용을 살펴보면 된다.

3.1 태스크 등록 및 타이머의 처리

ARM9의 main.cpp 파일을 보면 타이머를 설정하고 타이머 핸들러를 등록하는 부분이 있다.

// Timer의 Tick Interval 20ms  
#define TIMER_TICKINTERVAL_MS 20
/**
Timer의 핸들러  
*/  
extern "C" DWORD isrTimerInC( void )  
{
    char vcBuffer[ 2 ];  

    REG_IF |= IRQ_TIMER0;  
    VBLANK_INTR_WAIT_FLAGS |= IRQ_TIMER0;  

    g_uiTimerCount++;

    vcBuffer[ 1 ] = 0;  
    vcBuffer[ 0 ] = g_uiTimerCount & 0x7F;  
    // 타이머가 돌아가고 있다는 것을 표시한다.  
    SUBPRINT( 41, 0, vcBuffer );

    // 스케줄러를 호출한다.
    Scheduler();  

    return 0;  
}

/**  
IRQ를 설정한다.
  신버전.. libnds의 루틴을 사용하도록 수정  
*/  
void SetIrq( void )  
{  
    // 인터럽트 불가 설정
    REG_IME = 0;

    irqInit();
    irqSet( IRQ_VBLANK, isrVBlank );
    irqEnable( IRQ_VBLANK );  
    irqSet( IRQ_TIMER0, isrTimerInAsm );  
    irqEnable( IRQ_TIMER0 ); 

    // 20 ms 마다 한번씩 튀도록 한다.
    TIMER0_DATA = TIMER_FREQ_256( 1000 / TIMER_TICKINTERVAL_MS );  

    // 테스트 용으로 분주를 좀 늘려서 천천히 튀게 했다. 
    TIMER0_CR = TIMER_ENABLE | TIMER_IRQ_REQ | TIMER_DIV_256;
    REG_IF |= 0xFFFFFFFF;  
    REG_IME = 1;  
}

단순히 타이머를 20ms 마다 튀도록 설정해 놓고 isrTimerInC 함수에서 스케줄러 함수를 부르는 것을 볼 수 있다. 스케줄러 함수는 g_dwCurTask 및 g_dwNextTask 에 스위칭 할 태스크들을 설정해서 리턴한다. isrTimerInC 함수가 리턴되고 나면 위에서 설명했던 isrTimerInAsm 함수에서 실제로 스위칭하는 함수를 부르게 된다.

3.2 Snake 게임

원래 Snake 게임은 꼬리가 길어지고 점점 사과가 늘어나고 그래야 하지만.... 내가 만든 Snake 게임은... 꼬리도 안 길어지고 사과만 먹으면 된다. 사과(녹색 $ 표시)를 다 먹으면 먹은 사과의 개수와 Play하는데 걸린 시간이 표시된다. 사과를 다 먹기 전에 벽(하얀색 #)에 부딪히게되면 게임이 종료되므로 주의해야 한다. 게임이 종료되면 A 버튼을 누르면 다시 게임을 제개할 수 있다.

제일 주의할 점은.... 중독성이 있으니 자제를 해야한다는 것이다. 시간 갱신에 힘쓰다보면 큰일이 생기니 적당히 하도록 하자. @0@)/~~

3.3 기타 테스크

다른 테스크들은 매우 간단한 일만 하는 테스크이므로 굳이 설명을 하지 않겠다. 소스를 보면 쉽게 이해가 될터이니...(하는 일도 별로 없고.. ㅡ_ㅡ;;;)

4.실행화면


<NDS 커널의 실행화면>

NDS 커널을 실행하면 위와 같은 화면이 나온다. 총 5개의 태스크가 돌아가는 화면이며 각 태스크는 아래와 같은 위치에 있다.

NDS 커널의 테스트 동작 화면

위의 붉은 색 사각형 하나하나가 다 개별적인 태스크로 동작하며 시분할 스케줄링 기법을 이용하여 동시(??)에 동작한다. 특히 Task3번의 경우는 2초마다 사운드를 출력하도록 되어있기 때문에 주기적으로 울리는 소리를 들을 수 있다.

5.추가사항

5.1 2007/08/29 추가 사항

  • Snake 게임에 보글보글 배경음악 추가
  • Task3은 배경음악을 반복하는 주기를 50초로 설정함. 보글 보글 배경음악이 47초 가량이라서 2~3초 Delay가 생김
  • Snake가 움직일 때 움직이는 소리 추가
  • Snake가 죽을 때 죽는 소리 추가

5.첨부

186343_NDSKernel.zip
2.5 MB

03 비디오 모드 제어(Video Mode Control)

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

 

들어가기 전에...

 

0.시작하면서...

 NDS는 게임기로 제작되어있기 때문에 다양한 비디오 모드가 존재한다. 홈 브루를 개발하기위해서는 자신이 원하는 모드로 설정해야 하는데, 어떤 순서로 해야 하는지 알아보자.

 일단 그래픽을 화면에 표시하기 위해서는 LCD를 켜야 한다. LCD를 키는 부분은 파워 컨트롤(Power Control) 부분에서 하는 것이므로 04 파워 컨트롤(Power Control) 문서를 참조하도록 하자.

 그래픽쪽 파워를 설정했으면 이제 비디오 모드를 설정해야 한다. http://nocash.emubase.de/gbatek.htm#dsvideo 를 보면 상세하게 나와있으니 참고하고 몇가지 부분만 보자.

 참고로 파워 컨트롤에서 나왔지만 LCD Swap이 되지 않은 상태라면 하단 LCD가 Engine A가 되고 상단 LCD가 Engine B가 된다. Engine B는 A보다 약간 모자란데, GBA 게임을 넣으면 상단으로 표시되는걸 봐서 GBA 게임을 돌리기위한 엔진의 역할도 하고 있는 것 같다.

2D Engines
Includes two 2D Engines, called A and B. Both engines are accessed by the ARM9 processor
, each using different memory and register addresses:

Region______Engine A______________Engine B___________
  I/O Ports   4000000h              4001000h
  Palette     5000000h (1K)         5000400h (1K)
  BG VRAM     6000000h (max 512K)   6200000h (max 128K)
  OBJ VRAM    6400000h (max 256K)   6600000h (max 128K)
  OAM         7000000h (1K)         7000400h (1K)

Engine A additionally supports 3D and large-screen 256-color Bitmaps, plus main-memory-display and vram-display modes, plus capture unit.

 Engine A에는 B에는 없는 3D와 큰 비트맵을 처리할 수 있는 모드, Main Memory를 그대로 Display해주는 모드(프레임 버퍼 모드), 그리고 비디오 화면을 그대로 캡쳐해서 다시 메모리로 저장해주는 캡쳐모드 등등이 있다.

 Engine A와 Engine B의 VRAM 최대크기는 각각 512Kbyte 및 128Kbyte라는 것을 알아두자.

 

 BG VRAM 주소와 OBJ VRAM, OAM 주소는 고정되어 있으나 실제 VRAM 주소에 여러가지 VRAM들(VRAM_A, VRAMB, VRAM_C등등)을 맵핑하여 사용하는 방식이며 Video 모드에 따라서 적당한 크기의 VRAM을 맵핑하면 된다.

 위에서 잠깐 OAM이라는 생소한 용어가 나오는데, 스프라이트 관련 정보를 저장하는 메모리이다. 게임 제작에 대해서는 큰 비중을 두지 않을 것이므로 그냥 알고 넘어가자.

OAM - Object Attribute Memory
This memory area contains Attributes which specify position, size, color depth, etc. appearance for each of the 128 OBJs. Additionally, it contains 32 OBJ Rotation/Scaling Parameter groups. OAM is located at 07000000-070003FF (sized 1 KByte).

 위의 표에서 보면 알 수 있듯이 메인 메모리가 4Mbyte 밖에 안되는 것을 생각해 볼때 꽤 많은 비디오 메모리를 가지고 있는 것을 알 수 있다. 뒤에 비디오 메모리를 어느 LCD에다 맵핑할지를 결정하는 부분이 있는데, 맵핑하고 남은 영역은 데이터 영역으로도 활용 가능할 것 같다.

 

 비디오 모드를 설정하는 과정을 간단히 요약하면 아래와 같다.

NDS_비디오_컨트롤.PNG

  1. Display Control Register를 설정하여 비디오 모드를 설정한다.
  2. VRAMxCNT 레지스터를 이용하여 Video RAM을 MAIN BG Address-Engine A(6000000h), SUB BG Address-Engine B(6200000h)부터 적당히 맵핑해 준다.
  3. BG 모드 사용시 BGxCNT 레지스터를 사용하여 Background의 속성을 설정한다.
  4. 비디오 메모리에 쓴다.

 

 아래는 NDS의 Video Memory와 Controller의 관계를 그린것이다.

 DS Video Display System Block Diagram
            _____________               __________
  VRAM A -->| 2D Graphics |--------OBJ->|          |
  VRAM B -->| Engine A    |--------BG3->| Layering |
  VRAM C -->|             |--------BG2->| and      |
  VRAM D -->|             |--------BG1->| Special  |
  VRAM E -->|             |   ___       | Effects  |
  VRAM F -->|             |->|SEL|      |          |          ______
  VRAM G -->| - - - - - - |  |BG0|-BG0->|          |----+--->|      |
            | 3D Graphics |->|___|      |__________|    |    |Select|
            | Engine      |                             |    |Video |
            |_____________|--------3D----------------+  |    |Input |
             _______      _______              ___   |  |    |      |
            |       |    |       |<-----------|SEL|<-+  |    |and   |-->
            |       |    |       |    _____   |A  |     |    |      |
  VRAM A <--|Select |    |Select |   |     |<-|___|<----+    |Master|
  VRAM B <--|Capture|<---|Capture|<--|Blend|   ___           |Bright|
  VRAM C <--|Dest.  |    |Source |   |_____|<-|SEL|<----+    |A     |
  VRAM D <--|       |    |       |            |B  |     |    |      |
            |_______|    |_______|<-----------|___|<-+  |    |      |
             _______                                 |  |    |      |
  VRAM A -->|Select |                                |  |    |      |
  VRAM B -->|Display|--------------------------------+------>|      |
  VRAM C -->|VRAM   |                                   |    |      |
  VRAM D -->|_______|   _____________                   |    |      |
                       |Main Memory  |                  |    |      |
  Main   ------DMA---->|Display FIFO |------------------+--->|______|
  Memory               |_____________|
             _____________               __________           ______
  VRAM C -->| 2D Graphics |--------OBJ->| Layering |         |      |
  VRAM D -->| Engine B    |--------BG3->| and      |         |Master|
  VRAM H -->|             |--------BG2->| Special  |-------->|Bright|-->
  VRAM I -->|             |--------BG1->| Effects  |         |B     |
            |_____________|--------BG0->|__________|         |______|

 이제 각 파트에 대해서 자세히 알아보자.

 

1. Display Control

  제일 처음 Display Control 레지스터를 설정하여 표시될 모드를 설정해야 한다. http://nocash.emubase.de/gbatek.htm#lcdiodisplaycontrol를 보면 아래와 같이 나와있다(GBA의 LCD Control 설정이다. 일단 비트의 기능은 NDS와 거의 동일하니까 먼저 보자).

1.1 GBA Display Control Register

4000000h - DISPCNT - LCD Control (Read/Write)

Bit   Expl.
  0-2   BG Mode                (0-5=Video Mode 0-5, 6-7=Prohibited)
  3     Reserved for BIOS      (CGB Mode - cannot be changed after startup)
  4     Display Frame Select   (0-1=Frame 0-1) (for BG Modes 4,5 only)
  5     H-Blank Interval Free  (1=Allow access to OAM during H-Blank)
  6     OBJ Character VRAM Mapping (0=Two dimensional, 1=One dimensional)
  7     Forced Blank           (1=Allow access to VRAM,Palette,OAM)
  8     Screen Display BG0  (0=Off, 1=On)
  9     Screen Display BG1  (0=Off, 1=On)
  10    Screen Display BG2  (0=Off, 1=On)
  11    Screen Display BG3  (0=Off, 1=On)
  12    Screen Display OBJ  (0=Off, 1=On)
  13    Window 0 Display Flag   (0=Off, 1=On)
  14    Window 1 Display Flag   (0=Off, 1=On)
  15    OBJ Window Display Flag (0=Off, 1=On)
The table summarizes the facilities of the separate BG modes (video modes).
Mode  Rot/Scal Layers Size               Tiles Colors       Features
  0     No       0123   256x256..512x515   1024  16/16..256/1 SFMABP
  1     Mixed    012-   (BG0,BG1 as above Mode 0, BG2 as below Mode 2)
  2     Yes      --23   128x128..1024x1024 256   256/1        S-MABP
  3     Yes      --2-   240x160            1     32768        --MABP
  4     Yes      --2-   240x160            2     256/1        --MABP
  5     Yes      --2-   160x128            2     32768        --MABP

Features: S)crolling, F)lip, M)osaic, A)lphaBlending, B)rightness, P)riority.

 

BG Modes 0-2bit

BG Modes 0-2 are Tile/Map-based. BG Modes 3-5 are Bitmap-based, in these modes 1 or 2 Frames (ie. bitmaps, or 'full screen tiles') exists, if two frames exist, either one can be displayed, and the other one can be redrawn in background.

 

Blanking Bits 5, 7Bit

Setting Forced Blank (Bit 7) causes the video controller to display white lines, and all VRAM, Palette RAM, and OAM may be accessed.
"When the internal HV synchronous counter cancels a forced blank during a display period, the display begins from the beginning, following the display of two vertical lines." What ?
Setting H-Blank Interval Free (Bit 5) allows to access OAM during H-Blank time - using this feature reduces the number of sprites that can be displayed per line. 

 

Screen Display BG Bits 8~12Bit, Window Display Bits 13~14Bit, Object Window Display Bit 15Bit

By default, BG0-3 and OBJ Display Flags (Bit 8-12) are used to enable/disable BGs and OBJ. When enabling Window 0 and/or 1 (Bit 13-14), color special effects may be used, and BG0-3 and OBJ are controlled by the window(s).

 

Frame Selection 4bit

In BG Modes 4 and 5 (Bitmap modes), either one of the two bitmaps/frames may be displayed (Bit 4), allowing the user to update the other (invisible) frame in background. In BG Mode 3, only one frame exists.
In BG Modes 0-2 (Tile/Map based modes), a similar effect may be gained by altering the base address(es) of BG Map and/or BG Character data.

 

4000002h - Undocumented - Green Swap (R/W)

Normally, red green blue intensities for a group of two pixels is output as BGRbgr (uppercase for left pixel at even xloc, lowercase for right pixel at odd xloc). When the Green Swap bit is set, each pixel group is output as BgRbGr (ie. green intensity of each two pixels exchanged).

  Bit   Expl.
  0     Green Swap  (0=Normal, 1=Swap)
  1-15  Not used

This feature appears to be applied to the final picture (ie. after mixing the separate BG and OBJ layers). Eventually intended for other display types (with other pin-outs). With normal GBA hardware it is just producing an interesting dirt effect.
The NDS DISPCNT registers are 32bit (4000000h..4000003h), so Green Swap doesn't exist in NDS mode, however, the NDS does support Green Swap in GBA mode.

 

 

1.2 NDS Display Control Register

 이제 본격적으로 NDS에 대해서 알아보자. GBA와 비교해서 같은 이름의 비트는 거의 같은 일을 하므로 GBA의 정보와 같이 비교해서 보자.

DS DISPCNT

Bit  Engine Expl.
  0-2   A+B   BG Mode
  3     A     BG0 2D/3D Selection (instead CGB Mode) (0=2D, 1=3D)
  4     A+B   Tile OBJ Mapping        (0=2D; max 32KB, 1=1D; max 32KB..256KB)
  5     A+B   Bitmap OBJ 2D-Dimension (0=128x512 dots, 1=256x256 dots)
  6     A+B   Bitmap OBJ Mapping      (0=2D; max 128KB, 1=1D; max 128KB..256KB)
  7     Forced Blank           (1=Allow access to VRAM,Palette,OAM)
  8     Screen Display BG0  (0=Off, 1=On)
  9     Screen Display BG1  (0=Off, 1=On)
  10    Screen Display BG2  (0=Off, 1=On)
  11    Screen Display BG3  (0=Off, 1=On)
  12    Screen Display OBJ  (0=Off, 1=On)
  13    Window 0 Display Flag   (0=Off, 1=On)
  14    Window 1 Display Flag   (0=Off, 1=On)
  15    OBJ Window Display Flag (0=Off, 1=On)
  16-17 A+B   Display Mode (Engine A: 0..3, Engine B: 0..1, GBA: Green Swap)
  18-19 A     VRAM block (0..3=VRAM A..D) (For Capture & above Display Mode=2)
  20-21 A+B   Tile OBJ 1D-Boundary   (see Bit4)
  22    A     Bitmap OBJ 1D-Boundary (see Bit5-6)
  23    A+B   OBJ Processing during H-Blank (was located in Bit5 on GBA)
  24-26 A     Character Base (in 64K steps) (merged with 16K step in BGxCNT)
  27-29 A     Screen Base (in 64K steps) (merged with 2K step in BGxCNT)
  30    A+B   BG Extended Palettes   (0=Disable, 1=Enable)
  31    A+B   OBJ Extended Palettes  (0=Disable, 1=Enable)

 

 

BG Mode

Engine A BG Mode (DISPCNT LSBs) (0-6, 7=Reserved), Engine B도 5번까지는 같음

  Mode  BG0      BG1      BG2      BG3
  0     Text/3D  Text     Text     Text
  1     Text/3D  Text     Text     Affine
  2     Text/3D  Text     Affine   Affine
  3     Text/3D  Text     Text     Extended
  4     Text/3D  Text     Affine   Extended
  5     Text/3D  Text     Extended Extended
  6     3D       -        Large    -

Extended 모드는 Backgound 속성과 함께 다시 세부 모드로 나누어 진다. 즉 위의 GBA 모드와는 다르다.

Of which, the "Extended" modes are sub-selected by BGxCNT bits:

(BGxCNT.Bit7은 Colors/Palettes Bit이고 , Bit2는 Character Base Block의 첫번째 bit이다)

  BGxCNT.Bit7 BGxCNT.Bit2 Extended Affine Mode Selection
  0           CharBaseLsb rot/scal with 16bit bgmap entries (Text+Affine mixup)
  1           0           rot/scal 256 color bitmap
  1           1           rot/scal direct color bitmap

direct color bitmap 모드를 사용하면 R/G/B 각각이 5bit이고 Alpha가 1bit인 Color Mode를 사용할 수 있다. 뒤에서 이 모드를 이용하여 프레임 버퍼 모드와 비슷하게 설정할 것이다.

 

Engine B: Same as above, except that: Mode 6 is reserved (no Large screen bitmap), and BG0 is always Text (no 3D support).

 

Affine = formerly Rot/Scal mode (with 8bit BG Map entries)
Large Screen Bitmap = rot/scal 256 color bitmap (using all 512K of 2D VRAM)

 

Display Mode (DISPCNT.16-17):

0  Display off (screen becomes white)
  1  Graphics Display (normal BG and OBJ layers)
  2  Engine A only: VRAM Display (Bitmap from block selected in DISPCNT.18-19)
  3  Engine A only: Main Memory Display (Bitmap DMA transfer from Main RAM)

Mode 2-3 display a raw direct color bitmap (15bit RGB values, the upper bit in each halfword is unused), without any further BG,OBJ,3D layers, these modes are completely bypassing the 2D/3D engines as well as any 2D effects, however the Master Brightness effect can be applied to these modes. Mode 2 is particulary useful to display captured 2D/3D images (in that case it can indirectly use the 2D/3D engine).

 

BGxCNT
character base extended from bit2-3 to bit2-5 (bit4-5 formerly unused)

character base is used only in tile/map modes (not bitmap modes)

screen base is used in tile/map modes, screen base used in bitmap modes as BGxCNT.bits*16K, without DISPCNT.bits*64K

engine A screen base: BGxCNT.bits*2K + DISPCNT.bits*64K
  engine B screen base: BGxCNT.bits*2K + 0
  engine A char base: BGxCNT.bits*16K + DISPCNT.bits*64K
  engine B char base: BGxCNT.bits*16K + 0
engine A bitmap screen base: BGxCNT.bit*16K + 0
  engine B bitmap screen base: BGxCNT.bit*16K + 0 

위와 같이 Bitmap 모드일 경우 Offset은 16K가 곱해진다. 이것을 잘 기억해 두면 Double Buffering을 할 때 잘 사용할 수 있다.

 

screen base however NOT used at all for Large screen bitmap mode

bgcnt sc.size  text     rotscal    bitmap   large bmp
  0              256x256  128x128    128x128  512x1024
  1              512x256  256x256    256x256  1024x512
  2              256x512  512x512    512x256  -
  3              512x512  1024x1024  512x512  -

bitmaps that require more than 128K VRAM are supported on engine A only.

For BGxCNT.Bit7 and BGxCNT.Bit2 in Extended Affine modes, see above BG Mode description (extended affine doesn't include 16-color modes, so color depth bit can be used for mode selection. Also, bitmap modes do not use charbase, so charbase.0 can be used for mode selection as well).

for BG0, BG1 only: bit13 selects extended palette slot

(BG0: 0=Slot0, 1=Slot2, BG1: 0=Slot1, 1=Slot3)

Direct Color Bitmap BG, and Direct Color Bitmap OBJ
BG/OBJ Supports 32K colors (15bit RGB value) - so far same as GBAs BG.
However, the upper bit (Bit15) is used as Alpha flag. That is, Alpha=0=Transparent, Alpha=1=Normal (ie. on the NDS, Direct Color values 0..7FFFh are NOT displayed).

 위에서 BGxCNT 레지스터에 대한 설정이 잠깐 나왔는데, Background(BG) 모드를 사용하면 해당 BG에 대한 설정을 해줘야 한다. 자세한 내용은 다음 섹션에서 알아보자

 아래는 Charactor(Bitmap)/Screen 모드에 따른 Offset 별 주소 공간을 나타낸 것이다. http://www.dev-scene.com/NDS/Tutorials_Day_3를 보면 잘 나와있다.

메모리_주소.PNG

 

 

1.3 BG Mode Detail

 위에서 보면 BG 모드에 따라서 BG 0~3의 용도가 달라졌다. 이제 좀더 자세하게 알아보자. 이 부분에 대한 자세한 내용은 http://nocash.emubase.de/gbatek.htm#lcdvramoverview에서 찾을 수 있다.

 BG Mode 0,1,2 (Tile/Map based Modes)

06000000-0600FFFF  64 KBytes shared for BG Map and Tiles
  06010000-06017FFF  32 KBytes OBJ Tiles
The shared 64K area can be split into BG Map area(s), and BG Tiles area(s), the respective addresses for Map and Tile areas are set up by BG0CNT-BG3CNT registers. The Map address may be specified in units of 2K (steps of 800h), the Tile address in units of 16K (steps of 4000h).

 BG Mode의 0~2 모드는 위와 같이 영역이 정해진다.

 

BG Mode 0,1 (Tile/Map based Text mode)
The tiles may have 4bit or 8bit color depth, minimum map size is 32x32 tiles, maximum is 64x64 tiles, up to 1024 tiles can be used per map.
Item        Depth     Required Memory
  One Tile    4bit      20h bytes
  One Tile    8bit      40h bytes
  1024 Tiles  4bit      8000h (32K)
  1024 Tiles  8bit      10000h (64K) - excluding some bytes for BG map
  BG Map      32x32     800h (2K)
  BG Map      64x64     2000h (8K)
BG Mode 1,2 (Tile/Map based Rotation/Scaling mode)
The tiles may have 8bit color depth only, minimum map size is 16x16 tiles, maximum is 128x128 tiles, up to 256 tiles can be used per map.

BG Mode 3 (Bitmap based Mode for still images)
06000000-06013FFF  80 KBytes Frame 0 buffer (only 75K actually used)
  06014000-06017FFF  16 KBytes OBJ Tiles

BG Mode 4,5 (Bitmap based Modes)

06000000-06009FFF  40 KBytes Frame 0 buffer (only 37.5K used in Mode 4)
  0600A000-06013FFF  40 KBytes Frame 1 buffer (only 37.5K used in Mode 4)
  06014000-06017FFF  16 KBytes OBJ Tiles
Note
Additionally to the above VRAM, the GBA also contains 1 KByte Palette RAM (at 05000000h) and 1 KByte OAM (at 07000000h) which are both used by the display controller as well.

  위에서 3,4,5와 같은 경우는 우리가 흔히 사용하는 프레임 버퍼모드와 비슷하게 동작하게 만들 수 있다. 단 Direct Color 모드를 사용해야 하는데, Direct Color 모드일 경우 R/G/B 각각 5bit와 Alpha 1bit로 이루어지는 차이 밖에 없다.

 

  각 BG 모드에 따라서 위와 같이 사용된다. 좀 더 자세한 내용은 아래와 같다.

LCD VRAM Character Data

Each character (tile) consists of 8x8 dots (64 dots in total). The color depth may be either 4bit or 8bit (see BG0CNT-BG3CNT).

 

4bit depth (16 colors, 16 palettes)

Each tile occupies 32 bytes of memory(왜? 8*8*4bit니까), the first 4 bytes for the topmost row of the tile, and so on. Each byte representing two dots, the lower 4 bits define the color for the left dot, the upper 4 bits the color for the right dot.

8bit depth (256 colors, 1 palette)
Each tile occupies 64 bytes of memory(왜? 8*8*8bit니까), the first 8 bytes for the topmost row of the tile, and so on. Each byte selects the palette entry for each dot.

 

LCD VRAM BG Screen Data Format (BG Map)

The display background consists of 8x8 dot tiles, the arrangement of these tiles is specified by the BG Screen Data (BG Map). The separate entries in this map are as follows:

Text BG Screen (2 bytes per entry)
Specifies the tile number and attributes. Note that BG tile numbers are always specified in steps of 1 (unlike OBJ tile numbers which are using steps of two in 256 color/1 palette mode).

  Bit   Expl.
  0-9   Tile Number     (0-1023) (a bit less in 256 color mode, because
                           there'd be otherwise no room for the bg map)
  10    Horizontal Flip (0=Normal, 1=Mirrored)
  11    Vertical Flip   (0=Normal, 1=Mirrored)
  12-15 Palette Number  (0-15)    (Not used in 256 color/1 palette mode)
A Text BG Map always consists of 32x32 entries (256x256 pixels), 400h entries = 800h bytes. However, depending on the BG Size, one, two, or four of these Maps may be used together, allowing to create backgrounds of 256x256, 512x256, 256x512, or 512x512 pixels, if so, the first map (SC0) is located at base+0, the next map (SC1) at base+800h, and so on.

Rotation/Scaling BG Screen (1 byte per entry)
In this mode, only 256 tiles can be used. There are no x/y-flip attributes, the color depth is always 256 colors/1 palette.
Bit   Expl.
  0-7   Tile Number     (0-255)
The dimensions of Rotation/Scaling BG Maps depend on the BG size. For size 0-3 that are: 16x16 tiles (128x128 pixels), 32x32 tiles (256x256 pixels), 64x64 tiles (512x512 pixels), or 128x128 tiles (1024x1024 pixels).
The size and VRAM base address of the separate BG maps for BG0-3 are set up by BG0CNT-BG3CNT registers.

 

LCD VRAM Bitmap BG Modes

In BG Modes 3-5 the background is defined in form of a bitmap (unlike as for Tile/Map based BG modes). Bitmaps are implemented as BG2, with Rotation/Scaling support. As bitmap modes are occupying 80KBytes of BG memory, only 16KBytes of VRAM can be used for OBJ tiles.

BG Mode 3 - 240x160 pixels, 32768 colors
Two bytes are associated to each pixel, directly defining one of the 32768 colors (without using palette data, and thus not supporting a 'transparent' BG color).
Bit   Expl.
  0-4   Red Intensity   (0-31)
  5-9   Green Intensity (0-31)
  10-14 Blue Intensity  (0-31)
  15    Not used
The first 480 bytes define the topmost line, the next 480 the next line, and so on. The background occupies 75 KBytes (06000000-06012BFF), most of the 80 Kbytes BG area, not allowing to redraw an invisible second frame in background, so this mode is mostly recommended for still images only.

BG Mode 4 - 240x160 pixels, 256 colors (out of 32768 colors)
One byte is associated to each pixel, selecting one of the 256 palette entries. Color 0 (backdrop) is transparent, and OBJs may be displayed behind the bitmap.
The first 240 bytes define the topmost line, the next 240 the next line, and so on. The background occupies 37.5 KBytes, allowing two frames to be used (06000000-060095FF for Frame 0, and 0600A000-060135FF for Frame 1).

BG Mode 5 - 160x128 pixels, 32768 colors
Colors are defined as for Mode 3 (see above), but horizontal and vertical size are cut down to 160x128 pixels only - smaller than the physical dimensions of the LCD screen.
The background occupies exactly 40 KBytes, so that BG VRAM may be split into two frames (06000000-06009FFF for Frame 0, and 0600A000-06013FFF for Frame 1).

In BG modes 4,5, one Frame may be displayed (selected by DISPCNT Bit 4), the other Frame is invisible and may be redrawn in background.

  Frame이 1개 이상일 경우는 Display Control Register의 Bit4에 값을 설정함으로써 현재 표시되는 부분을 바꿀 수 있다.

 

1.4 Object

 Object에 대한 자세한 내용은 http://nocash.emubase.de/gbatek.htm#lcdobjoverview를 보자.

 

2. VRAM Control

 Display 모드를 선택했으니 이제 적당한 크기에 맞는 VRAM을 각 주소로 맵핑을 시켜야 한다. VRAM 제어에 대한 내용은 http://nocash.emubase.de/gbatek.htm#dsmemorycontrolvram에서 살펴볼 수 있다.

4000240h - NDS7 - VRAMSTAT - 8bit - VRAM Bank Status (R)

0     VRAM C enabled and allocated to NDS7  (0=No, 1=Yes)
  1     VRAM D enabled and allocated to NDS7  (0=No, 1=Yes)
  2-7   Not used (always zero)
The register indicates if VRAM C/D are allocated to NDS7 (as Work RAM), ie. if VRAMCNT_C/D are enabled (Bit7=1), with MST=2 (Bit0-2). However, it does not reflect the OFS value.

  위 레지스터는 VRAM C와 D가 ARM7에서 WRAM의 용도로 사용가능한가를 나타내는 레지스터이다.

 

2.1 VRAM의 종류 및 컨트롤 모드

  실제로 VRAM Control 레지스터는 8bit로 되어있으며 아래와 같다.

4000240h - VRAMCNT_A - 8bit - VRAM-A (128K) Bank Control (W)
4000241h - VRAMCNT_B - 8bit - VRAM-B (128K) Bank Control (W)

4000242h - VRAMCNT_C - 8bit - VRAM-C (128K) Bank Control (W)
4000243h - VRAMCNT_D - 8bit - VRAM-D (128K) Bank Control (W)
4000244h - VRAMCNT_E - 8bit - VRAM-E (64K) Bank Control (W)
4000245h - VRAMCNT_F - 8bit - VRAM-F (16K) Bank Control (W)
4000246h - VRAMCNT_G - 8bit - VRAM-G (16K) Bank Control (W)
4000248h - VRAMCNT_H - 8bit - VRAM-H (32K) Bank Control (W)
4000249h - VRAMCNT_I - 8bit - VRAM-I (16K) Bank Control (W)

0-2   VRAM MST              ;Bit2 not used by VRAM-A,B,H,I
  3-4   VRAM Offset (0-3)     ;Offset not used by VRAM-E,H,I
  5-6   Not used
  7     VRAM Enable (0=Disable, 1=Enable)
There is a total of 656KB of VRAM in Blocks A-I.

 

Notes

In Plain-CPU modes :

VRAM can be accessed only by the CPU (and by the Capture Unit, and by VRAM Display mode). In "Plain <ARM7>-CPU Access" mode, the VRAM blocks are allocated as Work RAM to the NDS7 CPU.

In BG/OBJ VRAM modes :

VRAM can be accessed by the CPU at specified addresses, and by the display controller.

In Extended Palette and Texture Image/Palette modes :

VRAM is not mapped to CPU address space, and can be accessed only by the display controller (so, to initialize or change the memory, it should be temporarily switched to Plain-CPU mode).

All VRAM (and Palette, and OAM) can be written to only in 16bit and 32bit units (STRH, STR opcodes), 8bit writes are ignored (by STRB opcode). The only exception is "Plain <ARM7>-CPU Access" mode: The ARM7 CPU can use STRB to write to VRAM (the reason for this special feature is that, in GBA mode, two 128K VRAM blocks are used to emulate the GBA's 256K Work RAM).

 

2.2 세부 모드별 VRAM 설정

  1. Plain ARM9-CPU Access (so-called LCDC mode)
        VRAM    SIZE  MST  OFS   ARM9
        A       128K  0    -     6800000h-681FFFFh
        B       128K  0    -     6820000h-683FFFFh
        C       128K  0    -     6840000h-685FFFFh
        D       128K  0    -     6860000h-687FFFFh
        E       64K   0    -     6880000h-688FFFFh
        F       16K   0    -     6890000h-6893FFFh
        G       16K   0    -     6894000h-6897FFFh
        H       32K   0    -     6898000h-689FFFFh
        I       16K   0    -     68A0000h-68A3FFFh
  2. ARM9 2D Graphics Engine A, BG-VRAM (max 512K)
        VRAM    SIZE  MST  OFS  
        A,B,C,D 128K  1    0..3  6000000h+(20000h*OFS)
        E       64K   1    -     6000000h
        F,G     16K   1    0..3  6000000h+(4000h*OFS.0)+(10000h*OFS.1)
  3. ARM9 2D Graphics Engine A, OBJ-VRAM (max 256K)
        VRAM    SIZE  MST  OFS  
        A,B     128K  2    0..1  6400000h+(20000h*OFS.0)  ;(OFS.1 must be zero)
        E       64K   2    -     6400000h
        F,G     16K   2    0..3  6400000h+(4000h*OFS.0)+(10000h*OFS.1)
  4. 2D Graphics Engine A, BG Extended Palette
        VRAM    SIZE  MST  OFS  
        E       64K   4    -     Slot 0-3  ;only lower 32K used
        F,G     16K   4    0..1  Slot 0-1 (OFS=0), Slot 2-3 (OFS=1)
  5. 2D Graphics Engine A, OBJ Extended Palette
        VRAM    SIZE  MST  OFS  
        F,G     16K   5    -     Slot 0  ;16K each (only lower 8K used)
  6. Texture/Rear-plane Image
        VRAM    SIZE  MST  OFS  
        A,B,C,D 128K  3    0..3  Slot OFS(0-3)   ;(Slot2-3: Texture, or Rear-plane)
  7. Texture Palette
       VRAM    SIZE  MST  OFS
        E       64K   3    -     Slots 0-3                 ;OFS=don't care
        F,G     16K   3    0..3  Slot (OFS.0*1)+(OFS.1*4)  ;ie. Slot 0, 1, 4, or 5
  8. ARM9, 2D Graphics Engine B, BG-VRAM (max 128K)
        VRAM    SIZE  MST  OFS  
        C       128K  4    -     6200000h
        H       32K   1    -     6200000h
        I       16K   1    -     6208000h
  9. ARM9, 2D Graphics Engine B, OBJ-VRAM (max 128K)
        VRAM    SIZE  MST  OFS  
        D       128K  4    -     6600000h
        I       16K   2    -     6600000h
  10. 2D Graphics Engine B, BG Extended Palette
        VRAM    SIZE  MST  OFS  
        H       32K   2    -     Slot 0-3
  11. 2D Graphics Engine B, OBJ Extended Palette
        VRAM    SIZE  MST  OFS   
        I       16K   3    -     Slot 0  ;(only lower 8K used)
  12. <ARM7>, Plain <ARM7>-CPU Access
        VRAM    SIZE  MST  OFS  
        C,D     128K  2    0..1  6000000h+(20000h*OFS.0)  ;OFS.1 must be zero

 위에서 보면 Engine A가 확실히 B보다는 VRAM 선택권이 넓다는 것을 알 수 있다. 그리고 Engine A의 경우는 최대 512Kbyte까지 맵핑 가능하며 Engine B의 경우 최대 128Kbyte까지 맵핑이 가능하다.

 이렇게 맵핑이 끝나고 나면 Background Control Register를 통해서 해당 Background에 대한 설정을 해줘야 한다.

 

3. Background Control

3.1 Background Control Register

 Display Control Register를 통해서 원하는 Display 모드를 설정했으면 그에 따른 부수적인 처리를 해줘야 한다. Background 모드(BG)를 이용할 경우 해당 BG를 어떻게 사용할 것인지에 대한 처리를 해야 하는데 자세한 내용은 http://nocash.emubase.de/gbatek.htm#lcdiobgcontrol 를 참고하고 몇가지만 보자.

4000008h - BG0CNT - BG0 Control (R/W)
400000Ah - BG1CNT - BG1 Control (R/W)

400000Ch - BG2CNT - BG2 Control (R/W)
400000Eh - BG3CNT - BG3 Control (R/W)

Bit   Expl.
  0-1   BG Priority           (0-3, 0=Highest)
  2-3   Character Base Block  (0-3, in units of 16 KBytes) (=BG Tile Data)
  4-5   Not used (must be zero)
  6     Mosaic                (0=Disable, 1=Enable)
  7     Colors/Palettes       (0=16/16, 1=256/1)
  8-12  Screen Base Block     (0-31, in units of 2 KBytes) (=BG Map Data)
  13    Display Area Overflow (0=Transparent, 1=Wraparound; BG2CNT/BG3CNT only)
  14-15 Screen Size (0-3)

In case that some or all BGs are set to same priority then BG0 is having the highest, and BG3 the lowest priority.

 위의 Display Control에서 보았던 Character Base Block에 대한 영역과 Screen Base Block에 대한 영역값을 볼 수 있다. 그리고 특이한 것이 Display Area를 넘어갈때 그것을 어떻게 처리할 것인지에 대한 설정도 나와있다. Transparent는 아무 것도 안하는 것 같고 Warparound는 넘어선 부분을 다시 0 Base로 하여서 덮어쓰는것 같다.

 

Internal Screen Size (dots) and size of BG Map (bytes):

Value  Text Mode      Rotation/Scaling Mode
  0      256x256 (2K)   128x128   (256 bytes)
  1      512x256 (4K)   256x256   (1K)
  2      256x512 (4K)   512x512   (4K)
  3      512x512 (8K)   1024x1024 (16K)

 

In 'Text Modes', the screen size is organized as follows:

The screen consists of one or more 256x256 pixel (32x32 tiles) areas. When Size=0: only 1 area (SC0), when Size=1 or Size=2: two areas (SC0,SC1 either horizontally or vertically arranged next to each other), when Size=3: four areas (SC0,SC1 in upper row, SC2,SC3 in lower row). Whereas SC0 is defined by the normal BG Map base address (Bit 8-12 of BG#CNT), SC1 uses same address +2K, SC2 address +4K, SC3 address +6K. When the screen is scrolled it'll always wraparound.

 

In 'Rotation/Scaling Modes', the screen size is organized as follows:

only one area (SC0) of variable size 128x128..1024x1024 pixels (16x16..128x128 tiles) exists (SC0). When the screen is rotated/scaled (or scrolled?) so that the LCD viewport reaches outside of the background/screen area, then BG may be either displayed as transparent or wraparound (Bit 13 of BG#CNT).

 

3.2 Background Scrolling Register

 위에서 Background Mode에 대해서 설정했으면 Scrolling에 대한 처리를 해야한다. Scrolling은 큰 Bitmap을 그려놓고 해당 Bitmap내에서 일부분을 화면에 표시할때 유용하게 사용할 수 있다. Scrolling에 대한 자세한 내용은 http://nocash.emubase.de/gbatek.htm#lcdiobgscrolling를 보도록 하자.

4000010h - BG0HOFS - BG0 X-Offset (W)
4000012h - BG0VOFS - BG0 Y-Offset (W)

Bit   Expl.
  0-8   Offset (0-511)
  9-15  Not used
Specifies the coordinate of the upperleft first visible dot of BG0 background layer, ie. used to scroll the BG0 area.

4000014h - BG1HOFS - BG1 X-Offset (W)
4000016h - BG1VOFS - BG1 Y-Offset (W)
Same as above BG0HOFS and BG0VOFS for BG1 respectively.

4000018h - BG2HOFS - BG2 X-Offset (W)
400001Ah - BG2VOFS - BG2 Y-Offset (W)
Same as above BG0HOFS and BG0VOFS for BG2 respectively.

400001Ch - BG3HOFS - BG3 X-Offset (W)
400001Eh - BG3VOFS - BG3 Y-Offset (W)
Same as above BG0HOFS and BG0VOFS for BG3 respectively.

The above BG scrolling registers are exclusively used in Text modes, ie. for all layers in BG Mode 0, and for the first two layers in BG mode .
In other BG modes (Rotation/Scaling and Bitmap modes) above registers are ignored. Instead, the screen may be scrolled by modifying the BG Rotation/Scaling Reference Point registers.

  위의 레지스터 설명을 보면 BG Layer에서 표시될 위치를 찍는 것임을 알 수 있다. 하지만 정상적으로 사용하기 위해서는 Text Mode 일때만 가능하다고 되어있다. 다른 모드에서는 Rotation/Scaling을 사용하라는 것인데, 나중에 확실히 테스트 해봐야겠다.

 

3.3 Background Rotation/Scaling Register

 Text 모드가 아닌 경우 스크롤및 화면에 사용될 수 있다. 자세한 내용은 http://nocash.emubase.de/gbatek.htm#lcdiobgrotationscaling을 살펴보자.

4000028h - BG2X_L - BG2 Reference Point X-Coordinate, lower 16 bit (W)
400002Ah - BG2X_H - BG2 Reference Point X-Coordinate, upper 12 bit (W)

400002Ch - BG2Y_L - BG2 Reference Point Y-Coordinate, lower 16 bit (W)
400002Eh - BG2Y_H - BG2 Reference Point Y-Coordinate, upper 12 bit (W)

These registers are replacing the BG scrolling registers which are used for Text mode, ie. the X/Y coordinates specify the source position from inside of the BG Map/Bitmap of the pixel to be displayed at upper left of the GBA display. The normal BG scrolling registers are ignored in Rotation/Scaling and Bitmap modes.

Bit   Expl.
  0-7   Fractional portion (8 bits)
  8-26  Integer portion    (19 bits)
  27    Sign               (1 bit)
  28-31 Not used
Because values are shifted left by eight, fractional portions may be specified in steps of 1/256 pixels (this would be relevant only if the screen is actually rotated or scaled). Normal signed 32bit values may be written to above registers (the most significant bits will be ignored and the value will be cut-down to 28bits, but this is no actual problem because signed values have set all MSBs to the same value).

Internal Reference Point Registers

The above reference points are automatically copied to internal registers during each vblank, specifying the origin for the first scanline. The internal registers are then incremented by dmx and dmy after each scanline.
Caution: Writing to a reference point register by software outside of the Vblank period does immediately copy the new value to the corresponding internal register, that means: in the current frame, the new value specifies the origin of the <current> scanline (instead of the topmost scanline).

 

4000020h - BG2PA - BG2 Rotation/Scaling Parameter A (alias dx) (W)
4000022h - BG2PB - BG2 Rotation/Scaling Parameter B (alias dmx) (W)
4000024h - BG2PC - BG2 Rotation/Scaling Parameter C (alias dy) (W)
4000026h - BG2PD - BG2 Rotation/Scaling Parameter D (alias dmy) (W)

Bit   Expl.
  0-7   Fractional portion (8 bits)
  8-14  Integer portion    (7 bits)
  15    Sign               (1 bit)

 

400003Xh - BG3X_L/H, BG3Y_L/H, BG3PA-D - BG3 Rotation/Scaling Parameters

Same as above BG2 Reference Point, and Rotation/Scaling Parameters, for BG3 respectively.

 

 위의 dx, dmx, dy, dmy에 대한 내용은 아래에 나온다.

dx (PA) and dy (PC)

When transforming a horizontal line, dx and dy specify the resulting gradient and magnification for that line. For example:
Horizontal line, length=100, dx=1, and dy=1. The resulting line would be drawn at 45 degrees, f(y)=1/1*x. Note that this would involve that line is magnified, the new length is SQR(100^2+100^2)=141.42. Yup, exactly - that's the old a^2 + b^2 = c^2 formula.

 

dmx (PB) and dmy (PD)

These values define the resulting gradient and magnification for transformation of vertical lines. However, when rotating a square area (which is surrounded by horizontal and vertical lines), then the desired result should be usually a rotated <square> area (ie. not a parallelogram, for example).
Thus, dmx and dmy must be defined in direct relationship to dx and dy, taking the example above, we'd have to set dmx=-1, and dmy=1, f(x)=-1/1*y.

Area Overflow

In result of rotation/scaling it may often happen that areas outside of the actual BG area become moved into the LCD viewport. Depending of the Area Overflow bit (BG2CNT and BG3CNT, Bit 13) these areas may be either displayed (by wrapping the BG area), or may be displayed transparent.
This works only in BG modes 1 and 2. The area overflow is ignored in Bitmap modes (BG modes 3-5), the outside of the Bitmaps is always transparent.

--- more details and confusing or helpful formulas ---

The following parameters are required for Rotation/Scaling

Rotation Center X and Y Coordinates (x0,y0)
  Rotation Angle                      (alpha)
  Magnification X and Y Values        (xMag,yMag)
The display is rotated by 'alpha' degrees around the center.
The displayed picture is magnified by 'xMag' along x-Axis (Y=y0) and 'yMag' along y-Axis (X=x0).

Calculating Rotation/Scaling Parameters A-D
A = Cos (alpha) / xMag    ;distance moved in direction x, same line
  B = Sin (alpha) / xMag    ;distance moved in direction x, next line
  C = Sin (alpha) / yMag    ;distance moved in direction y, same line
  D = Cos (alpha) / yMag    ;distance moved in direction y, next line
Calculating the position of a rotated/scaled dot
Using the following expressions,
x0,y0    Rotation Center
  x1,y1    Old Position of a pixel (before rotation/scaling)
  x2,y2    New position of above pixel (after rotation scaling)
  A,B,C,D  BG2PA-BG2PD Parameters (as calculated above)
the following formula can be used to calculate x2,y2:
x2 = A(x1-x0) + B(y1-y0) + x0
  y2 = C(x1-x0) + D(y1-y0) + y0

  꽤나 복잡한 공식이 있는데, 역시나 게임을 만들진 않을 것이므로 간단히 보고 넘어가자. 뒤에 위에서 나온 것들을 종합하여 프레임 버퍼 모드와 비슷하게 만드는 부분을 볼텐데 그것을 참고자하.

 

4. Window Register

  Window는 스크린을 네개의 영역으로 나눌때 사용된다는데, 자세한 내용은 해보지 않아서 잘 모르겠다. 나중에 테스트 후에 넣도록 하자. 일단 Window에 대한 설명은 http://nocash.emubase.de/gbatek.htm#lcdiowindowfeature에 나와있으니 참고하자.

 The Window Feature may be used to split the screen into four regions. The BG0-3,OBJ layers and Color Special Effects can be separately enabled or disabled in each of these regions.

The DISPCNT Register
DISPCNT Bits 13-15 are used to enable Window 0, Window 1, and/or OBJ Window regions, if any of these regions is enabled then the "Outside of Windows" region is automatically enabled, too.
DISPCNT Bits 8-12 are kept used as master enable bits for the BG0-3,OBJ layers, a layer is displayed only if both DISPCNT and WININ/OUT enable bits are set.

4000040h - WIN0H - Window 0 Horizontal Dimensions (W)
4000042h - WIN1H - Window 1 Horizontal Dimensions (W)

Bit   Expl.
  0-7   X2, Rightmost coordinate of window, plus 1
  8-15  X1, Leftmost coordinate of window
Garbage values of X2>240 or X1>X2 are interpreted as X2=240.

4000044h - WIN0V - Window 0 Vertical Dimensions (W)
4000046h - WIN1V - Window 1 Vertical Dimensions (W)
Bit   Expl.
  0-7   Y2, Bottom-most coordinate of window, plus 1
  8-15  Y1, Top-most coordinate of window
Garbage values of Y2>160 or Y1>Y2 are interpreted as Y2=160.

4000048h - WININ - Control of Inside of Window(s) (R/W)
Bit   Expl.
  0-3   Window 0 BG0-BG3 Enable Bits     (0=No Display, 1=Display)
  4     Window 0 OBJ Enable Bit          (0=No Display, 1=Display)
  5     Window 0 Color Special Effect    (0=Disable, 1=Enable)
  6-7   Not used
  8-11  Window 1 BG0-BG3 Enable Bits     (0=No Display, 1=Display)
  12    Window 1 OBJ Enable Bit          (0=No Display, 1=Display)
  13    Window 1 Color Special Effect    (0=Disable, 1=Enable)
  14-15 Not used

400004Ah - WINOUT - Control of Outside of Windows & Inside of OBJ Window (R/W)
Bit   Expl.
  0-3   Outside BG0-BG3 Enable Bits      (0=No Display, 1=Display)
  4     Outside OBJ Enable Bit           (0=No Display, 1=Display)
  5     Outside Color Special Effect     (0=Disable, 1=Enable)
  6-7   Not used
  8-11  OBJ Window BG0-BG3 Enable Bits   (0=No Display, 1=Display)
  12    OBJ Window OBJ Enable Bit        (0=No Display, 1=Display)
  13    OBJ Window Color Special Effect  (0=Disable, 1=Enable)
  14-15 Not used

The OBJ Window
The dimension of the OBJ Window is specified by OBJs which are having the "OBJ Mode" attribute being set to "OBJ Window". Any non-transparent dots of any such OBJs are marked as OBJ Window area. The OBJ itself is not displayed.
The color, palette, and display priority of these OBJs are ignored. Both DISPCNT Bits 12 and 15 must be set when defining OBJ Window region(s).

Window Priority
In case that more than one window is enabled, and that these windows do overlap, Window 0 is having highest priority, Window 1 medium, and Obj Window lowest priority. Outside of Window is having zero priority, it is used for all dots which are not inside of any window region.

 

5. 사용 예제

 프로그래밍하기 가장 간단한 모드인 프레임 버퍼 모드로 비디오 모드를 설정해 보자. 일단 Engine A와 Engine B에 모두 설정 가능한 프레임 버퍼모드는 256 * 192 pixel 32768 color(15bit)이다.

 그럼 설정하는 순서를 다시 생각해 보면 Display 모드를 선택하고, VRAM을 맵핑한 다음, Background 속성을 설정해 주면 된다.

 아래는 간단히 프레임 버퍼 모드로 설정하는 코드이다. 비디오 모드 5를 사용하여 BG2와 BG3을 Extended Afine 모드로 설정하고 Background의 크기를 화면 전체 크기(256 * 192 pixel)보다 크게 설정하여 전체를 커버 가능하도록 설정한다. 그리고 Background 모드는 Direct Color 모드를 사용하여 프레임 버퍼모드와 비슷하게 사용하고 BG3의 시작을 Offset 0에서 시작하도록 하여 VRAM의 첫번째 시작부터 화면에 표시되도록 했다.

  1. /**
        Main LCD 및 SUB LCD를 모두 16bit 256 * 196 로 설정한다.
            Frame Buffer와 같이 쓸 수 있도록 수정한다.
    */
    void InitVideoMode()
    {
        // set the mode for 2 text layers and two extended background layers
        videoSetMode( MODE_5_2D | DISPLAY_BG3_ACTIVE );
        videoSetModeSub( MODE_5_2D | DISPLAY_BG3_ACTIVE );
       
        // Video Memory를 설정한다. MAIN 같은 경우는 2개의 VRAM이 맵핑되어있으므로
        // 더블 버퍼의 사용도 가능하다.
        vramSetMainBanks( VRAM_A_MAIN_BG_0x06000000, VRAM_B_MAIN_BG_0x06020000,
                       VRAM_C_SUB_BG
    , VRAM_D_LCD);
  2.     // Background에 대한 설정을 한다. BG_BMP_BASE를 조절하면 스크롤 및
        // 더블 버퍼를 구현할 수 있다.
        BG3_CR = BG_BMP16_256x256 | BG_BMP_BASE( 0 );// | BG_PRIORITY( 3 );
  3.     // scale을 1, rotation을 0으로 설정하여 frame buffer와 같게 만듬
        BG3_XDX = 1 << 8;
        BG3_XDY = 0;
        BG3_YDX = 0;
        BG3_YDY = 1 << 8;
        // Translation(Reference Point X/Y-Coordinate)을 0으로 설정
        BG3_CX = 0;
        BG3_CY = 0;
        // x축 및 y축으로 100 pixel 이동
        //BG3_CX = 100 << 8;
        //BG3_CY = 100 << 8;
       
        SUB_BG3_CR = BG_BMP16_256x256 | BG_BMP_BASE( 0 );// | BG_PRIORITY( 3 );
        SUB_BG3_XDX = 1 << 8;
        SUB_BG3_XDY = 0;
        SUB_BG3_YDX = 0;
        SUB_BG3_YDY = 1 << 8;
        SUB_BG3_CX = 0;
        SUB_BG3_CY = 0;
  4.     // Display에서 VBlank interrupt를 발생시키도록 한다.
        REG_DISPSTAT |= DISP_VBLANK_IRQ;
  5. }

 위의 각 함수 및 메크로들은 ndslib 폴더에 source 및 include 폴더에 있는 video.h/c 파일에 있다. 매크로는 첨부파일을 참고하도록 하자.

 마지막에 있는 붉은 색 부분은 Display Status 레지스터에 VBLANK 인터럽트를 발생하도록 설정하는 부분이다. 이렇게 하면 주기적으로 화면을 다시 그리는 타이밍에 인터럽트가 발생하여 작업을 처리할 수가 있다.

 인터럽트에 대한 자세한 내용은 04 인터럽트 제어(Interrupt Control) 문서를 참조하자.

 

5.1 화면 스크롤

 만약 화면을 스크롤하고 싶다면 어떻게 하면 될까? BG3를 사용한다면 BG3_CYCX에 값을 넣어주면 된다. BG3_CY/CX는 하위 8bit는 소수점을 나타내고 상위 7Bit는 정수부를 나타내므로 8 만큼 좌측으로 Shift 하면 된다.

 

5.2 더블 버퍼링

 만약 더블 버퍼를 사용한다면 BG3_CX/CY를 사용해도 되지만 BG_BMP_BASE() 매크로를 이용하여서 Base Offset을 변경하는 방법도 사용할 수 있다. 256 * 192 pixel * 2Byte를 하면 한 화면에 총 데이터가 96Kbyte가 된다.

 Bitmap 모드일때 Base의 값에 16Kbyte를 곱하게 되므로 BG_BMP_BASE( 6 )과 같이 사용하게 되면 프레임 간에 겹치지 않게 사용할 수 있어서 더블 버퍼링이 가능하다.

 

5.3 인터럽트(Interrupt)

 LCD 화면을 Display 할때 한 라인 또는 전체 화면을 그렸을 때, 특정한 신호를 발생하게 할 수 있다. 한 라인을 그렸을 때 H Blank가 발생하며, 전체 화면을 다 그렸을 때가 V Blank가 발생한다. Display 인터럽트에 대한 자세한 내용은 http://nocash.emubase.de/gbatek.htm#lcdiointerruptsandstatus 에서 볼 수 있으며 NDS 인터럽트에 대한 내용은 04 인터럽트 제어(Interrupt Control)에서 찾을 수 있다.

 4000004h - DISPSTAT - General LCD Status (Read/Write)
Display status and Interrupt control. The H-Blank conditions are generated once per scanline, including for the 'hidden' scanlines during V-Blank.

Bit   Expl.
  0     V-Blank flag   (Read only) (1=VBlank)
  1     H-Blank flag   (Read only) (1=HBlank)
  2     V-Counter flag (Read only) (1=Match)
  3     V-Blank IRQ Enable         (1=Enable)
  4     H-Blank IRQ Enable         (1=Enable)
  5     V-Counter IRQ Enable       (1=Enable)
  6-7   Not used
  8-15  V-Count Setting            (0-227)
The V-Count-Setting value is much the same as LYC of older gameboys, when its value is identical to the content of the VCOUNT register then the V-Counter flag is set (Bit 2), and (if enabled in Bit 5) an interrupt is requested.

4000006h - VCOUNT - Vertical Counter (Read only)
Indicates the currently drawn scanline, values in range from 160-227 indicate 'hidden' scanlines within VBlank area.
Bit   Expl.
  0-7   Current scanline (0-227)
  8-15  Not Used
Note: This is much the same than the 'LY' register of older gameboys.

 

 

6. 첨부

 

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

03 NDS 홈브루(Homebrew) - Advanced KKAMAGUI Notepad

 

들어가기 전에...

64비트 멀티코어 OS 원리와 구조

0.2007/09/28 추가

 

0.시작하면서...

 처음 NDS 홈브루를 개발하면서 libfat의 능력 및 libnds의 테스트 용으로 간단한 메모장 프로그램인 KKAMAGUI Notepad를 개발하였다(자세한 내용은 00 KKAMAGUI Notepad 참조). 테스트 결과 libfat가 약간 불안함을 발견하였고, 터치스크린의 경우 튀는 문제가 발생하였다.

 하지만 분석 결과 튀는 순간이 처음 터치스크린을 찍는 부분과 터치스크린에서 펜을 때어낼 때임을 발견하였고 이를 버퍼와 범위 체크를 통해 어느정도 완화시켜서 그렇게 큰 문제는 되지 않았다.

 그후 5개월이 지난 지금... NDS에 대한 분석이 진행되면서 ARM7 코드도 같이 삽입하고 테스트 할 수 있게 되어 터치스크린 문제를 해결하고 기존에 부족했던 기능을 추가하여 새롭게 업그레이드를 했다. 그것이 바로 Advanced KKAMAGUI Notepad이다.

 

1.터치스크린(Touch Screen) 튐 현상 해결

 터치스크린의 튐 현상은 libnds의 고질적인 문제였다. 여러 홈브루를 테스트해봐도 터치가 튀는 현상을 발견할 수 있다. 이 부분을 해결하려면 ARM7 코드를 손을 대어야 했는데, 결국 해냈다( 장하다 KKAMAGUI @0@)/~~).

 자세한 내용은 참고. 터치스크린(Touch Screen)의 튐 현상 해결방안 문서를 참고하도록 하자.

 

2.파워 컨트롤(Power Control) 기능 추가

 기존의 KAMAGUI Notepad는 NDS가 접혔을 때... 즉 폴딩(Folding) 되었을 때 LCD를 끄는 처리만 되어있었다. 다른 게임들을 보면 알겠지만 NDS의 LED가 깜빡이면서 대기중인 것을 표시한다. Advanced KKAMAGUI Notepad 또한 그 기능을 추가하여 폴딩 되었을때 LED를 깜빡이도록 했다.

 

3.사운드(Sound) 기능 추가

 해당 버튼을 클릭하면 사운드가 출력되도록 하여 약간의 즐거움을 추가했다. 사운드 출력에 대한 내용은 08 사운드(Sound) 제어 와 참고. 롬 파일에 데이터(사운드, 이미지 등등) 파일 포함 방법 문서를 참고하자.

 

4.기타

  • 화면 색깔을 약간 바꾸고 직선 그리는 알고리즘을 이상한 알고리즘에서 Bresenham 알고리즘으로 변경했다. 대각선 같은 경우 기존의 알고리즘보다 훨씬 더 자연스럽다. 알고리즘에 대한 자세한 내용은 http://tong.nate.com/lovebridge/33470715를 참고하자.
  • 새 메모를 남기는 화면에서 "지우개" 버튼을 클릭하면 "연필"로 바뀌어서 클릭하면 연필 모드가 된다는 것을 추가했다.

 나머지 기능들은 거의 동일하니 00 KKAMAGUI Notepad를 참고하도록 하자.

 

5.화면 및 실행방법

 롬파일을 DLDI 패치하여 메모리에 담은 다음 실행하면 된다. 아래는 실행한 화면이다.

메인 화면
연필로 그리기 화면

 좌측에 보면 연필로 표시된 것을 확인할 수 있다.

 

6.마치며...

 이제야 NDS에 대해서 좀 알 것 같다. 3월에 처음 홈브루를 개발하면서 막연한 지식으로 덤벼들었는데, 요즘 상세하게 분석하고 Notepad에 추가 기능까지 넣고나니 약간 감이 잡히는 듯....

 역시 NDS 최고~ >ㅁ<)/~~!!

 

7.첨부

 

02 NDS 홈브루(Homebrew) - NDS 윈도우 시스템(Windows System)

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

 

들어가기 전에...

 

 

0.버전관리

  • 2007/10/18 21:12:24 : MovableWnd가 없어지고 Move 기능이 별도의 클래스로 이동함. 타이틀바를 클릭했을 때, 윈도우 이동을 지원하고 싶으면 CMovable 클래스를 사용하면 됨

 

0.시작하면서...

  내가 만든 NDS 윈도우 시스템(Window System)에 대한 문서이다. NDS 윈도우 시스템은 최대한 윈도우의 MFC 구조와 비슷하게 하여 윈도우 프로그래밍에 익숙한 사람이라면 누구나 쉽게 접근할 수 있도록 하는 것이 목적이다.

 현재 Base Window와 DC 정도만 구현되어있으며, 이것을 활용하여 만들어진 홈브류는 00 KKAMAGUI NOTEPAD 가 있다.

 현재 전체적인 윈도우의 구조는 아래와 같이 구성되어있다.

 클래스다이어그램.PNG

 <전체 클래스 다이어그램>

 주의할 점은 아래와 같다. 

  • 최대한 비슷하게 만들려는 의도지만, 여건상 메시지 처리 부분과 함수 호출의 순서에 약간 차이가 있을 수 있다. 따라서 윈도우 프로그래밍에 대한 지식으로 접근하면 제대로 동작하지 않을 수 있다.
  • 자동으로  처리되는 것은 거의 없다. 하다못해 ShowWindow() 함수를 이용해서 윈도우를 숨겼을 때, 단순히 윈도우를 그려주지만 않을 뿐 나머지 처리는 알아서 다 해야한다. 즉 원하는 게 있으면 전부 손으로 작성해야 한다.

 

 참고할 점은 아래와 같다.

  • 라이브러리는 RGB555 포맷을 사용하는 Frame Buffer 모드를 기본으로 사용한다고 가정하고 제작되었다.

    • 만약 다른 모드를 사용한다면 2D Raster 함수 부분을 적절하게 수정해야 할 것이다. 
    • 아래는 Frame Buffer 모드로 설정하는 예제이다. 
    1. /**
          Main LCD 및 SUB LCD를 모두 16bit 256 * 196 로 설정한다.
              Frame Buffer와 같이 쓸 수 있도록 수정한다.
      */
      void InitVideoMode()
      {
          // 16bit Color Mode 5
          videoSetMode( MODE_5_2D | DISPLAY_BG3_ACTIVE );
          videoSetModeSub( MODE_5_2D | DISPLAY_BG3_ACTIVE );
          
          // Video Memory를 설정한다. MAIN 같은 경우는 2개의 VRAM이 맵핑되어있으므로
          // 더블 버퍼의 사용도 가능하다.
          vramSetMainBanks( VRAM_A_MAIN_BG_0x06000000, VRAM_B_MAIN_BG_0x06020000,
                            VRAM_C_SUB_BG, VRAM_D_LCD);
    2.     // Background에 대한 설정을 한다. BG_BMP_BASE를 조절하면 스크롤 및
          // 더블 버퍼를 구현할 수 있다.
          BG3_CR = BG_BMP16_256x256 | BG_BMP_BASE( 0 ); //| BG_PRIORITY( 3 );
          // scale을 1, rotation을 0으로 설정하여 frame buffer와 같게 만듬
          BG3_XDX = 1 << 8;
          BG3_XDY = 0;
          BG3_YDX = 0;
          BG3_YDY = 1 << 8;
          // Translation(Reference Point X/Y-Coordinate)을 0으로 설정
          // 표시하는 위치를 옮기고 싶으면 조절
          BG3_CX = 0;
          BG3_CY = 0;
          // x축 및 y축으로 100 pixel 이동
          //BG3_CX = 100 << 8; 
          //BG3_CY = 100 << 8;
              
          SUB_BG3_CR = BG_BMP16_256x256 | BG_BMP_BASE( 0 );// | BG_PRIORITY( 3 );
          SUB_BG3_XDX = 1 << 8;
          SUB_BG3_XDY = 0;
          SUB_BG3_YDX = 0;
          SUB_BG3_YDY = 1 << 8;
          SUB_BG3_CX = 0;
          SUB_BG3_CY = 0;
  • 화면의 크기는 매크로로 정의되어있다. 화면이 더 넓어지거나 좁아진다면 수정해야 한다.

 

1.2D 래스터 연산(Raster Operation)

 윈도우 라이브러리는 클립핑이 지원되는 라인 그리기 함수 및 사각형 그리기 함수를 지원한다.

 라인을 그리는 함수는 Bresenham 알고리즘을 사용하고 클립핑 알고리즘으로는 cohen-sutherland 알고리즘을 이용했다. 사각형 타입은 Fill Rect와 Rect 두가지 타입이 있는데, Rect는 라인을 그리는 함수를 이용하여 구현되었다.

 2D Raster 함수들은 클립핑 영역을 받도록 되어있는데, 일단 기본 설정은 스크린에 그리는 것으로 고정되어있다. 따라서 Y 좌표의 계산은 Y * SCREEN_WIDTH로 설정되어있으며 추후 스크린이 아닌 메모리 영역에 그릴 경우 이 부분을 수정할 필요가 있다.

 2D Raster 함수들은 아래와 같이 구성되어있고 CDC 클래스에서 사용된다.

  1. void DrawLine( RECT stClippingRect, void* pvAddr, int iX1, int iY1, int iX2,
            int iY2, unsigned short usColor );
    void DrawBox( RECT stClippingRect, void* pvAddr, int iX1, int iY1, int iX2,
            int iY2, unsigned short usColor, bool bFill );
    void DrawPixel( RECT stClippingRect, void* pvAddr, int iX, int iY,
            unsigned short usColor );

 

2.한글/영문 출력

 한글/영문 출력 부분은 자작한 폰트 생성 툴을 이용해서 비트마스크 폰트를 생성한 후 이를 출력하는 방법으로 구현하였다. 물론 클립핑에 대한 처리도 되어있다.

 현재 폰트는 12x12 크기의 한글 폰트와  6x12 크기의 영문 폰트로 구성되어있다. Font 출력에 대한 함수들은 아래와 같이 구성되어있고 CDC 클래스에서 사용된다.

  1. #define ENG_FONT_WIDTH  6
    #define HAN_FONT_WIDTH  12
    #define ENG_FONT_HEIGHT 12
    #define HAN_FONT_HEIGHT 12
  2. // 한글/영어 다 찍을 수 있다.
    void PrintChar( RECT stClippingRect, void* pvBaseAddr, int iX, int iY,
            unsigned short usForeColor, unsigned short usBackColor, char cChar );
    void PrintString( RECT stClippingRect, void* pvBaseAddr, int iX, int iY,
            unsigned short usForeColor, unsigned short usBackColor, char* pcString );

 한글 폰트 생성 및 출력에 대한 내용은 01 NDS 한글 출력 라이브러리 문서를 참고하자.

 

3.Z-Order 처리

 Z-Order는 간단하게 구현되어있다. 윈도우 메니져가 Z-Order의 첫번째 윈도우만 관리하고 나머지는 윈도우 자신이 다음에 위치하는 윈도우의 포인터를 가지는 방식으로 관리한다. 이렇게 함으로써 윈도우 메니져의 자료구조를 단순하게 하고 윈도우 관리에 필요한 추가적인 자료구조를 없앴다.

 Z-Order의 구성은 윈도우를 화면에 그리는 순서와도 관계가 있는데 Z-Order의 Bottom에서부터 Z-Order의 Top 순으로 OnPaint() 함수를 호출해서 그린다.

 아래는 Z-Order의 순서와 실제 화면과의 관계를 나타낸 그림이다.

윈도우라이브러리1.PNG

<Z-Order & Screen>

 

 

4.윈도우 그리기 및 그리는 최소 영역 설정

 윈도우즈나 X-Window 같은 윈도우 시스템의 경우, 무식하게 화면 전체를 계속해서 갱신하지 않는다. 또한 윈도우 하나의 내용이 갱신되었다고 해서 윈도우 전체를 다시 그리는 일도 하지 않는다. 변화되면 해당 부분만 그려서 그리는 영역을 최소화하고, 이를 효율적으로 관리하여 효율을 최대한 높이기 위해 노력한다(화면을 그리는데 너무 많은 processing power를 사용한다면 일은 언제하는가? ㅡ_ㅡ;;;)

 처음에는 세세한 부분까지 변화된 영역을 검출하여 처리하려 했다가 알고리즘이 너무 복잡해지는 바람에 생략했었다. 뭐 사실 대충만든 프로토타입이 잘 작동해준 원인도 있지만... 윈도우 라이브러리를 계속 개발하다보니 속도가 점점 떨어지는 것이 눈에 보여서 어쩔 수 없이 넣게 되었다.

 윈도우 라이브러리에서 효율을 높이기위해 사용하는 방식은 3가지다.

  • 더블 버퍼링 : 메인 메모리를 할당하여 윈도우를 그리고 완전히 그려진 뒤에 비디오 메모리로 고속 전송한다. 고속 전송 함수는 간단히 어셈으로 짰다. ㅡ_ㅡ;;; 속도가 의심스러운 사람도 있을텐데, memcpy 함수보다 2배는 빠르니 믿어도 된다.(프로파일링해서 나온 결과다. 프로파일러에 대해서는  22 타이머(Timer)를 이용한 프로파일러(Profiler) 만들기 를 참고하자.) 
  • 일괄 그리기 : 윈도우가 변화되었다고 해서 버퍼에 직접 그리지 않는다. 윈도우를 다시 그려야 한다는 플래그와 영역만을 윈도우 매니저에게 알리고 윈도우 매니져는 메시지 처리가 끝난 뒤 일괄적으로 윈도우를 다시 그린다.
  • 그리기 영역 설정 : 조그만 윈도우가 변했다고 전체를 다시 그리는건 문제가 있다. 따라서 그려야 할 영역이 있을 때 이 영역들을 단순한 사각형 더하기로 계산하여 해당 영역에 있는 윈도우만 다시 그리도록 한다. 간단하지만 의외로 효율이 괜찮다. 

  이제 하나하나에 대해서 자세히 알아보자.

 

4.1 더블 버퍼링(Double Buffering)

 게임을 개발한 사람 or 윈도우 그림판 같은 프로그램을 개발해본 사람이라면 더블 버퍼링을 모르는 사람이 없을 것이다. 다행이도 NDS에는 비디오 메모리가 많기 때문에 비디오 메모리를 사용한 더블 버퍼링을 사용할 수 있지만, 조사해본 결과 한가지가 부족했다. 2개의 화면이므로 최소 4개의 메모리가 필요한데, 실제로 그림을 표시하기위해 사용 가능한 메모리는 3개 였다. ㅡ_ㅜ...

 물론 화면 하나는 비디오 메모리를 사용해서 비디오 스위칭을 이용한 더블 버퍼링을 사용하고, 나머지 하나는 메모리 전송을 이용한 더블버퍼링을 사용하면 되지만 윈도우 라이브러리가 복잡해지는 문제가 있어서 둘다 메인 메모리를 이용한 더블 버퍼링을 하기로 결정했다.

참고. NDS 속도에 대한 몇가지 테스트 문서를 참고하면 NDS에서 memcpy, for를 사용한 메모리 전송에 대한 결과를 알 수 있다. 실제 memcpy를 사용하면 7 ~ 8 ms정도가 걸리는데 자작한 고속 전송 함수를 사용하면 4 ms정도에 출력할 수 있다.

 

4.2 메시지 처리(Message Processing) 및 일괄 그리기(Batch Drawing)

 일괄로 그리기란 윈도우가 변경되는 시점에 그래픽 버퍼에 그리는 것이 아니라 변경된 윈도우에 상태를 저장한 뒤에 나중에 일괄적으로 모든 윈도우를 Z-Order의 역순으로 그려나가는 것을 말한다. 이렇게 한 이유는 NDS의 빈약한(??) 프로세싱 능력과 작은 메모리 때문에 윈도우들을 개별 태스크 or 개별 메시지 큐로 구분하지 못했기 때문이다.

 그렇다면 어떻게 멀티 윈도우를 구현한 것일까? 약간의 꼼수를 이용했다. 아래를 보자.

 

4.2.1 메시지 처리(Message Processing) 

 방법은 간단하다. 원래 윈도우 시스템이 개별 윈도우의 메시지 큐에 메시지를 던져줘서 처리하던 방식이라면 내가 만든 윈도우 시스템은 메시지를 Z-Order 순서로 전달하는 방식이라 할 수 있다. 다시 말하면 하나의 메시지를 전체 윈도우가 돌아가면서 사용한다는 말인데, 별도의 처리가 없으면 메시지가 전체에게 전달되므로 동작에 문제가 생긴다.

 따라서 이를 규제하는 방식이 윈도우 영역 및 메시지 종류, 그리고 윈도우 핸들을 이용해서 비교하여 자신의 핸들 or 자신의 윈도우 영역에서 발생한 것이면 메시지의 전달을 더이상 하지 않고 삭제함으로써 무한 메시지 전달을 막았다.

 아래는 이것을 그림으로 나타낸 것이다.

윈도우라이브러리2.PNG

<메시지 처리>

 좌측 상단의 4번 윈도우 자리에 클릭이 일어났을 때 해당 메시지가 어떻게 전달되고 어디서 처리가 완료되는가 하는 그림이다. 메시지는 Z-Order 순서로 전달되다가 4번 윈도우의 처리 루틴에서 자신의 영역에서 발생한 메시지 임을 깨닿고 메시지 처리를 완료한 후 Background 윈도우로 전달하지 않았다.

 

 위와 같은 알고리즘을 사용하기위한 전제조건은 무엇일까?

  • 한가지는 어플리케이션 프로그래머가 OnMessage() 함수에서 적당히 잘 처리한 다음, 메시지를 다른 윈도우에 전달해줘야 한다는 점이다. 위와 같은 상황에서 1번 윈도우에서 메시지를 더이상 뒤로 전달하지 않는다면 다른 윈도우는 메시지를 받지 못하기 때문에 굶주림 상태에 빠진다. 하지만 너무 잘 전달해서 자신의 메시지를 뒤로 전달하면 동작에 문제가 발생한다. 신중한 처리가 필요하다.
  • 다른 한가지는 윈도우는 메시지 처리 루틴에서 너무 시간을 끌면 뒤에 윈도우에게 메시지가 전달되는 시간이 더 걸리기 때문에 시간에 민감한 프로그램을 작성할 때 주의해야 한다는 점이다. 특히 윈도우를 다시 그리거나 하는 작업은 시간이 많이 걸리기 때문에 OnPaint() 같은 함수를 직접 호출해서 버퍼에 다시 그리는 일은 하지 말고 RedrawWindow() 함수를 사용해서 윈도우 매니져가 일괄로 화면을 그리도록 해야 한다.

 

4.2.2 일괄 그리기(Batch Drawing) 

 NDS의 빈약한 처리 능력 때문에 일괄 그리기를 선택했다고 앞서 이야기 했다. 일괄 그리기를 하면 좋은 점이 뭘까?

  • 첫째는 윈도우가 겹겹이 쌓여있을 때 끼어있는 윈도우의 "일부"를 그리는 문제를 간단히 처리할 수 있다. 위의 <메시지 처리>그림에서 2번 윈도우가 갱신되었다면 실제로 그려야하는 영역은 2번 윈도우의 영역에서 1번 윈도우의 영역을 뺀 부분이다. 이 시점에서 버퍼를 바로 갱신하려면 2번 윈도우를 그리고 1번 윈도우를 그 위에 다시 그리면 된다. 이러한 단순한 경우라면 괜찮지만 만약 윈도우가 겹겹이 쌓여있고 다들 일부분이 보이는 상태인데, 그 일부분만 보이는 화면이 계속 갱신되는 상황이라면??? 윈도우 위치에 따른 영역 계산 + 일부분 Draw 등등의 작업을 수행해야 한다. 알고리즘이 점점 복잡해지고 계산량도 점점 늘어난다. 실제로 이러한 방식으로 이전 KKAMAGUI OS를 개발했었는데, 계산량이 많아지다보니 오히려 성능이 그리 좋지 않았다. 그래서 고민한 끝에 일괄 그리기를 이용하여 처리했다.
  •  둘째는 윈도우 메시지를 전달하는 시간을 줄일 수 있다. 현재의 윈도우 라이브러리는 윈도우가 다른 윈도우에게 메시지를 전달하는 구조로 되어있기 때문에, 시간이 많이 걸리는 작업을 메시지 처리 루틴에서 하게되면 치명적이다. 윈도우를 다시 그리는 역할을 윈도우 매니져로 넘김으로써 메시지를 좀 더 빠르게 전달할 수 있고, 윈도우를 계속해서 다시 그리는 일을 하지 않음으로써  응답속도의 향상을 노릴 수 있다.

 

4.3 그리는 영역 설정

 위에서 일괄 그리기의 장점을 이야기 했다. 그렇다고 무턱대고 화면 전체를 다시 그려서 전송하면 효율이 좋을까? 전체 영역 중에 작은 윈도우 일부만 변했는데, 윈도우 전체를 다시 그리는 것은 비효율적이다. 따라서 그리는 영역의 범위를 정하여 해당 범위에 들어있는 윈도우만 다시 그린 후, 그리는 영역이 현재 윈도우 내부에 포함된다면 더이상 그리지 않도록 한다. 이렇게 하면 조금이나마 효율을 높일 수 있다.

 그리는 영역의 설정은 간단한 사각형 더하기로 해결했는데, 알고리즘은 아주 간단하다. 아래의 그림과 같이 각 영역의 Min/Max값을 얻어서 해당 영역 안을 그리도록 했다.

 윈도우라이브러리3.PNG

<그리는 영역 설정>

  위의 그림과 같이 1번 윈도우와 2번 윈도우가 변했다면 두 윈도우의 범위를 Min/Max한 붉은색 사각형이 다시 그려질 영역이 된다. 따라서 이 영역으로 Z-Order를 따라가면서 이 영역을 포함한 윈도우를 찾게되고 해당 윈도우를 그리는 것이다. 위에서 보면 1번 윈도우 및 2번 윈도우 그리고 3번 윈도우가 위 영역에 포함되므로 그려야 된다. 하지만 3번 윈도우 영역이 그리는 영역을 완전히 포함하므로 Background 윈도우는 다시 그릴 필요가 없으므로 3번 윈도우까지만 그린다.

 위와 같이 간단한 알고리즘을 이용해서 다시 그릴 영역을 어느정도 줄일 수 있다. 효율은... 그저 그렇다.. ㅡ_ㅡ;;; 하지만 안하는 것 보다는 좋다.

 

 

 

5.윈도우 관련 클래스 구현

5.1 CWnd 구현 

 CWnd 클래스는 아래와 같이 정의되어있다.

  1. // 기본 윈도우 클래스
    class CWnd
    {
    protected:
        RECT m_stWindowRect;
        bool m_bShow;
        WORD* m_wDisplayAddr;
        char m_vcTitle[ 33 ];
        // 윈도우에 그리기위해 넣음
        CWindowDC m_clWindowDC;
        // ZOrder를 위해 넣음
        CWnd* m_pclZOrderNext;
        CWnd* m_pclParent;
       
        //--------------------------------------------------------------------------
        // 아래는 Window Manager 관련 또는 기타 함수들
        // 쓰는 쪽에서는 아래의 함수를 건드릴 필요 없음
        //--------------------------------------------------------------------------
        void AddWindowToManager( CWnd* pclWnd );
        void DeleteWindowFromManager( CWnd* pclWnd );
        RECT CalcXButtonPosizion( void );
        CWindowManager* GetWindowManagerPtr( void );

  2.  

  3.     //--------------------------------------------------------------------------
        // 아래는 오버로딩할 수 있는 함수 목록
        // 아래의 함수만 상속받은 클래스에서 필요하면 재정의하자.
        //--------------------------------------------------------------------------
        virtual void OnEraseBkgnd( CDC* pclDC );
        virtual bool OnMessage( PMESSAGE pstMessage );
           
       
    public:
        CWnd();
        virtual ~CWnd();
        //--------------------------------------------------------------------------
        // 아래는 윈도우 메니져에서 사용할 용도로 설정된 함수들
        // 상속을 받아서 사용하는 경우라면 쓸 필요 없다.
        //--------------------------------------------------------------------------
        void RedrawZOrderBehindWindow( RECT stRect );
        void SetZOrderNext( CWnd* pclWindow );
        CWnd* GetZOrderNext( void );
       
        //--------------------------------------------------------------------------
        // 아래는 윈도우 사용에 관련된 함수들
        // 상속을 받아서 사용하는 경우라면 쓸 필요 없다.
        //--------------------------------------------------------------------------
        void Create( void* pvAddr, RECT stRect, char* pcName, bool bShow,
                CWnd* pclParent );
        void SetDisplayAddr( void* pvAddr );
        void* GetDisplayAddr( void );
       
        bool MoveWindow( int iX1, int iY1, int iX2, int iY2 );
        bool MoveWindow( RECT stRect );
        void ShowWindow( bool bShow );
        void RedrawWindow( void );
       
        void SetWindowText( char* pcTitle );
        char* GetWindowText( void );
        RECT GetWindowRect( void );
        RECT GetClientRect( void );
       
        void SetActiveWindow( void );
       
        void SetParent( CWnd* pclParent );
        CWnd* GetParent( void );
       
        void ScreenToClient( PPOINT pstPoint );
        void ScreenToClient( PRECT pstRect );
        void ClientToScreen( PPOINT pstPoint );
        void ClientToScreen( PRECT pstRect );
       
        void DestroyWindow( void );
        bool IsInWindowRect( int iX, int iY );
        bool IsInWindowRect( RECT stRect );
        bool IsInXButtonRect( int iX, int iY );
        bool IsInTitleBarRect( int iX, int iY );
        bool IsXButtonDownMessage( PMESSAGE pstMessage );
       
        bool DispatchMessage( PMESSAGE pstMessage );
        bool SendMessage( WORD wType, WPARAM wParam, LPARAM lParam );
        bool IsBelongMainWindowManager( void );
        bool IsBelongSubWindowManager( void );
        void GetXYFromMessage( PMESSAGE pstMessage, int* piX, int* piY );

  4.  

  5.     //--------------------------------------------------------------------------
        // 아래는 오버로딩할 수 있는 함수 목록
        // 아래의 함수만 상속받은 클래스에서 필요하면 재정의하자.
        //--------------------------------------------------------------------------
        virtual void OnPaint( void );
        virtual void OnDestroy( void );
        virtual void DrawXButton( CDC* pclDC );
        virtual void DrawTitleBar( CDC* pclDC );
    };

 최대한 MFC 클래스와 비슷하게 구현하려 노력했다. 낯익은 함수들이 반가울 것이다. CWnd 클래스는 모든 윈도우의 기본이 되는 윈도우로써 윈도우 기본 프레임을 그려주는 역할과 윈도우 메니져의 일부로써 자료구조 역할을 수행한다. 하지만 사실 거의 Draw 쪽 함수만 처리가 되어있을 뿐, 메시지 처리라든지 윈도우 이동에 관한 부분은 거의 가지고 있지 않다.

 정말 "기본" 적인 윈도우 구성에 대한 정보만 가지고 있다. 

 

5.2 CSkinWnd 구현 

 CSkinWnd는 그래픽 파일을 넣어주면 해당 그래픽 파일을 BitBlt 해서 윈도우에 스킨을 씌우는 프로그램이다. 스킨을 씌우는 방법은 향후 Library Tutorial을 다루면서 설명할 것이다. 그래픽 포맷인 KNG(KKAMAGUI NDS Graphic)에 대해서는 추후에 다룰 예정이다. 뭐 사실 RGB555 포맷으로 바꿔주는 것 밖에는 없다.

  1. class CSkinWnd : public CWnd
    {
    private:
        BYTE* m_pbKNGBuffer;
     
    public:
        CSkinWnd( void );
        ~CSkinWnd( void );
  2.     void SetKNGSkin( BYTE* pbKngBuffer );   
    protected:
        void OnPaint( void );
    }; 
  3. ... 구현 ... 
  4. /**
        화면 그리기 처리
            배경 그림을 그려준다.
    */
    void CSkinWnd::OnPaint( void )
    {
        CBitmap clBitmap;
        CDC clBitmapDC;
        CWindowDC clWindowDC( this );
        RECT stRect;
  5.     if( m_pbKNGBuffer == NULL )
        {
            return ;
        }
       
        // Bitmap 클래스에 KNG를 로드한 후 윈도우 전체에 bitblt 한다.
        clBitmap.LoadKNG( m_pbKNGBuffer );
        clBitmapDC.SelectObject( &clBitmap );
        stRect = GetWindowRect();
  6.     clWindowDC.BitBlt( 0, 0, GETWIDTH( stRect ), GETHEIGHT( stRect ),
            &clBitmapDC, 0, 0, 0 );

  윈도우 프로그래밍할 때 다이얼로그에 스킨을 씌워본 사람이라면 낯이 익은 코드일 것이다. 그래픽 포맷과 이미지 크기의 제한만 제외하면 거의 윈도우 프로그램하는 것과 같다.

 

5.3 CListWnd 구현 

 CListWnd는 윈도우의 List Box와 비슷한 기능을 하는 클래스다. 각 라인에 항목을 표시하고 선택된 항목을 표시해주는 기능을 한다. 부가적으로 Call Back을 지원하여 그려질 라인별로 그리는 방식에 대한 처리나 선택되었을 때 처리에 대한 구현도 표함하고 있다. CListWnd 자체는 Text String 기반으로 동작하지만, 구조체와 같은 정보를 삽입하고 Call Back만 잘 처리해 준다면 기타 여러가지 자료 구조도 표시할 수 있다. 이 부분에 대해서는 홈브루 중에 File Explorer를 참고하면 된다.

  1. class CListWnd : public CWnd
    {
    protected:
        CList m_clContentList;
  2.     int m_iTouchX;
        int m_iTouchY;
        int m_iLastX;
        int m_iLastY;
        int m_iViewItemNumber;
        bool m_bSelectMode;
        bool m_bLButtonDown;
  3. private:
        bool ProcessScrollMode( PMESSAGE pstMessage );
        bool ProcessSelectMode( PMESSAGE pstMessage );
        bool ProcessTitleBarMessage( int iX, int iY  );
       
    public:
        CListWnd( void );
        ~CListWnd( void );
  4.     bool AddItem( void* pvContent );
        void DeleteAllItem( void );
        bool DeleteItem( void* pvContent );
        int GetItemCount( void );
       
        void* GetItem( int iIndex );
        int GetSelectedItemIndex( void );
        bool IsSelectionMode( void );
        int GetMaxVisibleLineCount( void );
        void SetSelectionMode( bool bSelectMode );
        void SetFirstVisibleItem( int iIndex );
       
    protected:
        void OnPaint( void );
        bool OnMessage( PMESSAGE pstMessage );
  5.     //--------------------------------------------------------------------------
        // 리스트에 Item을 다른 방식으로 표시하고 싶으면 아래의 함수를 오버로딩해서
        // 원하는 대로 그리면 된다.
        //  한 라인 라인을 그릴때 호출되는 Call back 함수
        //--------------------------------------------------------------------------
        virtual void RenderContent( RECT stDrawRect, CDC* pclDC, void* pvContent,
                int iItemIndex );
       
        // 리스트의 Item이 선택되었을 때 호출되는 함수
        virtual void OnSelectCursorDown( void );
        virtual void OnSelectCursorUp( void );
    }; 

 

5.4 CChildWnd 구현 

 CChildWnd는 CWnd와 같으나 다른 점은 Window Manager에 등록이 되지 않기 때문에, 메시지에 대한 처리는 부모 윈도우가 Child Window에 넣어줘야 한다는 점이다. 이렇게 하면 편리한 점은 자식 윈도우를 부모 윈도우에 다 맡김으로써 Window Manager에 로드를 줄일 수 있고 Z-Order의 처리가 아주 간단하기 때문이다.

  1. // Child Wnd는 윈도우 매니저에 속하지 않는다는 점 말고는 CWnd와 동일하다.
    class CChildWnd : public CWnd
    {
    private:
        BYTE* m_pbKNGBuffer;
       
    public:
        CChildWnd( void );
        ~CChildWnd( void );
        
        // Override된 함수들
        void AddWindowToManager( CWnd* pclWnd );
        void DeleteWindowFromManager( CWnd* pclWnd );
    }; 

 

5.5 CDC 구현 

 CDC는 윈도우의 대표적인 그래픽 관련 클래스이다. CPaintDC와 CWindowDC도 구현되어있으며, 차의점은 CPaintDC 같은 경우 Title Bar의 높이를 자동으로 빼서 계산해 준다는 점이다.

  1. // 윈도우 관련 DC를 처리하는 Base Class
    // 하는일 없음
    class CDC
    {
    protected:
     CWnd* m_pclWindow;
     CBitmap* m_pclBitmap;
     int m_iLastX;
     int m_iLastY;
     int m_iTitleHeight;
     
    protected:
     void DrawRect( PRECT pstRect, WORD wColor, bool bFill );
     WORD* GetBufferAddr();
     RECT GetBufferRect();
     
    public:
     CDC();
     ~CDC();
     
     void SetWindow( CWnd* pclWindow );
     void SelectObject( CBitmap* pclBitmap );
     
     bool MoveTo( int iX, int iY );
     bool LineTo( int iX, int iY, WORD wColor );
     
     void FillRect( PRECT pstRect, WORD wColor );
     void FillRect( int iX1, int iY1, int iX2, int iY2, WORD wColor );
     void FrameRect( PRECT pstRect, WORD wColor );
     void FrameRect( int iX1, int iY1, int iX2, int iY2, WORD wColor );
     void TextOut( int iX, int iY, char* pcString, WORD wForeColor,
             WORD wBackColor );
     void BitBlt( int iDstX, int iDstY, int iWidth, int iHeight,
      CDC* pclDC, int iSrcX, int iSrcY, int iMode );
     void SetPixel( PPOINT pstPoint, WORD wColor );
    };
  2. // Paint DC
    class CPaintDC : public CDC
    {
    public:
     CPaintDC( CWnd* pclWindow );
     ~CPaintDC();
    };
  3. // Window DC
    class CWindowDC : public CDC
    {
    public:
     CWindowDC();
     CWindowDC( CWnd* pclWindow );
     ~CWindowDC();
    }; 

 

5.6 CBitmap 구현 

 CBitmap은 간단히 Bitmap 파일을 관리하는 클래스로 단순한 버퍼 역할을 한다. 지금은 KNG 파일 포맷을 사용하도록 되어있는데 아래에 KNG 구조체의 정보를 앞에 포함하고 내부 데이터는 RGB555 포맷을 사용하는 아주 간단한 구조의 파일이다.

  1. #define KNG_SIGNATURE "KNG"
  2. // KKAMAGUI NDS GRAPHIC 파일의 헤더 구조체
    typedef struct kkamaguiNdsGraphicStruct
    {
        char vcSignature[ 3 ]; // KNG 설정
        BYTE bVersion;         // 이미지 포맷 버전, 0x00 설정
        int iWidth;            // 이미지 넓이
        int iHeight;           // 이미지 높이
    } KNG, * PKNG;
  3. // Bitmap 관련 클래스
    class CBitmap
    {
    private:
        WORD* m_pwBitmap;
        SIZE m_stSize;
       
    public:
        CBitmap();
        ~CBitmap();
        
        WORD* GetBufferAddr();
        RECT GetBitmapRect();
       
        bool LoadKNG( BYTE* pbKNG );
    };

 

5.7 비디오 메모리 고속 전송 구현

 ARM 레지스터를 최대한 사용해서 전송하는 방식을 사용한다. 나름 빠르다.

  1. //  Frame Buffer 통짜를 빠르게 LCD로 복사해 주는 함수
    //      256 * 192 * 2 = 98304Byte
    //      r12 => Loop Count r2~r11 => 40Byte
    //      98304Byte = 40 * 2457(Loop Count) + 24(Remain)
    FastFrameBufferDump:
        push { r4 - r12 }
        // 2457 값을 바로 넣을 수 없어서 쉬프트 연산으로 넣는다.
        mov r4, #153
        mov r5, #9
        add r12, r5, r4, LSL #4
       
        // 40Byte Block씩 전송한다.
        Loop:
            ldmia r1!, { r2 - r11 }
            stmia r0!, { r2 - r11 }
            subs r12, #0x01
            bne Loop   
       
        // 남은 24Byte를 전송한다.
        ldmia r1, { r2 - r7 }
        stmia r0, { r2 - r7 }
           
        pop { r4 - r12 }
        bx lr

 

6.Graphic 기본 함수들 

6.1 Line 함수 

 Line 함수는 Bresenham 알고리즘으로 그리도록 되어있다. 물론 클립핑 부분은 여기서 하지 않고 따로 cohen-sutherland 알고리즘을 이용한다.

  1. //=============================================================================
    //
    //  cohen-sutherland 알고리즘을 이용한 클립핑
    //
    //=============================================================================
    #define LEFT_EDGE 0x01
    #define RIGHT_EDGE 0x02
    #define BOTTOM_EDGE 0x04
    #define TOP_EDGE 0x08
  2. //0000은 중앙
    #define INSIDE(a) ( !a )
    #define REJECT( a, b ) ( a & b )
    #define ACCEPT( a, b ) ( !( a | b ) )
  3. /**
        좌표가 MIN/MAX의 밖인지 안인지 판단
    */
    BYTE Encode( RECT stClippingRect, POINT stPt )
    {
        BYTE bCode;
       
        bCode = 0;
       
        if( stPt.iX < stClippingRect.iX1 )
        {
            bCode = bCode | LEFT_EDGE;
        }
        if( stPt.iX > stClippingRect.iX2 )
        {
            bCode = bCode | RIGHT_EDGE;
        }
        if( stPt.iY < stClippingRect.iY1 )
        {
            bCode = bCode | TOP_EDGE;
        }
        if( stPt.iY > stClippingRect.iY2 )
        {
            bCode = bCode | BOTTOM_EDGE;
        }
        return bCode;
    }
  4. /**
        두점을 교환
    */
    void SwapPoints( POINT* pstP1, POINT* pstP2 )
    {
        POINT stTemp;
       
        stTemp = *pstP1;
        *pstP1 = *pstP2;
        *pstP2 = stTemp;
    }
  5. /**
        두 속성을 교환
    */
    void SwapCodes( BYTE* bC1, BYTE* bC2 )
    {
        BYTE bTemp;
       
        bTemp = *bC1;
        *bC1 = *bC2;
        *bC2 = bTemp;
    }
  6. /**
        Line을 클립핑한다.
    */
    void ClipLines( RECT stClippingRect, void* pvAddr, POINT stP1, POINT stP2,
            WORD wColor )
    {
        BYTE bCode1, bCode2;
        int iDone = FALSE, iDraw = FALSE;
        float m;
  7.     if( stP2.iX != stP1.iX )//수직선이 아닐때
        {
            m = ( float ) ( stP2.iY - stP1.iY ) / ( stP2.iX - stP1.iX );
        }
        else
        {
            m = 0;
        }
       
        while( !iDone )
        {
            bCode1 = Encode( stClippingRect, stP1 );
            bCode2 = Encode( stClippingRect, stP2 );
           
            if( ACCEPT( bCode1, bCode2 ) )
            {
                //두점 다 안에 있으면 끝
                iDone = TRUE;
                iDraw = TRUE;
            }
            else
            {
                if( REJECT( bCode1, bCode2 ) )
                {
                    //두점다 화면 밖이면 끝
                    iDone = TRUE;
                }
                else
                {
                    // 만약 점 1이 안에 있으면 점 2를 사용한다.
                    if( INSIDE( bCode1 ) )
                    {
                        //p1이 중앙에 있으면 p2와 교환
                        SwapPoints( &stP1, &stP2 );
                        SwapCodes( &bCode1, &bCode2 );
                    }
                   
                    if( bCode1 & LEFT_EDGE )
                    {
                        stP1.iY += ( int ) ( ( stClippingRect.iX1 - stP1.iX ) * m );
                        stP1.iX = stClippingRect.iX1;
                    }
                    else if( bCode1 & RIGHT_EDGE )
                    {
                        stP1.iY += ( int ) ( ( stClippingRect.iX2 - stP1.iX ) * m );
                        stP1.iX = stClippingRect.iX2;
                    }
                    else if( bCode1 & TOP_EDGE )
                    {
                        if( stP2.iX != stP1.iX )//수직선이 아닐때
                        {
                            stP1.iX += ( int ) ( ( stClippingRect.iY1 - stP1.iY ) /
                                    m );
                        }
                        stP1.iY = stClippingRect.iY1;
                    }
                    else if( bCode1 & BOTTOM_EDGE )
                    {
                        if( stP2.iX != stP1.iX )//수직선이 아닐때
                        {
                            stP1.iX += ( int ) ( ( stClippingRect.iY2 - stP1.iY ) /
                                    m );
                        }
                        stP1.iY = stClippingRect.iY2;
                    }
                }
            }
           
            if( iDraw )
            {
                //선그리기
                BresenhamLine( pvAddr, stP1.iX, stP1.iY, stP2.iX, stP2.iY, wColor );
            }
        }
    }
  8. //=============================================================================
    //
    //  Bresenham 알고리즘을 이용한 클립핑
    //
    //=============================================================================
  9. /**
        라인을 그린다.
    */
    void BresenhamLine( void* pvAddr, int iX1, int iY1, int iX2, int iY2,
        unsigned short usColor )
    {
        int dy = iY2 - iY1;
        int dx = iX2 - iX1;
        int stepx, stepy;
        unsigned short* pusStartAddr = ( unsigned short* ) pvAddr;
       
        if( dy < 0 )
        {                                 // 기울기를 양수처리함
            dy = -dy;
            stepy = -1;
        }
        else
        {
            stepy = 1;
        }
     
        if( dx < 0 )
        {
            dx = -dx;
            stepx = -1;
        }
        else
        {
            stepx = 1;
        }
     
        dy <<= 1;                                     // dy*2 와 같은 의미(비트연산)
        dx <<= 1;                                     // dx*2 와 같은 의미(비트연산)
       
        pusStartAddr[ iX1 + SCREEN_WIDTH * iY1 ] = usColor;
        if( dx > dy )
        {
            int fraction = dy - (dx >> 1);     // dx>>1 은 dx/2와 같은 의미(비트연산)
            while( iX1 != iX2 )
            {
                if (fraction >= 0)
                {
                    iY1 += stepy;
                    fraction -= dx;                 // fraction -= 2*dx 과 같은 의미
                }
                iX1 += stepx;
                fraction += dy;                     // fraction -= 2*dy 과 같은 의미
               
                pusStartAddr[ iX1 + SCREEN_WIDTH * iY1 ] = usColor;
            }
        }
        else
        {
            int fraction = dx - (dy >> 1);
            while (iY1 != iY2)
            {
                if (fraction >= 0)
                {
                    iX1 += stepx;
                    fraction -= dy;
                }
                iY1 += stepy;
                fraction += dx;     
               
                pusStartAddr[ iX1 + SCREEN_WIDTH * iY1 ] = usColor;
            }
        }

 

6.2 Box 함수 

 Box 함수는 속이 찬 사각형과 빈 사각형 두가지 타입이 있다.

  1. /**
        Box를 그린다.
    */
    void DrawBox( RECT stClippingRect, void* pvAddr, int iX1, int iY1, int iX2,
            int iY2, unsigned short usColor, bool bFill )
    {
        int j;
        int iTemp;
        int iCount;
        WORD* pwAddr = ( WORD* ) pvAddr;
        WORD* pwTemp;
       
        // 속을 체우게 되어있으면 영역을 체운다.
        if( bFill == true )
        {
            // 영역 클립핑을 한다.
            MinMaxClipping( stClippingRect, &iX1, &iY1 );
            MinMaxClipping( stClippingRect, &iX2, &iY2 );
            iCount = iX2 - iX1 + 1;
            iTemp = iY1 * SCREEN_WIDTH + iX1;
           
            for( j = iY2 - iY1 ; j > 0 ; j-- )
            {
                pwTemp = pwAddr + iTemp;
                FastWordMemSet( pwTemp, usColor, iCount );
                iTemp += SCREEN_WIDTH;
            }
        }
        // 안 채우게 되어있으면 라인만 그린다. 클립핑은 DrawLine에서 해준다.
        else
        {
            DrawLine( stClippingRect, pvAddr, iX1, iY1, iX2, iY1, usColor );
            DrawLine( stClippingRect, pvAddr, iX1, iY2, iX2, iY2, usColor );
            DrawLine( stClippingRect, pvAddr, iX1, iY1, iX1, iY2, usColor );
            DrawLine( stClippingRect, pvAddr, iX2, iY1, iX2, iY2, usColor );
        }

 

6.3 Pixel 함수 

 Pixel 함수는 해당 위치에 RGB555 포맷으로 점을 찍는 역할을 한다. 

  1. /**
     점을 찍는다.
    */
    void DrawPixel( RECT stClippingRect, void* pvAddr, int iX, int iY,
            unsigned short usColor )
    {
        // 화면을 벗어나면 찍지 않는다.
        if( IsInRange( stClippingRect, iX, iY ) == false )
        {
            return ;
        }
       
     *( ( unsigned short* ) pvAddr + ( iY * SCREEN_WIDTH ) + iX ) = usColor;

 

6.4 한글/영문 출력 

 한글/영문 출력 부분은 01 NDS 한글 출력 라이브러리 를 참고하자.

 

7.설치 

7.1 Include 설치 

 devkitPro가 설치된 폴더에 libnds/include 폴더를 찾아서 하위에 window 폴더를 생성한뒤 첨부에 있는 include.zip 파일을 해제한다.

include_설치.PNG

<include 설치>

 

7.2 Library 설치 

 devkitPro가 설치된 폴더에 libnds/lib 폴더를 찾아서 libwindow.a 파일을 복사한다.

 

8.마치면서... 

 이상으로 윈도우 라이브러리 구성 및 구현 방법, 그리고 설치 방법까지 알아보았다. 실제 사용방법은 추후의 Tutorial을 통해 알아보도록 하자. 아래는  NDS 윈도우 시스템을 이용해서 만들어진 홈브루의 실제 화면이다.

화면5.PNG 화면1.PNG 화면2.PNG 화면3.PNG 화면4.PNG

 

9.첨부

 

 

 

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

02 NDS Hardware Spec And Memory Map

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

 

들어가기 전에...

 

1. 하드웨어 사양(Hardware Spec)

 

NDS에 대해서 Wikipedia에 검색을 해봤더니 아래와 같은 정보를 얻을 수 있었다(정말 대단하다 wikipedia~~!!).

Technical specifications

  • Mass: 275 grams (9.7 ounces).
  • Physical size: 148.7 x 84.7 x 28.9 mm (5.85 x 3.33 x 1.13 inches).
  • Screens: Two separate 3-inch TFT LCD, resolution of 256 x 192 pixels, dimensions of 62 x 46 mm and 77 mm diagonal, and a dot pitch of 0.24 mm. Note The gap between the screens is approximately 21mm, equivalent to about 92 "hidden" lines. The lowermost display of the Nintendo DS is overlaid with a resistive touch screen, which registers pressure from one point on the screen at a time, averaging multiple points of contact if necessary.
  • CPUs: Two ARM processors, an ARM946E-S main CPU and ARM7TDMI co-processor at clock speeds of 67 MHz and 33 MHz respectively, with 4 MB of main memory which requires 1.65 volts.
  • Card size
    • Data size: Up to 2 gigabit ( = 2048 Mb or 256 MB).
    • Physical size: 33.0 × 35.0 × 3.8 mm
    • Weight: About 4 grams

The system's 3D hardware performs transform and lighting, texture-coordinate transformation, texture mapping, alpha blending, anti-aliasing, cel shading and z-buffering. However, it uses Point (nearest neighbor) texture filtering, leading to some titles having a blocky appearance. The system is theoretically capable of rendering 120,000 triangles per second at 30 frames per second. Unlike most 3D hardware, it has a limit on the number of triangles it can render as part of a single scene; this limit is somewhere in the region of 4000 triangles. The 3D hardware is designed to render to a single screen at a time, so rendering 3D to both screens is difficult and decreases performance significantly.

The system has two 2D engines, one per screen. These are similar to (but more powerful than) the Game Boy Advance's 2D engine.

Games use a proprietary solid state ROM "Game Card" format resembling the memory cards used in other portable electronic devices such as digital cameras. It currently supports cards up to 2 gigabit[15] in size. The cards always have a small amount of flash memory or an EEPROM to save user data, for example progress in a game or high scores. The game cards are 33.0 × 35.0 × 3.8 mm, and weigh around 3.5 grams (1/8 ounces).

The unit has compatibility with Wi-Fi, and a special wireless format created by Nintendo and secured using RSA security signing (used by the wireless drawing and chatting program PictoChat for the DS). Wi-fi is used for accessing the Nintendo Wi-Fi Connection, where users can use the internet or compete with other users playing the same Wi-Fi compatible game.

 

  위 글을 간단히 요약하면 아래와 같다.

  • LCD : TFT-LCD 256 x 192 pixels
  • CPU :  ARM946E-S main CPU 67MHz. ARM7TDMI co-processor 33 MHz
  • RAM : 4Mbyte
  • Graphic : 3D/2D Engine
  • Input : Key and Touch Pad
  • Communication : Wi-Fi

 

 무엇보다 코어가 듀얼이라서 마음에 든다. @0@)/~ 램이 조금 부족한것 같은데... 어쩔 수 없으니... ㅜ_ㅜ

 아래는 전체적인 구성도이다. http://www.dev-scene.com/NDS/Tutorials_Day_2에서 찾아볼 수 있다.

 

Dov_DS_MemoryMap.png

 

2. 메모리맵(Memory Map)

2.1 전체 구성

 http://www.bottledlight.com/ds/index.php/Memory/Layout 와 http://nocash.emubase.de/gbatek.htm#dsmemorymaps를 참고하면 자세한 메모리 맵의 내용을 볼 수 있다.

 NDS Wiki Tech의 내용을 보면 아래와 같다.

ARM9

Name Region base Size Mirrored Width / modes
ITCM 0x00000000* 16 KB no 32 / all
DTCM 0x00800000* 16 KB no 32 / all
Main RAM 0x02000000 4 MB (8 MB) yes 16 / all
Shared RAM 0x03000000 32 KB yes 32 / 16,32
Registers 0x04000000 * * *
Palette RAM 0x05000000 2 KB yes 16 / 16,32
Video RAM 0x06000000 ? ? 16 / 16,32
Sprite RAM 0x07000000 2 KB yes 16 / 16,32
GBA cart ROM 0x08000000 32 MB no 16 / all
GBA cart RAM 0x0A000000 64 KB yes 8 / 8
BIOS (ARM9) 0xFFFF0000 4 KB no unknown / all

 

ARM7

 

Name Region base End Size Mirrored Width / modes
BIOS (ARM7) 0x00000000 0x00003FFF 16 KB no unknown
Main RAM 0x02000000 0x023FFFFF 4 MB (8 MB) yes 16 / all
Shared RAM 0x037F8000 0x037FFFFF 32 KB ? 32 / 16,32
Private RAM 0x03800000 0x0380FFFF 64 KB yes 32 / 16,32
Registers 0x04000000 * * * *
Wifi Control 0x04800000 0x04800FFF * yes** 16
Wifi MAC memory 0x04804000 0x04805FFF 8 KB yes** 16
GBA cart ROM 0x08000000 0x09FFFFFF 32 MB no 16 / all
GBA cart RAM 0x0A000000 0x0A00FFFF 64 KB yes 8 / 8

The ARM7 BIOS is protected via a PC check. The portion below PROTECTION_CR? can be read when PC < PROTECTION_CR?, and the portion beyond it can be read when PC < 0x4000.

 

The Main RAM is always available to both processors, although one has priority over it, and the other will be delayed if both try to access it at the same time.

The 'shared' RAM actually consists of two banks of 16 KB each, and either one only available to a single processor at a time. They can be switched back and forth, to implement a buffer passing scheme for e.g. wireless packets or a sound buffer.

ITCM and DTCM can be relocated using the system control coprocessor CP15

The ARM9 BIOS provides a handful of functions, but does little in the way of system setup. It clears some memory, then waits for the ARM7 bios to signal that system init is complete.

 ITCM과 DTCM 같은 경우는 조금 특별하므로 co-processor 쪽을 봐야 한다(cp15). 좀더 상세한 내용은 아래의 TCM 쪽에서 살펴보자.

 위의 내용 중에 중요한 부분은 메인 메모리 같은 경우 두 프로세스에서 항상 사용 가능하지만 동시에 접근하면 하나가 먼저 작업하고 다른 하나는 지연된다는 것이다.

 또 다른 중요한 부분은 Shared RAM 같은 경우는 두 프로세스 중에 하나만 접근 가능하고 이것을 스위칭을 통해 다른 프로세스에게 넘겨주는 방식으로 동작하는 것이다. 즉 한쪽이 데이터를 체운 후 다른 쪽이 받아서 처리하는 중계 역할인 버퍼로 사용될 수 있다.

 

 Video RAM영역 같은 경우는 자세하게 나와있지 않는데, 이부분은 GBA Tech의 문서를 보면 아래와 같이 잘 나와있다. http://nocash.emubase.de/gbatek.htm#dsmemorymaps에 내용들이다.

 ARM9

00000000h  Instruction TCM (32KB) (not moveable) (mirror-able to 1000000h)
  0xxxx000h  Data TCM        (16KB) (moveable)
  02000000h  Main Memory     (4MB)
  03000000h  Shared WRAM     (0KB, 16KB, or 32KB can be allocated to ARM9)
  04000000h  ARM9-I/O Ports
  05000000h  Standard Palettes (2KB) (Engine A BG/OBJ, Engine B BG/OBJ)
  06000000h  VRAM - Engine A, BG VRAM  (max 512KB)
  06200000h  VRAM - Engine B, BG VRAM  (max 128KB)
  06400000h  VRAM - Engine A, OBJ VRAM (max 256KB)
  06600000h  VRAM - Engine B, OBJ VRAM (max 128KB)
  06800000h  VRAM - "LCDC"-allocated (max 656KB)
  07000000h  OAM (2KB) (Engine A, Engine B)
  08000000h  GBA Slot ROM (max. 32MB)
  0A000000h  GBA Slot RAM (max. 64KB)
  FFFF0000h  ARM9-BIOS (32KB) (only 3K used)
The ARM9 Exception Vectors are located at FFFF0000h. The IRQ handler redirects to [DTCM+3FFCh].

ARM7
00000000h  ARM7-BIOS (16KB)
  02000000h  Main Memory (4MB)
  03000000h  Shared WRAM (0KB, 16KB, or 32KB can be allocated to ARM7)
  03800000h  ARM7-WRAM (64KB)
  04000000h  ARM7-I/O Ports
  04800000h  Wireless Communications Wait State 0
  04808000h  Wireless Communications Wait State 1
  06000000h  VRAM allocated as Work RAM to ARM7 (max. 256K)
  08000000h  GBA Slot ROM (max. 32MB)
  0A000000h  GBA Slot RAM (max. 64KB)
The ARM7 Exception Vectors are located at 00000000h. The IRQ handler redirects to [3FFFFFCh aka 380FFFCh].

Further Memory (not mapped to ARM9/ARM7 bus)
3D Engine Polygon RAM (52KBx2)
  3D Engine Vertex RAM (72KBx2)
  Firmware (256KB) (built-in serial flash memory)
  GBA-BIOS (16KB) (not used in NDS mode)
  NDS Slot ROM (serial 8bit-bus, max. 4GB with default protocol)
  NDS Slot EEPROM (serial 1bit-bus)

Shared-RAM
Even though Shared WRAM begins at 3000000h, programs are commonly using mirrors at 37F8000h (both ARM9 and ARM7). At the ARM7-side, this allows to use 32K Shared WRAM and 64K ARM7-WRAM as a continous 96K RAM block

사실 Video Memory는 A,B,C,D 외에도 E, F, G, H, I가 있다. 자세한 내용은 03 비디오 모드 제어(Video Mode Control)에서 볼 수 있다.

 

  음~ 갑자기 난데없이 WRAM 이란 용어가 나왔다. 역시나 궁금하니 wikipedia에 물었다.

Window RAM (WRAM)

Window RAM or WRAM is an obsolete type of semiconductor computer memory that was designed to replace video RAM (VRAM) in graphics adapters. It was developed by Samsung and also marketed by Micron Technology, but had only a short market life before being superseded by SDRAM and SGRAM.

WRAM has a dual-ported dynamic RAM structure similar to that of VRAM, with one parallel port and one serial port, but has extra features to enable fast block copies and block fills (so-called window operations). It was often clocked at 50 MHz. It has a 32-bit wide host port to enable optimal data transfer in PCI and VESA Local Bus systems. Typically WRAM was 50% faster than VRAM, but with costs 20% lower. It is sometimes erroneously called Windows RAM, because of confusion with the Microsoft Windows operating systems, to which it is unrelated apart from the fact that window operations could boost the performance of windowing systems.

It was used by Matrox on both their MGA Millennium and Millennium II graphics cards.

 비디오 메모리보다는 빠르고 싸다는데... 데이터 메모리 처럼 쓸 수 있나보다. 어쨋든 추가로 활용할 수 있는 메모리가 많다는 것은 좋은 거니깐 그리 알고 넘어가자.

 

 메모리에 대한 추가 정보는 http://neimod.com/dstek/ 에서 찾아볼 수 있다. 중복된 정보 말고 참고할만한 정보를 추리면 아래와 같다.

Memory mirroring

Every memory section, e.g. main memory, shared iwram, io, vram, palette, etc. are mirrored.
For example, Main Memory from 0200:0000-023F:FFFF is mirrored in 0240:0000-027F:FFFF, 0280:0000-02BF:FFFF, etc. until 02FF:FFFF
An other example, Shared IWRAM from 037F:8000-037F:FFFF, is mirrored from 0300:0000 to 037F:7FFF.
This is for each memory section, although it is sometimes difficult to tell where the mirroring starts and stops, ie. IO.

Shared IWRAM

The name shared IWRAM (consists of 2 16kb blocks) can be misleading, as only one CPU has access to it.
However, using WRAMCNT each 16k block can be assigned to a specific CPU.
When processing is done for example, the block can be assigned to the other CPU quickly for processing, without copying it via main memory or the IPC fifo.

Main Memory

Main memory, consisting of one big block of 4MB memory, can be accessed by both CPU's.
However, only one CPU can read/write/execute from it at a time. When both CPUs are trying to read main memory, one will have priority over the other.
See EXMEMCNT for more information.

 Shared RAM 영역은 WRAMCNT 레지스터를 통해 어느 CPU에서 사용가능한지 제어가 가능한 것 같다. 추후 사용해 보도록 하자.

 

2.2 ITCM, DTCM

 TCM에 대한 자세한 내용은 http://nocash.emubase.de/gbatek.htm#dsmemorycontrolcacheandtcm에서 찾아볼 수 있다. TCM은 ARM9에만 연결되어있으며 아래와 같다.

TCM and Cache are controlled by the System Control Coprocessor,
ARM CP15 System Control Coprocessor

The specifications for the NDS9 are:

Tightly Coupled Memory (TCM)

ITCM 32K, base=00000000h (fixed, not move-able)
  DTCM 16K, base=moveable  (default base=27C0000h)
Note: Although ITCM is NOT moveable, the NDS Firmware configures the ITCM size to 32MB, and so, produces ITCM mirrors at 0..1FFFFFFh. Furthermore, the PU can be used to lock/unlock memory in that region. That trick allows to move ITCM anywhere within the lower 32MB of memory.

Cache
Data Cache 4KB, Instruction Cache 8KB
  4-way set associative method
  Cache line 8 words (32 bytes)
  Read-allocate method (ie. writes are not allocating cache lines)
  Round-robin and Pseudo-random replacement algorithms selectable
  Cache Lockdown, Instruction Prefetch, Data Preload
  Data write-through and write-back modes selectable


Protection Unit (PU)
Recommended/default settings are:

 

Region  Name            Address   Size   Cache WBuf Code Data
  -       Background      00000000h 4GB    -     -    -    -
  0       I/O and VRAM    04000000h 64MB   -     -    R/W  R/W
  1       Main Memory     02000000h 4MB    On    On   R/W  R/W
  2       ARM7-dedicated  027C0000h 256KB  -     -    -    -
  3       GBA Slot        08000000h 128MB  -     -    -    R/W
  4       DTCM            027C0000h 16KB   -     -    -    R/W <= devkit ARM에서는 주소를 0x0B000000로 재설정해서 사용
  5       ITCM            01000000h 32KB   -     -    R/W  R/W
  6       BIOS            FFFF0000h 32KB   On    -    R    R
  7       Shared Work     027FF000h 4KB    -     -    -    R/W
위의 내용중에 DTCM영역에 대해서 잠깐 부연 설명을 하자면, 위의 값은 default로 설정되는 값인데 실제로 devkitARM의 crt0.S 파일을 살펴보면 위 영역을 다시 0xB000000 영역으로 재정의해서 사용함을 알 수 있다. 홈브루 파일을 보면서 헷갈리지 말자.
 
Notes: In Nintendos hardware-debugger, Main Memory is expanded to 8MB (for that reason, some addresses are at 27NN000h instead 23NN000h) (some of the extra memory is reserved for the debugger, some can be used for game development). Region 2 and 7 are not understood? GBA Slot should be max 32MB+64KB, rounded up to 64MB, no idea why it is 128MB?
 
DTCM and ITCM do not use Cache and Write-Buffer because TCM is fast. Above settings do not allow to access Shared Memory at 37F8000h? Do not use cache/wbuf for I/O, doing so might suppress writes, and/or might read outdated values.
The main purpose of the Protection Unit is debugging, a major problem with GBA programs have been faulty accesses to memory address 00000000h and up (due to [base+offset] addressing with uninitialized (zero) base values). This problem has been fixed in the NDS, for the ARM9 processor at least, still there are various leaks: For example, the 64MB I/O and VRAM area contains only ca. 660KB valid addresses, and the ARM7 probably doesn't have a Protection Unit at all. Alltogether, the protection is better than in GBA, but it's still pretty crude compared with software debugging tools.
Region address/size are unified (same for code and data), however, cachabilty and access rights are non-unified (and may be separately defined for code and data).

Note: The NDS7 doesn't have any TCM, Cache, or CP15. 

 홈브루를 컴파일 하는 과정을 살펴보면( 00 NDS makefile 및 NDS 파일 생성 과정 분석 ) ITCM 주소가 0x0000 이 아니라 0x1000000에 위치하도록 되어있는 것을 알 수있다.

 위에서 보면 ITCM들은 펌웨어에 의해서 32Mbyte 영역으로 설정되기 때문에 ITCM의 시작 주소를 바꾸는 것은 불가능하지만 mirror를 시켜서 32Mbyte안 쪽에 어디든지 가능하다. 이것을 Protection Unit을 사용해서 ITCM의 위치와 크기를 0x1000000에 있도록 설정해서 ITCM의 위치를 정확히 찝어 주는 것 같다. 상당히 복잡한 구조다. ㅜ_ㅜ

 

3. I/O 포트 맵(Port Map)

 컨트롤러와 연결된 I/O 주소는 http://nocash.emubase.de/gbatek.htm#dsiomaps 에서 찾을 수 있고 아래와 같다.

ARM9 I/O Map
Display Engine A

4000000h  4    2D Engine A - DISPCNT - LCD Control (Read/Write)
  4000004h  2    2D Engine A - DISPSTAT - General LCD Status (Read/Write)
  4000006h  2    2D Engine A - VCOUNT - Vertical Counter (Read only)
  4000008h  50h  2D Engine A (same registers as GBA, some changed bits)
  4000060h  2    DISP3DCNT - ?
  4000064h  4    DISPCAPCNT - Display Capture Control Register (R/W)
  4000068h  4    DISP_MMEM_FIFO - Main Memory Display FIFO (R?/W)
  400006Ch  2    MASTER_BRIGHT - Master Brightness Up/Down
DMA, Timers, and Keypad
40000B0h  30h  DMA Channel 0..3
  40000E0h  10h  DMA FILL Registers for Channel 0..3
  4000100h  10h  Timers 0..3
  4000130h  2    KEYINPUT
  4000132h  2    KEYCNT
IPC/ROM
4000180h  2  IPCSYNC - IPC Synchronize Register (R/W)
  4000184h  2  IPCFIFOCNT - IPC Fifo Control Register (R/W)
  4000188h  4  IPCFIFOSEND - IPC Send Fifo (W)
  40001A0h  2  AUXSPICNT - Gamecard ROM and SPI Control
  40001A2h  2  AUXSPIDATA - Gamecard SPI Bus Data/Strobe
  40001A4h  4  Gamecard bus timing/control
  40001A8h  8  Gamecard bus 8-byte command out
  40001B0h     romcrypt - (not sure if encryption can be accessed by arm9...?)
Memory and IRQ Control
4000204h  2  EXMEMCNT - External Memory Control (R/W)
  4000208h  2  IME - Interrupt Master Enable (R/W)
  4000210h  4  IE  - Interrupt Enable (R/W)
  4000214h  4  IF  - Interrupt Request Flags (R/W)
  4000240h  1  VRAMCNT_A - VRAM-A (128K) Bank Control (W)
  4000241h  1  VRAMCNT_B - VRAM-B (128K) Bank Control (W)
  4000242h  1  VRAMCNT_C - VRAM-C (128K) Bank Control (W)
  4000243h  1  VRAMCNT_D - VRAM-D (128K) Bank Control (W)
  4000244h  1  VRAMCNT_E - VRAM-E (64K) Bank Control (W)
  4000245h  1  VRAMCNT_F - VRAM-F (16K) Bank Control (W)
  4000246h  1  VRAMCNT_G - VRAM-G (16K) Bank Control (W)
  4000247h  1  WRAMCNT   - WRAM Bank Control (W)
  4000248h  1  VRAMCNT_H - VRAM-H (32K) Bank Control (W)
  4000249h  1  VRAMCNT_I - VRAM-I (16K) Bank Control (W)
Maths
4000280h  2  DIVCNT - Division Control (R/W)
  4000290h  8  DIV_NUMER - Division Numerator (R/W)
  4000298h  8  DIV_DENOM - Division Denominator (R/W)
  40002A0h  8  DIV_RESULT - Division Quotient (=Numer/Denom) (R/W?)
  40002A8h  8  DIVREM_RESULT - Division Remainder (=Numer MOD Denom) (R/W?)
  40002B0h  2  SQRTCNT - Square Root Control (R/W)
  40002B4h  4  SQRT_RESULT - Square Root Result (R/W?)
  40002B8h  8  SQRT_PARAM - Square Root Parameter Input (R/W)
  4000300h  4  POSTFLG - Undoc
  4000304h  2  POWCNT1 - Graphics Power Control Register (R/W)
3D Display Engine
4000320h..6A3h
Display Engine B
4001000h  4    2D Engine A - DISPCNT - LCD Control (Read/Write)
  4001008h  50h  2D Engine B (same registers as GBA, some changed bits)
  400106Ch  2    MASTER_BRIGHT - 16bit - Master Brightness Up/Down

4100000h  4    IPCFIFORECV - IPC Receive Fifo (R)
  4100010h  4    Gamecard bus 4-byte data in, for manual or dma read
Hardcoded RAM Addresses for Exception Handling
27FFD9Ch   ..  NDS9 Debug Stacktop / Debug Vector (0=None)
  DTCM+3FF8h 4   NDS9 IRQ Check Bits (hardcoded RAM address)
  DTCM+3FFCh 4   NDS9 IRQ Handler (hardcoded RAM address)
Main Memory Control
27FFFFEh  2    Main Memory Control
Further Memory Control Registers
ARM CP15 System Control Coprocessor

ARM7 I/O Map
4000004h  2   DISPSTAT
  4000006h  2   VCOUNT
  40000B0h  30h DMA Channels 0..3
  4000100h  10h Timers 0..3
  4000120h  4   debug siodata32
  4000128h  4   debug siocnt
  4000130h  2   keyinput
  4000132h  2   keycnt
  4000134h  2   debug rcnt
  4000136h  2   EXTKEYIN
  4000138h  1   RTC Realtime Clock Bus
  4000180h  2   IPCSYNC - IPC Synchronize Register (R/W)
  4000184h  2   IPCFIFOCNT - IPC Fifo Control Register (R/W)
  4000188h  4   IPCFIFOSEND - IPC Send Fifo (W)
  40001A0h  2   AUXSPICNT - Gamecard ROM and SPI Control
  40001A2h  2   AUXSPIDATA - Gamecard SPI Bus Data/Strobe
  40001A4h  4   Gamecard bus timing/control
  40001A8h  8   Gamecard bus 8-byte command out
  40001B0h  4   Gamecard Encryption
  40001B4h  4   Gamecard Encryption
  40001B8h  2   Gamecard Encryption
  40001BAh  2   Gamecard Encryption
  40001C0h  2   SPI bus Control (Firmware, Touchscreen, Powerman)
  40001C2h  2   SPI bus Data
  4000204h  2   EXMEMSTAT - External Memory Status
  4000206h  2   WIFIWAITCNT
  4000208h  4   IME
  4000210h  4   IE
  4000214h  4   IF
  4000240h  1   VRAMSTAT - VRAM-C,D Bank Status (R)
  4000241h  1   WRAMSTAT - WRAM Bank Status (R)
  4000300h  1   POSTFLG
  4000301h  1   HALTCNT (different bits than on GBA) (plus NOP delay)
  4000304h  2   POWCNT2  Sound/Wifi Power Control Register (R/W)
  4000308h  4   BIOSPROT - Bios-data-read-protection address
Sound Registers
4000400h 100h Sound Channel 0..15 (10h bytes each)
  40004x0h  4  SOUNDxCNT - Sound Channel X Control Register (R/W)
  40004x4h  4  SOUNDxSAD - Sound Channel X Data Source Register (W)
  40004x8h  2  SOUNDxTMR - Sound Channel X Timer Register (W)
  40004xAh  2  SOUNDxPNT - Sound Channel X Loopstart Register (W)
  40004xCh  4  SOUNDxLEN - Sound Channel X Length Register (W)
  4000500h  2  SOUNDCNT - Sound Control Register (R/W)
  4000504h  2  SOUNDBIAS - Sound Bias Register (R/W)
  4000508h  1  SNDCAP0CNT - Sound Capture 0 Control Register (R/W)
  4000509h  1  SNDCAP1CNT - Sound Capture 1 Control Register (R/W)
  4000510h  4  SNDCAP0DAD - Sound Capture 0 Destination Address (W?)
  4000514h  2  SNDCAP0LEN - Sound Capture 0 Length (R/W)
  4000518h  4  SNDCAP1DAD - Sound Capture 1 Destination Address (W?)
  400051Ch  2  SNDCAP1LEN - Sound Capture 1 Length (R/W)
gamecart...
4100000h  4   IPCFIFORECV - IPC Receive Fifo (R)
  4100010h  4   Gamecard bus 4-byte data in, for manual or dma read
WLAN Registers
4808036h  2   W
  4808158h  2   W
  480815Ah  2   W
  480815Ch  2   R
  480815Eh  2   R
  4808160h  2   W
  4808168h  2   W
  480817Ch  2   W
  480817Eh  2   W
  4808180h  2   R
  4808184h  2   W
Hardcoded RAM Addresses for Exception Handling
 
380FFDCh  ..  NDS7 Debug Stacktop / Debug Vector (0=None)
  380FFF8h  4   NDS7 IRQ Check Bits (hardcoded RAM address)
  380FFFCh  4   NDS7 IRQ Handler (hardcoded RAM address)

 

 

4. 마치며...

 일단 간단하게 NDS의 메모리 맵에 대해 알아보았다. 시스템 프로그램을 하려면 메모리맵은 필수이므로 자주 참고하도록 하자.

 

 

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

01 NDS 롬(ROM) 파일 포맷(Format) 분석

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

 

들어가기 전에...

0. 시작하면서...

 devkitARM이 *.nds 파일을 만드는 과정을 살펴보면 makefile을 참조해서 살펴보면 아래와 같은 과정을 거친다.

  1. object 파일을 링크하여 *.elf 파일을 만든다.
  2. *.elf 파일을 objcopy -O binary 옵션을 이용하여 코드와 데이터 영역만 추려내서 순수한 바이너리 파일인 *.arm9 or *.arm7 파일을 만든다.
  3. ndstool을 사용해서 *.arm9파일을 *.nds 파일로 만든다.

 

 그럼 도대체 이 NDS Rom 파일은 어떤 구조로 되어있는 것일까? 자세히 살펴보자.

 

1. NDS 롬(ROM) 헤더(Header) 구조

http://www.bottledlight.com/ds/index.php/FileFormats/NDSFormat를 참조하면 실제 어떤 구조로 되어있는지 알 수 있다.

Header format

Field Start End Size Example data (Metroid demo)
Game title 0x000 0x00B 12 "FIRST HUNT "
Game code 0x00C 0x00F 4 "AMFE"
Maker code 0x010 0x011 2 "01" (Nintendo)
Unit code 0x012 0x012 1 0x00
Device code 0x013 0x013 1 0x00
Card size 0x014 0x014 1 0x07 (2^(20 + 7) = 128Mb = 16 MB)
Card info 0x015 0x01E 10 0x00's
Flags 0x01F 0x01F 1 0x00
ARM9 source (ROM) 0x020 0x023 4 0x00004000 (must be 4 KB aligned)
ARM9 execute addr 0x024 0x027 4 0x02004800
ARM9 copy to addr 0x028 0x02B 4 0x02004000
ARM9 binary size 0x02C 0x02F 4 0x00081D58
ARM7 source (ROM) 0x030 0x033 4 0x000B3000
ARM7 execute addr 0x034 0x037 4 0x02380000
ARM7 copy to addr 0x038 0x03B 4 0x02380000
ARM7 binary size 0x03C 0x03F 4 0x00026494
Filename table offset (ROM) 0x040 0x043 4 0x000D9600
Filename table size 0x044 0x047 4 0x11B6
FAT offset (ROM) 0x048 0x04B 4 0x000DA800
FAT size 0x04C 0x04F 4 0x678
ARM9 overlay src (ROM) 0x050 0x053 4 0x00085E00
ARM9 overlay size 0x054 0x057 4 0x60
ARM7 overlay src (ROM) 0x058 0x05B 4 0
ARM7 overlay size 0x05C 0x05F 4 0
Control register flags for read 0x060 0x063 4 0x00586000
Control register flags for init 0x064 0x067 4 0x001808F8
Icon+titles (ROM) 0x068 0x06B 4 0x000DB000
Secure CRC16 0x06C 0x06D 2 0xC44D
ROM timeout 0x06E 0x06F 2 0x051E
ARM9 unk addr 0x070 0x073 4 0x020049EC
ARM7 unk addr 0x074 0x077 4 0x02380110
Magic number for unencrypted mode 0x078 0x07F 8 0x00's
ROM size 0x080 0x083 4 0x00EE3E44
Header size 0x084 0x087 4 0x4000
Unknown 5 0x088 0x0BF 56 0x00's
GBA logo 0x0C0 0x15B 156 data
Logo CRC16 0x15C 0x15D 2 0xCF56
Header CRC16 0x15E 0x15F 2 0x00F8
Reserved 0x160 0x1FF 160 0x00's

 

The control register flags contain latency settings and depends on the ROM manufacturer (Macronix, Matrix Memory).

Unknown2a and 'Header Size' contain flags that are (somewhat) used during boot as part of card CR writes. Unknown2b contains the size of a certain type of transfer done during boot, but it's range checked and cannot be reduced. Unknown3c is seemingly unused, and some code paths get data from 0x160 onward (only 0x170 bytes of a header fetch are actually retained by the BIOS)

Secure CRC16 calculation is performed on the ROM from 0x4000 to 0x7FFF, with an initial value of 0xFFFF.

Logo CRC16 calculation is performed on the header from 0x0C0 to 0x15B, with an initial value of 0xFFFF.

Header CRC16 calculation is performed on the header (after the previous two CRCs are filled) from 0x000 to 0x15D with an initial value of 0xFFFF.

Device code needs lower 3 bits to be 0. This is a similar behavior as GBA header.

 위의 정보를 보면 롬 영역 안쪽에 FAT File System이 들어가는 것을 알 수 있다. 즉 게임에서 필요한 데이터를 내부에 플래쉬에 저장하여 읽고 쓴다는 이야기다. @0@)/~ 참으로 놀라운 사실이 아닐 수 없다.

 저 부분을 분석하면 게임 내부에서 사용하는 데이터도 추출해 낼 수 있다는 말이된다. 물론 좀더 분석해봐야 알겠지만 상당히 흥미로운 사실이다.

  

1.1 ARM9 ROM의 시작 위치

 위에서 Header Size를 보면 0x4000으로 설정되어있다. ARM9 ROM의 시작위치는? 0x4000 인걸 알 수 있다. 옆에 "4Kbyte로 반드시 정렬해라"라는 이야기가 있는데, 게임롬에만 해당하는 건지 확실히 알 수 없지만 굳이 하지 않아도 될 것 같다. 특히 홈브루 같은 경우는 더욱...

 위 결과로 미루어 보면 ROM Header의 바로 뒤에 ARM9의 코드가 들어감을 알 수 있다.

  

1.2 ARM7 ROM의 시작 위치

 ARM7의 코드가 포함된 ROM의 위치는 ARM9의 바로 뒤에 연결 되어있지 않다. 다만 0x1000의 배수(4Kbyte)에 맞추어서 시작하고 있다.

 ARM7 코드가 로딩되는 위치의 주소를 보면 0x2380000으로 4Mybte의 메인 메모리 뒷쪽에 자리잡고 있다. NDS 게임의 경우 ARM7이 하는 일이 그렇게 많지 않아 뒷쪽으로 할당한 것 처럼 보인다.

  

1.3 롬 실행 시 메모리 배치

 롬 헤더에 보면 롬파일 내에 ARM9/7 코드가 어디서 시작하고 크기는 얼마나 되며 메모리에 어느 위치에 복사해주는지 정보가 나와있다. 또한 Entry 정보도 나와있는 걸로 보아 시작 시에 해당 메모리 위치로 코드를 복사하고 시작함을 알 수 있다.

  

2.아이콘(Icon) 및 광고(Banner) 헤더(Header) 구조

Icon + logo format (the banner)

This is a structure of size 32+512+32+256*6 = 2112 bytes with the following format:

 

Banner structure

Offset Size Description
0 2 Version (always 1)
2 2 CRC-16 of structure, not including first 32 bytes
4 28 Reserved
32 512 Tile Data
544 32 Palette
576 256 Japanese Title
832 256 English Title
1088 256 French Title
1344 256 German Title
1600 256 Italian Title
1856 256 Spanish Title

 

The icon is a 32x32 picture formed out of 4x4 16 color tiles and a single 16 color palette.

Following the icon data (icon offset + 576 bytes), are 6 unicode game titles, displayed in the DS menu depending on the selected language mode. Each title is padded to 128 characters (256 bytes).

The strings are in the same order as the language choice in Firmware.

 게임 아이콘 및 설명을 넣는 부분 같다. 아이콘이나 게임 타이틀 부분도 ndstool.exe를 이용하면 쉽게 넣을 수 있으므로 나중에 참고하자.

 

3.실제 롬 파일 분석

3.1 헤더 분석

 devkitPro를 설치하면 자동으로 설치되는 ndstool.exe 프로그램을 이용해서 KKAMAGUI NOTEPAD 롬파일을 분석해 보자. KKAMAGUI NOTEPAD의 롬파일은 00 KKAMAGUI NOTEPAD 에서 구할 수 있다. dstool.exe 프로그램은 아래와 같은 다양한 옵션을 가지고 있다.

 

 위의 옵션 중에 -i 옵션을 이용하면 롬의 정보를 볼 수 있다. i 옵션을 이용하여 롬 파일을 분석해 보자.

  1. D:\devkitPro\MyProject\NotePad>ndstool -i NotePad.nds

     

    Nintendo DS rom tool 1.33 - Jan 28 2007 21:02:20
    by Rafael Vuijk, Dave Murphy,  Alexei Karpenko
    Header information:
    0x00    Game title                      .
    0x0C    Game code                       ####
    0x10    Maker code
    0x12    Unit code                       0x00
    0x13    Device type                     0x00
    0x14    Device capacity                 0x01 (2 Mbit)
    0x15    reserved 1                      000000000000000000
    0x1E    ROM version                     0x00
    0x1F    reserved 2                      0x04
    0x20    ARM9 ROM offset                 0x200
    0x24    ARM9 entry address              0x2000000
    0x28    ARM9 RAM address                0x2000000
    0x2C    ARM9 code size                  0x31794
    0x30    ARM7 ROM offset                 0x31A00
    0x34    ARM7 entry address              0x37F8000

    0x38    ARM7 RAM address                0x37F8000
    0x3C    ARM7 code size                  0x12B8
    0x40    File name table offset          0x32E00
    0x44    File name table size            0x9
    0x48    FAT offset                      0x33000
    0x4C    FAT size                        0x0
    0x50    ARM9 overlay offset             0x0
    0x54    ARM9 overlay size               0x0
    0x58    ARM7 overlay offset             0x0
    0x5C    ARM7 overlay size               0x0
    0x60    ROM control info 1              0x007F7FFF
    0x64    ROM control info 2              0x203F1FFF
    0x68    Icon/title offset               0x0
    0x6C    Secure area CRC                 0x0000 (-, homebrew)
    0x6E    ROM control info 3              0x051E
    0x70    ARM9 ?                          0x0
    0x74    ARM7 ?                          0x0
    0x78    Magic 1                         0x00000000
    0x7C    Magic 2                         0x00000000
    0x80    Application end offset          0x00033000
    0x84    ROM header size                 0x00000200
    0xA0    ?                               0x4D415253
    0xA4    ?                               0x3131565F
    0xA8    ?                               0x00000030
    0xAC    ?                               0x53534150
    0xB0    ?                               0x00963130
    0x15C   Logo CRC                        0x9E1A (OK)
    0x15E   Header CRC                      0x32B1 (OK)

 홈브루의 경우는 게임의 경우와 조금 달리 ARM7의 이미지가 0x1000에서 시작하지 않고 있음을 알 수 있다. 그리고 ARM7의 코드가 4Mbyte의 공유영역에 올라가있는 것이 아니라 0x37F8000 주소에 Private RAM(64Kbyte)에 위치하고 있다. 02 NDS Spec에 보면 메모리 맵에 대한 자세한 내용을 알 수 있다.

 Secure area CRC 부분을 보면 이 값이 0x0000 이고 homebrew라고 표시되어있다. 실제 게임은 이 값이 0이 아닌 것일까? 몇가지 게임 롬을 분석해본 결과... 0x0000 이 아님을 알 수 있었다. 보안에 관련된 영역을 검사하는데 사용되는 것으로 생각된다.

 

3.2 FAT 영역 분석

 롬 파일 내부에 FAT 영역을 가진다. ndstool.exe 를 이용하면 -l 옵션으로 FAT 영역의 데이터를 볼 수 있다. KKAMAGUI NOTEPAD의 경우 FAT 영역에 별다른 데이터를 가지지 않으므로 다른 홈브루나 게임 롬을 열어봐야 된다. 홈브루 중에서는 아마 찾기 힘들 것이므로 게임 롬을 열어보자. 디렉토리 구조와 파일들이 줄줄이 나오는 것을 알 수 있다.

FATList.PNG

<ndstool.exe를 이용해서 롬 파일의 FAT 영역을 분석한 화면>

 

 그렇다면 ROM 파일에 있는 FAT 파일 시스템은 우리가 윈도우에서 포맷할 때 만들어지는 모든 파일 시스템 정보를 포함하고 있을까?

 이 부분은 ROM 파일을 열어서 확인을 해봐야 할 것 같다.

 

여기 체우기...

 

4. 데이터 영역 분석

 롬 데이터 영역은 .arm9 및 .arm7의 파일의 덤프 형태로 되어잇는데, 자세한 내용은 00 NDS makefile 및 NDS 파일 생성 과정 분석 페이지를 참조하자.

 

마치며...

 이상으로 NDS 롬 파일 구조에 대해서 알아보았다. 특별히 암호화도 되어있지 않고 checksum을 이용해서 롬 파일의 유효성을 체크한다.

 롬 파일의 구조를 알았으니, 그 안에서 데이터를 뽑아내어 언어를 한글화 한다던지, 그림 파일을 바꾼다던지 하는 것고 그리 어렵지 않을꺼라 생각한다.

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

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


01 libfat 업그레이드

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

 

들어가기 전에...

 

0. 시작하면서...

 홈 브루를 개발하다보면 자료를 저장하거나 데이터를 읽어올 필요가 있다. 이를 위히 FAT로 포맷된 카드에서 자료를 읽을 수 있도록 libfat라는 라이브러리를 지원한다. 하지만 최신의 장비와 같은 경우, Devkit pro에 포함된 기본 libfat.a 라이브러리에서 지원하지 않을 가능성이 크다.

 내가 가진 장비를 지원하기위해 libfat 라이브러리를 업그레이드 하는 과정을 살펴보자.

 

1. libfat 소스 다운로드 및 링크

 개발한 홈 브루가 데이터를 읽고 쓰려면 FAT 라이브러리가 있어야 한다. 그리고 당연히 하드웨어적인 섹터 IO를 수행하는 라이브러리가 있어야 한다. 기존의 Devkit Pro에 포함된 libfat.a가 IO를 지원하지 않는다는 가정하에 http://sourceforge.net/project/downloading.php?group_id=114505&use_mirror=nchc&filename=libfat-src-20070127.tar.bz2&12016483 에서 libfat 소스를 다운받아서 추가한 후 컴파일 하는 과정을 살펴보자.

 일단 현재( 2007/08/08 21:04:06 )까지 최신 버전인 libfat-src-20070127.tar.tar 파일을 다운 받아서 devkit pro를 설치한 폴더에 압축을 푼다. 그럼 아래와 같은 폴더 구조를 가질 것이다.

libfat1.PNG

 

  여기서 disk_io 폴더에 들어가서 disk.c 파일을 열고 아래와 같은 부분을 찾아본다.

  1. const IO_INTERFACE* ioInterfaces[] = {
     &_io_dldi,  // Reserved for new interfaces
    #ifdef NDS
     // Place Slot 1 (DS Card) interfaces here
     &_io_njsd, &_io_nmmc,
    #endif
     // Place Slot 2 (GBA Cart) interfaces here
     &_io_mpcf, &_io_m3cf, &_io_sccf, &_io_scsd, &_io_m3sd
    };

  위의 코드에서 보듯이 DLDI와 몇몇 장비만 추가적으로 지원한다는 것을 알 수 있다. 고로 내 장비의 이름이 위에 없다면 해당 장비를 판매하는 곳에 가서 libfat 용 파일을 Low IO를 수행하는 파일을 받아야 한다. 각 장비 개발사에 가면 아래와 같은 파일들을 구할 수 있을 것이다.

libfat2.PNG

 이제 이 파일을 disk_io 폴더에 복사해 넣고 io_r4tf.h 파일을 아래와 같이 수정한다.

  1. #ifndef IO_R4TF_H
    #define IO_R4TF_H
  2. // 'R4TF'
    #define DEVICE_TYPE_R4TF 0x46543452
  3. // 추가한 부분
    #ifdef NDS
    #define SUPPORT_R4TF
    #endif
  4. #include "disc_io.h"
  5. // 추가한 부분
    typedef IO_INTERFACE* LPIO_INTERFACE;
  6. // 수정한 부분
    extern IO_INTERFACE* R4TF_GetInterface(void);
  7. // 추가한 부분
    extern IO_INTERFACE io_r4tf;
  8. #endif

 

 그리고 disk.c 파일을 열어서 위에서 언급했던 부분을 아래와 같이 수정한다.

  1. const IO_INTERFACE* ioInterfaces[] = {
     &_io_dldi,  // Reserved for new interfaces
    #ifdef NDS
     // Place Slot 1 (DS Card) interfaces here
     &io_r4tf, //&_io_njsd, &_io_nmmc,
    #endif
     // Place Slot 2 (GBA Cart) interfaces here
     &_io_mpcf, &_io_m3cf, &_io_sccf, &_io_scsd, &_io_m3sd
    };

 

 자 이제 수정이 다 끝났으니 build를 수행할 차례이다. cmd.exe 를 실행시켜서 libfat가 있는 소스로 이동한다음 make를 입력하면 아래와 같이 컴파일 및 링크가 시작된다.

libfat3.PNG

<build 진행중>

 libfat4.PNG

<build 완료>

 

 위처럼 빌드 완료 화면이 나오지 않고 에러가 표시되면 잘못 수정한 것이므로 에러 메시지를 보고 마저 수정한 후 처리하도록 한다. 빌드가 완료되고 나면 libfat 폴더 밑의 nds 폴더 밑의 lib 폴더에 libfat.a 파일이 생긴걸 알 수 있다.

libfat5.PNG

<libfat.a 파일 정상 생성>

 

 위에서 생성된 파일을 아래와 같이 libnds 폴더 아래에 lib 폴더에 복사하면 설치가 끝난다.

libfat6.PNG

 

 

2. 테스트

 libfat 라이브러리를 빌드했으니 테스트를 한번 해보자. 아래는 directory를 탐색하고 파일을 생성해서 데이터를 쓰는 소스이다.

  1. #include <nds.h>
    #include <fat.h>
    #include <stdio.h>
    #include <sys/dir.h>
  2. bool TestFile( void );
  3. int main(void)
    {
        // LCD Core를 켠다.
        powerON( POWER_ALL_2D );
  4.     // 파일을 테스트 한다.
        TestFile();
  5.     while(1)
        {
            // 아무 interrupt나 대기한다.
            swiWaitForIRQ();
        }
       
        return 0;
    }
  6. /**
        파일을 테스트 한다.
    */
    bool TestFile( void )
    {
        char vcBuffer[ 256 ];
        FILE* fp;
        struct stat st;
        char filename[256];
        int i;
  7.     if( ( fatInitDefault() == false ) )
        {
            return false;
        }
  8.     // Directory Test
        DIR_ITER* dir;
        dir = diropen("/");
        if( dir == NULL )
        { 
            //iprintf("Unable to open the directory.\n");
        }
        else
        { 
            i = 0;
            while( dirnext( dir, filename, &st ) == 0 )
            {  
                // st.st_mode & S_IFDIR indicates a directory  
                //sprintf( vcBuffer, "%s: %s",
                //    (st.st_mode & S_IFDIR ? " DIR" : "FILE"), filename);
            }
        }
  9.     dirclose( dir );
  10.         
        // File Test
        //fp = fopen("fat1:/a.txt", "wb+");
        fp = fopen("/a.txt", "wb+");
        if( fp == NULL )
        {
            return false;
        }
  11.     // 읽기 테스트
        if( fread( vcBuffer, 1, sizeof( vcBuffer), fp ) != 0 )
        {
            // TODO Something...
        }
           
        if( fwrite( "testfile", 1, 8, fp ) <= 0 )
        {
            fclose( fp );
            return false;
        }
       
        fclose( fp );
        return true;
    }

 위에서 보는 것과 같이 파일 관련 함수인 fopen/fread/fwrite/fclose 함수와 디렉토리 관련 함수인 diropen/dirnext/dirclose를 그대로 사용할 수 있음을 알 수 있다.

 실행 결과 나온 프로그램을 NDS에서 실행하면 그냥 하얀 화면만 뜰 것이다. 그대로 종료한 후 다시 디스크를 열어보면 a.txt 파일이 생기고 그 안에 "testfile"이라는 내용이 들어있음을 확인 할 수 있다. 자세한 소스는 첨부 파일로 추가했다.

 만약 실행했는데, 정상적으로 파일이 생성되지 않았다면 라이브러리가 정상적으로 생성되지 않았기 때문이므로 위의 순서대로 다시 빌드해서 사용하도록 하자.

 

3. 첨부

 

 

 

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

00 NDS 홈브루 프로젝트 생성

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

 

들어가기 전에...

 

1.필수 파일

 NDS 홈브루 프로젝트를 만들고 NDS 롬 파일을 생성하기위해서는 최소 2개의 파일과 1개의 폴더가 필요하다.

 필요한 파일은 Makefile과 Main.cpp 이고 필요한 폴더는 Source 폴더이다. 폴더 이름을 Source 말고 다른 것으로 하면 컴파일이 안된다. 그 이유는 makefile에 포함된 아래와 같은 내용 때문이다.

  1. ......
  2. TARGET  := $(shell basename $(CURDIR))
    BUILD  := build
    SOURCES  := gfx source data 
    INCLUDES := include build
  3. ......
  4. CFILES  := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
    CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
    SFILES  := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
    BINFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.bin)))
  5. ......

 위에서 보는 것과 같이 gfx, source, data 폴더 아래에 있는 전체 폴더를 돌면서 폴더가 아닌 *.c *.cpp *.s *.bin 파일을 찾도록 되어있기 때문이다. makefile에 대한 자세한 내용은 00 NDS makefile 분석 파일을 참조하도록 하자.

 

 만약 NDS 홈브루 개발을 위한 최소한의 파일과 폴더만을 이용해서 구성한다면 아래와 같을 것이다.

최소프로젝트1.PNG    최소프로젝트2.PNG

<홈브루 생성을 위한 최소한의 파일 및 폴더>

 

2.main.cpp 및 기타 파일 구성

  main.cpp에 있는 내용은 examples 폴더에있는 Helloworld 예제로 복사해넣었다. 만약 다수의 프로젝트 파일이 있다면 위에서 언급한 폴더에 넣거나 그 하위 폴더를 생성해서 넣으면 된다.

  1. /*---------------------------------------------------------------------------------
  2.  $Id: main.cpp,v 1.7 2006/06/18 21:32:41 wntrmute Exp $
  3.  Simple console print demo
     -- dovoto
  4.  $Log: main.cpp,v $
     Revision 1.7  2006/06/18 21:32:41  wntrmute
     tidy up examples
     
     Revision 1.6  2005/09/16 12:20:32  wntrmute
     corrected iprintfs
     
     Revision 1.5  2005/09/12 18:32:38  wntrmute
     removed *printAt replaced with ansi escape sequences
     
     Revision 1.4  2005/09/05 00:32:19  wntrmute
     removed references to IPC struct
     replaced with API functions
     
     Revision 1.3  2005/08/31 03:02:39  wntrmute
     updated for new stdio support
     
     Revision 1.2  2005/08/03 06:36:30  wntrmute
     added logging
     added display of pixel co-ords
  5. ---------------------------------------------------------------------------------*/
    #include <nds.h>
  6. #include <stdio.h>
  7. volatile int frame = 0;
  8. //---------------------------------------------------------------------------------
    void Vblank() {
    //---------------------------------------------------------------------------------
     frame++;
    }
     
    //---------------------------------------------------------------------------------
    int main(void) {
    //---------------------------------------------------------------------------------
     touchPosition touchXY;
  9.  irqInit();
     irqSet(IRQ_VBLANK, Vblank);
     irqEnable(IRQ_VBLANK);
     videoSetMode(0); //not using the main screen
     videoSetModeSub(MODE_0_2D | DISPLAY_BG0_ACTIVE); //sub bg 0 will be used to print text
     vramSetBankC(VRAM_C_SUB_BG);
  10.  SUB_BG0_CR = BG_MAP_BASE(31);
     
     BG_PALETTE_SUB[255] = RGB15(31,31,31); //by default font will be rendered with color 255
     
     //consoleInit() is a lot more flexible but this gets you up and running quick
     consoleInitDefault((u16*)SCREEN_BASE_BLOCK_SUB(31), (u16*)CHAR_BASE_BLOCK_SUB(0), 16);
  11.  iprintf("      Hello DS dev'rs\n");
     iprintf("     www.devkitpro.org\n");
     iprintf("   www.drunkencoders.com");
  12.  while(1) {
     
      swiWaitForVBlank();
      touchXY=touchReadXY();
  13.   // print at using ansi escape sequence \x1b[line;columnH
      iprintf("\x1b[10;0HFrame = %d",frame);
      iprintf("\x1b[16;0HTouch x = %04X, %04X\n", touchXY.x, touchXY.px);
      iprintf("Touch y = %04X, %04X\n", touchXY.y, touchXY.py);  
     
     }
  14.  return 0;
    }

 

 

3.실행

 00 NDS 개발 킷(Devkit Pro) 설치 에 나와있는 컴파일 및 링크 방법을 이용해서 NDS 롬파일을 만들고 iDeaS에서 실행한 결과이다.

최소프로젝트3.PNG

 

4.마치며...

 makefile이 상당히 편리하게 되어있는 관계로 그리 어렵지않게 새 프로젝트를 생성할 수 있었다. 이제 홈브루의 세계로 빠져보자. @0@)/~

 

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

00 NDS 개발 킷(Devkit Pro) 설치

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

 

들어가기 전에...

 

0. 시작하면서...

 프로그램 개발을 위해서 개발 툴 킷의 설치는 필수이다. 개발 툴 킷이 얼마나 잘 만들어져 있느냐에 따라서 프로그램의 질까지 바뀌어 질 수 있다. NDS 또는 NDSL 개발을 위해 개발 툴 킷의 설치가 필수인데, Devkit Pro라는 툴 킷이 대표적이고 거의 유일하다.

 Devkit Pro는 http://www.devkitpro.org 에 가면 받을 수 있으며 NDS은 ARM9과 ARM7을 가지고 있으므로 DevkitARM을 받아야 한다. Devkit Pro는 PSP용 개발 툴인 Devkit PSP도 가지고 있으니 관심이 있으면 참고하자.

 

1.업데이트 파일 다운로드


http://sourceforge.net/project/showfiles.php?group_id=114505로 이동하면 Devkit Pro의 ARM Download 사이트로 이동하면데 현재( 2007/08/08 19:50:45 )까지 최신 릴리즈는 Release 20(2007/01/29) 버전이다. 다운로드하기 위해 클릭하면 소스 포지(Sourceforge)로 이동하는데, 여기서 자동 업데이트 설치 파일을 다운로드 하자.

 Devkit1.PNG

<다운로드 할 파일>

 만약 설치파일을 정상적으로 다운로드 할 수 없다면 02 NDS 에서 다운 받도록 하자.

 

2.업데이트 실행

 다운을 받고 나면 설치 과정을 거쳐야 한다. 다운로드한 DevkitPro Updater 파일을 적당한 폴더를 생성해서 넣고 실행하자. 그럼 아래와 같은 화면이 표시된다.

Devkit2.PNG

<업데이트 화면>

 위의 화면이 표시되면 Next 버튼을 눌러 파일을 다운 받자. 아래는 다운로드가 진행중이고 완료된 후 완전히 설치가 끝난 화면이다.

Devkit3.PNG

<다운로드 진행중>

Devkit4.PNG

<설치 완료>

 만약 설치파일을 정상적으로 다운로드 할 수 없다면 02 NDS 에서 다운 받도록 하자. d:\ndsl_develop 폴더에 설치를 했으니 해당 폴더로 이동하면 설치된 결과를 확인할 수 있다.

 

3.에뮬레이터(Emulator 설치)

 개발된 홈브루를 NDS에 직접 옮겨서 테스트하는 방법도 나쁘진 않지만, 파일을 옮기고 NDS를 재부팅하는 과정이 불편하다. NDS 에뮬레이터 프로그램을 이용하면 이러한 과정을 줄일 수 있으며 편리하게 개발할 수 있다.

 에뮬레이터 프로그램은 몇가지가 있는데, 그중 2가지 정도만 설치하면 테스트하는데 큰 문제는 없다.

 위의 2가지를 받아서 압축을 풀면 된다. 홈브루를 테스트하기위해서는 반복해서 실행해야 하므로 실행하기 편리한 곳에 압축을 풀자.

 

iDeas.PNG     nogba1.PNG

<iDeaS(좌측)과 No$gba(우측) 실행화면>

 

4.테스트 프로그램 컴파일 및 링크

  개발 툴킷이 정상적으로 설치되었다면 폴더의 내용은 아래와 비슷할 것이다.

nds폴더.PNG

<NDS 개발 폴더>

 그럼 이제 예제 프로그램을 하나 실행해 보자. examples 폴더의 하위에 보면 gba/gp32/nds 별 예제 프로그램들이 있다.

 

4.1 Programmer's Notepad2 사용

 이것을 빌드한 후에 3D 그래픽 예제를 한번 실행해 보자. nds->Graphics->Display_List_2로 이동하면 Display_List_2.pnproj 파일이 보일 것이다. 이것을 더블 클릭하면 아래와 같이 Programmers Notepad 2 프로그램이 뜬다.

nds_notepad2.PNG

 

<Programmers Notepad- Make 실행화면> 

 Tools 메뉴에서 make를 선택하거나 단축키인 Alt+1을 누르면 make를 실행할 수 있다. 위의 화면에서 아래쪽에 output을 보면 make가 정상적으로 실행된 것을 알 수 있는데, 다시 폴더로 가보면 Display_List_2.nds/.elf/.arm9 파일이 생긴 것을 확인할 수 있다.

 이것을 no$gba에 넣고 실행하면 아래와 같은 많이 보던 화면이 뜬다.

nogba1.PNG nogba2.PNG

<Display_List_2.nds 실행화면>

 바로 DirectX의 기본 프로젝트다. @0@)/~~ 이것을 iDeas에서도 돌릴 수 있지만 3D 가속이 느려서 굉장히 끊어진다. 예제가 많으니 하나하나 실행해서 돌려보자.

 각 에뮬레이터마다 특색이 있어서 둘다 실행해 봐야 할 것이다.

 

4.2 콘솔(console) 또는 다른 IDE 사용

 콘솔(cmd.exe)을 띄워서 해당 소스 폴더로 이동한 후, make를 입력하거나 IDE의 명령실행 창에서 make를 입력하면 컴파일 및 링크를 실행할 수 있다.

 make.PNG

 

 

5.마치면서...

 지금까지 NDS 개발 툴 킷인 Devkit Pro를 설치하고 예제 프로그램을 컴파일/링크하여 실행하는 과정을 알아보았다. NDS가 없어도 에뮬레이터가 있기 때문에 마음만 먹으면 누구나 할 수 있다. 우리 모두 NDS 홈브루의 세계로 빠져보자. @ㅁ@)/~~

 

6.첨부

 

 

 

 

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

00 NDS makefile 및 NDS 파일 생성 과정 분석

원본 :  http://kkamagui.springnote.com/pages/415876

 

들어가기 전에...

 

0,시작하면서...

 새로운 프로젝트를 생성하고 소스파일을 생성한 후 컴파일 및 링크를 거치면 *.nds 파일이 생기게 된다. C/CPP 파일을 이용해서 어떻게 NDS 롬 파일 형태를 만드는 걸까?

 소스 파일에서 NDS 파일이 만들어지기까지 아래의 단계를 거치게 된다.

  1. c/cpp 파일을 컴파일 하여 .o 파일 생성
  2. .o 파일을 링크하여 .elf 파일 생성
  3. .elf 파일을 objcopy 프로그램으로 binary 포맷으로 변경, .arm9/.arm7 파일 생성
  4. .arm9/.arm7 파일을 ndstool.exe 프로그램으로 함께 묶어 .nds 파일 생성

  위의 과정을 확실히 알아보기 위해서는 소스가 컴파일 및 링크되는 규칙이 담긴 makefile을 분석해야 한다.

 

1.makefile 분석

 makefile은 아래와 같은 내용으로 되어있다

  1. #---------------------------------------------------------------------------------
    .SUFFIXES:
    #---------------------------------------------------------------------------------
  2. ifeq ($(strip $(DEVKITARM)),)
    $(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM)
    endif
  3. include $(DEVKITARM)/ds_rules
  4. #---------------------------------------------------------------------------------
    # TARGET is the name of the output
    # BUILD is the directory where object files & intermediate files will be placed
    # SOURCES is a list of directories containing source code
    # INCLUDES is a list of directories containing extra header files
    #---------------------------------------------------------------------------------
    TARGET  := $(shell basename $(CURDIR))
    BUILD  := build
    SOURCES  := gfx source data 
    INCLUDES := include build
  5. #---------------------------------------------------------------------------------
    # options for code generation
    #---------------------------------------------------------------------------------
    ARCH := -mthumb -mthumb-interwork
  6. # note: arm9tdmi isn't the correct CPU arch, but anything newer and LD
    # *insists* it has a FPU or VFP, and it won't take no for an answer!
    CFLAGS := -g -Wall -O2\
        -mcpu=arm9tdmi -mtune=arm9tdmi -fomit-frame-pointer\
       -ffast-math \
       $(ARCH)
  7. CFLAGS += $(INCLUDE) -DARM9
    CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
  8. ASFLAGS := -g $(ARCH)
    LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -mno-fpu -Wl,-Map,$(notdir $*.map)
  9. #---------------------------------------------------------------------------------
    # any extra libraries we wish to link with the project
    #---------------------------------------------------------------------------------
    LIBS := -lfat -lnds9   <== -lfat는 libfat를 사용하기 위함이다.
     
     
    #---------------------------------------------------------------------------------
    # list of directories containing libraries, this must be the top level containing
    # include and lib
    #---------------------------------------------------------------------------------
    LIBDIRS := $(LIBNDS)
     
    #---------------------------------------------------------------------------------
    # no real need to edit anything past this point unless you need to add additional
    # rules for different file extensions
    #---------------------------------------------------------------------------------
    ifneq ($(BUILD),$(notdir $(CURDIR)))
    #---------------------------------------------------------------------------------
     
    export OUTPUT := $(CURDIR)/$(TARGET)
     
    export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir))
    export DEPSDIR := $(CURDIR)/$(BUILD)
  10. CFILES  := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
    CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
    SFILES  := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
    BINFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.bin)))
     
    #---------------------------------------------------------------------------------
    # use CXX for linking C++ projects, CC for standard C
    #---------------------------------------------------------------------------------
    ifeq ($(strip $(CPPFILES)),)
    #---------------------------------------------------------------------------------
     export LD := $(CC)
    #---------------------------------------------------------------------------------
    else
    #---------------------------------------------------------------------------------
     export LD := $(CXX)
    #---------------------------------------------------------------------------------
    endif
    #---------------------------------------------------------------------------------
  11. export OFILES := $(BINFILES:.bin=.o) \
         $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
     
    export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
         $(foreach dir,$(LIBDIRS),-I$(dir)/include) \
         $(foreach dir,$(LIBDIRS),-I$(dir)/include) \
         -I$(CURDIR)/$(BUILD)
     
    export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
     
    .PHONY: $(BUILD) clean
     
    #---------------------------------------------------------------------------------
    $(BUILD):
     @[ -d $@ ] || mkdir -p $@  <== 확실한 뜻은 모르겠으나 $(BUILD) 디렉토리가 생성되어있거나 디렉토리 만들어라 인것 같음
     @make --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
     
    #---------------------------------------------------------------------------------
    clean:
     @echo clean ...
     @rm -fr $(BUILD) $(TARGET).elf $(TARGET).nds $(TARGET).arm9 $(TARGET).ds.gba
     
     
    #---------------------------------------------------------------------------------
    else
     
    DEPENDS := $(OFILES:.o=.d)
     
    #---------------------------------------------------------------------------------
    # main targets
    #---------------------------------------------------------------------------------
    $(OUTPUT).nds :  $(OUTPUT).arm9
    $(OUTPUT).arm9 : $(OUTPUT).elf
    $(OUTPUT).elf : $(OFILES)
     
    #---------------------------------------------------------------------------------
    %.o : %.bin
    #---------------------------------------------------------------------------------
     @echo $(notdir $<)
     $(bin2o)
     
     
    -include $(DEPENDS)
     
    #---------------------------------------------------------------------------------------
    endif
    #---------------------------------------------------------------------------------------

 make에서 사용되는 괴 문자들이 많이 보이는데, 괴문자에 대한 자세한 내용은 02 간단한 Make 사용법에서 보도록하고 $@는 ':' 의 좌측을 의미하고 $<는 우측을 의미한다는 것 정도만 알아두자.

 재미있는 점은 코드가 thumb 모드로 생성된다는 것이다. thumb 모드는 32bit 명령이 16bit로 줄어든 형태로써 코드의 크기를 30% 정도 더 줄일 수 있는 장점이 있다.

 

 include하여 사용하고 있는 ds_rules 파일을 살펴보자. ds_rules 파일은 devkitARM 폴더 아래에 있고 아래와 같은 내용을 담고 있다.

  1. ifeq ($(strip $(DEVKITPRO)),)
    $(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>devkitPro)
    endif
  2. include $(DEVKITARM)/base_rules
  3. LIBNDS := $(DEVKITPRO)/libnds
  4. #---------------------------------------------------------------------------------
    %.ds.gba: %.nds
     @dsbuild $<
     @echo built ... $(notdir $@)
  5. #---------------------------------------------------------------------------------
    %.nds: %.arm9
     @ndstool -c $@ -9 $<
     @echo built ... $(notdir $@)
  6. #---------------------------------------------------------------------------------
    %.arm9: %.elf
     @$(OBJCOPY) -O binary $< $@
     @echo built ... $(notdir $@)
     
    #---------------------------------------------------------------------------------
    %.arm7: %.elf
     @$(OBJCOPY) -O binary $< $@
     @echo built ... $(notdir $@)
  7. #---------------------------------------------------------------------------------
    %.elf:
     @echo linking $(notdir $@)
     @$(LD)  $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@

 어떻게 하여 NDS 파일을 만드는지 위를 보면 확실히 알 수 있다 rule에 의해서 elf -> arm9 -> nds -> ds.gba 파일이 만들어지는 것이다. 그럼 ds_rules에서 inlcude하는 base_rules는 어떻게 구성되어있을까?

 

 아래는 base_rules 내용이다.

  1. #---------------------------------------------------------------------------------
    # path to tools - this can be deleted if you set the path in windows
    #---------------------------------------------------------------------------------
    export PATH  := $(DEVKITARM)/bin:$(PATH)
  2. #---------------------------------------------------------------------------------
    # the prefix on the compiler executables
    #---------------------------------------------------------------------------------
    PREFIX  := arm-eabi-
  3. export CC := $(PREFIX)gcc
    export CXX := $(PREFIX)g++
    export AS := $(PREFIX)as
    export AR := $(PREFIX)ar
    export OBJCOPY := $(PREFIX)objcopy
  4. #---------------------------------------------------------------------------------
    %.a:
    #---------------------------------------------------------------------------------
     @echo $(notdir $@)
     @rm -f $@
     $(AR) -rc $@ $^
  5. #---------------------------------------------------------------------------------
    %.arm.o: %.arm.cpp
     @echo $(notdir $<)
     $(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) -marm-c $< -o $@
     
    #---------------------------------------------------------------------------------
    %.arm.o: %.arm.c
     @echo $(notdir $<)
     $(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) -marm -c $< -o $@
  6. #---------------------------------------------------------------------------------
    %.iwram.o: %.iwram.cpp
     @echo $(notdir $<)
     $(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) -marm -mlong-calls -c $< -o $@
     
    #---------------------------------------------------------------------------------
    %.iwram.o: %.iwram.c
     @echo $(notdir $<)
     $(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) -marm -mlong-calls -c $< -o $@
  7. #---------------------------------------------------------------------------------
    %.itcm.o: %.itcm.cpp
     @echo $(notdir $<)
     $(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) -marm -mlong-calls -c $< -o $@
     
    #---------------------------------------------------------------------------------
    %.itcm.o: %.itcm.c
     @echo $(notdir $<)
     $(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) -marm -mlong-calls -c $< -o $@
  8. #---------------------------------------------------------------------------------
    %.o: %.cpp
     @echo $(notdir $<)
     $(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) -c $< -o $@
     
    #---------------------------------------------------------------------------------
    %.o: %.c
     @echo $(notdir $<)
     $(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) -c $< -o $@

  9. #---------------------------------------------------------------------------------
    %.o: %.s
     @echo $(notdir $<)
     $(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@
  10. #---------------------------------------------------------------------------------
    %.o: %.S
     @echo $(notdir $<)
     $(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@
  11. #---------------------------------------------------------------------------------
    # canned command sequence for binary data
    #---------------------------------------------------------------------------------
    define bin2o
     bin2s $< | $(AS) $(ARCH) -o $(@)
     echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(<F) | tr . _)`.h
     echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(<F) | tr . _)`.h
     echo "extern const u32" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(<F) | tr . _)`.h
    endef

 각 파일의 확장자에 따라서 어떻게 빌드해야 할지에 대한 구체적인 내용이 나와있다. 그럼 이 make 파일을 이용하여 build를 하면 컴파일 되고 적당히 링크되어 1차적으로 elf 파일이 생성될 것이다.

 컴파일해서 나온 .o 파일은 별다른 의문이 없지만 링크되어 나온 elf 파일은 조금 생각해 봐야 한다. objcopy로 elf 파일을 binary 형태로 변화시키는데, 그렇다면 elf 파일도 NDS 롬 파일 형태에 맞도록 뭔가 달라져야 할 것이 아닌가? 링크는 LDFLAG 옵션에 들어있는 ds_arm9.specs 을 참고하도록 되어있으니 Linker 쪽을 살펴보자.

 

2. 링커 스크립트(Linker Script) 분석

 ds_arm9.specs 파일은 devkitARM\arm-eabi\lib 폴더 아래에서 살펴볼 수 있다.

  1. %rename link                old_link
    %rename link_gcc_c_sequence old_gcc_c_sequence
  2. *link_gcc_c_sequence:
    %(old_gcc_c_sequence) --start-group -lsysbase -lc --end-group
  3. *link:
    %(old_link) -T ds_arm9.ld%s
  4. *startfile:
    ds_arm9_crt0%O%s crti%O%s crtbegin%O%s

 구체적인 내용은 확실히 모르겠지만 일단 ds_arm9.ld 파일을 참조하고 ds_arm9_crt, crti, crtbegin 으로 시작하는 파일과 관계가 있음을 유추할 수 있다. ds_arm9.ld 파일을 한번 살펴보면 아래와 같다.

  1. OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
    OUTPUT_ARCH(arm)
    ENTRY(_start)
  2. MEMORY {
  3.  rom : ORIGIN = 0x08000000, LENGTH = 32M
     ewram : ORIGIN = 0x02000000, LENGTH = 4M - 4k
     dtcm : ORIGIN = 0x0b000000, LENGTH = 16K
     itcm : ORIGIN = 0x01000000, LENGTH = 32K
    }
  4. __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;
  5. __sp_svc = __dtcm_top - 0x100;
    __sp_irq = __sp_svc - 0x100;
    __sp_usr = __sp_irq - 0x100;
  6. SECTIONS
    {
     .init :
     {
      __text_start = . ;
      KEEP (*(.init))
      . = ALIGN(4);  /* REQUIRED. LD is flaky without it. */
      } >ewram = 0xff
  7.  .plt : { *(.plt) } >ewram = 0xff
  8.  .text :   /* ALIGN (4): */
     {
      *(EXCLUDE_FILE (*.itcm*) .text)
  9.   *(.text.*)
      *(.stub)
      /* .gnu.warning sections are handled specially by elf32.em.  */
      *(.gnu.warning)
      *(.gnu.linkonce.t*)
      *(.glue_7)
      *(.glue_7t)
      . = ALIGN(4);  /* REQUIRED. LD is flaky without it. */
     } >ewram = 0xff
  10.  .fini           :
     {
      KEEP (*(.fini))
     } >ewram =0xff
  11.  __text_end = . ;
  12.  .rodata :
     {
      *(.rodata)
      *all.rodata*(*)
      *(.roda)
      *(.rodata.*)
      *(.gnu.linkonce.r*)
      SORT(CONSTRUCTORS)
      . = ALIGN(4);   /* REQUIRED. LD is flaky without it. */
     } >ewram = 0xff
  13.   .ARM.extab   : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >ewram
       __exidx_start = .;
      .ARM.exidx   : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } >ewram
       __exidx_end = .;
      /* Ensure the __preinit_array_start label is properly aligned.  We
         could instead move the label definition inside the section, but
         the linker would then create the section even if it turns out to
         be empty, which isn't pretty.  */
      . = ALIGN(32 / 8);
      PROVIDE (__preinit_array_start = .);
      .preinit_array     : { KEEP (*(.preinit_array)) } >ewram = 0xff
      PROVIDE (__preinit_array_end = .);
      PROVIDE (__init_array_start = .);
      .init_array     : { KEEP (*(.init_array)) } >ewram = 0xff
      PROVIDE (__init_array_end = .);
      PROVIDE (__fini_array_start = .);
      .fini_array     : { KEEP (*(.fini_array)) } >ewram = 0xff
      PROVIDE (__fini_array_end = .);
  14.  .ctors :
     {
     /* gcc uses crtbegin.o to find the start of the constructors, so
      we make sure it is first.  Because this is a wildcard, it
      doesn't matter if the user does not actually link against
      crtbegin.o; the linker won't look for a file to match a
      wildcard.  The wildcard also means that it doesn't matter which
      directory crtbegin.o is in.  */
      KEEP (*crtbegin.o(.ctors))
      KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
      KEEP (*(SORT(.ctors.*)))
      KEEP (*(.ctors))
      . = ALIGN(4);   /* REQUIRED. LD is flaky without it. */
     } >ewram = 0xff
  15.  .dtors :
     {
      KEEP (*crtbegin.o(.dtors))
      KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
      KEEP (*(SORT(.dtors.*)))
      KEEP (*(.dtors))
      . = ALIGN(4);   /* REQUIRED. LD is flaky without it. */
     } >ewram = 0xff
  16.  .eh_frame :
     {
      KEEP (*(.eh_frame))
      . = ALIGN(4);   /* REQUIRED. LD is flaky without it. */
     } >ewram = 0xff
  17.  .gcc_except_table :
     {
      *(.gcc_except_table)
      . = ALIGN(4);   /* REQUIRED. LD is flaky without it. */
     } >ewram = 0xff
     .jcr            : { KEEP (*(.jcr)) } >ewram = 0
     .got            : { *(.got.plt) *(.got) *(.rel.got) } >ewram = 0
  18.  .ewram ALIGN(4) :
     {
      __ewram_start = ABSOLUTE(.) ;
      *(.ewram)
      *ewram.*(.text)
      . = ALIGN(4);   /* REQUIRED. LD is flaky without it. */
     } >ewram = 0xff

  19.  .data ALIGN(4) :
     {
      __data_start = ABSOLUTE(.);
      *(.data)
      *(.data.*)
      *(.gnu.linkonce.d*)
      CONSTRUCTORS
      . = ALIGN(4);
      __data_end = ABSOLUTE(.) ;
     } >ewram = 0xff

  20.  __dtcm_lma = . ;
  21.  .dtcm __dtcm_start : AT (__dtcm_lma)
     {
      *(.dtcm)
      *(.dtcm.*)
      . = ALIGN(4);
      __dtcm_end = ABSOLUTE(.);
     } >dtcm = 0xff

  22.  __itcm_lma = __dtcm_lma + SIZEOF(.dtcm);
  23.  .itcm __itcm_start : AT (__itcm_lma)
     {
      *(.itcm)
      *itcm.*(.text)
      . = ALIGN(4);
      __itcm_end = ABSOLUTE(.);
     } >itcm = 0xff
     
     .sbss __dtcm_end :
     {
      __sbss_start = ABSOLUTE(.);
      __sbss_start__ = ABSOLUTE(.);
      *(.sbss)
      . = ALIGN(4);    /* REQUIRED. LD is flaky without it. */
      __sbss_end = ABSOLUTE(.);
     } >dtcm

  24.  __bss_lma = __itcm_lma + SIZEOF(.itcm) ;
     __appended_data = __itcm_lma + SIZEOF(.itcm) ;
     .bss __bss_lma : AT (__bss_lma)
     {
      __bss_start = ABSOLUTE(.);
      __bss_start__ = ABSOLUTE(.);
      *(.dynbss)
      *(.gnu.linkonce.b*)
      *(.bss*)
      *(COMMON)
      . = ALIGN(4);    /* REQUIRED. LD is flaky without it. */
      __bss_end = ABSOLUTE(.) ;
      __bss_end__ = __bss_end ;
     } >ewram

  25.  _end = . ;
     __end__ = . ;
     PROVIDE (end = _end);
  26.  /* Stabs debugging sections.  */
     .stab 0 : { *(.stab) }
     .stabstr 0 : { *(.stabstr) }
     .stab.excl 0 : { *(.stab.excl) }
     .stab.exclstr 0 : { *(.stab.exclstr) }
     .stab.index 0 : { *(.stab.index) }
     .stab.indexstr 0 : { *(.stab.indexstr) }
     .comment 0 : { *(.comment) }
     /* DWARF debug sections.
      Symbols in the DWARF debugging sections are relative to the beginning
      of the section so we begin them at 0.  */
     /* DWARF 1 */
     .debug          0 : { *(.debug) }
     .line           0 : { *(.line) }
     /* GNU DWARF 1 extensions */
     .debug_srcinfo  0 : { *(.debug_srcinfo) }
     .debug_sfnames  0 : { *(.debug_sfnames) }
     /* DWARF 1.1 and DWARF 2 */
     .debug_aranges  0 : { *(.debug_aranges) }
     .debug_pubnames 0 : { *(.debug_pubnames) }
     /* DWARF 2 */
     .debug_info     0 : { *(.debug_info) }
     .debug_abbrev   0 : { *(.debug_abbrev) }
     .debug_line     0 : { *(.debug_line) }
     .debug_frame    0 : { *(.debug_frame) }
     .debug_str      0 : { *(.debug_str) }
     .debug_loc      0 : { *(.debug_loc) }
     .debug_macinfo  0 : { *(.debug_macinfo) }
     /* SGI/MIPS DWARF 2 extensions */
     .debug_weaknames 0 : { *(.debug_weaknames) }
     .debug_funcnames 0 : { *(.debug_funcnames) }
     .debug_typenames 0 : { *(.debug_typenames) }
     .debug_varnames  0 : { *(.debug_varnames) }
     .stack 0x80000 : { _stack = .; *(.stack) }
     /* These must appear regardless of  .  */
    }

 위에서 보면 링크에 관련된 온갖 정보들이 다 들어있음을 알 수 있다. 주의깊게 볼 부분은 파란색 부분인데, 이 부분의 주소값들이 다 NDS의 메모리맵과 관계가 있다. 링크 스크립트에 대한 내용은 06 링커 스크립트(Linker Scrpit) 페이지를 참조하도록 하고 위에서 계속 반복해서 나오는 섹션에 대한 정보만 간단히 알아보자.

  1.  .text :   /* ALIGN (4): */
     {
      *(EXCLUDE_FILE (*.itcm*) .text)
  2.   *(.text.*)
  3.   *(.stub)
      /* .gnu.warning sections are handled specially by elf32.em.  */
      *(.gnu.warning)
      *(.gnu.linkonce.t*)
      *(.glue_7)
      *(.glue_7t)
      . = ALIGN(4);  /* REQUIRED. LD is flaky without it. */
     } >ewram = 0xff

  >ewram = 0xff 의 뜻은 .text 영역의 섹션을 위에서 정의했던 ewram 영역에 밀어넣되, .text 섹션의 값은 0xff 값으로 초기화 하라는 것이다. 즉 ewram 영역에 .text 영역이 생길꺼고 그 영역은 0xff로 체워진다. sbss 및 bss 영역 역시 ewram에 위치하는 걸 봐서 데이터도 모두 ewram 영역에 위치하므로, 너무 큰 배열이나 값들의 연속은 메모리를 지나치게 사용할 우려가 있으므로 주의해야 한다. 

 위에서 보면 모든 많은 섹션들이 >ewram으로 표시된 것을 보아 거의 ewram 영역인 메인 메모리에 올라감을 알 수 있다. 그렇다면 ROM과 같은 GBA 카드리지 영역은 안쓰는 걸까? 현재( 2007/08/16 21:59:18 )까지 분석된 결과로는 거의 안쓰이는 것 같다.

 

3.map 파일 분석

 위에서 보았던 링크 스크립트 파일을 이용하여 링커가 elf 파일을 생성하게 된다. 이때 생성된 elf 파일에 구체적인 데이터는 map 파일을 열어보면 알 수 있다. 아래는 map 파일의 내용을 간단히 추린 것이다.

  1.  Memory Configuration
  2. Name             Origin             Length             Attributes
    rom              0x08000000         0x02000000
    ewram            0x02000000         0x003ff000
    dtcm             0x0b000000         0x00004000
    itcm             0x01000000         0x00008000
    *default*        0x00000000         0xffffffff
  3. Linker script and memory map
  4.                 0x01000000                __itcm_start = 0x1000000
                    0x023ff000                __ewram_end = 0x23ff000
                    0x023ff000                __eheap_end = 0x23ff000
                    0x0b000000                __dtcm_start = 0xb000000
                    0x0b004000                __dtcm_top = 0xb004000
                    0x0b003ff8                __irq_flags = (__dtcm_top - 0x8)
                    0x0b003ffc                __irq_vector = (__dtcm_top - 0x4)
                    0x0b003f00                __sp_svc = (__dtcm_top - 0x100)
                    0x0b003e00                __sp_irq = (__sp_svc - 0x100)
                    0x0b003d00                __sp_usr = (__sp_irq - 0x100)
  5. .init           0x02000000      0x22c
                    0x02000000                __text_start = .
     *(.init)
    .init          0x02000000      0x220 d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/../../../../arm-eabi/lib/thumb/ds_arm9_crt0.o
                    0x02000000                _start
     .init          0x02000220        0x4 d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/thumb/crti.o
                    0x02000220                _init
     .init          0x02000224        0x8 d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/thumb/crtn.o
                    0x0200022c                . = ALIGN (0x4)
  6. .plt
     *(.plt)
  7. .text           0x02000240    0x1cae0
     *(EXCLUDE_FILE(*.itcm*) .text)
     .text          0x02000240        0x0 d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/../../../../arm-eabi/lib/thumb/ds_arm9_crt0.o
     .text          0x02000240        0x0 d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/thumb/crti.o
     .text          0x02000240       0x70 d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/thumb/crtbegin.o
     .text          0x020002b0      0x178 2DRaster.o
                    0x020002b0                DrawLine(void*, int, int, int, int, unsigned short)
                    0x0200041c                DrawPixel(void*, int, int, unsigned short)
                    0x02000370                DrawBox(void*, int, int, int, int, unsigned short, bool)
     .text          0x02000428      0x124 BootStub.o
                    0x020004d4                InitVideoMode()
                    0x02000428                SetIrq()
                    0x02000474                IrqHandler()
  8. ........................
  9. .data           0x0201ff64    0x138e0
                    0x0201ff64                __data_start = <code 342> (.)
     *(.data)
     .data          0x0201ff64        0x0 d:/ndsl_develo
  10.  .data          0x0201ff64        0x4 d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/thumb/crtbegin.o
                    0x0201ff64                __dso_handle
     .data          0x0201ff68        0x0 2DRaster.o
     .data          0x0201ff68        0x0 BootStub.o
     .data          0x0201ff68        0x0 DC.o
     .data          0x0201ff68        0x0 DrawingWindow.o
     .data          0x0201ff68        0x0 FileManager.o
     .data          0x0201ff68    0x12c20 Font.o
                    0x0201ff68                g_vusHanFont
  11.  ........................
  12.  .bss            0x020339d4    0x18e68 load address 0x020339d4
                    0x020339d4                __bss_start = <code 342> (.)
                    0x020339d4                __bss_start__ = <code 342> (.)
     *(.dynbss)
     *(.gnu.linkonce.b*)
     *(.bss*)
     .bss           0x020339d4        0x0 d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/../../../../arm-eabi/lib/thumb/ds_arm9_crt0.o
     .bss           0x020339d4        0x0 d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/thumb/crti.o
     .bss           0x020339d4       0x1c d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/thumb/crtbegin.o
     .bss           0x020339f0        0x0 2DRaster.o
     .bss           0x020339f0        0x3 BootStub.o
                    0x020339f2                g_ucIPCCount
                    0x020339f1                g_ucKeysCount
                    0x020339f0                g_ucVBlankCount
     .bss           0x020339f3        0x0 DC.o
     .bss           0x020339f3        0x0 DrawingWindow.o
     .bss           0x020339f3        0x0 FileManager.o
     .bss           0x020339f3        0x0 Font.o
     .bss           0x020339f3        0x0 InformationWindow.o
     *fill*         0x020339f3        0x1 00
     .bss           0x020339f4    0x18568 Main.o
                    0x020339f4                g_clInformationWindow
                    0x02033b60                g_clBackGroundWindow


map 파일의 내용들을 보면 위와 같은 내용을 확인할 수 있는다. 우리가 작성한 코드는 .text 영역에 있으며, 우리가 사용한 변수들은 .bss 영역에 있는 것을 확인할 수 있다. 그리고 초기화 된 데이터 같은 경우(g_vusHanFont)는 .data에 존재하는 것을 발견할 수 있다.

 

 

4.Objcopy 분석

 일단 컴파일 및 링크의 결과로 elf 파일이 생성되었음을 알았다. 그럼 objcopy를 통해 binary 파일로 만들면 어떤 일이 발생하는 걸까? objcopy에 대한 내용은 http://www.gnu.org/software/binutils/manual/html_chapter/binutils_3.html 에서 찾을 수 있다. 내용을 조금 살피다 보면 -O binary 옵션에 대한 내용을 찾을 수 있는데 아래와 같다.

objcopy can be used to generate a raw binary file by using an output target of `binary' (e.g., use `-O binary'). When objcopy generates a raw binary file, it will essentially produce a memory dump of the contents of the input object file. All symbols and relocation information will be discarded. The memory dump will start at the load address of the lowest section copied into the output file.

 각 섹션들의 덤프를 해준다는 이야기 같다. 제일 Lowest한 주소부터 그대로 섹션 덤프를 해준다는데... 확인을 해보자.

 KKAMAGUI NOTEPAD의 .elf 파일 용량은 500Kbyte이다. 하지만 .arm9 파일 용량은 207Kbyte이다. 사이즈가 상당히 줄었음을 알 수 있다. 그럼 데이터는 어떻게 구성되어있는 걸까?

 

4.1 objdump로 .elf 분석 및 .arm9 파일 분석

 elf 파일의 정보는 devkitPro를 설치하면 있는 objdump.exe 프로그램을 이용하면 볼 수 있다. 이 프로그램과 파일의 정보를 hex로 보여주는 울트라 에디트와 같은 Hex Editor를 사용해서 파일 내용을 비교해 보면 된다. 여기서 Hex Editor는 HxD를 이용하기로 한다. HxD는 http://mh-nexus.de/hxd/ 페이지에서 받을 수 있다.

 아래는 HxD로 Notepad.arm9 파일을 연 화면이다.

 

HxD1.PNG

<HxD의 .arm9 분석 화면>

  일단 대충 이런 내용이구나 정보만 알아놓고 objdump 프로그램을 이용해서 Notepad.elf 파일의 섹션 정보를 보자.

 

HxD2.PNG

<objdump의 실행화면>

 LMAVMA의 의미에 대해서 잠깐 알아보자.

  • LMA(Loaded Memory Address) :  실제 elf 파일이 메모리에 로드되는 위치. VMA와 일반적으로 동일
  • VMA(Virtual Memory Address) : 프로그램이 실행될 때 메모리의 위치. ROM 같은 경우는 메모리에 로드된 후에 실행될 위치로 코드가 이동되어야 하므로 일반적으로 다름.

 

4.2 TCM의 위치 및 메모리 로드 분석

  여기서는 ITCM 영역이 링커 스크립트에 의해 여러가지 조건 때문에 제일 앞쪽(0번 섹션)에 위치하지 못하여 어긋난 것 같다. NDS 롬 헤더에 TCM에 대한 정보 필드가 없고, objcopy 프로그램이 단순히 섹션의 내용을 VMA 순서대로 덤프를 하는데... 저 섹션들이 의미가 있을까?

 "답은 의미가 있다"  이다.

 \devkitPro\devkitARM\arm-eabi\lib 폴더에 가면 ARM9의 CRT 코드를 찾을 수 있다. ds_arm9_crt0.s 파일이 그것인데, 아래와 같은 내용을 담고 있다.

  1.  ldr r1, =__itcm_lma  @ Copy instruction tightly coupled memory (itcm section) from LMA to VMA (ROM to RAM)
     ldr r2, =__itcm_start
     ldr r4, =__itcm_end
     bl CopyMemCheck
  2.  ldr r1, =__dtcm_lma @ Copy data tightly coupled memory (dtcm section) from LMA to VMA (ROM to RAM)
     ldr r2, =__dtcm_start
     ldr r4, =__dtcm_end
     bl CopyMemCheck

 crt0 코드는 링크될때 가장 앞부분에 위치하는 코드로써 홈브루의 초기화 및 기타 작업을 담당하는 것 같다. 위에서 보면 __itcm 같은 변수를 사용하는 것을 알 수 있는데, 이 값들은 링크 스크립트 파일(ds_arm9.ld)을 보면 잘 정의되어있다. 그러므로 binary로 생성될때는 정확한 위치에 있지 않지만, 실행하면서 해당 위치에 옮겨지기때문에 가능하다.

 

4.3 .elf의 Hex 데이터 분석

그럼 이제 첫번째 섹션 .init의 시작위치를 알았으므로 이제 HxD 프로그램을 이용해서 .elf 파일의 0x8000 영역으로 옮겨보자.

HxD3.PNG

 위의 .arm9 파일과 동일함을 알 수 있다.

 다른 섹션들도 따라가보면 똑같음을 볼 수 있고, 이로서 objcopy의 -O binary 옵션이 하는 일은 섹션의 VMA 순서에 따라서 첫번째 VMA 주소를 시작으로 메모리 내용을 덤프하는 것과 같음을 알 수 있다.

 .arm9파일의 내용은 단순한 코드와 데이터의 집합이다. 

 

5. NDS Tool 분석

 이제 마지막 단계만 남았다. .arm9 파일을 이용해서 .nds 파일을 생성하는 단계이다. NDS 파일의 헤더 정보에 대해서는 05 NDS 롬(ROM) 파일 포맷(Format) 분석를 살펴보자. 해당 페이지의 결과를 보면 단순히 헤더를 붙이고 ARM9과 ARM7 코드를 붙여넣는 작업이 전부임을 알 수 있다. ㅜ_ㅜ... 어찌나 간단한지...

 참고삼아 ndstool.exe 를 이용하여 분석한 Notepad.nds 파일 정보를 붙여넣었다.

  1. D:\ndsl_develop\MyProject\Notepad-업로드용>ndstool -i Notepad.nds
    Nintendo DS rom tool 1.33 - Jan 28 2007 21:02:20
    by Rafael Vuijk, Dave Murphy,  Alexei Karpenko
    Header information:
    0x00    Game title                      .
    0x0C    Game code                       ####
    0x10    Maker code
    0x12    Unit code                       0x00
    0x13    Device type                     0x00
    0x14    Device capacity                 0x01 (2 Mbit)
    0x15    reserved 1                      000000000000000000
    0x1E    ROM version                     0x00
    0x1F    reserved 2                      0x04
    0x20    ARM9 ROM offset                 0x200
    0x24    ARM9 entry address              0x2000000
    0x28    ARM9 RAM address                0x2000000
    0x2C    ARM9 code size                  0x33C74
    0x30    ARM7 ROM offset                 0x34000
    0x34    ARM7 entry address              0x37F8000
    0x38    ARM7 RAM address                0x37F8000
    0x3C    ARM7 code size                  0x12B8
    0x40    File name table offset          0x35400
    0x44    File name table size            0x9
    0x48    FAT offset                      0x35600
    0x4C    FAT size                        0x0
    0x50    ARM9 overlay offset             0x0
    0x54    ARM9 overlay size               0x0
    0x58    ARM7 overlay offset             0x0339d4

    0x5C    ARM7 overlay size               0x0
    0x60    ROM control info 1              0x007F7FFF
    0x64    ROM control info 2              0x203F1FFF
    0x68    Icon/title offset               0x0
    0x6C    Secure area CRC                 0x0000 (-, homebrew)
    0x6E    ROM control info 3              0x051E
    0x70    ARM9 ?                          0x0
    0x74    ARM7 ?                          0x0
    0x78    Magic 1                         0x00000000
    0x7C    Magic 2                         0x00000000
    0x80    Application end offset          0x00035600
    0x84    ROM header size                 0x00000200
    0xA0    ?                               0x4D415253
    0xA4    ?                               0x3131565F
    0xA8    ?                               0x00000030
    0xAC    ?                               0x53534150
    0xB0    ?                               0x00963130
    0x15C   Logo CRC                        0x9E1A (OK)
    0x15E   Header CRC                      0xAD78 (OK)

  2. D:\ndsl_develop\MyProject\Notepad-업로드용>

  실제 .arm9 파일의 크기는 0x339D4의 크기인데, NDS Rom 파일이 생성될때 해당 롬의 크기가 0x2A0 만큼 더 큰 것을 보아 약간의 공간이 더 추가된 듯 하다. 그럼 어떤 데이터가 추가된 것을까? 아래는 .arm9 파일과 .nds 파일의 마지막 부분의 내용을 비교한 것이다.

HxD4.PNG

 

HxD5(1).PNG

<.arm9 파일(좌측)과 .nds 파일(우측) 내용>

 그림이 좀 심하게 일그러졌는데, 첨부파일의 큰 그림을 참조하도록 하고, 여기서 알 수 있는 사실은 추가된 0x2A0 공간만큼 0으로 체워져있다는 사실이다. 따라서 .arm9 바이너리 파일을 그대로 덤프한다는 사실에는 변함이 없다.

 

마치면서...

 NDS 롬(ROM) 파일 생성을 분석하는데, 자그마치 이틀이나 걸렸다. 상당히 복잡했고 이것 저것 볼 것도 많았지만, 확실히 NDS 파일에 대해서 알 수 있는 좋은 기회였다. 결국 NDS 파일은 ARM9과 ARM7 코드와 데이터를 포함하는 간단한 구조였던 것이다.

 이제 NDS는 나의 장난감이 된 것이나 다름없다. >ㅁ<)/~ 기다려라~ NDS야. 하하하하핫~~!!!

 

 

 

 

 

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

00 NDS 홈브루(Homebrew) - KKAMAGUI Notepad

원본 : http://kkamagui.springnote.com/pages/416259

 

들어가기 전에...

 

1.소개

 내가 만든 간단한 그림판 형식의 메모장 프로그램이다. 메모를 생성하고 삭제하는 기능을 가지고 있으며, 메모 보기 기능도 갖추고 있다.

 libfat를 사용하여 디스크에서 직접 데이터 파일을 사용하며 데이터 파일을 백업하여 데이터를 보존할 수 도 있다.

 사실 KKAMAGUI NOTEPAD 프로그램을 만들면서 NOTEPAD 프로그램을 개발하는 시간보다 윈도우 MFC 구조와 비슷하게 클래스를 구성하고 코딩하는 시간이 더 많이 걸렸다. UI에 전혀 소질이 없는지라... 출력물은 좀 엉망이지만... 조금만 손보면 그럴듯하게 바꿀 수도 있을듯....

 아래는 실행한 화면이다.

notepad11.PNG   NOTEPAD2.PNG   NOTEPAD1.PNG

<시작화면(좌측)과 메모를 입력하는 화면(가운데), 그리고 메모를 보는 화면(우측)>

 

 

2.프로그램 특징

 KKAMAGUI NOTEPAD는 아래와 같은 특징을 가지고 있다.

  • libfat 사용
    • libfat 파일을 수정하여 FAT 파일 시스템으로 포맷된 디스크에 접근하여 데이터 파일을 생성하고 수정함
    • 수정 방법은 01 libfat 업그레이드 부분을 참조
  • 데이터 파일 용량 최소화 
    • 화면 데이터 저장 시, 비트마스크를 이용하여 한 바이트에 8pixel 정보를 저장
    • 256 * 192 => 48Kbyte의 기존 사이즈를 6Kbyte로 줄임
  • 한글 출력
    • 수정한 한글 출력 루틴 및 자작 한글 폰트(굴림체 16x16 pixel 사용) 사용
    • 출처는 네이버 NDS 개발 까페
    • 한글 폰트 제작 및 출력에 관한 부분은 01 NDS 한글 출력 라이브러리 부분을 참조
  • Windows의 MFC 형식의 구조와 비슷한 윈도우 라이브러리 제작 및 사용
    • 자작한 클래스 사용
    • Windows manager class
    • Window base class/DC class
    • Z-Order 기능 지원
    • 환경적 제약사항(Single Task)으로 Message Loop 제거, 해당 윈도우의 Message 처리 루틴을 직접 호출하도록 변경
    • 자세한 내용은 02 NDS 윈도우 시스템 문서를 참조하자.

 

 

3.프로그램 기능

 KKAMAGUI NOTEPAD의 기능은 아래와 같다.

 

3.1 메모 입력 및 입력 취소 기능

  • 연필 및 지우개 선택 기능
    • 연필 모드로 화면 그리기 가능
    • 지우개 모드로 화면 지우기 가능
  • "지우개" 버튼 : 상단의 화면에 연필/지우개 표시
  • “확인” 버튼 : 메모 저장
  • “취소” 버튼 : 변경 사항 입력된 내용 버림

 Notepad4.PNG   ===>   Notepad6.PNG

 Notepad3.PNG   ===>  Notepad5.PNG

<연필 모드(좌측)과 지우개 모드(우측)>

 

 

3.2 메모 보기 및 삭제 기능

  • 기록된 메모 보기 : 상단 화면에 몇변째 메모인지가 표시된다.
  • “확인” 버튼 : 메뉴로 돌아감
  • “삭제” 버튼 : 메모 삭제
  • “다음/이전” 버튼 : 다음/이전 메모 보기

 

 Notepad8.PNG  ===>  Notepad10.PNG

 Notepad7.PNG  ===>  Notepad9.PNG

<첫번째 메모 화면(좌측)과 두번재 메모 화면(우측)>

 

 

3.3 특이사항

 메모장을 구현할 때 프로토타입(Prototype)을 만드는데 너무 열중하다 보니, 데이터 파일에 데이터를 저장하는 방식이 조금 이상하다.

 데이터를 저장할때 한바이트의 플래그 바이트와 나머지 6Kbyte 화면정보를 연속해서 저장하는 방식으로 동작한다. 데이터가 써지면 플래그 바이트를 0x01로 설정하고 메모가 지워지면 플래그 바이트를 0x00으로 설정하여 빈 공간임을 표시하는 것이다.

 메모의 추가 및 삭제가 여러번 반복되면 중간 중간에 플래그 Byte가 0x00 인 부분이 생기게된다. 메모가 추가되면 플래그 Byte 중에 0x00으로 설정된 제일 첫번째 것을 찾아서 해당 위치에 데이터를 추가하고 플래그 바이트를 0x01로 설정한다.

 이렇게 동작 하기 때문에 메모가 데이터 파일에 저장되는 순서하고 메모가 기록된 시간적 순서하고 일치하지 않는 문제가 있는데, 메모를 빼먹지는 않으므로(ㅡ,.ㅡ;;;) 그렇다는 것만 알고 넘어가자.

 

 

4.컴파일, 링크 그리고 실행

  Programmer's Notepad 2 프로그램을 이용할 시에는 Notepad.pnproj 파일을 열어서 컴파일 및 링크하면 되고, 콘솔(cmd.exe)을 사용하는 경우에는 make를 입력하거나 makefile.bat를 더블클릭하여 실행하면 된다. 자세한 방법은 00 NDS 개발 킷(Devkit Pro) 설치 문서를 참조하자.

 컴파일 및 링크가 정상적으로 끝나면 Notepad.nds 파일이 생성된다. 에뮬레이터나 디스크에 넣어서 직접 NDS에서 실행하면 된다.

 

 

5.첨부

 

 

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

참고. NDS 동영상 인코딩(BatchDpg)

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


들어가기 전에...

0.시작하면서...

 NDS로 동영상을 보려면 문쉘과 DPG 파일 포맷으로 인코딩해주는 툴이 필요하다. 물론 NDS의 사양이 그렇게 좋지 않기 때문에 인코딩시에 질을 많이 떨어뜨려야 하는 문제가 있지만... 나름 볼만하므로 통근시간이 길거나 학교까지 거리가 먼 사람들은 인코딩을 시도해 볼만하다.


 DPG 파일 포맷은 간단한 헤더와 MP2 Audio 그리고 MPEG1 Video 파일로 구성된다. 쉽게 이야기하면 오디오 파일 따로 비디오 파일 따로 생성하여 그냥 묶어놓은 것 뿐이다. 프로그램에 능한 사람이라면 인코딩 라이브러리를 사용해서 인코딩하여 DPG 파일을 생성하는 것도 가능할 것이다. 일반인들은 인코딩 프로그램을 사용하면 되는데, 많이 쓰는 인코딩 툴은 BatchDPG가 있다.

 BatchDPG를 이용해서 인코딩 하는 방법을 알아보도록 하자.


1.BatchDPG 설치

 BatchDPG 파일은 구글에서 검색하면 많이 나오는데, 거의 영문판이다. 다행이도 한글화시켜놓은 프로그램이 있으니 BatchDPG 1.2K 버전이다. 현재( 2007/09/23 20:16:34 )까지 1.3 Beta 버전까지 나와있는데, 구버전을 쓰기가 좀 그렇다고 느끼는 사람은  BatchDPG12KNew.zip 를 받으면 된다. 1.2K 버전에 1.3 Beta 버전의 Libary를 덮어씌운 버전이다.


 BatchDPG가 정상적으로 동작하려면 추가적으로 아래와 같은 것들이 더 필요하다.


2.BatchDPG 사용

 위의 두 프로그램을 다운받아 설치한 후 BatchDPG를 실행하면 아래와 같은 화면이 표시된다.

 화면.PNG


 사용방법은 아래와 같다.

  1. 동영상과 자막을 설정하고 맨 아래에 추가 버튼을 이용하여 작업을 추가한다.
  2. 작업할 동영상을 계속 반복하여 추가한다.
  3. 작업 추가가 끝났으면 맨 아래에 실행 버튼을 클릭하여 인코딩 작업을 수행한다.

 작업이 완료되면 해당 폴더에 DPG 파일이 생성된다.


3.마치면서...

 간단히 DPG로 인코딩하는 방법을 알아보았다. 이제 DPG의 세계로 빠져보자. @0@)/~~!!



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

참고. 3 in 1 Expansion Pack 사용법

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

 

들어가기 전에...

 

0.시작하면서...

 3 in 1 Expansion Pack은 NDS의 기능을 확장해주는 확장 팩 정도로 생각하면 되고 Slot 2 (GBA 팩을 꼽는 부분)에 삽입하도록 되어있다. Expansion Pack에 대한 자세한 내용은 http://wiki.gbatemp.net/index.php?title=3_in_1_Expansion_Pack_for_EZ-Flash_V 에서 참고할 수 있다.

 

사진.jpg

<Expansion Pack>

 

1.주요기능 

 Expansion Pack의 주요기능은 3가지이다. 

  • 진동 기능 
  • GBA 게임용 저장공간(NOR Flash) 
  • RAM 확장(16MByte). DS Browser에서 사용 가능

 

2.하드웨어 구성 

  • 256 Mb (32 MB) of NOR Flash Memory. This type of memory is able to retain data without requiring uninterrupted power. Depending on data size, writing may take up to several minutes. One typical use includes copying over a GBA ROM, thereby allowing the 3-in-1 to act like a real GBA cartridge.
  • 128 Mb (16 MB) of PSRAM. This memory does not retain data when the power is turned off. However it can be written at speeds much faster than NOR memory.
  • 4 Mb (512 KB) of battery backed SRAM for save data. The battery is necessary to retain data.
  • Programmable embedded "rumble pak" (haptic feedback) device  

 

3.기능설정 방법 

 http://blog.so-net.ne.jp/Rudolph/ 에서 「3in1_ExpPack_Tool_19a」을 클릭해서 파일을 다운 받는다. 다운로드가 불가능하면 첨부파일을 참조하도록 하자. 압축을 해제하면 롬 파일과 설명 파일이 있는데 DLDI 패치를 롬 파일에 수행한 다음 실행하면된다. 화면이 표시된 후 설정은 L, R, A, B, X, Y 버튼을 이용해서 수행할 수 있다.

 아래는 Read Me 파일에 있는 내용이다. 

PSRAM Mode:

(A) Load the selected game into PSRAM and run it.

  [select] Load the selected game into PSRAM and soft reset back to the flashcart menu.
  (only works on R4/M3 Simply, DSLink and SC DS(ONE))
 
  Until the power is turned off or the 3in1 is removed from the system, the game in PSRAM
  will be used when switching to GBA mode or using DS<->GBA linkage.

(B) Backup the save for the last game run from PSRAM.

(X) Backup all 512k of SRAM to SRAM.BIN.

(Y) Restore all 512k of SRAM from SRAM.BIN.

Games up to 16 megabytes, with SRAM, EEPROM or 512kbit Flash saves can be run from PSRAM.
Games that are larger than 16 megabytes, and games that use 1024kbit Flash saves must be
flashed to NOR.


NOR Mode:

(A) Write the selected game to NOR.

(X) Start the game in NOR.

(B) Backup the save from the NOR game to a file.

(Y) Restore the save for the NOR game from the save file.


Rumble Mode:

This screen has options for enabling rumble and the web browser RAM expansion.
The options on this screen will currently only work with the R4/M3 Simply, DSLink and the SC DS(ONE).

Select an option and press (A) to enable that feature and soft reset back to the
flashcart menu. The feature you enabled should now work for any DS game or app that
supports it.


Soft reset is executed by <START> for the R4/M3 Simply, DSLink and the SC DS(ONE) on all the Mode screens.  

 

4.마치면서... 

 NDS용 기능 확장팩인 3 in 1 Expansion Pack에 대해 간단히 알아보았다. Expansion Pack을 사용하면 GBA용 게임을 할 수 있기 때문에 게임 범위가 확장되는 장점도 있지만 홈브루 개발자라면 메인 메모리를 확장할 수 있기 때문에 램 확장에 더 관심이 있을 것이다. 

 다음에는 Expansion Pack을 설정하여 확장 램으로 바로 사용할 수 있는 방법에 대해서 알아보자. 

 

5.첨부 

 

 

 

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

지난번에 발견한 문쉘의 리부팅 플러그인 (reset.mse) 소스를 뒤지다가

혹시나 실행이 되지 않으면 어쩌나 걱정스러운 마음에 소스를 좀 수정해서

적당히 만든 다음 컴파일 하니 _BOOT_MP.NDS 파일이 나왔다.

약간 걱정이 되었지만 테스트 결과는 대성공 @0@)/~~

다만 약간 문제는 구버전의 fat 라이브러리를 사용하는데,

이것이 FAT Raw 데이터를 이용하는 부분이어서 현재 쓰고있는 fat 라이브러리로는 힘들꺼 같았다.

그렇다고 중복으로 fat 라이브러리를 다 링크하자니 쓸데없이 코드가 커지는 문제가 생기고... ㅡ_ㅡa...

결국 libfat에서 FAT Raw 데이터를 뽑기로 결심했는데, 얼래...

구버전의 데이터가 거의 그대로 libfat에 살아있었다.

얼쑤~!!! @0@)/~~

이런 대박이~!!!

쫌만 손보면 간단히 라이브러리 정도는 만들 수 있겠다.

앗싸~ 좋구나~ >ㅁ<)/~!!!!
쿨럭..;;; 처음에 후배가 준 라이브러리를 그냥 사용했다가 접속이 안되는 낭패를 겪고...

혹시나 해서 새로 컴파일한 후 사용하니 나름 괜찮게 동작했다.

하지만 역시나... 약간 불안정하고 간혹 먹통이 되는 상황이 생겨서 이거 원.... ㅡ_ㅡ;;;;;

한참을 테스트 하다보니

얼래??

이번에는 AP 쪽에서 내 접속을 막았나 보다.

AP 관리자 한테 가서 다시 열어달라 그래야지... ㅜ_ㅜ

아아... 역시나 아직 동작이 좀 불안하구나... ㅜ_ㅜ...
NDS 과제를 하는 후배가 발견했던데, 지난달(9월)에 릴리즈된 버전이란다.

후배 말로는 AP 랑 접속해서 통신이 잘된다 그러던데...

정말 그렇다면 완전 감동이 아닐 수 없다. @0@)/~~

시간나면 테스트를 한번 해봐야 할듯...

앗싸~ 좋구나~ >ㅁ<)/~

dswifi-src-0.3.3.zip
요즘 MP3 Decoder에 열을 올리고 있는지라...... 오늘도 어김없이 포기 못하고 소스를 뒤지고 있었다.

Helix 라는 녀석이 자꾸 눈이가길래... 이걸로 해보자 싶어서 삽질을 계속하다가 결국 올렸다.

이제 실제로 사용해서 Play가 되는지 확인하는 길만 남았는데....

학교를 가야하는 관계로 갔다와서 해야겠다...

아놔... thumb 모드로 코드가 컴파일 되게 해놨더니만 arm 모드 명령에서 자꾸 에러가 발생하는 걸 제대로 이해 못해서... ㅜ_ㅜ...

또 하루종일 삽질했다는... ㅜ_ㅜ...

내가 미쵸.... ㅜ_ㅜ...
크아~ 오늘까지 약 4일간의 노력으로 윈도우 라이브러리 및 윈도우 프로토타입, 그리고 목표로 했던 File Explorer까지 대충 완성했다.

어제 디렉토리 네비게이션을 제대로 못해서 한참을 삽질했는데... diropen() 이란 이름으로 되어있을줄은... ㅡ_ㅡ;;;;

덕분에 libfat를 제대로 뒤져가면서 분석했다. 나름 깔끔하게 잘 만들었두만...

윈도우 라이브러리는 좀더 정리한 다음 lib 파일로 공개해야겠다.

아래는 덤으로 만든 텍스트 뷰어 클래스와 리스트 클래스를 이용한 텍스트 뷰어 윈도우와와 File Explorer 이다.

각자가 윈도우로 구성되어있다.
사용자 삽입 이미지 사용자 삽입 이미지 사용자 삽입 이미지

+ Recent posts