Keil_C51绝对地址编译

来源:互联网 发布:天猫竞品数据分析表格 编辑:程序博客网 时间:2024/05/23 00:35

Keil C51中变量和函数的绝对地址定位问题:

 

1.  变量绝对地址定位

       1)    在定义变量时使用 _at_ 关键字加上地址就可.

              e.g.

                     unsigned char idata myvar _at_ 0x40;

              把变量 myvar 定义在 idata 0x40 , M51 文件中可以找到这麽一行

            IDATA   0040H     0001H     ABSOLUTE    

              表示有变量在 idata 0x0040处绝对地址定位.

       2)    使用 KeilC 编译器定义绝对地址的变量,方法待查.

 

2.  函数绝对地址定位

       1)    在程序中编写一函数 myTest

              void myTest(void)

              {

                     // Add your code here

              }

       2)    使用 KeilC 编译器定位绝对地址的函数,打开 Project -> Options for Target菜单,

              选中 BL51 Locate 选项卡, Code: 中输入:

              ?PR?myTest?MAIN(0x4000)

              把函数 myTest 定位到程序区的 0x4000,

              再次编译就可以了.

       3)    一次定位多个函数的方法

              同样地, 在程序中再编写另外一个函数 myTest1

              void myTest1(void)

              {

                     // Add your code here

              }

              Options for Target 菜单的 BL51 Locate选项卡的 Code: 中输入:

              ?PR?myTest1?MAIN(0x3900), ?PR?myTest?MAIN(0x4000)

              把函数 myTest1 定位在程序区的 0x3900, 把函数 myTest定义在程序区的 0x4000 ,

              重新编译就可以了.

              M51 文件中可以找到下面的内容:

 

              >> 3.obj TO Reader RAMSIZE (256) CODE (?PR?MYTEST1?MAIN (0X3900), ?PR?MYTEST?MAIN (0X4000))

 

                    3665H     029BH                  *** GAP ***

            CODE    3900H     0014H     UNIT         ?PR?MYTEST1?MAIN

                    3914H     06ECH                  *** GAP ***

            CODE    4000H     0014H     UNIT         ?PR?MYTEST?MAIN

       4)    函数的调用:

              程序中直接调用函数的方式就不说明了, 这里重点讲使用函数指针调用绝对地址处的函数的方法.

              (1)   定义调用的函数原形

                     typedef void (*CALL_MYTEST)(void);

              这是一个回调函数的原形, 参数为空.

              (2)   定义相应的函数指针变量

                     CALL_MYTEST    myTestCall = NULL;

              (3)   函数指针变量赋值, 指向我们定位的绝对地址的函数

                     myTestCall = 0x3900;

              指向函数 myTest1

              (4)   函数指针调用

                     if (myTestCall != NULL)

                     {

                            myTestCall();                //调用函数指针处的函数 myTest1, PC指针为 0x3900

                     }

              检查编译生成的 bin 文件, 0x3900 处可以看到 myTest1的内容, 0x4000处可以看到 myTest 的内容,

              (5)   其它说明:

                    如果在 0x3000 0x3900的程序空间没有内容时, myTestCall的地址指针指到 0x3800

                     ( 0x3000 0x3900 之间), 会从 0x3900处开始执行.

              至於在 Load 中调用 AP中的函数的方法与此类似, 但是相应的参数传递可能要另寻方法.

 

************************************

 

keil C51绝对地址访问

 

在利用keil进行8051单片机编程的时,常常需要进行绝对地址进行访问.特别是对硬件操作,DA AD 采样 ,LCD 液晶操作,打印操作.等等.
C51
提供了三种访问绝对地址的方法:
1.
绝对宏:
在程序中,用include<absacc.h>”即可使用其中定义的宏来访问绝对地址,包括:
CBYTE
XBYTEPWORDDBYTECWORDXWORDPBYTEDWORD
具体使用可看一看absacc.h便知

例如:
rval=CBYTE[0x0002];
指向程序存贮器的0002h地址
rval=XWORD [0x0002];
指向外RAM0004h地址
2. _at_
关键字
直接在数据定义后加上_at_ const即可,但是注意:
(1)
绝对变量不能被初使化;
(2)bit
型函数及变量不能用_at_指定。

例如:

