9_linux按键实验

一、创建工程

1、添加设备树节点

1)添加设备节点

​ 在根节点下创建设备节点:

gpioled {
		compatible = "gpioled_test";
		status = "okay";
		pinctrl-names = "default";
		pinctrl-0 = <&gpioled>;
		gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
	};

key_input {
		compatible = "key_test";
		status = "okay";
		pinctrl-names = "default";
		pinctrl = <&keyinput>;
		gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
	};

2)添加pinctrl节点

​ 在 iomuxc 节点下的子节点 imx6ul-evk 中添加 pinctrl 节点:

gpioled: ledgrp {
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO03__GPIO1_IO03   0x10b0	
			>;
		};

keyinput: keygrp {
			fsl,pins = <
				MX6UL_PAD_UART1_CTS_B__GPIO1_IO18   0xf080
			>;
		};

2、添加设备结构体

/* 设备结构体 */
struct key_dev {
	dev_t devid;			//设备号
	int major;				//主设备号
	int minor;				//次设备号
	struct class *class;	//类
	struct device *device;	//设备
	struct cdev cdev;		//字符设备	
	struct device_node *led_nd;	//led设备树节点
	struct device_node *key_nd; //key设备树节点
	int led_gpio;	//led gpio编号
	int key_gpio;	//key gpio编号
	int led_state;
	atomic_t key_val;	//键值
};

struct key_dev key;

3、编写加载和卸载注册函数

​ 加载和卸载注册函数如下:

module_init(key_init);
module_exit(key_exit);

入口函数:

/* 入口函数 */
static int __init key_init(void)
{
	int ret = 0;

	/* 设备号处理 */
	key.major = 0;
	if(key.major){	//设置了主设备号
		key.devid = MKDEV(key.major, key.minor);
		ret = register_chrdev_region(key.devid, DEVICE_CNT, DEVICE_NAME);
		if(ret < 0){	//注册设备号失败
			printk("fail to register devid\r\n");
			return -EINVAL;
		}
	}else{	//没有设置主设备号
		ret = alloc_chrdev_region(&key.devid, 0, DEVICE_CNT, DEVICE_NAME);
		if(ret < 0){	//申请设备号失败
			printk("fail to alloc devid\r\n");
			return -EINVAL;
		}
		key.major = MAJOR(key.devid);
		key.minor = MINOR(key.devid);
		printk("major is %d\r\nminor is %d\r\n",key.major, key.minor);
	}

	/* 注册字符设备 */	
	key.cdev.owner = THIS_MODULE;
	cdev_init(&key.cdev, &key_fops);
	ret = cdev_add(&key.cdev, key.devid, DEVICE_CNT);
	if(ret < 0){	//添加字符设备失败
		ret = -EINVAL;
		printk("fail to add cdev\r\n");
		goto fail_add_cdev;
	}

	/* 自动创建设备节点 */
	key.class = NULL;
	key.device = NULL;
	key.class = class_create(THIS_MODULE, DEVICE_NAME);
	if(key.class == NULL){	//创建类失败
		ret = -EINVAL;
		printk("fail to create class\r\n");
		goto fail_cclass;
	}
	key.device = device_create(key.class, NULL, key.devid, NULL, DEVICE_NAME);
	if(key.device == NULL){		//创建设备失败
		ret = -EINVAL;
		printk("fail to create device\r\n");
		goto fail_cdevice;
	}

	/* 获取led设备树节点 */
	key.led_nd = of_find_node_by_name(NULL, "gpioled");
	if(key.led_nd == NULL){
		printk("fail to find led_nd\r\n");
		ret = -EINVAL;
		goto fail_find_gpio_nd;
	}

	/* 获取key设备树节点 */
	key.key_nd = of_find_node_by_name(NULL, "key_input");
	if(key.key_nd == NULL){
		printk("fail to find led_nd\r\n");
		ret = -EINVAL;
		goto fail_find_gpio_nd;
	}

	/* 获取led_gpio编号 */
	key.led_gpio = of_get_named_gpio(key.led_nd, "gpios", 0);
	if(key.led_gpio < 0){	//获取led_gpio编号失败
		printk("fail to find led_nd\r\n");
		ret = -EINVAL;
		goto fail_find_gpio_num;
	}
	printk("led_gpio num: %d\r\n", key.led_gpio);

	/* 获取key_gpio编号 */
	key.key_gpio = of_get_named_gpio(key.key_nd, "gpios", 0);
	if(key.key_gpio < 0){	//获取led_gpio编号失败
		printk("fail to find key_nd\r\n");
		ret = -EINVAL;
		goto fail_find_gpio_num;
	}
	printk("key_gpio num: %d\r\n", key.key_gpio);

	/* 注册gpio */
	gpio_request(key.led_gpio, "gpioled");
	gpio_request(key.key_gpio, "gpiokey");

	/* 设置gpio输入输出 */
	gpio_direction_output(key.led_gpio, 1);
	gpio_direction_input(key.key_gpio);

	printk("key init\r\n");

	return 0;

fail_add_cdev:
	unregister_chrdev_region(key.devid, DEVICE_CNT);
fail_cclass:
	cdev_del(&key.cdev);
	unregister_chrdev_region(key.devid, DEVICE_CNT);
fail_cdevice:
	class_destroy(key.class);
	cdev_del(&key.cdev);
	unregister_chrdev_region(key.devid, DEVICE_CNT);
fail_find_gpio_nd:
	device_destroy(key.class, key.devid);
	class_destroy(key.class);
	cdev_del(&key.cdev);
	unregister_chrdev_region(key.devid, DEVICE_CNT);
fail_find_gpio_num:
	device_destroy(key.class, key.devid);
	class_destroy(key.class);
	cdev_del(&key.cdev);
	unregister_chrdev_region(key.devid, DEVICE_CNT);

	return ret;
}

