uCOS-ii V2.51을 8051에 포팅하는 포팅기를 적어볼까 합니다.



기존에 포팅경험이 있으신 분들에게는 별 시덥지 않은 포팅기가 될지도 모르지만,

처음 접하시는 분들은 많은 시행착오를 겪어야 하기 때문에,

조금이나마 그 시행착오를 줄이는데 도움이 됐으면 합니다.



uCOS를 포팅하는 방법은 Jean Labrosse님께서 쓰신 uCOS-ii 책에 자세히 설명되어 있으며,

인터넷상에 돌아다니는 문서도 많기 때문에,

포팅의 공통된 부분은 기존 책과 인터넷 자료를 참고하시기 바라며,

저는 8051에 포팅시 주의할 점 위주로 설명할까 합니다.



/********* AT89S52 에 uCOS-ii V2.51 포팅 준비하기. **********/



AT89S52는 ATmel (www.atmel.com) 에서 나온 8051제품입니다.

포팅의 가장 기본은 포팅하고자 하는 MCU에 대해서 이해하는 일이라고 생각하기에

AT89S52의 Features에서 uCOS-ii를 포팅할때 이해해야 할 부분에 대해서만 간단하게 설명하고 넘어가도록 하겠습니다.



AT89S52는 내부에 8Kbyte의 플래쉬 메모리와 256Byte의 내부램을 가지고 있습니다.

내부 플래쉬 메모리는 ISP기능을 지원하므로 사용자가 쉽게 프로그래밍할수 있게 되어 있습니다.



AT89S52는 최대 24MHz 와 33MHz 까지 지원하는 제품으로 나뉘어 있습니다.

24Mhz까지 지원하는 제품은 제품명뒤에 제품코드로 AT89S52 - 24AC 이런식으로 24MHz 지원 제품이라는 코드가 붙습니다.

33Mhz도 마찬가지로 AT89S52 - 33AC 라는 코드가 붙습니다.

이외에도 다른 코드들이 몇가지 더 있으며, ATmel에서 제공하는 AT89S52 데이타 쉬트를 참고하시기 바랍니다.

( 데이타쉬트는 www.atmel.com 에 있습니다.)



내부 플래쉬 메모리와 내부램 , 클럭 이정도만 알면, MCU에서 포팅에 필요한 기초적인 부분은 알았다고 생각되는데요....^^;;

uCOS 의 코드가 들어갈 플래쉬 메모리, uCOS에서 사용할 램, 그리고 uCOS에서 필요한 Tick을 제공할 클럭.

정말 간단하지 않습니까?? ^^



그럼 AT89S52 만으로 uCOS를 포팅할수 있을까?? 하는 부분에 대해서 생각해 봐야겠죠??



참고로 제가 포팅해본 바로는 uCOS가 컴파일 되었을때, 대략 7Kbyte의 코드 사이즈와 1.2kByte 정도의 내부램을 사용합니다.

물론, 옵티마이즈했을 경우에는 달라지겠지만 말입니다.

코드사이즈야 엇비슷하게 나오니...태스크 코드의 양이 많지만 않다면, AT89S52의 내부 플래쉬 메모리를 사용해도 되겠지만...

램의 경우에는 턱없이 부족하게 되죠...^^



그러므로 좀더 넉넉한 코드 메모리와 데이터 메모리를 위해서 외부에 롬과 램을 확장해서 사용해야 하겠습니다.

외부에 롬과 램을 확장하기에 어려운 환경이라면,

ATmel 에서 나온 AT89C51ED2 같은 경우에는 64Kbyte의 플래쉬 메모리와 2K바이트의 내부 램을 지원하므로 이런

디바이스들을 사용하시면 되겠죠??



이제 uCOS를 어떤 컴파일러를 사용해서 컴파일 하느냐가 문제네요...

uCOS는 ANSI C 된 부분과, 약간의 어셈블리어 소스로 구성되어 있습니다.

ANSI C로 된 부분은 8051용 C컴파일러라면 모두 지원하기에 상관이 없지만,

어셈블리어 부분에서는 컴파일러마다 조금씩 다르므로 이부분도 염두에 두셔야 합니다...



8051컴파일러는 여러 종류가 있고... 그중에서 가장 많이 쓰이는 컴파일러는 Keil이라고 생각되는데요....

개인적으로는 Keil이 많은 사람이 쓰기에 인터넷 상에 자료도 많고,

또 www.keil.com 에서 제공해주는 소스 샘플이나,

Q&A도 상당히 풍부하기에 이용하기에는 가장 좋다고 생각합니다.

또 써본바로는 디버깅환경이 실제 환경과 상당히 정확하고, 코드 옵티마이즈 부분또한

타 컴파일러에 비해서 상당히 좋다고 느꼈습니다.



이제 컴파일러와 MCU에 대한 부분이 다 준비가 되었으므로...

uCOS-ii 포팅 소스를 구해오면 포팅 준비는 끝났다고 봅니다...



포팅 소스는 www.uCOS-ii.com 사에트에서 제공되는 8051용 포팅소스를 받으시면 됩니다.

프리 포트 다운로드에 가셔서 Intel 란으로 들어가시면 keil 용으로 포팅된 포팅소스가 있습니다.