idata struct link list _at_ 0x40;
指定list结构从40h开始。
xdata char text[25b] _at_0xE000
;指定text数组从0E000H开始
提示:如果外部绝对变量是I/O端口等可自行变化数据,需要使用volatile关键字进行描述,请参考absacc.h

3.
连接定位控制
此法是利用连接控制指令code xdata pdata \data bdata地址进行,如要指定某具体变量地址,则很有局限性,不作详细讨论。

:(c51)

/*-------------------------------------------------------------------------- ABSACC.H Direct access to 8051, extended 8051 and Philips 80C51MX memory areas. Copyright (c) 1988-2002 Keil Elektronik GmbH and Keil Software, Inc. All rights reserved. --------------------------------------------------------------------------*/ #ifndef __ABSACC_H__ #define __ABSACC_H__ #define CBYTE ((unsigned char volatile code *) 0) #define DBYTE ((unsigned char volatile data *) 0) #define PBYTE ((unsigned char volatile pdata *) 0) #define XBYTE ((unsigned char volatile xdata *) 0) #define CWORD ((unsigned int volatile code *) 0) #define DWORD ((unsigned int volatile data *) 0) #define PWORD ((unsigned int volatile pdata *) 0) #define XWORD ((unsigned int volatile xdata *) 0) #ifdef __CX51__ #define FVAR(object, addr) (*((object volatile far *) (addr))) #define FARRAY(object, base) ((object volatile far *) (base)) #define FCVAR(object, addr) (*((object const far *) (addr))) #define FCARRAY(object, base) ((object const far *) (base)) #else #define FVAR(object, addr) (*((object volatile far *) ((addr)+0x10000L))) #define FCVAR(object, addr) (*((object const far *) ((addr)+0x810000L))) #define FARRAY(object, base) ((object volatile far *) ((base)+0x10000L)) #define FCARRAY(object, base) ((object const far *) ((base)+0x810000L)) #endif #endif :(c166) /*-------------------------------------------------------------------------- ABSACC.H Direct access to 166 memory areas for C166/EC++ Version 5. Copyright (c) 1992-2004 Keil Elektronik GmbH and Keil Software, Inc. All rights reserved. --------------------------------------------------------------------------*/ #ifndef __ABSACC_H__ #define __ABSACC_H__ #if (__MODEL__ == 0) #define MVAR(object, addr) (*((object volatile *) (addr))) #define MARRAY(object, base) ((object volatile *) (base)) #else #define MVAR(object, addr) (*((object volatile far *) (addr))) #define MARRAY(object, base) ((object volatile far *) (base)) #define HVAR(object, addr) (*((object volatile huge *) (addr))) #define HARRAY(object, base) ((object volatile huge *) (base)) #define XVAR(object, addr) (*((object volatile xhuge *) (addr))) #define XARRAY(object, base) ((object volatile xhuge *) (base)) #endif 

#endif

 

以下来自转载:

使用KeilC51软件,可以很方便地将代码或者数据绝对定位到某个地址。
1、代码定位:
方法1:使用伪指令CSEG。比如要将MyFunc1定位到代码区C:0x1000,则新建一个A51文件,添加以下内容:
PUBLIC MYFUNC1
CSEG AT 1000H
MYFUNC1:
;
其它代码
RET
在其它源文件中,就可以调用MyFunc()函数了。需要注意的是,编译器不检测传递参数的数目,仅检测函数是否有返回值。
方法2:使用BL51 Locate选项。比如在main.c中定义了一个MyFunc2函数,并且要将该函数定位到代码区C:0x2000,则从菜单中选择Project->Options for Target 'Target1',在弹出的对话框中选择BL51 Locate页,在下面的code栏中写上?PR?MYFUNC2?MAIN(0x2000)即可。
如果想定位多个函数,也可以使用*通配符。
2
、变量定位:
只有全局变量可以绝对定位,局部变量无法实现绝对定位。
方法1:使用_at_关键字。声明一个全局变量unsigned char data MyBuf1[8] _at_ 0x20;
方法2:使用BL51 Locate选项。比如将main.c中定义的所有data型的全局变量定位到数据区D:0x28开始的空间,则从菜单中
选择Project->Options for Target 'Target1',在弹出的对话框中选择BL51 Locate页,在下面的data栏中写上?DT?MAIN(0x28)即可。
如果是idata,则使用?ID?MAIN(0x28),如果是xdata,则使用?XD?MAIN(0x28),如果是pdata,则使用?PD?MAIN(0x28)
3
、堆栈定位:
STARTUP.A51文件中定义了堆栈区?STACK,其起始地址同样可以在BL51 Locate页中设置,在Stack栏写上?STACK(0x80)

