Sequencer

1. Introduction

The integration cost of a real time operating system can be penalizing in the case of a simple application:

  • Required RTOS skills
  • Increase the complexity of the application
  • Impact on sizing of RAM/ROM

The sequencer utility has been designed as a simple alternative to using a real-time operating system for less complex application cases. However, it does not cover all the services provided by an operating system. For instance, this software solution does not offer a preemption mechanism, which must be considered in the application design. We suggest using reentrant functions based on state machines instead of tasks that could potentially freeze the system. The following graphic shows how the sequencer handles the execution of an application.

#Task waiting for an event event#Scheduler#Scheduler#Scheduler#Scheduler#SchedulerUtility sequencer overview.jpg
  • Task creation: allows to initialize the task and render it callable by the internal scheduler of the sequencer.
  • Task enable: through a task or an interrupt, the task is enabled, and so could be executed by the scheduler.
  • Task Pause/Resume: allows to pause/resume the task execution from a scheduler point of view independent of whether the task is enabled or not.
  • Idle task: if the scheduler has not task to execute it call an optional hook function to manage entry in idle mode.
  • Task execution: calls the function associated to the task, the scheduler is locked until the function returns.
  • Sequencer: embeds a task scheduler, which sequences the task execution and also allows the task to stop until an event reception

2. Important things to know

To avoid any mismatch with the need of the application, it is important to understand what the sequencer is:

  • The sequencer is NOT an operating system.
  • The goal is not to compete with standard operating systems, but rather with standard bare metal implementations.
  • It is an optimized packaging of a while loop bare metal classic implementation.
  • It helps to avoid race conditions, which are often encountered in bare metal implementations, particularly when low power modes are implemented.

Additionally, it would be helpful to know what features are provided by the sequencer:

  • Up to 32 tasks are registered
  • Request a task to be executed
  • Pause and resume a task
  • Wait for a specific event (which might be not blocking)
  • Priority on tasks
  • Allow to manage an IDLE task

3. Example

The purpose of this section is to provide examples of how to use the different APIs and demonstrate the operation of the sequencer using an IO toggle. Users can connect this IO to an LED or an oscilloscope to observe the state of the IO. The examples illustrate the following basic functions of the sequencer:

  • Order the execution of a task
  • Deal with the low power
  • Pause/resume a task
  • Manage a task waiting for an event

3.1. Task managing GPIO toggle

This simple example shows how to handle a GPIO toggle with the sequencer:

  • TASK0: manage the update of the GPIO state
  • SysTick handler: order the sequencer to execute the TASK0 every 400 ms.

3.1.1. How to generate this example

  1. The first step is to generate, with STM32CubeMX, a project corresponding to your hardware.
    • The project must initialize a GPIO in output mode and associate "user label" to this GPIO (GPIO_TOGGLE).
  2. The second step is to update the project generated by STM32CubeMX
    • add the sequencer file stm32_seq.c inside the project (the file is located inside the firmware package /firmware/utilities/sequencer)
    • add the sequencer path to the include list /firmware/utilities/sequencer
    • copy the file /firmware/utilities/conf/utilities_conf_template.h inside your project as utlities_conf.h
  3. The third step is to update the code (refer to the STM32CubeMX tag to place the code)

update the file main.h

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN includes */
/* sequencer include */
#include "stm32_seq.h"
/* USER CODE END includes */
/* Exported constants --------------------------------------------------------*/
/* USER CODE BEGIN EC */
/* Task ID definition */
#define TASK0    1 << 0
/* USER CODE END EC */

update the file main.c

/* USER CODE BEGIN PFP */
/* task function prototype */
void function_Task0( void );
/* USER CODE END PFP */
  /* USER CODE BEGIN WHILE */
  /* sequencer initialization */
  UTIL_SEQ_Init();
  /* sequencer task registration */
  UTIL_SEQ_RegTask(TASK0, 0, function_Task0);
  while (1)
  {
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
	/* run the sequencer */  
	UTIL_SEQ_Run(~0);  
  }
   /* USER CODE END 3 */
}
/* USER CODE BEGIN 4 */
/* task definition */
void function_Task0( void )
{
	HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);
}
/* USER CODE END 4 */

