硬件定时器能够按照一定的频率周期性的有规律的给CPU发送中断信号,发送中断的频率(周期)可以通过软件编程来设置,硬件定时器产生的中断信号可以称之为时钟中断。
硬件定时器产生的中断同样也会有中断处理函数,只是这个中断处理函数由内核已经帮你写好,此中断处理函数同样被内核按照一定的频率周期性的有规律的被内核调用。
硬件定时器对应的中断处理函数会做如下内容:
HZ: 是内核的一个全局常量(不同CPU架构中这个宏定义不同),例如:
jiffies_64: 是内核的一个全局变量,64位(unsigned long long),记录自开机以来,硬件定时器给CPU发送的硬件定时器中断的次数,即每发生一次硬件定时器中断,jiffies_64自动加一(由硬件定时器的中断处理函数进行执行),也就是时间上加10ms。
jiffies: 也是内核的一个全局变量,32位(unsigned long),它的值取的是jiffies_64的低32位,也就是每发生一次硬件定时器中断,jiffies_64和jiffies都会加一,但是由于jiffies的存储范围的原因,通常用来记录时间间隔(记录流失的时间),切记:通常内核中使用jiffies一般是用来获取当前时刻,用来设置超时时间的。
unsigned long timeout = jiffies + 5*HZ;
//说明:jiffies:表示代码执行到这条语句对应的当前时刻的时间
//5*HZ=5*100=500:表示500次硬件定时器中断,一次为10ms
//所以5*HZ表示5秒钟
//timeout:5秒以后的时间
unsigned long timeout = jiffies + 2;
//说明:jiffies:表示代码执行到这条语句对应的当前时刻的时间
//2次硬件定时器中断,共20ms
//timeout:20ms以后的时间
unsigned long timeout = jiffies + 5*HZ;
//一堆代码
...
...
...
//以上代码执行完毕,判断是否发生了超时现象
if(jiffies > timeout)
//超时
else
//没超时
使用以上方法粗看上去没有问题,但是细想其实忽略了jiffies溢出的情况,如果开始取timeout的时候取得的值是jiffies溢出后的值,那么按照以上方式判断就会有很大问题了。Linux内核中引入了一个考虑到会有溢出情况的超时判断函数(time_after()),所以实际使用时应该如下使用:
unsigned long timeout = jiffies + 5*HZ;
//一堆代码
...
...
...
//以上代码执行完毕,判断是否发生了超时现象
if(time_after(jiffies, timeout))
超时
else
没超时
可以指定一个超时时间,一旦超时时间到期,内核会自动调用定时器的超时处理函数。切记:Linux内核软件定时器是基于软中断实现,所以其超时处理函数不能进行休眠操作。
数据结构:
struct timer_list {
unsigned long expires;
void (*function)(unsigned long data);
unsigned long data;
...
};
成员:
配套函数:
init_timer(&定时器对象);//初始化定时器对象
add_timer(&定时器对象);//向内核注册添加一个定时器,
del_timer(&定时器对象);//从内核中删除定时器
mod_timer(&定时器对象,新的超时时刻的时间);//修改定时器的超时时刻的时间
//注意:mod_timer=先del_timer,然后expires=jiffies+20*HZ,最后add_timer
设置定时亮灭GPIO, 在这里加入btn操作超时时间,key_up增加1s延迟,key_down降低1s延迟,延迟范围(0~10)。
#include
#include
#include
#include
#include
//声明描述LED的硬件信息的数据结构
//定义初始化4个LED硬件信息对象
//定义定时器对象
static struct timer_list mytimer;
//定时器的超时处理函数
//定时器超时时间到期,内核执行此函数
//切记:千万不能进行休眠操作
//data = (unsigned long)&g_data
static void mytimer_function(unsigned long data)
{
//1.如果是开灯,那就关灯;如果是关灯,那就开灯
//注意:不允许使用if ... else判断
printk("%s: data = %#x\n", __func__, *(int *)data);
//2.重新向内核添加定时器对象
/*以下两条语句相当之危险,因为他们访问了一个全局变量mytimer,如果将来有高优先级的硬件中断和高优先级的软中断来打断
* 这两条语句的执行,势必造成定时器的错误,需要考虑互斥问题
mytimer.expires = jiffies + 2*HZ; //重新添加新的超时时间
add_timer(&mytimer);
*/
//del_timer+expire=...+add_timer
//此函数非常安全,因为里面做了互斥访问机制
mod_timer(&mytimer, jiffies+2*HZ);
}
/*
static irqreturn_t button_isr(int irq, void *dev)
{
//此代码会篡改定时器的超时时间
mytimer.expires = jiffies + 50*HZ;
return IRQ_HANDLED;
}
*/
static int g_data = 0x5555;
static int mytimer_init(void)
{
//1.申请GPIO资源,配置GPIO为输出,输出1
//2.初始化定时器对象
init_timer(&mytimer);
//3.额外指定定时器的超时时间
mytimer.expires = jiffies + 2*HZ;
//4.额外指定定时器的超时处理函数
mytimer.function = mytimer_function;
//5.额外指定给超时处理函数传递的参数
mytimer.data = (unsigned long)&g_data;
//6.向内核添加定时器对象,开始倒计时
//一旦时间到期,内核调用超时处理函数并且删除定时器
add_timer(&mytimer);
return 0;
}
static void mytimer_exit(void)
{
//1.释放GPIO资源
//2.删除定时器对象
del_timer(&mytimer);
}
module_init(mytimer_init);
module_exit(mytimer_exit);
MODULE_LICENSE("GPL");
/*************************************************************************
> File Name: btn_drv.c
> Author:
> Mail:
> Created Time: 2019年12月29日 星期日 09时08分30秒
************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
struct btn_resource{
int gpio; //按键对应的GPIO
char *name;//gpio Name
int code;//按键值
};
struct led_resource{
int gpio;
char *name;
};
//定义吃时候按键对应的硬件信息
static struct btn_resource btn_info[] = {
{
.gpio = PAD_GPIO_A + 28,
.name = "KEY_UP",
.code = KEY_UP
},
{
.gpio = PAD_GPIO_B + 9,
.name = "KEY_DOWN",
.code = KEY_DOWN
}
};
static struct led_resource led_info[] ={
{
.gpio = PAD_GPIO_C + 12,
.name = "LED1"
},
{
.gpio = PAD_GPIO_C + 7,
.name = "LED2"
},
{
.gpio = PAD_GPIO_C + 11,
.name = "LED3"
},
{
.gpio = PAD_GPIO_B + 26,
.name = "LED4"
}
};
//记录按键的硬件信息
static struct btn_resource *pdata;
//定义定时器对象
static struct timer_list mytimer;
//定义定时器时间
static int mytimer_value = 5;
static int led_cmd_flag = 0;
//tasklet process mytimer_function led opts
static void timer_tasklet_function(unsigned long data)
{
//open led or close led
int i = 0;
int *p_data = data;
printk("%s : current timer value = %d, state = %d\n", __func__, mytimer_value, *p_data);
if(*p_data)
{
*p_data = 0;
for(i = 0; i < ARRAY_SIZE(led_info); i++)
{
gpio_set_value(led_info[i].gpio, 0);
}
}
else
{
*p_data = 1;
for(i = 0; i < ARRAY_SIZE(led_info); i++)
{
gpio_set_value(led_info[i].gpio, 1);
}
}
printk("tasklet process : %s \n", __func__);
}
//定义初始化一个tasklet对象
static DECLARE_TASKLET(mytimer_tasklet, timer_tasklet_function, (unsigned long)&led_cmd_flag);
//定时器处理函数
//data = (unsigned long)&g_data
static void mytimer_function(unsigned long data)
{
//登记底半部tasklet处理函数
tasklet_schedule(&mytimer_tasklet);
//add_timer(&mytimer);
mod_timer(&mytimer, jiffies + mytimer_value * HZ);
printk("schedule timer top process : %s \n", __func__);
}
//工作队列方式处理定时器延长或缩短
//work = &btn_work
static void btn_work_function(struct work_struct *work)
{
switch(pdata->code)
{
case KEY_UP:
//add expir timeout
if(mytimer_value <= 10)
mytimer_value++;
break;
case KEY_DOWN:
//submit expir timeout
if(mytimer_value > 0)
mytimer_value--;
break;
default:
printk("dev->code is not impire.\n");
}
printk("btn work function : %s \n", __func__);
}
//定义工作队列
static struct work_struct btn_work;
//中断处理函数
//不同的按键触发irq不同,KEY_UP对应irq = gpio_to_irq(GPIOA28)
//响应的参数不同,dev = &btn_info[0]或者dev = &btn_info[1]
static irqreturn_t button_isr(int irq, void *dev)
{
//获取当前硬件信息
pdata = (struct btn_resource *)dev;
//登记工作队列
schedule_work(&btn_work);