BL51 locate
选项卡中

code range xdata range如果不填写,编译默认将程序中相应代码和变量从空间前面取起

网上看到有人提到在keil中使用_at_进行绝对地址定位问题,我简单介绍一下它的用法。

使用_at_关键字对存储器进行绝对地址定位程序如下

i nclude<reg51.h>

char xdata LED_Data[50] _at_ 0x8000;

main()

{

LED_Data[0] = 0x23;

}

keil中运行以上程序可以在存储器窗口中输入 x0x8000可以看到0x8000地址中的值为0x23.

值得指出的几点是

1.在给变量LED_Data[50]定位绝对地址空间时,不能对其赋初值。

2.char xdata LED_Data[50] _at_ 0x8000;这条语句不能主函数中。有些网友提到在按着keil说明中用_at_进行绝对地址定位时,编译会出现错误274,就是将这条语句放在主函数中的原因。

3.keil中地址是自动分配的,所以除非特殊情况否则不提倡使用绝对地址定位。初学者因帖别注意。不要把c当作汇编使用。

对需要/RST复位后要保持变量不变,防止意外改变(比如升级到新程序,变量地址可能被编译器优化到其他地方),比较有用!!!!


STARTUP.A51 这个文件有什么用,有必要添加到工程吗?

如果不添加"startup.a51"文件,编译器就会自动加入一段初始化内存以及堆栈等的代码,这时的内存初始化部分你就无法去控制了,当然这在大部分情况下没什么关系。但是如果你想你的程序在复位后,内存里面的信息依然还保存着(所说的热复位),那么你就需要添加该启动文件,并且去里面修改内存初始化部分,不要初始化你需要保留的部分内存。

 

请问如何在keil编译器里,编程时指定函数的绝对地址 (无内容)

不好意思啊,我还从来没有接触过有这样要求情况,不过从网上其他地方找了一篇你参考一下吧,、函数定位:
假如要把C源文件 tools.c 中的函数
int BIN2HEX(int xx)
{
  ...
}
放在CODE MEMORY的0x1000处,先编译该工程,然后打开该工程的M51文件,在
* * *   C O D E   M E M O R Y   * * *
行下找出要定位的函数的名称,应该形如:
CODE    xxxxH     xxxxH     UNIT         ?PR?_BCD2HEX?TOOLS
然后在:
Project->Options for Target ...->BL51 LocateCode
中填写如下内容:

?PR?_BCD2HEX?TOOLS(0x1000)
再次Build,在M51中会发现该函数已放在CODE MEMORY的0x1000处了
2、赋初值的变量定位:
要将某变量定位在一绝对位置且要赋初值,此时用 _at_ 不能完成,则如下操作:
在工程中建立一个新的文件,如InitVars.c,在其中对要处理的变量赋初值(假设是code变
量):
char code myVer = {"COPYRIGHT 2001-11"};
然后将该文件加入工程,编译,打开M51文件,若定义的是code型,则在
* * *   C O D E   M E M O R Y   * * *
下可找到:
CODE    xxxxH     xxxxH     UNIT         ?CO?INITVARS
然后在:
Project->Options for Target ...->BL51 LocateCode
中填入:

?CO?INITVARS(0x200)
再次编译即可。
相应地,如为xdata变量,则InitVars.c中写:
char xdata myVer = {"COPYRIGHT 2001-11"};
然后将该文件加入工程,编译,打开M51文件,在
* * *  X D A T A   M E M O R Y  * * *
下可找到:
XDATA   xxxxH     xxxxH     UNIT         ?XD?INITVARS
然后在:
Project->Options for Target ...->BL51 LocateXdata
中填入:

?XD?INITVARS(0x200)
再次编译即可。相应地,若定义的是data/idata等变量,则相应处理即可。
3、若有多个变量或函数要进行绝对地址定位,则应按地址从低到高的顺序排列

**********************************

PIC 51 混编 C18指定数据绝对地址

 

51:

 

RSEG是段选择指令,要想明白它的意思就要了解段的意思。