update the file stm32xxxx_it.c :

void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */
  /* each 400 ms set task 0 to be run */
  if ((HAL_GetTick() % 400) == 0)
  {
	  UTIL_SEQ_SetTask(TASK0, 0);
  }
  /* USER CODE END SysTick_IRQn 1 */
}

3.1.2. MSC of the example

The MSC shows how the example works, we can easily identify two different parts

Utility gpio toggle lowpower.png
  • The first is the initialization of the sequencer
    • UTIL_SEQ_init initializes the sequencer environment
    • UTIL_SEQ_RegTask registers a TASK0 and associate a function to execute
  • The second part is an infinite loop on the UTIL_SEQ_Run function
    • The SysTick interrupt handler orders the sequencer to execute the TASK0 every 400 ms
    • When the sequencer detects a request to execute the TASK0, it calls the associated function

3.2. Task IDLE and low power management

This example is an evolution of the GPIO toggle example and shows how the sequencer can help create a low power application. Compared to the previous example, we need to add:

  • An IDLE task is responsible for putting the application in low power mode.
  • A counter time compatible with the low power mode (in our case the LPTIM IP) is responsible for exiting the low power mode when the counter expires.

The application involves initiating the counter with an autoreload feature. When an interrupt is generated upon the counter's expiration, the sequencer is requested to execute the task.

3.2.1. How to generate this example

  1. The first step is to generate with STM32CubeMX a project corresponding to your hardware, the project must initialize
    • a GPIO in output mode and associate "user label" to this GPIO (GPIO_TOGGLE).
    • RCC to enable the LSI clock and set the LSI clock as a source of the LPTIM
    • LPTIM with divider 32 (get a time reference of 1 ms with LSI) and enable the interrupt
  2. The second step is to update the project generated by STM32CubeMX
    1. add the sequencer file stm32_seq.c inside the project (the file is located inside the firmware package /firmware/utilities/sequencer)
    2. add the sequencer path to the include list /firmware/utilities/sequencer
    3. copy the file /firmware/utilities/conf/utilities_conf_template.h inside your project as utlities_conf.h
  3. The third step is to update the code (refer to the STM32CubeMX tag to place the code)

update the file main.h

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN includes */
/* sequencer include */
#include "stm32_seq.h"
/* USER CODE END includes */
/* Exported constants --------------------------------------------------------*/
/* USER CODE BEGIN EC */
/* Task ID definition */
#define TASK0    1 << 0
/* USER CODE END EC */

update the file main.c

/* USER CODE BEGIN PFP */
/* task function prototype */
void function_Task0( void );
/* USER CODE END PFP */
  /* USER CODE BEGIN WHILE */
  /* sequencer initialization */
  UTIL_SEQ_Init();
  /* sequencer task registration */
  UTIL_SEQ_RegTask(TASK0, 0, function_Task0);
  /* Start LPTIM counter with time of 400 ms */
  HAL_LPTIM_Counter_Start_IT(&hlptim1, 400 - 1);
  while (1)
  {
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
	/* run the sequencer */  
	UTIL_SEQ_Run(~0);  
  }
   /* USER CODE END 3 */
}
/* USER CODE BEGIN 4 */
/* task definition */
void function_Task0( void )
{
	HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);
}
/* LPTIM callback used to run task 0 */
void HAL_LPTIM_AutoReloadMatchCallback(LPTIM_HandleTypeDef *hlptim)
{
  if (hlptim->Instance == LPTIM1)
  {
	UTIL_SEQ_SetTask(TASK0, 0);
  }
}
/* Override IDLE sequencer function to manage the low power */
void UTIL_SEQ_Idle( void )
{
 HAL_SuspendTick();
 HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFI);
 SystemClock_Config();
 HAL_ResumeTick();
}
/* USER CODE END 4 */

