STM32mp157驱动开发—中断实验

参考的教程是正点原子linux驱动开发教程

使用的板子是正点原子stm32mp157开发板


裸机中,使用中断的处理方法:

  1. 使能中断,初始化相应的寄存器
  2. 注册中断服务函数,向irqTable数组指定的标号处写入中断服务函数
  3. 中断发生以后进入IRQ中断服务函数,中断服务函数在irqTable里面查找具体的中断处理函数,找到以后执行相应的中断处理函数

上半部与下半部

我们在使用request_irq申请中断的时候注册的中断服务函数属于中断处理的上半部。

上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上班部分完成

下半部:如果中断处理过程比较耗时,那么就将这些耗时的代码提出来,交给下半部去执行,这样中断函数就会快进快出。

分为上半部和下半部的主要目的就是:实现中断处理函数快进快出。

主要有以下几个参考点:

  1. 如果要处理的内容不希望被其他中断打断,那么可以放到上半部
  2. 如果要处理的任务对时间敏感,可以放到上半部
  3. 如果要处理的任务与硬件有关,可以放到上半部
  4. 除了上述的三点以外的任务,可以优先考虑放到下半部

下半部的方法有好几种,暂时用不到,大概学习了一下,等具体用到了在深入学习,咱不好高骛远。


实验内容:

利用中断对按键进行消抖,这里利用了定时器。

首先,对设备树的节点进行修改。

//dada2023.3.17
	key0{

		compatible = "dada,key";
		status = "okay";
		key-gpio = <&gpiog 3 GPIO_ACTIVE_LOW>;
		interrupt-parent = <&gpiog>;
		interrupts = <3 IRQ_TYPE_EDGE_BOTH>;
	};

我在上面标注了一下是谁改的,改的时间,为了方便。

这里分别设置了一下中断类型,以及触发的类型,选用的是上下沿都触发。

至于compatible属性就按照自己来定义吧,我写的是“dada,key”。

这些都是以前的知识了,只不过多了一个中断,也很好理解的,但是设备树这里中断有两种写法,我用的是第二种。

然后把设备树编译一下,放进tftp里面重新加载。

可以看到设备树里面已经有了。

接下来就是编写驱动。

照例,先定义结构体。

//定义key设备结构体
struct key_dev{
    dev_t devid;//设备号
    int major;  //主设备号
    int minor;  //次设备号
    struct cdev cdev;//注册函数
    struct class *class;//节点
    struct device *device;//设备
    struct device_node *nd;//设备节点
    int keydev;           //使用的设备号
    struct timer_list timer; //定时器的结构体
    int irq_num;          //中断号
    spinlock_t spinlock;  //自旋锁,用于中断
};

用枚举表示一下按键的状态。

//按键的状态
enum key_status {
    KEY_PRESS = 0,//按键按下
    KEY_RELEASE ,  //按键松开
    KEY_KEEP ,     //按键保持
};

定义一个全局变量,用来获取按键的状态。

static int status = KEY_KEEP;   //按键的状态

然后,获取设备树属性

//获取设备树属性内容
    //获取设备节点
    key.nd = of_find_node_by_path("/key0");
    if(key.nd == NULL){ 
        return -EINVAL;
    }

    //获取compatible属性
    ret = of_property_read_string(key.nd, "compatible", &str);
    if (ret<0)
    {
        printk("Compatible  find failed\r\n");
        return -EINVAL;
    }
    else if (strcmp(str,"dada,key"))
    {
        printk("key: Compatible match failed!\n");
    }

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

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

    //获取GPIO对应的中断号
    key.irq_num = irq_of_parse_and_map(key.nd,0);
    if(!key.irq_num){
        return -EINVAL;
    }

    //向gpio子系统申请使用gpio,使能gpio
    ret = gpio_request(key.keydev,"KEY-GPIO");
    if(ret){
        printk(KERN_ERR "gpioled: Failed to request key-gpio\n");//疑问点
        return ret;
    }

    //设置pi0为输出,并且默认上拉
    ret = gpio_direction_input(key.keydev);
    if(ret < 0)
    {
        printk("Can't set gpio!\r\n");
    }

    //获取设备树中指定的中断触发类型

    irq_flags = irq_get_trigger_type(key.irq_num);
    if(IRQF_TRIGGER_NONE == irq_flags){
        irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;//上下沿都触发
    }

    //申请中断
    ret = request_irq(key.irq_num,key_interrupt,irq_flags,"key0_IRQ",NULL);

    if(ret){
        gpio_free(key.keydev);
        return ret;
    }
    return 0;

在申请中断的时候,发现少了中断函数,没有关系,我们定义一个。

static irqreturn_t key_interrupt(int irq,void *dev_id){

    return IRQ_HANDLED;

}

别问,问就是从内核文件里学的。