그중에서 uCOS-ii 버젼 V2.52 용으로 된것을 이용하도록 하겠습니다.

제가 가지고 있던건 V2.51 인데... 별차이가 없기에..^^ 그냥 쓰셔도 됩니다.

만약에 다른 버젼의 uCOS-ii 소스를 가지고 계시다면 다른걸 받으셔도 무방합니다.

( 다운받으신 포팅소스에는 포팅화일만 있습니다. uCOS-ii 소스 화일은 Jean Labrosse님께서 쓰신 uCOS-ii 책에서

제공되는 CD에 있는 소스를 사용하시거나, 알아서 구하셔야 합니다...^^)



그럼 이제 포팅에 필요한 준비는 끝이 났습니다.


MPU : AT89S52 (ATEML社)

uCOS-ii 버젼: V2.51

컴파일러 : Keil v7.20



위와 같은 환경에 저번 포팅기에서 이야기했던,

uCOS-ii 포팅소스와 커널 소스를 가지고 포팅을 진행해 보도록하겠습니다.



각자 취향이라고 해야할까요??

취향마다 포팅시에 손대는 부분이 다를테니깐요..

저는 제방식대로 포팅을 진행하도록 하겠습니다.



이미 포팅이 되어있는 소스를 가지고 시작하는 것이기에...

어려운 일은 아니니 천천히 따라해보면 쉽습니다.



그럼 이제 AT89S52의 외부 프로그램 메모리와 외부 데이터 메모리에 포팅을 시작해 보도록 하겟습니다.



우선, 준비된 화일들을 다시한번 확인해 보도록 하겠습니다.



uCOS-ii 포팅소스:

<a href="http://www.ucos-ii.com/contents/products/ucos-ii/downloads/keil%20uCOS_II%20v2.52%20port.zip">http://www.ucos-ii.com/contents/products/ucos-ii/downloads/keil%20uCOS_II%20v2.52%20port.zip</a>



위 화일을 받으신후 압축을 푸시면 여러 화일들이 나옵니다.

그중에서 아래의 화일들만 분리해서 이제 포팅을 진행할 폴더에 복사하시기 바랍니다.

예) c:\keil\engine\ucos\port



INCLUDES.H -> 마스터 인클루드 화일 헤더화일들의 대표입죠..^^

OS_CFG.H -> OS 환경 설정 헤더 화일

OS_CPU.H -> OS CPU의존적인 설정 환경화일

OS_CPU_A.A51 -> OS CPU 의존적인 어셈화일

OS_CPU_C.C -> OS CPU 의존적인 C화일

os_kc51.h -> OS Keil 의존적인 헤더화일

os_kcdef.h -> OS Keil 의존적인 define 설정화일

EX1L.C -> main 함수가 들어있는 화일

uCOS_II.H -> uCOS_ii 정의 화일



받으신 포팅소스는 Keil에서 지원하는 PC에뮬레이터 소스가 같이 첨부되어 있는데...

불필요한 소스이기에 쓰지 않도록 하겠습니다.



uCOS-ii 커널소스:

커널 소스는 커널소스 폴더에 넣으시기 바랍니다.

예)c:\keil\engine\ucos\source



작업폴더는 c:\keil\engine\ucos 이곳이 되며, 이곳에 Keil 프로젝트 화일을 생성하시기 바랍니다.





/*************************************** 작업의 시작 1 ******************************************/



주의1: 포팅시에 에러가 난다고 해서, source 화일에 있는 ucos-ii 커널 소스는 절대 건드리시면 안됩니다. ^^



이제 포팅 소스들을 살펴보도록 하겠습니다.

아래에 보이는 소스는 ucos-ii.com 에서 받으신 소스를 AT89S52에 맞게 수정한 것입니다. 그러니 소스를 살펴보시면서

수정된 부분을 똑같이 수정하시기 바랍니다.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

< INCLUDES.H >



#ifndef __INCLUDES__
#define __INCLUDES__

#include <REGX51.H> //<-- AT89S52를 위한 헤더화일로 수정

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <setjmp.h>



/* These macros are intended to be used to re-declare the uC/OS-II functions to be reentrant */
#define TASK_REENTRANT large reentrant
#define KCREENTRANT large reentrant

#include "os_cpu.h"
#include "os_cfg.h"



/* These Keil keywords are used in uC/OS-II source files. So the next macro definitions are needed */
#define data ucos51_data
#define pdata ucos51_pdata
#define xdata ucos51_xdata

#include "ucos_ii.h"
#include "os_kc51.h" /* MUST NOT REMOVE THIS LINE */


#endif

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Includes.h 화일은 우리가 사용하게될 헤더화일들을 모아놓은 화일입니다.

그러니 나중에 헤더화일들을 일일이 인클루드 시킬 필요없이 이 화일만 추가 시키면 됩니다.

처음에 __INCLUDES__ 를 선언해줌으로써 includes.h 라는 화일이 두번 호출되는걸 막는 부분으로 시작이 됩니다.

AT89S52의 레지스터가 정의된 REGX51.H 화일와 표준 라이브러리 헤더화일들을 인클루드 시켰습니다.



