RP2040关键汇编函数解释
RP2040关键汇编函数解释
- 1 FreeRTOS源码下载地址
- 2 RP2040关键汇编函数解释
- 2.1 xPortPendSVHandler
- 2.2 vPortStartFirstTask
- 2.2.1 vPortStartFirstTask关键代码解释
- 2.2.2 vPortStartFirstTask源码
- 2.3 pxPortInitialiseStack
1 FreeRTOS源码下载地址
https://www.freertos.org/
2 RP2040关键汇编函数解释
2.1 xPortPendSVHandler
void xPortPendSVHandler( void )
{/* This is a naked function. */#if ( configNUMBER_OF_CORES == 1 )__asm volatile(" .syntax unified \n"" mrs r0, psp \n"" \n"" ldr r3, pxCurrentTCBConst2 \n" /* Get the location of the current TCB. */" ldr r2, [r3] \n"" \n"" subs r0, r0, #32 \n" /* Make space for the remaining low registers. */" str r0, [r2] \n" /* Save the new top of stack. */" stmia r0!, {r4-r7} \n" /* Store the low registers that are not saved automatically. */" mov r4, r8 \n" /* Store the high registers. */" mov r5, r9 \n"" mov r6, r10 \n"" mov r7, r11 \n"" stmia r0!, {r4-r7} \n"#if portUSE_DIVIDER_SAVE_RESTORE" movs r2, #0xd \n" /* Store the divider state. */" lsls r2, #28 \n"/* We expect that the divider is ready at this point (which is* necessary to safely save/restore), because:* a) if we have not been interrupted since we entered this method,* then >8 cycles have clearly passed, so the divider is done* b) if we were interrupted in the interim, then any "safe" - i.e.* does the right thing in an IRQ - use of the divider should* have waited for any in-process divide to complete, saved and* then fully restored the result, thus the result is ready in* that case too. */" ldr r4, [r2, #0x60] \n" /* SIO_DIV_UDIVIDEND_OFFSET */" ldr r5, [r2, #0x64] \n" /* SIO_DIV_UDIVISOR_OFFSET */" ldr r6, [r2, #0x74] \n" /* SIO_DIV_REMAINDER_OFFSET */" ldr r7, [r2, #0x70] \n" /* SIO_DIV_QUOTIENT_OFFSET *//* We actually save the divider state in the 4 words below* our recorded stack pointer, so as not to disrupt the stack* frame expected by debuggers - this is addressed by* portEXTRA_STACK_SIZE */" subs r0, r0, #48 \n"" stmia r0!, {r4-r7} \n"#endif /* portUSE_DIVIDER_SAVE_RESTORE */" push {r3, r14} \n"" cpsid i \n"" bl vTaskSwitchContext \n"" cpsie i \n"" pop {r2, r3} \n" /* lr goes in r3. r2 now holds tcb pointer. */" \n"" ldr r1, [r2] \n"" ldr r0, [r1] \n" /* The first item in pxCurrentTCB is the task top of stack. */" adds r0, r0, #16 \n" /* Move to the high registers. */" ldmia r0!, {r4-r7} \n" /* Pop the high registers. */" mov r8, r4 \n"" mov r9, r5 \n"" mov r10, r6 \n"" mov r11, r7 \n"" \n"" msr psp, r0 \n" /* Remember the new top of stack for the task. */" \n"#if portUSE_DIVIDER_SAVE_RESTORE" movs r2, #0xd \n" /* Pop the divider state. */" lsls r2, #28 \n"" subs r0, r0, #48 \n" /* Go back for the divider state */" ldmia r0!, {r4-r7} \n" /* Pop the divider state. *//* Note always restore via SIO_DIV_UDIVI*, because we will overwrite the* results stopping the calculation anyway, however the sign of results* is adjusted by the h/w at read time based on whether the last started* division was signed and the inputs' signs differed */" str r4, [r2, #0x60] \n" /* SIO_DIV_UDIVIDEND_OFFSET */" str r5, [r2, #0x64] \n" /* SIO_DIV_UDIVISOR_OFFSET */" str r6, [r2, #0x74] \n" /* SIO_DIV_REMAINDER_OFFSET */" str r7, [r2, #0x70] \n" /* SIO_DIV_QUOTIENT_OFFSET */#else /* if portUSE_DIVIDER_SAVE_RESTORE */" subs r0, r0, #32 \n" /* Go back for the low registers that are not automatically restored. */#endif /* portUSE_DIVIDER_SAVE_RESTORE */" ldmia r0!, {r4-r7} \n" /* Pop low registers. */" \n"" bx r3 \n"" .align 4 \n""pxCurrentTCBConst2: .word pxCurrentTCB \n");#else /* if ( configNUMBER_OF_CORES == 1 ) */__asm volatile(" .syntax unified \n"" mrs r1, psp \n"" \n"" adr r0, ulAsmLocals2 \n" /* Get the location of the current TCB for the current core. */" ldmia r0!, {r2, r3} \n"#if portRUNNING_ON_BOTH_CORES" ldr r0, [r2] \n" /* r0 = Core number */" lsls r0, r0, #2 \n"" adds r3, r0 \n" /* r3 = &pxCurrentTCBs[get_core_num()] */#else" \n" /* r3 = &pxCurrentTCBs[0] */#endif /* portRUNNING_ON_BOTH_CORES */" ldr r0, [r3] \n" /* r0 = pxCurrentTCB */" \n"" subs r1, r1, #32 \n" /* Make space for the remaining low registers. */" str r1, [r0] \n" /* Save the new top of stack. */" stmia r1!, {r4-r7} \n" /* Store the low registers that are not saved automatically. */" mov r4, r8 \n" /* Store the high registers. */" mov r5, r9 \n"" mov r6, r10 \n"" mov r7, r11 \n"" stmia r1!, {r4-r7} \n"#if portUSE_DIVIDER_SAVE_RESTORE/* We expect that the divider is ready at this point (which is* necessary to safely save/restore), because:* a) if we have not been interrupted since we entered this method,* then >8 cycles have clearly passed, so the divider is done* b) if we were interrupted in the interim, then any "safe" - i.e.* does the right thing in an IRQ - use of the divider should* have waited for any in-process divide to complete, saved and* then fully restored the result, thus the result is ready in* that case too. */" ldr r4, [r2, #0x60] \n" /* SIO_DIV_UDIVIDEND_OFFSET */" ldr r5, [r2, #0x64] \n" /* SIO_DIV_UDIVISOR_OFFSET */" ldr r6, [r2, #0x74] \n" /* SIO_DIV_REMAINDER_OFFSET */" ldr r7, [r2, #0x70] \n" /* SIO_DIV_QUOTIENT_OFFSET *//* We actually save the divider state in the 4 words below* our recorded stack pointer, so as not to disrupt the stack* frame expected by debuggers - this is addressed by* portEXTRA_STACK_SIZE */" subs r1, r1, #48 \n"" stmia r1!, {r4-r7} \n"#endif /* portUSE_DIVIDER_SAVE_RESTORE */#if portRUNNING_ON_BOTH_CORES" ldr r0, [r2] \n" /* r0 = Core number */#else" movs r0, #0 \n"#endif /* portRUNNING_ON_BOTH_CORES */" push {r3, r14} \n"" cpsid i \n"" bl vTaskSwitchContext \n"" cpsie i \n"" pop {r2, r3} \n" /* lr goes in r3. r2 now holds tcb pointer. */" \n"" ldr r1, [r2] \n"" ldr r0, [r1] \n" /* The first item in pxCurrentTCB is the task top of stack. */" adds r0, r0, #16 \n" /* Move to the high registers. */" ldmia r0!, {r4-r7} \n" /* Pop the high registers. */" mov r8, r4 \n"" mov r9, r5 \n"" mov r10, r6 \n"" mov r11, r7 \n"" \n"" msr psp, r0 \n" /* Remember the new top of stack for the task. */" \n"#if portUSE_DIVIDER_SAVE_RESTORE" movs r2, #0xd \n" /* Pop the divider state. */" lsls r2, #28 \n"" subs r0, r0, #48 \n" /* Go back for the divider state */" ldmia r0!, {r4-r7} \n" /* Pop the divider state. *//* Note always restore via SIO_DIV_UDIVI*, because we will overwrite the* results stopping the calculation anyway, however the sign of results* is adjusted by the h/w at read time based on whether the last started* division was signed and the inputs' signs differed */" str r4, [r2, #0x60] \n" /* SIO_DIV_UDIVIDEND_OFFSET */" str r5, [r2, #0x64] \n" /* SIO_DIV_UDIVISOR_OFFSET */" str r6, [r2, #0x74] \n" /* SIO_DIV_REMAINDER_OFFSET */" str r7, [r2, #0x70] \n" /* SIO_DIV_QUOTIENT_OFFSET */#else /* if portUSE_DIVIDER_SAVE_RESTORE */" subs r0, r0, #32 \n" /* Go back for the low registers that are not automatically restored. */#endif /* portUSE_DIVIDER_SAVE_RESTORE */" ldmia r0!, {r4-r7} \n" /* Pop low registers. */" \n"" bx r3 \n"" \n"" .align 4 \n""ulAsmLocals2: \n"" .word 0xD0000000 \n" /* SIO */" .word pxCurrentTCBs \n");#endif /* if ( configNUMBER_OF_CORES == 1 ) */
}
2.2 vPortStartFirstTask
2.2.1 vPortStartFirstTask关键代码解释
- 多核心场景,在多核心系统中,获取当前核心正在运行的任务的 TCB 地址。
#if portRUNNING_ON_BOTH_CORES" adr r1, ulAsmLocals \n" /* Get the location of the current TCB for the current core. */" ldmia r1!, {r2, r3} \n"" ldr r2, [r2] \n" /* r2 = Core number */" lsls r2, #2 \n"" ldr r3, [r3, r2] \n" /* r3 = pxCurrentTCBs[get_core_num()] */
-
adr r1, ulAsmLocals
:通过相对地址计算,将ulAsmLocals标签的地址加载到r1(ulAsmLocals是下面定义的本地数据区)。 -
ldmia r1!, {r2, r3}
:从ulAsmLocals
地址开始,连续读取两个字到r2和r3,并自动更新r1(!表示地址自增)。ulAsmLocals中存储的是
SIO 寄存器地址(0xD0000000)和pxCurrentTCBs数组地址。 -
ldr r2, [r2]
:读取 SIO 寄存器(通常用于获取当前核心 ID)的值到r2,即r2 = 当前核心编号(如 0 或 1)。 -
lsls r2, #2
:将核心编号左移 2 位(等价于 ×4,因为 TCB 指针是 32 位,占 4 字节),计算数组索引的字节偏移量。 -
ldr r3, [r3, r2]
:r3
初始为pxCurrentTCBs
数组的基地址,加上偏移量后,读取当前核心对应的pxCurrentTCB(当前任务控制块指针)到r3。 - 初始化任务栈指针(PSP)
" ldr r0, [r3] \n" /* The first item in pxCurrentTCB is the task top of stack. */
" adds r0, #32 \n" /* Discard everything up to r0. */
" msr psp, r0 \n" /* This is now the new top of stack to use in the task. */
-
ldr r0, [r3]
:r3是当前任务的 TCB 指针,TCB 的第一个成员是任务栈顶指针(pxTopOfStack),读取该值到r0。 -
adds r0, #32
:将栈顶指针增加 32 字节。FreeRTOS 任务栈中,栈顶依次保存了r4-r11等寄存器(共 8 个 32 位寄存器,8×4=32 字节),这里跳过这些已保存的寄存器,指向需要手动恢复的寄存器区域。这个和RP2040的pxPortInitialiseStack
相关。 -
msr psp, r0
:将r0的值写入 PSP(进程栈指针),设置任务使用的栈指针(用户任务运行在 PSP 栈上)。 - 切换到用户任务栈模式
" movs r0, #2 \n" /* Switch to the psp stack. */
" msr CONTROL, r0 \n"
" isb \n"
-
movs r0, #2
:将r0设为 2(二进制10)。 -
msr CONTROL, r0
:写入 CONTROL 寄存器(ARM 的控制寄存器)。CONTROL[1] = 1表示任务运行时使用 PSP 栈,而非 MSP 栈。 -
isb
:指令同步屏障,确保 CONTROL 寄存器的修改立即生效,后续指令使用新的栈配置。 -
恢复任务上下文,为执行用户任务代码做准备。
" pop {r0-r5} \n" /* Pop the registers that are saved automatically. */
" mov lr, r5 \n" /* lr is now in r5. */
" pop {r3} \n" /* Return address is now in r3. */
" pop {r2} \n" /* Pop and discard XPSR. */
-
pop {r0-r5}
:从 PSP 栈中弹出r0-r5寄存器的值(这些寄存器在任务切换时被自动保存到栈中)。 -
mov lr, r5
:将r5的值(栈中保存的lr,链接寄存器)写入实际的lr寄存器。 -
pop {r3}
:弹出栈中的返回地址(任务函数的入口地址)到r3。 -
pop {r2}
:弹出栈中的 XPSR(程序状态寄存器)值到r2(这里仅弹出但不使用,因为任务启动时 XPSR 已正确初始化)。 - 启用中断并启动任务
" cpsie i \n" /* The first task has its context and interrupts can be enabled. */
" bx r3 \n" /* Finally, jump to the user defined task code. */
-
cpsie i
:清除中断禁止位(Enable Interrupts),允许系统响应中断(任务启动后需要中断支持调度)。 -
bx r3
:跳转到r3存储的地址(用户任务函数的入口),正式启动第一个任务。 - 为多核心场景提供获取核心 ID 和当前 TCB 的必要数据。
#if portRUNNING_ON_BOTH_CORES" \n"" .align 4 \n""ulAsmLocals: \n"" .word 0xD0000000 \n" /* SIO */" .word pxCurrentTCBs \n"
#endif /* portRUNNING_ON_BOTH_CORES */
2.2.2 vPortStartFirstTask源码
代码路径:
FreeRTOSv202406.01-LTS\FreeRTOS-LTS\FreeRTOS\FreeRTOS-Kernel\portable\ThirdParty\GCC\RP2040\port.c
代码源码:
void vPortStartFirstTask( void )
{#if ( configNUMBER_OF_CORES == 1 )__asm volatile (" .syntax unified \n"" ldr r2, pxCurrentTCBConst1 \n" /* Obtain location of pxCurrentTCB. */" ldr r3, [r2] \n"" ldr r0, [r3] \n" /* The first item in pxCurrentTCB is the task top of stack. */" adds r0, #32 \n" /* Discard everything up to r0. */" msr psp, r0 \n" /* This is now the new top of stack to use in the task. */" movs r0, #2 \n" /* Switch to the psp stack. */" msr CONTROL, r0 \n"" isb \n"" pop {r0-r5} \n" /* Pop the registers that are saved automatically. */" mov lr, r5 \n" /* lr is now in r5. */" pop {r3} \n" /* Return address is now in r3. */" pop {r2} \n" /* Pop and discard XPSR. */" cpsie i \n" /* The first task has its context and interrupts can be enabled. */" bx r3 \n" /* Finally, jump to the user defined task code. */" .align 4 \n""pxCurrentTCBConst1: .word pxCurrentTCB\n");#else /* if ( configNUMBER_OF_CORES == 1 ) */__asm volatile (" .syntax unified \n"#if configRESET_STACK_POINTER" ldr r0, =0xE000ED08 \n" /* Use the NVIC offset register to locate the stack. */" ldr r0, [r0] \n"" ldr r0, [r0] \n"" msr msp, r0 \n" /* Set the msp back to the start of the stack. */#endif /* configRESET_STACK_POINTER */#if portRUNNING_ON_BOTH_CORES" adr r1, ulAsmLocals \n" /* Get the location of the current TCB for the current core. */" ldmia r1!, {r2, r3} \n"" ldr r2, [r2] \n" /* r2 = Core number */" lsls r2, #2 \n"" ldr r3, [r3, r2] \n" /* r3 = pxCurrentTCBs[get_core_num()] */#else" ldr r3, =pxCurrentTCBs \n"" ldr r3, [r3] \n" /* r3 = pxCurrentTCBs[0] */#endif /* portRUNNING_ON_BOTH_CORES */" ldr r0, [r3] \n" /* The first item in pxCurrentTCB is the task top of stack. */" adds r0, #32 \n" /* Discard everything up to r0. */" msr psp, r0 \n" /* This is now the new top of stack to use in the task. */" movs r0, #2 \n" /* Switch to the psp stack. */" msr CONTROL, r0 \n"" isb \n"" pop {r0-r5} \n" /* Pop the registers that are saved automatically. */" mov lr, r5 \n" /* lr is now in r5. */" pop {r3} \n" /* Return address is now in r3. */" pop {r2} \n" /* Pop and discard XPSR. */" cpsie i \n" /* The first task has its context and interrupts can be enabled. */" bx r3 \n" /* Finally, jump to the user defined task code. */#if portRUNNING_ON_BOTH_CORES" \n"" .align 4 \n""ulAsmLocals: \n"" .word 0xD0000000 \n" /* SIO */" .word pxCurrentTCBs \n"#endif /* portRUNNING_ON_BOTH_CORES */);#endif /* if ( configNUMBER_OF_CORES == 1 ) */
}
2.3 pxPortInitialiseStack
pxPortInitialiseStack
和vPortStartFirstTask
是相对应的。vPortStartFirstTask
的处理流程就是按照pxPortInitialiseStack
初始化的栈结构一一对应起来的。
StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,TaskFunction_t pxCode,void * pvParameters )
{/* Simulate the stack frame as it would be created by a context switch* interrupt. */pxTopOfStack--; /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */*pxTopOfStack = portINITIAL_XPSR; /* xPSR */pxTopOfStack--;*pxTopOfStack = ( StackType_t ) pxCode; /* PC */pxTopOfStack--;*pxTopOfStack = ( StackType_t ) portTASK_RETURN_ADDRESS; /* LR */pxTopOfStack -= 5; /* R12, R3, R2 and R1. */*pxTopOfStack = ( StackType_t ) pvParameters; /* R0 */pxTopOfStack -= 8; /* R11..R4. */return pxTopOfStack;
}