当前位置:   article > 正文

STM32MP157驱动开发——Linux中断实验(下)_mp157引脚配置

mp157引脚配置


一、续上节

  上节梳理完中断的相关知识,这一节就使用按键Key0作为中断源,在开发板上的Linux中添加一个外设中断。
开发步骤:
1.修改设备树文件,在KEY0设备节点中添加中断相关的属性
2.编写按键驱动程序和测试App程序
3.编译运行

二、驱动开发

1.修改设备树文件

  在之前使用gpio子系统开发按键驱动时,给设备树中添加了一个按键的key节点:

key {
		compatible = "amonter,key";
		status = "okay";
		key-gpio = <&gpiog 3 GPIO_ACTIVE_LOW>;
	};
  • 1
  • 2
  • 3
  • 4
  • 5

这一节就在此节点中添加中断相关的属性:

key {
	compatible = "amonter,key";
	status = "okay";
	key-gpio = <&gpiog 3 GPIO_ACTIVE_LOW>;
	interrupt-parent = <&gpiog>;
	interrupts = <3 IRQ_TYPE_EDGE_BOTH>;
	//interrupts-extended = <&gpiog 3 IRQ_TYPE_EDGE_BOTH>;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

设置 interrupt-parent 属性值为“gpiog”,因为 KEY0 所使用的 GPIO 为 PG3, 所以要设置 KEY0 的中断控制器为 gpiog
设置 interrupts 属性,也就是设置中断源,第一个 cells 的 3 表示 GPIOG 组的 3 号IO。IRQ_TYPE_EDGE_BOTH 为中断标志,表示上升沿和下降沿同时有效,所以按键按下和释放都有效。上一节讲述过,这个宏定义在文件include/linux/irq.h中,是一个有十种标志的枚举类型的成员
这两句描述等效于 interrupts-extended 的一句描述
修改完设备树后使用make dtbs命令重新编译出设备树文件,放在nfs的tftp目录用于启动开发板。

2.驱动及测试程序编写

中断程序驱动的开发在gpio子系统的按键驱动基础上进行修改,如下:
keyirq.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/semaphore.h>
#include <linux/of_gpio.h>
#include <linux/of_address.h>
#include <linux/of.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/irq.h>
#include <linux/of_irq.h>

#define KEY_CNT         1
#define KEY_NAME        "key"

/*定义按键状态*/
enum key_status {
    KEY_PRESS = 0,  /*按键按下*/
    KEY_RELEASE,    /*按键释放*/
    KEY_KEEP,       /*按键保持*/
};

/*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号*/

    struct timer_list timer;    /*定时器*/
    int irq_num;                /*中断号*/
    spinlock_t spinlock;        /*自旋锁*/
};

struct key_dev keydev;          /*创建key设备*/
static int status = KEY_KEEP;   /*按键状态*/

/*按键防抖处理,也就是开启定时器延时15ms,这里作为中断处理函数*/
static irqreturn_t key_interrupt(int irq, void *dev_id)
{
    mod_timer(&keydev.timer, jiffies + msecs_to_jiffies(15));
    return IRQ_HANDLED;
}

/*解析设备树中的按键属性*/
static int key_parse_dt(void){
    int ret;
    const char *str;

    /*设置Key所使用的GPIO*/
    /*获取设备节点*/
    keydev.nd = of_find_node_by_path("/key");
    if(keydev.nd == NULL){
        printk("keydev node not find!\r\n");
        return -EINVAL;
    }

    /*获取status值*/
    ret = of_property_read_string(keydev.nd, "status", &str);
    if(ret < 0){
        printk("read status failed!\r\n");
        return -EINVAL;
    }
    if(strcmp(str, "okay")){
        printk("key device not okay!\r\n");
        return -EINVAL;
    }

    /*获取compatible属性并匹配*/
    ret = of_property_read_string(keydev.nd, "compatible", &str);
    if(ret < 0){
        printk("read compatible failed!\r\n");
        return -EINVAL;
    }
    if(strcmp(str, "amonter,key")){
        printk("keydev: compatible match failed!\r\n");
        return -EINVAL;
    }

    /*获取设备树中的gpio属性,得到KEY0的设备编号*/
    keydev.key_gpio = of_get_named_gpio(keydev.nd, "key-gpio", 0);
    if(keydev.key_gpio < 0){
        printk("can't get key-gpio");
        return -EINVAL;
    }
    printk("key-gpio num = %d\r\n", keydev.key_gpio);

    /*获取GPIO对应的中断号*/
    keydev.irq_num = irq_of_parse_and_map(keydev.nd, 0);
    if(!keydev.irq_num){
        return -EINVAL;
    }
    printk("irq-num = %d\r\n", keydev.irq_num);

    return 0;
}