#define TASK_REENTRANT large reentrant
#define KCREENTRANT large reentrant



Keil 컴파일러는 재진입을 기본적으로 지원하지 않기에 재진입함수에는 large reentrant 라는 KEYWORD를 사용하게 됩니다.

large는 메모리 모델을 말하며, 외부 데이터 메모리에 reentrant 스택을 사용함을 말합니다.

재진입시에는 함수에서 선언된 데이터 변수들을 스택에 새로 잡아서 사용하게되며,

동일한 함수의 클론이 생성된다고 생각하면 됩니다.



reentrant가 지정되지 않게 되면, 함수의 재진입시, 진입 이전의 변수및 레지스터를 공유하게 되며, 컴파일시 재진입함수의 경고

메시지도 출력이 됩니다.



#define data ucos51_data
#define pdata ucos51_pdata
#define xdata ucos51_xdata



data, pdata, xdata 는 Keil 컴파일러의 KEYWORD로 사용되고 있지만, uCOS-ii 소스에서 사용되는 이름이기도 합니다.

그렇기 때문에 여기서 data라는 키워드가 사용되게 되면, 내부 데이터 영역을 가르키는게 아니라 ucos51_data라는

이름으로 변경하게 만들어서 uCOS-ii 소스의 변경없이 사용하도록 하였습니다.

(data 및 pdata 는 매개변수로 함수에 넘겨줄대 ucos-ii에서 동일한 이름으로 사용하고 있습니다.)

나중에 사용자가 data , pdata , xdata 라는 키워드를 사용하고 싶을때는 "os_kcdef.h" 헤더화일을 추가해서

data, pdata, xdata를 undef 시켜서 사용하도록 합니다.



다음 포팅기에서는 다른 화일들을 또 살펴보도록 하겠습니다.

화일들을 살펴보면서 조금씩 수정되는 부분이 있으니, 이렇게 조금씩 수정해 나가면서

전체 포팅화일들을 쭉~ 살펴보고, 프로젝트 화일에서 컴파일 옵션만 설정해주면....

포팅과정이 끝나게 됩니다...^^

 

< OS_KC51.H >

OS_KC51.h 헤더화일은 uCOS_ii.h 헤더화일에서 reentrant 한 함수들에 대해서 Keil 문법에 맞게

함수들의 프로토타입을 재정의해 놓았습니다.

이곳에서 재정의된 reentrant 함수들을 uCOS-ii 소스화일에도 똑같이 수정해 주어야 합니다.



....
#if OS_TASK_CREATE_EN > 0
INT8U OSTaskCreate(void (*task)(void *pd) KCREENTRANT, void *pdata, OS_STK *ptos, INT8U prio) KCREENTRANT;
#endif

...



OS_KC51.h의 일부분중 OSTaskCreate 함수가 KCREENTRANT 로 재정의가 되어있습니다.

(KCREENTRANT는 large reentrant 로 정의 되어있죠??)

그렇다면 우리는 OSTaskCreate함수가 정의되어있는 OS_TASK.c 화일에서

OSTaskCreate 함수에 KCREENTRANT를 추가해서 수정해 주어야 합니다.



이렇게 나머지 재정의 함수들에 대해서도 소스화일에서 KCREENTRANT 를 추가해주시기 바랍니다.

이부분에서만 uCOS-ii 소스 화일을 수정하시고 이후에는 절대 수정하시면 안됩니다...^^



< OS_CFG.H >

이 화일은 uCOS 환경설정 화일입니다.


#define OS_MAX_EVENTS 2 /* Max. number of event control blocks in your application ... */
/* ... MUST be > 0 */
#define OS_MAX_FLAGS 2 /* Max. number of Event Flag Groups in your application ... */
/* ... MUST be > 0 */
#define OS_MAX_MEM_PART 4 /* Max. number of memory partitions ... */
/* ... MUST be > 0 */
#define OS_MAX_QS 2 /* Max. number of queue control blocks in your application ... */
/* ... MUST be > 0 */
#define OS_MAX_TASKS 8 /* Max. number of tasks in your application ... */
/* ... MUST be >= 2 */


포팅소스의 OS_CFG.h 화일을 열어보시면 위와 같은 식으로 정의 되어있는 걸보실수 잇습니다

주석이 옆에 잘달려 있어서 어떤 정의를 하고 있는지 쉽게 알수 있습니다...^^



OS 이벤트, 태스크등의 갯수는 몇개로 할것이며, 어떤 함수들을 사용할것이냐 등등...

이부분의 설정은 커널사이즈에 영향을 미치게 되니, 찬찬히 살펴 보시면서 각각 설정해주시기 바라며

여기서는 포팅소스의 아랫부분에 정의된 클록 부분만 수정을 하도록 하겠습니다.



#define OS_TICKS_PER_SEC 50 /* Set the number of ticks in one second */


이부분은 OS_TICK을 1초에 몇번을 실행시키느냐 하는 부분인데...저희는 50으로 설정하도록 하겠습니다.

Tick은 1초에 50번 울리게 되며, 실행시마다 스케쥴링을 하게 됩니다.



< OS_CPU.H >



위 화일은 uCOS의 CPU 의존적인 설정이 들어있습니다.

