GorgonMeducer 发表于 2020-6-3 20:54:39

【交流】实时性迷思(1)

本帖最后由 GorgonMeducer 于 2020-6-5 04:53 编辑

【序】不知道你发现没有,平时我们讨论嵌入式软件开发时总绕不开与实时性(Real Time)相关的话题。相信不少朋友和我一样是通过实时性操作系统(Real Time Operating System, RTOS)第一次接
触到实时性概念的——我记得那还是大学时代、参加机器人竞赛的时候。工作以后自信地以为加深了
不少对实时性的本质认识——现在看来其实还未摸到门道。就这样浑浑噩噩一直到毕业后的第八年,
因为工作变动的原因,我被迫要在一周内要做一个实时性原理相关的研究报告,也就在那时,我体会
到了疯狂练功走火入魔的感觉:走路在思考、吃饭在看资料、头一直发烧一样的微微发热、甚至连睡
觉都在梦中推演模型——头发一把一把的掉,幸好有截稿时间,否则真的要秃了。

也就是经过那一次,我突然发现自己之前对实时性的认知可谓徒有其表,甚至从未做对实时性模型本身的定量分析——所幸,那次研究报告如期交付,工作变动也如愿以偿。然而,3年后我发现“我又双叕天真了”——那是有一次,我正跟人讨论嵌入式基本范式,就突然一个瞬间,脑海中原本毫不相关的一些模型猛地被联系到了一起(音效请脑补):

我甚至本能的立即意识到:之前自己在某篇文章中“言之凿凿”的推论过程其实存在巨大漏洞——当然,那本书从未出版过,而且会闲到对我进行深究的人估计也没有几个。
今天,即便我非常确信——在前方至少还有几道数学的深谷阻碍着我触碰“实时性”的圣杯——然而我并
不是计算机科学家,现有结论对我来说已经足够装逼https://mmbiz.qpic.cn/mmbiz_png/ltMWvZwjslibfx7VyzU1pPH8qxF6ibMPfMh7ckckicXBETJ1clXSAawYIcakUknw3GrqLhwSJLa58P1I9N3ibCwnug/640?wx_fmt=pnghttps://mmbiz.qpic.cn/mmbiz_png/ltMWvZwjslibfx7VyzU1pPH8qxF6ibMPfMh7ckckicXBETJ1clXSAawYIcakUknw3GrqLhwSJLa58P1I9N3ibCwnug/640?wx_fmt=pnghttps://mmbiz.qpic.cn/mmbiz_png/ltMWvZwjslibfx7VyzU1pPH8qxF6ibMPfMh7ckckicXBETJ1clXSAawYIcakUknw3GrqLhwSJLa58P1I9N3ibCwnug/640?wx_fmt=png。回头看来,根据
我的经验以及与朋友讨论的结果,大致认为大部分人对实时性的认知过程通常会分以下几个阶段:

[*]Lv1:“实时性” = “越快越好”,认为用好中断是保证实时性的关键;这类朋友通常最擅长的是裸
      机下的“前后台系统”;

[*]Lv2:“实时性” = RTOS,认为选一个好的RTOS,或者会用RTOS就可以保证实时性;这一阶段
      的朋友对RTOS充满了好奇,以编写自己的RTOS为“ 终(zhong)极(er)目标”;

[*]Lv3:“实时性” = 任务拆分,这一阶段已经能正确的理解实时性窗口的概念,意识到实时性并不
      意味着越快越好,但也认为“在可能的情况下”“快一点响应事件没啥坏处”;这一阶段的朋友可能已
      经可以在裸机和RTOS之间自由的反复横跳,无论是裸机下的状态机还是RTOS下的线程都已了如指
      掌、任务间通信更是游刃有余;

[*]Lv4:这一阶段开始思考实时性模型的特点,并逐渐意识到模型本身其实隐含了足以颠覆过往所有
      关于实时性认知的秘密;到达这一阶段的朋友通常觉得没必要、也没心思继续思考实时性更本质的
      数学意义——因为此时获得的结论已经足够了应付几乎所有的工程开发了。顺便说一下,我就在这
   里。

[*]Lv5:到了这个阶段,不仅脑洞大开、战斗力惊人、估计打针也没法阻止你抓破脖子了吧——以上
   只是暴露年龄的玩笑,但肯定可以水几篇SCI论文了……


在理解了实时性的模型以后,我(本能的排除了自己比较笨这个可能性,然后)意识到:其实这一过程完
全没必要如此漫长和曲折——很多结论和道理是如此简单——不仅书本上有,而且解释和学习起来都不费
什么力气。可能这就是“挠破头”想通某个道理之后,回头再看时忍不住要“苦笑”时的感受吧。
按照约定,为了将经验和知识分享给大家,从本文开始,我将以几篇文章的篇幅:从基础模型开始,由
浅入深、由理论到实践,推演关于实时性的几个重要结论——从而直接跳跃到Lv4的认知阶段。如果你
看了这个系列后有什么话想说的、想问的,还请在评论区写下您的留言。求评论、求转发、求收藏。