3.2.2. MSC of the example

The MSC shows how the example works, we can easily identify two different parts:

Utility gpio toggle lowpower.png
  • the first is the initialization of the sequencer
    • UTIL_SEQ_init initializes the sequencer environment
    • UTIL_SEQ_RegTask registers a TASK0 and associate a function to execute
  • the second part is an infinite loop on the UTIL_SEQ_Run function however the MSC show twice call to the function. This is aligned with the function algorithm that checks if there is a task to execute, enters low power mode and exits. The display of the two calls helps to understand how the sequencer manages the scheduling of the task in this specific case.
    • On the first call of the UTIL_SEQ_Run function
      • There is no task to execute. The sequencer enter execute the IDLE task.
      • The interrupt exit the system from the low power mode and execute the interrupt function that request the sequencer to execute the TASK0
      • The UTIL_SEQ_Run function continues its execution to complete the call to the IDLE task and exit
    • On the second call of the UTIL_SEQ_Run function
      • The sequencer detects a request to execute the TASK0, it calls the associated function
      • And call the IDLE task again until the next system wake-up and loop on this sequence

3.2.3. low power measurement

Thanks to ST low power monitor tools, see below a graph example of the power consumption

Utility lowpower meas.png

3.3. Task priority management

This example shows how the sequencer manage the task priority; three tasks are defined:

  • Task 0: order the execution of the task 0 with low priority, the task 1 with medium priority and task 2 with the highest priority.
  • Task 1: toggle GPIO with a frequency of 0.5 Hz for a duration of 5 seconds
  • Task 2: toggle GPIO with a frequency of 4 Hz for a duration of 5 seconds

The sequencer orders the execution of the tasks according to their priority.

3.3.1. How to generate this example

  1. The first step for the generation of this example is to generate with STM32CubeMX a project corresponding to your hardware, the project must initialize
    • a GPIO in output mode and associate "user label" to this GPIO (GPIO_TOGGLE).
  2. The second step is to update the project generated by STM32CubeMX
    1. add the sequencer file stm32_seq.c inside the project (the file is located inside the firmware package /firmware/utilities/sequencer)
    2. add the sequencer path to the include list /firmware/utilities/sequencer
    3. copy the file /firmware/utilities/conf/utilities_conf_template.h inside your project as utlities_conf.h
  3. The third step is to update the code (refer to STM32CubeMX tag to place the code)

update utilites_conf.h

/******************************************************************************
 * sequencer
 * (any macro that does not need to be modified can be removed)
 ******************************************************************************/
#define UTIL_SEQ_INIT_CRITICAL_SECTION( )
#define UTIL_SEQ_ENTER_CRITICAL_SECTION( )      UTILS_ENTER_CRITICAL_SECTION( )
#define UTIL_SEQ_EXIT_CRITICAL_SECTION( )       UTILS_EXIT_CRITICAL_SECTION( )
#define UTIL_SEQ_CONF_TASK_NBR                  (32U)
#define UTIL_SEQ_CONF_PRIO_NBR                  (3U)
#define UTIL_SEQ_MEMSET8( dest, value, size )   UTILS_MEMSET8((dest),(value),(size))

