• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

i.MX 6ULL 驱动开发 十按键断(阻塞处理)

武飞扬头像
lqonlylove
帮助1

一、IO 模型

二、Linux 内核等待队列

三、Linux 中断基本概念

四、按键原理

五、设计思路

1、初始化等待队列头。

2、应用程序使用 read 读取按键值时,当条件不满足时,在驱动程序 read 函数中添加等待队列项,并设置当前进程休眠。

3、当按键值满足时,唤醒当前进程。

六、添加设备树

1、确定引脚

通过原理图可以确定 key 使用 UART1_CTS 引脚。

2、查找引脚定义是否冲突

3、添加 pinctrl 子系统相关配置

pinctrl_key: keygrp {
	fsl,pins = <
		MX6UL_PAD_UART1_CTS_B__GPIO1_IO18		0xF080	/* KEY0 */
	>;
};

4、添加 gpio 子系统相关配置

key {
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "lq-key";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_key>;
	key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
	status = "okay";
};

5、添加 interrupts 相关配置

key {
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "lq-key";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_key>;
	key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
	interrupt-parent = <&gpio1>;
	interrupts = <18 IRQ_TYPE_LEVEL_LOW>;  /* 低电平触发 */
	status = "okay";
};

主要添加 interrupt-parentinterrupts 属性。

6、测试

1、编译设备树

onlylove@ubuntu:~/my/linux/linux-imx-4.1.15$ make dtbs
  CHK     include/config/kernel.release
  CHK     include/generated/uapi/linux/version.h
  CHK     include/generated/utsrelease.h
make[1]: 'include/generated/mach-types.h' is up to date.
  CHK     include/generated/bounds.h
  CHK     include/generated/asm-offsets.h
  CALL    scripts/checksyscalls.sh
  DTC     arch/arm/boot/dts/imx6ull-alientek-emmc.dtb
  DTC     arch/arm/boot/dts/imx6ull-alientek-nand.dtb
onlylove@ubuntu:~/my/linux/linux-imx-4.1.15$

2、拷贝编译成功的设备树文件

onlylove@ubuntu:~/my/linux/linux-imx-4.1.15$ ls /home/onlylove/my/tftp/ -l
total 11528
-rwxrwxr-x 1 onlylove onlylove 5901744 Sep 17 04:04 zImage
-rwxrwxr-x 1 onlylove onlylove 5901752 Aug 20 01:24 zImage.bak
onlylove@ubuntu:~/my/linux/linux-imx-4.1.15$ cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb /home/onlylove/my/tftp
onlylove@ubuntu:~/my/linux/linux-imx-4.1.15$ ls /home/onlylove/my/tftp/ -l
total 11568
-rw-rw-r-- 1 onlylove onlylove   39084 Sep 23 21:18 imx6ull-alientek-emmc.dtb
-rwxrwxr-x 1 onlylove onlylove 5901744 Sep 17 04:04 zImage
-rwxrwxr-x 1 onlylove onlylove 5901752 Aug 20 01:24 zImage.bak
onlylove@ubuntu:~/my/linux/linux-imx-4.1.15$

3、启动 linux 查看设备树解析是否成功

/ # cd /proc/
/proc # ls
1              41             65             driver         mounts
10             46             66             execdomains    mtd
11             47             7              fb             net
12             48             8              filesystems    pagetypeinfo
13             49             83             fs             partitions
14             5              84             interrupts     self
15             50             9              iomem          softirqs
16             51             95             ioports        stat
17             52             asound         irq            swaps
18             53             buddyinfo      kallsyms       sys
19             54             bus            key-users      sysrq-trigger
2              55             cgroups        keys           sysvipc
20             56             cmdline        kmsg           thread-self
21             57             consoles       kpagecount     timer_list
22             58             cpu            kpageflags     tty
23             59             cpuinfo        loadavg        uptime
24             6              crypto         locks          version
3              60             device-tree    meminfo        vmallocinfo
4              61             devices        misc           vmstat
40             62             diskstats      modules        zoneinfo
/proc # cd device-tree/
/sys/firmware/devicetree/base # ls
#address-cells                 key
#size-cells                    memory
aliases                        model
alphaled                       name
backlight                      pxp_v4l2
beep                           regulators
chosen                         reserved-memory
clocks                         sii902x-reset
compatible                     soc
cpus                           sound
gpioled                        spi4
interrupt-controller@00a01000
/sys/firmware/devicetree/base # cd key/
/sys/firmware/devicetree/base/key # ls
#address-cells    interrupt-parent  name              status
#size-cells       interrupts        pinctrl-0
compatible        key-gpio          pinctrl-names
/sys/firmware/devicetree/base/key # cat compatible
lq-key
/sys/firmware/devicetree/base/key #
/sys/firmware/devicetree/base/key #
学新通

