You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ARM内联汇编实现自定义上下文切换遇阻,求技术支持

ARM Cortex-M3上下文切换:内联汇编逻辑验证与调试指南

我正在开发基于STM32F10x的上下文切换项目,目前卡在ARM内联汇编环节。作为新手,我搞不懂怎么用调试器单步跟踪每个汇编上下文的执行,也不确定自己写的内联汇编逻辑是否正确,求技术帮助。

项目代码

#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_exti.h"


volatile int32_t TimeDelay = 0;
void Delay(uint32_t nTime);

uint32_t stack_Blink_1[9]= {0};
uint32_t *sp_Blink_1 = &stack_Blink_1[8];

uint32_t stack_Blink_2[9]= {0};
uint32_t *sp_Blink_2 = &stack_Blink_2[8];

uint32_t curr_Task = 0;
uint32_t next_Task = 0;
uint32_t *PSP_Array[2]={0};


void GPIO_SETUP(void);
void EXTI_SETUP(void);
void Blink_1(void);
void Blink_2(void);

int main(void){

    SysTick_Config(7200000);
    GPIO_SETUP();
    EXTI_SETUP();
    NVIC_EnableIRQ(EXTI2_IRQn);

    SCB->CCR |= SCB_CCR_STKALIGN_Msk;

    sp_Blink_1--;
    *(sp_Blink_1--) = (1U << 24); //xPSR
    sp_Blink_1--;
    sp_Blink_1--;
    *sp_Blink_1 = (uint32_t)&Blink_1; //PC
    sp_Blink_1-= 4;

    sp_Blink_2--;
    *(sp_Blink_2--) = (1U << 24); //xPSR
    sp_Blink_2--;
    sp_Blink_2--;
    *sp_Blink_2 = (uint32_t)&Blink_2; //PC
    sp_Blink_2-= 4;
    
    PSP_Array[0]=sp_Blink_1;
    PSP_Array[1]=sp_Blink_2;

    Blink_1();

    while(1){
        
    }
}

void PendSV_Handler(void){
    
    __ASM volatile(
                    //save current context
                    "MRS R0, PSP\n\t" // Get current process stack pointer value
                    "ISB\n\t"
                    "STMDB R0!, {R4-R11}\n\t" // Save R4 to R11 in task stack (8 regs)
                    "LDR R1, %[curr_Task]\n\t" 
                    "LDR R2, [R1]\n\t" // Get current task ID
                    "LDR R3, %[psp_array]\n\t"
                    "STR R0,[R3, R2, LSL #2]\n\t" // Save PSP value into PSP_array
                    //-----------------------------------------------------------------
                    //load next context
                    "LDR R4, %[next_task]\n\t"
                    "LDR R4, [R4]\n\t" // Get next task ID
                    "STR R4, [R1]\n\t" // Set curr_task = next_task
                    "LDR R0,[R3, R4, LSL #2]\n\t" // Load PSP value from PSP_array
                    "LDMIA R0!, {R4-R11}\n\t" // Load R4 to R11 from task
                    "MSR PSP, R0\n\t" // Set PSP to next task
                    "ISB\n\t"
                    "BX LR\n\t" // Return
                    //"ALIGN 4\n\t"
                    : // Output Operands
                    : [curr_Task] "m" (curr_Task),
                      [psp_array] "m" (PSP_Array),
                      [next_task] "m" (next_Task) // Input Operands
                );
}

void SysTick_Handler(void){
    if(TimeDelay > 0)
        TimeDelay--;
}

void EXTI2_IRQHandler(void){
    if(curr_Task == 0){
        EXTI_ClearITPendingBit(EXTI_Line2);
        curr_Task = 1;
    }else{
        EXTI_ClearITPendingBit(EXTI_Line2);
        curr_Task =0;
    }
    SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
}

void Delay(uint32_t nTime){
    TimeDelay = nTime;
    while(TimeDelay != 0);
}