段是程序代码或数据对象的存储单位。程序代码放到代码段,数据对象放到数据段。段分两种,一是绝对段,一是再定位段。绝对段在汇编语言中指定,在用L51联接的时候,地址不会改变。用于如访问一个固定存储器的i/o,或提供中断向量的入口地址。而再定位段的地址是浮动的。它的地址有L51对程序模块连接时决定,C51对源程序编译所产生的段都是再定位段,它都有段名和存储类型。绝对段没有段名。

说了这么多,大家可能还是不明白段是什么意思。别急,接着往下看。

例如,你写用C写了一个函数 void test_fun(void) {} , 存在test.c中,用编译器编译以后. SRC FILE中看到:

 

   ?PR?test_fun?TEST SEGMENT CODE //(函数放到代码段中)

 

    写这个函数体的时候: RSEG ?PR?test_fun?TEST //选择已定位的代码段为当前段 test_fun:

 

     ……//代码

所以函数的表达模式是这样: ?PR?函数名?文件名

而函数名又分: 1:无参函数 ?PR?函数名?文件名

2:有参函数 ?PR?_函数名?文件名

3:再入函数 ?PR?_?函数名?文件名

又例如你定义了全局变量 unsigned char data temp1,temp2; unsigned char xdata temp3;test.c文件中,编译器会为每个文件分0到多个全局数据段,相同类型的全局变量被存到同一段中。所以上面会编译成如下:

RSEG ? DT? TEST

. temp1: DS 1

. temp2: DS 1

;

RSEG ?XD? TEST

. temp3: DS 1

//下面是各个类型的数据全局段的表示

?CO? 文件名 //常数段

?XD? FILE_NAME //XDATA数据段

?DT? FILE_NAME //DATA数据段

?ID? FILE_NAME //IDATA…..

?BI? FILE_NAME // BIT …..

?BA? FILE_NAME //BDATA….

?PD? FILE_NAME //PDATA…..

看到这里大家应该明白段的意思了吧。也许你会问,这有什么作用哪?它就是用在当你需要用汇编语言写一部份程序的时候,把汇编写的函数放在这个问件中,改名xxx.a51,按上面的规则写。

编译就好。

既然知道了段的意思,现在我们回到SEG的用法上来。

A51中有两种段选择指令:再定位段选择指令和绝对段选择指令.它们用来选择当前段是再定位段还是绝对段。使用不同的段选择指令,将使程序定位在不同的地址空间之内。

 

1: 再定位段的选择指令是: RSEG段名

它用来选择一个在前面已经定义过的再定位段作为当前段。

用法就像我们上面的例子,先申明了一个函数段,后面写这个函数段。

2: 绝对段选择指令

CSEG [AT 绝对地址表达式] //绝对代码段

DSEG [AT 绝对地址表达式] //内部绝对数据段

XSEG [AT 绝对地址表达式] //外部绝对数据段

ISEG [AT 绝对地址表达式] //内部间接寻址绝对数据段

BSEG [AT 绝对地址表达式] //绝对位寻址段

它们的用法我举一个例子:

例如我们写串口中断程序,起始地址是0x23.就这样写

CSEG AT 0X23

LJMP serialISR

RSEG ?PR?serialISR?TEST

. serialISR:

 

 

PIC:

 

汇编函数使用同一个工程C文件中的变量,例如ICFLAGC文件中定义,则汇编文件中定义方式为

 

;定义外部变量

EXTERN ICFLAG

 

定义函数

 

例如

CARDATR:

 

...........

 

RET

 

 

GLOBAL CARDATR

 

 

在同一个工程文件下调用汇编中的函数CARDATR

 

则应该定义函数extern void CARDATR(void);

 

C18指定数据绝对地址

例如:

#pragma udata overlay RECBUFS =0x190 //200

UINT8 NUMBER;

UINT8 REC_BUF[31];

#pragma udata

 

************************************

**************************************8

标签:KEIL  C51  编程  

KEIL C51高级编程

KEIL C51高级编程

第一节绝对地址访问
C51
提供了三种访问绝对地址的方法:

1.绝对宏:
在程序中,用“#include”即可使用其中定义的宏来访问绝对地址,包括:

CBYTEXBYTEPWORDDBYTECWORDXWORDPBYTEDWORD

