FreeRTOS_idle task

Table of Contents

1. Detailed explanation of idle tasks

1.1 Introduction to idle tasks

1.2 Creation of idle tasks

1.3 Idle task function

2. Detailed explanation of idle task hook function

2.1 Hook function

2.2 Idle task hook function

3. Idle task hook function experiment

3.1 main.c


Idle tasks are an essential task for FreeRTOS. Other RTOS-like systems also have idle tasks, such as UCOS. As the name suggests, an idle task is a task that is run when the processor is idle. The idle task will start running when there are no other ready tasks in the system. The most important role of the idle task is to allow the processor to find tasks when it has nothing to do. Do something to prevent the processor from getting bored, so the idle task must have the lowest priority. Of course, in practice, precious processor resources will not be wasted like this, and FreeRTOS idle tasks will also perform some other processing.

1. Detailed explanation of idle tasks

1.1 Introduction to Idle Tasks

When the FreeRTOS scheduler is started, it will automatically create an idle task, thus ensuring that at least one task can run. However, this idle task uses the lowest priority. If there are other high-priority tasks in the application that are ready, this idle task will not compete with high-priority tasks for CPU resources. The idle task also has another important responsibility. If a task wants to call the function vTaskDelete() to delete itself, then the task control block TCB and task stack of this task, which are automatically allocated by the FreeRTOS system, need to be released in the idle task. , if another task is deleted, the corresponding memory will be released directly, and there is no need to release it in an idle task. Therefore, be sure to give idle tasks a chance to execute! Apart from this, idle tasks have no particularly important functions, so the time that idle tasks use the CPU can be reduced according to actual conditions (for example, when the CPU is running idle tasks, the processor enters a low-power mode).

Users can create application tasks with the same priority as the idle task. When the macro configIDLE_SHOULD_YIELD is 1, the application task can use the time slice of the idle task, which means that the idle task will give up the time slice to applications of the same priority.

1.2 Creation of idle tasks

When the function vTaskStartScheduler() is called to start the task scheduler, this function will automatically create an idle task. The code is as follows:

void vTaskStartScheduler( void )
{
    BaseType_t xReturn;
 
    //Create an idle task, using the lowest priority
    #if( configSUPPORT_STATIC_ALLOCATION == 1 ) (1) Use static methods to create idle tasks
    {
        StaticTask_t *pxIdleTaskTCBBuffer = NULL;
        StackType_t *pxIdleTaskStackBuffer = NULL;
        uint32_t ulIdleTaskStackSize;
 
        vApplicationGetIdleTaskMemory( & amp;pxIdleTaskTCBBuffer, & amp;pxIdleTaskStackBuffer,
                                        & amp;ulIdleTaskStackSize );
        xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,
                                             "IDLE",
                                             ulIdleTaskStackSize,
                                             ( void * ) NULL,
                                             (tskIDLE_PRIORITY | portPRIVILEGE_BIT),
                                             pxIdleTaskStackBuffer,
                                             pxIdleTaskTCBBuffer );
 
        if( xIdleTaskHandle != NULL )
        {
            xReturn = pdPASS;
        }
        else
        {
            xReturn = pdFAIL;
        }
    }
    #else (2) Use a dynamic method to create an idle task. The task function of the idle task is prvIdleTask(), and the task stack size is configMINIMAL_STACK_SIZE. The task stack size can be modified in FreeRTOSConfig.h. The task priority is tskIDLE_PRIORITY, and the macro tskIDLE_PRIORITY is 0, which means that the idle task has the lowest priority, and the user cannot modify the priority of the idle task at will!
    {
        xReturn = xTaskCreate( prvIdleTask,
                               "IDLE",
                               configMINIMAL_STACK_SIZE,
                               ( void * ) NULL,
                               (tskIDLE_PRIORITY | portPRIVILEGE_BIT),
                                & amp;xIdleTaskHandle );
    }
    #endif /* configSUPPORT_STATIC_ALLOCATION */
 