【击碎 “唯快不破” 的神话】
图1展示了一个标准的实时性模型:

[*]基于物理世界客观法则的限制,很多应用在制定需求说明的时候,从某一个事件发生的时刻计算,会
      规定一个死线(Dead Line),即:一旦事件发生了,如果不在这个死线之前完成整个对事件的处理,
      就视作失败;

[*]这里,从事件发生到死线这段时间长度,习惯上称为实时性窗口。当事件发生时,只有在死线内任意
       时刻完成了对事件的处理,才能称为实时性得到了满足;

[*]容易注意到,处理事件的过程也需要消耗时间——一般称为事件处理时间;


图1 实时性基本模型
考虑一个有趣的问题:对一个实时性任务来说,实时性窗口内的时间,其价值是一样的么?换句话说,横
竖处理事件消耗的时间是不变的,早点做迟点做都是做,有什么区别么?

图2 实时性窗口内不同时间段完成事件响应
对比图2所示的三种情况,可以很清楚的得出结论:理论上,从满足实时性的角度出发,在时间窗口内任意时段完成对事件的处理都满足实时性要求;早做没有任何额外的好处,“踩着上课铃到校”也没有任何惩罚——简单说就是早做迟做无所谓。
你说“我不管,我不管”,既然什么时候做都一样为什么不能“尽早做”?“你也说了尽早做没啥不好”,“中断来了,服务程序执行了,我想让它迟点执行也做不到啊?”

为了回答这个问题,我们不讲大道理,先看一个常见的例子:

[*]超级循环里有三个任务A、B和C;

void main(void)
{
    ...
    while(1) {
      task_a();
      task_b();
      task_c();
    }
}



[*]每个任务都使用轮询的方式在等待一个来自芯片外界的事件发生(先不考虑存在中断的情况);
[*]当一个任务函数被执行时会检查对应的事件是否已经发生,如果确实已经发生,则执行后续的处理;
      反之则立即退出任务函数——释放处理器;

[*]A、B、C三个事件的实时性窗口分别为10ms, 6ms和4ms;处理三个事件的处理程序分别需要4ms、
      3ms和0.4ms。如图3所示:

图3 三个事件的实时性窗口和事件处理时间示意图
[*]需要强调的是,task_a()、task_b()和task_c()三个函数的策略本质上都是一样的——“一旦检测到
       事件立即处理,绝不迟延”!

基于上述事实,容易发现:假如某一时刻,A、B、C三个函数都处于触发状态(等待处理的状态),而超级循环恰巧进入task_a()执行——这种情况其实比想象中容易发生,比如从task_a()退出到task_c()执行完成期间,事件A触发了;从task_b()退出到task_c()执行完成期间,事件B触发了;在task_c退出()之后恰巧事件C又触发了……此时,任务A会立即响应,消耗4ms的时间来完成事件处理;当从task_a()函数退出时,剩余给task_b()的时间窗口只有2ms(6ms-4ms),而事件B的处理函数需要3ms——显然事件B的实时性是无法得到保证的——当然事件C已经死得透透了……

图 4 “越快处理越好” 导致其它任务无法满足实时性要求通过上面的例子,我们知道“越快处理越好”是值得反思的——至少会存在情况导致系统在某些时刻无法
满足实时性要求;那么从模型上来说,如何理解这一现象呢?让我们重新来看图1所示的模型:

实际上,如果单纯从一个实时性任务自身出发来看,的确在实时性窗口内,任意时间完成事件的处理
都是一样的;然而,通过前面的举例我们其实可以发现,当一个系统中存在多个实时性任务时,虽然
一个实时性窗口内的任意时间对任务自己都是等价的,但越靠前的时间对“别人”来说是越宝贵的:

[*]当你使用“越快越好”策略时,你不会有额外的收益,而实际上是走了别人的路,让人无路可
       走——典型的损人不利己;

[*]当你在别人需要的时候,在自己实时性得到保证的前提下,尽可能让出对你没有额外价值的靠前
       的时间,实际上是一种“利他主义”;

[*]当所有的任务都采用这种利他策略时,就变成了“人人为我,我为人人”的合作策略——这种情况下,
      如果数学证明整个系统一定存在一个方案来满足所有任务的实时性需求,那么利他策略一定能找到
      这样的解决方案。

图 5 一种可能的解决方案(不是唯一)

作为一个系统开发者,我们显然是需要从全局考虑的,因此完全没有必要从单个实时性任务的自私
视角来看问题,因此结论就变得更为直接:实时性窗口内越靠前的时间价值越高,从总体上来看“单
纯”越快越好的策略对实时性是有害的。既然单纯的“越快越好”不可取,且“实时性窗口
内”越靠前的时间越有价值,是否意味着,其实“越靠后越好呢”?


