本文主要讲述从实际项目中一个GPIO口控制一个加密芯片上下电的功能,提供动态库给客户,并有Android应用层apk调用.so库文件的例子,希望能为大家字符设备驱动以及jni开发入门带来帮助!
以下描述参考摘录了别人的话:http://koliy.iteye.com/blog/1424304
android应用层要访问驱动,一般有三种方法。
1.应用层 ---> framwork层JNI ---> 驱动c
2.应用层 ---> framwork层JNI ---> 硬件抽象层HAL ----> 驱动c
3.应用层-->驱动c(读写字符设备)
第2种JNI方法有些改变和增加了HAl,对驱动有了封装,简单来讲,硬件驱动程序一方面分布在Linux内核中,另一方面分布在用户空间的硬件抽象层中。不要误会android把驱动分成两半了,其实android只是在驱动的调用接口上在增加一层对应接口功能来让framwork层JNI来调用。比如,驱动有read接口,HAl层就是封装read接口,做一个 hal_read接口,里面调用read接口,然后把hal_read接口给JNI调用。
明白了吗?这方面有人讲得非常好,有兴趣的可以点击:
http://blog.csdn.net/luoshengyang/article/details/6575988
好处就是对对厂家来说能把商业秘密隐藏起来,我们做驱动实验的话,操作会极其复杂,不过对理解android整个系统都是极其用用的,因为它从下到上涉及到了android系统的硬件驱动层,硬件抽象层,运行时库和应用程序框架层等等。
第3种涉及到Android上层读写设备节点,由于我们现在要讲的GPIO口是一个字符设备驱动,也会创建节点,所以可以通过此方法配置
这里由于客户需要我们的*.so库目前只讲第1种方法的实现:在此之前,请大家了解下JNI的编程方法,JNI是一种为JAVA和C、C++之间能互相访问所提供的编程接口(自行百度了解)
下面详细讲解开发过程(android系统大同小异),主要分为三部分:1.GPIO口字符设备驱动的实现,2.jni环境搭建及代码编写,3.应用层调用jni代码的实现
GPIO口字符设备驱动的实现
关于GPIO口字符设备驱动我们要做如下步骤:
1 找到原理图对应的GPIO口并配置它为输出管脚
gpio口配置(不同平台配置不一样)但目的是一样的 设置GPIO口输出,并默认低电平,我们接的是57管脚
<&range 56 1 0x1500>
<&range 57 1 0x1500> 代表57管脚做为gpio口使用,并默认低电平
这个io口寄存器地址:0xe46002e4
但是调试过程中发现
#define gpio_lp 57
gpio_request(gpio_lp,"pos_pwr");
gpio_set_value(gpio_lp,1);
GPIO调用request报错,导致GPIO不能用但是换个GPIO口后换个gpio口就不报错了,
这种原因是由于intel的特殊性:gpio在vmm的地方被调用,在kernel下就不能操作它(一般平台这样操作没问题的)
后续,想到了另外一种方法
void __iomem *ldo_mmio_base = ioremap(0xe46002e4, 4);
iowrite32(0x1700, ldo_mmio_base); //1700代表设置寄存器(0xe46002e4)为GPIO口,输出 为高
iowrite32(0x1500, ldo_mmio_base);//1500代表设置寄存器(0xe46002e4)为GPIO口,输出 为底
实现了IO口的控制
1.2 源代码我们放到linux-3.10/drivers/char下面让系统生成设备节点:/dev/mypos
linux-3.10依据自己的kernel名字不同而不同
linux-3.10/drivers/char/lp6252_switch.c
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
//#include <asm-generic/ioctl.h>
-
-
//ioctl
-
-
-
-
-
-
static int major = 0;
-
static struct class *pos_class;
-
struct cdev_pos {
-
struct cdev cdev;
-
};
-
struct cdev_pos *pos_dev;
-
-
static int pos_ioctl( struct file* filp, unsigned int cmd, unsigned long argv)
-
{
-
printk(KERN_INFO "entry kernel.... \n");
-
printk(KERN_INFO "%d\n", POS_PWR_ON);
-
<span style= "color:#ff0000;"> void __iomem *ldo_mmio_base = ioremap( 0xe46002e4, 4);</span>
-
-
switch(cmd)
-
{
-
case POS_PWR_ON:
-
{
-
-
gpio_set_value(gpio_lp, 1); //
-
printk(KERN_INFO "POS on\n");
-
-
iowrite32( 0x1700, ldo_mmio_base)
-
break;
-
}
-
case POS_PWR_OFF:
-
{
-
-
gpio_set_value(gpio_lp, 0);
-
printk(KERN_INFO "POS off \n");
-
-
iowrite32( 0x1500, ldo_mmio_base);
-
break;
-
}
-
default:
-
return -EINVAL;
-
}
-
return 0;
-
}
-
-
-
//open
-
static int pos_open( struct inode* i_node, struct file* filp)
-
{
-
printk(KERN_INFO "taosong open init.... \n");
-
int err;
-
-
err = gpio_request(gpio_lp, "pos_pwr");
-
if(err< 0)
-
{
-
printk(KERN_INFO "gpio request faile \n");
-
return err;
-
}
-
gpio_direction_output(gpio_lp, 1);
-
-
return 0;
-
}
-
-
//close
-
static void pos_close( struct inode* i_node, struct file* filp)
-
{
-
printk(KERN_INFO "taosong close init \n");
-
-
gpio_free(gpio_lp);
-
-
return ;
-
}
-
-
/* file operations */
-
struct file_operations fops={
-
.owner = THIS_MODULE,
-
.open = pos_open,
-
.unlocked_ioctl = pos_ioctl,
-
.release= pos_close,
-
};
-
-
static int __init pos_init( void)
-
{
-
printk(KERN_INFO "init .... \n");
-
dev_t dev_no;
-
int result,err;
-
err = alloc_chrdev_region(&dev_no, 0, 1, "my_pos"); //dynamic request device number
-
if(err< 0)
-
{
-
printk(KERN_INFO "ERROR\n");
-
return err;
-
}
-
major = MAJOR(dev_no);
-
pos_dev = kmalloc( sizeof( struct cdev_pos),GFP_KERNEL);
-
if(!pos_dev)
-
{
-
result = -ENOMEM;
-
goto fail_malloc;
-
}
-
memset(pos_dev, 0, sizeof(pos_dev));
-
-
cdev_init(&pos_dev->cdev,&fops);
-
pos_dev->cdev.owner = THIS_MODULE;
-
result = cdev_add(&pos_dev->cdev,dev_no, 1);
-
if(result < 0)
-
{ printk(KERN_INFO "error\n");
-
goto fail_add;
-
}
-
pos_class = class_create(THIS_MODULE, "mypos"); //in sys/class create sysfs file
-
device_create(pos_class, NULL, MKDEV(major, 0), NULL, "mypos"); //dynamic create device file /dev/mypos
-
return 0;
-
fail_add:
-
kfree(pos_dev);
-
fail_malloc:
-
unregister_chrdev_region(dev_no, 1);
-
return result;
-
-
}
-
-
static void __exit pos_exit( void)
-
{
-
dev_t dev_no= MKDEV(major, 0);
-
-
unregister_chrdev_region(dev_no, 1);
-
cdev_del(&pos_dev->cdev);
-
kfree(pos_dev);
-
device_destroy(pos_class,dev_no);
-
class_destroy(pos_class);
-
printk(KERN_INFO "exit........ \n");
-
}
-
module_init(pos_init);
-
module_exit(pos_exit);
-
MODULE_AUTHOR( "*@*.com");
-
MODULE_DESCRIPTION( "control_pos_power");
-
MODULE_LICENSE( "GPL");
要让此代码生效编译进去:
在相应的makefile改过来
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index e562ed5..98e871f 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -2,6 +2,7 @@
# Makefile for the kernel character device drivers.
#
+obj-y += lp6252_switch.o
obj-y += mem.o random.o
obj-$(CONFIG_TTY_PRINTK) += ttyprintk.o
obj-y += misc.o
要让这个节点让别人可读写,还必须修改节点的系统所有者以及他的权限,这个步骤我们一版在 init.rc中进行
我的代码是system/core/rootdir/init.rc
diff --git a/core/rootdir/init.rc b/core/rootdir/init.rc
index fc5d73f..b3095c0 100644
--- a/core/rootdir/init.rc
+++ b/core/rootdir/init.rc
@@ -308,6 +308,8 @@ on boot
chmod 0777 /dev/ttyS1
chmod 0777 /sys/devices/l68ie_switch/chip_switch
+ chown system system /dev/mypos
+ chmod 0766 /dev/mypos
chown system system /sys/devices/system/cpu/cpufreq/interactive/timer_rate
chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/timer_rate
至此一个可用的字符设备节点 /dev/mypos 已经生成并能供给上层可读写的权限
二.jni环境搭建及代码编写
2.1 NDK本地开发环境搭建
这里主要介绍在eclipse上搭建NDK开发环境。
以前做Android的项目要用到NDK就必须要下载NDK,下载安装Cygwin(模拟Linux环境用的),下载CDT(Eclipse C/C++开发插件),还要配置编译器,环境变量...
麻烦到不想说了,本人在网上查了一下资料,发现了一个超级快配置NDK的办法。
Step1:到Android官网下载Android的开发工具ADT(Android Development Tool的缩写),该工具集成了最新的ADT和NDK插件以及Eclipse,还有一个最新版本SDK。解压之后就可以用了,非常爽!
ADT插件:管理Android SDK和相关的开发工具的
NDK插件:用于开发Android NDK的插件,ADT版本在20以上,就能安装NDK插件,另外NDK集成了CDT插件
也可以在线更新ADT、NDK插件,不过速度超级慢...所以果断在网上下载集成开发工具ADT,下载链接见:http://developer.android.com/sdk/index.html 本地百度云地址链接:http://pan.baidu.com/s/1slcVhxF 密码:ucf9
Step2:到Android官网下载最新的NDK,注:NDK版本在r7(本文使用android-ndk-r9b)以上之后就集成了Cygwin,而且还是十分精简版。比起下载Cygwin要方便多啦!
下载链接见:http://developer.android.com/tools/sdk/ndk/index.html
android-ndk-r9b 百度云地址链接:http://pan.baidu.com/s/1hrPGtKC 密码:od7o
Step3:打开Eclipse,点Window->Preferences->Android->NDK,设置NDK路径,例如我的是E:\qf项目20160603\s600\android-ndk-r9b-windows-x86\android-ndk-r9b
Step4:新建一个Android工程,在工程上右键点击Android Tools->Add Native Support...,然后给我们的.so文件取个名字,例如:poscontrol
这时候工程就会多一个jni的文件夹,jni下有Android.mk和poscontrol.cpp(我这里改成.c文件了)文件。Android.mk是NDK工程的Makefile,poscontrol.cpp就是NDK的源文件。
Step5:右键项目工程点击Run as 就会生成libposcontrol.so
Step6:完成了,然后运行。运行之前先编译NDK,然后在编译JAVA代码。编译也许会遇到Unable to launch cygpath. Is Cygwin on the path?错误,解决办法如下:
1.工程右键,点Properties->C/C++ Build的Building Settings中去掉Use default build command,然后输入${NDKROOT}/ndk-build.cmd
2.在C/C++ Build中点击Environment,点Add...添加环境变量NDKROOT,值为NDK的根目录
3.再编译,问题就解决啦!
-
-
-
-
-
-
-
-
-
-
-
-
//驱动里的命令码.
-
-
-
-
-
-
int fd;
-
-
-
static const char *TAG= "012";
-
-
-
-
-
/* * Class: Linuxc
-
* Method: openled
-
* Signature: ()I
-
*/
-
-
JNIEXPORT void JNICALL Java_com_idean_s600_pay_manage_IntelPosPowerManager_posPowerOn(JNIEnv* env, jclass mc)
-
{
-
LOGI( "POWER ON BY QINFENG");
-
LOGI( "LED_ON:%d LED_OFF:%d",LED_ON,LED_OFF);
-
fd=open(DEVICE_NAME,O_RDWR);
-
if(fd< 0)
-
{
-
LOGI( "don't open dev");
-
}
-
else
-
{
-
ioctl(fd,LED_ON, NULL) ;
-
LOGI( "open success");
-
}
-
-
-
}
-
-
/* * Class: Linuxc
-
* Method: clsoeled
-
* Signature: ()V
-
*/
-
JNIEXPORT void JNICALL Java_com_idean_s600_pay_manage_IntelPosPowerManager_posPowerOff(JNIEnv* env, jclass mc)
-
{
-
LOGI( "POWER Off BY QINFENG");
-
ioctl(fd,LED_OFF, NULL) ;
-
close(fd);
-
-
}
#define DEVICE_NAME "/dev/mypos"
这个使我们驱动生成的节点,我们后面通过
fd=open(DEVICE_NAME,O_RDWR);
ioctl(fd,LED_ON,NULL) ;
调用节点并通过底层相对应的CMD (
LED_ON OR LED_OFF
)控制节点的高低电平(加密芯片的上电和下电)
JNICALL Java_com_idean_s600_pay_manage_IntelPosPowerManager_posPowerOn
Java_com_idean_s600_pay_manage_IntelPosPowerManager_posPowerOff
com_idean_s600_pay_manage_IntelPosPowerManager
包名:package com.idean.s600.pay.manage;
因JNI会把 '_' 转换成' . ' 所以在类名和函数接口中不要出现' _ ',以免应用层调用不到JNI接口,这方面对初学者来说极其重要,所以用eclipse生成的android类文件,最好改下类名。不了解对实验的热情打击比较重。
2.JNI函数分本地方法和静态方法。
本地方法:
public native int jni(); // 不带static 声明.
对应的 JNI 函数中参数的定义有改动:
Java_xx_xx_LedControl_jni(JNIEnv*env, jobject obj)
静态方法:
public static native int jni(); // 带static 声明.
对应的 JNI 函数中参数的定义有改动:
Java_xx_xx_LedControl_jni(JNIEnv*env, jclass cls)
输入命令 : ../../ndk-build
会生成 libs 和obj 2个文件。 libposcontrol.so文件放在 libs /armeabi/ 下
三.应用层调用jni代码的实现(apk编写)
关于这节主要贴一下源代码:
IntelPosPowerManager.java
-
package com.idean.s600.pay.manage;
-
-
-
import android.media.AudioManager;
-
import android.os.Bundle;
-
import android.app.Activity;
-
import android.content.Intent;
-
import android.util.Log;
-
import android.view.Menu;
-
import android.view.View;
-
import android.view.View.OnClickListener;
-
import android.widget.Button;
-
-
public class IntelPosPowerManager extends Activity {
-
-
-
private Button power_on;
-
private Button power_off;
-
private OnClickListener mylistener;
-
-
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.activity_intel_pos_power_manager);
-
power_on= (Button)findViewById(R.id.power_on);
-
power_off= (Button)findViewById(R.id.power_off);
-
-
//成功点击事件
-
power_on.setOnClickListener( new View.OnClickListener() {
-
-
public void onClick(View v) {
-
// TODO Auto-generated method stub
-
Log.d( "012", "power_on by android\n");
-
posPowerOn();
-
-
}
-
});
-
power_off.setOnClickListener( new View.OnClickListener() {
-
-
public void onClick(View v) {
-
// TODO Auto-generated method stub
-
Log.d( "012", "power_off by android\n");
-
posPowerOff();
-
-
}
-
});
-
-
-
-
}
-
-
-
protected void onDestroy(){
-
super.onDestroy();
-
}
-
-
-
-
-
-
public boolean onCreateOptionsMenu(Menu menu) {
-
// Inflate the menu; this adds items to the action bar if it is present.
-
getMenuInflater()
-
.inflate(R.menu.activity_intel_pos_power_manager, menu);
-
return true;
-
}
-
-
static {
-
try{
-
Log.i( "012", "try to load poscontrol.so");
-
System.loadLibrary( "poscontrol");
-
//加载本地库,也就是JNI生成的libxxx.so文件,下面再说。
-
} catch (UnsatisfiedLinkError ule){
-
Log.e( "012", "WARNING: Could not load poscontrol.so");
-
}
-
}
-
/**
-
* 控制金融芯片上电
-
*/
-
public native static void posPowerOn();
-
-
/**
-
* 控制金融芯片下电
-
*/
-
public native static void posPowerOff();
-
-
}
System.loadLibrary("poscontrol"); poscontrol名字要和.so库对应起来
本地调用方法:
-
控制金融芯片上电
-
public native static void posPowerOn();
-
控制金融芯片下电
-
public native static void posPowerOff();
附录xml文件布局:
activity_intel_pos_power_manager.xml
-
"1.0" encoding="utf-8" xml version=
-
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-
android:orientation= "vertical"
-
android:layout_width= "fill_parent"
-
android:layout_height= "fill_parent"
-
>
-
<TextView
-
android:id= "@+id/position"
-
android:layout_centerInParent= "true"
-
android:layout_width= "wrap_content"
-
android:layout_height= "wrap_content"
-
android:textSize= "25sp"
-
android:textColor= "#ff0000"
-
android:text= " power control "
-
/>
-
<Button
-
android:id= "@+id/power_on"
-
android:layout_width= "wrap_content"
-
android:layout_height= "wrap_content"
-
android:textSize= "18sp"
-
android:text= "power_on"
-
android:layout_toLeftOf= "@+id/position"
-
android:layout_centerHorizontal= "true"
-
android:layout_alignTop= "@+id/position"
-
/>
-
<Button
-
android:id= "@+id/power_off"
-
android:layout_width= "wrap_content"
-
android:layout_height= "wrap_content"
-
android:textSize= "18sp"
-
android:text= "power_off"
-
android:layout_toRightOf= "@+id/position"
-
android:layout_alignTop= "@+id/position"
-
/>
-
</RelativeLayout>
至此,从底层GPIO字符设备驱动 到 JNI 库文件实现,到上层apk调用JNI本地接口功能全部实现
附
通过按钮 power_on 和 power_off 就能够控制GPIO口的高低电平了