Android系统下如何通过外设实现GPIO中断触发调用Android程序执行

沙雕老板不知道在哪请了个沙雕项目经理,公司产品改进需要,要求在Android系统下通过GPIO硬件中断实现Android程序唤醒,我擦,我特么要死了,还要去搞Linux驱动,在此记录一下实现方式。

先上一张Android系统架构图:

Android系统底层基于Linux内核,所以要实现硬件中断唤醒Android程序,需要编写Linux驱动,注册对应GPIO中断函数,然后通过异步通知方式将中断信号发送到用户空间应用程序,用户空间应用程序需要注册信号,再将进程PID注册为异步通知方式,这样就能够接收驱动程序发过来的中断信号了,然后我们在用户空间注册的信号处理函数中唤醒Android程序。

1、GPIO中断驱动程序

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define touch_gpio GPIOA(6)   //这个引脚为中断输入引脚
struct fasync_struct *fasync_queue; //异步通知队列

//中断处理函数
static irqreturn_t touch_interrupt(int irq, void *dev_id) {
    /* 在中断服务函数中向应用层发送SIGIO信号-异步通知 */
    kill_fasync(&fasync_queue, SIGIO, POLL_IN); 
    printk("touch_interrupt\n");
    return IRQ_RETVAL(IRQ_HANDLED);     
}

static int touch_open(struct inode *inode, struct file *file)
{
    int ret;
    int virq;
    printk("touch_open\n");
    if(atomic_read(&file->f_count) != 0)
    {
        /*gpio申请*/
        ret = gpio_request(touch_gpio,"touch_gpio");
        if(ret!=0){
            printk("gpio_request failed.\n");
        }
        /*将gpio端口映射到中断号*/
        virq = gpio_to_irq(touch_gpio);
        if (IS_ERR_VALUE(virq)) {
            printk("map gpio [%d] to virq [%d] failed\n", touch_gpio, virq);
            return -EINVAL;
        }
        /* 申请中断并设置为高电平触发 */
        ret = request_irq(virq, touch_interrupt, IRQF_TRIGGER_RISING, "PA0_EINT", NULL);
        if (IS_ERR_VALUE(ret)) {
            printk("request virq %d failed, errno = %d\n", virq, ret);
            return -EINVAL;
        }
    }
    return 0;
}
static int touch_release(struct inode *inode, struct file *file)
{
    printk("touch_release\n");
    if(atomic_read(&file->f_count) == 0)
    {
        int virq;
        virq = gpio_to_irq(touch_gpio);
        if (IS_ERR_VALUE(virq)) {
            printk("map gpio [%d] to virq [%d] failed\n", touch_gpio, virq);
            return -EINVAL;
        }
        free_irq(virq,NULL);
        /*释放GPIO*/
        gpio_free(touch_gpio);
    }
    return 0;
}
static int touch_fasync(int fd, struct file *file, int on)
{
    /*将该设备登记到fasync_queue队列中去*/
    int ret = fasync_helper(fd, file, on, &fasync_queue); 
    if(ret<0){
        printk("failed touch_fasync \n");
        return ret;
    }
    return 0;
}
static struct file_operations touch_ops = {
    .owner = THIS_MODULE,
    .open = touch_open,
    .release = touch_release,
    .fasync = touch_fasync,
};

static struct miscdevice touch_device = {
	.minor	= MISC_DYNAMIC_MINOR,
	.fops	= &touch_ops,
	.name	= "touch_driver", // 杂项设备名称为“touch_driver”
};

static int touch_init(void)
{
    int ret;
    printk("touch_init\n");
    ret = misc_register(&touch_device);
    return ret;
}

static void touch_exit(void)
{
    printk("touch_exit\n");
    misc_deregister(&touch_device);
}

MODULE_LICENSE("GPL v2");
module_init(touch_init);
module_exit(touch_exit);

怎么编译驱动程序就不说了,我这里直接将驱动编译进Linux内核了。重新编译Android系统镜像,烧写到开发板上,启动系统,使用adb shell进入Android系统后台,通过dmesg | grep touch可以查看内核是否打印了“touch”文本相关信息。

2、若是Linux系统下,可以通过如下代码测试驱动是否正常。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

//中断处理函数
void touch_irq(int signum)
{
    printf("touch_irq called\n");
}