uCOS에서 사용하게될 데이터 형들에 대한 정의와 크리티컬 영역의 진입 방법 설정하는 부분등이 있습니다.

많은 양의 코드가 들어있지 않기에 쑥 살펴 보시기 바랍니다.





이제 OS_CPU_A.a51 화일과 OS_CPU_C.c 화일만 남았습니다..

이 두화일이 가장 신중히 살펴보아야 할곳입니다.



<OS_CPU_C.C>

=============================================================================================================

OS_CPU_C.C 화일에서는 컴파일러및 MCU에 종속적인 C함수들을 정의하고 있습니다.



OS_STK *OSTaskStkInit (void (*task)(void *pd) KCREENTRANT, void * vd, OS_STK *ptos, INT16U opt) KCREENTRANT



OSTaskStkInit 함수가 있습니다. 이 함수는 OSTaskCreate 함수를 사용해서 태스크를 생성할때 호출되는 함수로서,

인자로 받는 스택에 가상의 초기 스택프레임을 만들어 줍니다.

처음 인자로 받는 void (*task)(void *pd) KCREENTRANT 는 KCREENTRANT로 선언된 함수의 포인터 입니다.

void (*task)(void *pd) 는 함수의 포인터를 가르키며, 인자로 포인터를 받는 함수 포인터 입니다.

(모르신다면, C언어 책을 참고해 보시길...)



그 다음 두번째 인자인 void *vd 는 *task 포인터가 받는 인자값을 받게 됩니다.

OS_STK *ptos 는 스택의 시작포인터를 넘겨 받습니다.

그리고 INT16U opt 는 태스크의 우선순위 값입니다.



그럼 스택의 모양을 어떻게 만드는지 보도록 합시다.

여기서 만들게 되는 스택의 모양은 태스크스위칭시에 스택을 푸쉬팝하는 모양과 같아야합니다. ^^

그러므로 이부분에서 스택의 모양을 잘잡아주어야 합니다.



OS_STK *OSTaskStkInit (void (*task)(void *pd) KCREENTRANT, void * vd, OS_STK *ptos, INT16U opt) KCREENTRANT
{
INT8U * stk;

opt = opt; /* 'opt' is not used, prevent warning */
stk = (INT8U *) ptos; /* Load stack pointer */

stk -= sizeof(void *); /* Save the vd to external stack */
*(void**)stk = vd; /* */

stk -= sizeof(INT16U); /* The value should be loaded to PC */
*(INT16U*)stk = (INT16U) task; /* next time when this task is running */



/* Following is the registers pushed into hardware stack */
*--stk = 'A'; /* ACC */
*--stk = 'B'; /* B */
*--stk = 'H'; /* DPH */
*--stk = 'L'; /* DPL */
*--stk = PSW; /* PSW */
*--stk = 0; /* R0 */

stk -= sizeof(void *); /* Keil C uses R1,R2,R3 to pass the */
*(void**)stk = vd; /* arguments of functions. */

*--stk = 4; /* R4 */
*--stk = 5; /* R5 */
*--stk = 6; /* R6 */
*--stk = 7; /* R7 */

/* Following is the registers pushed into hardware stack manually to support the dallas 390 */
*--stk = 0x80; /* IE, EA is enabled */
/* Next is calculating the hardware stack pointer. */
*--stk = (INT8U) STACK_START-1 /* Initial value when main was called */
+1 /* IE */
+8 /* R0-R7, eight registers was saved */
+5 /* ACC, B, DPH, DPL, PSW, five registers */
+sizeof(INT16U) /* The PC value to be loaded */
;

return ((void *)stk);
}



스택초기화 함수를 위와 같이 AT89S52에 맞게 변경하였습니다. 현재 저희가 받은 포팅소스는 Dallas390에 포팅된 소스라서

기본적으로 8051코어를 사용하지만 추가된 레지스터가 몇개 있고, 또 PC(Program Counter)를 Dallas390은 24바이트사이즈를

사용하는 독특한 구조라서 변경해야 할 부분이 몇군데 있었습니다. 위와 같인 심플한 모양으로 변경하시기 바랍니다.



먼저 스택에 ACC, B, DPH, DPL, PSW, R0 ~ R7, 그리고 IE레지스터의 순으로 넣게 됩니다.

R1,R2,R3 레지스터는 Keil에서는 함수의 아규먼트 부분으로 사용되며, R3, R2, R1을 차례대로 꺼낼때 (void *) 형의 vd 값이 호출되게 됩니다. vd는 Generic 한 포인터 형으로서 Keil에서는 Generic 한 포인터의 매개변수는 R1,R2,R3을 사용하게 됩니다. ^^

(이부분은 Keil C에서 제공하는 C51.pdf에 보면 잘나와있습니다.)



그리고 IE레지스터에서 EA인터럽트 인에이블을 허용한 상태로 넘기게 되는데요..

만약 이부분에서 인터럽트를 인에이블 시키지 않아놓으면, 태스크 스위칭할때 인터럽트가 디스에이블상태로

되기때문에 태스크스위칭한후에는 틱함수가 불리지 않게 되게 됩니다. 인에이블 시켜야하겠죠.



