驱动程序开发:按键输入实验

前言

一、 修改设备树文件

1、 在.dts文件中的iomuxc节点的imx6ul-evk子节点下创建“pinctrl_key”的子节点:

1.	 /* djw key 2022.4.29 */  
2.	pinctrl_key: keygrp {  
3.	    fsl,pins = <  
4.	        MX6UL_PAD_UART1_CTS_B__GPIO1_IO18   0xF080  
5.	    >;  
6.	};  
7.	/* 该引脚是复用为GPIO使用 */  

2、 在.dts文件中的根节点创建“key”节点。

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

3、 检查该PIN脚有没有被其他外设使用,否则驱动程序在申请GPIO的时候会失败。

①	检查pinctrl设置,是否还有别的外设复用了。
②	检查该PIN脚有没有别的外设配置成了GPIO。

4、编译设备树“make dtbs”,启动Linux系统,进入“/proc/device-tree”目录中查看“key”节点是否存在,如果存在则说明设备树修改成功。


二、驱动程序架构的编写

1.	/* 
2.	 *  根据linux内核的程序查找所使用函数的对应头文件。 
3.	 */  
4.	#include <linux/module.h> // MODULE_LICENSE,MODULE_AUTHOR  
5.	#include <linux/init.h>       // module_init,module_exit  
6.	#include <linux/kernel.h> // printk  
7.	#include <linux/fs.h>     // struct file_operations  
8.	#include <linux/slab.h>       //kmalloc, kfree  
9.	#include <linux/uaccess.h>    // copy_to_user,copy_from_user  
10.	#include <linux/io.h>     //ioremap,iounmap  
11.	#include <linux/cdev.h>       //struct cdev,cdev_init,cdev_add,cdev_del  
12.	#include <linux/device.h> //class  
13.	#include <linux/of.h>     //of_find_node_by_path  
14.	#include <linux/of_gpio.h>  //of_get_named_gpio  
15.	#include <linux/gpio.h>     //gpio_request,gpio_direction_output,gpio_set_value  
16.	#include <linux/atomic.h>   //atomic_t  
17.	  
18.	/* 1.5 key设备结构体 */  
19.	struct key_dev {  
20.	    dev_t           devid;      /* 设备号 */  
21.	    int             major;      /* 主设备号 */  
22.	    int             minor;      /* 次设备号 */  
23.	    char            *name;      /* 设备名称 */  
24.	    int             dev_count;  /* 设备个数 */  
25.	    struct  cdev    cdev;       /* 注册设备结构体 */  
26.	    struct  class   *class;     /* 类 */  
27.	    struct  device  *device;    /* 设备 */  
28.	    struct  device_node *nd;    /* 设备节点 */  
29.	    int             key_gpio;   /* key所使用的GPIO编号 */      
30.	};  
31.	struct key_dev key;             /* 定义key设备结构体 */  
32.	  
33.	/* 
34.	 * inode: 传递给驱动的inode 
35.	 * filp : 要进行操作的设备文件(文件描述符) 
36.	 * buf  : 数据缓冲区 
37.	 * cnt  : 数据长度 
38.	 * ppos : 相对于文件首地址的偏移 
39.	 */  
40.	/* 2.1 打开字符设备文件 */  
41.	static int key_open(struct inode *inode, struct file *filp) {  
42.	    int ret = 0;  
43.	    /* 设置私有类数据 */  
44.	    filp->private_data = &key;  
45.	    return ret;  
46.	}  
47.	  
48.	/* 2.2 关闭字符设备文件 */  
49.	static int key_release(struct inode *inode, struct file *filp) {  
50.	    int ret = 0;  
51.	    return ret;  
52.	}  
53.	  
54.	/* 2.3 向字符设备文件读出数据 */  
55.	static ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) {  
56.	    int ret = 0;  
57.	    /* 提取私有属性 */  
58.	    struct key_dev *dev = filp->private_data;  
59.	    return ret;  
60.	}  
61.	  
62.	/* 2.4 向字符设备文件写入数据 */  
63.	static ssize_t key_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) {  
64.	    int ret = 0;  
65.	    return ret;  
66.	}  
67.	  
68.	/* 2.5 key设备操作集 */  
69.	static const struct file_operations key_fops = {  
70.	    .owner      =   THIS_MODULE,  
71.	    .open       =   key_open,  
72.	    .release    =   key_release,  
73.	    .read       =   key_read,  
74.	    .write      =   key_write,  
75.	};  
76.	  
77.	  
78.	/* 1.1 驱动模块入口函数 */  
79.	static int __init key_init(void) {  
80.	    int ret = 0;  
81.	    printk("key_init!\r\n");  
82.	    /***********************************************************************************************/  
83.	    /* 3.1 配置key结构体参数 */  
84.	    key.dev_count = 1;                  //设置设备号个数  
85.	    key.name  = "key";                  //设置设备名字  
86.	    key.major = 0;                      //0为系统分配设备号, 1为自定义设备号  
87.	    if(key.major) {  
88.	        key.devid = MKDEV(key.major,0); //自定义主设备号和次设备号,并整合为设备号结构体中  
89.	        ret = register_chrdev_region(key.devid, key.dev_count, key.name);   //注册设备号  
90.	    } else {  
91.	        alloc_chrdev_region(&key.devid, 0, key.dev_count, key.name);    //注册设备号,系统自动分配  
92.	        key.major = MAJOR(key.devid);  
93.	        key.minor = MINOR(key.devid);  
94.	    }  
95.	    if(ret < 0) {  
96.	        goto fail_devid;                //注册设备号失败  
97.	    }  
98.	    printk("key major = %d, minor = %d\r\n",key.major,key.minor);   //注册设备号成功了,就打印主次设备号  
99.	  
100.	    /* 3.2 注册或者叫添加字符设备 */  
101.	    cdev_init(&key.cdev, &key_fops);    //初始化cdev结构体  
102.	    ret = cdev_add(&key.cdev, key.devid, key.dev_count);    //添加字符设备  
103.	    if(ret < 0) {  
104.	        goto fail_cdev;                 //注册或者叫添加设备失败  
105.	    }  
106.	  
107.	    /* 3.3 自动创建设备节点 */  
108.	    key.class = class_create(THIS_MODULE, key.name);        //创建类  
109.	    if(IS_ERR(key.class)) {  
110.	        ret = PTR_ERR(key.class);  
111.	        goto fail_class;  
112.	    }  
113.	    key.device = device_create(key.class, NULL, key.devid, NULL, key.name); //创建设备  
114.	    if(IS_ERR(key.device)) {  
115.	        ret = PTR_ERR(key.device);  
116.	        goto fail_device;  
117.	    }  
118.	    /***********************************************************************************************/  
119.	  
120.	    return ret;  
121.	  
122.	fail_device:  
123.	    class_destroy(key.class);  
124.	fail_class:  
125.	    cdev_del(&key.cdev);  
126.	fail_cdev:  
127.	    unregister_chrdev_region(key.devid, key.dev_count);  
128.	fail_devid:  
129.	    return ret;  
130.	}  
131.	  
132.	/* 1.2 驱动模块出口函数 */  
133.	static void __exit key_exit(void) {  
134.	    /* 最后一一添加 , 注销字符设备驱动 */  
135.	    device_destroy(key.class, key.devid);                   //摧毁设备  
136.	    class_destroy(key.class);                               //摧毁类  
137.	    cdev_del(&key.cdev);                                    //注销字符设备结构体  
138.	    unregister_chrdev_region(key.devid, key.dev_count);     //注销设备号  
139.	}  
140.	  
141.	/* 1.3 注册驱动模块 */  
142.	module_init(key_init);  
143.	module_exit(key_exit);  
144.	  
145.	/* 1.4 驱动许可和个人信息 */  
146.	MODULE_LICENSE("GPL");  
147.	MODULE_AUTHOR("djw");  


