Github
整个RTT学习过程中对工程的完善和开发都记录在Github上的一个项目中,项目还会随着学习的深入进行更新
项目URL:https://github.com/HITLIVING/-RT-Thread-.git
读者可以通过Pull下来整个项目进一步了解
对模拟状态机的实现需要格外关注schedule_app.c
schedule_app.h
两个文件
综述
当工程实现的功能越来越复杂,需要设计专门的调度器来合理的支配工程中的各个模块
尤其是当设计的工程需要面对多任务,多流程环节的场合,往往需要设计一个机构,配合各种条件来完成任务
在RTT学习过程中,学会驱动了显示屏触摸屏等各个模块后,可以尝试开发一个具有界面和可视化管理的系统
外设配置
STM32F1RCT6芯片
野火MINI开发板的板载外设
典型结构
在单片机上实现模拟状态机,或者说是调度器,更多的是使用定时器和循环结构的配合
循环结构则是利用 switch-case
的检索,或者 if-else
的条件来实现switch-case
比 if-case
效率稍高一些,因为 swich-case
基于查找表检索
但是 switch-case
只能实现常量对比,而无法实现逻辑比较的检索
两种循环结构是单片机上实现调度的典型结构,但都存在一定问题
在执行时都需要检索,而且随着功能的丰富,调度树会原来越长,较难维护
这里介绍一种基于 函数指针 的调度器实现方法
该方法来源于网络,同时做出了一定的发展
在2019ROBOCON比赛的决策树采用了此结构运行状况良好
基本思路
定义了函数指针后,对函数可以采用变量式的管理方法,通过对变量的赋值即可实现特定函数的执行
使用此方法可以将调度器中的各个任务相互分隔,易于维护
同时仅需要对一变量赋不同的值即可实现状态的跳转
函数结构
声明
- 头文件:
enum MainSchStateType
{
//主界面状态
Interface_State,
//菜单状态
Menu_State,
//触屏校准状态
CheckTouch_State,
//画板状态
Palette_State,
//类型边界
Type_Num
};
头文件里声明了一个枚举类型
用来声明不同状态的标志符
- 源文件: ```c typedef enum MainSchStateType MainSch_State; typedef MainSch_State (*MainSch_Procedure)(void);
//当前执行状态 enum MainSchStateType MainSchStep = Interface_State; //上一周期状态 enum MainSchStateType Last_MainSchStep = Type_Num;
源文件将枚举类型重定义,方便使用<br />将函数指针重定义,方便使用(函数的返回类型为 `MainSch_State` ,函数指针的变量名为 `MainSch_Procedure` ,无传入参数)<br />记录当前需要执行的状态标志符,用来指定状态<br />记录上一周期执行的状态标志符,用来自动执行状态切换时的初始化
<a name="aXPui"></a>
## 定义
为方便说明,这里将各个状态的函数实体删去,详细内容可以参照原工程
```c
typedef enum MainSchStateType MainSch_State;
typedef MainSch_State (*MainSch_Procedure)(void);
enum MainSchStateType MainSchStep = Interface_State;
enum MainSchStateType Last_MainSchStep = Type_Num;
/*****************Main Schedule Procedure**********************/
MainSch_State step_Interface(void)
{
if(Last_MainSchStep!=MainSchStep)
{
/* step init */
//当状态切换时这里只执行一次
}
//更新,避免反复执行状态的初始化
Last_MainSchStep = MainSchStep;
/*自定义应用程序*/
...
//返回当前状态的标识符,使该状态循环
return MainSchStep;
}
MainSch_State step_Menu(void)
{
if(Last_MainSchStep!=MainSchStep)
{
/* step init */
//当状态切换时这里只执行一次
}
Last_MainSchStep = MainSchStep;
/*自定义应用程序*/
...
//返回当前状态的标识符,使该状态循环
return MainSchStep;
}
MainSch_State step_CheckTouch(void)
{
if(Last_MainSchStep!=MainSchStep)
{
/* step init */
//当状态切换时这里只执行一次
}
/*自定义应用程序*/
...
//返回某个标识符,使该状态仅被执行一次
return Menu_State;
}
MainSch_State step_Palette(void)
{
if(Last_MainSchStep!=MainSchStep)
{
/* step init */
//当状态切换时这里只执行一次
}
Last_MainSchStep = MainSchStep;
/*自定义应用程序*/
...
//返回当前状态的标识符,使该状态循环
return MainSchStep;
}
MainSch_State step_Num(void)
{
/*自定义应用程序*/
/*执行到这里有越界的风险*/
...
//返回某个安全的标识符,使该状态仅被执行一次
return Interface_State;
}
/* 各个状态的函数指针数组 */
/* 这里即实现了用数组来管理函数 */
/* 注意和标识符区分 */
MainSch_Procedure State_ProceSteps[] =
{
step_Interface,
step_Menu,
step_CheckTouch,
step_Palette,
step_Num
};
/**
* @brief System main Schedule Thread Entry
* @param None
* @return None
* @note Provide a timescale用线程做定时器
*/
static void thread_main_entry(void *parameter)
{
while(1)
{
/*定时器周期*/
rt_thread_mdelay(1);
/*根据标识符执行对应函数*/
/*并将当前状态号记录下来循环调用*/
/*当通过外部中断或其它地方修改了状态号,则执行修改后的状态号对应的函数*/
MainSchStep = (*State_ProceSteps[MainSchStep])();
}
}
#define MAIN_THREAD_PRIORITY 21
#define MAIN_THREAD_STACK_SIZE 1024
#define MAIN_THREAD_TIMESLICE 100
/**
* @brief System main Schedule Thread Init
* @param None
* @return None
* @note Provide a timescale用线程做定时器
*/
ALIGN(RT_ALIGN_SIZE)
void thread_System_Schedule_init(void)
{
rt_thread_t thread_main_obj = RT_NULL;
/* Build a thread */
thread_main_obj = rt_thread_create("thread_System_Schedule",
thread_main_entry,
RT_NULL,
MAIN_THREAD_STACK_SIZE,
MAIN_THREAD_PRIORITY,
MAIN_THREAD_TIMESLICE);
/* If get the thread object, start it */
if (thread_main_obj != RT_NULL)
{
rt_thread_startup(thread_main_obj);
}
else
{
/*自定义提示信息*/
error_SchFailed();
}
}
上机实验
开机界面(初始化时完成) 第一个状态(Interface) 第二个状态(Menu)
第三个状态(CheckTouch) 第四个状态(Platte)
完整工程见Github(-RT-Thread-)