보통 아두이노 코딩을 할 때 하나의 소스파일에 몽땅 코딩을 하게 되는데 이때 코드량이 길어지면서 보기에 불편하며 관리하기도 어렵다. 그리고 모터, 센서 등 특정 모듈만을 위한 코드들을 분리하여 깔끔하게 관리하고 싶을 때가 있다. 또 외부에서 c나 c++로 작성되어 이미 사용하고 있는 소스파일을 가져와서 사용해야 할 때도 있다. 이럴 때는 비슷한 기능을 하는 함수들 혹은 특정 모듈을 하나의 소스 파일로 분리하면 여러모로 편리해진다. 어떻게 보면 다른 개발 툴에서 Project 개념에 해당하는 기능이 Arduino IDE에서는 Sketchbook이다. 아두이노 sketch들을 모아서 관리하기 때문에 이렇게 네이밍을 한 것으로 보인다. 이번 글에서는 아두이노 IDE에서 Sketchbook을 통해 여러 소스 파일들이 어떻게 처리되는지 알아보자.
Sketchbook 생성
아두이노 IDE를 실행하면 비어있는 에디터가 아니라 마지막으로 사용한 Sketchbook이 열리면서 소스 파일들도 함께 열린다. 이 상태에서 새로운 Sketchbook 생성하기 위해서 File 메뉴에서 New를 선택(Ctrl+N)하여 새 에디터를 연다. 새 에디터가 열리면 이름이 sketch_xxxxxx 인 파일도 함께 생성된다. 처음 열렸던 에디터는 필요 없으니 닫아도 된다.
Sketchbook을 만들기 위해 File 메뉴에서 Save를 선택(Ctrl+S) 한다. 그러면 'Save sketch folder as...' 창이 나타나며 기본으로 설정되어있는 폴더 위치가 나온다. 이 위치는 변경 가능하다. '파일 이름'에 원하는 이름을 적어 넣고 저장을 누르면 기입한 이름의 폴더가 기본위치에 생성되며 해당 폴더 내에는 같은 이름의 확장자가 .ino인 sketch 파일이 생성된다. 아래는 이름을 'mytest'로 했을 때의 예이다.
이렇게 하면 'mytest'가 Sketchbook의 이름이 되어 Sketchbook 리스트에 나타난다.
Sketchbook이 생성될 기본 위치는 Preferences에 정의되어있으며 수정이 가능하니, 원하는 위치를 지정하면 된다. 이때 지정위치를 변경하면 IDE를 다시 실행해야 한다.
Sketchbook에 소스 파일들 추가
생성된 Sketchbook에 새로운 소스 파일을 추가해보자.
우측 상단에 있는 드롭다운 메뉴(▼)에서 'New Tab'(Ctrl+Shift+N)을 클릭한다. 그러면 에디터 창 아래에 오렌지 색의 바가 나타나는데, 여기에 생성할 파일 이름을 입력한 후 OK 버튼을 클릭하면 된다. 여기서는 "test 1"라고 입력하였다.
아래 그림은 새로 생성된 탭을 보여준다. 자세히 보면 탭 이름 옆에 보면 '§' 문양이 있는데 이것은 탭은 있지만 아직 파일이 저장되지 않아 표시되는 것이다. '§' 문양은 코드를 수정하고 저장하지 않았을 때 표시된다.
여기서 Ctrl+S로 파일 저장을 해보면 '§' 문양이 사라지면서 해당 폴더에 test1.ino가 생성된다. 앞에서 확장자를 따로 지정하지 않았지만 자동으로 확장자가 .ino인 아두이노 스케치 파일로 저장된다.
동일한 방법으로 test2.h, test3.c, test4.cpp를 추가해보자. 주의해야 할 것은 "test2.cpp"와 같이 확장자까지 포함된 이름을 'Name for new file:'에 지정해주어야 한다.
탭 우측의 드롭다운 메뉴(▼)에서 파일의 이름 변경 및 삭제도 가능하다. 탐색기에서 파일의 이름을 직접 변경하는 것도 가능하지만 IDE에 refresh 기능이 없어 IDE를 닫았다 다시 실행하면 변경된 파일 이름이 반영된다. 또한 새 소스파일을 탐색기에 바로 생성할 수 있는데 이때도 아두이노 IDE를 재실행해야 탭에 추가한 소스파일이 보인다.
sketch에서 c와 cpp 코드 사용하기
앞에서 만든 소스들은 아직 비어있는 상태이다. sketch에서 c와 cpp 코드를 어떻게 사용해야 하는지 간단한 예제를 통해 알아보자. 코드는 아래 '최종 코드' 섹션을 참조하면 되고 각 소스의 첫째줄의 주석에 있는 파일 이름이 해당 소스파일의 이름이다.
sketch(mytest)에서 다른 sketch(test1)의 함수 호출
이 부분은 기존 sketch 작성과 똑같이 하면 된다. 먼저 mytest를 blink 예제를 참조하여 작성한다.
test2.h에 ms delay 시간을 500ms, 1000ms, 2000ms로 define 한다. 그리고 전역 변수 count를 선언하고 0으로 초기화한다. 이렇게 작성된 test2.h를 mytest.ino에 include 한다. blink와 print 부분을 test1.ino에 blink()와 printCountTest1() 함수로 분리한다. mytest에서 이 두 함수를 호출한다. 여기까지 작성하고 컴파일한 다음 아두이노 보드에 업로드하면 LED가 깜박이면서 시리얼 모니터에는 1,2,3,4... 과 같이 출력될 것이다.
sketch(mytest)에서 cpp 코드(test4.cpp)의 함수 호출
c코드를 작성하기 전에 먼저 cpp코드부터 작성해보자. test2.h에 printCountTest4()를 선언한다. 그리고 test4.cpp에 이 함수를 정의한다. 이때 Serial 객체를 사용하므로 Android.h를 include 해야 한다. mytest에서 기존 count 출력과 분리하기 위해 400을 더한 count와 함께 test4에 정의된 printCountTest4()를 호출한다. 여기까지 하고 보드에 올려서 테스트해보면 1,401,2,402... 이렇게 출력될 것이다.
sketch(mytest)에서 c 코드(test3.c)의 함수 호출
여기서 살짝 복잡해진다. 왜냐하면 아두이노는 기본적으로 c++를 지원하기 때문에 c로 작성된 코드에서 아두이노가 기본으로 지원하는 다양한 객체(예:Serial)들을 직접 사용할 수 없다. 그리고 c로 작성된 코드를 그냥 컴파일하면 심벌이 name mangling이 되기 때문에 링크 시 에러가 발생한다.
먼저 name mangling 부분부터 해결해보자.
extern "C"를 사용하면 c의 심벌 그대로 나오기 때문에 c++에서 c의 object를 링크 시 에러가 발생하지 않는다. test2.h에 이미 선언된 printCountTest3() 함수 앞에 extern "C"를 붙여주면 된다. 정확히는 아래와 같이 #ifdef __cplusplus로 c++ 컴파일러 일 때만 extern "C"를 사용하도록 해야 하지만 아두이노 IDE가 c++로 컴파일되는 것을 알고 있기 때문에 코드를 복잡하게 할 필요 없이 여기서는 extern "C"만 사용하였다.
#ifdef __cplusplus
extern "C" {
#endif
void printCountTest3(int count);
#ifdef __cplusplus
}
#endif
다음으로 c에서 직접 아두이노의 Serial 객체를 사용할 수 없으므로 cpp 코드의 도움을 받아야 한다. test4.cpp에 printCountTest4ByTest3() 함수를 추가하고 Serial 객체를 사용하여 출력하도록 작성한다. 그리고 test3.c의 printCountTest3()에서는 이 함수를 호출하면 된다. 이때 c에서 printCountTest4ByTest3() 함수가 호출되기 때문에 여기에도 extern "C"를 붙여주어야 링크 에러가 발생하지 않는다. 마지막으로 mytest.ino에서 printCountTest3() 함수를 호출하도록 하여 보드에 올리면 최종적으로 아래의 결과를 볼 수 있다.
* c로 작성하면 번거롭고 실수할 여지가 많다. 그러니 처음부터 작성하는 경우라면 cpp로 코딩하는 것이 정신건강에 좋아 보인다. 그리고 c로 작성된 기존 코드를 아두이노 IDE에 가져와야 할 경우에는 cpp로 wrapper를 만들어 사용하면 된다.
최종 코드
// mytest.ino
#include "test2.h"
void setup() {
Serial.begin(9600);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
blink(MS_500);
printCountTest1(++count);
printCountTest3(count+300);
printCountTest4(count+400);
}
// test2.h
#define MS_500 500
#define MS_1000 1000
#define MS_2000 2000
int count = 0;
extern "C" void printCountTest3(int count);
void printCountTest4(int count);
// test1.ino
void blink(int ms) {
digitalWrite(LED_BUILTIN, HIGH);
delay(ms);
digitalWrite(LED_BUILTIN, LOW);
delay(ms);
}
void printCountTest1(int count) {
Serial.println(count);
}
// test3.c
void printCountTest3(int count) {
printCountTest4ByTest3(count);
}
// test4.cpp
#include <Arduino.h>
void printCountTest4(int count) {
Serial.println(count);
}
extern "C" void printCountTest4ByTest3(int count) {
Serial.println(count);
}
www.arduino.cc/en/Guide/Environment#sketchbook
www.arduino.cc/en/Guide/Environment#tabs-multiple-files-and-compilation
'mcu > arduino' 카테고리의 다른 글
아두이노에서 HM-10 다루기 (0) | 2021.04.10 |
---|
댓글