嵌入式linux驱动开发-内核定时器

Linux 内核提供的定时器 API 函数,通过这些定时器 API 函数我们可以完成很多要求定时的应用,也提供了短延时函数,比如微秒、纳秒、毫秒延时函数。

简介

内核时间管理简介

linux内核中也需要自己的时间管理,这就如同人的脉搏一样,让人各个功能有条不紊地运行着。

总体理解:

  • jiffies/HZ 就是系统运行时间,单位为秒。

具体代码:

timeout = jiffies + (n * HZ); /* 时间点 */

解释:正常HZ就是一秒多少次。
代码中HZ就是计数中断了多少次才完成了这一秒,n * HZ就表示多少秒。

其中:HZ

就和裸机开发中,晶振经过倍频、分频等操作得到时钟源地频率,设置好以后就周期性的产生定时中断,系统使用定时中断来计时,就是系统频率节拍率。常用地HZ有100Hz、 200Hz、 250Hz、 300Hz、 500Hz 和1000HZ。

HZ来源于根目录下include/asm-generic/param.h

# define HZ CONFIG_HZ

CONFIG_HZ又来源于根目录下的.config 文件,可使用图形界面配置,也可手动去目录config中更改。
在这里插入图片描述
高节拍率和低节拍率的优缺点:
高节拍率精度高,但中断服务函数占用处理器的时间增加。
低节拍率精度低,但中断服务函数占用处理器的时间较少。

其中:jiffies

很简单,就是用来计数的。

来源于文件 include/linux/jiffies.h

extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;

64位和32位两种数据类型。都有溢出风险,内核中又叫绕回,32 位的 jiffies 只需要 49.7 天就发生了绕回,对于 64 位的 jiffies 来说大概需要
5.8 亿年才能绕回。
在这里插入图片描述

API函数:
在这里插入图片描述
比如:unkown 超过 known ,time_after 函数返回真,否则返回假。其他函数自己根据意思理解。
在这里插入图片描述
应用:判断某段程序是否超时2s限制

1 unsigned long timeout;
2 timeout = jiffies + (2 * HZ); /* 超时的时间点 */
3 
4 /*************************************
5 具体的代码
6 ************************************/
7 
8 /* 判断有没有超时 */
9 if(time_before(jiffies, timeout)) {
10 		/* 超时未发生 */
11 } else {
12 		/* 超时发生 */
13 }

内核定时器简介

Linux 内核定时器采用系统时钟来实现,不同于PIT 等硬件定时器需要配置很多寄存器,官方linux内核已经配置完成了,我们只需要调用响应的API函数即可。

内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。

如下,Linux 内核使用timer_list 结构体表示内核定时器, timer_list 定义在文件include/linux/timer.h 中

struct timer_list {
	struct list_head entry;
	unsigned long expires; /* 定时器超时时间,单位是节拍数 */
	struct tvec_base *base;
	void (*function)(unsigned long); /* 定时处理函数 即中断服务函数*/
	unsigned long data; /* 要传递给 function 函数的参数 */
	int slack;
};

常用 API 函数

1、 init_timer 函数

初始化 timer_list 类型变量

void init_timer(struct timer_list *timer)

timer:要初始化定时器。
返回值: 没有返回值。

2、 add_timer 函数

向 Linux 内核注册定时器,即启动定时器

void add_timer(struct timer_list *timer)

timer:要注册的定时器。
返回值: 没有返回值。

3、 del_timer 函数

用于删除一个定时器,不管定时器有没有被激活都强制删除

int del_timer(struct timer_list * timer)

timer:要删除的定时器。
返回值: 0,定时器还没被激活; 1,定时器已经激活。

4、 del_timer_sync 函数

是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除,不能使用在中断上下文中。

int del_timer_sync(struct timer_list *timer)

timer:要删除的定时器。
返回值: 0,定时器还没被激活; 1,定时器已经激活。

5、 mod_timer 函数

用于修改定时值,如果定时器还没有激活的话, 会顺便激活定时器!

int mod_timer(struct timer_list *timer, unsigned long expires)

timer:要修改超时时间(定时值)的定时器。
expires:修改后的超时时间。
返回值: 0,调用 mod_timer 函数前定时器未被激活; 1,调用 mod_timer 函数前定时器已被激活。

定时器应用举例