具体使用可看一看absacc.h便知

例如:

rval=CBYTE[0x0002];指向程序存贮器的0002h地址

rval=XWORD [0x0002];指向外RAM0004h地址

2. _at_关键字
直接在数据定义后加上_at_ const即可,但是注意:

(1)绝对变量不能被初使化;

(2)bit型函数及变量不能用_at_指定。

例如:

idata struct link list _at_ 0x40;指定list结构从40h开始。

xdata char text[25b] _at_0xE000;指定text数组从0E000H开始

提示:如果外部绝对变量是I/O端口等可自行变化数据,需要使用volatile关键字进行描述,请参考absacc.h

3.连接定位控制
此法是利用连接控制指令code xdata pdata \data bdata对“段”地址进行,如要指定某具体变量地址,则很有局限性,不作详细讨论。

第二节 Keil C51与汇编的接口
1.
模块内接口
方法是用#pragma语句具体结构是:

#pragma asm

汇编行

#pragma endasm

这种方法实质是通过asmndasm告诉C51编译器中间行不用编译为汇编行,因而在编译控制指令中有SRC以控制将这些不用编译的行存入其中。

2.模块间接口
C
模块与汇编模块的接口较简单,分别用C51A51对源文件进行编译,然后用L51obj文件连接即可,关键问题在于C函数与汇编函数之间的参数传递问题,C51中有两种参数传递方法。

(1)通过寄存器传递函数参数

最多只能有3个参数通过寄存器传递,规律如下表:

参数数目
char
int
long,float
一般指针

1

2

3
R7

R5

R3
R6 & R7

R4 & R5

R2 & R3
R4
R7

R4R7
R1
R3

R1R3

R1R3

(2)通过固定存储区传递(fixed memory)

这种方法将bit型参数传给一个存储段中:

function_name?BIT

将其它类型参数均传给下面的段:?function_name?BYTE,且按照预选顺序存放。

至于这个固定存储区本身在何处,则由存储模式默认。

(3)函数的返回值

函数返回值一律放于寄存器中,有如下规律:

return type
Registev
说明


bit
标志位
由具体标志位返回

char/unsigned char 1_byte
指针
R7
单字节由R7返回

int/unsigned int 2_byte
指针
R6 & R7
双字节由R6R7返回,MSBR6

long&unsigned long
R4
R7
MSB
R4, LSB
R7

float
R4
R7
32Bit IEEE
格式


一般指针
R1
R3
存储类型在R3高位R2R1

(4) SRC控制

该控制指令将C文件编译生成汇编文件(.SRC),该汇编文件可改名后,生成汇编.ASM文件,再用A51进行编译。

第三节 Keil C51软件包中的通用文件
C51\LiB目录下有几个C源文件,这几个C源文件有非常重要的作用,对它们稍事修改,就可以用在自己的专用系统中。

1.动态内存分配
init_mem.C
:此文件是初始化动态内存区的程序源代码。它可以指定动态内存的位置及大小,只有使用了init_mem( )才可以调回其它函数,诸如malloc calloc,realloc等。

calloc.c:此文件是给数组分配内存的源代码,它可以指定单位数据类型及该单元数目。

malloc.c:此文件是malloc的源代码,分配一段固定大小的内存。

realloc.c:此文件是realloc.c源代码,其功能是调整当前分配动态内存的大小。

2. C51启动文件STARTUP.A51
启动文件STARTUP.A51中包含目标板启动代码,可在每个project中加入这个文件,只要复位,则该文件立即执行,其功能包括:

l定义内部RAM大小、外部RAM大小、可重入堆栈位置

l清除内部、外部或者以此页为单元的外部存储器

l按存储模式初使化重入堆栈及堆栈指针

l初始化8051硬件堆栈指针

lmain( )函数交权

开发人员可修改以下数据从而对系统初始化

常数名意义

IDATALEN待清内部RAM长度

XDATA START指定待清外部RAM起始地址

XDATALEN待清外部RAM长度

IBPSTACK是否小模式重入堆栈指针需初始化标志,1为需要。缺省为0

IBPSTACKTOP指定小模式重入堆栈顶部地址

XBPSTACK是否大模式重入堆栈指针需初始化标志,缺省为0

XBPSTACKTOP指定大模式重入堆栈顶部地址

