配色: 字号:
导致STM32芯片指令速度变化的问题分析过程
2013-03-06 | 阅:  转:  |  分享 
  
导致STM32芯片指令速度变化的问题分析过程

过年那几天将一份代码从TI的LM3S8962芯片移植到ST的STM32F103VB芯片上,

结果发现了STM32芯片指令速度会发生变化,本文将讲述这个问题的定位过程,从中你可

以看到作者根据问题的现象结合已有的知识,2次否定了出问题的地方,但随着逐步缩小定

位范围,认真分析现象,最终还是找回到了出问题的地方,并与网友讨论后,查找芯片手册

找到了问题的原因。本文的重点不在于介绍这个问题,而是在于介绍定位这个问题的思路以

及过程,很多问题通过仔细分析是可以找到原因的。

现象描述

下面这段延迟时间的函数在LM3S8962芯片上可以产生正确的延迟时间,但在

STM32F103VB芯片上却表现出不确定性,有时候可以延迟正确的时间,而有时候则变为正

确时间的1.25倍。比如,输入200,正常的延迟时间是2000ms,而出现异常时这段代码则

运行了2500ms。

voidDEV_DelayMs(U32uiMs)

{

unsignedinti;

unsignedintj;

j=5998uiMs;

for(i=0;;i++)

{

if(i==j)

{

break;

}

}

}

背景知识介绍

我写了2个具有任务切换功能的小型操作系统,其中一个我称之为wanlix,需要函数主

动调用任务切换函数才能发生任务切换,只有这一个功能,功能虽少,但也非常小巧,编译

后只有几百字节,适合程序空间只有几十K的小系统使用。另一个我称之为mindows,是

实时抢占式的内核,高优先级的任务会自动抢占低优先级的任务,支持信号量、队列等功能。

更多信息,请访问我的新浪博客blog.sina.com.cn/ifreecoding

其中DEV_DelayMs函数用来在任务中产生延迟,模拟任务的业务,其中mindows拥有

tick定时器,操作系统打印出的tick时间会反应出DEV_DelayMs函数的执行时间。

LM3S8962芯片与STM32F103VB芯片指令速度不一样,因此在移植这2个操作系统时

对DEV_DelayMs函数内的i、j数值做了精确的调整,并使用了O0(哦零,不优化)优化

选项,使之能产生精确的延迟。DEV_DelayMs函数在LM3S8962芯片上工作正常,但在

STM32F103VB芯片上却有时正常,有时异常。

原因分析

mindows操作系统的打印带有tick时间(每个tick是10ms),这个问题最先是在mindows

上发现的。程序中有一段2000ms的延迟,正常时,串口打印出这段延迟时间是2000ms,

如下所示:

TaskTest1--->TaskTest2!Tickis:200Task2isrunning!Tickis:200

但异常时打印却变成了2500ms,如下所示:

TaskTest1--->TaskTest2!Tickis:250Task2isrunning!Tickis:250

出现这个问题,有可能是tick时间不准,也有可能是DEV_DelayMs函数时间不准。在

运行时对比钟表的时间,发现tick时间是准的,那么说明是DEV_DelayMs函数时间不准。

既然是DEV_DelayMs函数不准,那么很可能是DEV_DelayMs函数在编译时生成的汇

编指令不一样,导致DEV_DelayMs函数执行时间的长短不一样,而且,这个异常还伴随着

一个特点——异常与编译是相关的,也就是说编译后一旦出现异常,那么无论复位多少次,

异常一直出现,编译后一旦正常,那么无论复位多少次,均不会出现异常。这一点使我更加

坚信了是编译器编译出了不同的代码导致了问题。为了证明这一点,将正常和异常时的

DEV_DelayMs函数进行反汇编,对比汇编代码,结果让我很失望,汇编代码完全一样(除

了函数跳转的绝对地址,但相对地址是一样的),这说明不是DEV_DelayMs函数的问题,

反汇编的代码如下:

正常的代码:

8019004:f241736emovwr3,#5998;0x176e8019008:fb00f203mul.wr2,r0,r3

801900c:f04f0300mov.wr3,#08019010:4619movr1,r3

8019012:bf00nop8019014:4291cmpr1,r2

8019016:d100bne.n801901a8019018:e003b.n8019022

801901a:f1010301add.wr3,r1,#1801901e:4619movr1,r3

8019020:e7f8b.n80190148019022:bf00nop

8019024:4770bxlr

异常的代码:

8019000:f241736emovwr3,#5998;0x176e

8019004:fb00f203mul.wr2,r0,r38019008:f04f0300mov.wr3,#0

801900c:4619movr1,r3801900e:bf00nop

8019010:4291cmpr1,r28019012:d100bne.n8019016

8019014:e003b.n801901e8019016:f1010301add.wr3,r1,#1

801901a:4619movr1,r3801901c:e7f8b.n8019010

801901e:bf00nop8019020:4770bxlr

mindows在运行DEV_DelayMs函数的同时还会产生tick中断,会不会是tick中断对

DEV_DelayMs函数产生了影响?为了验证这个问题,只需要在wanlix上运行DEV_DelayMs

函数就可以了,因为wanlix是主动切换任务的系统,如果没有调用任务切换函数,那么

DEV_DelayMs函数就会一直运行,与没有使用操作系统的情况是一样的。在wanlix上运行

DEV_DelayMs函数,对比钟表的时间,这个问题与mindows上表现的情况一模一样,依然

存在,说明也不是tick中断的问题。

上述2个原因都被排除了,我能想到的最后一个原因就是——硬件时钟不准确,导致芯

片运行的频率错误,最终导致了DEV_DelayMs函数执行时间不确定。但tick时钟和串口打