update the file main.c

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN includes */
/* sequencer include */
#include "stm32_seq.h"
/* USER CODE END includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* Task ID definition */
#define TASK0    1 << 0
#define TASK1    1 << 1
#define TASK2    1 << 2
/* Priority ID definition */
#define PRIO_HIGH   0
#define PRIO_MEDUIM 1
#define PRIO_LOW    2
/* Application define */
#define TASK1_FREQ       1000/1
#define TASK2_FREQ       1000/8
#define TASK_TOGGLE_TIME 5000
/* USER CODE END PTD */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
/* USER CODE BEGIN PFP */
void function_Task0( void );
void function_Task1( void );
void function_Task2( void );
/* USER CODE END PFP */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  /* sequencer initialization */
  UTIL_SEQ_Init();
  /* sequencer task registration */
  UTIL_SEQ_RegTask(TASK0, 0, function_Task0);
  UTIL_SEQ_RegTask(TASK1, 0, function_Task1);
  UTIL_SEQ_RegTask(TASK2, 0, function_Task2);
  UTIL_SEQ_SetTask(TASK0, PRIO_LOW);
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    /* Run the sequencer */
    UTIL_SEQ_Run(~0);
  }
  /* USER CODE END 3 */
/* USER CODE BEGIN 4 */
/* task0 function definition */
void function_Task0( void )
{
	UTIL_SEQ_SetTask(TASK0, PRIO_LOW);
	UTIL_SEQ_SetTask(TASK1, PRIO_MEDUIM);
	UTIL_SEQ_SetTask(TASK2, PRIO_HIGH);
}
/* task1 function definition */
void function_Task1( void )
{
	uint32_t time_start = HAL_GetTick();
	do {
	  HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);
	  HAL_Delay(TASK1_FREQ);
	} while(HAL_GetTick() - time_start < TASK_TOGGLE_TIME);
}
/* task2 function definition */
void function_Task2( void )
{
	uint32_t time_start = HAL_GetTick();
	do {
	  HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);
	  HAL_Delay(TASK2_FREQ);
	} while(HAL_GetTick() - time_start < TASK_TOGGLE_TIME);
}
/* USER CODE END 4 */

3.3.2. MSC of the example

The MSC shows how the example works, we can easily identify two different parts Utility task prio.png

  • the first part is the initialization of the sequencer
    • UTIL_SEQ_init initializes the sequencer environment
    • UTIL_SEQ_RegTask registers a TASK0 and associate a function to execute
    • UTIL_SEQ_RegTask registers a TASK1 and associate a function to execute
    • UTIL_SEQ_RegTask registers a TASK2 and associate a function to execute
    • UTIL_SEQ_SetTask(TASK0, PRIO_LOW): to request a first execution of the TASK0
  • the second part is an infinite loop on the UTIL_SEQ_Run function
    • TASK0 is executed
      • Reset the GPIO
      • Wait 2 seconds
      • Request to execute TASK0 with PRIO_LOW
      • Request to execute TASK1 with PRIO_MEDUIM
      • Request to execute TASK2 with PRIO_HIGH
    • TASK1 is executed; Toggle the GPIO with a frequency of 0.5 Hz
    • TASK2 is executed; Toggle the GPIO with a frequency of 4 Hz

(*) We are in a special case, because the TASK0 requests his own execution so this has impacts:

  • The UTIL_SEQ_Run never exits
  • The UTIL_SEQ_Run never calls the IDLE task

3.4. Task pause/resume

This example is an updated version of the task priority management example. However, in this case, the 'pause/resume' mechanism will be utilized to modify the task execution order.

TASK0: wait 2 seconds, enable for execution all the task (TASK0, TASK1, TASK2) and put TASK2 in suspend (means that the task is frozen and the sequencer discard the task execution)
TASK1: execute a GPIO toggle with a frequency TASK1_FREQ and resume TASK2 (allow the sequencer to execute the task)
TASK2: execute a GPIO toggle with a frequency TASK2_FREQ


3.4.1. How to generate this example

Apply the task priority management example and update task functions as below

