Deep anatomy ~ FreeRtos reading notes 3 freertos scheduler startup, interrupt priority management, interrupt priority grouping

3. Freertos scheduler startup, interrupt priority management, interrupt priority grouping

 

Never underestimate something insignificant, even a short line of code!

(The resolution of some pictures is too large and the display is not clear, save to local or drag to enlarge).

 

Many routines use the vTaskStartScheduler function as the last line of code executed by main, because after executing vTaskStartScheduler, the sovereignty is handed over to the freertos scheduler, and the program will never be returned in this function.

The main actions of vTaskStartScheduler are: create an IDLE task, initialize important registers such as systick, manually trigger an interrupt to reconfigure the MSP and return to handle mode to execute the first task. The figure shows the main process of the source code:

(Scheduler startup process)

Process Analysis:

3.1 Create IDLE task

A task named IDLE is created in the vTaskStartScheduler function. The function of this task is ignored first, and it will be self-defeating after the source code is broken one by one.

 

3.2 Turn off interrupts

Some non-reentrant code segments or some defined global variables often enter the critical section protection before using them, so as to avoid damaging the data currently being processed when an emergency occurs. For example, if some global variables are used in the interrupt or in multiple tasks, when the task is processing the data of a global array, the interrupt occurs and the data in the array is updated. some unforeseen results.

I remember that I wrote a lot of such error codes during my internship. I didn't consider whether the code could be re-entrant or not. As a result, I could only harm others and myself~.

There are some global variables in task.c. In order to protect these variable data from being abnormally destroyed, freertos uses interrupt masking, but this operation does not kill all maskable interrupts: freertos provides "optional" Managed Outage Scope". Taking a certain interrupt priority as a threshold, interrupts with a priority lower than (or equal to) this dividing line will all be masked, and higher interrupts will not be affected. Of course, the "higher" priority will not be executed in vain, and the price is that freertos will not let high-priority interrupts participate in any api calls. So whether the "high" priority is triggered or not will not affect the normal operation of freertos.

In fact, such a convenient shielding operation is also due to the power of the NVIC in the CM3/CM4. Writing here, finally leads to today's protagonist~. NVIC: Nested Vectored Interrupt Controller , which is equipped with an exception response system at the CM3/CM4 core level, supporting numerous system exceptions and external interrupts. Not only that, NVIC owners have more sophisticated designs, and the BASEPRI register is one of them. The threshold used for masking interrupts is stored in it. When masking, the highest priority value that needs to be masked interrupts is filled into the register to achieve the effect. Writing 0 to BASEPRI means stop masking any interrupts.

The threshold can be seen in the configuration header file: configMAX_SYSCALL_INTERRUPT_PRIORITY

The source code for freertos to perform interrupt masking:

mov r1, #configMAX_SYSCALL_INTERRUPT_PRIORITY                    

msr basepri, r1   

 

3.3 Priority Definition

An 8-bit register is used in CM3 to express interrupt priority, so in principle, CM3 supports 256 programmable priorities and 3 fixed priorities ( reset, NMI , hard fault ).

However, most CM3 chips will simplify the design and trim the effective bits of the priority to achieve the purpose of reducing the number of priorities. However, no matter how trimmed, the effective bits are counted from the high order.

Note: The minimum number of bits allowed by CM3 is 3 bits, that is, at least 8 levels of priority must be supported. Increasing the number of bits will add more cost and power consumption.

The smaller the value of the priority, the higher the priority .

For example, a register that uses 3 significant bits to express priority:

(Use 3 significant bits to express priority)

Writing 1 to the valid bit can read back 1, and the invalid bit can read back 0. In the above register, if 0xFF is written to it, 0xE0 is read back. You can use the read back method to determine how many levels of interrupts the chip supports.

Compare the next 3-bit and 4-bit priority status:

(3-4 priority comparison)

The above picture is still from the collection, it is really cool to have a screenshot. Obviously, 4 bits are more detailed than the priority range represented by 3 bits. Of course, this is nonsense, but it is more intuitive why the effective bits must be placed in the high order rather than the low order. This is also entirely for developers to consider, the high bit can simplify the cross-device migration of the program:

Suppose a program is to be transplanted from 4 bits to 3 bits (3 bits to 4 bits are definitely no problem, because 4 bits have a wider range), without deliberately changing the priority, as shown in the figure:

(MSB reduces valid bit condition)

After transplantation, the priority effective bit is reduced, the original two adjacent priorities are set to one, and the disappeared interrupt priority is pulled up, but these changes will not bring fatal errors.

You will not be so lucky if you use the low-order bits to represent the significant bits:

(LSB reduces significant bit condition)

