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 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.첨부

 

참고. 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@)/~~!!



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

지난번에 발견한 문쉘의 리부팅 플러그인 (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 모드 명령에서 자꾸 에러가 발생하는 걸 제대로 이해 못해서... ㅜ_ㅜ...

또 하루종일 삽질했다는... ㅜ_ㅜ...

내가 미쵸.... ㅜ_ㅜ...

오늘도 역시나 열심히 NDS용 그래픽 라이브러리를 손보다가 문득 지난번에 만들어 놓은 고속 복사 함수를 적용시켜보면 어떨까 하는 생각이 들어서 LDMIA, STMIA 함수를 이용해서 메모리를 전송하는 소스를 넣어봤다.

LCD의 해상도고 256 * 192 이고 한 점이 2Byte로 구성되므로 총 98304 Byte, 즉 96 KByte가 되는데 이것을 memcpy()로 복사하니 대략 7 ~ 8 ms 정도가 걸렸다.

이것을 내가 만든 함수로 교체하니 4 ms 로 줄어들었다. @0@)/~

덕분에 약간의 속도 향상이... ㅜ_ㅜ...

아아~ 아직 죽지 않았어.. ㅜ_ㅜ....

오늘 어찌하다가 집에 가지 못해서... 어제 하던 윈도우 시스템이나 마저 손볼려고 코드를 열었다.

그때가 11시쯤이었던가... 지금이 7시니까 대충 8시간쯤 삽질했네...

뭐 그래도 소득은 있으니까... ㅎㅎ

BitBlt 기능을 DC에 추가해서 윈도우에 스킨 입히는 작업과 NDS에서 사용하기위한 그래픽 포맷(Kkamagui Nds Graphic - KNG)을 대충 만들고 변환해주는 프로그램을 만들었다.

그리고 윈도우가 많아져서 깜빡임이 심해지는 걸 느끼고 내친김에 더블 버퍼링까지...

하드코어로 작업했기 때문에 속도가 그리 빠른 편은 아니지만... 그래도 얼추 쓸만은 하다.

아래는 스크린 샷...

사용자 삽입 이미지


ps) 아까 아는 형이 좀 쉬라고 하던데... 이것도 병이라고... ㅡ_ㅡ;;;

정말 그런듯... 왠지 허접한 기기만 보면 기능 확장하고 싶은 충동이... ㅜ_ㅜ...

프로그래머가 천직인가....

NDS에 쉘을 만들어보려고 고민하던중에, KKAMAGUI Notepad 프로그램을 만들면서 썼던 허접 윈도우 라이브러리를 사용하면 쉽게 개발할 수 있을 듯한 생각이 들었다.

어디까지 구현한지 가물가물해서 소스를 뒤지고 있는데...

얼래... ㅡ_ㅡ;;; 거의 구현 안되어있는 것과 마찬가지....

노트패드 기능 구현한다고 윈도우 껍데기나 겨우 만들어 놓은 상태랄까...

클립핑 처리는 전혀 안되있고.... ㅡ_ㅡ;;;

그래서 일단 클립핑부터 시작했는데, 이게 장난이 아니더라....

구글링해서 찾은 알고리즘은 제대로 동작안해서 또 삽질하고... ㅜ_ㅜ...

우의곡절끝에 겨우 0.1 버전을 완성할 수 있었다.

기능은 클립핑 처리 + 윈도우 Z Order 지원 + 터치스크린을 이용한 윈도우 이동 및 종료 등등이다.

내일은 좀더 손봐서 PNG 파일 포맷을 읽어 표시할 수 있도록 해야겠다.

사용자 삽입 이미지
NDS의 윈도우 라이브러리를 손보려고 코드를 고치다가...

얼래... 잘되던 코드가 안되는 문제를 발견했다.

한참을 디버깅하다가... 코드의 순서를 살짝 바꾸니 이상한 데이터가 덤프되는 것을 발견하고...

Optimazation 할때 스택쪽에 문제가 생기거나 Register를 잘못 사용하고 있는 것이 아닌가 추측...

옵션을 O2에서 O1으로 낮춘 다음 전부 다시 컴파일하여 실행하니....

아무 문제 없이 실행되었다 @0@)/!!!!!

이런... ㅜ_ㅜ... 내 시간 돌려도.. ㅜ_ㅜ
여자친구가 생일선물을 해준다고 하길래 PSP를 중고로 사달라고 할까 고민하다가 이미 NDS를 가지고 있는지라 쓰지 않을 것 같은 생각이 들었다.