为了验证另外一个极端“越慢越好(越靠后越好)”是否是正确的,我们不妨以同样的例子来推演一下,
仅仅更新task_a()、task_b()和task_c()的执行策略:从“越快越好”变为“越慢越好”——这实际上意
味着:

[*]每一个事件处理任务都清楚的知道“距离事件发生已经过去了多长时间”;
[*]为了做到“卡着上课铃进教室”,不到最后时刻,绝对不执行任务处理。

根据这一算法,我们推演得到以下的尴尬情形:
图 6 过于谦让的后果……不妨分析下过程:首先,task_a()执行,在了解到距离自己的最后时刻还有6ms的实时后毅然的决定把宝贵的时间留给他人;于是,CPU来到了下一个任务函数,基于类似的原因,task_b()也摆摆手……最终第一轮三个任务都决定再等一等……



如此谦让(浪费)了3ms以后,任务B终于决定下场——在执行了3ms任务处理后,成功的将随后的任务C逼上了绝路……随着A的沦陷,大型翻车现场成就达成……

从结论上看,另外一个极端“越慢越好”也是走不通的。那么究竟如何才能从模型分析的角度出发得
出一个令人信服的、容易理解的、满足所有任务实时性需求的方法呢?关于这一点,我们下次再聊。

【小结】从系统全局来看,实时性窗口内的时间越靠前越有价值,应该尽可能留给别的更紧急的任务来使用。
事件发生时“越快处理越好”的策略直接占用了它人的“生命线”——当所有的任务都试图“损人不利己”
时,那么整个系统没有一个任务是可以保证自己的实时性不被它人破坏的。从结论上看简单的“越快
越好”策略在实时性系统中是不允许的。








cgbabc 发表于 2020-6-3 23:02:48

:L:Ll图片没办法显示!

aleyn 发表于 2020-6-4 08:11:44

图挂了,图片来自公众号。。。

fire 发表于 2020-6-4 09:52:35

图片全部是微信转过来的,都挂掉 了

bg6agf 发表于 2020-6-4 10:21:37

fire 发表于 2020-6-4 09:52
图片全部是微信转过来的,都挂掉 了

不是可以直接粘贴图片吗。复制过来粘贴会不会有?

tao_oYMYe 发表于 2020-6-4 10:41:29

我可能还留在lv0的阶段

fire 发表于 2020-6-4 11:05:59

bg6agf 发表于 2020-6-4 10:21
不是可以直接粘贴图片吗。复制过来粘贴会不会有?

图片需要先保存在本地,然后复制粘贴就行了,是腾讯的问题,防止别人盗版图片

denike 发表于 2020-6-4 16:54:41

图挂了,图片来自公众号。。。

GorgonMeducer 发表于 2020-6-4 20:17:01

没事,我一会儿修正

GorgonMeducer 发表于 2020-6-5 04:53:23

图片修复完毕

bg6agf 发表于 2020-6-5 10:31:54

关于这一点,我们下次再聊。。。。。。

楼主变态。跟说书一样还 下回分解

csq463276932 发表于 2020-6-5 11:45:06


跟着楼主大牛学习。

Juggernaut 发表于 2020-6-5 17:34:11

好文好文

一心2013 发表于 2020-6-6 00:45:26

bg6agf 发表于 2020-6-5 10:31
关于这一点,我们下次再聊。。。。。。

楼主变态。跟说书一样还 下回分解 ...

;P所以要给他开个专门板块啊。

jack_yu 发表于 2020-6-6 07:59:31

听大师讲课。

jz701209李 发表于 2020-6-6 14:27:49

膜拜大神!!!!

litop 发表于 2020-6-6 22:04:58

大师风采依然 ......:lol:lol:lol:lol

杰杰 发表于 2020-6-7 23:04:42

学习了~

GorgonMeducer 发表于 2020-6-9 04:25:54

bg6agf 发表于 2020-6-5 10:31
关于这一点,我们下次再聊。。。。。。

楼主变态。跟说书一样还 下回分解 ...

哈哈……写文章的老毛病了……不怕你笑话……很多都是从此再无下文……

xad1974 发表于 2020-6-9 07:49:50

关于实时性在实际中终会碰到任务冲突的问题,所以有了优先级这个概念

yuguoliang 发表于 2020-6-10 15:43:13

坐等下回分解。。。。。。。。。。。。。。。。。

GorgonMeducer 发表于 2020-6-10 17:33:03

本帖最后由 GorgonMeducer 于 2020-6-10 17:34 编辑

xad1974 发表于 2020-6-9 07:49
关于实时性在实际中终会碰到任务冲突的问题,所以有了优先级这个概念
优先级其实在实时性系统里面不是用来解决所谓任务冲突的问题的。
而且,这里“任务冲突”其实我觉得你需要多说一下具体表示什么意思……