七、驱动编写

#include "linux/init.h"
#include "linux/module.h"
#include "linux/kdev_t.h"
#include "linux/fs.h"
#include "linux/cdev.h"
#include "linux/device.h"
#include <linux/uaccess.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <linux/sched.h>

#define NEWCHRDEV_MAJOR 0   		/* 主设备号(如果为0则让系统自动分配,如果大于0则使用指定设备号) */
#define NEWCHRDEV_MINOR 0   		/* 次设备号 */
#define NEWCHRDEV_COUNT 1   		/* 设备号个数 */
#define NEWCHRDEV_NAME  "key"       /* 名子 */
#define TIMEOUT 1000                 /* 定时器超时时间 */

typedef struct{
    struct device_node	*nd;        /* 设备节点 */
    int gpio;                       /* key gpio */
    int irqnum;                     /* 中断号 */
    struct tasklet_struct tasklet;  /* tasklet 变量(中断下半部) */
    unsigned long tasklet_data;     /* 传递给 tasklet 处理函数的参数 */
    struct work_struct work;        /* 工作队列 变量(中断下半部) */
    struct timer_list timer;        /* key 消抖定时器 */
    atomic_t keyvalue;              /* 按键状态 */
    wait_queue_head_t rq;           /* 读等待队列头 */
}lqkey_t;

typedef struct{
    struct cdev dev;        /* cdev 结构体 */
    int major;              /* 主设备号 */
    int minor;              /* 次设备号 */
    dev_t devid;            /* 设备号 */
    struct class *class;    /* 类 */
    struct device *device;  /* 设备 */
    lqkey_t key_data;         /* key 相关数据 */
}newchrdev_t;

newchrdev_t newchrdev;

static int keyio_read_wait_init(void)
{
    /* 初始化读等待队列头 */
    init_waitqueue_head(&newchrdev.key_data.rq);
    return 0;
}

/*
 * @description	: 按键IO初始化
 * @param 		: 无
 * @return 		: 无
 */
static int keyio_init(void)
{
    int ret = 0;
    /***** 处理设备树 *****/
    /* 1、获取设备树节点 */
    newchrdev.key_data.nd = of_find_node_by_path("/key");
	if (newchrdev.key_data.nd== NULL){
		printk("key node not find!\r\n");
		goto keyio_init_error1;
	}else {
		printk("beep node find!\r\n");
	}
    /* 1、获取设备树中的gpio属性 */
    newchrdev.key_data.gpio = of_get_named_gpio(newchrdev.key_data.nd, "key-gpio", 0);
	if(newchrdev.key_data.gpio < 0) {
		printk("can't get key-gpio");
		goto keyio_init_error1;
	}
	printk("key num = %d\r\n", newchrdev.key_data.gpio);

    /***** 使用gpio子系统设置引脚 *****/
    /* 1、向 gpio 子系统申请 GPIO 管脚 */
    ret = gpio_request(newchrdev.key_data.gpio,"key");
    if(ret){
        printk("can't request key-gpio!\r\n");
        goto keyio_init_error1;
    }
    /* 2、设置key-gpio为输入 */
    gpio_direction_input(newchrdev.key_data.gpio);
    if(ret < 0) {
		printk("can't set key-gpio!\r\n");
        goto keyio_init_error2;
	}
    return 0;

keyio_init_error2:
    gpio_free(newchrdev.key_data.gpio);
keyio_init_error1:
    return 1;
}

static int keyio_exit(void)
{
    /* 1、向gpio子系统请求释放gpio */
    gpio_free(newchrdev.key_data.gpio);
    return 0;
}

static void key_tasklet(unsigned long data)
{
    printk("key_tasklet\r\n");
}

static void key_work(struct work_struct *work)
{
    printk("key_work\r\n");
    mod_timer(&newchrdev.key_data.timer, jiffies   msecs_to_jiffies(TIMEOUT));
}

/* @description		: 中断服务函数
 *				  	  定时器用于按键消抖
 * @param - irq 	: 中断号 
 * @param - dev_id	: 设备结构
 * @return 			: 中断执行结果
 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
    printk("key0_handler\r\n");
//    tasklet_schedule(&newchrdev.key_data.tasklet);
    schedule_work(&newchrdev.key_data.work);
	return IRQ_RETVAL(IRQ_HANDLED);
}

/*
 * @description	: key中断初始化
 * @param 		: 无
 * @return 		: 无
 */