그래서 그냥 NDS에 메모리나 더 꼽을까 하고 G 마켓을 뒤졌는데...

어라? 왠 확장팩이 나왔다. 3 in 1 Expansion Pack for EZ-Flash라는 물건인데, 진동 + 메모리 확장 + GBA 팩 기능까지 하는 모양이다.

좀더 확실히 알기위해서 구글 형께 부탁했더니 아래와 같은 사이트를 보여줬다.

http://wiki.gbatemp.net/index.php?title=3_in_1_Expansion_Pack_for_EZ-Flash_V

사용자 삽입 이미지

뭐 진동이나 GBA를 할 수 있다는 그런건 별로 안땡기고... 램을 확장할 수 있는 점이 홈브루를 개발하는 나에게 가장 큰 장점인듯....

과연 얼마나 확장될까? 위의 사이트에 따르면...
  • 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

무려 16MByte @0@)/~!!!

NDS 자체의 램이 4MByte 정도니까 합이 20MByte @0@)/~!!!!!

두둥... PSP가 32MByte인 것을 감안할 때 굉장한 붐업이다...

ㅜ_ㅜ... 당장 질러서 테스트 프로그램이나 하나 만들어봐야겠다...

다들 기대하시라 ㅎㅎ

>ㅁ<)/~!!!

이럴수가... 다시 재발했다.. @0@)/~

예전보다야 나아졌지만 화면 우측에 필기를 하면 튀는 현상이 생긴다.

이렇게 되면 결국 Z 축(누르는 압력)을 이용해서 계산하는 수 밖에 없는데....

결국 쓰긴 써야하나....


터치 스크린에 어중간한 압력이 들어갔을 때, 엄한 값이 나오는듯 한데...

세게 눌러서 필기를 하면 괜찮은 것 보니....

아우... 머리야... 또 수정해야겠군....
LCD와 Graphic에 관련된 Power는 ARM 9에서도 건드릴 수 있었는데...

전체적인 System의 Power 관리(LED 포함)는 SPI를 이용해서 Power Management Device로 전송해야 한다...

아래는 SPI에 관한 내용... ARM 7을 통해서만 가능...

http://nocash.emubase.de/gbatek.htm#dsserialperipheralinterfacebusspi


아래는 Power Device에 관한 내용...
LED 색깔 바꾸는거, 깜박이는거... Shutdown 하는거 등등 가능...
http://nocash.emubase.de/gbatek.htm#dspowermanagement

시간나면 해봐야 겠다.

basicARM7 코드가 굉장히 간단하게 되어있던데...
소스를 얼핏 보니까 LCD의 HSync Interrupt를 받아서 Touch의 값을 읽도록 되어있었다.
혹시 이것때문에 LCD의 값이 튀는게 아닐까?
Touch의 Interrupt를 받아서 값을 읽으면 더 나을수도.... ㅡ_ㅡa...

일단 생각해 보자...
대단하다... 이미지 돌리기부터 해서 확대... 축소 같은 기능도 구현되어있다.. @0@)/~

이거라면 데이터 시트 같은 것도 NDS에 넣어 다니면서 볼 수 있겠던데....

크윽... 너무 좋은거 아냐.. ㅜ_ㅜ...
크아... libfat Library에 버그를 발견했다.

첫번째 버그는 파일을 열자마자 fseek()로 제일 처음으로 이동한다음, 다시 fseek()로
제일 마지막으로 옮겨간 후, write를 하면 정상적으로 write가 되지 않는 버그이고...

다른 한가지는 파일 끝에서 데이터를 쓰면 파일이 커지긴 하는데 쓰레기 데이터가
채워져서 커지는 버그이다.

일단 이 릴리즈 전버전으로 해서 다시 테스트 해보고 소스를 수정하던지...
아님 짜던지 해야겠다. ㅜ_ㅜ

아놔... 오늘도 하루를 다 날렸네.. ㅜ_ㅜ
invalid-file

파일 다운로드



흐.. 이런... libfat의 버그를 찾다 찾다 결국 그냥 포기...
(사실 귀찮아서 소스 보기를 포기... ㅡ_ㅡ )

버그를 우회하는 식으로 해결했다.
이놈이 글쎄.. fopen으로 r+ 옵션으로 열어서 파일 끝에서 데이터를 추가하면...
이상하게 변한다.... ㅡ_ㅡa.. 캐쉬 정책이랑 실제 flash write하는 기능이
제대로 동작하지 않는거 같기도 하고.... ㅜ_ㅜ...

