jssd 发表于 2023-3-17 17:11:18

请教:顺序事件型含等待的代码各位是怎样处理的?

场景:空闲的时候,等待一个触发信号A,然后执行事件1,然后等待时间T后,执行事件2,然后等待时间T2或者触发A2,执行事件3……
像这样的等待比较多,用switch好像特别啰嗦,各位有没有更好的方法更优雅的形式?

tim 发表于 2023-3-17 17:39:49

"更好的方法更优雅的形式"就是让别人去写,不自己写{:titter:}

月夜随想 发表于 2023-3-17 17:57:37

状态机   

擦鞋匠 发表于 2023-3-17 18:02:30

我看stm32 usb库 枚举相关代码,也是老老实实用switch状态机来写的。
我感觉,除非你学习傻孩子老师那种把宏用到极致,否则,这只是一段代码而已(又不是女人),结果才是最重要的...

t3486784401 发表于 2023-3-17 18:21:39

若顺序不乱,甚至可以写成阻塞式。

若有乱序要求,本质是自己写多线程,没啥好的办法

GZZXB 发表于 2023-3-17 20:06:16

看看大佬们是怎么做的进来学习学习{:smile:}
在一个项目中我是这么傻干的.   
把这个当做是一个事件驱动机制, 触发条件 和时间到都产生一个消息.
把每个执行事件做成一个函数,这些函数用一个函数指针管理.   

产生触发信号A -> 发消息(事件1函数地址)到消息队列 -> 轮询任务从消息对列获取消息
->用函数指针调用事件对应的函数.   

事件1函数
{
    uint32_tmsg;
    执行动作。。。
   msg = (uint32_t)等待时间T函数名;
   SendMsg(msg);
}
触发信号A产生
{
    uint32_tmsg;
   msg = (uint32_t)事件1函数名;
   SendMsg(msg);
}

轮询从消息队列取消息
{
       获取消息不为空且为函数地址消息
      {
            lpFun= (void(*)())msg;
            (*lpFun)();

            
      }
}

jssd 发表于 2023-3-17 21:05:47

GZZXB 发表于 2023-3-17 20:06
看看大佬们是怎么做的进来学习学习
在一个项目中我是这么傻干的.   
把这个当做是一个事件驱动机 ...
(引用自6楼)

在nordic 的sdk 里也是这种方法,但还是觉得比较麻烦。想用类似表格的那种,把所有的事件和触发条件都列出来,但还没想好怎么做……

jssd 发表于 2023-3-17 21:11:44

擦鞋匠 发表于 2023-3-17 18:02
我看stm32 usb库 枚举相关代码,也是老老实实用switch状态机来写的。
我感觉,除非你学习傻孩子老师那种把 ...
(引用自4楼)

其实我的目的也是比较简单,就是想搞一个比较适合自己的有效的做法,最好是形成一定的格式,这样才能快速的开发,而且不容易出错。如果每一次都是重新造轮子,真的很麻烦

jssd 发表于 2023-3-17 21:14:17

t3486784401 发表于 2023-3-17 18:21
若顺序不乱,甚至可以写成阻塞式。

若有乱序要求,本质是自己写多线程,没啥好的办法 ...
(引用自5楼)

如果每一个状态只有一个触发条件还好,但若有多几个,或者合并式的,就很麻烦,很难形成统一的风格,至少我目前没有什么好办法

jssd 发表于 2023-3-17 21:15:39

tim 发表于 2023-3-17 17:39
"更好的方法更优雅的形式"就是让别人去写,不自己写
(引用自2楼)

你说的对!哈哈?

Huaan 发表于 2023-3-17 21:41:27

不想搞状态机就上RTOS,多线程及线程间通信帮你解决顺序事件中阻塞的烦恼

albert_w 发表于 2023-3-18 07:00:52

RTOS不香吗,不然绕不开状态机,要不就是状态机的变体,只是何处事件触发和状态跳转

jiki911 发表于 2023-3-18 09:32:31

状态机基本能解决绝大多数问题,如果解决不了,就再加一个状态机。

GZZXB 发表于 2023-3-18 11:02:21