PBPSTACK是否Compact重入堆栈指针,需初始化标志,缺省为0

PBPSTACKTOP指定Compact模式重入堆栈顶部地址

PPAGEENABLE P2初始化允许开关

PPAGE指定P2

PDATASTART待清外部RAM页首址

PDATALEN待清外部RAM页长度

提示:如果要初始化P2作为紧凑模式高端地址,必须:PPAGEENAGLE1PPAGEP2值,例如指定某页1000H10FFH,则PPAGE10H,而且连接时必须如下:

L51

 

PDATA(1080H),其中1080H1000H10FFH中的任一个值。

以下是STARTUP.A51代码片断,红色是经常可能需要修改的地方:

;------------------------------------------------------------------------------

; This file is part of the C51 Compiler package

; Copyright KEIL ELEKTRONIK GmbH 1990

;------------------------------------------------------------------------------

; STARTUP.A51: This code is executed after processor reset.

;

; To translate this file use A51 with the following invocation:

;

; A51 STARTUP.A51

;

; To link the modified STARTUP.OBJ file to your application use the following

; L51 invocation:

;

; L51 , STARTUP.OBJ

;

;------------------------------------------------------------------------------

;

; User-defined Power-On Initialization of Memory

;

; With the following EQU statements the initialization of memory

; at processor reset can be defined:

;

; ; the absolute start-address of IDATA memory is always 0

IDATALEN EQU 80H ; the length of IDATA memory in bytes.

;

XDATASTART EQU 0H ; the absolute start-address of XDATA memory

XDATALEN EQU 0H ; the length of XDATA memory in bytes.

;

PDATASTART EQU 0H ; the absolute start-address of PDATA memory

PDATALEN EQU 0H ; the length of PDATA memory in bytes.

;

; Notes: The IDATA space overlaps physically the DATA and BIT areas of the

; 8051 CPU. At minimum the memory space occupied from the C51

; run-time routines must be set to zero.

;------------------------------------------------------------------------------

;

; Reentrant Stack Initilization

;

; The following EQU statements define the stack pointer for reentrant

; functions and initialized it:

;

; Stack Space for reentrant functions in the SMALL model.

IBPSTACK EQU 0 ; set to 1 if small reentrant is used.

IBPSTACKTOP EQU 0FFH+1 ; set top of stack to highest location+1.

;

; Stack Space for reentrant functions in the LARGE model.

XBPSTACK EQU 0 ; set to 1 if large reentrant is used.

XBPSTACKTOP EQU 0FFFFH+1; set top of stack to highest location+1.

;

; Stack Space for reentrant functions in the COMPACT model.

PBPSTACK EQU 0 ; set to 1 if compact reentrant is used.

PBPSTACKTOP EQU 0FFFFH+1; set top of stack to highest location+1.

;

;------------------------------------------------------------------------------

;

; Page Definition for Using the Compact Model with 64 KByte xdata RAM

;

; The following EQU statements define the xdata page used for pdata

; variables. The EQU PPAGE must conform with the PPAGE control used

; in the linker invocation.

;

PPAGEENABLE EQU 0 ; set to 1 if pdata object are used.

PPAGE EQU 0 ; define PPAGE number.

;

;------------------------------------------------------------------------------

3.标准输入输出文件
putchar.c

putchar.c是一个低级字符输出子程,开发人员可修改后应用到自己的硬件系统上,例如向CLDLEN输出字符。

缺省:putchar.c是向串口输出一个字符XON|XOFF是流控标志,换行符“\*n”自动转化为回车/换行“\r\n”。

getkey.c

getkey函数是一个低级字符输入子程,该程序可用到自己硬件系统,如矩阵键盘输入中,缺省时通过串口输入字符。 4. 其它文件
还包括对Watch-Dog有独特功能的INIT.A51函数以及对8×C751适用的函数,可参考源代码。

第四节段名协定与程序优化
1.
段名协定(Segment Naming Conventions)
C51
编译器生成的目标文件存放于许多段中,这些段是代码空间或数据空间的一些单元,一个段可以是可重定位的,也可以是绝对段,每一个可重定位的段都有一个类型和名字,C51段名有以下规定:

每个段名包括前缀与模块名两部分,前缀表示存储类型,模块名则是被编译的模块的名字,例如:

COmain1:表示main1模块中的代码段中的常数部分

