实验平台:友善之臂s3c2410
编译环境:ubuntu > arm-linux-gcc3.4.5
内核版本:Linux2.6
实验目的:在Linux下完成arm板上的8*8按键驱动开发,最终实现用一个中断程序实现64个按键的中断触发,按键由16个io口实现矩阵键盘。
初次接触linux的驱动开发,很多的知识只能边学边做,虽说上是比较慢的,可这也是一个需要经历的过程,最终不管怎样,也是实现了项目的基本要求,在此期间的也学到了一些很基础性的知识,当然,很多东西只看是不能解决问题的,只需实验一下,调试一下,结果比看要好的多了,在此也把我如何接触并如何面对一个毫无头绪的驱动问题下手总结如下。
入门:对于一个刚接触Linux的人来说只能是先了解驱动开发的原理了,动态加载驱动可以说是我了解的第一个概念了,在系统跑起来之后,把所编译的驱动用insmod再加载进去也就是所谓的动态加载了,当然也有卸载驱动的rmmod了,在这个基础上才正式的了解了一些很基础的命令知识。
理解驱动:这个过程也许是最为抽象的了,只能找了一个示例驱动来读,比较的难啃,有种只缘身陷此山中的感觉,幸亏还只是个按键驱动,短一点,这样的话最终还是了解了驱动的架构,主要还是由几个函数组成,各个函数各有用途,总体结构是不变的,
static int __init
s3c2410_button_init(void)//初始化硬件模块
static void __exit
s3c2410_button_exit(void)//卸载硬件模块
module_init(s3c2410_button_init); //加载硬件模块
module_exit(s3c2410_button_exit);//卸载
最主要的也是最基本的就是上面的几个子函数了,也可以说是格式化的东西了,一个驱动中所必须拥有的函数,初始化和卸载其实都是些相反的过程,正如初始化干了些什么,而卸载再把所做得事情按相反的顺序去掉就行了,
初始化硬件s3c2410_button_init(void)包括了:
register_chrdev(0,DEVICE_NAME,&s3c2410_button_fops);
//注册设备的函数,各参数含义:
0:需要动态分配一个设备号,如果注册成功的话,把设备号返回出来,当然这个设备号以后卸载时要用到的
DEVICE_NAME:所定义的设备名,自己定义,加载模块后就可以查到的。
&s3c2410_button_fops:一个应用文件表,其中定义了通过映射怎么操作设备等等。
devfs_mk_cdev(MKDEV(s3c2410_button_major,0),
S_IWUSR,DEVICE_NAME);
加载设备到文件系统,很格式化的东西
当然卸载硬件则是一个相反的过程了, s3c2410_button_exit()包括了:
devfs_remove(DEVICE_NAME);//刚好相反的过程
unregister_chrdev(s3c2410_button_major,DEVICE_NAME);
//卸载设备的函数,主要传递了设备号和设备名。
接下来主要是&s3c2410_button_fops这个表的建立了,其实主要是一些对应的关系了
static struct file_operations
s3c2410_button_fops = {
.owner
= THIS_MODULE,
.open= s3c2410_button_open,
.release=s3c2410_button_close,
.read= s3c2410_button_read,
};
在这里主要实现打开文件,关闭文件,读文件这几个功能,在驱动所写的程序中也就是这三个函数:
s3c2410_button_close()
s3c2410_button_open()
s3c2410_button_read()
也就是通过这个结构表的建立,我们可以在应用程序中通过open,release,read来调用这几个函数,作用还是挺大的,接下来也该看看这几个函数的内部结构了。
其中的open,close函数其实系统已经默认的做了很多的调用,也就是说如果只是打开文件关闭文件的话,里面置空就行了,系统会自动完成你所需要的。而read函数则是自己所要编写的。实现的一个基本功能就是内核到用户之间参数的传递,也就是把通过系统模式所读到的数据通过
copy_to_user(buff,(char
*)&button_ret,sizeof(unsigned char));
传递给用户使用,这样可以防止用户直接访问系统区域导致安全隐患。当然在read里面还要有一些访问各种端口所得到的数据等等,这也就是所要给用户使用的数据。
如果具备了以上的几个函数也就具有了一个驱动的大致的框架结构,可以正常的加载卸载驱动,不过具体到要驱动作些事情还是需要在各个子函数里添加一些需要用到的变量及用到的参数,这样驱动才能正式的运行,才能得到想要的结果。
键盘驱动的编写:其实也就是在原有的结构上添加一些变量,初始化一些端口,并访问,最后通过传递参数得到一个键值,这样一个键盘驱动才是最完全的。
首先说一下键盘驱动的方案,在这里的64个键盘,其中都是触发同一个中断,然后再通过中断程序扫描键盘,得到键值并存在队列里面,然后由read函数调用传递给用户使用,这里存在一个建队列的过程,这个队列其实就是一个环形的链表,由一个指针指向其中的一个结构体,然后存键值,取键值,
typedef struct {
unsigned
char buf[MAX_BUTTON_BUF];
unsigned
int head,tail;
wait_queue_head_t
wq;
} BUTTON_DEV;
这个就是链表的结构体,包含了一个数组主要用来存放键值的,还有head,tail是用来指示当前的存放键值位置和取键值的位置,最后的wq也就是一个等待队列,这样才能在阻塞模式下工作。当然在这里有个宏定义的很好,
#define INCBUF(x,mod)((++(x)) & ((mod)-1))
这个令我费了好长时间去理解,也就是组成环形链表的一个构成,mod的值为链表长度,当x在链表的范围时是不影响的,大于时则与后自动把X置为初始值重新开始。这样在设备初始化时也就需要初始化一下队列,把队列置为等待队列,
init_waitqueue_head(&(buttondev.wq));
这样才能在阻塞模式下更好的使用这个队列作为等待队列,另外初始化还应包括对中断的初始化,
set_irq_type(IRQ_EINT5,IRQT_FALLING);
这里把对应的IRQ_EINT5中断置为上升沿触发模式,当然模式也是可以变换的,这是根据自己的情况而定的。这样除了一些所必须的架构初始化,键盘初始化已经足够了。卸载当然是一样的原理,同样的架构是不变的。
打开文件open()时所需做的也是一些对各种参数的初始化,主要是对中断的申请:
request_irq(IRQ_EINT5, (void
*)&matrix_button_Isr, SA_INTERRUPT, DEVICE_NAME, NULL);
当然在关闭文件时也就调用了释放中断函数,把对应的中断函数关了:
free_irq(IRQ_EINT5,NULL);
其实这一切都是对应的过程。这样所对应的打开关闭文件已经做得差不多了,接下来主要就是对read()还有中断处理的操作了。
中断操作函数如下:
static void matrix_button_Isr(int irq, void
*dev_id, struct pt_regs *reg)
{
int
r;
disable_irq(IRQ_EINT5);
if(testkey()==1)
{
r=get_row();
get_row();
key=buttonstatus[row][col];
if(key!=0)storekey();
while(r==get_row())mdelay(1);
}enable_irq(IRQ_EINT5);
}
这里主要就是关中断,中断来后禁止其它中断函数,其实这里也是一个算法的问题,
其中的取键值的过程主要是,get_row()通过判断那个为低先得到键盘按下所在的行,然后get_col()再得到键盘所在的列,主要是各个列位依次置0,判断行是否变化,如果变化则是所要找的列,这样键值就可以通过列表查到了,最后还有一个开中断的设置,不过当时一直出现一个问题,在开中断后如果按住键不松的话将会一直有中断产生,最后只好用了一条while语句,一直等到键释放后才开中断。
接下来就只剩读函数了read(),在这里主要是取冲区的键值,这样在这种情况下如果有键值的话则直接取出,然后通过
copy_to_user(buff,(char
*)&button_ret,sizeof(unsigned char));
把值传递给用户区,如果没值的话则判断是否为阻塞模式,如果为非阻塞则直接返回,如果为阻塞则设置等待队列,一直等待直到唤醒后继续读值。
static ssize_t matrix_button_read(struct
file *filp, char *buff, size_t count,loff_t *offset)
{
static
unsigned char button_ret;
retry:
if(buttondev.head!=buttondev.tail)
{
button_ret=buttonRead();
copy_to_user(buff,(char
*)&button_ret,sizeof(unsigned char));
return
sizeof(unsigned char);
}
else
{
if(filp->f_flags & O_NONBLOCK)
return -EAGAIN;
wait_event_interruptible(buttondev.wq,flag);
flag=0;
if(signal_pending(current))
{
printk("rturn -ERESTARTSYS\n");
return -ERESTARTSYS;
}
goto
retry;
}
return
sizeof(unsigned char);