freertos idle task, blocking delay

Blocking state: if a task is currently waiting for an external event, it is said to be in blocking state.

The delay in rtos is called blocking delay, that is, when a task needs to delay, it will give up the right to use the CPU and enter the blocking state. During the period when the task is blocked, the CPU can execute other tasks (if other tasks are also in the delayed state, the CPU will run idle tasks). When the task is delayed, the CPU usage right is obtained again and the task continues to run.

Idle task: a task that runs when the processor is idle. When there are no other ready tasks in the system, the idle task starts running, and the priority of the idle task is the lowest.

Idle task

Define idle tasks:

#define portSTACK_TYPE	uint32_t
typedef portSTACK_TYPE StackType_t;
/*Defines the stack of idle tasks*/
#define configMINIMAL_STACK_SIZE	( ( unsigned short ) 128 )
StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];
/*Define task control blocks for idle tasks*/
TCB_t IdleTaskTCB;

Create idle task: it is created in the vTaskStartScheduler scheduler startup function.

/*Structure of task control block */
typedef struct tskTaskControlBlock
{
	volatile StackType_t    *pxTopOfStack;    /* Stack top */

	ListItem_t			    xStateListItem;   /* Task node */
    
    StackType_t             *pxStack; /* Start address of task stack */
	                                          
	char     pcTaskName[ configMAX_TASK_NAME_LEN ];/* Task name, in string form */

    TickType_t xTicksToDelay; /* For delay */
    
} tskTCB;
typedef tskTCB TCB_t;

/*Get the memory of idle tasks: task control block, starting address of task stack and size of task stack*/
void vApplicationGetIdleTaskMemory( TCB_t **ppxIdleTaskTCBBuffer, 
                                    StackType_t **ppxIdleTaskStackBuffer, 
                                    uint32_t *pulIdleTaskStackSize )
{
		*ppxIdleTaskTCBBuffer=&IdleTaskTCB;//Task control block for idle tasks
		*ppxIdleTaskStackBuffer=IdleTaskStack; //Task stack for idle tasks
		*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;//Stack size
}

void vTaskStartScheduler( void )
{
/*Create idle task start*/     

    TCB_t *pxIdleTaskTCBBuffer = NULL;               /* Used to point to idle task control block */
    StackType_t *pxIdleTaskStackBuffer = NULL;       /* Starting address of idle task stack */
    uint32_t ulIdleTaskStackSize;
    
    /* Get: task control block, start address of task stack and size of task stack */
    vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, 
                                   &pxIdleTaskStackBuffer, 
                                   &ulIdleTaskStackSize );    
    
    /*Create idle task*/
    xIdleTaskHandle = xTaskCreateStatic( (TaskFunction_t)prvIdleTask,              /* Task entry */
					                     (char *)"IDLE",                           /* Task name, in string form */
					                     (uint32_t)ulIdleTaskStackSize ,           /* Task stack size, in words */
					                     (void *) NULL,                            /* Task parameters */
					                     (StackType_t *)pxIdleTaskStackBuffer,     /* Start address of task stack */
					                     (TCB_t *)pxIdleTaskTCBBuffer );           /* Task control block */
    /* Add task to ready list */                                 
    vListInsertEnd( &( pxReadyTasksLists[0] ), &( ((TCB_t *)pxIdleTaskTCBBuffer)->xStateListItem ) );
    
/*Create idle task end*/
                                         
    /* Manually specify the first task to run */
    pxCurrentTCB = &Task1TCB;
                                         
    /* Initialize system time base counter */
    xTickCount = ( TickType_t ) 0U;
    
    /* Start scheduler */
    if( xPortStartScheduler() != pdFALSE )
    {
        /* If the scheduler is started successfully, it will not return, that is, it will not come here */
    }
}

//The following is the task entry of idle tasks. See, there is nothing done in it
//I used debug to find that it was stuck until the for didn't move.
//Through one-step operation, an interrupt occurs and the program cannot enter the interrupt.
static portTASK_FUNCTION( prvIdleTask, pvParameters )
{
	/* Prevent compiler warnings */
	( void ) pvParameters;
    
    for(;;)
    {
        /* Idle tasks do nothing for the time being */
    }
}

