分享一个状态机实现类操作系统延时风格的算法

2019-07-20 14:30发布

本帖最后由 FreeRTOS 于 2016-11-22 00:05 编辑

今天看到论坛里的一个求助帖子:
http://www.openedv.com/forum.php ... 39&page=1#pid505654
让我想起了之前一直想仿照contiki来解决传统状态机碰到多个延时不好处理的情况,今天恰好有心情说干就干!
相关资料请查阅contiki源码,本测试程序完全仿照contiki的算法实现,不过是简化版的,大神勿喷
关于状态机的基础知识我这里就不作详细讲解了,好了直接上源码:
[mw_shl_code=applescript,true]#define LINE_NUM_GET(num)   num = __LINE__; case __LINE__:
#define PROCESS_BEGIN(s)    switch(s) { case 0:
#define PROCESS_END()       }

/* 任务结构体 */
typedef struct _TaskStruct
{
    void (*TaskHook)(void);     // 要运行的任务函数
    uint32_t Counter;           // 任务全局节拍
    uint16_t LineNum;           // 行号
    uint8_t Run;                // 程序运行标记:0-不运行,1运行
} TaskStruct;

TaskStruct Tasks[3];


#define PROCESS_DELAY(task, n)      do                                             
                                    {                                               
                                        task.Counter = GlobalTimerCnt;              
                                        LINE_NUM_GET(task.LineNum);                 
                                        if( (GlobalTimerCnt - task.Counter) > n )   
                                        {                                          
                                            break;                                 
                                        }                                          
                                        return;                                    
                                    } while(0)[/mw_shl_code]
如果对contiki没了解过的话,看起来会有些吃力,这里我大概说明下。
在每个任务的开头与结尾必须是 PROCESS_BEGIN(s)和PROCESS_END(),至于这两个宏是什么等下再解释
然后就是仿照我们平时使用OS时最常用方式:在while(1)里完成对应的任务
好了,接下来就是最关键的部分PROCESS_DELAY(task, n),这部分代码就是以状态机来实现类操作系统延时!
说太多口水都干了,直接贴几个任务看看使用方法,使用非常简单:
[mw_shl_code=applescript,true]/**********************************************/
/*                                            */
/*                  任务1                     */
/*                                            */
/**********************************************/
void test_process1(void)
{
    PROCESS_BEGIN(Tasks[0].LineNum);
   
    while (1)
    {
        PROCESS_DELAY(Tasks[0], 1000);
        Uart1SendString("rocess1 Delay1 Test! ");
        
        PROCESS_DELAY(Tasks[0], 2000);
        Uart1SendString("rocess1 Delay2 Test! ");
        
        PROCESS_DELAY(Tasks[0], 1500);
        Uart1SendString("rocess1 Delay3 Test! ");
    }
   
    PROCESS_END();
}


/**********************************************/
/*                                            */
/*                  任务2                     */
/*                                            */
/**********************************************/
void test_process2(void)
{
    PROCESS_BEGIN(Tasks[1].LineNum);
   
    while (1)
    {
        PROCESS_DELAY(Tasks[1], 700);
        Uart1SendString("rocess2 Delay1 Test! ");
        
        PROCESS_DELAY(Tasks[1], 1500);
        Uart1SendString("rocess2 Delay2 Test! ");
    }
   
    PROCESS_END();
}


/**********************************************/
/*                                            */
/*                  任务3                     */
/*                                            */
/**********************************************/
void test_process3(void)
{
    PROCESS_BEGIN(Tasks[2].LineNum);
   
    while (1)
    {
        PROCESS_DELAY(Tasks[2], 500);
        Led_Toggle(LED1);
    }
   
    PROCESS_END();
}[/mw_shl_code]
任务1与任务2是串口每隔一段时间打印出测试字符串,任务3是小灯最喜欢的跑灯任务,没有之一!!!

下面就是创建任务,主要是对结构体成员LineNum清零(必须)和设置任务回调函数:
[mw_shl_code=applescript,true]/* 任务初始化 */
    memset((void*)Tasks, 0, sizeof(Tasks));
    Tasks[0].TaskHook = test_process1;
    Tasks[1].TaskHook = test_process2;
    Tasks[2].TaskHook = test_process3;[/mw_shl_code]

好了接下来看执行,执行跟状态机一样,就是各个任务对应的函数轮着来,值得注意的是并没有任务在原地死等,所有任务都以查询的方式进入和退出!!!
[mw_shl_code=applescript,true]while(1)
    {
        /* 任务执行,本质还是状态机,但类似操作系统风格 */
        for(i=0; i<3; i++)
        {
            Tasks.TaskHook();
        }
    }[/mw_shl_code]

到这里就完成了以状态机的方式来实现类操作系统风格的延时!可能各位对代码一时摸不着头脑,我大概说下算法的思想。
把宏PROCESS_BEGIN()与PROCESS_END()展开可以发现其实就是一个switch结构,任务里面的延时调用了PROCESS_DELAY()之后
会把行号记录在任务结构成员LineNum,而算法的精髓也是在于这里,每次调用任务的回调函数时switch结构会跳转到记录行号的位置
然后查询任务结构体成员Counter的值是否已达到延时的节拍,如果还没延时完毕就直接return,任务不需要在原地死等。由于所有任务共用一个堆栈,因此任务中不能出现临时变量,如果实在需要使用变量,那么就遵从contiki的建议使用静态变量,
个人不建议使用全局变量,没别的原因,主要是看起来太TM烦!

算法的大概思想就是这样,小灯近段时间烦心事太多了,所以没有来得及检查代码,只是粗略测试了好像没问题,希望各位大神
能帮忙检查下然后把问题报告给我,我会第一时间修改!

小灯采用阿波罗STM32F7开发板做的测试,如果你们手头上的板子不是的话,要么光顾下原子网店,否则就自行修改下吧。
Process_Delay.rar (1.08 MB, 下载次数: 1266) 2016-11-21 23:51 上传 点击文件名下载附件


友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
37条回答
ianhom
2019-07-20 22:38
赞一个!
我也写了一个实现类似的延时,欢迎大神前来指导工作 https://github.com/ianhom/MOE/bl ... Demo/Task_PT_Demo.c  
[mw_shl_code=c,true]
    while(1)
    {
        TASK_PT_DEMO_LED_On(LED_RED);
        PT_DELAY(1000);
        TASK_PT_DEMO_LED_Off(LED_RED);

        TASK_PT_DEMO_LED_On(LED_GREEN);
        PT_DELAY(1000);
        TASK_PT_DEMO_LED_Off(LED_GREEN);

        TASK_PT_DEMO_LED_On(LED_BLUE);
        PT_DELAY(1000);
        TASK_PT_DEMO_LED_Off(LED_BLUE);
}[/mw_shl_code]

一周热门 更多>