/* USER CODE BEGIN 4 */
/* task0 function definition */
void function_Task0( void )
{
	HAL_Delay(2000);
	UTIL_SEQ_SetTask(TASK0, PRIO_LOW);
	UTIL_SEQ_SetTask(TASK1, PRIO_MEDUIM);
	UTIL_SEQ_SetTask(TASK2, PRIO_HIGH);
	UTIL_SEQ_PauseTask(TASK2);
}
/* task1 function definition */
void function_Task1( void )
{
	uint32_t time_start = HAL_GetTick();
	do {
	  HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);
	  HAL_Delay(TASK1_FREQ);
	} while(HAL_GetTick() - time_start < TASK_TOGGLE_TIME);
	UTIL_SEQ_ResumeTask(TASK2);
}
/* task2 function definition */
void function_Task2( void )
{
	uint32_t time_start = HAL_GetTick();
	do {
	  HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);
	  HAL_Delay(TASK2_FREQ);
	} while(HAL_GetTick() - time_start < TASK_TOGGLE_TIME);
}
/* USER CODE END 4 */

3.4.2. MSC of the example

The MSC shows how the example works, we can easily identify two different parts: Utility task suspend.png

  • the first part is the initialization of the sequencer
    • UTIL_SEQ_init initializes the sequencer environment
    • UTIL_SEQ_RegTask registers a TASK0 and associate a function to execute
    • UTIL_SEQ_RegTask registers a TASK1 and associate a function to execute
    • UTIL_SEQ_RegTask registers a TASK2 and associate a function to execute
    • UTIL_SEQ_SetTask(TASK0, PRIO_LOW) : to request a first execution of the TASK0
  • the second part is an infinite loop on the UTIL_SEQ_Run function
    • TASK0 is executed
      • Reset the GPIO
      • Wait 2 seconds
      • Request to execute TASK0 with PRIO_LOW
      • Request to execute TASK1 with PRIO_MEDUIM
      • Request to execute TASK2 with PRIO_HIGH
      • Pause the TASK2
    • TASK1 is executed
      • Toggle the GPIO with a frequency of 4 Hz
      • Resume the TASK2
    • TASK2 is executed; Toggle the GPIO with a frequency of 0.5 Hz

(*) We are in a special case, because the TASK0 requests his own execution so this has impacts:

  • The UTIL_SEQ_Run never exits
  • The UTIL_SEQ_Run never calls the IDLE task

3.5. Task waiting for an event

This example is an update of the task priority management example, the TASK2 wait for an event generated by TASK1.

3.5.1. How to generate this example

Apply the task priority management example and update the task functions as below

update file main.c

/* Event definition */
#define EVENT0   1 << 0
/* #define SUSPEND */
/* USER CODE END PTD */
/* USER CODE BEGIN 4 */
/* task0 function definition */
void function_Task0( void )
{
	UTIL_SEQ_SetTask(TASK0, PRIO_LOW);
	UTIL_SEQ_SetTask(TASK1, PRIO_MEDUIM);
	UTIL_SEQ_SetTask(TASK2, PRIO_HIGH);
}
/* task1 function definition */
void function_Task1( void )
{
	uint32_t time_start = HAL_GetTick();
	do {
	  HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);
	  HAL_Delay(TASK1_FREQ);
	} while(HAL_GetTick() - time_start < TASK_TOGGLE_TIME);
	UTIL_SEQ_SetEvt(EVENT0);
}
/* task2 function definition */
void function_Task2( void )
{
	uint32_t time_start;
	UTIL_SEQ_WaitEvt(EVENT0);
	time_start = HAL_GetTick();
	do {
	  HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);
	  HAL_Delay(TASK2_FREQ);
	} while(HAL_GetTick() - time_start < TASK_TOGGLE_TIME);
}
/* USER CODE END 4 */

3.5.2. MSC of the example