Blocking delay

The task function is as follows: the delay function is replaced by software delay as blocking delay.

void Task1_Entry( void *p_arg )
{
	for( ;; )
	{
#if 0        
		flag1 = 1;
		delay( 100 );/*Software delay*/		
		flag1 = 0;
		delay( 100 );
		
		/* Thread switching, here is manual switching */
        portYIELD();
#else
		flag1 = 1;
        vTaskDelay( 2 );/*Blocking delay*/		
		flag1 = 0;
        vTaskDelay( 2 );
#endif        
	}
}

Vtask delay blocking delay function is called in the task function, as follows.

/*Definition of blocking delay function */
void vTaskDelay( const TickType_t xTicksToDelay )
{
    TCB_t *pxTCB = NULL;
    
    /* Gets the task control block of the current task */
    pxTCB = pxCurrentTCB;
    
    /* Set delay time: xTicksToDelay SysTick delay cycles */
    pxTCB->xTicksToDelay = xTicksToDelay;
    
    /* Task switching */
    taskYIELD();
}

Then the taskYIELD function is called in vtask delay, as follows. The purpose is to generate PendSV interrupt and enter the PendSV interrupt service function.

/* Interrupt control and state register (SCB_ICSR): 0xe000ed04
 * Bit 28 PENDSVSET: PendSV set-pending bit
 */
#define portNVIC_INT_CTRL_REG		( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
#define portNVIC_PENDSVSET_BIT		( 1UL << 28UL )

#define portSY_FULL_READ_WRITE		( 15 )
/* Scheduler utilities. */
#define portYIELD()																\
{																				\
	/* Set the interrupt hold bit of PendSV to generate context switching */								\
	portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;								\
																				\
	/* Barriers are normally not required but do ensure the code is completely	\
	within the specified behaviour for the architecture. */						\
	__dsb( portSY_FULL_READ_WRITE );											\
	__isb( portSY_FULL_READ_WRITE );											\
}

The PendSV interrupt service function is as follows. The vTaskSwitchContext context switching function is called to find the ready task with the highest priority, and then update pxCurrentTCB.

__asm void xPortPendSVHandler( void )
{
//	extern uxCriticalNesting;
	extern pxCurrentTCB;
	extern vTaskSwitchContext;

	PRESERVE8

    /* When entering PendSVC Handler, the environment in which the last task was run is:
       xPSR,PC(Task entry address), R14, R12, R3, R2, R1, R0 (formal parameters of the task)
       The values of these CPU registers will be automatically saved to the task stack, and the rest r4~r11 need to be saved manually */
    /* Get the task stack pointer to r0 */
	mrs r0, psp
	isb

	ldr	r3, =pxCurrentTCB		/* Load the address of pxCurrentTCB to r3 */
	ldr	r2, [r3]                /* Load pxCurrentTCB to r2 */

	stmdb r0!, {r4-r11}			/* Store the values of CPU registers r4~r11 to the address pointed to by r0 */
	str r0, [r2]                /* Store the new stack top pointer of the task stack to the first member of the current task TCB, that is, the stack top pointer */				
                               

	stmdb sp!, {r3, r14}        
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY / * enter critical section*/
	msr basepri, r0
	dsb
	isb
	bl vTaskSwitchContext       /* Call the function vTaskSwitchContext to find a new task to run, and realize task switching by pointing the variable pxCurrentTCB to the new task */ 
	mov r0, #0 / * exit critical section*/
	msr basepri, r0
	ldmia sp!, {r3, r14}        /* Restore r3 and r14 */

	ldr r1, [r3]
	ldr r0, [r1] 				/* The first item of the currently active task TCB saves the stack top of the task stack, and now the stack top value is stored in R0*/
	ldmia r0!, {r4-r11}			/* Out of stack */
	msr psp, r0
	isb
	bx r14                      
	nop
}

The vTaskSwitchContext context switching function is as follows.

When a task needs to be delayed, it will give up the right to use the CPU and enter the blocking state. During the period when the task is blocked, the CPU can execute other tasks (if other tasks are also in the delayed state, the CPU will run idle tasks). When the task is delayed, the CPU usage right is obtained again and the task continues to run.

