RT-Thread快速入门-定时器管理

 

1时钟节拍

任何操作系统都需要提供一个时钟节拍,以供系统处理所有和时间有关的事件,如延时、线程的时间片轮转调度以及定时器超时等。时钟节拍(OS Tick)是操作系统中最小的时间单位。

时钟节拍是特定的周期性中断,这个中断之间的时间间隔取决于具体的应用,一般是 1-100ms。时钟节拍率越快,系统的额外开销就越大。

RT-Thread 中,一个时钟节拍的时长根据 rtconfig.h 配置文件中, RT_TICK_PER_SECOND 的 定 义 来 调 整, 等 于 1/RT_TICK_PER_SECOND 秒 。

时钟节拍的实现

时钟节拍由配置为中断触发模式的硬件定时器产 生,在中断服务程序中调用如下函数,通知操作系统已经过去一个系统时钟:

void rt_tick_increase(void)
{
  struct rt_thread *thread;

  /* 全局 rt_tick 递增 */
#ifdef RT_USING_SMP
  rt_cpu_self()->tick ++;
#else
  ++ rt_tick;
#endif

  /* 检查时间片 */
  thread = rt_thread_self();

  -- thread->remaining_tick;
  if (thread->remaining_tick == 0)
  {
    /* 重新赋初值 */
    thread->remaining_tick = thread->init_tick;
    /* 线程挂起 */
    thread->stat |= RT_THREAD_STAT_YIELD;

    /* yield */
    rt_thread_yield();
  }

  /* 检查定时器 */
  rt_timer_check();
}

从源代码中可以看出,每经过一个时钟节拍,全局变量 rt_tick 的值就会加 1。然后检查当前线程的时间片是否用完,以及是否有定时器超时。如果当前线程的时间片用完,则进行同优先级线程之间的切换。

不同的硬件定时器中断实现都不同,以 STM32 定时器中断为例:

void SysTick_Handler(void)
{
  /* 进 入 中 断 */
  rt_interrupt_enter();
  ……
  rt_tick_increase();
  /* 退 出 中 断 */
  rt_interrupt_leave();
}

在中断函数中,调用 rt_tick_increase() 对全局变量 rt_tcik 加 1。

rt_tick 的值表示了系统从启动到现在共经过的时钟节拍个数。

2定时器工作机制

RT-Thread 提供的定时器基于系统的节拍,提供了基于节拍整数倍的定时能力,即定时器定时以时钟节拍为单位。如此,定时器定时长短是 OS Tick 时长的整数倍。

如果一个时钟节拍是 10ms,那么系统软件定时器时长只能是 10ms、20ms、100等,而不能是 15ms。

定时器介绍

RT-Thread 提供了两种类型的定时器:

  • 单次触发定时器。这类定时器触发一次定时器事件后,会自动停止。

  • 周期触发定时器。这类定时器会周期性地触发定时器事件,直到用户手动停止。

另外,根据超时函数执行时所处的上下文环境,RT-Thread 的定时器有两种工作模式:

  • HARD_TIMER 模式,超时函数在中断上下文环境中执行。

  • SOFT_TIMER  模式,在系统创建的定时器线程上下文环境中执行。

HARD_TIMER 模式的定时器

这种模式是 RT-Thread 定时器默认的工作方式,定时器超时后,超时函数在系统时钟中断的上下文环境中执行。

这种情况下,对于超时函数的要求与中断服务例程的要求相同:执行时间应该尽量短、执行时不应该导致当前线程挂起等。否则会导致其他中断的响应时间加长,或抢占了其他线程执行的时间。

SOFT_TIMER 模式的定时器

这种工作模式,需要通过宏定义 RT_USING_TIMER_SOFT 来决定是否启用。启用这个模式后,RT-Thread 会在初始化时创建一个 timer 线程,SOFT_TIMER 模式的定时器超时函数都会在 timer 线中执行。

定时器如何工作

RT-Thread 维护着两个重要的全局变量:

  • rt_tick , 当前系统经过的时钟节拍个数。

  • rt_timer_list , 定时器链表。创建并激活的定时器都会按照超时时间从小到大进行排序,插入到这个链表中。

如下图所示,系统当前的 rt_tick 值为 20,且已经创建并启动了三个定时器:(1)定时为 50 个节拍的 Timer1(2)定时为 100 个节拍的 timer2(3)定时为 500 个节拍的 timer3。

