按键是设备中是最常见的人机交互方式,本节中将学习两部分。
(1)如何4个GPIO 16个按键的实现;
(2)Linux input按键驱动开发实例编程;
在常见的按键驱动中,我们可以设计一个按键矩阵,给按键设定一个坐标。如由8个GPIO驱动的4x4矩阵按键,便是通过水平、垂直两个维度对按键进行坐标化。本节讲到的由4GPIO驱动16按键的设计,是GPIO按键驱动按键的一种优化,该方案需要软硬结合,在按键矩阵中添加晶体管,以完成4GPIO驱动16按键的方案。硬件实现如下示:
如上图示,4个GPIO默认都是上拉输入(即默认在高电平状态),当有按键按下时,4个GPIO的某个就会被拉为低电平。读取数值的原理为通过扫描4个GPIO进行按键判断。
扫描方法如下:
(1)KEY1-4 设置为上拉输入,程序依次读取这四个IO的值,此时读取的到值是对应上图E列的按键,当发现某个IO为低电平时,返回此时扫描的序号,该序号可以辨别是哪个按键按下的;
(2)设KEY1输出低电平,KEY2-4设置为上拉输入,程序读取KEY2-4IO的值,此时对应的是A列IO
(3)设KEY2输出低电平,KEY1、KEY3-4设置为上拉输入,程序读取KEY1、KEY3-4IO的值,此时对应的是B列IO
(4)设KEY3输出低电平,KEY1-2、KEY-4设置为上拉输入,程序读取KEY1-2、KEY-4IO的值,此时对应的是C列IO
(5)设KEY4输出低电平,KEY1-3设置为上拉输入,程序读取KEY1-3IO的值,此时对应的是D列IO
static int read_kbd_key(void)
{
int key_num = 0;
int i, j;
/*设GPIO全为上拉输入*/
if (gpio_direction_set(0x00) == -1)
goto err_direction_set;
/*读取E列IO*/
for (j = 0; j < 4; j++) {
if ((gpio_get_value(gpio_pin[j]) & 0x01) == 0)
return key_num;
key_num++;
}
/*读取A-D列IO*/
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) {
if (j == i)
continue;
if ((gpio_get_value(gpio_pin[j]) & 0x01) == 0)
return key_num;
key_num++;
}
}
err_direction_set:
return -1;
}
Linux驱动编程包含两个部分,第一个是对硬件设备初始化;第二个是根据Linux驱动框架填充驱动代码。
对于Linux 3.x有引入设备树及gpiolib的内核,一般会在编译内核的时候已经配置好处理器的所有IO,并使用GPIOLib对所有GPIO进行统一管理,因此如需操作某个GPIO那么可以直接调用内核gpiolib库提供的操作函数库,申请对某IO的控制权。引入GPIOLib的目的是避免多个驱动控制一个IO所带来的混乱,gpiolib需要在编译内核的时候选上支持gpiolib。
输入子系统由驱动层、输入子系统核心、事件处理层三部分组成。一个输入事件,如鼠标移动、键盘按下等通过Driver->Inputcore->Event handler->userspace的顺序到达用户控件的应用程序。
struct input_dev 结构数据说明:
struct input_dev { |
|
const char *name |
设备名 |
char *phys; |
设备文件节点名 |
char *uniq |
全球唯一的ID号 |
struct input_id id |
设备id;用于匹配事件处理层handler |
unsigned long evbit |
表示设备支持的事件类型 |
unsigned long keybit |
表示支持的按键类型 |
unsigned long relbit |
支持相对坐标的值 |
unsigned long absbit |
支持绝对坐标的值 |
……… |
|
相关函数说明
struct input_dev *input_allocate_device |
动态分配一个struct input_dev 结构;返回一个指针 |
input_register_device(*dev) |
注册input设备;dev为inputDev指针 |
input_unregister_device(*dev) |
注销input设备;dev为inputDev指针 |
set_bit(event,bit) |
设置input子系统支持哪些事件;参数为事件类型,设置位。如设置支持按键:set_bit(EV_KEY,input_dev->evbit); |
input_report_key(*dev, code, value) |
上报按键事件及值 |
input_report_rel(*dev, code, value) |
上报相对值 |
input_report_abs (*dev, code, value) |
上报绝对值 |
input_sync(*dev) |
同步上报事件 |
|
|
#include
#include
#include
#include
#include
#include //kmalloc头文件
#include
#include
#include
#include
struct input_dev *inputKeyDev; /*input设备指针*/
//上报示例处理
static int inputDev_IRQHandler()
{
input_report_key(inputKeyDev,KEY_1, !gpio_get_value(KEY_1));
input_sync(inputKeyDev);
return 0;
}
static int __init inputDev_Init(void)
{
int nRet = -1;
/*分配一个input结构*/
inputKeyDev = input_allocate_device();
if (!inputKeyDev) {
nRet = -ENOMEM;
goto iExit0;
}
/*设置支持事件类型*/
set_bit(EV_SYN,inputKeyDev->evbit);
set_bit(EV_KEY,inputKeyDev->evbit);
set_bit(KEY_1,inputKeyDev->keybit);
/*注册到输入子系统中*/
nRet = input_register_device(inputKeyDev);
if(nRet)
goto iExit0;
return nRet;
iExit0:
if(inputKeyDev!=NULL) input_free_device(inputKeyDev);
return nRet;
}
static void __exit inputDev_Exit(void)
{
/*注销输入子系统设备*/
input_unregister_device(inputKeyDev);
/*释放申请的内存*/
input_free_device(inputKeyDev);
}
module_init(inputDev_Init);
module_exit(inputDev_Exit);
MODULE_LICENSE("GPL");
/*****************************************************************
* 包含头文件
******************************************************************/
#include
#include
#include
#include
#include
#include //kmalloc头文件
#include
#include
#include
#include
#include
#include
#include
#include
/*****************************************************************
* 宏定义(仅在当前C文件使用的宏定义写在当前C文件中,否则需写在H文件中)
******************************************************************/
#define IKEY_PIN_1 (GPIOD(16))
#define IKEY_PIN_2 (GPIOD(17))
#define IKEY_PIN_3 (GPIOD(19))
#define IKEY_PIN_4 (GPIOD(22))
#define INPUT_IKEY_NAME "iKey"
#define IKEY_TIMEOUT_MS (80) /*80ms扫描一次按键*/
/*****************************************************************
* 结构定义(仅在当前C文件使用的结构体写在当前C文件中,否则需写在H文件中)
******************************************************************/
struct _iKeyInfoSt{
int iKeyNum; /*保存按键值*/
int iKeyUpFlag; /*是否上报*/
int nQueWaiting; /*等待队列条件*/
wait_queue_head_t iwaitQue; /*等待队列*/
struct hrtimer ihrtimer; /*高精度定时器*/
struct timer_list iKeyTimer; /*轮询定制器,每隔50ms轮询一次键盘*/
struct work_struct iKeyDectWork; /*键盘检测工作队列*/
struct delayed_work iKeyDelayWork;/*键盘检测工作队列*/
struct input_dev *inputKeyDev; /*input设备指针*/
};
/*****************************************************************
* 全局变量定义
******************************************************************/
struct _iKeyInfoSt *piKeyInfoSt = NULL;
static unsigned int igpio_pin[] = {
IKEY_PIN_1, IKEY_PIN_2, IKEY_PIN_3, IKEY_PIN_4,
};
/*键盘对应的上报的键值*/
static u32 iKeycodes[16] = {
KEY_DOWN,
KEY_F1,
KEY_HOME,
KEY_UP,
KEY_8,
KEY_0,
KEY_1,
KEY_5,
KEY_BACKSPACE,
KEY_2,
KEY_6,
KEY_9,
KEY_3,
KEY_4,
KEY_7,
KEY_KPASTERISK
};
/*****************************************************************
* 静态变量定义
******************************************************************/
/*****************************************************************
* 外部变量声明(如果全局变量没有在其它的H文件声明,引用时需在此处声明,
*如果已在其它H文件声明,则只需包含此H文件即可)
******************************************************************/
/*****************************************************************
* 函数原型声明
******************************************************************/
static enum hrtimer_restart iKey_hrtimerHander(struct hrtimer *timer)
{
piKeyInfoSt->nQueWaiting = 1;
wake_up(&piKeyInfoSt->iwaitQue);
return HRTIMER_NORESTART;
}
static int iKey_gpioSetDirection(unsigned int ndir)
{
unsigned int i = 0;
for (i = 0; i < 4; i++) {
if (ndir & BIT(i)) {
if (gpio_direction_output(igpio_pin[i], 0)) {
return -1;
}
} else {
if (gpio_direction_input(igpio_pin[i])) {
return -1;
}
}
}
/*启动高精度定时器,结合等待队列延时1ms*/
hrtimer_start(&piKeyInfoSt->ihrtimer,ktime_set(0,1500*1000),HRTIMER_MODE_REL);
wait_event(piKeyInfoSt->iwaitQue,piKeyInfoSt->nQueWaiting);
piKeyInfoSt->nQueWaiting = 0;
return 0;
}
static int iKey_readValue(void)
{
int key_num = 0;
int i, j;
if (iKey_gpioSetDirection(0x00) == -1)
goto iExit;
for (j = 0; j < 4; j++)
{
if ((gpio_get_value(igpio_pin[j]) & 0x01) == 0)
return key_num;
key_num++;
}
for (i = 0; i < 4; i++) {
if (iKey_gpioSetDirection(BIT(i)) == -1)
goto iExit;
for (j = 0; j < 4; j++)
{
if (j == i)
continue;
if ((gpio_get_value(igpio_pin[j]) & 0x01) == 0)
return key_num;
key_num++;
}
}
iExit:
return -1;
}
static void iKey_delayWorkCallBack(struct work_struct *data)
{
int key_num = 0;
key_num = iKey_readValue();
if (key_num == piKeyInfoSt->iKeyNum)
{
input_report_key(piKeyInfoSt->inputKeyDev, iKeycodes[piKeyInfoSt->iKeyNum], 1);
input_sync(piKeyInfoSt->inputKeyDev);
piKeyInfoSt->iKeyUpFlag = 1;
}
}
static void iKey_dectWorkCallBack(struct work_struct *data)
{
int key_num = 0;
/*扫描读取按键值*/
key_num = iKey_readValue();
if (key_num != -1)
{
if(piKeyInfoSt->iKeyUpFlag!=1) piKeyInfoSt->iKeyNum = key_num;
/*延时20ms消抖*/
schedule_delayed_work(&piKeyInfoSt->iKeyDelayWork, msecs_to_jiffies(20));
}
else if(piKeyInfoSt->iKeyUpFlag == 1)
{
input_report_key(piKeyInfoSt->inputKeyDev, iKeycodes[piKeyInfoSt->iKeyNum], 0);
input_sync(piKeyInfoSt->inputKeyDev);
piKeyInfoSt->iKeyUpFlag = 0;
}
mod_timer(&piKeyInfoSt->iKeyTimer, jiffies + msecs_to_jiffies(IKEY_TIMEOUT_MS));
}
void iKey_timerCallBack(unsigned long arg)
{
schedule_work(&piKeyInfoSt->iKeyDectWork);
}
static int iKey_gpioInit()
{
int i = 0;
char chlabel[16] ={0};
for (i = 0; i < 4; i++)
{
if (gpio_is_valid(igpio_pin[i]))
{
memset(chlabel, 0, 16);
sprintf(chlabel, "igpio_%d", i);
if (gpio_request(igpio_pin[i], chlabel))
{
pr_err("gpio_request failed [%d]\n", igpio_pin[i]);
goto iExit;
}
}
else
{
pr_err("wrong gpio num [%d]\n", igpio_pin[i]);
goto iExit;
}
}
return 0;
iExit:
pr_err("gpio init failed\n");
for (; i > 0; i--) {
gpio_free(igpio_pin[i - 1]);
}
return -1;
}
static void iKey_gpioExit(void)
{
unsigned int i = 0;
for (i = 0; i < 4; i++)
{
gpio_free(igpio_pin[i]);
}
}
static int __init iKey_Init(void)
{
int ni = 0;
int nRet = -1;
/*分配内存保存结构*/
piKeyInfoSt = kzalloc(sizeof(struct _iKeyInfoSt), GFP_KERNEL);
if (!piKeyInfoSt){
nRet = -ENOMEM;
goto iExit0;
}
/*分配一个input结构*/
piKeyInfoSt->inputKeyDev = input_allocate_device();
if (!piKeyInfoSt->inputKeyDev) {
nRet = -ENOMEM;
goto iExit0;
}
/*填充对应结构的数据*/
piKeyInfoSt->inputKeyDev->name = INPUT_IKEY_NAME;
piKeyInfoSt->inputKeyDev->phys = "iKeyPhys";
piKeyInfoSt->inputKeyDev->id.bustype = BUS_HOST;
piKeyInfoSt->inputKeyDev->id.vendor = 0x0001;
piKeyInfoSt->inputKeyDev->id.product = 0x0001;
piKeyInfoSt->inputKeyDev->id.version = 0x0100;
/*设置支持事件类型*/
set_bit(EV_SYN,piKeyInfoSt->inputKeyDev->evbit);
set_bit(EV_KEY,piKeyInfoSt->inputKeyDev->evbit);
/*设置支持哪些按键*/
for (ni = 0; ni < 16; ni++)
set_bit(iKeycodes[ni], piKeyInfoSt->inputKeyDev->keybit);
/*注册到输入子系统中*/
nRet = input_register_device(piKeyInfoSt->inputKeyDev);
if(nRet)
goto iExit0;
/*使用高精度定时器+等待队列进行延时*/
piKeyInfoSt->iKeyUpFlag = 0;
piKeyInfoSt->nQueWaiting = 0;
init_waitqueue_head(&piKeyInfoSt->iwaitQue);
hrtimer_init(&piKeyInfoSt->ihrtimer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);
piKeyInfoSt->ihrtimer.function = iKey_hrtimerHander; /* 设置回调函数 */
/*初始化工作队列*/
INIT_WORK(&piKeyInfoSt->iKeyDectWork, iKey_dectWorkCallBack);
INIT_DELAYED_WORK(&piKeyInfoSt->iKeyDelayWork, iKey_delayWorkCallBack);
/*Linux内核定时器初始化*/
init_timer(&piKeyInfoSt->iKeyTimer);
piKeyInfoSt->iKeyTimer.function= iKey_timerCallBack;
piKeyInfoSt->iKeyTimer.expires = jiffies + msecs_to_jiffies(IKEY_TIMEOUT_MS);
add_timer(&piKeyInfoSt->iKeyTimer);
/*初始化GPIO*/
nRet = iKey_gpioInit();
if(nRet<0)
goto iExit0;
return 0;
iExit0:
if(piKeyInfoSt->inputKeyDev!=NULL) input_free_device(piKeyInfoSt->inputKeyDev);
if(piKeyInfoSt!=NULL) kfree(piKeyInfoSt);
return nRet;
}
static void __exit iKey_Exit(void)
{
/*注销输入子系统设备*/
input_unregister_device(piKeyInfoSt->inputKeyDev);
/*删除定时器*/
del_timer(&piKeyInfoSt->iKeyTimer);
/*释放已申请的GPIO*/
iKey_gpioExit();
/*删除工作队列*/
cancel_work_sync(&piKeyInfoSt->iKeyDectWork);
cancel_delayed_work(&piKeyInfoSt->iKeyDelayWork);
/*删除高精度定时器*/
hrtimer_cancel(&piKeyInfoSt->ihrtimer);
/*释放申请的内存*/
if(piKeyInfoSt->inputKeyDev!=NULL) input_free_device(piKeyInfoSt->inputKeyDev);
if(piKeyInfoSt!=NULL) kfree(piKeyInfoSt);
}
module_init(iKey_Init);
module_exit(iKey_Exit);
MODULE_LICENSE("GPL");