-
Keil Compiler 8051, 8952 케일 컴파일러 셋팅하기임베디드/8051 2009. 12. 10. 09:50
학교다닐 때 메모해 두던 내용이었다.
Keil 5.10 컴파일러 쓰기 ^^;
안녕 하세요? 이번에는 8051 C 컴파일러 중에서 가장 좋다고 하는(벤치마크 테스트) Keil의 컴파일러의 사용법을 이야기 하겠다. 우선 모든 파일이 인스톨 되어 있다고 가정하겠다.
0. 프로그램 인스톨(알아서 필요한 프로그램을 구한다.)
1. autoexec.bat 설정
SET PATH=C:\Keil\C51\BIN; // C51 컴파일러가 있는 경로
SET C51LIB=C:\Keil\C51\LIB; // 라이브러리 경로
SET C51INC=C:\Keil\C51\INC; // include 경로
PATH파일 설정 아무데서나 컴파일러.링커를 실행할 수 있도록 해주고, 라이브러리.인클루드 데렉토리를 설정해 준다.
2. 컴파일 하기
우선 가장 쉽게 컴파일하는 방법은
>c51 hello.c 이렇게 하면 된다. 그러면 hello.obj 라는 오브젝트 파일이 생성된다.
3. 링크하기
요기가 무지하게 고생한 부분인데, 꼭 필요한 파일이 있다. hello.lin (임의의 화일)이라는 링크화일이 존재해야 한다.
>bl51 @hello.lin 그러면 hello라는 절대 오브젝트 화일이 생성된다.
4. 실행화일 만들기(hex)
요것도 무쟈게 고생한 부분인데, 사용법은 간단하다.
>oh51 hello 그러면 hello.hex 파일이 생성된다.
위와 같이 가장 간단히 실행시킬 수 있는 예를 들어 설명해 보았다. -2000.09.20-
그럼 실용적으로 사용할 수 있도록 Makefiles을 만들어 보자. 아래의 파란색의 고딕문자체로 된 부분이 nmake의 셋팅값이다. (이걸 모르는 사람은 알아서 공부하길 바란다.)
COMPILER = Keil 8051 C-Compiler V5.10
PROJ_DIR = E:\C51\EXAMPLE\HELLO
AS_EXE = E:\C51\BIN\A-51
CC_EXE = E:\C51\BIN\C51
LD_EXE = E:\C51\BIN\BL51
HEX_EXE = E:\C51\BIN\OH51
XTALK_LOCATION = E:\FTP\CPU\C51\$(OUTPUT).HEX
RM = del
AS_CMD = MACRO EP PR XR
CC_CMD = CD LC SM SB ROM(SMALL)
# If you use "SRC" option in CC_CMD line, $(FILE_NAME).OBJ is not created!! Remember this....
LD_CMD = RS(256) CO(0000H) XD(8000H) IX
OBJS = hello.obj
OUTPUT = hello
#####################################################################
all: main
main: $(OBJS)
$(LD_EXE) @<<DEBUG.LNK
$(OBJS) TO $(OUTPUT).ABS $(LD_CMD)
<<
$(HEX_EXE) $(OUTPUT)
# COPY $(OUTPUT) $(OUTPUT).ABS
COPY $(OUTPUT).HEX $(XTALK_LOCATION)
.c.obj:
$(CC_EXE) $(OUTPUT).c $(CC_CMD)
.a51.obj:
$(AS_EXE) $(OUTPUT).a51 $(AS_CMD)
#####################################################################
clean:
$(RM) *.obj
$(RM) *.bak
$(RM) *.lst
$(RM) *.hex
$(RM) *.m51
좀더 구체적으로 컴파일 옵션에 대해 얘기를 하자면....
CD : *.LST 화일안에 어셈블리어를 생성한다.
PR : *.LST 파일을 생성한다. EX) #pragma pr(\usr\list\sample.lst)
DEBUG : EMULATOR에서 쓸 수 있도록 OBJ 파일안에 여러 가지 정보를 생성한다.
SM(또는 CP, LA) : 메모리 모델을 SMALL로 잡는다. (또는 CP, LA) DATA MEMORY는 내부를 이용한다.
CP에서는 외부 245 BYTES 외부 메모리를 사용하고(@R0/R1), LA는 64KBYTES의 외부 DATA MEMORY를 이용할 수 있다.(DPTR) EX) #pragma compact
RB : 레지스터 뱅크를 선택한다. EX) #pragma RB(2)
꼭 함수밖에서 선언해야 한다. char lyze() { }....
IN(n) : 인터럽트의 LJMP를 생성한다. 즉 8 * n + 3에서 interrupt #를 입력한다.
OJ : 오브젝트의 이름을 변경하여 준다. EX) #pragma oj(sample_1.obj)
다음은 링크 옵션에 대하여 알아보자.
우선 value의 표현은 1011B, 2048D, 0D784FH로 한다.
RAMSIZE(value) : on-chip data RAM의 크기 지정
PRECEDE(seg) : 레지스터와 비트 memory에 있는 segments를 배치
BIT(saddr | seg) : BIT seg.
DATA(saddr | seg) : DATA seg.
IDATA(saddr | seg) : IDATA seg.
STACK(seg) : STACK seg.
XDATA(saddr | seg) : XDATA seg.
CODE(saddr | seg) : CODE segment을 배치
Bx : 뱅크를 설정 x 는 0-3
IX : *.lst 파일에 크로스 레퍼런스를 보여준다.
PR : *.LST 파일 생성
RF : 레지스터의 사용을 나타낸다. (사실은 나두 모름 *^.^* )
이것으로 간단하게 기초사용법을 적어 보았다. 조금 더 안으로 들어가면 라이브러리 만드는 방법 및 어셈과 링크하는 법, 인라인 어셈블 사용법, 포인터 함수로 프로그램 실행하기, 등등은 나중에 소개하기로 하겠다. -2000.09.21-
아르키메데스 컴파일러 --> Keil 컴파일러로 변환하기
1. exit 함수가 존재하지 않기 때문에 이 분은 삭제를 하던지 해야한다.
2. 인터럽트의 구문 형식이 조금(?) 다르다.
아 --> interrupt [0x03] void RTL8019AS_INT0(void)
케 --> void RTL8019AS_INT0() interrupt 0
이렇게 바꾸면 된다. ‘0’ 이라고 표시한 숫자는 아래의 공식으로 구할 수 있다.
Interrupt Address = (Number * 8) + 3
3. 포트를 비트로 출력할 때
아 --> P1.1;
케 --> P1_1; (이것은 헤더화일을 어떻게 했는지에 따라 다르다.)
정말 짜증 난다. 하드웨어 바꿀 때 포팅이라는 것은 많이 들어 봤지만, 이것은 컴파일러 포팅이다. 어셈블리어도 다른 회사걸루 바꾸면 힘들다는 것을 알지만, 하물며 씨 에서도 이 정도 일줄은 몰랐다. 시바~ -.- 좃島!! 하루 빨리 XX통일이 되었으면 좋겠다..
4. 포인터를 사용한 외부 메모리 사용
아 -->
#define inport_io(address) (*(char *)(0x010000+address)) /*read data momory */
#define outport_io(address,value) (*(char *)(0x010000+address)) = value /* write data momory */
#define read_code(address) (((char *)0x020000)[address]) /* read program memory */
#define read_ram(address) (*(char *)(0x010000+address)) /*read data momory */
#define write_ram(address,value) (*(char *)(0x010000+address)) = value /* write data momory */
케 -->
#define XBYTE ((unsigned char *) 0x20000L) /* use @DPTR */
#define PBYTE ((unsigned char *) 0x30000L) /* use @R0 */
#define DBYTE ((unsigned char *) 0x40000L) /* data */
#define CBYTE ((unsigned char *) 0x50000L) /* code */
#define EN_CMD XBYTE [0x300] /* The command register (for all pages) 300번지 액세스*/
#define write_ram(address,value) ((char *)0x20000L+address) = value
#define read_ram(address) ((char *)0x20000L+address)
#define inport_io(address) (*(byte *)0x20000+address))
#define outport_io(address,value) (*(byte *)0x20000+address)) = value
3바이트중에서 첫 번째는 메모리 타입 두 번째는 상위옵셋 세 번째는 하위옵션을 가리킨다. IDATA(1) XDATA(2) PDATA(3) DATA(4) CODE(5) 위의 예제는 전부 XDATA를 액세스한다.
참고: 갑자기 하다가 생각이 난건데, 프랭클린 컴파일러와 케일(카일?) 컴파일러는 거의 똑같다. 옵션지정에서부터 문법 등등.. 하지만 몇가지 틀린 것이 있다. 예를 들면 RAMSIZE는 RS라고 표기해야 링크시에 에러가 나지 않는다. 암튼 이런류의 에러는 커맨드 프람프트상에서 한줄씩 실행해야 쉽게 찾을 수 있다. 안 되는거 X빠지게 하는 것 보다는 머리를 살짝 돌려서 해보는 것도 현명하다. ^^;
* 인라인 어셈블 방법
아르키메데스 같은 경우에는 _opc(0xhh) 라는 명령을 써서 인라인 명령어를 흉내낼 수 있다. 이것은 어셈블리어에서 DB명령으로 기계어 코드를 삽입하는 것과 유사한 특징을 가지고 있다.
케일에서는 inline(롤러블레이드 이름하고 같네요 ^^; -_-;) asm을 위해 아래와 같이 한다. #pragma asm
inc r0 // this is just here to add inline asm to the mix..
#pragma endasm
실제로 해보니까 무슨 src control을 해주어야 한다고 나온다. 아마도 옵션 문제인 것 같다.
* 인터럽트 벡터를 램번지에 만들기... (이거 어디다 쓰는지 모르는 사람은 몰라도 별 상관 없다.) 2가지 방법을 소개하겠다.
1. #pragma INTVECTOR(0x8000) /* Set vector area start to 0x8000 */
2. C51 PROGRAM.C INTVECTOR(0x4000)
* 프로그램의 시작위치 바꾸기
BL51 PROGRAM.OBJ CODE(0x4000)
1. 배열에서의 문제해결
이더넷 패킷을 보내는 실험을 하다가 패킷을 만들려고 배열에다가 62 bytes 의 정보를 저장했는데,
환경은 스몰모드였다. 잘 실행이 안되다가 나중에 code라는 pragra의 성격을 가지고 있는 넘을 사용하니까 잘 동작이 되었네요... 그 예제는 아래에 있습니다.
code byte test_packet[] = {0x00, 0x10, 0x4b, 0x18, 0xd8, 0x30, 0x00, 0x10,\
0x4b, 0x18, 0xd8, 0x30, 0x08, 0x00, 0x45, 0x00,\
0x00, 0x31, 0x05, 0x11, 0x40, 0x00, 0x80, 0x06,\
0x00, 0x00, 0xca, 0x1e, 0x14, 0xaa, 0xca, 0x1e,\
0x14, 0xac, 0x00, 0x50, 0x04, 0x47, 0xb2, 0x0e,\
0x02, 0x66, 0x4c, 0xb8, 0xec, 0x0a, 0x70, 0x12,\
0x44, 0x70, 0x8f, 0x3d, 0x00, 0x00, 0x02, 0x04,\
0x05, 0xb4, 0x01, 0x01, 0x04, 0x02};
2. 실제로 프로그램을 짜다보면 data 형이 몇 바이트를 나타내는지 매우 중요하다. 예를 들면 도스용 int 는 2바이트를 나타내지만, 윈도우에서는 4바이트를 나타낸다. 그래서 sizeof를 이용하여 실제의 값을 알아볼 필요가 있다. 주의할 것은 내장 함수로 되어 있은 printf를 이용하여 값을 찍어보면 엉뚱한 값이 출력된다. printf(" char: %3d byte",sizeof(char)); <--- 잘못된 동작을 하는 명령어 (?_?)
결국 나중에는 한바이트를 출력하는 루틴을 이용하여 출력해보니 정상으로 출력되었다. 임베디드 시스템에서는 보통 내장함수를 쓰는 것 보다는 자신이 새로 만들어 쓰는 것이 유리할 때가 많다. 참고로 아래는 케일 컴파일러에서 몇 바이트를 나타내는지 보여준 예이다.
#ifdef STRING_TEST
printf("The size of some fundamental types is computed.");
printf("\n\r char: "); putb_ser(sizeof(char));
printf("\n\r short: "); putb_ser(sizeof(short));
printf("\n\r int: "); putb_ser(sizeof(int));
printf("\n\r long: "); putb_ser(sizeof(long));
printf("\n\r float: "); putb_ser(sizeof(float));
printf("\n\r double: "); putb_ser(sizeof(double));
printf("\n\r long double: "); putb_ser(sizeof(long double));
printf("\n\r unsigned: "); putb_ser(sizeof(unsigned));
printf("\n\runsigned short: "); putb_ser(sizeof(unsigned short));
printf("\n\r unsigned long: "); putb_ser(sizeof(unsigned long));
get_serial();
#endif
결과: The size of some fundamental types is computed...
char : 01, short : 02, int : 02, long : 04, float : 04, double : 04, long double : 04,
unsigned : 02, unsigned short : 02, unsigned long : 04
- 2000. 11. 08 -
인라인 어셈블러 사용법
#pragma SRC(hello.src) /* 오브젝트 코드 대신에 소스 코드를 생성한다. */
void main (void)
{
#pragma asm /* 어셈블러 사용시에 써야할 프라그마 */
NOP
#pragma endasm
}
소스는 위와 같은데, 맨 처음 할때는 컴파일 옵션에서 소스코드를 생성해야 한다. "SRC" 옵션을 사용하면 가능하다. 이렇게 해야만 위의 소스를 사용할 환경이 완료된 것이다. 주의할 것은 인라인 어셈블을 사용하기 전에 FILE.SRC가 존재해야 하며 그런후에 추가 시켜야 한다. - 2000. 11. 29 -
참고 : 함수 포인터의 같은 예
(*ptr)(); and ptr(); 두 개가 어셈블되면 꼭 같다. 이것은 취향 문제다. 예를 들면 통통하고 키작은 여자를 좋아하는 나처럼... ^^;
참고 : #define문에서 정의할 내용이 한문장으로 끝나지 않을 경우는 문장의 맨 마지막에 역슬래쉬(\)를 붙여서 사용한다. 그러면 매크로 쓰기가 한결 편할 것이다.
ex) ((void (code *) (void)) 0x0000) ();
에러잡기
일반적으로 Keil Compiler에서 에러가 발생한다면 Keil homepage로 가면 쉽게 찾아볼 수 있다. 예를 들면 다음과 같은 에러가 발생했다고 한다면...
FATAL ERROR 213 (I/O ERROR ON WORKFILE)
http://www.keil.com/support 로 가서.. Enter search text에다가 “FATAL ERROR 213” 위의 에러문장을 써준다. 그리고 엔터를 꾸~욱!
그러면 다음과 같이 출력이 된다.
ANSWER
This problem is the result of a TMP environment variable either pointing to an invalid directory or a non-existant directory. Make sure your evironment pathspecs are pointing to a valid TMP directory. This variable may also be defined in the autoexec.bat file. Confirm that the file is correctly specified there as well. Our tools use any of the more common terms for tempory file storage. So check TMP, TEMP, and C51TMP when looking for the problem.
결국은 임시 디렉토리가 없는 것으로 되어 있어서 일어나는 일이다. B.U.T ~!!
내경험1) 나두 위의 메시지를 보구나서 '아 임시 디렉토리가 없어서 그렇구나~!‘ 하고 생각을 했었다. 하지만 내 윈도우 2000에서는 실행이 안되었다. 그래서 TMP도 만들어 보구, TEMP... 등등등... C:\ 에도 만들고 D:\에도 만들어보구.. 하지만 에러는 똑같이 출력 되었다.
그래서 생각한 것이 ‘윈98에서는 되겠지~!’하고 생각했다. 요즘 세상에 구하기 힘든 윈98을 겨우 찾았다. 하지만, 네트웍 상에서 원2000을 보는 권한이 없다.. 잠시 실망을 하려다 FTP://AGI.SARANG.NET으로 접속을 해서 카피를 했다. 이 카피하는 이야기만 해서 이 지면에 다 쓰지도 못한다.(생략) 여튼간에 makefile의 환경변수를 못 잡아서 몽창 한 디렉토리에 카피를 했다.(C51컴파일러, 소스코드도..) ^^; 이것이 인간 승리인가 싶더니.. @.@ 이것도 안 되었다. 결국은 몇시간 허비를 했다. 출발점으로 돌아와서 makefile을 보니까 매크로로 정의한 파일이 보이지 않았다. 한마디로 김새는 에러다. 파일명이 잘못 되었거나 없었던 에러였던 것이다. 식자우환이라고 조금 알게 된 것이 도리어 디버깅을 엉뚱한 방향으로 갔던 것이다. 이 글을 읽는 사람이 나 빼고 있을지는 모르겠지만, 왜 이런 에러코드가 발생했는지 행동하기에 앞서 한번쯤 생각해야 한다는 것이다. -_-;
내경험2) INCLUDE 할때 안 될 때가 있었다. 왜 그랬냐면 파일명이 8.3을 넘었기 때문이었다. DOS용 컴파일러 이기 때문에 길게 이름을 썼다가는 (예를들면 initialize8051.c) 멀쩡한 컴파일러만 바보로 만들게 된다. 속담인가?? x xx 나뭇꾼 연장 탓만 한다.
내경험3) 인클루드가 된 것을 확인한 후에 각각을 모듈로 만들었다. 하지만 에러가 없을리 만무한다. 먼 마지막에 나온 에러가 뭐였냐면.... 다음과 같다.
MULTIPLE PUBLIC DEFINITIONS
SYMBOL: LOADING_READY
MODULE: MAIN.OBJ
원인: 한 파일을 모듈로 컴파일하기 위해서 각 GLOBAL변수를 2번이상 정의했기 때문이다.
main.c --> data byte loading_ready = 0;
monitor.c --> extern data byte loading_ready = 0;
대처: 특정값을 한번만 정의한다.
main.c --> data byte loading_ready = 0;
monitor.c --> extern data byte loading_ready;
내경험3) EROR 141 IN LINE 136 OF TINYTCP.H : syntax error near '['
코드: BYTE data[tcp_MaxData]; /* data to send */
코드를 자세히 보면 data라는 8051의 내부램을 쓰는 예약어가 있음을 알 수 있다.
따라서 data를 다른이름으로 바꾸어야 한다. 나는 datas로 바꾸었다..
워닝(warning)잡기
일반적으로 warning을 무시하기 쉽지만 시스템이 켜져서 프로그램이 방대해지면 갑자기 시스템이 멈추는 에러의 원인이 된다.
내경험1) *** WARNING 15: MULTIPLE CALL TO SEGMENT
SEGMENT: ?PR?ETHERNET_INIT?DRV_8019
CALLER1: ?PR?TIMEOUT_T0_INT?MAIN
CALLER2: ?C_C51STARTUP
위와 같은 메시지를 보았다것이라 면 이것은 흔히 볼수 있는 재진입함수(reentrant function)관한 것이다. 한마디로 말해서 하나의 함수를 2군데에서 불러 사용할 때 일어나는 현상이다.
해결책) 위의 예에서 보면 ETHERNET_INIT()이라는 함수의 끝에다가 reentrant라고 붙여주면된다. 아래는 실제 사용법을 적어 놓았다.
예) void ethernet_init(void) reentrant;
void ethernet_init(void) reentrant
{...}
주의: Impact Of Overlaying On Program Construction
L51을 사용하는 법칙은 어떤 두개의 변수도 local 데이타를 동시에 겹쳐 쓰기 할 수 없다. Re-entrant함수는 한 개의 함수가 두개의 다른 장소로부터 동시에 불려질 수 있도록 확장한 것이다.이런 경우 99%는 겹쳐 쓰기 기능은 완벽하게 동작하지만 기대하지 않은 결과가 생긴다. 적용되는 기초 -->
1. 함수 포인터를 사용하는 간접적으로 불려지는 함수
2. 함수의 jump 테이블로부터 불려진 함수
3. re-entrant함수 ( 부적절한 또는 전혀 선언되지 않은 함수)
이런 선언하에서 linker는 다음과 같은 경고를 한다.
MULTIPLE CALL TO SEGMENT
UNCALLED SEGMENT
RECURSIVE CALL TO SEGMENT 2001.01.17
이해가 안되는 부분...
인라인 어셈블을 쓰려고 하면 c 소스를 어셈블리어로 바꾼후에 다시 어셈블리어를 오브젝트로 만들어야 한다. 그런데 이 과정을 거치면서 전혀 동작이 안 될 때가 있다. 왜 그럴까?
2001.05.01
--> 해결책 : 어셈블리어 소스를 어셈블한후에 링크할 때의 문제이다. 정확한 원인은 암튼 메모리 모델에 따른 lib를 넣어서 링크해야 한다. 예를 들면
ex) bl51 hello.obj, c51l.lib to hello.abs rs(256) co(0000h) xdata(8000h) ix RF(test.REG)
하면 된다. 이거 해결하려고 여기 저기 돌아다니면서 물어 보았다. 결국은 다른 링커들이 어떻게 사용을 했는지 보면서 라이브러리를 포함시킨 것은 알았지만 컴마(,)를 빼먹어서 이것역시 안되는가하고 생각을 했다. 씨바~ 이러다가 언제 프로그램을 짜나~
2001.05.02
아~ 그리고 갑자기 생각이 났는데, 만약에 passing parameters 어쩌구 저쩌구 라는 에러나 warning이 나오면 컴파일 옵션에서 NOREGPARMS를 지워준다. 이것은 무엇이냐! 예를 들어 어떤 함수가 있는데, 파라메터가 x나 많다고 생각을 해보자. 그러면 8051의 레지스터는 한계가 있기 때문에 여기에다 전부 저장을 할 수 없다. 그래서 이 옵션을 지우면 함수에서 사용하는 파라메터를 특정 램의 위치에 저장하여 참조하게 된다. 속도는 느리겠지만, 여기저기 산재해 있는 소스를 포팅하다 보면 이것 때문에 고생을 할 때가 있다. TINYTCP프로그램을 8051에다가 포팅할 때 이 방법을 몰라서 함수를 뜯어 고친적이 있다. 다행히도 이렇게 무식한 방법을 씀으로서, 조금은 덜떨어진 컴파일러에 맞게 소스를 포팅하기가 쉽다는 장점이야 있겠지만...
uCOS-II를 포팅하다가 보면 어셈블리어와 C소스의 연동을 잘 해야한다. 예를 들면
1.
INTU OSIntNesting = 0x55;
function(){
#pragma asm
MOV DPTR, #OSIntNesting
MOVX A,@DPTR
MOV P1, A /* 포트1에는 0x55가 출력됨 */
#pragma endasm
}
2. 라벨에는 콜론(:)을 꼭 써야한다.
3. PUSH A, POP A는 PUSH ACC, POP ACC로 써야한다. 종말 이상타~ 그냥 컴파일러가 알아서 인식하면 어디가 뚤어지나? -,.-
참고(?) : 범용 포트로 되어 있는 P1을 비트단위로 제어할 때 쓰는 방식을 몰라서 헤더파일 reg52.h에 당음과 같이 추가를 했다. 정의가 안 되어 있는걸까? 아니면 내가 모르는 걸까?
sbit P1_7 = P1^7;
.....(생략)
sbit P1_0 = P1^0; /* added by myself */
2001.6 언제인지 모르게...
잡담: 하루는 동아리 동기인 뚱뚱이 종x이가 2층과 9층을 오르내리면서 롬을 구워간적이 있었다. 내용은 대충 이렇다. /EA단자를 GND에 붙였는데도, 새로 구운 외부롬이 동작하지 않는다는 것이었다. 왜 그럴까? -_-? 고민할 시간도 없이 2층과 9층을 오르내리면서 그 문제를 풀었다. 나도 왜 그런지 모른다. (사실은 그런 버그에는 관심이 없다 -.-a ) 암튼 CPU에 들어있는 내용을 지우니까 외부롬에서 정확히 읽혀진다는 것이었다. 그냥 한번 적어봤다. 한밤중에 할 일 없어서리....
2001.08.05 랩미팅을 준비하며..
이것은 롬에뮬에이터(ROM Emulator)를 사용하고 있는 사람한테는 먼 이야기일지도 모른다. 하지만, 다운로딩(8000h 번지에..)하게 만든 보드를 가지고 있는 사람한테는 필수적인 내용이다. 무엇이냐면.. 인터럽트를 사용한다면 거기에 따른 인터럭트 벡터 테이블이 다운로딩번지에 정확히 생성되어야 하는데, 일반적으로 코드를 8000h 번지에 생성시키면 그렇게 되지가 않는다. 나도 지나가다 들은 이야기인데, 그 이유는 8000h 번지에 코드가 덮어씌어져서 interrupt vector table이 없어진다는 내용이었다. 따라서 bl51.exe(링크할 때) 옵션으로 코드영역을 바꾸어 주어야 한다. 다음과 같이 주어야 한다. ?pr?main?test(8100h)
여기서 main은 당연히 main()함수이고 test는 test.c라는 파일을 나타낸다.
2001.08.14일 day before the late dog days
내가 keil 컴파일러를 이용하면서 다른 프로그램을 이식할 경우에 겪었던 가장 드러웠던 것은 indirect call error에 관한 것이다. 다음은 원문을 인용한 것이다. 이 내용은 고수만 읽어보기 바란다. 그냥 단순한 프로그램이나 돌리는 사람은 읽을 필요가 없을 것이다.
C51: PASSING PARAMETERS TO INDIRECTLY CALLED FUNCTIONS
SYMPTOMS
I'm using function pointers and object-oriented programming techniques in my application. Most of the time my program works as expected. But when I try to pass several parameters to functions that are called via pointers, I get the following compiler error message:
Error 212: Indirect call: Parameters do not fit within registers.
The program example below demonstrates this:
void (*CallBack1) (void *, unsigned char);
void (*CallBack2) (void *, void *);
void (*CallBack3) (char, char, char);
void (*CallBack4) (char, char, char, char);
unsigned char c, d, e, f;
char *ptr;
void test (void) {
CallBack1 (ptr, c); // works
CallBack2 (ptr, ptr); // fails - C51 generates an error message
// indirect call: parameters do not fit within
registers */
CallBack3 (c, d, e); // works
CallBack4 (c, d, e, f); // fails - C51 generates an error message
// indirect call: parameters do not fit within
registers */
}
CAUSE
Unlike most 16-bit and 32-bit microcontrollers, the 8051 is not a stack based architecture. When parameters do not fit into the CPU registers, the Keil Cx51 Compiler by default uses direct memory locations for parameter passing. This technique generates very efficient code but limits the parameters that can be passed to indirectly called functions. When parameters passed to a function via a function pointer will not fit into registers, the compiler cannot determine where in memory to place the parameters since the function is not known at call-time.
RESOLUTION
There are two ways to solve your programming problem.
1. Create reentrant functions using the reentrant function attribute. The compiler simulates a stack-based architecture which makes it possible to pass a virtually unlimited number of parameters to indirectly called functions. For example:
void (*CallBack1) (void *, unsigned char);
void (*CallBack2) (void *, void *) reentrant;
void (*CallBack3) (char, char, char);
void (*CallBack4) (char, char, char, char) reentrant;
unsigned char c, d, e, f;
char *ptr;
void test (void) {
CallBack1 (ptr, c); // works
CallBack2 (ptr, ptr); // works, but now the function that gets called
// need to have the reentrant attribute
CallBack3 (c, d, e); // works
CallBack4 (c, d, e, f); // works, but now the function that gets called
// need to have the reentrant attribute
}
2. Limit the number and types of parameters so that they all fit into CPU registers. Do this when you need utmost performance or when program size is critical. The parameter passing method is described in the Cx51 Compiler User's Guide, Chapter 6 under Function Parameters. Refer to this chapter to determine how to change your function parameters to fit into registers. For example:
void (*CallBack1) (void *, unsigned char);
void (*CallBack2) (void xdata *, void xdata *);
void (*CallBack3) (char, char, char);
void (*CallBack4) (char, char, int);
unsigned char c, d, e, f;
char xdata *ptr;
void test (void) {
CallBack1 (ptr, c); // works
CallBack2 (ptr, ptr); // works, but pointers are memory typed now
CallBack3 (c, d, e); // works
CallBack4 (c, d, e | (f<<8)); // works, but two chars are packed into
// one int parameter
}