Skip to main content
 首页 » 编程设计

Android LED硬件访问服务(1)——AS App编写

2022年07月19日148lori

一、Android Studio应用编程

1.应用程序界面layout对应的界面是activity_main.xml,后台对应的java文件是MainActivity.java,修改activity_main.xml来修改UI显示效果,
点击UI上的控件的事件处理由MainActivity.java完成,一般放在onCreate()函数内。

2.当拖动修改界面的时候,对应的activity_main.xml会被自动修改。layout上面的控件的排布应该由layout的属性来决定。

3.视频上使用的AS的模拟器是Nexus 5,我Tiny4412上使用的Nexus One模拟器。

4.System.loadLibrary("hardcontrol"); 这个库应该放在开发板上的/vendor/lib或/system/lib下,也可以将这个库打包进应用程序里面。

5.修改这个xml文件后编译一下工程,可能会使AS根据xml文件生成一些java源代码。

6.比如下面编译报错Toast找不到,然后在Toast上弹出提示是不是XXX, 按Ctrl+enter即可确认,然后就可以编译成功了。
Toast.makeText(getApplicationContext(), "LED2 on",Toast.LENGTH_SHORT).show();

7.其实这个App可以在手机上运行,前提是手机开启了调试模式,而且Android版本要超过4.0,因为在建立此工程时指定了最小SDK版本号为4.0。

二、使用到的工程文件和更改

1.activity_main.xml注释

<?xml version="1.0" encoding="utf-8"?> 
//默认是android.support.constraint.ConstraintLayout,居中排列,改为LinearLayout后控件线性排列 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:app="http://schemas.android.com/apk/res-auto" 
    xmlns:tools="http://schemas.android.com/tools" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:orientation="vertical"  //添加它后由之前的控件水平排列变为垂直排列 
    tools:context=".MainActivity"> 
 
    <TextView //首字母大写就表示它是一个类对象。Ctrl+单击:到这个类定义的位置处,Ctrl+h:查看这个类的继承关系。 
        android:layout_width="wrap_content"  //表示显示信息的宽度,"wrap_content"表示宽度取决于其内容 
        android:layout_height="wrap_content" //其高度也是由其内容决定 
        android:text="I am coming!" 
        app:layout_constraintBottom_toBottomOf="parent" 
        app:layout_constraintLeft_toLeftOf="parent" 
        app:layout_constraintRight_toRightOf="parent" 
        app:layout_constraintTop_toTopOf="parent" /> 
 
    /* 下面是手动添加的部分 */ 
    <Button //双击“Button”,然后按下Shift+Fn+F1会在浏览器中打开Button的使用帮助文档,可根据帮助文档编写Button事件的处理函数。 
        android:id="@+id/BUTTON"  //给Button控件添加一个id,此后,MainActivity.java中就可以通过findViewById(R.id.BUTTON)来attach这个Button。 
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:text="ALL ON"/>  //这个Button上默认显示的内容 
 
    <CheckBox  //输入"<",然后tab键,系统帮我们补全了宽度和高度的代码 
        android:id="@+id/LED1" 
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:text="LED1" 
        android:onClick="onCheckboxClicked"/> 
 
    <CheckBox 
        android:id="@+id/LED2" 
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:text="LED2" 
        android:onClick="onCheckboxClicked"/> 
 
    <CheckBox 
        android:id="@+id/LED3" 
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:text="LED3" 
        android:onClick="onCheckboxClicked"/> 
 
    <CheckBox 
        android:id="@+id/LED4" 
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:text="LED4" 
        android:onClick="onCheckboxClicked"/> 
</LinearLayout>

2.MainActivity.java注释

/* 这个包的位置是AS自动生成的 */ 
package com.example.mm.app_0001_led_demo; 
 
import android.support.v7.app.AppCompatActivity; 
import android.os.Bundle; 
import android.widget.Button; 
import android.view.View; 
import android.widget.CheckBox; 
import android.widget.Toast; 
import com.example.mm.hardLibrary.*; 
 
public class MainActivity extends AppCompatActivity { 
 
    private boolean ledon = false; 
    /*双击Button,Shift+Fn+F1可以在浏览器中打开其帮助文档*/ 
    private Button button = null; 
 
    private CheckBox checkBoxLed1 = null; 
    private CheckBox checkBoxLed2 = null; 
    private CheckBox checkBoxLed3 = null; 
    private CheckBox checkBoxLed4 = null; 
 
    class MyButtonListener implements View.OnClickListener { //OnClickListener is a inner interface of View 
 
