标签:
首先说一下我的开发环境,硬件环境开发板使用的是全志的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(); } }
这样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); } }
说了。
下面再看一下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
特此感谢。
标签:
原文地址:http://blog.csdn.net/dingfengen/article/details/51611976