Learn FreeRTOS - (Create Tasks) - 2 from 0

Keywords: C

supplement

Before we begin today's content, let's add the last article. From Single Chip Microcomputer to Operating System-1 A little missing knowledge.

 BaseType_t xTaskCreate(       TaskFunction_t pvTaskCode,
                               const char * const pcName,
                               uint16_t usStackDepth,
                               void *pvParameters,
                               UBaseType_t uxPriority,
                               TaskHandle_t *pvCreatedTask
                           );

The problem of stack size in creating tasks is described in task.h as follows:

/**
* @param usStackDepth The size of the task stack specified as the number of variables the stack * can hold - not the number of bytes.  For example, if the stack is 16 bits wide and  
* usStackDepth is defined as 100, 200 byteswill be allocated for stack storage.
*/

When a task is created, the kernel is divided into a unique stack that each task assigns to itself. The usStackDepth value is used to tell the kernel how much stack space it should allocate.

This value specifies how many words the stack space can hold, not how many bytes.

The document also shows that if it is 16-bit wide, if usStackDepth = 100; then it is 200 byte s.

Of course, I use stm32, 32-bit width, usStackDepth=100; that's 400 byte s.

All right, that's it. Now let's officially begin our topic today.

I have learned the application layer of things, many of the underlying things I do not understand, the level is limited, please forgive the mistakes.

In fact, when I write my own articles, I also follow my brother's book to look at the bottom of things, but I do not understand myself and dare not scribble. So, in this series of articles "From SCM to Operating System", I will talk about the bottom layer, more about the application layer, mainly the aspects of use.

According to the general habit of writing code, all kinds of initialization in main function have been completed, and the task has been created successfully, then the task scheduling can be started.

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//Setting System Interrupt Priority Grouping 4
    Delay_Init();                       //Delay function initialization 
    Uart_Init(115200);                  //Initialize Serial Port
    LED_Init();                     //Initialize LED
    KEY_Init(); 
    //Create Start Tasks
    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 task functions
                (UBaseType_t    )START_TASK_PRIO,       //Task Priority
                (TaskHandle_t*  )&StartTask_Handler);   //Task Handle
    vTaskStartScheduler();          //Open Task Scheduling
}

Let's take a brief look at the process of creating tasks. Although you can use them, you need to know about them.

Note: The creation tasks described below are xTaskCreate (dynamic creation) rather than static creation.

pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); 
 /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
             if( pxStack != NULL )
             {
                 /* Allocate space for the TCB. */
                 pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
                 /*lint !e961 MISRA exception as the casts are only redundant for some paths. */
                 if( pxNewTCB != NULL )
                 {
                    /* Store the stack location in the TCB. */
                    pxNewTCB->pxStack = pxStack;
                }
                else
                {
                    /* The stack cannot be used as the TCB was not created.  Free
                    it again. */
                    vPortFree( pxStack );
                }
            }
            else
            {
                pxNewTCB = NULL;
            }
        }

Firstly, pvPortMalloc is used to allocate space to the task stack. If (pxStack!= NULL), if the memory application is successful, then the task control block is applied for memory. PxNewTCB = TCB_t*) pvPortMalloc (sizeof (TCB_t); pvPortMalloc (also used); if the task control block memory application fails, the memory vPortFree (pxStack) of the previously successful task stack is released;

Then it initializes the task related things and adds the newly initialized task control block to the prvAddNewTaskToReadyList (pxNewTCB) in the list.

Finally, return the status of the task, pdPASS if it succeeds, and errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY if it fails.

prvInitialiseNewTask(    pxTaskCode, 
                         pcName, 
                         ( uint32_t ) usStackDepth,
                         pvParameters,
                         uxPriority, 
                         pxCreatedTask,
                         pxNewTCB, 
                         NULL );
            prvAddNewTaskToReadyList( pxNewTCB );
            xReturn = pdPASS;
        }
        else
        {
            xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
        }
        return xReturn;
    }
// Relevant macro definitions
#define pdPASS            ( pdTRUE )
#define pdTRUE            ( ( BaseType_t ) 1 )
/* FreeRTOS error definitions. */
#define errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY    ( -1 )

Specific static void prvInitialiseNewTask() implementation refers to FreeRTOS task. C file 767 lines of code. Specific static void prvAddNewTaskToReadyList (TCB_t*pxNewTCB) implementation refers to 963 lines of code in FreeRTOS tasks.c file.

Because these are static functions in tasks.c, which are only used by xTaskCreate to create intra-task calls, we do not need to pay attention to the implementation of these functions, of course, if you need to understand.

Once the task is created, task scheduling is started:

vTaskStartScheduler();          //Open Task Scheduling

In task scheduling, an idle task is created (all we will do is create tasks dynamically, but static creation is actually the same).

