2015년 7월 23일 목요일

아두이노에서 외부 라이브러리 설치하기 (Install library in arduino)

아두이노를 사용하다 보면 외부 라이브러리를 설치해야 하는 경우가 많다. 외부 라이브러리를 설치하는 방법은 크게 두가지가 있다.

아두이노에서 외부 라이브러리는 내문서->Arduino->libraries 디렉토리에 설치되어야 한다. 그리고 설치되는 라이브러리의 디렉토리 이름과 디렉토리 내의 헤더파일 이름은 동일해야만 한다. 즉 예를 들어 설치할 라이브러리 이름이 Foo 라고 한다면 디렉토리 구조는 다음과 같다.

내 문서 -> Arduino -> libraries -> Foo --- Foo.cpp
                                      +- Foo.h
                                      +- ...
                                      +- ...

위와 같이 되어 있어야 아두이노 스케치에서 #include <Foo.h>라고 해서 제대로 라이브러리를 사용할 수 있다.  라이브러리 zip 파일을 다운받아 압축을 해제하면 디렉토리 이름이 Foo-master로 되어 있거나 또는 Foo 디렉토리 아래에 Foo 라는 이름의 서브 디렉토리가 있고 그 안에 파일이 들어있는 경우 아두이노에서 스케치를 컴파일 할 때 헤더파일을 찾을 수 없다는 에러가 발생하게 된다.

내 문서 -> Arduino -> libraries -> Foo-master --- Foo.cpp
                                             +- Foo.h
                                             +- ...
                                             +- ...

내 문서 -> Arduino -> libraries -> Foo -> Foo --- Foo.cpp
                                             +- Foo.h
                                             +- ...
                                             +- ...

즉 위의 두가지 경우 모두 에러가 발생하게 되므로 정확한 디렉토리 구조에 맞게 디렉토리 이름을 바꾸거나 파일의 위치를 옮겨줘야 한다.

첫번째는 1.6 버젼으로 올라가며 추가된 기능으로 메뉴의 '스케치 -> Include Library -> Manage Libraries...'를 이용하는 것이다.


Manage Libraries... 항목을 선택하면 Library Manager 창이 열린다.


여기서 원하는 항목을 찾아 선택하면 오른쪽 아래 부분에 생기는 'Install' 버튼을 눌러 설치하면 된다.

이 방법이 가장 편리하긴 하지만 아두이노 라이브러리는 계속 추가되고 있으므로 원하는 라이브러리가 없을 경우가 있다. 그 때는 검색으로 github등에서 파일을 직접 다운받아 설치해 줘야만 한다.

다운받은 라이브러리 압축을 해제한 후 디렉토리를 '내 문서 -> Arduino -> libraries' 로 옮겨 준 다움 아두이노를 재시동하면 된다.


정상 설치가 되었다면 아두이노를 재시동하고 '파일 -> 예제'에 보면 FlexiTimer2 항목이 보이고 예제 코드(여기서는 FlashLed)를 선택해 컴파일 하면 에러가 없이 컴파일이 완료될 것이다.




아두이노등의 임베디드 시스템의 변수 값 오버플로우 문제(Variable overflow problem in embedded systems)

아두이노에서 10ms 마다 스위치 상태를 검사하는 코드를 작성한다고 해 보자.

void loop() {
  static unsigned long last = 0;
  static boolean swPrev = HIGH;
  unsigned long now;

  now = millis();
  if ((now-last)>=10) {
    digitalWrite(13,digitalRead(10));
    last = millis();
  }
}






위와 같은 코드가 될텐데 동작시켜 보면 생각한대로 아무 문제 없이 동작한다. 하지만 이 코드의 경우 치명적인 버그가 숨어있다. millis() 함수는 unsigned long 타입의 값을 리턴해 준다. unsigned long 타입의 경우 0 ~ 4,294,967,295 까지의 숫자가 들어갈 수 있다. 4,294,967,295 ms면 엄청나게 긴 시간(대략 50일)임에는 틀림없다. 하지만 임베디드 시스템의 경우 한번 켜 놓으면 전원을 끄지 않고 몇년씩 계속 동작시키는 경우도 흔하다. 위의 코드를 사용하면 처음부터 약 50일간은 문제없이 동작하지만 약 50일 정도가 지나 4,294,967,295 -> 0으로 변한 이후부터는 동작하지 않는 문제가 발생한다.

예를 들어 마지막으로 스위치를 감지했을 때 시간값이 last 변수에 들어가게 되는데 그 값이 4,294,967,290이었다고 해 보자. 그 때 부터 5 ms 후에는 4,294,967,295가 되고 다시 1ms가 더 지나면 4,294,967,256이 아니고 0이 되어 버린다. 즉 now-last 값이 6이 아니고 -4,294,967,290이 되어 버리는 것이다. 즉 그때 부터는 (now-last) > 10 이라는 조건이 만족될 수가 없게 되어 버리기 때문에 스위치가 눌렸는지 검사를 하지 않게 된다. 


이런 타입의 에러가 임베디드 시스템에서 아주 악성인 이유는 프로그램을 만들어 테스트 해 보면 정상적으로 잘 동작한다는 것이다. 그래서 제품을 출시해도 아무 문제가 없는데 출시 시작한지 약 두달 정도가 지나면 문제가 발견되기 시작한다는 것이다. 그리고 디버깅을 위해 버그를 재현하기 위해 개발실에서 다시 테스트 해 보면 전원을 넣은지 50일이 지나지 않았기 때문에 버그를 재현할 수 없다. 개발자가 버그를 재현할 수 없으면 버그의 원인을 찾기 힘들기 때문에 디버깅이 매우 어렵다. 

처음부터 이런 부분을 생각해 변수 값이 overflow 되는 경우에도 동작할 수 있도록 프로그램을 작성하는 연습을 해야만 한다.

위의 코드라면 이런식으로 수정해 주면 된다.

#define MAX 0xFFFFFFFF

boolean diff(unsigned long now, unsigned long prev, unsigned long d)
{
  if (now>prev) return ((now-prev)>=d);
  else return (((MAX-prev)+now+1)>=d);
}

void loop() {
  static unsigned long last = 0;
  static boolean swPrev = HIGH;
  unsigned long now;

  now = millis();
  if (diff(now, last, 10)) {
    digitalWrite(13,digitalRead(10));
    swPrev = LOW;
    last = millis();
  }

}


이제 now 변수의 값이 overflow 되어 0으로 돌아가도 정상적으로 비교되어 위에서 이야기 한 버그가 발생하지 않는걸 확인할 수 있다.