本帖最后由 野马-425178 于 2017-5-12 09:58 编辑 问题描述: 使用MDK4或5编译ST的USB-host库时,里面有个USB_OTG_BSP_uDelay函数,本函数本身在-03级优化下是没有问题的,但我手痒,对里面的变量的定义顺序做了一下修改,结果-o3优化下出问题了(表现为该延时函数不准确,导致部分U盘枚举失败),请教大师分析一下: 错误改法: 将定义 “const Uint32 uTime = (Uint32)120 * usec / 7;” 放到 “__IO Uint32 uCount = 0;” 代码前面。 我的问题:仅仅是变量定义的顺序不同,为什么优化后结果却不一样(不是存储对齐的问题)? 正常的代码如下: void USB_OTG_BSP_uDelay(const uint32_t usec) { __IO Uint32 uCount = 0; const Uint32 uTime = (Uint32)120 * usec / 7; while (1) { if (++uCount > uTime) { return; } } } 错误的代码如下: void USB_OTG_BSP_uDelay(const uint32_t usec) { const Uint32 uTime = (Uint32)120 * usec / 7; __IO Uint32 uCount = 0; while (1) { if (++uCount > uTime) { return; } } } 附件是正常的与错误的反汇编截图,请大家指点! |
ä¿®æ¹åçåæ±ç¼æªå¾
æ£å¸¸çåæ±ç¼æªå¾
1.首先,强烈建议按如下规则执行定义和赋值(越是新的编译器越是会消弱这种规则,比如MDK-ARM V5):1.1.在块开始的地方,首先定义变量,既在做任何赋值运算前定义完所有变量!
1.2.注意是块,没说是一定是函数头,但函数头确实是一个块开始的地方。另外,凡是左花括号“{”开始的地方都是块开始的地方。
在块开始的地方先定义变量,再执行赋值语句。
2.如下图:
uCount是定义时给了初值,这是定义不是赋值。uTime是在定义时有个计算过程,usec不是确定的值,在翻译为汇编时必然有个计算过程,语法看起来是定义时给了初值,但其中有个计算过程,这个计算过程如果在定义uCount之前执行的话,就违反了上述的第1条。所以请按照第1条写代码。
3.如下图:
看为什么会出错(我的理解,可以讨论)!
在0x08024984地址处的代码是往r1写了0,然后在0x08024986地址处存入内存该r1=0;在0x08024988地址处执行了r1+=1;此时r1=1,接着在0x0802498a地址处存入内存该r1,此时存入内存内r1=1;然后在0x0802498c地址处判断r1和r0,r0既是uTime的值;显然r1小于r0,在0x0802498e地址处跳到了0x08024988地址处,此时r1=2,再在0x0802498a地址处存入内存!和正确的汇编代码比较后你会发现此错误的汇编总是存入内存而没有从内存读!没有读内存也没什么问题,因为r1总是正确的加1了,只是缺少了一个从内存中读出r1再加的过程,这个看似“多此一举”的动作为什么会那么重要呢?
显然“错误”的汇编没有按照__IO(既:volatile)的属性来处理r1(只完成了一半:存)!
按照volatile的规则,每次都需要从ram中(而不是寄存器中)读写数据,那么按此规则执行一定是正确的,否则可能会出现问题。
楼主可以再看下,该delay函数退出时r1的值是多少?以及谁会打断该函数?
最后,建议使用最新的编译器和ST官方的代码!!
HAL最新的usb-host中没有该USB_OTG_BSP_uDelay函数的使用,都是用的HAL_DELAY函数:
使用HAL_Delay:
评分
查看全部评分
优化不一样,应该是正常现象吧
感谢您的回复,现在问题是优化后运行不正常了,出现该延时函数不正确了。2个图对比的结果是少了一个LDR加载数据的指令。
现在还这么客气的人不多啦!!!!出现不正常指的是什么???延时函数不出来?还是延时时间不对?
让版主费心了!
这个问题是我在USB-HOST枚举U盘时发现的,如果将将定义 “const Uint32 uTime = (Uint32)120 * usec / 7;” 放到 “__IO Uint32 uCount = 0;” 代码前面,就出现部分U盘无法枚举,而把定义顺序反过来就正常了。
我现在想了解的是为什么这两个变量定义顺序交换一下就会出现异常,到底我错在哪里了。
正常的代码如下:
void USB_OTG_BSP_uDelay(const uint32_t usec)
{
__IO Uint32 uCount = 0;
const Uint32 uTime = (Uint32)120 * usec / 7;
while (1)
{
if (++uCount > uTime)
{
return;
}
}
}
错误的代码如下:
void USB_OTG_BSP_uDelay(const uint32_t usec)
{
const Uint32 uTime = (Uint32)120 * usec / 7;
__IO Uint32 uCount = 0;
while (1)
{
if (++uCount > uTime)
{
return;
}
}
}
正常和错误的反汇编截图在顶贴有附图,感谢版主!
首先呢,USB我没有搞过,只是以前稍微看过一点点,这方面你会比我清楚很多!!!!但是呢,我看你都定位到错误的原因是那个延时函数!!!所以我想问的是,你把变量顺序改过后,这个延时函数出现了那些问题,是变成死循环了,还是延时不达标了!!!!
哦,是延时函数不达标引起的。我不熟悉汇编,还要麻烦您看下顶帖的反汇编对比图。
如果是软延时呢,那就是很正常不过的了,因为有时候编译器会采用不同的优化策略,即使是非常小的改动,甚至无关的改动!!!硬延时的话,那就要看看数值是否正确了!!!如果是软延时,个人建议可以加大一下数值,或者直接用宏nop,如果要求非常高的化!!!!
做了些实验,发现情况是这样的:
USB_OTG_BSP_uDelay函数在优化后如果有调用LDR汇编指令的时候即满足设计微秒级延时要求,但如果优化后没有调用LDR指令则延时时间被缩短且不符合要求了;
ST库提供的原始代码在优化时刚好有调用LDR指令(因为该变量定义的顺序刚好使MDK优化时认为无法保存寄存器变量,需要每次LDR重载),所以就没有问题;
而我把变量定义顺序修改后,经过MDK优化则把LDR指令去掉了,但是MDK也没有错,因为uTime是const变量,如果优化策略认为不需要每次重载的时候就不需要LDR了,结果导致时间不准确。
实际上,最安全的做法应该是要使用“__IO”来修饰变量“uTime”,而不是用“const”。
版主的回复也是对的,因为这个函数设计的时候就是用const修饰了uTime变量(不一定每次都重新加载),所以LDR被优化掉也算是正常,而我正好又是需要这个指令的运行时间。
在阿莫上刚好有个帖子,关于MDK优化省略了LDR操作的问题,里面描述了和我这个问题很类似。
http://www.amobbs.com/thread-4214047-1-1.html
该网址下以下内容和我的问题类似:
1、你好,我发现如果那个while(unIdleCount!=200); 不是一个空等待的循环,(当然,里面不是的环就不是等待2Ms了,而是在这2MS里一直执行某个操作了),这样的话就不会出现这种情况了,在比较之前
会有一个LDR的操作,保证读取的是RAM里的数据,而这时我并没有用volatile修饰他,而且我的优化等级选的是最高的3也没关系
2、另一个,我发现,只要unIdleCount=0不是紧挨着下面那个判断语句,两句之间隔了一些函数,就算这些函数没有操作unIdleCount,在比较之前也会有那个LDR命令获得unIdleCount最新的值
22: TIM3_Int_Init(500,7199);//10Khz的计数频率,计数到5000为500ms
23:
0x08000A08 8820 LDRH r0,[r4,#0x00] //这条语句是在下面那个判断之前突然出现的,用来获得最新的值
24: while(unIdleCount!=10);
25:
0x08000A0A 280A CMP r0,#0x0A
0x08000A0C D1FD BNE 0x08000A0A
同样,这时我也没有用volatile修饰unIdleCount,那应该就是这时寄存器里面没有保存unIdleCount了,(因为这之前的操作保存不了了),所以会重新读,
3、后来我又脑残的做了几个实验,发现在unIdleCount=0;和while(unIdleCount!=10);之间相隔的有东西的时候,不管是函数(需要跳转)还是简单运算,只要编译器有办法用一个寄存器保存unIdleCount 的值
同时又能完成这之间的运算,他就会直接比较寄存器里面的值,(此时我任然选的是最高优化级别)我的是4.7版本的MDK。
于是我又重复了1里面说的,发现只要while里面有东西,哪怕简单得足以让可以保存unIdleCount的值,但是他也会每次都读取最新的值,
于是我真的有点感慨,真的很智能,(他的理念或许就是这样),完全没意义的事就不用每次都浪费时间和代码空间读取最新的值了,但是
真的有事要做的话就得严谨地每次读取最新的值看是不是满足条件,应不应该执行了,哈哈,说的优点多
解释的非常精辟,感谢高手指点,我还以为自己找到原因了,看来还得继续深究:)
1:USB_OTG_BSP_uDelay函数退出时uTime=0x33,uCount=0x34;
2:因为项目移植HAL库改动较多风险也较大,所以暂时沿用标准库。
3:项目中我使用2个USB分别作为host和device,这2个USB都使用这个延时函数,是否会重入还不确定,另外中断源较多,运行中被中断基本是必然的,但只要时间不缩短是没有问题。
4:对于你的分析,我的问题是既然uCount是__IO,为什么MDK在优化后只存不取呢?(是因为没有按照你说的1.1,1.2规则来写代码引起的吗)?
也就是说,如果没有按照那个规则来定义变量,MDK就可能忽略这个__IO要求(我目前使用的是MDK5.23.0)?
严格的函数写法应该如下,对么?
void USB_OTG_BSP_uDelay(const uint32_t usec)
{
__IO Uint32 uTime, uCount;
uCount = 0;
uTime = (Uint32)120 * usec / 7;
do
{
if (++uCount > uTime)
{
return;
}
}
while (1);
}
下面是这个改法后的反汇编截图:
显然的,你这么写后,47EE处开始的代码和其循环,总是把值从ram中取出,然后加,又存到了ram,最后比较,如此循环,这个是没问题的。