그래서 파일 끝에서 추가할때는 a+ 옵션을 줘서 일부러 클러스터를 다 따라가게
만들고, 추가없이 파일을 수정만때는 r+ 옵션으로 열어서 바꾸도록 했다.

이렇게 하니 별 이상없이 동작하는구나... ㅜ_ㅜ...

크윽.. 젠장... 나중에 삼성과제 끝나고 나면 그걸 그대로 올려야 겠다.
역시... 내손으로 만드는게 좀더 믿음직 하네...

사용자 삽입 이미지
아우... 메모장 하나 만드는데 왜이렇게 오래 걸리는지... ㅡ_ㅡ;;;

메모장 하나 만들려고 윈도우 구조 다 세우고 Window base Class 만들고 CDC 만들고...
아주 난리다... ㅡ_ㅡa...

왜 이짓을 하는지 의문이긴한데... 어느정도 대충 가다는 나왔다는 ㅎㅎㅎ
이제 조금만 더하면 그림판 같은걸 만들 수 있겠다.

앗싸~ >ㅁ<)/~
libfat를 받아서 내 의사용 microSD io 코드를 추가한 다음
약간의 수정을 거쳐 만든 library 및 소스

테스트 결과....

이상없음~!!!

이로서 파일/폴더 관리 및 생성이 가능해졌다...

정말 대단하군.. @0@)/~

ZOrder 및 Window Frame을 구현한 간단한 윈도우 시스템을 구현했다.
정말 아주 간단한... ㅡ_ㅡa.. 그냥 윈도우 그리고 이동시켰을 때 처리 정도만
해놓은 버전...

나름대로 잘 동작해서 흐믓하다.
일단 윈도우 API와 비슷하게 구현했다.

그럭저럭 만족~ ㅎㅎ

사용자 삽입 이미지
Interrupt, Touch Screen, Button을 이용해서 간단한 메모장을 만들었다.

크으... 뭐 쉽진 않았지만... 기본적인 부분들은 어느정도 Library에서
구현 되어있었기때문에, 스펙을 보면서 구현하다가 잘 안되면 소스 뒤져서 확인하는 방식으로
진행했다.

여튼 나름 괜찮게 된다는거 ㅋㅋ
MicroSD 접근용 IO Interface 소스

출처 : http://chishm.drunkencoders.com/libfat/

libfat

A FAT library for the Nintendo GBA and DS

This is the successor to GBA NDS FAT. It features better reentrancy support, cleaner source code and is built as a proper library.

Installation

libfat is packaged as part of DevkitPro. Download the DevkitPro Updater and run it to start the installation. You will need to install as a minimum DevkitARM r20, libnds and libfat. I also suggest you install the GBA and NDS examples.

Using libfat in a project

The following assumes that you are using one of the example NDS templates.

The first step is adding libfat to the list of libraries. In the ARM9 Makefile (or the top level one if there is no specific ARM9 Makefile), look for the list of libraries. It should look like:

LIBS	:= -lnds9

Change this to:

LIBS	:= -lfat -lnds9

The order is important! Make sure that -lfat comes before -lnds9. The include directory will already be set, so you don't need to worry about this.

Next, you'll need to include fat.h in your main source file. Near the top of the file, insert the line:

#include <fat.h>

Before you can use any file functions, you'll need to initialise the library. You should only do this once within the execution of the program. Somewhere within the program's startup code, insert the line:

fatInitDefault();

This will initialise libfat and set the number of cached sectors to the default (8 on the DS, 2 on the GBA). It will also set libfat as the default stdio file device. If you prefer to use your own settings, use the line:

fatInit(cache_size, set_as_default);

In this case, cache_size is the number of sectors to store in the cache at any one time and if set_as_default is true it then libfat will be the default stdio file device.

Both fatInit and fatInitDefault return true if the library was successfully initialised and false otherwise.

That is all that's needed to get libfat running within your project.

Using files

Through the magic of DevkitARM and libfat, files can be openned like on any other POSIX system. Most of the functions in the stdio.h header should work. You do not need to worry about the cache. It will be taken care of, as long as you remember to close any open files before turning off the power.

Paths are separated by a forward slash -- / . Directories are specified by their path. The directory itself can be refered to as . and the parent as ... The root directory is simply / or /..

