从 Cortex M0+ 上的硬故障中恢复

Recover from Hard Fault on Cortex M0+

直到现在,我在向量 table:

中定义了一个 C 中的硬故障处理程序
.sect ".intvecs"

.word _top_of_main_stack
.word _c_int00
.word NMI  
.word Hard_Fault
.word Reserved
.word Reserved  
.word Reserved
.word Reserved
.word Reserved
.word Reserved
.word Reserved
.word Reserved
.word Reserved
.word Reserved
.word Reserved
.word Reserved
....
....
....

我们的一个测试通过写入一个不存在的地址触发了硬故障(故意)。测试完成后,调用函数的处理程序 returns 和皮质将从故障中恢复。值得一提的是,处理程序没有任何参数。

现在我正在编写一个真正的处理程序。 我为堆栈帧创建了一个结构,因此我们可以在出现故障时打印 PC、LR 和 xPSR:

typedef struct
{
    int     R0              ;  
    int     R1              ;  
    int     R2              ;  
    int     R3              ;  
    int     R12             ;
    int     LR              ; 
    int     ReturnAddress   ; 
    int     xPSR            ;

}   InterruptStackFrame_t  ;

我在 C 中的硬故障处理程序定义为:

void Hard_Fault(InterruptStackFrame_t* p_stack_frame)
{
    // Write to external memory that I can read from outside
    /* prints a message containing information about stack frame:
     * p_stack_frame->LR, p_stack_frame->PC, p_stack_frame->xPSR,
     * (uint32_t)p_stack_frame (SP)
     */
}

我创建了一个汇编函数:

        .thumbfunc  _hard_fault_wrapper
_hard_fault_wrapper: .asmfunc
    MRS    R0, MSP    ; store pointer to stack frame
    BL     Hard_Fault ; go to C function handler
    POP    {R0-R7}    ; pop out all stack frame
    MOV    PC, R5     ; jump to LR that was in the stack frame (the calling function before the fault)

.endasmfunc

现在是时候说我没有 OS,所以我不必检查 LR 的位[2],因为我肯定知道我使用的是 MSP 而不是 PSP。

程序编译运行正常,我使用JTAG确保所有寄存器都恢复到想要的值。 当执行最后一条命令 (MOV PC, R5) PC returns 到正确的地址时,但在某些时候,调试器指示 M0 被锁定在硬故障中并且无法恢复。

我不明白使用 C 函数作为处理程序与调用 C 函数的汇编函数之间的区别。

有人知道问题出在哪里吗?

最终,我将使用一个会卡住处理器的断言函数,但我希望它是可选的并由我决定。

解释"old_timer"的评论:

在 Cortex 上进入异常或中断处理程序时,LR 寄存器具有特殊值。

通常您从异常处理程序 return 只需跳转到该值(通过将该值写入 PC 寄存器)。

Cortex CPU 将自动从堆栈中弹出所有寄存器,并重置中断逻辑。

但是当直接跳转到存储在堆栈中的PC时,您会破坏一些寄存器并且不会恢复中断逻辑。

因此这不是一个好主意。

相反,我会这样做:

    .thumbfunc  _hard_fault_wrapper
_hard_fault_wrapper: .asmfunc
    MRS    R0, MSP
    B      Hard_Fault

编辑

使用 B 指令可能不起作用,因为 B 指令允许的 "distance" 比 BL 指令更受限制。

不过,您可以使用两种可能性(不幸的是,我不确定这些是否一定有效)。

第一个将return到进入汇编程序处理程序时在LR寄存器中传递的地址:

    .thumbfunc  _hard_fault_wrapper
_hard_fault_wrapper: .asmfunc
    MRS    R0, MSP
    PUSH   {LR}
    BL     Hard_Fault
    POP    {PC}

第二个将间接进行跳跃:

    .thumbfunc  _hard_fault_wrapper
_hard_fault_wrapper: .asmfunc
    MRS    R0, MSP
    LDR    R1, =Hard_Fault
    MOV    PC, R1

编辑 2

You cannot use LR because it holds EXC_RETURN value. ... You have to read the LR from stack and you must clean the stack from the stack frame, because the interrupted program doesn't know that a frame was stored.

根据 Cortex M3 手册,您 必须 通过编写三个 EXC_RETURN 之一退出异常处理程序PC 寄存器的值。

如果您只是跳转到存储在堆栈帧中的 LR 值,您将留在异常处理程序中!

如果在程序中发生了一些愚蠢的事情,CPU 将假定异常处理程序内部发生异常并挂起。

我假设 Cortex M0 在这一点上与 M3 的工作方式相同。

如果你想在异常处理期间修改一些CPU寄存器,你可以修改堆栈帧。当您将 EXC_RETURN 值写入 PC 寄存器时,Thc CPU 将自动 pop 来自堆栈帧的所有寄存器。

如果要修改堆栈帧中不存在的寄存器之一(例如R5),您可以直接在异常处理程序中修改它。

这表明您的中断处理程序存在另一个问题:

指令POP {R0-R7}将设置寄存器R4R7的值匹配被中断的程序。根据C代码,R12也会被销毁。这意味着在程序被中断时这四个寄存器突然改变而程序没有为此做好准备!