HC9S12X 定义及访问直接寻址区

从官方文档TN238中,我们知道了HC9S12X微控制器有直接寻址区这个概念,并且由它的示例得知访问直接寻址区的变量只需要一个字节的地址;嗯,比常用的扩展寻址区省了一个字节,很不错的特性,但是它并没有说清楚定义并访问直接寻址区的整个步骤。

后来,发现自带的代码示例里有DirectData这个示例工程,哇,赶快开始学习。使用示例工程的时候十分成功,但是当想在自己的代码上使用直接寻址区的变量时就出问题了,只要uCOS-II一切换任务,程序就直接跑飞。搞了我好久。好在终于明白怎么回事了。来给大家分享下学习成果。

定义变量到直接寻址区

基础知识

我们知道S12X单片机是16位单片机,正常访问内存地址的时候需要使用16位地址。但是访问直接寻址区(或说,使用直接寻址模式)时却只需要8位地址,那另外8位哪去了呢?其实另外(高)8位是由DIRECT寄存器控制的,比如现在DIRECT寄存器中的值是0x30,然后你访问0x34处的变量其实就是在访问0x3034。

修改prm文件

从另一个角度来看,因为直接寻址只用到了8位地址,所以必须得把所有要直接寻址的变量都定义在某个0xXX00-0xXXFF地址内,只可以少不可以多。为了实现这一步,需要修改prm文件。

下面假设我要把直接寻址区定在0x2000-0x20FF。

打开prm文件,把原来的:

SEGMENTS
...
      RAM           = READ_WRITE  DATA_NEAR            0x2000 TO   0x3FFF;
...
END
PLACEMENT
...
END

修改为:

SEGMENTS
...
      RAM_DIRECT    = READ_WRITE  DATA_NEAR            0x2000 TO   0x20FF;
      RAM           = READ_WRITE  DATA_NEAR            0x2100 TO   0x3FFF;
...
END
PLACEMENT
...
      DIRECT_DATA       INTO  RAM_DIRECT;
...
END

数据定义和声明

对所有要定义在直接寻址区的变量:
像这样进行定义:

#pragma DATA_SEG __SHORT_SEG DIRECT_DATA
int oftenUsed1, oftenUsed2, oftenUsed3; /* these variables are accessed with 8-bit addresses */

int oftenUsed[10] = {
  0,1,2,3,4,5,6,7,8,9
};
#pragma DATA_SEG DEFAULT

像这样进行声明:

#pragma DATA_SEG __SHORT_SEG DIRECT_DATA
extern int oftenUsed1, oftenUsed2, oftenUsed3; 
extern int oftenUsed[];
#pragma DATA_SEG DEFAULT

这样链接器就知道要把这些变量放到DIRECT_DATA这个segment中。编译器就会考虑用直接寻址的方式访问它们。

