참고. NDS 홈브루에 DLDI 패치하는 방법

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

 

들어가기 전에...

0.시작하면서...

 이 문서는 파일에 접근하는 NDS 홈브루를 정상적으로 실행하기위해 DLDI 패치를 적용하는 방법을 설명한 문서이다.

 

1.DLDI란?

 DLDI는 Dynamically Linked Device Interface의 약자로써, NDS 홈브루 개발에 사용되는 libfat 라이브러리에서 사용한다. libfat는 기기에 포함된 MicroSD 또는 SD와 같은 메모리 카드 영역에 접근하여 파일을 읽고 쓰는 기능을 하는 라이브러리이다. 메모리 카드에 데이터를 읽고 쓰는 NDS 홈브루라면 반드시 사용하고 있는 라이브러리이다.

 하지만 각 기기마다 메모리 카드에 접근하는 방식에 차이가 있기 때문에, 자신의 기기에서 정상적으로 실행하기 위해서는 DLDI에 패치를 가해야 한다.

 

2.DLDI 패치 프로그램 다운로드

 DLDI 프로그램은 http://chishm.drunkencoders.com/DLDI/에서 다운로드 받을 수 있다.

 웹 페이지의 중간쯤 보면 아래와 같은 화면이 보일 것이다. 붉은 사각형으로 표시된 "Win32 GUI Download" 를 클릭하여 파일을 저장하자.

DLDI.PNG

<DLDI 패치 프로그램>

 다운로드 받은 파일의 압축을 해제하면 여러 파일이 나오는데, 그 중 "dlditool32.exe" 파일만 있으면 되므로, 해당 파일만 남기고 나머지는 모두 삭제하자.

 

3.DLDI 패치 파일 다운로드

 패치 프로그램을 다운로드 받았으니 이제 패치 파일을 다운로드 받아야 한다. 패치파일은 http://chishm.drunkencoders.com/DLDI/ 의 상단(아래 화면 참조)에서 받을 수 있다. 

 자신의 기기에 맞는 파일을 다운로드하여 위에서 다운 받은 "dlditool32.exe" 파일이 있는 폴더에 넣자. 만약 파일이 정상적으로 다운로드 되지 않는다면 dlditool-win32-gui.zip에서 다운로드 하자. DLDI 패치 파일과 패치 프로그램이 포함된 압축 파일이다.

DLDI_Patch_File.PNG

<DLDI 패치 파일>

 

4.홈브루 패치 실행

4.1 초기 화면 및 기기 선택

 이제 "dlditool32.exe"을 실행하면 아래와 같은 화면이 표시될 것이다.

실행1.PNG

<초기 화면>

 최상단의 붉은색 콤보박스는 홈브루를 동작시킬 기기를 선택한다. 만약 자신의 기기가 나타나지 않는다면 위로 돌아가서 다시 DLDI 패치 파일을 다운로드해서 "dlditool32.exe파일과 같은 폴더에 넣어두자.

 

4.2 NDS 홈브루 파일 선택

 이제 실제 패치할 홈브루를 선택해야 한다. 홈브루 선택은 우측 중앙 쯤에 있는 "..." 버튼으로 선택하면 된다. "..." 버튼을 누르면 아래와 같은 파일 선택 창이 나타날 것이다. NDS 파일을 찾아서 넣어두자.

파일선택.PNG

<NDS 홈브루 파일 선택>

4.3 패치 적용

 파일을 선택했으면 제일 아래 쪽에 "Patch" 버튼을 누르면 된다.

패치실행.PNG

<패치 적용>

 

 패치가 성공하면 아래와 같은 메시지가 표시될 것이다. 이제 홈브루를 기기에 복사해서 실행하면 정상적으로 실행되는 것을 확인할 수 있다.

 

패치성공.PNG

<패치 성공>

 

5.마치면서...

 이상으로 NDS 홈브루에 DLDI 패치를 적용하는 방법을 알아보았다. 이제 NDS의 기능을 확장해주는 홈브루의 세계로 빠져보자. ^^)/~

 

 

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

공지사항
많은 분들이 닌텐도 DS용 한글 입력 메모장을 쓰고 계시는데,
주의사항이 있어서 알려드립니다.

한글 입력 메모장은 기존 버전과 호환성을 위해 한 페이지에 쓸 수 있는 글 량이 제한되어 있습니다.
스크롤이 없는 이전 버전은 쓸 수 있는 최대 길이가 한 페이지를 꽉 채우는 정도 였습니다.

따라서 한글로 가득 채운다면 220자 정도, 영문으로 가득 채운다면 440자 정도가 최대이니

적당히 채우시고(?) 다음 페이지로 이동하시기 바랍니다. ㅠㅠ

나중에 시간이 나면 업데이트할 때 이를 표시해주는 기능을 넣을 예정이니
불편하시더라도 주의해서 쓰시기 바랍니다.


 드디어 닌텐도 DS(NDS)용 한글 입력 메모장 프로그램 v1.2가 나왔습니다. 많은 분들이 건의하셨던 커서 이동 기능과 메모 스크롤 기능이 추가되었습니다. 이제 메모를 수정하기위해 전체를 수정하지 않아도 됩니다. ^^)/~

 데이터 파일은 기존의 1.1 버전과 호환되므로 NDS 파일만 교체하시면 정상적으로 수행됩니다. ^^
사용자 삽입 이미지

 다만 에디트 박스 컨트롤이 완전하지가 않아서 커서 이동 시에 한글 처리가 미흡합니다. ^^;;; 그 때문에 한글을 삭제하실 때 주의하실 점이 있습니다.

 1. 한글을 지우실 때 커서를 한글 뒤쪽에 위치시킨다음 백스페이스를 2번 눌러주셔야합니다. 만약 한번만 누르시면 글자가 깨어집니다. 깨어졌을 경우 한번 더 백스페이스를 눌러주시면 정상적으로 표시됩니다.

 2. 커서가 한글의 가운데에 위치했을 때 백스페이스를 누르시면 역시 글자가 깨어집니다. 물론 이때 커서를 오른쪽으로 한칸 이동한 다음 다시 백스페이스를 누르면 깨끗하게 지워집니다.

 한글이 원래 2자리를 차지하기때문에 발생하는 문제인데, 다음 버전에 수정할 계획입니다. 급히 릴리즈하느라 소프트웨어 리셋 부분 또한 완전히 처리하지 못했는데, 다음 버전에 같이 수정하여 릴리즈하겠습니다. ^^;;;

 아래는 홈브루 파일입니다.
DLDI 패치는 필수입니다 ^^ http://kkamagui.tistory.com/469 를 참고하세요 ^^;;


================ Version History ================
Version 1.2 - http://kkamagui.tistory.com/468 참고
1. 커서 표시 및 이동 기능 : 내용을 수정하려면 백스페이스를 이용해서 데이터를 모두 지웠던 불편함 때문에 추가. 윈도우의 에디트박스와 같이 커서를 표시하고 커서 위치를 통해 추가/수정/삭제하도록 변경함. 커서 이동은 왼쪽 방향키로 가능
2. 메모장 스크롤 기능 : 메모를 최대 한 페이지까지 밖에 입력하지 못했던 불편함 때문에 추가. 커서를 이용해서 스크롤 가능하도록 변경함


Version 1.1 - http://kkamagui.tistory.com/410 참고
1. 소프트웨어 리셋 기능 : "START" 버튼을 클릭하여 펌웨어 시작화면으로 돌아가는 기능 추가. 파워를 껏다가 켜야했던 기존의 불편함을 줄임
2. TXT 추가 기능 : 메모 페이지에서 "A"버튼을 누르면 루트 디렉토리에 DATA + 페이지번호 +.txt 형식으로 txt 파일을 생성. PC에서 데이터 파일 공유 가능
사용자 삽입 이미지
3. 메모 페이지 확장 : 메모장의 페이지를 40 페이지를 확장. 기존의 20 페이지용 데이터 파일의 경우, 안전한 사용을 위해 모든 데이터를 백업해 놓고 루트 디렉토리에 있는 NOTEDATA.DAT 파일을 삭제하길 권장

Version 1.0 - http://kkamagui.tistory.com/238 참고
1. 완성형 한글/영문/숫자 입력 기능 : 완성형 한글 오토마타를 이용하여 한글 조합 및 출력 가능. 실제 키보드와 거의 동일하게 자판을 배열하고 구성함. Shift 키와 Delete 키를 지원하고 Space바 좌측의 모드 변환 키를 통해서 키보드의 타입 변경 가능.
2. 메모 입력 기능 : 1줄당 최대 영문 42자 or 한글 21자 입력 가능. 최대 10줄까지 입력가능.
3. 메모 페이지 이동 기능 : 메모 페이지를 최대 20장까지 지원. 키보드 상단의 "<<" or ">>" 버튼으로 메모 페이지 이동가능. 최상단에 현재 페이지 번호 표시.
4. 메모 내용 저장 기능 : 페이지 이동 시 or ""저장" 버튼 클릭 시 메모를 저장하는 기능. NDS 재시작 후에도 이전 메모 기록 확인 가능


 버그는 블로그의 덧글로 부탁드립니다. ^^)/~

 그럼 좋은 하루 되세요 ^^)/~


 ㅜ_ㅜ... 한시간 내내 여자친구랑 디자인했습니다. 어찌 될지는 모르겠지만 좀더 손 본 다음에 올리겠습니다. ㅜ_ㅜ)/~

 화면 구성만 한시간 내내 했네요. ㅜ_ㅜ... 천지 이런... 흑흑... ㅜ_ㅜ

 왠지 허무한듯한... ㅠ_ㅠ 아우...

사용자 삽입 이미지



 드디어 새로 만든 에디트 박스를 한글 입력 메모장 홈브루에 적용했습니다. 기본적인 기능은 1.1 버전과 거의 동일하니 기본 기능에 대한 내용은 http://kkamagui.tistory.com/410 을 참고하시면 됩니다.

 아래 화면에서 붉은 색으로 표시된 것이 커서이고, NDS의 왼쪽 방향키를 이용해서 움직일 수 있습니다. 물론 스크롤 또한 가능합니다. ^^)/~
사용자 삽입 이미지


 
주의해야 할 것은 삭제 단위가 한글 단위가 아닌 영어 단위라서 한글을 삭제할 때는 2번 Backspace를 눌러줘야 한다는 것입니다. 한번만 누르실 경우, 이상한 글자가 표시되는데 당황하지 마시고 한번 더 누르시면 깨끗하게 지워집니다.

 지울 부분의 데이터를 분석해서 한글이면 2자를 지우게 하는 것도 가능하지만 커서의 위치와 밀접한 관련이 있어서 손보기가 쉽지 않더군요. ㅜ_ㅜ 일단 어느 정도 안정화된 뒤에 처리할 생각입니다. ^^;;;

 현재 테스트 버전에서는 입력 한계가 없으나 저장할 때 잘려서 저장되니 너무 많이 입력하시면... 잘려서 나올지도... ㅜ_ㅜ;;;;

 혹시 버그가 있으면 덧글로 제보 부탁드립니다. ^^)/~
 그럼 좋은 하루 되세요 ;)



 원래 계획은 오늘 NDS로 포팅을 시작하는 것이었는데, 시험을 치다보니 시간이 늦어서 결국 포기했습니다. ㅜ_ㅜ 요즘 잠도 잘 못자서 얼굴이 뒤집어졌는데, 포팅을 시작하게 되면 끝날 때까지 안잘 것 같아서... ^^;;;

 대신 어느 정도 완성된 에디트 박스(Edit Box)를 올립니다. Version은 아직 버그가 좀 있을 수 있기 때문에 0.9로 했습니다. ^^ 아래는 직접 키보드를 이용해서 메모를 변경하고 추가한 화면입니다.
사용자 삽입 이미지 사용자 삽입 이미지

 방향키로 커서 이동이 가능하고 키보드로 알파벳, 스페이스, 엔터 그리고 백스페이스를 사용할 수 있습니다.

 내일까지 테스트 해본 후에 큰 버그가 없으면 함수 좀 정리해서 NDS에 포팅해야겠습니다. ^^)/~ 별 문제 없이 끝났으면 좋겠네요. ㅎㅎ 이제 커서 이동까지 가능해지면 그동안 빗발쳤던(??) 요구사항을 어느정도 수렴한듯 하니까요. ^^;;;;

 엇~ 벌써 시간이 이렇게나... 이만 자야겠습니다. ^^)/~
 다들 좋은 밤 되세요 >ㅁ<)/~


 휴가를 틈타 NDS 메모장 홈브루를 손보려고 집에 PC 뒤졌는데, 옛날 소스 밖에는 없더군요. ㅜ_ㅜ 그래서 제 스프링노트(http://kkamagui.springnote.com)와 블로그(http://kkamagui.tistory.com)를 검색했더니 역시나... ㅜ_ㅜ

 다른 소스는 다 올려놨으면서 이 소스는 왜 안올려 놓은건지... 하는 수 없이 윈도우 환경에서 구현하기로 했습니다. NDS를 하다말고 갑자기 왠 윈도우냐 하시는 분이 계실지 모르겠습니다. ^^;;;
 
 NDS용 메모장 홈브루는 제가 틈틈이 만든 NDS 윈도우 라이브러리(http://kkamagui.tistory.com/37)를 기반으로 작성되었기 때문에, 윈도우 환경(MFC)과 매우 유사하기 때문에 가능한 것이지요. 개발을 편하게 진행하기 위해서 필요한 부분만 최대한 비슷하게 구현했습니다.

 현재 커서와 데이터 출력, 스크롤 부분까지 처리가 끝났고 이제 남은 것은 커서가 있는 부분을 위주로 추가/수정/삭제하는 부분만 추가하면 되는데... NDS의 한정된 메모리를 사용해야 하기 때문에, 어떻게 하는 것이 좋을지 상당히 고민스럽습니다. 일단은 좀 더 생각해봐야겠습니다. ^^;;;;

 아래는 실제 구현된 에디트 박스의 모습입니다. 글자 사이에 있는 빨간 선이 커서를 나타내고 제일 바깥쪽에 있는 붉은 색 선은 NDS의 LCD 영역을 나타냅니다.

사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지


 


























 아래에 첨부된 압축파일을 풀어서 실행하면 간단한 테스트 해볼 수 있으니, 심심하시면 한번... ^^)/~ 키보드의 화살표 키로 커서를 이동할 수 있으며, 그에 따른 커서 위치 처리와 스크롤에 관한 처리를 테스트 할 수 있습니다. ^^


 아아~ 생각보다 진도가 안나가는 군요. ㅜ_ㅜ)/~
 그럼 다들 좋은 밤 되시길~



 대박 연휴를 맞아 집에 내려오게됬습니다. 집에서 인터넷으로 열차시간을 확인한 뒤, 역으로 왔더니 고새 매진이더군요. ㅡ_ㅡa... 어쩔 수 없어 한시간 뒤 기차를 예매하고 서점으로 갔습니다. 역에 갈때마다 한번씩 들리는 버릇이 생겼거든요. ^^;;;;

 제일 먼저 마이크로소프트웨어 5월호를 들었습니다. 하는 일이 응용 프로그램과 거리가 멀다보니, 위쪽(?) 소식이 많이 궁금합니다(좋은 소식 덧글로 많이 전해주세요 ㅜ_ㅜ...). 마소 기사 목록을 죽 훓어보다보니 블로그 관리를 위한 툴에 대한 기사가 있던데, 갑자기 뭔가 번득하더군요. @0@)/~

 블로그에 수시로 들러서 글쓰고 답글을 달지만, "블로그"에서 그 일을 해야한다는 고정관념 같은게 있었습니다. ㅡ_ㅡa. 그런데 저 기사 제목을 보고 나니 당장 툴을 만들어야겠다는 생각이 들더군요. 일일이 덧글 목록을 살펴서 답글을 달다보면 보지 못하고 넘어가는 덧글이 종종있거든요. ^^;;;;(혹시 제가 덧글에 답글을 달지 않았다면, 이 자리를 빌어 죄송하다는 말씀을 드립니다. (__) 일부러 안단게 아니니 용서해주세요 ㅜ_ㅜ)

 연휴 기간에 덧글을 검색하는 테스트 프로그램부터 간단히 만들어볼까 생각중입니다. Python으로 만들면 간단히 할 수 있을 듯도 한데, 원체 오래되서 기억이 가물가물하네요. ^^;;; 이번에는 OpenAPI를 사용해서 덧글 리스트를 뽑아봐야겠습니다. ^^)/~

 그리고 이전에 만들었던 NDS용 한글 메모장(http://kkamagui.tistory.com/410)PSP에 포팅할까 생각중인데, 그 이전에 한글 메모장부터 손볼 생각입니다. 메모장의 가장 큰 문제였던 커서 이동이 안되는 부분을 방향키를 이용해서 이동할 수 있도록 할까 합니다. 물론 커서도 추가해야겠지요. ^^)/~

 연휴에 푹 쉬나 했더니 이번 연휴도 바쁘겠군요. ^^)/~ 역시 천성이 개발자인듯 합니다. ㅎㅎ
 그럼 다들 좋은밤 되시길~ ^^

 얼마전에 제 블로그에 태용님께서 조그만 선물을 해주시겠다는 댓글을 남기셨습니다. 재미있는 장난감이라고 하시길래, 게임 시디나 괜찮은 셈플 어플리케이션이겠거니 생각하고 있었는데.... NDS용 닥터를... 그것도 무려 3종류나 기부해 주셨습니다. 태용님께서 홈브루 개발자를 지원하는 프로젝트를 진행하시는데, 제가 거기에 포함된 모양입니다. ㅜ_ㅜ... 이런 영광이.... ㅜ_ㅜ

 아래는 기부해주신 닥터의 사진입니다. DSTT, Acekard2, Acekard R.P.G 3종류입니다.
사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지

 제가 쓴 글이 다른 분에게 도움이 되고, 그걸로 인해 제가 다시 도움을 받게 될 줄은 몰랐습니다. 왠지 얼떨떨한 기분이군요. ^^;;;; 뭐랄까... 마치 외국에서만 가능할꺼라고 생각했던 일이 저한테 닥치니... 기분이 굉장히 좋습니다. 안그래도 가지고 있는 구형 NDS에 오래된 M3 SD가 달려있어서 동생이 고생하곤했는데, 이제는 그럴 일이 없겠군요. ^^)/~

 태용님께서 기부해주신 닥터는 더 열심히 개발하고, 홈브루 릴리즈할 때 테스트를 열심히 하라는 뜻인 것 같습니다(맞는가요? ㅎㅎ ^^;;;;). 열심히 하겠습니다. >ㅁ<)/~ 그리고 나중에 시간나면 제가 갖고있는 닥터와 비교해서 리뷰도 하나 쓰겠습니다.

 마지막으로 얼굴도 모르는 저에게 이런 멋진 선물을 주신 태용님께 다시 한번 깊은 감사를 드립니다. ^ㅡ^)/~

 그럼 다들 좋은밤 되세요~


 테스트용으로 만든 한글 입력 메모장에 대해서 많은 분들의 문의가 있으셔서, 오늘 드디어 구석에 박아두었던 소스를 꺼냈습니다. 일단 전체적인 수정을 하기 보다는 많은 분이 필요로 하셨던 TXT 저장 기능과 리셋기능을 먼저 추가했습니다. ^^

 대부분의 기능은 기존의 1.0 버전(http://kkamagui.tistory.com/238)과 크게 차이가 없습니다. 특별히 이번에 추가된 기능만 나열하면 아래와 같습니다.
1. 소프트웨어 리셋 기능 : "START" 버튼을 클릭하여 펌웨어 시작화면으로 돌아가는 기능 추가. 파워를 껏다가 켜야했던 기존의 불편함을 줄임
2. TXT 추가 기능 : 메모 페이지에서 "A"버튼을 누르면 루트 디렉토리에 DATA + 페이지번호 +.txt 형식으로 txt 파일을 생성. PC에서 데이터 파일 공유 가능
사용자 삽입 이미지
3. 메모 페이지 확장 : 메모장의 페이지를 40 페이지를 확장. 기존의 20 페이지용 데이터 파일의 경우, 안전한 사용을 위해 모든 데이터를 백업해 놓고 루트 디렉토리에 있는 NOTEDATA.DAT 파일을 삭제하길 권장

 아래는 실행 화면을 캡쳐한 것입니다.
사용자 삽입 이미지

 추가 요구사항이나 기타 건의사항은 http://kkamagui.tistory.com으로 부탁드립니다. ^^
 그럼 좋은 하루 되세요 >ㅁ<)/~



 틈틈히 만든 닌텐도 DS(이하 NDS)용 한글 입력기와 간단한 메모장 프로그램을 공개합니다. 생각보다 일이 많아서 릴리즈가 조금 늦어졌습니다. NDS용 홈브루(Homebrew)는 많이 있습니다만, 한글 입력이 가능한 홈브루가 없어서 만들어야겠다고 생각만하다가 이제서야 만들었네요. ^^;;;

 프로그램은 아래와 같이 최상단의 페이지 번호와 그 아래의 메모입력 화면, 마지막으로 한글/영문/숫자 키보드로 이루어져있습니다. NDS의 듀얼 스크린의 장점이 여기서 나오더군요. ^^
사용자 삽입 이미지

사용자 삽입 이미지 사용자 삽입 이미지

 이 프로그램이 제공하는 기능은 아래와 같습니다.
1. 완성형 한글/영문/숫자 입력 기능 : 완성형 한글 오토마타를 이용하여 한글 조합 및 출력 가능. 실제 키보드와 거의 동일하게 자판을 배열하고 구성함. Shift 키와 Delete 키를 지원하고 Space바 좌측의 모드 변환 키를 통해서 키보드의 타입 변경 가능.
2. 메모 입력 기능 : 1줄당 최대 영문 42자 or 한글 21자 입력 가능. 최대 10줄까지 입력가능.
3. 메모 페이지 이동 기능 : 메모 페이지를 최대 20장까지 지원. 키보드 상단의 "<<" or ">>" 버튼으로 메모 페이지 이동가능. 최상단에 현재 페이지 번호 표시.
4. 메모 내용 저장 기능 : 페이지 이동 시 or ""저장" 버튼 클릭 시 메모를 저장하는 기능. NDS 재시작 후에도 이전 메모 기록 확인 가능

 프로그램의 기능은 굉장히 간단한데, 글로 쓸려니 길어집니다. ^^ 한번 써보시면 더 쉽게 이해하실 수 있을 겁니다. 아래는 실제 사용하는 동영상입니다.



그럼 좋은하루 되시길~ ^ㅡ^)/~
ps) 아래는 실행파일입니다.


 약 이틀간의 작업끝에 한글 오토마타의 수정을 끝내고 아래와 같은 메모장 프로토타입을 만들었습니다. 이제 상단에 기능 버튼(오늘 일자 표시 및 삭제 기능)만 추가하면 릴리즈해도 괜찮겠군요. 시연 동영상이라도 하나 찍어서 올려야겠습니다. 물론 실행파일도 같이요 ^^)/~
사용자 삽입 이미지

메모장 프로토 타입




 제가 만든 홈브루가 굉장히 많은데, 이번 것 만큼 손이 많이가는 것도 처음인것 같네요. 여튼 진행상태로 보면 오늘 내일 안에 메모장 프로토타입을 완성할 수 있을 것 같습니다. 파일로 저장하고 여러 메모를 관리할 수 있도록 하면 간단한 Todo List 메모용으로 사용해도 되겠네요 ^^;;;

 여유가 좀 된다면 메모 검색화면이나 기타 기능도 넣으면 좋겠는데, 일단 고민을 좀 해봐야겠습니다. 일단은 1차 릴리즈에서 버그가 없는 것이 더 중요하니까요 ^^)/~~


예전에 NDS 한글 출력할때 참고했던 완성형 한글 코드입니다. 이제 확장 완성형으로 가야하는데, 참고삼아 이글루스 블로그에서 옮겨옵니다. ^^

실제 한글코드는 아래와 같이 블럭단위로 나누어서 표시되는데 블럭의 제일 첫 부분 xxx0와 블럭의 가장 마지막 부분 xxxF 부분은 사용하지 않으니 맵핑할때 주의해야 합니다.

사용자 삽입 이미지
한글 코드 배치

아래는 전체 2350자의 코드입니다. ^^

KSC5601-1987 한글코드 총 2350자

자음.... 
----------------- 
ㄱ 0xA4A1    ㄲ 0xA4A2    ㄳ 0xA4A3    ㄴ 0xA4A4    ㄵ 0xA4A5 
ㄶ 0xA4A6    ㄷ 0xA4A7    ㄸ 0xA4A8    ㄹ 0xA4A9    ㄺ 0xA4AA 
ㄻ 0xA4AB    ㄼ 0xA4AC    ㄽ 0xA4AD    ㄾ 0xA4AE    ㄿ 0xA4AF 
ㅀ 0xA4B0    ㅁ 0xA4B1    ㅂ 0xA4B2    ㅃ 0xA4B3    ㅄ 0xA4B4 
ㅅ 0xA4B5    ㅆ 0xA4B6    ㅇ 0xA4B7    ㅈ 0xA4B8    ㅉ 0xA4B9 
ㅊ 0xA4BA    ㅋ 0xA4BB    ㅌ 0xA4BC    ㅍ 0xA4BD    ㅎ 0xA4BE


모음... 
----------------- 
ㅏ 0xA4BF    ㅐ 0xA4C0    ㅑ 0xA4C1    ㅒ 0xA4C2    ㅓ 0xA4C3 
ㅔ 0xA4C4    ㅕ 0xA4C5    ㅖ 0xA4C6    ㅗ 0xA4C7    ㅘ 0xA4C8 
ㅙ 0xA4C9    ㅚ 0xA4CA    ㅛ 0xA4CB    ㅜ 0xA4CC    ㅝ 0xA4CD 
ㅞ 0xA4CE    ㅟ 0xA4CF    ㅠ 0xA4D0    ㅡ 0xA4D1    ㅢ 0xA4D2 
ㅣ 0xA4D3

