__xdata volatile int var0;
__xdata volatile int var1 = 1;
void main(void) {
// 功能代码
}
以上这个简单到发指的程序,在STC8A8K64U中遇到个奇怪问题:var0
的初始值有时候不是0,而 var1
的初始值也不是1,是一些奇怪的随机值。
第一感觉就是XRAM的初始化可能存在BUG,于是稍微了解了一下SDCC中XRAM初始化的原理,没想到还真发现了一些好玩的东西,给大家做个分享。
一些前置知识。
- 无论是Keil还是SDCC,在真正执行main函数之前,会自动插入一些初始化代码,这里面就包括RAM、IRAM和XRAM的初始化等。
- 在从C编译成汇编的过程中,编译器会按照功能和特性的不同,会生成很多辅助的段或者叫做小节。与本问题相关的小节主要有:
;--------------------------------------------------------
; uninitialized external ram data
;--------------------------------------------------------
.area XSEG (XDATA)
_var0::
.ds 2
;--------------------------------------------------------
; initialized external ram data
;--------------------------------------------------------
.area XISEG (XDATA)
_var1::
.ds 2
;--------------------------------------------------------
; external ram initial data
;--------------------------------------------------------
.area XINIT (CODE)
__xinit__var1:
.byte #0x01, #0x00;
未初始化外部变量存储区 XSEG
,var0
就属于这个区,因为它定义在外部扩展RAM中(__xdata),且代码中没有给它赋初始值;
已初始化外部变量存储区 XISEG
,var1
就属于这个区,因为它定义在外部扩展RAM中(__xdata),且代码中有给它赋初始值;
已初始化外部变量初始值区 XINIT
,注意这个实际上是在代码区里面,是ROM不是RAM,最终会烧录在flash的特定位置。
另外,在编译过程中会生成一些辅助变量,s_是特定区的起始位置,l_是特定区的长度。如 s_XSEG
就是 XSEG
区的起始位置。
按照C语言规范,未初始化的全局变量(如 var0
)应当默认给初始值0,这个SDCC帮我们做了,代码在 crtxclear.asm
中:
.globl __XPAGE
__mcs51_genXRAMCLEAR::
mov r0,#l_PSEG
mov a,r0
orl a,#(l_PSEG >> 8)
jz 00006$
mov r1,#s_PSEG
mov __XPAGE,#(s_PSEG >> 8)
clr a
00005$: movx @r1,a
inc r1
djnz r0,00005$
00006$:
mov r0,#l_XSEG
mov a,r0
orl a,#(l_XSEG >> 8)
jz 00008$
mov r1,#((l_XSEG + 255) >> 8)
mov dptr,#s_XSEG
clr a
00007$: movx @dptr,a
inc dptr
djnz r0,00007$
djnz r1,00007$
00008$:
已初始化的全局变量(如var1),也需要执行一些操作才能有初始值,这个SDCC帮我们做了,代码在 crtxinit.asm
中:
.globl __XPAGE
__mcs51_genXINIT::
mov r1,#l_XINIT
mov a,r1
orl a,#(l_XINIT >> 8)
jz 00003$
mov r2,#((l_XINIT+255) >> 8)
mov dptr,#s_XINIT
mov r0,#s_XISEG
mov __XPAGE,#(s_XISEG >> 8)
00001$: clr a
movc a,@a+dptr
movx @r0,a
inc dptr
inc r0
cjne r0,#0,00002$
inc __XPAGE
00002$: djnz r1,00001$
djnz r2,00001$
mov __XPAGE,#0xFF
00003$:
其实这里就已经能发现问题了,当给XRAM内存赋值时,使用的地址寄存是 R0
和 R1
。而 R0
和 R1
只是一个8位的寄存器,还需要其它方式组合出16位地址,才能完全访问STC8A8K64U完整8KB的XRAM。从代码里面看到使用 __XPAGE
做了高8位地址,那么就继续追查 __XPAGE
是个什么东西。最终是在 crtpagesfr.asm
里面:
__XPAGE == 0xa0 ; 0xa0 is P2 on the original 8051
这个 __XPAGE
不就是P2端口么?所有问题豁然开朗。
在传统8052单片机中,如果要扩展RAM,是需要实实在在外接RAM芯片的。P2
接RAM芯片的地址高8位,P0
口接RAM芯片地址低8位和数据位。SDCC默认的实现正是基于这一点,用 __XPAGE
(也就是 P2
)锁存了XRAM地址高8位,然后只需要操作地址低8位和数据即可(都是 P0
)。
这在当时的硬件下应该能减少端口操作提升效率,但这并不适合现在STC系列的所有单片机,因为STC的XRAM其实也是内置的,和 P2
、P0
没有丝毫关系,再按当年的方式来操作必然不能正确生效了。
到这里解决方案其实已经呼之欲出了,重写SDCC内置的方法就能解决问题。已知需要重写的有 crtxclear.asm
、crtxinit.asm
和 crtxstack.asm
,这个等以后有空了再继续分享吧!