好了,我们要清楚一件事情,这个中断是用来干啥的。

对,是用来消抖的。

当我们按下去或者松开,要触发中断并实现一个消抖的功能。

那么,就要用到延时。

这就想到之前学的内核定时器,完全可以实现这个功能。

好,现在去初始化一下我们的内核定时器。

在这之前,我们还需要把字符驱动设备的注册流程走下去。

//注册字符
    //构造设备号
    if(key.major)//定义了主设备号
    {

        key.devid = MKDEV(key.major,0);
        ret = register_chrdev_region(key.devid,DTSLED_CNT,DTSLED_NAME);
        if(ret<0){
            printk("key driver register failed!\r\n");
            goto free_gpio;
        }
    }else {
        ret = alloc_chrdev_region(&key.devid,0,DTSLED_CNT,DTSLED_NAME);//没有定义就从内核申请
        if(ret<0){
            printk("key driver register failed!\r\n");
            goto free_gpio;
        }

    }

    //添加字符设备
    key.cdev.owner = THIS_MODULE;
    cdev_init(&key.cdev,&key_fops);//初始化
    ret = cdev_add(&key.cdev,key.devid,DTSLED_CNT);//添加字符设备
    if(ret<0){
            goto del_unregister;
    }

    //自动创建设备节点
    key.class = class_create(THIS_MODULE, DTSLED_NAME);
    if (IS_ERR(key.class)) {
		pr_err("QAT: class_create failed for adf_ctl\n");
		goto err_cdev_del;
	}

    //创建设备
    key.device = device_create(key.class, NULL,
				   key.devid,
				   NULL,  DTSLED_NAME);
	if (IS_ERR( key.device)) {
		pr_err("QAT: failed to create device\n");
		goto destroy_class;
	}

    //初始化定时器
    timer_setup(&key.timer,key_timer_fuction,0);

    printk("key_init\r\n");
    return 0;

 destroy_class:
   class_destroy(key.class);
 err_cdev_del:
   cdev_del(&key.cdev);
 del_unregister:
   unregister_chrdev_region(key.devid, DTSLED_CNT);
 free_gpio:
   gpio_free(key.keydev);
   return -EIO;

对了,别忘了初始化一下自旋锁,毕竟这里用到了中断,自旋锁是可以用到中断里的,因为它不会引发休眠!!!

 int ret;
    //初始化自旋锁
    spin_lock_init(&key.spinlock);
    //设备树属性获取
    ret = keygpio_init();
    if(ret)
       return ret;

这段加在前面那段的前面就行。

那么问题又来了,初始化定时器的时候,需要一个定时器中断函数。

也就是到了时间,我们要做一个什么的动作。

当然是获取稳定之后的键值,然后给当前的状态了。

有了这个思路,上代码!!

static void key_timer_fuction(struct timer_list * arg){

    static int last_val = 1;
    unsigned long flags ;
    int current_val;

    //自旋锁上锁
    spin_lock_irqsave(&key.spinlock,flags);

    //读取当前的状态值,status是一个全局变量!
    current_val = gpio_get_value(key.keydev);
    //如果current_val为0,无论last_val是什么,结果都0,当前肯定是被按下的
    if(0 == current_val && last_val) 
       status = KEY_PRESS;
    else if(1 == current_val && !last_val)
       status = KEY_RELEASE; //如果c为1,那么就判断一下上一次是不是1或者2,如果是就是保持
    else
       status = KEY_KEEP;
    

    last_val = current_val;

    //解锁
    spin_unlock_irqrestore(&key.spinlock,flags);

}

注释写的已经很明白了,如果还看不懂怎么判断的话,可以评论区留言,因为我也怕我理解错了,毕竟我还是个小白。

然后再去完善一下中断。

其实就是中断调用定时器了。

这里采用mod_timer函数,是老朋友了。

static irqreturn_t key_interrupt(int irq,void *dev_id){

    mod_timer(&key.timer,jiffies + msecs_to_jiffies(15));
    return IRQ_HANDLED;

}

这样大部分就已经完全了。

想要从应用层调用,那么还得用上file_operations这个结构体定义的函数。

因为是读取键值,所以采用的是read函数,很简单,只要获取一下键值就行了。

/*
* @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;
    unsigned long flags;

    spin_lock_irqsave(&key.spinlock,flags);

    ret = copy_to_user(buf,&status,sizeof(int));

    status = KEY_KEEP;

    spin_unlock_irqrestore(&key.spinlock,flags);


    return ret;

}

这样,驱动就已经编写好了。

然后测试就更简单了,循环读取按键值并且打印出来就行。

接下来,上开发板测试一下吧!

我快速按然后松开,发现都能完美的读取出来,程序很成功,如果没有这个消抖的话,还是会出问题的。

成功了!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值