PRfunction1modulemodule模块中函数function1的可执行段,具体规定参阅手册。

2.程序优化
C51
编译器是一个具有优化功能的编译器,它共提供六级优化功能。确保生成目标代码的最高效率(代码最少,运行速度最快)。具体六级优化的内容可参考帮助。

C51中提供以下编译控制指令控制代码优化:

OPTIMIZE(SJXE):尽量采用子程序,使程序代码减少。

NOAREGS:不使用绝对寄存器访问,程序代码与寄存器段独立。

NOREGPARMS:参数传递总是在局部数据段实现,程序代码与低版本C51兼容。

OPTIMIZE(SIZE)AK OPTIMIZE(speed)提供6级优化功能,缺省为: OPTIMIZE(6,SPEED)

 

第五节 Keil C51的代码效率

一、存储模式的影响
存储模式决定了缺省变量的存储空间,而访问各空间变量的汇编代码的繁简程度决定了代码率的高低。

例如:一个整形变量i,如放于内存18H19H空间,则++i的操作编译成四条语句:

INC 0x19

MOV A,0x19

JNZ 0x272D

INC 0x18

0x272D

而如果放于外存空间0000H0001H++i的操作编译成九条语句:

MOV DPTR0001

MOVX A@ DPTR

INC A

MOVX @ DPTR,A

JNz #5

MOV OPTR,#0000

MOVX A,@DPTR

INC A

MOVX @ DPTR,A

就汇编之后的语句而言,对外部存储器的操作较内部存储器操作代码率要低得多,生成的语句为内存的两倍以上,而程序中有大量的这种操作,可见存储模式对代码率的响了。

因此程序设计的原则是

1、存储模式从small-Compact-large依次选择,实在是变量太多,才选large模式。

2、即使选择了large模式,对一些常用的局部的或者可放于内存中的变量,最好放于内存中,以尽量提高程序的代码率。

二、程序结构的影响
程序的结构单元包括模块、函数等等。同样的功能,如果结构越复杂,其所涉及的操作、变量、功能模块函数等就越多,较之结构性好,代码简单的程序其代码率自然就低得多。

此外程序的运行控制语句,也是影响代码率的关键因素,例如:switch -case语句,许多编译器都把它们译得非常复杂,Keil C51也不例外,相对较为简易的Switch-case语句,编译成跳转指令形式,代码率较高,但对较为复杂的Switch-Case,则要调用一个系统库函数?C?ICASE进行处理,非常复杂。

再如if( ),while( ),等语句也是代码相对较低的语句,但编译以后比switch-case要高得多。

因此建议设计者尽量少用switch-case之类语句来控制程序结构,以提高代码率。

除以上两点外,其它因素也会对代码率产生影响,例如:

是否用寄存器传递参数NOAREGS选项是否有

是否包括调试信息:即DEBUG选项

是否包括扩展的调试信息:即BJECTEXTEND

 

第六节如何优化C语言代码(程序员必读)

1、选择合适的算法和数据结构
应该熟悉算法语言,知道各种算法的优缺点,具体资料请参见相应的参考资料,有
很多计算机书籍上都有介绍。将比较慢的顺序查找法用较快的二分查找或乱序查找
法代替,插入排序或冒泡排序法用快速排序、合并排序或根排序代替,都可以大大
提高程序执行的效率。.选择一种合适的数据结构也很重要,比如你在一堆随机存
放的数中使用了大量的插入和删除指令,那使用链表要快得多。
数组与指针语句具有十分密码的关系,一般来说,指针比较灵活简洁,而数组则比
较直观,容易理解。对于大部分的编译器,使用指针比使用数组生成的代码更短,
执行效率更高。但是在Keil中则相反,使用数组比使用的指针生成的代码更短。。


2
、使用尽量小的数据类型
能够使用字符型(char)定义的变量,就不要使用整型(int)变量来定义;能够使用
整型变量定义的变量就不要用长整型(long int),能不使用浮点型(float)变量就
不要使用浮点型变量。当然,在定义变量后不要超过变量的作用范围,如果超过变
量的范围赋值,C编译器并不报错,但程序运行结果却错了,而且这样的错误很难
发现。
ICCAVR中,可以在Options中设定使用printf参数,尽量使用基本型参数(%c
%d
%x%X%u%s格式说明符),少用长整型参数(%ld%lu%lx%lX格式说明
),至于浮点型的参数(%f)则尽量不要使用,其它C编译器也一样。在其它条件不
变的情况下,使用%f参数,会使生成的代码的数量增加很多,执行速度降低。