static int keyirq_init(void)
{
    int ret = 0;
    /***** 使用中断子系统设置 *****/
    /* 1、从设备树获取key-gpio对于中断号 */
    newchrdev.key_data.irqnum = irq_of_parse_and_map(newchrdev.key_data.nd, 0);
    if (!newchrdev.key_data.irqnum){
        goto keyirq_init_error1;
    }
    /* 2、向中断子系统请求中断号 */
    ret = request_irq(newchrdev.key_data.irqnum, key0_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,"KEY0",&newchrdev);
    if(ret < 0){
        printk("can't request irq!\r\n");
        goto keyirq_init_error1;
    }
    /***** 使用中断上下部(tasklet和工作队列选择一个) *****/
    /* 1、初始化中断下半部 */
//    tasklet_init(&newchrdev.key_data.tasklet,key_tasklet,newchrdev.key_data.tasklet_data);
    INIT_WORK(&newchrdev.key_data.work, key_work);
    return 0;
keyirq_init_error1:
    return 1;
}

static int keyirq_exit(void)
{
    /* 1、向中断子系统请求释放中断号 */
    free_irq(newchrdev.key_data.irqnum,&newchrdev);
    /***** 使用中断上下部(tasklet和工作队列选择一个) *****/
    /* 1、销毁中断下半部 */
//    tasklet_kill(&newchrdev.key_data.tasklet);
    flush_work(&newchrdev.key_data.work);
    return 0;
}

void key_timer_function(unsigned long arg)
{
    printk("key_timer_function\r\n");
    /* 1、设置按键状态(按键按下) */
    atomic_set(&newchrdev.key_data.keyvalue,0x01);
    /* 2、唤醒等待队列 */
    wake_up_interruptible(&newchrdev.key_data.rq);
}

/*
 * @description	: key消抖定时器初始化
 * @param 		: 无
 * @return 		: 无
 */
static int keytimer_init(void)
{
    /* 1、创建定时器 */
    init_timer(&newchrdev.key_data.timer);
    newchrdev.key_data.timer.function = key_timer_function;
    newchrdev.key_data.timer.expires = jiffies   msecs_to_jiffies(TIMEOUT);
    newchrdev.key_data.timer.data = (unsigned long)&newchrdev;
    return 0;
}

