Technical Focus



새로운 모험, 임베디드 시스템에 적용해보는 안드로이드 활용 첫 걸음 2

글: (주)하드커널 김형섭/ 김용요/ 윤동열

지난 호에서는 ODROID-7에 적용할 수 있는 오실로스코프 확장보드를 제작하고, ADC 블록을 제어할 수 있는 디바이스 드라이버를 제작하여, 스코프로부터 아날로그 신호 데이터를 취득할 수 있게 하였다. 이번 호에서는 JNI와 Android SDK를 이용하여 취합한 데이터를 안드로이드 App에 표시해주는 방법을 설명하겠다.


- /dev/adc 권한 설정
Android system 안에서 ADC Device node인 /dev/adc 권한을 0666으로 설정하여 스코프 App 이 접근 가능하도록 만든다.
1)  Froyo(Android 2.2) : system/core/init/devices.c 파일안의 devperms[ ] 구조체에 추가한다.
2)  Gingerbread(Android 2.3) : system/core/init/ueventd.c 파일을 열어 보면 타겟보드에서 /ueventd.rc 와 /ueventd.[hardware].rc 파일을 파싱해서 권한 설정하는 것을 알 수 있다. 여기서 [hardware] 는 /proc/cpuinfo 정보 중 Hardware 의 값(odroid7)을 대입하게 된다. 즉 /ueventd.odroid7.rc 파일을 파싱하여 권한을 설정하게 된다.
    한편 ueventd.odroid7.rc 파일은 Android 소스 중 device/hardkernel/odroid7/device.mk 파일에서 device/hardkernel/
odroid7/conf/ueventd.product.rc 파일을 타겟 디바이스의 /ueventd.odroid7.rc 로 복사하게 된다.
    결국, /device/hardkernel/odroid7/conf/ueventd.product.rc 파일에 /dev/adc 노드를 추가함으로써 권한이 설정된다
.


3. Android JNI
안드로이드 App은 기본적으로 Java를 사용하여 프로그래밍을 하게 된다. 하지만 Java에서 직접적으로 하드웨어를 제어하기는 쉽지 않고, JNI(Java Native Interface)를 통해서 Native C를 통해 접근하게 된다. 이를 위해 안드로이드에서는 NDK(Native Development kit)를 제공한다.

NDK
1) SDK/NDK 설치 및 설정
     - Installing the SDK(http://developer.android.
com/sdk/installing.html)
     - ADT Plugin for Eclipse(http://developer.android.
com/sdk/eclipse-adt.html)
     - Android NDK(http://developer.android.com/
sdk/ndk/index.html)

위의 링크를 참고하여 설치 및 설정할 수 있으며, SDK/
NDK 설치와 설정은 인터넷과 책을 통해 좀 더 많은 정보를 얻을 수 있다. 따라서 OdroidOscilloscope에 관한 정보만을 범주로서 한정 하고, 다음 단계로 넘어가기로 한다.

[소스 8] jni/jniScope.c 파일안의 openScope 함수 

[소스 8]의 함수이름을 보면 Java 다음의 com_
hardkernel_OdroidOscilloscope는 [그림 3] Odroid
Oscilloscope 디렉토리 구조 중 src 폴더 밑의 Java 파일들의 폴더 위치라는 것을 알 수 있다. 그리고, 마지막의 Scope
_openScope 는 Scope.java 파일의 openScope 함수를 나타낸다.
즉, Java_com_hardkernel_OdroidOscilloscope_
Scope_openScope 함수는 src/com/hardkernel/Odroid
Oscilloscope/Scope.java 파일 안의 openScope 함수가 호출 되면 [소스 8]의 Java_com_hardkernel_Odroid
Oscilloscope_Scope_openScope 함수가 호출되고, 다시 18번째 줄인 Native C로 코딩된 openScope 함수가 호출되는 구조를 가지고 있다.

4) Device Driver C level control
[그림 3] OdroidOscilloscope 디렉토리 구조를 보면 src/com/hardkernel/OdroidOscilloscope 폴더에 *.java 파일을 볼 수 있다. 이 Java 파일들이 사람(End User)과 소통(Communication)하게 되고(User Interface), 이 Java 파일들은 jni 폴더의 C소스들과 소통하며, C소스들은 ioctl(I/O Control)이라는 인터페이스를 통하여 리눅스 커널과 소통하고, 마지막으로 커널은 ODROID-7 의 하드웨어와 소통하여, 결국 우리가 원하는 것을 보여준다. 중간중간에는 서로 소통할 수 있는 규약(Protocol)이 반드시 존재하며, 그 규약에 맞추어 소통할 수 있게 만드는 행위를 엔지니어가 밤낮의 구별 없이 하는 일이다.