3
、使用自加、自减指令
通常使用自加、自减指令和复合赋值表达式(a-=1a+=1)都能够生成高质量的
程序代码,编译器通常都能够生成incdec之类的指令,而使用a=a+1a=a-1之类
的指令,有很多C编译器都会生成二到三个字节的指令。在AVR单片适用的ICCAVR
GCCAVR
IARC编译器以上几种书写方式生成的代码是一样的,也能够生成高质量
incdec之类的的代码。

4
、减少运算的强度
可以使用运算量小但功能相同的表达式替换原来复杂的的表达式。如下:
(1)
、求余运算。
    a=a%8;
可以改为:
    a=a&7;
说明:位操作只需一个指令周期即可完成,而大部分的C编译器的“%”运算均是调
用子程序来完成,代码长、执行速度慢。通常,只要求是求2n方的余数,均可使用
位操作的方法来代替。

(2)
、平方运算
    a=pow(a,2.0);
可以改为:
    a=a*a;
说明:在有内置硬件乘法器的单片机中(51系列),乘法运算比求平方运算快得多
,因为浮点数的求平方是通过调用子程序来实现的,在自带硬件乘法器的AVR单片
机中,如ATMega163中,乘法运算只需2个时钟周期就可以完成。既使是在没有内置
硬件乘法器的AVR单片机中,乘法运算的子程序比平方运算的子程序代码短,执行
速度快。
如果是求3次方,如:
    a=pow(a,3.0);
更改为:
    a=a*a*a

则效率的改善更明显。

(3)
、用移位实现乘除法运算
    a=a*4;
    b=b/4;
可以改为:
    a=a<<2;
    b=b>>2;
说明:通常如果需要乘以或除以2n,都可以用移位的方法代替。在ICCAVR中,如果
乘以2n,都可以生成左移的代码,而乘以其它的整数或除以任何数,均调用乘除法
子程序。用移位的方法得到代码比调用乘除法子程序生成的代码效率高。实际上,
只要是乘以或除以一个整数,均可以用移位的方法得到结果,如:
    a=a*9
可以改为:
    a=(a<<3)+a

5
、循环
(1)
、循环语
对于一些不需要循环变量参加运算的任务可以把它们放到循环外面,这里的任务包
括表达式、函数的调用、指针运算、数组访问等,应该将没有必要执行多次的操作
全部集合在一起,放到一个init的初始化程序中进行。

(2)
、延时函数:
通常使用的延时函数均采用自加的形式:
    void delay (void)
    {
unsigned int i;
    for (i=0;i<1000;i++)
    ;
    }
将其改为自减延时函数:
    void delay (void)
    {
unsigned int i;
        for (i=1000;i>0;i--)
    ;
    }
两个函数的延时效果相似,但几乎所有的C编译对后一种函数生成的代码均比前一
种代码少1~3个字节,因为几乎所有的MCU均有为0转移的指令,采用后一种方式能
够生成这类指令。
在使用while循环时也一样,使用自减指令控制循环会比使用自加指令控制循环生
成的代码更少1~3个字母。
但是在循环中有通过循环变量“i”读写数组的指令时,使用预减循环时有可能使
数组超界,要引起注意。

(3)while
循环和dowhile循环
while循环时有以下两种循环形式:
unsigned int i;
    i=0;
    while (i<1000)
    {
        i++;
   //
用户程序
    }
或:
unsigned int i;
    i=1000;
    do
    i--;
    //
用户程序
    while (i>0);
在这两种循环中,使用dowhile循环编译后生成的代码的长度短于while循环。

6
、查表
在程序中一般不进行非常复杂的运算,如浮点数的乘除及开方等,以及一些复杂的
数学模型的插补运算,对这些即消耗时间又消费资源的运算,应尽量使用查表的方
式,并且将数据表置于程序存储区。如果直接生成所需的表比较困难,也尽量在启
了,减少了程序执行过程中重复计算的工作量。

7
、其它
比如使用在线汇编及将字符串和一些常量保存在程序存储器中,均有利于优化

*************************************