本文主要講述從實際項目中一個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口的高低電平了