*--stk = (INT8U) STACK_START-1 /* Initial value when main was called */
+1 /* IE */
+8 /* R0-R7, eight registers was saved */
+5 /* ACC, B, DPH, DPL, PSW, five registers */
+sizeof(INT16U) /* The PC value to be loaded */
;



STACK_START 는 내부 데이터 변수로 선언된 스택의 시작번지를 가르키고 있으며, OS_CPU_A.a51 화일에 정의되어

있습니다. 그래서 extern 으로 설정되어있죠.



extern idata unsigned char STACK_START[1];



우리가 초기에 만든 스택 모양의 제일 마지막에는 외부에 정의된 스택의 내용을 내부스택에 넣기위해서 총 push된 스택의 크기가

저장되게 됩니다.



이부분이 가장 중요한 스택프레임 만드는 부분이였고, 다른 부분도 살펴보죠. ^^



extern INT8U xdata * data C_XBP;



앞으로 많이 보게될 C_XBP입니다. 이는 외부에 선언된 스택의 포인터입니다.




void OSStartHighRdy(void) KCREENTRANT
{
OSTaskSwHook();
OSRunning=1;

C_XBP=OSTCBHighRdy->OSTCBStkPtr;

LoadCtx();
}



이부분에서는 OSSTART 함수에서 호출되며, 현재 가장 우선순위가 높은 스택의 포인터를 C_XBP에 저장한 후에

Context Switching 을하게 됩니다.



void C_OSCtxSw(void) KCREENTRANT
{


OSTCBCur->OSTCBStkPtr = C_XBP;

OSTaskSwHook();
OSTCBCur = OSTCBHighRdy;
OSPrioCur = OSPrioHighRdy;

C_XBP = OSTCBCur->OSTCBStkPtr;
LoadCtx();
}



C_OSCtxSw(void) 함수는 C함수에서 호출하는 문맥전환 함수입니다. 이 함수도

문맥전환을 위해서 LoadCtx()함수를 호출하게 됩니다.



void OSIntCtxSw(void) KCREENTRANT
{
EA=0;
SP=SaveSP;

C_XBP=OSTCBCur->OSTCBStkPtr;

#pragma ASM
EXTRN CODE(_?KCOSCtxSw)
MOV A, #LOW _?KCOSCtxSw
PUSH ACC
MOV A, #HIGH _?KCOSCtxSw
PUSH ACC
RETI
#pragma ENDASM
}



OSIntCtxSw(void) 함수는 인터럽트 루틴에서 호출하는 문맥전환 함수입니다.

위 어셈명령어를 간단히 소개하자면, OS_CPU_A.a51에 정의된 _?KCOSCtxSw 함수의

주소를 ACC를 이용해서 push 한다음에 RETI 명령을 수행하게 되면,

KCOSCtxSw로 되돌아가게 됩니다. ^^



void OSTickISR(void) interrupt 1
{
/* Do this first */
OSIntNesting++; /* Increment ISR nesting level directly to speed up processing */
// OSIntEnter(); /* Must be called first at every hardware interrupt entry point */
if(OSIntNesting==1) /* Only at the outerest interrupt we do these. */
{
#pragma ASM
PUSH IE
#pragma ENDASM
EA=0;
SaveSP=SP;
OSTCBCur->OSTCBStkPtr=C_XBP; /* OSTCBCur->OSTCBStkPtr is free now, so it can be used to story the value of SP */
EA=1;
}
OSTimeTick(); /* Must be called during tick isr */
OSIntExit(); /* Must be called finally at every hardware interupt exit point */
if(OSIntNesting==0)
{
EA=0;
C_XBP=OSTCBCur->OSTCBStkPtr;
SP=SaveSP;
#pragma ASM
POP IE
#pragma ENDASM
}
}



OS_CPU_C.c 화일에는 Tick 인터럽트 함수가 선언되어있습니다.

void OSTickISR(void) 함수가 바로 Tick 함수로서 이부분에서 OS Tick 을 증가시켜,

스케쥴링함수를 호출합니다.



void SerialIntr(void) interrupt 4
{
/* Do this first */
OSIntNesting++; /* Increment ISR nesting level directly to speed up processing */
// OSIntEnter(); /* Must be called first at every hardware interrupt entry point */
if(OSIntNesting==1) /* Only at the outerest interrupt we do these. */
{
#pragma ASM
PUSH IE
#pragma ENDASM
EA=0;
SaveSP=SP;
OSTCBCur->OSTCBStkPtr=C_XBP; /* OSTCBCur->OSTCBStkPtr is free now, so it can be used to story the value of SP */
EA=1;
}


/* Next you can do something for yourself. */
/* Finally, before exit from ISR, must do as following. */
OSIntExit(); /* Must be called finally at every hardware interupt exit point */
if(OSIntNesting==0)
{
EA=0;
C_XBP=OSTCBCur->OSTCBStkPtr;
SP=SaveSP;
#pragma ASM
POP IE
#pragma ENDASM
}
}


void SerialIntr(void) 함수는 포팅한 사람이 만들어 놓은 시리얼통신 인터럽트 프로그램으로

사용하셔도 되고 지우셔도 되고 편하신대로 하시면됩니다.

