【linux驱动】定时器的使用

本文详细介绍了Linux驱动中定时器的原理、配置方法(包括HZ的设定),提供了API示例以及如何在GPIO中断中使用定时器进行按键防抖处理。通过实例展示了定时器的初始化、启动、停止和删除操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【linux驱动】定时器的使用

1.介绍

1.1相关名词

HZ、jiffies、tick

Linux系统启动后,每隔固定周期就会发出timer interrupt(IRQ 0),HZ用来定义每一秒发生多少次timer interrupt;
一般HZ的值并不确定,可以被修改设定;可供修改的值有:100HZ 200HZ 250HZ 300HZ 500HZ 1000HZ;
具体修改方法下面有
Tick是HZ的倒数,意即timer;
也就是发生一次中断的时间;比如HZ是250时,tick为1/250s,也就是4ms;
Jiffies为Linux核心变数(unsigned long)用来记录系统开启以来,发生的timer interrupt的次数;这里需要注意的就是32位的类型会出现溢出导致数据不准确(这个时间好像是30天还是40天,记不住,大概是这个时间);64位的类型就不会出现这个问题,溢出都要几百万年了;其他的倒是不用怎么关注,定时器的使用,程序里面都是套路;下面是一些其他的内容,提供给想了解的同志。

在这里插入图片描述

1.2配置HZ的方法

menuconfig

打开menuconfig配置界面

cd xxx/Linux-xxx.xxx 
make menuconfig

在这里插入图片描述

我的系统默认选择的是100HZ

其他选项:

在这里插入图片描述

.config

cd 到对应linux源码顶层目录下
vi .config

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
在这里插入图片描述

修改后,可以使用下面的命令:
esc 后  :wq!
要是只是看看,就直接esc :q!

2.API

头文件:

#include <linux/timer.h>

对应结构体:

    struct timer_list { 
        struct hlist_node entry; //构成内核链表相关成员
        unsigned long  expires; //定时器到期时间
        void   (*function)(struct timer_list *); //定时器处理函数
        u32			flags; //一般填写为0
    };

初始化定时器:

方式1void timer_func(struct timer_list *timer)
 {
 }
 mytimer.expires = jiffies+HZ;  //# define HZ CONFIG_HZ 定时1s
 timer_setup(&mytimer, timer_func, 0);


方式2/*我的好像是linux版本太低,导致无法使用比较新的API,因此使用下面的方式*/
 void timer_func(struct timer_list *timer)
 {
 }
init_timer(&mytimer);
mytimer.expires = jiffies + HZ/50;
mytimer.function = timer_func;  // 设置定时器到期时调用的回调函数

还有其他的方式,这里就不一一赘述,有兴趣的可以多多探索

启动定时器:

    void add_timer(struct timer_list *timer)
    //启动定时器,定时器启动之后只会执行一次,add_timer只能调用一次,
    //如果第二次调用内核会崩溃
    
    int mod_timer(struct timer_list *timer, unsigned long expires)
    //功能再次启动定时器

删除定时器:

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

3.示例

key.h

#ifndef __KEY_CTRL_H__
#define __KEY_CTRL_H__

#include <linux/timer.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/timer.h>

#define KEY_NAME1 "KEY_USER1"
#define KEY_NAME2 "KEY_USER2"
#define CONSUMER_LABEL1 "user1"
#define CONSUMER_LABEL2 "user2"

#define PATH_DTS_KEY_USR1 "/psd_key_irqs/user1"
#define PATH_DTS_KEY_USR2 "/psd_key_irqs/user2"

#define KMD_ERR(str) \
	printk("%s %s line: %d %s \n", __FILE__, __FUNCTION__, __LINE__, str);

typedef struct my_key {
    char *dev_name;
    struct device_node *key_node;
    unsigned int key_irq_no;
    int key_num;
    int key_status;//led开关状态
    int Level_state;//电平状态
    struct timer_list mytimer; // 分配定时器
} key_ctrl_t;

/*key1设备树控制的初始化*/
int key1_ctrl_init(key_ctrl_t * key);
/*key2设备树控制的初始化*/
int key2_ctrl_init(key_ctrl_t * key);
/*led设备树控制的卸载处理函数*/
void key_ctrl_exit(key_ctrl_t * key);

#endif

key.c

#include"key_ctrl.h"
#include <linux/timer.h>


void my_timer_callback1(unsigned long para)
{
        printk("key1 down...\n");
}

void my_timer_callback2(unsigned long para)
{
        printk("key2 down...\n");
}

irqreturn_t key1_irq_handle(int irq, void* dev)
{
    key_ctrl_t *key1 = (key_ctrl_t *)dev;
    // 启动定时器
    mod_timer(&key1->mytimer, jiffies + HZ/50);
    return IRQ_HANDLED;
}

irqreturn_t key2_irq_handle(int irq, void* dev)
{
    key_ctrl_t * key2 = (key_ctrl_t *)dev;
    // 启动定时器
    mod_timer(&key2->mytimer, jiffies + HZ/50);
    return IRQ_HANDLED;
}