实时性系统里面,在RMS算法下,优先级的划分不是根据所谓任务在应用的重要性来决定的,而是纯粹根据实时性窗口的大小来划分优先级的——实时性窗口越小,优先级越高。关于这个问题,我原计划是在这个系列的第三篇文章里详细说明的。如果你们有兴趣,也可以直接看维基百科的RMS算法:

https://en.wikipedia.org/wiki/Rate-monotonic_scheduling

yuguoliang 发表于 2020-6-12 10:12:37

天下武功,唯快不破。

zhuhai2004 发表于 2020-6-12 10:39:14

所谓的实时性只是能在明确的时间内执行到的任务都可以算实时任务了吧。如果执行时间不能确定的,那坏处太大。

GorgonMeducer 发表于 2020-6-16 22:45:12

zhuhai2004 发表于 2020-6-12 10:39
所谓的实时性只是能在明确的时间内执行到的任务都可以算实时任务了吧。如果执行时间不能确定的,那坏处太大 ...
不是的,不是在明确的时间内“执行到”,而是必须在一个规定的时间范围内完成整个“事件”的处理。
执行时间不确定,说明这个任务就是不满足实时性的——不能因为有的任务无法满足确定性要求,就反过来认为实时性是不可能达到,或者是不合理的,甚至想来修改实时性的定义——这是很可怕的因果倒置的想法。

hqgboy 发表于 2020-6-17 09:53:23

学习。。。。。。。

ba_wang_mao 发表于 2020-6-23 14:50:28

GorgonMeducer 发表于 2020-6-16 22:45
不是的,不是在明确的时间内“执行到”,而是必须在一个规定的时间范围内完成整个“事件”的处理。
执行时 ...

傻孩子版主,从前在阿莫的网站看你写的东东,今天在火哥的网站上又看到你写的东东,很兴奋。
    关于实时操作系统我有个建议,能否给大家用实战的角度出发讲讲RTOS中级编程方面的应用,虽然网络上RTOS编程入门一大把,但是都只讲入门方面的知识和理论,中级方面的应用基本上网络上找不到。

    我最近在编写FreeRTOS就遇到一点点困难,问题如下:

            1、USART1每隔100毫秒采集3路模拟量信号组1
            2、USART2每隔100毫秒采集3路模拟量信号组2
            3、SD2405ALPI任务每隔1秒钟采集一次SD2405ALPI时钟芯片的时间(年、月、日、时、分、秒)
            4、USART3----和上位机通信(主从通信,服务器定时每隔500毫秒索要一次数据)
            5、USART4----和移动终端通信(主从通信,通信协议MODBUS-TCP协议)。

            上位机每隔500毫秒和设备索要数据,数据为:USART1的3路模拟量数据,USART2的3路模拟量数据,SD2405ALPI任务每隔1秒钟采集的时间(年、月、日、时、分、秒)
移动终端每隔500毫秒和设备索要数据,数据为:USART1的3路模拟量数据,USART2的3路模拟量数据,SD2405ALPI任务每隔1秒钟采集的时间(年、月、日、时、分、秒)


            我按照如下方法规划任务,结果上位机接收数据和显示数据正常,但是移动终端接收和显示数据异常,当我把上位机任务代码屏蔽,则移动终端接收和显示数据正常,不知道为什么会出现这种现象,请帮忙指导。


             我的任务规划如下:


            (1)、USART1中断服务程序接收的结尾,发送信号量1,激活USART1任务。
            (2)、USART1任务,收到信号量1,开始解析报文,解析完毕,先给上位机发送消息队列,然后给移动终端发送消息队列
            (3)、USART2中断服务程序接收的结尾,发送信号量2,激活USART2任务。
            (4)、USART2任务,收到信号量2,开始解析报文,解析完毕,先给上位机发送消息队列,然后给移动终端发送消息队列
             (5)、SD2405ALPI任务 是个周期性任务,延时节拍为1秒,定时1秒读取1次时间(年、月、日、时、分、秒),然后先给上位机发送消息队列,然后给移动终端发送消息队列。
         (6)、USART3中断服务程序接收的结尾,发送信号量3,激活USART3任务。
            (7)、USART3任务(上位机),收到信号量3,开始解析报文,解析完毕,开始等待接收USART1发送的消息队列,等待USART2发送的消息队列,等待SD2405ALPI任务的消息队列。
         (8)、USART4中断服务程序接收的结尾,发送信号量4,激活USART4任务。
            (9)、USART4任务(移动终端),收到信号量4,开始解析报文,解析完毕,开始等待接收USART1发送的消息队列,等待USART2发送的消息队列,等待SD2405ALPI任务的消息队列。


         


GorgonMeducer 发表于 2020-6-23 18:59:05

本帖最后由 GorgonMeducer 于 2020-6-23 19:05 编辑