出口函数:

/* 出口函数 */
static void __exit key_exit(void)
{
	led_switch(LED_OFF);
	gpio_free(key.led_gpio);
	gpio_free(key.key_gpio);
	device_destroy(key.class, key.devid);
	class_destroy(key.class);
	cdev_del(&key.cdev);
	unregister_chrdev_region(key.devid, DEVICE_CNT);
}

3、编写设备的具体操作函数

/* open函数 */
static int key_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &key;
	key.led_state = 1;
	return 0;
}

/* read函数 */
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int value;
	int ret = 0;
	struct key_dev *dev = filp->private_data;

	if(gpio_get_value(dev->key_gpio) == 0){	//key按下
		while(!gpio_get_value(dev->key_gpio))	//等待按键释放
		atomic_set(&dev->key_val, KEY_VAL);
		led_switch(dev->led_state = !(dev->led_state));
	}else{
		atomic_set(&dev->key_val, INVALKEY);
	}
	value = atomic_read(&dev->key_val);
	ret = copy_to_user(buf, &value, sizeof(value));

	return 0;
}

/* 操作函数集合 */
static const struct file_operations key_fops = {
	.owner = THIS_MODULE,
	.open = key_open,
	.read = key_read,
};

4、添加头文件及创建虚拟地址指针

​ 参考 linux 内核的驱动代码时,找到可能用到的头文件,添加进工程。在调用系统调用函数库函数时,在终端使用 man 命令可查看调用的函数需要包含哪些头文件。
​ man 命令数字含义:1:标准命令 2:系统调用 3:库函数
​ 添加以下头文件:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

#define DEVICE_NAME "key_input"
#define DEVICE_CNT 1
#define LED_ON  1
#define LED_OFF 0
#define KEY_VAL		1
#define INVALKEY    0

5、添加 License 和作者信息

​ 驱动的 License 是必须的,缺少的话会报错,在文件最末端添加以下代码:

MODULE_LICENSE("GPL");
MODULE_AUTHOR("lzk");

6、编写led状态切换函数

/* LED状态切换函数 */
void led_switch(int led_state)
{
	if(led_state == LED_ON){
		gpio_set_value(gpioled.led_gpio, 0);	//使用gpio子系统的API函数
	}else{
		gpio_set_value(gpioled.led_gpio, 1);
	}
}

二、编写测试应用程序

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#define KEY_VAL		1
#define INVALKEY    0

int main(int argc, char *argv[])
{
    int fd = 0;
    int ret = 0;
    char *filename;
    int keyvalue;
    
    if(argc != 3){
        printf("missing parameter!\r\n");
        return -1;
    }

    filename = argv[1];
    fd = open(filename, O_RDWR);
    if(fd < 0){
        printf("open file %s failed\r\n", filename);
        return -1;
    }

    while(1){
        read(fd, &keyvalue, sizeof(keyvalue));
        if(keyvalue == KEY_VAL){
            printf("KEY0 Press\r\n");/* 按下 */
        }
    }


    ret = close(fd);
    if(ret < 0) //返回值小于0关闭文件失败
    {
        printf("close file %s failed\r\n", filename);
        return -1;
    }

    return 0;
}

三、编译和测试

1、编写Makefile,编译驱动程序

​ 驱动程序源码需要编译成.ko模块,创建Makefile:

KERNELDIR := /home/liuzhikai/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := key.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
  • 第1行,KERNELDIR 表示开发板所使用的 Linux 内核源码目录,使用绝对路径。

  • 第2行,CURRENT_PATH 表示当前路径。

  • 第3行,obj-m 表示将key.c 这个文件编译为模块。

  • 第5行,默认目标为 kernel_modules。

  • 第8行,具体的编译命令,后面的 modules 表示编译模块,-C表示将当前的工作目录切换到指定目录中。M表示模块源码目录。“make modules”命令中加入 M=dir 以后程序会自动到指定的 dir 目录中读取模块源码并将其编译出 .ko文件。

    编译成功以后会生成一个 key.ko== 文件,这个文件就是驱动模块。

2、编译驱动程序

​ 测试 APP 要在 ARM 开发板上运行,所以使用交叉编译器编译:

arm-linux-gnueabihf-gcc keyAPP.c -o ledAPP

​ 编译完成生成 keyAPP 的可执行程序。

3、运行测试

​ linux 系统选择网络启动,并且用 nfs 挂载根文件系统,U-Boot 设置如下:
​ bootcmd 的值:

tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000

​ bootargs 的值:

console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.138:/home/liuzhikai/linux/nfs/rootfs ip=192.168.1.123:192.168.1.138:192.168.1.2:255.255.255.0::eth0:off

​ 将 dtsled.koledAPP 拷贝到以下目录(不存在的目录创建):

sudo cp dtsled.ko keyApp /home/liuzhikai/linux/nfs/rootfs/lib/modules/4.1.15/ -f

​ 使用如下命令加载驱动模块:

depmod
modprobe dtsled.ko  //加载驱动模块
./keyAPP /dev/dtsled 1  //执行应用程序,打开led灯
./keyAPP /dev/dtsled 0  //执行应用程序,关闭led灯
rmmod dtsled.ko  //卸载驱动模块
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值