结合时间触发+消息+protothread思想+支持优先级的非抢占调度器

2019-12-13 18:20发布

本帖最后由 summarize 于 2013-5-19 14:47 编辑

废话少说,先上stm8s103 IAR库工程代码压缩包。
Schedule-IAR-STM8S103.rar (416.35 KB, 下载次数: 538) 2013-5-19 14:15 上传 点击文件名下载附件

工程是在stm8s103f3单片机上调度通过,已经用消息实现了 UART1_TX模块的共享,即UART1_RX接收到的数据+0x11后再通过UART_TX模块发送回去,同时ADC1 通道3的转换结果也通过UART1_TX模块发送出去.见下图

1.ADC1转换结果每1秒上传一次到PC。测试式给ADC1通道3供的是5V电,所以结果是0x03ff.即1023.

共享时接收到的数据.png (61.3 KB, 下载次数: 0) 下载附件 2013-5-19 14:16 上传

3.支持非抢占式优先级调度,优先级顺序就是创建任务时的顺序,由高到底。其实现思想是,每一个任务运行结束后,都重新回到第一个创建的任务处按顺序查找某个任务是否满足运行条件,所以先创建的任务会先被“发现”其满足运行条件并运行之,核心代码如下

a.任务控制块数据结构
  1. struct SchTcb
  2. {
  3. #if SCH_CFG_Q_EN > 0u
  4.   void          *pData;       //消息指针
  5.   SCH_UINT8 Size;         //消息大小
  6. #endif

  7.   SCH_DLY_TYPE        TimeCounter;  //定时计数器,时基为 "SCH_SYS_TICKS_MS"
  8.   void          (*pTask)();   //任务指针
  9.   struct SchTcb *pNextTCB;    //下一个任务控制块指针
  10. };
复制代码b.调度核心
  1. void SCHTaskSchedStart(void)
  2. {
  3. SCHED_SART:
  4.        
  5.   pCurTCB = pFirstTCB;                        //指向第一个创建的任务,之后按创建时的顺序执行下去

  6.   while (1)                                 
  7.   {
  8.     SCHTimeTick();                            //如果任务Tick满足条件,则将其置于可执行状态

  9.     if (SCH_TASK_RUN == pCurTCB->TimeCounter) //任务处于可执行状态
  10.     {
  11.       pCurTCB->TimeCounter = SCH_TASK_PEND;   //设置为挂起状态,保证任务只执行一次

  12.       pCurTCB->pTask();                       //执行当前任务控制块指向的任务

  13.       goto SCHED_SART;                        //每执行完一个任务,都重新查找一次可执行最高优先级任务
  14.     }

  15.     pCurTCB = pCurTCB->pNextTCB;              //指向下一个任务控制块,查找下个任务是否可执行
  16.   }
  17. }
复制代码“schedule.c”和"schedule.h"已经设置为只读属性,无特殊情况不建议修改,"sch_cfg.h"则为开放给用户的接口,可定义数据类型、调度器节拍和配置是否使用消息。

本人水平有限,欢迎大家测试、指正不足。

友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
101条回答
summarize
2019-12-14 06:42
本帖最后由 summarize 于 2013-7-22 01:46 编辑
soosqt 发表于 2013-6-13 09:14
支持楼主,强列希望出个PIC版本


使用说明文档已经出来了:
自制类OS调试器应用与说明
一、调度器功能说明。
本调度器是集时间触发、支持消息、支持非抢占优先级调度,借鉴了protothread思想,而使得其实现与使用类似于OS的调度器(仿ucos),所以暂时叫类OS调度器吧。
1.     时间触发:即任务可以定时执行,如每间隔一定时间执行一次,应用如定时采样、LED闪烁等,而且此间隔在任务执行过程中是可以修改的。2.     支持消息:即任务除了定时执行方式外,还可以等待消息到了才执行,应用如串口接收处理,即等串口接收完一帧数据后,发消息通知处理任务去处理这些数据。每次只发送一个消息时,也可以当邮箱来用。3.     支持非抢占优先级:任务创建的顺序就是优先级顺序(由高到低)。一般调度器都是所以任务顺序执行,那么所以任务执行一圈的时间有时也是比较长的,如5ms,当有一个任务的实时性要求较高,如2ms内必须响应,那么顺序式调度器就不能满足要求了,而我们的非抢占优先级调度就发挥作用了,因为它是每个任务执行完后都会重新查找下一个就绪的最高优先级任务并执行。这个任务的最快响应时间就由执行时间最长的那个任务决定,如执行时间最长的任务的时间为1.5ms,能满足要求。4.     类OS:因为借鉴了protothread思想,使得其实现与使用类似于OS,同时在实现与风格也仿照了ucos,所以对于熟悉OS的朋友,用起来就更顺心了。而且它也可以作为学习OS的前奏。5.     全C语言实现,移植方便,只须一个硬件定时器为其提供调度节拍的“心跳”即可。二、调度器的移植与应用。
1.     下载调度器相关文件,并包含到自己的工程中下载最新版本的“Schedule V1.01.rar”;网址:
http://www.amobbs.com/thread-5534907-1-1.html
解压后“Schedule”文件夹下有“sch_cfg.h”、“schedule.h”、“schedule.c”(后两个已经设置为只读属性文件);
将这三个文件复制到自己的工程目录下,在工程的main函数中包含“sch_cfg.h”,此头文件会自动包含“schedule.h”。

