嵌入式驱动开发详解4(内核定时器)

news2024/12/26 18:55:36

文章目录

  • 前言
  • 通用定时器
    • 系统节拍
    • 节拍数与时间转换
    • 基本框架
    • 定时器使用
    • 代码展示
    • 通用定时器特点
  • 高精度定时器

前言

LInux内核定时器是一种基于未来时间点的计时方式,以当前时刻来启动的时间点,以未来的某一时刻为终止点。比如,现在是10点5分,我要定时5分钟,那么定时就是10点5分+5分钟=10点10分。这个和手机闹钟很相似。比如你要定一个第二天早晨8点的闹钟,就是当前时间定时到第二天早晨8点。

通用定时器

系统节拍

在了解定时器之前,先来了解一下内核系统节拍的设置。硬件定时器提供时钟源,时钟源的频率可以设置, 设置好以后 就周期性的产生定时中断,系统使用定时中断来计时。中断周期性产生的频率就是系统频率, 也叫做节拍率(tick rate)(有的资料也叫系统频率),比如 1000Hz,100Hz 等等说的就是系统节拍 率。系统节拍率是可以设置的,单位是 Hz,我们在编译 Linux 内核的时候可以通过图形化界面 设置系统节拍率,按照如下路径打开配置界面:

-> Kernel Features -> Timer frequency ( [=y])

内核系统节拍设置
默认情况下选择 100Hz。设置好以后打开 Linux 内核源码根目录下的.config 文件中CONFIG_HZ会等于设置好的值,Linux 内核会使用 CONFIG_HZ 来设置自己的系统时 钟。打开文件 include/asm-generic/param.h,有如下内容:

# undef HZ 
# define HZ CONFIG_HZ 
# define USER_HZ 100 
# define CLOCKS_PER_SEC (USER_HZ)

这里需要注意的是,并不是节拍率越高越好,高节拍率会提高系统时间精度,但是高节拍率会导致中断的产生更加频繁,频繁的中断会加剧系统的负担。

Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动的时候会 将 jiffies 初始化为 0,jiffies 定义在文件 include/linux/jiffies.h 中,如下图所示,jiffies_64 和 jiffies 其实是同一个东西,jiffies_64 用于 64 位系统,而 jiffies 用于 32 位系统。
在这里插入图片描述

节拍数与时间转换

为了方便开发,Linux 内核提供了几个 jiffies 和 ms、us、ns 之间的转换函数,如表所示:
在这里插入图片描述
有时候我们需要在内核中实现短延时,尤其是在 Linux 驱动中。Linux 内核提供了毫秒、微 秒和纳秒延时函数:
在这里插入图片描述

基本框架

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

struct timer_list {
    /*
     * All fields that change during normal runtime grouped to the
     * same cacheline
     */
    struct hlist_node   entry;  
    unsigned long       expires;   /*定时器超时时间,单位节拍数*/
    void            (*function)(unsigned long);/*定时处理函数*/
    unsigned long       data;/*要传递function函数的参数*/
    u32         flags;#ifdef CONFIG_TIMER_STATS
    int         start_pid;
    void            *start_site;
    char            start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
    struct lockdep_map  lockdep_map;
#endif
};

在上面这个结构体中,有几个参数需要重点关注一下。一个是expires到期时间,单位是节拍数。等于定时的当前的始终节拍计数(存储在系统的全局变量和jiffies)+定时时长对应的时钟节拍数量。

那么如何把时间 转换成节拍数量呢?示例:假如从现在开始定时1秒,转换成节拍数量是多少呢? 内核中有一个宏HZ,表示一秒对应的时钟节拍数,那么我们就可以通过这个宏来把时间转换成节拍数。所以,定时1秒就是expires = jiffies + 1*HZ。前面已经说明了HZ是怎么设置的。

定时器使用

  • 当我们定义了一个 timer_list 变量以后一定 要先用 init_timer 初始化一下
void init_timer(struct timer_list *timer)
  • add_timer 函数用于向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后, 定时器就会开始运行
void add_timer(struct timer_list *timer)
  • del_timer 函数用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。
int del_timer(struct timer_list * timer)
  • del_timer_sync 函数是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除,
int del_timer_sync(struct timer_list *timer)
  • mod_timer 函数用于修改定时值,如果定时器还没有激活的话,mod_timer 函数会激活定时器!
int mod_timer(struct timer_list *timer, unsigned long expires)

其中timer:要修改超时时间(定时值)的定时器。 expires:修改后的超时时间。

代码展示

