当前位置:首页 > 嵌入式 > 嵌入式教程
[导读]LED和蜂鸣器是最简单的GPIO的应用,都不需要任何外部输入或控制。按键同样使用GPIO接口,但按键本身需要外部的输入,即在驱动程序中要处理外部中断。按键硬件驱动原理图如图11-7所示。

11.6按键驱动程序实例11.6.1按键工作原理

LED和蜂鸣器是最简单的GPIO的应用,都不需要任何外部输入或控制。按键同样使用GPIO接口,但按键本身需要外部的输入,即在驱动程序中要处理外部中断。按键硬件驱动原理图如图11-7所示。在图11-7的4×4矩阵按键(K1~K16)电路中,使用4个输入/输出端口(EINT0、EINT2、EINT11和EINT19)和4个输出端口(KSCAN0~KSCAN3)。

图11.7按键驱动电路原理图

按键驱动电路使用的端口和对应的寄存器如表11-18所示。

表11.18 按键电路的主要端口

管脚

端口

输入/输出

管脚

端口

输入/输出

KEYSCAN0

GPE11

输出

EINT0

EINIT0/GPF0

输入/输出

KEYSCAN1

GPG6

输出

EINT2

EINT2/GPF2

输入/输出

KEYSCAN2

GPE13

输出

EINT11

EINT11/GPG3

输入/输出

KEYSCAN3

GPG2

输出

EINT19

EINT19/GPG11

输入/输出

因为通常中断端口是比较珍贵且有限的资源,所以在本电路设计中,16个按键复用了4个中断线。那怎么样才能及时而准确地对矩阵按键进行扫描呢?

某个中断的产生表示,与它所对应的矩阵行的4个按键中,至少有一个按键被按住了。因此可以通过查看产生了哪个中断,来确定在矩阵的哪一行中发生了按键操作(按住或释放)。例如,如果产生了外部2号线中断(EINT2变为低电平),则表示K7、K8、K9和K15中至少有一个按键被按住了。这时候4个EINT端口应该通过GPIO配置寄存器被设置为外部中断端口,而且4个KSCAN端口的输出必须为低电平。

在确定按键操作所在行的位置之后,我们还得查看按键操作所在列的位置。此时要使用KSCAN端口组,同时将4个EINT端口配置为通用输入端口(而不是中断端口)。在4个KSCAN端口中,轮流将其中某一个端口的输出置为低电平,其他3个端口的输出置为高电平。这样逐列进行扫描,直到按键所在列的KSCAN端口输出为低电平,此时按键操作所在行的EINT管脚的输入端口的值会变成低电平。例如,在确认产生了外部2号中断之后,进行逐列扫描。若发现在KSCAN1为低电平时(其他端口输出均为高电平),GPF2(EINT2管脚的输入端口)变为低电平,则可以断定按键K8被按住了。

以上的讨论都是在按键的理想状态下进行的,但实际的按键动作会在短时间(几毫秒至几十毫秒)内产生信号抖动。例如,当按键被按下时,其动作就像弹簧的若干次往复运动,将产生几个脉冲信号。一次按键操作将会产生若干次按键中断,从而会产生抖动现象。因此驱动程序中必须要解决去除抖动所产生的毛刺信号的问题。

11.6.2按键驱动程序

首先按键设备相关的数据结构的定义如下所示:

/*butt_drv.h*/

……

typedefstruct_st_key_info_matrix/*按键数据结构*/

{

unsignedcharkey_id;/*按键ID*/

unsignedintirq_no;/*对应的中断号*/

unsignedintirq_gpio_port;/*对应的中断线的输入端口地址*/

unsignedintkscan_gpio_port;/*对应的KSCAN端口地址*/

}st_key_info_matrix;

typedefstruct_st_key_buffer/*按键缓冲数据结构*/

{

unsignedlongjiffy[MAX_KEY_COUNT];/*按键时间,5s以前的铵键作废*/

unsignedcharbuf[MAX_KEY_COUNT];/*按键缓冲区*/

unsignedinthead,tail;/*按键缓冲区头和尾*/

}st_key_buffer;

……

