1.前言

人生只有三天,昨天、今天和明天。昨天已然成为过去,明天尚在未来,拥有的不过是今天。
每一个今天,终将成为昨天,每一个明天,也都会成为今天,如此往复,抓住现在,珍惜未来,才能过好这一生。

这段箴言道出了时间的宝贵,我们需要珍惜时间,高效的利用时间。对于人如此,对于软件设计也同样如此,优良的软件设计往往能高效利用处理器的执行时间,最大程度的减少低效率的操作。

很久以前做过一个测量温湿度的项目,当时使用的额是AHT21这款芯片,该芯片再触发测量之后,需要等待80ms,AHT21的datasheet如下:

当时设计的代码大致如下:

程序每次读AHT21温度数据时都需要延时等待100ms,假设程序设计的采样频率是5HZ,就是一秒钟读5次AHT21温度数据。那么MCU在执行过程中每一秒就有500ms在死等”,MCU的有效使用率直接下降为50%!由此可见设计高效的延时对提高系统的执行效率有多么重要。

2.设计背景

在enuo v0.03版本中增加了延时表,这个延时表并非真正的延时表,它本质上是一个停止表,需要停止运行的任务就存放到该表中,该表中的任务将一直处于停止状态,直到有其它任务恢复此表中的任务。
这种设计只是实现了任务两个状态切换(就绪态和停止态),工程如下:

3.设计目标

enuo v0.04的设计目标是增加一个阻塞延时功能,当任务需要延时等待一定时间时,任务调用延时函数,此时操作系统会将该任务从就绪表中移除,同时该任务会插入延时列表,延时表中的任务在延时时间没有到期之前不会被执行。当延时完成时任务自动恢复到就绪状态,之后操作系统会调度执行该任务。为完成设计目的需要完成以下功能:
1、增加一个计时功能,使用一个32位的全局变量作为计时器,在系统节拍中断函数中完成自加。
2、每次系统节拍函数进入中断后,完成延时表中的任务延时检测,检测任务延时是否到期。
3、完成任务延时表功能,任务根据延时时间长短插入延时表,延时短的任务在延时列表的前端。

4.设计环境

硬件环境是使用STM32F401RE为核心的自制开发板,软件环境是使用的KEIL V5.2 开发工具。

5.设计过程

5.1阻塞式延时

相信大家去吃一些美味小食时都排队的经历吧,这种漫长的等待经历是不是还历历在目?除了在队伍里傻站着和刷刷手机,啥也干不了。

但是有些商场就有一种很好的等待机制:无线提示器。客户点餐后就可以在一定范围内自由活动,当你点的餐做好了,无线提示器会提示你去取餐。这种方式可以极大的减轻客户站立排队的低效和痛苦。无线提示器如下图:

上面展示了两种等待模式:
1、阻塞式等待
2、非阻塞式等待

阻塞式等待和非阻塞式等待关注的是任务在执行等待时的状态,阻塞式等待是指任务在等待期间会被挂起(处于停止状态),等待结束之后任务将会继续运行,记忆方法:当前任务被阻塞(不运行)。非阻塞式等待任务在等待期间不会被挂起,记忆方法:当前任务被不阻塞(一直运行)

回到上面排队的例子,第一种站立式排队模式,当你处在这种等待模式下时你必须一直保存这种排队等待的状态,直到你买到你需要的商品,这种模式就是非阻塞式等待
第二种无线提示器模式,当客户完成商品付款后,就可以在一定范围内自由活动,直到收到无线提示器会提示就可以直接去取商品,这种模式就是阻塞式等待。非常明显在大多数情况下阻塞式等待会更加高效

5.2延时策略

实现等待很多种策略,常见的策略有以下两种:

1、倒计时法
2、闹钟法

大家一定都看过火箭发射的场面,那清晰洪亮的倒计时声仿佛就在耳边回响:

10…9…8…7…6…5…4…3…2…1…0

火箭发射的就是使用的倒计时法,这种策略只需要关注剩余时间。

大部分人早上起床的时候肯定是被设定的闹钟叫醒,这种策略使用起来十分方便,只用关注设定的时间即可。

