Create the first task source code analysis

After the above operations, we can start the first task. The function prvStartFirstTask() is used to start the first task. This is an assembly function. The source code of the function is as follows:

__asm void prvStartFirstTask( void )
{
PRESERVE8
ldr r0, =0xE000ED08 ;R0=0XE000ED08 (1)
ldr r0, [r0] ;取 R0 所保存的地址处的值赋给 R0 (2)
ldr r0, [r0] ;获取 MSP 初始值 (3)
msr msp, r0 ;复位 MSP (4)
cpsie I ;使能中断(清除 PRIMASK) (5)
cpsie f ;使能中断(清除 FAULTMASK) (6)
dsb ;数据同步屏障 (7)
isb ;指令同步屏障 (8)
svc 0 ;触发 SVC 中断(异常) (9)
nop
nop
}

(1) Save 0XE000ED08 in register R0. Generally speaking, the vector table should be stored from the starting address (0X00000000). However, some applications may need to modify or redefine the vector table at runtime. The Cortex-M processor provides a function called vector table relocation. characteristic. The vector table relocation feature provides a programmable register called the vector table offset register (VTOR). The address of the VTOR register is 0XE000ED08. The vector table can be redefined through this register. For example, in the ST official library of STM32F103, the VTOR register will be set through the function SystemInit(). The code is as follows: SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; //VTOR =0x08000000+0X00 Through the above line of code, the start address of the vector table is redefined to 0X08000000, and the start address of the vector table stores the initial value of MSP.

(2). Read the data at the address stored in R0 and save it in the R0 register, that is, read the value in the register VTOR and save it in the R0 register. After this line of code is executed, the value of R0 should be 0X08000000 . This is the starting address of the redefinition of the vector table

(3) Read the data at the address stored in R0 and save it in the R0 register, that is, read the data stored at the address 0X08000000 and save it in the R0 register (because the value of MSP is saved in the first of variables) . We know that the initial address of the vector table stores the initial value of the main stack pointer MSP. After this line of code is executed, register R0 stores the initial value of MSP. Now let's look at (1), (2), (3) these three steps are just to get the initial value of MSP!

(4) Reset MSP, the initial value of MSP is saved in R0, assigning it to MSP is equivalent to resetting MSP.

(5) and (6), enable interrupt, for details about these two instructions, please refer to Section 4.2.3 of "Chapter 4 Architecture" in "The Definitive Guide".

(7) and (8), data synchronization and instruction synchronization barriers, for details of these two instructions, please refer to Section 5.6.13 of "Chapter 5 Instruction Set" in "The Definitive Guide".

(9) Call SVC instruction to trigger SVC interrupt , SVC is also called request management call, SVC and PendSV exception are very important for OS design. SVC exceptions are triggered by the SVC instruction. For details about SVC, please refer to Section 10.3 of "Chapter 10 OS Support Features" in "The Definitive Guide". In FreeRTOS, only the SVC exception is used to start the first task, and SVC is no longer used in subsequent programs.

8.2.4 SVC interrupt service function

The SVC interrupt is triggered by calling the SVC instruction in the function prvStartFirstTask(), and the start of the first task is completed in the SVC interrupt service function. The SVC interrupt service function should be SVC_Handler(), but in FreeRTOSConfig.h through #define The way to redefine xPortPendSVHandler() is as follows:

#define xPortPendSVHandler PendSV_Handler The function vPortSVCHandler() is defined in the file port.c. This function is also written in assembly. The source code of the function is as follows:

__asm void vPortSVCHandler( void )
{
PRESERVE8
ldr r3, =pxCurrentTCB ;R3=pxCurrentTCB 的地址 (1)
ldr r1, [r3] ;取 R3 所保存的地址处的值赋给 R1 (2)
ldr r0, [r1] ;取 R1 所保存的地址处的值赋给 R0 (3)
ldmia r0!, {r4-r11, r14} ;出栈 ,R4~R11 和 R14 (4)
msr psp, r0 ;进程栈指针 PSP 设置为任务的堆栈 (5)
isb ;指令同步屏障
mov r0, #0 ;R0=0 (6)
msr basepri, r0 ;寄存器 basepri=0,开启中断 (7)
orr r14, #0xd ; (8)
bx r14 (9)
}

(1) Get the storage address of pxCurrentTCB pointer, pxCurrentTCB is a pointer to TCB_t, this pointer always points to the running task. Here first get the address where the pointer is stored

(2) Obtain the address of the first member through the address of the task control block

(3) Obtain the value of the first member through the address of the first member, and the value of the first member is the value at the top of the stack when the function is created. This value is the address of the top of the stack at this moment.

 (4), R4~R11, R14 These registers are popped out of the stack. The instruction LDMIA is used here. The LDMIA instruction is a multi-load/store instruction, but here is a multi-load/store access instruction with write-back. The usage is as follows: LDMIA Rn! , {reg list} means to read from the memory location specified by Rn Multiple words are fetched, the address is incremented (IA) after each read, and Rn is written back after the transfer is complete. For STM32, the address increases by 4 bytes at a time, such as the following code: LDR R0, =0X800 LDMIA R0!, {R2~R4} The above two lines of code are to assign the data at address 0X800 to register R2, and assign the data at address 0X804 to Register R3, assign the data of address 0X8008 to R4 register, and then, the important point comes! At this time R0 is 800A! Through this step, we restore the values ​​of registers R4~R11 from the task stack. Some friends here will ask, why are the registers R0~R3, R12, PC, and xPSR not restored? This is because these registers will be automatically popped (restored) by the MCU when exiting the interrupt, while R4~R11 need to be popped manually by the user. We will talk about this when we analyze the PendSV interrupt service function. After this step, let's take a look at where the top pointer of the stack points to? As shown in Figure 8.2.4.5:

 It can be seen from Figure 8.3.4.5 that after restoring R4~R11 and R14, the top pointer of the stack should point to the address 0X20000EB8, which is the storage address for saving the value of register R0. After exiting the interrupt service function, the process stack pointer PSP should restore other register values ​​from this address.

(5) Set the process stack pointer PSP, PSP=R0=0X20000EB8, as shown in Figure 8.2.4.6

(6) Set register R0 to 0.

(7) Set the register BASEPRI to R0, which is 0, and enable the interrupt!

(8) The value of R14 register is ORed with 0X0D, and the result is the new value of R14 register. It means that the CPU enters the thread mode and uses the process stack after exiting the exception!

 (9) After executing this line of code, the hardware automatically restores the values ​​of the registers R0~R3, R12, LR, PC and xPSR, the stack uses the process stack PSP, and then executes the task function saved in the register PC. So far, the task scheduler of FreeRTOS has officially started running!

Guess you like

Origin blog.csdn.net/qq_51519091/article/details/131627449