【源代码附在最后,其中如有涉及到的其他函数实现过程,会在后面发布专门的篇章进行解析】

STM32WB55任务调度器,是通过定义不同的uint32类型变量,协同进行任务管理。

(1)这32bit,1个bit代表1个任务,因此可将每个变量当作列表去理解(这些列表并不是真正意义上完整的列表,只是当作列表在使用)

【后续源码讲解过程,这些变量也将按照列表的说法展开】

(2)该任务调度器具备优先级,每个优先级下最多有32个任务

        第一层优先级是优先级任务列表,第二层是同优先级任务列表中不同任务的ID(即所占bit位置,位置越高,优先级越高)

(3)同优先级下的任务按照任务注册时给的ID号顺序执行,ID号越高代表该任务在同级优先级任务列表下的执行优先级越高

注:建议使用枚举类型参数给分配ID号,避免任务注册时被覆盖

一、初始化

/**
 * @brief  This function initializes the sequencer resources.
 *
 * @note   It shall not be called from an ISR.
 *
 */
void UTIL_SEQ_Init( void );

1.相关参数初始化

(1)任务激活状态【任务激活列表】初始化为0(当某个任务激活时,根据ID号将对应的bit置1)

eg.TasdSet初始状态为0,当激活一个ID号为1的任务时,TaskSet被修改为1

(2)任务运行状态【运行态任务列表】初始化为~0U,即32bit全部初始化为1【对应bit为1说明该任务为运行态,为0说明该任务被挂起】

(3)任务阻塞状态【阻塞态任务列表】初始化为~0U,即32bit全部初始化为1【对应bit为1说明该任务未阻塞,为0说明该任务被阻塞】(此处的阻塞并不一定是真的阻塞,当不想某个任务被调度器安排执行时也可将对应bit置1,调度器会忽略该任务)

(4)事件等待完成标志【事件等待完成列表】初始化为0(当某个任务获取到等待的事件时,该任务对应的bit位置1)【等待结束

(5)事件等待标志【事件等待列表】初始化位0(当某个任务等待事件时,该任务对应的bit位置1)【等待过程

(6)当前执行的任务ID初始化为0

2.将后期存储具体任务的函数指针数组TaskCb所有元素初始化为0

该函数指针数组原型声明如下

3.将当前所有的优先级任务列表初始化

(1)每个优先级任务列表包含的所有任务激活状态初始化为0,即未激活(TaskPrio数组下标数字越小,该任务列表所代表的优先级越高

(2)每个优先级任务列表的所有任务运行状态初始化为0(0表示对应任务已运行,1表示对应任务未运行

4.该函数接口未实现,无实际意义,可忽略

二、运行

/**
 * @brief This function requests the sequencer to execute all pending tasks using round robin mechanism.
 *        When no task are pending, it calls UTIL_SEQ_Idle();
 *        This function should be called in a while loop in the application
 *
 * @param Mask_bm list of task (bit mapping) that is be kept in the sequencer list.
 *
 * @note  It shall not be called from an ISR.
 * @note  The construction of the task must take into account the fact that there is no counting / protection
 *        on the activation of the task. Thus, when the task is running, it must perform all the operations
 *        in progress programmed before its call or manage a reprogramming of the task.
 *
 */
void UTIL_SEQ_Run( UTIL_SEQ_bm_t Mask_bm );

1.备份阻塞任务列表信息,将传递的最新阻塞态任务列表Mask_bm和当前阻塞态任务列表SuperMask按位与,获取到最新需要执行的任务列表【具体某个任务是否执行,还取决于该任务的其它状态标记】

(1)任务在执行过程中,如果已经执行过的任务就绪,但当前优先级列表任务还未执行结束,那么已经就绪的任务不会执行,直至本轮任务全部执行结束,才会重头开始执行【不存在抢占式调度】。

(2)此处的按位与,在官方例程的框架中主要作用体现在任务调度器运行的嵌套,当任务运行过程中有一个任务需要等待事件时,任务调度器就会停在该位置死等,直到事件获取为止。同时开启新一轮的任务调度器:

        1)继续执行后续任务,直至事件获取结束,结束当前新一轮调度器,回到上一次开启的调度器。

        2)如果在新一轮调度器执行完成后,事件还未获取,那么此时的调度器依然会执行,但是屏蔽掉了等待事件的任务ID,即在阻塞列表中进行了标记,在该调度器执行过程中所有该ID位置所标识的任务均不会执行,这是当前调度器的一个缺陷

