【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
};
初始化定时器:
方式1:
void 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,祝各位定时器使用愉快!