아두이노 개발 툴 1.0에 대한 소스 분석 글이다.
http://knight76.tistory.com/entry/아두이노-개발-툴-소스-10-분석을-위한-환경-셋팅 에 이어 소스 분석을 시작한다. 프로세싱 개발 툴과 너무 흡사한 UI를 가졌기 때문에 소스 분석이 조금 더 쉬운 것 같다.
먼저 빌드 순서를 파악하고, 소스를 파악한다. (짜잘한 것은 제외) 그리고 주석이 약간 틀린 부분이 있었다.ㅠ
1. 빌드 순서
(1) build/build.xml
운영체제(플랫폼)에 맞는 빌드 콜을 호출한다. 윈도우의 경우 windows-build target이 가장 먼저 실행이 된다.
1) todo.txt 파일의 revistion 숫자, processing/app/Base.java 파일의 revision 정보와 비교해서 revision 번호를 맞춘다.
2) 윈도우 운영체제인지 확인한다.
3) core 디렉토리와 app 디렉토리에 ant 컴파일을 시킨다.
- core/build.xml
core 디렉토리를 컴파일하고 bin/core.jar 파일을 만든다.
- app/build.xml
app 디렉토리를 컴파일하고, bin/pde.jar 파일을 만든다.
4) windows/work 디렉토리를 생성 후, 실행에 필요한 libraray (jar, dll)과 driver 파일들을 복사한다. 그리고, window/work/hardware 디렉토리에 윈도우에서 동작할 수 있는 avr 툴(avr 체인) 압축 파일을 풀어둔다.
5) windows/work 디렉토리의 여러 디렉토리에 셋팅을 한다.
1) windows/work/tools 디렉토리에 Mangler.java 소스를 복사한다.
2) window/work/library 디렉토리에 아두이노 library 파일을 복사한다.
3) window/work/hardware 디렉토리에 아두이노 부트로더, 펌웨어등을 복사한다.
3) window/work/example 디렉토리에는 간단히 예제들을 복사해둔다.
6) launch관련 파일들을 모은다. launch4j와 설정파일, 관련 클래스들을 모아둔다.
7) launch4j를 실행하고, 컴파일하고 링킁하고 실행파일(build\windows\work\arduino.exe)을 생성한다.
launch4j를 이용하여 exe 파일을 만드는 config.xml 파일이다.
<launch4jConfig>
<dontWrapJar>true</dontWrapJar>
<headerType>gui</headerType>
<jar>lib</jar>
<outfile>arduino.exe</outfile>
<errTitle></errTitle>
<cmdLine></cmdLine>
<chdir>.</chdir>
<priority>normal</priority>
<downloadUrl>http://java.sun.com/javase/downloads/</downloadUrl>
<supportUrl></supportUrl>
<customProcName>false</customProcName>
<stayAlive>false</stayAlive>
<manifest></manifest>
<icon>application.ico</icon>
<classPath>
<mainClass>processing.app.Base</mainClass>
<cp>lib/pde.jar</cp>
<cp>lib/core.jar</cp>
<cp>lib/jna.jar</cp>
<cp>lib/ecj.jar</cp>
<cp>lib/RXTXcomm.jar</cp>
</classPath>
<jre>
<path>java</path>
<minVersion>1.5.0</minVersion>
<maxVersion></maxVersion>
<jdkPreference>preferJre</jdkPreference>
<opt>-Xms128m -Xmx128m</opt>
</jre>
<splash>
<file>about.bmp</file>
<waitForWindow>true</waitForWindow>
<timeout>60</timeout>
<timeoutErr>true</timeoutErr>
</splash>
<messages>
<startupErr>An error occurred while starting the application.</startupErr>
<bundledJreErr>This application was configured to use a bundled Java Runtime Environment but the runtime is missing or corrupted.</bundledJreErr>
<jreVersionErr>This application requires at least Java Development Kit</jreVersionErr>
<launcherErr>The registry refers to a nonexistent Java Development Kit installation or the runtime is corrupted.</launcherErr>
<instanceAlreadyExistsMsg>An application instance is already running.</instanceAlreadyExistsMsg>
</messages>
</launch4jConfig>
|
2. UI (Editor)
processing.app.Base.java 소스에 main 메서드를 따라가 본다.
OS(플랫폼)을 확인하고, preferences.txt 프로퍼티 화일(크기..)을 읽는다. theme/theme.txt 프로퍼티 설정을 읽어 editor에 대한 프로퍼티를 확인한다. look&feel 을 설정후, 임시폴더를 만들고 Base 객체를 생성한다.
Base 생성자 코드를 따라가면, examples, libraries, tools 디렉토리 패스와 프로젝트 파일로 저장할 path (sketchbook path) 를 지정해둔다. hardware 디렉토리를 잘 기억한다. Editor 클래스를 초기화하여 UI를 실행한다. 이전에 아두이노 개발 툴을 종료했을 때 사용했던 스케치 파일이 있었으면 툴 실행시 스케치 파일을 열어둔다.
UI에 관련된 대부분의 특징은 프로세싱 개발 툴과 흡사하다. processing.app 패키지 밑에 잘 들어가 있다.
운영체제에 맞는 특징에 대해서는 운영체제별로 Platform.java 소스에 dependant한 코드들이 들어가 있다. 재미있는 것은 아두이노 오픈 소스 개발자가 macosx 쪽에 Think Different 클래스를 추가했다. 오픈소스의 센스라고 해야 하나 ㅎㅎ 먼가 대단하다고 생각한 코드는 아니지만 웃음코드에 박수~
public class Platform extends processing.app.Platform {
….
public void init(Base base) {
System.setProperty("apple.laf.useScreenMenuBar", "true");
ThinkDifferent.init(base);
..
}
public class ThinkDifferent implements ApplicationListener {
… OS X application 이벤트 (open, about 등등)에 대해서 처리하려고 만들었다.
}
아두이노 개발 툴을 UI 초기화하고 menu를 생성하고 editor를 만들면서.. serial port 정보와 타겟 보드가 나와있는데, 이는 processing.app.EditorLineStatus.java 클래스에서 설정 파일을 읽어오는 코드로 동작하게 되어 있다.
맨 처음 읽을 때는 preference.txt 파일에 있는 설정 정보를 읽는다. 이전에 Serial 포트 변경이 있을 때마다 설정값을 변경시키도록 되어 있따.
# ARDUINO PREFERENCES
board = uno
target = arduino
serial.port=COM1
|
3. 아두이노 개발 툴의 스케치 파일 읽고 쓰기
파일을 읽는 시퀀스는 그냥 평범하다.
EditorHeader.rebuildMenu() –> SkechCode.load() –> Base.loadFile() –> PApplet.loadStrings() –>PApplet.createInput() 로 이어져 있다. 파일을 line을 seperator로 String[](내부적으로는 BufferedReader(FileInputStream)클래스를 사용)타입으로 간단하게 파일을 일고, PApplet.join() 메서드에서 String[]에 line separator를 넣어서 String 객체로 리턴한다.
파일을 쓰는 구조는 추측할 수 있도록 파일을 읽는 반대로 구성되어 있다.
4. Sketch 파일
아두이노 개발 툴에서 저장하는 파일들을 스케치(Sketch) 라고 한다. 무엇인가를 그리고 거기에 맞게 그린다는 차원에서는 좋아 보이는 개념인 것 같다. processing.app.Sketch.java 클래스는 pde, java 확장자를 지원하고 cpp를 지원하지 않는다. 또한 여러 버그로 인한 많은 땜질 코드들이 보인다.
processing.app.SketchCode.java 클래스는 코드 자체를 의미하는 클래스로서, 코드가 읽고, 쓰고, 변경될 때 사용되는 클래스이다.
5. Compile
빌드 Call stack은 다음과 같다.
에디터에서 빌드메뉴를 선택하면, Editor.buildSketchMenu() –>Editor.handleRun()->Editor.presentHandler()
->Thread인 headerListFromIncludePath.run() –> Sketch.build() 로 진행한다.
(1) 전처리 (preprocessor)
컴파일을 하기 전에 전처리(preprocess) 과정을 거친다. Sketch.preprocess() , PdePreprocessor.preprocess()을 참조한다. 소스에 맨 끝에는 \n과 멀티 라인으로 된 주석은 정리되도록 했다. OOME 나 NPE가 발생한 적이 있는 듯 하다. unicode 설정에 되어 있으면 소스를 unicode로 바꾼다.
pde 나 ino 확장자는 가진 파일들은 include 헤더 파일 스트링을 찾아서 저장해 둔다. 소스에서 컴파일과 관련없이 주석, 전처리 지시자, 싱글/더블 따옴표를 모두 스페이스로 만들어버리고 함수 이외 지역에 사용했던 top level brace를 뺀다. include header 와 정리된 prototype의 line수를 저장해서 나중에 소스를 만들 때 이용한다.
C++ 클래스를 하나 만들어 pde/ino파일에서 사용했던 include 헤더 파일과 #include “Arduino.h”를 추가하고, 잘 정리한 prototype을 넣은 후 cpp 파일로 만들고 컴파일 한다.
(2) 컴파일
Compiler.compile() 메서드에서 정의하고 있는 컴파일러는 avr-gcc (호스트환경은 윈도우이지만, 타겟보드에서 실행할 수 있도록 cross 컴파일을 지원하는 컴파일러) 를 사용하고 있다. avr base path는 hardware/tools/avr/bin/ 디렉토리를 기준으로 한다.
보드(아두이노 우노)에 맞는 core 라이브러리들이 참조될 수 있도록 라이브러리(hardware\arduino\cores\arduino 디렉토리 밑에 있는 h, cpp 파일들) 들을 잘 정리해둔다. sketch 파일을 찾아 리스트에 추가하고 컴파일한다. variants도 필요하면 path에 추가해준다.
1) 스케치 파일 컴파일
--- path 디렉토리에 s 확장자로 끝나는 파일을 컴파일
command 창에서 동작할 수 있도록 명령어를 조합하고 object 파일을 만들기 위해서 gcc 컴파일을 실행한다.
avr-gcc –c –g -assembler-with-cpp -mmcu={$mcu} -DF_CPU={$cpu} -DARDUINO={$number} –I{IncludePath} {$source file} –o {$object name} |
--- path 디렉토리에 c 확장자로 끝나는 파일을 컴파일
avr-gcc –c –g –Os –Wall –ffunction-section –fdata-section -mmcu={$mcu} -DF_CPU={$cpu} –MMD -DARDUINO={$number} –I{IncludePath} {$source file} –o {$object name} |
--- path 디렉토리에 cpp 확장자로 끝나는 파일을 컴파일
avr-gcc –c –g –Os –Wall -fno-exceptions –ffunction-section –fdata-section -mmcu={$mcu} -DF_CPU={$cpu} –MMD -DARDUINO={$number} –I{IncludePath} {$source file} –o {$object name} |
2) 라이브러리 컴파일
아두이노 개발 툴에서 짠 소스에서의 include 헤더에 대한 정보들을 모아 관련 디렉토리에서 컴파일한다. 컴파일 하는 방법은 s, c, cpp 확장자로 끝나는 파일로 위의 컴파일 방법과 같다.
3) 코어 파일 컴파일
cores 디렉토리 밑에 있는 파일들을 찾아 컴파일하고 core.a 파일로 만든다.
4) 링킹
avr-gcc –Os -Wl,--gc-sections -mmcu=atmega328p –o {$primaryclass}.elf {$objectfile} core.a –L{$buildpath} –lm |
5) EEPROM data 를 만들기
avr-objcopy –O –R ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 {$primaryclass}.elf {$primaryclass}.eep |
6) .hex 파일 만들기
avr-objcopy –O –R ihex .eeprom {$primaryclass}.elf {$primaryclass}.eep |
(3) 보드에 이미지 업로드
EEPROM data를 잘 구워 아두이노 보드에 넣는(upload) 작업을 할 때, processing.app.Upload 클래스, AvrdudeUploader 클래스를 사용한다. 부트 로더를 굽거나 (burn bootlaoder) 할 떄도 사용된다.
Sketch.upload() –> AvrdudeUploader.uploadUsingPreferences() –>AvrdudeUploader.avrdude() 를 호출하는 구조로 되어 있으며, avrdude 라는 명령어를 이용해서 플래쉬에 hex 파일을 굽는다. (-Uflash:w:파일.hex:i’ )
아두이노의 부트로더를 굽는 방법은 avrdude 명령어에 -Uefuse:w:~~~ 옵션을 넣는다.
통신방법은 여러가지가 있다. 아마도 운영체제 때문으로 생각된다. 만약 -Pusb 파라미터를 주어서 usb로 통신할 수 있으며, -P {serial port 번호 } 파라미터를 주어서 serial port로 통신할수도 있다.
-b 파라미터로 굽는 속도를 조절할 수 있다.
그리고, Serial 객체를 생성해서 통신하도록 되어 있다.
Serial 클래스는 내부적으로 gnu.io 패키지의 포트 정보를 확인하는 CommPortIdentifier 클래스를 사용하고 있으며, MessageConsumer에 대한 리스너 등록, 이벤트 발생, 데이터를 읽는 형태를 가지고 있다.
(4) 윈도우 사용시 레지스트리 이용 - jna
처음 시작할 때, 윈도우에서 ino, pde 파일을 더블 클릭했을 때 아두이노.exe 파일을 실행시키기 위해서, 윈도우 레지스트리에 추가된다. 관련 파일은 Platform.checkAssociations(), Registry 클래스, Advapi32 클래스이다. 여기서도 processing처럼 jna를 아주 잘 쓰고 있다.
public interface Advapi32 extends StdCallLibrary {
…
Advapi32 INSTANCE = (Advapi32) Native.loadLibrary("Advapi32", Advapi32.class, Options.UNICODE_OPTIONS);
..
} |
이상 끝.. 자바로 만들어져서 소스가 어렵지 않다.
참고...
아두이노 개발 툴 없이 소스와 툴만 가지고 컴파일할 때 사용하는 방법은 다음과 같다. 소스에서 사용하는 컴파일 방법과 동일하다.
http://andrew-j-norman.blogspot.com/2011/12/bypassing-ide.html