jssd 发表于 2023-3-17 21:05
在nordic 的sdk 里也是这种方法,但还是觉得比较麻烦。想用类似表格的那种,把所有的事件和触发条件都列 ...
(引用自7楼)

    参考MFC消息映射机制,用一个.h文件里实现消息映射。用宏把这些触发条件和执行事件预定义好.
    #define触发条件_MSG    执行事件函数名
    ...
   
    这样只要维护好这个映射表,.c中的执行代码基本不用修改,修改每个条件时只修改消息映射会不会更好点呢?
    是不是接近你说的类似表格?

磊磊映画 发表于 2023-3-20 16:13:20

状态机了解一下

t3486784401 发表于 2023-3-20 16:28:00

GZZXB 发表于 2023-3-18 11:02
参考MFC消息映射机制,用一个.h文件里实现消息映射。用宏把这些触发条件和执行事件预定义好.
    #de ...
(引用自14楼)

MFC背后帮你写好switch了,而且后台代码更不易读…

442502587 发表于 2023-3-20 18:58:39

消息队列   

机器人天空 发表于 2023-3-20 23:27:13

stateMachine状态机框架
传送门: https://github.com/misje/stateMachine说明文档: http://misje.github.io/stateMachine/index.html

jssd 发表于 2023-3-26 14:47:22

https://blog.csdn.net/qq_37662088/article/details/122441289
https://blog.csdn.net/qq_36969264/article/details/122365696
结合了这两位大侠的代码搞了一个比较合适自己的状态机
#ifndef __fsm_H
#define __fsm_H

#include <stdbool.h> //true false
#include <stdint.h>        //uint8_t
#include <stdio.h>        //printf

typedef struct action_map
{
        uint8_t state;
        void (*EnterAct)(void);                //进入状态之前的执行函数,执行一次
        void (*RunningAct)(void);        //状态执行函数,一直执行
        void (*ExitAct)(void);                //退出状态之时的执行函数,执行一次
}action_map_t; /* 动作action表描述 */

typedef struct event_map
{
        uint8_t stCurState;                        //当前状态
        uint8_t stNextState;                //下一个状态
        bool (*trans_fun)(void);        //转移条件函数
}event_map_t; /* 事件event表描述 */

typedef struct fsm
{
        uint8_t state;                                //当前状态
        uint8_t actSum;                                //动作总数
        uint8_t eventSum;                        //事件总数
        int8_t runActionID;                //执行函数的id
        action_map_t *pActionMap;        //动作执行
        event_map_t *pEventMap;                //状态转移
        struct fsm *next;                        //链表
}fsm_t; /* 状态机控制结构 */

void FSM_Init(fsm_t* pFsm,event_map_t* pEventMap,uint8_t eveMapSum,action_map_t* pActionMap,uint8_t actMapSum,uint8_t curState);

#endif /* __fsm_H */

#include "fsm.h"
#include "system.h"

static fsm_t* head_handle = NULL;


int8_t FSM_EnterAct(fsm_t* pFsm)
{
        int8_t i;
        //printf("FSM_EnterAct:pFsm->state=%d\n",pFsm->state);
        if(pFsm->pActionMap){
                for(i=0; i<pFsm->actSum; i++){
                        if(pFsm->state == pFsm->pActionMap.state){
                                if(pFsm->pActionMap.EnterAct){
                                        pFsm->pActionMap.EnterAct();        //执行进入函数
                                }
                                return i;
                        }
                }
        }
        return -1;
}

int8_t FSM_ExitAct(fsm_t* pFsm)
{
        int8_t i;
        //printf("FSM_ExitAct:pFsm->state=%d\n",pFsm->state);
        if(pFsm->pActionMap){
                for(i=0; i<pFsm->actSum; i++){
                        if(pFsm->state == pFsm->pActionMap.state){
                                if(pFsm->pActionMap.ExitAct){
                                        pFsm->pActionMap.ExitAct();        //执行退出函数
                                }
                                return i;
                        }
                }
        }
        return -1;
}

void FSM_Run(fsm_t* pFsm)//传入当前状态,因为需要对其进行修改,所以传变量指针
{
        uint8_t i;
    for(i=0;i<pFsm->eventSum;i++){
      if ((pFsm->pEventMap.stCurState == pFsm->state) && (pFsm->pEventMap.trans_fun() == true))//当前状态相等并且转移条件为真
      {
            //这里可以添加自己需要执行的代码
                        FSM_ExitAct(pFsm);
            pFsm->state = pFsm->pEventMap.stNextState;//转移为下一个状态
                        pFsm->runActionID = FSM_EnterAct(pFsm);
                        break;
      }
    }
        if(i==pFsm->eventSum){
                if((pFsm->runActionID>0)&&(pFsm->pActionMap.RunningAct)){
                        pFsm->pActionMap.RunningAct();        //执行退出函数
                }
        }
}

void FSM_Process(void)
{
        fsm_t* target;
        for(target=head_handle;target;target=target->next){
                FSM_Run(target);
        }
}



void FSM_Init(fsm_t* pFsm,event_map_t* pEventMap,uint8_t eveMapSum,action_map_t* pActionMap,uint8_t actMapSum,uint8_t curState)
{
        //以链表形式
        fsm_t* target = head_handle;
        while(target){
                if(target==pFsm) return;
                target = target->next;
        }
        pFsm->next = head_handle;
        head_handle = pFsm;
       
        pFsm->state = curState;
        pFsm->actSum = actMapSum;
        pFsm->eventSum = eveMapSum;
        pFsm->pEventMap = pEventMap;
        pFsm->pActionMap = pActionMap;
       
        pFsm->runActionID = FSM_EnterAct(pFsm);
       
        Process_Start(0,"FSM_Process",FSM_Process);
       
        printf("pFsm->actSum=%d,pFsm->eventSum=%d\n",pFsm->actSum,pFsm->eventSum);
}


/********************END OF FILE************/


使用
#include "fsm.h"

typedef enum {
    state_1 = 0,
    state_2,
    state_3,
    state_4
}State;

int x;
int y = 5;

bool trans_1to2(void)
{
    return (x == y);
}

bool trans_1to3(void)
{
    return (x > y);
}

bool trans_2to3(void)
{
    return (x > y);
}

bool trans_3to4(void)
{
    return (x == y);   
}

bool trans_4to1(void)
{
    return (x < y);
}



void state1_entry(void)
{
        printf("state1_entry\n");
}
void state1_do(void)
{
        //printf("state1_do\n");
}
void state1_exit(void)
{
        printf("state1_exit\n");
}

void state2_entry(void)
{
        printf("state2_entry\n");
}
void state2_do(void)
{
        //printf("state2_do\n");
}
void state2_exit(void)
{
        printf("state2_exit\n");
}

void state3_entry(void)
{
        printf("state3_entry\n");
}
void state3_do(void)
{
        printf("state3_do\n");
}
void state3_exit(void)
{
        printf("state3_exit\n");
}

void state4_entry(void)
{
        printf("state4_entry\n");
}
void state4_do(void)
{
        //printf("state4_do\n");
}
void state4_exit(void)
{
        printf("state4_exit\n");
}

event_map_t eventMap[] = {
       {state_1,state_2,trans_1to2},
       {state_2,state_3,trans_2to3},
       {state_3,state_4,trans_3to4},
       {state_4,state_1,trans_4to1},
       {state_1,state_3,trans_1to3}
};

action_map_t actionMap[] =
{
        {state_1,        0,        state1_do,        state1_exit},
        {state_2,        state2_entry,        state2_do,        0},
        {state_3,        state3_entry,        0,        state3_exit},
        {state_4,        state4_entry,        state4_do,        state4_exit},
};

fsm_t fsm1;


void USR_Init(void)
{
        FSM_Init(&fsm1,eventMap,sizeof(eventMap)/sizeof(event_map_t),actionMap,sizeof(actionMap)/sizeof(action_map_t),state_1);
}

GZZXB 发表于 2023-3-27 09:50:13

jssd 发表于 2023-3-26 14:47
https://blog.csdn.net/qq_37662088/article/details/122441289
https://blog.csdn.net/qq_36969264/articl ...
(引用自19楼)

    请教下楼主触发条件(定时到或收到串口命令等等)和FSM你打算怎么耦合?消息机制吗?
