Linux系统驱动(十三)Linux内核定时器

一、内核定时器原理

内核当前时间通过 jiffies (内核时钟节拍数) 获取,它是一位64位的变量。
在linux内核启动的时候,jiffies开始(按照一定频率)增加。
在驱动中可以直接使用jiffies获取当前时间。

定时器每增加1走的时间由频率决定,定时器的频率可以通过 make menuconfig 进行选配,选配后的结果在 .config 文件中保存,选项是 CONFIG_HZ。

linux-5.10.61内核 CONFIG_HZ=100,周期是1/100s,定时器每增加1走10ms。
ubuntu的内核 CONFIG_HZ=250,定时器每增加1走4ms。

二、定时器API

1.分配对象
 	struct timer_list {
        struct hlist_node entry; 	//构成内核链表成员
        unsigned long expires; 		//定时器到期时间
        void (*function)(struct timer_list *);//定时器处理函数(定时时间到执行的函数)
        u32	flags; //新版本内核添加,一般填写为0
    };
    
    struct timer_list mytimer;

2.对象初始化
    mytimer.expires = jiffies+HZ; //定时1s 
 	timer_setup(&mytimer, 定时器处理函数, 0); 
	//HZ是内核中定义的一个宏,+HZ总是1s
	
3.启动定时器
    void add_timer(struct timer_list *timer)
    功能:启动定时器,这个定时器只会执行一次
    备注:add_timer只能调用一次,多次调用内核会崩溃
    
	int mod_timer(struct timer_list *timer, unsigned long expires)
	功能:再次启动定时器

4.删除定时器
 	int del_timer(struct timer_list *timer)

三、使用定时器让LED灯闪烁

实现了六盏灯同亮同灭

#include <linux/module.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include "mynode.h"
/*设备树文件	
	myleds{
		core_leds{
			led1 = <&gpioz 5 0>;
			led2 = <&gpioz 6 0>;
			led3 = <&gpioz 7 0>;
		};
		extend_leds{
			led1 = <&gpioe 10 0>;
			led2 = <&gpiof 10 0>;
			led3 = <&gpioe 8 0>;
		};
	};
*/
struct device_node *core_leds, *extend_leds;

int core_gpiono[3];
int extend_gpiono[3];

char *cname[3]={"led1","led2","led3"};

struct timer_list mytimer;
int flag = 0;
void timer_handler(struct timer_list *timerlist){
    if(flag){
        gpio_set_value(core_gpiono[0],1);
        gpio_set_value(core_gpiono[1],1);
        gpio_set_value(core_gpiono[2],1);
        gpio_set_value(extend_gpiono[0],1);
        gpio_set_value(extend_gpiono[1],1);
        gpio_set_value(extend_gpiono[2],1);
        flag=0;
    }else{
        gpio_set_value(core_gpiono[0],0);
        gpio_set_value(core_gpiono[1],0);
        gpio_set_value(core_gpiono[2],0);
        gpio_set_value(extend_gpiono[0],0);
        gpio_set_value(extend_gpiono[1],0);
        gpio_set_value(extend_gpiono[2],0);
        flag=1;
    }
    //再次启动定时器
    mod_timer(&mytimer,jiffies+HZ);
}

static __init int mynode_init(void){
    int ret;
    int i;

    /***初始化gpio***/
    //1. 获取设备节点
    core_leds = of_find_node_by_path("/myleds/core_leds");
    if(NULL == core_leds){
        pr_err("of_find_node_by_name failed\n");
        ret = -ENODATA;
        goto err0;
    }
    extend_leds = of_find_node_by_path("/myleds/extend_leds");
    if(NULL == extend_leds){
        pr_err("of_find_node_by_name failed\n");
        ret = -ENODATA;
        goto err0;
    }
    //2. 获取gpio号
    for(i=0;i<3;i++){
        core_gpiono[i] = of_get_named_gpio(core_leds,cname[i],0);
        if(core_gpiono[i]<0){
            pr_err("core_gpiono of_get_named_gpio failed\n");
            ret = core_gpiono[i];
            goto err0;
        }
    }
    for(i=0;i<3;i++){
        extend_gpiono[i] = of_get_named_gpio(extend_leds,cname[i],0);
        if(core_gpiono[i]<0){
            pr_err("extend_gpiono of_get_named_gpio failed\n");
            ret = core_gpiono[i];
            goto err0;
        }
    }
    //3. 申请GPIO号
    for(i=0;i<3;i++){
        ret = gpio_request(core_gpiono[i],NULL);
        if(ret){
            pr_err("core_gpiono gpio_request failed\n");
            goto err1;
        }
    }
    for(i=0;i<3;i++){
        ret = gpio_request(extend_gpiono[i],NULL);
        if(ret){
            pr_err("extend_gpiono gpio_request failed\n");
            goto err2;
        }
    }
    //4. 初始化led为输出,低电平
    for(i=0;i<3;i++){
        gpio_direction_output(core_gpiono[i],0);
        gpio_direction_output(extend_gpiono[i],0);
    }
    /***初始化定时器***/
    //初始化对象
    mytimer.expires = jiffies + HZ;
    timer_setup(&mytimer,timer_handler,0);
    //启动定时器
    add_timer(&mytimer);
    return 0;

err2:
    for(--i;i>=0;i--){
        gpio_free(extend_gpiono[i]);
    }
    i=3;
err1:
    for(--i;i>=0;i--){
        gpio_free(core_gpiono[i]);
    }
err0:
    return ret;
}