下面是矩阵按键数组的定义,数组元素的信息(一个按键信息)按照0行0列,0行1列,…,3行2列,3行3列的顺序逐行排列。

staticst_key_info_matrixkey_info_matrix[MAX_COLUMN][MAX_ROW]=

{

{{10,IRQ_EINT0,S3C2410_GPF0,S3C2410_GPE11},/*0行0列*/

{11,IRQ_EINT0,S3C2410_GPF0,S3C2410_GPG6},

{12,IRQ_EINT0,S3C2410_GPF0,S3C2410_GPE13},

{16,IRQ_EINT0,S3C2410_GPF0,S3C2410_GPG2}},

{{7,IRQ_EINT2,S3C2410_GPF2,S3C2410_GPE11},/*1行0列*/

{8,IRQ_EINT2,S3C2410_GPF2,S3C2410_GPG6},

{9,IRQ_EINT2,S3C2410_GPF2,S3C2410_GPE13},

{15,IRQ_EINT2,S3C2410_GPF2,S3C2410_GPG2}},

{{4,IRQ_EINT11,S3C2410_GPG3,S3C2410_GPE11},/*2行0列*/

{5,IRQ_EINT11,S3C2410_GPG3,S3C2410_GPG6},

{6,IRQ_EINT11,S3C2410_GPG3,S3C2410_GPE13},

{14,IRQ_EINT11,S3C2410_GPG3,S3C2410_GPG2}},

{{1,IRQ_EINT19,S3C2410_GPG11,S3C2410_GPE11},/*3行0列*/

{2,IRQ_EINT19,S3C2410_GPG11,S3C2410_GPG6},

{3,IRQ_EINT19,S3C2410_GPG11,S3C2410_GPE13},

{13,IRQ_EINT19,S3C2410_GPG11,S3C2410_GPG2}},

};

下面是与按键相关的端口的初始化函数。这些函数已经在简单的GPIO字符设备驱动程序里被使用过。此外,set_irq_type()函数用于设定中断线的类型,在本实例中通过该函数将4个中断线的类型配置为下降沿触发式。

staticvoidinit_gpio(void)

{

s3c2410_gpio_cfgpin(S3C2410_GPE11,S3C2410_GPE11_OUTP);/*GPE11*/

s3c2410_gpio_setpin(S3C2410_GPE11,0);

s3c2410_gpio_cfgpin(S3C2410_GPE13,S3C2410_GPE13_OUTP);/*GPE13*/

s3c2410_gpio_setpin(S3C2410_GPE13,0);

s3c2410_gpio_cfgpin(S3C2410_GPG2,S3C2410_GPG2_OUTP);/*GPG2*/

s3c2410_gpio_setpin(S3C2410_GPG2,0);

s3c2410_gpio_cfgpin(S3C2410_GPG6,S3C2410_GPG6_OUTP);/*GPG6*/

s3c2410_gpio_setpin(S3C2410_GPG6,0);

s3c2410_gpio_cfgpin(S3C2410_GPF0,S3C2410_GPF0_EINT0);/*GPF0*/

s3c2410_gpio_cfgpin(S3C2410_GPF2,S3C2410_GPF2_EINT2);/*GPF2*/

s3c2410_gpio_cfgpin(S3C2410_GPG3,S3C2410_GPG3_EINT11);/*GPG3*/

s3c2410_gpio_cfgpin(S3C2410_GPG11,S3C2410_GPG11_EINT19);/*GPG11*/

set_irq_type(IRQ_EINT0,IRQT_FALLING);

set_irq_type(IRQ_EINT2,IRQT_FALLING);

set_irq_type(IRQ_EINT11,IRQT_FALLING);

set_irq_type(IRQ_EINT19,IRQT_FALLING);

}

下面讲解按键驱动的主要接口,以下为驱动模块的入口和卸载函数。

/*初始化并添加structcdev结构到系统之中*/

staticvoidbutton_setup_cdev(structcdev*dev,

intminor,structfile_operations*fops)

