레이블이 Raspberry Pi 2인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Raspberry Pi 2인 게시물을 표시합니다. 모든 게시물 표시

2015년 9월 9일 수요일

라즈베리 파이 GPIO를 고속으로 제어하기



라즈베리 파이에서 GPIO 핀을 제어하는 가장 쉬운 방법은 sysfs를 사용하는 것이다.

쉘에서라면 다음과 같은 식으로 GPIO 핀을 제어할 수 있다.

$ echo "4" > /sys/class/gpio/export
$ echo "out" > /sys/class/gpio/gpio4/direction
 
# Set up GPIO 7 and set to input
$ echo "7" > /sys/class/gpio/export
$ echo "in" > /sys/class/gpio/gpio7/direction
 
# Write output
$ echo "1" > /sys/class/gpio/gpio4/value
 
# Read from input
$ cat /sys/class/gpio/gpio7/value 
 
# Clean up
$ echo "4" > /sys/class/gpio/unexport
$ echo "7" > /sys/class/gpio/unexport
 
프로그램 상에서라면 '/sys/class/gpio/' 디렉토리 내의 파일들을 open한 후 read/write로 GPIO를 제어할 수 있다.

이렇게 sysfs를 사용하면 매 비트를 조작할 때 마다 오버헤드가 커서 라즈베리 파이2에서 단순히 GPIO 핀을 H/L로 토글하는데도 6.8KHz로밖에 동작할 수 없다.

하지만 라즈베리 파이2에 들어있는 SoC인 BCM2836의 GPIO레지스터를 직접 조작하면 무려 50MHz의 속도로 GPIO핀을 토글할 수 있게 된다. (약 7350배의 속도 향상) 게다가 sysfs를 사용하는 경우는 한번에 한 비트씩만 조작이 가능하지만 GPIO 레지스터를 조작하면 한번에 여러 비트의 GPIO 조작이 가능하기 때문에 더욱 더 고속 제어가 가능해진다.

BCM2836의 경우 I/O제어 레지스터는 0x3f20 0000 ~ 0x3f20 00b0의 메모리 공간에 매핑되어 있다.

리눅스에서 프로세스는 하드웨어나 물리 주소에 직접 접근이 불가능하다. 그러므로 이 문제를 해결하기 위해 사용되는 것이 '/dev/mem' 디바이스이다. /dev/mem은 메모리 공간에 해당하는 가상 파일이다. 그러므로 이 파일을 오픈해서 파일에 read/write하면 파일이 매핑되어 있는 메모리 공간에 값을 읽고 쓸 수 있게 되는 것이다.

그런데 파일에 값을 read/write 하는건 귀찮기 때문에 등장하는 것이 mmap 함수이다. mmap은 파일의 일부를 메모리처럼 억세스 할 수 있게 해 준다. C에서 말하자면 파일의 특정 장소를 포인터로 지시해 그 포인터를 통해 직접 읽고 쓰기가 가능해진다.

아래 코드가 라즈베리 파이2에서 GPIO 레지스터에 대한 포인터를 얻어오는 함수이다.

unsigned int *get_base_addr()
{
  int fd=open("/dev/mem/", O_RDWR | O_SYNC);
  if (fd<0) {
    printf("can not open /dev/mem\n"); exit(-1);
  }
  #define PAGE_SIZE (4096)
  void *mmaped = mmap(NULL,
                      PAGE_SIZE,
                      PROT_READ | PROT_WRITE,
                      MAP_SHARED,
                      fd,
                      0x3f200000);
  if (mmaped<0) {
    printf("mmap failed\n"); exit(-1);
  }
  close(fd);
  return (unsigned int *)mmaped;
}

 
주의) 라즈베리 파이2는 BCM2836 SoC를 사용하는데 여기서는 GPIO 레지스터가 0x3f20 0000번지에 
매핑되어 있지만 라즈베리 파이1의 BCM2835는 0x2020 0000번지에 매핑되어 있다.
즉 라즈베리 파이1에서 사용하려면 위의 함수에서 0x3f200000을 0x20200000으로 변경 해 줘야만 한다.
 
포인터를 얻었으면 사용하고자 하는 GPIO 핀을 입력 또는 출력으로 사용할 지 설정해 줘야 한다. 
각 I/O 포트의 설정은 3비트 값을 사용한다. 32비트 레지스터에 각각 10개 I/O핀을 설정한다.
 
예를 들어 GPIO0의 설정 레지스터는 0x3f20 0000 번지의 32비트 값 중 비트 2~0, GPIO11의 
설정 레지스터는 0x3f20 0004 번지의 32비트 값 중 비트 5~3이 된다.
공식화 하면 GPIO p의 설정 레지스터는 0x3f200000+(p/10) 번지의 비트 (p%10)*3+2~(p%10)*3가 된다.
 
아래 코드는 포트 설정을 위한 함수이다. 
 