        /* Ctrl + i 是复写快捷键,按下后复写哪个函数直接点击提示即可 */ 
        @Override 
        public void onClick(View v) { 
            ledon = !ledon; 
            if (ledon) { 
                button.setText("ALL OFF"); 
                checkBoxLed1.setChecked(true); 
                checkBoxLed2.setChecked(true); 
                checkBoxLed3.setChecked(true); 
                checkBoxLed4.setChecked(true); 
                for (int i = 0; i < 4; i++) { 
                    HardControl.ledCtrl(i, 1); 
                } 
            } else { 
                button.setText("ALL ON"); 
                checkBoxLed1.setChecked(false); 
                checkBoxLed2.setChecked(false); 
                checkBoxLed3.setChecked(false); 
                checkBoxLed4.setChecked(false); 
                for (int i = 0; i < 4; i++) { 
                    HardControl.ledCtrl(i, 0); 
                } 
            } 
        } 
    } 
 
    public void onCheckboxClicked(View view) { 
        // Is the view now checked? 
        boolean checked = ((CheckBox) view).isChecked(); 
 
        // Check which checkbox was clicked 
        switch(view.getId()) { 
            case R.id.LED1: 
                if (checked) { 
                    HardControl.ledCtrl(1, 1); 
                    Toast.makeText(getApplicationContext(), "LED1 on", Toast.LENGTH_SHORT).show(); 
                } else { 
                    HardControl.ledCtrl(1, 0); 
                    Toast.makeText(getApplicationContext(), "LED1 off", Toast.LENGTH_SHORT).show(); 
                } 
                break; 
            case R.id.LED2: 
                if (checked) { 
                    HardControl.ledCtrl(2, 1); 
                    Toast.makeText(getApplicationContext(), "LED2 on", Toast.LENGTH_SHORT).show(); 
                } else { 
                    HardControl.ledCtrl(2, 0); 
                    Toast.makeText(getApplicationContext(), "LED2 off", Toast.LENGTH_SHORT).show(); 
                } 
                break; 
            case R.id.LED3: 
                if (checked) { 
                    HardControl.ledCtrl(3, 1); 
                    Toast.makeText(getApplicationContext(), "LED3 on", Toast.LENGTH_SHORT).show(); 
                } else { 
                    HardControl.ledCtrl(3, 0); 
                    Toast.makeText(getApplicationContext(), "LED3 off",Toast.LENGTH_SHORT).show(); 
                } 
                break; 
            case R.id.LED4: 
                if (checked) { 
                    HardControl.ledCtrl(4, 1); 
                    Toast.makeText(getApplicationContext(), "LED4 on", Toast.LENGTH_SHORT).show(); 
                } else { 
                    HardControl.ledCtrl(4, 0); 
                    Toast.makeText(getApplicationContext(), "LED4 off", Toast.LENGTH_SHORT).show(); 
                } 
                break; 
        } 
    } 
 
    /*onCreate是方法的入口,我们所有的操作都在这里面做*/ 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 
 
        /*打开驱动设备节点*/ 
        HardControl.ledOpen(); 
 
        /*  
         * 光标定位到()中后,Ctrl+Shift+Space自动填充Button实现强制类型转换。 
         * 这里使用了findViewById(),在main_activity.xml中就需要给Button控件指定一个id. 
         * 双击BUTTON,按Ctrl+b会跳转到main_activity.xml文件中定义的地方 
         * 双击BUTTON,按Alt+Fn+F7可以打开其引用的地方。我这里没有跳出来R.java,但是老师的跳出来了。 
         * 光标定位在()中,Ctrl+Shift+Space键自动补上括号中的Button 
         */ 
        button = (Button) findViewById(R.id.BUTTON); 
        /* 找到这个Button并设置其监听器,此后按下Button后会触发上面的onClick()被调用 */ 
        button.setOnClickListener(new MyButtonListener()); 
 
        /* 光标定位在()中间,Ctrl+shift+Space会自动填充强制类型转换*/ 
        checkBoxLed1 = (CheckBox) findViewById(R.id.LED1); 
        checkBoxLed2 = (CheckBox) findViewById(R.id.LED2); 
        checkBoxLed3 = (CheckBox) findViewById(R.id.LED3); 
        checkBoxLed4 = (CheckBox) findViewById(R.id.LED4); 
 
        /* 
        // OnClickListener是View类的内部类,而且是一个接口,这里定义了一个匿名类, 
        // 看起来很别扭,改为上面的操作了。 
        button.setOnClickListener(new View.OnClickListener() { 
            public void onClick(View v) { 
                // Code here executes on main thread after user presses button 
                ledon = !ledon; 
                if (ledon) { 
                    button.setText("ALL OFF"); 
                } else { 
                    button.setText("ALL ON"); 
                } 
            } 
        }); 
        */ 
    } 
}