[#M_== 전체 코드 보기(클릭) ==|== 전체 코드 닫기(클릭) ==|
------------------------------------------------------------------------- 
가 0xB0A1    각 0xB0A2    간 0xB0A3    갇 0xB0A4    갈 0xB0A5     
갉 0xB0A6    갊 0xB0A7    감 0xB0A8    갑 0xB0A9    값 0xB0AA 
갓 0xB0AB    갔 0xB0AC    강 0xB0AD    갖 0xB0AE    갗 0xB0AF 
같 0xB0B0    갚 0xB0B1    갛 0xB0B2    개 0xB0B3    객 0xB0B4 
갠 0xB0B5    갤 0xB0B6    갬 0xB0B7    갭 0xB0B8    갯 0xB0B9 
갰 0xB0BA    갱 0xB0BB    갸 0xB0BC    갹 0xB0BD    갼 0xB0BE 
걀 0xB0BF    걋 0xB0C0    걍 0xB0C1    걔 0xB0C2    걘 0xB0C3 
걜 0xB0C4    거 0xB0C5    걱 0xB0C6    건 0xB0C7    걷 0xB0C8 
걸 0xB0C9    걺 0xB0CA    검 0xB0CB    겁 0xB0CC    것 0xB0CD 
겄 0xB0CE    겅 0xB0CF    겆 0xB0D0    겉 0xB0D1    겊 0xB0D2 
겋 0xB0D3    게 0xB0D4    겐 0xB0D5    겔 0xB0D6    겜 0xB0D7 
겝 0xB0D8    겟 0xB0D9    겠 0xB0DA    겡 0xB0DB    겨 0xB0DC 
격 0xB0DD    겪 0xB0DE    견 0xB0DF    겯 0xB0E0    결 0xB0E1 
겸 0xB0E2    겹 0xB0E3    겻 0xB0E4    겼 0xB0E5    경 0xB0E6 
곁 0xB0E7    계 0xB0E8    곈 0xB0E9    곌 0xB0EA    곕 0xB0EB 
곗 0xB0EC    고 0xB0ED    곡 0xB0EE    곤 0xB0EF    곧 0xB0F0 
골 0xB0F1    곪 0xB0F2    곬 0xB0F3    곯 0xB0F4    곰 0xB0F5 
곱 0xB0F6    곳 0xB0F7    공 0xB0F8    곶 0xB0F9    과 0xB0FA 
곽 0xB0FB    관 0xB0FC    괄 0xB0FD    괆 0xB0FE    괌 0xB1A1 
괍 0xB1A2    괏 0xB1A3    광 0xB1A4    괘 0xB1A5    괜 0xB1A6 
괠 0xB1A7    괩 0xB1A8    괬 0xB1A9    괭 0xB1AA    괴 0xB1AB 
괵 0xB1AC    괸 0xB1AD    괼 0xB1AE    굄 0xB1AF    굅 0xB1B0 
굇 0xB1B1    굉 0xB1B2    교 0xB1B3    굔 0xB1B4    굘 0xB1B5 
굡 0xB1B6    굣 0xB1B7    구 0xB1B8    국 0xB1B9    군 0xB1BA 
굳 0xB1BB    굴 0xB1BC    굵 0xB1BD    굶 0xB1BE    굻 0xB1BF 
굼 0xB1C0    굽 0xB1C1    굿 0xB1C2    궁 0xB1C3    궂 0xB1C4 
궈 0xB1C5    궉 0xB1C6    권 0xB1C7    궐 0xB1C8    궜 0xB1C9 
궝 0xB1CA    궤 0xB1CB    궷 0xB1CC    귀 0xB1CD    귁 0xB1CE 
귄 0xB1CF    귈 0xB1D0    귐 0xB1D1    귑 0xB1D2    귓 0xB1D3 
규 0xB1D4    균 0xB1D5    귤 0xB1D6    그 0xB1D7    극 0xB1D8 
근 0xB1D9    귿 0xB1DA    글 0xB1DB    긁 0xB1DC    금 0xB1DD 
급 0xB1DE    긋 0xB1DF    긍 0xB1E0    긔 0xB1E1    기 0xB1E2 
긱 0xB1E3    긴 0xB1E4    긷 0xB1E5    길 0xB1E6    긺 0xB1E7 
김 0xB1E8    깁 0xB1E9    깃 0xB1EA    깅 0xB1EB    깆 0xB1EC 
깊 0xB1ED    까 0xB1EE    깍 0xB1EF    깎 0xB1F0    깐 0xB1F1 
깔 0xB1F2    깖 0xB1F3    깜 0xB1F4    깝 0xB1F5    깟 0xB1F6 
깠 0xB1F7    깡 0xB1F8    깥 0xB1F9    깨 0xB1FA    깩 0xB1FB 
깬 0xB1FC    깰 0xB1FD    깸 0xB1FE    깹 0xB2A1    깻 0xB2A2 
깼 0xB2A3    깽 0xB2A4    꺄 0xB2A5    꺅 0xB2A6    꺌 0xB2A7 
꺼 0xB2A8    꺽 0xB2A9    꺾 0xB2AA    껀 0xB2AB    껄 0xB2AC 
껌 0xB2AD    껍 0xB2AE    껏 0xB2AF    껐 0xB2B0    껑 0xB2B1 
께 0xB2B2    껙 0xB2B3    껜 0xB2B4    껨 0xB2B5    껫 0xB2B6 
껭 0xB2B7    껴 0xB2B8    껸 0xB2B9    껼 0xB2BA    꼇 0xB2BB 
꼈 0xB2BC    꼍 0xB2BD    꼐 0xB2BE    꼬 0xB2BF    꼭 0xB2C0 
꼰 0xB2C1    꼲 0xB2C2    꼴 0xB2C3    꼼 0xB2C4    꼽 0xB2C5 
꼿 0xB2C6    꽁 0xB2C7    꽂 0xB2C8    꽃 0xB2C9    꽈 0xB2CA 
꽉 0xB2CB    꽐 0xB2CC    꽜 0xB2CD    꽝 0xB2CE    꽤 0xB2CF 
꽥 0xB2D0    꽹 0xB2D1    꾀 0xB2D2    꾄 0xB2D3    꾈 0xB2D4 
꾐 0xB2D5    꾑 0xB2D6    꾕 0xB2D7    꾜 0xB2D8    꾸 0xB2D9 
꾹 0xB2DA    꾼 0xB2DB    꿀 0xB2DC    꿇 0xB2DD    꿈 0xB2DE 
꿉 0xB2DF    꿋 0xB2E0    꿍 0xB2E1    꿎 0xB2E2    꿔 0xB2E3 
꿜 0xB2E4    꿨 0xB2E5    꿩 0xB2E6    꿰 0xB2E7    꿱 0xB2E8 
꿴 0xB2E9    꿸 0xB2EA    뀀 0xB2EB    뀁 0xB2EC    뀄 0xB2ED 
뀌 0xB2EE    뀐 0xB2EF    뀔 0xB2F0    뀜 0xB2F1    뀝 0xB2F2 
뀨 0xB2F3    끄 0xB2F4    끅 0xB2F5    끈 0xB2F6    끊 0xB2F7 
끌 0xB2F8    끎 0xB2F9    끓 0xB2FA    끔 0xB2FB    끕 0xB2FC 
끗 0xB2FD    끙 0xB2FE    끝 0xB3A1    끼 0xB3A2    끽 0xB3A3 
낀 0xB3A4    낄 0xB3A5    낌 0xB3A6    낍 0xB3A7    낏 0xB3A8 
낑 0xB3A9    나 0xB3AA    낙 0xB3AB    낚 0xB3AC    난 0xB3AD 
낟 0xB3AE    날 0xB3AF    낡 0xB3B0    낢 0xB3B1    남 0xB3B2 
납 0xB3B3    낫 0xB3B4    났 0xB3B5    낭 0xB3B6    낮 0xB3B7 
낯 0xB3B8    낱 0xB3B9    낳 0xB3BA    내 0xB3BB    낵 0xB3BC 
낸 0xB3BD    낼 0xB3BE    냄 0xB3BF    냅 0xB3C0    냇 0xB3C1 
냈 0xB3C2    냉 0xB3C3    냐 0xB3C4    냑 0xB3C5    냔 0xB3C6 
냘 0xB3C7    냠 0xB3C8    냥 0xB3C9    너 0xB3CA    넉 0xB3CB 
넋 0xB3CC    넌 0xB3CD    널 0xB3CE    넒 0xB3CF    넓 0xB3D0 
넘 0xB3D1    넙 0xB3D2    넛 0xB3D3    넜 0xB3D4    넝 0xB3D5 
넣 0xB3D6    네 0xB3D7    넥 0xB3D8    넨 0xB3D9    넬 0xB3DA 
넴 0xB3DB    넵 0xB3DC    넷 0xB3DD    넸 0xB3DE    넹 0xB3DF 
녀 0xB3E0    녁 0xB3E1    년 0xB3E2    녈 0xB3E3    념 0xB3E4 
녑 0xB3E5    녔 0xB3E6    녕 0xB3E7    녘 0xB3E8    녜 0xB3E9 
녠 0xB3EA    노 0xB3EB    녹 0xB3EC    논 0xB3ED    놀 0xB3EE 
놂 0xB3EF    놈 0xB3F0    놉 0xB3F1    놋 0xB3F2    농 0xB3F3 
높 0xB3F4    놓 0xB3F5    놔 0xB3F6    놘 0xB3F7    놜 0xB3F8 
놨 0xB3F9    뇌 0xB3FA    뇐 0xB3FB    뇔 0xB3FC    뇜 0xB3FD 
뇝 0xB3FE    뇟 0xB4A1    뇨 0xB4A2    뇩 0xB4A3    뇬 0xB4A4 
뇰 0xB4A5    뇹 0xB4A6    뇻 0xB4A7    뇽 0xB4A8    누 0xB4A9 
눅 0xB4AA    눈 0xB4AB    눋 0xB4AC    눌 0xB4AD    눔 0xB4AE 
눕 0xB4AF    눗 0xB4B0    눙 0xB4B1    눠 0xB4B2    눴 0xB4B3 
눼 0xB4B4    뉘 0xB4B5    뉜 0xB4B6    뉠 0xB4B7    뉨 0xB4B8 
뉩 0xB4B9    뉴 0xB4BA    뉵 0xB4BB    뉼 0xB4BC    늄 0xB4BD 
늅 0xB4BE    늉 0xB4BF    느 0xB4C0    늑 0xB4C1    는 0xB4C2 
늘 0xB4C3    늙 0xB4C4    늚 0xB4C5    늠 0xB4C6    늡 0xB4C7 
늣 0xB4C8    능 0xB4C9    늦 0xB4CA    늪 0xB4CB    늬 0xB4CC 
늰 0xB4CD    늴 0xB4CE    니 0xB4CF    닉 0xB4D0    닌 0xB4D1 
닐 0xB4D2    닒 0xB4D3    님 0xB4D4    닙 0xB4D5    닛 0xB4D6 
닝 0xB4D7    닢 0xB4D8    다 0xB4D9    닥 0xB4DA    닦 0xB4DB 
단 0xB4DC    닫 0xB4DD    달 0xB4DE    닭 0xB4DF    닮 0xB4E0 
닯 0xB4E1    닳 0xB4E2    담 0xB4E3    답 0xB4E4    닷 0xB4E5 
닸 0xB4E6    당 0xB4E7    닺 0xB4E8    닻 0xB4E9    닿 0xB4EA 
대 0xB4EB    댁 0xB4EC    댄 0xB4ED    댈 0xB4EE    댐 0xB4EF 
댑 0xB4F0    댓 0xB4F1    댔 0xB4F2    댕 0xB4F3    댜 0xB4F4 
더 0xB4F5    덕 0xB4F6    덖 0xB4F7    던 0xB4F8    덛 0xB4F9 
덜 0xB4FA    덞 0xB4FB    덟 0xB4FC    덤 0xB4FD    덥 0xB4FE 
덧 0xB5A1    덩 0xB5A2    덫 0xB5A3    덮 0xB5A4    데 0xB5A5 
덱 0xB5A6    덴 0xB5A7    델 0xB5A8    뎀 0xB5A9    뎁 0xB5AA 
뎃 0xB5AB    뎄 0xB5AC    뎅 0xB5AD    뎌 0xB5AE    뎐 0xB5AF 
뎔 0xB5B0    뎠 0xB5B1    뎡 0xB5B2    뎨 0xB5B3    뎬 0xB5B4 
도 0xB5B5    독 0xB5B6    돈 0xB5B7    돋 0xB5B8    돌 0xB5B9 
돎 0xB5BA    돐 0xB5BB    돔 0xB5BC    돕 0xB5BD    돗 0xB5BE 
동 0xB5BF    돛 0xB5C0    돝 0xB5C1    돠 0xB5C2    돤 0xB5C3 
돨 0xB5C4    돼 0xB5C5    됐 0xB5C6    되 0xB5C7    된 0xB5C8 
될 0xB5C9    됨 0xB5CA    됩 0xB5CB    됫 0xB5CC    됴 0xB5CD 
두 0xB5CE    둑 0xB5CF    둔 0xB5D0    둘 0xB5D1    둠 0xB5D2 
둡 0xB5D3    둣 0xB5D4    둥 0xB5D5    둬 0xB5D6    뒀 0xB5D7 
뒈 0xB5D8    뒝 0xB5D9    뒤 0xB5DA    뒨 0xB5DB    뒬 0xB5DC 
뒵 0xB5DD    뒷 0xB5DE    뒹 0xB5DF    듀 0xB5E0    듄 0xB5E1 
듈 0xB5E2    듐 0xB5E3    듕 0xB5E4    드 0xB5E5    득 0xB5E6 
든 0xB5E7    듣 0xB5E8    들 0xB5E9    듦 0xB5EA    듬 0xB5EB 
듭 0xB5EC    듯 0xB5ED    등 0xB5EE    듸 0xB5EF    디 0xB5F0 
딕 0xB5F1    딘 0xB5F2    딛 0xB5F3    딜 0xB5F4    딤 0xB5F5 
딥 0xB5F6    딧 0xB5F7    딨 0xB5F8    딩 0xB5F9    딪 0xB5FA 
따 0xB5FB    딱 0xB5FC    딴 0xB5FD    딸 0xB5FE    땀 0xB6A1 
땁 0xB6A2    땃 0xB6A3    땄 0xB6A4    땅 0xB6A5    땋 0xB6A6 
때 0xB6A7    땍 0xB6A8    땐 0xB6A9    땔 0xB6AA    땜 0xB6AB 
땝 0xB6AC    땟 0xB6AD    땠 0xB6AE    땡 0xB6AF    떠 0xB6B0 
떡 0xB6B1    떤 0xB6B2    떨 0xB6B3    떪 0xB6B4    떫 0xB6B5 
떰 0xB6B6    떱 0xB6B7    떳 0xB6B8    떴 0xB6B9    떵 0xB6BA 
떻 0xB6BB    떼 0xB6BC    떽 0xB6BD    뗀 0xB6BE    뗄 0xB6BF 
뗌 0xB6C0    뗍 0xB6C1    뗏 0xB6C2    뗐 0xB6C3    뗑 0xB6C4 
뗘 0xB6C5    뗬 0xB6C6    또 0xB6C7    똑 0xB6C8    똔 0xB6C9 
똘 0xB6CA    똥 0xB6CB    똬 0xB6CC    똴 0xB6CD    뙈 0xB6CE 
뙤 0xB6CF    뙨 0xB6D0    뚜 0xB6D1    뚝 0xB6D2    뚠 0xB6D3 
뚤 0xB6D4    뚫 0xB6D5    뚬 0xB6D6    뚱 0xB6D7    뛔 0xB6D8 
뛰 0xB6D9    뛴 0xB6DA    뛸 0xB6DB    뜀 0xB6DC    뜁 0xB6DD 
뜅 0xB6DE    뜨 0xB6DF    뜩 0xB6E0    뜬 0xB6E1    뜯 0xB6E2 
뜰 0xB6E3    뜸 0xB6E4    뜹 0xB6E5    뜻 0xB6E6    띄 0xB6E7 
띈 0xB6E8    띌 0xB6E9    띔 0xB6EA    띕 0xB6EB    띠 0xB6EC 
띤 0xB6ED    띨 0xB6EE    띰 0xB6EF    띱 0xB6F0    띳 0xB6F1 
띵 0xB6F2    라 0xB6F3    락 0xB6F4    란 0xB6F5    랄 0xB6F6 
람 0xB6F7    랍 0xB6F8    랏 0xB6F9    랐 0xB6FA    랑 0xB6FB 
랒 0xB6FC    랖 0xB6FD    랗 0xB6FE    래 0xB7A1    랙 0xB7A2 
랜 0xB7A3    랠 0xB7A4    램 0xB7A5    랩 0xB7A6    랫 0xB7A7 
랬 0xB7A8    랭 0xB7A9    랴 0xB7AA    략 0xB7AB    랸 0xB7AC 
럇 0xB7AD    량 0xB7AE    러 0xB7AF    럭 0xB7B0    런 0xB7B1 
럴 0xB7B2    럼 0xB7B3    럽 0xB7B4    럿 0xB7B5    렀 0xB7B6 
렁 0xB7B7    렇 0xB7B8    레 0xB7B9    렉 0xB7BA    렌 0xB7BB 
렐 0xB7BC    렘 0xB7BD    렙 0xB7BE    렛 0xB7BF    렝 0xB7C0 
려 0xB7C1    력 0xB7C2    련 0xB7C3    렬 0xB7C4    렴 0xB7C5 
렵 0xB7C6    렷 0xB7C7    렸 0xB7C8    령 0xB7C9    례 0xB7CA 
롄 0xB7CB    롑 0xB7CC    롓 0xB7CD    로 0xB7CE    록 0xB7CF 
론 0xB7D0    롤 0xB7D1    롬 0xB7D2    롭 0xB7D3    롯 0xB7D4 
롱 0xB7D5    롸 0xB7D6    롼 0xB7D7    뢍 0xB7D8    뢨 0xB7D9 
뢰 0xB7DA    뢴 0xB7DB    뢸 0xB7DC    룀 0xB7DD    룁 0xB7DE 
룃 0xB7DF    룅 0xB7E0    료 0xB7E1    룐 0xB7E2    룔 0xB7E3 
룝 0xB7E4    룟 0xB7E5    룡 0xB7E6    루 0xB7E7    룩 0xB7E8 
룬 0xB7E9    룰 0xB7EA    룸 0xB7EB    룹 0xB7EC    룻 0xB7ED 
룽 0xB7EE    뤄 0xB7EF    뤘 0xB7F0    뤠 0xB7F1    뤼 0xB7F2 
뤽 0xB7F3    륀 0xB7F4    륄 0xB7F5    륌 0xB7F6    륏 0xB7F7 
륑 0xB7F8    류 0xB7F9    륙 0xB7FA    륜 0xB7FB    률 0xB7FC 
륨 0xB7FD    륩 0xB7FE    륫 0xB8A1    륭 0xB8A2    르 0xB8A3 
륵 0xB8A4    른 0xB8A5    를 0xB8A6    름 0xB8A7    릅 0xB8A8 
릇 0xB8A9    릉 0xB8AA    릊 0xB8AB    릍 0xB8AC    릎 0xB8AD 
리 0xB8AE    릭 0xB8AF    린 0xB8B0    릴 0xB8B1    림 0xB8B2 
립 0xB8B3    릿 0xB8B4    링 0xB8B5    마 0xB8B6    막 0xB8B7 
만 0xB8B8    많 0xB8B9    맏 0xB8BA    말 0xB8BB    맑 0xB8BC 
맒 0xB8BD    맘 0xB8BE    맙 0xB8BF    맛 0xB8C0    망 0xB8C1 
맞 0xB8C2    맡 0xB8C3    맣 0xB8C4    매 0xB8C5    맥 0xB8C6 
맨 0xB8C7    맬 0xB8C8    맴 0xB8C9    맵 0xB8CA    맷 0xB8CB 
맸 0xB8CC    맹 0xB8CD    맺 0xB8CE    먀 0xB8CF    먁 0xB8D0 
먈 0xB8D1    먕 0xB8D2    머 0xB8D3    먹 0xB8D4    먼 0xB8D5 
멀 0xB8D6    멂 0xB8D7    멈 0xB8D8    멉 0xB8D9    멋 0xB8DA 
멍 0xB8DB    멎 0xB8DC    멓 0xB8DD    메 0xB8DE    멕 0xB8DF 
멘 0xB8E0    멜 0xB8E1    멤 0xB8E2    멥 0xB8E3    멧 0xB8E4 
멨 0xB8E5    멩 0xB8E6    며 0xB8E7    멱 0xB8E8    면 0xB8E9 
멸 0xB8EA    몃 0xB8EB    몄 0xB8EC    명 0xB8ED    몇 0xB8EE 
몌 0xB8EF    모 0xB8F0    목 0xB8F1    몫 0xB8F2    몬 0xB8F3 
몰 0xB8F4    몲 0xB8F5    몸 0xB8F6    몹 0xB8F7    못 0xB8F8 
몽 0xB8F9    뫄 0xB8FA    뫈 0xB8FB    뫘 0xB8FC    뫙 0xB8FD 
뫼 0xB8FE    묀 0xB9A1    묄 0xB9A2    묍 0xB9A3    묏 0xB9A4 
묑 0xB9A5    묘 0xB9A6    묜 0xB9A7    묠 0xB9A8    묩 0xB9A9 
묫 0xB9AA    무 0xB9AB    묵 0xB9AC    묶 0xB9AD    문 0xB9AE 
묻 0xB9AF    물 0xB9B0    묽 0xB9B1    묾 0xB9B2    뭄 0xB9B3 
뭅 0xB9B4    뭇 0xB9B5    뭉 0xB9B6    뭍 0xB9B7    뭏 0xB9B8 
뭐 0xB9B9    뭔 0xB9BA    뭘 0xB9BB    뭡 0xB9BC    뭣 0xB9BD 
뭬 0xB9BE    뮈 0xB9BF    뮌 0xB9C0    뮐 0xB9C1    뮤 0xB9C2 
뮨 0xB9C3    뮬 0xB9C4    뮴 0xB9C5    뮷 0xB9C6    므 0xB9C7 
믄 0xB9C8    믈 0xB9C9    믐 0xB9CA    믓 0xB9CB    미 0xB9CC 
믹 0xB9CD    민 0xB9CE    믿 0xB9CF    밀 0xB9D0    밂 0xB9D1 
밈 0xB9D2    밉 0xB9D3    밋 0xB9D4    밌 0xB9D5    밍 0xB9D6 
및 0xB9D7    밑 0xB9D8    바 0xB9D9    박 0xB9DA    밖 0xB9DB 
밗 0xB9DC    반 0xB9DD    받 0xB9DE    발 0xB9DF    밝 0xB9E0 
밞 0xB9E1    밟 0xB9E2    밤 0xB9E3    밥 0xB9E4    밧 0xB9E5 
방 0xB9E6    밭 0xB9E7    배 0xB9E8    백 0xB9E9    밴 0xB9EA 
밸 0xB9EB    뱀 0xB9EC    뱁 0xB9ED    뱃 0xB9EE    뱄 0xB9EF 
뱅 0xB9F0    뱉 0xB9F1    뱌 0xB9F2    뱍 0xB9F3    뱐 0xB9F4 
뱝 0xB9F5    버 0xB9F6    벅 0xB9F7    번 0xB9F8    벋 0xB9F9 
벌 0xB9FA    벎 0xB9FB    범 0xB9FC    법 0xB9FD    벗 0xB9FE 
벙 0xBAA1    벚 0xBAA2    베 0xBAA3    벡 0xBAA4    벤 0xBAA5 
벧 0xBAA6    벨 0xBAA7    벰 0xBAA8    벱 0xBAA9    벳 0xBAAA 
벴 0xBAAB    벵 0xBAAC    벼 0xBAAD    벽 0xBAAE    변 0xBAAF 
별 0xBAB0    볍 0xBAB1    볏 0xBAB2    볐 0xBAB3    병 0xBAB4 
볕 0xBAB5    볘 0xBAB6    볜 0xBAB7    보 0xBAB8    복 0xBAB9 
볶 0xBABA    본 0xBABB    볼 0xBABC    봄 0xBABD    봅 0xBABE 
봇 0xBABF    봉 0xBAC0    봐 0xBAC1    봔 0xBAC2    봤 0xBAC3 
봬 0xBAC4    뵀 0xBAC5    뵈 0xBAC6    뵉 0xBAC7    뵌 0xBAC8 
뵐 0xBAC9    뵘 0xBACA    뵙 0xBACB    뵤 0xBACC    뵨 0xBACD 
부 0xBACE    북 0xBACF    분 0xBAD0    붇 0xBAD1    불 0xBAD2 
붉 0xBAD3    붊 0xBAD4    붐 0xBAD5    붑 0xBAD6    붓 0xBAD7 
붕 0xBAD8    붙 0xBAD9    붚 0xBADA    붜 0xBADB    붤 0xBADC 
붰 0xBADD    붸 0xBADE    뷔 0xBADF    뷕 0xBAE0    뷘 0xBAE1 
뷜 0xBAE2    뷩 0xBAE3    뷰 0xBAE4    뷴 0xBAE5    뷸 0xBAE6 
븀 0xBAE7    븃 0xBAE8    븅 0xBAE9    브 0xBAEA    븍 0xBAEB 
븐 0xBAEC    블 0xBAED    븜 0xBAEE    븝 0xBAEF    븟 0xBAF0 
비 0xBAF1    빅 0xBAF2    빈 0xBAF3    빌 0xBAF4    빎 0xBAF5 
빔 0xBAF6    빕 0xBAF7    빗 0xBAF8    빙 0xBAF9    빚 0xBAFA 
빛 0xBAFB    빠 0xBAFC    빡 0xBAFD    빤 0xBAFE    빨 0xBBA1 
빪 0xBBA2    빰 0xBBA3    빱 0xBBA4    빳 0xBBA5    빴 0xBBA6 
빵 0xBBA7    빻 0xBBA8    빼 0xBBA9    빽 0xBBAA    뺀 0xBBAB 
뺄 0xBBAC    뺌 0xBBAD    뺍 0xBBAE    뺏 0xBBAF    뺐 0xBBB0 
뺑 0xBBB1    뺘 0xBBB2    뺙 0xBBB3    뺨 0xBBB4    뻐 0xBBB5 
뻑 0xBBB6    뻔 0xBBB7    뻗 0xBBB8    뻘 0xBBB9    뻠 0xBBBA 
뻣 0xBBBB    뻤 0xBBBC    뻥 0xBBBD    뻬 0xBBBE    뼁 0xBBBF 
뼈 0xBBC0    뼉 0xBBC1    뼘 0xBBC2    뼙 0xBBC3    뼛 0xBBC4 
뼜 0xBBC5    뼝 0xBBC6    뽀 0xBBC7    뽁 0xBBC8    뽄 0xBBC9 
뽈 0xBBCA    뽐 0xBBCB    뽑 0xBBCC    뽕 0xBBCD    뾔 0xBBCE 
뾰 0xBBCF    뿅 0xBBD0    뿌 0xBBD1    뿍 0xBBD2    뿐 0xBBD3 
뿔 0xBBD4    뿜 0xBBD5    뿟 0xBBD6    뿡 0xBBD7    쀼 0xBBD8 
쁑 0xBBD9    쁘 0xBBDA    쁜 0xBBDB    쁠 0xBBDC    쁨 0xBBDD 
쁩 0xBBDE    삐 0xBBDF    삑 0xBBE0    삔 0xBBE1    삘 0xBBE2 
삠 0xBBE3    삡 0xBBE4    삣 0xBBE5    삥 0xBBE6    사 0xBBE7 
삭 0xBBE8    삯 0xBBE9    산 0xBBEA    삳 0xBBEB    살 0xBBEC 
삵 0xBBED    삶 0xBBEE    삼 0xBBEF    삽 0xBBF0    삿 0xBBF1 
샀 0xBBF2    상 0xBBF3    샅 0xBBF4    새 0xBBF5    색 0xBBF6 
샌 0xBBF7    샐 0xBBF8    샘 0xBBF9    샙 0xBBFA    샛 0xBBFB 
샜 0xBBFC    생 0xBBFD    샤 0xBBFE    샥 0xBCA1    샨 0xBCA2 
샬 0xBCA3    샴 0xBCA4    샵 0xBCA5    샷 0xBCA6    샹 0xBCA7 
섀 0xBCA8    섄 0xBCA9    섈 0xBCAA    섐 0xBCAB    섕 0xBCAC 
서 0xBCAD    석 0xBCAE    섞 0xBCAF    섟 0xBCB0    선 0xBCB1 
섣 0xBCB2    설 0xBCB3    섦 0xBCB4    섧 0xBCB5    섬 0xBCB6 
섭 0xBCB7    섯 0xBCB8    섰 0xBCB9    성 0xBCBA    섶 0xBCBB 
세 0xBCBC    섹 0xBCBD    센 0xBCBE    셀 0xBCBF    셈 0xBCC0 
셉 0xBCC1    셋 0xBCC2    셌 0xBCC3    셍 0xBCC4    셔 0xBCC5 
셕 0xBCC6    션 0xBCC7    셜 0xBCC8    셤 0xBCC9    셥 0xBCCA 
셧 0xBCCB    셨 0xBCCC    셩 0xBCCD    셰 0xBCCE    셴 0xBCCF 
셸 0xBCD0    솅 0xBCD1    소 0xBCD2    속 0xBCD3    솎 0xBCD4 
손 0xBCD5    솔 0xBCD6    솖 0xBCD7    솜 0xBCD8    솝 0xBCD9 
솟 0xBCDA    송 0xBCDB    솥 0xBCDC    솨 0xBCDD    솩 0xBCDE 
솬 0xBCDF    솰 0xBCE0    솽 0xBCE1    쇄 0xBCE2    쇈 0xBCE3 
쇌 0xBCE4    쇔 0xBCE5    쇗 0xBCE6    쇘 0xBCE7    쇠 0xBCE8 
쇤 0xBCE9    쇨 0xBCEA    쇰 0xBCEB    쇱 0xBCEC    쇳 0xBCED 
쇼 0xBCEE    쇽 0xBCEF    숀 0xBCF0    숄 0xBCF1    숌 0xBCF2 
숍 0xBCF3    숏 0xBCF4    숑 0xBCF5    수 0xBCF6    숙 0xBCF7 
순 0xBCF8    숟 0xBCF9    술 0xBCFA    숨 0xBCFB    숩 0xBCFC 
숫 0xBCFD    숭 0xBCFE    숯 0xBDA1    숱 0xBDA2    숲 0xBDA3 
숴 0xBDA4    쉈 0xBDA5    쉐 0xBDA6    쉑 0xBDA7    쉔 0xBDA8 
쉘 0xBDA9    쉠 0xBDAA    쉥 0xBDAB    쉬 0xBDAC    쉭 0xBDAD 
쉰 0xBDAE    쉴 0xBDAF    쉼 0xBDB0    쉽 0xBDB1    쉿 0xBDB2 
슁 0xBDB3    슈 0xBDB4    슉 0xBDB5    슐 0xBDB6    슘 0xBDB7 
슛 0xBDB8    슝 0xBDB9    스 0xBDBA    슥 0xBDBB    슨 0xBDBC 
슬 0xBDBD    슭 0xBDBE    슴 0xBDBF    습 0xBDC0    슷 0xBDC1 
승 0xBDC2    시 0xBDC3    식 0xBDC4    신 0xBDC5    싣 0xBDC6 
실 0xBDC7    싫 0xBDC8    심 0xBDC9    십 0xBDCA    싯 0xBDCB 
싱 0xBDCC    싶 0xBDCD    싸 0xBDCE    싹 0xBDCF    싻 0xBDD0 
싼 0xBDD1    쌀 0xBDD2    쌈 0xBDD3    쌉 0xBDD4    쌌 0xBDD5 
쌍 0xBDD6    쌓 0xBDD7    쌔 0xBDD8    쌕 0xBDD9    쌘 0xBDDA 
쌜 0xBDDB    쌤 0xBDDC    쌥 0xBDDD    쌨 0xBDDE    쌩 0xBDDF 
썅 0xBDE0    써 0xBDE1    썩 0xBDE2    썬 0xBDE3    썰 0xBDE4 
썲 0xBDE5    썸 0xBDE6    썹 0xBDE7    썼 0xBDE8    썽 0xBDE9 
쎄 0xBDEA    쎈 0xBDEB    쎌 0xBDEC    쏀 0xBDED    쏘 0xBDEE 
쏙 0xBDEF    쏜 0xBDF0    쏟 0xBDF1    쏠 0xBDF2    쏢 0xBDF3 
쏨 0xBDF4    쏩 0xBDF5    쏭 0xBDF6    쏴 0xBDF7    쏵 0xBDF8 
쏸 0xBDF9    쐈 0xBDFA    쐐 0xBDFB    쐤 0xBDFC    쐬 0xBDFD 
쐰 0xBDFE    쐴 0xBEA1    쐼 0xBEA2    쐽 0xBEA3    쑈 0xBEA4 
쑤 0xBEA5    쑥 0xBEA6    쑨 0xBEA7    쑬 0xBEA8    쑴 0xBEA9 
쑵 0xBEAA    쑹 0xBEAB    쒀 0xBEAC    쒔 0xBEAD    쒜 0xBEAE 
쒸 0xBEAF    쒼 0xBEB0    쓩 0xBEB1    쓰 0xBEB2    쓱 0xBEB3 
쓴 0xBEB4    쓸 0xBEB5    쓺 0xBEB6    쓿 0xBEB7    씀 0xBEB8 
씁 0xBEB9    씌 0xBEBA    씐 0xBEBB    씔 0xBEBC    씜 0xBEBD 
씨 0xBEBE    씩 0xBEBF    씬 0xBEC0    씰 0xBEC1    씸 0xBEC2 
씹 0xBEC3    씻 0xBEC4    씽 0xBEC5    아 0xBEC6    악 0xBEC7 
안 0xBEC8    앉 0xBEC9    않 0xBECA    알 0xBECB    앍 0xBECC 
앎 0xBECD    앓 0xBECE    암 0xBECF    압 0xBED0    앗 0xBED1 
았 0xBED2    앙 0xBED3    앝 0xBED4    앞 0xBED5    애 0xBED6 
액 0xBED7    앤 0xBED8    앨 0xBED9    앰 0xBEDA    앱 0xBEDB 
앳 0xBEDC    앴 0xBEDD    앵 0xBEDE    야 0xBEDF    약 0xBEE0 
얀 0xBEE1    얄 0xBEE2    얇 0xBEE3    얌 0xBEE4    얍 0xBEE5 
얏 0xBEE6    양 0xBEE7    얕 0xBEE8    얗 0xBEE9    얘 0xBEEA 
얜 0xBEEB    얠 0xBEEC    얩 0xBEED    어 0xBEEE    억 0xBEEF 
언 0xBEF0    얹 0xBEF1    얻 0xBEF2    얼 0xBEF3    얽 0xBEF4 
얾 0xBEF5    엄 0xBEF6    업 0xBEF7    없 0xBEF8    엇 0xBEF9 
었 0xBEFA    엉 0xBEFB    엊 0xBEFC    엌 0xBEFD    엎 0xBEFE 
에 0xBFA1    엑 0xBFA2    엔 0xBFA3    엘 0xBFA4    엠 0xBFA5 
엡 0xBFA6    엣 0xBFA7    엥 0xBFA8    여 0xBFA9    역 0xBFAA 
엮 0xBFAB    연 0xBFAC    열 0xBFAD    엶 0xBFAE    엷 0xBFAF 
염 0xBFB0    엽 0xBFB1    엾 0xBFB2    엿 0xBFB3    였 0xBFB4 
영 0xBFB5    옅 0xBFB6    옆 0xBFB7    옇 0xBFB8    예 0xBFB9 
옌 0xBFBA    옐 0xBFBB    옘 0xBFBC    옙 0xBFBD    옛 0xBFBE 
옜 0xBFBF    오 0xBFC0    옥 0xBFC1    온 0xBFC2    올 0xBFC3 
옭 0xBFC4    옮 0xBFC5    옰 0xBFC6    옳 0xBFC7    옴 0xBFC8 
옵 0xBFC9    옷 0xBFCA    옹 0xBFCB    옻 0xBFCC    와 0xBFCD 
왁 0xBFCE    완 0xBFCF    왈 0xBFD0    왐 0xBFD1    왑 0xBFD2 
왓 0xBFD3    왔 0xBFD4    왕 0xBFD5    왜 0xBFD6    왝 0xBFD7 
왠 0xBFD8    왬 0xBFD9    왯 0xBFDA    왱 0xBFDB    외 0xBFDC 
왹 0xBFDD    왼 0xBFDE    욀 0xBFDF    욈 0xBFE0    욉 0xBFE1 
욋 0xBFE2    욍 0xBFE3    요 0xBFE4    욕 0xBFE5    욘 0xBFE6 
욜 0xBFE7    욤 0xBFE8    욥 0xBFE9    욧 0xBFEA    용 0xBFEB 
우 0xBFEC    욱 0xBFED    운 0xBFEE    울 0xBFEF    욹 0xBFF0 
욺 0xBFF1    움 0xBFF2    웁 0xBFF3    웃 0xBFF4    웅 0xBFF5 
워 0xBFF6    웍 0xBFF7    원 0xBFF8    월 0xBFF9    웜 0xBFFA 
웝 0xBFFB    웠 0xBFFC    웡 0xBFFD    웨 0xBFFE    웩 0xC0A1 
웬 0xC0A2    웰 0xC0A3    웸 0xC0A4    웹 0xC0A5    웽 0xC0A6 
위 0xC0A7    윅 0xC0A8    윈 0xC0A9    윌 0xC0AA    윔 0xC0AB 
윕 0xC0AC    윗 0xC0AD    윙 0xC0AE    유 0xC0AF    육 0xC0B0 
윤 0xC0B1    율 0xC0B2    윰 0xC0B3    윱 0xC0B4    윳 0xC0B5 
융 0xC0B6    윷 0xC0B7    으 0xC0B8    윽 0xC0B9    은 0xC0BA 
을 0xC0BB    읊 0xC0BC    음 0xC0BD    읍 0xC0BE    읏 0xC0BF 
응 0xC0C0    읒 0xC0C1    읓 0xC0C2    읔 0xC0C3    읕 0xC0C4 
읖 0xC0C5    읗 0xC0C6    의 0xC0C7    읜 0xC0C8    읠 0xC0C9 
읨 0xC0CA    읫 0xC0CB    이 0xC0CC    익 0xC0CD    인 0xC0CE 
일 0xC0CF    읽 0xC0D0    읾 0xC0D1    잃 0xC0D2    임 0xC0D3 
입 0xC0D4    잇 0xC0D5    있 0xC0D6    잉 0xC0D7    잊 0xC0D8 
잎 0xC0D9    자 0xC0DA    작 0xC0DB    잔 0xC0DC    잖 0xC0DD 
잗 0xC0DE    잘 0xC0DF    잚 0xC0E0    잠 0xC0E1    잡 0xC0E2 
잣 0xC0E3    잤 0xC0E4    장 0xC0E5    잦 0xC0E6    재 0xC0E7 
잭 0xC0E8    잰 0xC0E9    잴 0xC0EA    잼 0xC0EB    잽 0xC0EC 
잿 0xC0ED    쟀 0xC0EE    쟁 0xC0EF    쟈 0xC0F0    쟉 0xC0F1 
쟌 0xC0F2    쟎 0xC0F3    쟐 0xC0F4    쟘 0xC0F5    쟝 0xC0F6 
쟤 0xC0F7    쟨 0xC0F8    쟬 0xC0F9    저 0xC0FA    적 0xC0FB 
전 0xC0FC    절 0xC0FD    젊 0xC0FE    점 0xC1A1    접 0xC1A2 
젓 0xC1A3    정 0xC1A4    젖 0xC1A5    제 0xC1A6    젝 0xC1A7 
젠 0xC1A8    젤 0xC1A9    젬 0xC1AA    젭 0xC1AB    젯 0xC1AC 
젱 0xC1AD    져 0xC1AE    젼 0xC1AF    졀 0xC1B0    졈 0xC1B1 
졉 0xC1B2    졌 0xC1B3    졍 0xC1B4    졔 0xC1B5    조 0xC1B6 
족 0xC1B7    존 0xC1B8    졸 0xC1B9    졺 0xC1BA    좀 0xC1BB 
좁 0xC1BC    좃 0xC1BD    종 0xC1BE    좆 0xC1BF    좇 0xC1C0 
좋 0xC1C1    좌 0xC1C2    좍 0xC1C3    좔 0xC1C4    좝 0xC1C5 
좟 0xC1C6    좡 0xC1C7    좨 0xC1C8    좼 0xC1C9    좽 0xC1CA 
죄 0xC1CB    죈 0xC1CC    죌 0xC1CD    죔 0xC1CE    죕 0xC1CF 
죗 0xC1D0    죙 0xC1D1    죠 0xC1D2    죡 0xC1D3    죤 0xC1D4 
죵 0xC1D5    주 0xC1D6    죽 0xC1D7    준 0xC1D8    줄 0xC1D9 
줅 0xC1DA    줆 0xC1DB    줌 0xC1DC    줍 0xC1DD    줏 0xC1DE 
중 0xC1DF    줘 0xC1E0    줬 0xC1E1    줴 0xC1E2    쥐 0xC1E3 
쥑 0xC1E4    쥔 0xC1E5    쥘 0xC1E6    쥠 0xC1E7    쥡 0xC1E8 
쥣 0xC1E9    쥬 0xC1EA    쥰 0xC1EB    쥴 0xC1EC    쥼 0xC1ED 
즈 0xC1EE    즉 0xC1EF    즌 0xC1F0    즐 0xC1F1    즘 0xC1F2 
즙 0xC1F3    즛 0xC1F4    증 0xC1F5    지 0xC1F6    직 0xC1F7 
진 0xC1F8    짇 0xC1F9    질 0xC1FA    짊 0xC1FB    짐 0xC1FC 
집 0xC1FD    짓 0xC1FE    징 0xC2A1    짖 0xC2A2    짙 0xC2A3 
짚 0xC2A4    짜 0xC2A5    짝 0xC2A6    짠 0xC2A7    짢 0xC2A8 
짤 0xC2A9    짧 0xC2AA    짬 0xC2AB    짭 0xC2AC    짯 0xC2AD 
짰 0xC2AE    짱 0xC2AF    째 0xC2B0    짹 0xC2B1    짼 0xC2B2 
쨀 0xC2B3    쨈 0xC2B4    쨉 0xC2B5    쨋 0xC2B6    쨌 0xC2B7 
쨍 0xC2B8    쨔 0xC2B9    쨘 0xC2BA    쨩 0xC2BB    쩌 0xC2BC 
쩍 0xC2BD    쩐 0xC2BE    쩔 0xC2BF    쩜 0xC2C0    쩝 0xC2C1 
쩟 0xC2C2    쩠 0xC2C3    쩡 0xC2C4    쩨 0xC2C5    쩽 0xC2C6 
쪄 0xC2C7    쪘 0xC2C8    쪼 0xC2C9    쪽 0xC2CA    쫀 0xC2CB 
쫄 0xC2CC    쫌 0xC2CD    쫍 0xC2CE    쫏 0xC2CF    쫑 0xC2D0 
쫓 0xC2D1    쫘 0xC2D2    쫙 0xC2D3    쫠 0xC2D4    쫬 0xC2D5 
쫴 0xC2D6    쬈 0xC2D7    쬐 0xC2D8    쬔 0xC2D9    쬘 0xC2DA 
쬠 0xC2DB    쬡 0xC2DC    쭁 0xC2DD    쭈 0xC2DE    쭉 0xC2DF 
쭌 0xC2E0    쭐 0xC2E1    쭘 0xC2E2    쭙 0xC2E3    쭝 0xC2E4 
쭤 0xC2E5    쭸 0xC2E6    쭹 0xC2E7    쮜 0xC2E8    쮸 0xC2E9 
쯔 0xC2EA    쯤 0xC2EB    쯧 0xC2EC    쯩 0xC2ED    찌 0xC2EE 
찍 0xC2EF    찐 0xC2F0    찔 0xC2F1    찜 0xC2F2    찝 0xC2F3 
찡 0xC2F4    찢 0xC2F5    찧 0xC2F6    차 0xC2F7    착 0xC2F8 
찬 0xC2F9    찮 0xC2FA    찰 0xC2FB    참 0xC2FC    찹 0xC2FD 
찻 0xC2FE    찼 0xC3A1    창 0xC3A2    찾 0xC3A3    채 0xC3A4 
책 0xC3A5    챈 0xC3A6    챌 0xC3A7    챔 0xC3A8    챕 0xC3A9 
챗 0xC3AA    챘 0xC3AB    챙 0xC3AC    챠 0xC3AD    챤 0xC3AE 
챦 0xC3AF    챨 0xC3B0    챰 0xC3B1    챵 0xC3B2    처 0xC3B3 
척 0xC3B4    천 0xC3B5    철 0xC3B6    첨 0xC3B7    첩 0xC3B8 
첫 0xC3B9    첬 0xC3BA    청 0xC3BB    체 0xC3BC    첵 0xC3BD 
첸 0xC3BE    첼 0xC3BF    쳄 0xC3C0    쳅 0xC3C1    쳇 0xC3C2 
쳉 0xC3C3    쳐 0xC3C4    쳔 0xC3C5    쳤 0xC3C6    쳬 0xC3C7 
쳰 0xC3C8    촁 0xC3C9    초 0xC3CA    촉 0xC3CB    촌 0xC3CC 
촐 0xC3CD    촘 0xC3CE    촙 0xC3CF    촛 0xC3D0    총 0xC3D1 
촤 0xC3D2    촨 0xC3D3    촬 0xC3D4    촹 0xC3D5    최 0xC3D6 
쵠 0xC3D7    쵤 0xC3D8    쵬 0xC3D9    쵭 0xC3DA    쵯 0xC3DB 
쵱 0xC3DC    쵸 0xC3DD    춈 0xC3DE    추 0xC3DF    축 0xC3E0 
춘 0xC3E1    출 0xC3E2    춤 0xC3E3    춥 0xC3E4    춧 0xC3E5 
충 0xC3E6    춰 0xC3E7    췄 0xC3E8    췌 0xC3E9    췐 0xC3EA 
취 0xC3EB    췬 0xC3EC    췰 0xC3ED    췸 0xC3EE    췹 0xC3EF 
췻 0xC3F0    췽 0xC3F1    츄 0xC3F2    츈 0xC3F3    츌 0xC3F4 
츔 0xC3F5    츙 0xC3F6    츠 0xC3F7    측 0xC3F8    츤 0xC3F9 
츨 0xC3FA    츰 0xC3FB    츱 0xC3FC    츳 0xC3FD    층 0xC3FE 
치 0xC4A1    칙 0xC4A2    친 0xC4A3    칟 0xC4A4    칠 0xC4A5 
칡 0xC4A6    침 0xC4A7    칩 0xC4A8    칫 0xC4A9    칭 0xC4AA 
카 0xC4AB    칵 0xC4AC    칸 0xC4AD    칼 0xC4AE    캄 0xC4AF 
캅 0xC4B0    캇 0xC4B1    캉 0xC4B2    캐 0xC4B3    캑 0xC4B4 
캔 0xC4B5    캘 0xC4B6    캠 0xC4B7    캡 0xC4B8    캣 0xC4B9 
캤 0xC4BA    캥 0xC4BB    캬 0xC4BC    캭 0xC4BD    컁 0xC4BE 
커 0xC4BF    컥 0xC4C0    컨 0xC4C1    컫 0xC4C2    컬 0xC4C3 
컴 0xC4C4    컵 0xC4C5    컷 0xC4C6    컸 0xC4C7    컹 0xC4C8 
케 0xC4C9    켁 0xC4CA    켄 0xC4CB    켈 0xC4CC    켐 0xC4CD 
켑 0xC4CE    켓 0xC4CF    켕 0xC4D0    켜 0xC4D1    켠 0xC4D2 
켤 0xC4D3    켬 0xC4D4    켭 0xC4D5    켯 0xC4D6    켰 0xC4D7 
켱 0xC4D8    켸 0xC4D9    코 0xC4DA    콕 0xC4DB    콘 0xC4DC 
콜 0xC4DD    콤 0xC4DE    콥 0xC4DF    콧 0xC4E0    콩 0xC4E1 
콰 0xC4E2    콱 0xC4E3    콴 0xC4E4    콸 0xC4E5    쾀 0xC4E6 
쾅 0xC4E7    쾌 0xC4E8    쾡 0xC4E9    쾨 0xC4EA    쾰 0xC4EB 
쿄 0xC4EC    쿠 0xC4ED    쿡 0xC4EE    쿤 0xC4EF    쿨 0xC4F0 
쿰 0xC4F1    쿱 0xC4F2    쿳 0xC4F3    쿵 0xC4F4    쿼 0xC4F5 
퀀 0xC4F6    퀄 0xC4F7    퀑 0xC4F8    퀘 0xC4F9    퀭 0xC4FA 
퀴 0xC4FB    퀵 0xC4FC    퀸 0xC4FD    퀼 0xC4FE    큄 0xC5A1 
큅 0xC5A2    큇 0xC5A3    큉 0xC5A4    큐 0xC5A5    큔 0xC5A6 
큘 0xC5A7    큠 0xC5A8    크 0xC5A9    큭 0xC5AA    큰 0xC5AB 
클 0xC5AC    큼 0xC5AD    큽 0xC5AE    킁 0xC5AF    키 0xC5B0 
킥 0xC5B1    킨 0xC5B2    킬 0xC5B3    킴 0xC5B4    킵 0xC5B5 
킷 0xC5B6    킹 0xC5B7    타 0xC5B8    탁 0xC5B9    탄 0xC5BA 
탈 0xC5BB    탉 0xC5BC    탐 0xC5BD    탑 0xC5BE    탓 0xC5BF 
탔 0xC5C0    탕 0xC5C1    태 0xC5C2    택 0xC5C3    탠 0xC5C4 
탤 0xC5C5    탬 0xC5C6    탭 0xC5C7    탯 0xC5C8    탰 0xC5C9 
탱 0xC5CA    탸 0xC5CB    턍 0xC5CC    터 0xC5CD    턱 0xC5CE 
턴 0xC5CF    털 0xC5D0    턺 0xC5D1    텀 0xC5D2    텁 0xC5D3 
텃 0xC5D4    텄 0xC5D5    텅 0xC5D6    테 0xC5D7    텍 0xC5D8 
텐 0xC5D9    텔 0xC5DA    템 0xC5DB    텝 0xC5DC    텟 0xC5DD 
텡 0xC5DE    텨 0xC5DF    텬 0xC5E0    텼 0xC5E1    톄 0xC5E2 
톈 0xC5E3    토 0xC5E4    톡 0xC5E5    톤 0xC5E6    톨 0xC5E7 
톰 0xC5E8    톱 0xC5E9    톳 0xC5EA    통 0xC5EB    톺 0xC5EC 
톼 0xC5ED    퇀 0xC5EE    퇘 0xC5EF    퇴 0xC5F0    퇸 0xC5F1 
툇 0xC5F2    툉 0xC5F3    툐 0xC5F4    투 0xC5F5    툭 0xC5F6 
툰 0xC5F7    툴 0xC5F8    툼 0xC5F9    툽 0xC5FA    툿 0xC5FB 
퉁 0xC5FC    퉈 0xC5FD    퉜 0xC5FE    퉤 0xC6A1    튀 0xC6A2 
튁 0xC6A3    튄 0xC6A4    튈 0xC6A5    튐 0xC6A6    튑 0xC6A7 
튕 0xC6A8    튜 0xC6A9    튠 0xC6AA    튤 0xC6AB    튬 0xC6AC 
튱 0xC6AD    트 0xC6AE    특 0xC6AF    튼 0xC6B0    튿 0xC6B1 
틀 0xC6B2    틂 0xC6B3    틈 0xC6B4    틉 0xC6B5    틋 0xC6B6 
틔 0xC6B7    틘 0xC6B8    틜 0xC6B9    틤 0xC6BA    틥 0xC6BB 
티 0xC6BC    틱 0xC6BD    틴 0xC6BE    틸 0xC6BF    팀 0xC6C0 
팁 0xC6C1    팃 0xC6C2    팅 0xC6C3    파 0xC6C4    팍 0xC6C5 
팎 0xC6C6    판 0xC6C7    팔 0xC6C8    팖 0xC6C9    팜 0xC6CA 
팝 0xC6CB    팟 0xC6CC    팠 0xC6CD    팡 0xC6CE    팥 0xC6CF 
패 0xC6D0    팩 0xC6D1    팬 0xC6D2    팰 0xC6D3    팸 0xC6D4 
팹 0xC6D5    팻 0xC6D6    팼 0xC6D7    팽 0xC6D8    퍄 0xC6D9 
퍅 0xC6DA    퍼 0xC6DB    퍽 0xC6DC    펀 0xC6DD    펄 0xC6DE 
펌 0xC6DF    펍 0xC6E0    펏 0xC6E1    펐 0xC6E2    펑 0xC6E3 
페 0xC6E4    펙 0xC6E5    펜 0xC6E6    펠 0xC6E7    펨 0xC6E8 
펩 0xC6E9    펫 0xC6EA    펭 0xC6EB    펴 0xC6EC    편 0xC6ED 
펼 0xC6EE    폄 0xC6EF    폅 0xC6F0    폈 0xC6F1    평 0xC6F2 
폐 0xC6F3    폘 0xC6F4    폡 0xC6F5    폣 0xC6F6    포 0xC6F7 
폭 0xC6F8    폰 0xC6F9    폴 0xC6FA    폼 0xC6FB    폽 0xC6FC 
폿 0xC6FD    퐁 0xC6FE    퐈 0xC7A1    퐝 0xC7A2    푀 0xC7A3 
푄 0xC7A4    표 0xC7A5    푠 0xC7A6    푤 0xC7A7    푭 0xC7A8 
푯 0xC7A9    푸 0xC7AA    푹 0xC7AB    푼 0xC7AC    푿 0xC7AD 
풀 0xC7AE    풂 0xC7AF    품 0xC7B0    풉 0xC7B1    풋 0xC7B2 
풍 0xC7B3    풔 0xC7B4    풩 0xC7B5    퓌 0xC7B6    퓐 0xC7B7 
퓔 0xC7B8    퓜 0xC7B9    퓟 0xC7BA    퓨 0xC7BB    퓬 0xC7BC 
퓰 0xC7BD    퓸 0xC7BE    퓻 0xC7BF    퓽 0xC7C0    프 0xC7C1 
픈 0xC7C2    플 0xC7C3    픔 0xC7C4    픕 0xC7C5    픗 0xC7C6 
피 0xC7C7    픽 0xC7C8    핀 0xC7C9    필 0xC7CA    핌 0xC7CB 
핍 0xC7CC    핏 0xC7CD    핑 0xC7CE    하 0xC7CF    학 0xC7D0 
한 0xC7D1    할 0xC7D2    핥 0xC7D3    함 0xC7D4    합 0xC7D5 
핫 0xC7D6    항 0xC7D7    해 0xC7D8    핵 0xC7D9    핸 0xC7DA 
핼 0xC7DB    햄 0xC7DC    햅 0xC7DD    햇 0xC7DE    했 0xC7DF 
행 0xC7E0    햐 0xC7E1    향 0xC7E2    허 0xC7E3    헉 0xC7E4 
헌 0xC7E5    헐 0xC7E6    헒 0xC7E7    험 0xC7E8    헙 0xC7E9 
헛 0xC7EA    헝 0xC7EB    헤 0xC7EC    헥 0xC7ED    헨 0xC7EE 
헬 0xC7EF    헴 0xC7F0    헵 0xC7F1    헷 0xC7F2    헹 0xC7F3 
혀 0xC7F4    혁 0xC7F5    현 0xC7F6    혈 0xC7F7    혐 0xC7F8 
협 0xC7F9    혓 0xC7FA    혔 0xC7FB    형 0xC7FC    혜 0xC7FD 
혠 0xC7FE    혤 0xC8A1    혭 0xC8A2    호 0xC8A3    혹 0xC8A4 
혼 0xC8A5    홀 0xC8A6    홅 0xC8A7    홈 0xC8A8    홉 0xC8A9 
홋 0xC8AA    홍 0xC8AB    홑 0xC8AC    화 0xC8AD    확 0xC8AE 
환 0xC8AF    활 0xC8B0    홧 0xC8B1    황 0xC8B2    홰 0xC8B3 
홱 0xC8B4    홴 0xC8B5    횃 0xC8B6    횅 0xC8B7    회 0xC8B8 
획 0xC8B9    횐 0xC8BA    횔 0xC8BB    횝 0xC8BC    횟 0xC8BD 
횡 0xC8BE    효 0xC8BF    횬 0xC8C0    횰 0xC8C1    횹 0xC8C2 
횻 0xC8C3    후 0xC8C4    훅 0xC8C5    훈 0xC8C6    훌 0xC8C7 
훑 0xC8C8    훔 0xC8C9    훗 0xC8CA    훙 0xC8CB    훠 0xC8CC 
훤 0xC8CD    훨 0xC8CE    훰 0xC8CF    훵 0xC8D0    훼 0xC8D1 
훽 0xC8D2    휀 0xC8D3    휄 0xC8D4    휑 0xC8D5    휘 0xC8D6 
휙 0xC8D7    휜 0xC8D8    휠 0xC8D9    휨 0xC8DA    휩 0xC8DB 
휫 0xC8DC    휭 0xC8DD    휴 0xC8DE    휵 0xC8DF    휸 0xC8E0 
휼 0xC8E1    흄 0xC8E2    흇 0xC8E3    흉 0xC8E4    흐 0xC8E5 
흑 0xC8E6    흔 0xC8E7    흖 0xC8E8    흗 0xC8E9    흘 0xC8EA 
흙 0xC8EB    흠 0xC8EC    흡 0xC8ED    흣 0xC8EE    흥 0xC8EF 
흩 0xC8F0    희 0xC8F1    흰 0xC8F2    흴 0xC8F3    흼 0xC8F4 
흽 0xC8F5    힁 0xC8F6    히 0xC8F7    힉 0xC8F8    힌 0xC8F9 
힐 0xC8FA    힘 0xC8FB    힙 0xC8FC    힛 0xC8FD    힝 0xC8FE
 아이팟 터치(iPod Touch)의 한글 지원 소식에 감명받아서 새벽에 잠깐 짬을내서 Nintendo DS( NDS )용 한글 입력기를 만들어봤습니다. ^^;;; 아직은 테스트 상태라서 많이 불안정한데, 일단 동작은 하니까 스크린 샷을 올려봅니다. ^^

사용자 삽입 이미지

한글 입력 테스트


 

 아이팟 터치의 키보드가 너무 예뻐서 그대로 올려봤는데, NDS에 올리니 약간 포스가 떨어지는군요. ^^;;;
 
 오늘 테스트 덕분에 한글 출력에 한가지 문제를 발견했습니다. 제가 NDS에 올린 폰트는 완성형이었는데, 윈도우에서는 확장 완성형 폰트를 사용했습니다. @0@)/!!! 왠지 '틍'과 같은 단어를 입력할때 깨어진다 싶었더니... 확장 완성형에서 지원하는 문자라서 그랬습니다.

 확장 완성형 폰트를 만들어 넣는 것은 큰 문제가 안되지만.... 폰트 데이터가 너무 커지면 용량의 한계때문에 문제가 발생할 소지가 있어서 상당히 조심스럽습니다. 이 문제는 좀더 고민해본 다음 결정해야겠습니다. ^^;;;

 벌써 해가뜨네요... ㅜ_ㅜ... 오늘도 일찍 일어나기는 힘들 것 같습니다. ㅜ_ㅜ...
 
