实验内容介绍见《GEC210嵌入式系统开发教材20131120(更新)》第93页--“5.3 嵌入式 Linux 下的按键中断实验”
5.3 嵌入式 Linux 下的按键中断实验
4、 掌握内核中断、定时器及等待队列的使用。
4、 下载并调试独立按键应用程序。
4、 ARM 应用程序的基本架构。
2、 软件:PC 操作系统 ubuntu10.04、minicom、arm-linux-gcc 交叉编译环境。
a) 原理图:
由硬件原理图可知按键分别接在 KP_COL0---KP_COL3 和 KP_ROW0---KP-ROW3 上即是 GPH2 0-3)
(和 GPH3(0-3)上。
b) 寄存器简介:
GPH2 包含四个寄存器用于控制 IO 端口的功能,包括 GPH2CON、GPH2DAT、GPH2DRV、GPH2PUD。(GPH3 包含类似寄存器,详细请查看 S5PV210 数据手册)
GPH2CON 用 于 设 置 GPH2 的 功 能 , 例 如 GPH2CON0[0:3] 为 0b0000 时 GPH2[0] 作 为 输 入 ;
GPH2CON0[0:3] 为 0b0001 时 GPH2[0] 作 为 输 出 端 口 ; GPH2CON0[0:3] 为 0b0010 时 为 保 留 未 用 ;
GPH2CON0[0:3] 为 0b0011 时 作 为 按 键 的 列 ; GPH2CON0[0:3] 为 0b0011~0b1110 保 留 未 使 用 ;
GPH2CON0[0:3]为 0b1111 时 GPH2[0]作为外部中断使用。
(GPH3CON 功能类似,这里不再赘述,详细请查看S5PV210数据手册)
GPH2DAT[7:0]: 当 GPH2CON 将 GPH2 设置成输入模式时,GPH2DAT 相应的位对应着引脚的状态;
当 GPH2CON 将 GPH2 设置成输出模式是,GPH2DAT 相应的位和引脚的状态一致。 GPH2CON 将 GPH2当设置成其他功能模式时,GPH2DAT 相应的位的状态是未知的。GPH2PUD 用于设置 GPH2 IO 端口的上拉电阻 0b00 禁止上拉/下拉电阻;0b01 禁止下拉电阻;0b10禁止上拉电阻;0b11 保留未使用。
实验原理:
1、
该驱动设计为一个字符设备。9个 GPIO 引脚设置为外部中断引脚,双边沿触发(上升沿和下降沿均触发信号)。向系统注册的两个中断,调用共同的中断处理函数。当有键按下时,在中断中查询引脚状态,以确定是哪个 GPIO 脚被按下。
2、 注册中断处理函数,驱动程序可以通过下面的函数注册并激活一个中断处理程序,以便中断:
int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *), unsigned long irq_flags, const char * devname, void *dev_id)
第一个参数 irg 表示要分配的中断号。对某些设备,如传统 PC 设备上的系统时钟或键盘,这个值通常是预先设定死的。而对于大多数其他设备来说,这个值要么是可以通过探测获取,要么可以动态确定。
第二个参数 handler 是一个指针,指向处理这个中断的实际中断处理程序。只要操作系统一接收到中断,该函数就被调用。要注意,handler 函数的原型是特定的——它接受三个参数,并有一个类型为 irqreturn_t的返回值。我们将在本章随后的部分讨论这个函数。
第三个参数 irqflags 可以为 0,也可能是下列一个或多个标志的位掩码:
IRQF_DISABLED:
此 标 志 表 明 给 定 的 中 断 处 理 程 序 是 一 个 快 速 中 断 处 理 程 序 ( fast interrupt handler)。过去,Linux 将中断处理程序分为快速和慢速两种。那些可以迅速执行但调用频率可能会很高的中断服务程序,会被贴上这样的标签。通常这样做需要修改中断处理程序的行为,使它们能够尽可能快地执行。现在,加不加此标志的区别只剩下一条了:在本地处理器上,快速中断处理程序在禁止所有中断的情况下运行。这使得快速中断处理程序能够不受其他中断干扰,得以迅速执行。而缺省情况下(没有这个标志),除了正运行的中断处理程序对应的那条中断线被屏蔽外,其他所有中断都是激活的。除了时钟中断外,绝大多数中断都不使用标志。
IRQF_SAMPLE_RANDOM:
此标志表明这个设备产生的中断对内核熵池(entropy pool)有贡献。内核熵池负责提供从各种随机事件导出的真正的随机数。如果指定了该标志,那么来自该设备的中断间隔时间就会作为墒填充到熵池。如果你的设备以预知的速率产生中断(比如系统定时器),或者可能受外部攻击者(例如连网设备)的影响,那么就不要设置这个标志。相反,有其他很多硬件产生中断的速率是不可预知的,所以都能成为一种较的熵源。关于内核熵池更多的信息。请参考附录 C。
IRQF_SHARED:
此标志表明可以在多个中断处理程序之间共享中断线。在同一个给定线上注册的每个处理程序必须指定这个标志;否则,在每条线上只能有一个处理程序。有关共享中断处理程序的更多信息将在下面的小节中提供。
第四个参数 devname 是与中断相关的设备的 ASCII 文本表示法。例如,PC 机上键盘中断对应的这个值为“keyboard”。这些名字会被/proc/irq 和/proc/interrupt 文件使用,以便与用户通信。
第五个参数 dev_id 主要用于共享中断线 request_irq()成功执行会返回 0。如果返回非 0 值,就表示有犯错误发生,在这种情况下,指定的中断处理程序不会被注册。最常见的错误是-EBUSY,它表示给定的中断线已经在使用(或者当前用户或者你没有指定 SA_SHIRQ)。
释放中断处理线,可以调用 void free_irq(unsigned int irq, void *dev_id)
中断处理程序的返回值是一个特殊类型:irqreturn_t。中断处理程序可能返回两个特殊的值:IRQ_NONE和 IRQ_HANDLED。
3、 内核定时器的使用:
定时器由结构 time_list 表示,定义在文件
struct timer_list{
struct list_head entry; /*包含定时器的链表*/
unsigned long expires; /*以jiffies为单位的定时值*/
spinlock_t lock; /*保护定时器的锁*/
void (*function)(unsigned long);/*定时器处理函数*/
unsigned long data; /*传给处理函数的长整形参数*/
struct tvec_t_base_s *base;/*定时器内部值,用户不要使用*/
};
幸运的是,使用定时器并不需要深入了解该数据结构。事实上,过深的陷入该结构,反而会使你的代码不能保证对可能发生的变化提供支持。内核提供了一组与定时器相关的接口用来简化管理定时器的操作。所有这些接口都声明在文件
创建定时器时需要先定义它:
struct timer_list my_timer;
接着需要通过一个辅助函数初始化定时器数据结构的内部值,初始化必须在使用其他定时器管理函数对定时器进行操作前完成。
Init_timer(&my_timer);
现在你可以填充结构中需要的值了:
my_timer.expires=jiffies+delay; /*定时器超时时的节拍数*/
my_timer.data=0; /*给定时器处理函数传入0值*/
my_timer.function=my_function; /*定时器超时时调用的函数*/
my_timet.expires 表示超时时间,它是以节拍为单位的绝对计数值。如果当前 jiffies 计数等于或大于
my_timet.expires,那么 my_timer.function 指向的处理函数就会开始执行,另外该函数还要使用长整型参数
my_timer.data。所以正如我们从 timer_list 结构看到的形式,处理函数必须符合下面的函数原形:
void my_timer_function (unsigned long data);
data 参数使你可以利用同一个处理函数注册多个定时器,只需通过该参数就能区别对待它们。如果你不需要这个参数,可以简单地传递 0(或任何其他值)给处理函数。
最后,你必须激活定时器:
add_timer(&my_timer);
有时可能需要更改已经激活的定时器超时时间,所以内核通过函数 mod_timer()来实现该功能,该函数可以改变指定的定时器超时时间:
mod_timer (&my_timer,jiffies+mew_delay);
mod_timer()函数也可操作那些已经初始化,但还没有被激活的定时器,如果定时器未被激活,Mod_timer()会激活它。如果调用时定时器未被激活,该函数返回 0;否则返回 1。但不论哪种情况,一旦从 mod_timer()函数返回,定时器都将被激活而且设置了新的定时值。
如果需要在定时器超时前停止定时器,可以使用 del_timer()函数:
del_timer(&my_timer);
被激活或未被激活的定时器都可以使用该函数,如果定时器还未被激活,该函数返回 0;否则返回 1。
4、 睡眠和唤醒
当进程等待事件(可以是输入数据,子进程的终止或是其他什么)时,它需要进入睡眠状态以便其他进程可以使用计算资源。你可以调用如下函数之一让进程进入睡眠状态:
void interruptible_sleep_on(struct wait_queue **q);
void sleep_on(struct wait_queue **q);
int wait_event_interruptible(wait_queue_head_t q, int condition);
然后用如下函数之一唤醒进程:
void wake_up(struct wait_queue **q);
void wake_up_interruptible(struct wait_queue **q);
5、 驱动代码简析(详细代码请查看附件)
驱动代码:
< driver / button_driver.c >
/*
**
**This is a button driver.
**The author is Alan.
**
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEVICE_NAME "buttons"//设备名称
struct button_desc {
int gpio;
int number;
char *name;
struct timer_list timer;
};
static struct button_desc buttons[]={// 设备中断号和名称
{ S5PV210_GPH2(0), 0, "KEY0" },
{ S5PV210_GPH2(1), 1, "KEY1" },
{ S5PV210_GPH2(2), 2, "KEY2" },
{ S5PV210_GPH2(3), 3, "KEY3" },
{ S5PV210_GPH3(0), 4, "KEY4" },
{ S5PV210_GPH3(1), 5, "KEY5" },
{ S5PV210_GPH3(2), 6, "KEY6" },
{ S5PV210_GPH3(3), 7, "KEY7" },
};
static volatile char key_values[]={
'0','0','0','0','0','0','0','0'
};
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);//声明一个等待队列并初始化
static volatile int ev_press = 0;
static void gec210_buttons_timer(unsigned long _data)
{
struct button_desc *bdata = (struct button_desc *)_data;
int down;
int number;
unsigned tmp;
tmp=gpio_get_value(bdata->gpio);
/* active low */
down=!tmp;
printk("KEY %d: %08x\n",bdata->number,down);
number=bdata->number;
if(down != (key_values[number] & 1))//down为0,确定按键被按下
{
key_values[number]='0'+down;
ev_press =1;
wake_up_interruptible(&button_waitq);//唤醒等待队列
}
}
static irqreturn_t button_interrupt(int irq,void *dev_id)//中断处理函数
{
struct button_desc *bdata =(struct button_desc *)dev_id;
mod_timer(&bdata->timer,jiffies+msecs_to_jiffies(40));
return IRQ_HANDLED;//return 1
}
static int gec210_buttons_open(struct inode *inode,struct file *file)
{
int irq;
int i;
int err;
for(i=0;i=0;i--)
{
if(!buttons[i].gpio)
continue;
irq=gpio_to_irq(buttons[i].gpio);
disable_irq(irq);//禁止中断
free_irq(irq,(void *)&buttons[i]);//释放中断
del_timer_sync(&buttons[i].timer);//清楚定时器
}
return -EBUSY;
}
ev_press = 1;
return 0;
}
static int gec210_buttons_close(struct inode *inode,struct file *file)
{
int irq,i;
for(i=0;if_flags & O_NONBLOCK)
return -EAGAIN;
else
wait_event_interruptible(button_waitq,ev_press);
}
ev_press =0;
err = copy_to_user((void *)buffer,(const void*)(&key_values),
min(sizeof(key_values),count));
return err ? -EFAULT :min(sizeof(key_values),count);
}
static unsigned int gec210_buttons_poll(struct file *file,
struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(file,&button_waitq,wait);//查询有无按键中断
if(ev_press)
mask |= POLLIN | POLLRDNORM;//有数据可读\有普通数据可读
return mask;
}
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.open = gec210_buttons_open,
.release = gec210_buttons_close,
.read = gec210_buttons_read,
.poll = gec210_buttons_poll,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
static int __init button_dev_init(void)
{
int ret;
ret = misc_register(&misc);//注册misc设备
printk(DEVICE_NAME "\tinitialized\n");
return ret;
}
static void __exit button_dev_exit(void)
{
misc_deregister(&misc);//注销misc设备
}
module_init(button_dev_init);
module_exit(button_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALAN");
< driver / Makefile >
ifneq ($(KERNELRELEASE),)
obj-m :=button_driver.o
else
module-objs :=button_driver.o
KERNELDIR :=/home/gec/linux_kernel/linux2.6.35.7/
PWD :=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
$(RM) *.ko *.mod.c *.mod.o *.o *.order *.symvers *.cmd
< app / button_test.c >
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ARRY_SIZE(x) (sizeof(x)/sizeof(x[0]))
int main(int argc,char **argv)
{
int button_fd;
char current_button_value[8]={0};//板载八个按键 key2-9
char prior_button_value[8]={0};//用于保存按键的前键值
button_fd = open("/dev/buttons",O_RDONLY);
if(button_fd < 0)
{
perror("open device :");
exit(1);
}
while(1)
{
int i;
if(read(button_fd,current_button_value,
sizeof(current_button_value))!=sizeof(current_button_value))
{
perror("read buttons:");
exit(1);//exit(0) 表示程序正常, exit(1)/exit(-1)表示程序异常退出,exit() 结束当前进程/当前程序/,在整个程序中,只要调用 exit ,就结束
}
for(i=0;i
< app / Makefile >
Exec := button_test
Obj := button_test.c
CC := arm-linux-gcc
$(Exec) : $(Obj)
$(CC) -o $@ $(Obj) $(LDLIBS$(LDLIBS-$(@)))
clean:
rm -vf $(Exec) *.elf *.o
测试结果: