记一次开启编译优化选项后程序无法正常运行的调试过程
在开发的时候一直使用的Debug
构建类型方便调试,今天换成Release
突然发现程序不能运行了,屏幕都不亮了,于是尝试找出到底是哪里的代码导致的问题,先定位一下是哪个文件吧。保持Release
构建类型不变,在CMakeLists.txt
中将部分文件的编译优化关闭,逐步排查。比如我这里把Hardware
目录下所有的 C 文件的编译优化选项关闭:
# 递归地遍历 Hardware 目录下所有以 .c 为后缀名的文件
file(GLOB_RECURSE OPT_FILES "Hardware/*.c")
# 对找到的每一个文件设置 "-O0" 属性,即关闭编译优化
foreach(file ${OPT_FILES})
set_source_files_properties(${file} PROPERTIES COMPILE_OPTIONS "-O0")
endforeach()
重新构建之后,程序运行正常了,说明确实是Hardware
目录下某部分代码导致的,然后如法炮制,直到确定是哪一个文件导致的,经过排查,当我关闭if_port.c
这个文件的优化后,屏幕亮了!虽然显示错乱了,但是也证明这个if_port.c
有猫腻,先解决它吧。
这是乐升半导体给的官方固件库中的文件,看了一下,里面有两个函数嫌疑十分之大,一眼就锁定了:
void Delay_us(uint16_t time) {
uint16_t i = 0;
while (time--) {
i = 12; //自己定义
while (i--);
}
}
void Delay_ms(uint16_t time) {
uint16_t i = 0;
while (time--) {
i = 12000; //自己定义
while (i--);
}
}
这两个经典延时函数,真是太典了,里面这个while
循环,非常有可能被编译器认为是冗余代码,从而优化掉,而乐升这块显示屏在初始化的时候又有延时等待的要求,所以猜测是这两个延时函数被优化掉了导致显示屏没有正确初始化,所以黑屏了。直接使用自定义的系统定时器延时替换掉,这里直接替换函数体,而不是替换每处调用,一是太麻烦了懒得一处一处替换(虽然有查找-替换),二是为了尽量不改变原函数的接口,替换之后:
void Delay_us(uint16_t time) {
systick_delay_us(time);
}
void Delay_ms(uint16_t time) {
systick_delay_ms(time);
}
重新构建,屏幕不出意外成功点亮,但是还有上面说的显示错乱,说明还存在其他问题,继续定位到文件LT768.c
,这个文件里代码就比较多了,一眼看过去也看不出来哪里有问题,使用二分法吧,把部分代码关闭优化,部分代码开启优化,具体做法如下:
#pragma GCC optimize("O0")
/* 此处是关闭优化的代码段 */
#pragma GCC optimize("O3")
/* 此处是开启优化的代码段 */
经过排查,最终定位到一个函数:
void LCD_RegisterWrite(unsigned char Cmd, unsigned char Data) {
LCD_WR_REG(Cmd);
LCD_WR_DATA(Data);
}
里面就很简单的两行,是两个宏函数,跳转过去看一下,长这样:
#define LCD_COMM_ADD *((uint16_t *)0X60000000)
#define LCD_DATA_ADD *((uint16_t *)0X60020000)
#define LCD_WR_REG(cmd) do {\
LCD_COMM_ADD = (uint16_t)cmd;\
} while (0)
#define LCD_WR_DATA(data) do {\
LCD_DATA_ADD = (uint16_t)data;\
} while (0)
注意到这两个宏函数实际就是对上面那两个硬件地址直接解引用并赋值,但是并没有使用volatile
修饰,这极有可能导致由于寄存器重命名等优化而没有正确操作到硬件地址,于是加上volatile
修饰:
#define LCD_COMM_ADD *((volatile uint16_t *)0X60000000)
#define LCD_DATA_ADD *((volatile uint16_t *)0X60020000)
#define LCD_WR_REG(cmd) do {\
LCD_COMM_ADD = (uint16_t)cmd;\
} while (0)
#define LCD_WR_DATA(data) do {\
LCD_DATA_ADD = (uint16_t)data;\
} while (0)
重新构建运行,一切正常了。另外我特意去看了看官方原代码,发现是有volatile
的(被typedef
成vu16
),不知道谁移植的时候漏掉了。。。