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

沙雕老板不知道在哪请了个沙雕项目经理,公司产品改进需要,要求在Android系统下通过GPIO硬件中断实现Android程序唤醒,我擦,我特么要死了,还要去搞Linux驱动,在此记录一下实现方式。
先上一张Android系统架构图:
Android|Android系统下如何通过外设实现GPIO中断触发调用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 #defineLOG_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|Android系统下如何通过外设实现GPIO中断触发调用Android程序执行
文章图片

当我们将开关闭合时,在Android Studio的Logcat中会看到打印日志
【Android|Android系统下如何通过外设实现GPIO中断触发调用Android程序执行】01-02 09:23:40.710 2201-2201/com.fox.touchtest D/v4l2_capture: touch_irq called

    推荐阅读