沙雕老板不知道在哪请了个沙雕项目经理,公司产品改进需要,要求在Android系统下通过GPIO硬件中断实现Android程序唤醒,我擦,我特么要死了,还要去搞Linux驱动,在此记录一下实现方式。
先上一张Android系统架构图:
Android系统底层基于Linux内核,所以要实现硬件中断唤醒Android程序,需要编写Linux驱动,注册对应GPIO中断函数,然后通过异步通知方式将中断信号发送到用户空间应用程序,用户空间应用程序需要注册信号,再将进程PID注册为异步通知方式,这样就能够接收驱动程序发过来的中断信号了,然后我们在用户空间注册的信号处理函数中唤醒Android程序。
#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 Studio的Logcat中会看到打印日志
01-02 09:23:40.710 2201-2201/com.fox.touchtest D/v4l2_capture: touch_irq called