单片机程序调用子程序、进入中断后回到原来的位置,硬件错误中断定位(快速定位)产生的错误代码

  • 一、简介
    • 1. 寄存器组
    • 2.在 Thumb 状态下的 PC 寄存器(R15)
    • 3.进入和退出中断/异常
      • 1.进入
      • 2.退出
    • 4.特别说明
  • 二、调用子程序
    • 1分析寄存器
  • 三、中断返回
    • 1.分析反汇编
    • 2.分析堆栈
  • 四、硬件错误中断
    • 1.实现一个硬件错误程序
    • 2.定位错误位置
    • 3.使用工具快速定位错误

一、简介

  在编写程序时一般并不会在意在单片机中程序是如何执行的,在单片机中如何做到切换子程序后再回到原来的调用子程序的地方,在单片机中进入中断后和调用子程序回到原来的位置有什么区别,在编写程序后单片机运行时有时会进入硬件错误中断,时常为此抓破脑袋也不知是为何产生。

  这篇文章将解决以下内容:

  • 子程序和中断后如何回到原来的程序中及器区别;
  • 出现硬件错误中断后如何定位(快速定位)发生错误的代码。

注意:这篇文章将不会深入讨论堆栈内容,所以子程序中将不会再有子程序调用。

  解决以上问题将用到以下知识:

1. 寄存器组

在这里插入图片描述

  具体的寄存器描述参照:CM3和CM4内核中的寄存器组R0-R15的作用,尽量详细+举例

2.在 Thumb 状态下的 PC 寄存器(R15)

  在 CM3 中的指令至少是半字对齐的,所以 PC 的 LSB 总是读回 0。
  在跳转程序时,直接写 PC 的值或者使用跳转指令,加载到 PC 的数值必须是奇数( LSB=1),用来表明是在Thumb 状态下执行。如果写入 PC 的数值中 LSB != 1,则视为企图转入 ARM 模式,CM3 将产生一个 fault 异常(这是因为CM3 只使用 Thumb‐2 指令集)。

3.进入和退出中断/异常

1.进入

  响应异常/中断时,需要做以下动作:

  • 1.寄存器(R0-R3、R12、LR、PC、xPSR)入栈;
  • 2.取向量(从向量表中获取异常/中断的入口函数地址);
  • 3.更新寄存器(更新堆栈指针SP,更新连接寄存器LR,更新程序计数器PC)

注意:在 异常/中断 中 LR 的值将被重新解释为 EXC_RETURN (一种新的值)
lr被重新解释
寄存器入栈的顺序:
寄存器入栈

2.退出

   退出时只需要将 LR 的值送入 PC 即可,即启动处理器的中断返回序列:

  • 1.出栈:将进入中断时压入栈中的寄存器恢复。
  • 2.更新NVIC寄存器:随着异常的返回,它的活动位也被硬件清除。

4.特别说明

  以下程序均使用 STM32F407VE 芯片,基于 RT-Thread Studio 平台的通用项目建立,并且部分使用 QEMU 仿真,硬件错误中断程序使用单片机实机运行。

二、调用子程序

  编写以下内容(在同一文件)

/// 子程序
void subroutine(void)
{
    int i = 0;
    i++;
}

int main(void)
{
    subroutine(); ///< 断点
    return 0;
}

  编译开始在线调试运行,并且在 main 函数的 subroutine() 函数调用处插入断点。

1分析寄存器

  进入 main 函数后,寄存器中的值如下:
main函数寄存器值
  按下 F11 ,跳转进入 subroutine ,进入该函数后寄存器的值如下,需要特别关注的是 lr 寄存器的值,为 0x8002ca9 ,打开反汇编代码框显示如下图2,lr 中存储的即为在 main 函数中调用该函数的下一条命令 return 0 (地址为:为 0x8002ca8)。

至于 lr 中的值为为什么比真实的地址多1,请看 简介第2条

在这里插入图片描述
在这里插入图片描述

三、中断返回

  编写以下内容(在同一文件),这会触发一个 PendSV 异常并且进入PendSV 中断。

#define    NVIC_INT_CTRL        0xE000ED04          /* interrupt control state register */
#define    NVIC_PENDSVSET       0x10000000          /* value to trigger PendSV exception */

void PendSV_Handler(void)
{
    int i=0;	///< 断点
    i++;
}

void subroutine(void)
{
    int i = 0;
    i++;
}

void trigger_PendSV(void)
{
    *((volatile unsigned long *)(NVIC_INT_CTRL)) |= NVIC_PENDSVSET;
}

int main(void)
{
    trigger_PendSV();
    subroutine();
    return 0;
}

1.分析反汇编

  在中断服务函数 PendSV_Handler 的反汇编中,首先执行(这里没有谈内核隐含的自动压栈操作,隐含的操作在 简介第 3 条 详细介绍)寄存器 R7 的压栈操作,接着将 SP 指针向下偏移12个地址长度。
反汇编分析

2.分析堆栈

  查看 SP 寄存器所指的内存空间,从反汇编的内容可知,需要将 SP 指针按 32 位 4 字节地址移动 4 个长度,即为中断之前的内核的隐含入栈内容,这个内容指示了在退出中断后应该返回的位置。
在这里插入图片描述
在这里插入图片描述

四、硬件错误中断

  如上所见,中断均可以使用该方式来查找中断返回的地址,换句话说,可以找到发生中断的大致位置,同理也可以使用该方法找到发生硬件错误的原因。

1.实现一个硬件错误程序

  编写以下内容(在同一文件),这会触发一个 fault 异常并且进入 HardFault_Handler 中断。

注意:以下代码在单片机平台运行,仿真平台不会触发除零异常,也不会进入 HardFault_Handler 中断。

void HardFault_Handler(void)
{
    while(1);
}


int main(void)
{
    int i =0, k,j=10;
    //使能除0异常
    *((volatile unsigned long *)(0xE000ED14)) |= 0x10;
    k = j/i;
    return 0;
}

2.定位错误位置

  借助 《三、中断返回》 章节的分析过程,可以在 SP 指针中找到对应的堆栈内存,从内存中可以找到 LR 指针和 PC 指针的值,这将作为定位错误的主要方式。
  为了结合简述中的 EXC_RETURN 功能,这里使用了 RT-Thraed 操作系统,所以在进入 HardFault_Handler 中断服务函数时 lr 的值是 0xfffffffd ,表示返回线程时使用线程堆栈(PSP)。
在这里插入图片描述
  查看 PSP 堆栈数据,以及查看 PSP 指针所指向的内存空间。
在这里插入图片描述
在这里插入图片描述

  在获取到 LR(0x08000B49) 和 PC(0x0800FEDA) 的值后,需要使用反汇编的内容查找对应的地址来定位发生错误(触发 fault 异常的地址),一般来讲只需要使用 PC 的值即可定位到错误地点。
在这里插入图片描述

3.使用工具快速定位错误

  上述的定位错误方式需要使用调试器来实时调试获取错误信息,这会增加许多的工作量,不是一个最优的定位错误的方法,推荐使用工具:CmBacktrace: ARM Cortex-M 系列 MCU 错误追踪库
  CmBacktrace (Cortex Microcontroller Backtrace)是一款针对 ARM Cortex-M 系列 MCU 的错误代码自动追踪、定位,错误原因自动分析的开源库。该库的移植说明请参照开源库的 README_ZH.md 文件,本文不再赘述。
  但是本文并不会完全使用该库,只使用了如下内容:

/// cm_backtrace/fault_handler/gcc/cmb_fault.S
.syntax unified
.thumb
.text

/* NOTE: If use this file's HardFault_Handler, please comments the HardFault_Handler code on other file. */

.global HardFault_Handler
.type HardFault_Handler, %function
HardFault_Handler:
    MOV     r0, lr                  /* get lr */
    MOV     r1, sp                  /* get stack pointer (current is MSP) */
    BL      cm_backtrace_fault

Fault_Loop:
    BL      Fault_Loop              /* while(1) */
-------------------------------------------------------------------------------------------

/// cm_backtrace/cm_backtrace.c
void cm_backtrace_fault(uint32_t fault_handler_lr, uint32_t fault_handler_sp)

  按需修改以上代码后,当出现错误时会在控制台依次打印 R0-R3、R12、LR、PC、xPSR 的值,拿到这些值后在开源库的目录 tools/addr2line/win64/addr2line.exe 找到该工具,在终端中使用该工具。

注意:使用该工具前,必须生成 .elf 文件。

在这里插入图片描述

在这里插入图片描述

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