3.HardControl.java

/* 参考MainActivity.java, HardControl.java存放在目录com/example/mm/hardLibrary下的,所以包名是这个 */ 
package com.example.mm.hardLibrary; 
 
public class HardControl { 
    /* 指定与驱动通过的JNI函数 */ 
    public static native int ledCtrl(int which, int status); 
    public static native int ledOpen(); 
    public static native void ledClose(); 
 
    static { 
        try { 
            /*选中这一行,Ctrl+Alt+t自动补全catch异常的代码*/ 
            System.loadLibrary("hardcontrol"); 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } 
    } 
}

三、JNI库实现

1.hardcontrol.c实现

#include <stdio.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <unistd.h> 
#include <sys/ioctl.h> 
#include <jni.h> 
#include <android/log.h> /*liblog*/ 
 
jint g_fd; 
 
/*可以先使用java -jni <class文件>来查看这些native函数的函数签名*/ 
jint led_open(JNIEnv *env, jclass cls) { 
    g_fd = open("/dev/leds", O_RDWR); 
    if (g_fd < 0) { 
        /*Android liblog中的打印函数,它可以在AS和串口上打印出来,不能使用printf(),它无法打印出来*/ 
        __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "led_open failed!"); 
        return -1; 
    } 
    __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "led_open called"); 
    return 0; 
} 
 
 
jint led_ctrl(JNIEnv * env, jclass cls, jint which, jint status) { 
    jint ret; 
    /*这里status和which是反的,为了使用tiny4412_led.c这个驱动*/ 
    ret = ioctl(g_fd, status, which); 
    if (ret != 0) { 
        __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "led_ctrl failed!: which=%d, status=%d", which, status); 
        return -1; 
    } 
    __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "led_ctrl called: which=%d, status=%d", which, status); 
    return 0; 
} 
 
void led_close(JNIEnv *env, jclass cls) { 
    close(g_fd); 
    __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "led_close called!"); 
} 
 
 
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 
 
static const JNINativeMethod methods[] = { 
    {"ledOpen", "()I", (void *)led_open}, 
    {"ledCtrl", "(II)I", (void *)led_ctrl}, 
    {"ledClose", "()V", (void *)led_close}, 
}; 
 
 
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { 
    JNIEnv *env; 
    jclass cls; 
 
    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4)) { 
        return JNI_ERR; /* JNI version not supported */ 
    } 
 
    /*注意这里是包名(定义这些native函数的文件路径名)*/ 
    cls = (*env)->FindClass(env, "com/example/mm/hardLibrary/HardControl"); 
    if (cls == NULL) { 
        return JNI_ERR; 
    } 
 
    if ((*env)->RegisterNatives(env, cls, methods, ARRAY_SIZE(methods)) < 0) 
        return JNI_ERR; 
 
    return JNI_VERSION_1_4; 
}

2.将编译出来的libhardcontrol.so放到apk包中的方法:
(1)编译.so文件的时候需要额外加上-nostdlib选项。
加这个选项的目录是要.so文件不要使用标准的libc.so.6库,而是使用我们指定的C库libc.so。因为tiny4412开发板上没有libc.so.6这个库文件,只有libc.so这个文件.
$ arm-linux-gcc -shared -fPIC hardcontrol.c -o libhardcontrol.so -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ -nostdlib /media/ubuntu/works/tiny4412/android-5.0.2/prebuilts/ndk/9/platforms/android-18/arch-arm/usr/lib/libc.so

(2)在app/libs目录下建立armeabi子目录,放入.so文件。

(3)修改Module:app的build.gradle,在顶层defaultConfig下加:

sourceSets { 
    main { 
        jniLibs.srcDirs = ['libs']  表示so文件是放在这个libs目录下面 
    } 
}

注意修改了gradle文件之后必须点击工具栏sync一下,否则libs文件夹根本不会编译静apk文件中,可以使用2345好压打开apk文件进行查看。

3.不能在hardcontrol.so文件中直接是使用printf(),printf()的打印我们是看不见的。需要使用Android提供的liblog库进行打印,它可以在串口终端也可以在AS的logcat窗口打印出来。

#include <android/log.h> /*liblog*/
__android_log_print(ANDROID_LOG_DEBUG, "JNIDemo", "xxxxx");

如下可以编译成功:
$ arm-linux-gcc -shared -fPIC hardcontrol.c -o libhardcontrol.so -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ -I /media/ubuntu/works/tiny4412/android-5.0.2/prebuilts/ndk/9/platforms/android-19/arch-arm/usr/include -nostdlib /media/ubuntu/works/tiny4412/android-5.0.2/prebuilts/ndk/9/platforms/android-18/arch-arm/usr/lib/libc.so

