mmap을 이용하여 GPIO 제어하기 - #1. Clock Enabling

아래 가이드는 C 언어와 Linux에 능숙하고 Computer Organization, Microprocessor를 수강한 적 있는 학부생에게 추천합니다.!


왜 mmap을 이용하여 direct로 접근하나요?

The memory-mapping method allows you to directly access the registors that control the
GPIOs. Memory-mapping is very fast (about 1000 times faster than file I/O!), but only
processes with root permissions can use it.

1에 따르면 memory-mapping 함수는 register에 직접 접근하도록 하며, 이는 file I/O 방식보다 1000배는 빠르다고 합니다.

BeagleBone Black의 GPIO는 기본적으로 disable 되어 있는데, 이를 활성화는 방법에는 2가지가 있습니다.

  1. SYSFS를 이용한 GPIO export.
  2. Register를 이용하여 직접 clock을 enable 하는 것.

여기서는 C 코드를 이용하여 register를 직접 제어하는 것에 포커스를 맞추겠습니다.
(SYSFS를 이용한 GPIO는 Beagleboard 홈페이지에 가면 친절하게 설명해줍니다)

들어가기에 앞서 아래의 준비물이 필요합니다.

주로 Technical Reference Manual(이하 TRM)과 Header Table 문서를 참조하니 GPIO PIN 개념을 이해하고 TRM에서 register 정보를 찾을 수 있어야 합니다.


Register를 이용하여 clock을 enable 하기

리눅스는 대부분의 device를 파일로 추상화하여 제공합니다, 모듈화가 아주 잘 되어 있죠.
Device를 파일로 접근한다면 모든 처리 루틴을 아래와 같이 쉽게 생각할 수 있습니다.

  1. 파일을 연다.
  2. 파일에 데이터를 쓴다.
  3. 파일을 닫는다.

우리는 메모리에 직접 접근하여 register에 데이터를 쓰기(write)할 것이므로 메모리를 추상화한 /dev/mem을 이용할 것입니다.

...
fd = open("/dev/mem", O_RDWR|O_SYNC);

/* 기타 처리 루틴 ~ */

gpio_addr = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, GPIO_BASE_ADDR);

*(gpio_addr + led_addr) = 0x01;     // LED를 On하는 로직이라 가정.
...

/dev/mem을 오픈하고 mmap()을 호출하는 것 까진 좋으나 mapped memory를 통하여 값에 접근하려면 아마도 실패할 것입니다.
이는 GPIO의 clock이 기본적으로 disable 되어 있기 때문입니다.

Patricia 씨가 구글 그룹스에 남긴 글입니다.

일단 그건 clock 문제야, 우리가 BeagleBone Black을 부팅할 때 GPIO 1, 2, 그리고 3의 clocking system은 비활성화 되버려 (GPIO 0은 항상 활성 상태).

따라서 다음과 같은 방법이 있는데:

  • Pin export 하기 (and then the system enables the clock automatically)
  • CM_PER_GPIO#_CLKCTRL 레지스터의 일부 bits 수정하기 (TRM의 CM_PER 페이지 참조)

— Patricia N.2

따라서 CM_PER_GPIO2_CLKCTRL을 활성화하기 위해 TRM 문서의 CM_PER_GPIO2_CLKCTRL3MODULEMODE을 확인해야 합니다.
해당 register의 값을 2로 수정하므로써 clock이 enable 되는 것입니다.

아래 소스코드를 분석하여 확실하게 이해하세요.
(간단하게 작성하였으므로 완벽하게 동작하지 않습니다.!)

#define CM_PER_BASE 0x44E00000

#define CM_PER_GPIO1 0xAC
#define CM_PER_GPIO2 0xB0
#define CM_PER_GPIO3 0xB4

#define MODE_ENABLE 0x2

void init_GPIO_enable(int fd);

int main(void)
{
    ...

    init_GPIO_enable(mem_fd);

    ...
}

void init_GPIO_enable(int fd)
{
    ...

    // CM_PER_BASE 주소를 기반으로 memory mapped 합니다.
    cm_per_addr = (char *)mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, CM_PER_BASE);

    ...

    // CM_PER_GPIO2의 오프셋 주소의 값을 수정하여 Enable 합니다. 
    *(cm_per_addr + CM_PER_GPIO2) = MODE_ENABLE;

    ...
}

이제 GPIO_OE, GPIO_DATAIN, GPIO_DATAOUT을 이용하여 값을 읽고 쓸수 있습니다.
한번 enable 하면 Process를 종료하더라도 해당 register의 값은 변하지 않습니다.