1. 程式人生 > >Android 下led 的控制(上)--Android部分

Android 下led 的控制(上)--Android部分

首先說一下我的開發環境,硬體環境開發板使用的是全志的CQA83T板子,Android開發是windows下的eclipse。關於Android下控制led,主要有兩大部分,一是Android程式,二是Linux驅動開發。Android部分的開發肯定要使用Android ndk,jni程式設計,通過jni來呼叫Linux下的C函式從而控制led裝置。關於ndk的安裝,和簡單使用我在另外的部落格裡面已經寫了,有興趣的可以自己看看。這篇部落格住要是講一下Android部分的開發,這裡預設led驅動正常。

先看一下我的工程目錄,如下圖:


紅色是我們工程的所有檔案,後面打對勾是我們需要編輯的檔案。

第一步:java部分檔案編輯,這部分主要三個檔案,佈局檔案activity_main.xml、邏輯控制檔案MainActivity.java、led控制封裝類A83TLED.java三個檔案。

(1)佈局檔案activity_main.xml

佈局檔案沒什麼可講的,就是一列按鈕,檔案內容如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">
    
    <Button 
        android:id="@+id/device_open_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="device_open"/>
    
    <Button 
        android:id="@+id/led1_on_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="led1_on"/>
    
    <Button 
        android:id="@+id/led1_off_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="led1_off"/>
    
    <Button 
        android:id="@+id/led2_on_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="led2_on"/>
    
    <Button 
        android:id="@+id/led2_off_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="led2_off"/>
    
    <Button 
        android:id="@+id/beep_on_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="beep_on"/>
    
    <Button 
        android:id="@+id/beep_off_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="beep_off"/>
    
     <Button 
        android:id="@+id/device_close_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="device_close"/>
    
</LinearLayout>
實際顯示的佈局圖如下: 雖然說沒什麼講的,還是說一下每個按鈕的作用吧。

device_open:裝置開啟按鈕,在Linux下一切皆檔案,想要操作led,首先要開啟led裝置。

led1_on:開啟1號led

led1_off:關閉1號led

led2_on:開啟2號led

led2_off:關閉2號led

beep_on:開啟蜂鳴器,這裡說一下,蜂鳴器的控制和led一樣,開關訊號,單引腳,即是隻需要控制引腳的輸出電平是0或1即可。所以驅動和led寫在一起。

beep_off:關閉蜂鳴器

device_close:關閉裝置。

(2)看一下A83TLED.java這個檔案。這個檔案主要是對本地方進行宣告,封裝了幾個控制函式,以及載入本地庫。

檔案內容如下:

package com.coban.a83tled;

import java.io.IOException;

import android.util.Log;

public class A83TLed {
	private static final String TAG = "A83TLed";
	private int ret;
	
	public A83TLed(String path) throws SecurityException, IOException{
		ret = open_led_device(path);
		if (ret < -1) {
			Log.e(TAG, "native open returns null");
			throw new IOException();
		}
	}
	
	//開啟裝置
	public void open_led(){
		ret = open_led_device("/dev/led");
		if (ret < -1) {
			Log.e(TAG, "native open returns null");
		}
	}
	
	//開啟led
	public void on_led(int num){
		ioctl_led(num,1);
	}
	
	//關閉led
	public void off_led(int num){
		ioctl_led(num,0);
	}
	
	//關閉裝置
	public void close_led(){
		close_led_device();
	}
	
	// JNI宣告本地函式
	private native static int open_led_device(String path);
	private native void ioctl_led(int i, int j);
	private native void close_led_device();
	
<span style="white-space:pre">	</span>//載入本地庫
	static{
		System.loadLibrary("a83tled");
	}
	
}

檔案很簡單,首先是類的構造,類的方法,JNI本地函式宣告,載入本地庫。在類宣告時就打開了裝置,當然也提供了開啟裝置的方法open_led(),既然提供了開啟裝置

那對應的也提供了關閉裝置函式close_led(),另外提供了led的開啟on_led()和關閉off_led()方法。這些方法都是呼叫了jni函式,而jni又呼叫Linux函式。這個檔案就是這樣了。

(3)下面來看一下控制邏輯檔案MainActivity.java檔案。這個檔案主要是提供控制邏輯,直接面向使用者。

檔案內容如下:

package com.coban.a83tled;