static int keytimer_exit(void)
{
    /* 删除、创建定时器 */
    del_timer(&newchrdev.key_data.timer);
    return 0;
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int newchrdev_open(struct inode *inode, struct file *filp)
{
    printk("newchrdev_open!\r\n");
    filp->private_data = &newchrdev; /* 设置私有数据 */
    return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t newchrdev_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char keystatus = 0;
//    printk("newchrdev_read!\r\n");

    /* 1、定义一个等待队列项(wait为等待队列项名字,current表示当前等待队列项属于当前进程) */
    DECLARE_WAITQUEUE(wait, current);
    /* 2、确定按键值 */
    keystatus = atomic_read(&newchrdev.key_data.keyvalue);
    /* 3、如果按键没按下,进程进入休眠 */
    if(keystatus == 0xFF){
        /* 4、将等待队列项添加到等待队列头 */
        add_wait_queue(&newchrdev.key_data.rq, &wait);
        /* 5、设置进程状态 */
        __set_current_state(TASK_INTERRUPTIBLE);
        /* 6、进行任务切换 */
        schedule();
        /* 7、处理信号唤醒情况 */
        if(signal_pending(current)){
            ret = -ERESTARTSYS;
            goto wait_error;
        }
        /* 8、设置唤醒后进程状态 */
        __set_current_state(TASK_RUNNING);
        /* 9、将等待队列项从等待队列头移除 */
        remove_wait_queue(&newchrdev.key_data.rq, &wait);
    }
    /* 10、获取按键值返回到应用程序 */
    if(keystatus == 0x01){
        ret = copy_to_user(buf, &keystatus, sizeof(keystatus));
        atomic_set(&newchrdev.key_data.keyvalue,0xFF);
    }else{
        ret = -EINVAL;
        goto data_error;
    }
	return 0;
wait_error:
    __set_current_state(TASK_RUNNING);
    remove_wait_queue(&newchrdev.key_data.rq, &wait);
data_error:
	return ret;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t newchrdev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    printk("newchrdev_write!\r\n");
    return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int newchrdev_release(struct inode *inode, struct file *filp)
{
    printk("newchrdev_release!\r\n");
	return 0;
}

static const struct file_operations newchrdevops = {
    .owner   = THIS_MODULE,
    .open = newchrdev_open,
	.read = newchrdev_read,
	.write = newchrdev_write,
	.release = newchrdev_release,
};

/* 驱动入口函数 */
static int __init newchrdev_init(void)
{
    /* 驱动入口函数具体内容 */
    /* 1、字符设备号分配 */
    int ret;
    newchrdev.major = NEWCHRDEV_MAJOR;
    if(newchrdev.major){
        newchrdev.minor = NEWCHRDEV_MINOR;
        newchrdev.devid = MKDEV(newchrdev.major, newchrdev.minor);
        ret = register_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT,NEWCHRDEV_NAME);
    }else{
        ret = alloc_chrdev_region(&newchrdev.devid,0,NEWCHRDEV_COUNT,NEWCHRDEV_NAME);
        newchrdev.major = MAJOR(newchrdev.devid);
        newchrdev.minor = MINOR(newchrdev.devid);
    }
    if(ret < 0){
        printk("newchrdev xxx_chrdev_region failed!\r\n");
        goto newchrdev_chrdev_region_failed;
    }
    printk("newchrdev major=%d,minor=%d\r\n",newchrdev.major,newchrdev.minor);

    /* 2、注册字符设备 */
    newchrdev.dev.owner = THIS_MODULE;
    cdev_init(&newchrdev.dev,&newchrdevops);
    ret = cdev_add(&newchrdev.dev,newchrdev.devid,NEWCHRDEV_COUNT);
    if(ret < 0){
        printk("newchrdev cdev_add failed!\r\n");
        goto newchrdev_cdev_add_failed;
    }

    /* 3、创建类 */
    newchrdev.class = class_create(THIS_MODULE,NEWCHRDEV_NAME);
    if(IS_ERR(newchrdev.class)) {
        printk("newchrdev class_create failed!\r\n");
        goto newchrdev_class_create_failed;
    }

    /* 4、创建设备 */
    newchrdev.device = device_create(newchrdev.class,NULL,newchrdev.devid,NULL,NEWCHRDEV_NAME);
    if(IS_ERR(newchrdev.device)){
        printk("newchrdev device_create failed!\r\n");
        goto neschrdev_device_creat_failed;
    }

    ret = keyio_init();
    if(ret != 0){
        goto keyio_init_failed;
    }

    ret = keyirq_init();
    if(ret != 0){
        goto keyirq_init_failed;
    }

    keytimer_init();
    /* 初始化按键状态值 */
    atomic_set(&newchrdev.key_data.keyvalue,0xFF);

    keyio_read_wait_init();

    return 0;
keyirq_init_failed:
    keyio_exit();
keyio_init_failed:
neschrdev_device_creat_failed:
    class_destroy(newchrdev.class);
newchrdev_class_create_failed:
    cdev_del(&newchrdev.dev);
newchrdev_cdev_add_failed:
    unregister_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT);

newchrdev_chrdev_region_failed:   /* 字符设备号分配失败处理函数(未分配资源,因此不做处理) */
    return ret;
}

/* 驱动卸载函数 */
static void __exit newchrdev_exit(void)
{
    keytimer_exit();
    keyirq_exit();
    keyio_exit();
    /* 驱动卸载函数具体内容 */
    /* 4、删除设备 */
    device_destroy(newchrdev.class,newchrdev.devid);
    /* 3、删除类 */
    class_destroy(newchrdev.class);
    /* 2、注销字符设备 */
    cdev_del(&newchrdev.dev);
    /* 1、释放设备号 */
    unregister_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT);
}

module_init(newchrdev_init);
module_exit(newchrdev_exit);

MODULE_LICENSE("GPL");
学新通

八、应用程序编写

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"

int main(int argc, char *argv[])
{
    int fd = 0, retvalue = 0;
    char readbuf = 0;
    if(argc != 2){
		printf("Error Usage!\r\n");
		return -1;
	}

    fd = open(argv[1],O_RDWR);
    if(fd < 0){
        printf("Can't open file %s\r\n", argv[1]);
        return -1;
    }
    printf("sizeof(readbuf) = %d\r\n",sizeof(readbuf));
    while(1){
        retvalue = read(fd, &readbuf, sizeof(readbuf));
        if (retvalue < 0){

        }else{
            if (readbuf)
                printf("key value = %#X\r\n", readbuf);
        }
    }
    close(fd);
    return 0;
}
学新通

九、系统资源使用情况

学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhibfikb
系列文章
更多 icon
同类精品
更多 icon
继续加载