int key1_ctrl_init(key_ctrl_t * key)
{
    int ret;
    key->key_node = of_find_node_by_path(PATH_DTS_KEY_USR1);
    if(IS_ERR(key->key_node))
    {
        KMD_ERR("of_find_node_by_path ERR");
        ret = -ENODATA;
        goto exit;
    }
    // 2.解析得到软中断号
    key->key_irq_no = irq_of_parse_and_map(key->key_node, 0);
    if (key->key_irq_no == 0) {
        printk("irq_of_parse_and_map error\n");
        ret = -EAGAIN;
        goto exit_node;
        // 资源暂时不可用
    }

    init_timer(&key->mytimer);
    key->mytimer.expires = jiffies + HZ/50;
    // timer_setup_on_stack(&key->mytimer, my_timer_callback1, 0);
    key->mytimer.function = my_timer_callback1;  // 设置定时器到期时调用的回调函数
    add_timer(&key->mytimer);              //将定时器加入到系统定时器链表中

    ret = request_irq(key->key_irq_no, key1_irq_handle,
        IRQF_TRIGGER_LOW, KEY_NAME1, key);
    if (ret) {
        printk("request_irq key1 error\n");
        goto exit_irq;
    }
    return 0;
exit_irq:
    free_irq(key->key_irq_no, NULL);
exit_node:
    of_node_put(key->key_node);
exit:
    return ret;  // 出错返回
}

int key2_ctrl_init(key_ctrl_t * key)
{
    int ret;
    key->key_node = of_find_node_by_path(PATH_DTS_KEY_USR2);
    if(IS_ERR(key->key_node))
    {
        KMD_ERR("of_find_node_by_path ERR");
        ret = -ENODATA;
        goto exit;
    }

    // 2.解析得到软中断号
    key->key_irq_no = irq_of_parse_and_map(key->key_node, 0);
    if (key->key_irq_no == 0) {
        printk("irq_of_parse_and_map error\n");
        of_node_put(key->key_node); // 清理已获取的节点
        ret = -EAGAIN;
        goto exit_node;
        // 资源暂时不可用
    }

    init_timer(&key->mytimer);        
    key->mytimer.expires = jiffies + HZ/50;
    // timer_setup(&key->mytimer, my_timer_callback2, 0);
    key->mytimer.function = my_timer_callback2;  // 设置定时器到期时调用的回调函数
    add_timer(&key->mytimer);                //将定时器加入到系统定时器链表中

    ret = request_irq(key->key_irq_no, key2_irq_handle,
        IRQF_TRIGGER_LOW, KEY_NAME2, key);
    if (ret) {
        printk("request_irq key2 error\n");
        goto exit_irq;
    }
    return 0;
exit_irq:
    free_irq(key->key_irq_no, NULL);
exit_node:
    of_node_put(key->key_node);
exit:
    return ret;  // 出错返回
}

void key_ctrl_exit(key_ctrl_t * key)
{
    free_irq(key->key_irq_no,key);
    if (timer_pending(&key->mytimer)) {
        del_timer_sync(&key->mytimer);
    }

}

MODULE_DESCRIPTION("key_ctrl_driver");
MODULE_LICENSE("GPL");

4.调试

这里的按键防抖需要注意以下几点:

首先不加定时器防抖看下,按键硬件是否已经存在防抖机制
其次,使用定时器的时候要确定linux的HZ是多少,确定一次timer irq是多久,再添加防抖代码
再一个就是一般来说,按键抖动会在20ms左右,
最后就是如果HZ是100hz、jiffies+1就是在计数的基础上延时10ms触发自定义的定时器中断函数

over,祝各位定时器使用愉快!

### 实现 Linux 驱动程序中的定时器功能 #### 定义和初始化定时器 为了在 Linux 驱动程序中使用定时器,首先需要定义并初始化 `timer_list` 结构体实例。这可以通过静态声明或动态分配来完成。 ```c #include <linux/timer.h> static struct timer_list my_timer; // 初始化定时器 void init_my_timer(void) { setup_timer(&my_timer, timer_callback_function, 0); } ``` 此代码片段展示了如何创建一个名为 `my_timer` 的定时器对象,并通过调用 `setup_timer()` 函数为其指定回调函数 `timer_callback_function`[^4]。 #### 设置定时器超时时间和启动定时器 设置好定时器之后,下一步就是配置其到期时间并通过 `mod_timer()` 启动它: ```c unsigned long delay = msecs_to_jiffies(1000); // 将毫秒转换为节拍数 int start_timer(void) { return mod_timer(&my_timer, jiffies + delay); } ``` 这里设置了定时器在一秒钟后触发事件;注意 `msecs_to_jiffies()` 是用于将实际的时间单位(如毫秒)转化为内核使用的节拍计数值。 #### 编写定时器回调函数 当设定的时间到达时,内核会自动调用之前注册过的回调函数,在该函数内部可以放置希望周期性执行的任务逻辑: ```c void timer_callback_function(struct timer_list *t) { printk(KERN_INFO "Timer expired\n"); // 如果还需要继续循环,则重新加载定时器 start_timer(); } ``` 这段代码实现了简单的日志打印操作作为例子展示,同时再次激活定时器以便形成连续的工作流[^2]。 #### 清除定时器资源 最后不要忘记编写清理路径以释放任何已分配给定时器的数据结构或其他关联资源: ```c void cleanup_timer(void) { del_timer_sync(&my_timer); } ``` 上述过程描述了一个完整的流程,从定义到最终清除一个基本的 Linux 内核定时器实例[^3]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值