The MSC shows how the example works, we can easily identify two different parts Utility task wait event.png

  • the first part is the initialization of the sequencer
    • UTIL_SEQ_init initializes the sequencer environment
    • UTIL_SEQ_RegTask registers a TASK0 and associate a function to execute
    • UTIL_SEQ_RegTask registers a TASK1 and associate a function to execute
    • UTIL_SEQ_RegTask registers a TASK2 and associate a function to execute
    • UTIL_SEQ_SetTask(TASK0, PRIO_LOW) : to request a first execution of the TASK0
  • the second part is an infinite loop on the UTIL_SEQ_Run function
    • TASK0 is executed
      • Reset the GPIO
      • Wait 2 seconds
      • Request to execute TASK0 with PRIO_LOW
      • Request to execute TASK1 with PRIO_MEDUIM
      • Request to execute TASK2 with PRIO_HIGH
    • TASK2 is executed and stops to wait for EVENT0
    • TASK1 is executed
      • Toggle the GPIO with a frequency of 4 Hz
      • Generate EVENT0
    • TASK2 continues his execution

(*) We are in a special case, because the TASK0 requests his own execution so this has impacts:

  • The UTIL_SEQ_Run never exits
  • The UTIL_SEQ_Run never calls the IDLE task

4. Implementation detail

The sequencer is a simple utility to manage tasks scheduling without embedding the complexity of an RTOS. This software runs with major concepts:

  • TASK: a simple function called by the sequencer
  • TASK id: number between 0 and 31 (task coded on 32 bit)
  • EVENT: event management to wake up a task
  • SCHEDULER: manager which order the tasks execution

4.1. Internal state of the sequencer

This section describes the usage of global variables within the sequencer.

TaskSet This variable is the list of the tasks executable by the sequencer.
Read by UTIL_SEQ_Run, UTIL_SEQ_IsScheduleableTask
Write by UTIL_SEQ_Init, UTIL_SEQ_SetTask, UTIL_SEQ_Run
Default Value UTIL_SEQ_NO_BIT_SET

4.2. Task life cycle

This figure represents how a task lives during the application execution. Utility task lfie cycle.JPG

  • IDLE: the task has been initialized and ready to run
  • RUNNING: the task has been inserted in the scheduler activity, and become running according to the scheduler rule.
  • WAIT: the task is stopped and a new scheduler instance is called to run the tasks, until the event reception.
  • PAUSE: the task could be paused in the state IDLE or RUNNING. This feature could be used to delay a task treatment.

4.3. Event management

The sequencer offers a feature to put a task waiting for an event, but this feature has consequences. It is important to understand the mechanism:

  1. A wait on an event calls a second instance of the scheduler, which becomes recursive. The application needs to ensure the impact on its call stack sizing.
  2. When the scheduler is running and waiting for an event, it still allows entering stop mode except if the waited event is set.

Below there is an example of the stack execution when an event is waited

UTIL_SEQ_Run(~0U);              Sequencer running to schedule all the task       
--> Task_1                                  Process A running
----> UTIL_SEQ_WaitEvt(1);      Process A wait for an event 1
------> UTIL_SEQ_EvtIdle(1,1);  Sequencer task 1 is waiting for an event 1 
--------> UTIL_SEQ_Run(~1);     Run all the task except 1, which is waiting for an event

The SEQ_UTIL_EvtIdle() function provided inside the sequencer is restrictive because when a task is waiting for an event it can no longer be executed.

The application can rewrite this function according to its needs and the sequencer allows all the cases. It is therefore possible that a pending task waiting for an event runs inside a sub scheduler. This use case is possible, but is not recommended. Use carefully the event feature.

4.4. Scheduler

The flowchart below shows the algorithm used by the function UTIL_SEQ_Run() to schedule the tasks execution. The goals of the scheduler are:

  • Ensure a good repartition of the task execution
  • Manage entry in the IDLE task

Utility scheduler.jpg


The “SuperMask” is used to prevent the reactivation of a task during the recursive call of UTIL_SEQ_Run(), otherwise the system could stay frozen or become incoherent.

(*) the system enters in the task execution state according to two conditions:

  • A mask status (TaskSet & TaskMask & SuperMask) which indicates if a task shall run
  • An event waited (EvtSet & EvtWaited), used to exit the function UTIL_SEQ_Run() if a waited event is detected.