2.根据任务激活列表(TaskSet)、运行态任务列表(TaskMask)、阻塞态任务列表(SuperMask),三者按位与,结合是否有事件需要等待(以及等待的状态),判断是否有任务需要运行

2.1有任务需要运行【有可执行的任务,且无等待事件(或等待事件还未完成)】

(1)清空计数值count,进一步确定当前需要执行的最高优先级任务列表,并记录该优先级任务列表位置count

(2)获取到当前需要执行的任务列表

(3)如果当前的优先级任务列表中任务均未执行,则将当前优先级任务列表所有任务的运行状态标记为未运行

(4)获取当前要执行的最新任务列表中,优先级最高的任务ID,即最大ID

注:最大ID获取方式为从高位向低位判断有多少个bit为0,即可得到当前任务列表中最高优先级任务ID,SEQ_BitPosition函数实现如下

(5)将当前优先级任务列表中准备执行的任务状态修改为已运行

(6)代码在临界段内,将准备执行的任务从激活任务列表TaskSet和所有优先级任务列表TaskPrio[counter].priority移除

注:TaskSet在对应任务执行完成后,相对应的bit就会置0,标识该任务未激活,此处对于整个调度器框架而言具有非常的意义:

        1)当多个优先级任务列表同一个任务激活时【既一个任务在不同的优先级列表中激活】,只能执行最高优先级列表的任务,其余该位置任务被从相关任务列表移除

        2)理论上每个优先级任务列表都能最多有32个任务,但后面篇章讲到任务注册时,就会发现该调度器框架真正能注册的任务数量就是32个,其余列表最终都是为了这个注册任务的函数指针数组所服务

(7)执行该任务所关联的函数

(8)修改相关任务列表(临时列表),并从2开始继续执行,直至无任务需要执行

3.当前运行任务ID置为默认,下面的函数体没有实现,可忽略

4.低功耗状态进入临界段,修改相关临时参数,无任务需要执行且无等待事件(或等待事件还未获取)则进入低功耗模式

【该程序框架低功耗进入临界段依然是普通方式进入临界段,进入低功耗模式函数未真正实现,可忽略】

5.阻塞态任务列表恢复当前调度器运行前的状态

至此,任务调度器的初始化和运行相关源码解析结束。

【如有理解有误地方,欢迎指正,勿喷】

【内容对你有帮助的话,点个关注吧~ 你的支持是我更新的动力!】

/* 调度器初始化 */
void UTIL_SEQ_Init( void )
{
  TaskSet = UTIL_SEQ_NO_BIT_SET;
  TaskMask = UTIL_SEQ_ALL_BIT_SET;
  SuperMask = UTIL_SEQ_ALL_BIT_SET;
  EvtSet = UTIL_SEQ_NO_BIT_SET;
  EvtWaited = UTIL_SEQ_NO_BIT_SET;
  CurrentTaskIdx = 0U;
  (void)UTIL_SEQ_MEMSET8((uint8_t *)TaskCb, 0, sizeof(TaskCb));
  for(uint32_t index = 0; index < UTIL_SEQ_CONF_PRIO_NBR; index++)
  {
      TaskPrio[index].priority = 0;
      TaskPrio[index].round_robin = 0;
  }
  UTIL_SEQ_INIT_CRITICAL_SECTION( );
}