After the red area appears, the priority of more than 7 will increase, which will bring unpredictable errors to the previous program.

 

3.4 Priority Grouping

In the last function xPortStartScheduler, there is a piece of code that is executed when the assertion macro is turned on. The assertion configuration must be turned on during the debugging phase, so that the program can detect exceptions in time during the debugging phase. The program segment is as follows:

At first glance, I don't know what the purpose of this process is to achieve. Before trying to figure it out, we must first understand the principle of priority grouping.

Regarding the information on priority grouping, cite the original words of the treasure book: "In order to make the preemption function more controllable, CM3 also divides the 256-level priority into two segments, namely preemption priority and subpriority. There is a register called "application interrupt and reset control register", which has a bit field called "priority group". The value of this bit field has an effect on each priority configurable exception - give it priority The level is divided into bit segments: the bit segment where the MSB is located (on the left) corresponds to the preemption priority, and the bit segment where the LSB is located (on the right) corresponds to the sub-priority ".

(grouping bit and grouping priority correspondence table)

The preemption priority determines the preemption behavior: when the system is responding to an exception L, if an exception H with a higher preemption priority comes, H can preempt L. Sub-priority deals with "housekeeping": when more than one exception with the same preemption priority is pending, the exception with the highest sub-priority is responded to first. This priority grouping specifies that the subpriority is at least 1 bit. Therefore, the preemption priority is at most 7 bits, resulting in the phenomenon that there are only 128 preemptions at most. However, CM3 allows grouping from bit 7, where all bits express sub-priority and none express preemption priority, so that preemption does not occur between all priority-programmable exceptions - equivalent to between them. The interrupt nesting mechanism of CM3 is disabled.

Note: The grouping position can be in the invalid bit, and the effect of any invalid bit is the same, and they all drop the sub-priority.

Then after understanding the priority grouping, analyze the main action of the above assertion macro, first write 0xFF to an external interrupt priority configuration register and then read it back, mainly to determine how many priority valid bits the chip has, and then move to the left according to the valid bits. Decrease the maximum number of groups from 7 by 1, so that the maximum position of the group that can be divided into the maximum number of preemption priorities is obtained. is divided into 8 groups, but the grouping positions that can divide it into 8 groups are 4, 3, 2, 1, 0. This program only takes the maximum value so the value of ulMaxPRIGROUPValue is 4.

The ulMaxPRIGROUPValue value will be used in the interrupt api provided by freertos, and the vPortValidateInterruptPriority() function will be executed first in these apis. There are two assertion macros in this function, the first one checks whether the current interrupt priority is lower than the maskable interrupt (the above-mentioned priority higher than the maskable interrupt is not allowed to call the interrupt api of freertos); the second one Check whether the grouping value set in NVIC is greater than ulMaxPRIGROUPValue. If it is greater than ulMaxPRIGROUPValue, it means that there are main priority and sub-priority. Freertos does not allow sub-priority, otherwise the assertion macro will serve, and paste the original comment: Priority grouping: The interrupt controller (NVIC) allows the bits that define each interrupt's priority to be split between bits that define the interrupt's pre-emption priority bits and bits that define the interrupt's sub-priority.   For simplicity all bits must be defined to be pre-emption priority bits . The following assertion will fail if this is not the case (if some bits represent a sub-priority).

As described in the comments, it is for simplicity not to set subpriorities.

 

3.5 The final configuration of the scheduler

Also before the scheduler starts:

1.       Configure the priority of two interrupt PendSV and SysTick, configure the macro in the header file

configKERNEL_INTERRUPT_PRIORITY indicates their interrupt level. The value of configKERNEL_INTERRUPT_PRIORITY must be greater than or equal to the value of the macro configMAX_SYSCALL_INTERRUPT_PRIORITY, or the priority of PendSV and SysTick interrupts must be within the range of maskable interrupts. Otherwise, it will be a mess.

2.       Turn on the heart systick of freertos, and fill in the count value to determine the frequency of systick interruption, which is also the frequency of task scheduling or the length of time slices.

3.       Jump into the prvPortStartFirstTask function:

ldr r0, NVIC_VTABLE_R   // NVIC_VTABLE_R: 0xE000ED08 , the address of the vector table offset register, the vector table needs to be redirected now

ldr r0, [r0]        

ldr r0, [r0]       //Remove the new address of MSP  

msr msp, r0     //Reconfigure MSP address                               

cpsie i       //Open interrupt                     

etc

isb

svc #0    //Trigger SVC interrupt

So far, before entering the SVC interrupt, the freertos open scheduler code process has been executed, and freertos will enter the right track after waiting for the SVC interrupt processing to complete. Run it! fucking source code

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325441903&siteId=291194637
Recommended