如果是消息机制,准备以什么方式派发独立消息(某个FSM响应)全局消息(所有FSM都响应)?
    如果不是,那么trans_fun里想用什么方式和触发条件耦合?
   

jssd 发表于 2023-3-27 11:11:45

GZZXB 发表于 2023-3-27 09:50
请教下楼主触发条件(定时到或收到串口命令等等)和FSM你打算怎么耦合?消息机制吗?
如果是消息机制, ...
(引用自20楼)

event_map_t eventMap[] = {
       {state_1,state_2,trans_1to2},
       {state_2,state_3,trans_2to3},
       {state_3,state_4,trans_3to4},
       {state_4,state_1,trans_4to1},
       {state_1,state_3,trans_1to3}
};
事件触发,也就是定义的eventMap的事件触发函数为真就会触发当前的状态转移。至于耦合,这里需要转换一下,把所需要的条件直接写到事件触发函数里判断。
但实际使用起来,原理没问题,只是感觉不比用switch case简单,反而有点啰嗦的感觉。。。主要是要列一大堆的函数。。。

GZZXB 发表于 2023-3-27 11:48:29

jssd 发表于 2023-3-27 11:11
event_map_t eventMap[] = {
       {state_1,state_2,trans_1to2},
       {state_2,state_3,trans_2to3 ...
(引用自21楼)

    把所需要的条件直接写到事件触发函数里判断。
你指的是怎么个直接法?每个触发函数都里这样写吗 \
   
   获取按键值   if(按键条件满足)   切换到next_state
   获取串口命令if(串口命令满足)   切换到next_state
   ...
   
这样不太符合接口规范,不太好做到弱耦合吧?假定这个工程有串口事件(也就是你说的条件)移植到另一个工程时(没有串口模块),每个判断函数的代码都需要修改.

sweet_136 发表于 2023-3-27 12:01:04

那个do函数 一直在主程序执行..
那就是反复调用.. 会不会比较麻烦呢?

jssd 发表于 2023-3-27 12:15:41

GZZXB 发表于 2023-3-27 11:48
把所需要的条件直接写到事件触发函数里判断。
你指的是怎么个直接法?每个触发函数都里这样写吗 \
   ...
(引用自22楼)

bool trans_1to2(void)
{
    if(keydown) return true;
    if(uartCom) return true;
    ...
    return false;
}

像这样,但这样也有一个问题,比如按键,如果使用类似multibutton这样的回调的,就很难用比如:
按键回调
key_callback(){ keydown = 1; }
触发函数
bool trans_1to2(void)
{
        if(keydown){
                keydown = 0;
                return true;
        }
    return false;
}

这样需要一个fifo记录key的触发,当然这个例子fifo深度只有1,但如果多个触发函数都用到呢?这就麻烦了

所以把这个触发函数改成事件代号会好一些,比如:
enum{
        event_1,
      event_2,
      ...
};
当有消息回调时,进行切换状态
key_callback(){ fsm_trans(curState,event_1)}

这样其实还有一个问题,就是要是event_1是key1down&&key2down共同的结果呢?感觉又回到的上面的问题

jssd 发表于 2023-3-27 12:19:28

sweet_136 发表于 2023-3-27 12:01
那个do函数 一直在主程序执行..
那就是反复调用.. 会不会比较麻烦呢?
(引用自23楼)

我也觉得貌似多余了。。。
还是有待改进,刚好现在在搞AT命令的8266,觉得这个AT流程搞完,比较适用的状态机应该也可以提取出来了

cgeng 发表于 2023-3-27 12:38:50

我自己的感觉是:
在MCU上面开发,如果逻辑复杂到这样,与其搞一个复杂的状态机框架,不如直接上rtos。可以避免很多问题,代码可读性也会好很多。

除非应用场景特殊,无法上rtos才有必要引入这种状态机框架。

asj1989 发表于 2023-3-27 12:44:06

推荐上rtos,我在最近的项目使用freertos,效果确实挺好

yplin27 发表于 2023-3-27 12:49:57

这种用 rust 的 async/awit 实现起来就很简单,底层原理就是状态机 + 异步运行时
页: [1]
查看完整版本: 请教:顺序事件型含等待的代码各位是怎样处理的?