/*要搞清楚谁是输入*/
/*在这里,按键控制对应的中断引脚,从而控制对应的IO寄存器*/
/*相当于信息从外面输入*/
/*我们要做的是根据对应的输入信息,来采取相应的响应动作*/
/*这就达到了中断响应的目的*/
/*其核心就是要检测*/
/*那么,该如何去检测呢?*/
/*通过什么来检测呢?*/
/*在这里,按键控制对应的中断引脚,从而控制对应的IO寄存器*/
/*相当于信息从外面输入*/
/*我们要做的是根据对应的输入信息,来采取相应的响应动作*/
/*这就达到了中断响应的目的*/
/*其核心就是要检测*/
/*那么,该如何去检测呢?*/
/*通过什么来检测呢?*/
/*如何得知一个设备究竟用到哪些资源呢?*/
/*这是个非常重要的问题*/
/*我想应该看具体的电路原理图*/
/*只有看图,才能了解具体的电路连接情况*/
/*从而得知设备所需的硬件资源*/
/*厂商的原理图通常给的都比较详细*/
/*这是个非常重要的问题*/
/*我想应该看具体的电路原理图*/
/*只有看图,才能了解具体的电路连接情况*/
/*从而得知设备所需的硬件资源*/
/*厂商的原理图通常给的都比较详细*/
#include <linux/module.h>/*模块有关的*/
#include <linux/kernel.h>/*内核有关的*/
#include <linux/fs.h>/*文件系统有关的*/
#include <linux/init.h>/*init*/
#include <linux/delay.h>/*delay*/
#include <linux/poll.h>/*poll*/
#include <linux/irq.h>/*中断*/
#include <asm/irq.h>/*中断*/
#include <linux/interrupt.h> /*linux中断*/
#include <asm/uaccess.h> /*uaccess*/
#include <mach/regs-gpio.h>/*寄存器设置*/
#include <mach/hardware.h> /*hardware*/
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>/*内核有关的*/
#include <linux/fs.h>/*文件系统有关的*/
#include <linux/init.h>/*init*/
#include <linux/delay.h>/*delay*/
#include <linux/poll.h>/*poll*/
#include <linux/irq.h>/*中断*/
#include <asm/irq.h>/*中断*/
#include <linux/interrupt.h> /*linux中断*/
#include <asm/uaccess.h> /*uaccess*/
#include <mach/regs-gpio.h>/*寄存器设置*/
#include <mach/hardware.h> /*hardware*/
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#define DEVICE_NAME "My_Buttons"/*设备名称*/
/*定义按钮中断的描述结构体*/
/*由它把按钮中断的信息综合起来*/
struct button_desc {
int irq; /*中断号,中断号唯一表示一个中断*/
int pin; /*中断控制的寄存器,该寄存器的值由中断引脚设置,我们希望从该寄存器读出控制信息*/
int pin_setting; /*中断的引脚,该引脚的电平由按键来控制,从而最终我们由按键控制了寄存器的值*/
int number; /*编号*/
char *name; /*名称*/
};
/*由它把按钮中断的信息综合起来*/
struct button_desc {
int irq; /*中断号,中断号唯一表示一个中断*/
int pin; /*中断控制的寄存器,该寄存器的值由中断引脚设置,我们希望从该寄存器读出控制信息*/
int pin_setting; /*中断的引脚,该引脚的电平由按键来控制,从而最终我们由按键控制了寄存器的值*/
int number; /*编号*/
char *name; /*名称*/
};
/*指定4个按键的信息*/
/*这样,资源就组织起来了*/
/*事实上,在这里我们不仅组织起了硬件资源*/
/*我们也把一定的软件资源也糅合进去了*/
/*像中断号*/
static struct button_desc my_buttons[] = {
{IRQ_EINT1, S3C2410_GPF1, S3C2410_GPF1_EINT1, 0, "KEY1"}, /* K1 */
{IRQ_EINT4, S3C2410_GPF4, S3C2410_GPF4_EINT4, 1, "KEY2"}, /* K2 */
{IRQ_EINT2, S3C2410_GPF2, S3C2410_GPF2_EINT2, 2, "KEY3"}, /* K3 */
{IRQ_EINT0, S3C2410_GPF0, S3C2410_GPF0_EINT0, 3, "KEY4"}, /* K4 */
};
/*这样,资源就组织起来了*/
/*事实上,在这里我们不仅组织起了硬件资源*/
/*我们也把一定的软件资源也糅合进去了*/
/*像中断号*/
static struct button_desc my_buttons[] = {
{IRQ_EINT1, S3C2410_GPF1, S3C2410_GPF1_EINT1, 0, "KEY1"}, /* K1 */
{IRQ_EINT4, S3C2410_GPF4, S3C2410_GPF4_EINT4, 1, "KEY2"}, /* K2 */
{IRQ_EINT2, S3C2410_GPF2, S3C2410_GPF2_EINT2, 2, "KEY3"}, /* K3 */
{IRQ_EINT0, S3C2410_GPF0, S3C2410_GPF0_EINT0, 3, "KEY4"}, /* K4 */
};
/*存放各个按键在发生中断情况下的值*/
/*volatile是什么意思呢?*/
/*这个数组是我们存放按键操作结果的,因此非常重要*/
static volatile char key_values [] = {'0', '0', '0', '0'};
/*volatile是什么意思呢?*/
/*这个数组是我们存放按键操作结果的,因此非常重要*/
static volatile char key_values [] = {'0', '0', '0', '0'};
/*该宏应该是创建了一个等待队列*/
/*等待队列,是进程调度的一种重要方法*/
/*等待队列也很有意思,button_waitq,表示按键等待的队列*/
/*就是说,按键一按下,就会激活其等待队列里的进程,来做相应的处理*/
/*因此,按键的等待队列,或者说中断所设置的等待队列,*/
/*是中断处理中非常重要的资源,它大大扩展了中断处理的能力*/
/*button_waitq应该是等待队列的名称*/
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
static volatile int ev_press = 0; /*唤醒等待的条件*/
/*中断服务程序*/
/*那么如何检测到有中断发生呢?*/
/*并且中断发生了,知道发生了什么样的中断呢?*/
/*中断有很多种,该中断服务程序究竟该服务于哪一个中断呢?*/
/*显然,要把中断号与中断服务程序联结起来,构成一个整体*/
/*这个工作可以在open函数里做*/
/*那么如何检测到有中断发生呢?*/
/*并且中断发生了,知道发生了什么样的中断呢?*/
/*中断有很多种,该中断服务程序究竟该服务于哪一个中断呢?*/
/*显然,要把中断号与中断服务程序联结起来,构成一个整体*/
/*这个工作可以在open函数里做*/
/*参数irq---中断号*/
/*中断服务程序应该是与中断号一一对应的*/
/*对应于某个中断号的中断一发生,就会调用该中断号对应的服务程序*/
/*那么,检测中断的发生,就成了先决条件*/
/*参数dev_id ---具体是哪一个按钮*/
/*中断服务程序应该是与中断号一一对应的*/
/*对应于某个中断号的中断一发生,就会调用该中断号对应的服务程序*/
/*那么,检测中断的发生,就成了先决条件*/
/*参数dev_id ---具体是哪一个按钮*/
/*此中断服务程序,在每中断一次,就要对key_values数组设一下值*/
/*并对数组可读标志位ev_press设一下值*/
/*并唤醒在等待队列里的进程*/
/*这是中断处理经常要做的事情*/
/*在这里,等待队列button_waitq里经常等待的进程是数组的读取进程*/
/*就是说,读取进程在没有读到数据的时候就一直在等待,等待按键的输入*/
/*读取进程在等待,并不代表所有进程在等待,其它进程该干啥干啥去*/
static irqreturn_t button_interrupt(int irq, void *dev_id)
{
/*对传入的资源进行处理*/
struct button_desc *this_button = (struct button_desc *)dev_id;
int down;
/*并对数组可读标志位ev_press设一下值*/
/*并唤醒在等待队列里的进程*/
/*这是中断处理经常要做的事情*/
/*在这里,等待队列button_waitq里经常等待的进程是数组的读取进程*/
/*就是说,读取进程在没有读到数据的时候就一直在等待,等待按键的输入*/
/*读取进程在等待,并不代表所有进程在等待,其它进程该干啥干啥去*/
static irqreturn_t button_interrupt(int irq, void *dev_id)
{
/*对传入的资源进行处理*/
struct button_desc *this_button = (struct button_desc *)dev_id;
int down;
/*获取寄存器的值*/
/*这一步至关重要*/
/*s3c2410_gpio_getpin()函数直接获取寄存器的值*/
/*要注意,按一下按钮,会发生两次中断*/
/*即按下是一次中断,放开又是一次中断*/
down = !s3c2410_gpio_getpin(this_button->pin);/*寄存器与中断引脚的联结*/
/*这一步至关重要*/
/*s3c2410_gpio_getpin()函数直接获取寄存器的值*/
/*要注意,按一下按钮,会发生两次中断*/
/*即按下是一次中断,放开又是一次中断*/
down = !s3c2410_gpio_getpin(this_button->pin);/*寄存器与中断引脚的联结*/
/*下面对down的值进行处理*/
/*即是要把数据经过一定的变换存入key_values数组中*/
if (down != (key_values[this_button->number] & 1))
{
key_values[this_button->number] = '0' + down;
ev_press = 1; /*表示中断发生了,数组已经可读了*/
/*即是要把数据经过一定的变换存入key_values数组中*/
if (down != (key_values[this_button->number] & 1))
{
key_values[this_button->number] = '0' + down;
ev_press = 1; /*表示中断发生了,数组已经可读了*/
/*button_waitq队列里存放有相应的处理进程*/
/*如读取数组的值的进程*/
/*要注意wake_up_interruptible()这些函数的用法*/
wake_up_interruptible(&button_waitq); /*唤醒休眠的进程*/
}
/*如读取数组的值的进程*/
/*要注意wake_up_interruptible()这些函数的用法*/
wake_up_interruptible(&button_waitq); /*唤醒休眠的进程*/
}
return IRQ_RETVAL(IRQ_HANDLED);/*返回*/
}
}
/*驱动函数open调用的具体函数*/
/*由open函数具体实现硬件的初始化工作*/
/*以及软件的初始化工作*/
/*为我们的按键设备的运行创造好环境*/
static int tq2440_button_open(struct inode *inode, struct file *file)
{
int i;/*循环变量,因为有4个按钮*/
int err = 0;/*中断注册函数的返回值*/
/*由open函数具体实现硬件的初始化工作*/
/*以及软件的初始化工作*/
/*为我们的按键设备的运行创造好环境*/
static int tq2440_button_open(struct inode *inode, struct file *file)
{
int i;/*循环变量,因为有4个按钮*/
int err = 0;/*中断注册函数的返回值*/
/*对每个按钮分别处理,用for循环来做*/
/*联结中断号和相应的中断服务程序*/
/*这一步类似于前面所说的驱动的注册*/
/*我们可以成功称作中断的注册*/
for (i = 0; i < sizeof(my_buttons)/sizeof(my_buttons[0]); i++)
{
if (my_buttons[i].irq < 0)
continue;
/*注册中断,my_buttons[i]是该中断享有的资源,会被传入buttons_interrupt,进行处理*/
err = request_irq(my_buttons[i].irq, button_interrupt, IRQ_TYPE_EDGE_BOTH,
my_buttons[i].name, (void *)&my_buttons[i]);
if (err)/*注册失败的处理*/
break;/*跳出循环*/
}
/*联结中断号和相应的中断服务程序*/
/*这一步类似于前面所说的驱动的注册*/
/*我们可以成功称作中断的注册*/
for (i = 0; i < sizeof(my_buttons)/sizeof(my_buttons[0]); i++)
{
if (my_buttons[i].irq < 0)
continue;
/*注册中断,my_buttons[i]是该中断享有的资源,会被传入buttons_interrupt,进行处理*/
err = request_irq(my_buttons[i].irq, button_interrupt, IRQ_TYPE_EDGE_BOTH,
my_buttons[i].name, (void *)&my_buttons[i]);
if (err)/*注册失败的处理*/
break;/*跳出循环*/
}
/*若有一个按钮中断注册失败*/
/*则还需把前面注册成功的中断给拆了*/
if (err)
{
i--;/*回到前面一个按钮的处理*/
for (; i >= 0; i--)/*依此拆除*/
{
if (my_buttons[i].irq < 0)
continue;
disable_irq(my_buttons[i].irq);/*使中断不起作用*/
free_irq(my_buttons[i].irq, (void *)&my_buttons[i]); /*释放中断资源*/
}
return -EBUSY;/*中断注册没成功的最终的返回值*/
}
/*则还需把前面注册成功的中断给拆了*/
if (err)
{
i--;/*回到前面一个按钮的处理*/
for (; i >= 0; i--)/*依此拆除*/
{
if (my_buttons[i].irq < 0)
continue;
disable_irq(my_buttons[i].irq);/*使中断不起作用*/
free_irq(my_buttons[i].irq, (void *)&my_buttons[i]); /*释放中断资源*/
}
return -EBUSY;/*中断注册没成功的最终的返回值*/
}
ev_press = 1;/*表示数组已经可读了*/
/*有几个非常重要的问题*/
/*中断注册后,并设置好其中断类型之后,当有中断发生时,*/
/*即按下某个按钮时,系统能够自动检测到有中断发生吗?*/
/*检测到有中断发生,它能够自动辨别是几号中断吗?*/
/*知道了是几号中断,那么它能自动调用其中断服务程序吗?*/
/*对这几个问题的解答,够成了linux系统中断处理机制的核心*/
/*中断注册后,并设置好其中断类型之后,当有中断发生时,*/
/*即按下某个按钮时,系统能够自动检测到有中断发生吗?*/
/*检测到有中断发生,它能够自动辨别是几号中断吗?*/
/*知道了是几号中断,那么它能自动调用其中断服务程序吗?*/
/*对这几个问题的解答,够成了linux系统中断处理机制的核心*/
return 0;/*正常返回*/
}
}
/*release调用的具体函数*/
/*设备软件环境的拆卸*/
/*具体就是中断的释放工作*/
/*因为中断资源,也是系统宝贵的资源,所以不用的时候,要释放*/
static int tq2440_button_close(struct inode *inode, struct file *file)
{
int i; /*循环变量,要操作好几个按键*/
/*设备软件环境的拆卸*/
/*具体就是中断的释放工作*/
/*因为中断资源,也是系统宝贵的资源,所以不用的时候,要释放*/
static int tq2440_button_close(struct inode *inode, struct file *file)
{
int i; /*循环变量,要操作好几个按键*/
/*for循环,对各个按键依此释放中断*/
for (i = 0; i < sizeof(my_buttons)/sizeof(my_buttons[0]); i++)
{
if (my_buttons[i].irq < 0)
continue;
free_irq(my_buttons[i].irq, (void *)&my_buttons[i]); /*释放资源*/
}
for (i = 0; i < sizeof(my_buttons)/sizeof(my_buttons[0]); i++)
{
if (my_buttons[i].irq < 0)
continue;
free_irq(my_buttons[i].irq, (void *)&my_buttons[i]); /*释放资源*/
}
return 0;/*返回*/
}
}
/*read调用的具体函数*/
/*由它读取键盘输入的结果*/
/*实质上就是读取key_values数组的值*/
/*它完成了键盘作为输入设备的核心功能*/
/*数组是否可读,要根据标志位ev_press来判断*/
/*如果数组可读,则读取数据到用户buffer中*/
/*如果数组不可读,则进程进入等待队列,等待到数组可读为止*/
/*等待队列机制,是中断管理中常用到的机制*/
/*因为有些进程经常需要等待某一事件的发生*/
/*注意__user,指的是用户空间*/
/*即要把键盘的输入结果读取到用户空间去*/
/*由它读取键盘输入的结果*/
/*实质上就是读取key_values数组的值*/
/*它完成了键盘作为输入设备的核心功能*/
/*数组是否可读,要根据标志位ev_press来判断*/
/*如果数组可读,则读取数据到用户buffer中*/
/*如果数组不可读,则进程进入等待队列,等待到数组可读为止*/
/*等待队列机制,是中断管理中常用到的机制*/
/*因为有些进程经常需要等待某一事件的发生*/
/*注意__user,指的是用户空间*/
/*即要把键盘的输入结果读取到用户空间去*/
/*要注意,该read函数,只读取一次中断的值,而不是连续地读入*/
/*要做到连续地读入,则需要做一个循环,不断地调用该read函数,但那不是驱动程序里该做的事情*/
static int tq2440_button_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
unsigned long err;/*copy_to_user()函数的返回值*/
/*要做到连续地读入,则需要做一个循环,不断地调用该read函数,但那不是驱动程序里该做的事情*/
static int tq2440_button_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
unsigned long err;/*copy_to_user()函数的返回值*/
/*如果key_values 数组里没有值,则会此进程会休眠*/
/*一直到中断来临之后,中断服务程序会唤醒此休眠进程从而继续读取值*/
/*key_values数组里有没有值,是靠ev_press标志位来判断的*/
/*有值,就是1,无值,就是0*/
/*一直到中断来临之后,中断服务程序会唤醒此休眠进程从而继续读取值*/
/*key_values数组里有没有值,是靠ev_press标志位来判断的*/
/*有值,就是1,无值,就是0*/
/*进程等待队列的机制,是进程调度的一种方法*/
if (!ev_press)/*标志位为0,即无数据时*/
{
if (filp->f_flags & O_NONBLOCK) /*不能以非阻塞读取*/
return -EAGAIN;
else
/*进程休眠,放进button_waitq等待队列*/
/*这里,把ev_press标志位设成了休眠进程的标志位了?*/
/*这是为了便于利用poll_wait函数*/
/*也就是利于select函数*/
wait_event_interruptible(button_waitq, ev_press);/*继续休眠*/
/*在中断处理函数中,此进程会被唤醒*/
/*唤醒前,ev_press 已被置1了*/
/*唤醒后的执行点从这里开始*/
}
{
if (filp->f_flags & O_NONBLOCK) /*不能以非阻塞读取*/
return -EAGAIN;
else
/*进程休眠,放进button_waitq等待队列*/
/*这里,把ev_press标志位设成了休眠进程的标志位了?*/
/*这是为了便于利用poll_wait函数*/
/*也就是利于select函数*/
wait_event_interruptible(button_waitq, ev_press);/*继续休眠*/
/*在中断处理函数中,此进程会被唤醒*/
/*唤醒前,ev_press 已被置1了*/
/*唤醒后的执行点从这里开始*/
}
/*下面就是标志位为1,即有数据可读的的处理情况*/
ev_press = 0;/*对标志位置0,表示读取过了*/
ev_press = 0;/*对标志位置0,表示读取过了*/
/*那就开始往用户空间读数据呗*/
err = copy_to_user(buff, (const void *)key_values, min(sizeof(key_values), count));
return err ? -EFAULT : min(sizeof(key_values), count);/*读取正确,则返回读取到的字节数*/
}
err = copy_to_user(buff, (const void *)key_values, min(sizeof(key_values), count));
return err ? -EFAULT : min(sizeof(key_values), count);/*读取正确,则返回读取到的字节数*/
}
/*poll调用的具体函数*/
/*poll实质上是select的调用函数*/
/*如果有按键数据,则select会立刻返回*/
/*如果没有按键数据,则等待*/
/*实质上这是键盘等待输入的机制*/
/*poll实质上是select的调用函数*/
/*如果有按键数据,则select会立刻返回*/
/*如果没有按键数据,则等待*/
/*实质上这是键盘等待输入的机制*/
/*此函数会监测进程队列button_waitq里的进程*/
/*例如,如果s3c2440_button_read所在的进程的标志位ev_press置为1了*/
/*那么就不会再等待了*/
/*这实质上就是select函数的运行机制*/
/*例如,如果s3c2440_button_read所在的进程的标志位ev_press置为1了*/
/*那么就不会再等待了*/
/*这实质上就是select函数的运行机制*/
/*1.用户层应用程序调用select(),底层调用poll())*/
/*2.核心层调用sys_select() ------> do_select()*/
/* 最终调用文件描述符fd对应的struct file类型变量的struct file_operations *f_op的poll函数。*/
/* poll指向的函数返回当前可否读写的信息。*/
/* 1)如果当前可读写,返回读写信息。*/
/* 2)如果当前不可读写,则阻塞进程,并等待驱动程序唤醒,重新调用poll函数,或超时返回。*/
/*3.驱动需要实现poll函数。*/
/* 当驱动发现有数据可以读写时,通知核心层,核心层重新调用poll指向的函数查询信息。*/
/* poll_wait(filp,&wait_q,wait) // 此处将当前进程加入到等待队列中,但并不阻塞*/
/* 在中断中使用wake_up_interruptible(&wait_q)唤醒等待队列*/
static unsigned int tq2440_button_poll( struct file *file, struct poll_table_struct *wait)
{
unsigned int mask = 0;
/*2.核心层调用sys_select() ------> do_select()*/
/* 最终调用文件描述符fd对应的struct file类型变量的struct file_operations *f_op的poll函数。*/
/* poll指向的函数返回当前可否读写的信息。*/
/* 1)如果当前可读写,返回读写信息。*/
/* 2)如果当前不可读写,则阻塞进程,并等待驱动程序唤醒,重新调用poll函数,或超时返回。*/
/*3.驱动需要实现poll函数。*/
/* 当驱动发现有数据可以读写时,通知核心层,核心层重新调用poll指向的函数查询信息。*/
/* poll_wait(filp,&wait_q,wait) // 此处将当前进程加入到等待队列中,但并不阻塞*/
/* 在中断中使用wake_up_interruptible(&wait_q)唤醒等待队列*/
static unsigned int tq2440_button_poll( struct file *file, struct poll_table_struct *wait)
{
unsigned int mask = 0;
/*把当前进程添加到wait参数指定的等待列表(poll_table)中,但并不阻塞*/
poll_wait(file, &button_waitq, wait);
if (ev_press)
/*POLLIN : 如果设备可被不阻塞地读, 这个位必须设置*/
/*POLLRDNORM : 这个位必须设置, 如果"正常"数据可用来读. 一个可读的设备返回( POLLIN|POLLRDNORM )*/
mask |= POLLIN | POLLRDNORM;/*返回一个位掩码, 可读,见ldd3.chm*/
return mask;
}
poll_wait(file, &button_waitq, wait);
if (ev_press)
/*POLLIN : 如果设备可被不阻塞地读, 这个位必须设置*/
/*POLLRDNORM : 这个位必须设置, 如果"正常"数据可用来读. 一个可读的设备返回( POLLIN|POLLRDNORM )*/
mask |= POLLIN | POLLRDNORM;/*返回一个位掩码, 可读,见ldd3.chm*/
return mask;
}
/*驱动函数的设置*/
/*分别将前面的驱动函数设置进来*/
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.open = tq2440_button_open,
.release = tq2440_button_close,
.read = tq2440_button_read,
.poll = tq2440_button_poll,
};
/*分别将前面的驱动函数设置进来*/
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.open = tq2440_button_open,
.release = tq2440_button_close,
.read = tq2440_button_read,
.poll = tq2440_button_poll,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
/*module_init调用的具体函数*/
/*模块创建时的初始化函数*/
/*而具体的硬件初始化工作,它可以不做*/
/*而把它留给fops里的函数来做*/
static int __init dev_init(void)
{
int ret; /*设备注册的返回值*/
/*模块创建时的初始化函数*/
/*而具体的硬件初始化工作,它可以不做*/
/*而把它留给fops里的函数来做*/
static int __init dev_init(void)
{
int ret; /*设备注册的返回值*/
ret = misc_register(&misc);/*注册成混杂设备*/
return 0;
}
}
/*模块卸载时的扫尾工作*/
/*主要是设备的卸载工作*/
static void __exit dev_exit(void)
{
misc_deregister(&misc);
}
/*主要是设备的卸载工作*/
static void __exit dev_exit(void)
{
misc_deregister(&misc);
}
module_init(dev_init);
module_exit(dev_exit);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ding yuanpu");
MODULE_DESCRIPTION("My_Buttons for TQ2440 Board");
MODULE_AUTHOR("ding yuanpu");
MODULE_DESCRIPTION("My_Buttons for TQ2440 Board");