但是执行时报错:找不到__android_log_print,所以还需要添加一个库:
在prebuilts目录下查找liblog.so,然后拷贝和头文件对应的loblog.so,链接的时候要链接到它,改为:
$ arm-linux-gcc -shared -fPIC hardcontrol.c -o libhardcontrol.so -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ -I /media/ubuntu/works/tiny4412/android-5.0.2/prebuilts/ndk/9/platforms/android-19/arch-arm/usr/include -nostdlib /media/ubuntu/works/tiny4412/android-5.0.2/prebuilts/ndk/9/platforms/android-18/arch-arm/usr/lib/libc.so /media/ubuntu/works/tiny4412/android-5.0.2/prebuilts/ndk/9/platforms/android-19/arch-arm/usr/lib/liblog.so

====>使用arm-linux-gcc可以直接链接成功Android工程编译出来的库!
====>编译时加了-nostdlib,运行时再找不到符号,不一定就是开发板上没有这个库了,可能编译时根本就没有链接这个库。

4.驱动中的class_create()和device_create()的作用是在/sys目录下创建一些文件,然后udev机制根据这些文件创建设备节点。

四、驱动

驱动使用的是tiny4412_leds.c

#include <linux/kernel.h> 
#include <linux/module.h> 
#include <linux/miscdevice.h> 
#include <linux/fs.h> 
#include <linux/types.h> 
#include <linux/moduleparam.h> 
#include <linux/slab.h> 
#include <linux/ioctl.h> 
#include <linux/cdev.h> 
#include <linux/delay.h> 
 
#include <linux/gpio.h> 
#include <mach/gpio.h> 
#include <plat/gpio-cfg.h> 
 
 
#define DEVICE_NAME "leds" 
 
static int led_gpios[] = { 
    EXYNOS4212_GPM4(0), 
    EXYNOS4212_GPM4(1), 
    EXYNOS4212_GPM4(2), 
    EXYNOS4212_GPM4(3), 
}; 
 
#define LED_NUM        ARRAY_SIZE(led_gpios) 
 
 
/*要使用这个驱动就需要将cmd和arg的值在JNI层互换*/ 
static long tiny4412_leds_ioctl(struct file *filp, unsigned int cmd, 
        unsigned long arg) 
{ 
    switch(cmd) { 
        case 0: 
        case 1: 
            if (arg > LED_NUM) { 
                return -EINVAL; 
            } 
 
            gpio_set_value(led_gpios[arg], !cmd); 
            //printk(DEVICE_NAME": %d %d\n", arg, cmd); 
            break; 
 
        default: 
            return -EINVAL; 
    } 
 
    return 0; 
} 
 
static struct file_operations tiny4412_led_dev_fops = { 
    .owner            = THIS_MODULE, 
    .unlocked_ioctl    = tiny4412_leds_ioctl, /*没有提供open和close指针也是可以使用的*/ 
}; 
 
static struct miscdevice tiny4412_led_dev = { 
    .minor            = MISC_DYNAMIC_MINOR, 
    .name            = DEVICE_NAME, 
    .fops            = &tiny4412_led_dev_fops, 
}; 
 
static int __init tiny4412_led_dev_init(void) { 
    int ret; 
    int i; 
 
    for (i = 0; i < LED_NUM; i++) { 
        ret = gpio_request(led_gpios[i], "LED"); 
        if (ret) { 
            printk("%s: request GPIO %d for LED failed, ret = %d\n", DEVICE_NAME, 
                    led_gpios[i], ret); 
            return ret; 
        } 
 
        s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT); 
        gpio_set_value(led_gpios[i], 1); 
    } 
 
    ret = misc_register(&tiny4412_led_dev); 
 
    printk(DEVICE_NAME"\tinitialized\n"); /*启动时打印了这个,说明它注册的驱动*/ 
 
    return ret; 
} 
 
static void __exit tiny4412_led_dev_exit(void) { 
    int i; 
 
    for (i = 0; i < LED_NUM; i++) { 
        gpio_free(led_gpios[i]); 
    } 
 
    misc_deregister(&tiny4412_led_dev); /*这个应该放在前面*/ 
} 
 
module_init(tiny4412_led_dev_init); 
module_exit(tiny4412_led_dev_exit); 
 
MODULE_LICENSE("GPL"); 
MODULE_AUTHOR("FriendlyARM Inc.");

本文参考链接:https://www.cnblogs.com/hellokitty2/p/10780970.html