这三个定时器分别加上系统当前时间 rt_tick, 从小到大排序链接在 rt_timer_list 中:

图片

 

rt_tick 随着硬件定时器的触发一直在增长,50 个节拍后,rt_tick 从 20 增长到 70,与 Timer1 的 timerout 值相同,这时会触发 Timer1 定时器关联的超时函数,同时将其从 rt_timer_list 链表上删除。

同理,100 个节拍和 500 个节拍过去后,Timer2 和 Timer3 定时器的超时函数会被触发执行,将定时器 Timer2 和 Timer3 从 rt_timer_list 中删除。

定时器控制块

定时器控制块是 RT-Thread 用于管理定时器的一个数据结构,由结构体 struct rt_timer 定义形成定时器内核对象,再链接到内核容器中进行管理。

定时器控制块会存储定时器的一些信息,例如初始时钟节拍数、超时到达的节拍数、定时器之间连接用的链表结构、超时回调函数等。具体定义如下:

struct rt_timer
{
  struct rt_object parent;
  rt_list_t row[RT_TIMER_SKIP_LIST_LEVEL]; /* 定时器链表节点 */
  
  void (*timeout_func)(void *parameter);  /* 定时器超时函数 */
  void *parameter;                        /* 超时函数的参数 */
  
  rt_tick_t init_tick;     /* 定时器设定的超时节拍数 */
  rt_tick_t timeout_tick;  /* 定时器实际超时时的节拍数 */
};
typedef struct rt_timer *rt_timer_t;

3定时器管理

前面介绍了定时器相关的理论知识,那么 RT-Thread 提供了怎样的定时器操作函数,以及如何使用它们呢?

RT-Thread 提供的定时器相关的操作包括:

  • 创建/初始化定时器

  • 启动定时器

  • 控制定时器

  • 删除/脱离定时器

所有定时器会在定时超时后从定时器链表中被删除,而周期性定时器会在它再次启动时被加入定时器链表中。

图片

 

1. 创建定时器

创建一个定时器有两种方式:动态创建和静态初始化。

动态创建一个定时器,使用如下函数接口:

rt_timer_t rt_timer_create(const char *name,
                           void (*timeout)(void *parameter),
                           void       *parameter,
                           rt_tick_t   time,
                           rt_uint8_t  flag)

调用此函数后,内核自动从内存堆中分配一个定时器控制块,然后初始化该定时器控制块。各个参数说明如下:

参数描述
name定时器名称
timeout定时器超时函数指针
parameter定时器超时函数的入口参数
time定时器超时时间,单位是时钟节拍
flag创建定时器的参数,其值包括单次定时、周期定时、硬件定时器、软件定时器等

创建失败,返回 RT_NULL。创建成功,则返回定时器控制块指针。

定时器标志用到的宏定义:

#define RT_TIMER_FLAG_ONE_SHOT 0x0    /* 单 次 定 时 */
#define RT_TIMER_FLAG_PERIODIC 0x2    /* 周 期 定 时 */

#define RT_TIMER_FLAG_HARD_TIMER 0x0  /* 硬 件 定 时 器 */
#define RT_TIMER_FLAG_SOFT_TIMER 0x4  /* 软 件 定 时 器 */

上面两组可以以 "或"逻辑方式赋值给 flag

静态创建一个定时器,需要用户定义一个定时器控制块结构体 struct rt_timer 变量,然后 rt_timer_init() 函数对其初始化。该函数原型如下:

void rt_timer_init(rt_timer_t timer,
                  const char *name,
                  void   (*timeout)(void* parameter),
                  void   *parameter,
                  rt_tick_t time, rt_uint8_t flag);

该函数比 rt_timer_create() 多了一个参数 timer,其他参数都相同,不再赘述。参数 timer 实际上是定时器控制块指针。

2. 启动定时器

定时器创建之后,不会被立即启动,需要在调用启动定时器函数接口后,才开始工作。

RT-Thread 提供的启动定时器函数如下:

rt_err_t rt_timer_start(rt_timer_t timer);

函数的参数 timer 为定时器控制块指针(定时器句柄),指向要启动的定时器控制块。

调用启动函数后,定时器的状态更改为激活状态,并按照超时时间顺序插入到 rt_timer_list 队列链表中。

启动定时器后,如果想停止它,可以用下面的函数:

rt_err_t rt_timer_stop(rt_timer_t timer);

调用该函数后,定时器状态更改为停止,并从 rt_timer_list 链表中脱离出来,不参与定时器超时检查。

函数返回 RT_EOK,表示成功停止定时器。返回 -RT_ERROR,说明定时器已经处于停止状态了。

4定时器应用演示

理论+实践是学习新知识最有效的方法。

举例来演示如何创建定时器。这个例程动态创建两个定时器,一个单次定时器,一个周期定时器,并让定时器运行一段时间后停止。代码如下:

#include <rtthread.h>

/* 定时器的控制块 */
static rt_timer_t timer1;
static rt_timer_t timer2;
static int cnt = 0;

/* 定时器1超时函数 */
static void timeout1(void *parameter)
{
 rt_kprintf("periodic timer is timeout %d\n", cnt);
 /* 运行第 10 次,停止周期定时器 */
 if (cnt++>= 9)
 {
  rt_timer_stop(timer1);
  rt_kprintf("periodic timer was stopped! \n");
 }
}
/* 定时器 2 超时函数 */
static void timeout2(void *parameter)
{
 rt_kprintf("one shot timer is timeout\n");
}

int main()
{
 /* 创建定时器1周期定时器 */
 timer1 = rt_timer_create("timer1", timeout1,
                RT_NULL, 10,
                RT_TIMER_FLAG_PERIODIC);
 /* 启动定时器1 */
 if (timer1 != RT_NULL) 
 {
  rt_timer_start(timer1);
 }
 
 /* 创建定时器2单次定时器 */
 timer2 = rt_timer_create("timer2", timeout2,
                RT_NULL, 30,
                RT_TIMER_FLAG_ONE_SHOT);
 /* 启动定时器2 */
 if (timer2 != RT_NULL) 
 {
  rt_timer_start(timer2);
 }
 
 return 0;
}

编译运行结果如下:

图片

 

周期性定时器 1 的超时函数,每 10 节拍运行 1 次,共运行 10 次,之后停止(调用 rt_timer_stop())。

单次定时器 2 的超时函数在 30 个时钟节拍后运行一次。

下面举例说明静态创建定时器,需要定义定时器控制块结构变量,然后调用初始化函数对其初始化:

#include <rtthread.h>

/* 定时器的控制块 */
static struct rt_timer timer1;
static struct rt_timer timer2;
static int cnt = 0;

/* 定时器1超时函数 */
static void timeout1(void* parameter)
{
  rt_kprintf("periodic timer is timeout\n");
  /* 运行10次 */
  if (cnt++>= 9)
  {
   rt_timer_stop(&timer1);
  }
}
/* 定 时 器 2 超 时 函 数 */
static void timeout2(void* parameter)
{
 rt_kprintf("one shot timer is timeout\n");
}

int main(void)
{
  /* 初始化定时器1 */
  rt_timer_init(&timer1, "timer1", /* 定 时 器 名 字 是 timer1 */
              timeout1, RT_NULL, 10, 
              RT_TIMER_FLAG_PERIODIC); /* 周期定时器 */
 /* 初始化定时器2 */
  rt_timer_init(&timer2, "timer2", /* 定 时 器 名 字 是 timer2 */
              timeout2, RT_NULL, 30,
              RT_TIMER_FLAG_ONE_SHOT); /* 单次定时器 */

/* 启动定时器 */
  rt_timer_start(&timer1);
  rt_timer_start(&timer2);

 return 0;
}

其执行结果与动态创建示例相同。

5其他定时器管理函数

初学者掌握定时器创建使用即可,RT-Thread 还提供了其他的定时器管理函数,可以了解学习。

1. 删除定时器

动态创建的定时器,可以用下面的函数删除:

rt_err_t rt_timer_delete(rt_timer_t timer);

调用这个函数接口后,系统会把这个定时器从 rt_timer_list 链表中删除,然后释放相应的定时器控制块占有的内存。

静态创建的定时器,可以用下边的函数脱离定时器:

rt_err_t rt_timer_detach(rt_timer_t timer);  

脱离定时器时,系统会把定时器对象从内核对象容器中脱离,但是定时器对象所占有的内存不会被释放。

2. 控制定时器

RT-Thread 也额外提供了定时器控制函数接口,以获取或设置更多定时器的信息。控制定时器函数接口如下:

rt_err_t rt_timer_control(rt_timer_t timer, rt_uint8_t cmd, void* arg);

控制定时器函数接口可根据命令类型参数,来查看或改变定时器的设置。

参数 cmd 为用于控制定时器的命令,当前支持四个命令:设置定时时间、查看定时时间、设置单次触发、设置周期触发。

#define RT_TIMER_CTRL_SET_TIME      0x0  /* 设置定时器超时时间 */
#define RT_TIMER_CTRL_GET_TIME      0x1  /* 获得定时器超时时间 */
#define RT_TIMER_CTRL_SET_ONESHOT   0x2  /* 设置定时器为单次定时器 */
#define RT_TIMER_CTRL_SET_PERIODIC  0x3  /* 设置定时器为周期型定时器 */

arg 为控制命令的参数。

OK,今天先到这,加油~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot 整合 分布式搜索引擎 Elastic Search 实现 搜索、分页与结果过滤

文章目录 ⛄引言一、酒店搜索和分页⛅需求分析⚡源码编写 二、酒店结果过滤⌚需求分析⏰修改搜索业务 ✅效果图⛵小结 ⛄引言 本文参考黑马 分布式Elastic search Elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据…

【java实习评审】对热门小说更新时的聚集访问流量进行性能优化优化,有较好的设计

大家好&#xff0c;本篇文章分享一下【校招VIP】免费商业项目“推推”第一期书籍详情模块java同学的文档周最佳作品。该同学来自西安建筑科技大学软件工程专业。 本项目亮点难点&#xff1a;1 热门书籍在更新点的访问压力&#xff0c;2 书籍更新通知的及时性和有效性&#xff…

浅谈能源管理系统在水泥行业中设计分析

安科瑞 华楠 摘要&#xff1a;水泥企业作为我国产业结构中重要的耗能产业&#xff0c;同时对环境的污染也比较大&#xff0c;因此在水泥企业中建立能源管理系统&#xff0c;对水泥企业的生产过程过程进行全过程的监控和管理&#xff0c;对于降低企业的能源消耗和提高企业的经济…

24 鼠标常用事件

鼠标进入&#xff1a;enterEvent鼠标离开&#xff1a;leaveEvent鼠标按下&#xff1a;mousePressEvent鼠标释放&#xff1a;mouseRelaseEvent鼠标移动&#xff1a;mouseMoveEvent 提升为自定义控件MyLabel 代码&#xff1a; //mylabel.h #ifndef MYLABEL_H #define MYLABEL_H#…

ESP32(MicroPython) 两轮差速五自由度机械臂小车

这次的项目在软件上没多少调整&#xff0c;但本人希望分享一下硬件上的经验。 小车使用两轮差速底盘&#xff0c;驱动轮在小车中间&#xff0c;前后都要万向轮。这种形式可以实现0转弯半径&#xff0c;但受万向轮及用于加高的铜柱的规格限制&#xff0c;两个万向轮难以调到相同…

基于netlify生成custom SSL certificate

&#xff08;1&#xff09;腾讯云申请 &#xff08;2&#xff09;域名控制台解析 &#xff08;3&#xff09;Nginx下载&#xff08;crt: CA certificate Chain)

C++教程 从0开始

0基础C教程 从0开始 课堂现在开始 如需学习 请订阅该标签 什么是C&#xff1f; 这个不是太重要 自行查看该链接即可 C_百度百科C&#xff08;c plus plus&#xff09;是一种计算机高级程序设计语言&#xff0c;由C语言扩展升级而产生&#xff0c;最早于1979年由本贾尼斯特劳…

轻量级Firefox Send替代方案Gokapi

想不到一个域名的变动会影响这么大&#xff0c;访问量出现断崖式下跌。由此可见&#xff0c;平时的访问应该只是一些 RSS 的访问而已。 上面是 Pageviews&#xff0c;下面是 Uniques 今天略有回升 难怪那些大公司要花钱买域名了&#xff0c;不过老苏是个佛系的人&#xff0c;一…

使用MQ发送对象错误

说明&#xff1a;使用RabbitMQ发送消息&#xff0c;消息是对象&#xff0c;出现下面这样的错误&#xff1b; 错误信息&#xff1a;Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.hmall.item.pojo.Item (no Cr…

通过问题解决者手册推动你的结果 - 提高思维能力的 17 种方法