static int key_gpio_init(void)
{
    int ret;
    unsigned long irq_flags;

    /*向GPIO子系统申请使用GPIO*/
    ret = gpio_request(keydev.key_gpio, "KEY0");
    if(ret){
        printk(KERN_ERR "keydev: failed to request key-gpio!\r\n");
        return ret;
    }

    /*设置PG3为GPIO输入模式*/
    ret = gpio_direction_input(keydev.key_gpio);
    if(ret < 0){
        printk("can't set gpio mode!\r\n");
        return ret;
    }

    /*获取设备树中指定的中断触发类型*/
    irq_flags = irq_get_trigger_type(keydev.irq_num);
    if(IRQF_TRIGGER_NONE == irq_flags){
        irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;;
    }
    /*申请中断*/
    ret = request_irq(keydev.irq_num, key_interrupt, irq_flags, "Key0_IRQ", NULL);
    if(ret){
        gpio_free(keydev.key_gpio);
        return ret;
    }

    return 0;
}

static void key_timer_function(struct timer_list *arg)
{
    static int last_val = 1;
    unsigned long flags;
    int current_val;

    /*自旋锁上锁*/
    spin_lock_irqsave(&keydev.spinlock, flags);
    /*读取按键值并判断按键当前状态*/
    current_val = gpio_get_value(keydev.key_gpio);
    if(0 == current_val && last_val){   /*按键按下*/
        status = KEY_PRESS;
    } else if(1 == current_val && !last_val) {      /*按键释放*/
        status = KEY_RELEASE;
    } else{         /*保持*/
        status = KEY_KEEP;
    }

    last_val = current_val;

    /*自旋锁解锁*/
    spin_unlock_irqrestore(&keydev.spinlock, flags);
}

/*设备open操作函数*/
static int key_open(struct inode *inode, struct file *filp)
{
    return 0;
}

/*设备read操作函数*/
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){
    unsigned long flags;
    int ret;

    /*自旋锁上锁*/
    spin_lock_irqsave(&keydev.spinlock, flags);
    /*将按键信息发送给应用程序*/
    ret = copy_to_user(buf, &status, sizeof(int));
    /*状态重置*/
    status = KEY_KEEP;
    /*自旋锁解锁*/
    spin_unlock_irqrestore(&keydev.spinlock, flags);

    return ret;
}

/*设备write操作函数*/
static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

/*设备release操作函数*/
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,
};

/*驱动入口函数*/
static int __init mykey_init(void){
    int ret;
    /*初始化自旋锁*/
    spin_lock_init(&keydev.spinlock);

    /*设备树解析*/
    ret = key_parse_dt();
    if(ret){        /*出错*/
        return ret;
    }
    /*gpio中断初始化*/
    ret = key_gpio_init();
    if(ret){    /*出错*/
        return ret;
    }

    /*注册字符设备驱动*/
    /*创建设备号,直接使用系统分配的设备号*/
    ret = alloc_chrdev_region(&keydev.devid, 0, KEY_CNT, KEY_NAME);     //由系统分配设备号
    if(ret < 0){
        pr_err("%s couldn't alloc_chrdev_region, ret = %d\r\n", KEY_NAME, ret);
        goto free_gpio;
    }
    keydev.major = MAJOR(keydev.devid);
    keydev.minor = MINOR(keydev.devid);
    printk("kendev major = %d, minor = %d\r\n", keydev.major, keydev.minor);

    /*初始化cdev*/
    keydev.cdev.owner = THIS_MODULE;
    cdev_init(&keydev.cdev, &key_fops);
    /*添加一个cdev*/
    cdev_add(&keydev.cdev, keydev.devid, KEY_CNT);
    if(ret < 0){
        goto del_unregister;
    }

    /*创建类*/
    keydev.class = class_create(THIS_MODULE, KEY_NAME);
    if(IS_ERR(keydev.class)){
        goto del_cdev;
    }

    /*创建设备*/
    keydev.device = device_create(keydev.class, NULL, keydev.devid, NULL,KEY_NAME);
    if(IS_ERR(keydev.device)){
        goto destroy_class;
    }

    /*初始化timer,设置定时器处理函数*/
    timer_setup(&keydev.timer, key_timer_function, 0);  //未设置周期,所以不会激活定时期
    return 0;

destroy_class:
    device_destroy(keydev.class, keydev.devid);
del_cdev:
    cdev_del(&keydev.cdev);
del_unregister:
    unregister_chrdev_region(keydev.devid, KEY_CNT);
free_gpio:
    free_irq(keydev.irq_num, NULL);
    gpio_free(keydev.key_gpio);
    return -EIO;
}

