从零开始构建嵌入式实时操作系统5——设计延时功能
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——设计延时功能相关推荐
- 从零开始构建嵌入式实时操作系统2——重构
1.前言 本人是一个普通的中年程序员,并不是圈内的大牛,写嵌入式操作系统这一系列的文章并不是要显示自己的技术,而是出于对嵌入式的热爱.非常幸运,本人毕业后的十几年一直从事嵌入式行业,遇到过各种坑,也收 ...
- 从零开始构建嵌入式实时操作系统3——任务状态切换
1.前言 一个行者问老道长:"您得道前,做什么?"老道长:"砍柴担水做饭."行者问:"那得道后呢?"老道长:"砍柴担水做饭.&qu ...
- 嵌入式系统开发16——嵌入式实时操作系统uC/OS的简介及简单应用
本文主要介绍嵌入式实时操作系统(RTOS),并且以uc/OS为例,将其移植到stm32F103C8T6上,构建3个任务:其中两个task分别以1s和3s周期对LED灯进行点亮-熄灭的控制:另外一个ta ...
- 嵌入式实时操作系统ucos-ii_「正点原子NANO STM32开发板资料连载」第三十六章 UCOSII 实验 1任务调度...
1)实验平台:alientek NANO STM32F411 V1开发板2)摘自<正点原子STM32F4 开发指南(HAL 库版>关注官方微信号公众号,获取更多资料:正点原子 第三十六章 ...
- 嵌入式实时操作系统ucos-ii_「正点原子NANO STM32开发板资料连载」第三十八章 UCOSII 实验 3...
1)实验平台:alientek NANO STM32F411 V1开发板2)摘自<正点原子STM32F4 开发指南(HAL 库版>关注官方微信号公众号,获取更多资料:正点原子 第三十八章 ...
- 嵌入式实时操作系统11——操作系统内核运行原理
先展示一个操作系统运行动态图 1.操作系统内核关键知识点 本文将用一个实际的工程例子来剖析操作系统内核运行原理.在此之前我们先回顾一下之前文章讲述的重点知识点. <嵌入式实时操作系统3--任务切 ...
- 嵌入式实时操作系统1——初识嵌入式实时操作系统
嵌入式实时操作系统是什么 嵌入式实时操作系统是一个特殊的程序,是一个支持多任务的运行环境.嵌入式实时操作系统最大的特点就是"实时性",如果有一个任务需要执行,实时操作系统会立即执行 ...
- 嵌入式实时操作系统-VxWorks(基础)
1.实时系统定义:是指那些产生系统输出的时间对于系统是至关重要的系统. 实时系统可根据时限对其性能(或效益)影响程度的不同,分为软实时系统SRT和硬实时系统HRT. 软时限是指时限的错过不会损害系统的 ...
- 《嵌入式实时操作系统uC/OS-II》学习摘要
二. 实时系统概念 1.实时系统的特点 如果逻辑和时序出现了偏差,将会引起严重后果.有两种类型的实时系统:软实时系统和硬实时系统. 在软实时系统系统中,系统的宗旨是指各个任务尽快地运行,而不要求限定某 ...
最新文章
- SpringCloud Alibaba微服务实战(一) - 基础环境搭建
- 七大Github机器学习热门项目
- 危害企业IT系统最严重的五个安全威胁
- c语言平滑raw图像(取平均值法)
- python中的模块原则_python 的模块与包
- CCIE学习(7)——VLAN相关命令汇总
- rest模式get,post,put,delete简单讲解
- Numpy中求向量和矩阵的范数
- ORDER BY NEWID()【原创】
- 【开源】iTest教学辅助系统源代码
- win ftp 指定的密码无效。请键入新密码。_重设OS X (macOS)系统帐户密码的5种方法...
- 用vue开发顶端粘滞效果的页面
- WebGrid 在asp.net mvc中的使用和理解(译)
- python画图代码乔治-2020阅读书单
- 大小端转换定义结构体的技巧
- db9针232接口波特率标准_9针RS232-422-485接口定义
- npy文件的处理方式
- 稀土铕配合物掺杂聚苯乙烯荧光微球/含铕配合物聚苯乙烯荧光微球/稀土磁性荧光微球制备
- 谷歌火狐等浏览器Flash安装失败,安装后进入网站仍提示未安装Flash
- adb最新版下载地址