运行的程序,有响应指令的触发式程序,也有一直运行的守护程序,周期程序。贴别是在单片机/嵌入式领域,大部分程序都是周期性的执行,进行数据采集,处理,上传等功能,因此我们需要能够适配各种情况的周期任务框架。
可以先看这篇文章:C/C++语言性能分析方法及性能分析工具的使用
我们一般是通过高中的周期函数来获得对于周期的精确性理解。
下面是有关周期的数学描述:
完成一次振动所需要的时间,称为振动的周期。
若f(x)为周期函数,则把使得f(x+l)=f(x)对定义域中的任何x都成立的最小正数l,称为f(x)的(基本)周期。
在计算机中,完成一个循环所需要的时间;或访问一次存储器所需要的时间,亦称为周期 [4] 。周期函数的实质:两个自变量值整体的差等于周期的倍数时,两个自变量值整体的函数值相等。如:f(x+6) =f(x-2)则函数周期为T=8。
按照我们一般的理解,周期任务是指每个任务的开始执行时刻(或完成该任务的时刻)都相隔一个固定的时间,还是指在一段时间内,该任务必须执行一次。
周期任务的第一种表述更接近数学上的精确描述。
周期任务的第二种表述则更加贴合实际。
在业界,周期任务的表述为:
周期任务是指计算机系统按一定周期到达并请求运行,每次请求称为任务的一个任务实例,任务实例所属任务的起始时刻称为该任务实例的到达时刻,任务实例被置为就绪态的时刻称为该任务实例的释放时刻。
可以看出,这个周期任务的描述更加准确,且考虑到了操作系统对任务的调度。在有操作系统时,任务是不能直接由创建到执行态的,任务是否执行是由操作系统来决定的。我们能控制的,是把该任务置位就绪态,等待操作系统分配CPU资源。
因此,我的周期任务的第二种描述应该是业界周期任务表述的白话版。
实际的周期任务只要能满足在每一段时间内都执行一次就算满足了其周期性。
讲到周期,就必须讲时间,时刻,而这些都需要时间基准和时间度量。
因此我们需要对时钟有一定的了解,可以参考这篇文章:对单片机中时钟的理解
下面我以VxWorks5.5和RTT为例来说明一下定时器。
vxworks中的定时方法:VxWorks几种常用的延时方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-svUfYOu1-1646148117765)(https://raw.githubusercontent.com/xkyvvv/blogpic2/main/img/20220301222958.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vtuTg7Pa-1646148117766)(https://raw.githubusercontent.com/xkyvvv/blogpic2/main/img/20220301223234.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iscO42tl-1646148117767)(https://raw.githubusercontent.com/xkyvvv/blogpic2/main/img/20220301223643.png)]
可以参考官方文档:时钟管理
对以上背景知识了解之后,我们可以构建出怎样的周期任务框架呢?
在裸机上和在RTOS操作系统上有什么不同。
分享一篇非常非常好的文章,来自无际大佬,单片机怎么做定时器矩阵,彻底解决各种定时问题?。
看完这篇文章,我们就对单片机裸机编程中的周期任务框架了然于心了。
一般的RTOS都会提供创建任务的接口,但是不一定会提供创建周期任务的接口,因此一般如果要想在RTOS上使用创建周期任务的接口,需要我们使用定时器和创建任务的接口一起来生成一个创建周期任务的接口。当然有的RTOS是提供了创建周期任务的接口,比如接下来我们要介绍的国产开源RTOS——RT-Thread 。
在RT-Thread 官方文档提供的示例代码中:
#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 timer_static_sample(void) { /* 初始化定时器 */ rt_timer_init(&timer1, "timer1", /* 定时器名字是 timer1 */ timeout1, /* 超时时回调的处理函数 */ RT_NULL, /* 超时函数的入口参数 */ 10, /* 定时长度,以 OS Tick 为单位,即 10 个 OS Tick */ RT_TIMER_FLAG_PERIODIC); /* 周期性定时器 */ rt_timer_init(&timer2, "timer2", /* 定时器名字是 timer2 */ timeout2, /* 超时时回调的处理函数 */ RT_NULL, /* 超时函数的入口参数 */ 30, /* 定时长度为 30 个 OS Tick */ RT_TIMER_FLAG_ONE_SHOT); /* 单次定时器 */ /* 启动定时器 */ rt_timer_start(&timer1); rt_timer_start(&timer2); return 0; } /* 导出到 msh 命令列表中 */ MSH_CMD_EXPORT(timer_static_sample, timer_static sample);
仿真运行结果如下:
\ | / - RT - Thread Operating System / | \ 3.1.0 build Aug 24 2018 2006 - 2018 Copyright by rt-thread team msh >timer_static_sample msh >periodic timer is timeout periodic timer is timeout one shot timer is timeout periodic timer is timeout periodic timer is timeout periodic timer is timeout periodic timer is timeout periodic timer is timeout periodic timer is timeout periodic timer is timeout periodic timer is timeout复制错误复制成功
周期性定时器 1 的超时函数,每 10 个 OS Tick 运行 1 次,共运行 10 次(10 次后调用 rt_timer_stop 使定时器 1 停止运行);单次定时器 2 的超时函数在第 30 个 OS Tick 时运行一次。
对于一些没有提供创建周期任务接口的RTOS,我们就需要自己写了,但是建议在写之前先参考一下成熟的周期任务接口实现,以防写出来的接口有bug。