倒计时法的算法逻辑是:每一次判断剩余时间是否为0,如果剩余时间不为0,就将剩余时间减一,如果剩余时间为0则完成延时等待。

闹钟法的算法逻辑是:比较当前时间是否等于设定时间,不等于设定时间则继续等待,等于设定时间则完成延时等待。

5.3两种策略对比

倒计时法需要完成一次时间判断操作和一次时间减法操作,闹钟法只需要完成一次时间判断操作。

假设现在有100个任务,使用倒计时法需要完成100次时间判断操作和100次时间减法操作。而闹钟法只要完成100次时间判断操作

如果我们将延时任务按照顺序排列,延时短的任务放在延时队列的前端,只就意味着最快完成延时的任务肯定是延时队列的第一个任务,因此我们只用判断延时队列的第一个任务是否完成延时操作即可

假设现在有100个任务,使用倒计时法需要完成100次时间减法操作和1次判断延时队列首个任务操作。而闹钟法只要完成1次判断延时队列首个任务操作。 由于闹钟法执行步骤较少,enuo使用闹钟法机制。

闹钟法机制虽然执行效率高,但是同时存在一个时间归零问题,我们知道当前时间是23点,如果需要等待2小时,闹钟时间就变成了1点,因此闹钟法需要额外处理时间归零问题

5.4系统节拍

操作系统需要一个周期性的时钟源,这个时钟源称之为时钟节拍。为了生成时周期性钟节拍,通常需要使用硬件定时器,配置硬件定时器产生一个固定频率的中断(通常为10~1000Hz之间),当中断发生时,中断服务程序调用操作系统中的一个特殊程序:系统时钟节拍服务。

系统时钟节拍可以配置为10Hz,也可以配置为1000Hz。时钟节拍值越大说明硬件定时器产生的中断越频繁,系统时钟节拍服务就越频繁的执行。

高频率时钟节拍的优点:

1、提高操作系统时间管理精度
2、提高任务抢占准确度

例如10Hz的时钟节拍,意味着的操作系统执行时间粒度为100ms,系统中的周期性事件最快为100ms一次,无法有更高的精度。例如1000Hz的时钟节拍,此时的执行时间粒度就提高了100倍,此时系统中的周期性事件最快为1ms一次,时间精度可以达到1ms。

enuo系统定义一个全局变量heartbeat ,记录了系统节拍时间。系统中断服务程序中执行heartbeat 自加工作,更新系统节拍时间。代码如下:

volatile uint32_t  heartbeat ;
/*********************************************************************************************************
* @名称  : SysTick_Handler
* @描述  : 系统中断服务程序
**********************************************************************************************************/
void SysTick_Handler(void)
{   /* 系统节拍心跳计数  */ heartbeat++;/* 延时处理  */   delay_handle();/* 开始任务切换调度 */scheduler_task();
}

5.5加入延时队列

需要延时等待的任务执行延时函数delay,延时函数会将指定的任务从原有的链表中移除,并将任务插入延时列表,延时列表中的任务将不会被执行。
任务延时的结束时间是当前的系统节拍时间加上延时时间,延时列表中的任务按照结束时间从小到大进行排列,延时列表的第一个任务就是最快被唤醒的任务。
延时函数中末尾有一次主动任务调度请求,此时操作系统将立即执行下一个就绪的任务。代码如下:

/*********************************************************************************************************
* @名称  : 延时
* @描述  : 延时单位为一个系统心跳节拍
**********************************************************************************************************/
void delay( uint32_t delay ,task_tcb_t *task)
{/* 保存滑动指针位置 */list_node_t * const new_node = &task->link;task->link.sort_value = heartbeat + delay;/* 移除原有链表关系 */list_remove(new_node);/* 插入滑动指针末尾 */ list_sort_insert( &delay_list , new_node);/* 调度任务 */scheduler_task();
}


系统开始运行时task0,task1,task2都处于就绪状态,当task1执行延时操作后,系统将task1从就绪列表中移除,同时将task1插入延时列表中,此时系统任务的总体状态如下:

5.6延时处理

