相信使用过 SDCC 开发 51单片机程序的人都遇到过,同样的代码(变量声明有区别,只因两者修饰符不同(诸如sfr与__sfr)),在 Keil 中调试正常,但 SDCC 编译出的程序就有问题。这个其实是 Keil 与 SDCC 之间的差别所致了,经过网上多方查阅验证,发现使用 SDCC 时需要注意几个地方:
一、位变量取反操作使用“!”(非)而不是“~”(按位取反)
SDCC在处理位变量(__bit、__sbit类型)的“~”(按位取反)操作时会进行类型提升,出现不确定的结果,所以引脚反转时应写成“ LED = !LED ”而非“ LED = ~LED ”的形式。
二、中断服务函数的声明或定义要在 main 函数所在文件出现
有时候为了代码方便管理,我们会将中断服务函数和相应模块放在一个文件里(如 timer 中断放在 timer.c 中)或者集中放在一个文件中(如 interrupt.c)。如果 main 函数在不同的文件里(如 main.c),中断服务程序是不会工作的。这时必须在 main.c 文件中加入中断服务函数的声明(如 timer_irq __interrupt 1 是的,中断函数的声明也要加上中断号。也可以将声明放在一个头文件里,然后在 main.c 文件中使用 #include 宏包含)。
三、数据字节序为小端存储(Little Endian)
SDCC 与 Keil 最大的区别就是数据存储采用小端字节序,标准的51(包括8052)单片机寄存器地址采用的是小端序(参考定时器2的计数器TH2(0xCD)、TL2(0xCC)),而 MCU 厂商自行扩展的寄存器地址则因厂商而不同,例如 STC 采用的是与 Keil 一样的大端序(参考PWMA的计数器PWMA_CNTRH(0xFECE)、PWMA_CNTRL(0xFECF)),WCH 则采用的是小端序。所以当使用大端序的 MCU 时,程序中一次性存取多字节寄存器(大于1个字节)就会出错,例如 STC 的 MCU 直接读取 PWMA_CNTR(0xFECE)获取到的就是错误的结果。所以在小端序的 MCU 无所谓,但在大端序的 MCU ,多字节的寄存器应该分别读取高低字节的数据然后自行拼接。
四、char类型默认是 unsigned char
不加 unsigned 或 signed 修饰的 char 变量,SDCC 默认解释是无符号的(这点和 Keil 是一样的),当变量用于计算或比较时需要特别注意。
只要规避了上面几个坑,虽然 SDCC 开发出来的程序空间占用仍然偏大,但运行一般是没什么问题了。