ba_wang_mao 发表于 2020-6-23 14:50
傻孩子版主,从前在阿莫的网站看你写的东东,今天在火哥的网站上又看到你写的东东,很兴奋。
    关于实 ...
看这么多文字好头疼。一般这类分析都是用图来做的,你是否可以用数据流图描述下你的业务逻辑?
这里,外设统一用横线来表示(RX和TX可以用两个不同的横线来表示),任务或者各类数据加工的步骤用圆圈来表示,FIFO用横线表示。数据流图中,用箭头表示数据的流动,箭头上要标注数据的类型。
另外,显示数据异常这只是个现象,你要首先隔离出造成数据异常的问题,然后根据问题,再来看如何通过合理设计任务关系来避免。
那么,这里显示异常究竟是什么原因导致的?你通信用数据帧了么?数据帧校验了么?校验成功了么?
如果加了校验,通信就总是失败,说明是数据帧的完整性有问题,这时候就要定位数据帧完整性是在哪个阶段遭到破坏的,是发送阶段?接收阶段?还是产生数据帧的时候一开始就有问题?

千万不要笼统的通过置换法,就判断数据显示异常就是多任务造成的,这样的判断对解决问题毫无帮助。因为我们对数据显示异常的最直接原因根本不清楚——如果发现数据在发送buffer里是好的,但是通信的时候出现问题,或者是接收的时候出现了丢失,我们就可以着重分析这个问题,如果在buffer里就是错的,那么就要看是不是别的原因,比如缓冲溢出之类的。


ba_wang_mao 发表于 2020-6-24 09:12:44

本帖最后由 ba_wang_mao 于 2020-6-24 09:16 编辑

GorgonMeducer 发表于 2020-6-23 18:59
看这么多文字好头疼。一般这类分析都是用图来做的,你是否可以用数据流图描述下你的业务逻辑?
这里,外设 ...
谢谢!

         分析图如下:



    教课书上都说,如果任务A给任务C发送消息队列,任务B给任务D发送消息队列,则应该将任务C和任务D合并成1个任务。

   但是由于任务C是上位机通信,任务D是移动端通信,2者的通信协议不同,而且处在不同的串口,没有办法合并成1个任务,但是我又希望把现场采集的数据分发给任务C和任务D,不知道怎样根据FREERTOS提供的消息传输才能可靠的传输现场数据给上位机和移动端。

ba_wang_mao 发表于 2020-6-24 09:33:29

本帖最后由 ba_wang_mao 于 2020-6-24 09:35 编辑

源代码如下:

//Scheduler includes.
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"


////////////////////////////////////////////////////
//消息队列规划:
////////////////////////////////////////////////////
//                MSG = 1 --->表示接收到DI1_Task采集2路DI
//                        MSG = 第1路DI
//                        MSG = 第2路DI
//                MSG = 2 --->表示接收到DI2_Task采集2路DI
//                        MSG = 第1路DI
//                        MSG = 第2路DI
//             MSG = 3 --->表示接收到任务5的的SD2405时钟芯片的时间
//                        MSG = 年
//                        MSG = 月
//                        MSG = 日
//                        MSG = 时
//                        MSG = 分
//                        MSG = 秒
//                        MSG = 星期
//----------------------------------------------------------------------



/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbutils.h"
#include "mb.h"
/* ----------------------- Modbus includes ----------------------------------*/

#include "stm32f10x.h"
#include "platform.h"
#include "BSP_GPIO.h"
#include "BSP_NVIC.h"
#include "i2c_gpio.h"
#include "i2c_ee.h"
#include <SD2405ALPI.h>
#include "BSP_USART2.h"
#include "DMA.h"
#include "EC20.h"


////////////////////////////////////////////////////
//任务句柄
////////////////////////////////////////////////////
static TaskHandle_t xHandleTaskLED1 = NULL;
static TaskHandle_t xHandleTaskLED2 = NULL;
static TaskHandle_t xHandleTaskDI1 = NULL;
static TaskHandle_t xHandleTaskDI2 = NULL;
static TaskHandle_t xHandleTaskSD2405ALPI = NULL;
static TaskHandle_t xHandleTaskFreeModbus = NULL;
static TaskHandle_t xHandleTaskEC20 = NULL;



////////////////////////////////////////////////////
//任务
////////////////////////////////////////////////////
static void LED1_Task(void *pvParameters);
static void LED2_Task(void *pvParameters);
static void DI1_Task(void *pvParameters);
static void DI2_Task(void *pvParameters);
static void SD2405ALPI_Task(void *pvParameters);
static void FreeModbus_Task(void *pvParameters);
static void EC20_Task(void *pvParameters);



////////////////////////////////////////////////////
// 二值信号量定义
SemaphoreHandle_t xSemaphore = NULL;
////////////////////////////////////////////////////



////////////////////////////////////////////////////
//消息队列
////////////////////////////////////////////////////
QueueHandle_t xQueue_FreeMODBUS = NULL;
QueueHandle_t xQueue_EC20 = NULL;