사용자 인터럽트를 정의할때는 OSIntEnter() 함수와 OSIntExit() 함수를 사용하시고, OSIntNexting 변수로

중첩 인터럽트에 대한 정보를 처리하시면 됩니다.

참고하자면, 위에서 설명한 Tick 함수나 시리얼 인터럽트 서비스루틴에서는 OSIntEnter 함수의 사용대신에

직접 OSIntNesting 변수를 증가시켜주었죠?? 좀더 속도향상을 위해서...



OS_CPU_C.c 화일에는 이외에 Hook함수가 몇개 정의 되어있는데,

빈껍데기에 불과하고, 사용자가 필요할때 정의해서 쓰시면됩니다.



위에서 제가 Dallas390으로 포팅된 소스를 전부 변경했습니다. 위에서 설명하지 않은 함수들은

그냥 변경없이 쓰시면됩니다.

^^ 별로 크게 바꾸거나 변경한 부분은 없기 때문에 쭉 살펴보시기 바랍니다.

그럼 이상 OS_CPU_C.c 화일에 대한 설명은 마치고

다음에는 OS_CPU_A.a51 함수도 좀 손을 보도록해서 포팅기의 마지막으로 가도록 하겠습니다.

 

<OS_CPU_A.a51>

=================================================================================================================


$NOMOD51


OS_CPU_A.a51 소스의 제일 위에는 $NOMODE51 이라는 지시어가 적혀있습니다.

이를 주석처리를 하거나 지워주시기 바랍니다.

Dallas390은 어셈블리어를 컴파일할때 AX51을 사용하였고, AX51은 Pre-Define 된 8051 레지스터들을 사용하지 않고,

자체적으로 사용하기에 위 옵셥을 줘야했지만, AT89S52는 A51 컴파일러를 사용할것이기에 위 옵션을 생략하도록 하겠습니다.



PUBLIC ?C_XBP, C_XBP

EXTRN CODE(_?C_OSCtxSw)

PUBLIC _?LoadCtx, STACK_START, _?OSCtxSw, _?KCOSCtxSw



LoadXBP MACRO
MOV DPH, C_XBP
MOV DPL, C_XBP+1
ENDM

SaveXBP MACRO
PUSH IE
CLR EA
MOV C_XBP, DPH
MOV C_XBP+1, DPL
POP IE
ENDM

LoadREG MACRO REG
MOVX A, @DPTR
MOV REG, A
ENDM

SaveREG MACRO REG
MOV A, REG
MOVX @DPTR, A
ENDM

; The PUSHA now emulates the pushing sequence what Keil C does.
PUSHR MACRO
IRP REG, <ACC, B, DPH, DPL, PSW, 0, 1, 2, 3, 4, 5, 6, 7>
PUSH REG
ENDM
ENDM

POPR MACRO
IRP REG, <7, 6, 5, 4, 3, 2, 1, 0, PSW, DPL, DPH, B, ACC>
POP REG
ENDM
ENDM



위 소스들은 OS_CPU_A.a51에서 사용하게될 메크로 들입니다.

LoadXBP 메크로는 외부스택포인터(C_XBP)의 포인터를 DPH, DPL에 로드하는 메크로입니다.

외부에 우리가 잡아놓은 테스크스택의 위치를 가져와서 내부스택으로 Context 를 옮기는 경우에 사용됩니다.

SaveXBP는 LoadXBP의 반대이겠죠?? ^^

LoadREG는 REG에 매개변수로 받은 레지스터에 DPTR에서 로드되는 값을 넣게 됩니다.

SaveREG는 역시 그 반대죠...



PUSHR 과, POPR 메크로는 Context Swithing 할때, 스택에 푸쉬하고 팝하는 이중메크로입니다. ^^

저 순서는 우리가 OS_STK *OSTaskStkInit 함수에서 잡아주었던 스택의 모양과 동일해야겠죠??



; Declare the external stack pointer by ourself, so that we can use it freely.
; you know, in the name of '?C_XBP' it can not be used in c modules but in the
; name of 'C_XBP' it can do.

DT?C_XBP SEGMENT DATA
RSEG DT?C_XBP
?C_XBP: ; These two labels point to the same address
C_XBP: ;
DS 2

; Declare a label 'Stack' in the hardware stack segment so that we know where it begins.
?STACK SEGMENT IDATA
RSEG ?STACK
STACK_START:
DS 1



C_XBP는 외부 데이터에 위치한 태스크스택의 주소값을 저장하는 공간으로 사용되고,

STACT_START 는 내부 스택포인터의 위치를 갖게 됩니다.

좀더 부연 설명을 하자면,

?C_XBP를 위해서 DT_?C_XBP라는 재배치가능한 세그먼트를 생성한후에

?C_XBP의 값을 스타트업 코드에서 넘겨받게 됩니다. (스타트업 코드 참조...)

?C_XBP나 C_XBP나 동일한 값이며, ?C_XBP는 C함수에서는 호출이 불가능하므로, C_XBP를 C에서 사용하게됩니다.