void vTaskSwitchContext( void )
{
	if( pxCurrentTCB == &IdleTaskTCB )//If the current thread is an idle thread
	{
		if(Task1TCB.xTicksToDelay == 0)//If thread 1 delay time ends
		{            
            pxCurrentTCB =&Task1TCB;//Switch to thread 1
		}
		else if(Task2TCB.xTicksToDelay == 0)//If the delay time of thread 2 ends (thread 1 is in delay)
		{
            pxCurrentTCB =&Task2TCB;//Switch to thread 2
		}
		else
		{
			return;		/* If the thread delay has not expired, return and continue to execute the idle thread */
		} 
	}
	else//The current task is not an idle task
	{
		if(pxCurrentTCB == &Task1TCB)//If the current thread is thread 1
		{
			if(Task2TCB.xTicksToDelay == 0)//If thread 2 is not in delay
			{
                pxCurrentTCB =&Task2TCB;//Switch to thread 2
			}
			else if(pxCurrentTCB->xTicksToDelay != 0)//If thread 1 enters the delay state (thread 2 is also in the delay state)
			{
                pxCurrentTCB = &IdleTaskTCB;//Switch to idle thread
			}
			else 
			{
				return;		/* Return without switching */
			}
		}
		else if(pxCurrentTCB == &Task2TCB)//If the current thread is thread 2
		{
			if(Task1TCB.xTicksToDelay == 0)//If thread 1 is not in delay
			{
                pxCurrentTCB =&Task1TCB;//Switch to thread 1
			}
			else if(pxCurrentTCB->xTicksToDelay != 0)//If thread 2 enters the delay state (thread 1 is also in the delay state)
			{
                pxCurrentTCB = &IdleTaskTCB;//Switch to idle thread
			}
			else 
			{
				return;		/* Return without switching*/
			}
		}
	}
}

As can be seen from the above code, the vTaskSwitchContext context switching function judges whether the task is ready or continues to delay by seeing whether xTicksToDelay is zero.

xTicksToDelay decreases in what cycle and where. This cycle is provided by the SysTick interrupt.

SysTick

SysTick is the system timer. When the value of the reload value register decreases to 0, the system timer generates an interrupt to cycle back and forth.

The following is the initialization of SysTick.

//In the main function
    /* Start the scheduler and start multi task scheduling. If it is started successfully, it will not be returned */
    vTaskStartScheduler();  

//task. The xPortStartScheduler function is called in C
void vTaskStartScheduler( void )
{
    //..... Omit some codes
    
    /* Start scheduler */
    if( xPortStartScheduler() != pdFALSE )
    {
        /* If the scheduler is started successfully, it will not return, that is, it will not come here */
    }
}

//port.c inside
//The xPortStartScheduler scheduler starts the function, which calls the vportsetuptimerinthrupt function to initialize SysTick
BaseType_t xPortStartScheduler( void )
{
    /* Configure PendSV and SysTick to have the lowest interrupt priority */
	portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
	portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
    
    /* Initialize SysTick */
    vPortSetupTimerInterrupt();

	/* Start the first task and don't return */
	prvStartFirstTask();

	/* It shouldn't run here */
	return 0;
}

//system_ARMCM4.c Documents
#define  XTAL            (50000000UL)     /* Oscillator frequency */
#define  SYSTEM_CLOCK    (XTAL / 2U)

//FreeRTOSConfig.h file
//System clock size
#define configCPU_CLOCK_HZ			( ( unsigned long ) 25000000 )	
//How many times does SysTick interrupt per second? It is configured to interrupt once in 100 and 10ms
#define configTICK_RATE_HZ			( ( TickType_t ) 100 )