=== 2007/11/26 추가 ===

 너무 많은 분들이 이 글을 보러 오셔서, 오늘까지 진행상황을 더 추가했습니다. ^^ 폰트를 더 추가할까 하다가 NDS의 용량이 그리 크지 않아서 포기하고 한글을 조합하는 오토마타를 조금 손봐서 2360자용 한글 입력기를 만들었습니다.

 현재 한글/영어 대소문자/숫자 및 특수문자 모두 입력 가능하며 특수문자는 자판 숫자 및 배열 때문에 빠진 것이 좀 있습니다. 추후 작업을 조금 더 진행하여 특수문자를 다 추가해야 할 예정입니다. (혹시 아이팟 가지고 계신분 있으시면 특수문자쪽 키보드 자판 배열 좀 알려주세요 ^^;;;)

 완성형 한글 입력 및 영어, 숫자 입력을 캡쳐한 화면입니다. 다음에 작업이 완료되면 새글을 통해 릴리즈하겠습니다. ^^
사용자 삽입 이미지

영어 대문자 입력

사용자 삽입 이미지

영어 소문자 입력



사용자 삽입 이미지

한글 입력

사용자 삽입 이미지

한글 및 숫자/특수문자 입력


 ps) 작업을 하다보니 어느새 날이 밝아오는군요. ㅜ_ㅜ...