void Blink_1(void){
    while(1){
        GPIO_ResetBits(GPIOB,GPIO_Pin_1);
        Delay(10);
        GPIO_SetBits(GPIOB,GPIO_Pin_1);
        Delay(10);
    }
}

void Blink_2(void){
    while(1){
        GPIO_ResetBits(GPIOB,GPIO_Pin_0);
        Delay(10);
        GPIO_SetBits(GPIOB,GPIO_Pin_0);
        Delay(10);
    }
}

void GPIO_SETUP(void){

    GPIO_InitTypeDef GPIO_InitStruct_A2;
    GPIO_InitStruct_A2.GPIO_Pin  = GPIO_Pin_2;
    GPIO_InitStruct_A2.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStruct_A2.GPIO_Mode = GPIO_Mode_IPD;

    GPIO_InitTypeDef GPIO_InitStruct_B0;
    GPIO_InitStruct_B0.GPIO_Pin  = GPIO_Pin_0;
    GPIO_InitStruct_B0.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStruct_B0.GPIO_Mode = GPIO_Mode_Out_PP;

    GPIO_InitTypeDef GPIO_InitStruct_B1;
    GPIO_InitStruct_B1.GPIO_Pin  = GPIO_Pin_1;
    GPIO_InitStruct_B1.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStruct_B1.GPIO_Mode = GPIO_Mode_Out_PP;

    GPIO_InitTypeDef GPIO_InitStruct_C13;
    GPIO_InitStruct_C13.GPIO_Pin  = GPIO_Pin_13;
    GPIO_InitStruct_C13.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStruct_C13.GPIO_Mode = GPIO_Mode_Out_PP;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

    GPIO_Init(GPIOA,&GPIO_InitStruct_A2);
    GPIO_Init(GPIOB,&GPIO_InitStruct_B0);
    GPIO_Init(GPIOB,&GPIO_InitStruct_B1);
    GPIO_Init(GPIOC,&GPIO_InitStruct_C13);

    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, 2);

    GPIO_SetBits(GPIOB,GPIO_Pin_0);
    GPIO_SetBits(GPIOB,GPIO_Pin_1);
    GPIO_SetBits(GPIOC,GPIO_Pin_13);

}

void EXTI_SETUP(void){

    EXTI_InitTypeDef EXTI_InitStruct_2;
    EXTI_InitStruct_2.EXTI_Line = EXTI_Line2;
    EXTI_InitStruct_2.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStruct_2.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_InitStruct_2.EXTI_LineCmd = ENABLE;

    EXTI_Init(&EXTI_InitStruct_2);
    
}

一、内联汇编逻辑问题分析

1. 任务切换核心逻辑矛盾

EXTI2_IRQHandler中直接修改curr_Task,但PendSV_Handler的汇编逻辑是从next_Task读取目标任务ID,再将curr_Task覆盖为next_Task的值。这会导致:触发中断后curr_Task被修改,但PendSV执行时又会把curr_Task改回next_Task的初始值(0),完全无法完成切换。

修复方案:
修改EXTI2_IRQHandler,切换的是next_Task而非curr_Task:

void EXTI2_IRQHandler(void){
    EXTI_ClearITPendingBit(EXTI_Line2);
    next_Task = curr_Task == 0 ? 1 : 0;
    SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
}

2. 任务栈初始化错误

Cortex-M3的任务栈需要符合异常返回栈帧结构,才能保证切换后正确恢复执行。当前代码存在两个问题:

  • 栈空间不足:stack_Blink_1/2仅定义了9个uint32_t,但任务栈需要容纳:8个手动保存的寄存器(R4-R11) + 8个自动压栈的寄存器(xPSR、PC、LR、R12、R3-R0),至少需要16个uint32_t空间,当前栈会出现越界访问。
  • 栈帧结构不完整:初始化时仅设置了xPSR和PC,缺少LR、R12、R3-R0的占位,第一次切换任务时会读取非法内存值。

