最近学习了FSM有限状态机,通过网上的优秀案例,我自己实践了一下发现比较高效,从此写项目又多了一套框架选择,下面我来讲解分享一下这个简单的例程。
1、先总结一下它的大致思路:
- 事件标志(类似于模式1、模式2这样)
- 动作标志(当前动作、下一个动作)
- 动作函数(点亮led的操作)
大致的流程框架图结合代码:(代码中有注释,方便理解)
有了这些大致的思路,下面就可以来编写标志位
2、标志位的初始化:
/* 事件标志位 */
// 模式=事件 mode也可以写成event
typedef enum
{
Mode1 = 1,
Mode2,
Mode3,
Mode4,
};
/* 动作标志位 */
typedef enum // 动作
{
LEW1_ON,
LEW2_ON,
LEW3_ON,
LEW4_ON,
};
3、动作函数的编写
void Led1(void)
{
printf("led1 on\n");
}
void Led2(void)
{
printf("led2 on\n");
}
void Led3(void)
{
printf("led3 on\n");
}
void Led4(void)
{
printf("led4 on\n");
}
4、定义一个状态表结构,用来表示一个状态机的状态,综合前三点加上下一个状态
typedef struct ModeStruct_Init_s // 结构体初始化,综合
{
unsigned int mode; //事件
unsigned int CurState; //当前状态
void (*ModeFun)(); //函数指针
unsigned int NextState; //下一个状态
}ModeStruct_Init_t;
5、根据上面的结构体,去配置成结构数组,即为状态表
ModeStruct_Init_t StepTable[] =
{
/* 事件标志 当前动作 动作函数 下一个动作 */
{Mode1, LEW1_ON, Led1, LEW2_ON},
{Mode2, LEW2_ON, Led2, LEW3_ON},
{Mode3, LEW3_ON, Led3, LEW4_ON},
{Mode4, LEW4_ON, Led4, LEW1_ON},
};
6、再定义一个状态机总结构,表示一个状态机
typedef struct FSM_s // 记录当前状态
{
ModeStruct_Init_t* TempTable; //指向的状态表
unsigned int TempState; //FSM当前所处的状态
}FSM_t;
7、编写初始化状态机的函数:
// 初始化FSM,相当于将传进来的结构体进行配置
void InitModeTable(FSM_t* pModeTable)
{
// StepTable状态表/ModeStruct_Init_t 16/4=4 获取长度4
// g_state_max_num 记得定义,我这里在其它地方定义了
g_state_max_num = sizeof(StepTable) / sizeof(ModeStruct_Init_t);
pModeTable->TempState = LEW1_ON; // 初始状态开启LED1
pModeTable->TempTable = StepTable; // 初始化TempTable指向状态表StepTable
}
8、编写事件处理函数:(精髓)
/* 事件处理 */
// 将当前状态结构体传进来,将模式标志位传进来
void FSM_EventHandle(FSM_t* pModeTable, uint16_t Mode)
{
// 将传进来的结构体状态表 = 赋值给临时结构体状态表
ModeStruct_Init_t* pActTable;
// 空函指针
void (*eventActFun)() = NULL;
uint16_t i;
uint16_t flag = 0; //标识是否满足条件
uint16_t NextState = 0;
uint16_t CurState = 0;
pActTable = pModeTable->TempTable;
// 将传进来当前状态结构体 当前所处的状态 = 赋值给临时状态
CurState = pModeTable->TempState;
/* 获取当前动作函数 遍历传进来的mode对比状态表中的mode 和 当前状态是否等于传进来的临时状态*/
for (i = 0; i<g_state_max_num; i++)
{
//当且仅当当前状态下来个指定的事件,我才执行它
if (Mode == pActTable[i].mode && CurState == pActTable[i].CurState)
{
// 标志位置1
flag = 1;
/* 执行动作函数,就是执行状态表中对应的函数 */
// 这句也可以,为了展示采用下面一句
// pActTable[i].ModeFun();
eventActFun = pActTable[i].ModeFun;
/* 执行状态转移,调用FSM_StateTransfer函数,将下一个状态赋值给下一次操作 */
// 这句也可以,为了展示采用下面一句
// pModeTable->TempState = pActTable[i].NextState;
FSM_StateTransfer(pModeTable, pActTable[i].NextState); // 此处函数放在下面定义
break;
}
}
if (flag) //如果满足条件了
{
/* 动作执行 */
if (eventActFun)
{
// 在这里执行动作表里面的动作函数
eventActFun();
}
}
else
{
// do nothing
}
}
执行状态转移函数:
void FSM_StateTransfer(FSM_t* pModeTable, uint16_t mode)
{
pModeTable->TempState = mode;
}
9、有了这些程序结构,就可以进行主函数调用函数
extern void test(int *mode);
void main()
{
FSM_t ModeTable;
mode = Mode1;
InitModeTable(&ModeTable);
while(1)
{
// Scan_key(); // 按键去触发条件
test(&mode); // 循环去触发条件
FSM_EventHandle(&ModeTable,mode);
sleep(20); // 延时函数,制造循环间隔,按键触发条件可以不加
}
}
/* 循环去触发条件 */
void test(int *mode)
{
if (*mode == 4)
{
*mode = 1;
}
else
{
(*mode)++;
}
}