(**) the system used the same condition as (*) to control if we can enter the ILDE task, taking into account the actions of the interrupts.
(***) When a task has been executed, the task ID is removed from the round robin mask to avoid a second task call. This means that a task can be executing only the next call of the function UTIL_SEQ_Run().

4.5. API description

4.5.1. initialization

/**
 * @brief This function initializes the sequencer resources.
 *
 */
void UTIL_SEQ_Init( void );

/**
 * @brief This function un-initializes the sequencer resources.
 *
 */
void UTIL_SEQ_DeInit( void );

4.5.2. Task creation

/**
 * @brief This function registers a task in the sequencer.
 *
 * @param TaskId_bm The id of the task
 *        It shall be (1<<task_id) where task_id is the number assigned when the task has been registered
 * @param Flags Flags are reserved param for future use
 * @param Task Reference of the function to be executed
 *
 */
void UTIL_SEQ_RegTask( UTIL_SEQ_bm_t TaskId_bm, uint32_t Flags, void (*Task)( void ) );

4.5.3. Task Enable

/**
 * @brief This function requests a task to be executed
 *
 * @param TaskId_bm The Id of the task
 *        It shall be (1<<task_id) where task_id is the number assigned when the task has been registered
 * @param TaskPrio The priority of the task
 *        It shall a number from 0 (high priority) to 31 (low priority)
 *        The priority is checked each time the sequencer needs to select a new task to execute
 *        It does not permit to preempt a running task with lower priority
 *
 */
void UTIL_SEQ_SetTask( UTIL_SEQ_bm_t TaskId_bm , uint32_t TaskPrio );

/**
 * @brief This function checks if a task could be scheduled.
 *
 * @param TaskId_bm The Id of the task
 *        It shall be (1<<task_id) where task_id is the number assigned when the task has been registered
 * @retval 0 if not 1 if true
 */
uint32_t UTIL_SEQ_IsScheduleableTask( UTIL_SEQ_bm_t TaskId_bm);

4.5.4. Task Pause/Resume

/**
 * @brief This function prevents a task to be called by the sequencer even when set with UTIL_SEQ_SetTask()
 *        By default, all tasks are executed by the sequencer when set with UTIL_SEQ_SetTask()
 *        When a task is paused, it is moved out from the sequencer list
 *
 * @param task_id_bm: The Id of the task
 *         It shall be (1<<task_id) where task_id is the number assigned when the task has been registered
 *
 */
void UTIL_SEQ_PauseTask( UTIL_SEQ_bm_t task_id_bm );

/**
 * @brief This function allows to know if the task has been put in pause.
 *        By default, all tasks are executed by the sequencer when set with UTIL_SEQ_SetTask()
 *        The exit of the pause is done by the function UTIL_SEQ_ResumeTask.
 *
 * @param TaskId_bm The Id of the task
 *        It shall be (1<<task_id) where task_id is the number assigned when the task has been registered
 *
 */
uint32_t UTIL_SEQ_IsPauseTask( UTIL_SEQ_bm_t task_id_bm );

/**
 * @brief This function allows again a task to be called by the sequencer if set with UTIL_SEQ_SetTask()
 *        This is used in relation with UTIL_SEQ_PauseTask()
 *
 * @param TaskId_bm The Id of the task
 *        It shall be (1<<task_id) where task_id is the number assigned when the task has been registered
 *
 */
void UTIL_SEQ_ResumeTask( UTIL_SEQ_bm_t task_id_bm );

4.5.5. Task Event

/**
 * @brief This function sets an event that is waited with UTIL_SEQ_WaitEvt()
 *
 * @param EvtId_bm event id bit mask
 *
 * @note an event must be a 32-bit mapping where only 1 bit is set
 *
 */
void UTIL_SEQ_SetEvt( UTIL_SEQ_bm_t EvtId_bm );