修复方案:

// 扩大栈空间到16个uint32_t
uint32_t stack_Blink_1[16]= {0};
uint32_t *sp_Blink_1 = &stack_Blink_1[15];

uint32_t stack_Blink_2[16]= {0};
uint32_t *sp_Blink_2 = &stack_Blink_2[15];

// 正确初始化任务栈帧(模拟异常压栈结构)
void init_task_stack(uint32_t **sp, void (*task_func)(void)){
    // 依次压入:xPSR, PC, LR, R12, R3, R2, R1, R0
    (*sp)--;
    **sp = (1U << 24); // xPSR(必须设置T位为1,Thumb模式)
    (*sp)--;
    **sp = (uint32_t)task_func; // PC
    (*sp)--;
    **sp = (uint32_t)0xFFFFFFFD; // LR(异常返回时返回线程模式,使用PSP)
    (*sp)--;
    **sp = 0; // R12
    (*sp)--;
    **sp = 0; // R3
    (*sp)--;
    **sp = 0; // R2
    (*sp)--;
    **sp = 0; // R1
    (*sp)--;
    **sp = 0; // R0
    // 预留R4-R11的位置(8个寄存器,初始化时无需赋值,第一次切换时会保存)
    *sp -= 8;
}

// main中调用初始化
int main(void){
    // ... 其他初始化代码 ...
    init_task_stack(&sp_Blink_1, Blink_1);
    init_task_stack(&sp_Blink_2, Blink_2);
    PSP_Array[0]=sp_Blink_1;
    PSP_Array[1]=sp_Blink_2;
    // ...
}

3. PendSV汇编逻辑细节优化

  • 读取curr_Task和next_Task时,直接用LDR R1, =curr_Task会更直观,但当前用%[curr_Task]的约束是正确的,编译器会自动处理地址加载。
  • 保存PSP后,无需额外ISB指令,MRS和STMDB后已经保证指令同步。

二、汇编上下文调试指南

1. 调试环境准备

使用Keil MDK或STM32CubeIDE,开启调试会话后:

  • 打开Disassembly视图:查看编译后的汇编代码,定位到PendSV_Handler的内联汇编部分。
  • 打开寄存器视图:重点关注PSP、R0-R11、LR、xPSR的值。
  • 打开内存视图:输入sp_Blink_1或sp_Blink_2的地址,实时查看任务栈的内容变化。

2. 单步调试步骤

  1. 在EXTI2_IRQHandler的SCB->ICSR |= ...行设置断点,触发外部中断后暂停。
  2. 查看curr_Task和next_Task的值,确认切换目标正确。
  3. 单步进入PendSV_Handler(F11),逐行执行内联汇编:
    • 执行MRS R0, PSP后,检查R0的值是否等于当前任务的栈指针(比如sp_Blink_1)。
    • 执行STMDB R0!, {R4-R11}后,查看内存视图中R0指向的位置,确认R4-R11的值已保存到栈中。
    • 执行到STR R0,[R3, R2, LSL #2]后,检查PSP_Array[curr_Task]的值是否更新为新的栈指针。
    • 加载下一个任务的PSP后,执行LDMIA R0!, {R4-R11},检查寄存器视图中R4-R11的值是否恢复为目标任务的保存值。
    • 执行BX LR后,程序会跳转到目标任务的执行点,查看GPIO状态是否切换,确认上下文切换成功。

3. 关键验证点

  • 任务栈的xPSR值必须为0x01000000(T位为1,Thumb模式),否则会触发硬件错误。
  • 异常返回时LR的值必须为0xFFFFFFFD,确保返回线程模式并使用PSP。
  • 每次切换后,PSP的值必须对应目标任务的栈指针,curr_Task必须等于next_Task。

内容的提问来源于stack exchange,提问作者Clyde Xander

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.11 11:45:55