1 struct timer_list timer; /* 定义定时器 */
2 
3 /* 定时器回调函数 */
4 void function(unsigned long arg)
5 {
6 		/*
7 		* 定时器处理代码
8 		*/
9
10 		/* 如果需要定时器周期性运行的话就使用 mod_timer
11 		* 函数重新设置超时值并且启动定时器。
12 		*/
13 		mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000));
14 }
15
16 /* 初始化函数 */
17 void init(void)
18 {
19 		init_timer(&timer); /* 初始化定时器 */
20
21 		timer.function = function; /* 设置定时处理函数 */
22 		timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间 2 秒 */
23 		timer.data = (unsigned long)&dev; /* 将设备结构体作为参数 */
24
25 		add_timer(&timer); /* 启动定时器 */
26 }
27
28 /* 退出函数 */
29 void exit(void)
30 {
31 		del_timer(&timer); /* 删除定时器 */
32 		/* 或者使用 */
33 		del_timer_sync(&timer);
34 }
  • 自己理解:以上的定时器就不是底层的配置寄存器了,而是抽象的利用结构体定义自己的定时器,定时器里有中断函数、计数初值等变量,function函数中的mod_timer函数相当于计数溢出重新装入初值。
  • 这一套示例可以整合到设备驱动文件中去,如在其中的设备结构体里可以定义struct timer_list类型的定时器结构体变量

Linux 内核短延时函数

Linux 内核提供了毫秒、微秒和纳秒延时函数:
在这里插入图片描述

实验程序编写

还是3大部分:设备树.dts、驱动文件.c、App文件.c

修改设备树文件

永远记住,设备树只是描述设备的板级信息,是为上层的llinux内核服务的。

1 pinctrl_led: ledgrp {
2 		fsl,pins = <
3 			MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
4 		>;
5 };
......
1 gpioled {
2 		#address-cells = <1>;
3 		#size-cells = <1>;
4 		compatible = "atkalpha-gpioled";
5 		pinctrl-names = "default";
6 		pinctrl-0 = <&pinctrl_led>;		//使用上边儿pinctrl_描述的复用和电气属性 pinctrl-0 属性设置 LED 灯所使用的 PIN 对应的 pinctrl 节点
7 		led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
8 		status = "okay";
9 }

最重要的是检查 PIN 是否被其他外设使用,否则就发生冲突了!!!

驱动程序编写

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: timer.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: Linux内核定时器实验
其他	   	: 无
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/7/24 左忠凯创建
***************************************************************/
#define TIMER_CNT		1		/* 设备号个数 	*/
#define TIMER_NAME		"timer"	/* 名字 		*/
#define CLOSE_CMD 		(_IO(0XEF, 0x1))	/* 关闭定时器 */
#define OPEN_CMD		(_IO(0XEF, 0x2))	/* 打开定时器 */
#define SETPERIOD_CMD	(_IO(0XEF, 0x3))	/* 设置定时器周期命令 */
#define LEDON 			1		/* 开灯 */
#define LEDOFF 			0		/* 关灯 */

/* timer设备结构体 */
struct timer_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	struct device_node	*nd; /* 设备节点 */
	int led_gpio;			/* key所使用的GPIO编号		*/
	int timeperiod; 		/* 定时周期,单位为ms */
	struct timer_list timer;/* 定义一个定时器*/
	spinlock_t lock;		/* 定义自旋锁 */
};

struct timer_dev timerdev;	/* timer设备 */

/*
 * @description	: 初始化LED灯IO,open函数打开驱动的时候
 * 				  初始化LED灯所使用的GPIO引脚。
 * @param 		: 无
 * @return 		: 无
 */
static int led_init(void)
{
	int ret = 0;

	timerdev.nd = of_find_node_by_path("/gpioled");
	if (timerdev.nd== NULL) {
		return -EINVAL;
	}

	timerdev.led_gpio = of_get_named_gpio(timerdev.nd ,"led-gpio", 0);
	if (timerdev.led_gpio < 0) {
		printk("can't get led\r\n");
		return -EINVAL;
	}
	
	/* 初始化led所使用的IO */
	gpio_request(timerdev.led_gpio, "led");		/* 请求IO 	*/
	ret = gpio_direction_output(timerdev.led_gpio, 1);
	if(ret < 0) {
		printk("can't set gpio!\r\n");
	}
	return 0;
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int timer_open(struct inode *inode, struct file *filp)
{
	int ret = 0;
	filp->private_data = &timerdev;	/* 设置私有数据 */

	timerdev.timeperiod = 1000;		/* 默认周期为1s */
	ret = led_init();				/* 初始化LED IO */
	if (ret < 0) {
		return ret;
	}

	return 0;
}

/*
 * @description		: ioctl函数,
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - cmd 	: 应用程序发送过来的命令
 * @param - arg 	: 参数
 * @return 			: 0 成功;其他 失败
 */
static long timer_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct timer_dev *dev =  (struct timer_dev *)filp->private_data;
	int timerperiod;
	unsigned long flags;
	
	switch (cmd) {
		case CLOSE_CMD:		/* 关闭定时器 */
			del_timer_sync(&dev->timer);
			break;
		case OPEN_CMD:		/* 打开定时器 */
			spin_lock_irqsave(&dev->lock, flags);
			timerperiod = dev->timeperiod;
			spin_unlock_irqrestore(&dev->lock, flags);
			mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerperiod));
			break;
		case SETPERIOD_CMD: /* 设置定时器周期 */
			spin_lock_irqsave(&dev->lock, flags);
			dev->timeperiod = arg;
			spin_unlock_irqrestore(&dev->lock, flags);
			mod_timer(&dev->timer, jiffies + msecs_to_jiffies(arg));
			break;
		default:
			break;
	}
	return 0;
}