印时钟表现出都是正确的情况,使得这个问题看起来又不像是芯片时钟的问题。我使用的是

芯片自带的库函数来设置tick和串口,也许库函数里做了一些自适应功能使tick和串口时钟

在错误的芯片时钟下也能计算出它们正确的工作时钟?死马当活马医吧,先看看时钟是否有

问题。查看正常和异常情况下与tick时钟有关的寄存器,结果完全正常,这说明芯片时钟也

是没有问题的。而且,异常情况的出现是与代码是否重新编译相关的,与芯片是否重新启动

是无关的,又是概率性出现的,这也说明了不是硬件时钟的问题。这下我真的黔驴技穷了。

经过反复试验,终于又发现一个重要的线索:增量编译其它函数时也会触发这个问题概

率性出现。增量编译的意思是只编译其中一部分文件,然后一起连接成目标程序。比如说有

a.c和b.c这两个文件,先将这两个文件编译成a.o和b.o,然后再将a.o和b.o链接成目标文

件。当只有a.c文件做了改动时,那么我们可以只编译a.c,使用新生成的a.o与原来的b.o

一起链接成新的目标文件,这就是增量编译。

DEV_DelayMs函数位于unoptimize.c文件中,当我改动其它c文件而没有改动

unoptimize.c文件做增量编译时,这个问题也会概率性出现。这说明什么?这说明这个问题

与DEV_DelayMs函数无关,是其它函数导致的。但这个问题又确实是在DEV_DelayMs函

数上表现出来的,而DEV_DelayMs函数又与其它函数没有任何耦合,这似乎是不可能的事

情。问题定位到这里已经走不下去了。

既然是修改其它文件里的函数对DEV_DelayMs函数有影响,那么只能试着改其它文件

中的函数来找出其中规律了。在试验中又发现,在不相关的文件中随便加入几条无用的指令

都有可能随机触发这个问题,这似乎又是不可能的事情。

问题说到这里,我已经把当时定位时所获得的全部信息都介绍全了,现在,你是否能猜

到这个问题的原因?下面就是见证奇迹的时刻!

在不相干的文件中修改不相干的指令,那么对DEV_DelayMs函数的影响是——链接后

的绝对地址不同。难道是DEV_DelayMs函数所在的地址会对运行结果产生影响?带着最后

一丝希望,我构造出了将DEV_DelayMs函数链接到不同地址的多种情况,最后终于发现了

导致这个问题的一个规律。

当DEV_DelayMs函数被编译到8字节对齐(0b1000结尾)的地址,运行时间就是错误

的,当被编译到4字节对齐(0b100结尾)的地址,运行时间就是正确的!

原文定位到这里就结束了,至于为什么会产生这个问题,我也不清楚,原以为是芯片的

bug。后来在论坛上有网友提出是FLASH读取速度不均衡造成的,我觉得有可能,但如果

是这样的话那只能说这个芯片做的不是很好,因为这个现象是与地址相关的,而且表现出来

的速度差异太大了,达到了1:0.8,并且在TI的cortex内核上没有发现这个问题。因此我

查了一下ST的FLASH手册,发现有如下这段话:“预取缓冲器包含两个数据块,每个数据

块有8个字节;预取指令(数据)块直接映像到闪存中,因为数据块的大小与闪存的宽度相同,

所以读取预取指令块可以在一个读周期完成。设置预取缓冲器可以使CPU更快地执行,CPU

读取一个字的同时下一个字已经在预取缓冲器中等候,即当代码跳转的边界为8字节的倍

数时,闪存的加速比例为2。”从中可以看到其中提到了“跳转”,提到了“8字节对齐”,

从上面的2段反汇编代码可以看到,区别就在于跳转的地址不同,并且我验证的结论也是有

关8字节对齐的,因此这段话与我上面所得出的结论是一致的。至于上面2段反汇编后的代

码为何会表现出指令执行速度上的差异,我结合这段话没有找到原因。

解决方法

经过前面的分析,我们需要将DEV_DelayMs函数链接到4字节对齐的地址空间,需要

修改链接文件,在我的工程中这个链接文件名称叫STM3210E-EVAL.sct,你可以到我的博

客mindows5.3节及以后的章节中找到这部分代码,修改如下:

LR_IROM20x080190040x00000FFC{;loadregionsize_region

ER_IROM20x080190040x00000FFC{;loadaddress=executionaddress

unoptimize.o

}

}

上面这段代码的含义是,将unoptimize.o文件链接到0x08019004这个地址空间,这就

是一个4字节对齐的地址空间,而unoptimize.o是由unoptimize.c文件编译成的,而

unoptimize.c文件中只有DEV_DelayMs这一个函数,这样就将DEV_DelayMs函数链接到了

4字节对齐的0x08019004地址空间了。

经过这么处理DEV_DelayMs函数就可以正常工作了。

另外,在ST的FLASH手册中也指出了可以通过寄存器关闭“预取缓冲器”功能,不

过还有一些额外的限制,由于我没有完全理解这些描述,这里就不介绍了,有兴趣的同学可

以参考PM0042编程手册“STM32F10xxx闪存编程”,里面有描述。

经验总结

可以说这个问题不是问题,芯片就是这么设计的,当初没有仔细看FLASH手册,误以

为这是一个bug。

1.出现问题时应该仔细阅读相关文档,避免“问题”就是芯片特性。

2.看一遍芯片的buglist,看看其中描述的bug是否会遇到。

3.对于问题表现出的现象一定要运用已知的知识仔细分析,尽管过程不会很顺利,有时

候甚至还会引向另一个错误的方向,但最终仍会缩小范围,解决问题。

献花(0)
+1
(本文系自由编程首藏)