三、基于驱动程序框架编写需要实现的驱动程序

在驱动程序中使用一个整形变量来表示按键值,应用程序通过 read 函数来读取按键值,判断按键有没有按下。在这里,这个保存按键值的变量就是个共享资源,驱动程序要向其写入按键值,应用程序要读取按键值。所以我们要对其进行保护,对于整形变量而言我们首选的就是原子操作,使用原子操作对变量进行赋值以及读取。

1.	/* 
2.	 *  根据linux内核的程序查找所使用函数的对应头文件。 
3.	 */  
4.	#include <linux/module.h> // MODULE_LICENSE,MODULE_AUTHOR  
5.	#include <linux/init.h>       // module_init,module_exit  
6.	#include <linux/kernel.h> // printk  
7.	#include <linux/fs.h>     // struct file_operations  
8.	#include <linux/slab.h>       //kmalloc, kfree  
9.	#include <linux/uaccess.h>    // copy_to_user,copy_from_user  
10.	#include <linux/io.h>     //ioremap,iounmap  
11.	#include <linux/cdev.h>       //struct cdev,cdev_init,cdev_add,cdev_del  
12.	#include <linux/device.h> //class  
13.	#include <linux/of.h>     //of_find_node_by_path  
14.	#include <linux/of_gpio.h>  //of_get_named_gpio  
15.	#include <linux/gpio.h>     //gpio_request,gpio_direction_output,gpio_set_value  
16.	#include <linux/atomic.h>   //atomic_t  
17.	  
18.	#define KEYPRESS    0XF0    //按下  
19.	#define KEYRELEASE  0X00    //释放  
20.	  
21.	/* 1.5 key设备结构体 */  
22.	struct key_dev {  
23.	    dev_t           devid;      /* 设备号 */  
24.	    int             major;      /* 主设备号 */  
25.	    int             minor;      /* 次设备号 */  
26.	    char            *name;      /* 设备名称 */  
27.	    int             dev_count;  /* 设备个数 */  
28.	    struct  cdev    cdev;       /* 注册设备结构体 */  
29.	    struct  class   *class;     /* 类 */  
30.	    struct  device  *device;    /* 设备 */  
31.	    struct  device_node *nd;    /* 设备节点 */  
32.	    int             key_gpio;   /* key所使用的GPIO编号 */    
33.	  
34.	    atomic_t        key_value;  /* 按键值 */         
35.	};  
36.	struct key_dev key;             /* 定义key设备结构体 */  
37.	  
38.	/* 
39.	 * inode: 传递给驱动的inode 
40.	 * filp : 要进行操作的设备文件(文件描述符) 
41.	 * buf  : 数据缓冲区 
42.	 * cnt  : 数据长度 
43.	 * ppos : 相对于文件首地址的偏移 
44.	 */  
45.	/* 2.1 打开字符设备文件 */  
46.	static int key_open(struct inode *inode, struct file *filp) {  
47.	    int ret = 0;  
48.	    /* 设置私有类数据 */  
49.	    filp->private_data = &key;  
50.	    return ret;  
51.	}  
52.	  
53.	/* 2.2 关闭字符设备文件 */  
54.	static int key_release(struct inode *inode, struct file *filp) {  
55.	    int ret = 0;  
56.	    return ret;  
57.	}  
58.	  
59.	/* 2.3 向字符设备文件读出数据 */  
60.	static ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) {  
61.	    int ret = 0;  
62.	    int value;      //原子类型的变量不能直接使用,要转换为int类型  
63.	    /* 提取私有属性 */  
64.	    struct key_dev *dev = filp->private_data;  
65.	    /* 按下为低电平 */  
66.	    if(gpio_get_value(dev->key_gpio) == 0) {      
67.	        while (!gpio_get_value(dev->key_gpio));     //等待松开  
68.	        atomic_set(&dev->key_value, KEYPRESS);      //设置为按下状态  
69.	    } else {  
70.	        atomic_set(&dev->key_value, KEYRELEASE);    //设置为松开状态  
71.	    }  
72.	    value = atomic_read(&dev->key_value);  
73.	    ret = copy_to_user(buf, &value, sizeof(value));  
74.	  
75.	    return ret;  
76.	}  
77.	  
78.	/* 2.4 向字符设备文件写入数据 */  
79.	static ssize_t key_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) {  
80.	    int ret = 0;  
81.	    return ret;  
82.	}  
83.	  
84.	/* 2.5 key设备操作集 */  
85.	static const struct file_operations key_fops = {  
86.	    .owner      =   THIS_MODULE,  
87.	    .open       =   key_open,  
88.	    .release    =   key_release,  
89.	    .read       =   key_read,  
90.	    .write      =   key_write,  
91.	};  
92.	  
93.	/* 4.1 key GPIO 的初始化 */  
94.	static int key_gpio_init(struct key_dev *dev) {  
95.	    int ret = 0;  
96.	    /* 4.1.1 根据设备树的设备节点路径获取设备节点 */  
97.	    dev->nd = of_find_node_by_path("/key");  
98.	    if(dev->nd == NULL) {  
99.	        ret = -EINVAL;  
100.	        goto fail_nd;  
101.	    }  
102.	    /* 4.1.2 获取到key对应的设备节点中的GPIO信息 */  
103.	    dev->key_gpio = of_get_named_gpio(dev->nd, "key-gpio", 0);  
104.	    if(dev->key_gpio < 0) {  
105.	        ret = -EINVAL;  
106.	        goto fail_gpio;  
107.	    }  
108.	    printk("key gpio num = %d\r\n",dev->key_gpio);  //获取key的GPIO编号  
109.	    /* 4.1.3 请求GPIO */  
110.	    ret = gpio_request(dev->key_gpio, "key-gpio");  
111.	    if(ret) {  
112.	        ret = -EBUSY;  
113.	        printk("IO %d can't request!\r\n",dev->key_gpio);  
114.	        goto fail_request;  
115.	    }  
116.	    /* 4.1.4 使用IO,设置IO的输出方向 */  
117.	    ret = gpio_direction_input(dev->key_gpio);  
118.	    if(ret < 0) {  
119.	        ret = -EINVAL;  
120.	        goto fail_input;  
121.	    }  
122.	    return 0;  
123.	  
124.	fail_input:  
125.	    gpio_free(dev->key_gpio);   //释放GPIO  
126.	fail_request:  
127.	fail_gpio:  
128.	fail_nd:  
129.	    return ret;  
130.	}  
131.	  
132.	/* 1.1 驱动模块入口函数 */  
133.	static int __init key_init(void) {  
134.	    int ret = 0;  
135.	    printk("key_init!\r\n");  
136.	    /***********************************************************************************************/  
137.	    /* 3.1 配置key结构体参数 */  
138.	    key.dev_count = 1;                  //设置设备号个数  
139.	    key.name  = "key";                  //设置设备名字  
140.	    key.major = 0;                      //0为系统分配设备号, 1为自定义设备号  
141.	    if(key.major) {  
142.	        key.devid = MKDEV(key.major,0); //自定义主设备号和次设备号,并整合为设备号结构体中  
143.	        ret = register_chrdev_region(key.devid, key.dev_count, key.name);   //注册设备号  
144.	    } else {  
145.	        alloc_chrdev_region(&key.devid, 0, key.dev_count, key.name);    //注册设备号,系统自动分配  
146.	        key.major = MAJOR(key.devid);  
147.	        key.minor = MINOR(key.devid);  
148.	    }  
149.	    if(ret < 0) {  
150.	        goto fail_devid;                //注册设备号失败  
151.	    }  
152.	    printk("key major = %d, minor = %d\r\n",key.major,key.minor);   //注册设备号成功了,就打印主次设备号  
153.	  
154.	    /* 3.2 注册或者叫添加字符设备 */  
155.	    cdev_init(&key.cdev, &key_fops);    //初始化cdev结构体  
156.	    ret = cdev_add(&key.cdev, key.devid, key.dev_count);    //添加字符设备  
157.	    if(ret < 0) {  
158.	        goto fail_cdev;                 //注册或者叫添加设备失败  
159.	    }  
160.	  
161.	    /* 3.3 自动创建设备节点 */  
162.	    key.class = class_create(THIS_MODULE, key.name);        //创建类  
163.	    if(IS_ERR(key.class)) {  
164.	        ret = PTR_ERR(key.class);  
165.	        goto fail_class;  
166.	    }  
167.	    key.device = device_create(key.class, NULL, key.devid, NULL, key.name); //创建设备  
168.	    if(IS_ERR(key.device)) {  
169.	        ret = PTR_ERR(key.device);  
170.	        goto fail_device;  
171.	    }  
172.	    /***********************************************************************************************/  
173.	  
174.	    /***********************************************************************************************/  
175.	    /* 5.1 初始化key的GPIO */  
176.	    ret = key_gpio_init(&key);  
177.	    if(ret < 0) {  
178.	        printk("gpio init failed! ret = %d\r\n",ret);  
179.	        goto fail_device;  
180.	    }  
181.	    /*  */  
182.	    atomic_set(&key.key_value, KEYRELEASE);  //无效按键值,也就是松开  
183.	    /***********************************************************************************************/  
184.	    return ret;  
185.	  
186.	fail_device:  
187.	    class_destroy(key.class);  
188.	fail_class:  
189.	    cdev_del(&key.cdev);  
190.	fail_cdev:  
191.	    unregister_chrdev_region(key.devid, key.dev_count);  
192.	fail_devid:  
193.	    return ret;  
194.	}  
195.	  
196.	/* 1.2 驱动模块出口函数 */  
197.	static void __exit key_exit(void) {  
198.	    /* 最后一一添加 , 注销字符设备驱动 */  
199.	    gpio_free(key.key_gpio);                               //释放IO  
200.	    device_destroy(key.class, key.devid);                   //摧毁设备  
201.	    class_destroy(key.class);                               //摧毁类  
202.	    cdev_del(&key.cdev);                                    //注销字符设备结构体  
203.	    unregister_chrdev_region(key.devid, key.dev_count);     //注销设备号  
204.	}  
205.	  
206.	/* 1.3 注册驱动模块 */  
207.	module_init(key_init);  
208.	module_exit(key_exit);  
209.	  
210.	/* 1.4 驱动许可和个人信息 */  
211.	MODULE_LICENSE("GPL");  
212.	MODULE_AUTHOR("djw"); 

