Keil C51 笔记

我只是做了一个汇总。转载请注明出处。
不时更新中。
内容比较杂,如果下面的内容有什么问题,请告诉我。谢谢!

参考资料:
http://blog.csdn.net/zhbsniper/article/details/7176136
http://blog.csdn.net/husion01/article/details/8443289

Keil C 使用经过扩展的 ANSI C 作为编写语言,不支持结构体内的函数和函数内联,函数体内所有的变量定义都应放在其它代码的前面,且 for 循环初始条件中作为计数器的变量是不能像C++那样即用即定义的。
1.51单片机的存储器配置为:
1.1.内存 256B,地址为0x00 – 0xFF。其中
1.1.1.低128B(data/idata)可直接/间接寻址,包括寄存器也使用这部分的内存。
1.1.2.高128B(idata)只可间接寻址(Intel 8052)。
1.2.特殊功能寄存器(sfr),128B,其内存地址使用的是 0x80 – 0xFF,只可直接寻址。也就是说,SFR与内存的高128B的地址是一样的,但存储并不是重叠的。它们依靠不同的寻址方式来区分。
1.3.外存(xdata),64KB,地址为 0x0000 – 0xFFFF。使用16位的 DPTR 间接寻址。
1.4.代码区(code),64KB,地址为 0x0000 – 0xFFFF。
因此,Keil C 中的指针有三种,分别对应不同的存储区域:

char xdata *px;       /* ptr to xdata */
char idata *pi;       /* ptr to idata */
char code  *pc;       /* ptr to code  */

2.可以使用 bit 数据类型来定义布尔型变量。但无法定义 bit 数组或指针。
3.可以使用 #define 指令来定义简单的数值常量,这样可以保证程序效率最高。如果使用 const 修饰符定义常量数组或结构体,则在编译时会根据变量前的其他修饰符(如 idata、xdata、pdata)占用相应的内存区域,这是十分浪费的。一般定义常量的方法是将常量放在程序段。例如

code const char data[16] = {1, 2, 3, ...};

由于程序段一般是只读的,因此更加常用的声明方法是

code char data[16] = {1, 2, 3, ...};

在默认情况下,Keil会将程序中使用到的字符串常量放在程序段。
4.Keil对中文的支持不是很好,这主要体现在定义含有中文的字符串常量的时候,在编译后的使用过程中可能会发现实际生成的字符串不正确。解决方法是使用其他工具,例如 WinHex 将字符串转换为字节数组。例如

//请输入一个数字
code char string1[] = {0xC7, 0xEB, 0xCA, 0xE4, 0xC8, 0xEB, 0xD2, 0xBB, 0xB8, 0xF6, 0xCA, 0xFD, 0xD7, 0xD6, 0xA3, 0xBA, 0x00};
...
puts(string1);	//将字符串送入 UART

注意ASCIIZ字符串应当以 0x00 结尾。
5.Keil中函数的前三个参数是通过寄存器来传递的,其它参数通过内存中的固定地址传递。
6.Keil的链接器BL51使用了数据覆盖(Data Overlaying)技术,而不是堆栈来保存函数的局部变量。在链接的时候,BL51会生成函数的调用树。如果两个函数之间没有调用关系,那么这两个函数的某些局部变量可能会使用相同的内存地址。
BL51这样做的原因并不是为了节约内存。事实上,这样做根本节约不了内存。
6.>如果一个函数func1调用另一个函数func2,那么func1、func2的局部变量根本就不能是同一块内存。C51还是要为他们分配不同的RAM,这跟使用堆栈相比,节约不了内存。
6.>如果func1,func2不是在一个调用链上,那么C51可以通过覆盖分析,让它们的局部变量共享相同的内存地址。但这样也不会比使用堆栈节约内存,因为既然它们是在不同的调用链上,那么当其中一个函数运行时,那么另外一个函数必然不在其生命期内,它所占用的堆栈也已释放,归还给系统。
真实的原因(C51使用覆盖段作为局部变量的存放地的原因)是51的指令系统没有一个有效的相对寻址(变址寻址)的指令,这使得使用堆栈作为变量的代价太过昂贵。
然而,BL51的数据覆盖会带来以下两个问题:
6.1.默认情况下,所有的函数是不可重入的。而递归函数应当使用 reentrant 关键字:

