EE/Embedded Systems

[Android][3] On-Board LED 제어하기

esmJK 2021. 9. 24. 22:53

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)