import java.io.IOException;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {

	private Button led1On = null;
	private Button led1Off = null;
	private Button led2On = null;
	private Button led2Off = null;
	private Button beepOn = null;
	private Button beepOff = null;
	
	private Button deviceOpen = null;
	private Button deviceClose = null;
	
	private A83TLed a83tLed = null;
	
	
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        try {
			a83tLed = new A83TLed("/dev/led");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        
        initLayout();
        btnListener();
        
    }
    
    //初始化介面
    private void initLayout(){
    	led1On = (Button)this.findViewById(R.id.led1_on_btn);
    	led1Off = (Button)this.findViewById(R.id.led1_off_btn);
    	led2On = (Button)this.findViewById(R.id.led2_on_btn);
    	led2Off = (Button)this.findViewById(R.id.led2_off_btn);
    	beepOn = (Button)this.findViewById(R.id.beep_on_btn);
    	beepOff = (Button)this.findViewById(R.id.beep_off_btn);
    	
    	deviceOpen = (Button)this.findViewById(R.id.device_open_btn);
    	deviceClose = (Button)this.findViewById(R.id.device_close_btn);
    }

    //按鈕監聽
    private void btnListener(){
    	
    	led1On.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View arg0) {
				// TODO Auto-generated method stub
				a83tLed.on_led(1);
			}
		});
    	
    	led1Off.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View arg0) {
				// TODO Auto-generated method stub
				a83tLed.off_led(1);
			}
		});
    	
    	led2On.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View arg0) {
				// TODO Auto-generated method stub
				a83tLed.on_led(2);
			}
		});
    	
    	led2Off.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View arg0) {
				// TODO Auto-generated method stub
				a83tLed.off_led(2);
			}
		});
    	
    	beepOn.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View arg0) {
				// TODO Auto-generated method stub
				a83tLed.on_led(3);
			}
		});
    	
    	beepOff.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View arg0) {
				// TODO Auto-generated method stub
				a83tLed.off_led(3);
			}
		});
    	
    	deviceOpen.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View arg0) {
				// TODO Auto-generated method stub
				a83tLed.open_led();			}
		});
    	
    	deviceClose.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View arg0) {
				// TODO Auto-generated method stub
				a83tLed.close_led();
			}
		});
    	
    }
    
    @Override
    protected void onDestroy() {
    	// TODO Auto-generated method stub
    	super.onDestroy();
    	a83tLed.close_led();
    }
    
}

這裡主要就是對按鈕的初始化和監聽,然後呼叫類A83TLed裡的方法實現控制。

這樣java檔案就編寫完了。

第二步:生成jni標頭檔案

上面一步,把java檔案的編輯完成了,然後我們要生成標頭檔案了。

(1)在工程目錄下新建jni資料夾。

(2)使用dos視窗進入工程目錄下面,使用javah命令生成自動生成標頭檔案。具體命令如下

javah -classpath src -d jni com.coban.a83tled.A83TLed

然後你會在jni資料夾下找到生成的標頭檔案com_coban_a83tled_A83TLed.h

有關操作我在另一篇部落格裡面都有詳細說明,http://blog.csdn.net/dingfengen/article/details/51604877

那我們來看一下標頭檔案:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_coban_a83tled_A83TLed */

#ifndef _Included_com_coban_a83tled_A83TLed
#define _Included_com_coban_a83tled_A83TLed
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_coban_a83tled_A83TLed
 * Method:    open_led_device
 * Signature: (Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_com_coban_a83tled_A83TLed_open_1led_1device
  (JNIEnv *, jclass, jstring);

/*
 * Class:     com_coban_a83tled_A83TLed
 * Method:    ioctl_led
 * Signature: (II)V
 */
JNIEXPORT void JNICALL Java_com_coban_a83tled_A83TLed_ioctl_1led
  (JNIEnv *, jobject, jint, jint);

/*
 * Class:     com_coban_a83tled_A83TLed
 * Method:    close_led_device
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_coban_a83tled_A83TLed_close_1led_1device
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
主要包含的就是本地函式宣告。

第三步:本地C或C++檔案的編輯。這裡主要是倆檔案,一個是a83tled.c,另一個是Android.mk。最後生成的so庫的全名是liba83tled.so,這個名字和c檔名保持一致。

以及前面java里加載的庫名,也是這個檔名。這一點我曾經出過錯誤,在此也提醒一下大家。首先在jni資料夾下建立a83tled.c和Android.mk檔案。

先看a83tled.c這個檔案的內容:

#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <jni.h>

#include "android/log.h"

static const char *TAG = "a83tled";
static int fd = -1;

#define IOCTL_SET_ON 1
#define IOCTL_SETOFF 0

#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)

JNIEXPORT jint JNICALL Java_com_coban_a83tled_A83TLed_open_1led_1device (JNIEnv *env, jclass thiz, jstring path)
{
	jboolean iscopy;
	const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);
	LOGD("Opening %s",path_utf);
	fd = open("/dev/led",O_WRONLY,0777);
	LOGD("open fd = %d",fd);

	(*env)->ReleaseStringUTFChars(env, path, path_utf);

	if(fd < -1)
	{
		LOGE("Cannot open port");
		/* TODO: throw an exception */
		return NULL;
	}

	return fd;

}


JNIEXPORT void JNICALL Java_com_coban_a83tled_A83TLed_ioctl_1led (JNIEnv *env, jobject thiz, jint i, jint j)
{
	if(fd > -1)
	{
		if(1 == j)
		{
			ioctl(fd,IOCTL_SET_ON,i);
		}
		else
		{
			ioctl(fd,IOCTL_SETOFF,i);
		}
	}
}


