XDATA相关(转)
修改浏览权限 | 删除
指针变量本身有地址吗从数据存储类型来说,8051系列有片内、片外程序存储器,片内、片外数据存储器,片内程序存储器还分直接寻址区和间接寻址类型,分别对应code、data、xdata、idata以及根据51系列特点而设定的pdata类型,使用不同的存储器,将使程序执行效率不同,在编写C51程序时,最好指定变量的存储类型,这样将有利于提高程序执行效率(此问题将在后面专门讲述)。与ANSI-C稍有不同,它只分SAMLL、COMPACT、LARGE模式,各种不同的模式对应不同的实际硬件系统,也将有不同的编译结果。
在51系列中data,idata,xdata,pdata的区别
data:固定指前面0x00-0x7f的128个RAM,可以用acc直接读写的,速度最快,生成的代码也最小。
idata:固定指前面0x00-0xff的256个RAM,其中前128和data的128完全相同,只是因为访问的方式不同。idata是用类似C中的指针方式访问的。汇编中的语句为:mox ACC,@Rx.(不重要的补充:c中idata做指针式的访问效果很好)
xdata:外部扩展RAM,一般指外部0x0000-0xffff空间,用DPTR访问。
pdata:外部扩展RAM的低256个字节,地址出现在A0-A7的上时读写,用movx ACC,@Rx读写。这个比较特殊,而且C51好象有对此BUG,建议少用。但也有他的优点,具体用法属于中级问题,这里不提。
startup.a51的作用 和汇编一样,在C中定义的那些变量和数组的初始化就在startup.a51中进行,如果你在定义全局变量时带有数值,如unsigned char data xxx="100";,那startup.a51
中就会有相关的赋值。如果没有=100,startup.a51就会把他清0。(startup.a51==变量的初始化)。 这些初始化完毕后,还会设置SP指针。对非变量区域,如堆栈区,将不会有赋值或清零动
作。 有人喜欢改startup.a51,为了满足自己一些想当然的爱好,这是不必要的,有可能错误的。比如掉电保护的时候想保存一些变量, 但改startup.a51来实现是很笨的方法,实际只要利用非变量区域的特性,定义一个指针变量指向堆栈低部:0xff处就可实现。, 为什么还要去改? 可以这么说:任何时候都可以不需要改startup.a51,如果你明白它的特性。
bit
是在内部数据存储空间中 20H .. 2FH 区域中一个位的地址,这在DATA的20H以后以字节形式出现,可互相参照。另外加上8051 可寻址 的SFR,但刚刚试过,只是00H--7FH起作用,也就是说当数据有
变化时颜变红,以后的从80H到--FFH就不是位寻址区了,是位寻址的特殊寄存器,如涉及到了可位寻址的那11个当然会有反应。
复位后,程序计数器PC的内容为0000H,内部RAM各单元的值不确定。各功能寄存器的复位值如下:堆栈指针SP的复位值为07H,累加器ACC、寄存器B的复位值为00H,数据指针DPTR的复位值为000
0H,而p0、p1、p2、p3四个口的复位值为0FFH。其他SFR如PSW、TCON、TMOD、TL0、TH0、TL1、TH1的复位值也为00H。
wave中是低128字节和高128字节(0-7FH),低128字节是片内RAM区,高128字节(80-FFH)是SFR(特殊功能寄存器)bit则是位于低128字节的20H .. 2FH 区域,即data的20H .. 2FH 区域
code
是在 0000H .. 0FFFFH 之间的一个代码地址。
我用
ORG 5000H
TAB: DB 22H,3BH,43H,66H,5H,6DH,88H后,
CODE从5000H开始以后变成DB各位
data
是在 0 到 127 之间的一个数据存储器地址,或者加 128 .. 255 范围内的一个特殊功能寄存器(SFR)地址。两者访问的方式不同
idata
是 0 to 255 范围内的一个 idata 存储器地址。
idata与data重合低128字节,有的地方只有DATA表示256字节的片内RAM,
xdata 是 0 to 65535 范围内的一个 xdata 存储器地址。
指针类型和存储区的关系详解
一、存储类型与存储区关系
data ---> 可寻址片内ram
bdata ---> 可位寻址的片内ram
idata ---> 可寻址片内ram,允许访问全部内部ram
pdata ---> 分页寻址片外ram (MOVX @R0) (256 BYTE/页)
xdata ---> 可寻址片外ram (64k 地址范围FFFFH)
code ---> 程序存储区 (64k 地址范围),对应MOVC @DPTR
二、指针类型和存储区的关系
对变量进行声明时可以指定变量的存储类型如:
uchar data x和data uchar x相等价都是在内ram区分配一个字节的变量。
同样对于指针变量的声明,因涉及到指针变量本身的存储位置和指针所指向的存储区位置不同而进行相应的存储区类型关键字的
使用如:
uchar xdata * data pstr
是指在内ram区分配一个指针变量("*"号后的data关键字的作用),而且这个指针本身指向xdata区("*"前xdata关键字的作用),
可能初学C51时有点不好懂也不好记。没关系,我们马上就可以看到对应“*”前后不同的关键字的使用在编译时出现什么情况。
......
uchar xdata tmp[10]; //在外ram区开辟10个字节的内存空间,地址是外ram的0x0000-0x0009
......
第1种情况:
uchar data * data pstr;
pstr=tmp;
首先要提醒大家这样的代码是有bug的, 他不能通过这种方式正确的访问到tmp空间。 为什么?我们把编译后看到下面的汇编
代码:
MOV 0x08,#tmp(0x00) ;0x08是指针pstr的存储地址
看到了吗!本来访问外ram需要2 byte来寻址64k空间,但因为使用data关键字(在"*"号前的那个),所以按KeilC编译环境来说
就把他编译成指向内ram的指针变量了,这也是初学C51的朋友们不理解各个存储类型的关键字定义而造成的bug。特别是当工程中的
默认的存储区类为large时,又把tmp[10] 声
明为uchar tmp[10] 时,这样的bug是很隐秘的不容易被发现。
第2种情况:
uchar xdata * data pstr;
pstr = tmp;
这种情况是没问题的,这样的使用方法是指在内ram分配一个指针变量("*"号后的data关键字的作用),
而且这个指针本身指向
xdata区("*"前xdata关键字的作用)。编译后的汇编代码如下。
MOV 0x08,#tmp(0x00) ;0x08和0x09是在内ram区分配的pstr指针变量地址空间
MOV 0x09,#tmp(0x00)
这种情况应该是在这里所有介绍各种情况中效率最高的访问外ram的方法了,请大家记住他。
第3种情况:
uchar xdata * xdata pstr;
pstr=tmp;
这中情况也是对的,但效率不如第2种情况。编译后的汇编代码如下。
MOV DPTR, #0x000A ;0x000A,0x000B是在外ram区分配的pstr指针变量地址空间
MOV A, #tmp(0x00)
MOV @DPTR, A
INC DPTR
MOV A, #tmp(0x00)
MOVX @DPTR, A
这种方式一般用在内ram资源相对紧张而且对效率要求不高的项目中。
第4种情况:
uchar data * xdata pstr;
pstr=tmp;
如果详细看了第1种情况的读者发现这种写法和第1种很相似,是的,同第1 种情况一样这样也是有bug的,但是这次是把pstr分
配到了外ram区了。编译后的汇编代码如下。
MOV DPTR, #0x000A ;0x000A是在外ram区分配的pstr指针变量的地址空间
MOV A, #tmp(0x00)
MOVX @DPTR, A
第5种情况:
uchar * data pstr;
pstr=tmp;
大家注意到"*"前的关键字声明没有了,是的这样会发生什么事呢?下面这么写呢!对了用齐豫的一首老歌名来说就是 “请跟我
来”,请跟我来看看编译后的汇编代码,有人问这不是在讲C51吗? 为什么还要给我们看汇编代码。C51要想用好就要尽可能提升C51
编译后的效率,看看编译后的汇编会帮助大家尽快成为生产高效C51代码的高手的。还是看代码吧!
MOV 0x08, #0X01 ;0x08-0x0A是在内ram区分配的pstr指针变量的地址空间
MOV 0x09, #tmp(0x00)
MOV 0x0A, #tmp(0x00)
注意:这是新介绍给大家的,大家会疑问为什么在前面的几种情况的pstr指针变量都用2 byte空间而到这里就用3 byte空间了
呢?这是KeilC的一个系统内部处理,在KeilC中一个指针变量最多占用 3 byte空间,对于没有声明指针指向存储空间类型的指针,
系统编译代码时都强制加载一个字节的指针类型分辩值。具体的对应关系可以参考KeilC的help中C51 User's Guide。
第6种情况:
uchar * pstr;
pstr=tmp;
这是最直接最简单的指针变量声明,但他的效率也最低。还是那句话,大家一起说好吗!编译后的汇编代码如下。
MOV DPTR, #0x000A ;0
x000A-0x000C是在外ram区分配的pstr指针变量地址空间
MOV A, #0x01
MOV @DPTR, A
INC DPTR
MOV DPTR, #0x000A
MOV A, #tmp(0x00)
MOV @DPTR, A
INC DPTR
MOV A, #tmp(0x00)
MOVX @DPTR, A
这种情况很类似第5种和第3种情况的组合,既把pstr分配在外ram空间了又增加了指针类型的分辨值。
浅谈C51内存优化(data idata xdata)
对 51 单片机内存的认识,很多人有误解,最常见的是以下两种
① 超过变量128后必须使用compact模式编译
实际的情况是只要内存占用量不超过 256.0 就可以用 small 模式编译
② 128以上的某些地址为特殊寄存器使用,不能给程序用
与 PC 机不同,51 单片机不使用线性编址,特殊寄存器与 RAM 使用重复的重复的地址。但访问时采用不同的指令,所以并不会占用 RAM 空间。
由于内存比较小,一般要进行内存优化,尽量提高内存的使用效率。
以 Keil C 编译器为例,small 模式下未指存储类型的变量默认为data型,即直接寻址,只能访问低 128 个字节,但这 128 个字节也不是全为我们的程序所用,寄存器 R0-R7必须映射到低RAM,要占去 8 个字节,如果使用寄存组切换,占用的更多。
所以可以使用 data 区最大为 120 字节,超出 120 个字节则必须用 idata 显式的指定为间接寻址,另外
堆栈至少要占用一个字节,所以极限情况下可以定义的变量可占 247 个字节。当然,实际应用中堆栈为一个字节肯定是不够用的,但如果嵌套调用层数不深,有十几个字节也够有了。
为了验上面的观点,写 了个例子
#define LEN 120
data UCHAR tt1[LEN];
idata UCHAR tt2[127];
void main()
{
UCHAR i,j;
for(i = 0; i < LEN; ++i )
{
j = i;
tt1[j] = 0x55;
}
}
可以计算 R0-7(8) + tt1(120) + tt2(127) + SP(1) 总共 256 个字节
keil 编译的结果如下:
Program Size: data=256.0 xdata=0 code=30
creating hex file from ".\Debug\Test"...
".\Debug\Test" - 0 Error(s), 0 Warning(s).
(测试环境为 XP + Keil C 7.5)
这段代码已经达到了内存分配的极限,再定义任何全局变量或将数组加大,编译都会报错 107
这里要引出一个问题:为什么变量 i、j 不计算在内?
这是因为 i、j 是局部变量,编译器会试着将其优化到寄存器 Rx 或栈。问题也就在这了,如果局部变量过多或定义了局部数组,编译器无法将其优化,就必须使用 RAM 空间,虽然全局变量的分配经过精心计算没有超出使用范围,仍会产生内存溢出的错误!
而编译器是否能成功的优化变量是根据代码来的
上面的代码中,循环是臃肿的,变量 j 完全不必要,那么将代码改成
UCHAR i;
UCHAR j;
for(i = 0; i < LEN; ++i )
{
tt1[i] = 0x55;
}
再编译
看看,出错 了吧!
因为编译器不知道该如何使用 j,所以没能优化,j 须占 RAM 空间,RAM 就溢出了。
(智能一点的编译器会自动将这个无用 的变量去掉,但这个不在讨论之列了)
另外,对 idata 的定义的变量最好放在 data 变量之后
对于这 一种定义
uchar c1;
idata uchar c2;
uchar c3;
变量 c2 肯定会以间接寻址,但它有可能落在 data 区域,就浪费了一个可直接寻址的空间
变量优化一般要注意几点:
①让尽可能多的变量使用直接寻址,提高速度
假如有两个单字节的变量,一个长119的字符型数组
因为总长超过 120 字节,不可能都定义在 data 区
按这条原则,定义的方式如下:
data UCHAR tab[119];
data UCAHR c1;
idata UCHaR c2;
但也不是绝的,如果 c1, c2 需要以极高的频率访问,而 tab 访问不那么频繁
则应该让访问量大的变量使用直接寻址:
data UCAHR c1;
data UCHaR c2;
idata UCHAR tab[119];
这个是要根据具体项目需求来确定的
②提高内存的重复利用率
就是尽可能的利用局部变量,局部变量还有个好处是访问速度比较快
由前面的例子可以看出,局部变量 i, j 是没有单独占用内存的
子程序中使用内存数目不大的变量尽量定义为局部变量
③对于指针数组的定义,尽可能指明存储类型
尽量使用无符号类型变量
一般指针需要一个字节额外的字节指明存储类型
8051 系列本身不支持符号数,需要外加库来处理符号数,一是大大降低程序运行效率,二是需要额外的内存
④避免出现内存空洞
可以通过查看编译器输出符号表文件(.M51)查看
对前面的代码,M51文件中关于内存一节如下:
* * * * * * * D A T A M E M O R Y * * * * * * *
REG 0000H 0008H ABSOLUTE "REG BANK 0"
DATA 0008H 0078H UNIT ?DT?TEST
IDATA 0080H 007FH UNIT ?ID?TEST
IDATA 00FFH 0001H UNIT ?STACK
第一行显示寄存器组0从地址0000H开始,占用0008H个字节
第二行显示DATA区变量从0008H开 始,占用0078H个字节
第三行显示IDATA区变量从0080H开始,占用007F个字节
第四行显示堆栈从00FFH开始,占 0001H个字节
由于前面代码中变量定义比较简单,且连续用完了所有空间,所以这里显示比较简单
变量定义较多时,这里会有很多行
如果全局变量与局部变量分配不合理,就有可能出现类似下面的行
0010H 0012H *** GAP ***
该行表示从0010H开始连续0012H个字节未充分利用或根本未用到
出现这种情况最常见的原因是局变量太多、多个子程序中的局部变量数目差异太 大、使用了寄存器切换但未充分利用
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论