一、编写驱动 1. 编写模块本系列文章所编写的驱动源码仓库,欢迎Star:https://github.com/Mculover666/linux_driver_study。
#include2. 输入设备#include #include #include #include #include #include #include #include struct key_dev { struct device_node *node; int gpio; int debounce_interval; struct timer_list timer; struct input_dev *input; int irq; }; static struct key_dev key; static irqreturn_t key0_handler(int irq, void *dev_id) { struct key_dev *dev = (struct key_dev *)dev_id; // 启动软件定时器, 消抖时间后再去读取 mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->debounce_interval)); dev->timer.data = (volatile long)dev_id; return IRQ_RETVAL(IRQ_HANDLED); } static void timer_handler(unsigned long arg) { int val; struct key_dev *dev = (struct key_dev *)arg; val = gpio_get_value(dev->gpio); if (val == 0) { } else { } } static int key_init(void) { int ret; uint32_t debounce_interval = 0; // 获取设备树节点 key.node = of_find_node_by_name(NULL, "key0"); if (!key.node) { printk("key0 node find fail!n"); return -1; } // 提取gpio key.gpio = of_get_named_gpio(key.node, "key-gpio", 0); if (key.gpio < 0) { printk("find key-gpio propname fail!n"); return -1; } // 初始化gpio ret = gpio_request(key.gpio, "key-gpio"); if (ret < 0) { printk("gpio request fail!n"); return -1; } gpio_direction_input(key.gpio); // 解析设备树,获取去抖时长 if (of_property_read_u32(key.node, "debounce-interval", &debounce_interval) < 0) { printk("find debounce-interval fail!n"); goto error; } // 设置去抖时长 if (debounce_interval) { ret = gpio_set_debounce(key.gpio, key.debounce_interval * 1000); if (ret < 0) { printk("gpio_set_debounce not support, use soft timer!n"); // 设置软件定时器用于消抖 init_timer(&key.timer); key.timer.function = timer_handler; key.debounce_interval = debounce_interval; } } // 获取中断号 key.irq = gpio_to_irq(key.gpio); if (key.irq < 0) { printk("gpio_to_irq fail!n"); goto error; } // 申请中断 ret = request_irq(key.irq, key0_handler, IRQF_TRIGGER_FALLING, "key_irq", &key); if (ret < 0) { printk("irq request fail, ret is %d!n", ret); goto error; } return 0; error: gpio_free(key.gpio); return -1; } static void key_deinit(void) { // 释放中断 free_irq(key.irq, &key); // 释放gpio gpio_free(key.gpio); // 删除定时器 del_timer(&key.timer); } static int __init key_module_init(void) { return key_init(); } static void __exit key_module_exit(void) { key_deinit(); } module_init(key_module_init); module_exit(key_module_exit); MODULE_AUTHOR("Mculover666"); MODULE_LICENSE("GPL");
引入头文件:
#include
(1)添加全局变量:
struct key_dev {
struct device_node *node;
int gpio;
int debounce_interval;
struct timer_list timer;
struct input_dev *input;
int irq;
};
(2)分配/注册输入设备
在按键初始化函数中,编写以下代码。
// 分配输入设备
key.input = input_allocate_device();
if (!key.input) {
printk("irq input_allocate_device fail!n");
goto error;
}
为了使用 setbit 函数,引入头文件:
#include
编写初始化输入设备的代码:
// 初始化输入设备 key.input->name = "keyinput"; set_bit(EV_KEY, key.input->evbit); // 按键事件 set_bit(EV_REP, key.input->evbit); // 重复事件 set_bit(KEY_0, key.input->keybit); // 设置产生哪些按键
注册输入设备:
// 注册输入设备
ret = input_register_device(key.input);
if (ret < 0) {
printk("input_register_device fail!n");
goto error;
}
(3)注销/释放输入设备
在按键去初始化函数中,添加输入设备的注销与释放。
static void key_deinit(void)
{
// 注销输入设备
input_unregister_device(key.input);
// 释放输入设备
input_free_device(key.input);
// 释放中断
free_irq(key.irq, &key);
// 释放gpio
gpio_free(key.gpio);
// 删除定时器
del_timer(&key.timer);
}
3. 上报事件
在消抖定时器超时后,上报事件值:
static void timer_handler(unsigned long arg)
{
int val;
struct key_dev *dev = (struct key_dev *)arg;
static int i = 0;
// 上报输入事件
val = gpio_get_value(dev->gpio);
printk("--->report, i = %dn", i++);
if (val == 0) {
input_report_key(dev->input, KEY_0, 1);
input_sync(dev->input);
input_report_key(dev->input, KEY_0, 0);
input_sync(dev->input);
}
}
在上报按键按下之后,要再上报按键松开,不然Linux内核就会一直以为按键处于按下状态!
4. 编译模块KERNEL_DIR = /home/mculover666/imx6ull/kernel/linux-imx6ull obj-m = input_key.o build: kernel_module kernel_module: $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules clean: $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean二、编写应用程序
#include三、测试 1. 加载模块,查看输入设备节点#include #include #include #include #include static struct input_event event; int main(int argc, char *argv[]) { int fd; int ret; if (argc != 2) { printf("usage: ./test_input_key [device]n"); return -1; } fd = open(argv[1], O_RDWR); if (fd < 0) { printf("open file %s fail!", argv[1]); return -1; } while (1) { memset(&event, 0, sizeof(event)); ret = read(fd, &event, sizeof(event)); switch (event.type) { case EV_SYN: break; case EV_KEY: if (event.code < BTN_MISC) { // 键盘键值 printf("key %d %sn", event.code, event.value ? "press" : "release"); } else { printf("btn %d %sn", event.code, event.value ? "press" : "release"); } break; case EV_REL: break; case EV_ABS: break; case EV_MSC: break; case EV_SW: break; } } }
加载模块之前,查看设备节点,加载模块之后再次查看设备节点:
可以看到新增的设备为 /dev/event4。