Java, Native C, 커널과의 의사소통
그럼, 실질적으로 Java 파일과 C소스, 그리고 C소스와 커널과의 소통이 어떻게 이루어지는지 소스를 통하여 알아보자.



[소스 9] Scope.java

제일 상단 User Interface [소스 9] Scope.java 파일의 일부이다. 256 줄에서 JNI 라이브러리를 Load 하고 있다. 이 파일의 전체 이름은 libScope.so 이고, ndk-build로 생성된 JNI 라이브러리 파일이다.



그림 4. ndk-build

[그림 4] ndk-build에서 libScope.so 의 생성과 위치, 라이브러리의 정보를 알 수 있다.
[소스 9] Scope.java 의 250, 251, 252, 253 줄에서 Native 코드의 원형(Prototype)를 선언함으로써 빌드할 때 JNI 라이브러리를 참조할 수 있게 하였다.

openScope 함수는 /dev/adc 디바이스 노드를 OPEN 하는 함수,
readScope 함수는 /dev/adc 디바이스 노드에서 ADC 값을 읽어들이는 함수,
requestADC 함수는 ADC 값을 읽어서 버퍼에 쌓아두라고 요청하는 함수,
closeScope 함수는 /dev/adc 디바이스 노드를 CLOSE 하는 함수이다.

이 중에서 requestADC 함수를 따라가 보도록 하겠다.
JAVA App(Scope.java)  Native C(jni/jniScope.c)  (jni/Scope.c)  Kernel device driver의 순서이다.



[소스 10] Scope.java 파일안의 requestADC 함수


[소스 10] Scope.java 파일에서 MSG_SAVE_ADC_REQ 메시지를 받으면, mAmpScaleIndex와 mTimeBaseIndex를 인자와 함께 requestADC 함수를 호출한다.
mAmpScaleIndex는 오실로스코프의 현재 AmpScale
(Vertical) 값이며, Default 값은 3 이고, mTimeBaseIndex는 오실로스코프의 현재 TimeScale(Horizental) 값이며, Default 값은 4 이다.
[소스 11] jni_requestADC 함수를 보면 jni/jniScope.c 파일에서 Java 와 Native C 를 이어주고 있다.
Jni/jniScope.c 파일 안의 Java_com_hardkernel_
OdroidOscilloscope_Scope_requestADC 함수는 jni/Scope.c 파일의 requestADC 함수를 호출하게 된다. 결국, [소스 10] Scope.java 파일의 requestADC 함수 인자(mAmpScaleIndex, mTimeBaseIndex)는 [소스 11]의 jni/Scope.c 의 requestADC 함수의 인자(volts, sec)로 할당(assign) 된다.



[소스 11] jni_requestADC 함수



[소스 11]에서 Jni/Scope.c 의 41번째 줄과 47번째 줄에서는 IOCTL 를 이용하여 커널을 제어한다.

ioctl 함수의 원형은 다음과 같다.
int ioctl(int d, int request,...);

return형은 int형이고, d는 /dev/adc 디바이스 노드를 OPEN해서 얻은 파일기술자이며, int request에는 명령을 넣는다. 여기서는 jni/Scope.h에 정의된 것처럼 ioctl의 두 번째 인자 request 를 unique하게 새로 생성하여 적용하였다. _IOW 매크로는 커널 소스 중 include/asm-generic/
ioctl.h에 정의되어 있으며, 3번째 인자는 void*형으로 선언하여 어떤 타입의 인자일지라도 할당할 수 있게 만들었다.
Jni/Scope.h의 ADC_INPUT_PIN, ADC_REQ_BUF, ADC__PERIOD request는 커널 소스인 [소스 6] ADC 디바이스 드라이버 ioctl 함수 중 adc.h 파일에서도 같은 request가 정의되어 있는 것을 확인할 수 있다(커널 소스 설명은 [소스 6] ADC 디바이스 드라이버 ioctl 함수의 설명 참고).
위와 같이, JAVA, Native C, 커널간 서로 소통이 이루어진다.

