amoBBS 阿莫电子论坛

 找回密码
 注册
搜索
bottom↓
12
返回列表 发新帖
楼主: Gorgon_Meducer

[交流][微知识]一种简单易用的状态机

  [复制链接]
发表于 2012-12-11 13:21:34 | 显示全部楼层
喜欢傻孩子的技术贴,
之前看过源码,没看懂,这次有傻孩子专门讲解,太棒了。
程序框架的写法在mcu上一直没有一个完美的方案,目前采用切换任务的方法也不错,但是如果遇到新的mcu架构,taskswitch比较难移植,希望楼主方法有效
发表于 2012-12-11 13:23:21 | 显示全部楼层
来标记下 先 有空来讨论下
发表于 2012-12-11 13:47:37 | 显示全部楼层
本帖最后由 dr2001 于 2012-12-11 13:51 编辑
Gorgon_Meducer 发表于 2012-12-11 12:01
恩,同意你的分析。
我之前强调的是一种能力:用指针法有能力进一步实现真阻塞(代码密度降低的问题,我 ...


98楼中所提到的“指针法”显然有混淆概念的嫌疑:把基于函数指针的实现状态机的方法,和基于函数指针的实现调度器的方法,混而一谈。

从状态机的表达方法上,前面已经达成了共识,基于函数指针的实现方法和基于switch的实现方法,二者表达是等效的(至少目前看,似乎是这样,但应该会有特例)。

阻塞,就是先把执行权交还调度器(假定非抢占调度),等待特定事件发生后继续。而是否重复调用处于阻塞状态的状态机任务(非抢占调度),不在于状态机的表达方式,而在于调度器自身,阻塞的实现方式和要求,等等。
如果调度器要求轮询任务,确认任务状态,那就不能“真阻塞”;如果阻塞的实现方式要求轮询某个标志位(假定无法用事件或者其它方法完成),比如FPGA的某个内部标志,IIC外设的某个标志,也不能“真阻塞”。
而这些,和状态机的表达方法无关。

从实现调度器的方法上,显然基于函数指针调用任务函数的实现方法能够获得更多的好处,从功能上,效率上,代码的通用性上,等等。
硬编码的大循环逐个调用函数的方法显然能够实现相似的功能,只不过非常烂罢了。这两个实现方法本身没太多可讨论的,好坏区别明显。

所以,98楼的结论对于调度器的实现的话,基本同意。

btw:
从实现整个状态转换图的角度看,状态机基于switch的实现方法的粒度比基于函数指针实现方法的粒度大。因此,switch方法对于状态变量/次态变量自身可以进行适当的编码,从而合并一些重复代码。不过这个对于目前Flash大而RAM不足的通常情况来说,显得吸引力不足。
switch方法由于代码的顺序执行特征,对于复杂关系的状态机产生的描述易读性差;独立描述状态的指针法自然有优势。函数指针能够和调度器的实现方案自然结合,或许在目前我尚未学习到的情况下带来具有*独占性*的好处。

对98楼提到的,“在一些需要真阻塞的环境下,支持真阻塞的函数指针法状态机无法转换成纯用switch的状态机(并仍保持真阻塞性)“的例子非常感兴趣。

供讨论。
 楼主| 发表于 2012-12-11 20:16:16 | 显示全部楼层
dr2001 发表于 2012-12-11 13:47
98楼中所提到的“指针法”显然有混淆概念的嫌疑:把基于函数指针的实现状态机的方法,和基于函数指针的实 ...

本质上,函数指针实现的状态机就是一个调度器——这是我一直以来的认知,所以如果说混淆,那么应该是有意为之。
关于例子,其实是并不复杂。考虑一个子状态机调用的情况:
1、假设switch结构实现了一个多级的子状态机调用,在某一个子状态机中,状态机等待一个临界标志的释放,这里的
     等待是通过轮询的状态来实现的。
2、这里做讨论的前提是,纯用switch实现状态机,也就是说在主循环里面所有的任务都是实现排列在那里的,假设使
     用的是全状态机开发模式,也就是说每一个任务都是非阻塞的,都是状态机:


  1. typedef enum {
  2.     fsm_rt_on_going = 0,
  3.     fsm_rt_cpl = 1,
  4.     fsm_rt_wait_for_obj = -1
  5. }fsm_rt_t;

  6. int main(void)
  7. {
  8.     ...

  9.     while(1) {
  10.         fsm_rt_t tReturn = fsm_rt_on_going;
  11.         tReturn &= task_a();
  12.         tReturn &= task_b();
  13.         tReturn &= task_c();
  14.         ...
  15.         if (fsm_rt_on_going != tReturn) {
  16.             idle_task();
  17.         }
  18.     }
  19.     return 0;
  20. }
复制代码
上面的代码是一个常见的使用switch模式的状态机多任务结构,也近似支持阻塞。

3、假设task_a和task_b都是较大深度的状态机,调用了若干子状态机——比如比较大的协议栈,考虑一个GUI任务,
    一个usb协议栈之类的。
4、假设task_a()中某一个子状态机等待一个关键资源,被阻塞的,则,目前只能通过查询状态的方式来等待,当然
    状态机可以通过返回fsm_rt_wait_for_obj来报告阻塞。
5、假设所有的任务都阻塞了,也就是返回fsm_rt_wait_for_obj,于是idle_task被执行了,系统进入休眠状态了。
6、现在一个资源释放了,比如一个时间标志,该标志是属于task_c()的,于是系统从idle_task中醒来,为了确认
    具体是哪个任务的标志被释放了,在运行task_c()之前,尽管task_a()和task_b()都仍然处于阻塞状态,但每一次
    系统都要一层一层的进入子状态机,其间要经历多次压栈、出栈、switch的查表或者比较操作,最后发现仍然阻塞。
7、可见为了运行激活状态的task_c(),task_a()和task_b()虽然处于阻塞状态,但仍然消耗了可观的CPU时间。如果
    系统的应用逻辑就是task_a()和task_b()是task_c()的主线程,比如task_c()是一个GUI的显示设备刷新任务,负责
    将显示缓冲刷新到外部设备上,task_a()是应用线程,task_b()是GUI的逻辑线程,实现了GUI的逻辑。那么一个
    可能的工作顺序是,task_a()实现用户应用的时候会调用task_b()提供的某些绘图操作,task_a()下达刷新请求,
    task_b()异步的完成GUI的各个逻辑土层的绘制和叠加,然后task_b启动task_c()的缓冲区刷新任务,并阻塞,等待
    task_c()任务完成。我们假设还有其他的任务,比如task_d之类的,实现串口通讯之类的,这里不作讨论。在这种
    情况下,task_c()作为刷新进程,既不能独占系统(可能会影响task_a)也不能效率太低。简单说就是,task_c()
    很希望自己的工作时间无限接近超级循环循环一次的时间,或者说task_c()希望自己的工作时间接近系统任务的
    大周期。那么除了task_c()和其它激活的任务,比如task_d(),task_b()和task_a()这样可能处于阻塞状态的任务
    就浪费了CPU时间。
以上的情况就是一个典型的,需要真阻塞的情况:假如task_a()和task_b()这样阻塞的任务,一旦阻塞了就真的不会
消耗CPU时间了,那么剩下处于活跃状态的task_c()和task_d()就能最大限度的发挥CPU的能力,或者说CPU的运算
能力都用在刀刃上了。

我没有说逻辑上用函数指针方法写的真阻塞的状态机无法转换成等效的switch模式,逻辑上,绝对可以转换,但如果
我们强调真阻塞带来的效率上的意义,那么显然switch是无法做到的。

考虑上面的例子,在使用函数指针法的时候,阻塞的任务将不在出现于就绪队列中,也不会被round-robin,那么系统
的效率就能最大的发挥。当阻塞的资源得到释放的时候,被阻塞的任务会重新加入到就绪队列中,从而有效地占用处理
器资源。

这就是我说的例子。
发表于 2012-12-11 20:41:08 来自手机 | 显示全部楼层
记个号,回去仔细看看
发表于 2012-12-11 22:04:44 | 显示全部楼层
Gorgon_Meducer 发表于 2012-12-11 20:16
本质上,函数指针实现的状态机就是一个调度器——这是我一直以来的认知,所以如果说混淆,那么应该是有意 ...


  1. static void idle_task(void)
  2. {
  3.     sleep();
  4. }

  5. int main(void)
  6. {
  7.     ...
  8.     while(1) {
  9.         if (!scheduler()) {
  10.             //! return false means system is idle, run idle to enter sleep mode.
  11.             idle_task();
  12.         }
  13.     }

  14.     return 0;
  15. }

  16. //! the most amazing part is here
  17. /*! \note this is a simple demonstration for the fact that the scheduler can works well in
  18. *! real multi-task system from a simple front/back-end system to multi-task OS environment.
  19. *! Is it useful? *^_^* Try to think the multi-core and multi-thread programmng model.
  20. */
  21. ISR(TIM0_COMPA_vect)
  22. {
  23.     //! 1ms compare match interrupt service routine

  24.     //! we can call scheduler both at super loop and ISR simultaneously. this is useful!
  25.     scheduler();
  26. }
  27. ...
  28. ISR(ADC_vect)
  29. {

  30.     ...
  31.     scheduler();
  32. }
  33. ...
复制代码
按照你调度器一贴的这种调度法来说,对你的“真阻塞”有点疑问。
你的“真阻塞”应该是指的在一个任务等待一个资源时,将它从就绪队列中移出去,这样调度器完全不用去调度它节约了CPU时间,实现方式应该时状态机返回NULL函数指针。
问题就来了,从你调度器现在给出的代码来看,如何唤醒这个任务?中断里仅仅是对调度函数的调用,而阻塞的任务现在已经不在就绪队列里了,如何去唤醒?
另一方面,中断里去调用这个调度器,ADC中断完全有可能调度到一个UART任务,这是刻意的?

 楼主| 发表于 2012-12-11 22:48:02 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2012-12-11 22:50 编辑
ifree64 发表于 2012-12-11 22:04
按照你调度器一贴的这种调度法来说,对你的“真阻塞”有点疑问。
你的“真阻塞”应该是指的在一个任务等 ...


我这个帖子一直说的是有能力,这个帖子楼主位的代码不具备这个功能,但是有能力修改成这种形式。
你可以看我那个楼主位帖子里面提到的更复杂的状态机调度器。或者也可以根据这个帖子里面提到的
原理和部分代码,通过另外一个微知识中介绍的调度器来改进。


其实关键性的代码在92楼给出了,90楼给了一个应用例子。你可以直接看这两个楼层的内容。其实这个
真阻塞的功能我是第一次正式的提出来——虽然在那个复杂的状态机调度器里面早就提供了这个功能,
但实际上并没有人认真去看过代码。
发表于 2012-12-12 08:35:26 | 显示全部楼层
本帖最后由 dr2001 于 2012-12-12 09:06 编辑
Gorgon_Meducer 发表于 2012-12-11 20:16
本质上,函数指针实现的状态机就是一个调度器——这是我一直以来的认知,所以如果说混淆,那么应该是有意 ...


我认为,针对:“状态机使用函数指针方式,基于每个状态进行表述;还是,使用switch/case,基于某一组状态转换的关系集合进行表述。” 讨论它们的等价性、适用范围以及各种情形下的优劣是有价值的。
对于调度器的讨论,完全可以另起话题。

我以为,之前我所说的状态机转换的实质,在于:
C语言下,只要调用函数/跳转就需要引用函数的入口地址/目标地址,这是必然的。问题无外乎,我是直接使用目标地址?还是使用索引来引用目标地址?
函数指针法,显然是直接引用的地址;swtich case只是对其提供了间址的包装,只不过,这个包装是静态的。

因此,只要是静态的间址能够覆盖的能力,函数指针法能有的,switch case的方法也能有。静态间址的优劣似乎也没啥可讨论的。

因此,请考察以下代码:

  1. Queue schdActiveTask;
  2. void Scheduler(void) {
  3.   int schdNextState = FetchQueue(schdActiveTask);
  4.   int taskNextState;
  5.   if(schdNextState == Nothing) {
  6.     schdNextState = 0;
  7.   }
  8.   switch(schdNextState) {
  9.   case 0:
  10.     CallTask_Idle_PowerDown();
  11.     break;
  12.   case 1:
  13.     taskNextState = CallTask_A(schdNextState, Argument, ...);
  14.     break;
  15.   case 2:
  16.     taskNextState = CallTask_B(schdNextState, Argument, ...);
  17.     break;
  18.   default:
  19.     ErrorHandler();
  20.     break;
  21.   }
  22.   //  Processing of taskNextState, Adj. Queue if required.
  23. }
复制代码
你的调度器怎么调度,剩下的就怎么干。只是把函数指针变成了ID而已。但是,确实是switch case的,硬编码的。
显然,这里不能说,又不可以使用queue了,要基于纯switch实现。这就和指针法不能用Queue是一个事儿了。

优劣简析:
1、平:完成的是指针到int的映射,因此,指针法调度的优劣基本上是平行迁移过来的。优劣出现的原因在于映射是静态的,而不是动态的。
2、优:省RAM。状态少的话,队列可以全用uint8_t的,比void *的函数指针队列省,而且队列是确定长度的(因为和硬编码相关)。
3、平或劣(依赖于实现):递增序列的case编码,编译器实现为跳转表,和指针调度一样是O(1)的复杂度,只是多几条指令,这是平。如果没有跳转表优化,那效率低,是劣。
4、优:支持任务完全差异化的入口。
5、劣:静态编码,任务总数固定;只能“屏蔽任务”,不能删除。

case的自动递增序列编码是可以实现的。参考boost的preprocessor库就行了。只是到处include不好看而已。
另外,case的静态跳转表实现成数组维护起来更方便,也不失灵活性。
发表于 2012-12-12 09:28:45 | 显示全部楼层
huyugv_830913 发表于 2012-11-16 17:47
我是做linux驱动开发的,个人感觉宏用的太复杂了,覆盖了很多东西,建议:
1. 可以考虑把状态机函数写成标 ...

标记一下,有空看看
 楼主| 发表于 2012-12-12 10:40:37 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2012-12-12 11:00 编辑
dr2001 发表于 2012-12-12 08:35
我认为,针对:“状态机使用函数指针方式,基于每个状态进行表述;还是,使用switch/case,基于某一组状 ...


非常非常精彩!可谓醍醐灌顶啊!短短的几段话和一段调度器片断把问题分析的如此透彻,让我自愧不如。
这几年在论坛上很少遇到这样有启发性的讨论了!

现在看来,我之前应该是有一个明显的思维定势,switch因为是静态的,指针法是动态的,虽然知道二者
在函数地址上一个间接一个直接的区别,但因为思维定势的存在,并没有沿着switch实现调度的方向做更多
的垦拓。今天看来,这一部分的思路一下被你打开了。我会认真整理着部分资料,它将作为新书调度器部分
前半部一个很重要的模型演化讨论的蓝本。

应该说,状态机只是一个逻辑,实现逻辑的方式很多,switch是一种,if-else是一种,函数指针也是一种,
既然基于相同的逻辑,理论上都能相互等效转化——当然,如果像我最初那样因为偏爱指针法就限定switch
不可以借助额外的手段(比如你用的队列),而让指针法可以自由发挥,这显然是不公平的——从这个意义
上说,作为思想的实现,只要有足够的研究和方法,不同的写法之间最终都能找到等效转换的方式,只是有
效率上的不同而已。

关于你的优劣评价,我大体都是同意的,只是第四点,switch法和指针法都可以做到,但这种多入口的方式
是有嫌疑破坏结构化编程单入单出的要求,在应用的时候还是需要尽可能避免的;第五点,我认为问题要换
一个视角,所谓的屏蔽任务,并不是一个“真缺点”,函数指针法的删除任务也不是一个“真优点”:因为任务
代码在编译时刻其实都是确定下来的,函数指针法调度器的所谓动态增加任务和删除任务并不会创造任务,
而本质上只是实现了一些任务的多上下文副本重入而已,简单说也是一种屏蔽和去除屏蔽的关系。

最后我想说说switch法一个天然的优势:
>>轻量级。是的,显而易见,switch法更轻量级一些,它写出来的状态机天然支持子状态机调用,而函数
    指针法虽然结构清晰,在非子状态机调用的情况下也许代码大小与switch法接近,但为了实现子状态机
    调用,就需要额外的编码去人工实现出入栈操作。
    这一优势让其更适合小FLASH的环境,比如4K以下或者2K以下MCU的环境。当然,如果要实现更多的高
    级功能,switch法也是需要额外工具辅助的,比如队列等等。

从本质上来说,switch法以及if-else法都是间接的地址调用,当系统规模较大时,“间接”所引入的额外开销
就开始积累,从而增加系统开销,当系统开销增大时,switch法轻量级的优势早就不被重视(系统规模增大
则相应FLASH大小反而不是关键,一部分的系统及开销,比如稍微复杂些的调度器也就是允许的),这种
情况下,函数指针法开始发挥它灵活的集团军作战优势——非常适合团队模块化开发——因为本质上它可以
是操作系统的一个非抢占版本,而非阻塞代码解决了多任务的问题,从而指针法派生出来的调度器开发模式
能从操作系统的概念和优势中借鉴到很多东西——当然,纯粹的指针法是做不到的,还是需要调度器的。


一些补充

关于到处include的问题,这并不是一个问题,有统一的方法解决,不会真的让你到处include。当逐渐引入数
组来维护switch方法的时候,实际上在某种程度上属于小数点级别的演进,逻辑上逐步向函数指针法靠近。
其实有时候明确区分方法是在是教条主义,我一直主张打开思路而模糊技巧——可事实上,只谈思路,不明确
而分门别类的介绍一些基本技巧,沟通是很难的。这一方面我已经逐步意识到这个问题,并会在新书的编写
中尝试一种解决方案:先从基本点出发逐步推演出一个复杂的模型,然后从需求考虑,分级别的将复杂模型
逐步简化成满足一些典型应用场景的简化模型。这是一个从简陋到复杂,从复杂到简单的过程。希望能提供
一些宝贵的意见。

从处理器构架的角度来说switch法和函数指针法的区别在于,前者有同时需要指令总线和数据总线的帮助来
完成状态的切换;而者后(函数指针法)通常情况下是仅通过数据总线来获取目标函数的地址地,从这个视
角来看,还是有很多文章可以做的,特别是考虑加密特性的时候。
发表于 2012-12-17 14:31:26 来自手机 | 显示全部楼层
好深奥的样子,学习一下
发表于 2012-12-17 22:43:00 | 显示全部楼层
Mark FSM,Function pointer & Switch Case statement
发表于 2012-12-24 21:56:34 | 显示全部楼层
崭新的思路,值得提倡
发表于 2012-12-26 17:15:56 | 显示全部楼层
漂亮。比我买的书介绍好多了
发表于 2013-1-9 11:45:50 | 显示全部楼层
先标记一下,慢慢研究。。。
发表于 2013-1-9 12:29:14 | 显示全部楼层
做个标记,午觉醒来再看
发表于 2013-1-10 00:25:43 | 显示全部楼层
ifree64 发表于 2012-12-10 21:35
看傻孩子写的指针法状态机思路很好,但也产生了好奇,到底指针法和switch大法相比较,资源占用如何呢?于是 ...

代码3没看明白rbyte是怎么传递到p_state中的,还是省略了某些程序?
发表于 2013-1-10 19:08:11 | 显示全部楼层
顶了再看!
发表于 2013-1-21 09:08:05 | 显示全部楼层
傻孩子的简单易用的状态机,好好学习
发表于 2013-1-21 09:25:05 | 显示全部楼层
进来学习下
发表于 2013-1-21 09:43:09 | 显示全部楼层
Mark                 
发表于 2013-2-28 18:36:33 | 显示全部楼层
Gorgon_Meducer 发表于 2012-12-12 10:40
非常非常精彩!可谓醍醐灌顶啊!短短的几段话和一段调度器片断把问题分析的如此透彻,让我自愧不如。
这 ...

选择题: 1、傻孩子指针法模板。2、protothread模板。 建议傻孩子的帖子能不能按照发帖时间排排好,我刚刚用上前面一个模板,后面有翻出个新模板。变化太大要重新熟悉一番。
发表于 2013-4-7 20:22:35 | 显示全部楼层
多谢楼主分享
发表于 2013-4-8 16:07:01 | 显示全部楼层
宏我一般只用来定义、计算一些常数……极少用来构建代码
发表于 2013-4-9 00:31:10 | 显示全部楼层
发表于 2013-4-12 14:42:47 | 显示全部楼层
好贴 MARK
发表于 2013-4-14 14:03:02 | 显示全部楼层
多谢楼主分享
发表于 2013-5-7 15:53:10 | 显示全部楼层
指针型状态机 mark
发表于 2013-5-10 21:26:16 | 显示全部楼层
学习一下。。。。
发表于 2013-10-12 22:28:08 | 显示全部楼层
复杂,这样做不知道能用好它容易否
发表于 2013-11-5 00:18:15 | 显示全部楼层
Mark 现在还是看不懂。。。。
发表于 2013-11-5 11:56:13 | 显示全部楼层
还是不错的,mark
发表于 2013-11-5 13:55:40 | 显示全部楼层
很好,感谢,
发表于 2013-11-6 19:56:56 | 显示全部楼层
好贴,谢谢
发表于 2013-12-11 09:23:11 | 显示全部楼层
真的挺好
发表于 2014-4-1 20:08:16 | 显示全部楼层
基础太差了,完全看不懂
发表于 2014-4-2 22:10:45 | 显示全部楼层
还不错,谢谢
发表于 2014-8-13 09:04:23 | 显示全部楼层
状态机用在单片机中我很赞成,想学你的程序结构,但程序中到处都是宏,阅读时还要不停地找到宏定义是什么,那么多宏有些喧宾夺主。
 楼主| 发表于 2014-8-13 20:12:04 | 显示全部楼层
黄晨0410 发表于 2014-8-13 09:04
状态机用在单片机中我很赞成,想学你的程序结构,但程序中到处都是宏,阅读时还要不停地找到宏定义是什么, ...

你要克制将宏展开的欲望。单纯的按照格式使用就好了。
发表于 2014-8-13 20:59:36 | 显示全部楼层
席地而坐,开始看
发表于 2014-8-13 21:27:31 | 显示全部楼层
mark,晚上看
发表于 2014-8-14 14:16:39 | 显示全部楼层
顶顶顶!!!!!!!!!!!!!!!!!!!!
发表于 2014-8-18 16:47:09 | 显示全部楼层
mark,状态机
发表于 2014-8-27 21:36:15 | 显示全部楼层
mark,学习!
发表于 2014-8-28 22:12:13 | 显示全部楼层
mark,好好看一下~
发表于 2014-9-12 14:40:07 | 显示全部楼层
这个要MARK
发表于 2014-9-19 11:46:10 | 显示全部楼层
收藏一下,此贴甚好,有思想碰撞,哈
发表于 2014-9-23 00:02:18 | 显示全部楼层
甚好,还没有写过具体代码。。。校赛的一个实时时钟折腾过3个晚上。。深有体会逻辑的重要性。
发表于 2014-9-23 10:16:55 | 显示全部楼层
第一个感觉,好复杂。看着像linux内核代码,表现形式有些过于庞大复杂了,让人望而生畏。
但是傻孩子的编程思想我喜欢,比起你的代码,更喜欢看你的文字,收益多多。
发表于 2014-10-16 19:22:49 | 显示全部楼层
以前就发现了,不过当时不能回复,现在可以回复帖子了,特意来支持一下
发表于 2014-11-5 09:22:44 | 显示全部楼层
希望没有来晚~
发表于 2014-11-24 20:58:21 | 显示全部楼层
暂时没看懂,做个记号周末有空静下心来看
发表于 2014-12-2 09:20:17 | 显示全部楼层
学习了.....
头像被屏蔽
发表于 2014-12-2 15:07:03 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
发表于 2014-12-20 17:43:35 | 显示全部楼层
我花了很大的力气学了C++。认识了C++的优美后,就认定它了。一条道走到黑。现在就算编个LED闪烁小程序,不写个class就不爽。
附上我用C++编写的程序。这个附件是个C++工程模板,在IAR710版本可以编译通过。也注释了部分代码,方便阅读。另外也可以用source insight编辑查阅。我不会弄相对路径,自行添加库文件吧。如果能弄好SI相对路径包含文件就更好了。
这个程序是我将要在一个产品中使用的部分。写LED、beep、串口的基本的想法是,在STM32的硬件平台上,做到通用。如获得一个LED(或者BEEP),只要提供端口名字和引脚编号:cLED(GPIOA,GPIO_PIN_0)。要使用串口,只要指定串口号和波特率:usart UsartA(USART1,19200).蜂鸣器一个简单的start(),输入3个参数,可以实现响几次,每次多长时间,每次停歇时间。如果用C写的话,好像做不出来。
我发程序目的是希望引起一个关于C++和OOC怎样相互结合使用的讨论。我现在都不会写C了。
但c++带来的苦恼也很多。可参考的C++单片机程序很少。就是孤身一人作战的感觉。傻孩子的模块封装一和二,高焕堂的《UML+OOPC》,Miro Samek《UML状态机程序设计》第二版,都是很好很难得的好教材,但都要舍弃C++,重新用C,万般不舍。
我就是傻孩子说的形式主义者。我知道你在笑我:只会形式,还不会取其精神(因为这些都是OO思想)。  的确,我的水平就停留在这个层面上了(思想僵化了)。就像刚进入武馆的新学徒,只会照样子比划,比划多了,精神慢慢才能领会。

要不要丢弃C++,用C的宏定义来做C++的事情(跟着你的招式比划比划)?

 楼主| 发表于 2014-12-20 20:27:43 | 显示全部楼层
guolun 发表于 2014-12-20 17:43
我花了很大的力气学了C++。认识了C++的优美后,就认定它了。一条道走到黑。现在就算编个LED闪烁小程序,不 ...

那你再看看模块封装三,应该更有帮助——因为就是形式主义的东西。
发表于 2014-12-21 17:48:15 | 显示全部楼层
Gorgon_Meducer 发表于 2014-12-20 20:27
那你再看看模块封装三,应该更有帮助——因为就是形式主义的东西。

上次我没上传成功附件。错过了跟傻大师交流的一次机会。现在上传。
关于模块封装的123,我都没弄清楚。但我知道,我追求的不是3那部分的东西。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
发表于 2014-12-21 19:48:35 | 显示全部楼层
搬凳子来学习,涨姿势
发表于 2014-12-21 19:50:57 | 显示全部楼层
y574924080 发表于 2013-11-5 00:18
Mark 现在还是看不懂。。。。

以前不懂,现在呢
 楼主| 发表于 2014-12-22 22:31:52 | 显示全部楼层
guolun 发表于 2014-12-21 17:48
上次我没上传成功附件。错过了跟傻大师交流的一次机会。现在上传。
关于模块封装的123,我都没弄清楚。但 ...

这是什么东西哈?这么大?如果是工程,一定要clean一下。
发表于 2014-12-23 10:44:38 | 显示全部楼层
本帖最后由 guolun 于 2014-12-23 10:46 编辑
Gorgon_Meducer 发表于 2014-12-22 22:31
这是什么东西哈?这么大?如果是工程,一定要clean一下。


这是一个C++工程模板。有几个例子。原来的test有错误,今天修改了,仿真了。没问题了。clean以后,文档体积还是很大。干脆把debug和setting目录删除了。现在苗条了。
希望大师指点。我还是很困惑:要不要丢弃C++,用C的宏定义来做C++的事情(跟着你的招式比划比划)?    用C++,可交流可参考的程序不多啊。我一大把年纪了,如果参加你的训练班,固有的旧思维会增加我的学习难度吧?我看了你的宏都很不习惯呢。C++提倡不用宏,用模板。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
 楼主| 发表于 2014-12-23 16:52:03 | 显示全部楼层
guolun 发表于 2014-12-23 10:44
这是一个C++工程模板。有几个例子。原来的test有错误,今天修改了,仿真了。没问题了。clean以后,文档体 ...

你的库我昨天看过了……我觉得即便是C++你也没用好……你完全没有发挥interface的核弹威力。
我们以某个模块为例子吧,我提议用usart。你把这部分驱动代码片段粘贴出来,我粘贴出来我的
一对比就知道了。
发表于 2014-12-23 22:10:22 | 显示全部楼层
本帖最后由 guolun 于 2014-12-23 22:14 编辑

跟你切磋,太好了。
/* 下面的接口是发送一个数据的,工作于非阻塞的方式。它只把待发送的数据扔到队列,
方法-work()会自动检查队列是否有数据待发送,自动检查发送缓冲是否可以用。发送成功就返回TRUE*/
bool usart::send_byte(u8 ch){   
// 扔一个数据到队列
    if(send_data_count<size){ // 队列未满,可以塞进去
        if(send_data_tail >= size) // 先处理队列越界问题
            send_data_tail = 0; // 头尾相接,成循环队列
        send_data[send_data_tail] = ch; // 数据塞进去
        send_data_tail++;    // 指向下一个位置
        send_data_count++;     // 计数器+1
        return true;    // 告诉主调程序,数据发送成功
    }
    else{   // 队列满了,数据没塞进去
        return false;  
    }
}

// 上面的接口配合work()使用,发送数据省时省力。
/* 这个方法放在大循环中,负责把队列的数据送到串口数据寄存器 */
void usart::work(){      
    if(send_data_count>0){   // 有数据待发送            
        if(USART_GetFlagStatus(pUsart,USART_FLAG_TXE) == 1){ // 可以发送
            if(send_data_head>=size)  // 处理越界问题  
                send_data_head = 0;      
            USART_SendData(pUsart,send_data[send_data_head]);
            send_data_count--;   
            send_data_head++;
            if(send_data_count == 0){  // 消除数据出错可能   
                send_data_head = 0;  
                send_data_tail = 0;      
            }
        }
    }
}
如何把C++接口的发挥更好?请斧正。
 楼主| 发表于 2014-12-23 23:00:32 | 显示全部楼层
guolun 发表于 2014-12-23 22:10
跟你切磋,太好了。
/* 下面的接口是发送一个数据的,工作于非阻塞的方式。它只把待发送的数据扔到队列,
...

你给这个内容没有多少意义。也看不出用C++和C有什么区别。我觉得你对OO其实不得要领——说的也许重了。
你应该展示你如何定义串口类的,如何设计接口的,如何考虑效率,易用性和扩展性的。从你的代码里面只看出
你是内置了一个FIFO,仅此而已。应该说对OO的讨论毫无意义。
发表于 2014-12-24 09:41:07 | 显示全部楼层
我真的对OO不得要领。所以没有理解要讨论的东西。

// 类的私有成员:
        USART_TypeDef * pUsart;   // 用于指向用户指定的串口
        GPIO_TypeDef * pGPIO;     // 指向串口的GPIO
        u16 TXPin;                        
        u16 RXPin;
        u32 baud;

// 构造函数实现:       
        pUsart = Usartx;
        if(pUsart == USART1){
                pGPIO = GPIOA;
                TXPin = GPIO_Pin_9;
                RXPin = GPIO_Pin_10;
        }
        .........
构造函数定义:usart(USART_TypeDef * Usartx,  u32 baudrate);
我设计这个usart类,考虑易用。实例化时用户只要指定串口号和波特率。
初始化对象时根据指向串口地址的指针,自动初始化相关串口的gpio和其它。
用户这样使用它:usart UsartA(USART1,115200);
感觉这样比C方便。

发送接口的设计,不能让它查询“free标志”而等待。把数据扔到队列就可以。所以需要一个FIFO。
但代价是大循环里面多了一个任务(或者叫线程?)--work。
正如你所说的,这些不是C++的内容。不详细说了。

以上如果没有说到点子上,请你直接点拨。
 楼主| 发表于 2014-12-24 10:23:47 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2014-12-24 10:25 编辑
guolun 发表于 2014-12-24 09:41
我真的对OO不得要领。所以没有理解要讨论的东西。

// 类的私有成员:


你可以理解为下面的代码是我定义的usart接口,并根据芯片具体usart的数量实现了USART_COUNT个静态实例。
这个接口是抽象出来的,和具体的芯片无关,上层可以依赖这个接口开发,而不需要太担心库移植的问题。
其实这是用OO概念写了,用C++实现起来其实更方便。使用方法上也是很方便的,比如给你看个例子:

  1.         IO_CFG(
  2.             //! configure PA20 as USART0 txd
  3.             {PA20,  IO_WORKS_AS_USART1_TXD},
  4.             //! configure PA21 as usart0 RXD with bulid-in pull-up resistor enabled
  5.             {PA21,  IO_WORKS_AS_USART1_RXD, IO_PULL_UP},
  6.         );

  7.         //! configure USART1
  8.         USART_CFG(
  9.             USART1,

  10.             //! baudrate configuration
  11.             //USART_NO_AUTO_BAUD      |   
  12.             USART_AUTO_BAUD_MODE0   |       //!< enable auto baud rate mode
  13.             USART_AUTO_RESTART      |       //!< auto restart auto baud detect if failed
  14.             
  15.             //! parity configuration
  16.             USART_NO_PARITY         |       //!< no parity (default)
  17.             //USART_EVEN_PARITY       |
  18.             //USART_ODD_PARITY        |
  19.    
  20.             USART_1_STOPBIT         |       //!< 1 stop bit (default)
  21.             //USART_2_STOPBIT         |   

  22.             USART_ENABLE_FIFO       |       //!< enable build-in FIFO (default)
  23.             //USART_DISABLE_FIFO      |     

  24.             USART_8_BIT_LENGTH      |       //!< 8 bit length
  25.             //USART_7_BIT_LENGTH      |
  26.             //USART_6_BIT_LENGTH      |
  27.             //USART_5_BIT_LENGTH      |

  28.             USART_ASYNC_MODE        |       //!< UART
  29.             //USART_SYNC_MODE         |
  30.             0,
  31.             //9600                          //!< baudrate (no use if autobaud mode is enabled)
  32.         );

  33.         //! Enable USART1
  34.         USART1.Enable();

  35. ...
  36.         while(1) {
  37.             //! software loop-back demo
  38.             if (USART1.ReadByte(&chByte)) {
  39.                 while(!USART1.WriteByte(chByte));
  40.             }
  41.             ...
  42.         }

复制代码

  1. #ifndef __USART1_H__
  2. #define __USART1_H__

  3. /*============================ INCLUDES ======================================*/
  4. #include ".\app_cfg.h"
  5. #include ".\i_io_usart.h"
  6. #include "..\device.h"

  7. /*============================ MACROS ========================================*/                       
  8. #define USART_CFG(__USART, ...)                                             \
  9.         do {                                                                \
  10.             usart_cfg_t tCFG = {__VA_ARGS__};                               \
  11.             __USART.Init(&tCFG);                                            \
  12.         } while(0)

  13. /*============================ MACROFIED FUNCTIONS ===========================*/
  14. /*============================ TYPES =========================================*/
  15. //! \name usart working mode
  16. //! @{
  17. typedef enum {
  18.     USART_NO_AUTO_BAUD      = 0x00,
  19.     USART_AUTO_BAUD_MODE0   = 0x01,
  20.     USART_AUTO_BAUD_MODE1   = 0x03,
  21.     USART_AUTO_RESTART      = 0x04,
  22.     USART_NO_AUTO_RESTART   = 0x00,

  23.     USART_NO_PARITY         = 0x00,
  24.     USART_EVEN_PARITY       = 0x18,
  25.     USART_ODD_PARITY        = 0x08,
  26.     USART_FORCE_1_PARITY    = 0x28,
  27.     USART_FORCE_0_PARITY    = 0x38,

  28.     USART_1_STOPBIT         = 0x00,
  29.     USART_2_STOPBIT         = 0x40,

  30.     USART_ENABLE_FIFO       = 0x00,
  31.     USART_DISABLE_FIFO      = 0x80,

  32.     USART_5_BIT_LENGTH      = 0x0000,
  33.     USART_6_BIT_LENGTH      = 0x0100,
  34.     USART_7_BIT_LENGTH      = 0x0200,
  35.     USART_8_BIT_LENGTH      = 0x0300,
  36.       
  37.     USART_SYNC_MODE         = 0x0400,
  38.     USART_ASYNC_MODE        = 0x0000
  39. }em_usart_mode_t;
  40. //! @}

  41. //! \name usart configuration
  42. //! @{
  43. typedef struct {
  44.     uint16_t            hwMode;
  45.     uint32_t            wBaudrate;
  46. } usart_cfg_t;
  47. //! @}

  48. //! \name usart_irda_mode_t
  49. //! @{
  50. typedef enum {
  51.     //! \name IRDA EN
  52.     //! @{
  53.     USART_IRDA_DISABLE           = 0x00,         //!< disable irda
  54.     USART_IRDA_ENABLE            = 0x01,         //!< enable irda
  55.     //! @}
  56.    
  57.     //! \name Inverted set
  58.     //! @{
  59.     USART_IRDA_NOT_INVERTED      = 0x00,         //!< not inverted the input
  60.     USART_IRDA_INVERTED          = 0x02,         //!< inverted the input
  61.     //! @}
  62.    
  63.     //! \name Fix pulse en
  64.     //! @{
  65.     USART_IRDA_FIXPULSE_DISABLE  = 0x00,         //!< disable fixpulse
  66.     USART_IRDA_FIXPULSE_ENABLE   = 0x04,         //!< enable fixpulse
  67.     //! @}
  68.    
  69.     //! \name Pluse Div Set
  70.     //! @{
  71.     USART_IRDA_PULSEDIV_2        = 0x00,         //!< 2* T_pclk(fixpulse enable)
  72.     USART_IRDA_PULSEDIV_4        = 0x08,         //!< 4* T_pclk(fixpulse enable)
  73.     USART_IRDA_PULSEDIV_8        = 0x10,         //!< 8* T_pclk(fixpulse enable)
  74.     USART_IRDA_PULSEDIV_16       = 0x18,         //!< 16* T_pclk(fixpulse enable)
  75.     USART_IRDA_PULSEDIV_32       = 0x20,         //!< 32* T_pclk(fixpulse enable)
  76.     USART_IRDA_PULSEDIV_64       = 0x28,         //!< 64* T_pclk(fixpulse enable)
  77.     USART_IRDA_PULSEDIV_128      = 0x30,         //!< 128* T_pclk(fixpulse enable)
  78.     USART_IRDA_PULSEDIV_256      = 0x38          //!< 256* T_pclk(fixpulse enable)
  79.     //! @}
  80. } em_usart_irda_mode_t;
  81. //! @}

  82. //! \name usart_IrDA_cfg_t
  83. //! @{
  84. typedef struct {
  85.     uint16_t            hwIrDAMode;            //!< IrDA config word
  86. } usart_IrDA_cfg_t;
  87. //! @}
  88.       
  89. //! \name smart card working mode
  90. //! @{
  91. typedef enum {
  92.     SMART_CARD_DISABLE              =0x00,
  93.     SMART_CARD_ENABLE               =0x01,
  94.     SMART_CARD_NACK_DISABLE         =(1<<1),
  95.     SMART_CARD_NACK_ENABLE          =0x00,
  96.     SMART_CARD_PROT_T0              =0x00,
  97.     SMART_CARD_PROT_T1              =(1<<2),
  98.     SMART_CARD_TXRETRY_DISABLE      =0x00,
  99.     SMART_CARD_TXRETRY_1            =(1<<5),
  100.     SMART_CARD_TXRETRY_2            =(2<<5),
  101.     SMART_CARD_TXRETRY_3            =(3<<5),
  102.     SMART_CARD_TXRETRY_4            =(4<<5),
  103.     SMART_CARD_TXRETRY_5            =(5<<5),
  104.     SMART_CARD_TXRETRY_6            =(6<<5),
  105.     SMART_CARD_TXRETRY_7            =(7<<5),
  106. }em_smartcard_mode_t;
  107. //! @}

  108. //! \name smart card config
  109. //! @{
  110. typedef struct {
  111.     uint8_t chMode;
  112.     uint8_t chGuardTime;
  113. }usart_smartcard_cfg_t;
  114. //! @}

  115. //! \name class: usart_irda_t
  116. //! @{
  117. DEF_INTERFACE(usart_smartcard_t)
  118.     bool        (*Init)(usart_smartcard_cfg_t *ptCFG);
  119. END_DEF_INTERFACE(usart_smartcard_t)
  120. //! @}

  121. //! \name usart 485 working mode
  122. //! @{
  123. typedef enum {
  124.     USART_485_DISABLE               = 0x00,
  125.     USART_485_MULTI_STATION         = 0x01,
  126.     USART_485_DISABLE_RX            = 0x02,
  127.     USART_485_AUTO_ADDRESS_MATCH    = 0x04,
  128.     USART_485_USE_DTR_AS_DIR_CTRL   = 0x08,   
  129.     USART_485_ENABLE_AUTO_DIR_CTRL  = 0x10,
  130.     USART_485_DIR_PIN_IDLE_LOW      = 0x20
  131. }em_usart_485_mode_t;
  132. //! @}

  133. //! \name usart 485 configuration
  134. //! @{
  135. typedef struct {
  136.     uint8_t chMode;
  137.     uint8_t chAddress;
  138.     uint8_t chDelay;
  139. } usart_485_cfg_t;
  140. //! @}

  141. //! \name usart sync working mode
  142. //! @{
  143. typedef enum {
  144.     //! \name SYNC SCLK
  145.     //! @{
  146.     USART_SYNC_SCLK_IN                  = 0x00,         //!< Synchronous slave mode (SCLK in)
  147.     USART_SYNC_SCLK_OUT                 = 0x02,         //!< Synchronous master mode (SCLK out)
  148.     //! @}

  149.     //! \name SYNC SAMP
  150.     //! @{
  151.     USART_SYNC_FALLING_EDGE_SAMP        = 0x04,         //!< RxD is sampled on the falling edge of SCLK
  152.     USART_SYNC_RISING_EDGE_SAMP         = 0x00,         //!< RxD is sampled on the rising edge of SCLK
  153.     //! @}

  154.     //! \name SYNC TSBYPASS
  155.     //! @{
  156.     USART_SYNC_TSBYPASS_DISABLE         = 0x00,         //!< Transmit synchronization bypass in slave mode disable
  157.     USART_SYNC_TSBYPASS_ENABLE          = 0x08,         //!< Transmit synchronization bypass in slave mode enable
  158.     //! @}

  159.     //! \name SYNC CSCEN
  160.     //! @{
  161.     USART_SYNC_CSCEN_DISABLE            = 0x00,         //!< Continuous master clock disable
  162.     USART_SYNC_CSCEN_ENABLE             = 0x10,         //!< Continuous master clock enable
  163.     //! @}

  164.     //! \name SYNC SSSDIS
  165.     //! @{
  166.     USART_SYNC_SEND_START_STOP_DISABLE  = 0x20,         //!< Do not send start/stop bits
  167.     USART_SYNC_SEND_START_STOP_ENABLE   = 0x00,         //!< Send start and stop bits as in other modes.
  168.     //! @}

  169.     //! \name SYNC CCCLR
  170.     //! @{
  171.     USART_SYNC_CCCLR_DISABLE        = 0x00,         //!< CSCEN is under software control
  172.     USART_SYNC_CCCLR_ENABLE         = 0x40,         //!< Hardware clears CSCEN after each character is received
  173.     //! @}
  174. }em_usart_sync_mode_t;
  175. //! @}

  176. //! \name usart sync configuration
  177. //! @{
  178. typedef struct {
  179.     uint8_t chMode;
  180. } usart_sync_cfg_t;
  181. //! @}

  182. //! \name class: usart_irda_t
  183. //! @{
  184. DEF_INTERFACE(usart_irda_t)
  185.     bool        (*Init)(usart_IrDA_cfg_t *ptCFG);
  186. END_DEF_INTERFACE(usart_irda_t)
  187. //! @}

  188. //! \name class: usart_485_t
  189. //! @{
  190. DEF_INTERFACE(usart_485_t)
  191.     bool        (*Init)(usart_485_cfg_t *ptCFG);
  192.     fsm_rt_t    (*SendAddress)(uint8_t chAddress);
  193. END_DEF_INTERFACE(usart_485_t)
  194. //! @}

  195. //! \name class: usart_sync_t
  196. //! @{
  197. DEF_INTERFACE(usart_sync_t)
  198.     bool        (*Init)(usart_sync_cfg_t *ptCFG);
  199. END_DEF_INTERFACE(usart_sync_t)
  200. //! @}

  201. //! \name class: usart_t
  202. //! @{
  203. DEF_INTERFACE(usart_t)
  204.     bool        (*Init)(usart_cfg_t *ptCFG);
  205.     bool        (*Idle)(void);
  206.     bool        (*Enable)(void);
  207.     bool        (*Disable)(void);
  208.     bool        (*WriteByte)(uint8_t chByte);
  209.     bool        (*ReadByte)(uint8_t *pchByte);

  210.     struct {
  211.         dma_tsf_info_t (*Output)(void);
  212.         dma_tsf_info_t (*Input)(void);
  213.     } DMAInfo;
  214.    
  215.     u32_property_t Baudrate;
  216.     usart_485_t  RS485;
  217.     usart_irda_t IrDA;     
  218.     usart_smartcard_t SmartCard;     
  219.     usart_sync_t SYNC;
  220. END_DEF_INTERFACE(usart_t)
  221. //! @}


  222. /*============================ GLOBAL VARIABLES ==============================*/
  223. extern const usart_t USART[USART_COUNT];

  224. /*============================ LOCAL VARIABLES ===============================*/
  225. /*============================ PROTOTYPES ====================================*/

  226. #endif

复制代码


希望通过这个能引导你从面向接口开发的方面思考问题。
发表于 2014-12-24 12:10:17 | 显示全部楼层
看到过一个场景的帖子不错
发表于 2014-12-24 14:23:29 | 显示全部楼层
午饭都消化完了。但你这盘大餐我还没有消化完呢。
 楼主| 发表于 2014-12-24 14:25:47 | 显示全部楼层
guolun 发表于 2014-12-24 14:23
午饭都消化完了。但你这盘大餐我还没有消化完呢。

细嚼慢咽,多喝水
发表于 2015-2-1 19:04:05 | 显示全部楼层
顶贴顶贴,今天看了好几个状态机的帖子
发表于 2015-2-1 23:00:09 | 显示全部楼层
精彩。没看完,记录一下慢慢看。
发表于 2015-2-6 15:36:23 | 显示全部楼层
像我这样的水平需要把宏展开了慢慢理才能明白,先把代码扣下拉来跑跑看结果,
我总觉得代码写成这样,头一眼看上去:那不是C,而是脚本
可能是我没有体会好楼主思路上的优势,除了栈深度可以预测,似乎没有别的
好处呢,读这代码有种想把所有宏都展开的想法。
发表于 2015-2-6 22:36:37 | 显示全部楼层
抱歉,就目前来说我只看前面几楼帖子,
还没有看到有关于Tiny_FSM的使用文档,所以要看懂在demo只能使用FSM的支撑宏展开看C
源码才能看懂,以下是我根据demo展开的C源码,和第一个原始的函数指针demo思路是一样的

一条线:当前状态在运行时,满足指定条件返回下一个状态的函数指针,强制void *输出,外面再转调用,这样循环下去

以下是展开的第2个demo纯C代码:

  1. #include "fsm.h"

  2. /*! \brief function that output a char with none-block manner
  3. *! \param chByte target char
  4. *! \retval true the target char has been put into the output buffer
  5. *! \retval false service is busy
  6. */

  7. //extern bool serial_out(uint8_t chByte);
  8. //
  9. //extern void toggle_led_a(void);
  10. //extern void toggle_led_b(void);

  11. #define serial_out(chByte) printf("%c", chByte)
  12. #define toggle_led_a()     printf("toggle_led_a()\n")
  13. #define toggle_led_b()     printf("toggle_led_b()\n")


  14. #define SERIAL_OUT(__BYTE)      serial_out(__BYTE)

  15. /*
  16. //展开前
  17. //---------------------------------------------------------------------------------

  18. DEF_TINY_FSM(Print_String)
  19.     DEF_PARAM
  20.         uint8_t *pchString;
  21.     END_DEF_PARAM
  22.     PRIVATE bool m_CriticalSection = false;
  23.    
  24.     PRIVATE TINY_STATE(Print_String, Print_Init);
  25.     PRIVATE TINY_STATE(Print_String, Print_Output);
  26. END_DEF_TINY_FSM

  27. */

  28. //展开后

  29.     typedef struct tiny_fsm_Print_String_arg tiny_fsm_Print_String_arg_t;           \
  30.     typedef void *(*tiny_fsm_Print_String_task)(tiny_fsm_Print_String_arg_t *pArg); \
  31.     struct tiny_fsm_Print_String_arg
  32.     {
  33.         uint8_t *pchString;
  34.     };
  35.     static bool m_CriticalSection = false;
  36.    
  37.     void *tiny_fsm_state_Print_Init(tiny_fsm_Print_String_arg_t *pArg);
  38.     void *tiny_fsm_state_Print_Output(tiny_fsm_Print_String_arg_t *pArg);

  39. /*
  40. //展开前
  41. //---------------------------------------------------------------------------------
  42. PRIVATE TINY_STATE(Print_String, Print_Init) BEGIN
  43.     if ((NULL == PARAM) || (NULL == PARAM->pchString)) {
  44.         TINY_FSM_END;           //!< end fsm
  45.     } else if ('\0' == (*PARAM->pchString)) {
  46.         TINY_FSM_END;           //!< end fsm
  47.     } else if (m_CriticalSection) {
  48.         TINY_FSM_TRANSFER_TO(Print_Init);   //!< try to enter critical section
  49.     }
  50.     m_CriticalSection = true;
  51.    
  52.     TINY_FSM_TRANSFER_TO(Print_Output)
  53. END
  54. */


  55. //展开后
  56. static void *tiny_fsm_state_Print_Init(tiny_fsm_Print_String_arg_t *pArg) {
  57.     if ((NULL == PARAM) || (NULL == PARAM->pchString)) {
  58.         return NULL;           //!< end fsm
  59.     } else if ('\0' == (*PARAM->pchString)) {
  60.         return NULL;           //!< end fsm
  61.     } else if (m_CriticalSection) {
  62.         return (void *)&tiny_fsm_state_Print_Init;   //!< try to enter critical section
  63.     }
  64.     m_CriticalSection = true;
  65.    
  66.     return (void *)&tiny_fsm_state_Print_Output;
  67. }

  68. /*
  69. //展开前
  70. //---------------------------------------------------------------------------------
  71. PRIVATE TINY_STATE(Print_String, Print_Output) BEGIN
  72.    
  73.     if (SERIAL_OUT(*(PARAM->pchString))) {
  74.         PARAM->pchString++;
  75.         if ('\0' == (*PARAM->pchString)) {
  76.             //! complete
  77.             m_CriticalSection = false;      //!< leave critical section      
  78.             TINY_FSM_END;                   //!< complete
  79.         }
  80.     }

  81.     TINY_FSM_TRANSFER_TO(Print_Output)      //!< reflexive
  82. END
  83. */



  84. //展开后
  85. void *tiny_fsm_state_Print_Output(tiny_fsm_Print_String_arg_t *pArg) {  
  86.     if (SERIAL_OUT(*(pArg->pchString))) {
  87.         pArg->pchString++;
  88.         if ('\0' == (*pArg->pchString)) {
  89.             //! complete
  90.             m_CriticalSection = false;      //!< leave critical section      
  91.             return NULL;                    //!< complete
  92.         }
  93.     }
  94.     return (void *)&tiny_fsm_state_Print_Output;      //!< reflexive
  95. }



  96. /*
  97. //展开前
  98. //---------------------------------------------------------------------------------
  99. int main(void)
  100. {
  101.     NEW_TINY_FSM(Print_String, DemoStringA)
  102.     NEW_TINY_FSM(Print_String, DemoStringB)
  103.     static uint8_t chDemoA[] = "Hello world";
  104.     static uint8_t chDemoB[] = "Tiny FSM Demo";

  105.    
  106.     while(true) {
  107.         //! call DemoStringA, the instance of the tiny FSM Print_String
  108.         CALL_TINY_FSM(Print_String, DemoStringA, Print_Init)
  109.             PARAM_INIT
  110.                 chDemoA     //!< output string "Hello world"
  111.             END_PARAM_INIT

  112.         END_CALL_TINY_FSM(Print_String)

  113.         //! an example of checking whether a sepecified fsm is complete or not
  114.         if (IS_TINY_FSM_CPL(DemoStringA)) {
  115.             toggle_led_a();    //!< do something here. E.g. toggle a LED
  116.         }

  117.         //! call DemoStringB, the instance of the tiny FSM Print_String
  118.         CALL_TINY_FSM(Print_String, DemoStringB, Print_Init)
  119.             PARAM_INIT
  120.                 chDemoB     //!< output string "Tiny FSM Demo"
  121.             END_PARAM_INIT

  122.         END_CALL_TINY_FSM(Print_String)

  123.         //! an example of checking whether a sepecified fsm is complete or not
  124.         if (IS_TINY_FSM_CPL(DemoStringB)) {
  125.             toggle_led_b();    //!< do something here. E.g. toggle a LED
  126.         }
  127.     }

  128.     return 0;
  129. }

  130. */


  131. //展开后
  132. int main(void)
  133. {
  134.     tiny_fsm_Print_String_task s_TinyFSMDemoStringA = NULL;
  135.     tiny_fsm_Print_String_task s_TinyFSMDemoStringB = NULL;

  136.     static uint8_t chDemoA[] = "Hello world";
  137.     static uint8_t chDemoB[] = "Tiny FSM Demo";

  138.    
  139.     while(true) {
  140.                         //! call DemoStringA, the instance of the tiny FSM Print_String
  141.                         do {                                                                                \
  142.                                 tiny_fsm_Print_String_task *s_ptTinyFSMTemp = &s_TinyFSMDemoStringA;              \
  143.                                 bool bReset = (NULL == s_TinyFSMDemoStringA);                                     \
  144.                                 tiny_fsm_Print_String_task s_TinyFSMStart = &(tiny_fsm_state_Print_Init);         \
  145.                                 static tiny_fsm_Print_String_arg_t tParam, tResetParam =
  146.                                 {
  147.                                         chDemoA     //!< output string "Hello world"
  148.                                 };                                                                                 \
  149.                                 if (bReset) {                                                                      \
  150.                                                 tParam = tResetParam;                                                          \
  151.                                                 *s_ptTinyFSMTemp = s_TinyFSMStart;                                             \
  152.                                 }                                                                                  \
  153.                                 *s_ptTinyFSMTemp = (tiny_fsm_Print_String_task)(*s_ptTinyFSMTemp)( &tParam );      \
  154.                         } while(false);

  155.                         //! an example of checking whether a sepecified fsm is complete or not
  156.                         if ((NULL == s_TinyFSMDemoStringA)) {
  157.                                         toggle_led_a();    //!< do something here. E.g. toggle a LED
  158.                         }

  159.                                         //! call DemoStringB, the instance of the tiny FSM Print_String

  160.                         do {                                                                                  \
  161.                                         tiny_fsm_Print_String_task *s_ptTinyFSMTemp = &s_TinyFSMDemoStringB;              \
  162.                                         bool bReset = (NULL == s_TinyFSMDemoStringB);                                     \
  163.                                         tiny_fsm_Print_String_task s_TinyFSMStart = &(tiny_fsm_state_Print_Init);         \
  164.                                         static tiny_fsm_Print_String_arg_t tParam, tResetParam =
  165.                                         {
  166.                                                         chDemoB     //!< output string "Tiny FSM Demo"
  167.                                         };                                                                                \
  168.                                         if (bReset) {                                                                     \
  169.                                                 tParam = tResetParam;                                                           \
  170.                                                 *s_ptTinyFSMTemp = s_TinyFSMStart;                                              \
  171.                                         }                                                                                 \
  172.                                                                                                                                                                                                                                                                                                                                                                                 \
  173.                                          *s_ptTinyFSMTemp = (tiny_fsm_Print_String_task)(*s_ptTinyFSMTemp)( &tParam );    \
  174.                  } while(false);


  175.                 //! an example of checking whether a sepecified fsm is complete or not
  176.                 if ((NULL == s_TinyFSMDemoStringB)) {
  177.                                 toggle_led_b();    //!< do something here. E.g. toggle a LED
  178.                 }
  179.   }
  180.    return 0;
  181. }
复制代码




展开之后的明了了。但是看这C代码我又有新的想法了:
1、代码运行的效率效率和OS来说没得说;
2、这代码是一个很简单的LED翻转的demo,如果在项目上应用这样的结构,那得分N多个状态
   而且这些状态的的跳转有改变维护怎么方便的处理?
3、代码可读性没有体现出来,初学需要文档才能输入手;
4、在一个项目上应用这样的FSM估计有上百个状态转换,上千或是更多都可能,画状态图估计比编码的时间还长;
5、这种结构怎么体现同步和并发?
6、bool bReset = (NULL == s_TinyFSMDemoStringA);  这句应该可以省去bReset中间变量
7、还是那句话,有点像脚本,不习惯,{}没必要换成BEGIN END吧,本来就是C,看着写着都方便。
8、继续看下面的帖子,呵呵






发表于 2015-2-6 22:55:35 | 显示全部楼层
4楼的数据帧解析很有实际的使用意义,不过应该说usart_rxc_isr_handler状态机入口及内部的状态运行不能有阻塞的代码
PS: 第79行的:PRIVTE NEW_TINY_FSM(Frame_Decoding, tFrameDeocoder)
       的PRIVTE  应该改成 PRIVATE
在main()里加个使用数据帧使用的同步状态机demo多好啊
发表于 2015-2-7 11:25:39 | 显示全部楼层
smset 发表于 2012-11-24 16:18
指针法状态思路很新颖的,赞!

这和电脑软件设计模式中其中的一种很类似(不好意思,具体名字我 ...

虽然这是老的帖子了,还是值得翻出来回味的。
我比赞同69和70楼帖子的说法,把握一个细分的度问题,也就是69楼说的颗粒度,尤其是状态多的项目;
另外楼主这样的代码结构,栈深度可预测只是一方面,他的重点应该是以状态机结构和os对比吧。
发表于 2015-2-10 14:31:47 | 显示全部楼层
楼主可否能提供以下90和92楼的支撑宏呢
我拿来实际仿真跑跑,谢谢
发表于 2015-2-10 14:56:18 来自手机 | 显示全部楼层
楼主是合肥淫?
 楼主| 发表于 2015-2-10 15:12:20 | 显示全部楼层

是啊,咋啦?你也是?
 楼主| 发表于 2015-2-10 15:13:41 | 显示全部楼层
worldsing 发表于 2015-2-10 14:31
楼主可否能提供以下90和92楼的支撑宏呢
我拿来实际仿真跑跑,谢谢

这部分因为商用的缘故已经闭源了。
发表于 2015-2-10 15:47:57 | 显示全部楼层
Gorgon_Meducer 发表于 2015-2-10 15:12
是啊,咋啦?你也是?

老乡见老乡,两眼泪汪汪
发表于 2015-2-27 22:57:44 | 显示全部楼层
学习,好久不用状态机了,用纯C实现了自己的OS
发表于 2015-2-28 10:15:42 | 显示全部楼层
基于函数指针&函数指针+调度器,要是再能开个调度器的解读就perfect
发表于 2015-3-12 15:47:26 | 显示全部楼层
个人觉得宏定义只是傻孩子实现状态机的一种方法,也肯定可以通过其它方式去实现.
宏定义的代码确实可读性较为困难.
发表于 2015-10-15 17:29:20 | 显示全部楼层
真心不错,学习了
发表于 2015-10-15 21:12:19 | 显示全部楼层
简单易用的状态机
发表于 2015-12-9 11:09:20 | 显示全部楼层
mark一下
发表于 2015-12-14 08:36:43 | 显示全部楼层
顶了个肺
发表于 2018-7-15 17:32:55 来自手机 | 显示全部楼层
worldsing 发表于 2015-2-6 22:36
抱歉,就目前来说我只看前面几楼帖子,
还没有看到有关于Tiny_FSM的使用文档,所以要看懂在demo只能使用FSM ...

谢谢展开,之前看的头都大了。
发表于 2018-7-15 17:33:11 来自手机 | 显示全部楼层
worldsing 发表于 2015-2-6 22:36
抱歉,就目前来说我只看前面几楼帖子,
还没有看到有关于Tiny_FSM的使用文档,所以要看懂在demo只能使用FSM ...

谢谢展开,之前看的头都大了。
发表于 2018-11-28 21:14:05 来自手机 | 显示全部楼层
mark……
 楼主| 发表于 2018-11-29 00:39:26 | 显示全部楼层

此贴介绍的模板是基于函数指针的。如果你对基于switch的状态机模板感兴趣,推荐看另外一个帖子
发表于 2018-11-29 09:21:51 | 显示全部楼层
Gorgon_Meducer 发表于 2018-11-29 00:39
此贴介绍的模板是基于函数指针的。如果你对基于switch的状态机模板感兴趣,推荐看另外一个帖子 ...

好的,多谢,正在研读~~~
发表于 2019-12-13 17:37:59 | 显示全部楼层
如果能用按键点灯的方法搞个实例会好一些,很多朋友都是从点灯开始的,例如:搞1-3个键,搞1-8个灯,每个灯表示不同的任务,可以做个简单的加减法,这样的话会更好的体现状态机的优点及实用性。只是本人一点建议,不喜勿喷,谢谢!
发表于 2019-12-13 18:45:48 来自手机 | 显示全部楼层
好资料,谢谢
发表于 3 天前 | 显示全部楼层
现在因为种种原因回到了C51,C51函数局部变量分配在固定地址,采用OVERLAP方式分析内存占用进行优化,
使用函数指针动态调用,C51将无法分析调用链,使得OVERLAP无效,这里提到的函数指针法用到C51上,就不方便了。
 楼主| 发表于 昨天 02:50 | 显示全部楼层
xlee 发表于 2020-2-26 13:18
现在因为种种原因回到了C51,C51函数局部变量分配在固定地址,采用OVERLAP方式分析内存占用进行优化,
使用 ...

嗯,51很多东西都不是很方便。
友情提示:标题不合格、重复发帖,将会被封锁ID。详情请参考:论坛通告:封锁ID、获得注册邀请码、恢复被封ID、投诉必读
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|阿莫电子论坛(原ourAVR/ourDEV) ( 公安备案:44190002001997(交互式论坛) 工信部备案:粤ICP备09047143号 )

GMT+8, 2020-2-29 18:13

阿莫电子论坛, 原"中国电子开发网"

© 2004-2018 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表