/*驱动出口函数*/
static void __exit mykey_exit(void){
    cdev_del(&keydev.cdev);
    unregister_chrdev_region(keydev.devid, KEY_CNT);
    del_timer_sync(&keydev.timer);      /*删除timer*/
    device_destroy(keydev.class, keydev.devid);
    class_destroy(keydev.class);
    free_irq(keydev.irq_num, NULL);     /*释放中断*/
    gpio_free(keydev.key_gpio);         /*释放IO*/
}

module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("amonter");
MODULE_INFO(intree, "Y");

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293

①首先包含中断相关的头文件<linux/irq.h><linux/of_irq.h>
②将按键的键值封装成一个枚举类型key_status,包括三种状态:按下、释放、保持
③在设备结构体中增加三个成员变量:定时器、中断号、自旋锁
④维护一个中断服务函数 key_interrupt() ,在此中断中进行按键消抖处理(本质是延时15ms,使用定时器来计时)
⑤将从设备树中解析属性以及初始化GPIO设备分成两部分:key_parse_dt() 用来从设备树中解析属性,与gpio子系统下的驱动相比,多了一个申请中断号的步骤。key_gpio_init() 则用来向Linux内核申请使用GPIO,此部分还额外增加了向内核申请中断的步骤,使用 request_irq 申请中断,并设置 key_interrupt 函数作为按键中断处理函数,当按键中断发生之后便会跳转到该函数执行;request_irq 函数会默认使能中断,所以不需要 enable_irq 来使能中断;当然也可以在申请成功之后先使用 disable_irq 函数禁用中断,等所有工作完成之后再来使能中断,这样会比较安全,更推荐这样使用。
⑥key_timer_function() 为定时器处理函数,用于获取按键的键值,并以此来判断按键当前的状态,此部分用自旋锁进行保护
⑦key_read() 将按键当前的键值传递给用户程序,也用自旋锁保护
⑧在驱动入口函数中,增加了timer的初始化,其中包含着定时器处理函数的注册
⑨在驱动入口函数中获取gpio设备出错时,以及驱动注销函数中,在注销字符设备之后,就删除定时器;另外在释放gpio设备之前,先对中断进行释放

3.测试程序

测试程序的功能为:不断读取/dev/key设备文件来获取按键状态,0表示按下,1表示松开,2表示按键保持某个状态。
key_irqApp.c

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

int main(int argc, char *argv[])
{
    int fd, ret;
    int key_val;

    /* 判断传参个数是否正确 */
    if(2 != argc) {
        printf("Usage:\n" "\t./keyApp /dev/key\n");
        return -1;
    }

    /* 打开设备 */
    fd = open(argv[1], O_RDONLY);
    if(0 > fd) {
        printf("ERROR: %s file open failed!\n", argv[1]);
        return -1;
    }

    while(1){
        read(fd, &key_val, sizeof(int));
        if (0 == key_val){
            printf("Key Press\n");
        } else if(1 == key_val){
            printf("Key Release\n");
        }
    }

    /* 关闭设备 */
    close(fd);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

在循环中不断的读取按键值,如果读取到的值是 0 则打印“Key Press”,读取到的值是 1 则打印“Key Release”。

三、编译及测试

对驱动文件和测试程序进行编译,然后放到nfs的对应目录下,运行开发板测试。
在这里插入图片描述
使用cat /proc/interrupts命令就可以查看到注册的中断信息,再使用./key_irqApp /dev/key命令来测试中断,当按下开发板的KEY0键,就会打印出按键按下和释放的信息,并且由于中断中做了按键消抖处理,所以不会因为按键抖动导致发生误判的情况:
在这里插入图片描述
最后使用rmmod keyirq.ko卸载驱动设备。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号