Android 기기에서 UI로 직접 하드웨어를 제어할 수 있는 어플리케이션을 만드려면 JNI (Java Native Interface)의 도움을 받아야 합니다. 이제부터는 IDE 사용법과 makefile 작성보다는 개발을 위한 프로세스에 초점을 맞추도록 하겠습니다. 실제 코드로직 작성 이외의 작업은 Eclipse에서 제공하는 툴에 의존합니다.
토글버튼을 터치하면 보드에 내장된 LED를 켜고 끌 수 있는 어플리케이션과 디바이스드라이버를 만들도록 하겠습니다.

우선 화면을 구성하는 Activity를 정의하기 위해 XML 파일을 작성합니다.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="edu.skku.baseled.MainActivity" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:layout_weight="1.17"
android:gravity="center|top"
android:orientation="vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Base LED Control"
android:textSize="18dp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_weight="0.00"
android:gravity="center"
android:orientation="horizontal" >
<CheckBox
android:id="@+id/checkBox1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<CheckBox
android:id="@+id/checkBox2"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<CheckBox
android:id="@+id/checkBox3"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<CheckBox
android:id="@+id/checkBox4"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<CheckBox
android:id="@+id/checkBox5"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<CheckBox
android:id="@+id/checkBox6"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<CheckBox
android:id="@+id/checkBox7"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<CheckBox
android:id="@+id/checkBox8"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
메인 화면의 이벤트 처리를 담당하는 MainActivity.java 파일입니다.
package edu.skku.sm5baseled;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.CheckBox;
import android.widget.Toast;
import edu.skku.sm5baseled_jnidriver.BaseLed_JNIDriver;
public class MainActivity extends Activity {
BaseLed_JNIDriver mDriver = new BaseLed_JNIDriver();
//int LedData;
byte[] iLedData = {0,0,0,0,0,0,0,0};
CheckBox Led1, Led2, Led3, Led4, Led5, Led6, Led7, Led8;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Led1 = (CheckBox) findViewById(R.id.checkBox1);
Led2 = (CheckBox) findViewById(R.id.checkBox2);
Led3 = (CheckBox) findViewById(R.id.checkBox3);
Led4 = (CheckBox) findViewById(R.id.checkBox4);
Led5 = (CheckBox) findViewById(R.id.checkBox5);
Led6 = (CheckBox) findViewById(R.id.checkBox6);
Led7 = (CheckBox) findViewById(R.id.checkBox7);
Led8 = (CheckBox) findViewById(R.id.checkBox8);
Led1.setChecked(false);
Led1.setOnClickListener(new CheckBox.OnClickListener() {
public void onClick(View v) {
if(Led1.isChecked())
iLedData[0] = 1;
else
iLedData[0] = 0;
mDriver.write(iLedData);
}
});
Led2.setChecked(false);
Led2.setOnClickListener(new CheckBox.OnClickListener() {
public void onClick(View v) {
if(Led2.isChecked())
iLedData[1] = 1;
else
iLedData[1] = 0;
mDriver.write(iLedData);
}
});
Led3.setChecked(false);
Led3.setOnClickListener(new CheckBox.OnClickListener() {
public void onClick(View v) {
if(Led3.isChecked())
iLedData[2] = 1;
else
iLedData[2] = 0;
mDriver.write(iLedData);
}
});
Led4.setChecked(false);
Led4.setOnClickListener(new CheckBox.OnClickListener() {
public void onClick(View v) {
if(Led4.isChecked())
iLedData[3] = 1;
else
iLedData[3] = 0;
mDriver.write(iLedData);
}
});
Led5.setChecked(false);
Led5.setOnClickListener(new CheckBox.OnClickListener() {
public void onClick(View v) {
if(Led5.isChecked())
iLedData[4] = 1;
else
iLedData[4] = 0;
mDriver.write(iLedData);
}
});
Led6.setChecked(false);
Led6.setOnClickListener(new CheckBox.OnClickListener() {
public void onClick(View v) {
if(Led6.isChecked())
iLedData[5] = 1;
else
iLedData[5] = 0;
mDriver.write(iLedData);
}
});
Led7.setChecked(false);
Led7.setOnClickListener(new CheckBox.OnClickListener() {
public void onClick(View v) {
if(Led7.isChecked())
iLedData[6] = 1;
else
iLedData[6] = 0;
mDriver.write(iLedData);
}
});
Led8.setChecked(false);
Led8.setOnClickListener(new CheckBox.OnClickListener() {
public void onClick(View v) {
if(Led8.isChecked())
iLedData[7] = 1;
else
iLedData[7] = 0;
mDriver.write(iLedData);
}
});
}
@Override
protected void onPause() {
mDriver.close();
super.onPause();
}
@Override
protected void onResume() {
if(mDriver.open("/dev/baseled") < 0)
Toast.makeText(getApplicationContext(), "Driver Open failed", Toast.LENGTH_LONG).show();
super.onResume();
}
}
mDriver는 BaseLedJNIDriver의 인스턴스입니다. 개별 클래스에 따로 정의해두어 기능단위를 명확히 보여줄 수 있도록 합니다.
BaseLedJNIDriver.java
package edu.skku.sm5baseled_jnidriver;
public class BaseLed_JNIDriver {
private boolean mConnectFlag;
static {
System.loadLibrary("BaseLed_JNIDriver");
}
private native static int openDriver(String path);
private native static void closeDriver();
private native static void writeDriver(byte[] data, int length);
public BaseLed_JNIDriver(){
mConnectFlag = false;
}
public int open(String driver){
if(mConnectFlag) return -1;
if(openDriver(driver)>0){
mConnectFlag = true;
return 1;
} else {
return -1;
}
}
public void close(){
if(!mConnectFlag) return;
mConnectFlag = false;
closeDriver();
}
protected void finalize() throws Throwable{
close();
super.finalize();
}
public void write(byte[] data){
if(!mConnectFlag) return;
writeDriver(data, data.length);
}
}
이후 JNI Interface를 생성합니다. 위에서 System.loadLibrary("BaseLed_JNIDriver")를 호출한 것을 볼 수 있는데, 아래에 보이는 C 로직이 이에 해당합니다.
BaseLed_JNIDriver.c
#include <jni.h>
#include <fcntl.h>
int fd = 0;
JNIEXPORT jint JNICALL Java_edu_skku_sm5baseled_1jnidriver_BaseLed_1JNIDriver_openDriver
(JNIEnv * env, jclass class, jstring path){
jboolean iscopy;
const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);
fd = open(path_utf, O_WRONLY);
(*env)->ReleaseStringUTFChars(env, path, path_utf);
if (fd < 0)
return -1;
else
return 1;
}
JNIEXPORT void JNICALL Java_edu_skku_sm5baseled_1jnidriver_BaseLed_1JNIDriver_closeDriver
(JNIEnv * env, jclass class){
if (fd > 0)
close(fd);
}
JNIEXPORT void JNICALL Java_edu_skku_sm5baseled_1jnidriver_BaseLed_1JNIDriver_writeDriver
(JNIEnv * env, jclass class, jbyteArray arr, jint count){
jbyte * chars = (*env)->GetByteArrayElements(env, arr, 0);
if (fd>0)
write(fd, (unsigned char *)chars, count);
(*env)->ReleaseByteArrayElements(env, arr, chars, 0);
}
이후 Eclipse에서 File -> New -> Convert to a C/C++ Project 를 선택합니다.
"The wizard adds C/C++ Nature to the selected projects to enable C/C++ Tools Support for them. It also converts old-style C/C++ projects to the new style."
이후 C/C++ Perspective에서 빌드하면 네이티브 라이브러리와 안드로이드 어플리케이션을 함께 컴파일하게 되며, libBaseLed_JNIDriver.so 라이브러리 파일이 생성됩니다.
(so == shared object)
driver 설치 후 권한 확인 (Terminal 환경에서 명령)
lsmod
insmod /system/lib/modules/baseled_driver.ko
chmod 777 /dev/baseled
이후 IDE로 안드로이드 앱을 실행합니다 (Run as ... > Android Application)
'EE > Embedded Systems' 카테고리의 다른 글
[Android][2] Device Driver 컴파일 (0) | 2021.09.17 |
---|---|
[Android][1] 커널 빌드 (0) | 2021.09.17 |
[nRF52BLE][4]C Development with SoftDevice API (5) | 2021.01.04 |
[nRF52BLE][3]Components for Data Transfer (0) | 2021.01.04 |
[nRF52BLE][2]Connection and Softdevice (0) | 2021.01.04 |