void int main()
{
    //设备文件名
    char* dev = "/dev/touch_driver";
    fd = open(dev, O_RDWR);
    if(fd<0){
        printf("can not open \n");
    }
    //注册一个信号, 启动信号驱动机制
    signal(SIGIO, touch_irq);
    //将本应用程序的进程号告诉给内核,最终使得驱动程序可以成功发送信号给应用程序
    fcntl(fd, F_SETOWN, getpid());
    //取得当前文件描述符的状态
    int oflags = fcntl(fd, F_GETFL);
    //设置fasync标记,最终会调用驱动的fasync->fasync_helper
    fcntl(fd, F_SETFL, oflags|FASYNC);
    while(1)
    {
        sleep(1);
    }
    return 0;

}

3、编写Android应用程序测试(Android下需要使用jni调用本地层程序来注册信号)

编写Java类接口和对应的jni代码实现

public class TouchSenior {
    public native int open(String filepath); // 打开设备文件
    public native void setState(int fd,boolean isOpen); // 设置异步通知是否开启
    public native void close(int fd); // 关闭设备文件
    static {
        System.loadLibrary("touch"); // 这里将jni实现的本地方法编译为libtouch.so动态库
    }
}

编写jni实现代码

#include 

#ifdef __cplusplus
extern "C" {
#endif

#include 
#define  LOG_TAG    "v4l2_capture"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG  , LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO   , LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN   , LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR  , LOG_TAG, __VA_ARGS__)


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

static int fd;

//中断函数
void touch_irq(int signum)
{
    LOGD("touch_irq called\n");
}

JNIEXPORT jint JNICALL
Java_com_yobotics_touchtest_TouchSenior_open(JNIEnv *env, jobject instance, jstring filepath_) {
    const char *filepath = (*env)->GetStringUTFChars(env, filepath_, 0);
    fd = open(filepath, O_RDWR);
    if(fd<0){
        LOGD("can not open \n");
    }
    //注册一个信号, 启动信号驱动机制
    signal(SIGIO, touch_irq);

    //将本应用程序的进程号告诉给内核,最终使得驱动程序可以成功发送信号给应用程序
    fcntl(fd, F_SETOWN, getpid());

    (*env)->ReleaseStringUTFChars(env, filepath_, filepath);
    return fd;
}

JNIEXPORT void JNICALL
Java_com_yobotics_touchtest_TouchSenior_setState(JNIEnv *env, jobject instance, jint fd,
                                                 jboolean isOpen) {
    if ( isOpen ){
        int oflags = fcntl(fd, F_GETFL);
        fcntl(fd, F_SETFL, oflags|FASYNC);//设置fasync标记
    } else{
        int oflags = fcntl(fd, F_GETFL);
        fcntl(fd, F_SETFL, (oflags&(!FASYNC))); // 取消fasync标记
    }
}

JNIEXPORT void JNICALL
Java_com_yobotics_touchtest_TouchSenior_close(JNIEnv *env, jobject instance, jint fd) {
    close(fd);
}

#ifdef __cplusplus
}
#endif

应用程序调用该接口方法实现注册中断,接受中断

public class MainActivity extends AppCompatActivity {

    TouchSenior touchSenior;
    int fd;

    @BindView(R.id.btn_open)
    Button btnOpen;
    @BindView(R.id.btn_close)
    Button btnClose;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        touchSenior = new TouchSenior();
        fd = touchSenior.open("/dev/touch_driver");
        Log.d("TTTTT", "fd:" + fd);
    }

    @Override
    protected void onDestroy() {
        touchSenior.close(fd);
        super.onDestroy();
    }

    @OnClick(R.id.btn_open)
    public void onBtnOpenClicked() {
        touchSenior.setState(fd,true);
    }

    @OnClick(R.id.btn_close)
    public void onBtnCloseClicked() {
        touchSenior.setState(fd,false);
    }
}

点击打开按钮后,Android应用程序会调用本地方法设置进程接受该中断信号,这样我们就可以在中断函数中执行响应的操作来唤醒Android相关的程序,执行相应的操作了。

4、测试GPIO中断

这里使用GPIOA6引脚,将其作为中断输入引脚,因为Linux驱动里边没有中断上拉下拉的标准接口,所以测试的时候需要先将GPIOA6引脚接一个外部下拉电路,电路图如下:

Android系统下如何通过外设实现GPIO中断触发调用Android程序执行_第1张图片

当我们将开关闭合时,在Android Studio的Logcat中会看到打印日志

01-02 09:23:40.710 2201-2201/com.fox.touchtest D/v4l2_capture: touch_irq called

 

你可能感兴趣的:(Android,学习,Linux,嵌入式)