2007년 6월 7일 목요일

button.c 분석

최종 소스는 ftp://ftp.embeddedarm.com/ts-arm-linux-cd/samples/ 에 위치합니다.
button.c는 입력을 받아 보드에 있는 2개의 led에 점멸을 표시합니다. 여기서는 단순히 LED 점멸만 하는 즉, 쓰기 기능을 하는 것으로 하겠습니다.
TS-7200 Hardware Manual(ts-7200-manual-rev2.2.pdf) 의 7.1 Status LEDs를 보면 Red와 Green 두가지의 LED가 board에 존재하는 것을 알수 있습니다. 사실 EP9302 CPU가 두개의 LED를 지원한다고 보는게 맞겠죠. 메모리위치는 0x8080_0020입니다. 소스코드를 살펴보기전에 다시 한번 GPIO에 대해서 개략적으로 살펴보죠. EP9301 User's Guide 의 Chapter 21에 GPIO 대해서 상세히 나옵니다. Instruction의 내용만 훓어보면 EP9302는 7개의 GPIO port를 지원합니다. 각 port는 동일하게 DR, DDR이 존재하고 interrupt 처리와 같은 기능을 위한 resister도 존재하는데 이기능을 지원하는 port를 EGPIO(Enhaunced GPIO) 라고 합니다. port는 A, B, C, D, E, F, G, H가 존재하고 EGPIO에 해당하는 것은 port A, port B, port F입니다. EP9302는 32bit이기 때문에 register크기도 모두 32bit이지만 실질적으로 사용하는 크기는 다음과 같습니다.
- port A: 8bits
- port B: 8bits
- port C: 1bit
- port E: 2bits
- port F: 3bits
- port G: 2bits
- port F: 4bits

우리가 지금부터 하고자하는 것은 port E에 해당합니다. 2bit가 유효한데 각각 Red, Green LED를 의미합니다. 문서에는 RDLED, GRLED라고 되어있네요. jp.c 와는 다르게 여기에서는 LED를 켜고 끄기 위해서는 쓰기 작업을 해야합니다. 따라서 DR 뿐만 아니라 DDR에 대해서도 작업을 수행해야 합니다. 쓰기 작업은 Read Modify Write (RMW) 방식으로 한다고 했는데 여기서는 Write 만 하고 확인은 직접 board의 LED를 보면서 확인할수 있습니다.

번지 번호 하나 외우고 들어가죠. 0x8084_0000 번지. 이 번지에서 부터 차례대로 GPIO의 resister가 매핑되어 있습니다. EP9301 User's Guide 에는 각 port 의 번지가 나와있는데 TS7200 Hardware 매뉴얼에는 해당 기능에 대한 이름을 열거해 두어서 훨씬 보기 편합니다. 우리의 관심사인 port E의 DR, DDR의 번지는 각각 0x8084_0020, 0x8084_0024 번지에 위치합니다. DR과 DDR의 각 비트위치는 서로 일치합니다. 즉, 0번째 bit가 RDLED의 값과 읽기/쓰기 모드를 나타내고 1번째 bit는 GRLED의 값과 읽기/쓰기 모드를 의미합니다. 지금까지 장황하게 설명했으니 실제 소스를 들여다 보죠.

line 17:
volatile unsigned int *PEDR, *PEDDR, *PBDR, *PBDDR, *GPIOBDB;
주요변수에 대해서 volatile 키워드를 사용했습니다. 그전에는 쓸 기회가 없었는데(사실 뭔지도 몰랐음^^) 이럴때 필요 하더군요. 간단히 말하자면 기계어로 컴파일 될때 최적화에 따른 부작용을 없애자는 거죠. 이 키워드를 쓰면 해당 변수가 들어간 코드에 대해서 컴파일러가 최적화 작업을 수행하지 않습니다. 따라서 소스 레벨에서는 문제 없다가 실행시에 문제가 발생하는 것을 사전에 막아줍니다. 여기서는 unsigned int *로 선언을 했습니다. 여기서는 PEDR, PEDDR만 쓸 예정인데 unsigned char *로 해도 사실은 무방합니다.

line 23:
start = mmap(0, getpagesize(), PROT_READPROT_WRITE, MAP_SHARED, fd, 0x80840000);
마지막 파라미터가 많이 보던 값이죠. GPIO에 접근할때는 이 번지로~~

line 26, line 27:
PEDR = (unsigned int *)(start + 0x20); // port e data
PEDDR = (unsigned int *)(start + 0x24); // port e direction register
start를 unsigned char * 로 선언해 둔 이유를 알겠죠. base 번지로 부터의 상대 주소에 접근할 때 요런식으로 많이 씁니다. 소스코드 보기에도 좋구요. 깔끔하잖아요. PEDR, PEDDR을 unsigned int *로 선언했으니 형변환하는 거 잊지말구요.

line 31:
*PEDDR = 0xff; // all output (just 2 bits)
가장 핵심적인 내용인데 LED에 대해서 쓰기 모드로 지정하겠다는 거죠. 나중에 DR에 값을 쓰면 그 값이 써지게 되어서 결과적으로 LED가 점멸하게 되는 것을 알수 있습니다.

line 35, 36, 37:
while (state & 0x01) { // wait until button goes low
state = *PBDR; // remember bit 0 is pulled up with 4.7k ohm
}
요기서는 위의 코드를 그냥 주석처리하세요. input을 기다리는 내용인데 하드웨어 꾸미기 귀찮아서...

line 42, 44:
*PEDR = 0xff;
*PEDR = 0x00;
위의 코드에서 DDR를 write 모드로 변경했기 때문에 DR에 값을 쓰면 그 결과가 바로 눈으로 나옵니다. 42번 라인을 정확하게 한다면 *PEDR = 0x03; 으로 하는게 맞을거 같은데 사실 32bit의 DR 내용중에서 하위 2bit를 제외하고는 사용하지 않는 내용이니 큰 문제가 되지 않습니다. for() 문과 중간에 sleep()을 주어서 반복하게 되니 차례대로 꺼졌다 켜졌다를 반복하는 것을 눈으로 볼수 있습니다. 간단하죠.

그렇다면 NetBSD 에서는 어떻게 확인할까요?
불행히도 빠져 있네요. 하지만 /usr/src/sys/arch/evbarm/tsarm/tspld.c 에 추가해서 sysctl에 표시되게 하는 방법으로 한다면 일관적이고 사용자 입장에서 편할것 같습니다. Jesse Off 가 별로 필요없을 거 같아 추가를 안했을도 있겠네요. 눈으로 보이는데 굳이 프로그램으로 확인할 필요도 없을 거 같구 굳이 그걸 프로그램에서 알 필요도 없지 않을까요.

알고 보면 상당히 간단합니다. 근데 키패드와 Text LCD는 조금 복잡하네요. scan 기법을 쓰다보니 하드웨어적인 사항도 잘 알아야 되고... 다음엔 이것에 대해서 한번 적어 볼까합니다.

댓글 없음: