目录

  • Linux下按键驱动原理(使用原子操作)
  • 硬件原理图分析
  • 实验程序编写
    • 修改设备树文件
    • 按键驱动程序编写
    • 编写测试APP(循环读取按键值)
  • 运行测试
    • 编译驱动程序和测试APP
    • 运行测试(while循环导致CPU占用率很高,后面使用阻塞非阻塞IO处理)

在前几章我们都是使用的GPIO 输出功能,还没有用过GPIO 输入功能,本章我们就来学习一下如果在Linux 下编写GPIO 输入驱动程序。I.MX6U-ALPHA 开发板上有一个按键,我们就使用此按键来完成GPIO 输入驱动程序,同时利用第四十七章讲的原子操作来对按键值进行保护。

Linux下按键驱动原理(使用原子操作)

按键驱动和LED 驱动原理上来讲基本都是一样的,都是操作GPIO,只不过一个是读取GPIO 的高低电平,一个是从GPIO 输出高低电平。本章我们实现按键输入,在驱动程序中使用一个整形变量来表示按键值,应用程序通过read 函数来读取按键值,判断按键有没有按下。在这里,这个保存按键值的变量就是个共享资源,驱动程序要向其写入按键值,应用程序要读取按键值。所以我们要对其进行保护,对于整形变量而言我们首选的就是原子操作,使用原子操作对变量进行赋值以及读取。Linux 下的按键驱动原理很简单,接下来开始编写驱动。

注意,本章例程只是为了演示Linux 下GPIO 输入驱动的编写,实际中的按键驱动并不会采用本章中所讲解的方法,Linux 下的input 子系统专门用于输入设备!

硬件原理图分析

本章实验硬件原理图参考15.2 小节即可。

实验程序编写

本实验对应的例程路径为:开发板光盘-> 2、Linux 驱动例程-> 11_key。

修改设备树文件

1、添加pinctrl 节点

I.MX6U-ALPHA 开发板上的KEY 使用了UART1_CTS_B 这个PIN,打开imx6ull-alientek-emmc.dts,在iomuxc 节点的imx6ul-evk 子节点下创建一个名为“pinctrl_key”的子节点,节点内容如下所示:

1 pinctrl_key: keygrp {2   fsl,pins = <
3   MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* KEY0 */
4   >;
5 };

第3 行,将GPIO_IO18 这个PIN 复用为GPIO1_IO18,电气属性为0xF080。

2、添加KEY 设备节点

在根节点“/”下创建KEY 节点,节点名为“key”,节点内容如下:

1 key {2   #address-cells = <1>;
3   #size-cells = <1>;
4   compatible = "atkalpha-key";
5   pinctrl-names = "default";
6   pinctrl-0 = <&pinctrl_key>;
7   key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
8   status = "okay";
9 };

第6 行,pinctrl-0 属性设置KEY 所使用的PIN 对应的pinctrl 节点。
第7 行,key-gpio 属性指定了KEY 所使用的GPIO。

3、检查PIN 是否被其他外设使用

在本章实验中蜂鸣器使用的PIN 为UART1_CTS_B,因此先检查PIN 为UART1_CTS_B 这个PIN 有没有被其他的pinctrl 节点使用,如果有使用的话就要屏蔽掉,然后再检查GPIO1_IO18这个GPIO 有没有被其他外设使用,如果有的话也要屏蔽掉。

设备树编写完成以后使用“make dtbs”命令重新编译设备树,然后使用新编译出来的imx6ull-alientek-emmc.dtb 文件启动Linux 系统。启动成功以后进入“/proc/device-tree”目录中查看“key”节点是否存在,如果存在的话就说明设备树基本修改成功(具体还要驱动验证),结果如图49.3.1.1 所示:

按键驱动程序编写

设备树准备好以后就可以编写驱动程序了,新建名为“11_key”的文件夹,然后在11_key文件夹里面创建vscode 工程,工作区命名为“key”。工程创建好以后新建key.c 文件,在key.c里面输入如下内容:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名     : key.c
作者      : 左忠凯
版本      : V1.0
描述      : Linux按键输入驱动实验
其他      : 无
论坛      : www.openedv.com
日志      : 初版V1.0 2019/7/18 左忠凯创建
***************************************************************/
#define KEY_CNT         1       /* 设备号个数    */
#define KEY_NAME        "key" /* 名字       *//* 定义按键值 */
#define KEY0VALUE       0XF0    /* 按键值      */
#define INVAKEY         0X00    /* 无效的按键值  *//* key设备结构体 */
struct key_dev{dev_t devid;         /* 设备号   */struct cdev cdev;        /* cdev     */struct class *class;  /* 类        */struct device *device;    /* 设备    */int major;               /* 主设备号   */int minor;              /* 次设备号   */struct device_node  *nd; /* 设备节点 */int key_gpio;            /* key所使用的GPIO编号        */atomic_t keyvalue;        /* 按键值      */
};struct key_dev keydev;        /* key设备 *//** @description    : 初始化按键IO,open函数打开驱动的时候*                   初始化按键所使用的GPIO引脚。* @param         : 无* @return       : 无*/
static int keyio_init(void)
{keydev.nd = of_find_node_by_path("/key");if (keydev.nd== NULL) {return -EINVAL;}keydev.key_gpio = of_get_named_gpio(keydev.nd ,"key-gpio", 0);//0是索引,在设备树里的第一个if (keydev.key_gpio < 0) {printk("can't get key0\r\n");//被占用return -EINVAL;}printk("key_gpio=%d\r\n", keydev.key_gpio);/* 初始化key所使用的IO */gpio_request(keydev.key_gpio, "key0");    /* 请求IO */gpio_direction_input(keydev.key_gpio);    /* 设置为输入 */return 0;
}/** @description      : 打开设备* @param - inode     : 传递给驱动的inode* @param - filp   : 设备文件,file结构体有个叫做private_data的成员变量*                       一般在open的时候将private_data指向设备结构体。* @return             : 0 成功;其他 失败*/
static int key_open(struct inode *inode, struct file *filp)
{int ret = 0;filp->private_data = &keydev;     /* 设置私有数据 */ret = keyio_init();                /* 初始化按键IO */if (ret < 0) {return ret;}return 0;
}/** @description      : 从设备读取数据 * @param - filp  : 要打开的设备文件(文件描述符)* @param - buf    : 返回给用户空间的数据缓冲区* @param - cnt  : 要读取的数据长度* @param - offt  : 相对于文件首地址的偏移* @return             : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{int ret = 0;int value;struct key_dev *dev = filp->private_data;if (gpio_get_value(dev->key_gpio) == 0) {         /* key0按下 */while(!gpio_get_value(dev->key_gpio));       /* 等待按键释放 */atomic_set(&dev->keyvalue, KEY0VALUE);       //设置按键值为0} else {   atomic_set(&dev->keyvalue, INVAKEY);     /* 无效的按键值 */}value = atomic_read(&dev->keyvalue);ret = copy_to_user(buf, &value, sizeof(value));//发送给上层应用return ret;
}/** @description      : 向设备写数据 * @param - filp   : 设备文件,表示打开的文件描述符* @param - buf     : 要写给设备写入的数据* @param - cnt     : 要写入的数据长度* @param - offt  : 相对于文件首地址的偏移* @return             : 写入的字节数,如果为负值,表示写入失败*/
static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/** @description      : 关闭/释放设备* @param - filp   : 要关闭的设备文件(文件描述符)* @return             : 0 成功;其他 失败*/
static int key_release(struct inode *inode, struct file *filp)
{return 0;
}/* 设备操作函数 */
static struct file_operations key_fops = {.owner = THIS_MODULE,.open = key_open,.read = key_read,.write = key_write,.release =    key_release,
};/** @description : 驱动入口函数* @param       : 无* @return       : 无*/
static int __init mykey_init(void)
{/* 初始化原子变量 */atomic_set(&keydev.keyvalue, INVAKEY);/* 注册字符设备驱动 *//* 1、创建设备号 */if (keydev.major) {      /*  定义了设备号 */keydev.devid = MKDEV(keydev.major, 0);register_chrdev_region(keydev.devid, KEY_CNT, KEY_NAME);} else {                        /* 没有定义设备号 */alloc_chrdev_region(&keydev.devid, 0, KEY_CNT, KEY_NAME);  /* 申请设备号 */keydev.major = MAJOR(keydev.devid); /* 获取分配号的主设备号 */keydev.minor = MINOR(keydev.devid);    /* 获取分配号的次设备号 */}/* 2、初始化cdev */keydev.cdev.owner = THIS_MODULE;cdev_init(&keydev.cdev, &key_fops);/* 3、添加一个cdev */cdev_add(&keydev.cdev, keydev.devid, KEY_CNT);/* 4、创建类 */keydev.class = class_create(THIS_MODULE, KEY_NAME);if (IS_ERR(keydev.class)) {return PTR_ERR(keydev.class);}/* 5、创建设备 */keydev.device = device_create(keydev.class, NULL, keydev.devid, NULL, KEY_NAME);if (IS_ERR(keydev.device)) {return PTR_ERR(keydev.device);}return 0;
}/** @description  : 驱动出口函数* @param       : 无* @return       : 无*/
static void __exit mykey_exit(void)
{/* 注销字符设备驱动 */cdev_del(&keydev.cdev);/*  删除cdev */unregister_chrdev_region(keydev.devid, KEY_CNT); /* 注销设备号 */device_destroy(keydev.class, keydev.devid);class_destroy(keydev.class);
}module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

第36~46 行,结构体key_dev 为按键的设备结构体,第45 行的原子变量keyvalue 用于记录按键值。
第56~74 行,函数keyio_init 用于初始化按键,从设备树中获取按键的gpio 信息,然后设置为输入。将按键的初始化代码提取出来,将其作为独立的一个函数有利于提高程序的模块化设计。
第83~94 行,key_open 函数通过调用keyio_init 函数来始化按键所使用的IO,应用程序每次打开按键驱动文件的时候都会初始化一次按键IO。
第104~120 行,key_read 函数,应用程序通过read 函数读取按键值的时候此函数就会执行。第110 行读取按键IO 的电平,如果为0 的话就表示按键按下了,如果按键按下的话第111 行就等待按键释放。按键释放以后标记按键值为KEY0VALUE。
第135~171 行,驱动入口函数,第138 行调用atomic_set 函数初始化原子变量默认为无效值。
第178~186 行,驱动出口函数。

key.c 文件代码很简单,重点就是key_read 函数读取按键值,要对keyvalue 进行保护。

编写测试APP(循环读取按键值)

新建名为keyApp.c 的文件,然后输入如下所示内容:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名     : keyApp.c
作者      : 左忠凯
版本      : V1.0
描述      : 按键输入测试应用程序
其他      : 无
使用方法     :./keyApp /dev/key
论坛      : www.openedv.com
日志      : 初版V1.0 2019/1/30 左忠凯创建
***************************************************************//* 定义按键值 */
#define KEY0VALUE   0XF0
#define INVAKEY     0X00/** @description       : main主程序* @param - argc   : argv数组元素个数* @param - argv    : 具体参数* @return            : 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd, ret;char *filename;int keyvalue;if(argc != 2){printf("Error Usage!\r\n");return -1;}filename = argv[1];/* 打开key驱动 */fd = open(filename, O_RDWR);if(fd < 0){printf("file %s open failed!\r\n", argv[1]);return -1;}/* 循环读取按键值数据! */while(1) {read(fd, &keyvalue, sizeof(keyvalue));if (keyvalue == KEY0VALUE) {  /* KEY0 */printf("KEY0 Press, value = %#X\r\n", keyvalue);   /* 按下 */}}ret= close(fd); /* 关闭文件 */if(ret < 0){printf("file %s close failed!\r\n", argv[1]);return -1;}return 0;
}

第51~56 行,循环读取/dev/key 文件,也就是循环读取按键值,并且将按键值打印出来。

运行测试

编译驱动程序和测试APP

1、编译驱动程序
编写Makefile 文件,本章实验的Makefile 文件和第四十章实验基本一样,只是将obj-m 变量的值改为key.o,Makefile 内容如下所示:

KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)obj-m := key.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modulesclean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

第4 行,设置obj-m 变量的值为key.o。
输入如下命令编译出驱动模块文件:

make -j32

编译成功以后就会生成一个名为“key.ko”的驱动模块文件。
2、编译测试APP
输入如下命令编译测试keyApp.c 这个测试程序:

arm-linux-gnueabihf-gcc keyApp.c -o keyApp

编译成功以后就会生成keyApp 这个应用程序。

运行测试(while循环导致CPU占用率很高,后面使用阻塞非阻塞IO处理)

将上一小节编译出来的key.ko 和keyApp 这两个文件拷贝到rootfs/lib/modules/4.1.15 目录中,重启开发板,进入到目录lib/modules/4.1.15 中,输入如下命令加载key.ko 驱动模块:

depmod //第一次加载驱动的时候需要运行此命令
modprobe key.ko //加载驱动

驱动加载成功以后如下命令来测试:

./keyApp /dev/key

输入上述命令以后终端显示如图49.4.2.1 所示:

按下开发板上的KEY0 按键,keyApp 就会获取并且输出按键信息,如图49.4.2.2 所示:

从图49.4.2.2 可以看出,当我们按下KEY0 以后就会打印出“KEY0 Press, value = 0XF0”,表示按键按下。但是大家可能会发现,有时候按下一次KEY0 但是会输出好几行“KEY0 Press, value = 0XF0”,这是因为我们的代码没有做按键消抖处理。如果要卸载驱动的话输入如下命令即可:

rmmod key.ko

Linux按键输入实验(体验一下输入驱动,实际开发使用input子系统处理)相关推荐

  1. 鼠标驱动之-sys节点-input子系统

    首先需要了解sys节点和linux驱动编程的知识,在linux内核<linux/>下有着对应的实现.本例实现创建sys节点,外围程序通过input子系统控制鼠标位置. 第一步编写驱动代码, ...

  2. 嵌入式 按键输入实验(修改版)

    按键输入实验 1. 硬件设计: 开发板上有4个按键:key0,key1,key2和wk_up,分别接在PE4,PE3,PE2和PA0上: 2. 软件设计: KEY0. KEY1 和 KEY2 是低电平 ...

  3. Linux 驱动开发 四十八:Linux INPUT 子系统实验

    一.input 子系统简介 input 就是输入的意思,因此 input 子系统就是管理输入的子系统,是 Linux 内核针对某一类设备而创建的框架. 比如按键输入.键盘.鼠标.触摸屏等等这些都属于输 ...

  4. linux input输入子系统分析《四》:input子系统整体流程全面分析

    1      input输入子系统整体流程 本节分析input子系统在内核中的实现,包括输入子系统(Input Core),事件处理层(Event Handler)和设备驱动层.由于上节代码讲解了设备 ...

  5. linux 设备驱动 百度,Linux设备驱动之input子系统

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 作者:武汉华嵌嵌入式培训中心 讲师 李家凯 对于输入类设备如键盘.鼠标.触摸屏之类的Linux驱动,内核提供input子系统,使得这类设备的处理变得非常便 ...

  6. platform框架--Linux MISC杂项框架--Linux INPUT子系统框架--串行集成电路总线I2C设备驱动框架--串行外设接口SPI 设备驱动框架---通用异步收发器UART驱动框架

    platform框架 input. pinctrl. gpio 子系统都是 Linux 内核针对某一类设备而创建的框架, input子系统是管理输入的子系统 pinctrl 子系统重点是设置 PIN( ...

  7. 嵌入式Linux开发24——Linux 按键输入实验

    文章目录 Linux 下按键驱动原理 程序编写 1.修改设备树文件 1.1 添加 pinctrl 节点 1.2 添加 KEY 设备节点 1.3 检查 PIN 是否被其他外设使用 2.按键驱动程序编写 ...

  8. ARM(IMX6U)裸机按键输入实验(BSP+SDK、GPIO输入与输出、按键消抖)

    参考:Linux之ARM(IMX6U)裸机按键输入实验(GPIO的输出与输入) 作者:一只青木呀 发布时间: 2020-08-17 21:43:37 网址:https://blog.csdn.net/ ...

  9. 【吃灰板子捡起来】按键输入实验

    按键输入实验 一.驱动源码 二.应用源码 三.实验总结 一.驱动源码 硬件原理图 #include "nuc972_gpio.h"#include <linux/module ...

最新文章

  1. 【Python之路】第二篇--初识Python
  2. 关于页面打印window.print()的样式问题
  3. 微信小程序之设置不同的顶部导航栏名称
  4. CSS-3 Animation 的使用
  5. LeetCode: 107_Binary Tree Level Order Traversal II | 二叉树自底向上的层次遍历 | Easy
  6. 论营销的重要性:以一个磁铁为例
  7. php自定义框架,「php 框架」自定义php框架(篇一) - seo实验室
  8. Elasticsearch 基础入门
  9. Spark Shuffle系列-----1. Spark Shuffle与任务调度之间的关系
  10. (49)VHDL实现8位奇偶校验电路(while loop语句)
  11. PHP7 pecl 安装 mongodb扩展 和 PHPLIB
  12. html css 自动滚动代码,使用CSS自动滚动
  13. 开源网络监控管理系统:OpenNMS
  14. Android应用开发的一些思考
  15. 某个题库中的题目数量修改了,为何展示题号展示时没有它?
  16. 基于canoe的bootload刷写程序
  17. 2022江西省职业院校技能大赛春季赛网络安全赛项样题
  18. 蜜瓜文案:水果店蜜瓜简单文案,蜜瓜水果朋友圈配的文案
  19. 【转】D365 FO第三方集成(四)---客户端调用
  20. 小武学fpgaStep1

热门文章

  1. 移动服务安全现状分析!
  2. 2017-2018-1 20155301 《信息安全系统设计基础》第7周学习总结
  3. 推荐一个不错的 Chrome 插件,百变皮肤,还可以去广告
  4. 测试人员报BUG的正确姿势
  5. Android IOS WebRTC 音视频开发总结(八十七)-- WebRTC中丢包重传NACK实现分析
  6. php代码上线,实现版本切换
  7. 【HDOJ】1597 find the nth digit
  8. [XMOVE自主设计的体感方案] XMove Studio管理系统(二)应用开发API简要介绍
  9. ADSL之PPPOE
  10. 关于Nand ECC 错误