//Next, initialize SysTick
/* SysTick Control register */
#define portNVIC_SYSTICK_CTRL_REG			( * ( ( volatile uint32_t * ) 0xe000e010 ) )
/*SysTick Reload register*/
#define portNVIC_SYSTICK_LOAD_REG			( * ( ( volatile uint32_t * ) 0xe000e014 ) )
/*SysTick Clock source selection*/
#ifndef configSYSTICK_CLOCK_HZ
	#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ//configSYSTICK_CLOCK_HZ=configCPU_CLOCK_HZ
	/* Ensure that the SysTick clock is consistent with the kernel clock */
	#define portNVIC_SYSTICK_CLK_BIT 	 (1ul < < 2ul) / / unsigned long shaping 32-bit binary, shifted left by two bits
#else
	#define portNVIC_SYSTICK_CLK_BIT	( 0 )
#endif

#define portNVIC_SYSTICK_INT_BIT			( 1UL << 1UL )
#define portNVIC_SYSTICK_ENABLE_BIT			( 1UL << 0UL )

//The functions to initialize SysTick are as follows
void vPortSetupTimerInterrupt( void )
{
     /* Sets the value of the reload register */
    portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
    
    /* Set the clock of the system timer equal to the kernel clock
       Enable SysTick timer interrupt
       Enable SysTick timer */
    portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | 
                                  portNVIC_SYSTICK_INT_BIT |
                                  portNVIC_SYSTICK_ENABLE_BIT ); 
}

After initializing SysTick, let's take a look at the interrupt service function of SysTick.

It is clear now that xTicksToDelay decreases with the interrupt cycle of SysTick.

// port.c file, SysTick interrupt service function
//The xtask incrementtick function is called to update the system time base
void xPortSysTickHandler( void )
{
	/* Off interrupt enters critical section*/
    vPortRaiseBASEPRI();
    
    /* Update system time base */
    xTaskIncrementTick();

	/* Interrupt exit critical section*/
    vPortClearBASEPRIFromISR();
}

//task.c documents,
static volatile TickType_t xTickCount 				= ( TickType_t ) 0U;
void xTaskIncrementTick( void )
{
    TCB_t *pxTCB = NULL;
    BaseType_t i = 0;
    
    /* Update the system time base counter xTickCount, which is a Global variables defined in C */
    const TickType_t xConstTickCount = xTickCount + 1;
    xTickCount = xConstTickCount;//Add 1 to xTickCount
    
    /* Scan the xTicksToDelay of all threads in the ready list. If it is not 0, subtract 1 */
	for(i=0; i<configMAX_PRIORITIES; i++)
	{
        pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &pxReadyTasksLists[i] ) );
		if(pxTCB->xTicksToDelay > 0)
		{
			pxTCB->xTicksToDelay --;
		}
	}
    
    /* Task switching */
    portYIELD();
}

Experimental phenomenon

It can be seen from this that the high-level time is 20ms, which is exactly 20ms of the blocking delay. Moreover, the waveforms of the two tasks are the same, as if the CPU is doing two things at the same time. This is the benefit of blocking latency.

Why?

At first, all tasks did not enter the delay.

When a task abandons the CPU (enters the delay), at this moment, the CPU immediately turns to run another task (the other task also enters the delay immediately). This is because the uvTaskDelay blocking delay function calls the taskYIELD() task switching function. Therefore, the PendSV interrupt is generated and enters the PendSV interrupt service function xPortPendSVHandler.

In the PendSV interrupt service function, call the vTaskSwitchContext context switching function. Since both tasks are in the process of delay, they begin to switch to idle tasks.

When the value of the reload value register decreases to 0, the system timer generates an interrupt, enters the interrupt function of the system timer, changes xTicksToDelay, and then calls the task switching function portYIELD() again. The purpose is to generate PendSV interrupt and enter the PendSV interrupt service function.

Then call the vTaskSwitchContext context switching function again to judge whether the two tasks are still delaying. If task 1 is not delaying, switch to task 1 immediately. In task 1, call uvTaskDelay to block the delay function, and repeat the above activities again.

So the waveform is almost synchronous.

Previously, the software delay was used to write delay(100) in the task function, which belongs to the cpu. After running this delay, the task is switched. As shown in the figure below, after the high and low levels of a task are completed, the next task is switched.

Keywords: Embedded system Single-Chip Microcomputer FreeRTOS

Added by AndrewJ1313 on Mon, 31 Jan 2022 19:20:07 +0200