49 使用linux内核源码里的矩阵键盘驱动<GPIO driven matrix keypad support>
2017-06-28 15:42
1061 查看
这个设备驱动适用于,矩阵键盘的每行,每列都是接到一个IO口, 行线接的IO口有中断功能.
需要在linux内核配置里选上相关的配置。在内核源码目录下:
使用新内核启动后,可以查看出设备驱动是否已选择上:
/sys/bus/platform/drivers/目录下应有”matrix-keypad”目录
驱动源码在”drivers/input/keyboard/matrix_keypad.c”, 里面是一个平台驱动,我们只要写平台设备描述硬件的资源与此驱动匹配即可.
/////////////////////////////////////
//通过probe函数,可以确定我们写平台设备时只需通过platform_data成员提供平台驱动所需的信息,无需再提供resource.
//再确定结构体matrix_keypad_platform_data的每个成员的作用即可,如不清楚具体用途,可以在驱动代码里通过查看对成员值的访问推出用途.
/////////////////////
行线接: PA7, PA8, PA9, PA10
列线接: PA20, PA21, PC4, PC7
mypdev.c
/////////////////////////////////////////////////////////////
设备驱动的工作原理:
1) 设备驱动的probe函数
2) 当设备文件open地, 触发调用matrix_keypad_start函数
3) 按键的扫描
4) 行线的中断处理函数
需要在linux内核配置里选上相关的配置。在内核源码目录下:
make menuconfig ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- Device Drivers ---> Input device support ---> [*] Keyboards ---> <*> GPIO driven matrix keypad support
选择上后,再编内核,再使用新的内核镜像启动系统
使用新内核启动后,可以查看出设备驱动是否已选择上:
/sys/bus/platform/drivers/目录下应有”matrix-keypad”目录
驱动源码在”drivers/input/keyboard/matrix_keypad.c”, 里面是一个平台驱动,我们只要写平台设备描述硬件的资源与此驱动匹配即可.
488 static struct platform_driver matrix_keypad_driver = { 489 .probe = matrix_keypad_probe, 490 .remove = __devexit_p(matrix_keypad_remove), 491 .driver = { 492 .name = "matrix-keypad", 493 .owner = THIS_MODULE, 494 #ifdef CONFIG_PM 495 .pm = &matrix_keypad_pm_ops, 496 #endif 497 }, 498 }; 499 module_platform_driver(matrix_keypad_driver); //通过阅读平台驱动的probe函数,可得知我们写的平台设备应提供具本哪些硬件信息. 378 static int __devinit matrix_keypad_probe(struct platform_device *pdev) 379 { 380 const struct matrix_keypad_platform_data *pdata; //平台设备提供的platform_data 381 const struct matrix_keymap_data *keymap_data; 382 struct matrix_keypad *keypad; 383 struct input_dev *input_dev; 384 unsigned short *keycodes; 385 unsigned int row_shift; 386 int err; 388 pdata = pdev->dev.platform_data; ... 394 keymap_data = pdata->keymap_data; ... 433 matrix_keypad_build_keymap(keymap_data, row_shift, 434 input_dev->keycode, input_dev->keybit); //从keymap_data里分解出行列键对应的键码 ... 439 err = init_matrix_gpio(pdev, keypad); //注册行线的中断号 ... 443 err = input_register_device(keypad->input_dev); //输入设备对象注册 ... 456 return err; 457 }
/////////////////////////////////////
//通过probe函数,可以确定我们写平台设备时只需通过platform_data成员提供平台驱动所需的信息,无需再提供resource.
//再确定结构体matrix_keypad_platform_data的每个成员的作用即可,如不清楚具体用途,可以在驱动代码里通过查看对成员值的访问推出用途.
"include/linux/input/matrix_keypad.h" #define KEY(row, col, val) ((((row) & (MATRIX_MAX_ROWS - 1)) << 24) |\ (((col) & (MATRIX_MAX_COLS - 1)) << 16) |\ ((val) & 0xffff)) #define KEY_ROW(k) (((k) >> 24) & 0xff) #define KEY_COL(k) (((k) >> 16) & 0xff) #define KEY_VAL(k) ((k) & 0xffff) #define MATRIX_SCAN_CODE(row, col, row_shift) (((row) << (row_shift)) + (col)) struct matrix_keymap_data { const uint32_t *keymap; //装载按键对应的键码数组, 注意每个键码需要使用宏KEY来写。也就是一个32位数据里,行,列,键码各占用8, 8, 16位. unsigned int keymap_size; //键码数组的元素个数 }; struct matrix_keypad_platform_data { const struct matrix_keymap_data *keymap_data; //键码数据对象的首地址 const unsigned int *row_gpios; //行线用的IO口 const unsigned int *col_gpios; //列线用的IO口 unsigned int num_row_gpios; //多少个行线 unsigned int num_col_gpios; //多少个列线 unsigned int col_scan_delay_us; //扫描列线时间隔时间 unsigned int debounce_ms; //防抖动的间隔时间 unsigned int clustered_irq; //行线是否共用一个中断, 设0则每个行线的中断是独立的 unsigned int clustered_irq_flags; bool active_low; //键按下时,行线是否为低电平 bool wakeup; bool no_autorepeat; //按键按下时是否重复提交按键, 设1就是不重复,设0重复 };
/////////////////////
行线接: PA7, PA8, PA9, PA10
列线接: PA20, PA21, PC4, PC7
mypdev.c
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/input.h> #include <mach/gpio.h> #include <linux/input/matrix_keypad.h> u32 keys[] = { KEY(0, 0, KEY_R), //第0行,第0列的键的键码为KEY_R KEY(0, 1, KEY_E), KEY(0, 2, KEY_B), KEY(0, 3, KEY_O), KEY(1, 0, KEY_T), KEY(1, 1, KEY_ENTER), KEY(1, 2, KEY_SPACE), KEY(1, 3, KEY_L), KEY(2, 0, KEY_S), KEY(2, 1, KEY_A), KEY(2, 2, KEY_B), KEY(2, 3, KEY_C), KEY(3, 0, KEY_UP), KEY(3, 1, KEY_DOWN), KEY(3, 2, KEY_LEFT), KEY(3, 3, KEY_RIGHT), }; //键码数组 struct matrix_keymap_data mdata = { .keymap = keys, .keymap_size = ARRAY_SIZE(keys), }; u32 rows[] = {GPIOA(7), GPIOA(8), GPIOA(9), GPIOA(10)}; //行线的IO口 u32 cols[] = {GPIOA(20), GPIOA(21), GPIOC(4), GPIOC(7)}; //列线的IO口 struct matrix_keypad_platform_data pdata = { .keymap_data = &mdata, .row_gpios = rows, .col_gpios = cols, .num_row_gpios = ARRAY_SIZE(rows), .num_col_gpios = ARRAY_SIZE(cols), .col_scan_delay_us = 100, .debounce_ms = 10, .active_low = 1, .no_autorepeat = 0, }; struct platform_device mypdev = { .name = "matrix-keypad", .id = -1, .dev = { .platform_data = &pdata, }, }; module_driver(mypdev, platform_device_register, platform_device_unregister); MODULE_LICENSE("GPL");
/////////////////////////////////////////////////////////////
设备驱动的工作原理:
1) 设备驱动的probe函数
static int __devinit matrix_keypad_probe(struct platform_device *pdev) { const struct matrix_keypad_platform_data *pdata; const struct matrix_keymap_data *keymap_data; struct matrix_keypad *keypad; //设备驱动对每个匹配上的设备准备的数据 struct input_dev *input_dev; pdata = pdev->dev.platform_data; keymap_data = pdata->keymap_data; input_dev = input_allocate_device(); //发配输入设备对象的空间 input_dev->open = matrix_keypad_start; //当设备文件open时,触发调用matrix_keypad_start input_dev->close = matrix_keypad_stop; //当设备文件close时,触发调用 INIT_DELAYED_WORK(&keypad->work, matrix_keypad_scan); //初始化工作任务,当工作任务keypad->work得到处理时,matrix_keypad_scan函数得到调用. 而且这个工作任务是指定多久后才会执行的. matrix_keypad_build_keymap(keymap_data, row_shift, input_dev->keycode, input_dev->keybit); //从keymap_data里分解出行,列,键值数据 err = init_matrix_gpio(pdev, keypad); //请求IO口,列线输出!active_low电平.申请行线的双边沿中断. 中断处理函数是matrix_keypad_interrupt. 而且关闭所有的行线的中断功能(disable_irq_nosync). err = input_register_device(keypad->input_dev); //输入设备对象注册 ... }
2) 当设备文件open地, 触发调用matrix_keypad_start函数
static int matrix_keypad_start(struct input_dev *dev) { struct matrix_keypad *keypad = input_get_drvdata(dev); keypad->stopped = false; ... schedule_delayed_work(&keypad->work, 0); //也就马上调用工作任务的处理函数matrix_keypad_scan return 0; }
3) 按键的扫描
static void matrix_keypad_scan(struct work_struct *work) //工作任务的处理函数 { struct matrix_keypad *keypad = container_of(work, struct matrix_keypad, work.work); struct input_dev *input_dev = keypad->input_dev; const struct matrix_keypad_platform_data *pdata = keypad->pdata; uint32_t new_state[MATRIX_MAX_COLS]; int row, col, code; activate_all_cols(pdata, false); //把所有的列线改为输入功能 memset(new_state, 0, sizeof(new_state)); for (col = 0; col < pdata->num_col_gpios; col++) { activate_col(pdata, col, true);//把指定的列线改为输出 //检查行线的电平, 按键的状态和数组new_state存放起来 for (row = 0; row < pdata->num_row_gpios; row++) new_state[col] |= row_asserted(pdata, row) ? (1 << row) : 0; activate_col(pdata, col, false); //把所有的列线改为输入功能 } //把按键上次状与现在的状态进行对比, 如果不一样就汇报键数据 for (col = 0; col < pdata->num_col_gpios; col++) { uint32_t bits_changed; bits_changed = keypad->last_key_state[col] ^ new_state[col]; if (bits_changed == 0) continue; for (row = 0; row < pdata->num_row_gpios; row++) { if ((bits_changed & (1 << row)) == 0) continue; code = MATRIX_SCAN_CODE(row, col, keypad->row_shift); input_event(input_dev, EV_MSC, MSC_SCAN, code); input_report_key(input_dev, keypad->keycodes[code], new_state[col] & (1 << row)); } } input_sync(input_dev); memcpy(keypad->last_key_state, new_state, sizeof(new_state)); activate_all_cols(pdata, true); //把所有列线改为输出 /* Enable IRQs again */ spin_lock_irq(&keypad->lock); keypad->scan_pending = false; enable_row_irqs(keypad); //恢复行线中断 spin_unlock_irq(&keypad->lock); }
4) 行线的中断处理函数
static irqreturn_t matrix_keypad_interrupt(int irq, void *id) { struct matrix_keypad *keypad = id; unsigned long flags; spin_lock_irqsave(&keypad->lock, flags); /* * See if another IRQ beaten us to it and scheduled the * scan already. In that case we should not try to * disable IRQs again. */ if (unlikely(keypad->scan_pending || keypad->stopped)) goto out; disable_row_irqs(keypad); //又关闭行线的中断功能,直到在matrix_keypad_scan函数触发时才会重新打开中断功能 keypad->scan_pending = true; schedule_delayed_work(&keypad->work, msecs_to_jiffies(keypad->pdata->debounce_ms)); //安排工作任务在平台数据里设的debounce_ms后执行任务处理函数matrix_keypad_scan out: spin_unlock_irqrestore(&keypad->lock, flags); return IRQ_HANDLED; }
相关文章推荐
- 50 使用linux内核源码里的led驱动<LED Support for GPIO connected LEDs>
- 47 使用linux内核源码里的按键驱动<GPIO Buttons>
- 48 使用linux内核源码里的按键轮询驱动<Polled GPIO buttons>
- Linux内核驱动GPIO的使用
- Linux内核驱动GPIO的使用
- Linux内核驱动GPIO的使用
- Linux内核驱动GPIO的使用
- Linux内核驱动之GPIO子系统(一)GPIO的使用
- 使用@Controller注解为什么要配置<mvc:annotation-driven />
- <自己动手写操作系统>第三章——pmtest3源码解析:使用LDT
- Linux内核驱动之GPIO子系统(一)GPIO的使用
- 解决 spring mvc 3.0 结合 hibernate3.2 使用<tx:annotation-driven>声明式事务无法提交的问题
- Linux内核部件分析<6> 设备驱动模型之device
- 使用@Controller注解为什么要配置<mvc:annotation-driven />
- Linux内核驱动之GPIO子系统(一)GPIO的使用
- Linux内核驱动之GPIO子系统GPIO的使用
- Linux内核部件分析<9> 设备驱动模型之device-driver
- 在VC中使用Matrix<LIB>C++数学库
- struts2系列七之使用ModelDriven<User>
- 解决 spring mvc 3.0 结合 hibernate3.2 使用<tx:annotation-driven>声明式事务无法提交的问题