系统中断服务程序中执行heartbeat自加工作,更新系统节拍时间后执行延时处理函数delay_handle,进入延时处理函数后先判断延时列表中的任务数量;如果延时列表中的任务数量不为0,就会判断延时列表第一个任务是否完成延时;如果延时列表第一个任务延时时间到期,就会将任务从延时列表中移除,同时将该任务加入就绪列表末尾。代码如下:

/*********************************************************************************************************
* @名称  : delay_handle
* @描述  : 延时处理
**********************************************************************************************************/
void delay_handle(void)
{/* 判断延时列表是否有任务  */ if( delay_list.node_value != 0 ){/* 判断延时列表第一个任务 是否完成延时 */if(delay_list.head.next->sort_value <= heartbeat){list_node_t * delay_node;delay_node = delay_list.head.next;/* 移除原有链表关系 */list_remove(delay_node);/* 插入滑动指针末尾 */ list_insert_sliding_pointer_end( &ready_list , delay_node);         }}
}
/*********************************************************************************************************
* @名称  : SysTick_Handler
* @描述  : 系统中断服务程序
**********************************************************************************************************/
void SysTick_Handler(void)
{   /* 系统节拍心跳计数  */ heartbeat++;/* 延时处理  */   delay_handle();/* 开始任务切换调度 */scheduler_task();
}

假设task1,task2在延时列表中,此时系统任务的状态如下:

当系统节拍时间更新后,task1延时时间到期,系统将task1从延时列表中移除,将task1插入就绪列表中,此时系统任务的总体状态如下:

6.运行结果

测试任务代码如下:

/*********************************************************************************************************
* @名称  :task0
* @描述  :任务0
**********************************************************************************************************/
void task0(void)
{while(1){/* 测试跟踪 */task_debug_num0++;    test_function();/* 延时 */delay(5,&my_task0);         }
}
/*********************************************************************************************************
* @名称  : task1
* @描述  : 任务1
**********************************************************************************************************/
void task1(void)
{   while(1){task_debug_num1++;   /* 测试跟踪 */test_function();/* 延时 */      delay(50,&my_task1);}
}
/*********************************************************************************************************
* @名称  : task2
* @描述  : 任务2
**********************************************************************************************************/
void task2(void)
{   while(1){task_debug_num2++;   /* 测试跟踪 */test_function();/* 延时 */delay(100,&my_task2);}
}

工程中有3个任务task0,task1,task2,其中task0每执行一次就延时5个节拍,task1每执行一次就延时50个节拍,task2每执行一次就延时100个节拍。

因此运行一段时间后task0,task1,task2各种的task_debug_num之比约为20:2:1

仿真结果如下:

task_debug_num之比约为20:2:1 ,实现延时功能的设计目标。

总结:本文讲解了延时的不同模式和策略,描述了enuo延时功能的设计过程

希望获取源码的朋友们在评论区里留言。

未完待续…
实时操作系统系列将持续更新
创作不易希望朋友们点赞,转发,评论,关注。
您的点赞,转发,评论,关注将是我持续更新的动力
作者:李巍
Github:liyinuoman2017
CSDN:liyinuo2017
今日头条:程序猿李巍

