硬件平台:Tiny4412标准版+android5.0
从电路原理图可以知道LED灯连接到处理器的GPM4的0-3端口,且LED被上拉到3.3V的源,只有GPIO口输出低电平时就能点亮LED灯;
只要把GPM4CON对应的区域设置为0x1且GPM4DAT对应bit设置为0,则IO口就会输出低电平;
#include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/io.h> #include <linux/fs.h> #include <linux/ioctl.h> #include <linux/miscdevice.h> #define DEVICE_NAME "tiny4412-leds" #define GPM4CON (0x11000000+0x02E0) #define GPM4DAT (0x11000000+0x02E4) #define GPM4_0 0 #define GPM4_1 1 #define GPM4_2 2 #define GPM4_3 3 #define GPM4_ON 0 #define GPM4_OFF 1 //幻数 #define LEDS_MAGIC 'a' #define LEDS_PGM4_0_ON _IO(LEDS_MAGIC, 1) #define LEDS_PGM4_0_OFF _IO(LEDS_MAGIC, 2) #define LEDS_PGM4_1_ON _IO(LEDS_MAGIC, 3) #define LEDS_PGM4_1_OFF _IO(LEDS_MAGIC, 4) #define LEDS_PGM4_2_ON _IO(LEDS_MAGIC, 5) #define LEDS_PGM4_2_OFF _IO(LEDS_MAGIC, 6) #define LEDS_PGM4_3_ON _IO(LEDS_MAGIC, 7) #define LEDS_PGM4_3_OFF _IO(LEDS_MAGIC, 8) unsigned int *gpm4_con = NULL; unsigned int *gpm4_dat = NULL; /* 设置IO口输出电平 */ static void set_gmp4_out(unsigned char cpm4_n, unsigned char status) { unsigned int temp; temp = readl(gpm4_dat); if (status == GPM4_ON) temp &= ~(0x1<<cpm4_n); else temp |= 0x1<<cpm4_n; writel(temp, gpm4_dat); } static long tiny4412_leds_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { case LEDS_PGM4_0_ON: set_gmp4_out(GPM4_0, GPM4_ON); break; case LEDS_PGM4_0_OFF: set_gmp4_out(GPM4_0, GPM4_OFF); break; case LEDS_PGM4_1_ON: set_gmp4_out(GPM4_1, GPM4_ON); break; case LEDS_PGM4_1_OFF: set_gmp4_out(GPM4_1, GPM4_OFF); break; case LEDS_PGM4_2_ON: set_gmp4_out(GPM4_2, GPM4_ON); break; case LEDS_PGM4_2_OFF: set_gmp4_out(GPM4_2, GPM4_OFF); break; case LEDS_PGM4_3_ON: set_gmp4_out(GPM4_3, GPM4_ON); break; case LEDS_PGM4_3_OFF: set_gmp4_out(GPM4_3, GPM4_OFF); break; defautl : break; } return 0; } static const struct file_operations tiny4412_leds_fops = { .owner = THIS_MODULE, .unlocked_ioctl = tiny4412_leds_ioctl, }; static struct miscdevice tiny4412_leds_miscdev = { .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &tiny4412_leds_fops, }; static int tiny4412_leds_init(void) { unsigned int data; unsigned int ret; //IO初始化,将物理地址映射为虚拟地址 gpm4_con = ioremap(GPM4CON, 4); gpm4_dat = ioremap(GPM4DAT, 4); if (!gpm4_con || !gpm4_con) { printk("ioremap faild!\n"); goto error1; } /* 将GPM4[0]-GPM4[3]设置为输出 */ data = readl(gpm4_con); data &= ~((0xf<<12)|(0xf<<8)|(0xf<<4)|(0xf<<0)); data |= (0x1<<12)|(0x1<<8)|(0x1<<4)|(0x1<<0); writel(data, gpm4_con); ret = misc_register(&tiny4412_leds_miscdev); //注册混杂设备驱动 if (ret) { printk("misc_register faild!\n"); goto error2; } printk("tiny4412_leds_init!\n"); return 0; error2: iounmap(gpm4_con); iounmap(gpm4_dat); error1: return -ENOMEM; } static void tiny4412_leds_exit(void) { unsigned int data; misc_deregister(&tiny4412_leds_miscdev); data = readl(gpm4_dat); data |= (0x1<<3)|(0x1<<2)|(0x1<<1)|(0x1<<0); writel(data, gpm4_dat); iounmap(gpm4_con); iounmap(gpm4_dat); printk("tiny4412_leds_exit!\n"); } MODULE_LICENSE("GPL"); MODULE_AUTHOR("Chen Jinpeng"); module_init(tiny4412_leds_init); module_exit(tiny4412_leds_exit);
led_oper.c
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <linux/ioctl.h> #define DEVICE_NAME "/dev/tiny4412-leds" //幻数 #define LEDS_MAGIC 'a' #define LEDS_PGM4_0_ON _IO(LEDS_MAGIC, 1) #define LEDS_PGM4_0_OFF _IO(LEDS_MAGIC, 2) #define LEDS_PGM4_1_ON _IO(LEDS_MAGIC, 3) #define LEDS_PGM4_1_OFF _IO(LEDS_MAGIC, 4) #define LEDS_PGM4_2_ON _IO(LEDS_MAGIC, 5) #define LEDS_PGM4_2_OFF _IO(LEDS_MAGIC, 6) #define LEDS_PGM4_3_ON _IO(LEDS_MAGIC, 7) #define LEDS_PGM4_3_OFF _IO(LEDS_MAGIC, 8) int main (int argc, char *argv[]) { int leds_fd = 0; int cmd; if (argc < 2) { printf("please entry the correct operation parameter! \n"); return 0; } leds_fd = open(DEVICE_NAME, O_RDWR); if (leds_fd == -1) { printf("open device faild! \n"); return 0; } cmd = atoi(argv[1]); //把终端上收到的字符串命令转换成整型 //printf("argv:%s \n", argv[1]); /* 执行 ./led_oper 11 点亮LED1 , 执行./led_oper 10 关闭LED1,其他LED灯以此类推 */ switch (cmd) { case 10: ioctl(leds_fd, LEDS_PGM4_0_OFF); break; case 11: ioctl(leds_fd, LEDS_PGM4_0_ON); break; case 20: ioctl(leds_fd, LEDS_PGM4_1_OFF); break; case 21: ioctl(leds_fd, LEDS_PGM4_1_ON); break; case 30: ioctl(leds_fd, LEDS_PGM4_2_OFF); break; case 31: ioctl(leds_fd, LEDS_PGM4_2_ON); break; case 40: ioctl(leds_fd, LEDS_PGM4_3_OFF); break; case 41: ioctl(leds_fd, LEDS_PGM4_3_ON); break; defautl : break; } close(leds_fd); return 0; }
obj-m := tiny4412_leds.o KDIR := /home/workplace/Tiny4412/linux-3.0.86/ # arm all: make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-linux- arm-linux-gcc -static led_oper.c -o led_oper clean: rm -f *.ko *.o *.mod.o *.mod.c *.symvers modul* *oper
安装完tiny4412_leds.ok后,要执行chmod 666 /dev/tiny4412-leds让应用程序能够访问该节点; 然后使用led_oper测试led驱动程序,然而在使用led_oper时会发现权限不够,chmod 777之后也是出现权限不够的提示,原因是SD卡有没可执行的权限,应该必须修改挂载权限,参考友善的用户手册还是无法重新挂载SD卡分区,只能将led_oper拷贝到/data目录下应该/data目录有可执行的权限,然后就可以验证led驱动了;
创建JNI接口类tiny4412Leds.java
编写JNI接口,同时加载动态链接库
package com.example.tiny4412_leds; public class tiny4412Leds { static { System.loadLibrary("tiny4412-leds"); } public native int ledsOperation(int ledNum, boolean status); //接口定义了两个参数:LED编号和开关状态 }创建了一个ledsOperation的JNI接口,通过该接口能够访问linux下的设备节点;
在dos命令行进入tiny4412_leds\src\com\example\tiny4412_leds目录下,执行javac tiny4412Leds.java生成tiny4412Leds.class
后退到src目录,执行javah -jni 类名加包名 (类名和包名建议直接copy)
编译之后就会在src目录下生成com_example_tiny4412_leds_tiny4412Leds.h文件
com_example_tiny4412_leds_tiny4412Leds.h文件就是对应于上面定义的Java接口的C/C++头文件。打开这个文件,可以看到系统已经为我们自动完成了接口函数的声明:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_example_tiny4412_leds_tiny4412Leds */ #ifndef _Included_com_example_tiny4412_leds_tiny4412Leds #define _Included_com_example_tiny4412_leds_tiny4412Leds #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_tiny4412_leds_tiny4412Leds * Method: ledsOperation * Signature: (IZ)I */ JNIEXPORT jint JNICALL Java_com_example_tiny4412_1leds_tiny4412Leds_ledsOperation (JNIEnv *, jobject, jint, jboolean); #ifdef __cplusplus } #endif #endif这个接口函数的命名方式只是在前面加上了Java包名。
有了JNI的C/C++头文件,就可以在C层实现JNI接口了。首先在工程目录下创建一个jni目录,这个目录就是专门用来放C/C++代码的。把com_example_tiny4412_leds_tiny4412Leds.h文件复制到jni目录下,并在这里创建一个com_example_tiny4412_leds_tiny4412Leds.c文件,拷贝com_example_tiny4412_leds_tiny4412Leds.h里的函数接口到com_example_tiny4412_leds_tiny4412Leds.c中并编写实现;
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <linux/ioctl.h> #include "com_example_tiny4412_leds_tiny4412Leds.h" #define DEVICE_NAME "/dev/tiny4412-leds" #define LED_0 1 #define LED_1 2 #define LED_2 3 #define LED_3 4 //幻数 #define LEDS_MAGIC 'a' #define LEDS_PGM4_0_ON _IO(LEDS_MAGIC, 1) #define LEDS_PGM4_0_OFF _IO(LEDS_MAGIC, 2) #define LEDS_PGM4_1_ON _IO(LEDS_MAGIC, 3) #define LEDS_PGM4_1_OFF _IO(LEDS_MAGIC, 4) #define LEDS_PGM4_2_ON _IO(LEDS_MAGIC, 5) #define LEDS_PGM4_2_OFF _IO(LEDS_MAGIC, 6) #define LEDS_PGM4_3_ON _IO(LEDS_MAGIC, 7) #define LEDS_PGM4_3_OFF _IO(LEDS_MAGIC, 8) /* * Class: com_example_tiny4412_leds_tiny4412Leds * Method: ledsOperation * Signature: (II)I */ jint JNICALL Java_com_example_tiny4412_1leds_tiny4412Leds_ledsOperation (JNIEnv *env, jobject obj, jint ledsNum, jboolean status) { int leds_fd = 0; leds_fd = open(DEVICE_NAME, O_RDWR); //打开设备节点 if (leds_fd == -1) { return 1; } switch (ledsNum) { case LED_0: if (status) ioctl(leds_fd, LEDS_PGM4_0_ON); else ioctl(leds_fd, LEDS_PGM4_0_OFF); break; case LED_1: if (status) ioctl(leds_fd, LEDS_PGM4_1_ON); else ioctl(leds_fd, LEDS_PGM4_1_OFF); break; case LED_2: if (status) ioctl(leds_fd, LEDS_PGM4_2_ON); else ioctl(leds_fd, LEDS_PGM4_2_OFF); break; case LED_3: if (status) ioctl(leds_fd, LEDS_PGM4_3_ON); else ioctl(leds_fd, LEDS_PGM4_3_OFF); break; defautl : break; } close(leds_fd); return 0; //操作成功返回0 }
先在工程目录的jni下Android.mk文件:
然后打开Android.mk文件在里面输入如下内容:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_LDLIBS := -llog LOCAL_MODULE := tiny4412-leds LOCAL_SRC_FILES := com_example_tiny4412_leds_tiny4412Leds.c include $(BUILD_SHARED_LIBRARY)LOCAL_MODULE为模块名,要跟tiny4412Leds类中的System.loadLibrary("tiny4412-leds");保持一致;
写完了mk文件就可以开始编译C/C++代码了。可以下windows下编译,也可以在linux下编译;而编译os库需要通过NDK工具进行编译;
在网上下载 android-ndk-r9d-windows-x86_64.zip ,操作系统多少位的就下载相应的;然后解压,存放的路径不能有中文字符和空格;在环境变量path中添加ndk的目录;
在dos命令行下输入:ndk-bulid
输出未找到应用项目路径即表明开发环境配置成功;
然后进入项目顶层目录,执行ndk-build
在将libs目录下生成armeabi目录,并armeabi目录下生成so文件;
在网上下载 android-ndk-r10d-linux-x86_64.bin ,操作系统多少位的就下载相应的;
拷贝到自己的工作目录下然后执行 ./android-ndk-r10d-linux-x86_64.bin 就把文件解压好了;
执行export PATH="$PATH:/home/chenjp/workplace/android-ndk-r10d" 将路径添加到环境变量中
执行ndk-build
输出未找到应用项目路径即表明开发环境配置成功;
进入项目的jni目录,然后执行ndk-build
在将libs目录下生成armeabi目录,并armeabi目录下生成so文件;
有时候,我们能够看到其他一些apk包中的lib目录下有armeabi armeabi-v7a mips x86等其他平台的so库;
而编译器默认是生成armeabi库,如果想生成其他平台的库,可以执行:ndk-build APP_ABI="armeabi armeabi-v7a x86" 或 APP_ABI := all,这样就能生成其他平台的库文件了;
如果不想每次都加上APP_ABI参数,可以在jni目录下创建Application.mk 文件;
在Application.mk 文件中写入 APP_ABI := armeabi armeabi-v7a mips x86 或 APP_ABI := all;
这样直接使用ndk-build指令就可以生成其他平台的so库了;
到这jni编译也介绍完毕了,接下来就编写android应用程序了;
编写activity_main.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" > <CheckBox android:id="@+id/leds1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="LED1"/> <CheckBox android:id="@+id/leds2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="LED2"/> <CheckBox android:id="@+id/leds3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="LED3"/> <CheckBox android:id="@+id/leds4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="LED4"/> </LinearLayout>
编写MainActivity.java
package com.example.tiny4412_leds; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.Toast; public class MainActivity extends Activity { final int LED_0 = 1; final int LED_1 = 2; final int LED_2 = 3; final int LED_3 = 4; tiny4412Leds led = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); led = new tiny4412Leds(); CheckBox leds1 = (CheckBox) findViewById(R.id.leds1); CheckBox leds2 = (CheckBox) findViewById(R.id.leds2); CheckBox leds3 = (CheckBox) findViewById(R.id.leds3); CheckBox leds4 = (CheckBox) findViewById(R.id.leds4); leds1.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // TODO Auto-generated method stub if (led.ledsOperation(LED_0, isChecked) == 0) { Toast.makeText(MainActivity.this, "Led1操作成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, "操作失败", Toast.LENGTH_SHORT).show(); } } }); leds2.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // TODO Auto-generated method stub if (led.ledsOperation(LED_1, isChecked) == 0) { Toast.makeText(MainActivity.this, "Led2操作成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, "操作失败", Toast.LENGTH_SHORT).show(); } } }); leds3.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // TODO Auto-generated method stub if (led.ledsOperation(LED_2, isChecked) == 0) { Toast.makeText(MainActivity.this, "Led3操作成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, "操作失败", Toast.LENGTH_SHORT).show(); } } }); leds4.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // TODO Auto-generated method stub if (led.ledsOperation(LED_3, isChecked) == 0) { Toast.makeText(MainActivity.this, "Led4操作成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, "操作失败", Toast.LENGTH_SHORT).show(); } } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }应用程序使用四个CheckBox控件,对LED进行操作;
运行结果:
点击按键复选框,可以从开发板上看到对应的led会被点亮;
至此led之jni实现编写完毕,如果操控底层硬件能够通过简单的open和ioctl等实相对比较简单的操作完成时,可以通过jni机制直接调用c/c++代码,实现就硬件设备的操作;就无需去编写繁琐的硬件访问服务程序;
JNI部分参考:http://blog.sina.com.cn/s/blog_4298002e01013zk8.html