大部分人对产品管理的理解都是解决问题&#xff0c;这是他们的主要工作——找出客户的问题是什么并解决它们。但现在&#xff0c;热衷于解决问题的问题是&#xff0c;当我们看到问题时&#xff0c;本能反应是“我该如何解决它&#xff1f;” 这意味着&#xff1a;当我试图自己解…

SPEC CPU 2006 docker gcc:4 静态编译版本 Ubuntu 22.04 LTS 测试报错Invalid Run

runspec.sh #!/bin/bash source shrc ulimit -s unlimited runspec -c gcc41.cfg -T all -n 1 int fp > runspec.log 2>&1 & tail -f runspec.log runspec.log 由于指定了-T all&#xff0c;导致-n 1 失效&#xff0c;用例运行了三次&#xff08;后续验证&…

iptables的备份和还原

iptables的备份和还原 1、写在命令行当中的都是临时设置 2、把规则配置写在服务的文件当中&#xff0c;形成永久有效 备份&#xff1a;把iptables里面所有的配置都保存在/opt/ky30.bak中 iptables-save > /opt/ky30.bak 例&#xff1a; 默认配置文件在/etc/sysconfig/ip…

自然语言处理NLP介绍——NLP简介

目录 内容先进性说明内容大纲概要云服务器的使用 内容先进性说明 内容大纲概要 云服务器的使用

STM32MP157驱动开发——GPIO 和 和 Pinctrl 子系统的概念

文章目录 Pinctrl 子系统重要概念概述重要概念pin controller&#xff1a;client device&#xff1a; 代码中怎么引用 pinctrl GPIO 子系统重要概念概述在设备树中指定引脚在驱动代码中调用 GPIO 子系统头文件常用函数实例&#xff1a; BSP工程师针对芯片的寄存器写Pinctrl子系…

HTTPS安全套接字层超文本传输协议

HTTPS安全套接字层超文本传输协议 HTTPS简介HTTPS和HTTP的主要区别客户端在使用HTTPS方式与Web服务器通信时的步骤SSL/TLS协议的加密&#xff08;握手&#xff09;过程为什么数据传输阶段使用对称加密HTTPS 的优点HTTPS 的缺点HTTPS 的优化证书优化会话复用 HTTPS简介 HTTP协议…

PPT逻辑设计与完美呈现

PPT逻辑设计与完美呈现 https://haoxinyunxueyuan.zhixueyun.com/#/study/course/detail/detailInfoCD00——朱宁川 logo设计神器: https://www.zitijia.com/logodiy/index 一 PPT设计 第一章、PPT的灵魂设计-5W PPT灵魂设计(5W) 以终为始&#xff0c;从目标出发 why 目…

防抖和节流

1. 防抖 1.1 定义 针对高频触发事件&#xff0c;让事件处理函数的逻辑代码延迟一段时间再执行&#xff0c;如果在延迟的这段时间再次触发&#xff0c;则重新开始计时&#xff0c;直到在计时的这段时间内没有再次触发&#xff0c;则执行事件处理函数的逻辑代码 原因&#xff…

(五)RabbitMQ-进阶 死信队列、延迟队列、防丢失机制

Lison <dreamlison163.com>, v1.0.0, 2023.06.23 RabbitMQ-进阶 死信队列、延迟队列、防丢失机制 文章目录 RabbitMQ-进阶 死信队列、延迟队列、防丢失机制死信队列延迟队列延迟队列介绍**延迟队列_死信队列_的实现**延迟队列_插件实现下载插件RabbitMQ 配置类RabbitMQ …

SpringCloudAlibaba微服务实战系列(一)Nacos服务注册发现

SpringCloudAlibaba微服务实战系列&#xff08;一&#xff09;Nacos服务注册发现 实战前先做一个背景了解。 单体架构、SOA和微服务 单体架构&#xff1a;近几年技术的飞速发展&#xff0c;各种各样的服务已经进入到网络化。单体架构发布时只需要打成一个war或jar包发布即可&a…

2023年的深度学习入门指南(19) - LLaMA 2源码解析

2023年的深度学习入门指南(19) - LLaMA 2源码解析 上一节我们学习了LLaMA 2的补全和聊天两种API的使用方法。本节我们来看看LLaMA 2的源码。 补全函数text_completion源码解析 上一节我们讲了LLaMA 2的编程方法。我们来复习一下&#xff1a; generator Llama.build(ckpt_di…