/****************************************************** ************************/
/******************************Omit other codes************************ *************/
/****************************************************** ************************/
}

1.3 Idle task function

The task function of the idle task is prvIdleTask(), but this function cannot actually be found because it is implemented through macro definition. There is the following macro definition in the file portmacro.h:

#define portTASK_FUNCTION(vFunction, pvParameters) void vFunction(void *pvParameters)

Among them, portTASK_FUNCTION() is defined in the file tasks.c. It is the task function of the idle task. The source code is as follows:

static portTASK_FUNCTION( prvIdleTask, pvParameters ) (1) Expanding this line is static void prvIdleTask(void *pvParameters). When creating an idle task, the task function name is prvIdleTask()
{
    (void) pvParameters; //Prevent error reporting
 
    //This function is the idle task function of FreeRTOS. The idle task will be automatically created when the task scheduler is started.
 
    for( ;; )
    {
        //Check whether there are tasks to delete themselves, and if so, release the task control blocks TCB and TCB of these tasks
        //Task stack memory
 
        prvCheckTasksWaitingTermination(); (2) Call the function prvCheckTasksWaitingTermination to check whether there are deleted tasks that need to release memory. When a task calls the function vTaskDelete() to delete itself, the task will be added to the list. If xTasksWaitingTermination is empty, if it is not empty Then release the memory corresponding to all tasks in the list in turn (the memory of the task control block TCB and task stack)
 
        #if (configUSE_PREEMPTION == 0)
        {
 
            //If the preemptive kernel is not used, force a task switch to see if there are other
            //The task is valid. If you use a preemptive kernel, this step is not needed, because as long as there is any
            //Any task that is valid (ready) will automatically seize CPU usage rights.
 
            taskYIELD();
        }
        #endif /* configUSE_PREEMPTION */
 
        #if ( ( configUSE_PREEMPTION == 1 ) & amp; & amp; ( configIDLE_SHOULD_YIELD == 1 ) ) (3) Using a preemptive kernel and configIDLE_SHOULD_YIELD is 1, indicating that idle tasks need to give up time slices to other ready tasks of the same priority
        {
            //If you use a preemptive kernel and enable time slice scheduling, when there are tasks and idle tasks are shared
            //At a priority level, and if the task is in the ready state, the idle task should give up this time
            //Interval time slice, give the remaining time of this time slice to this ready task. If at idle task priority
            //If there are multiple user tasks in the ready list below, execute these tasks.
 
            if( listCURRENT_LIST_LENGTH( (4) Check whether the ready task list with priority tskIDLE_PRIORITY (idle task priority) is empty. If it is not empty, call the function taskYIELD() to perform a task switch.
                     & amp;( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) )> ( UBaseType_t ) 1 )
            {
                taskYIELD();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        #endif
 
        #if (configUSE_IDLE_HOOK == 1)
        {
            extern void vApplicationIdleHook( void );
 
            //Execute user-defined idle task hook function, pay attention! You cannot use any
            //API function that can cause blocking of idle tasks.
            vApplicationIdleHook(); (5) If the idle task hook function is enabled, this hook function will be executed. The function name of the idle task hook function is vApplicationIdleHook(). This function needs to be written by the user! When writing this hook function, you must not call any API functions that can block idle tasks.
        }
        #endif /* configUSE_IDLE_HOOK */
 
        //If Tickless mode is enabled, execute the relevant processing code
        #if ( configUSE_TICKLESS_IDLE != 0 ) (6) configUSE_TICKLESS_IDLE is not 0, indicating that the low-power Tickless mode of FreeRTOS is enabled.
        {
            TickType_t xExpectedIdleTime;
 
            xExpectedIdleTime = prvGetExpectedIdleTime(); (7) Call the function prvGetExpectedIdleTime() to obtain the duration of the processor entering low-power mode. This value is stored in the variable xExpectedIdleTime, and the unit is the number of clock ticks.
            if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) (8) The value of xExpectedIdleTime must be greater than configEXPECTED_IDLE_TIME_BEFORE_SLEEP to be valid
            {
                vTaskSuspendAll(); (9) handles Tickless mode and suspends the task scheduler, which actually functions as critical section code protection
                {
 
                    //The scheduler has been suspended, collect the time value again, this time the time value can be
                    //use 
 
                    configASSERT(xNextTaskUnblockTime >= xTickCount);
                    xExpectedIdleTime = prvGetExpectedIdleTime(); (10) Get the time value again, this time the time value is directly used for portSUPPRESS_TICKS_AND_SLEEP()
 
                    if( xExpectedIdleTime >=
                        configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
                    {
                        traceLOW_POWER_IDLE_BEGIN();
                        portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ); (11) Call portSUPPRESS_TICKS_AND_SLEEP() to enter low-power Tickless mode
                        traceLOW_POWER_IDLE_END();
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                (void) xTaskResumeAll(); (12) Resume task scheduler
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        #endif /* configUSE_TICKLESS_IDLE */
    }
} 

2. Detailed explanation of idle task hook function

2.1 Hook function

There are multiple hook functions in FreeRTOS. Hook functions are similar to callback functions. When a certain function (function) is executed, the hook function will be called. The specific content of the hook function is written by the user. If you don’t need to use the hook function, don’t worry about anything. The hook function is an optional function, and you can choose which hook function to use through macro definition.

Macro definition: Description:

configUSE_IDLE_HOOK Idle task hook function , the idle task will call this hook function

configUSE_TICK_HOOK Time slice hook function, xTaskIncrementTick() will call this hook function. This hook function will eventually be used by the beat interrupt service function, which for STM32 is

Tick timer interrupt service function

configUSE_MALLOC_FAILED_HOOK Memory application failure hook function, this hook function will be called when the function pvPortMalloc() fails to apply for memory.

configUSE_DAEMON_TASK_STARTUP_HOOK Daemon task startup hook function, the daemon task is also Timer service task

The method of using the hook function is basically the same. The user enables the corresponding hook function and then writes the content of the hook function according to actual needs.

2.2 Idle task hook function

The idle task hook function will be called during each idle task running cycle. If you want to process a task under the idle task priority, you have two options:

1. Process tasks in the idle task hook function

It is necessary to ensure that at least one task in the system can run at any time, so any API function that can block idle tasks, such as vTaskDelay(), or other semaphores or queues with blocking time, must not be called in the idle task hook function. Operation function.

2. Create a task with the same priority as the idle task

Creating a task is the best solution, but this method also consumes more RAM.

To use the idle task hook function, you must first change the macro configUSE_IDLE_HOOK to 1 in FreeRTOSConfig.h, and then write the idle task hook function vApplicationIdleHook(). Usually, the processor is set to low-power mode in the idle task hook function to save power. In order to distinguish it from the Tickless mode that comes with FreeRTOS, this low-power implementation method is called universal low-power consumption mode. (Because almost all RTOS systems can use this method to achieve low power consumption).

There are a total of three tasks in the above picture. They are one idle task (Idle) and two user tasks (Task1 and Task2). The idle task has been run three times in total, namely (1), ( 2) and (3), where T1 to T12 are 12 moments. Let’s analyze the whole process from these two low-power implementation methods respectively.

1. Universal low power mode

If the universal low-power mode is used, each tick timer interrupt will wake up the processor from the low-power mode. Taking (1) as an example, the processor wakes up from the low-power mode at T2, but then due to There are no other tasks ready so the processor goes into low power mode again. The three moments T2, T3 and T4 are the same. It repeatedly enters and exits low power consumption. The ideal situation should be to enter low power consumption from T1 moment and then exit at T5 moment.

In (2), the idle task only works for two clock ticks, but it also executes entry and exit of low-power mode. Obviously this is of little significance, because it also takes time to enter and exit low-power mode.

In (3), the idle task is awakened by an external interrupt at time T12, and the specific processing process of the interrupt is in task 2 (using a semaphore to achieve synchronization between the interrupt and the task).

2. Low power consumption Tickless mode

The processor enters the low-power mode at time T1 in (1) and exits the low-power mode at time T5. Compared with the general low-power mode, there are three fewer operations to enter and exit the low-power mode.

In (2), since the idle task only runs for two clock ticks, there is no need to enter low-power mode. Note that in Tickless mode, low power consumption mode will only be entered when the running time of idle tasks exceeds a certain minimum threshold. This threshold is set by configEXPECTED_IDLE_TIME_BEFORE_SLEEP.

The situation in (3) is the same as in general low power mode.

It can be seen that compared to the general low-power mode, the Tickless mode that comes with FreeRTOS is more reasonable and effective, so if you have low-power design requirements, try to use the Tickless mode that comes with FreeRTOS.

3. Idle task hook function experiment

This lab learns howto achieve low power consumption in the FreeRTOS idle task hook function. Use the WFI instruction in the idle task hook function to put the processor into sleep mode.

3.1 main.c

#include "stm32f4xx.h"
#include "FreeRTOS.h" //Note here that you must first reference the FreeRTOS header file, and then reference task.h
#include "task.h" //There is a sequence relationship
#include "LED.h"
#include "LCD.h"
#include "Key.h"
#include "usart.h"
#include "delay.h"
#include "string.h"
#include "beep.h"
#include "malloc.h"
#include "timer.h"
#include "queue.h"
#include "semphr.h"


//task priority
#define START_TASK_PRIO 1 //Used to create two other tasks
//Task stack size
#define START_STK_SIZE 256
//task handle
TaskHandle_t StartTask_Handler;
//task function
void start_task(void *pvParameters);

//task priority
#define TASK1_TASK_PRIO 2 //Control LED0 to flash to indicate that the system is running
//Task stack size
#define TASK1_STK_SIZE 256
//task handle
TaskHandle_t Task1Task_Handler;
//task function
void task1_task(void *pvParameters);

//task priority
#define DATAPROCESS_TASK_PRIO 3 //Instruction processing function
//Task stack size
#define DATAPROCESS_STK_SIZE 256
//task handle
TaskHandle_t DataProcess_Handler;
//task function
void DataProcess_task(void *pvParameters);

//Binary semaphore handle
SemaphoreHandle_t BinarySemaphore; //Binary semaphore handle

//Command value used for command parsing
#define LED1ON 1
#define LED1OFF 2
#define BEEPON 3
#define BEEPOFF 4
#defineCOMMANDERR 0xFF

//Events that need to be processed before entering low power mode
//ulExpectedIdleTime: Low power mode running time
void PreSleepProcessing(void) //Because the binary semaphore experiment uses the serial port, the GPIOB~H clock is not enabled!
{
    //Turn off some peripheral clocks that are not used in low-power mode
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,DISABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,DISABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD,DISABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,DISABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,DISABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,DISABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOH,DISABLE);
}

//Things that need to be processed after exiting low power mode
//ulExpectedIdleTime: Low power mode running time
void PostSleepProcessing(void)
{
    //Open those peripheral clocks that have been turned off after exiting low power mode
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD,ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOH,ENABLE);
}

//Idle task hook function
void vApplicationIdleHook(void)
{
    __disable_irq();
    __dsb(portSY_FULL_READ_WRITE);
__isb(portSY_FULL_READ_WRITE);
\t
PreSleepProcessing(); //Things that need to be processed before entering sleep mode
__wfi(); //Enter sleep mode
PostSleepProcessing(); //Things that need to be processed after exiting sleep mode
\t
__dsb(portSY_FULL_READ_WRITE);
__isb(portSY_FULL_READ_WRITE);
__enable_irq();
}

//The function LowerToCap is used to uniformly convert the lowercase letters in the commands sent from the serial port into uppercase letters.
//This way you don't need to be case sensitive when sending commands, because the development board will uniformly convert them to uppercase.
//Convert lowercase letters in the string to uppercase
//str: string to be converted
//len: string length
void LowerToCap(u8 *str,u8 len)
{
    u8i;
    for(i=0;i<len;i + + )
    {
        //Determine whether the ASCII code of the string is between 96 and 123
        if((96<str[i]) & amp; & amp;(str[i]<123)) //lowercase letters
        {
            //ASCII code is an encoding system used to represent characters. In ASCII code, each character is assigned a unique integer value.
            //The ASCII code value of uppercase letters is 65 to 90
            //The ASCII code value of lowercase letters is 97 to 122. So once it is determined that the ASCII code value is within the range of lowercase letters, you only need to subtract 32 from the ASCII code value to convert to uppercase.
            str[i] = str[i] - 32; //Convert to uppercase
        }
    }
}

//The function CommandProcess is used to convert the received command string into a command value. For example, the command "LED1ON" is converted into a command value of 0 (the macro LED1ON is 0)
//Command processing function, convert string command into command value
//str: command
//Return value: 0xFF, command error; other values, command value
u8 CommandProcess(u8 *str)
{
    u8 CommandValue = COMMANDERR;
    if(strcmp((char*)str,"LED1ON")==0) //strcmp string comparison function
        //This function will compare two parameters; when comparing, the ASCII value of the character will be compared.
        //If the ASCII code value of str1 is less than str2, return a negative number; otherwise, return a positive number;
        //If the ASCII code value of str1 is equal to str2, return 0. At this time, the if judgment statement is established.
        CommandValue = LED1ON; //The macro of LED1ON is set to 1, that is, 1 is input on the serial port, and the if judgment statement is established.
    else if(strcmp((char*)str,"LED1OFF")==0)
        CommandValue = LED1OFF; //Input 2 on the serial port, and the if judgment statement is established
    else if(strcmp((char*)str,"BEEPON")==0)
        CommandValue = BEEPON; //Input 3 on the serial port, and the if judgment statement is established
    else if(strcmp((char*)str,"BEEPOFF")==0)
        CommandValue = BEEPOFF; //Input 4 on the serial port, and the if judgment statement is established
    return CommandValue;
}

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); //Set the system interrupt priority
    delay_init(168);
    uart_init(115200);
    LED_Init();
    KEY_Init();
    BEEP_Init();
    LCD_Init();
    my_mem_init(SRAMIN); //Initialize the internal memory pool
    
    POINT_COLOR=RED;
    LCD_ShowString(10,10,200,16,16,"ATK STM32F407");
    LCD_ShowString(10,30,200,16,16,"FreeRTOS Example");
    LCD_ShowString(10,50,200,16,16,"Binary Semaphore");
    LCD_ShowString(10,70,200,16,16,"Command Data:");
    
    //Create a start task
    xTaskCreate((TaskFunction_t)start_task, //task function
                (const char* )"start_task", //Task name
                (uint16_t )START_STK_SIZE, //Task stack size
                (void* )NULL, //Parameters passed to the task function
                (UBaseType_t )START_TASK_PRIO, //Task priority
                (TaskHandle_t* ) & amp;StartTask_Handler); //task handle
    vTaskStartScheduler(); //Enable task scheduling
}

//Start task task function
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL(); //Enter the critical section
    
    //Create a binary semaphore, that is, create a queue with a length of 1
    BinarySemaphore = xSemaphoreCreateBinary(); //xSemaphoreCreateBinary function is a function to dynamically create a binary semaphore
    //Return NULL, the creation of the binary semaphore fails; return other values, indicating the handle of the binary semaphore that was successfully created;
    //So BinarySemaphore represents the handle of the successfully created binary semaphore;
    
    //Create Task1 task
    xTaskCreate((TaskFunction_t)task1_task, //task function
                (const char* )"task1_task", //Task name
                (uint16_t )TASK1_STK_SIZE, //task stack size
                (void* )NULL, //Parameters passed to the task function
                (UBaseType_t )TASK1_TASK_PRIO, //task priority
                (TaskHandle_t* ) & amp;Task1Task_Handler); //task handle
    //Create Task2 task
    xTaskCreate((TaskFunction_t)DataProcess_task, //task function
                (const char* )"DataProcess_task", //Task name
                (uint16_t )DATAPROCESS_STK_SIZE, //Task stack size
                (void* )NULL, //Parameters passed to the task function
                (UBaseType_t )DATAPROCESS_TASK_PRIO, //task priority
                (TaskHandle_t* ) & amp;DataProcess_Handler); //task handle
                                  
    vTaskDelete(StartTask_Handler); //Delete the start task
    taskEXIT_CRITICAL(); //Exit the critical section
}

