STMCU小助手
发布时间:2022-3-4 20:13
|
问题: 该问题由某客户提出,发生在 STM32F103VBT6 器件上。据其工程师讲述:在为 STM32 调试软件过程中,遇到了一个怪现象:有如表(一)所示的一段程序中,KeyIn 是一个全局变量。当有按键按下时,键盘的中断服务程序会将对应的键值放入其中,当按键释放后,键盘中断服务程序则把 RESET 值放入该变量。行(1)的条件语句在主程序中,检测有无按键按下,并处理。行(2)则是等待按键释 放。调试时,在行(1)和行(2)处各设置一个断点,然后全速运行程序。当程序停在行(1)时按下按键,继续全速运行程序。当程序停在行(2)时,放开按键,继续全速运行程序。这时,发现程序没有向下执行,而是依然回到并停留在行(2)处。打开观察窗口,观察变量 KeyIn 的值,确认其值为RESET。再次起动全速运行,程序依然回到行(2),而不是向下执行。
调研: 检查其软件工程,确认其所使用的工具链为 IAR Embedded Workbench IDE 5.3,工程设置中的优化等级为 High+Balanced。重复测试,确认现象如其所述。使用反汇编窗口检查编译器生成的指令,如图(一)所示:
从 图 ( 一 ) 中 得 知 , 由 语 句 while(KeyIn != RESET) 生 成 的 指 令 位 于 地 址 区 间 0x800032C0x8000330,共有 3 条。中,指令 LDR R1,[R0]负责将变量 KeyIn 的值装载到寄存器 R1 中,指令 CMP R1,#0 对该值进行检测,而跳转指令 BNE.N ??main_1 则是实现循环。进一步观察,发现 3条指令中,只有后两条参与了循环,而指令 LDR 只执行一次,不参与循环。于是可以得出这样的结 论:如果在执行该循环语句的第一次循环时变量 KeyIn 的值非 0,那么装载到寄存器 R1 中的值就非0,于是比较结果为“不相等”,从而进入下一次循环。由于从第二次开始,执行该循环时不更新寄存器 R1 的值,所以无论变量 KeyIn 的取值如何,比较的结果都是“不相等”,都会再次进入下一次循环。于是,一个无休止的循环诞生了。 将工程的优化选项更改为 High+Size,重新编译并观察反汇编结果,如图(二)所示:
这次由语句 while(KeyIn != RESET) 生成的指令变为 4 条,而且在每次循环中都重新向寄存器中装载变量 KeyIn 的值。从实际执行的效果上看,符合源代码所描述的逻辑。 将工程的优化选项改回到 High+Balanced 模式,并修改源程序,将变量 KeyIn 定义成 volatile 型变量,如表(二)所示:
重新编译并观察反汇编的结果,如图(三)所示:
这种情况下,语句 while(KeyIn != RESET) 生成的指令为 3 条,但每次循环都重新装载变量 KeyIn的值到寄存器。测试其执行效果,符合源代码所描述的逻辑。 结论: 通过以上的测试可以得知,该问题是由于编译器对 C 语言代码进行高度的优化时,对某些语句解析错误造成的。 处理: 将变量 KeyIn 定义成 volatile 型变量,如表(二)所示。 建议: 既然这是一种由编译器的编译错误造成的问题,可否期待新版本的编译器为我们加以改正?非常不幸的是编译器真的很难做到这一点!更不幸的是几乎所有的编译器都有类似的问题!因为,在引用多个变量做循环处理时,如果对每个变量在每次循环中都重新装载,势必大大的降低代码的效率,根本谈不上优化。而编译器又无从分辨出哪些变量有可能发生改变,于是,只好认为只要在本循环中没有对其更改的变量就不会发生变化。所以,不能寄期望于编译器为我们解决这类问题。但这一问题还是要解决的,特别是基于多任务操作系的软件,其中有大量的变量由多个任务共享,如果发生此类问题将导致严重的错误。如何解决?有一个关键的切入点,就是:虽然编译器分辨不出哪些变量有可有发生改变,但是软件的设计者却是知道的。这样,完全可以通过某种方式告诉编译器,哪些变量有可以发生改变,或者通过一些手段迫使编译器对某个变量重新装载。将变量定义成 volatile 型变量,是告知编译器该变量有可以发生改变的一种方法。通常,外设的寄存器都定义成 volatile 型变量,因为这些变量的值有可能被硬件改变。定义成 volatile 型的变量在使用时往往不如普通的变量方便,因为要考虑对其读写的次序问题。比如表(三)所示的程序,编译器在对其编译的时候,会毫不客气的给出的警告:
可以人为的为这些变量设定读写次序,而避免产生相关的警告。如表(四)所示:
从表(四)中可以看出,定义这个次序不是一般的麻烦!那么,对于普通的变量,如何迫使编译器对其重新装载呢?我们可以尝试用函数来代替变量。比如,对表(一)所示的程序进行修改,得到表 (五)所示的程序:
其中的 GetKeyIn()函数定义在另外一个 C 语言文件中。该函数只有一行语句,就是返回变量 KeyIn的值。以 High+Balanced 优化参数对表(五)的程序进行编译,并观察其反汇编代码,如图(四):
从图(四)中可以看到 while(GetKeyIn() != RESET) 语句被编译成了 3 条指令。其中,BLGetKeyIn 指令是用来调用函数 GetKeyIn()以取得变量 KeyIn 的值的,而且在每次循环中都对其调用,以重新装载变量 KeyIn 的值。在使用这种方法时,返回变量取值的函数一定要放在另外一个 C语言文件中才有效。因为编译器进行编译的时候,是对工程中的 C 语言文件逐个进行的。由于被调用的函数不在当前的 C 语言文件中,所以编译器不知道该函数做了哪些事情,只好认为其返回值是有可能改变的,从而每次循环都对其调用,于是我们的目的就达到了。 |
【经验分享】STM32 HAL库移植FreeModbus详细步骤
【逢7发帖赢大礼】STM32开发之IC门禁卡UID读取方法!
实战经验 | Keil工程使用NEAI库的异常问题
经验分享 | FDCAN数据段波特率增加后发送失败的问题分析
STM32几种流行开发环境的优缺点
好用软件推荐-TouchGFX Designer
OpenSTLinux 6.1发布:M33-TD加持+安全升级,STM32MPU开发效率翻倍!
【经验分享】STM32CubeIDE汉化
手把手教你VSCode搭建STM32开发环境
RTOS在STM32中的应用
微信公众号
手机版