前言
首先介绍一下定时器原理。
在linux系统中定时器有分为软定时和硬件定时器。
以海思某款芯片为例,定时器模块又称为Timer模块,主要实现定时、计数功能。
Timer 具有以下特点:
- 带可编程 8 位预分频器的 32bit/16bit 减法定时器/计数器。
- Timer 的计数时钟为 3MHz 时钟。
- 支持 3 种计数模式:自由运行模式、周期模式和单次计数模式。
- 有 2 种载入计数初值的方法,分别通过 TIMERx_LOAD 和 TIMERx_BGLOAD 寄存
器实现。 - 当前的计数值可随时读取。
- 当计数值减到 0 时会产生一个中断。
Timer 基于一个 32bit/16bit(可配置)减法计数器。计数器的值在每个计数时钟的上
升沿减 1。当计数值递减到零,Timer 将产生一个中断。
而基于硬件提供的信号源,系统就可以按照信号中断来计数,计数在固定频率下对应固定的时间,根据预设的时间参数即可产生定时中断信号,这就是软定时。
本文主要介绍软定时器的使用
一、内核定时器
1.介绍
内核定时器是内核用来控制在未来某个时间点(基于jiffies(节拍总数))调度执行某个函数的一种机制,相关函数位于 <linux/timer.h> 和 kernel/timer.c 文件中。
当内核定时器定时时间到达时,会进入用户指定的函数,相当于软中断。内核定时器注册开启后,运行一次就不会再运行(相当于自动注销),我们可以重新设置定时器的超时时间,让定时器重复运行。
每当时钟中断发生时,全局变量jiffies(一个32位的unsigned long 变量)就加1,因此jiffies记录了linux系统启动后时钟中断发生的次数,驱动程序常利用jiffies来计算不同事件间的时间间隔。内核每秒钟将jiffies变量增加HZ次。因此,对于HZ值为100的系统,jiffy+1等于隔了10ms,而对于HZ为1000的系统,jiffy+1仅为1ms。
内核定时器结构体:下面列出了需要关心的成员
struct timer_list {
unsigned long expires; //设置超时时间,用jiffies作为基准值
void (*function)(unsigned long); //类似中断服务函数,设置定时器到时后处理的函数
unsigned long data; //中断服务函数的参数
}
expires设置:以当前时间为基准加上延时时间,时间基准用jiffies变量表示
2、定时器使用
2.1.初始化
定义定时器结构体
struct timer_list my_timer;
static unsigned int out_time = 5; // 5秒
my_timer.expires = jiffies + out_time * HZ; // 设置超时时间,jiffies是基准值
定义定时器到时后的处理函数
static void TimeOutFunc(unsigned long data)
{
printk("TimeOut\n");
}
初始化定时器
timer_setup(&my_timer, TimeOutFunc, 0);
2.2.启动
add_timer(&my_timer);
初始化和启动一般可以写在内核模块驱动的module_init中
2.3.关闭
(void)del_timer(&my_timer);
关闭一般可以写在内核模块驱动的module_exit中
二、中断
1.介绍
不介绍了,反正就是硬件发出个中断信号,linux系统就响应然后执行一段程序(中断处理函数),执行完之后再回到断电处继续执行之前的工作。
介绍一下基本概念:
中断号(IRQ—interrupt request):中断号也叫中断线,每个中断都有一个中断号,通过中断号即可区分不同的中断。Linux 内核中使用一个 int 变量表示中断号。 Linux内核为每个中断线分配一个唯一的中断编号(IRQ)(可以使用gpio_to_irq函数,获取到对应的中断编号),也称为中断请求号。不同的计算机系统分配方式不同的。
2.使用
假设我们的中断由某个GPIO管脚上升沿触发
首先需要使用gpio_request函数向内核申请需要的GPIO引脚
gpio_request(GPIO_XX, NULL);
2.1 初始化
unsigned int irq_num;
unsigned int irqflags;
int iRet;
static unsigned int gpio_irq_type = RISING_EDGE;
module_param(gpio_irq_type, uint, S_IRUGO); // 只读
MODULE_PARM_DESC(gpio_irq_type, "RISING_EDGE"); //上升沿
/* 设置方向为输入 */
gpio_dev_input(gpio_num);
/* 上升沿 共享中断 */
irqflags = IRQF_TRIGGER_RISING;
irqflags |= IRQF_SHARED;
/* 根据GPIO编号映射中断号 */
irq_num = gpio_to_irq(gpio_num);
/* 注册中断 */
iRet = request_irq(irq_num, my_interrupt_func, irqflags, "MY_interrupt", &gpio_irq_type);
其中,中断处理函数文章来源:https://www.toymoban.com/news/detail-828421.html
static irqreturn_t wiper_interrupt(int irq, void *dev_id)
{
printk("interrupt func\n");
mod_timer(&my_timer, jiffies + out_time * HZ); // 刷新定时器
return IRQ_HANDLED;
}
2.2 注销
/* 释放注册的中断 */
free_irq(gpio_to_irq(gpio_num), &gpio_irq_type);
/* 释放注册的GPIO编号 */
gpio_free(GPIO_XX);
/* 清除定时器 */
(void)del_timer(&wiper_timer);
假设我们的外设在正常运行时总是能每隔一段时间以中断的形式反馈运行状态。
我们通过在GPIO触发的中断里对定时器进行刷新,从而可以实现超时处理操作,也就是说,如果外设没有正常运行,外设没有能够在一定时间内反馈中断从而刷新定时器,则会导致定时器倒计时结束,触发定时器到时后的处理函数。文章来源地址https://www.toymoban.com/news/detail-828421.html
到了这里,关于[驱动开发]Linux内核定时器与中断的简单应用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!