下面这个代码实现了内核定时器周期性的点亮和熄灭开发板上的 LED 灯,当CMD = 3 是LED 灯的闪烁周期可以由内核定时器来设置,这里用到了ioctrl的相关知识,linux内核给用户提供了两类系统调用函数:一类是数据操作函数,比如read、write…。 另外一类函数是非数据操作函数,比如ioctl…,用户程序可以用ioctl给底层设备发送指令。 如果不是很熟悉这一块内容的话可以参考我的下一篇帖子:嵌入式驱动开发详解5(ioctl的使用)
应用层代码如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include "string.h"
#include <stdlib.h>
#include <sys/ioctl.h>


#define CLOSE_CMD  			_IO(0XEF, 0X1)   /* 关闭定时器 */
#define OPEN_CMD   			_IO(0XEF, 0X2)   /* 打开定时器 */
#define SETPERIOD_CMD  		_IO(0XEF, 0X3)   /* 设置定时器周期命令 */

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("file %s open failed!\r\n",filename);
        return -1;
    }

    while(1){
        printf("Input CMD:");
        ret = scanf("%d",&cmd);
        if(ret != 1){  /* 参数输入错误 */
            fgets(str,100,stdin);   /* 防止卡死 */
        }
        if(cmd == 1)
            cmd = CLOSE_CMD;
        else if(cmd == 2)
            cmd = OPEN_CMD;
        else if(cmd == 3){
            cmd = SETPERIOD_CMD;
            printf("Input Timer Period:");
            ret = scanf("%d",&arg);
            if(ret != 1){
                fgets(str,100,stdin); 
            }
        }
        ioctl(fd,cmd,arg);
    }
    ret = close(fd);
    if(ret < 0){
        printf("file %s close failed! \r\n",filename);
        return -1;
    }
    return 0;
}

内核层代码如下:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>  //copy
#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>

#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)   /* 璁剧疆瀹氭椂鍣ㄥ懆鏈熷懡浠� */   //铏界劧姝ゅ娌℃湁鍐檃rg锛屼絾鏄渶鍚庡鏋滄湁arg鍙傛暟杩樻槸鍙互浼犺繘鏉�

#define LED_ON 1
#define LED_OFF 0

struct timer_dev{
	dev_t devid; 
	int major;
	int minor;
	struct cdev cdev;
	struct class *class;
	struct device *device;
	struct device_node *nd;
	int led_gpio;
	int timeperiod;  			
	struct timer_list timer;  
	spinlock_t lock;
};

struct timer_dev timerdev;

static int led_init(void)
{
	int ret;
	timerdev.nd = of_find_node_by_path("/gpioled");
	if(timerdev.nd == NULL){
		printk("timerdev node cant not find!!\r\n");
		ret = -1;
		goto fail_node;
	}else{
		printk("timerdev node found!!\r\n");
	}
	timerdev.led_gpio = of_get_named_gpio(timerdev.nd,"led-gpio",0);
	if(timerdev.led_gpio < 0){
		printk("cant not get led-gpio\r\n");
		ret = -1;
		goto fail_node;
	}
	printk("led-gpio-num=%d\r\n",timerdev.led_gpio);
	gpio_request(timerdev.led_gpio,"led");
	ret = gpio_direction_output(timerdev.led_gpio,1);
	if(ret < 0){
		printk("can`t set gpio!!!\r\n");
	}
	return 0;
fail_node:
	return ret;
	
}

static int timer_open(struct inode *inode, struct file *file)
{
	int ret = 0;
	file->private_data = &timerdev;
	timerdev.timeperiod = 1000;
	ret = led_init();
	if(ret < 0){
		return ret;
	}
	return 0;
}

static long timer_unlocked_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
	unsigned long flags;
	int timerperiod;
	struct timer_dev *dev = filep->private_data;
	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(dev->timeperiod));
		break;
	default:
		break;
	}
	return 0;
}

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;
	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(timerperiod));
}

static const struct file_operations timer_fops = {
	.owner	= THIS_MODULE,
	.open	= timer_open,
	.unlocked_ioctl	= timer_unlocked_ioctl,
};