2.     配置”sch_cfg.h”文件”sch_cfg.h”内有三个地方可配置,如下:
#define SCH_SYS_TICKS_MS              1      //定义调度系统时钟节拍时间(ms)
#define SCH_HW_TIM_MS        0.1   //硬件定时器中断(溢出)周期(ms)

#define SCH_CFG_Q_EN        1u  //任务内建消息使能


个为定义调度系统时钟节拍时间,单位是ms,一般配置为1~10ms即可,对于执行速度快的单片机选如stm8s、avr等配置1ms,传统的51单片机速度慢的选5ms以上比较好,实在不懂就选个10ms吧。

个为硬件定时器中断(溢出)周期,单位也是ms,调度器的时间节拍是基于硬件定时器的,所以最少要有一个硬件定时器并开启中断,还要注意硬件定时的周期必须小于等于调度器的节拍时间,这个硬件定时器的周期也是要根据单片机的执行速度和自己的应用系统要求来定,与调度节拍类似,对于执行速度快的单片机选如stm8s、avr等配置0.1ms,传统的51单片机速度慢的选0.5ms以上比较好,实在不懂就选个1ms。

个为任务内建消息使能选项,根据须要使能或禁止。如果使能,则每个任务控制块会增加如下内容:
#if SCH_CFG_Q_EN > 0u
  void         *pData;       //消息指针
  SCH_UINT8                  Size;         //消息的大小
#endif
如果是比较简单的系统,不须要用到消息传递功能,则可关闭此功能。以节省内存空间。

3.     硬件定时器中断中增加调度器节拍函数调度器的时间节拍是基于系统硬件定时器的,因为它们之间得有“联系”才行,通过在定时器中断中增加宏“SchedTicksInc()”即可。

4.     定义任务控制块(TCB)调度器管理的对像是任务控制块,所以要为每个任务定义自己的任务控制块。
如我们要建立一个vTestTask,则除了编写vTestTask任务函数外,还要定义它的任务控制块TestTaskTcb,定义格式为:SCH_TCB   TestTaskTcb;按此方法,我们可以编写更多的任务vTestTaskN,并定义好它的任务控制块 TestTaskNTcb。

5.     创建任务任务编写完全并定义好它的控制块后,就可以创建任务了, 创建任务的顺序就是任务优先级的顺序,由高到低。创建任务的例子如下:
SCHTaskCreate(&TestTaskTcb, vTestTask);   //创建任务vTestTask;
SCHTaskCreate(&TestTaskNTcb, vTestTaskN); //创建任务vTestTaskN;

6.     启动调度器。任务创建好后,即可启动调度器,调用启动调度器函数:SCHTaskSchedStart();

7.     使用例子。
以上所说的都是只针对调度器而言,对于一个完整的系统,main函数至少应该包含如下部分:
Void main()
{
    disable_interrupt();  //关全局中断
Peripherals_Config();//硬件系统初始化,必须至少包含一个硬件定时器
SCHTaskCreate(&TestTaskTcb, vTestTask);//至少创建一个任务
enable_interrupt();  //开全局中断
SCHTaskSchedStart();//启动调度器,永不返回。
}

定时执行任务例子,每500ms执行一次Fun函数。
void vTestTask (void)
{
Static unsigned char s_u8Var;//定义变量,注意任务内应定义为静态变量

  SCHTaskBegin();  //任务实体内容开始,必须有的固定语句。

         while(1)  //每个任务内都要有一个死循环。同时还要有一个挂起任务的操//如延时或等待消息。同时注意不能使用switch语句,但可在调用函数内使用
  {
    UserFun ();       //可以是语句或函数,调用的函数内部可使用局部变量

    SCHCurTaskDly(500 / SCH_SYS_TICKS_MS);//delay500ms,根据须要调整时间
  }

  SCHTaskEnd();   //任务实体内容结束,必须有的固定语句。
}