static __exit void mynode_exit(void){
    int i;
    for(i=0;i<3;i++){
        gpio_set_value(core_gpiono[i],0);
        gpio_set_value(extend_gpiono[i],0);
    }
    for(i=0;i<3;i++){
        gpio_free(extend_gpiono[i]);
        gpio_free(core_gpiono[i]);
    }
}

module_init(mynode_init);
module_exit(mynode_exit);
MODULE_LICENSE("GPL");

四、使用定时器对按键进行消抖

在这里插入图片描述

  • 注:GPIO仅需要读取其状态即可,不存在竞态的问题,因此无需申请GPIO号,仅获取即可。
  • GPIO在任意复用模式下都支持读操作。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/of_gpio.h>

/*设备树文件	
	mykeys{
		interrupt-parent = <&gpiof>;
		interrupts = <9 0>, <7 0>, <8 0>;
		keys_no = <&gpiof 9 0>, <&gpiof 7 0>, <&gpiof 8 0>;
	};
*/
struct device_node *devnode; 	//获取的设备树节点
unsigned int irq[3];			//软中断号
unsigned int key_gpiono[3];		//gpio号
struct timer_list mytimer;		//定时器
char *irq_name[3]={"key1_irq","key2_irq","key3_irq"};

irqreturn_t my_irq_handle(int irq, void *dev){
    mod_timer(&mytimer,jiffies+1);  //按下按键启动定时器
    return IRQ_HANDLED;
}

void timer_handler(struct timer_list *timerlist){
    if(!gpio_get_value(key_gpiono[0])){
        printk("key1 down...\n");
    }
    if(!gpio_get_value(key_gpiono[1])){
        printk("key2 down...\n");
    }
    if(!gpio_get_value(key_gpiono[2])){
        printk("key3 down...\n");
    }
}

static __init int mynode_init(void){
    int ret,i;
    /******初始化gpio******/
    //定时器初始化完会进入一次定时器中断处理函数,如果此时没有获取gpio号就会出错,因此初始化gpio就要放在定时器初始化前面
    //获取节点
    devnode=of_find_node_by_path("/mykeys");
    if(devnode == NULL){
        pr_err("of_find_node_by_path error\n");
        ret = -EINVAL;
        goto err0;
    }
    //获取gpio号
    for(i=0;i<3;i++){
        key_gpiono[i] = of_get_named_gpio(devnode,"keys_no",i);
        if(!key_gpiono[i]){
            pr_err("of_get_named_gpio error\n");
            ret = key_gpiono[i];
            goto err0;
        }
    }
    /******初始化定时器******/
    mytimer.expires=jiffies+1;  //定时10ms
    timer_setup(&mytimer,timer_handler,0);
    //如果先初始化中断,在定时器未初始化时触发中断的话,就会导致mod_timer第一个参数为空指针导致崩溃
    //因此先初始化定时器,再初始化中断
    /******注册中断******/
    //获取软中断号
    for(i=0;i<3;i++){
        irq[i]=irq_of_parse_and_map(devnode,i);
        if(!irq[i]){
            pr_err("irq_of_parse_and_map error\n");
            ret = -EINVAL;
            goto err1;
        }
    }
    //注册中断
    for(i=0;i<3;i++){
        ret = request_irq(irq[i],my_irq_handle,IRQF_TRIGGER_FALLING,irq_name[i],NULL);
        if(ret){
            pr_err("request_irq error\n");
            goto err2;
        }
    }
    //开启定时器
    add_timer(&mytimer);
    return 0;
err2:
    for(--i;i>=0;i--){
        free_irq(irq[i],NULL);
    }
err1:
    del_timer(&mytimer);
err0:
    return ret;
}

static __exit void mynode_exit(void){
    int i;
    del_timer(&mytimer);
    for(i=0;i<3;i++){
        free_irq(irq[i],NULL);
    }
}

module_init(mynode_init);
module_exit(mynode_exit);
MODULE_LICENSE("GPL");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值