打印

(修改)导致STM32芯片指令速度变化的问题分析过程

[复制链接]
12537|62
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
ifreecoding|  楼主 | 2012-3-31 23:29 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 ifreecoding 于 2012-4-3 13:18 编辑

导致STM32芯片指令速度变化的问题分析过程.pdf (174.01 KB)
      过年那几天将一份代码从TILM3S8962芯片移植到STSTM32F103VB芯片上,结果发现了STM32芯片指令速度会发生变化,本文将讲述这个问题的定位过程,从中你可以看到作者根据问题的现象结合已有的知识,2次否定了出问题的地方,但随着逐步缩小定位范围,认真分析现象,最终还是找回到了出问题的地方,并与网友讨论后,查找芯片手册找到了问题的原因。本文的重点不在于介绍这个问题,而是在于介绍定位这个问题的思路以及过程,很多问题通过仔细分析是可以找到原因的。
现象描述
      下面这段延迟时间的函数在LM3S8962芯片上可以产生正确的延迟时间,但在STM32F103VB芯片上却表现出不确定性,有时候可以延迟正确的时间,而有时候则变为正确时间的1.25倍。比如,输入200,正常的延迟时间是2000ms,而出现异常时这段代码则运行了2500ms

void DEV_DelayMs(U32 uiMs)
{
    unsigned int i;
    unsigned int j;

    j = 5998 * uiMs;

    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函数内的ij数值做了精确的调整,并使用了O0(哦零,不优化)优化选项,使之能产生精确的延迟。DEV_DelayMs函数在LM3S8962芯片上工作正常,但在STM32F103VB芯片上却有时正常,有时异常。
原因分析
      mindows操作系统的打印带有tick时间(每个tick10ms),这个问题最先是在mindows上发现的。程序中有一段2000ms的延迟,正常时,串口打印出这段延迟时间是2000ms,如下所示:
Task Test1 ---> Task Test2! Tick is: 200
Task2 is running! Tick is: 200
      但异常时打印却变成了2500ms,如下所示:
Task Test1 ---> Task Test2! Tick is: 250
Task2 is running! Tick is: 250
      出现这个问题,有可能是tick时间不准,也有可能是DEV_DelayMs函数时间不准。在运行时对比钟表的时间,发现tick时间是准的,那么说明是DEV_DelayMs函数时间不准。
      既然是DEV_DelayMs函数不准,那么很可能是DEV_DelayMs函数在编译时生成的汇编指令不一样,导致DEV_DelayMs函数执行时间的长短不一样,而且,这个异常还伴随着一个特点——异常与编译是相关的,也就是说编译后一旦出现异常,那么无论复位多少次,异常一直出现,编译后一旦正常,那么无论复位多少次,均不会出现异常。这一点使我更加坚信了是编译器编译出了不同的代码导致了问题。为了证明这一点,将正常和异常时的DEV_DelayMs函数进行反汇编,对比汇编代码,结果让我很失望,汇编代码完全一样(除了函数跳转的绝对地址,但相对地址是一样的),这说明不是DEV_DelayMs函数的问题,反汇编的代码如下:

正常的代码:
8019004:f241 736e movw r3, #5998; 0x176e
8019008:fb00 f203  mul.w r2, r0, r3
801900c:f04f 0300  mov.w r3, #0
8019010:4619        mov r1, r3
8019012:bf00         nop
8019014:4291        cmp r1, r2
8019016:d100        bne.n 801901a <DEV_DelayMs+0x16>
8019018:e003        b.n 8019022 <DEV_DelayMs+0x1e>
801901a:f101 0301 add.w r3, r1, #1
801901e:4619        mov r1, r3
8019020:e7f8         b.n 8019014 <DEV_DelayMs+0x10>
8019022:bf00        nop
8019024:4770        bx lr
异常的代码:
8019000:f241 736e movw r3, #5998; 0x176e
8019004:fb00 f203  mul.w r2, r0, r3
8019008:f04f 0300  mov.w r3, #0
801900c:4619         mov r1, r3
801900e:bf00          nop
8019010:4291         cmp r1, r2
8019012:d100         bne.n 8019016 <DEV_DelayMs+0x16>
8019014:e003         b.n 801901e <DEV_DelayMs+0x1e>
8019016:f101 0301 add.w r3, r1, #1
801901a:4619         mov r1, r3
801901c:e7f8          b.n 8019010 <DEV_DelayMs+0x10>
801901e:bf00         nop
8019020:4770        bx lr
      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.cb.c这两个文件,先将这两个文件编译成a.ob.o,然后再将a.ob.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读取速度不均衡造成的,我觉得有可能,但如果是这样的话那只能说这个芯片做的不是很好,因为这个现象是与地址相关的,而且表现出来的速度差异太大了,达到了10.8,并且在TIcortex内核上没有发现这个问题。因此我查了一下STFLASH手册,发现有如下这段话:“预取缓冲器包含两个数据块,每个数据块有8个字节;预取指令(数据)块直接映像到闪存中,因为数据块的大小与闪存的宽度相同,所以读取预取指令块可以在一个读周期完成。设置预取缓冲器可以使CPU更快地执行,CPU读取一个字的同时下一个字已经在预取缓冲器中等候,即当代码跳转的边界为8字节的倍数时,闪存的加速比例为2”从中可以看到其中提到了“跳转”,提到了“8字节对齐”,从上面的2段反汇编代码可以看到,区别就在于跳转的地址不同,并且我验证的结论也是有关8字节对齐的,因此这段话与我上面所得出的结论是一致的。至于上面2段反汇编后的代码为何会表现出指令执行速度上的差异,我结合这段话没有找到原因。
解决方法

字数限制请下载文档看
沙发
王紫豪| | 2012-4-1 00:04 | 只看该作者
这个很深奥啊,楼主很认真。经验总结值得赞赏!!!!

使用特权

评论回复
板凳
mcuisp| | 2012-4-1 00:13 | 只看该作者
这个真不是Bug、、、

使用特权

评论回复
地板
kanprin| | 2012-4-1 00:17 | 只看该作者
不知道楼主是否可以把
unsigned int i;
unsigned int j;
改成
unsigned long i;
unsigned long j;
然后再试试?

使用特权

评论回复
5
Simon21ic| | 2012-4-1 09:41 | 只看该作者
把汇编贴上来看一下,看一下循环部分的地址和长度

使用特权

评论回复
评分
参与人数 1威望 +6 收起 理由
icecut + 6 的确是循环体的大小影响了缓存的命中率.
6
午夜霓虹| | 2012-4-1 09:48 | 只看该作者
我没看楼主的分析过程,因为我认为楼主的结论是错误的,这不是BUG的问题,是内部FLASH数据读取速度导致的不同。

使用特权

评论回复
7
uc_stm32f050| | 2012-4-1 09:51 | 只看该作者
我没看楼主的分析过程,因为我认为楼主的结论是错误的,这不是BUG的问题,是内部FLASH数据读取速度导致的不同。
午夜霓虹 发表于 2012-4-1 09:48


应该是这个原因。

导致的结果,某些指令执行时间,与对其有关。
类似,STM8也存在这个问题。

使用特权

评论回复
8
香水城| | 2012-4-1 11:05 | 只看该作者
给条裤子,鼓励思考。但不表示认同结论。

我有一个疑问,难道8字节对齐的地址,不属于4字节对齐的地址吗?

使用特权

评论回复
9
byeyear| | 2012-4-1 11:22 | 只看该作者
分析过程非常细致
不过我认为这个问题是CM的变长指令集(2字节指令和4字节指令混合)和指令流水造成的 不能说是bug
LZ所说4字节对齐 应该是说4字节对齐但不在8字节对齐 例如 4,12,20这样的地址
LZ的程序段在某些情况下需要N次prefetch,但在另一些情况下需要N+1次prefetch
于是造成了这种状况

不过很好奇楼主为嘛要做酱子的延时程序
对于现在的32bit CPU(大多数有指令流水,有的还有cache),这段代码的移植性……

使用特权

评论回复
10
ifreecoding|  楼主 | 2012-4-1 11:29 | 只看该作者
这个问题表现出同一条指令在不同的地址上运行,最终执行时间不同,如果这都不是bug那什么是bug?
难道说我们编译出的程序,它执行的效果受限于它编译的地址(同一块存储区域,而且效率是1:0.8)?如果是这样的话那么程序执行结果就是不可控的了,而且在同为cortex内核的LM3S8962芯片没有这个问题,在ARM7内核上也没发现有这个问题。

回香主,我所说的8字节对齐是以0b1000结尾的地址,4字节对齐是以0b100结尾的地址。

使用特权

评论回复
11
香水城| | 2012-4-1 11:38 | 只看该作者
这个问题表现出同一条指令在不同的地址上运行,最终执行时间不同,如果这都不是bug那什么是bug?
难道说我们编译出的程序,它执行的效果受限于它编译的地址(同一块存储区域,而且效率是1:0.8)?如果是这样的话那么 ...
ifreecoding 发表于 2012-4-1 11:29


如果访问Flash有等待周期,则就会出现这个问题,这不是因为指令的执行时间改变,而是由于取指令的操作需要等待。

这是系统结构的问题,不是Bug。STM32F2和F4引入的ART加速器,就是为了解决这个问题产生的影响。

使用特权

评论回复
12
金融小数| | 2012-4-1 12:26 | 只看该作者
没搞懂

使用特权

评论回复
13
suse-lj| | 2012-4-1 12:45 | 只看该作者
向楼主的解决问题的精神看起。但我不知道楼主结论如何

使用特权

评论回复
14
yzzly| | 2012-4-1 12:51 | 只看该作者
我看变量定义就有问题

使用特权

评论回复
15
ifreecoding|  楼主 | 2012-4-1 12:57 | 只看该作者
FLASH存取时间是有影响,但在这个问题上表现出与地址相关,而且可以影响到1:0.8的比例,这个比例已经很大了。如果一个项目使用一个算法大量计算数据,就会表现出某些时候使用10分钟完成,某些时候使用8分钟完成,这种不确定性太大了,我想没有哪个人希望使用具有这样不确定性的芯片吧

使用特权

评论回复
16
香水城| | 2012-4-1 13:16 | 只看该作者
FLASH存取时间是有影响,但在这个问题上表现出与地址相关,而且可以影响到1:0.8的比例,这个比例已经很大了。如果一个项目使用一个算法大量计算数据,就会表现出某些时候使用10分钟完成,某些时候使用8分钟完成,这 ...
ifreecoding 发表于 2012-4-1 12:57


LZ可以去研究一下PC中的CPU,不管是Intel的还是AMD的,哪一个是有确定执行时间的?

使用特权

评论回复
17
小嘿| | 2012-4-1 13:20 | 只看该作者
与地址相关。。比较严重吖。。表示关注

使用特权

评论回复
18
Simon21ic| | 2012-4-1 13:42 | 只看该作者
本帖最后由 Simon21ic 于 2012-4-1 13:44 编辑

都说了,传上汇编的看看,并确认flash的等待是几个周期
既然已经有1:0.8这个比例了,根据stm32的64位宽flash以及2个64位宽的i cache,就可以大致判断是什么原因了。
估计是由于地址的问题,使得多了一次flash读取操作。

使用特权

评论回复
19
mohanwei| | 2012-4-1 14:41 | 只看该作者
不希望优化的,至少要加个volatile,否则你添加了其他代码,重新编译,可能这个delay函数又变样了……
void DEV_DelayMs(volatile U32 uiMs)
{
    volatile unsigned int i;
    volatile unsigned int j;

    j = 5998 * uiMs;

    for(i = 0; ; i++)
    {
        if(i == j)
        {
            break;
        }
    }
}

使用特权

评论回复
20
mohanwei| | 2012-4-1 14:42 | 只看该作者
另外有没有考虑到你在delay的时候,来了n个systick,uart,timer,eth……中断

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

2

主题

68

帖子

3

粉丝