//-------------------------------------------------------------
//互斥信号量拍照功能使用的全局变量
//-------------------------------------------------------------
CLOCK_Type MODBUS_Clock;
BOOL MODBUS_DI1_1;
BOOL MODBUS_DI1_2;
BOOL MODBUS_DI2_1;
BOOL MODBUS_DI2_2;





void LED1_Task(void *pvParameters)
{      
      AT24C128_Init();
      AT24C128_Read();
      taskENTER_CRITICAL();
      xTaskCreate(LED2_Task, "GPIO_LED2"                        , configMINIMAL_STACK_SIZE , NULL, tskIDLE_PRIORITY + 4, &xHandleTaskLED2);
      xTaskCreate(DI1_Task, "DI1"                                        , configMINIMAL_STACK_SIZE , NULL, tskIDLE_PRIORITY + 5, &xHandleTaskDI1);
      xTaskCreate(DI2_Task, "DI2"                                        , configMINIMAL_STACK_SIZE , NULL, tskIDLE_PRIORITY + 6, &xHandleTaskDI2);
      xTaskCreate(SD2405ALPI_Task, "SD2405ALPI"      , configMINIMAL_STACK_SIZE , NULL, tskIDLE_PRIORITY + 7, &xHandleTaskSD2405ALPI);
      xTaskCreate(FreeModbus_Task, "MODBUS"               , configMINIMAL_STACK_SIZE , NULL, tskIDLE_PRIORITY + 8, &xHandleTaskFreeModbus);      
      xTaskCreate(EC20_Task, "EC20"                                 , configMINIMAL_STACK_SIZE , NULL, tskIDLE_PRIORITY + 9, &xHandleTaskEC20);
      taskEXIT_CRITICAL();
      
    while ( 1 )
    {
      GPIO_LED1_ON();
                GPIO_LED2_ON();
                GPIO_LED3_ON();
      vTaskDelay(500 / portTICK_RATE_MS);
      GPIO_LED1_OFF();
                GPIO_LED2_OFF();
                GPIO_LED3_OFF();      
      vTaskDelay(500 / portTICK_RATE_MS);                        
    }
}





void LED2_Task(void *pvParameters)
{
    while(1)
    {
      GPIO_LED4_ON();
                GPIO_LED5_ON();
      vTaskDelay(250 / portTICK_RATE_MS);
      GPIO_LED4_OFF();
                GPIO_LED5_OFF();
      vTaskDelay(250 / portTICK_RATE_MS);      
    }
}






void DI1_Task(void *pvParameters)
{
      const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
      BOOL DI1_1;
      BOOL DI1_2;      
      uint8_t di11_array;
      uint8_t di12_array;      
      uint8_t Buffer;

      
    while(1)
    {
                //第一次采集:采集行程1A和行程1B
                di11_array = GPIO_DI1_1();
                di12_array = GPIO_DI1_2();               
                vTaskDelay(20 / portTICK_RATE_MS);
                //第二次采集:采集行程1A和行程1B
                di11_array = GPIO_DI1_1();
                di12_array = GPIO_DI1_2();      
                              
                if ((di11_array == di11_array) && (di12_array == di12_array))
                {      
                        DI1_1 = (di11_array ? TRUE : FALSE);
                        DI1_2 = (di12_array ? TRUE : FALSE);      
                }                              
               
                Buffer = 1;
                Buffer = DI1_1;
                Buffer = DI1_2;      
               
////////////////////////////////////////////////////////////////////////////////////////////////
//采集数据通过串口转WIFI模块,使用MODBUS协议通过消息队列传输给移动手机
////////////////////////////////////////////////////////////////////////////////////////////////
      if( xQueueSend(xQueue_FreeMODBUS, (void *) &Buffer , xTicksToWait) != pdPASS )

                {
                }
                else
                {
                        // 发送成功
                }                        


////////////////////////////////////////////////////////////////////////////////////////////////
//采集数据通过移远EC20模块,使用自定义协议通过消息队列传输给上位机
////////////////////////////////////////////////////////////////////////////////////////////////
                Buffer = 1;
                Buffer = DI1_1;
                Buffer = DI1_2;                        
      if( xQueueSend(xQueue_EC20, (void *) &Buffer , xTicksToWait) != pdPASS )
                {
                         //发送失败,即使等待了10个时钟节拍
                }
               
                vTaskDelay(100 / portTICK_RATE_MS);      
    }
}