//Task1 task
//Control LED0 to flash to indicate that the system is running
void task1_task(void *pvParameters)
{
    while(1)
    {
        LED0=!LED0;
        vTaskDelay(500); //Delay 500ms, which is 500 clock beats
    }
}

//DataProcess_task function
//Instruction processing tasks, control different peripherals according to the received instructions
void DataProcess_task(void *pvParameters)
{
    u8len=0;
    u8 CommandValue=COMMANDERR;
    BaseType_t err=pdFALSE;
    
    u8 *CommandStr;
    
    while(1)
    {
        err=xSemaphoreTake(BinarySemaphore,portMAX_DELAY); //Get the semaphore function; return value pdTURE, get the semaphore successfully; pdFALSE, get the semaphore failed;
        //The first parameter is the semaphore handle to be obtained
        //The second parameter, blocking time, is set to portMAX_DEALY here, which is translated as waiting infinitely until the semaphore is obtained.
        if(err==pdTRUE) //Get the semaphore successfully
        {
            len=USART_RX_STA & amp;0x3fff; //Get the length of data received this time
            //Receive status
            //bit15, reception completion flag
            //bit14, received 0x0d
            //bit13~0, the number of valid bytes received
            CommandStr=mymalloc(SRAMIN,len + 1); //Apply for memory. The pointer points to the first address of the requested memory.
            sprintf((char*)CommandStr,"%s",USART_RX_BUF); //Print the receive buffer area and save the data in the receive buffer area to CommandStr
            CommandStr[len]='\0'; //Add string end symbol
            //CommandStr is a pointer with a length of len. The array starts from the subscript 0, so len represents the last one of the array.
            LowerToCap(CommandStr,len); //Convert the string to uppercase
            CommandValue=CommandProcess(CommandStr); //Command parsing, that is, obtaining the macro defined above 1 2 3 4
            if(CommandValue!=COMMANDERR)//If the judgment statement is established, it means that CommandValue is not equal to 0xFF, which is one of the instructions LED1ON, LED1OFF, BEEPON, and BEEPOFF
            {
                printf("The command is: %s\r\\
",CommandStr);
                switch(CommandValue)
                {
                    case LED1ON:
                        LED1=0;
                        break;
                    case LED1OFF:
                        LED1=1;
                        break;
                    case BEEPON:
                        BEEP=1;
                        break;
                    case BEEPOFF:
                        BEEP=0;
                        break;
                }
            }
            else
            {//When the command is wrong, the development board will send a command error message to the serial port debugging assistant.
                //For example, if we send the LED1_off command, the serial port assistant will display: Invalid command, please re-enter!!
                printf("Invalid command, please re-enter!!\r\\
");
            }
            USART_RX_STA = 0;
            memset(USART_RX_BUF,0,USART_REC_LEN); //Clear the serial port receive buffer
            myfree(SRAMIN,CommandStr); //Release memory
        }
    }
}


The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. Algorithm skill tree Home page Overview 57457 people are learning the system