5) OdroidOscilloscope 의 전체적인 동작 설명
여기서는 ODROID-7에서 OdroidOscilloscope가 어떻게 동작 되는지를 확인하면서, OdroidOscilloscope 소스 코드 빌드와 함께 전체적인 동작 설명을 하겠다.

a. ODROID 웹사이트(http://dev.odroid.com/sigong/
nf_file_board/nfile_board.php)에서 Oscilloscope 에 대한 소스를 모두 다운로드 받을 수 있다. 여기서는 Android 2.3 Gingerbread, Kernel 2.6.35, Oscilloscope App을 가지고 설명할 것이다.

b. Android Build(Android 2.3 Gingerbread)
    device/hardkernel/odroid7/conf/ueventd.
product.rc 파일을 에디터로 열어서 최 하단 /dev/adc 에 관한 권한 설정을 추가해준다[소스 12].



[소스 12] /dev/adc 권한설정


*Android Build 및 Update(타겟보드에 이미지 Writing)에 관하여 좀 더 자세한 설명은 아래 링크 참조
  (http://dev.odroid.com/projects/android-gb/)




그림 5. kernel build


[그림 5] 와 같이 make odroid7_scope_defconfig를 하여 커널 Configuration를 설정한 후 make 명령을 내리면, 커널 이미지인 arch/arm/boot/zImage가 생성된다.
참고로 j6 옵션은 jobs 개수를 의미한다. 동시에 실행할 수 있는 쓰래드 개수를 설정함으로써 좀 더 빨리 make를 수행할 수 있다.

*Kernel Build 및 Update(타겟보드에 이미지 Writing)에 관하여 좀 더 자세한 설명은 아래 링크 참조
  (http://dev.odroid.com/projects/odroida#s-9)

d. App Build
    아래 [그림 6]은 다운로드한 Oscilloscope.tgz를 압축을 풀고, ndk-build로 빌드한 그림이다. libs 폴더가 생성된 것을 볼 수 있다.
    ndk-build로 라이브러리를 생성한 후, Eclipse SDK로 빌드하면, bin/Scope.apk 가 생성된다. 그 후, adb tool를 이용해서 아래와 같이 실행하면 ODROID-7에 OdroidOscilloscope가 Install되며 ODROID-7 타겟보드에 OdroidOscilloscope라는 App이 보이게 된다.



그림 6. Oscilloscope App Build



그림 7. Eclipse






그림 8. OdroidOscilloscope App 과 Spare Parts


e. ODROID-7 보드에서 Oscilloscope를 실행하기 전에 Spare Parts에서 Compatibility Mode를 끄고, 시스템을 재 시작한 뒤 Oscilloscope 를 다시 실행한다.

f. 처음 Oscilloscope 를 실행하면 requestADC 함수를 실행하여, 커널 영역 버퍼에 ADC값을 쌓는다. 그리고, 다시 App는 메시지 핸들러를 이용하여 메시지를 자기 자신에게 던진다.

    그 메시지를 받는 App은 ADC 값을 커널 영역에서 읽어 들이고, 이때 ADC 데이터를 Native C쪽에서 Timescale, AmpScale, Position 맞게 가공한 후 App쪽으로 데이터를 보낸다.

    가공된 ADC 값에 따라 화면에 그림을 그리고, 다시 자신에게 메시지를 보내어, 그림을 그리는 동안에 ADC 값을 커널 버퍼에 저장을 반복하여 App이 Active되어 있는 동안 무한 실행되는 구조이다.

g. 다음 페이지의 [소스 13] Message Handler 중
    136 줄 MSG_SAVE_ADC_REQ 메시지를 받고, requestADC 함수가 실행됨.
    127 줄 MSG_GET_ADC_REQ 메시지를 받고, readScope 함수가 실행됨.
    113 줄 mScopedraw.setData 함수에서는 readScope 함수에서 return 된 mVal를 인자로 받아서 화면 사이즈에 맞게 계산하여 버퍼에 넣게 된다.

    여기서 mVal는 public int [] mVal = new int
[BUFFER_CNT]로 BUFFER_CNT는 602으로 정의되어 있다. 602의 의미는 107 줄의 NOTI_ADC_
START mVal[0] 이고, 112 줄의 NOTI_ADC_END 는 mVal[601], 나머지는 화면 사이즈 600 픽셀이다.

    NOTI_ADC_START는 4001, NOTI_ADC_END는 4002 값으로 Static Final 정의 되어 있으며, 이 중간의 mVal[1] 부터 mVal[600]까지의 데이터가 실질적인 화면에 그려지는 ADC 데이터이다.



[소스 13] Message Handler

h. [소스 14] Drawing에서 88, 89줄은 ch1_data[0]에서 ch1_data[599]까지 데이터를 width(화면가로길이)만큼 위의 왼쪽을 기준으로 데이터를 저장하고, 122줄에서 저장된 ch1_data를 가지고 라인을 그리고 있다.
    96줄의 PlotPoints 함수는 쓰레드로서 App이 Active되어 있는 동안 무한 실행되는 구조이다.



[소스 14] Drawing


4. LED 제어
이번에는 다음 페이지 [그림 2]의 확장보드 회로도에 있는 LED1을 커널단에서 제어해 볼 것이며, 커널의 ADC 드라이버에서 데이터를 읽을 때, LED1을 깜빡여서 ADC 데이터를 읽어 들이고 있다는 것을 알 수 있게 할 것이다.



그림 2. 확장보드 회로도


[그림 2] 회로도를 보면, CON1의 8번 핀을 PULL DOWN으로 만들어 주면, Q1의 스위치 기능이 ON이 되어 VCC에서 GND로 전류가 흐르게 되고, 결국 LED1이 ON이 되는 것을 알 수 있다.
CON1는 ODROID-7 Base board의 CON4의 8번 핀을 통하여, B2B conector J4의 23번 핀에 연결되어 있다. 그리고, Base board의 B2B conector J4는 CPU board의 B2B conector J4에 연결되어, 최종적으로 우리가 제어할 CPU(S5PC110) R18 Ball(Pin Name:XJTDO)에 연결되어 있다.

그럼 이 핀을 어떻게 제어해야 하는지 알기 위해 CPU
(S5PC110) 데이터시트를 보자.



[데이터시트 6] Port Group ETC0 Control Register


[데이터시트 6]의 4.2.50 표의 ETC0[3]과 ETC0[4]을 보면 Pin Name이 둘 다 XjTDI로 되어 있지만, 잘못된 정보(오타)이다. ETC[4]가 XjTDO 이다. 소스를 통해 직접 제어해 봄으로써 확인할 수 있다.
[데이터시트 6]의 4.2.50.1 표의 ETC0PUD의 Bit n=4, 2n+1:2n, 즉 [9:8] Bit를 01로 만듦(Pull-down enabled)으로써, LED1를 ON할 수 있다.
[소스 15] 중 228 줄과 233 줄에서 각각 LED on/off 함수를 호출하고 있다.
210 줄의 scopeLEDon 함수와 215 줄의 scopeLEDoff 함수는 각각 다른 방법으로 제어를 하지만, 결국 Port Group ETC0 Control Register ETC0PUD의 8, 9번째 비트를 제어함(Pull-down enabled /disable)으로써, LED를 on/off 한다.




[소스 15] LED on/off


5. Odroid7 데모




그림 9. 데모




6. 마치며
이번 연재를 통해서 NDK를 설치하여 JNI를 사용하여 안드로이드 App에서 하드웨어를 제어하고 데이터를 수집하여 표시해 주는 안드로이드판 오실로스코프를 제작하였다.
독자는 안드로이드 NDK와 JNI의 사용법만 잘 알게 된다면, 안드로이드에서 직접 하드웨어 제어가 가능하고, 또한 다양한 공개 C 라이브러리를 안드로이드 App에 포팅하여 할 수 있어, 현재 스마트폰에 한정된 안드로이드 OS의 사용 영역이 산업용 기기나 다른 제어 디바이스들로 더욱더 늘려 나갈 수 있을 것이다.


참고문헌

Android Bluetooth Oscilloscope :
http://projectproto.blogspot.com/2010/09/android-bluetooth-oscilloscope.html
Android SDK : http://d.android.com/sdk/
Android NDK : http://d.android.com/sdk/ndk/
ODROID-7 : http://dev.odroid.com/projects/odroid-t , http://hardkernel.com
JNI : http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html 





  
회원가입 후 이용바랍니다.
개의 댓글
0 / 400
댓글 정렬
BEST댓글
BEST 댓글 답글과 추천수를 합산하여 자동으로 노출됩니다.
댓글삭제
삭제한 댓글은 다시 복구할 수 없습니다.
그래도 삭제하시겠습니까?
댓글수정
댓글 수정은 작성 후 1분내에만 가능합니다.
/ 400
내 댓글 모음
저작권자 © 테크월드뉴스 무단전재 및 재배포 금지