本实验通过GPIO口拉高拉低控制小灯的亮灭,作为刚刚从应用层转framework的小兵,写这篇文章希望对大家的学习有帮助。
##什么是GPIO
GPIO,英文全称为General-Purpose IO ports,也就是通用IO口。嵌入式系统中常常有数量众多,但是结构却比较简单的外部设备/电路,对这些设备/电路有的需要CPU为之提供控制手段,有的则需要被CPU用作输入信号。而且,许多这样的设备/电路只要求一位,即只要有开/关两种状态就够了,比如灯亮与灭。对这些设备/电路的控制,使用传统的串行口或并行口都不合适。
所以在微控制器芯片上一般都会提供一个“通用可编程IO接口”,即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口的控制
###实现
源代码我放到kenel-3.10/drivers/char下面让系统生成设备节点:/dev/mtgpio,具体需要修改两个地方。
首先进入到kernel-3.10/drivers/char目录,新增文件,我这里命名为lp6735_switch.c ,具体代码如下:
kernel-3.10/drivers/char$ vim lp6735_switch.c
#include <linux/module.h> /* For module specific items */
#include <linux/moduleparam.h> /* For new moduleparam's */
#include <linux/types.h> /* For standard types (like size_t) */
#include <linux/errno.h> /* For the -ENODEV/... values */
#include <linux/kernel.h> /* For printk/panic/... */
#include <linux/fs.h> /* For file operations */^M
#include <linux/ioport.h> /* For io-port access */
#include <linux/platform_device.h> /* For platform_driver framework */
#include <linux/init.h> /* For __init/__exit/... */
#include <linux/uaccess.h> /* For copy_to_user/put_user/... */
#include <linux/io.h> /* For inb/outb/... */
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/slab.h> /*kamlloc */
//#include <asm-generic/ioctl.h>
//ioctl
#define CMD_FLAG 'i'
#define led_PWR_ON _IOR(CMD_FLAG,0x00000001,__u32)
#define led_PWR_OFF _IOR(CMD_FLAG,0x00000000,__u32)
#define gpio_lp 57
static int major =0;
static struct classclass *led_class;
struct cdev_led {
struct cdev cdev;
};
struct cdev_led *led_dev;
static int led_ioctl(struct file* filp,unsigned int cmd,unsigned long argv)
{
printk(KERN_INFO "entry kernel.... \n");
printk(KERN_INFO "%d\n", led_PWR_ON);
void __iomem *ldo_mmio_base = ioremap(0xe46002e4, 4);
switch(cmd)
{
case led_PWR_ON:
{
#if 0
gpio_set_value(gpio_lp,1); //
printk(KERN_INFO "led on\n");
#endif
iowrite32(0x1700, ldo_mmio_base)
break;
}
case led_PWR_OFF:
{
#if 0
gpio_set_value(gpio_lp,0);
printk(KERN_INFO "led off \n");
#endif
iowrite32(0x1500, ldo_mmio_base);
break;
}
default:
return -EINVAL;
}
return 0;
}
//open
static int led_open(struct inode* i_node,struct file* filp)
{
printk(KERN_INFO "larsonzhong open init.... \n");
int err;
// larsonzhong Content between #if 0 #endif would comment, because there is no actual device
#if 0
err = gpio_request(gpio_lp,"led_pwr");
if(err<0)
{
printk(KERN_INFO "gpio request faile \n");
return err;
}
gpio_direction_output(gpio_lp,1);
#endif
return 0;
}
//close
static void led_close(struct inode* i_node,struct file* filp)
{
printk(KERN_INFO "larsonzhong close init \n");
// larsonzhong Content between #if 0 #endif would comment, because there is no actual device
#if 0
gpio_free(gpio_lp);
#endif
return ;
}
/* file operations */
struct file_operations fops={
.owner = THIS_MODULE,
.open = led_open,
.unlocked_ioctl = led_ioctl,
.release= led_close,
};
static int __init led_init(void)
{
printk(KERN_INFO "init .... \n");
dev_t dev_no;
int result,err;
err = alloc_chrdev_region(&dev_no,0,1,"my_led"); //dynamic request device number
if(err<0)
{
printk(KERN_INFO "ERROR\n");
return err;
}
major = MAJOR(dev_no);
led_dev = kmalloc(sizeof(struct cdev_led),GFP_KERNEL);
if(!led_dev)
{
result = -ENOMEM;
goto fail_malloc;
}
memset(led_dev,0,sizeof(led_dev));
cdev_init(&led_dev->cdev,&fops);
led_dev->cdev.owner = THIS_MODULE;
result = cdev_add(&led_dev->cdev,dev_no,1);
if(result <0)
{ printk(KERN_INFO "error\n");
goto fail_add;
}
led_class = class_create(THIS_MODULE,"mtgpio"); //in sys/class create sysfs file
device_create(led_class,NULL,MKDEV(major,0),NULL,"mtgpio"); //dynamic create device file /dev/myled
return 0;
fail_add:
kfree(led_dev);
fail_malloc:
unregister_chrdev_region(dev_no,1);
return result;
}
static void __exit led_exit(void)
{
dev_t dev_no=MKDEV(major,0);
unregister_chrdev_region(dev_no,1);
cdev_del(&led_dev->cdev);
kfree(led_dev);
device_destroy(led_class,dev_no);
class_destroy(led_class);
printk(KERN_INFO "exit........ \n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_AUTHOR("larsonzhong@gmail.com");
MODULE_DESCRIPTION("control_led_power");
MODULE_LICENSE("GPL");
保存退出,然后我们还需要修改一个文件,就是同级目录下的makefile文件。
在kernel-3.10/drivers/char$ vim Makefile 里面新增一段:
+obj-y += lp6735_switch.o
具体代码,因为csd对一些特俗符号作了处理,只能传图:
这样加入lp6735_switch.c并修改Makefile后固件生成就会在 /dev/ 下生成节点 /dev/mtgpio
要让这个节点让别人可读写,还必须修改节点的系统所有者以及他的权限,这个步骤我们一版在 init.rc中进行
我的代码是system/core/rootdir/init.rc
...
# added by larsonzhong@163.com change my devices mqgpio
chown system system /dev/mtgpio
chmod 0766 /dev/mtgpio
...
##开始实验
我们假设我们要用的小灯的设备是dev/mtgpio##Jni头文件
新建一个安卓工程,然后新建类,我这里是GpioLED.java。
public class GpioLED {
//控制LED上电
public native static void ledPowerOn();
// 控制LED下电
public native static void ledPowerOff();
}
生成头文件
方法1:简单粗暴
使用javah生成,请注意java环境已经搭建好(包括环境配置classpath配置)。
进入到源码下的bin目录下的classes目录,使用javah -jni 包名.类名,我这里是javah -jni com.coban.ledsimple.LEDGpio
我们再目录下就能看到头文件,打开头文件。把里面的声明copy出来。
方法2:更便捷
点击编译按钮旁边带工具箱的编译按钮旁边的小三角,弹出下拉列表,点击External Tools Configurations,弹出了一个对话框,
我们选中Program然后点击上面的新建按钮。然后如下填写:
Main卡:
Name:javah
Location:选择javah所在目录。我的是C:\Program Files\Java\jdk1.8.0_102\bin\javah.exe
Working Diractory:点击variables按钮弹出列表,选择project_loc确定,然后再后面加上\src,我的结果是${project_loc}\src
Arguments:-classpath ${project_loc}\bin\classes -d ${project_loc}\jni -jni ${java_type_name}
Refresh卡:
勾选 refresh Resource upon completion
Common卡:
勾选 External Tools 然后点击apply
###jni实现
新建c文件和mk文件。如果你使用的是eclipse并且配置了ndk(注意不仅在eclipse要配置ndk,在环境变量也要配置)否则可能出现Unable to launch cygpath. Is Cygwin on the path?] 错误。
右击Project->Android Tools->Add Native Support
然后你会看到工程目录下多了一个文件夹和两个文件,我们把刚刚copy的头文件声明粘贴过来。
我这里的代码实现是这样的:ledcontrol.c
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<errno.h>
#include<unistd.h>
#include<sys/ioctl.h>
#include<jni.h> // 一定要包含此文件
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include <android/log.h>
//驱动里的命令码.
#define CMD_FLAG 'i'
#define LED_ON _IOR(CMD_FLAG,0x00000001,__u32)
#define LED_OFF _IOR(CMD_FLAG,0x00000000,__u32)
#define DEVICE_NAME "/dev/mtgpio"
int fd;
static const char *TAG="012";
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)
/* * Class: Linuxc
* Method: openled
* Signature: ()I
*/
JNIEXPORT void JNICALL Java_com_coban_ledsimple_LEDGpio_ledPowerOn(JNIEnv* env, jclass mc)
{
LOGI("POWER ON BY LARSON");
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_coban_ledsimple_LEDGpio_ledPowerOff(JNIEnv* env, jclass mc)
{
LOGI("POWER Off BY LARSON");
ioctl(fd,LED_OFF,NULL) ;
close(fd);
}
请注意,这里我用到了日志和一些其他的头文件,我们需要把相关的库包含进来。修改Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS := -lm -llog
LOCAL_MODULE := ledcontrol
LOCAL_SRC_FILES := ledcontrol.c
include $(BUILD_SHARED_LIBRARY)
要注意的是LOCAL_LDLIBS := -lm -llog 不要添加在include $(CLEAR_VARS)的前面,否则会被清掉,导致无效。
Application.mk
APP_ABI := all
这里的意思是编译所有平台的so文件,如果想指定某个编译
APP_ABI := armeabi armeabi-v7a x86
中间用空格隔开
###生成so文件
我们直接把软件往设备上推或者build一把。会在工程目录下看到多出了一个obj文件夹,
而且在libs下多出了好几个文件夹,这些文件夹对应不同的平台。
###APP实现
这里我就直接贴代码了
HelloJniLED.java
package com.example.hellojni;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class HelloJniLED extends Activity {
private Button power_on;
private Button power_off;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
power_on = (Button) findViewById(R.id.power_on);
power_off = (Button) findViewById(R.id.power_off);
power_on.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("012", "power_on by android\n");
LEDGpio.ledPowerOn();
}
});
power_off.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("012", "power_off by android\n");
LEDGpio.ledPowerOff();
}
});
}
}
LEDGpio.java
package com.coban.ledsimple;
import android.util.Log;
public class LEDGpio {
static {
try {
Log.i("012", "try to load ledcontrol.so");
System.loadLibrary("ledcontrol");
} catch (UnsatisfiedLinkError ule) {
Log.e("012", "WARNING: Could not load ledcontrol.so");
}
}
public native static void ledPowerOn();
public native static void ledPowerOff();
}
布局文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text=" power control "
android:textColor="#ff0000"
android:textSize="25sp" />
<Button
android:id="@+id/power_on"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/position"
android:layout_centerHorizontal="true"
android:layout_toLeftOf="@+id/position"
android:text="power_on"
android:textSize="18sp" />
<Button
android:id="@+id/power_off"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/position"
android:layout_toRightOf="@+id/position"
android:text="power_off"
android:textSize="18sp" />
</RelativeLayout>