void gpio_mode(unsigned int *addr, int port, int mode)
{
  if (0<port || port >31) {
    printf("port out of range: %d\n", port);
    exit(-1);
  }
  unsigned int *a = addr + (port/10);
  unsigned int mask = ~(0x7 << ((port%10) * 3));
  *a &= mask;
  *a |= (mode & 0x7) << ((port%10) * 3);
}  
 
포트 설정이 되었으면 이제 포트 값을 H/L로 변경할 수 있다. 
여기서는 GPIO핀을 H로 만들때 사용하는 레지스터와 L로 만들때 사용하는 레지스터가 따로 있다. 
즉 H로 만들 때 사용하는 레지스터에 '1'을 써 넣은 포트만 값이 H로 바뀌고 나머지 포트는 값이 
그대로 유지된다.
마찬가지로 L로 만들 때 사용하는 레지스터에 '1'을 써 넣은 포트만 값이 L가 되고 나머지 비트는 
값에 변화가 없게 된다.
예를 들어 GPIO0의 값을 H로 하고 싶으면 0x3f20 001c 번지에 0x0000 0001을 써 주면 GPIO0만 
H가 되고 나머지 GPIO는 값의 변화가 없다.
동일하게 GPIO1의 값을 L로 하고 싶으면 0x3f20 0028 번지에 0x0000 0002를 써 주면 된다. 
이렇게 레지스터를 이용하면 한번에 여러 비트를 H로 만들거나, 여러 비트를 L로 만들어 줄 수 있다.
 
void gpio_set(unsigned int *addr, int port)
{
  if (0<port || port>31) {
    printf("set: port out of range: %d\n", port);
    exit(-1);
  }
  *(addr+7) = 0x1 << port;
}

void gpio_clear(unsigned int *addr, int port)
{
  if (0<port || port>31) {
    printf("clear: port out of range: %d\n", port);
    exit(-1);
  }
  *(addr+10) = 0x1 << port;
} 
 

위의 함수들을 사용해 GPIO0를 제어하는 코드의 틀은 다음과 같다.
 

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mmap.h>
#include <unistd.h>

#define GPIO_IN     0
#define GPIO_OUT    1

unsigned int *get_base_addr();
void gpio_mode(unsigned int *, int, int);
void gpio_set(unsigned int *, int);
void gpio_clear(unsigned int *, int);

int main(int argc, char **argv)
{ 
  volatile unsigned int *addr = get_base_addr();
 
  ... 
  gpio_mode(addr, 0, GPIO_OUT);
  ...
  ...
  gpio_set(addr, 0);
  ... 
  gpio_clear(addr, 0);
  ...
}
 

참고로 라즈베리 파이2에서 모든 함수 호출이나 딜레이 없이 가장 빨리 GPIO 포트를 토글하는 경우 
최대 50MHz로 스위칭이 가능하다.
 
  ...
  for (;;) {
    *(0x3f200000+7) = 1<<0;   // GPIO 0 set
    *(0x3f200000+10) = 1<<0;  // GPIO 0 clear
  } 
  ...
 

2015년 9월 6일 일요일

라즈베리파이2에서 NEON을 사용하기 위해 gcc-5.1.0 컴파일하기

라즈베리 파이에 사용된 프로세서에는 NEON이라는 SIMD (Single Instruction Multiple Data)처리 유닛이 들어있다. 이 NEON을 사용하면 최대 128비트까지 동시에 처리가 가능해 지기 때문에 프로그램 실행에 상당한 성능 향상을 가져올 수 있게 된다. x86계열로 치면 MMX, SSE, AVX 명령등에 이에 해당한다.


char 타입의 연산인 경우 약 5배, short 타입 연산은 약 3배, int 타입 연산은 약 1.7배, float 타입 연산은 약 2.3배 정도로 실행 속도가 향상된다. 즉 반복적인 연산이 많은 scientific computation이나 signal processing, 암호화, 압축, 인코딩 등에 사용하면 매우 유용하다.



부동 소수점만 보면 단정도(single precision)의 경우 라즈베리 파이1이 약 70 MFlops, 라즈베리 파이2에서 NEON을 사용하지 않으며 약 150 MFlops, NEON을 사용하면 약 310 MFlops의 속도가 나온다. 부동 소수점 배정도(double precision)의 경우 라즈베리 파이1이 약 35 MFlops, 라즈베리 파이2에서는 약 150 MFlops정도의 속도가 나오게 된다. (부동소수점 연산에서 배정도의 경우 NEON이 지원하지 않음)

단 컴파일 시 NEON 명령을 지원하도록 하려면 현재 distribution에 포함되어 있는 gcc로는 안되고 NEON을 지원하도록 gcc-5.1.0을 설치해 줘야 한다.

먼저 gcc-5.1.0 소스코드를 다운로드 한다.

$ curl -O http://ftp.tsukuba.wide.ad.jp/software/gcc/releases/gcc-5.1.0/gcc-5.1.0.tar.bz2
$ tar xvf gcc-5.1.0.tar.bz2

gcc를 컴파일 하기 위해서는 디폴트 swap 파티션 용량이 부족하기 때문에 swap 파티션 용량을 늘려줘야 한다.