{

interr;

intdevno=MKDEV(button_major,minor);

cdev_init(dev,fops);/*初始化结构体structcdev*/

dev->owner=THIS_MODULE;

dev->ops=fops;/*关联到设备的file_operations结构*/

err=cdev_add(dev,devno,1);/*将structcdev结构添加到系统之中*/

if(err)

{

printk(KERN_INFO"Error%daddingbutton%dn",err,minor);

}

}

……

/*驱动初始化*/

staticintbutton_init(void)

{

intret;

/*将主设备号和次设备号定义到一个dev_t数据类型的结构体之中*/

dev_tdev=MKDEV(button_major,0);

if(button_major)

{/*静态注册一个设备,设备号先前指定好,并设定设备名,用cat/proc/devices来查看*/

ret=register_chrdev_region(dev,1,BUTTONS_DEVICE_NAME);

}

else

{/*由系统动态分配主设备号*/

ret=alloc_chrdev_region(&dev,0,1,BUTTONS_DEVICE_NAME);

button_major=MAJOR(dev);/*获得主设备号*/

}

if(ret<0)

{

printk(KERN_WARNING"Button:unabletogetmajor%dn",button_major);

returnret;

}

/*初始化和添加结构体structcdev到系统之中*/

button_setup_cdev(&button_dev,0,&button_fops);

printk("Buttondriverinitialized.n");

return0;

}

/*驱动卸载*/

staticvoid__exitbutton_exit(void)

{

cdev_del(&button_dev);/*删除结构体structcdev*/

/*卸载设备驱动所占有的资源*/

unregister_chrdev_region(MKDEV(button_major,0),1);

printk("Buttondriveruninstalledn");

}

module_init(button_init);/*初始化设备驱动程序的入口*/

module_exit(button_exit);/*卸载设备驱动程序的入口*/

MODULE_AUTHOR("David");

MODULE_LICENSE("DualBSD/GPL");

按键字符设备的file_operations结构定义为:

staticstructfile_operationsbutton_fops=

{

.owner=THIS_MODULE,

.ioctl=button_ioctl,

.open=button_open,

.read=button_read,

.release=button_release,

};

以下为open和release函数接口的实现。

/*打开文件,申请中断*/

staticintbutton_open(structinode*inode,structfile*filp)

{

intret=nonseekable_open(inode,filp);

if(ret<0)

{

returnret;

}

init_gpio();/*相关GPIO端口的初始化*/

ret=request_irqs();/*申请4个中断*/

if(ret<0)

{

returnret;

}

init_keybuffer();/*初始化按键缓冲数据结构*/

returnret;

}

/*关闭文件,屏蔽中断*/

staticintbutton_release(structinode*inode,structfile*filp)

{

free_irqs();/*屏蔽中断*/

return0;

}

在open函数接口中,进行了GPIO端口的初始化、申请硬件中断以及按键缓冲的初始化等工作。在以前的章节中提过,中断端口是比较宝贵而且数量有限的资源。因此需要注意,最好要在第一次打开设备时申请(调用request_irq函数)中断端口,而不是在驱动模块加载的时候申请。如果已加载的设备驱动占用而在一定时间段内不使用某些中断资源,则这些资源不会被其他驱动所使用,只能白白浪费掉。而在打开设备的时候(调用open函数接口)申请中断,则不同的设备驱动可以共享这些宝贵的中断资源。

以下为中断申请和释放的部分以及中断处理函数。

/*中断处理函数,其中irq为中断号*/

staticirqreturn_tbutton_irq(intirq,void*dev_id,structpt_regs*regs)

{

unsignedcharucKey=0;

disable_irqs();/*屏蔽中断*/

/*延迟50ms,屏蔽按键毛刺*/

udelay(50000);

ucKey=button_scan(irq);/*扫描按键,获得进行操作的按键的ID*/

if((ucKey>=1)&&(ucKey<=16))

{

/*如果缓冲区已满,则不添加*/

if(((key_buffer.head+1)&(MAX_KEY_COUNT-1))!=key_buffer.tail)

{

spin_lock_irq(&buffer_lock);

key_buffer.buf[key_buffer.tail]=ucKey;

key_buffer.jiffy[key_buffer.tail]=get_tick_count();

key_buffer.tail++;

key_buffer.tail&=(MAX_KEY_COUNT-1);

spin_unlock_irq(&buffer_lock);

}

}

init_gpio();/*初始化GPIO端口,主要是为了恢复中断端口配置*/

enable_irqs();/*开启中断*/

returnIRQ_HANDLED;/*2.6内核返回值一般是这个宏*/

}

