资料目录
TI-MCU专区
ARM CORTEX-M3程序进入错误中断调试笔记
发布日期:2011/11/23
用ARM CORTEX-M3内核的MCU做开发,在调试程序时经常遇到会遇到程序进入错误中断的情况,图 11所示。

遇到这种情况,很多朋友茫然不知所措,因为目前主流的开发环境IAR和KEIL都只提供单步跟踪的功能,而没有轨迹跟踪或单步后退的功能。如果程序较小或程序进入main()函数不久就进入错误中断,还可以通过单步跟踪的方法找出问题。但如果程序很大或程序运行很久后才进入错误中断就非常头痛了,不知程序是从哪里跳进了错误中断,更不知道引发错误中断的原因。
格芯单片机在使用TI的ARM CORTEX-M3过程中也曾遇到这些问题,并总结出了一些经验,在此无私奉献给大家,为大家解决问题提供一个思路。
声明:此方法对内存泄露不管用。
言归正传,我们知道ARM CORTEX-M3进入中断时,内核自动将R0,R1,R2,R3,R12,LP,PC,XPSR 8个寄存器压入堆栈,图 12所示。而在退出中断程序时,内核自动从堆栈中恢复这8个寄存器的值。

我们知道,PC 总是指向正在取值的指令,那么在进入错误中断时,堆栈中保存的PC指向进入错误中断前执行的最后一条指令。
LR 用于保存子程序的返回地址,那么在进入错误中断时,堆栈中保存的LR的值即进入错误中断前执行的函数地址。
由此可以看出,我们可以通过堆栈中的PC与LR的值找出进入错误中断前执行的最后一条指令与执行的最后一个函数,从而可以定位出发生错误的地点,进而可以分析出引发错误的原因。
当前主流ARM编译,如KEIL,IAR等都是使用满递减的堆栈增长方式,即堆栈是从高地址开始,向低地址增长,图 12所示。
我们知道在错误中断中,程序一直在做死循环,所以进入中断程序后,SP的值是没有变化的。
由此我们可以得出堆栈中保存的LR的计算公式
LR = SP + 5*4
5 是因为LR是进入中断时,内核压栈时,从SP往上第6个数据
4 是因为内核压栈时是4字节对齐
LR = SP + 20
由此我们可以得出堆栈中保存的PC的计算公式
PC = SP + 6*4
6 是因为PC是进入中断时,内核压栈时,从SP往上第7个数据
4 是因为内核压栈时是4字节对齐
PC = SP + 24
需要注意的是ARM CORTEX-M3内核的堆栈分为主堆栈(MSP)和进程堆栈(PSP)。在没有使用OS的情况下,中断程序与普通程序都使用MSP;在使用OS的情况下,中断程序使用MSP,而进程程序使用PSP。
我们以一个实例来说明,如程序清单 11所示,这是本来是一个完整的串口初始化函数,但我特意屏蔽掉了L(1)语句,由于没有使能UART0外设,这样程序在执行L(2)时会进入错误中断。
程序清单 11 示例程序
int main(void)
{
// Set the clocking to run directly from the crystal.
//
SysCtlClockSet(SYSCTL_SYSDIV_1 | SYSCTL_USE_OSC | SYSCTL_OSC_MAIN |
SYSCTL_XTAL_16MHZ);
//
// Enable processor interrupts.
IntMasterEnable();
// 使能UATR, 配置UART IO
SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
// SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA); L(1)
//
// Set GPIO A0 and A1 as UART pins.
//
GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1); L(2)
//
// Configure the UART for 115,200, 8-N-1 operation.
//
UARTConfigSetExpClk(UART0_BASE, SysCtlClockGet(), 115200,
(UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE |
UART_CONFIG_PAR_NONE));
//
// Enable the UART interrupt.
//
IntEnable(INT_UART0);
UARTIntEnable(UART0_BASE, UART_INT_RX | UART_INT_RT);
//
// Prompt for text to be entered.
UARTSend((unsigned char *)"Enter text: ", 12);
// Loop forever echoing data through the UART.
while(1)
{
}
}
在进入错误中断后,我们可以寄存器串口看到MSP的值是0x200000c8,如图 13所示。

由于本例程没有使用OS,所以MSP就是SP的值。根据上面的公式
LR = SP + 20
= 0x200000c8 + 20
= 0x200000dc
我们在MEMERY窗口,输入0x200000dc,可以看出0x200000dc地址的值是0x00000707,如图 14所示。这个0x00000707就是最后执行的函数的地址。

得到最后执行的函数的地址后,我们在汇编程序窗口,输入0x00000707,IAR开发环境就自动跳转到对应的汇编函数,如图 15所示。

根据汇编窗口提示,进入错误中断时最后调用的一个函数是void GPIOPinTypeUART(unsigned long ulPort, unsigned char ucPins),即L(2)函数调用的第一个函数。