等待消息到来再执行的例子,如等待串口接收中断发来消息:
void vUartReceiveData(void)
{
static uint8_t s_u8RxBuffer[UART_RX_BUF_SIZE];
static uint8_t s_u8RxCounter;

static uint8_t *pDataBuffer, u8DataSize;
SCHTaskBegin();

  while(1)
  {
   SCHTaskQpend();           //任务等待接收中断发来消息

   pDataBuffer = (uint8_t *)UartRxTcb.pData;
   u8DataSize  = UartRxTcb.Size;

    for(s_u8RxCounter = 0; s_u8RxCounter < u8DataSize; s_u8RxCounter++)
    {
             s_u8RxBuffer[s_u8RxCounter] =(*pDataBuffer) + 0x11;      pDataBuffer++;
    }

    for(u8DataSize = 0; u8DataSize < 255; u8DataSize++)  //借用u8DataSize

{ //检查UART_TX发送任务队列是否可用

              if (SCHTaskGetQFree(&UartTxTcb) ==SCH_Q_FREE)            {
       SCHTaskQpost(&UartTxTcb,
                     &s_u8RxBuffer[0],
                     s_u8RxCounter); //将接收的的数据+0x11后发回去
       break;
      }
     else
      {
       SCHCurTaskDly(1 / SCH_SYS_TICKS_MS); //delay 1ms
      }
    }
  }

SCHTaskEnd();
}

串口接收中断中接收完一帧数据后向其发送消息:
SCHTaskQpost(&UartRxTcb,//接收处理函数的TCB地址                     &s_u8IsrRxBuffer[0,//串口接收缓冲区首地址                     s_u8IsrRxCounter);   //接收数据大小(字节)
三、调度器任务控制块及各函数说明。
1.“schedule.h”文件下:
      ①任务控制块:struct SchTcb{#if SCH_CFG_Q_EN > 0u  void          *pData;       //消息指针  SCH_UINT8     Size;         //消息的大小#endif   SCH_DLY_TYPE      TimeCounter;  //定时计数器,时基为 "SCH_SYS_TICKS_MS"  void          (*pTask)();   //任务指针  struct SchTcb *pNextTCB;    //下一个任务控制块指针};      ②SchedTicksInc()”,此为调度器节拍计数器递增函数,在硬件定时器中调用。     ③SCHTaskBegin()”,此为固定使用函数,放在任务的开始处(变量定义后)。      ④“SCHTaskEnd()”,此为固定使用函数,放在任务的结尾处。     ⑤“SCHCurTaskPend()”, 挂起(暂停)当前任务,即任务自身。
⑥“SCHCurTaskDly(Ticks)”,当前任务延时Ticks个时间节拍。
        ⑦“SCHTaskCallSub(SubTaskName)”,任务内调用子任务,子任务格式与主任务相同。
⑧“SCHTaskQpend()”,当前任务等待消息(到来后才往下执行)。
2.“schedule.c”文件下:
①“void SCHTaskQpost(SCH_TCB   *pPostTCB,                      void      *pData,                     SCH_UINT8 Size)”,此为释放(发送)消息函数,“pPostTCB”指向要接收消息的任务的任务控制块;“pData”指向消息的首地址;“Size”为消息的大小(字节为单位)。 ②“SCH_UINT8 SCHTaskGetQFree(SCH_TCB   *pTaskTCB)”,此为查询任务消息状态,是否是自由(可用)或忙(不可用),调用SCHTaskQpend()时会将其设置为自由(可用)状态。“pTaskTCB”指向要查询的任务的任务控制块。返回值是“SCH_Q_FREE”或“SCH_Q_BUSY”,即可用或不可用。 ③“void SCHTaskCreate(SCH_TCB           *pNewTCB,                      void              (*pNewTask)(void))”,此为创建任务函数,“pNewTCB”指向要创建的任务的任务控制块,“pNewTask”为要创建任务的首地址(或叫函数名)。     ④“void SCHTaskSchedStart(void)”,此为启动调度器函数,调用后,则开始进行任务调度,每个任务结束后都会重新查找就绪的最高优先级任务并运行,此函数永不返回。    ⑤“void SCHTimeTick(void)”,此为任务节拍处理函数,每个调度节拍到来时,将任务的节拍延时计数器减1(如果其值大于0),由“SCHTaskSchedStart()”函数调用。 Stm32/8和8051的工程模板见帖子:http://www.amobbs.com/thread-5534907-1-1.html
但要注意工程模板那里的调度器并不是最新版本,下载后请自行更新为最新版本。

                              
                                                            2013-07-22


PDF文档:
自制类OS调试器使用说明.pdf (109.4 KB, 下载次数: 502) 2013-7-22 01:17 上传 点击文件名下载附件

一周热门 更多>