=== 2007/12/01 추가 ===

 프로토타입 개발을 완료했습니다. ^^ 자세한 내용은 http://kkamagui.tistory.com/238 에서 확인하실 수 있습니다. 동영상과 프로그램을 포함하고 있습니다. ^^
 아는 후배에게 방금 들었습니다. POC 2007 세미나에서 NDS를 이용한 해킹을 주제로 "Hacking with Nintendo DS" 를 발표하셨던 i3eat 님께서 제 사이트를 참고하셨다더군요. 세미나에 참석했던 후배가 물어봤는데 그렇게 말씀하셨답니다.

 POC 2007에 직접 가지 못해서 자세한 내용은 알지 못하지만, 왠지 큰 세미나에 제가 한몫 한 것 같아서 기분이 좋군요.(사실 많이 뿌듯하다는 ^ㅡ^). 나중에 기회가되면 POC 2007 세미나 자료를 봐야겠군요.

 NDS 홈브루(Homebrew) 개발에 대한 내용은 http://kkamagui.tistory.com/category/NDS%20홈브루(Homebrew)에서 보실 수 있습니다. ^^

 나중에 i3eat 님을 만나게되면 인사라도 드려야겠습니다. ^^  
 왠지 오늘은 잠이 잘 올것 같네요~ 다들 좋은밤 되시길~ ^^
 티스토리에 글을 쓰다가 문득 위에 "동영상" 이라는 버튼이 보였습니다. 이게 뭘까? 한참을 고민하다가 설마하는 생각에 제가 가지고 있는 동영상을 올려보니 블로그에 표시되는 것이 아니겠습니까? @0@ 이글루스에서는 상상도 할 수 없던 것이 티스토리로 옮기니까 가능해 지는군요.

 생각난 김에 예전에 만들어 뒀던 닌텐도 DS( NDS )용 작은 커널(Kernel) 시연 동영상을 올립니다. ^^ 커널은 OS(Operating System)의 핵심적인 부분으로 주변기기 제어에 필요한 잡다한 코드들을 제외한 핵심이라고 보시면 되는데요, 어차피 닌텐도 DS( NDS )는 그렇게 주변기기가 많지 않고 마이크 말고는 다 지원하고 있으니 작은 OS라고 해도 괜찮을 것 같습니다.
 
 아래에서 시연 동영상을 보실 수 있습니다. 시작 화면이 시커멓게 나와서 조금 안타깝군요. ㅡ_ㅜ... 운영체제답게 멀티 태스킹을 지원하고 사운드 및 키패드를 이용해서 게임(??)을 Play 할 수 있습니다. 정겨운 버블버블 음악이 게임 내내 흘러나오고 @$를 다 먹으면 게임이 끝납니다. ^^;;;

 구현에 관한 자세한 내용은 제 티스토리의 http://kkamagui.tistory.com/40 에서 보실 수 있고 NDS 홈브루 개발에 대한 내용은 제 티스토리의 홈브루 개발 카테고리 http://kkamagui.tistory.com/category/NDS%20홈브루(Homebrew) 에서 보실 수 있습니다.

 그럼 화질은 좀 떨어지지만 즐겁게 감상하시길 바랍니다. ^^;;;;

 


참고. 터치스크린(Touch Screen)의 튐 현상 해결방안

들어가기 전에...

0.2007/09/27 터치 문제 해결

  • Z 값을 이용해서 터치 스크린의 문제를 해결함
  • GBA Tek에 있는 소스와 PALib 쪽의 ARM7 함수를 섞어서 해결
    • sPressure = (((( touch.px + 1 ) * touch.z2) >> 6) / touch.z1) - (touch.px >> 6);
    • 위 식의 문제는 펜으로 강하게 터치했을 때 터치 스크린의 좌측의 값은 0이고 우측의 값은 3이 나온다는 것임
    • 터치 스크린의 X 좌표를 이용하게 되어있으므로 좌측의 경우 X 좌표가 0에 가까워져서 분해능이 떨어지는 문제도 발생(좌측의 경우 강약에 따라서 01, 우측의 경우 330 정도의 범위)
    • sPressure1 = ( touch.z2/( touch.z1 - 1 ) );
    • 위 식의 문제는 강하게 터치했을때 터치 스크린의 좌측 일정 범위를 넘어서면 값이 튀는 것임(X좌표의 범위가 0~20 정도일때 값이 급격하게 변함)
    • 그외의 범위에서는 세게 눌렀을 때 값이 우측에서 좌측으로 갈때 3->2->1->0 정도로 변함
  • 결국 두 값의 MIN을 취해서 해결
  • 어느정도 세기의 문제와 X 축 좌표에 따른 압력값 편차의 문제를 해결함
  • 압력값은 5정도 보다 작도록 설정하면 안튀는 것 같음
  • 아래는 사용한 코드
    // 압력계산 코드  
    sPressure = (((( touch.px + 1 ) \* touch.z2) >> 6) / touch.z1) - (touch.px >> 6);  
    sPressure1 = ( touch.z2/( touch.z1 - 1 ) );  
    if( sPressure1 < sPressure )  
    {  
        sPressure2 = sPressure1;  
    }  
    else  
    {  
        sPressure2 = sPressure;  
    }

    // 아래의 Touch와 XY버튼은 특별한 케이스이다.  
    // Touch Down  
    // Down 메시지는 계속 보낸다.  
    if( ( usSpecial & ( 0x01 << 6 ) ) && ( touch.x != 0 ) && ( touch.y != 0 ) && **( sPressure2 < 5 )** )

0.2007/09/22 터치 문제 발견

  • 아직 확실하게 터치 스크린 튀는 문제가 해결이 안됬음
    • 아무래도 확실하게 해결하기는 어려운 문제인듯 함... 약간의 기교로 처리할 수는 있는데... 그것 또한 완전하지 않음
  • 결국 원래의 Touch 함수를 사용하고 Z축 값을 입력 받아서 사용하도록 만듬
  • PALib 쪽의 ARM7 의 Main 함수를 참고
  • 아래는 수정된 VcountHandler() 함수
    /**  
        VCount Handler 함수  
            템플릿의 기본 소스를 아주 간단한 로직으로 수정  
    */  
    void VcountHandler()  
    {  
        touchPosition tempPos;  
        unsigned short usButton;  
        unsigned long ulTemp;  
        int a, b;  

        ulTemp = \_touchReadTemperature( &a, &b );  
        usButton = REG\_KEYXY;  

        // 만약 Touch가 되었으면 좌표를 읽어서 설정한다.  
        if( ( usButton & ( 1 << 6 ) ) == 0x00 )  
        {  
            tempPos = touchReadXY();
            if( tempPos.x == 0 || tempPos.y == 0 )  
            {  
                IPC->mailBusy = 1;  
                IPC->buttons = usButton | (1 << 6);  
                IPC->temperature = ulTemp;  
                IPC->mailBusy = 0;  
            }  
            else  
            {     
                IPC->mailBusy = 1;  
                IPC->touchX         = tempPos.x;  
                IPC->touchY   = tempPos.y;  
                IPC->touchXpx  = tempPos.px;  
                IPC->touchYpx  = tempPos.py;  
                IPC->touchZ1  = tempPos.z1;  
                IPC->touchZ2  = tempPos.z2;  
                IPC->temperature    = ulTemp;  
                IPC->buttons        = usButton;  
                IPC->mailBusy = 0;  
            }  
        }  
        else  
        {  
            IPC->mailBusy = 1;  
            IPC->buttons = usButton;  
            IPC->temperature = ulTemp;  
            IPC->mailBusy = 0;  
        }  

        g\_iVCount ^= (80 ^ 130);  
        SetYtrigger(g\_iVCount);  
    } 
  • 아래는 사용하는 소스다
        // Button의 상태를 읽는다.  
        // Mail이 Busy이면 대기한다.  
        while( IPC->mailBusy );  
        usSpecial = ~( IPC->buttons );  
        touch.x = IPC->touchX;  
        touch.y = IPC->touchY;  
        touch.px = IPC->touchXpx;  
        touch.py = IPC->touchYpx;  
        touch.z1 = IPC->touchZ1;  
        touch.z2 = IPC->touchZ2;  
        sPressure = (((IPC->touchXpx \* IPC->touchZ2) >> 6) / IPC->touchZ1) - (IPC->touchXpx >> 6);  

        // Z축 테스트 용  
        DrawBox( ( void\* ) 0x06000000, 16, 16, 256, 32, BIT( 15 ), TRUE );  
        sprintf( vcBuffer, "%d %d %d", touch.z1, touch.z2, sPressure );  

        HanPutStr(( void\* ) 0x06000000, 16, 16, RGB15( 0x1F, 0x1F, 0x1F ) | BIT( 15 ), vcBuffer );   
        ... 
        if( ( usSpecial & ( 0x01 << 6 ) ) &&  
            ( touch.x != 0 ) && ( touch.y != 0 ) && **( sPressure < 9 )** )

0.시작하면서... 2007/09/22 이전...

문쉘의 터치 스크린 관련 소스를 분석해보면 아주 간단하게 되어있음을 알 수 있다. 문서를 읽어보면 터치 스크린에서 값을 읽는 데는 어느정도 시간이 걸리고 또한 정확한 값을 읽기 위해서는 여러번 반복해서 읽어줘야 한다는 것을 알 수 있다. 문쉘의 터치스크린 소스 분석 부분은 07 문쉘(Moon shell)의 터치스크린(Touch Screen) 소스를 참고하고 터치스크린 제어에 대한 부분은 06 키패드(KeyPad) 및 터치스크린(Touch Screen) 제어 부분을 참고하도록 하자.

왜 터치스크린의 값이 이토록 튀는 것일까? 아래와 같은 문제점때문이 아닐까 추측하고 이것을 해결하려 노력했다.

  • X의 좌표값을 읽고 Y의 좌표값을 읽었을 때 SPI를 통해서 값을 읽고 이것을 몇변 반복하는 동안 시간이 걸린다.
  • 터치스크린으로 부터 값을 읽는 동안 언제든지 사용자의 동작에 따라 펜이 Release 될 수 있다.
  • ARM7에서 값을 쓰고 ARM9에서 값을 읽을 때 동기화의 문제

이제 위의 문제에 대해서 하나하나 살펴보도록 하자.

1.X/Y 좌표 값을 읽는 타이밍(Timing) 문제

터치스크린쪽 스펙을 보면 터치스크린의 값이 유효하지 않을 수 있으니, 여러번 읽어서 사용하라고 되어있다. 실제 코드도 그것을 증명하듯 libnds도 그렇고 문쉘의 소스도 여러번 읽게 되어있다. 실제로 유효하지 않은 값인지를 판단하는 부분은 양쪽이 다 다르지만...

여러번 읽어서 유효한 값을 찾는 것도 중요하지만, 여기에는 큰 문제가 있다. 일단 SPI를 통해서 읽기 때문에 메모리에서 바로 읽는 것 보다는 더 큰 시간이 걸리고(스펙을 보라... 클럭이 1~3MHz 정도다.. ㅡ_ㅡ;;;) X의 좌표를 여러번 읽게 되므로 Y의 좌표를 읽을 때 쯤에는 상당한 시간이 지나있다. 반복하는 회수에 따라서 차이가 있긴 하겠지만 많이 읽으면 읽을 수록 갭은 커친다.

문쉘의 경우에는 5번을 반복하되 읽은 값이 처음 읽은 값과 비교하여 오차범위가 30 내외이면 유효한 값이라 판단하고 그 값을 사용하게 된다. 5번 반복하는 것도 좋지만 어차피 유효한 값을 체크하는 것이라면 3번 정도도 충분하고 오차범위도 30은 큰편이라 생각되어 10으로 줄였다.

물론 오차범위를 줄이면 유효한 값이라 판단되는 범위가 줄어들어서 펜이 Release가 되었다고 인식될 확률이 높지만, 펜이 자주 릴리즈되는 문제는 타이머를 사용하면 해결 할 수 있기 때문에 될 수 있으면 최대한 유효한 정보를 얻는데 초첨을 두었다.

2.펜 릴리즈(Pen Release) 문제

값을 반복해서 읽다 보면 도중에 펜이 릴리즈되어 값이 유효하지 않는 경우가 생긴다. 이때 값은 문서에 보면 X 축의 유효하지 않은 값은 0으로 송신한다. 하지만 Y의 경우는 0이 아니라 0xFFF로 온다. Y 값 같은 경우 0xFFF가 수신되면 0으로 변경하여 값을 넣게 하고 응용 프로그램에서 터치스크린의 Touch.X, Touch.Y의 값이 둘다 0이 아닐때만 사용하도록 하여 정확도를 높였다.

3. ARM9과 ARM7의 동기화(Syncronization) 문제

사실 이 부분은 가상 IPC 구조체에 Busy 플래그를 1로 설정하는 것으로 처리하고 굳이 신경쓰지 않았다. 그 이유는 ARM7에서 터치스크린에 값을 읽는 시간은 디스플레이의 V Count를 이용하여 80번째 스캔라인에서 읽도록 했기 때문이다. ARM9의 경우 가상 IPC에서 값을 읽는 시간은 VBlank 때이므로 시간적 차이가 좀 있다. 따라서 크게 신경쓰지 않았다. 추후 더 자주 읽어야하거나 어떤 문제가 발생하면 좀 더 동기화에 세밀한 신경을 써야 할지도 모르겠다.

4.구현

아래는 실제 코드인 _touch.c 파일의 내부이다. 첨부 파일로도 추가해 놓았다.

#include "_touch.h"
#include <nds.h>  
#include <nds/jtypes.h>  
#include <nds/system.h>  
#include <nds/arm7/touch.h>  
#include <nds/registers_alt.h>
#include <stdlib.h>

// 글로벌 변수들  
static bool gs_bTouchInit = false;  
static long gs_lXScale, gs_lYScale;  
static long gs_lXOffset, gs_lYOffset;
/**  
    문쉘에서 사용하는 touchRead 함수  
        libnds 함수랑 크게 차이 없음  
*/  
__attribute__((noinline)) static uint16 _touchRead(uint32 command) {  
 uint16 result;  
 SerialWaitBusy();
 // 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();
 // Write the second command and clock in part of the data  
 REG_SPIDATA = 0;  
 SerialWaitBusy();  
 result = REG_SPIDATA;
 // Clock in the rest of the data (last transfer)  
 REG_SPICNT = SPI_ENABLE | 0x201;  
 REG_SPIDATA = 0;  
 SerialWaitBusy();
 // Return the result  
 return ((result & 0x7F) << 5) | (REG_SPIDATA >> 3);  
}

/**  
    온도를 읽는 함수  
        libnds와 크게 차이 없음  
*/  
uint32 _touchReadTemperature(int * t1, int * t2) {  
 *t1 = _touchRead(TSC_MEASURE_TEMP1);  
 *t2 = _touchRead(TSC_MEASURE_TEMP2);  
 return 8490 * (*t2 - *t1) - 273*4096;  
}
 /**  
    TOUCH의 값을 읽어서 유효성을 체크하여 값을 리턴하는 함수  
*/  
__attribute__((noinline)) static s32 readTouchValue(int measure, int retry, int range)  
{  
    int i;  
    s32 this_value=0, this_range;
    s32 last_value = _touchRead(measure | 1);
    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 >= retry ) this_value = 0;
   return this_value;  
}

/**  
    터치 스크린 사용에 관련된 변수값을 설정하고 초기화 하는 함수  
*/  
void _touchReadXY_AutoDetect(void)  
{  
    gs_lXScale = ((PersonalData->calX2px - PersonalData->calX1px) << 19) / ((PersonalData->calX2) - (PersonalData->calX1));
    gs_lYScale = ((PersonalData->calY2px - PersonalData->calY1px) << 19) / ((PersonalData->calY2) - (PersonalData->calY1));

    gs_lXOffset = ((PersonalData->calX1 + PersonalData->calX2) * gs_lXScale  - ((PersonalData->calX1px + PersonalData->calX2px) << 19) ) / 2;
    gs_lYOffset = ((PersonalData->calY1 + PersonalData->calY2) * gs_lYScale  - ((PersonalData->calY1px + PersonalData->calY2px) << 19) ) / 2;
    gs_bTouchInit = true;  
}

/**  
    외부에서 사용하는 함수  
        실제 Pixel 좌표와 Raw 좌표를 리턴  
*/  
touchPosition _touchReadXY()  
{
    touchPosition touchPos; 
    if(gs_bTouchInit==false)
    {
        REG_IME=0;
        while(1);
    }

    // 매크로를 사용하지 않고 그냥 hex 값으로 사용함
    touchPos.x = readTouchValue(0xD4 | 1, 2, 10);
    touchPos.y = readTouchValue(0x94 | 1, 2, 10);
    touchPos.z1 = readTouchValue(0xB4 | 1, 2, 30);
    touchPos.z2 = readTouchValue(0xC4 | 1, 2, 30);  

    // x의 값은 0이면 Release이지만 y는 0xfff이면 Release다. 따라서 처리 로직 추가  
    if( touchPos.y == 0xFFF )  
    {  
        touchPos.y = 0;  
    }  

    s16 px = ( touchPos.x * gs_lXScale - gs_lXOffset + gs_lXScale/2 ) >>19;
    s16 py = ( touchPos.y * gs_lYScale - gs_lYOffset + gs_lYScale/2 ) >>19;
    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;
    touchPos.px = px;
    touchPos.py = py;
    return touchPos;  
}

5.마치며...

실제 위의 코드를 적용하여 새로운 버전의 KKAMAGUI Notepad를 구현하였는데, 튀는 문제가 없어졌다. 앞으로 저 소스를 libfat 대신 이용해야 겠다.

6.첨부

179533__touch.h
0.00MB
180156__touch.c
0.01MB

