지시자(directive)인 volatile 은 변수 선언문의 변수형 앞에 두어서 컴파일러가 그 변수를 접근하는 방식을 지정한다.


 일반적으로 변수 데이터는 런타임에 RAM영역에 저장되지만 어떤 변수는 레지스터(register)에 임시로 저장되어 사용하기도 하는데 이 경우 속도면에서는 월씬 유리하지만 RAM에 저장된 실제 데이터와 레지스터에 저장된 (임시)데이터가 서로 다른 경우가 발생할 수도 있다.


 어떤 변수를 volatile로 지정하면 그 변수 데이터는 레지스터의 임시 저장소가 아니라 RAM에서 직접 읽어오도록 컴파일된다. 아두이노 프로그램의 경우 보통은 volatile로 정의할 필요는 없으나 인터럽트 서비스 루틴 (ISR) 내부에서 그 값이 변경되는 변수는 반드시 volatile로 선언해야 실시간으로 변경되는 데이터 값을 ISR 외부에서 정확하게 읽어올 수 있게 된다. 예를 들면 다음과 같다.


// toggles LED when interrupt pin changes state

int pin = 13;
volatile int state = LOW;

void setup()
{
 pinMode(pin, OUTPUT);
 attachInterrupt(0, blink, CHANGE);
}

void loop()
{
 digitalWrite(pin, state);
}

void blink()
{
 state = !state;
}


위의 예에서 blink()함수가 ISR이며 이 안에서 변경되는 변수 state는 volatile로 지정하였음을 확인할 수 있다.

