1、 一.51 的存储器结构 笼统来说单片机片内存储器分为 CODE 区和 data 区,cpu 从 code 区读取指令,对 data 区的数据进行运算处理。前者在程序运行中为只读,一般为FLASH,用来放置程序代码和一些只读的数据(如字模表之类),后者可以随机读写,用来存放程序运行中的临时数据,如局部变量或全局变量,全局变量一直占用着 RAM 内存,而局部变量在使用完后会自动清除 RAM 空间。当然在片外,可以外扩 FLASH 和 RAM(此时称为 XRAM,因为寻址方式不一样),外扩的大小与单片机寻址能力有关。有的单片机将外扩 RAM 封装在片内,如 AT89C51,所以有了所谓的片内 XR
2、AM。二.变量的存储模式变量是一种在程序执行过程中能不断变化的量。它有数据类型、存储类型、存储器模式和有效范围四种属性。C 语言规定每个变量必须有一个标识符作为变量名,在使用一个变量前,必须先对变量进行定义,指出其数据类型和存储模式。以便编译系统为其分配存储单元。在 C51 中对变量进行定义的格式如下:存储类型 数据类型 存储器类型 变量名表,如 auto char data i;存储类型指明变量的存储区域,而变量的存储类型和变量在程序中说明的位置决定了它的作用范围。存储类型含义与 C 语言相同。存储类型有四种:auto 型、extern 型、static 型、register 型,缺省为 a
3、uto 型(看来我一直都是凹凸型啊)。其区别如下:auto:自动变量。存储在内存的堆栈区,属于临时性存储变量,并不长期占用内存,可以被多次覆盖。register:寄存器变量。register 与 auto 一样属于自动类别。区别在于register 的值保存在 CPU 的寄存器中。计算机中只有寄存器中的数据才能直接参与运算,而一般变量是放在内存中的,变量参加运算是,需要先把变量从内存中取到寄存器中,然后计算。所以一般把使用最频繁的变量定义成 register变量。register 变量只能在函数中定义,并只能是 int 和 char 型。static:静态变量。声明静态变量的,也就是 C 语言
4、中的私有成员.如果在一个函数中声明一个静态变量,静态变量的空间不在栈里面,而是存储在静态空间里,这个函数结束后,静态变量的值依旧存在,内存不会收会此变量占用的内存空间,而是等整个程序都结果后才收回静态变量空间。extern:外部类型。extern 用来声明外部变量,可以用于此程序外的程序中(可在两个 C 文件间交叉使用),类型要一致。变量在数据运行时被分配了一定的内存空间,该空间在整个运行程序中,只要程序存在,自始自终都被该变量使用,即其值始终不变。数据类型就不用多说了,bit,byte,char 什么的。存储器类型与单片机的寻址方式有关,影响程序的执行效率。下表是传统C51 的存储器类型,不
5、同单片机类型有所差别。 空间名称 地址范围 说明DATA D:00H7FH 片内 RAM 直接寻址区BDATA D:20H2FH 片内 RAM 位寻址区IDATA I:00HFFH 片内 RAM 间接寻址区XDATA X:0000HFFFFH 64K 片外 RAM 数据区CODE C:0000HFFFFH 64K 片内外 ROM 代码区BANK0BANK31B0:0000HFFFFH:B31:0000HFFFFH分组代码区,最大可扩展32X64KB ROM,应该只能以 BANK 为单位读写。简单解释一下:data: 低 128 字节,可直接寻址,可以用 acc 直接读写的,速度最快,生成的代码
6、也最小。bdata:16 字节位寻址区(当然也可以按字节寻址),一般很小。idata: 固定指前面 0x00-0xff 的 256 个 RAM,其中前 128 和 data 的 128 完全相同,只是因为访问的方式不同。idata 是用类似 C 中的指针方式访问的。汇编中的语句为: MOVX ACC,Rx。xdata: 外部扩展 RAM,一般指外部 0x0000-0xffff 空间,有的集成于片内,用DPTR 访问。pdata: 外部扩展 RAM 的低 256 个字节,用 movx ACC,Rx 读写。这个比较特殊,而且 C51 好象有对此 BUG,建议少用。如果要指定存储地址可用如:ucha
7、r bdata LED _at_ 0x20;sbit led_7=led7;/用关键字 sbit 定义位变量来独立访问可寻址位对象int bdata ab2;/在可位寻址区定义数组 ab2sbit ab12=as112; 操作符“”后面的位位置的最大值取决于指定的基址类型 char 0-7,int 0-15,long 0-31。定义变量时如果省略了“存储器类型”,则按编译模式 SAMLL, COMPACT,LARGE 所规定的默认存储器类型确定变量的存储区域,(#pragma 预编译命令,可以指定函数的默认存储器模式。)C51 编译器的三种存储器模式(默认的存储器类型)对变量的影响如下:1.
8、SMALL:变量被定义在 8051 单片机的内部数据存储器中,因此对这种变量的访问速度最快。另外,所有的对象,包括堆栈,都必须嵌入内部数据存储器,而堆栈的长度是很重要的,实际栈长取决于不同函数的嵌套深度。2. COMPACT:变量被定义在分页外部数据存储器中,外部数据段的长度可达256 字节。这时对变量的访问是通过寄存器间接寻址(MOVXRi)进行的,堆栈位于 8051 单片机内部数据存储器中。采用这种编译模式时,变量的高 8 位地址由 P2 口确定。3. LARGE:变量被定义在外部数据存储器中(最大可达 64K 字节),使用数据指针 DPTR 来间接访问变量,访问数据速度慢,增加程序代码的
9、长度。存储器模式决定了缺省变量的存储空间,而访问各空间变量的汇编代码的繁简程度决定了代码率的高低。例如:一个整形变量 i,如放于内存 18H、19H 空间,则+ i 的操作编译成四条语句:INC 0x19MOV A,0x19JNZ 0x272DINC 0x180x272D:而如果放于外存空间 0000H、0001H 则+i 的操作编译成九条语句:MOV DPTR,0001MOVX A, DPTRINC AMOVX DPTR,AJNz #5MOV OPTR,#0000MOVX A,DPTRINC AMOVX DPTR,A对于函数而言,一个函数的存储器模式确定了函数的参数和局部变量在内存中的地址空
10、间,规则与变量定义一致。在定义一个函数时可以明确制定该函数的存储器模式,一般的形式如下:函数类型 函数名(形式参数表) 存储器模式其中的存储器模式是选项,未说明时则按该函数编译时的默认存储器模式处理。例如:#pragma large int func1(int a1, int a2) small int c;int func2(int b1, int b2) int c;#pragma 预编译命令,可以指定函数的默认存储器模式。C51 允许采用存储器的混合模式编程,充分利用单片机中有限的存储器空间,同时还可以加快程序运行的速度。三、网友的一点经验1.data 区空间小,所以只有频繁用到或对运算
11、速度要求很高的变量才放到 data区内,比如 for 循环中的计数值。2.data 区内最好放局部变量。因为局部变量的空间是可以覆盖的(某个函数的局部变量空间在退出该函数是就释放,由别的函数的局部变量覆盖),可以提高内存利用率。当然静态局部变量除外,其内存使用方式与全局变量相同;3.确保你的程序中没有未调用的函数。在 Keil C 里遇到未调用函数,编译器就将其认为可能是中断函数。函数里用的局部变量的空间是不释放,也就是同全局变量一样处理。这一点 Keil C 做得很愚蠢,但也没办法。4.程序中遇到的逻辑标志变量可以定义到 bdata 中,可以大大降低内存占用空间。在 51 系列芯片中有 16
12、 个字节位寻址区 bdata,其中可以定义 8*16=128 个逻辑变量。定义方法是: bdata bit LedState;但位类型不能用在数组和结构体中。5.其他不频繁用到和对运算速度要求不高的变量都放到 xdata 区。6.如果想节省 data 空间就必须用 large 模式,将未定义内存位置的变量全放到xdata 区。当然最好对所有变量都要指定内存类型。7.当使用到指针时,要指定指针指向的内存类型。在 C51 中未定义指向内存类型的通用指针占用 3 个字节;而指定指向 data 区的指针只占 1 个字节;指定指向 xdata 区的指针占 2 个字节。如指针 p 是指向data 区,则应
13、定义为: char data *p;。还可指定指针本身的存放内存类型,如:char data * xdata p;。其含义是指针 p 指向 data 区变量,而其本身存放在xdata 区。四.关于 STARTUP.A51用 C 语言编程时,开机时执行的代码并非是从 main()函数的第一句语句开始的,在 main()函数的第一句语句执行前要先执行一段起始代码,这段起始代码的源程序名为 STARTUP.A51。其作用可以看看源代码,简单来说就是进行变量的初始化,设置 SP 指针、堆栈空间等。如果考虑冷复位和热复位时的数据保存问题可以通过修改它实现(貌似不推荐)。BTW,冷复位用英文来表示是 Re
14、start,热复位用英文来表示是 Reset。我们把单片机从没加电到加上电源,而自动产生的复位称为冷复位;单片机在已经通电的情况下,给它一个复位信号,称为热复位。冷复位会使单片机的特殊功能寄存器和数据存储器的内容都改变;而热复位只是特殊功能寄存器的内容改变而单片机的内部数据存储器的内容不变。六.关于中断我都困了,坚持做完吧,难得这么整理一回。中断时很好用的,与查询方式分半边天。C51 编译器支持在 C 语言程序中直接编写 51 单片机的中断服务程序,C51 编译对函数定义进行了扩展,增加了一个关键字 interrupt,interrupt 是函数定义时的一个选项,加上它函数将函数定义成中断服务
15、函数。函数类型 函数名(形式参数表) interrupt nusing ninterrupt 后面的 n 为中断号,n 的取值范围为 031,编译器从 8n+3 处产生中断向量。C51 编译器还扩展了一个关键字 using,专门用来选择单片机的寄存器组,缺省时由编译器选择一个寄存器组作为绝对寄存器组访问。对于这个 using,多说几句,普通函数也可以用。任何时候,单片机只能用到四组寄存器中的一组,一般情况下 keil 来自动分配,用 USING 来选择其中的一组,目的是提高效率减少出入栈次数。编写 8051 单片机中断函数时应遵循以下规则:(l)中断函数不能进行参数传递,如果中断函数中包含任何
16、参数声明都将导致编译出错。(2)中断函数没有返回值,如果企图定义一个返回值将得到不正确的结果。因此建议在定义中断函数时将其定义为 void 类型,以明确说明没有返回值。(3)在任何情况下都不能直接调用中断函数,否则会产生编译错误。因为中断函数的返回是由 8051 单片机指令 RETI 完成的,RETI 指令影响 8051 单片机的硬件中断系统。如果在没有实际中断调求的情况下直接调用中断函数,RETI指令的操作结果会产生一个致命的错误。(4)如果中断函数中用到浮点运算,必须保存浮点寄存器的状态,当没有其它程序执行浮点运算时可以不保存。(5)如果在中断函数中调用了其它函数,则被调用函数所使用的寄存
17、器组必须与中断函数相同。用户必须保证按要求使用相同的寄存器组。否则会产生不正确的结果,这一点必须引起足够的注意。如果定义中断函数时没有使用using 选项,则由编译器选择一个寄存器组作绝对寄存器组访问。七、关于 volatilevolatile 是类型修饰符,影响编译器编译的结果,一般这个修饰符用来告知编译器,被修饰的变量是个“易变的”变量(volatile 的本意是“易变的”),与 volatile 变量有关的运算,不要进行编译优化,以免出错。(加 volatile关键字的变量有关的运算,将不进行编译优化。)例如:volatile int i=10;int j = i;int k = i;(
18、1)volatile 告诉编译器 i 是随时可能发生变化的,每次使用它的时候必须从 i 的地址中读取,因而编译器生成的可执行码会重新从 i 的地址读取数据放在 k 中。(2)而优化做法是,由于编译器发现两次从 i 读数据的代码之间的代码没有对i 进行过操作,它会自动把上次读的数据放在 k 中。而不是重新从 i 里面读。这样以来,如果 i 是一个寄存器变量或者表示一个端口数据就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问,不会出错。常用情况:1、中断服务程序中修改的供其它程序检测的变量需要加 volatile;2、多任务环境下各任务间共享的标志应该加 volatile;3、存储器映射的硬件寄存器通常也要加 volatile 说明,因为每次对它的读写都可能由不同意义