참고. 롬 파일에 데이터(사운드, 이미지 등등) 파일 포함 방법

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

 

들어가기 전에...

 

0.시작하면서...

 사운드를 출력하거나 이미지를 출력하기위해서는 데이터가 필요하다. libfat를 이용해서 직접 디스크에 접근하는 방법도 있지만, 작은 크기의 파일이라면 롬 파일에 포함하는 것이 빠르고 편리하다.

 devkitPro에는 bin2s.exe라는 프로그램을 가지고 있다. bin2s는 binary 파일을 그대로 덤프하여 .s 확장자를 가진 어셈블리어 파일을 만들어 주는데 이것을 이용하면 .s 파일을 같이 링크하여 프로그램에 포함 시킬 수 있다.

 

1.bin2s.exe

 아래는 콘솔창에서 bell.bin 파일의 내용을 bell.s 파일로 덤프한 화면이다.

bin2s.PNG

 

 아래는 bell.s 파일의 내용이다. 단순하게 섹션을 나누고 global 키워드로 레이블을 export 한 것임을 알 수 있다.

  1. /* Generated by BIN2S - please don't edit directly */
     .section .rodata
     .balign 4
     .global bell_bin_size
    bell_bin_size: .int 27842
     .global bell_bin
    bell_bin:
     .byte   2,  0,  9,  0,  7,  0,  5,  0,  3,  0,  1,  0,255,255,  4,  0
     .byte   5,  0,  5,  0,  5,  0,  4,  0,  3,  0,  8,  0,  2,  0,  0,  0
     .byte   2,  0,  1,  0,  2,  0,  3,  0,  6,  0,  6,  0,  7,  0,  7,  0
  2. ......생략......
  3.  .byte   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
     .byte   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
     .byte   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
     .byte   0,  0
  4.  .global bell_bin_end
    bell_bin_end:

 

2.프로젝트에 추가 방법

 위에서 binary 파일을 어셈블리어 파일로 바꾸는 것에 대해서 알아보았다. 만약 프로젝트에 포함할 파일이 100개라면 100개를 다 일일이 바꿔주고 처리해야 할까?

 답은 "그렇지 않다" 이다. 프로젝트가 있는 makefile을 열어보면 맨 아래쪽에 아래와 같은 bin 파일에 대한 처리 부분이 나와있다.

  1. #---------------------------------------------------------------------------------
    %.bin.o : %.bin
    #---------------------------------------------------------------------------------
     @echo $(notdir $<)
     @$(bin2o)

 bin2o를 사용해서 bin 파일로 바꾼다는 것이다. bin2o? 우리가 알고있는 것은 bin2s.exe 인데??? bin2o는 bin2s.exe를 사용하는 쉘 매크로이고 devkitPro 폴더에 있는 base_rules 파일에 아래와 같이 정의되어있다.

  1. #---------------------------------------------------------------------------------
    # canned command sequence for binary data
    #---------------------------------------------------------------------------------
    define bin2o
     bin2s $< | $(AS) $(ARCH) -o $(@)
  2.  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

 위에서 보는 것과 같이 바로 어셈블리어 컴파일러(AS)에 넣어서 .o 파일을 생성한다. 그 아래에 있는 부분은 헤더파일을 생성하는 부분으로 파일명으로 .h파일을 생성하고 그 안에 아래와 같은 내용을 추가해 준다.

  1. extern const u8 bell_bin_end[];
    extern const u8 bell_bin[];
    extern const u32 bell_bin_size;

 즉 소스에서 a = bell_bin[ 3 ]와 같이 사용할 수 있도록 헤더를 구성해 주는 것이다.

 

3.makefile 확인 및 수정 방법

 이제 편리한 사용을 위해 프로젝트의 makefile을 손볼 차례다. makefile을 조금만 수정하면 특정 폴더에 bin 파일을 넣는 것 만으로도 롬파일에 데이터를 포함하고 또 사용할 수 있다.

 보통 데이터는 data 폴더를 사용하므로 bin 파일도 data 폴더에 넣는 걸로 하자.

  1. #---------------------------------------------------------------------------------
    # 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
    # DATA is a list of directories containing binary files
    # all directories are relative to this makefile
    #---------------------------------------------------------------------------------
    BUILD  := build
    SOURCES  := source MyLibrary 
    INCLUDES := include build
    DATA  :=  data
  2. ...... 생략 ......

  3. #---------------------------------------------------------------------------------
    # you need a rule like this for each extension you use as binary data
    #---------------------------------------------------------------------------------
    %.bin.o : %.bin
    #---------------------------------------------------------------------------------
     @echo $(notdir $<)
     @$(bin2o) 

 위에서 조금 의아한 부분이 INCLUDES에 있는 build 일 것이다. 왜 build 폴더를 Include에 넣느냐 하면, bin2o에 의해 생성된 .o 파일과 .h 파일이 build 폴더에서 생성되기 때문이다. 일단 한번 돌려보면 알 수 있다.

 

