在调试RTC过程中,程序在主循环中履行两次后就进入hard fault的while(1)中止,keil显现调试窗口显现imprecise data bus error。完善RTC装备的时序也杯水车薪。网上查到一些hard fault的材料:
<STM32F10xxx Cortex-M3 programming manual>2.3.2对hard fault, bus fault等有详细的解说。keil的网站上也有概括性的解说:hard fault由bus fault, memory management fault或usage fault引起,前者有固定的仅次于NMI的高优先级;调试过程中呈现的bus error归于bus fault,是取指或取值时的内存过错。ST论坛上关于hard fault的评论,大牛们说:
是因为读写了一个不合法方位,
“100% of the hard faults Ive had are caused by variables accessing out of bounds.”,
“The Cortex-M3 pushes fault context on to the stack (some 8 dwords as I recall), I think Joseph Yiu has some example of instrumenting this. This could should permit you to determine the faulting PC. With this and the register info, and a map file you should be able to zero in on what is going on.
MRS R0, PSP ; Read PSP
LDR R1, [R0, #24] ; Read Saved PC from Stack” 能看到犯错的PC值却是一个很便利的作业,不过还没试过。
回头看自己的程序,从最简逻辑开端烧写运转,发现当增加到在Time_Display()时进入了hard fault。检查代码,函数中界说了一个char类型数组,用于寄存需求显现到LCD上的时刻字符串,但数组长度小于字符串长度。增大长度,就处理了问题。公然如大牛们所说,问题存在于数组越界。
之前我也犯过相似过错,可其时的现象是,串口实践宣布的数据和数组中的数据比较,后半部分时对时错。其时的变量为大局变量,此处变量为局部变量。查到如下阐明:“一个由c/C++编译的程序占用的内存分为以下几个部分: 1、栈区(stack)— 由编译器主动分配开释 ,寄存函数的参数值,局部变量的值等。其操作方法相似于数据结构中的栈。2、堆区(heap) — 一般由程序员分配开释, 若程序员不开释,程序结束时或许由OS收回 。留意它与数据结构中的堆是两回事,分配方法却是相似于链表,呵呵。3、大局区(静态区)(static)—,大局变量和静态变量的存储是放在一块的,初始化的大局变量和静态变量在一块区域, 未初始化的大局变量和未初始化的静态变量在相邻的另一块区域。 – 程序结束后有体系开释. 4、文字常量区—常量字符串便是放在这儿的。 程序结束后由体系开释. 5、程序代码区—寄存函数体的二进制代码。”大局变量储存在大局区,它的越界将影响其他变量的值,对程序运转不会有丧命影响。局部变量在栈中,一起入栈的还有函数的回退地址,函数参数等。本次呈现问题的代码段为:
- voidTime_Show(void)
- {
- while(1)
- {
- /*If1shasbeenelapased*/
- if(TimeDisplay==1)
- {
- uint32_tCounter=0;
- Counter=RTC_GetCounter();
- Time_Display(Counter);
- TimeDisplay=0;
- }
- }
- }
- voidTime_Display(uint32_tTimeVar)
- {
- uint32_tTHH=0,TMM=0,TSS=0;
- charbuf[10];
- /*ResetRTCCounterwhenTimeis23:59:59*/
- if(TimeVar==0x0001517F)
- {
- RTC_WaitForLastTask();
- RTC_SetCounter(0x0);
- /*WaituntillastwriteoperationonRTCregistershasfinished*/
- RTC_WaitForLastTask();
- }
- /*Computehours*/
- THH=TimeVar/3600;
- /*Computeminutes*/
- TMM=(TimeVar%3600)/60;
- /*Computeseconds*/
- TSS=(TimeVar%3600)%60;
- /*sprintf(buf,”0x%08x”,buf);
- sprintf(buf,”0x%08x”,&TimeVar);
- sprintf(buf,”0x%08x”,&THH);
- sprintf(buf,”0x%08x”,&TMM);
- sprintf(buf,”0x%08x”,&TSS);
- */
- sprintf(buf,”%0.2d:%0.2d:%0.2d”,THH,TMM,TSS);
- LCD_DisplayStringLine(LCD_LINE_1,buf);
- }
在Time_Display()中经过用sprintf将地址赋值给变量(即代码中凝视掉的sprintf句子),并在LCD上显现的方法观察到,栈内的变量散布状况为:
能够看出,栈从内存地址高位向低位成长,参数在栈底,变量依照界说的次序顺次往上摞。体系给buf多留了两字节的空间,其他变量(包含函数参数timevar和局部变量TXX)在内存中顺次严密摆放,没有呈现windows中将函数回退地址的入栈时刻放于参数之后,使参数和变量之间有四字节空地的状况。这阐明函数的回退地址和一些寄存器的入栈保存还有其他机遇。一起留意到,代码中有用sprintf获得变量地址的句子时,作业正常,不会进入hardfault。因而有必要比较两段代码对内存空间形成的影响。
1. 进入hard fault是在Time_Show()函数一个循环履行结束时。因而有必要看一下汇编,了解详细对寄存器和内存的数据读写操作:
- 208:if(TimeDisplay==1)
- 209:{
- 0x08000E724C05LDRr4,[pc,#20];@0x08000E88
- 210:uint32_tCounter=0;
- 0x08000E742500MOVSr5,#0x00
- 0x08000E766820LDRr0,[r4,#0x00]
- 0x08000E782801CMPr0,#0x01
- 0x08000E7AD1FCBNE0x08000E76
- 211:Counter=RTC_GetCounter();
- 0x08000E7CF7FFFE2CBL.WRTC_GetCounter(0x08000AD8)
- 212:Time_Display(Counter);
- 0x08000E80F7FFFF98BL.WTime_Display(0x08000DB4)
- 213:TimeDisplay=0;
- 0x08000E846025STRr5,[r4,#0x00]
- 214:}
- 0x08000E86E7F6B0x08000E76
在这一段中,R4寄存变量TimeDisplay的地址,R0为TimeDisplay的值。循环的最终一步,寄存器R4中的地址加0作为新地址,R5从内存中的该新地址取值存入。假如R4指向的地址不合法,则读取该地址很有或许发生hard fault。
2.检查Time_Display()的汇编
(1)添加了显现变量地址的代码,而无hard fault的状况。
主循环的开端部分汇编代码如下,每次进入循环只需将Time_Display()时入栈的回退地址弹出作为PC。
- 200:voidTime_Show(void)
- 0x08000E44B009ADDsp,sp,#0x24
- 0x08000E46BD00POP{pc}
- 0x08000E48517FSTRr7,[r7,r5]
刚进入Time_Display()时的汇编代码如下,进入时将R0和LR寄存器压入栈中。
- 165:voidTime_Display(uint32_tTimeVar)
- 0x08000DACE8BD4010POP{r4,lr}
- 0x08000DB0F7FFBEEAB.WRTC_WaitForLastTask(0x08000B88)
- 166:{
- 0x08000DB4B501PUSH{r0,lr}
- 0x08000DB6B088SUBsp,sp,#0x20
- 167:uint32_tTHH=0,TMM=0,TSS=0;
- 168:charbuf[10];
此刻STM32芯片寄存器和内存的状况如下图所示。
根绝汇编中把R0和LR压入栈中的指令,对应LR和R0的值,在局部变量地点内存空间寻觅,能够发现LR最早入栈,接着是函数参数和其他变量,这和最开端打印出的各变量地址也是符合的。因而,假如buf越界不是太多,仅仅改写了其他局部变量的数据,不影响回退地址。别的,检查函数一切汇编代码,没有对R4的操作。至函数履行完结并回来,R4的值一直为0x20000000。综上,函数能够持续履行而不会犯错。
(2)发生hard fault的状况。
主循环的开端部分汇编代码如下,需求在Time_Display()后的寄存器值和回退地址都弹出。
196:voidTime_Show(void) - 0x08000D90BD1FPOP{r0-r4,pc}
- 0x08000D94517FSTRr7,[r7,r5]
刚进入Time_Display()时的汇编代码如下,将R0-R4,及LR都压入栈中。
- 165:voidTime_Display(uint32_tTimeVar)
- 0x08000D40E8BD4010POP{r4,lr}
- 0x08000D44F7FFBEEAB.WRTC_WaitForLastTask(0x08000B1C)
- 166:{
- 167:uint32_tTHH=0,TMM=0,TSS=0;
- 168:charbuf[10];
- 169:/*ResetRTCCounterwhenTimeis23:59:59*/
- 0x08000D48B51FPUSH{r0-r4,lr}
- 0x08000D4A4604MOVr4,r0
此刻STM32芯片寄存器和内存的状况如下图所示。
此刻buf的地址为0x200003ec,即R1的开端方位。变量和寄存器值的掩盖联系,或许是编译器检测到R1~R3的值在出栈后将不会被运用,而对内存进行的优化。此刻内存中没有其他局部变量的方位,是因为在改动了代码的状况下,编译器判别为,只需在寄存器里就能够完结核算操作,因而改变了函数的汇编代码,没有占用内存空间。buf的赋值是按从低地址到高地址的次序进行的。从内存的分配图中能够看出,假如buf越界,数组元素超越12个,就将影响到R4的内容。而如1中所述,R4的内容是Time_Display()退出后,需求读取的内存地址。假如经sprintf()后,buf内有15个字符,加上0x00,共16个字符,正好彻底掩盖R4,且R4的最高位为0x00,显然是一个不合法的内存空间,因而将进入hard fault。假如buf内的字符数落在(12,16)区间内,R4的地址合法(仍为0x20最初),不会进入hard fault,但地址已被修正,过错的内存空间中数值不知道,程序跑飞。这些剖析与实践测验结果是共同的。
问题得到了解说,也不知花了一天时刻剖析这些值不值。犯错与否,除了程序自身的正确以外,编译器将C翻译成汇编的发挥程度也是很大的决定因素。想防止这些头疼的问题,定论就一句话:数组不要越界。