此文章是Android应用控制底层硬件的小实验,记录下来,以防后面忘记如何操作。后面也可以按照此流程进行其他开发
开发平台:DMATEK PAD-4412
内核:Linux3.2.0
系统:Android4.0
作者:lyp461340781
Android系统中上层UI是使用Java语言完成的,涉及到底层驱动的话,需要SO库(JNI层)的连接。所以针对LED控制,将从底层驱动、JNI层SO连接库和上层UI界面进行设计。
为了操作方便,此处LED驱动程式采用动态编译,可随时进行修改、编译和载入驱动。
1、首先编写底层的LED驱动程序led.c,源码如下:
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/power_supply.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/irq.h>
#include <linux/wakelock.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <asm/mach-types.h>
#include <mach/hardware.h>
#include <mach/gpio-dma4412.h>
#include <plat/gpio-cfg.h>
#include <plat/adc.h>
#include <linux/memory.h>
#include <linux/io.h>
#include <linux/miscdevice.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <asm/uaccess.h>
#define LED_ON 1
#define LED_OFF 0
#define LED_MAJOR 230
#define led_name "led"
#define GPIO_GPK3_LED18 EXYNOS4_GPK3(3) //led 18
#define GPIO_GPK3_LED19 EXYNOS4_GPK3(4) //led 19
static void led_off(int led_num)
{
switch(led_num)
{
case 1: //led1
gpio_direction_output(GPIO_GPK3_LED18,0);
break;
case 2: //led2
gpio_direction_output(GPIO_GPK3_LED19,0);
break;
default:
break;
}
}
static void led_on(int led_num)
{
switch(led_num)
{
case 1: //led1
gpio_direction_output(GPIO_GPK3_LED18,1);
break;
case 2: //led2
gpio_direction_output(GPIO_GPK3_LED19,1);
break;
default:
break;
}
}
static ssize_t led_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
return count;
}
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
return 0;
}
static int led_open(struct inode *inode, struct file *file)
{
printk("led_open +++++++1\n" );
gpio_direction_output(GPIO_GPK3_LED18,0);
gpio_direction_output(GPIO_GPK3_LED19,0);
printk("led_open -------1\n" );
return 0;
}
/* release command for led device file */
static int led_close(struct inode *inode, struct file *file)
{
// printk("led_close +++1\n");
return 0;
}
/* ioctl command for led device file */
//static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
static int led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
int num;
printk("cmd=%d\n",cmd);
switch(cmd)
{
case LED_ON:
//printk("&&&&&&&&&& LED ON &&&&&&&&&&&&&&\n");
ret = copy_from_user(&num, (int *)arg, sizeof(int));
if(ret != 0)
{
printk("gpio_ioctl: copy_from_user failed\n");
return(-EFAULT);
}
printk("--------LED ON,num = %d-----------\n",num);
led_on(num);
break;
case LED_OFF:
ret = copy_from_user(&num, (int *)arg, sizeof(int));
if(ret != 0)
{
printk("gpio_ioctl: copy_from_user failed\n");
return(-EFAULT);
}
printk("********LED OFF,num = %d**********\n",num);
led_off(num);
break;
default:
break;
}
return 0;
}
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.read = led_read,
.write = led_write,
.open = led_open,
.release = led_close,
.unlocked_ioctl = led_ioctl,
};
static int __init led_init(void)
{
int retval;
retval = register_chrdev(LED_MAJOR,led_name,&led_fops);
if(retval < 0)
{
printk(KERN_WARNING "can't register major number %d\n",LED_MAJOR);
return retval;
}
printk("LED driver register success!\n");
return 0;
}
static void __exit led_exit(void)
{
unregister_chrdev(LED_MAJOR,led_name);
printk("LED driver release success!\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_AUTHOR("lyp");
MODULE_DESCRIPTION("led driver");
MODULE_LICENSE("GPL");
2、编写LED驱动程序的Makefile(与led.c同目录),源码如下:
#obj-$(CONFIG_PAD4412_LEDS) += led.o
CC =/usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-gcc
4412X_KERNEL_DIR = /home/liuyipin/4412/PAD4412/android_kernel_dma-pad4412_V1.1
ifneq ($(KERNELRELEASE),)
obj-m := led.o
else
KERNELDIR ?= $(4412X_KERNEL_DIR)
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.bak *.mod.*
endif
3、进入LED驱动程序的目录,编译LED驱动程序,使用make命令进行编译。编译成功后会生成led.ko文件,即LED的驱动程式。
4、进行LED界面的设计,在Eclipse中新建一个工程Led_Control,主要代码如下:
1)main.xml代码如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<ImageView
android:id="@+id/led_imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:src="@drawable/index" />
<Button
android:id="@+id/ledon_button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/led_text"
android:layout_marginTop="25dp"
android:layout_toLeftOf="@+id/led_text"
android:text="LED1开" />
<Button
android:id="@+id/ledoff_button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/led_imageView"
android:layout_alignRight="@+id/ledoff_button1"
android:layout_marginBottom="28dp"
android:text="LED2关" />
<Button
android:id="@+id/ledon_button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/ledoff_button2"
android:layout_alignBottom="@+id/ledoff_button2"
android:layout_toLeftOf="@+id/led_text"
android:text="LED2开" />
<Button
android:id="@+id/ledoff_button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/ledon_button1"
android:layout_alignBottom="@+id/ledon_button1"
android:layout_toRightOf="@+id/led_text"
android:text="LED1关" />
<TextView
android:id="@+id/led_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/led_imageView"
android:layout_centerHorizontal="true"
android:layout_marginTop="25dp"
android:text="LED TEST" />
</RelativeLayout>
2)MainActivity.java代码如下:
package com.dmatek.ledcontral;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
public class MainActivity extends Activity {
private Button mButton1;
private Button mButton2;
private Button mButton3;
private Button mButton4;
//private Button mButton5;
public int fd = 0;
public int led_on = 1;
public int led_off = 0;
private Button.OnClickListener button1_listener= new Button.OnClickListener()
{
public void onClick(View v)
{
Linuxc.send(1, led_on);
}
};
private Button.OnClickListener button2_listener= new Button.OnClickListener()
{
public void onClick(View v)
{
Linuxc.send(1, led_off);
}
};
private Button.OnClickListener button3_listener= new Button.OnClickListener()
{
public void onClick(View v)
{
Linuxc.send(2, led_on);
}
};
private Button.OnClickListener button4_listener= new Button.OnClickListener()
{
public void onClick(View v)
{
Linuxc.send(2, led_off);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton1=(Button)findViewById(R.id.ledon_button1);
mButton2 =(Button) findViewById(R.id.ledoff_button1);
mButton3 =(Button) findViewById(R.id.ledon_button2);
mButton4 =(Button) findViewById(R.id.ledoff_button2);
//mButton5 =(Button) findViewById(R.id.ledoff_button2);
fd = Linuxc.openled();
if (fd < 0){
setTitle("open device false!");
// 打开设备文件失败的话,就退出
finish();
}
else {
setTitle("open device success!");
}
/*使用setOnClickListener来监听事件*/
mButton1.setOnClickListener(button1_listener);
mButton2.setOnClickListener(button2_listener);
mButton3.setOnClickListener(button3_listener);
mButton4.setOnClickListener(button4_listener);
}
@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;
}
}
3)添加Class类Linuxc,Linuxc.java代码如下:
package com.dmatek.ledcontral;
import android.util.Log;
public class Linuxc {
static{
try{
Log.i("JNI","Trying to load libled.so");
System.loadLibrary("led");
}
catch(UnsatisfiedLinkError ule){
Log.e("JNI","WARNING:Could not load libled.so");
}
}
public static native int openled();
public static native int closeled();
public static native int send(int led_num,int on_off);
}
5、LED JNI设计
1)在Led_Control工程目录中新建jni目录,然后输入cmd进入DOS界面,进入Led_Control工程的jni目录下。如下图所示:
2)执行命令:javah -classpath "H:\Android\Android_Project\Led_Contral\bin" com.dmatek.ledcontarl.Linuxc
3)会在当前目录下生成一个com.dmatek.ledcontral.Linuxc.h文件
4)编写com.dmatek.ledcontral.Linuxc.c文件,源码如下:
#include <jni.h>
#include "com_dmatek_ledcontral_Linuxc.h"
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define DEVICE_BLTEST "/dev/led"
int fd;
JNIEXPORT jint JNICALL Java_com_dmatek_ledcontral_Linuxc_openled
(JNIEnv *env, jclass mc)
{
fd= open(DEVICE_BLTEST,O_RDONLY);
return fd;
}
JNIEXPORT jint JNICALL Java_com_dmatek_ledcontral_Linuxc_closeled
(JNIEnv *env, jclass mc)
{
close(fd);
}
JNIEXPORT jint JNICALL Java_com_dmatek_ledcontral_Linuxc_send
(JNIEnv *env, jclass mc, jint a, jint b)
{
ioctl(fd,b,&a);
}
5)生成SO链接库
打开Cygwin终端,进入Led_Contral目录,执行ndk-bulid命令,会在Led_Contarl工程libs\armeabi目录下生成libled.so
6)重新编译Android应用程序即可。
6、测试:
1)将led.ko拷贝到SD卡中,将SD卡插入开发平台,打开PC的串口工具,进入SD卡目录进行led驱动的挂载。
# insmod led.ko
# busybox mknod /dev/led c 230 0
2)安装APK到开发平台,打开Led_Contral应用程序进行测试。
内核源码下载链接:http://download.csdn.net/detail/lyp461340781/9490210
APK源码下载链接:http://download.csdn.net/detail/lyp461340781/9490216
备注:
不同版本内核的ioctl函数实现
从linux-2.6.36之后,已经由unlocked_ioctl替代原来的ioctl。其中驱动的变化就是函数参数去掉inode参数。这也是我前面提及到的我在项目开发过程中遇到的问题所在。那时候项目开发时,旧平台的内核是Linux-2.6.32的,新平台的内核是Linux-3.6.5的,所以要修改ioctl函数,去掉struct inode *inode这个参数就可以了。在驱动开发中,如果大家遇到像我这样ioctl调用失效的问题,可以查看一下内核版本,然后看看是否是ioctl函数实现不一样导致的!
备注内容来自http://blog.csdn.net/zengxianyang/article/details/51008746