如何用宏实现一个状态机框架?
我在这里参考了西门子s7-200 plc的stl编程样式。
首先来看看工控中状态转移图的实现方式:
[size=0.83em]2016-5-18 23:54 上传
下载附件 [size=0.83em](189.29 KB)
这个就是实现状态机框架的宏定义
- //定义状态机切换的四种状态
- enum sta {idel, waitLive, live, waitIdel};
- //状态的总数设定,根据需要修改状态总数
- #define smMum 100
- //全局状态变量
- sta s[smMum];
- //状态开始符,用的时候后面不要加";"
- #define LSCR(i) {\
- int index=i;\
- bool mc=s[index]==live ;\
- if(s[index]!=idel){\
- if(s[index]==waitLive){s[index]=live;}\
- if(s[index]==waitIdel){s[index]=idel;}
- //状态转移
- #define SCRT(t) ({s[index]=waitIdel,s[t]=waitLive;})
- //状态结束符
- #define SCRE }}
- //设置状态
- #define setSm(i) s[i]=waitLive
- //重置状态
- #define rstSm(i) s[i]=waitIdel
- //读取状态
- #define readSm(i) ({s[i]==live;})
复制代码
这个是延时继电器的宏定义,可以参考上个帖子。- //宏名:delayRelay。返回值:bool 到达时间。输入值:enable使能,del延时时间。作用:延时继电器,当enable使能后,延时del毫秒后接通。
- //static unsigned long tim = ({Serial.println("start"), millis();}); //这句用于测试是否能在static 变量设置的时候加入只执行一次的初始化代码块。
- #define delayRelay(enable,del) ({\
- static unsigned long tim = millis();\
- if (!enable) {\
- tim = millis();\
- }\
- millis() - tim >= del;\
- })
复制代码
这个是边沿检测的宏定义,可以参考上上个帖子- //逻辑量的四种状态:低,上升,高,低
- enum edgeSta {low, trg, high, pop};
- //宏名:edge。 返回值:逻辑量状态。输入参数:bool值 用途:检测逻辑量的边沿。
- #define edge(ReadData) ({\
- static bool Trg = false;\
- static bool Cont = false;\
- static bool Pop = false;\
- Trg = ReadData & (ReadData ^ Cont); \
- Pop = Cont != ReadData & !Trg; \
- Cont = ReadData; \
- enum edgeSta sta = low;\
- if (Cont) {\
- sta = high;\
- } else {\
- sta = low;\
- }\
- if (Trg) {\
- sta = trg;\
- }\
- if (Pop) {\
- sta = pop;\
- }\
- sta;\
- })
- #define raiseEdge(ReadData) ({edge(ReadData)==trg;})
复制代码
这个是主程序- //自定义状态机的状态
- enum myState {l0_st0, l0_st1, l1_st0, l1_st1, l1_st2, l1_st3, l1_st4, l1_st5, l2_st0, l2_st1, l2_st2, l2_st3};//根据个人的需要尽量取容易理解的名字,当然不设置枚举也是可以的,直接用0,1,2,3.....
- void setup() {
- // put your setup code here, to run once:
- Serial.begin(9600);
- setSm(l0_st0);
- //setSm(l1_st0);
- //setSm(l2_st0);
- pinMode(8, INPUT_PULLUP); //pin8接按键
- }
- void loop() {
- // put your main code here, to run repeatedly:
-
- //loop0是一个简单的无限循环,状态0,1,0,1。。。。。
- LSCR(l0_st0)
- if (raiseEdge(mc))
- {
- Serial.println("Sta0");
- }
- if (delayRelay(mc, 1000))
- {
- SCRT(l0_st1);
- }
- SCRE
-
- LSCR(l0_st1)
- if (raiseEdge(mc))
- {
- Serial.println("Sta1");
- }
- if (delayRelay(mc, 1000))
- {
- SCRT(l0_st0);
- }
- SCRE
-
- //loop1是一个并行分支结构
- LSCR(l1_st0)
- if (mc) {
- SCRT(l1_st1);
- SCRT(l1_st2);
- }
- SCRE
-
- LSCR(l1_st1)
- if (delayRelay(mc, 1000))
- {
- SCRT(l1_st3);
- }
- SCRE
-
- LSCR(l1_st2)
- if (delayRelay(mc, 3000))
- {
- SCRT(l1_st4);
- }
- SCRE
-
- LSCR(l1_st3)
- if (raiseEdge(mc))
- {
- Serial.println("st3");
- }
- SCRE
-
- LSCR(l1_st4)
- if (readSm(l1_st3) && readSm(l1_st4))
- {
- SCRT(l1_st5);
- rstSm(l1_st3);
- rstSm(l1_st4);
- Serial.println("st3,4");
- }
- SCRE
-
- LSCR(l1_st5)
- if (delayRelay(mc, 1000))
- {
- SCRT(l1_st0);
- Serial.println("reStart");
- }
- SCRE
-
- //loop2是一个选择分支结构,当8端口的按键不按下时进入分支1,当8端口的按键按下时进入分支2。
- LSCR(l2_st0)
- if (!digitalRead(8))
- {
- SCRT(l2_st2);
- }
- else
- {
- SCRT(l2_st1);
- }
- SCRE
-
- LSCR(l2_st1)
- if (raiseEdge(mc))
- {
- Serial.println("key up,way0");
- }
- if (delayRelay(mc, 1000))
- {
- SCRT(l2_st3);
- }
- SCRE
-
- LSCR(l2_st2)
- if (raiseEdge(mc))
- {
- Serial.println("key down,way1");
- }
- if (delayRelay(mc, 1000))
- {
- SCRT(l2_st3);
- }
- SCRE
-
- LSCR(l2_st3)
- if (delayRelay(mc, 1000))
- {
- SCRT(l2_st0);
- Serial.println("reStart");
- }
- SCRE
-
- }
复制代码
这个是loop()中的三个状态循环结构,delayRelay是无阻塞的,因此多个状态机构成的循环可以并行运行,在setup()中注释或开启可以单独观察每个状态。
[size=0.83em]2016-5-19 10:47 上传
下载附件 [size=0.83em](101.95 KB)
取出单个状态来解释一下状态机的工作:- LSCR(l0_st0)//这个是loop0的状态0,状态开始标志。
- if (raiseEdge(mc))//mc类比plc中的母线,代表状态机的执行状态,当状态转移时会延迟一个周期,用于进行定时器等的复位操作,此处检测状态开始时的上升沿,打印状态。
- {
- Serial.println("Sta0");
- }
- if (delayRelay(mc, 1000))//状态机延时1000ms后转跳至loop0的状态1。
- {
- SCRT(l0_st1);//状态转移的同时会自动复位本状态,同样新转移的状态会延迟一个周期用于初始化定时器等操作。
- }
- SCRE//状态结束标识符
复制代码
|