/*申请4个中断*/

staticintrequest_irqs(void)

{

intret,i,j;

for(i=0;i<MAX_COLUMN;i++)

{

ret=request_irq(key_info_matrix[i][0].irq_no,

button_irq,SA_INTERRUPT,BUTTONS_DEVICE_NAME,NULL);

if(ret<0)

{

for(j=0;j<i;j++)

{

free_irq(key_info_matrix[j][0].irq_no,NULL);

}

return-EFAULT;

}

}

return0;

}

/*释放中断*/

static__inlinevoidfree_irqs(void)

{

inti;

for(i=0;i<MAX_COLUMN;i++)

{

free_irq(key_info_matrix[i][0].irq_no,NULL);

}

}

中断处理函数在每次中断产生的时候会被调用,因此它的执行时间要尽可能得短。通常中断处理函数只是简单地唤醒等待资源的任务,而复杂且耗时的工作则让这个任务去完成。中断处理函数不能向用户空间发送数据或者接收数据,不能做任何可能发生睡眠的操作,而且不能调用schedule()函数。

为了简单起见,而且考虑到按键操作的时间比较长,在本实例中的中断处理函数button_irq()里,通过调用睡眠函数来消除毛刺信号。读者可以根据以上介绍的对中断处理函数的要求改进该部分代码。

按键扫描函数如下所示。首先根据中断号确定操作按键所在行的位置,然后采用逐列扫描法最终确定操作按键所在的位置。

/*

**进入中断后,扫描铵键码

**返回:按键码(1~16),0xff表示错误

*/

static__inlineunsignedcharbutton_scan(intirq)

{

unsignedcharkey_id=0xff;

unsignedcharcolumn=0xff,row=0xff;

s3c2410_gpio_cfgpin(S3C2410_GPF0,S3C2410_GPF0_INP);/*GPF0*/

s3c2410_gpio_cfgpin(S3C2410_GPF2,S3C2410_GPF2_INP);/*GPF2*/

s3c2410_gpio_cfgpin(S3C2410_GPG3,S3C2410_GPG3_INP);/*GPG3*/

s3c2410_gpio_cfgpin(S3C2410_GPG11,S3C2410_GPG11_INP);/*GPG11*/

switch(irq)

{/*根据irq值确定操作按键所在行的位置*/

caseIRQ_EINT0:

{

column=0;

}

break;

caseIRQ_EINT2:

{

column=1;

}

break;

caseIRQ_EINT11:

{

column=2;

}

break;

caseIRQ_EINT19:

{

column=3;

}

break;

}

if(column!=0xff)

{/*开始逐列扫描,扫描第0列*/

s3c2410_gpio_setpin(S3C2410_GPE11,0);/*将KSCAN0置为低电平*/

s3c2410_gpio_setpin(S3C2410_GPG6,1);

s3c2410_gpio_setpin(S3C2410_GPE13,1);

s3c2410_gpio_setpin(S3C2410_GPG2,1);

if(!s3c2410_gpio_getpin(key_info_matrix[column][0].irq_gpio_port))

{/*观察对应的中断线的输入端口值*/

key_id=key_info_matrix[column][0].key_id;

returnkey_id;

}

/*扫描第1列*/

s3c2410_gpio_setpin(S3C2410_GPE11,1);

s3c2410_gpio_setpin(S3C2410_GPG6,0);/*将KSCAN1置为低电平*/

s3c2410_gpio_setpin(S3C2410_GPE13,1);

s3c2410_gpio_setpin(S3C2410_GPG2,1);

if(!s3c2410_gpio_getpin(key_info_matrix[column][1].irq_gpio_port))

{

key_id=key_info_matrix[column][1].key_id;

returnkey_id;

}

/*扫描第2列*/

s3c2410_gpio_setpin(S3C2410_GPE11,1);

s3c2410_gpio_setpin(S3C2410_GPG6,1);

s3c2410_gpio_setpin(S3C2410_GPE13,0);/*将KSCAN2置为低电平*/

s3c2410_gpio_setpin(S3C2410_GPG2,1);

if(!s3c2410_gpio_getpin(key_info_matrix[column][2].irq_gpio_port))

{

key_id=key_info_matrix[column][2].key_id;

returnkey_id;

}

/*扫描第3列*/

s3c2410_gpio_setpin(S3C2410_GPE11,1);

s3c2410_gpio_setpin(S3C2410_GPG6,1);

s3c2410_gpio_setpin(S3C2410_GPE13,1);

s3c2410_gpio_setpin(S3C2410_GPG2,0);/*将KSCAN3置为低电平*/

if(!s3c2410_gpio_getpin(key_info_matrix[column][3].irq_gpio_port))

{

key_id=key_info_matrix[column][3].key_id;

returnkey_id;

}

}

returnkey_id;

}