(출처 : https://www.arduino.cc/en/Reference/Volatile )



Posted by 살레시오

 풀업이 된 푸시버튼이 2번 핀에 연결되었다고 가정하자. 이 버튼은 외부에 풀업이 된 상태이기 때문에 안 눌려진 상태에서는 1값이 읽혀지고 눌려지면 0값이 읽힌다. 부저는 11번핀에 연결되었다고 가정한다.


 이 때 버튼이 눌려지는 시점에서 멜로디가 울리도록 하는 프로그램은 다음과 같다. tone()함수는 ISR 내부에서는 정상적으로 동작하기 않기 때문에 인터럽트 기능은 사용할 수 없다는 점에 유의하자.


#include "pitches.h"
#define BUZ 11
#define K 2

void setup() {
 pinMode(K, INPUT);
}

int iBtnPrev = 1; //버튼의 이전 상태값을 저장하는 변수

void loop() {
 int iBtn = digitalRead(K);
 if (iBtn == 0 && iBtnPrev == 1)
   melody();
 iBtnPrev = iBtn;
}

void melody() {
 tone(BUZ, NOTE_C4);
 delay(100);
 tone(BUZ, NOTE_D4);
 delay(100);
 tone(BUZ, NOTE_E4);
 delay(100);
 noTone(BUZ);
}


이 프로그램을 다운로드하면 버튼이 눌리는 순간 ‘도레미’ 멜로디가 울린다.



Posted by 살레시오

 아두이노에는 tone()함수가 제공되는데 부저나 스피커로 음을 발생시킬 수 있는 함수이다. 기본 문법은 다음과 같다.


tone(pin, freq [, duration]);


  • pin : 부저나 스피커가 연결된 디지털 핀번호

  • freq : 주파수 (범위 : 31 ~ 65535)

  • duration : (옵션) 음의 발생 시간


여기서 duration은 주파수 지속시간으로서 이 시간이 지난 후에 noTone()함수가 자동으로 호출되는 것과 같은 효과를 낸다.  duration이  생략되면 noTone()함수가 호출될 때까지 음이 계속 발생된다. 예를 들어서 11번 핀에 부저가 연결되었다면 다음과 같이 한다.


tone(11, 262); // ‘도’음 발생
delay(500);
noTone(11);


이 코드는 ‘도’음을 0.5초간 발생시키는데 다음과 같이 할 수 있다.


tone(11, 262, 500);


이렇게 하면 음 발생 직후 다른 작업을 수행할 수 있으며 500ms후에는 내부 인터럽트가 발생하여 음발생이 자동으로 정지된다. 하지만 단음만 생성할 경우에는 상관없지만 연속음을 생성할 경우에는 주의해야 한다.


tone(11, 262, 500); // ‘도’음
tone(11, 294, 500); // ‘레’음
tone(11, 330, 500); // ‘미’음


이렇게 하면 맨 마지막의 ‘미’음만 발생하게 된다. (왜?) 따라서 올바른 동작을 위해서는 다음과 같이 해야 한다.


tone(11, 262); // ‘도’음 발생
delay(500);
tone(11, 294); // ‘레’음 발생
delay(500);
tone(11, 330); // ‘미’음 발생
delay(500);
noTone(11);
tone(11, 262); // ‘도’
delay(500);
tone(11, 294); // ‘레’
delay(500);
tone(11, 330, 500); // ‘미’


tone()함수는 내부적으로 타이머를 사용하므로 다음과 같은 점들을 주의해야 한다.

  • 이 함수를 사용할 경우 3번 11번 핀의 PWM이 정상적으로 동작되지 않는다.

  • 한 번에 하나의 주파수만 발생시킬 수 있으며 여러 핀에 동시에 다른 음을 발생시킬 수 없다.


 특정 음에 해당되는 주파수를 일일이 숫자로 기입하는 것은 가독성 면에서 좋지 않으며 이를 개선하기 위해서 아두이노에서는 음의 주파수만을 모아놓은 “pitches.h”라는 헤더파일을 제공한다. 내용은 다음과 같다.


/*************************************************
* Public Constants
*************************************************/
#define NOTE_B0  31
#define NOTE_C1  33
#define NOTE_CS1 35
#define NOTE_D1  37
#define NOTE_DS1 39
#define NOTE_E1  41
#define NOTE_F1  44
#define NOTE_FS1 46
#define NOTE_G1  49
#define NOTE_GS1 52
#define NOTE_A1  55
#define NOTE_AS1 58
#define NOTE_B1  62
#define NOTE_C2  65
#define NOTE_CS2 69
#define NOTE_D2  73
#define NOTE_DS2 78
#define NOTE_E2  82
#define NOTE_F2  87
#define NOTE_FS2 93
#define NOTE_G2  98
#define NOTE_GS2 104
#define NOTE_A2  110
#define NOTE_AS2 117
#define NOTE_B2  123
#define NOTE_C3  131
#define NOTE_CS3 139
#define NOTE_D3  147
#define NOTE_DS3 156
#define NOTE_E3  165
#define NOTE_F3  175
#define NOTE_FS3 185
#define NOTE_G3  196
#define NOTE_GS3 208
#define NOTE_A3  220
#define NOTE_AS3 233
#define NOTE_B3  247
#define NOTE_C4  262
#define NOTE_CS4 277
#define NOTE_D4  294
#define NOTE_DS4 311
#define NOTE_E4  330
#define NOTE_F4  349
#define NOTE_FS4 370
#define NOTE_G4  392
#define NOTE_GS4 415
#define NOTE_A4  440
#define NOTE_AS4 466
#define NOTE_B4  494
#define NOTE_C5  523
#define NOTE_CS5 554
#define NOTE_D5  587
#define NOTE_DS5 622
#define NOTE_E5  659
#define NOTE_F5  698
#define NOTE_FS5 740
#define NOTE_G5  784
#define NOTE_GS5 831
#define NOTE_A5  880
#define NOTE_AS5 932
#define NOTE_B5  988
#define NOTE_C6  1047
#define NOTE_CS6 1109
#define NOTE_D6  1175
#define NOTE_DS6 1245
#define NOTE_E6  1319
#define NOTE_F6  1397
#define NOTE_FS6 1480
#define NOTE_G6  1568
#define NOTE_GS6 1661
#define NOTE_A6  1760
#define NOTE_AS6 1865
#define NOTE_B6  1976
#define NOTE_C7  2093
#define NOTE_CS7 2217
#define NOTE_D7  2349
#define NOTE_DS7 2489
#define NOTE_E7  2637
#define NOTE_F7  2794
#define NOTE_FS7 2960
#define NOTE_G7  3136
#define NOTE_GS7 3322
#define NOTE_A7  3520
#define NOTE_AS7 3729
#define NOTE_B7  3951
#define NOTE_C8  4186
#define NOTE_CS8 4435
#define NOTE_D8  4699
#define NOTE_DS8 4978


이 헤더파일은 전역 상수 정의로만 이루어져 있으므로 실행 파일의 용량을 증가시키진 않는다. 따라서 이 파일을 폴도에 복사한 후 인클루드시켜서 필요한 주파수를 사용하면 된다.


#include “pitches.h”
tone(11, NOTE_C3, 1000); // 3도 ‘도’음을 1초간 발생
...


이렇게 주파수(숫자) 대신 상수 이름을 사용하면 가독성이 높아지게 된다.




Posted by 살레시오

 시중에서 쉽게 구할 수 있는 저가형 초음파 센서 모듈(HC-HR04)는 그림과 같이 4핀 인터페이스를 갖는다. Vcc/GND는 전원핀이고 Trig는 초음파를 발생시키는 펄스 신호를 내보내는 핀이며 Echo는 반사파가 감지되었음을 알려주는 신호선이다. 따라서 이 모듈을 사용하려면 아두이노의 디지털 핀 두 개가 필요하다.


초음파 센서 모듈인 HC=SR04를 이용하기 위해서 아두이노의 NewPing 라이브러리를 이용해 보자. 압축된 zip 파일을 다운로드 받은 후에 다음과 같이 아두이노 IDE에서 등록할 수 있다.


스케치 > Include Library > Add .ZIP Library


설치하였다면 다음과 같이 메뉴에 항목이 새로 만들어진다. 이것을 선택하면 프로그램에 #include “NewPing.h” 가 추가되고 라이브러리 함수를 사용할 수 있다.



NewPing 라이브러리의 생성자는 다음과 같다.


NewPing sonar(trigger_pin, echo_pin [, max_cm_distance]);


해당되는 핀 번호 두 개와 최대 측정 거리(기본값 500 cm)를 주게 되어 있으며 최대 측정 거리는 생략할 수 있다.

이 라이브러리는 다양한 함수를 제공하고 있으나 여기에서 사용할 함수는 sonar.ping() 이다. 이 함수는 초음파가 발사되고 그 반사파가 감지될 때까지 걸린 시간을 microsecond 단위의 정수로 반환한다. 따라서 이 값을 라이브러리에 기정의된 상수 US_ROUNDTRIP_CM 으로 나누면 장애물까지의 거리를 cm단위로 얻을 수 있다.


sonar.ping(); // 반사파가 감지될 때 까지의 시간을 us 단위의 정수로 반환
// 감지 가능 거리 내에 장애물 없을 때 0을 반환
US_ROUNDTRIP_CM // us를 cm단위로 바꾸어주는 상


완전한 예제는 다음과 같다. trig 는 2번 핀에 echo 는 3번 핀에 연결되었다고 가정한다.


#include <NewPing.h>
#define TRIGGER_PIN  2
#define ECHO_PIN     3
NewPing sonar(TRIGGER_PIN, ECHO_PIN);
void setup() {
 Serial.begin(9600);
}

void loop() {
 float fDist = (float)sonar.ping()/US_ROUNDTRIP_CM;
 Serial.print("Ping: ");
 Serial.print(fDist);
 Serial.println(" cm");
 delay(100);
}


위에서 fDist 변수값을 구하는데 sonar.ping()함수의 반환값을 float로 변환했음에 유의하자. 이렇게 해야 정확한 실수값이 구해지게 된다.




Posted by 살레시오

 인텔의 갈릴레오 보드는 아두이노와 똑같은 개발환경을 이용할 수 있는 고성능의 임베디드 보드이다.


[그림 1] 갈릴레오 보드의 패키지


잠시 사용해 본 간단한 느낌은 일단 업로드 속도가 일반 아두이노보다 매우 빠르다는 것과 아두이노의 개발환경과 거의 동일하다는 것이다. 하지만 리눅스 OS 위에서 동작이 되기 때문에 실제 저수준의 성능이 매우 높다고 단정할 수는 없다.

[그림 2] 갈릴레오 보드와 아두이노 프로미니 보드의 TWI 통신 실험


위 사진은 아두니오 프로미니 (좌상단)과 갈릴레오 보드를 TWI로 연결하여 통신 실험을 하는 것이다. 매우 잘 동작한다.  약간의 구글링 결과 제조사 홈페이지에서 확인한 바에 의하면 I2C 통신의 경우 갈릴레오 보드는 마스터로만 사용할 수 있고 100kHz의 속도만을 지원한다. 또한 GPIO핀의 신호를 변화시키는데 2ms 가 소요되고 실제 오버헤드를 고려한다면 230 Hz 정도가 최대이다. 생각보다 성능이 높지는 않은 것 같다.


 이전 포스트에서 두에에서 했던 것과 똑같은 실험을 수행해 보았다. 즉 다음과 같은 코드를 실행시킨 다음 2번 핀의 주파수를 측정해 보는 것이다.


void setup() {
   pinMode(2, OUTPUT);
}
void loop() {
   while(1) {
       digitalWrite(2, HIGH);
       digitalWrite(2, LOW);
   }
}

이 프로그램은 2번 핀을 단순히 on/off 시키는 것이다. 결과는 아래 그림과 같은데 좀 의외였다. [그림 ]에서 스코프의 세로줄 한 칸은 1ms 이다. 따라서 구형파의 한 주기가 약 8.5ms이고 이것을 주파수로 환산하면 약 118 Hz 라는 어이 없는 결과가 나온다. 400MHz 짜리 갈릴레오가 16MHz 짜리 우노 보다 하드웨어 제어 성능이 한참 아래라는 결론이다. 아무리 갈릴레오가 리눅스 위에서 돌아가고 물리적인 핀들이 내부적으로는 I2C로 제어된다고는 하지만 성능이 너무 낮은 것 아닌가 하는 생각이 든다.

[표 1] 성능 비교표

보드

프로세서

동작 클럭

구형파의 한 주기  시간

주파수

아두이노 우노 (uno)

ATmega328

16 MHz

5 us

200,000 Hz (200 kHz)

아두이노 두에 (due)

AT91SAM3X8E

87 MHz

8 us

125,000 Hz (125KHz)

인텔 갈릴레오

intel Quark

400 MHz

8.5 ms

     118 Hz

 물론 갈릴레오는 원보드 마이컴이라서 일반적인 아두이노와 종류와 그 타겟 분야가 다르기는 하다. 하지만 단순히 굉장히 빠른 아두이노라는 오해를 가지고 실제 프로젝트에 적용시키려면 충분한 검토가 이루어져야 할 것 같다.



Posted by 살레시오

Due보드의 PWM 개요

 아두이노 두에 (arduino due) 의 PWM 핀은 다음 그림의 핀맵에 나온 바와 같이 같이 D2부터 D13까지 12개를 사용할 수 있다. 따라서 우노보다 더 많은 핀을 PWM출력으로 사용할 수 있다.


[그림 1] 아두이노 두에의 핀맵


단순히 핀 수만 많은 것이 아니라 우노와 달리 최대 해상도를 12비트까지 지정해 줄 수 있다. 따라서 우노는 [0, 255]범위의 출력(8비트)을 사용했었는데 두에의 숫자 범위는  [0, 4095] 이다.


 PWM의 해상도를 조절하려면 다음과 같은 함수를 사용해야 한다.


analogWriteResolution(nBits);


입력인수는 몇 비트를 사용할 것인가를 지정해주면 되며 기본값은 8비트이다. 만약 12비트로 해상도를 높이고 싶다면 다음과 같이 하면 된다.


analogWriteResolution(12);


그런 다음 analogWrite()함수를 사용하면 된다. 예를 들어서


analogWrite(6, 4095); // 6번핀에 4095값을 내보낸다.


 만약 어떤 아날로그 센서의 입력이 [0, 1023] 범위의 값인데 이것을 [0. 4095]범위의 값으로 변환하고 싶다면 map()이라는 함수를 사용하면 된다.


map(sVal, sMin, sMax, cMin, cMax);


이 함수는 sVal 값을 원래의 범위인 [sMin, sMax] 에서 변환하고자 하는 범위 [cMin, cMax]에 해당하는 값으로 바꾸는 함수이다. 만약 (0, 1023)범위의 센서값을 [0,4096]값으로 바꾸고 싶다면 다음과 같이 하면 된다.


map(sVal, 0, 1023, 0, 4095);


이 함수들을 사용해서 PWM을 12비트 해상도로 변경하고 [0,1023]범위의 센서값을 PWM 으로 매핑하는 예는 다음과 같다.


analogWriteResolution(12);

analogWrite(12, map(sensorVal, 0, 1023, 0, 4095));


여기서 sensorVal 변수에는 10비트 아날로그 센서값이 저장되었다고 가정한다.

PWM 주파수 변경

 아두이노 Due의 PWM 주파수는 1KHz 로 정해져 있다. 그런데 응용 분야에 따라서 이 주파수를 변경해 주어야 하는 경우가 있다. 하지만 아두이노에서 PWM의 주파수를 바꿔주는 공식 API 는 없어서 무척이나 불편하다. 그래도 AVR 계열의 아두이노에서는 이런 불편을 해소하기 위해서 사용자가 작성한 라이브러리 (PWM.h)라도 있는 것 같은데 Due의 경우에는 아직까지 그런 라이브러리는 없는 것 같다.


 구글링을 해본 결과 아주 방법이 없는 것은 아니었다. 일단 다음의 헤더 파일을 연다.(윈도의 경우)


Program Files > Arduino > hardware > arduino > sam > variants > arduino_due_x > variant.h


64비트 윈도라면 Program Files(x86) 폴더 밑을 뒤져야 한다. 이 파일을 열면 다음과 같이 정의된 부분이 나온다.


/*
* PWM
*/
#define PWM_INTERFACE PWM
#define PWM_INTERFACE_ID ID_PWM
#define PWM_FREQUENCY 1000
#define PWM_MAX_DUTY_CYCLE 255
#define PWM_MIN_DUTY_CYCLE 0
#define PWM_RESOLUTION 8


이 상수값들을 변경하면 PWM의 주파수나 기본 분해능을 설정할 수 있다고 한다. 예를 들어서 주파수를 10KHz로 변경하고 싶다면 PWM_FREQUENCY 를 10000 으로 바꾸면 된다.


[그림 3] dc모터 실험 (2V 이상 인가되어야 회전이 시작됨)


조그만 장난감 모터는 1KHz 주파수로도 충분하다. 필자가 가지고 있는 모터는 조금 용량이 큰데 (12V/12.9W) 이 정도만 되도 주파수를 조금 키워야 한다. 몇 번 실험해보니 12KHz 정도가 적당한 것 같다.



Posted by 살레시오

 이탈리아어로 due 는 두에 라고 읽고 '둘'이라는 뜻이다. 숫자 1, 2, 3 이 uno, due, tre ... 이다. 다른 것들은 모두 AVR계열의 프로세서를 사용해서 최대 클럭 속도(16MHz)나 배정도(double) 실수 사용에 제약이 있으나 이것은 ARM Cortex-M3 프로세서를 사용하여 훨씬 높은 성능을 가지고 있다.


[그림 1] 아두이노 Due 의 외형

일단 클럭주파수가 84MHz이고 내부적으로 3.3V를 사용 (보통 다른 아두이노 보드는 5V로 동작한다)하므로 라즈베리파이의 GPIO와 핀끼리 바로 연결할 수 있다는 장점도 있다. 또한 AVR 기반의 아두이노 보드와 달리 DAC도 내장하고 있으며 프로그램에서 64bit double형도 다룰 수 있다고 한다. 성능이 높은 만큼 가격은 다소 비싼 편이다. 동작 클럭이 84MHz이므로 단순히 비교하면 우노보드보다 5배 이상 성능이 높으며 디지털 핀수가 굉장히 많아졌고 PWM 개수도 우노보드보다 더 많으며 해상도도 12bit까지 지정할 수 있다. 그리고 모든 디지털핀에 인터럽트를 설정해 줄 수 있다는 것도 큰 장점이다. 우노의 경우 두 개의 외부 인터럽트만 사용할 수 있다.


 자세한 사양은 다음 표와 같다.

[표 1] 아두이노 Due 보드의 사양

Microcontroller

AT91SAM3X8E

Operating Voltage

3.3V

Input Voltage (recommended)

7-12V

Input Voltage (limits)

6-16V

Digital I/O Pins

54 (of which 12 provide PWM output)

Analog Input Pins

12

Analog Outputs Pins

2 (DAC)

Total DC Output Current on all I/O lines

130 mA

DC Current for 3.3V Pin

800 mA

DC Current for 5V Pin

800 mA

Flash Memory

512 KB all available for the user applications

SRAM

96 KB (two banks: 64KB and 32KB)

Clock Speed

84 MHz

 두에보드에는 USB연결단자가 두 개가 있는데 기본적으로 DC잭에 가까운 마이크로USB포트와 PC를 연결하면 전원이 공급되며 프로그램을 업로드할 수 있는 환경이 된다. 이 경우 별도로 DC잭으로 전원을 공급할 필요는 없으나 필요할 경우 7V~12V를 연결해야 한다.


[그림 2] 아두이노 Due의 프로그래밍 포트

 한 가지 주의할 점은 기존 아두이노보드들은 5V로 구동되는데 비해서 이것은 구동 전압이 3.3V라는 것이다. 입출력 핀에 5V신호를 인가하면 보드에 손상이 올 수도 있다고 하니 주의해야 한다.


 PC와 USB를 연결하고 스케치를 실행한 후 도구>보드>아두이노Due(Programming port)를 선택하고 도구>포트>COM? 에서 올바른 포트를 선택하면 일단 환경설정이 끝난 것이다. 예제의 Blink 를 열어서 업로드하면 보드상의 LED 가 깜빡이는 것을 볼 수 있다.

핀의 구조를 도시한 다이어그램은 다음과 같다.

[그림 3] 아두이노 Due의 핀맵

 한 가지 주의할 점은 클럭이 84MHz 이니까 16MHz 를 사용하는 우노보다 6배 이상 성능이 높을 것이라고 기대하면 안된다는 것이다. 성능 테스트를 위해서 간단한 프로그램으로 테스트를 해 보았다. 프로그램은 다음과 같이 2번 핀을 연속적으로 on/off 시키는 것이다. 그리고 이 핀의 주파수가 어떻게 나오는가를 스코프로 찍어보는 것이다.


void setup() {
   pinMode(2, OUTPUT);
}
void loop() {
   while(1) {
       digitalWrite(2, HIGH);
       digitalWrite(2, LOW);
   }
}

2번 핀을 스코프로 찍어서 본 결과는 다음과 같다. 화질이 안 좋지만 세로 줄 한 칸이 1us이므로 구형파의 한 주기가 5us 이다. 따라서 주파수는 200kHz라는 것을 알 수 있다. 가로줄은 한 칸이 1V이므로 on 상태에서 3.3V 의 값을 갖는것을 알 수 있다.

[그림 4] 실험 결과

똑같은 프로그램을 아두이노 우노에 집어 넣고 실행 시킨 결과 구형파의 한 주기가 8us 가 나왔다. 주파수로 따지면 125kHz이다.


 이것만으로 단순하게 성능비교를 하기에는 부족하지만 두에가 우노보다 약 1.6배의 성능을 보여준다. 우노의 클럭주파수가 16MHz 이고 두에가 84MHz이다. 내부적인 연산 속도는 5배 이상(실수 연산의 경우에는 더 높은 성능을 기대할 수 있을 것이다.)의 성능을 가질 것으로 짐작할 수 있으나 디지털 핀과 같은 하드웨어를 제어하는 경우에는 우노에 비해서 5배 이상의 성능을 기대한다는 것이 무리라는 결론이 나온다.



Posted by 살레시오

 이전 포스트에서 작성한 라이브러리를 스케치에서 사용하는 방법을 알아보자.

#include <Morse.h> //라이브러리 인클루드
Morse morse(13);
void setup() {
}
void loop() {
   morse.dot(); morse.dot(); morse.dot();
   morse.dash(); morse.dash(); morse.dash();
   morse.dot(); morse.dot(); morse.dot();
   delay(3000);
}

이 파일은 원래의 스케치파일과 비교하여 몇 가지 다른 점이 있다. 먼저 라이브러리를 인클루드하기 위해서 #include <Morse.h>를 서두에 추가한 것이다. 이것으로 라이브러리에서 정의된 클래스를 이용할 수 있다. 두 번째로 클래스의 인스턴스를 다음과 같이 생성한다는 것이다.

Morse morse(13);

이것이 실행되면 Morse 클래스의 생성자가 실행되며 13이라는 핀번호를 넘겨주게 된다. 이 생성자 안에서 pinMode()함수를 실행했으므로 setup() 함수에서는 그럴 필요가 없다. 그리고 dot(), dash() 함수를 호출하기 위해서는 이 클래스 인스턴스를 매개로 해야 한다. 예를 들어서 13번 핀과 12번 핀에 LED가 연결되어 있다면 다음과 같이 하면 된다.

Morse morse(13);
Morse morse2(12);


멤버 함수 하이라이트 기능

 만약 아두이노 IDE를 사용한다면 추가로 keyword.txt 파일을 만들어서 하이라이트될 함수명을 지정해 줄 수 있다.


Morse KEYWORD1
dash KEYWORD2
dot KEYWORD2

 

각각의 줄은 함수명과 탭구분자 그리고 KEYWORD1/KEYWORD2 라는 지정어가 와야 한다. KEYWORD1은 클래스명을 지정하며 KEYWORD2 는 멤버함수임을 지정한다.

예제 파일 제공

 만약 다른 사용자들의 편의를 제공하기 위해서 예제를 제공하고 싶다면 Morse 폴더 밑에 examples 폴더를 생성한 후 그 안에 각각의 예제를 위한 폴더들을 만들고 그 안에 예제파일을 두면 된다. 이 예제들은 아두이노 IDE의 파일>예제 메뉴에 자동으로 나타난다.

#ifndef ~ #endif 전처리문의 이해

 동일한 헤더 파일(.h 파일)을 중복해서 선언하면 문법적으로 오류를 발생하게 되어 있다. 예를 들어서 MyLib.cpp 파일에서

#include “MyLib.h”
#include “MyLib.h”
....

와 같이 중복선언을 한다면 컴파일러 오류가 발생할 것이다. 사용자가 이런 식으로 작성하는 경우는 거의 없겠지만 개발자의 의도와 관계없이 이런 경우가 발생할 수도 있다. 예를 들어서 a.h 에서 b.h 를 인클루드 했는데 a.cpp 파일에서 이를 간과하고 a.h 와 b.h 둘 다 인클루드 했다면 결과적으로 b.h 는 두 번 포함되는 것이다.


 이 문제는 #ifndef _상수 ~ #endif 전처리문으로 회피할 수 있다. 전처리문은 컴파일이 수행되기 이전에 처리되며 이것의 동작은 이것으로 묶인 부분이 컴파일에 포함되려면 ‘_상수’ 가 정의되지 않은 상태여야 한다.(if not defined 가 조합된 명령어가 #ifndef 이다.)

해결책은 이전 포스트에서 언급한 바와 같이 헤더 파일 전체를 다음과 같이 이 전처리문으로 묶는 것이다.

#ifndef MYLIB_H
#define MYLIB_H
class MyLib {
   ...
};
#endif

이제는 MyLib.h 헤더파일을 아무리 중복해서 인클루드해도 오류가 발생하지 않는다. 그 원리는 맨 처음 인클루드 명령에서는 MYLIB_H라는 상수가 정의되지 않았기 때문에 #ifndef ~ #endif 안의 프로그램이 수행되는데 그 안에서는 MYLIB_H 상수가 정의된다. 즉 맨 처음 이 구문이 포함될 때 상수 MYLIB_H가 정의되므로 만약 중복되서 인클루드되는 경우가 발생하더라도 두 번째 부터는 #ifndef ~ #endif 안의 프로그램이 포함이 되지 않는 것이다.

 결과적으로 맨 처음에 수행되는  #include “MyLib.h” 문만 처리되며 그 이후의 중복되는 #inclde 명령은 처리되지 않게 되어 MyLib 클래스의 선언부가 한 번만 확장된다.



Posted by 살레시오

 본 포스트에서는 사용자 라이브러리를 작성하는 방법을 예제로 설명하도록 하겠다. 다음과 같이 간단한 LED로 모르스 부호를 표시하는 스케치 프로그램으로 시작한다.


int pin = 13;
void setup() {
   pinMode(pin, OUTPUT);
}
void loop() {
   dot(); dot(); dot();
   dash(); dash(); dash();
   dot(); dot(); dot();
   delay(3000);
}
void dot() {
   digitalWrite(pin, HIGH);
   delay(250);
   digitalWrite(pin, LOW);
   delay(250);
}
void dash() {
   digitalWrite(pin, HIGH);
   delay(1000);
   digitalWrite(pin, LOW);
   delay(250);
}

 

이것을 업로드하여 실행하면 내장 LED로 SOS신호를 3초 간격으로 반복하여 생성한다.

 이 프로그램에서 dot() 함수와 dash()를 라이브러리로 빼내서 만들어보도록 하겠다. 여기서는 내장 LED를 사용하였지만 라이브러리에는 LED가 연결한 핀을 생성자에서 지정할 수 있도록 할 것이다. 생성자 안에서는 pinMode()함수를 호출하여 지정된 핀을 출력으로 지정하도록 한다. 이제 변환을 시작해 보자.

 아두이노 라이브러리는 보통 두 개의 파일이 필요하다. 헤더 파일(.h 파일)에는 라이브러리에서 제공하는 클래스와 변수 등의 선언이 위치한다. 클래스 멤버함수 등의 실제 코드는 .cpp 파일에 작성하면 된다. 이렇게 작성해 놓으면 만약 다른 사용자가 이 라이브러리를 참고할 때에는 헤더 파일만 보면 될 것이다. 다른 사용자가 굳이 보지 않아도 되는 실제 코드 부분은 다른 파일로 분리시켜서 가독성을 향상시키는 것이다.

 헤더파일의 핵심은 다음과 같은 클래스 선언 부분이다.


class Morse {
   public:
       Morse(int pin);
       void dot();
       void dash();
   private:
       int _pin;
};

 

클래스에는 사용되는 함수와 변수의 선언이 있으며 외부에서 호출해야 하는 함수는 public 으로, 외부에 굳이 노출시킬 필요가 없는 함수/변수는 private 으로 선언한다. 클래스에는 생성자(constructor)라는 것이 있는데 이것은 클래스의 인스턴스가 생성되는 시점에서 호출되는 함수이다. C++의 생성자는 클래스와 이름이 같으며 출력형식은 지정해주지 않는다.

 헤더파일을 작성할 때 추가로 고려해야 되는 사항이 있는데 먼저 아두이노의 표준 함수들을 사용하기 위해서 다음과 같이 Arduino.h 헤더파일을 인클루드시켜야 한다. 이 헤더는 스케치파일의 경우 자동으로 인클루드되므로 별도로 작성할 필요는 없지만 라이브러리 파일의 경우에는 명시적으로 다음과 같이 포함시켜야 한다.

#include "Arduino.h“

또한, 전체 헤더파일을 다음과 같은 전처리문으로 둘러싸주는 것이 필요하다.


#ifndef Morse_h
#define Morse_h
// the #include statment and code go here...
#endif

이것은 C++에서 특정 헤더파일은 중복해서 인클루드되는 것을 방지하는 일반적인 방법이므로 이해가 가지 않아도 상관없지만 관련 부분을 검색에서 한 번 숙지하기를 권한다.

 마지막으로 주석문을 이용하여 라이브러리에 대한 간단한 설명을 헤더 파일 머리에 작성해주는 것이 좋다. 여기에는 보통 보통 작성자, 날짜, 버전, 라이센스에 대한 정보를 기록한다. 이러한 고려사항들을 모두 적용하면 헤더 파일은 다음과 같은 모양을 가질 것이다.

/*
Morse.h - Library for flashing Morse code.
Created by David A. Mellis, November 2, 2007.
Released into the public domain.
*/
#ifndef Morse_h
#define Morse_h
#include "Arduino.h"
class Morse {
   public:
       Morse(int pin);
       void dot();
       void dash();
   private:
       int _pin;
};
#endif

 이제 실제 코드가 위치하는 .cpp 파일의 작성법에 대해서 알아보자. 이 파일은 Arduino.h 와 방금 작성한 Morse.h 파일을 처음에 인클루드해야 한다.

#include "Arduino.h"
#include "Morse.h"

그 다음 생성자를 작성한다. 이 생성자는 클래스 인스턴스를 생성할 때 호출되므로 여기에서 pinMode()함수를 호출하여 사용자가 지정한 핀을 출력으로 설정하는 것이 좋다.


Morse::Morse(int pin) {
   pinMode(pin, OUTPUT);
   _pin = pin;
}

 

함수명 앞의 Morse:: 는 이 함수가 Morse라는 클래스의 멤버함수라는 것을 지정한다. 이 클래스의 다른 함수들을 정의할 때에도 같은 문법이 적용된다. 그리고 private 변수 _pin에서 맨 앞의 _은 이 변수가 private 변수라는 것을 관례적으로 표시하는 방법이며 또한 함수의 입력 인수 pin과도 구별시켜주는 역할을 한다.

 그 다음으로 원래의 스케치 파일에서 가져온 dot()함수와 dash()함수를 정의하면 된다. 이 함수의 내부에서는 스케치 파일과 다르게 클래스의 멤버 변수 _pin을 사용한다.

void Morse::dot() {
   digitalWrite(_pin, HIGH);
   delay(250);
   digitalWrite(_pin, LOW);
   delay(250);
}
void Morse::dash() {
   digitalWrite(_pin, HIGH);
   delay(1000);
   digitalWrite(_pin, LOW);
   delay(250);
}

또한 간단한 설명을 담고 있는 주석문을 파일 서두에 포함시키는 것이 좋다. 전체 .cpp 파일은 다음과 같다.

/*
Morse.cpp - Library for flashing Morse code.
Created by David A. Mellis, November 2, 2007.
Released into the public domain.
*/
#include "Arduino.h"
#include "Morse.h"
Morse::Morse(int pin) {
   pinMode(pin, OUTPUT);
   _pin = pin;
}
void Morse::dot() {
   digitalWrite(_pin, HIGH);
   delay(250);
   digitalWrite(_pin, LOW);
   delay(250);
}
void Morse::dash() {
   digitalWrite(_pin, HIGH);
   delay(1000);
   digitalWrite(_pin, LOW);
   delay(250);
}

이것으로 라이브러리의 작성은 끝났으며 이 파일들은 {내문서}\Arduino\libraries\MyLib 폴더에 위치하여야 아두이노 하프가 인클루드시킬 수 있다. 먼저 {내문서}\Arduino\libraries 밑에 Morse 폴더를 생성한 후 이 두 파일을 그 안에 복사해서 넣으면 된다. 폴도의 구성도를 그려보면 다음과 같다.


[그림 1] Morse 라이브러리의 폴더 구조

이제 아두이노 IDE를 재실행시키면 Sketch>Include library 메뉴의 하위에 Morse 항목이 새로 생긴 것을 확인할 수 있을 것이다.

[그림 2] 생성된 Morse 라이브러리

주의할 점은 헤더 파일과 본체 파일 모두 이름이 속한 폴더와 같아야 한다는 것이다. 그리고 확장자는 정확하게 .h와 .cpp 이어야 한다.



Posted by 살레시오

 본 포스트에서는 인터넷으로 내려 받은 사용자 라이브러리를 설치하는 방법과 직접 라이브러리를 작성하는 방법에 대해서 좀 더 자세히 설명하도록 하겠다.

 아두이노 IDE를 설치하면 {내문서}\Arduino\libraris 폴더가 생성되고 여기에 다운로드 받은 라이브러리를 복사하거나 사용자가 직접 작성한 라이브러리를 저장한다. 컴파일을 수행할 때 이 폴더는 자동으로 인클루드되어서 여기에 있는 라이브러리들이 링크된다.

 라이브러리는 기본적으로 폴더 단위로 저장되어야 한다. 예를 들어서 MyLib 라는 이름으로 (보통 라이브러리명은 대문자로 시작한다.) 라이브러리를 작성한다면 {내문서}\Arduino\libraris 폴더 밑에 MyLib라는 폴더가 있어야 하고 이 폴더 밑에 MyLib.h 헤더 파일과 MyLib.cpp 파일이 있어야 한다. 만약 예제 파일을 제공하고 싶다면 example 폴더 밑에 예제 파일의 이름과 같은 폴더를 두고 그 밑에 .ino 파일을 위치해 두어야 한다. 이를 도시하면 다음과 같다.


[그림 1] 윈도에서 아두이노 라이브러리 폴더의 구조

보통은 라이브러리는 클래스나 변수 등을 선언한 헤더 파일(.h)과 클래스 멤버함수의 정의부가 있는 .cpp 파일로 나뉜다. 그리고 헤더 파일에는 MyLib 클래스가 선언되어 있어야 한다. 보통은 헤더 파일과 본체 파일(.cpp 파일)로 나뉘지만 모든 선언과 정의를 헤더 파일에 둘 수도 있다. 하지만 헤더 파일에는 클래스의 선언부를 작성하고 그것을 구현한 cpp파일을 별도로 두는 것이 더 일반적이고 바람직하다. 이렇게 구성하면 이 라이브러리를 사용하는 스케치파일에서는 헤더 파일만 인클루드해서 사용할 수 있고, 여러 곳에서 중복해서 사용할 경우에도 헤더 파일만 인클루드 하면 되기 때문이다


 아두이노 홈페이지(http://arduino.cc/en/Reference/Libraries)에서 다양한 아두이노의 라이브러리에 대해서 설명하고 있다. 사용자는 여기에서 필요한 라이브러리를 다운로드 받아서 설치할 수 있다. 예를 들어서 OneWire 라는 라이브러리를 다운로드 받았다고 하자. 그러면 다운로드 폴더에 OneWire.zip 파일이 다운로드 되어 있을 것이다. 이 압축파일에는 OneWire.h, OneWire.cpp 파일이 들어 있다. 이 경우 이 압축파일을 이용하여 바로 사용자 라이브러리 폴더에 복사할 수 있는데 다음과 같이 Sketch>Include library > Add .ZIP Library 메뉴를 이용하여 압축파일을 선택해 주면 된다.

[그림 2] 아두이노 IDE의 라이브러리 관리 메뉴

이 메뉴를 선택한 후 OneWire.zip 파일을 선택하면 자동으로 사용자 라이브러리 폴더에 압축이 해제되서 복사해 들어가게 된다.

 OneWire.zip 파일은 다운로드 받은 압축파일에 필요한 라이브러리 파일이 직접 들어가 있는 경우이지만 경우에 따라서 인터넷에서 받은 파일이 여러 버전의 라이브러리를 가지고 있는 것도 있다. 예를 들어서 우노용과 두에용이 분리되어서 있는 경우이다. 이런 경우에는 사용자가 직접 압축을 해제한 후에 필요한 폴더만 사용자 라이브러리 폴더에 수동으로 복사해 주어야 한다.



Posted by 살레시오