四、编写测试APP(keyAPP.c)

1.	#include <sys/types.h>  
2.	#include <sys/stat.h>  
3.	#include <fcntl.h>  
4.	#include <stdio.h>  
5.	#include <unistd.h>  
6.	#include <stdlib.h>  
7.	#include <string.h>  
8.	  
9.	/* 宏定义按键状态标志 */  
10.	#define KEYPRESS      0XF0  
11.	#define KEYRELEASE    0X00  
12.	  
13.	  
14.	/* 
15.	 * argc:应用程序argv数组元素个数  
16.	 * argv[]:具体打参数内容,字符串形式  
17.	 * ./keyAPP <filename> 
18.	 * ./keyAPP /dev/key 
19.	 */  
20.	  
21.	int main(int argc, char *argv[])  
22.	{  
23.	    int fd, ret;         //fd文件描述符变量,返回值临时变量  
24.	    int value = 0;       //按键值  
25.	    char *filename;  
26.	    //判断输入的参数是否为两个参数  
27.	    if(argc != 2) {  
28.	        printf("ERROR USAGE!\r\n");  
29.	        return -1;  
30.	    }  
31.	  
32.	    filename = argv[1];            //第二个元素  
33.	    fd = open(filename,O_RDWR);    //打开key驱动文件  
34.	    if(fd < 0) {  
35.	        printf("file %s open failed!\r\n",filename);  
36.	        return -1;  
37.	    }  
38.	  
39.	    /* 循环读取按键值 */  
40.	    while(1) {  
41.	        read(fd, &value, sizeof(value));  
42.	        if(value == KEYPRESS) {  
43.	            printf("key press, value = %d\r\n",value);  
44.	        }  
45.	    }  
46.	  
47.	    close(fd);  
48.	    if(ret < 0){  
49.	        printf("file %s close failed!\r\n", argv[1]);  
50.	        return -1;  
51.	    }  
52.	    return 0;  
53.	} 

四、 运行测试

1、编译驱动程序:make
2、编译测试APP:arm-linux-gnueabihf-gcc keyApp.c -o keyApp
3、启动Linux,进入/lib/modueles/4.1.15目录中,使用“depmod”和“modprobe key.ko”命令加载驱动模块。最后使用“./keyAPP /dev/key”命令启动测试APP进行试验测试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邓家文007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值