以下是read函数接口的实现。首先在按键缓冲中删除已经过时的按键操作信息,接下来,从按键缓冲中读取一条信息(按键ID)并传递给用户层。

/*从缓冲删除过时数据(5s前的按键值)*/

staticvoidremove_timeoutkey(void)

{

unsignedlongtick;

spin_lock_irq(&buffer_lock);/*获得一个自旋锁*/

while(key_buffer.head!=key_buffer.tail)

{

tick=get_tick_count()-key_buffer.jiffy[key_buffer.head];

if(tick<5000)/*5s*/

break;

key_buffer.buf[key_buffer.head]=0;

key_buffer.jiffy[key_buffer.head]=0;

key_buffer.head++;

key_buffer.head&=(MAX_KEY_COUNT-1);

}

spin_unlock_irq(&buffer_lock);/*释放自旋锁*/

}

/*读键盘*/

staticssize_tbutton_read(structfile*filp,

char*buffer,size_tcount,loff_t*f_pos)

{

ssize_tret=0;

remove_timeoutkey();/*删除过时的按键操作信息*/

spin_lock_irq(&buffer_lock);

while((key_buffer.head!=key_buffer.tail)&&(((size_t)ret)<count))

{

put_user((char)(key_buffer.buf[key_buffer.head]),&buffer[ret]);

key_buffer.buf[key_buffer.head]=0;

key_buffer.jiffy[key_buffer.head]=0;

key_buffer.head++;

key_buffer.head&=(MAX_KEY_COUNT-1);

ret++;

}

spin_unlock_irq(&buffer_lock);

returnret;

}

以上介绍了按键驱动程序中的主要内容。

11.6.3按键驱动的测试程序

按键驱动程序的测试程序所下所示。在测试程序中,首先打开按键设备文件和gpio设备(包括4个LED和蜂鸣器)文件,接下来,根据按键的输入值(按键ID)的二进制形式,LEDD9~D12发亮(例如,按下11号按键,则D9、D10和D12会发亮),而蜂鸣器当每次按键时发出声响。

/*butt_test.c*/

#include<sys/stat.h>

#include<fcntl.h>

#include<stdio.h>

#include<sys/time.h>

#include<sys/types.h>

#include<unistd.h>

#include<asm/delay.h>

#include"butt_drv.h"

#include"gpio_drv.h"

main()

{

intbutt_fd,gpios_fd,i;

unsignedcharkey=0x0;

butt_fd=open(BUTTONS_DEVICE_FILENAME,O_RDWR);/*打开按钮设备*/

if(butt_fd==-1)

{

printf("Openbuttondevicebuttonerrr!n");

return0;

}

gpios_fd=open(GPIO_DEVICE_FILENAME,O_RDWR);/*打开GPIO设备*/

if(gpios_fd==-1)

{

printf("Openbuttondevicebuttonerrr!n");

return0;

}

ioctl(butt_fd,0);/*清空键盘缓冲区,后面参数没有意义*/

printf("PressNo.16keytoexitn");

do

{

if(read(butt_fd,&key,1)<=0)/*读键盘设备,得到相应的键值*/

{

continue;

}

printf("KeyValue=%dn",key);

for(i=0;i<LED_NUM;i++)

{

if((key&(1<<i))!=0)

{

ioctl(gpios_fd,LED_D09_SWT+i,LED_SWT_ON);/*LED发亮*/

}

}

ioctl(gpios_fd,BEEP_SWT,BEEP_SWT_ON);/*发声*/

sleep(1);

for(i=0;i<LED_NUM;i++)

{

ioctl(gpios_fd,LED_D09_SWT+i,LED_SWT_OFF);/*LED熄灭*/

}

ioctl(gpios_fd,BEEP_SWT,BEEP_SWT_OFF);

}while(key!=16);/*按16号键则退出*/

close(gpios_fd);

close(butt_fd);

return0;

}