void DI2_Task(void *pvParameters)
{
      const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
      BOOL DI2_1;
      BOOL DI2_2;      
      uint8_t di21_array;
      uint8_t di22_array;
      uint8_t Buffer;
      
      
    while(1)
    {
                //第一次采集:采集行程2A和行程2B
                di21_array = GPIO_DI2_1();
                di22_array = GPIO_DI2_2();               
                vTaskDelay(20 / portTICK_RATE_MS);
                //第二次采集:采集行程2A和行程2B
                di21_array = GPIO_DI2_1();
                di22_array = GPIO_DI2_2();               
                              
                if ((di21_array == di21_array) && (di22_array == di22_array))
                {      
                        DI2_1 = (di21_array ? TRUE : FALSE);
                        DI2_2 = (di22_array ? TRUE : FALSE);
                }
      
////////////////////////////////////////////////////////////////////////////////////////////////
//采集数据通过串口转WIFI模块,使用MODBUS协议通过消息队列传输给移动手机
////////////////////////////////////////////////////////////////////////////////////////////////
                Buffer = 2;
                Buffer = DI2_1;
                Buffer = DI2_2;      
      if( xQueueSend(xQueue_FreeMODBUS, (void *) &Buffer , xTicksToWait) != pdPASS )
                {
                }
                else
                {
                        // 发送成功
                }      

////////////////////////////////////////////////////////////////////////////////////////////////
//采集数据通过移远EC20模块,使用自定义协议通过消息队列传输给上位机
////////////////////////////////////////////////////////////////////////////////////////////////
                Buffer = 2;
                Buffer = DI2_1;
                Buffer = DI2_2;                        
      if( xQueueSend(xQueue_EC20, (void *) &Buffer , xTicksToWait) != pdPASS )
                {
                         //发送失败,即使等待了10个时钟节拍
                }
               
                vTaskDelay(100 / portTICK_RATE_MS);                  
    }
}




void SD2405ALPI_Task(void *pvParameters)
{      
      const TickType_t xTicksToWait = pdMS_TO_TICKS(100UL );
      CLOCK_Type Clock;
      uint8_t Buffer;
      
      SD2405ALPI_Init();
    while(1)
    {      
                SD2405ALPI_I2CReadTime(&Clock.second , &Clock.minute , &Clock.hour , &Clock.week , &Clock.day , &Clock.month , &Clock.year);                        
                              
////////////////////////////////////////////////////////////////////////////////////////////////
//采集数据通过串口转WIFI模块,使用MODBUS协议通过消息队列传输给移动手机
////////////////////////////////////////////////////////////////////////////////////////////////
                Buffer = 3;
                Buffer = Clock.year;
                Buffer = Clock.month;
                Buffer = Clock.day;
                Buffer = Clock.hour;
                Buffer = Clock.minute;
                Buffer = Clock.second;
                Buffer = Clock.week;
      if( xQueueSend(xQueue_FreeMODBUS, (void *) &Buffer , xTicksToWait) != pdPASS )
                {
                }
                else
                {
                        // 发送成功
                }      

////////////////////////////////////////////////////////////////////////////////////////////////
//采集数据通过移远EC20模块,使用自定义协议通过消息队列传输给上位机
////////////////////////////////////////////////////////////////////////////////////////////////               
                Buffer = 3;
                Buffer = Clock.year;
                Buffer = Clock.month;
                Buffer = Clock.day;
                Buffer = Clock.hour;
                Buffer = Clock.minute;
                Buffer = Clock.second;
                Buffer = Clock.week;               
      if( xQueueSend(xQueue_EC20, (void *) &Buffer , xTicksToWait) != pdPASS )
                {
                         //发送失败,即使等待了10个时钟节拍
                }
               
                vTaskDelay(1000 / portTICK_RATE_MS);                  
               
    }
}





////////////////////////////////////////////////////////////////////////////////////////////////
//移动手机任务
////////////////////////////////////////////////////////////////////////////////////////////////
static void FreeModbus_Task(void *pvParameters)
{
      const TickType_t xTicksToWait = pdMS_TO_TICKS( 200UL );
      BaseType_t xStatus;      
      eMBErrorCode    eStatus;
      uint8_t Buffer;      

      
      eStatus = eMBInit( MB_RTU, 0x01, 0, 115200, MB_PAR_NONE);
    /* Enable the Modbus Protocol Stack. */
    eStatus = eMBEnable();
      eStatus = eStatus;
    while(1)
    {                        
                xStatus = xQueueReceive(xQueue_FreeMODBUS, &Buffer, xTicksToWait);//portMAX_DELAY);
                if( xStatus == pdPASS )
                {                        
                        switch (Buffer)
                        {
                              case 1:
                                        MODBUS_DI1_1 = Buffer;
                                        MODBUS_DI1_2 = Buffer;
                                        break;
                              case 2:
                                        MODBUS_DI2_1 = Buffer;
                                        MODBUS_DI2_2 = Buffer;                                                      
                                        break;
                              case 3:
                                        MODBUS_Clock.year = Buffer;
                                        MODBUS_Clock.month = Buffer;
                                        MODBUS_Clock.day = Buffer;
                                        MODBUS_Clock.hour = Buffer;
                                        MODBUS_Clock.minute = Buffer;
                                        MODBUS_Clock.second = Buffer;
                                        MODBUS_Clock.week = Buffer;
                                        break;
                        }//switch (Buffer)
                }//if( xStatus == pdPASS )
               
                ( void )eMBPoll();
                        
                /* Here we simply count the number of poll cycles. */
                //usRegInputBuf++;
                //vTaskDelay(50);
    }
}





