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

STM32F1协程框架下高栈消耗ISR独立栈的优化方案问询

基于STM32F1的协程框架ISR独立栈优化方案

问题背景

我在基于STM32F1的嵌入式项目中使用s_task协程框架,该框架简化了应用开发,但每个协程拥有独立栈空间,需按中断嵌套的最坏情况分配栈大小。其中USB通信ISR的栈消耗较大,若为所有协程栈都按最坏情况分配会造成RAM浪费。我已为该ISR实现独立栈,但希望优化实现:一是消除额外的嵌套函数调用,二是让所有ISR共享独立于协程的栈,且不修改现有协程框架。

现有实现代码

#define stringify(s) xstringify(s)
#define xstringify(s) #s

extern "C" {
  
void USB_LP_CAN1_RX0_IRQHandler_impl(void);

#define USB_STACK_SIZE 128
void* _usb_old_sp;
void* _usb_stack[USB_STACK_SIZE];

__attribute__((naked)) void USB_LP_CAN1_RX0_IRQHandler(void) {
    __asm__ volatile(" push {r0, lr} ");
    __asm__ volatile(" ldr  r0, =_usb_old_sp  ");
    __asm__ volatile(" str  sp, [r0]");
    __asm__ volatile(" ldr  sp, =_usb_stack+" stringify(USB_STACK_SIZE) "*4  ");
    USB_LP_CAN1_RX0_IRQHandler_impl();
    __asm__ volatile(" ldr  r0, =_usb_old_sp  ");
    __asm__ volatile(" ldr  sp, [r0]");
    __asm__ volatile(" pop {r0, pc} ");
}

void USB_LP_CAN1_RX0_IRQHandler_impl(void) {
  // ...USB ISR处理逻辑...
}

}

优化思路与建议

1. 消除嵌套函数的简洁实现

将原impl函数内的逻辑直接嵌入裸函数中,通过手动保存/恢复调用寄存器实现C代码执行,避免嵌套调用:

#define stringify(s) xstringify(s)
#define xstringify(s) #s

extern "C" {
  
#define USB_STACK_SIZE 128
void* _usb_old_sp;
void* _usb_stack[USB_STACK_SIZE];

__attribute__((naked)) void USB_LP_CAN1_RX0_IRQHandler(void) {
    __asm__ volatile(" push {r0, lr} ");
    __asm__ volatile(" ldr  r0, =_usb_old_sp  ");
    __asm__ volatile(" str  sp, [r0]");
    __asm__ volatile(" ldr  sp, =_usb_stack+" stringify(USB_STACK_SIZE) "*4  ");
    
    // 手动保存C代码所需的调用寄存器
    __asm__ volatile(" push {r1-r3, r12, lr} ");
    
    // --- 直接写入原impl函数的USB ISR逻辑 ---
    // 例如:USB中断处理、数据收发等代码
    // ...
    
    // 恢复调用寄存器
    __asm__ volatile(" pop {r1-r3, r12, lr} ");
    
    __asm__ volatile(" ldr  r0, =_usb_old_sp  ");
    __asm__ volatile(" ldr  sp, [r0]");
    __asm__ volatile(" pop {r0, pc} ");
}

}

2. 所有ISR共享独立栈的最优方案

利用Cortex-M内核的**主栈指针(MSP)与进程栈指针(PSP)**特性,实现协程栈与ISR栈的天然隔离:

  • 协程框架通常使用PSP作为线程栈(每个协程独立栈),线程模式下通过CONTROL寄存器的SPSEL位选择PSP
  • Cortex-M硬件默认在进入中断时自动切换到MSP,因此所有ISR会自动使用MSP指向的主栈

具体步骤:

  1. 调整启动文件的主栈配置:在startup_stm32f10x_xx.s中修改主栈的大小和地址,分配一块足够容纳所有ISR(含中断嵌套)的栈空间:
    ; 示例:分配1KB主栈,可根据实际ISR栈消耗调整
    Stack_Size      EQU     0x00000400
    
        AREA    STACK, NOINIT, READWRITE, ALIGN=3
    Stack_Mem       SPACE   Stack_Size
    __initial_sp
    
  2. 确认协程框架使用PSP:s_task框架初始化时会自动设置CONTROL寄存器的SPSEL位(__set_CONTROL(__get_CONTROL() | (1 << 1))),确保线程模式使用PSP。

此方案无需为每个ISR编写栈切换代码,所有ISR自动共享主栈,协程栈与ISR栈完全隔离,且不需要修改协程框架核心逻辑。

3. 多ISR共享栈的过渡宏实现

若暂时无法修改启动文件,可通过宏定义实现通用的ISR共享栈包装,避免重复代码:

#define stringify(s) xstringify(s)
#define xstringify(s) #s

// 共享ISR栈配置
#define SHARED_ISR_STACK_SIZE 256
void* _shared_isr_old_sp;
void* _shared_isr_stack[SHARED_ISR_STACK_SIZE];

// 通用ISR共享栈包装宏
#define ISR_WITH_SHARED_STACK(ISR_NAME) \
__attribute__((naked)) void ISR_NAME(void) { \
    __asm__ volatile(" push {r0, lr} "); \
    __asm__ volatile(" ldr  r0, =_shared_isr_old_sp  "); \
    __asm__ volatile(" str  sp, [r0]"); \
    __asm__ volatile(" ldr  sp, =_shared_isr_stack+" stringify(SHARED_ISR_STACK_SIZE) "*4  "); \
    __asm__ volatile(" push {r1-r3, r12, lr} "); \
    ISR_NAME##_impl(); \
    __asm__ volatile(" pop {r1-r3, r12, lr} "); \
    __asm__ volatile(" ldr  r0, =_shared_isr_old_sp  "); \
    __asm__ volatile(" ldr  sp, [r0]"); \
    __asm__ volatile(" pop {r0, pc} "); \
} \
void ISR_NAME##_impl(void)

// 使用示例:为USB和USART1 ISR配置共享栈
ISR_WITH_SHARED_STACK(USB_LP_CAN1_RX0_IRQHandler) {
    // USB ISR处理逻辑
}

ISR_WITH_SHARED_STACK(USART1_IRQHandler) {
    // USART1 ISR处理逻辑
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 00:10:36