int fact(int n) reentrant
{
	if (n) return n * fact(n - 1);
	return 1;
}

这样BL51会在内存中开辟出一段区域用于模拟堆栈。
6.2.BL51在生成调用树的时候,对函数之间直接调用的分析是正确的,但对含有指针的间接调用则不能很好地处理。例如

int (*Action)();

int MyAction()
{
	return 100;
}

int CallAction()
{
	return Action();
}

void main()
{
	Action = MyAction;
	CallAction();
}

如果工程名为 Test,源代码文件为 test.c,那么在编译后,我们可以在 Test.M51 文件中找到

SEGMENT
  +--> CALLED SEGMENT
---------------------
?C_C51STARTUP
  +--> ?PR?MAIN?TEST

?PR?MAIN?TEST
  +--> ?PR?MYACTION?TEST
  +--> ?PR?CALLACTION?TEST

也就是说,BL51认为main函数调用了CallAction和MyAction。但其实main只是取得了MyAction的地址。实际的调用发生在CallAction函数中。
错误的调用树可能会带来不必要的内存浪费和 WARNING L13: RECURSIVE CALL TO SEGMENT。
为了修正调用树,我们可以在 Options for Target 对话框的 BL51 Misc 选项卡下的 Overlay 中填入

main ~ MyAction,	/*删除错误的调用关系*/
CallAction ! MyAction	/*建立正确的调用关系*/

随后,我们便会发现调用树变成了

?C_C51STARTUP
  +--> ?PR?MAIN?TEST

?PR?MAIN?TEST
  +--> ?PR?CALLACTION?TEST

?PR?CALLACTION?TEST
  +--> ?PR?MYACTION?TEST

修正完毕。
也可以打开“use linker control file”,然后依次点击 Create 和 Edit,并在 .lin 文件中加入

OVERLAY
(
	main ~ MyAction, /*删除错误的调用关系*/
	CallAction ! MyAction /*建立正确的调用关系*/
)

也可以达到相同的目的。
在尝试使用静态函数指针来保存函数的地址以备随后的调用时,可能会发生 WARNING L13。此时建议对调用树进行修正。具体的实例可以参阅 BL51 User’s Guide/Data Overlaying/Manipulating the Call Tree/Function Pointers一节。
关于修改调用树的具体信息,请参阅 BL51 User’s Guide/Linking Programs/Directives/OVERLAY 一节。
7.STARTUP.A51是每个工程必须具有的引导文件,其中完成了变量的初始化操作。如果你在建立工程时没有将此文件加入,那么Keil会使用默认的STARTUP.A51完成编译。
8.使用总线结构时,可以使用 xdata 的指针(xdata*),absacc.h中的部分宏,或者是在声明变量时使用 _at_ 关键字来访问指定地址的外设IO。
对于使用 _at_ 关键字的情况,应当使用下面的方式来定义外设变量
xdata volatile char LcdIO _at_ 0x8000;
其中,volatile 是为了防止 Keil 对变量存取过度优化,例如防止将下面这两行代码的第一行优化掉:

LcdIO = 0x00;
LcdIO = 0x05;

9.不要在中断处理程序中使用printf。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

ERROR: si-captcha.php plugin: GD image support not detected in PHP!

Contact your web host and ask them to enable GD image support for PHP.

ERROR: si-captcha.php plugin: imagepng function not detected in PHP!

Contact your web host and ask them to enable imagepng for PHP.

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

Content is available under CC BY-SA 3.0 unless otherwise noted.