////////////////////////////////////////////////////////////////////////////////////////////////
//上位机任务
////////////////////////////////////////////////////////////////////////////////////////////////
void EC20_Task(void *pvParameters)
{
      USART2_Configuration();
      USART2_DMA_Tx_Configuration();
      USART2_DMA_Rx_Configuration();      
      
      
    while(1)
    {               
                EC20_4G_Poll();
    }
}





int main(void)
{
      __set_PRIMASK(1);         
      GPIO_Configuration();
      NVIC_Configuration();
      
      // 创建消息队列
      //第1个参数是消息队列支持的消息个数
      //第2个参数是每个消息的大小,单位字节。
      //返回值, 如果创建成功会返回消息队列的句柄,否则返回NULL
      //使用这个函数要注意以下问题:
      //1. FreeRTOS 的消息传递是数据的复制,而不是传递的数据地址,这点要特别注意。
      xQueue_FreeMODBUS = xQueueCreate(20 , 10);                //可以装载20个消息,每个消息10个字节
      xQueue_EC20 = xQueueCreate(20 , 10);                //可以装载20个消息,每个消息10个字节
      xSemaphore = xSemaphoreCreateBinary();
      if( xQueue_FreeMODBUS != NULL && xQueue_EC20 != NULL && xSemaphore != NULL)
      {
                xTaskCreate(LED1_Task, "GPIO_LED1", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 3 , &xHandleTaskLED1);
                vTaskStartScheduler();
      }
    while (1);      
}





GorgonMeducer 发表于 2020-6-25 02:04:55

本帖最后由 GorgonMeducer 于 2020-6-25 02:19 编辑

ba_wang_mao 发表于 2020-6-24 09:12
谢谢!

         分析图如下:

“如果任务A给任务C发送消息队列,任务B给任务D发送消息队列,则应该将任务C和任务D合并成1个任务。”
这句话看起来很奇怪,A和C是一组关系,B和D是一组关系,看不出这两组关系有什么关联,为什么说C和D要合并呢?你是不是理解错了什么?至少单纯从你描述的这句话来说,我觉得这是毫无道理的。
另外上次我问的关键问题,你并没有回答:什么“直接”原因导致你说的显示不正确,你按照我介绍的思路尝试隔离了么?
为了防止你误解,我这里要先说明一下:
1、贴完整代码出来,很多时候并没有帮助,因为没有硬件环境,也没有硬件环境的说明,根本没法调试
2、我只能帮你提供思路,也许我误会了你,但千万不要认为“我提供了源代码”你一定能帮我解决问题——有这种想法是不切实际的。因为我其实没有义务帮你调试源代码。何况如果我真要解决你的问题,我多半会从整理代码开始,最后多半就是重写了整个代码。
3、解决问题还是要靠你自己。所以我提供了思路,为了防止你误解,这里我重新强调下思路:

首先,要通过各种方法,隔离,分析,追踪等等,找到导致“显示不正常”的“最直接原因”。
其次,分析这个最直接原因是由于什么导致的,如果是任务设计不合理,这时候就要画两种图表:1)数据流图(你画的那个我虽然大体能看懂,但很遗憾那不是数据流图,只能算示意图,信息量也不够)2)任务触发图。有了这两个图,才能分析任务与任务之间的矛盾和关系。本质上就是要分析 数据流图中,每一对生产者和消费者的关系是否合理,数据的吞吐是否满足要求等等。

最后,说一点直接从你代码里看到的现象:你发送消息的时候从来没有判断是否成功(if的两个分支啥都没做),我建议你可以先排除,是否存在消息发送失败的情况(用assert)。一般来说,你遇到的问题有可能是多任务之间共享资源(一段存储空间)的时候出现竞争导致的——至少这个需要排除。解决方案一般是检查多任务之间共享的资源是否进行了必要的临界区保护。至少在你的代码里,我看不到任何任务之间的双向握手,感觉都是单方面做完一件事情也不管别人是否还在处理前一件事情,总之先丢出去再说——这种情况,在数据流的角度来说,就是缺乏流控制,很容易引发问题。


最后,我想说,你的代码做了一件我惊掉下巴的事情……你直接把一个局部变量的地址作为消息的一部分传给另外一个任务……也许在你的应用里看似没问题……但你要知道,这种做法就好比“刻舟求剑”——任务之间可以用来传递的地址,一定是生命周期可控的——比如静态变量,或者是动态分配来的变量,这种直接传局部变量地址的做法,实在是需要摒弃的。
页: [1]
查看完整版本: 【交流】实时性迷思(1)