xReturn = xTaskCreate(    prvIdleTask,
                          "IDLE", configMINIMAL_STACK_SIZE,
                          ( void * ) NULL,
                          ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
                          &xIdleTaskHandle ); 
/*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
    }
//Relevant macro definitions:
#define tskIDLE_PRIORITY            ( ( UBaseType_t ) 0U )
#ifndef portPRIVILEGE_BIT
    #define portPRIVILEGE_BIT ( ( UBaseType_t ) 0x00 )
#endif
#define configUSE_TIMERS                        1                              
 //Enable Software Timer for 1:00

From the above code, we can see that the priority of the idle task is tskIDLE_PRIORITY 0, that is to say, the priority of the idle task is the lowest. When the CPU is idle, it executes idle tasks, waiting to switch higher priority tasks at any time.

If we use a software timer, we also need to create a timer task. The function created is:

#if ( configUSE_TIMERS == 1 )
    BaseType_t xTimerCreateTimerTask( void )

And then turn off the interruption.

portDISABLE_INTERRUPTS();

As for the reasons for the interruption, there are also explanations:

/* Interrupts are turned off here, toensure a tick does not occur
before or during the call toxPortStartScheduler().  The stacks of
the created tasks contain a status wordwith interrupts switched on
so interrupts will automatically getre-enabled when the first task
starts to run. */

Interrupts are turned off here to ensure that ticks do not occur before or during the call to xPortStartScheduler(). The task created by the stack contains a status word that opens the interrupt, so the interrupt will automatically restart and start running on the first task.

So how to open the interrupt???? This is a very important question.

Don't worry, we will open the interrupt in the SVC interrupt service function.

Look at the code:

__asm void vPortSVCHandler( void )
{
         PRESERVE8
         ldr    r3, =pxCurrentTCB  /* Restore the context. */
         ldrr1, [r3]                            /* UsepxCurrentTCBConst to get the pxCurrentTCB address. */
         ldrr0, [r1]                            /* Thefirst item in pxCurrentTCB is the task top of stack. */
         ldmiar0!, {r4-r11}             /* Pop theregisters that are not automatically saved on exception entry and the criticalnesting count. */
         msrpsp, r0                                   /*Restore the task stack pointer. */
         isb
         movr0, #0
         msr  basepri, r0
         orrr14, #0xd
         bxr14
}
msr  basepri, r0

It turns the interrupt on. It doesn't matter if you don't understand it. I don't know the compilation either. It's better if you can understand it.

xSchedulerRunning = pdTRUE;

Task Scheduling Begins to Run

/* If configGENERATE_RUN_TIME_STATS isdefined then the following
macro must be defined to configure thetimer/counter used to generate
the run time counter time base. */
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

If configGENERATE_RUN_TIME_STATS uses time statistics, the macro is 1, then the user must implement a macro portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(), which is used to configure a timer or counter.

When we get to our point and start task scheduling, the task won't return.

if( xPortStartScheduler() != pdFALSE )
{
   /*Should not reach here as if the scheduler is running the
    functionwill not return. */                   
 }

Then you can start the first task, it's very difficult, right? I also felt it at the beginning, but writing this article, I think it's okay, not too difficult, maybe it's also looking at the code and other people's books. Writing things is actually quite good, can deepen understanding, people who have written articles know that understanding is not necessarily able to write out, so I still hope that friends. They can contribute. Jay is always welcome...

To start the task, just add your own code according to the routine template. It's very simple.

Create tasks first:

 xTaskCreate((TaskFunction_t )led0_task,    
              (const char*    )"led0_task",  
              (uint16_t       )LED0_STK_SIZE,
              (void*          )NULL,                                    
              (UBaseType_t    )LED0_TASK_PRIO,   
              (TaskHandle_t*  )&LED0Task_Handler);  
   //Create the task of LED 1
  xTaskCreate((TaskFunction_t )led1_task,    
              (const char*    )"led1_task",  
              (uint16_t       )LED1_STK_SIZE,
              (void*          )NULL,
              (UBaseType_t    )LED1_TASK_PRIO,
              (TaskHandle_t*  )&LED1Task_Handler);      

Start task scheduling after creating a task:

1vTaskStartScheduler();          //Open Task Scheduling

Then the task function is implemented concretely:

//LED 0 Task Function
void led0_task(void *pvParameters)
{
   while(1)
    {
       LED0=~LED0;
       vTaskDelay(500);
    }
}  

//LED 1 Task Function
void led1_task(void *pvParameters)
{
   while(1)
    {
       LED1=0;
       vTaskDelay(200);
       LED1=1;
       vTaskDelay(800);
    }
}

Okay, so far as today's introduction is concerned, it will continue to be updated in the future. Please look forward to it.~

Welcome to discuss the knowledge of operating system

Our group number is 783234154.

Welcome to the "IoT Development of the Internet of Things" Public Number

Posted by avvllvva on Mon, 14 Oct 2019 23:22:31 -0700