/* 设备操作函数 */
static struct file_operations timer_fops = {
	.owner = THIS_MODULE,
	.open = timer_open,
	.unlocked_ioctl = timer_unlocked_ioctl,
};

/* 定时器回调函数 */
void timer_function(unsigned long arg)
{
	struct timer_dev *dev = (struct timer_dev *)arg;
	static int sta = 1;
	int timerperiod;
	unsigned long flags;

	sta = !sta;		/* 每次都取反,实现LED灯反转 */
	gpio_set_value(dev->led_gpio, sta);
	
	/* 重启定时器 */
	spin_lock_irqsave(&dev->lock, flags);
	timerperiod = dev->timeperiod;
	spin_unlock_irqrestore(&dev->lock, flags);
	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod)); 
 }

/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init timer_init(void)
{
	/* 初始化自旋锁 */
	spin_lock_init(&timerdev.lock);

	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	if (timerdev.major) {		/*  定义了设备号 */
		timerdev.devid = MKDEV(timerdev.major, 0);
		register_chrdev_region(timerdev.devid, TIMER_CNT, TIMER_NAME);
	} else {						/* 没有定义设备号 */
		alloc_chrdev_region(&timerdev.devid, 0, TIMER_CNT, TIMER_NAME);	/* 申请设备号 */
		timerdev.major = MAJOR(timerdev.devid);	/* 获取分配号的主设备号 */
		timerdev.minor = MINOR(timerdev.devid);	/* 获取分配号的次设备号 */
	}
	
	/* 2、初始化cdev */
	timerdev.cdev.owner = THIS_MODULE;
	cdev_init(&timerdev.cdev, &timer_fops);
	
	/* 3、添加一个cdev */
	cdev_add(&timerdev.cdev, timerdev.devid, TIMER_CNT);

	/* 4、创建类 */
	timerdev.class = class_create(THIS_MODULE, TIMER_NAME);
	if (IS_ERR(timerdev.class)) {
		return PTR_ERR(timerdev.class);
	}

	/* 5、创建设备 */
	timerdev.device = device_create(timerdev.class, NULL, timerdev.devid, NULL, TIMER_NAME);
	if (IS_ERR(timerdev.device)) {
		return PTR_ERR(timerdev.device);
	}
	
	/* 6、初始化timer,设置定时器处理函数,还未设置周期,所有不会激活定时器 */
	init_timer(&timerdev.timer);
	timerdev.timer.function = timer_function;
	timerdev.timer.data = (unsigned long)&timerdev;
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit timer_exit(void)
{
	
	gpio_set_value(timerdev.led_gpio, 1);	/* 卸载驱动的时候关闭LED */
	del_timer_sync(&timerdev.timer);		/* 删除timer */
#if 0
	del_timer(&timerdev.tiemr);
#endif

	/* 注销字符设备驱动 */
	cdev_del(&timerdev.cdev);/*  删除cdev */
	unregister_chrdev_region(timerdev.devid, TIMER_CNT); /* 注销设备号 */

	device_destroy(timerdev.class, timerdev.devid);
	class_destroy(timerdev.class);
}

module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

编写测试 APP

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: timerApp.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: 定时器测试应用程序
其他	   	: 无
使用方法	:./timertest /dev/timer 打开测试App
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/7/24 左忠凯创建
***************************************************************/

/* 命令值 */
#define CLOSE_CMD 		(_IO(0XEF, 0x1))	/* 关闭定时器 */
#define OPEN_CMD		(_IO(0XEF, 0x2))	/* 打开定时器 */
#define SETPERIOD_CMD	(_IO(0XEF, 0x3))	/* 设置定时器周期命令 */

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, ret;
	char *filename;
	unsigned int cmd;
	unsigned int arg;
	unsigned char str[100];

	if (argc != 2) {
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];

	fd = open(filename, O_RDWR);
	if (fd < 0) {
		printf("Can't open file %s\r\n", filename);
		return -1;
	}

	while (1) {
		printf("Input CMD:");
		ret = scanf("%d", &cmd);
		if (ret != 1) {				/* 参数输入错误 */
			gets(str);				/* 防止卡死 */
		}

		if(cmd == 1)				/* 关闭LED灯 */
			cmd = CLOSE_CMD;
		else if(cmd == 2)			/* 打开LED灯 */
			cmd = OPEN_CMD;
		else if(cmd == 3) {
			cmd = SETPERIOD_CMD;	/* 设置周期值 */
			printf("Input Timer Period:");
			ret = scanf("%d", &arg);
			if (ret != 1) {			/* 参数输入错误 */
				gets(str);			/* 防止卡死 */
			}
		}
		ioctl(fd, cmd, arg);		/* 控制定时器的打开和关闭 */	
	}

	close(fd);
}

调试命令:

./timerApp /dev/timer
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值