(위에 주석이 잘되어있죠?? ^^_

물론 STACK_START도 같은 식이죠....





; Load context from the external stack pointed by C_XBP
PR?LoadCtx SEGMENT CODE
RSEG PR?LoadCtx
_?LoadCtx:
LoadXBP ; Load the C_XBP to DPTR

LoadREG SP ; Load the hardware stack pointer
INC DPTR ;

MOV R0, SP ; Now we pop the hardware stack
LC_1: ; from the external one.
LoadREG @R0 ; Did not use the PUSH ACC instruction for if we want to
INC DPTR ; do so, we have to DEC DPTR, which costs much.
DEC R0 ;
CJNE R0, #STACK_START-1, LC_1 ;

SaveXBP ; after the hardware stack has been popped,
; the external stack pointer should be adjusted

RestoreCtx:
WANTFASTER EQU 1
$IF WANTFASTER
POP PSW ; A little bit dangerous but it works. C is PSW.7 and EA is IE.7
MOV EA, C ; They are at the same bit location of a byte.
$ELSE
POP ACC ; Safe way to do the same thing.
RLC A ;
MOV EA, C ;
$ENDIF
; Now that the context has been loaded into hardware
POPR ; stack, what we need do is just popping them upto registers.
;

RET ; Now everything is ready, a RET will bring the task to run.

; Task level context switch entry point, which is intended to be called by task gracefully.
_?OSCtxSw:
PUSHR ; Save current context first into hardware stack
PUSH IE

_?KCOSCtxSw: ; Now begin pushing hardware stack to external one
LoadXBP ; Load the external stack pointer first to prepare storing
; data into it.

MOV A, SP ; Calculate how much memory in external stack needed
CLR C ; so that we can adjust the external stack pointer
SUBB A, #STACK_START-1 ; Calculated the length of hardware stack

MOV R0, A ; Save the length of hardware stack to R0, which is used as a counter on saving hardware stack.

NC A ; Add the space for storing SP

CLR C
XCH A, DPL ; Now ACC contains the right amount of external stack memory should be used.
SUBB A, DPL ; Adjust the external pointer.stored in DPTR to make to point to the new stack top

; from where we will store hardware stack.
JNC SC_1
DEC DPH
SC_1:
MOV DPL,A ; Now DPTR contains the external stack pointer after pushing context into external stack.

SaveXBP ; Save to external stack pointer.
; Keeps the DPTR containing the external stack pointer still.
SaveREG SP ; Save hardware stack pointer in the top of external stack

SC_2:
INC DPTR ;
POP ACC ; Pop the data from hareware stack
MOVX @DPTR, A ; and save into external one.
DJNZ R0, SC_2 ; Remember, R0 contains the hardware stack's length.

LJMP _?C_OSCtxSw ;




위 소스가 수정된 소스입니다...^^

달라진 부분은 별루 없구요... AX51문법으로 된 #BYTE0 (STACK_START-1) 이런문법을 바꾼거 밖엔 없네요...



그외에 위에 메크로 조금 수정한거...^^

문맥의 저장, 로드, 스위칭에 관련된 부분인데, 상당히 짧기에 별설명없이 쭉 훍어보시면 이해가가시리라 믿습니다.

대략 설명하자면,

_?LoadCtx: 함수같은 경우는 외부스택에 저장된 문맥들을 내부스택으로 옮겨와서,

POPR 메크로를 사용해서 태스크스위칭을 하게되는 부분입니다. ^^

_?KCOSCtxSw 같은 경우는 그 반대이구요... ^^

쭉 명령어들을 따라가시면서 분석해보세요...어렵지 않습니다...



그럼 이걸로 OS_CPU_A.a51 화일도 끝이 났네요...



그럼 이제 다음 포팅기에서는 이제까지 살펴본 화일들을 이용해서, 실제로 포팅을 해보는 일만 남았네요.



정말 짧게 쓰고자했던 포팅기였는데 생각보다 넘 길어져서...당황스럽네요...^^;



그럼 다음에는 스타트업코드를 살짝쿵 살펴보고 마무리를 짓죠...

이제 해야할 일은, 지금까지 AT89S52에 맞게 수정했던 화일들을 가지고,

스타트업코드와 함께 컴파일 하는 일만 남았군요....^^



스타트업코드는 Keil 컴파일러에서 기본적으로 제공해주는 코드를 사용하도록 하겠습니다.



Keil 컴파일러가 설치되어있는 아래 폴더에서 STARTUP.A51 화일을 우리가 작업하는 폴더로 복사하시기 바랍니다.

C:\Keil\C51\LIB\STARTUP.A51



이제 STARTUP.A51 에서 우리가 살펴보아야 할부분만 보도록 하죠~



<STARTUP.a51>

=================================================================================================================

; ; the absolute start-address of IDATA memory is always 0
IDATALEN EQU 80H ; the length of IDATA memory in bytes.

XDATASTART EQU 8000H ; the absolute start-address of XDATA memory
XDATALEN EQU 8000H ; 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.


스타트업 코드를 보시면 위와 같이 데이터 영역의 크기와 시작 번지를 지정하는 부분이 있습니다.

IDATALEN은 내부 데이터의 크기를 나타내고, 시작번지는 언제나 0번지라고 친절하게 주석이 달려있죠??

XDATASTART 외부데이터의 시작번지는 8000번지로 잡겠습니다.(8000번지부터 FFFF번지까지 램영역으로 잡자.)

XDATALEN 외부데이터의 크기는 32Kbyte인 8000h로 설정하겠습니다.

PDATA 영역은 우리는 사용하지 않기때문에 0으로 설정해둡니다.



; 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 1 ; 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.



이부분은 이제 c에서 사용할 reentrant 펑션을 위한 스택공간을 설정하는 부분입니다.

역시 주석에 따라서 설정을 위와같이 해주시면 됩니다.

XBPSTACK 의 값을 1로 했을때, 사용하겠다는 의미이고, XBPSTACK은 Large 메모리 모드에서

사용하는 외부스택입니다.



그 위에 쭉 스타트업 코드를 살펴보시면, 메모리를 초기화 하는 부분이 있고,

마지막에 스택포인터를 설정해주고 C함수의 main으로 점프를하게됩니다.


IF XDATALEN <> 0
MOV DPTR,#XDATASTART
MOV R7,#LOW (XDATALEN)
IF (LOW (XDATALEN)) <> 0
MOV R6,#(HIGH (XDATALEN)) +1
ELSE
MOV R6,#HIGH (XDATALEN)
ENDIF
CLR A
XDATALOOP: MOVX @DPTR,A
INC DPTR
DJNZ R7,XDATALOOP
DJNZ R6,XDATALOOP
ENDIF

위 코드가 외부 데이터를 초기화 하는 부분이죠...^^



IF XBPSTACK <> 0
EXTRN DATA (?C_XBP)

MOV ?C_XBP,#HIGH XBPSTACKTOP
MOV ?C_XBP+1,#LOW XBPSTACKTOP
ENDIF



이코드는 EXTERN 으로 선언된 ?C_XBP에 위에서 설정해주었던, XBPSTACKTOP의 값을

넣어주는 부분입니다.

?C_XBP는 우리가 OS_CPU_A.a51 화일에서 보았었죠?? ^^

이곳에서 그 값을 넣어주게 됩니다.



MOV SP,#?STACK-1

그리고 마지막 부분에는 이런식으로 내부데이터메모리에서 스택스그먼트의 시작주소를

스택에 집어넣고 C로 점프하게 됩니다.

8051에서 스택의 위로 자라게 되죠... 그러므로 ?STACK 세그먼트가 자리잡게 되는 위치는

내부데이터의 마지막 부분에 자리잡게 됩니다.

============================================================================================

이제 스타트업 코드도 살펴보았고 컴파일을 해보도록 하겠습니다.





그럼 Keil 프로젝트 화일을 만들어서 아래와 같은 화일들을 등록하시기 바랍니다.

=================================

uCOS_ii.C

OS_CPU_C.c

OS_CPU_A.a51

main.c

task.c

STARTUP.a51

=================================

uCOS_ii.C 화일에는 ucosii 소스화일들이 인클루드되어 있어서, uCOS_ii.c 화일을 컴파일하게 되면,

uCOSii 소스화일들이 전부 컴파일이 됩니다.

그리구 우리가 포팅한 OS_CPU_C 와 OS_CPU_A 화일도 등록을 해주시고요...

그리고 제가 간단하게 만들어놓은 task.c 화일도 등록을 해놓겠습니다.

task.c 화일은 간단하게 태스크들을 정의해 놓았습니다.



이제 프로젝트 옵션에 가셔서 프로젝트 옵션을 설정해야합니다.

Device : AT89S52 (LX51, AX51 은 사용하지 않습니다.)

Target : 메모리 모델과 코드 모델은 LARGE 입니다.

offchip 코드 메모리는 0x0000 ~ 0x7ffff

offchip 데이터 메모리는 0x8000 ~ 0xffff

C51 : 컴파일 레벨은 정당히 설정하세요... ^^

A51 : SFR names는 Define 시킵니다.

BL51 : 타겟 다이알로그에 있는 세팅을 사용합니다.



이제 컴파일 하신후에 디버깅 모드에서

멀티태스킹이 되시는 모습을 보시기 바랍니다. ^^

Keil 컴파일러에서 제공하는 시뮬레이터는 실제와 상당히 정확해서...

시뮬레이터만 이용해도 디버깅하기에 충분합니다. 만약 생각되로 잘 움직이지 않는다면,

디버깅 모드에서 Step in 으로 찬찬히 살펴보시면 어디가 잘못된지 금방 보일테니...

끈기를 가지고 포팅해보시기 바랍니다.



이상으로 포팅기를 마치고, 포팅된 소스들을 전부 모아서 압축화일로 등록을 해놓겠습니다.



가끔씩 생각날때, 짬날때 쪼금씩 적다가 보니...

중간 중간 빼먹은게 있지 않나 생각도 되고, 잘못된 부분도 있을거 같네요...^^



잘못된 부분이 있거나 의문되시는 부분은 직접 리플이나, 메일로 연락주시면, 바로 수정하거나

답변을 드리도록 하겠습니다.



그럼 즐거운 포팅되세요~



 


 

 

Posted by 김용환 '김용환'

댓글을 달아 주세요