首先编译和加载按键驱动程序,而且要创建设备文件节点。

$makeclean;make/*驱动程序的编译*/

$insmodbutt_dev.ko/*加载buttons设备驱动*/

$cat/proc/devices/*通过这个命令可以查到buttons设备的主设备号*/

$mknod/dev/buttonsc2520/*假设主设备号为252,创建设备文件节点*/

接下来,编译和加载GPIO驱动程序,而且要创建设备文件节点。

$makeclean;make/*驱动程序的编译*/

$insmodgpio_drv.ko/*加载GPIO驱动*/

$cat/proc/devices/*通过这个命令可以查到GPIO设备的主设备号*/

$mknod/dev/gpioc2510/*假设主设备号为251,创建设备文件节点*/

然后编译并运行驱动测试程序。

$arm-linux-gcc–obutt_testbutt_test.c

$./butt_test

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

Windows Embedded Compact 7(简称WinCE)是一种专为嵌入式系统设计的操作系统,具有体积小、效率高、可定制性强的特点。在WinCE中设置自动运行软件,通常是为了满足设备在启动后自动执行特定任务的...

关键字: 嵌入式系统 软件 操作系统

今天,小编将在这篇文章中为大家带来Windows 11系统的有关报道,通过阅读这篇文章,大家可以对Windows 11系统具备清晰的认识,主要内容如下。

关键字: Windows 操作系统

全新随插即用方案简化虚拟化实时IIoT平台的设置

关键字: 计算机模块 IIoT 操作系统

目前,HarmonyOS NEXT星河预览版已经正式面向开发者开放申请,面向鸿蒙原生应用及元服务开发者提供的集成开发环境——DevEco Studio也迎来功能更细化的4.1版本。

关键字: HarmonyOS 操作系统

华为P40是一款备受关注的高端智能手机,搭载了华为自研的鸿蒙操作系统。鸿蒙系统作为华为自主研发的操作系统,具有高度的可定制性和扩展性,能够为用户带来全新的使用体验。本文将详细介绍华为P40鸿蒙系统的升级方法,帮助用户更好...

关键字: 华为P40 智能手机 操作系统

安装Linux操作系统并不复杂,下面是一个大致的步骤指南,以帮助您完成安装。1. 下载Linux发行版:首先,您需要从Linux发行版官方网站下载最新的ISO镜像文件。

关键字: Linux 操作系统 ISO镜像

计算机是由一堆硬件组成的,为了有限的控制这些硬件资源,于是就有了操作系统的产生,操作系统是软件子系统的一部分,是硬件基础上的第一层软件。

关键字: Linux 操作系统 计算机

Linux操作系统是一套免费使用和自由传播的类Unix操作系统,通常被称为GNU/Linux。它是由林纳斯·托瓦兹在1991年首次发布的,并基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。Lin...

关键字: Linux 操作系统

华为鸿蒙系统作为华为推出的全新一代操作系统,自发布以来备受关注。本文将对华为鸿蒙系统的实际体验进行详细评测,旨在帮助读者了解该系统的优缺点。

关键字: 华为 鸿蒙系统 操作系统

随着华为鸿蒙OS系统的发布,越来越多的人开始关注这一全新的操作系统。鸿蒙OS系统的界面设计作为用户体验的重要组成部分,也备受关注。本文将详细介绍鸿蒙操作系统界面的设计理念、特点以及与其他系统的对比。

关键字: 华为鸿蒙 操作系统 界面设计
关闭
关闭