When libfat is not the default stdio device, you will need to refer to files and directories with a device specifier. This is done by using fat: before a path. You can also use a single digit in the device specifier, such as fat0: or fat1:. It is possible to use both slots at once on a DS. This is done by specifying the slot with a number. The full list of device specifiers is:

  • fat: -- the default device
  • fat0: -- same as fat:
  • fat1: -- the device in Slot-1 of the DS
  • fat2: -- the device in Slot-2 of the DS
  • fat3: -- a custom mounted device

To open a file called graphics.bin for reading within a directory called app_data located on the card in Slot-2, you would use:

FILE* test = fopen ("fat2:/app_data/graphics.bin", "rb");

If libfat is set as default, and you don't mind which slot the card is in, you could use:

FILE* test = fopen ("/app_data/graphics.bin", "rb");

If you are already in app_data (after a chdir("/app_data"), for example), you could use:

FILE* test = fopen ("graphics.bin", "rb");

Using directories

Directory functions do not follow POSIX standards. Custom functions are used for various reasons.

To add directory support to a project, you'll need to include <sys/dir.h>


To open a directory, use:

DIR_ITER* dp = diropen ("/directory/path/");

diropen returns NULL and sets errno on failure.


To iterate through a directory, use:

dirnext (dp, filename, &filestat);

dp is a previously openned directory, filename will be filled with the filename of the next file or directory, and filestat will be filled with the file's stats. If filestat is NULL, it won't be filled.

To start at the begining of a directory again, use:

dirreset (dp);

To close a directory, use:

dirclose (dp);

dirnext, dirreset, and dirclose all return 0 on success. If they fail for any reason, they will return -1 and set errno. dirnext will set errno to ENOENT if there are no file or directory names left in the directory.


The directory functions mkdir and chdir work as normal.

A quick file listing demo:

struct stat st;char filename[256]; // to hold a full filename and string terminator
DIR_ITER* dir;dir = diropen ("/");
if (dir == NULL) {
iprintf ("Unable to open the directory.\n");
}
else {
while (dirnext(dir, filename, &st) == 0) { // st.st_mode & S_IFDIR indicates a directory
iprintf ("%s: %s\n", (st.st_mode & S_IFDIR ? " DIR" : "FILE"),
filename);
}
}
허허... 이거 나원참....

ARM7쪽만 Touch Screen 쪽에 접근할 수 있어서 어찌 할까 고민중이었는데....
이런 대박이... ㅡ_ㅡa... libnds에서 이미 다 제공해 주는...

ARM7쪽 더미 코드를 둬서 이미 ARM9 쪽으로 IPC를 사용해서 넘기도록 되어있었다.
으으.... 그럼 일단 이건 그냥 쓰도록 하고...

ARM9쪽 코드를 우선적으로 libnds에 의존하지 않는 방향으로 수정하는게 나을듯도...
일단 그래 하던동...
반나절 삘삘 노력해서 겨우 16x16을 출력하도록 수정했다. ㅎㅎ

자모 다 넣고 완성형 한글을 넣어서 이제 잘나온다.
>ㅁ<)/~ 잇힝~ 좋구나아~

사용자 삽입 이미지
대단한걸.. @0@)/~

이거 완전 거의 Application을 짤 수 있도록 만들어놨네...

정말 대단.. @0@)/~
가에서 부터 힝까지만 있는 거였군....

계산하는 알고리즘이 0xb0a1을 0으로 하는 base를 사용하더라... ㅠ_ㅠ..

그럼 자음에 대한 폰트는 내가 만들어서 써줘야 하는건가?? ㅡ_ㅡa...

크윽... 뭔가 일이 커지는 듯한... 폰트 생성 툴이라도 만들던지 해야지 원....

앗싸~ 한글/영문 출력~~!!

아직 약간 좀 이상하긴 하지만 되긴 된다...

한 비트 때문에 삽질을 오만상 하긴 했지만.. ㅡ_ㅡa...

뭐 되긴 된다는거 ㅋㅋ


사용자 삽입 이미지

사이트를 돌다가 여기저기 들러서 스펙이랑
메모리 구조랑 보면서 이것 저것 했다...

개발에 대한 준비랄까...
뭐 일단 메모리 구조와 코어는 필수니까 열심히 보고 있는데...

보면 볼 수 록 괜찮다는 생각이 든다...
괜찮은 개발 Library도 있고... ㅜ_ㅜ

왠지 할일이 확~ 줄어든 듯 하기는 하지만...
기분은 참 좋은... ㅎㅎ

당분간 심심하지는 않겠구만 ㅋㅋ

+ Recent posts