注意:
对于所有要使用这变量的代码,一定要让它见到(比如include包含这个声明的头文件)以上形式(主要是#pragma语句)的定义或者声明,否则它不会知道这个变量在直接寻址区,也不会进行对应优化。

访问直接寻址区的变量

配置编译器

首先要配置编译器,让编译器知道你使用了DIRECT寄存器,以及知道DIRECT寄存器的值,这样编译器才知道怎么优化及产生正确的代码。

菜单栏 Edit->XXXX Settings… 或 Alt + F7 打开配置窗体。
选择Target->Compiler for HC12。添加命令行选项-CpDirect8192或者-CpDirect0x2000。
这句是让编译器知道直接寻址区定义在了0x2000上,同时也定义了宏。

注意:
如果用到了汇编代码,在Target->Assembler for HC12里最好也添加命令行选项-CpDirect8192或-CpDirect0x2000。

修改DIRECT寄存器的值

尽可能早地,起码在用到直接寻址区之前(比如main函数的一开始),修改DIRECT寄存器的值为你配置的地方对应的值,这个只能手动改,编译器不会帮忙添加这段代码。

  DIRECT = __DIRECT_ADR__ >> 8;   /* initialize the DIRECT register. This value matches 
                                     the value specified on the command line of the Compiler 
                                     (e.g. 0x20 for -CpDIRECT0x2000) */

这样产生的代码才能正确的访问直接寻址区。

生成的访问代码

访问直接寻址区的代码没什么特别的,就和访问扩展寻址区的一样;区别是生成的代码。

  ...
   52:      oftenUsed1 = oftenUsed1 + 2*oftenUsed2 +3*oftenUsed3;
  0003 dc00         [3]     LDD   oftenUsed2
  0005 59           [1]     LSLD  
  0006 d300         [3]     ADDD  oftenUsed1
  0008 b745         [1]     TFR   D,X
  000a dc00         [3]     LDD   oftenUsed3
  000c cd0003       [2]     LDY   #3
  000f 13           [1]     EMUL  
  0010 1ae6         [2]     LEAX  D,X
  0012 5e00         [2]     STX   oftenUsed1
  ...
   64:      normalUsed1 = normalUsed1 + 2*normalUsed2 +3*normalUsed3;
  002f fc0000       [3]     LDD   normalUsed2
  0032 59           [1]     LSLD  
  0033 f30000       [3]     ADDD  normalUsed1
  0036 b745         [1]     TFR   D,X
  0038 fc0000       [3]     LDD   normalUsed3
  003b cd0003       [2]     LDY   #3
  003e 13           [1]     EMUL  
  003f 1ae6         [2]     LEAX  D,X
  0041 7e0000       [3]     STX   normalUsed1
  ...

可以看出,每次访问直接寻址区都比访问扩展寻址区的变量少产生1字节代码。

另外,直接寻址区的数组如果使用变量系数,则没有任何优化。

   ...
   31:    oftenUsed1 = oftenUsed[i];     /* -> thereis no gain to access arrays in the direct page range with variable index     */
  0009 ee80         [3]     LDX   0,SP
  000b 1848         [2]     LSLX  
  000d ece20000     [4]     LDD   oftenUsed,X
  0011 5c00         [2]     STD   oftenUsed1
  ...
   39:    nornalUsed1 = nornalUsed[i];  /* above                                           */
  000d ee80         [3]     LDX   0,SP
  000f 1848         [2]     LSLX  
  0011 ece20000     [4]     LDD   nornalUsed,X
  0015 7c0000       [3]     STD   nornalUsed1
  ...

但是如果是使用常量系数的话,就能得到优化

  ...
   56:    oftenUsed[3] = 10;     /* howerver, with a constant index, arrays accesses can be optimized too   */
  0024 c60a         [1]     LDAB  #10
  0026 87           [1]     CLRA  
  0027 5c00         [2]     STD   oftenUsed:6
  ...
   68:    nornalUsed[3] = 10;   
  0054 c60a         [1]     LDAB  #10
  0056 87           [1]     CLRA  
  0057 7c0000       [3]     STD   nornalUsed:6
  ...

注意事项

① 可能有聪明的小朋友想到,可以使用DIRECT寄存器和直接访问模式配合的方式来寻址,这样如果需要成片成片访问地址时就可以增加效率了!但是这有个问题,芯片手册中(198页)提到,DIRECT寄存器在特殊模式下可以任意写,但是在其他模式下只能写一次。所以可能你在BDM调试时没有问题,但是正常运行时却出问题。当然,这条我没有进行验证,感兴趣的朋友可以试试。

② DIRECT寄存器必须由用户代码来初始化。自动生成的代码不初始化DIRECT寄存器。

③ 虽然这里是以 RAM存储器\变量 作为例子,但实际上直接寻址区可以定义在整个逻辑地址的任意地方,如寄存器区、EEPROM、RAM、D-flash、P-flash。

④ 前面说到我在uCOS-II中使用直接寻址区时程序会跑飞
最后发现原因就是因为没有给汇编器添加命令行选项-CpDirect8192。

而uCOS-II有一个汇编文件 os_cpu_a.s,其中有很多负责出栈入栈PPAGE、RPAGE、EPAGE和GPAGE的代码。如:

    pula                               ; Get value of PPAGE register
    staa   PPAGE                       ; Store into CPU's PPAGE register                                

    pula                               ; Get value of RPAGE register
    staa   RPAGE                       ; Store into CPU's RPAGE register                                

    pula                               ; Get value of EPAGE register
    staa   EPAGE                       ; Store into CPU's EPAGE register                                

    pula                               ; Get value of GPAGE register
    staa   GPAGE                       ; Store into CPU's GPAGE register   

在默认情况下,汇编器会将其优化,生成的代码是(以PPAGE为例,其他类似):

  122  122   000016 32              pula                               ; Get value of PPAGE register
  123  123   000017 5A15            staa   PPAGE                       ; Store into CPU's PPAGE register   

这是使用了直接寻址的方式来进行访问,因为汇编器认为我们没用到DIRECT寄存器,所以认为其值为默认的0,所以就优化为了使用直接寻址的方式访问PPAGE寄存器(地址为0x0015)。但是由于我已经把DIRECT寄存器设置为了0x20,这样就变成实际访问的是0x2015,结果就出问题跑飞了。

为了生成正确的程序,一种方式是按如上所述添加命令行选项,让汇编器意识到现在DIRECT寄存器的值是0x20,这样就不会进行这种优化。另外一种是把所有XPAGE寄存器前面都加个 ‘>’ ,这样就可以强制使用扩展寻址了:

    pula                               ; Get value of PPAGE register
    staa   >PPAGE                      ; Store into CPU's PPAGE register     

用任一方法后,生成的代码就变成了:

  122  122   000016 32              pula                               ; Get value of PPAGE register
  123  123   000017 7A00 15         staa   >PPAGE                      ; Store into CPU's PPAGE register       

这样后就不会跑飞了。 但是因为所有ISR都要写类似的东西,所以还是直接添加命令行选项吧。

这也引出了个尴尬的问题,如果要使用直接寻址区这个特性的话,就得使得uCOS每次任务切换或者进出中断都会多出8个字节的开销(2*4),更进一步想,原来访问0x0000-0x00FF的地址(默认为各种寄存器)的代码都会默认优化为直接寻址,改了DIRECT寄存器后等于对这里的寄存器的访问全部都多了1字节。

所以到底要不要使用直接寻址这个特性呢 -_-|| 可能得权衡下。

参考文献

[1] Freescale semiconductor. MC9S12XEP100 Reference Manual Covers MC9S12XE Family . 2008.
[2] Freescale semiconductor. HCS12X – Data Definition. https://www.nxp.com/docs/en/application-note/TN238.pdf
译文:http://blog.csdn.net/lin_strong/article/details/78110162

猜你喜欢

转载自blog.csdn.net/lin_strong/article/details/78535785