4.Wave or MP3 파일을 Raw 파일로 바꾸는 방법

 Wave나 MP3 파일을 NDS에서 출력할 수 있는 Sound 파일로 바꾸려면 일단 Raw Data로 변경한 후, libnds의 PlaySound() 옵션에 적절하게 파라메터를 넘겨서 Play 해주면 된다.

 Raw Data로 바꾸는 방법은 공개 소프트웨어인 Switch(http://www.nch.com.au/switch/plus.html)를 이용하면 쉽게 Raw Data로 변경가능하다. 아래는 Wave 파일을 넣어서 Raw 파일로 변경하는 화면이다.

 Switch.PNG

<Switch 인코딩(Encoding) 화면>

 위의 붉은 색 사각형이 주의깊게 봐야 할 부분이다. 순서는 아래와 같다.

  1. .raw 파일로 output 형식을 설정한다.
  2. Encoder Options 버튼을 누른다.
  3. Codec Settings에서 옵션을 잘 조정하고 주의 깊게 봐둔다.(PlaySound() 함수에서 사용되는 옵션들이다. NDS 배경음악으로는 저정도도 괜찮다. 더 높으면 좋지만 낭비라고 생각한다.)
  4. Convert 버튼을 누른다.

 

결과 파일은 .raw 파일이 나오는데 이것을 data 폴더에 .bin으로 고치고 사용하면 된다. 자세한 사용법은 04 NDS 커널(Kernel) 만들기 소스를 참고하면 된다.

 

5.마치면서...

 지금까지 데이터 파일을 삽입하는 방법에 대해서 알아보았다. 간단한 wave 파일 같은 경우는 쉽게 포함할 수 있으니 한번 테스트 해보도록 하자.

 

 

 

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

참고. 디버그 영역을 이용한 가상 IPC 통신

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

 

들어가기 전에...

 

0.시작하면서...

 NDS는 ARM9과 ARM7 간의 통신을 위해 IPC 통신 기능을 가지고 있다. 하지만 IPC 설정을 해줘야 하고 보낼 수 있는 데이터의 양도 최대 64byte까지로 한정되어있기 때문에 무엇인가 부족한 면이 있다.

 그렇기에 libnds에서는 ARM7과 ARM9이 공유하는 메모리를 이용해서 가상의 IPC 기능을 구현했는데, \devkitPro\libnds\source\include\nds 폴더에 ipc.h 파일에 나와있다.


  1. //---------------------------------------------------------------------------------
    typedef struct sTransferSoundData {
    //---------------------------------------------------------------------------------
      const void *data;
      u32 len;
      u32 rate;
      u8 vol;
      u8 pan;
      u8 format;
      u8 PADDING;
    } TransferSoundData, * pTransferSoundData;

  2. //---------------------------------------------------------------------------------
    typedef struct sTransferSound {
    //---------------------------------------------------------------------------------
      TransferSoundData data[16];
      u8 count;
      u8 PADDING[3];
    } TransferSound, * pTransferSound;

  3. //---------------------------------------------------------------------------------
    typedef struct sTransferRegion {
    //---------------------------------------------------------------------------------
     vint16 touchX,   touchY;  // TSC X, Y
     vint16 touchXpx, touchYpx; // TSC X, Y pixel values
     vint16 touchZ1,  touchZ2; // TSC x-panel measurements
     vuint16 tdiode1,  tdiode2;  // TSC temperature diodes
     vuint32 temperature;   // TSC computed temperature
  4.  uint16 buttons;    // X, Y, /PENIRQ buttons
  5.  union {
      vuint8 curtime[8];  // current time response from RTC
  6.   struct {
       vu8 command;
       vu8 year;  //add 2000 to get 4 digit year
       vu8 month;  //1 to 12
       vu8 day;   //1 to (days in month)
  7.    vu8 weekday;   // day of week
       vu8 hours;  //0 to 11 for AM, 52 to 63 for PM
       vu8 minutes;  //0 to 59
       vu8 seconds;  //0 to 59
      } rtc;
     } time;
  8.  uint16 battery;   // battery life ??  hopefully.  :)
     uint16 aux;    // i have no idea...
  9.  // Don't rely on these below, will change or be removed in the future
     pTransferSound soundData;
  10.  vuint32 mailAddr;
     vuint32 mailData;
     vuint8 mailRead;
     vuint8 mailBusy;
     vuint32 mailSize;
    } TransferRegion, * pTransferRegion;

  11. static inline
    TransferRegion volatile * getIPC(); // __attribute__ ((deprecated));
  12. static inline
    TransferRegion volatile * getIPC() {
     return (TransferRegion volatile *)(0x027FF000);

 TransferRegion 이라는 구조체를 정의해서 사용하는데, 터치 스크린의 좌표 및 RTC, 그리고 Sound 데이터 모두가 위의 구조체에 들어있다. 그리고 구조체가 존재하는 영역은 0x027FF000 으로 이 영역은 메인 메모리 영역이다. 일반적으로는 사용할 수 없고 Debug 모드일때 8Mbyte로 메모리가 확장되는데 그때 사용할 수 있는 영역이다. NDS 홈브루 실행 시에 디버그 모드로 진입하는지는 확실치 않으나 저 영역은 디버그 모드가 아니라도 접근할 수 있는 영역이 아닐까 조심스럽게 추측해 본다.

 

2.마치며...

 위의 구조체를 조금 바꾸면 우리가 원하는 데이터를 끼워 넣는 것도 가능하다. 혹시 추가로 기능이 필요하면 넣어서 사용하도록 하자.

 

 

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

참고. THUMB 코드와 ARM 코드 및 상호 호출(Interworking)

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

 

들어가기 전에...

 

0.시작하면서...

 NDS 개발 툴킷인 devkitPro를 보면 C 코드 같은 경우 THUMB 코드를 사용하도록 되어있다. 하지만 인터럽트가 발생하면 ARM 모드로 전환되고, 기타 ARM 코드로 생성된 라이브러리나 어셈블리로 짜여진 ARM 코드 등등 같은 것을 같이 호출하여 사용하기위해 interwork 옵션으로 중간에 proxy 함수(베니어 함수-veneer)를 사용하여 이를 처리한다.

 Proxy 코드는 어떤식으로 생기는 것일까? NDS 커널에 간단한 테스트를 추가하여 이를 확인해 보기로 했다.  NDS에서 C 코드는 기본적으로 Thumb 모드로 컴파일 된다. 하지만 어셈블리어 코드는 ARM 모드로 컴파일한다. 이 두 언어의 코드를 함께 빌드한 후, 역어셈블리(Disassembly)해보면 어떤 식으로 호출되는지 알 수 있다.

NDS 커널에 대한 내용은 21 NDS 커널(Kernel) 만들기를 참고하도록 하자.

 

1.THUMB 코드와 ARM 코드 작성

NDS 커널에 간단하게 아래의 루틴을 추가하였다.

  1. <main.cpp 파일>
  2. ...... 생략 ......
  3. extern "C" void Test3( void ); <== assem.s 파일애서 만든 ARM 코드 함수
  4. ...... 생략 ......
  5. int main()
  6. {
  7. ...... 생략 ......
  8.     Test3();
  9. ...... 생략 ......
  10. }

 

  1. <assem.s 파일>
  2.     .global Test3 

  3. ...... 생략 ......
  4. Test3:
        bx lr 

 위의 코드를 보면 알 수 있듯이 C에서 간단히 함수를 호출하고 어셈블리어에서는 그냥 리턴하도록 구현되어있다.

 

2.결과 파일 디스어셈블리(Disassembly)

2.1 테스트 함수 디스어셈블리(THUMB->ARM 호출)

이것을 컴파일 및 링크하여 나온 결과를 objdump.exe로 디스어셈블리 해보면 아래와 같은 결과를 볼 수 있다.

  1. 0200128c <main>:

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

     20012d6: f014 fdb3  bl 2015e40 <__Test3_from_thumb>

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

     

    02015e40 <__Test3_from_thumb>:
     2015e40: 4778       bx pc 2015e42: 46c0       nop   (mov r8, r8)

     

  2. 02015e44 <__Test3_change_to_arm>: 2015e44: eaffafbd  b 2001d40 <Test3>

     

    02001d40 <Test3>: 2001d40: e12fff1e  bx lr 2001d44: e1a00000  nop   (mov r0,r0)
     2001d48: e1a00000  nop   (mov r0,r0)
     2001d4c: e1a00000  nop   (mov r0,r0)

 ARM 코드를 바로 호출하는게 중간에 THUMB 모드에서 ARM 모드로 전환하는 코드를 통해 호출하고 있는 것을 볼 수 있다. BX 명령은 오퍼런드(Operand)의 0bit 값을 CPSR의 THUMB 모드 비트로 설정해 주는 역할을 하는 명령어이다. 즉 오퍼런드의 0bit가 1이면 Thumb 모드가 되고 0이면 ARM 모드가 된다. 명령어에 대한 자세한 내용은 참고. ARM 어셈블리(Assembly)를 참고하도록 하자.

 이제 main 함수부터 순차적으로 따라가보자. 맨 위에 <main>에서 bl 2014e40을 실행하면 __Test3_from_thumb() 함수가 호출되고, bx pc를 호출하면 PC는 항상 현재 실행하는 명령 + ( 2 * 명령어 크기 )의 위치에 있으므로(즉 짝수 이므로), bx pc를 하는 순간 ARM 모드로 전환된다. 그리고 PC는 현재 실행 중인 주소(2015e40) + 4byte( 2 * Thumb 모드 명령어 크기(2) ) 다음에 있는 b 2001d40을 실행하게 된다. 이렇게 하여 무사히 Thumb -> ARM으로 호출되었다.

 그런데 뭔가 좀 이상한 느낌이 들지 않는가? BL 명령으로 __Test3_from_thumb 을 호출하고 나면 BX PC 명령에 의해 코드가 4Byte 또는 2Byte로 정렬되어있기 때문에 ARM 모드로 전환된다. 이러한 상태에서 ARM 코드를 정상적으로 실행한 뒤에 BX LR 명령으로 원래 루틴으로 돌아가는데... 정상적으로 다시 실행이 되려면 lr에 들어있는 0번째 bit가 1로 설정되어있어야 한다는 말이 된다.

 실제로 그럴까? ARM 문서에 보면 그냥 LR에 들어가는 값은 현재 코드 위치 + 4라고만 되어있었다. 이 설명 만으로는 BX LR 을 수행했을 때 THUMB 모드로 정상적으로 돌아갈 수 없다. 그래서 테스트 코드에서 LR값을 받아 찍어보았다.

 

 결과는 원래 리턴할 위치에 1값이 더 더해져 있었다. 원래 LR값은 0x20012D6에 4가 더해져서 0x20012DA가 되어야 한다. 그러나 실제 들어있는 값은 0x20012DB가 들어있었다. 이것은 결국 THUMB 모드의 BL 같은 경우 LR에 PC + 4 + 1의 값을 넣는다는 것을 의미했다. @0@)/~!!! 뭐 여튼 알아냈으니 다행... ARM모드의 경우는 BL 시에 LR의 값은 PC + 8이다.

 아래는 ARM Architecture Manual에서 찾은 THUMB 모드에서 BL, BLX에 대한 내용이다.

bl1.PNG

bl2.PNG

 

 

 

2.2 타이머 함수 디스어셈블리(ARM->THUMB)

 그럼 이번에는 반대의 경우를 보자. 인터럽트 핸들러 함수인 타이머 함수 핸들러 같은 경우 ARM 코드로 되어있다. 하지만 스케줄러를 호출하는 함수 같은 경우는 C로 된 함수를 호출하여 그 안에서 처리한다. 이 부분에 대한 코드는 아래와 같다.

  1. 2001c40 <isrTimerInAsm>: 2001c40: e92d4000  stmdb sp!, {lr}
     2001c44: eb00507a  bl 2015e34 <__isrTimerInC_from_arm> 2001c48: e59f00bc  ldr r0, [pc, #188] ; 2001d0c <g_dwCurTask>
     2001c4c: e59f10c0  ldr r1, [pc, #192] ; 2001d14 <g_dwNextTask>
     2001c50: e3510000  cmp r1, #0 ; 0x0
     2001c54: 0a000001  beq 2001c60 <TIMEREND>
     2001c58: e28d2004  add r2, sp, #4 ; 0x4
     2001c5c: eb000001  bl 2001c68 <SwitchTask2>
  2. 02015e34 <__isrTimerInC_from_arm>:
     2015e34: e59fc000  ldr ip, [pc, #0] ; 2015e3c <__isrTimerInC_from_arm+0x8>
     2015e38: e12fff1c  bx ip
     2015e3c: 020013fd  andeq r1, r0, #-201326589 ; 0xf4000003
  3. 020013fc <isrTimerInC>: 20013fc: b510       push {r4, lr}
     20013fe: b084       sub sp, #16
  4. ...... 생략 ......
  5.  2001466: b004       add sp, #16
     2001468: bd10       pop {r4, pc}

 위의 코드에서 보면 역시나 BX IP에서 IP 레지스터가 가리키는 값이 0x20013FDisrTimerInC의 주소 + 1로 설정되어있음을 알 수 있다. isrTimerInC 함수에서 lr 레지스터를 꺼내어 PC에 넣는 순간 역시 BX와 마찬가지로 레지스터의 0 bit가 CPSR의 Thumb 비트에 설정된다.

 

3.마치며...

 ARM 코드와 THUMB 코드를 서로 호출할 때 어떠한 일이 일어나는지 확인해 보았다. 모드가 여러종류가 존재하다보니 이러한 복잡한 부분이 생기는 것 같은데... 한번 봐두는 것도 나쁘지 않은 것 같다. THUMB 모드의 명령어와 ARM 모드의 명령어의 동작이 약간씩 다르므로 익혀두는 것도 나쁘지 않은 것 같다.

 ARM을 열심히 하도록 하자 @0@)/~

 

 

 

 

 

 

 

 

 

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

참고. Software Reset 방법

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

 

들어가기 전에...

 

0.업데이트 

  • 2007/10/24 03:39:39 : 특정 기기 지원에 대한 추가 부분은 23 Soft Reset 분석 의 첨부파일을 참고하자.

     

     

0.시작하면서...

 홈브루를 실행하면 가장 큰 문제가 다시 부트메뉴로 돌아갈려면 전원을 껐다 켜야한다는 것이다. 이것이 불편하여 Software Reset을 지원하는 라이브러리를 구상하게 되었다.

 

1.rebootlib

 세상에 내가 생각하는 모든 것은 이미 구현되었다고 누가 말했던듯... rebootlib라는 라이브러리가 이미 나와있었다. http://lickr.org/files/rebootlib/rebootlib_1.1r.zip에서 다운 받을 수 있으며 개발자 사이트는 http://licklick.wordpress.com/category/rebootlib/ 이다. 상당히 재미있는 것을 많이 하고 있으므로 사이트에 들려서 한번 둘러보는 것도 나쁘지 않은 듯 하다.

 소스를 다운받아 열어보면 알겠지만, 특정 기기는 지원하지 않는다는... ㅜ_ㅜ... 그게 하필 내가 쓰고 있는 거라는... ㅜ_ㅜ

홈페이지1.PNG

<개발자 사이트>

2.Moonshell Plug-in

 NDS를 사용한다면 모르면 간첩인 문쉘(Moonshell)의 Plug-in 중에 mse10_reset 디렉토리를 보면 Software Reset에 대한 내용이 나와있다. 문쉘은 http://mdxonline.dyndns.org/archives/2007/03/moonshell_ver171_dl.shtml에서 받을 수 있으며 SourcePlug-in SDK를 모두 받을 수 있다.

 하지만 역시 특정 기기는 지원하지 않는다는... ㅜ_ㅜ...

홈페이지2.PNG

<문쉘(Moonshell) 개발자 사이트>

3.마치면서...

 좋은 라이브러리도 있고 레퍼런스도 있지만... 좀더 기다려야 할듯하다. 빨리 버전업이 되었으면.... ㅜ_ㅜ

 

 

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

참고. NDS 속도에 대한 몇가지 테스트

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

 

들어가기 전에...

 

0.시작하면서...

 NDS의 실제 속도는 얼마나 될까? 클럭으로 따지자면 둘다 100MHz가 안되니 그리 빠르다고는 할 수 없다. 하지만 이 수치만 가지고는 뭔가 와닿지가 않는다.

 우리가 쉽게 느낄 수 있는 것? 화면 그리는데 몇 ms 걸리고, 얼마의 크기의 메모리를 복사하는데 몇 ms가 걸리고 하는 말이 더 와닿지 않는가? 지금부터 프로파일러를 사용해서 NDS의 속도에 대한 몇가지를 테스트 해보자. 프로파일러에 대한 내용은 22 타이머(Timer)를 이용한 프로파일러(Profiler) 만들기 에서 찾을 수 있다.

 

1.메모리 복사 속도

 메모리 속도 테스트는 RGB555 포맷을 사용하는 프레임 버퍼(Frame Buffer)모드의 화면 전체를 복사하는 것을 테스트 했다. 참고로 스크린의 크기는 256 * 192 이므로 총 메모리양은 256 * 192 * 2 = 98304 Byte이다.

 여기서 사용된 코드는 모두 GCC의 최적화 옵션 중 최고인 -O2 옵션으로 컴파일 되고 링크되었다.

 

1.1 메인 메모리(Main Memory) -> 메인 메모리(Main Memory)

1.1.1 memcpy 사용

 아래는 프로파일링 테스트에 사용된 함수이다.

  1. /**
        FrameBuffer의 내용을 LCD에 덤프한다.
    */
    void CWindowManager::DumpFrameBufferToScreen( void )
    {
        memcpy( SUB_FRAMEBUFFER, MAIN_FRAMEBUFFER, SCREEN_HEIGHT * SCREEN_WIDTH * 2 );
  2.     memcpy( MAIN_FRAMEBUFFER, SUB_FRAMEBUFFER, SCREEN_HEIGHT * SCREEN_WIDTH *  2 );
    }

  테스트 결과는 아래와 같다.

  • NDS : 17 ~ 18 ms,한 화면을 복사하는데 8.5 ~ 9 ms
  • NDS Emulator(iDeaS) : 23 ~ 24 ms,한 화면을 복사하는데 11.5 ~ 12 ms

 

1.1.2 for 루프 사용

 아래는 프로파일링 테스트에 사용된 함수이다.

  1. /**
        FrameBuffer의 내용을 LCD에 덤프한다.
    */
    void CWindowManager::DumpFrameBufferToScreen( void )
    {
     int i;
     
     DWORD* pdwDst;
     DWORD* pdwSrc;
     
     pdwDst = ( DWORD* ) SUB_FRAMEBUFFER;
     pdwSrc = ( DWORD* ) MAIN_FRAMEBUFFER;
     
     for( i = SCREEN_HEIGHT * SCREEN_WIDTH / 2 ; i > 0 ; i-- )
     {
      *pdwDst = *pdwSrc;
      pdwDst++;
      pdwSrc++;
     }
         
     pdwDst = ( DWORD* ) MAIN_FRAMEBUFFER;
     pdwSrc = ( DWORD* ) SUB_FRAMEBUFFER;
     
     for( i = SCREEN_HEIGHT * SCREEN_WIDTH / 2 ; i > 0 ; i-- )
     {
      *pdwDst = *pdwSrc;
      pdwDst++;
      pdwSrc++;
     }
    }

  테스트 결과는 아래와 같다.

  • NDS : 17 ~ 18 ms,한 화면을 복사하는데 8.5 ~ 9 ms
  • NDS Emulator(iDeaS) : 29 ~ 30 ms,한 화면을 복사하는데 14.5 ~ 15 ms

 

1.1.3 고속 메모리 전송 함수 사용(자작)

 ARM 모드의 12개 레지스터를 사용해서 블럭 전송을 하는 소스코드인데, 자세한 것은 02 NDS 홈브루(Homebrew) - NDS 윈도우 시스템(Windows System)의 내용을 참고하자. 시간은 4 ~ 5 ms 정도(앞서 테스트의 반) 나온다.

 

1.2 메인 메모리(Main Memory) -> 비디오 메모리(Video Memory)

1.2.1 memcpy 사용

 아래는 프로파일링 테스트에 사용된 함수이다.

  1. /**
        FrameBuffer의 내용을 LCD에 덤프한다.
    */
    void CWindowManager::DumpFrameBufferToScreen( void )
    {
        memcpy( LCD_MAIN_ADDR, MAIN_FRAMEBUFFER, SCREEN_HEIGHT * SCREEN_WIDTH *
                2 );
       
        memcpy( LCD_SUB_ADDR, SUB_FRAMEBUFFER, SCREEN_HEIGHT * SCREEN_WIDTH *
                2 );
    }

 테스트 결과는 아래와 같다.

  • NDS : 14 ~ 15 ms,한 화면을 복사하는데 7 ~ 7.5 ms
  • NDS Emulator(iDeaS) : 23 ~ 24 ms,한 화면을 복사하는데 11.5 ~ 12 ms

 

1.2.2 For 루프 사용

 아래는 프로파일링 테스트에 사용된 함수이다.

  1. /**
        FrameBuffer의 내용을 LCD에 덤프한다.
    */
    void CWindowManager::DumpFrameBufferToScreen( void )
    {
     int i;
     
     DWORD* pdwDst;
     DWORD* pdwSrc;
     
     pdwDst = ( DWORD* ) LCD_MAIN_ADDR;
     pdwSrc = ( DWORD* ) MAIN_FRAMEBUFFER;
     
     for( i = SCREEN_HEIGHT * SCREEN_WIDTH / 2 ; i > 0 ; i-- )
     {
      *pdwDst = *pdwSrc;
      pdwDst++;
      pdwSrc++;
     }     
       
     pdwDst = ( DWORD* ) LCD_SUB_ADDR;
     pdwSrc = ( DWORD* ) SUB_FRAMEBUFFER;
     
     for( i = SCREEN_HEIGHT * SCREEN_WIDTH / 2 ; i > 0 ; i-- )
     {
      *pdwDst = *pdwSrc;
      pdwDst++;
      pdwSrc++;
     }
    }

 테스트 결과는 아래와 같다.

  • NDS : 16 ~ 17 ms,한 화면을 복사하는데 8 ~ 8.5 ms
  • NDS Emulator(iDeaS) : 29 ~ 30 ms,한 화면을 복사하는데 14.5 ~ 15 ms

 

1.2.3 고속 메모리 함수 사용(자작)

 여기 추가하기...

 

1.3 비디오 메모리(Video Memory) -> 메인 메모리(Main Memory)

1.3.1 memcpy 사용

 아래는 프로파일링 테스트에 사용된 함수이다.

  1. /**
        FrameBuffer의 내용을 LCD에 덤프한다.
    */
    void CWindowManager::DumpFrameBufferToScreen( void )
    {
        memcpy( MAIN_FRAMEBUFFER, LCD_MAIN_ADDR, SCREEN_HEIGHT * SCREEN_WIDTH *
                2 );
       
        memcpy( SUB_FRAMEBUFFER, LCD_SUB_ADDR, SCREEN_HEIGHT * SCREEN_WIDTH *
                2 );

 테스트 결과는 아래와 같다.

  • NDS : 13 ~ 14 ms,한 화면을 복사하는데 6.5 ~ 7 ms
  • NDS Emulator(iDeaS) : 23 ~ 24 ms,한 화면을 복사하는데 11.5 ~ 12 ms

 

1.3.2 For 루프 사용

 아래는 프로파일링 테스트에 사용된 함수이다.

  1. /**
        FrameBuffer의 내용을 LCD에 덤프한다.
    */
    void CWindowManager::DumpFrameBufferToScreen( void )
    {
     int i;
     
     DWORD* pdwDst;
     DWORD* pdwSrc;
     
     pdwDst = ( DWORD* ) MAIN_FRAMEBUFFER;
     pdwSrc = ( DWORD* ) LCD_MAIN_ADDR;
     
     for( i = SCREEN_HEIGHT * SCREEN_WIDTH / 2 ; i > 0 ; i-- )
     {
      *pdwDst = *pdwSrc;
      pdwDst++;
      pdwSrc++;
     }     
       
     pdwDst = ( DWORD* ) SUB_FRAMEBUFFER;
     pdwSrc = ( DWORD* ) LCD_SUB_ADDR;
     
     for( i = SCREEN_HEIGHT * SCREEN_WIDTH / 2 ; i > 0 ; i-- )
     {
      *pdwDst = *pdwSrc;
      pdwDst++;
      pdwSrc++;
     }
    }

 테스트 결과는 아래와 같다.

  • NDS : 16 ~ 17 ms,한 화면을 복사하는데 8 ~ 8.5 ms
  • NDS Emulator(iDeaS) : 29 ~ 30 ms,한 화면을 복사하는데 14.5 ~ 15 ms

 

1.3.3 고속 메모리 전송 함수(자작)

 여기 추가하기...

 

1.4 비디오 메모리(Video Memory) -> 비디오 메모리(Video Memory)

1.4.1 memcpy 사용

 아래는 프로파일링 테스트에 사용된 함수이다.

  1. /**
        FrameBuffer의 내용을 LCD에 덤프한다.
    */
    void CWindowManager::DumpFrameBufferToScreen( void )
    {
        memcpy( LCD_SUB_ADDR, LCD_MAIN_ADDR, SCREEN_HEIGHT * SCREEN_WIDTH *
                2 );
       
        memcpy( LCD_MAIN_ADDR, LCD_SUB_ADDR, SCREEN_HEIGHT * SCREEN_WIDTH *
                2 );
    }

 테스트 결과는 아래와 같다.

  • NDS : 16 ~ 17 ms,한 화면을 복사하는데 8 ~ 8.5 ms
  • NDS Emulator(iDeaS) : 23 ~ 24 ms,한 화면을 복사하는데 11.5 ~ 12 ms

 

1.4.2 For 루프 사용

 아래는 프로파일링 테스트에 사용된 함수이다.

  1. /**
        FrameBuffer의 내용을 LCD에 덤프한다.
    */
    void CWindowManager::DumpFrameBufferToScreen( void )
    {
     int i;
     
     DWORD* pdwDst;
     DWORD* pdwSrc;
     
     pdwDst = ( DWORD* ) LCD_SUB_ADDR;
     pdwSrc = ( DWORD* ) LCD_MAIN_ADDR;
     
     for( i = SCREEN_HEIGHT * SCREEN_WIDTH / 2 ; i > 0 ; i-- )
     {
      *pdwDst = *pdwSrc;
      pdwDst++;
      pdwSrc++;
     }     
       
     pdwDst = ( DWORD* ) LCD_MAIN_ADDR;
     pdwSrc = ( DWORD* ) LCD_SUB_ADDR;
     
     for( i = SCREEN_HEIGHT * SCREEN_WIDTH / 2 ; i > 0 ; i-- )
     {
      *pdwDst = *pdwSrc;
      pdwDst++;
      pdwSrc++;
     }
    }

 테스트 결과는 아래와 같다.

  • NDS : 19 ~ 20 ms,한 화면을 복사하는데 9.5 ~ 10 ms
  • NDS Emulator(iDeaS) : 39 ~ 40 ms,한 화면을 복사하는데 19.5 ~ 20 ms

 

1.4.3 고속 메모리 전송 함수(자작)

 ARM 모드의 12개 레지스터를 사용해서 블럭 전송을 하는 소스코드인데, 자세한 것은 02 NDS 홈브루(Homebrew) - NDS 윈도우 시스템(Windows System)의 내용을 참고하자. 시간은 4 ~ 5 ms 정도(앞서 테스트의 반) 나온다.

 

1.5 메모리 복사에 대한 결론

  • 비디오 메모리 -> 비디오 메모리를 제외하고는 모두 거의 비슷
  • 블럭 전송 시간 < memcpy 시간 <= for 루프 시간
  • 에뮬레이터의 속도는 믿을 것이 못됨
  • 블럭 전송이 가장 빠름

 

 

 

 

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

참고. DPG 파일 포맷

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

 

들어가기 전에...

 

0.시작하면서...

 문쉘(MoonShell)의 동영상 포맷인 DPG에 대해서 알아보자.

 

1.파일 포맷(File Format)

 아래는 Wikipedia에 등록된 내용이다.

File Format

[edit] General Outline

The DPG file specification is simple. All DPG files contain a 36 byte header, followed by audio, and then a standard mpeg-1 video stream. The audio format can differ; older DPG files used a special WAV format audio, but newer versions of moonshell have phased that out in favor of MP2 audio.

[edit] File Structure

The file begins with a 36 byte header (Note: all of the numbers below are hexadecimal.):

  • 44 50 47 30 (this stands for DPG0 in ASCII)
  • Four bytes for the number of frames in the video
  • Two bytes for the frames per second that the video runs
  • 00 00
  • Four bytes for the audio sample rate
  • 00 00 00 00 (this was the number of audio channels, now deprecated in favor of MP2 audio)
  • 24 00 00 00 (this is the start of the audio file, i.e. right after the header)
  • Four bytes for the length, in bytes, of the audio
  • Four bytes for the above length + 36 bytes (i.e. the start of the video file)
  • Four bytes for the length, in bytes, of the video

(A C program and source is included in the BatchDPG source code to simplify the header generation)

After a header has been made, concatenate everything together:

  • Linux & Mac (OS X Terminal): cat header.head audio.mp2 video.raw.mpg > ndsmpeg.dpg
  • Windows: copy /b header.head + audio.mp2 + video.raw.mpg ndsmpeg.dpg

[edit] Audio Encoding

The audio is encoded in standard MP2 format. For example, using ffmpeg to encode in.avi into out.mp2 at a 22050 sample rate and a bitrate of 64 kb/s:

ffmpeg -i in.avi -vn -ab 64k -ar 22050 out.mp2

[edit] Video Encoding

MoonShell can be very particular about the types of MPEG video it will decode correctly. Videos made with ffmpeg do not work very well; MEncoder seems to be a better option. The FPS and bitrate rely on personal preference, but the DS does not have much processing power - lower bitrates and framerates seem to work better. Finally, since the DS screen is so small, rescaling to 256 by 192 is the most space-efficient.

Here is an example command using MEncoder (this should be on one line):

mencoder in.avi -o out.mpg -nosound -ovc lavc -lavcopts vcodec=mpeg1video:vrc_buf_size=327:vrc_maxrate=512:vbitrate=256:vstrict=-1 -ofps 18 -vf scale=256:192

This produces an AVI with a working MPEG video stream, but it should be extracted into a raw format with ffmpeg, however, this is not required:

ffmpeg -i out.mpg -an -vcodec copy -f mpeg1video out.raw.mpg

Or, in more recent versions of mencoder you can just add the following option:

-of rawvideo

This makes MEncoder output raw video with no need to extract it later with ffmpeg.

 

 

2.DPG 파일 생성 소스

  1. #include "stdio.h"
    #include "stdlib.h"
  2. void putint(unsigned int i, FILE* outfile) {
       fputc(i & 0xff, outfile);
       i >>= 8;
       fputc(i & 0xff, outfile);
       i >>= 8;
       fputc(i & 0xff, outfile);
       i >>= 8;
       fputc(i & 0xff, outfile);
    }
  3. int main(int argc, char* argv[]) {
       int frames;
       int fps;
       int samplerate;
       int audiosize;
       int videosize;
      
       //char* filename = "header",0;
       FILE* outfile;
      
       if (argc != 7) {
          printf("usage: headermaker <frames> <fps> <samplerate> <audiosize> <videosize> <filename>\n");
          return -1;
       }
      
       // read our parameters
       frames  = atoi(argv[1]);
       fps   = atoi(argv[2]);
       samplerate = atoi(argv[3]);
       audiosize = atoi(argv[4]);
       videosize = atoi(argv[5]);
      
       outfile = fopen(argv[6], "wb");
      
       // magic number:
       fputc(0x44, outfile);
       fputc(0x50, outfile);
       fputc(0x47, outfile);
       fputc(0x30, outfile);
      
       // frames
       putint(frames, outfile);
      
       // fps
       fputc(0x00, outfile);
       fputc(fps%256, outfile);
      
       fputc(0x00, outfile);
       fputc(0x00, outfile);
      
       // samplerate
       putint(samplerate, outfile);
      
       // channels
       putint(0, outfile);
      
       // begin and size audio
       putint(36, outfile);
       putint(audiosize, outfile);
      
       // begin and size video
       putint(36+audiosize, outfile);
       putint(videosize, outfile);
      
       fclose(outfile);
      
       return 0;
    }

 

3.마치면서...

 아주 간단한 파일 포맷이다. 시간나면 인코딩 라이브러리를 이용해서 직접 생성해봐야겠다.

 

 

 

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

참고. ARM 어셈블리(Assembly)

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

 

들어가기 전에...

 

 

1.ARM/THUMB Common Part

1.1 Condition Code

 ARM의 어셈블리 명령어들은 뒤에 post fix가 붙는데, 그 post fix의 의미는 아래와 같다. 일반적으로 아무것도 붙지 않으면 AL이라고 가정한다. Branch 명령인 B와 같은 경우 그냥 사용하면 BAL이 되고 BEQ와 같은 조합으로 CPSR에 있는 Condition flag를 이용하여 조건 분기와 같은 역할을 할 수 있는 것이다.

inst1.PNG

 

1.2 Addressing Mode

 addressMode.PNG

 

 

1.3 Shifter operand Mode

addressMode2.PNG

12. <Rm>, =XXX => XXX의 Address를 Rm에 넣는다.

 

 

 

1.4 Post-Fix

  • S : 계산 후 CPSR에 SPSR을 넣어 복구
  • ! : 계산 결과를 Base Register에 적용
  • ^ : PC가 포함된 경우는 연산 후 CPSR에 SPSR을 넣어 복구 or SYSTEM/USER 모드가 아닌 경우이고 PC가 없는 경우는 SYSTEM/USER 모드 레지스터에 값을 로드/스토어

 

 

2.ARM/THUMB 명령어 리스트

2.1 필수 참고 사항

  • <Rd> : 목적지 레지스터를 의미, 일반적으로 결과값을 저장하거나 전송할 레지스터의 값
  • <post fix : 'S'>
    • <Rd>에 PC가 있을 경우 계산 후 CPSR에  SPSR의 값을 대입. SPSR이 존재하지 않는 모드(SYSTEM/USER) 모드일 경우는 동작을 보장할 수 없음
    • <Rd> PC가 없을 경우 계산 결과 플래그를 CPSR에 업데이트
  • <post fix : '!'> : 계산 후 Base Register의 값을 갱신. Multiple Load/Store Instruction 시에 유용히 쓰임
  • <shifter_operand> : #1와 같이 값 자체로도 쓰일 수 있으며 LSL #2와 같이 배럴 쉬프터(Shifter)를 사용해서 할 수도 있다.

 

2.2 ARM 모드 Instruction List

  • ADC : Add with carry 
    • ADC{<cond>}{S} <Rd>, <Rn>, <shifter_operand>
    • <Rd> = <Rn> + <sifter_operand>

 

  • ADD : 
    • ADD{<cond>}{S} <Rd>, <Rn>, <shifter_operand>

 

  • AND : 
    • AND{<cond>}{S} <Rd>, <Rn>, <shifter_operand>

 

  • B, BL : Branch
    • B{L}{<cond>} <target_address>
    • BL의 경우 LR 레지스터에 PC + 4의 값을 대입

 

  • BIC : Bit Clear
    • BIC{<cond>}{S} <Rd>, <Rn>, <shifter_operand>

 

  • BKPT : Breakpoint
    • BKPT <immediat>

 

  • BLX : Branch with Link and Exchange
    • BLX{<cond>} <target_addr>
    • Target_Addr의 값의 bit 0의 값으로 CPSR의 THUMB 모드를 설정한다.
    • ARM 모드와 THUMB 모드를 변환하는데 사용된다.

 

  • BX : Branch And Exchange
    • BLX{<cond>} <Rm>
    • Rm의 bit 0의 값으로 CPSR의 THUMB 모드를 설정한다.
    • ARM 모드와 THUMB 모드를 변화하는데 사용된다.

 

  • CDP : Coprocessor Data Processing 
    • CDP{<cond>} <coproc>, <opcode_1>, <CRd>, <CRn>, <CRm>, <opcode_2>
    • CDP2{<cond>} <coproc>, <opcode_1>, <CRd>, <CRn>, <CRm>, <opcode_2>

 

  • CLZ : Count Leading Zeros 
    • CLZ{<cond>} <Rd>, <Rm>
    • MSB부터 LSB로 가면서 1로 설정된 첫번째 bit까지 0이 몇개인가 헤아린다.

 

  • CMN : Compare Negative(음수)
    • CMN{<cond>} <Rd>, <Shifter_operand> 
    • Add를 기반으로 비교
    • 두개가 같으면(결과가 0이면) CPSR의 Z 비트가 1로 설정
    • Shifter_operand의 nagative 값과 <Rd>를 비교한 후 결과를 CPSR에 저장

 

  • CMP : Compare
    • CMP{<cond>} <Rd>, <Shifter_operand>
    • Substract를 기반으로 비교
    • 두개가 같으면(결과가 0이면) CPSR의 Z 비트가 1로 설정
    • Shifter_operand의 값과 <Rd>를 비교한 후 결과를 CPSR에 저장

 

  • EOR : Exclusive OR
    • EOR{<cond>}{S} <Rd>, <Rn>, <Shifter_operand>

 

  • LDC : Load Coprocessor
    • LDC{<cond>}{L} <coproc>, <CRd>, <addressing_mode>
    • LDC2{<cond>}{L} <coproc>, <CRd>, <addressing_mode>

 

  • LDM : Load Multiple Instruction
    • LDM{<cond>}<addressing_mode> <Rn>{!}, <registers>
    • PC가 <registers>에 포함되어있으면 BX 명령과 같은 동작을 한다. (0 bit에 따라서 THUMB 모드와 ARM 모드가 바뀐다.)
    • registers는 낮은 번호의 레지스터부터 높은 번호의 레지스터 순서로 정렬되어야 한다.
    • '!'가 붙으면 Load가 끝난 Rn 레지스터의 값이 Registers의 갯수 * 4 만큼 증가된다.
    • 레지스터의 낮은 번호는 메모리의 낮은 주소(Rn의 시작주소)에 로드되고 레지스터의 높은 주소는 메모리의 높은 주소(Rn + ( regiser 개수 - 1 ) * 4)에 로드된다.
    • LDM{<cond>}<addressing_mode> <Rn>{!}, <registers_without_pc>^
      • resisters 에 포함된 레지스터는 User Mode의 레지스터를 의미한다.
    • LDM{<cond>}<addressing_mode> <Rn>{!}, <registers_pc>^  
      • 레지스터를 로드한 후에 SPSR의 값을 CPSR에 넣는다.

 

  • LDR : Load Register
    • LDR{<cond>} <Rd>, <addressing_mode>
    • <Rd>가 PC인 경우는 BX와 같이 동작한다.

 

  • LDRB : Load Register Byte
    • LDR<cond>}B <Rd>, <addressing_mode>
    • <Rd>에는 하위 8bit는 값이 설정되고 나머지는 0으로 체워진다.

 

  • LDRBT : Load Register Byte Width Translation 
    • LDR{<cond>}BT <Rd>, <post_indexed_addressing_mode>

 

  • LDRH : Load Register Halfword
    • LDR{<cond>}H <Rd>, <addressing_mode>

 

  • LDRSB : Load Register Signed Byte
    • LDR{<cond>}SB <Rd>, <addressing_mode>

 

  • LDRSH : Load Register Signed Halfword 
    • LDR{<cond>}SH <Rd>, <addressing_mode>

 

  • LDRT : Load Register with Translation
    • LDR{<cond>}T <Rd>, <post_indexed_addressing_mode>

 

  • MCR : Move to Coprocessor from ARM Register
    • MCR{<cond>} <coproc>, <opcode_1>, <Rd>, <CRn>, <CRm>, {, <opcode_2>}
    • MCR2{<cond>} <coproc>, <opcode_1>, <Rd>, <CRn>, <CRm>, {, <opcode_2>}

 

  • MLA : Multiply Accumlator 
    • MLA{<cond>}{S} <Rd>, <Rm>, <Rs>, <Rn>
    • Rd = ( Rm * Rs + Rn )

 

  • MOV : Move
    • MOV{<cond>}{S} <Rd>, <shifter_operand> 

 

  • MRC : Move To ARM Register from Coprocessor
    • MRC{<cond>} <coproc>, <opcode_1>, <Rd>, <CRn>, <CRm>, {, <opcode_2>}
    • MRC2{<cond>} <coproc>, <opcode_1>, <Rd>, <CRn>, <CRm>, {, <opcode_2>}

 

  • MRS : Move PSR to General-purpose register
    • MRS{<cond>} <Rd>, CPSR
    • MRS{<cond>} <Rd>, SPSR 

 

  • MSR : Move To Status Register From ARM
    • MSR{<cond>} CPSR_<fields>, #<Immediate> 
    • MSR{<cond>} CPSR_<fields>, <Rm> 
    • MSR{<cond>} SPSR_<fields>, #<Immediate> 
    • MSR{<cond>} SPSR_<fields>, <Rm> 
    • PSR의 특정 비트 플래그만 조작할 수 있다. 
      • fields : c or x or s or f

 

  • MUL : Multiply
    • MUL{<cond>}{S} <Rd>, <Rm>, <Rs>
    • <Rd> = <Rm> + <Rs>

 

  • MVN : Move Negative
    • MVN{<cond>}{S} <Rd>, <shifter_operand> 

 

  • ORR : Logical OR
    • ORR{<cond>}{S} <Rd>, <Rn>, <shifter_operand> 

 

  • RSB : Reverse Subtract
    • RSB{<cond>}{S} <Rd>, <Rn>, <shifter_operand> 
    • <Rd> = <shifter_operand> - <Rn>

 

  • RSC : Reverse Substract Width Carry
    • RSC{<cond>}{S} <Rd>, <Rn>, <shifter_operand> 
    • <Rd> = <shifter_operand> - <Rn> - NOT(C Flag)

 

  • SBC : Substract with Carry
    • SBC{<cond>}{S} <Rd>, <Rn>, <shifter_operand> 
    • <Rd> = <Rn> - <shifter_operand> - NOT(C Flag)

 

  • SMLAL : Signed Multiply Accumulate Long
    • SMLAL{<cond>}{S} <RdLo>, <RdHi>, <Rm>, <Rs> 
    •  SMLAL.PNG

 

  • SMULL : Signed Multiply Long
    • SMULL{<cond>}{S} <RdLo>, <RdHi>, <Rm>, <Rs> 
    • SMULL.PNG

 

  • STC : Store Coprocessor
    • STC{<cond>}{L} <coproc>, <CRd>, <addressing_mode>
    • STC2{L} <coproc>, <CRd>, <addressing_mode>
    • coprocessor의 레지스터 <CRd>로 부터 <addressing_mode>로 값을 저장

 

  • STM : Store Multiple
    • STM{<cond>}<addressing_mode> <Rn>{!}, <registers>
    • STM{<cond>}<addressing_mode> <Rn>{!}, <registers>^
    • <Rn>의 메모리주소부터 시작하여 <registers>의 값을 저장
    • 레지스터의 낮은 번호는 메모리의 낮은 주소(Rn의 시작주소)에 저장되고 레지스터의 높은 주소는 메모리의 높은 주소(Rn + ( regiser 개수 - 1 ) * 4)에 저장된다.
    • '^'가 포함되면 User 모드의 레지스터가 저장됨

 

  • STR : Store Register
    • STR{<cond>} <Rd>, <addressing_mode>
    • <addressing_mode> = <Rd>

 

  • STRB : Store Register Byte
    • STR{<cond>}B <Rd>, <addressing_mode>
    • <addressing_mode> = Rd[7:0]

 

  • STRBT : Store Register Byte width Translation
    • STR{<cond>}BT <Rd>, <post_indexed_addressing_mode>

 

  • STRH : Store Register Halfword
    • STR{<cond>}H <Rd>, <addressing_mode>

 

  • STRT : Store Register with Translation
    • STR{<cond>}T <Rd>, <post_indexed_addressing_mode>

 

  • SUB : Substract
    • SUB{<cond>}{S} <Rd>, <Rn>, <shifter_operand>
    • <Rd> = <Rn> - <shifter_operand>

 

  • SWI : Software Interrupt
    • SWI{<cond>} <immed_24> 

 

  • SWP : Swap
    • SWP{<cond>} <Rd>, <Rm>, [<Rn>]
    • <Rd> = [<Rn>],  [<Rn>] = <Rm>

 

  • SWPB : Swap Byte
    • SWP{<cond>}B <Rd>, <Rm>, [<Rn>]
    • <Rd> = [<Rn>],  [<Rn>] = <Rm>

 

  • TEQ : Test Equivalence
    • TEQ{<cond>} <Rd>, <shifter_operand>
    • Logical Exclusive OR를 이용해서 비교
    • 두 값이 같으면 CPSR의 Z 플래그가 1로 세팅

 

  • TST : Test
    • TST{<cond>} <Rn>, <shift_operand>
    • AND 이용해서 비교
    • 결과가 0이면 CPSR의 Z 비트가 1로 셋팅

 

  • UMLAL : Unsigned Multiply Accumulate Long
    • UMLAL{<cond>}{S} <RdLo>, <RdHi>, <Rm>, <Rs>
    • UMLAL.PNG

 

  • UMULL : Unsigned Multiply Long
    • UMULL{<cond>}{S} <RdLo>, <RdHi>, <Rm>, <Rs>
    • UMLL.PNG

 

 

2.3 THUMB 모드 Instruction List

  • BL, BLX : Branch with Link, Branch with Link Exchange
    • BL <target_addr>
    • BLX <target_addr>
    • BL3.PNG
    • BL4.PNG
    • THUMB 모드의 BL은 LR 레지스터에 0bit를 1로 셋팅하여 BX LR과 같은 명령 사용 시 THUMB 모드로 다시 돌아올 수 있도록 하고 있다.

 

 

3.ARM C Calling Convention

 ARM의 경우 레지스터가 많기 때문에 Intel과는 달리 4개의 파라메터까지는 Register 0 ~ Register 3에 저장하게 된다. R0 같은 경우는 일반적으로 리턴 값으로 사용되기도 하며, Register 4 ~ Register 8은 Callee에서 사용하는 레지스터이다.

 Caller는 R0 ~ R3 까지를 저장하고 복원할 책임을 가지며 Callee는 R4 ~ R8 까지를 저장하고 복원하는 책임을 가진다.

arm_calling.PNG

<APCS(Arm Procedure Call Standard)에서 각 레지스터의 역할>

 ARM에서 Branch를 하면 LR 레지스터에 Return Address가 저장되므로, 다시 다른 함수를 Call 하게 되면 LR 레지스터를 저장하고 다시 복구해야 한다.

 

4.마치며...

 ARM 어셈블리에 대해서 간략하게나마 정리했다. 시간 날때 틈틈히 찾아보도록 하자.

 

 

 

 

 

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

참고. ARM Processor Overview

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

 

들어가기 전에...

0.시작하면서...

 예전에 한번 StrongARM(ARM7)을 이용해서 간단한 OS를 만들어 본적이 있었다. 물론 아주 오래전 일이라 기억이 잘 안나지만 ARM 메뉴얼과 어셈블리어를 가지고 시름하여 결국 태스트 스위칭 기능까지 구현할 수 있었다.

 시간이 흘러... 이제 NDS에 커널을 올려볼까 하고 있는데... 다시 어셈블리어를 보고 프로세서에 대해서 볼려니 아주 눈물이 난다. ㅜ_ㅜ.... 그래도 일단 봐야지... 어찌하겠는가... 크윽.. ㅜ_ㅜ

 간단하게 프로그래밍하기위한 기능적인 부분만 보자.

 

1.ARM Architecture 소개

 ARM은 RISC 구조의 CPU로써 매우 간단한 명령에들로 구성되어있다. ARM이 하버드 아키텍쳐를 사용했으며 어쩌구 저쩌구 하는 내용은 일단 생략하고... 나의 입장에서 보는 ARM의 구조는 그냥 단순한 블랙박스인데... 레지스터가 굉장히 많고 여러가지 모드를 가진 32bit CPU라는 정도이다. ARM에 대한 자세한 내용은 첨부에 올린 ARM Architecture Manual을 참고하도록 하자.

 

1.1 ARM Register

1.1.1 Register Overview And General Register

 ARM은 여러모드를 가지고 있는데, 한 모드에서는 32bit R0 ~ R15까지 16개의 범용 레지스터 및 CPSR/SPSR의 상태 레지스터를 가진다.

 몇개의 레지스터는 특수한 이름과 의미를 가지고 있는데 아래와 같다.

  • R13 : Stack의 Top을 가리키는 sp 레지스터
  • R14 : 함수 호출 시 리턴 주소를 저장하는 용도의 Link Register(lr) 레지스터
  • R15 : 경우는 현재 수행중인 명령 위치를 가리키는 pc 레지스터

 

  몇개의 레지스터는 모드에 따라서 각 모드 전용의 레지스터를 가지는데, 일단 아래를 보자.

ARM_Register.PNG

<ARM의 Register들>

 

 위에서 보면 좌측 하단의 작은 삼각형이 있는 레지스터들이 있다. 이 레지스터들이 해당 모드의 전용 레지스터로써 R13(sp)와 R14(lr)의 경우는 거의 모든 모드에서 개별 모드의 레지스터를 사용한 다는 것을 알 수 있다. 그리고 User 모드와 System 모드는 레지스터를 그대로 공유한다. 일단 이 정도만 알아두자.

 

 PC에 대해서 잠깐 언급할 것이 있는데, 첨부에 포함된 ARM Architecture Manual에 의하면 ARM 명령어 모드 일때  PC는 현재 명령 주소 + 8의 위치를 가리키고 THUMB 모드 일때는 현재 명령 주소 + 4의 위치를 가리킨다고 되어있다. 이 부분은 PC를 Base로 주소 연산을 할때는 상당이 주의해야할 부분이므로 잘 알아두록 하자. 단 STR 명령에 의해서 값이 레지스터->메모리로 저장될 때 이때는 PC + 8 이 될 수 도 있고 PC + 12가 될 수 도 있는데... 일단 8이라고 알아두자.

 

1.1.2 PSR(Program Status Registers)

 PSR(Program Status Register) 레지스터는 현재 프로그램이 실행되는 모드를 나타내는 레지스터로써 현재 상태를 나타내는 CPSR과 이전의 상태를 나타내는 SPSR 레지스터가 존재한다. SPSR의 경우는 User 모드나 System 모드에서 실행중인 프로그램이 특정 Exception 모드로 변경되었을 때, User/System 모드의 CPSR을 저장하는 용도로 사용된다.

PSR.PNG

<PSR 레지스터의 구조>

 

 ARM Architecture Manual을 보면 각 플래그에 대해서 아래와 같이 설명해 놓았다.

 

PSR1.PNG

PSR2.PNG

PSR3.PNG

PSR4.PNG

<PSR 레지스터 설명>

  PSR의 아래 5bit는 Mode를 바꾸는데 사용된다. 직접 값을 CPSR이나 SPSR에 넣어서 모드를 바꾸는 것 또한 가능하므로 특정 모드로 전환할 수 있다. THUMB 모드의 경우 T Bit가 1로 설정된다는 정도만 알아두자.

 

2.ARM CPU Mode

 ARM은 총 7개의 Processor Mode를 가지고 있다.

ARM_Mode.PNG

<ARM Processor Mode>

 앞서 살펴봤듯이 User 모드를 제외한 모든 모드는 특권(Priviledge) 모드이고  User/System을 제외한 다른 모드는 Exception 모드이다.

 재미있는 사용법을 가진 모드를 몇가지 살펴보면 아래와 같다.

  • FIQ : ISR 중에 특별히 빨리 처리가 되어야 하는 인터럽트 처리 모드
  • IRQ : 일반적인 인터럽트 처리 모드
  • Undefined : 명령이나 코프로세서가 존재하지 않아서 발생한 Exception 모드. 여기서 특수한 처리를 해주면 명령을 Emulation 하는 것도 가능

 

 

3.ARM Exception Vector And Handling

3.1 Exception Vector

 Exception이 발생하면 당연히 Exception에 대한 처리를 해줘야 하는데 Exception에 따른 처리가 들어있는 곳이 바로 Exception Vector이다.

 Exceptoin Vector의 경우 0x000000000 의 주소에 위치하거나 설정에 따라서 0xFFFF0000 위치에 위치할 수 있다.

 Exception_Vector.PNG

<ARM Exception Vector Table>

 위에서 보면 0x14가 빠져있는데 이것은 Reserved 된 Vector 이기 때문에 사용되지 않는다. Exception Vector의 경우 단순히 4Byte의 크기를 가지므로 해당 Exception을 처리하는 루틴으로 jmp하는 명령이 일반적으로 들어있다.

 

3.2 Exception Handling

 Exception이 발생하면 어떤 일이 생길까? 단순히 모드만 변화되는 것일까? 실제로 Exception이 발생했을 때 일어나는 일은 아래와 같다.

  1. // R14(lr) 레지스터에는 Exception 처리를 끝내고 돌아갈 주소가 저장된다.
  2. R14_<exception_mode> = return link 
  3. SPSR_<exception_mode> = CPSR 
  4. CPSR[4:0] = excpetion mode number 
  5. // ARM 모드로 강제 전환
  6. CPSR[ 5 ] = 0 
  7. // 만약 reset 이나 FIQ 같은 경우이면 FIQ를 Disable 시킨다.
  8. if <exception_mode> == Reset or FIQ then 
  9.     CPSR[ 6 ] = 1
  10. // 인터럽트는 무조건 발생 불가
  11. CPSR[ 7 ] = 1 
  12. // PC에 exception vector의 주소가 입력 됨으로써 해당 주소의 코드가 실행되게 된다.
  13. PC = exception vector address 
  14. ...... Exception 처리 ......
  15. // 처리가 완전히 끝난 후 되돌아 간다.
  16. CPSR = SPSR
  17. PC = R14_<exception_mode>

 위의 굵은 부분이 처리 흐름 부분인데, 인터럽트를 불가하고 Exception을 처리한다음 다시 복구하는 것을 알 수 있다. CPSR의 값을 SPSR의 값으로 복원하고 PC를 변경하는 것은 이것을 자동으로 해주는 명령어를 통해 할 수 있는데 접미사로 "S'가 붙은 MOVS와 LDMS와 같은 명령으로 가능하다.

 아래는 Exception Handler의 Return 부분을 보여주는 예제이다.

  1. SUB R14, R14, #4 
  2. STMFD SP!, { XXXXX, R14 } 
  3. ...... 처리 ...... 
  4. LDMFD SP!, { XXXXX, PC }^ 
  5. or MOVS PC, R14
  6. or SUBS PC, R14, #4

 ARM 어셈블리에어 대해서는 참고. ARM 어셈블리(Assembly) 를 참고하도록 하고 접미사 S와 ^만 봐두고 넘어가자.

 Exception 처리에 대한 자세한 내용은 ARM Manual의 A2-14에 잘 나와있으므로 참고하자.

 

3.3 Exception Priority

 Exception이라도 우선 순위가 다른 데, 아래같은 순서를 가진다.

  • 1 (Highest) : Reset
  • 2 : Data Abort 
  • 3 : FIQ 
  • 4 : IRQ 
  • 5 : Prefetch Abort 
  • 6 (Lowest) : Undefined instruction SWI 

 

4.Memory Mapped I/O

 Memory Mapped I/O는 Port I/O 방식과 달리 메모리 주소에 IO와 통신하는 라인을 연결하여 Memory에 값을 쓰면 버스를 통해 컨트롤러에게 전달되는 방식이다. 따라서 메모리 주소를 공유해서 사용하므로 프로그래밍 시에 접근이 용이하다는 장점이 있으나 실제 사용가능한 메모리 공간이 줄어든다는 단점도 있다.

 Port I/O 같은 경우는 Intel CPU가 대표적으로써 I/O를 위한 별도의 주소공간이 존재하고 I/O를 위해 별도의 명령( IN/OUT )을 통해 데이터를 주고 받는 방식이다.

 == 여기 나중에 내용 더 체우기 ==

 

4.마치면서...

 ARM에서 프로그래밍을 하기위한 배경지식을 쌓기위해 ARM CPU에 대해서 간략하게 알아보았다. 이제 본격적으로 프로그래밍을 한번 해보자. @0@)/~

 

5.첨부

 

 

 

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

26 윈도우 라이브러리(Window Library) 사용을 위한 프로젝트(Project) 만들기

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


들어가기 전에...

0.시작하면서...

 윈도우 라이브러리(Window Library)를 이용해서 개발을 편리하게 할 수 있도록 만든 윈도우 라이브러리용 커스텀 프로젝트이다. 소프트웨어 리셋 라이브러리(Reset Library)도 같이 링크하도록 되어있으니 두 라이브러리 모두 필요하다.

 윈도우 라이브러리에 대한 내용은 02 NDS 윈도우 시스템(Windows System) 문서를 참조하면 되고, 소프트웨어 리셋 라이브러리(Soft Reset Library)에 대한 내용은 06 소프트웨어 리셋 라이브러리(Software Reset Library) 문서를 참조하면 된다.


1.첨부



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

25 ARM7/ARM9 라이브러리 프로젝트(Library Project) 만들기(작성중)


들어가기 전에...

0.시작하면서...



1.사용법



2.마치면서...



3.첨부



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

24 WIFI 라이브러리 설치 및 사용방법(작성중)

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

 

들어가기 전에...

 

0.시작하면서...

 NDS에서 WIFI를 사용할 수 있도록 하는 libwifi가 2007년 9월 일자로 업데이트 되었다. 자세한 내용은 http://www.devkitpro.org 를 참고하면 되고 다운로드는 https://sourceforge.net/project/showfiles.php?group_id=114505&package_id=199021&release_id=541526 에서 직접 내려받을 수 있다

 이제 소스 컴파일부터 실제 사용까지 과정을 알아보자. 

 

1.소스 컴파일 및 설치

 위의 링크에서 dswifi-src-XXX.XXX 로 표시되어있는 source가 포함된 버전을 받는다. 라이브러리를 받지 않고 굳이 소스를 받는 이유는 현재 사용중인 데브킷 프로의 버전과 라이브러리가 맞지 않을 수도 있고 기타 다른 라이브러리(PALib 등등)를 사용하고 있다면 데브킷 프로의 라이브러리들이 교체되었을 가능성도 있으므로 이런 일련의 문제를 한방에 해결하기위해 소스를 컴파일 하는 것이다.

 소스를 다운받아 적당한 폴더에 압축을 풀고 해당 디렉토리로 이동하여 콘솔창에서 make를 입력하면 라이브러리를 빌드할 수 있다.

 빌드가 끝나면 아래와 같은 화면이 나온다. 

dswifi.PNG

<빌드완료>

 

 이제 빌드가 완료되었으므로 dswifi의 include와 lib 폴더devkitpro가 설치된 libnds 폴더 아래에 복사한다. 같은 이름의 파일들이 있다고 덮어쓸지를 물어보는 데, 전부 다 덮어쓰도록 하자. 이상으로 소스 컴파일 및 설치가 끝났다.

 

2.라이브러리 사용 

 dswifi를 사용하기위해서는 ARM9과 ARM7 양쪽 코어에서 작업을 해줘야 한다. 조금 까다로운 절차가 필요한데, 다행이 예제 셈플을 제공하고 있다. 예제 셈플은
http://www.akkit.org/dswifi/ 에서 받을 수 있다(라이브러리를 만든 저작자 같다). 비록 0.3 버전의 테스트 소스이지만 정상적으로 동작한다. 물론 손을 좀 봐야하는 것은 당연한 이야기!!!

 

TODO 

  • WIFI 사용 예제 넣기. 아래의 소스를 정리해서 편리하게 쓸 수 있도록 정리하기
  • Ad-hoc 소스 정리해서 넣기 

 

 

3.마치면서... 

 

4.첨부 

 

 

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

23 Soft Reset 분석

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

 

들어가기 전에...

 

0.시작하면서...

 홈브루 개발자라면 한번쯤은 자신이 개발한 홈브루도 소프트 리셋(전원 버튼을 누르지 않고 재부팅 시키는 방법)에 대해서 고민해 봤을 것이다. 문쉘(MoonShell)을 보면 플러그인 기능으로 Reset.mse를 통해 소프트 리셋(Soft Reset) 기능을 지원한다. 하지만 사실 문쉘 소스에 있는 Reset.mse는 모든 카드를 지원하지 않는다. 아무래도 제작자가 다 테스트 해보기엔 무리였을지도...

 그런데 얼마전에 3 in 1 Expansion Pack 을 쓰면서 우연히 Rudolph(루돌프인가.. ㅡ_ㅡa..)라는 분이 이 문제를 해결했다는 것을 알았다. 이분 덕택에 문쉘 소프트 리셋 기능이 잘 동작하게 된듯....

 이제 본격적으로 한번 분석해 보자. 

 

1.ARM9 코드 분석

  1. //---------------------------------------------------------------------------------
    int main(void) {
    //---------------------------------------------------------------------------------
      MSEINFO_Readed=MSE_GetMSEINFO(&MSEINFO);
     
      REG_IME=0;
     
      POWER_CR = POWER_ALL_2D;
    //  POWER_CR &= ~POWER_SWAP_LCDS;
      POWER_CR |= POWER_SWAP_LCDS;
     
      SetARM9_REG_WaitCR();
     
      irqInit();
     
      {
        void InitVRAM(void);
        InitVRAM();
        void ShowMSEINFO(void);
        ShowMSEINFO();
        void SoftReset(void);
        SoftReset();
      }
     
      while(1);

 Soft Reset 함수를 부르는거 말고는 별로 하는 것이 없는 듯 하다.

 

  1. void SoftReset(void)

    {

    const char *pname=MSEINFO.AdapterName;

     

    _consolePrintf("go to farmware menu. [%s]\n",pname);

     

    ERESET RESET=RESET_NULL;

     

    if(strcmp(pname,"M3CF")==0){

    cartSetMenuMode_M3CFSD();

    RESET=RESET_MENU_M3CF;

    }

    if(strcmp(pname,"M3SD")==0){

    cartSetMenuMode_M3CFSD();

    RESET=RESET_MENU_M3SD;

    }

    if(strcmp(pname,"MPCF")==0){

    cartSetMenuMode_MPCF();

    RESET=RESET_MENU_MPCF;

    }

    if(strcmp(pname,"SCCF")==0){

    cartSetMenuMode_SCCFSD();

    RESET=RESET_MENU_SCCF;

    }

    if(strcmp(pname,"SCSD")==0){

    cartSetMenuMode_SCCFSD();

    RESET=RESET_MENU_SCSD;

    }

    if(strcmp(pname,"SCLT")==0){

    cartSetMenuMode_SCCFSD();

    RESET=RESET_MENU_SCSD;

    }

    if(strcmp(pname,"EZSD")==0){

    cartSetMenuMode_EZSD();

    RESET=RESET_MENU_EZSD;

    }

    if(strcmp(pname,"DLMS")==0){

    RESET=RESET_MENU_DSLink;

    IPCEX->RESET=RESET;

    LinkReset_ARM9();

    while(1);

    }

     

    //====== R4TF was added.

    if(strcmp(pname,"R4TF")==0){

    if(FAT_InitFiles() == false) {

    _consolePrintf("Can not initialized FAT.\n");

    while(1);

    }

    FAT_FILE *r4 = FAT_fopen("/_DS_MENU.DAT", "rb");

    (*(vu32*)0x027FFE18) = r4->dirEntSector*512+r4->dirEntOffset*32; <== 이부분 주의

    FAT_fclose(r4);

    _consolePrintf("_DS_MENU.DAT = %08X\n", *(vu32*)0x027FFE18);

     

    _boot_VRAM_clear();

     

    RESET=RESET_MENU_R4TF;

    IPCEX->RESET=RESET;

     

    REG_IME = 0;

    REG_IE = 0;

    REG_IF = REG_IF;

     

    WAIT_CR = 0xE880;

    REG_IPC_SYNC = 0;

    DMA0_CR = 0;

    DMA1_CR = 0;

    DMA2_CR = 0;

     

    ret_menu9_R4();

    while(1);

    }

     

    //====== EZ5S was added.

    if(strcmp(pname,"EZ5S")==0){

    RESET=RESET_MENU_EZ5S;

    IPCEX->RESET=RESET;

    ret_menu9_EZ5();

    while(1);

    }

    //====== by Rudolph (2007/05/25)

     

     

    if(RESET==RESET_NULL){

    _consolePrintf("not support adapter type.\n");

    while(1);

    }

     

    *(vu16*)(0x04000208) = 0; //REG_IME = IME_DISABLE;

    *(vu16*)(0x04000204) |= 0x0880; //sysSetBusOwners(false, false);

    *((vu32*)0x027FFFFC) = 0;

    *((vu32*)0x027FFE04) = (u32)0xE59FF018;

    *((vu32*)0x027FFE24) = (u32)0x027FFE04;

     

    IPCEX->RESET=RESET;

     

    asm("swi 0x00"); //swiSoftReset();

    asm("bx lr");

     

    while(1);

    }

 (*(vu32*)0x027FFE18) = r4->dirEntSector*512+r4->dirEntOffset*32; 코드에서 실제로 카드 내에 FAT 영역의 주소를 ARM7에 넘겨줘서 ARM7이 카드 명령을 통해 데이터를 읽게 만드는 부분이 중요한 부분인것 같다. ARM7에서도 카드에 접근해서 데이터를 읽을 수 있음을 보여주는 예이다.

 이부분에 대한 자세한 내용은 아래의 1.1 참고 부분을 참조하자. 

 

 아래는 위에서 호출하는 _boot_VRAM_clear() 함수와 ret_menu9_R4() 함수이다.

  1. //====== R4TF was added.
    static void _boot_VRAM_clear()
    {
    /*********
     u16 _VRAM_C_CR=VRAM_C_CR;
     u16 _VRAM_D_CR=VRAM_D_CR;
  2.  VRAM_C_CR = VRAM_ENABLE | (1 | 0);
     VRAM_D_CR = VRAM_ENABLE | (1 | (1<<3));
  3.  memset((char*)0x06000000, 0x00, 0x40000);
  4.  VRAM_C_CR=_VRAM_C_CR;
     VRAM_D_CR=_VRAM_D_CR;
    *********/
  5. /* 덇렄밒궸?렑궠귢귡궴궞귣궻귒긏깏귺 */
     u16 *vr;
     int i;
  6.  u16 _VRAM_C_CR=VRAM_C_CR;
  7.  VRAM_C_CR = VRAM_ENABLE | VRAM_C_MAIN_BG_0x6000000;
  8.  vr = (u16*)0x06010000;
     for(i = 0; i < 0x10000/2; i++) {
      *vr = 0x0000;
      vr++;
     }
  9.  VRAM_C_CR=_VRAM_C_CR;
  10. }
    //====== by Rudolph (2007/05/25) 

 

  1.  .ALIGN
     .GLOBAL ret_menu9_R4
     .CODE 32
     .ARM
  2. ret_menu9_R4:
  3.  mov r0, #0x2000
     orr r0, r0, #0x78
     mov r1, #0x00
     mcr 15, 0, r0, cr1, cr0, 0
     mcr 15, 0, r1, cr7, cr5, 0
     mcr 15, 0, r1, cr7, cr6, 0
     mcr 15, 0, r1, cr7, cr10, 4
     orr r0, r0, #0x50000
     mcr 15, 0, r0, cr1, cr0, 0
  4.       ldr r0, =0x027FFDF8
          ldr r1, =0xE51FF004
          str r1, [r0, #0x0]   @ (027ffdf8)=E51FF004:ldr r15,[r15, #-0x4]
          str r0, [r0, #0x4]   @ (027ffdfC)=027FFDF8
  5.  bx r0    @ JUMP 027FFDF8
  6.  .END 

 위의 ret_menu9_R4까지 호출하면 ARM9은 처리가 완전히 끝난다. 

 

1.1 참고 (*(vu32*)0x027FFE18) = r4->dirEntSector*512+r4->dirEntOffset*32 코드 분석

 2007/07/26 테스트 결과  r4->dirEntSector 값은 실제 _DS_MENU.DAT가 존재하는 Directory Entry의 물리 섹터 번호를 의미하고 r4->dirEntOffset*32 값은 Directory Entry 내의 Offset을 의미했다. 위의 코드를 아래와 같이 고쳐서 테스트 해본 결과 0x2E0 섹터의 2번째 Offset에 _DS_MENU.DAT 파일이 존재하는 것으로 나왔는데, 실제 확인 결과 그러했다.

  1. //====== R4TF was added.
      if(strcmp(pname,"R4TF")==0){
     if(FAT_InitFiles() == false) {
      _consolePrintf("Can not initialized FAT.\n");
      while(1);
     }
     FAT_FILE *r4 = FAT_fopen("/_DS_MENU.DAT", "rb");
     (*(vu32*)0x027FFE18) = r4->dirEntSector*512+r4->dirEntOffset*32;
     _consolePrintf("_DS_MENU.DAT = %08X\n", *(vu32*)0x027FFE18);
     _consolePrintf("Entry Sector = %08X, Entry Offset = %08X\n", r4->dirEntSector, r4->dirEntOffset );
     FAT_fclose(r4);
     while( 1 );
     _boot_VRAM_clear();
  2.  RESET=RESET_MENU_R4TF;
     IPCEX->RESET=RESET;
  3.  REG_IME = 0;
     REG_IE = 0;
     REG_IF = REG_IF;
  4.  WAIT_CR = 0xE880;
     REG_IPC_SYNC = 0;
     DMA0_CR = 0;
     DMA1_CR = 0;
     DMA2_CR = 0;
  5.  ret_menu9_R4();
     while(1);
      } 

R4.PNG

<실제 분석한 화면>

 이것으로 보아 R4에는 디렉토리 엔트리 정보를 이용해서 실제 파일을 읽어들일 수 있음을 알 수 있었다. 

 또한 rebootlib 소스를 분석하면서 저 _DS_MENU.DAT 파일이 특수한 형태로 암호화된 nds 파일이라는 것을 알 수 있었고, ARM7 소스에서 왜 카드 명령을 통해 다시 읽어오는가도 짐작할 수 있었다. 리버싱을 통해 R4에게 명령을 내리고 R4 카드가 디코딩을 하면 그것을 다시 메모리에 복사하면 굳이 압축 해제 알고리즘을 몰라도 처리 가능하기 때문이었다.

 

 

2.ARM7 코드 분석

 아래는 ARM7에서 사용하는 함수 메인이다.

  1. __attribute__((noinline)) static void main_Proc_Reset(ERESET RESET)
    {
      switch(RESET){
        case RESET_NULL: return; break;
        case RESET_VRAM: {
          REG_IME = IME_DISABLE; // Disable interrupts
          REG_IF = REG_IF; // Acknowledge interrupt
          *((vu32*)0x027FFE34) = (u32)0x06000000; // Bootloader start address for VRAM
          swiSoftReset(); // Jump to boot loader
        } break;
        case RESET_GBAMP: boot_GBAMP(); break;
        case RESET_GBAROM: boot_GBAROM(); break;
        case RESET_MENU_DSLink: LinkReset_ARM7(); break;
        case RESET_MENU_MPCF: break;
        case RESET_MENU_M3CF: break;
        case RESET_MENU_M3SD: break;
        case RESET_MENU_SCCF: break;
        case RESET_MENU_SCSD: break;
        case RESET_MENU_EZSD: break;
    //====== R4TF was added.
        case RESET_MENU_R4TF: ret_menu7_R4(); break;
    //====== EZ5S was added.
        case RESET_MENU_EZ5S: ret_menu7_EZ5(); break;
    //====== by Rudolph (2007/05/25)
      }
  2.   *(vu16*)(0x04000208) = 0;       //REG_IME = IME_DISABLE;
      *((vu32*)0x027FFE34) = *((vu32*)0x027FFFF8);
      asm("swi 0x00");                //swiSoftReset();
      asm("bx lr");

 

  1. void ret_menu7_R4()
    {
     char *adr;
     u32 blk, siz;
     u32 i;
     u32 *mem;
  2.  REG_IME = 0;
     REG_IE = 0;
     REG_IF = REG_IF;
  3.  REG_IPC_SYNC = 0;
     DMA0_CR = 0;
     DMA1_CR = 0;
     DMA2_CR = 0;
     DMA3_CR = 0;
  4.  while((*(vu32*)0x027FFDFC) != 0x027FFDF8); // Timing adjustment with ARM9
  5.  mem = (u32*)0x02000000;
     for(i = 0; i < 0x3FF800/4; i++) {
      *mem = 0x00000000;
      mem++;
     }
    // memset((u8*)0x2000000, 0x00, 0x3FF800);
  6.  while(_set_r4menu());
  7.  adr = (char*)0x027FFE00;
     _read_r4menu(adr, 0);   // Header

  8.  blk = (*(vu32*)0x027FFE20) / 512;
     adr = (char*)(*(vu32*)0x027FFE28);
     siz = (*(vu32*)0x027FFE2C);
     for(i = 0; i < siz; i += 512) {  // ARM9
      _read_r4menu(adr, blk);
      blk++;
      adr += 512;
     }
  9.  blk = (*(vu32*)0x027FFE30) / 512;
     adr = (char*)(*(vu32*)0x027FFE38);
     siz = (*(vu32*)0x027FFE3C);
     for(i = 0; i < siz; i += 512) {  // ARM7
      _read_r4menu(adr, blk);
      blk++;
      adr += 512;
     }
  10.  *(vu32*)0x027FFDFC = *(vu32*)0x027FFE24;
     asm("swi 0x00");   // JUMP 0x027FFE34
  11.  while(1);

 

  1. static int _set_r4menu()
    {
     u32 add;
  2.  add = (*(vu32*)0x027FFE18); <== 이 부분이 _DS_MENU.DAT에 대한 FAT 정보를 읽는 부분이다.
  3.  while(CARD_CR2 & CARD_BUSY);
  4.  CARD_CR1H = 0xC0;
     CARD_COMMAND[0] = 0xB4;
     CARD_COMMAND[1] = (add >> 24) & 0xFF;
     CARD_COMMAND[2] = (add >> 16) & 0xFF;
     CARD_COMMAND[3] = (add >> 8) & 0xFF;
     CARD_COMMAND[4] = add & 0xFF;
    // CARD_COMMAND[5] = 0x00;
    // CARD_COMMAND[6] = 0x00;
    // CARD_COMMAND[7] = 0x00;
  5.  CARD_CR2 = 0xA7586000;
     while(!(CARD_CR2 & CARD_DATA_READY));
  6.  return(CARD_DATA_RD);

 

  1. static int _read_r4menu(char *buf, u32 blk)
    {
     int s = 0;
     u32 *buf32;
  2.  buf32 = (u32*)buf;
     blk *= 2;

  3.  do {
      while(CARD_CR2 & CARD_BUSY);
      CARD_CR1H = 0xC0;
      CARD_COMMAND[0] = 0xB6;
      CARD_COMMAND[1] = (blk >> 16) & 0xFF;
      CARD_COMMAND[2] = (blk >> 8) & 0xFF;
      CARD_COMMAND[3] = blk & 0xFF;
      CARD_COMMAND[4] = 0x00;
    //  CARD_COMMAND[5] = 0x00;
    //  CARD_COMMAND[6] = 0x00;
    //  CARD_COMMAND[7] = 0x00;
      CARD_CR2 = 0xA7586000;
      while(!(CARD_CR2 & CARD_DATA_READY));
     } while(CARD_DATA_RD);
  4.  while(CARD_CR2 & CARD_BUSY);

  5.  CARD_CR1H = 0xC0;
     CARD_COMMAND[0] = 0xBF;
     CARD_COMMAND[1] = (blk >> 16) & 0xFF;
     CARD_COMMAND[2] = (blk >> 8) & 0xFF;
     CARD_COMMAND[3] = blk & 0xFF;
     CARD_COMMAND[4] = 0x00;
    // CARD_COMMAND[5] = 0x00;
    // CARD_COMMAND[6] = 0x00;
    // CARD_COMMAND[7] = 0x00;
     CARD_CR2 = 0xA1586000;
  6.  do {
      while(!(CARD_CR2 & CARD_DATA_READY));
      *buf32 = CARD_DATA_RD;
      buf32++;
      s += 4;
     } while(CARD_CR2 & CARD_BUSY);
  7.  return(s);
    }

 

 위에서 사용된 CARD 관련 명령들은 libnds.h에 정의되어있으며 아래와 같다. 

  1. // Card bus
    #define CARD_CR1       (*(vuint16*)0x040001A0)
    #define CARD_CR1H      (*(vuint8*)0x040001A1)
    #define CARD_EEPDATA   (*(vuint8*)0x040001A2)
    #define CARD_CR2       (*(vuint32*)0x040001A4)
    #define CARD_COMMAND   ((vuint8*)0x040001A8)
  2. #define CARD_DATA_RD   (*(vuint32*)0x04100010)
  3. #define CARD_1B0       (*(vuint32*)0x040001B0)
    #define CARD_1B4       (*(vuint32*)0x040001B4)
    #define CARD_1B8       (*(vuint16*)0x040001B8)
    #define CARD_1BA       (*(vuint16*)0x040001BA)

  4. #define CARD_CR1_ENABLE  0x80  // in byte 1, i.e. 0x8000
    #define CARD_CR1_IRQ     0x40  // in byte 1, i.e. 0x4000

  5. // CARD_CR2 register:
  6. #define CARD_ACTIVATE   (1<<31)  // when writing, get the ball rolling
    // 1<<30
    #define CARD_nRESET     (1<<29)  // value on the /reset pin (1 = high out, not a reset state, 0 = low out = in reset)
    #define CARD_28         (1<<28)  // when writing
    #define CARD_27         (1<<27)  // when writing
    #define CARD_26         (1<<26)
    #define CARD_22         (1<<22)
    #define CARD_19         (1<<19)
    #define CARD_ENCRYPTED  (1<<14)  // when writing, this command should be encrypted
    #define CARD_13         (1<<13)  // when writing
    #define CARD_4          (1<<4)   // when writing
  7. // 3 bits in b10..b8 indicate something
    // read bits
    #define CARD_BUSY       (1<<31)  // when reading, still expecting incomming data?
    #define CARD_DATA_READY (1<<23)  // when reading, CARD_DATA_RD or CARD_DATA has another word of data and is good to go

 CARD에 직접 명령을 내려서 이것을 처리할려면 R4에 대해서 자세히 안다는 전제가 필요한데... 정말 대단한 사람들이 아닐 수 없다. 이걸 다 어떻게 분석한거지... ㅡ_ㅡ;;;

 Card에 대한 내용은 GBATEK(http://nocash.emubase.de/gbatek.htm#dsmemorycontrolcartridgesandmainram)에서 찾을 수 있다.

 

 

3.libfat 코드 분석 

 구버전의 GBA FS는 File ID에 FILE_STRUCTURE 포인터를 그대로 넘기도록 되어있어서 File ID를 이용하면 FAT 관련 정보를 모두 얻어낼 수 있었다. 하지만 libfat로 업그레이드 되면서 POSIX 표준 함수(fopen, fwrite, fread, fclose 등)을 지원하게 되었고 구조가 조금 달라지게 되었다. 물론 FILE* 값을 이용해서 fileno() 함수로 File ID를 얻어올 수 있지만 테스트 결과 GBA FS 처럼 FILE_STRUCTURE로 캐스팅해서 값을 정상적으로 얻을 수 없었다(물론 잠결에 테스트 했기때문에, 좀더 테스트를 진행해 봐야 한다.. ㅡ_ㅡa..)

 다행이도 libfat의 Partition 관련 변수가 export 되어있으므로 그 변수를 이용해서 FAT 관련 기본정보를 얻을 수 있고, diropen() 과 같은 함수를 사용하면 Directry 구조체를 얻을 수 있는데 이것을 이용하면 Directory Entry에 대한 정보를 얻을 수 있으므로 GBA FS 수준의 정보를 얻을 수 있다. 

3.1 dir.h 분석

  1. /* Directory iterator for mantaining state between dir* calls */
    typedef struct {
        int device;
        void *dirStruct;
    } DIR_ITER;
  2. DIR_ITER* diropen (const char *path);
    int dirreset (DIR_ITER *dirState);
    int dirnext (DIR_ITER *dirState, char *filename, struct stat *filestat);
    int dirclose (DIR_ITER *dirState); 

 위의 코드를 보면 diropen() 함수는 DIR_ITER* 를 리턴한다. 구조체의 dirStruct 필드가 심상치 않은데, 예상대로 아래에서 보듯 DIR_STATE_STRUCT 구조체를 얻을 수 있다. 

 

3.2 fatdir.c 

  1. int _FAT_dirnext_r (struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) {
     DIR_STATE_STRUCT* state = (DIR_STATE_STRUCT*) (dirState->dirStruct);
  2.  // Make sure we are still using this entry
     if (!state->inUse) {
      r->_errno = EBADF;
      return -1;
     } 

 DIR_STATE_STRUCT는 아래에서 볼 수 있다.

 

3.3 fatdir.h 

  1. typedef struct {
     PARTITION* partition;
     DIR_ENTRY currentEntry;
     u32 startCluster;
     bool inUse;
     bool validEntry;
    } DIR_STATE_STRUCT;

 만세, PARTITION 정보와 DIR_ENTRY 구조체 정보를 얻을 수 있다.

 

3.4 patition.h 

  1. #ifdef NDS
    typedef enum {PI_DEFAULT, PI_SLOT_1, PI_SLOT_2, PI_CUSTOM} PARTITION_INTERFACE;
    #else
    typedef enum {PI_CART_SLOT} PARTITION_INTERFACE;
    #endif
  2. typedef struct {
     u32 fatStart;
     u32 sectorsPerFat;
     u32 lastCluster;
     u32 firstFree;
    } FAT;
  3. typedef struct {
     const IO_INTERFACE* disc;
     CACHE* cache;
     // Info about the partition
     bool readOnly;  // If this is set, then do not try writing to the disc
     FS_TYPE filesysType;
     u32 totalSize;
     u32 rootDirStart; <== 실제 Root Directory가 시작되는 물리 섹터 번호. 이것으로 접근하면 바로 Root Directory를 찾을 수 있다.
     u32 rootDirCluster;
     u32 numberOfSectors;
     u32 dataStart;
     u32 bytesPerSector;
     u32 sectorsPerCluster;
     u32 bytesPerCluster;
     FAT fat;
     // Values that may change after construction
     u32 cwdCluster;   // Current working directory cluser
     u32 openFileCount;
    } PARTITION;

 FAT에 관련된 거의 대부분의 정보는 PARTITION 구조체에서 얻을 수 있다. IO_INTERFACE 구조체를 이용하면 현재 사용중인 Device의 타입을 알 수 있다.

 

  1. extern PARTITION* _FAT_partitions[];

 위와 같이 사용하면 실제 libfat에 있는 파티션 정보를 얻어올 수 있고, 인덱스로 PI_DEFAULT( 0 ), PI_SLOT_1( 1 ), PI_SLOT_2( 2 ), PI_CUSTOM( 3 )과 같은 값을 넘겨주면 해당 Partition 정보에 접근할 수 있다. 보통 Default로 설정하여 libfat를 사용하므로 PI_DEFAULT or 0을 넣으면 Partition 정보를 얻을 수 있다.

 

3.5 disc_io.h

  1. #define FEATURE_MEDIUM_CANREAD  0x00000001
    #define FEATURE_MEDIUM_CANWRITE  0x00000002
    #define FEATURE_SLOT_GBA   0x00000010
    #define FEATURE_SLOT_NDS   0x00000020
  2. typedef bool (* FN_MEDIUM_STARTUP)(void) ;
    typedef bool (* FN_MEDIUM_ISINSERTED)(void) ;
    typedef bool (* FN_MEDIUM_READSECTORS)(u32 sector, u8 numSecs, void* buffer) ;
    typedef bool (* FN_MEDIUM_WRITESECTORS)(u32 sector, u8 numSecs, void* buffer) ;
    typedef bool (* FN_MEDIUM_CLEARSTATUS)(void) ;
    typedef bool (* FN_MEDIUM_SHUTDOWN)(void) ;

  3. typedef struct {
     unsigned long   ul_ioType ;
     unsigned long   ul_Features ;
     FN_MEDIUM_STARTUP  fn_StartUp ;
     FN_MEDIUM_ISINSERTED fn_IsInserted ;
     FN_MEDIUM_READSECTORS fn_ReadSectors ;
     FN_MEDIUM_WRITESECTORS fn_WriteSectors ;
     FN_MEDIUM_CLEARSTATUS fn_ClearStatus ;
     FN_MEDIUM_SHUTDOWN  fn_Shutdown ;
    } IO_INTERFACE, *LPIO_INTERFACE ;

 ul_ioType을 자세히 보면 4Byte의 Charactor ASCII 값으로 되어있고 0Byte ~ 3Byte의 순서로 "M3SD", "M3CF", "R4TF" 와 같은 문자들이 들어있다. 이것을 이용하면 현재 사용중인 Device의 정보도 알 수 있다.

 

3.6 Directory Navigation으로 정보 뽑기 예제

 첨부에 있는 msev10_reset_R4EZ5_kkamagui.zip의 ARM9 코드를 조금 수정하여 GBA FS를 사용하지 않고 libfat를 이용해서 정보를 뽑아낸 예제이다.

  1. //====== R4TF was added.
      if(strcmp(pname,"R4TF")==0)
      {
        PARTITION* pstPartition;
        DIR_ITER* pstCur;
        DIR_STATE_STRUCT* pstDirState;
        int iOffset;
        int iRootDirSector;
       
        char vcBuffer[ 256 ];
       
        fatInitDefault();
       
        iOffset = -1;
        pstCur = diropen( "/" );
        if( pstCur == NULL )
        {
      _consolePrintf("Can Not Open /_DS_MENU.DAT File.\n");
            while( 1 );
        }
        while( dirnext( pstCur, vcBuffer, NULL ) == 0 )
        {
            pstDirState = ( DIR_STATE_STRUCT* ) pstCur->dirStruct; <== 여기가 포인트~ pstDirState를 이용하면 FAT 관련 섹터정보를 얻을 수 있음 자세한건 위의 구조체 참조
            _consolePrintf( "%s %08X\n",
                vcBuffer, pstDirState->currentEntry.dataStart.offset );
            if( strcmp( vcBuffer, "_DS_MENU.DAT" ) == 0 )
            {
                // Offset 자체가 1부터 카운팅 되기 때문에 실제 인덱스를 알려면
                // 1을 빼야한다.
                iOffset = pstDirState->currentEntry.dataStart.offset - 1;
               
                _consolePrintf( "DirEntry = %08X\n", pstDirState->currentEntry.dataStart.offset );
                break;
            }
        }
        dirclose( pstCur );
       
        pstPartition = _FAT_partitions[ 0 ];
        _consolePrintf("partition = %08X %08X\n", 
            pstPartition->rootDirStart, pstPartition->bytesPerSector );
        iRootDirSector = pstPartition->rootDirStart;
  2.     // R4 CART에 넘겨줄 _DS_MENU.DAT의 정보가 담긴 Directory Offset 값
        (*(vu32*)0x027FFE18) = iRootDirSector * 512 + iOffset * 32;
        
        while( 1 ); 

Directory_테스트.PNG

<실행 결과>

 

4.마치면서... 

 간단히 NDS를 소프트웨어적으로 리셋하는 코드에 대해서 살펴보았다. 조금씩 Device 마다 차이는 있지만 결국 NDS의 상태를 초기상태와 비슷하게 만들거나 또는 Firmware를 강제로 메모리에 올린다음 그것을 실행하게 방식으로 처리하는 것을 알 수 있었다.

 더 궁금한 점은 첨부에 올려놓은 소스를 보면 될 것 같고, 다음에는 이것을 이용하여 만든 Reset Library를 소개하고 사용법에 대해서 알아보겠다.

 

5.첨부 

 

 

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

22 타이머(Timer)를 이용한 프로파일러(Profiler) 만들기

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

 

들어가기 전에...

 

0.시작하면서...

 프로그램을 만들다 보면 이상하게 느린 경우가 있다. 대부분이 알고리즘을 잘못 선택했거나, 아니면 쓸데없는 루프를 돈다거나, 극히 드문 경우지만 버퍼 오버플로우가 나서 다른 함수를 실컷 수행하다가 운좋게 다시 돌아오는 경우도 있다(실제로 겪어봤다... ㅡ_ㅡ;;;)

 이런 경우 GetTickCount()나 혹은 다른 카운팅 함수를 이용해서 실행 시간을 측정해서 원인을 분석하는게 일반적이다. 시간을 측정하기 위해서는 어느정도 만족할만한 시간 분해능을 가진 타이머가 필요한데, 마침 NDS에서는 4개의 Timer를 가지고 있으므로 이것을 이용하여 프로파일러를 만들어보자.

 

1.타이머(Timer) 설정

 타이머는 3번 타이머를 사용한다고 했다. 그럼 이제 남은건 분해능 설정인데, 1/1000초 정도면 괜찮을 것 같다. 아래는 Timer를 1/1000초로 설정한는 소스이다.

  1. /**
     프로파일러 초기화
      1/1000 마다 한번씩 튀게 만든다.
    */
    void CProfiler::Initialize( void )
    {
        TIMER3_DATA = TIMER_FREQ_256( 1000 ); 
        TIMER3_CR = TIMER_ENABLE | TIMER_IRQ_REQ | TIMER_DIV_256;
     
        irqSet( IRQ_TIMER3, isrProfilerTimer );
        irqEnable( IRQ_TIMER3 );

 

2.최대 프로파일러(Profiler) 개수

 여러군데 값을 저장하여 테스트 할 수 있으므로 어느정도의 개수를 가지도록 해야 할텐데... 일단 지금은 10개로 해놨다.

 

3.구현

 프로파일러를 구현하는 방법은 의외로 간단하다. 프로파일링을 시작하는 부분에서 현재 Timer의 값을 저장하고, 후에 결과를 출력할 부분에서 현재 값과 저장한 값의 차이를 리턴하여 걸린 시간을 리턴하면 된다.  시간을 계속 갱신하여 인터벌을 계산하고 싶으면 업데이트를 수행하면 된다(윈도우에서 간단하게나마 GetTickCount()를 이용해서 테스트 해본 사람은 금방 알 것이다.)

 

아래는 Profiler.h의 내용이다.

  1. #ifndef __PROFILER_H__
    #define __PROFILER_H__

  2. #define MAX_PROFILERCOUNT 10
    #define DWORD unsigned int
  3. class CProfiler
    {
    protected:
     static DWORD ms_vdwTime[ MAX_PROFILERCOUNT ];
     
    public:
     CProfiler();
     ~CProfiler();
  4.  static void Initialize( void );
     static void Finalize( void );
     
     static void Update( int iIndex );
     static DWORD GetInterval( int iIndex );
     static bool IsValidIndex( int iIndex );
    };
  5. #endif /*PROFILER_H_*/

 

 아래는 Profiler.cpp의 내용이다.

  1. #include "Profiler.h"
    #include <nds.h>
  2. // Static 변수
    DWORD CProfiler::ms_vdwTime[ MAX_PROFILERCOUNT ];
  3. static DWORD gs_dwTimerTick = 0;
  4. /**
     Profiler Timer의 핸들러 루틴
    */
    void isrProfilerTimer( void )
    {
     gs_dwTimerTick++;
    }

  5. /**
     Constructor
    */
    CProfiler::CProfiler()
    {
  6. }
  7. /**
     Destructor
    */
    CProfiler::~CProfiler()
    {
  8. }
  9. /**
     프로파일러 초기화
      1/1000 마다 한번씩 튀게 만든다.
    */
    void CProfiler::Initialize( void )
    {
     TIMER3_DATA = TIMER_FREQ_256( 1000 );
     TIMER3_CR = TIMER_ENABLE | TIMER_IRQ_REQ | TIMER_DIV_256;
     
        irqSet( IRQ_TIMER3, isrProfilerTimer );
        irqEnable( IRQ_TIMER3 );
    }
  10. /**
     프로파일러 종료
    */
    void CProfiler::Finalize( void )
    {
     TIMER3_CR &= ~TIMER_ENABLE;
     
        irqSet( IRQ_TIMER3, 0 );
        irqDisable( IRQ_TIMER3 );
    }

  11. /**
     현재 Timer 값을 Update 한다.
    */
    void CProfiler::Update( int iIndex )
    {
     if( IsValidIndex( iIndex ) == false )
     {
      return ;
     }
     ms_vdwTime[ iIndex ] = gs_dwTimerTick;
    }
  12. /**
      걸린 시간을 얻는다.
    */
    DWORD CProfiler::GetInterval( int iIndex )
    {
     if( IsValidIndex( iIndex ) == false )
     {
      return 0xFFFFFFFF;
     }
     return ( gs_dwTimerTick - ms_vdwTime[ iIndex ] );
    }
  13. /**
     인덱스가 유효한가 리턴한다.
    */
    bool CProfiler::IsValidIndex( int iIndex )
    {
     if( ( iIndex < 0 ) || ( iIndex >= MAX_PROFILERCOUNT ) )
     {
      return false;
     }
     return true;

 

4.사용법

 프로파일러는 Static 함수들을 가지고 있으므로 굳이 객체를 만들 필요 없다. 아래는 사용방법을 나타낸 것이다.

  1. // 초기화 
  2. CProfiler::Initialize();
  3. ...... 생략 ......
  4. // 프로파일링 시작 시점
  5. // 0번 프로파일러의 값을 갱신한다. 
  6. CProfiler::Update( 0 );
  7. DoSomething();
  8. printf( "실행하는데 걸린 시간 = %d", CProfiler::GetInterval( 0 ) );
  9. // 다시 0번 프로파일러 값 갱신
  10. CProfiler::Update( 0 );

 아주 간단하다. Update() 및 GetInterval() 함수에 사용된 인덱스는 현재 0 ~ 9번까지 사용할 수 있다.

 

5.마치면서...

 NDS의 타이머를 이용해서 간단한 프로파일러를 만드는 방법에 대해 알아보았다. NDS의 열악한 디버깅 환경으로 인해 속도 측정에 어려운 부분이 있었다. 이제 프로파일러도 생겼으니 열심히 효율을 높여보자. @0@)/~!!!

 

6.첨부

 

 

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

20 ARM7/ARM9 커스텀 프로젝트(Custom Project) 만들기

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

 

들어가기 전에...

 

0.시작하면서

 ARM9만 사용해도 어느정도 기능을 갖춘 홈브루를 만들 수 있다. 왠만한 컨트롤은 ARM9에서 할 수 있으며, ARM7에서만 가능한 기능도 IPC를 통해서 libnds가 어느정도 구현해 놓았기 때문이다.

 하지만 여기서 안주할 수 없다. ARM7에 특별한 기능을 추가하기 위해서는 당연히 ARM7 작성하는 것이 필수이다. 그럼 어떻게 ARM7 코드를 작성하여 ARM9 코드와 같이 NDS 파일을 만들 수 있는지 알아보자.

 

1.Root의 makefile

 \devkitPro\examples\nds\templates 폴더에 가면 Combine 폴더가 있다. Combine 폴더는 ARM7과 ARM9의 elf 파일을 생성하고 그것을 하나로 뭉쳐 nds 파일을 생성하도록 make 파일이 구성되어있다. 일단 Root 폴더에 있는 makefile을 보자.

  1. #---------------------------------------------------------------------------------
    .SUFFIXES:
    #---------------------------------------------------------------------------------
    ifeq ($(strip $(DEVKITARM)),)
    $(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM)
    endif
  2. include $(DEVKITARM)/ds_rules
  3. export TARGET  := $(shell basename $(CURDIR))
    export TOPDIR  := $(CURDIR)

  4. .PHONY: $(TARGET).arm7 $(TARGET).arm9
  5. #---------------------------------------------------------------------------------
    # main targets
    #---------------------------------------------------------------------------------
    all: $(TARGET).ds.gba
  6. $(TARGET).ds.gba : $(TARGET).nds
  7. #---------------------------------------------------------------------------------
    $(TARGET).nds : $(TARGET).arm7 $(TARGET).arm9
     ndstool -c $(TARGET).nds -7 $(TARGET).arm7 -9 $(TARGET).arm9
  8. #---------------------------------------------------------------------------------
    $(TARGET).arm7 : arm7/$(TARGET).elf
    $(TARGET).arm9 : arm9/$(TARGET).elf
  9. #---------------------------------------------------------------------------------
    arm7/$(TARGET).elf:
     $(MAKE) -C arm7
     
    #---------------------------------------------------------------------------------
    arm9/$(TARGET).elf:
     $(MAKE) -C arm9
  10. #---------------------------------------------------------------------------------
    clean:
     $(MAKE) -C arm9 clean
     $(MAKE) -C arm7 clean
     rm -f $(TARGET).ds.gba $(TARGET).nds $(TARGET).arm7 $(TARGET).arm9

  위에서 보는 것과 같이 ARM7과 ARM9 폴더 각각의 make를 실행하여 .arm7, .arm9 파일을 생성하고 그것을 ndstool로 합쳐서 nds 파일을 만든다.

 

2.arm9 및 arm7의 makefile

 arm9의 makefile에 대한 설명은 00 NDS makefile 및 NDS 파일 생성 과정 분석에서 이미 했으니 찾아보도록 하자. ARM7 또한 ARM9의 makefile과 다른 것이 거의 없으니 위 문서를 참조하면 된다.

 

3.makefile의 수정

 templete 폴더에 있는 makefile은 arm7과 arm9이 거의 비슷하지만 약간 다르다. 아래는 ARM7의 INCLUDES 값이다.

  1. INCLUDES := include build

 arm9 및 arm7에 build 폴더를 추가하자. bin2s.exe 파일을 통해 binary 데이터를 롬파일에 추가하면 build 폴더에 .h 파일이 생성된다. 따라서 편리하게 쓰기위해서 build 폴더를 include에 추가하자.

 자작한 Custom Library를 위한 폴더를 추가해서 Custom 폴더에 자주쓰고 거의 변하지 않는 파일들을 넣어서 나중에 다시 사용할 수 있도록 하자.  arm7/arm9 makefile의 SOURCES 부분에 MyLibrary 폴더를 추가하고 arm7/arm9 각각의 폴더에 MyLibrary 폴더를 추가한다. 앞으로 이 폴더에는 자체 제작한 라이브러리 파일을 넣을 것이다.

 DATA 부분에는 롬파일에 메모리 배열형태로 추가할 데이터가 있는 폴더를 넣는다. data 폴더로 설정하고 .bin 파일의 이름으로 파일을 생성하면 bin2s.exe 실행파일에 의해 .o 파일이 생성되어 같이 링크되게 된다.

  1. SOURCES  := source MyLibrary
  2. DATA     := data

 이 프로젝트를 빌드하면 .arm9 및 .arm7 파일이 생성되고 .nds 파일이 만들어 진다.

 

4.실행 결과

 생성된 .nds 파일을 실행하면 터치 스크린의 좌표를 찍어주는 아래와 같은 화면이 보인다.

Project.PNG

<실행화면>

 

5.마치며...

 지금까지 ARM7 및 ARM9 모두를 사용하는 Templete 파일을 이용해서 나만의 Custom Project를 생성하는 방법을 알아보았다. 이로써 우리는 ARM7 및 ARM9 코드를 모두 작성할 수 있게 되었다. 폴더가 접혔을 때 백그라운드 처리 또는 끄기, 리부팅과 같은 ARM7에서만 접근 가능한 작업들을 할 수 있게 된 것이다.

 이제 마구마구 기능을 추가해서 멋진 홈브루를 만들어 보자. @0@)/~

 

6.첨부

 

 

 

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

+ Recent posts