JNIEXPORT void JNICALL Java_com_coban_a83tled_A83TLed_close_1led_1device(JNIEnv *env, jobject thiz)
{
	if(fd > -1)
	{
		close(fd);
	}
}


這個檔案主要是實現了標頭檔案裡宣告的函式。對熟悉Linux程式設計的朋友應該很簡單了,就是用ioctl函式控制led的亮和滅,用檔案操作函式,開啟和關閉裝置。就不再具體細

說了。

下面再看一下Android.mk檔案,其內容如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

TARGET_PLATFORM := android-3
LOCAL_MODULE    := a83tled
LOCAL_SRC_FILES := a83tled.c
LOCAL_LDLIBS    := -llog

include $(BUILD_SHARED_LIBRARY)

這個Androd.mk檔案不長,下面我們來簡單解釋下:

LOCAL_PATH := $(call my-dir)

一個Android.mk 檔案首先必須定義好LOCAL_PATH變數。它用於在開發樹中查詢原始檔。在這個例子中,巨集函式’my-dir’, 由編譯系統提供,用於返回當前路徑(即

包含Android.mk file檔案的目錄)。

include $( CLEAR_VARS)

CLEAR_VARS由編譯系統提供,指定讓GNU MAKEFILE清除許多LOCAL_XXX變數(例如 LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES, 

等等...), 除LOCAL_PATH 。這是必要的,因為所有的編譯控制檔案都在同一個GNU MAKE執行環境中,所有的變數都是全域性的。

TARGET_PLATFORM := android-3

TARGET_PLATFORM是Android.mk 解析的時候,目標 Android 平臺的名字, 其中,android-3 -> Official Android 1.5 system images 

       android-4 -> Official Android 1.6 system images , android-5 -> Official Android 2.0 system images

LOCAL_MODULE := a83tled

  編譯的目標物件,LOCAL_MODULE變數必須定義,以標識你在Android.mk檔案中描述的每個模組。名稱必須是唯一的,而且不包含任何空格。注意:編譯

系統會自動產生合適的字首和字尾,換句話說,一個被命名為'a83tled'的共享庫模組,將會生成'liba83tled.so'檔案。

  重要注意事項:如果你把庫命名為‘liba83tled’,編譯系統將不會新增任何的lib字首,也會生成 'liba83tled.so',這是為了支援來源於Android平臺的原始碼的Android.mk檔案,如果你確實需要這麼做的話。

LOCAL_SRC_FILES := a83tled.c

  LOCAL_SRC_FILES變數必須包含將要編譯打包進模組中的C或C++原始碼檔案。注意,你不用在這裡列出標頭檔案和包含檔案,因為編譯系統將會自動為你找出依賴型的檔案;

僅僅列出直接傳遞給編譯器的原始碼檔案就好。

  注意,預設的C++原始碼檔案的副檔名是’.cpp’. 指定一個不同的副檔名也是可能的,只要定義LOCAL_DEFAULT_CPP_EXTENSION變數,不要忘記開始的小圓點(也就是’.cxx’,而不是’cxx’)

<span style="background-color: rgb(255, 255, 255);">     LOCAL_LDLIBS    := -llog</span>
LOCAL_LDLIBS:  編譯模組時要使用的附加的連結器選項。這對於使用‘-l’字首傳遞指定庫的名字是有用的。

include $(BUILD_SHARED_LIBRARY)

  BUILD_SHARED_LIBRARY表示編譯生成共享庫,是編譯系統提供的變數,指向一個GNU Makefile指令碼,負責收集自從上次呼叫'include $(CLEAR_VARS)'以來,定義在LOCAL_XXX變數中的所有資訊,

並且決定編譯什麼,如何正確地去做。還有 BUILD_STATIC_LIBRARY變量表示生成靜態庫:lib$(LOCAL_MODULE).a, BUILD_EXECUTABLE 表示生成可執行檔案。

第四步,就是編譯了。

首先:再工程目錄中,右鍵點選工程名 --> Android tools --> Add Native Support  來新增ndk支援,會出現如下介面:


然後是編譯,點選工具欄裡的錘子圖示,如下圖:


如果沒有錯誤,下載到開發板測試。如下圖,


我的這裡沒有錯誤。

簡單總結一下,這裡我比他們給的例程裡面多了一個開啟裝置函式,在java層。這個程式的執行過程是這樣的,介面按鈕(xml檔案) --> 邏輯控制層(MainActivity.java) --> java實際呼叫層(A83TLed.java) --> jni層(a83tled.c) --> linux應用層 --> linux底層驅動。

這裡參考瞭如下連線:

http://blog.csdn.net/ly131420/article/details/9619269

http://www.cnblogs.com/devinzhang/archive/2012/02/29/2373729.html

特此感謝。