从零开始构建嵌入式实时操作系统5——设计延时功能相关推荐

  1. 从零开始构建嵌入式实时操作系统2——重构

    1.前言 本人是一个普通的中年程序员,并不是圈内的大牛,写嵌入式操作系统这一系列的文章并不是要显示自己的技术,而是出于对嵌入式的热爱.非常幸运,本人毕业后的十几年一直从事嵌入式行业,遇到过各种坑,也收 ...

  2. 从零开始构建嵌入式实时操作系统3——任务状态切换

    1.前言 一个行者问老道长:"您得道前,做什么?"老道长:"砍柴担水做饭."行者问:"那得道后呢?"老道长:"砍柴担水做饭.&qu ...

  3. 嵌入式系统开发16——嵌入式实时操作系统uC/OS的简介及简单应用

    本文主要介绍嵌入式实时操作系统(RTOS),并且以uc/OS为例,将其移植到stm32F103C8T6上,构建3个任务:其中两个task分别以1s和3s周期对LED灯进行点亮-熄灭的控制:另外一个ta ...

  4. 嵌入式实时操作系统ucos-ii_「正点原子NANO STM32开发板资料连载」第三十六章 UCOSII 实验 1任务调度...

    1)实验平台:alientek NANO STM32F411 V1开发板2)摘自<正点原子STM32F4 开发指南(HAL 库版>关注官方微信号公众号,获取更多资料:正点原子 第三十六章 ...

  5. 嵌入式实时操作系统ucos-ii_「正点原子NANO STM32开发板资料连载」第三十八章 UCOSII 实验 3...

    1)实验平台:alientek NANO STM32F411 V1开发板2)摘自<正点原子STM32F4 开发指南(HAL 库版>关注官方微信号公众号,获取更多资料:正点原子 第三十八章 ...

  6. 嵌入式实时操作系统11——操作系统内核运行原理

    先展示一个操作系统运行动态图 1.操作系统内核关键知识点 本文将用一个实际的工程例子来剖析操作系统内核运行原理.在此之前我们先回顾一下之前文章讲述的重点知识点. <嵌入式实时操作系统3--任务切 ...

  7. 嵌入式实时操作系统1——初识嵌入式实时操作系统

    嵌入式实时操作系统是什么 嵌入式实时操作系统是一个特殊的程序,是一个支持多任务的运行环境.嵌入式实时操作系统最大的特点就是"实时性",如果有一个任务需要执行,实时操作系统会立即执行 ...

  8. 嵌入式实时操作系统-VxWorks(基础)

    1.实时系统定义:是指那些产生系统输出的时间对于系统是至关重要的系统. 实时系统可根据时限对其性能(或效益)影响程度的不同,分为软实时系统SRT和硬实时系统HRT. 软时限是指时限的错过不会损害系统的 ...

  9. 《嵌入式实时操作系统uC/OS-II》学习摘要

    二. 实时系统概念 1.实时系统的特点 如果逻辑和时序出现了偏差,将会引起严重后果.有两种类型的实时系统:软实时系统和硬实时系统. 在软实时系统系统中,系统的宗旨是指各个任务尽快地运行,而不要求限定某 ...

最新文章

  1. SpringCloud Alibaba微服务实战(一) - 基础环境搭建
  2. 七大Github机器学习热门项目
  3. 危害企业IT系统最严重的五个安全威胁
  4. c语言平滑raw图像(取平均值法)
  5. python中的模块原则_python 的模块与包
  6. CCIE学习(7)——VLAN相关命令汇总
  7. rest模式get,post,put,delete简单讲解
  8. Numpy中求向量和矩阵的范数
  9. ORDER BY NEWID()【原创】
  10. 【开源】iTest教学辅助系统源代码
  11. win ftp 指定的密码无效。请键入新密码。_重设OS X (macOS)系统帐户密码的5种方法...
  12. 用vue开发顶端粘滞效果的页面
  13. WebGrid 在asp.net mvc中的使用和理解(译)
  14. python画图代码乔治-2020阅读书单
  15. 大小端转换定义结构体的技巧
  16. db9针232接口波特率标准_9针RS232-422-485接口定义
  17. npy文件的处理方式
  18. 稀土铕配合物掺杂聚苯乙烯荧光微球/含铕配合物聚苯乙烯荧光微球/稀土磁性荧光微球制备
  19. 谷歌火狐等浏览器Flash安装失败,安装后进入网站仍提示未安装Flash
  20. adb最新版下载地址

热门文章

  1. 浮云人脸搜索软件最新版本上线,宣告人脸搜索又进一步
  2. 遥感图像辐射增强ERDAS实现
  3. 西方哲学史 -- 赫拉克利特
  4. 华为过程可信cib是指_华为流程规范考.doc
  5. kindle所支持的格式
  6. 【白板推导系列笔记】降维-主成分分析-概率角度(Probabilistic PCA)
  7. 九度OJ 1177:查找 (字符串操作)
  8. Linux系统进程的理解与相关命令的使用系列
  9. html转pdf(总结五种方法Java)
  10. 人工智能的终局:让机器理解人类的思维方式