/* 调度器运行 */
void UTIL_SEQ_Run( UTIL_SEQ_bm_t Mask_bm )
{
  uint32_t counter;
  UTIL_SEQ_bm_t current_task_set;
  UTIL_SEQ_bm_t super_mask_backup;
  UTIL_SEQ_bm_t local_taskset;
  UTIL_SEQ_bm_t local_evtset;
  UTIL_SEQ_bm_t local_taskmask;
  UTIL_SEQ_bm_t local_evtwaited;

  /*
   * When this function is nested, the mask to be applied cannot be larger than the first call
   * The mask is always getting smaller and smaller
   * A copy is made of the mask set by UTIL_SEQ_Run() in case it is called again in the task
   */
  super_mask_backup = SuperMask;
  SuperMask &= Mask_bm;

  /*
   * There are two independent mask to check:
   * TaskMask that comes from UTIL_SEQ_PauseTask() / UTIL_SEQ_ResumeTask
   * SuperMask that comes from UTIL_SEQ_Run
   * If the waited event is there, exit from  UTIL_SEQ_Run() to return to the
   * waiting task
   */
  local_taskset = TaskSet;
  local_evtset = EvtSet;
  local_taskmask = TaskMask;
  local_evtwaited =  EvtWaited;
  while(((local_taskset & local_taskmask & SuperMask) != 0U) && ((local_evtset & local_evtwaited)==0U))
  {
    counter = 0U;
    /*
     * When a flag is set, the associated bit is set in TaskPrio[counter].priority mask depending
     * on the priority parameter given from UTIL_SEQ_SetTask()
     * The while loop is looking for a flag set from the highest priority maskr to the lower
     */
    while((TaskPrio[counter].priority & local_taskmask & SuperMask)== 0U)
    {
      counter++;
    }

    current_task_set = TaskPrio[counter].priority & local_taskmask & SuperMask;

    /*
     * The round_robin register is a mask of allowed flags to be evaluated.
     * The concept is to make sure that on each round on UTIL_SEQ_Run(), if two same flags are always set,
     * the sequencer does not run always only the first one.
     * When a task has been executed, The flag is removed from the round_robin mask.
     * If on the next UTIL_SEQ_RUN(), the two same flags are set again, the round_robin mask will mask out the first flag
     * so that the second one can be executed.
     * Note that the first flag is not removed from the list of pending task but just masked by the round_robin mask
     *
     * In the check below, the round_robin mask is reinitialize in case all pending tasks haven been executed at least once
     */
    if ((TaskPrio[counter].round_robin & current_task_set) == 0U)
    {
      TaskPrio[counter].round_robin = UTIL_SEQ_ALL_BIT_SET;
    }

  /*
   * Read the flag index of the task to be executed
	 * Once the index is read, the associated task will be executed even though a higher priority stack is requested
	 * before task execution.
	 */
    CurrentTaskIdx = (SEQ_BitPosition(current_task_set & TaskPrio[counter].round_robin));

    /*
     * remove from the roun_robin mask the task that has been selected to be executed
     */
    TaskPrio[counter].round_robin &= ~(1U << CurrentTaskIdx);

    UTIL_SEQ_ENTER_CRITICAL_SECTION( );
    /* remove from the list or pending task the one that has been selected to be executed */
    TaskSet &= ~(1U << CurrentTaskIdx);
    /* remove from all priority mask the task that has been selected to be executed */
    for (counter = UTIL_SEQ_CONF_PRIO_NBR; counter != 0U; counter--)
    {
      TaskPrio[counter - 1U].priority &= ~(1U << CurrentTaskIdx);
    }
    UTIL_SEQ_EXIT_CRITICAL_SECTION( );

    /* Execute the task */
    TaskCb[CurrentTaskIdx]( );
    /* 每执行一个任务喂狗一次 */
    //HAL_IWDG_Refresh(&hiwdg);
    
    local_taskset = TaskSet;
    local_evtset = EvtSet;
    local_taskmask = TaskMask;
    local_evtwaited = EvtWaited;
  }

  /* the set of CurrentTaskIdx to no task running allows to call WaitEvt in the Pre/Post ilde context */
  CurrentTaskIdx = UTIL_SEQ_NOTASKRUNNING;
  UTIL_SEQ_PreIdle( );

  UTIL_SEQ_ENTER_CRITICAL_SECTION_IDLE( );
  local_taskset = TaskSet;
  local_evtset = EvtSet;
  local_taskmask = TaskMask;
  if ((local_taskset & local_taskmask & SuperMask) == 0U)
  {
    if ((local_evtset & EvtWaited)== 0U)
    {
      UTIL_SEQ_Idle( );
    }
  }
  UTIL_SEQ_EXIT_CRITICAL_SECTION_IDLE( );

  UTIL_SEQ_PostIdle( );

  /* restore the mask from UTIL_SEQ_Run() */
  SuperMask = super_mask_backup;

  return;
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