static int __init timer_init(void)
{
	int ret;
	/* 鍒濆鍖栧師瀛愬彉閲� */
	spin_lock_init(&timerdev.lock);

	if(timerdev.major){
		timerdev.devid = MKDEV(timerdev.major,timerdev.minor);
		ret = register_chrdev_region(timerdev.devid, TIMER_CNT, TIMER_NAME);
	}else{
		ret = alloc_chrdev_region(&timerdev.devid,0,TIMER_CNT,TIMER_NAME);
		timerdev.major = MAJOR(timerdev.devid);
		timerdev.minor = MINOR(timerdev.devid);
		printk("alloc_chrdev_region major=%d minor=%d\r\n",timerdev.major, timerdev.minor);
	}
	if (ret < 0) {
		printk("Could not register\r\n");
		goto fail_devid;
	}
	timerdev.cdev.owner = THIS_MODULE;
	cdev_init(&timerdev.cdev, &timer_fops);
	ret = cdev_add(&timerdev.cdev,timerdev.devid,TIMER_CNT);
	if(ret < 0){
		printk("Could not cdev\r\n");
		goto fail_cdev;
	}
	timerdev.class = class_create(THIS_MODULE,TIMER_NAME);
	if(IS_ERR(timerdev.class)){
		ret = PTR_ERR(timerdev.class);
		goto fail_class;
	}
	timerdev.device = device_create(timerdev.class,NULL,timerdev.devid,NULL,TIMER_NAME);
	if(IS_ERR(timerdev.device)){
		ret = PTR_ERR(timerdev.device);
		goto fail_device;
	}
	
	//鍒濆鍖杢imer锛岃缃畾鏃跺櫒澶勭悊鍑芥暟锛岃繕鏈缃懆鏈燂紝鎵€浠ヤ笉浼氭縺娲诲畾鏃跺櫒
	init_timer(&timerdev.timer);
	timerdev.timer.function = timer_function;
	timerdev.timer.data = (unsigned long)&timerdev;  //璁剧疆瑕佷紶閫掔粰 timer_function 鍑芥暟鐨勫弬鏁颁负 timerdev 鐨勫湴鍧€
	return 0;
fail_device:
	class_destroy(timerdev.class);
fail_class:
	cdev_del(&timerdev.cdev);
fail_cdev:
	unregister_chrdev_region(timerdev.devid,TIMER_CNT);
fail_devid:
	return ret;
}