$ sudo dd if=/dev/zero of=/swapfile bs=1M count=2048
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile

현재 apt-get으로 설치되는 autoconf 버젼은 2.69인데 gcc-5.1.0은 2.64를 사용하도록 설정되어 있다. 그래서 이걸 2.69를 사용하도록 수정해 줘야 한다.

(1) gcc-5.1.0 디렉토리에 있는 configure.ac 파일 안의 AC_PREREQ 값을 '2.64'에서 '2.69'로 수정
(2) gcc-5.1.0/config 디렉토리에 있는 override.m4 파일 안의 _GCC_AUTOCONF_VERSION 값을  '2.64'에서 '2.69'로 수정

GCC-5.1.0에서는 병렬화를 위해 언어확장으로 cilk를 표준으로 사용하므로 cilk도 활성화 시켜 줌

(1) gcc-5.1.0/libcilkrts 디렉토리에 있는 configure.tgt 안의 UNSUPPORTED=1 을 앞에 '#'를 붙여 comment out 시킴 ('1'을 '0'으로 바꾸는건 안됨)
(2) gcc-5.1.0/libcilkrts/runtime/config/generic 디렉토리에 있는 cilk-abi-vla.c 안의 vla_internal_heap_free 함수를 호출하는 부분에서 첫번째 인자를 't'에서 'p'로 수정
(3) gcc-5.1.0/libcilkrts/runtime/config/generic 디렉토리에 있는 os_fence.h 안의 __cilkrts_fence의 정의 부분의 맨 앞에 '//'를 추가해 comment out 시킴
COMMON_SYSDEP void __cilkrts_fence(void); /// < MFENCE instruction
(4) gcc-5.1.0/libcilkrts/runtime/config/generic 디렉토리에 있는 os_fence.h 파일에 아래줄을 추가
#define __cilkrts_fence() __asm__ volatile ("DSB")

빌드를 위해 GMP, MPFR, LIBMPC가 필요하므로 설치해 줌

$ sudo apt-get install libgmp-dev libmpfr-dev libmpc-dev

빌드를 시작

$ mkdir b; cd b
$ ../configure --enable-languages=c,c++ \
--prefix=/usr/local/gcc-5.1.0 \
--target=arm-linux-gnueabihf \
--with-arch=armv7-a \
--with-fpu=vfp \
--with-float=hard \
--build=arm-linux-gnueabihf \
--host=arm-linux-gnueabihf
$ make
$ sudo make install

설치가 완료된 후 gcc-5.1.0을 사용하도록 환경변수를 설정해 준다. '.profile' 또는 '.bashrc' 파일의 맨 마지막에 다음 두 줄을 추가한다.

export PATH=/usr/local/gcc-5.1.0/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/gcc-5.1.0/lib:$LD_LIBRARY_PATH

라즈베리파이1에서 컴파일 하는 경우 configure 옵션을 다르게 줘야 한다.

$ ../configure --enable-languages=c,c++ \
--prefix=/usr/local/gcc-5.1.0 \
--target=arm-linux-gnueabihf \ 
--with-fpu=vfp \
--with-float=hard \
--build=arm-linux-gnueabihf \
--host=arm-linux-gnueabihf

빌드에는 상당한 인내심이 필요하다. 'make' 명령으로 빌드하는데 라즈베리파이2에서 대략 30시간 정도 걸렸다.

바로 사용하고 싶은 사람들은 아래 링크에서 컴파일 된 gcc-5.1.0을 받아 /usr/local 디렉토리에 풀어주고 환경변수 설정만 하면 된다.

gcc-5.1.0.compiled_for_rpi2.tgz

2015년 2월 4일 수요일

라즈베리 파이 2에서 Windows 10 지원 발표

오늘 받아본 메일에 놀라운 내용이 들어있었다.
MS가 Raspberry Pi 2에서도 Windows 10을 지원한다는 것이다.


기존의 라즈베리는 성능이 딸려 힘들텐데...라는 생각을 하고 있었는데 확인해보니 라즈베리 파이 2라는 보드가 새로 나왔다.


사진으로 봐서는 기존의 B+ 모델과 거의 동일한 형태인데 사양은 많이 좋아졌다.

기존 모델은 BMC2835 SoC를 장착해 700MHz ARM 11 코어를 사용했는데 비해 새 모델은 BCM2836 SoC를 장착해 900MHz quad-core ARM Cortex A7을 사용하기 때문에 기존 모델에 비해 대략 6배 정도의 속도 향상이 있다고 한다. 메모리도 1GB로 이전 모델에 비해 두배가 되었다.

그럼에도 불구하고 가격은 이전 모델과 동일하게 $35라고 한다.

라즈베리 파이 2에서 사용될 윈도우 10은 개발자 커뮤니티에 무료로 제공한다고 하니 앞으로는 DIY에도 윈도우를 사용한 작품들도 늘어날 듯 하다.

윈도우 10 지원은 오늘 발표된 것이고 다음달쯤 상세 사항을 공개한다고 한다.

Can not wait to see Windows 10 working on Raspberry pi 2!!!