/**
 * @brief This function may be used to clear the event before calling UTIL_SEQ_WaitEvt()
 *        This API may be useful when the UTIL_SEQ_SetEvt() is called several time to notify the same event.
 *        Due to software architecture where the timings are hard to control, this may be an unwanted case.
 *
 * @param EvtId_bm event id bm
 *        It must be a bit mapping where only 1 bit is set
 *
 */
void UTIL_SEQ_ClrEvt( UTIL_SEQ_bm_t EvtId_bm );

/**
 * @brief This function waits for a specific event to be set. The sequencer loops UTIL_SEQ_EvtIdle() until the event is set
 *        When called recursively, it acts as a First in / Last out mechanism. The sequencer waits for the
 *        last event requested to be set even though one of the already requested event has been set.
 *
 * @param EvtId_bm event id bit mask
 *        It shall be a bit mapping where only 1 bit is set
 *
 */
void UTIL_SEQ_WaitEvt( UTIL_SEQ_bm_t EvtId_bm );

/**
 * @brief This function returns whether the waiting event is pending or not
 *        It is useful only when the UTIL_SEQ_EvtIdle() is overloaded by the application. In that case, when the low
 *        power mode needs to be executed, the application shall first check whether the waiting event is pending
 *        or not. Both the event checking and the low power mode processing should be done in critical section
 *
 * @retval 0 when the waited event is not there or the evt_id when the waited event is pending
 */
UTIL_SEQ_bm_t UTIL_SEQ_IsEvtPend( void );

/**
 * @brief This function loops until the waiting event is set
 *        The application may either enter low power mode or call UTIL_SEQ_Run()
 *        When not implemented by the application, it calls UTIL_SEQ_Run(0) which means all tasks are removed from
 *        sequencer list and only UTIL_SEQ_Idle() is called. In that case, only the low power mode is executed.
 *
 * @param task_id_bm: The task id that is currently running. When task_id_bm = 0, it means UTIL_SEQ_WaitEvt( )
 *                     has been called outside a registered task (that is, at startup before UTIL_SEQ_Run( ) has been called
 * @param evt_waited_bm: The event id that is waited.
 *
 */
void UTIL_SEQ_EvtIdle( UTIL_SEQ_bm_t task_id_bm, UTIL_SEQ_bm_t evt_waited_bm );

4.5.6. Task Idle

/**
 * @brief This function is called by the sequencer in critical section (PRIMASK bit) when
 *          - there are no more tasks to be executed
 *          AND
 *          - there are no pending event or the pending event is still not set
 *        The application should enter low power mode in this function
 *        When this function is not implemented by the application, the sequencer keeps running a while loop (Run mode)
 *
 */
void UTIL_SEQ_Idle( void );

/**
 * @brief This function is called by the sequencer outside the critical section just before calling UTIL_SEQ_Idle( )
 *        UTIL_SEQ_PreIdle() is considered as the last task executed before calling UTIL_SEQ_Idle( )
 *        In case a task or an event is set from an interrupt handler just after UTIL_SEQ_PreIdle() is called,
 *        UTIL_SEQ_Idle() will not be called.
 *
 */
void UTIL_SEQ_PreIdle( void );

/**
 * @brief This function is called by the sequencer outside the critical section either
 *        - after calling UTIL_SEQ_Idle( )
 *        OR
 *        - after calling UTIL_SEQ_PreIdle( ) without call to UTIL_SEQ_Idle() due to an incoming task set or event 
 *          requested after UTIL_SEQ_PreIdle() has been called.
 *
 *        Note: UTIL_SEQ_PostIdle() is always called if UTIL_SEQ_PreIdle() has been called and never called otherwise
 *
 */
void UTIL_SEQ_PostIdle( void );

4.5.7. Scheduler

/**
 * @brief This function requests the sequencer to execute all pending tasks using the round robin mechanism.
 *         When no task is 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 kept in the sequencer list.
 *
 */
void UTIL_SEQ_Run( UTIL_SEQ_bm_t mask_bm );