static void __exit timer_exit(void)
{
	gpio_set_value(timerdev.led_gpio,1);
	del_timer_sync(&timerdev.timer);
	printk("timer_exit\r\n");
	cdev_del(&timerdev.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("hbb");

通用定时器特点

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

高精度定时器

Linux内核提供了高精度定时器,能够实现纳秒(ns)级别的定时精度。这主要得益于内核中的hrtimer框架,它允许开发者创建和运行高精度的定时器。
关于详细的hrtimer结构体的实现可以参考下面这篇文章linux内核定时器

到这里对通用定时器大致说明就结束了,后面有新的相关的重要的内容会继续进行更新。
对代码有兴趣的同学可以查看链接https://github.com/NUAATRY/imx6ull_dev

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2254324.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣-图论-3【算法学习day.53】

前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向和记录学习过程&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非…

2024年认证杯SPSSPRO杯数学建模D题(第一阶段)AI绘画带来的挑战解题全过程文档及程序

2024年认证杯SPSSPRO杯数学建模 D题 AI绘画带来的挑战 原题再现&#xff1a; 2023 年开年&#xff0c;ChatGPT 作为一款聊天型AI工具&#xff0c;成为了超越疫情的热门词条&#xff1b;而在AI的另一个分支——绘图领域&#xff0c;一款名为Midjourney&#xff08;MJ&#xff…

各种常见生信格式文件的随机抽样

样本检验、随机生成数据、模拟用等&#xff0c;都需要从现有测序数据中随机抽样出一小部分数据来&#xff0c;按照自己需求。 0&#xff0c;最经典的方式&#xff1a; 使用awk等&#xff0c;只要了解各种数据格式具体的行列组成&#xff08;一般是headerrecord&#xff09;&a…

【技展云端,引擎蓝天】2025涡轮展之民用航空发动机技术分论坛及展览展示

2023年全球航空发动机市场规模约为1139.72亿美元&#xff0c;预计到2030年将达到1511.95亿美元&#xff0c;年均复合增长率为4.12%。这主要得益于全球航空运输需求的不断增长、新兴市场的快速扩张以及更高效、更环保的发动机技术创新。 航空发动机是一种高度复杂和精密的热力机…

【算法】——前缀和(矩阵区域和详解,文末附)

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 一&#xff1a;前缀和模版 二&#xff1a;前缀和模版2 三&#xff1a;寻找数组的中心下标 四&#x…

【kotlin 】内联类(value class / inline class)

官方文档&#xff1a;https://kotlinlang.org/docs/inline-classes.html 注&#xff1a;inline class 关键字已经被废弃&#xff0c;取而代之的是value class。现在使用内联类需要定义类为value class&#xff0c;并使用JvmInline注解进行标注。 一、使用场景 有时候&#xff…

【热门主题】000076 探索单片机的奥秘:原理、编程与应用全解析

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【热…

泷羽sec学习打卡-shell命令9

声明 学习视频来自B站UP主 泷羽sec,如涉及侵权马上删除文章 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都 与本人无关,切莫逾越法律红线,否则后果自负 关于shell的那些事儿-shell完结 方法一方法二重定向示例1示例2示例3 文件描述符例1例2 实践是检验真理的…

【Java】Scanner类的使用

Scanner类&#xff1a;从输入源&#xff08;键盘&#xff09;读取数据&#xff08;Java自己已经写好的一个类&#xff09; 使用&#xff1a; 1.导入Scanner类&#xff1a;import java.util.Scanner;&#xff08;为使用Scanner类做准备&#xff09; 2.创建Scanner类的对象&am…

摩尔线程 国产显卡 MUSA 并行编程 学习笔记-2024/12/04

Learning Roadmap&#xff1a; Section 1: Intro to Parallel Programming & MUSA Deep Learning Ecosystem&#xff08;摩尔线程 国产显卡 MUSA 并行编程 学习笔记-2024/11/30-CSDN博客&#xff09;UbuntuDriverToolkitcondapytorchtorch_musa环境安装(2024/11/24-Ubunt…

Mac安装MINIO服务器实现本地上传和下载服务

0.MINIO学习文档 Minio客户端mc使用 | Elibaron学习笔记 1.Mac安装MINIO 中文官方网址&#xff1a;MinIO下载和安装 | 用于创建高性能对象存储的代码和下载内容 (1) brew 安装 brew install minio/stable/minio &#xff08;2&#xff09;安装完成&#xff0c;执行brew i…

2024-12-03OpenCV图片处理基础

OpenCV图片处理基础 OpenCV的视频教学&#xff1a;https://www.bilibili.com/video/BV14P411D7MH 1-OpenCV摄像头读取 OpenCV使用摄像头读取图片帧&#xff0c;点击S保存当前帧到指定文件夹&#xff0c;点击Q关闭窗口&#xff0c;点击其他按钮打印按钮的值 要实现这个功能&…

nginx中tcp_nodelay、types_hash_max_size都是什么配置?

nginx中tcp_nodelay、types_hash_max_size都是什么配置&#xff1f; 在 Nginx 中&#xff0c;tcp_nodelay 和 types_hash_max_size 是两个不同的配置项&#xff0c;它们分别与网络性能优化和 MIME 类型的管理相关。 1. tcp_nodelay 功能&#xff1a; 控制是否启用 TCP_NODELAY…

openGauss开源数据库实战十九

文章目录 任务十九 openGauss DML 语句测试任务目标实施步骤一、准备工作二、INSERT语句三、DELETE语句四、UPDATE语句五、清理工作 任务十九 openGauss DML 语句测试 任务目标 掌握DML语句的用法,包括INSERT语句、DELETE语句和UPDATE语句。 实施步骤 一、准备工作 使用Li…

400G智算网络助力知名自动驾驶企业算力训练提效

根据Gartner的最新趋势预测&#xff0c;自动驾驶技术正迅速发展&#xff0c;预计在未来几年内将带来显著的商业效益&#xff0c;特别是在决策智能和边缘人工智能领域。目前&#xff0c;一家领军企业正积极拥抱基于大模型的数字化转型之路&#xff0c;作为自动驾驶领域的佼佼者&…

openEuler 知:安装 GNOME 桌面

openEuler 标准版 ISO 镜像默认不带桌面安装方式&#xff0c;可以先用最小化方式安装系统&#xff0c;然后单独安装 GNOME 组来实现桌面化 dnf group install GNOME -y安装完后&#xff0c;将 systemd 默认 target 设置为 graphical.target systemctl set-default graphical.…

《ODIN: A Single Model for 2D and 3D Segmentation》CVPR2024

斯坦福和微软&#xff1a; 代码链接&#xff1a;ODIN: A Single Model For 2D and 3D Perception 论文链接&#xff1a;2401.02416 摘要 这篇论文介绍了ODIN&#xff08;Omni-Dimensional INstance segmentation&#xff09;&#xff0c;一个能够同时处理2D RGB图像和3D点云…

多行为推荐-KBS 24|基于HyperGRU对比网络的短视频推荐多行为序列建模

论文&#xff1a;https://www.sciencedirect.com/science/article/abs/pii/S0950705124004751?via%3Dihub 关键词&#xff1a;短视频推荐&#xff0c;多行为推荐&#xff0c;对比学习&#xff0c;RNN 1 动机 这是我第一次看短视频推荐里涉及到多行为的论文&#xff0c;动机还…

企业网双核心交换机实现冗余和负载均衡(MSTP+VRRP)

MSTP&#xff08;多生成树协议&#xff09; 通过创建多个VLAN实例&#xff0c;将原有的STP、RSTP升级&#xff0c;避免单一VLAN阻塞后导致带宽的浪费&#xff0c;通过将VLAN数据与实例绑定&#xff0c;有效提升网络速率。 VRRP&#xff08;虚拟路由冗余协议&#xff09; 用…