23
Author: 효준 Source: HiTEL Digital Sig. Date: 2004.9.29 (1) 강좌 소개 아시겠지만 uC/OS-II 소스가 공개되어 있는 유명한 RTOS 입니다 . 이번 강좌에서는 uC/OS-II EP7209 보드에 포팅하는 방법에 대한 내용입니다 . 강좌를 이해하시기 위해서는 uC/OS-II 대한 기본 지식과 , EP7209 대한 지식을 필요로 합니다 . 제가 EP7209 택한 이유는 , 얼마전에 보드를 구입했기 때문인데요 , CPU ARM720 Core CPU 입니다 . 따라서 ... 강좌 내용을 이해하신다면 다른 ARM7 CPU 포팅할 때에도 적용하실 있을 것이라 생각합니다. 해당 보드는 현재 www.nanowit.com에서 10만원 정도에 판매되고 있습니다. 저는 회사와는 상관이 없는 사람이므로 보드에 대한 문의는 해당 사이트에서 하시길 바랍니다. 강좌는 그리 길지 않을 것이라 생각합니다. 제목 그대로 '포팅'만을 다룰 것이기 때문입니다. 차후에 기회가 닿는다면 uC/OS-II 전반에 관한 강좌도 했으면 합니다만, 현재로선 그저 생각일 따름입니 . 다음은 강좌 계획입니다 . 1. 강좌소개 2. 포팅을 위한 환경 3. Stack 모양 만들기 4. OSTaskStkInit 함수 5. IRQ 핸들러와 OSTickISR 함수 6. OSStartHighRdy 함수 7. OSCtxSw 함수 8. OSIntCtxSw함수 9. 마무리 계획대로 될런지는 모르겠지만 ... 아무쪼록 많은 성원 부탁드립니다 . Additional Resources: μC/OS-II 포팅강좌 μC/OS-II EP7209(ARM7) 포팅 Page 1 of 23 Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // μ C/OS-II 포팅강좌... 2004-09-30 http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

µC/OS-II EP7209(ARM7) 포팅 - altera.co.kr 2004-09-30. 포팅에 대한 강좌를 읽으시는 분이라면,

Embed Size (px)

Citation preview

Author: 김 효준 Source: HiTEL Digital Sig. Date: 2004.9.29

(1) 강좌 소개

잘 아시겠지만 uC/OS-II 는 소스가 공개되어 있는 유명한 RTOS 입니다. 이번 강좌에서는 uC/OS-II를 EP7209 보드에 포팅하는 방법에 대한 내용입니다. 강좌를 이해하시기 위해서는 uC/OS-II에 대한 기본 지식과, EP7209에 대한 지식을 필요로 합니다.

제가 EP7209를 택한 이유는, 얼마전에 이 보드를 구입했기 때문인데요, 이 CPU는 ARM720 Core의 CPU입니다. 따라서... 강좌 내용을 잘 이해하신다면 다른 ARM7 CPU에 포팅할 때에도 적용하실 수 있을 것이라 생각합니다.

해당 보드는 현재 www.nanowit.com에서 약 10만원 정도에 판매되고 있습니다. 저는 그 회사와는 별 상관이 없는 사람이므로 보드에 대한 문의는 해당 사이트에서 하시길 바랍니다.

강좌는 그리 길지 않을 것이라 생각합니다. 제목 그대로 '포팅'만을 다룰 것이기 때문입니다. 차후에 기회가 닿는다면 uC/OS-II 전반에 관한 강좌도 했으면 합니다만, 현재로선 그저 생각일 따름입니

다.

다음은 강좌 계획입니다.

1. 강좌소개 2. 포팅을 위한 환경 3. Stack 모양 만들기 4. OSTaskStkInit 함수 5. IRQ 핸들러와 OSTickISR 함수 6. OSStartHighRdy 함수 7. OSCtxSw함수 8. OSIntCtxSw함수 9. 마무리

계획대로 잘 될런지는 모르겠지만... 아무쪼록 많은 성원 부탁드립니다.

Additional Resources: µC/OS-II 포팅강좌

µC/OS-II EP7209(ARM7) 포팅

Page 1 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

(2) 포팅을 위한 환경

포팅을 위해 제가 사용한 환경을 위주로 설명하려고 합니다. 역시 EP7209보드를 기준으로 설명하겠습니다만... 꼭 해당 보드가 있어야 한다는 의미는 아닙니다.

사실 강좌의 핵심 부분은 ARM7이나 StrongARM계열에서의 uC/OS Context Switcher 구현으로, 해당 부분을 잘 이해 하신다면, 다른 CPU에도 충분히 테스트해 보실 수 있으리라 믿습니다.

참... 제가 EP7209에 uC/OS-II를 포팅을 마쳐서 www.nanowit.com에 올려 두었습니다. 강좌가 끝날 무렵에 디동 자료실에도 올리겠습니다.

자... 그럼 본격적으로 uC/OS-II 포팅을 위한 환경을 알아보죠.

1. 컴파일러

EP7209 CPU는 정확하게는 ARM720T코어를 사용합니다. 720은 일반 ARM7에 MMU까지 포함한 코어입니다. 동작 방식은 StrongARM의 MMU와도 같습니다. 저는 pSOS 2.5에 포함되어 있던 ARM Software Development 2.51을 사용했는데요... 해당 Tool은 공개용 툴은 아니구요, ARM 사이트에서 판매를 하고 있습니다. 1년 쯤 전에 해당 사이트에 Evaluation CD를 신청

해서 받은 기억이 있습니다. 지금도 될려나 모르겠네요...

혹은 Linux에 크로스 컴파일러를 설치해서 사용하기도 하는데요, 제가 Linux를 잘 몰라서... 그냥 SDT를 사용했습니다.

아무튼... 당연히 ARM7 C 및 ASM 컴파일러가 있어야 겠지요.

2. Board

계속 언급했듯이 NANOWIT의 EP7209 보드를 사용했습니다. 해당 보드의 특징으로는, 1Mega Byte 의 Flash 메모리와 약 37.5K 의 내부 SRAM을 사용할 수 있습니다. 사용할 수 있는 RAM이 적다는 것이 이 보드의 단점 중의 하나입니다. 하지만 uC/OS-II를 올리기 위해서라면, 그 정도의 메모리면 충분합니다. 참 그리고 Serial 포트를 사용한 Flash Write 기능을 제공하므

로, 별도의 장비가 없이 개발환경을 구축할 수 있다는 장점이 있습니다.

uC/OS를 올리기 위해서 기본적으로 필요한 기능은 Serial과 Timer정도면 됩니다.

3. Kernel Source

필수적으로 uC/OS-II 커널 소스가 있어야 합니다. 해당 소스는 labrosse의 책에 포함되어 있는 소스로, 책 저자의 재산이므로 책과 함께 구하셔야 합니다.

해당 소스에는 CPU에 독립적인 Kernel 소스와 8086을 위한 포팅 코드 및 예제들이 포함되어 있습니다. 이밖에 www.ucos-ii.com에 가시면 다른 CPU에 대한 포팅 소스들을 다운받을 수 있습니다. 해당 사이트에는 ARM7계열의 다른 CPU에 대한 포팅 코드들도 있습니다만, 제 경우엔 공부도 할 겸해서, 관련 소스를 참조하지 않고 포팅을 했습니다. 따라서 제가 사용한 방법외에

도 다른 방법이 있을 수도 있습니다. 관심 있으신 분들은 ARM7을 사용한 다른 코드도 비교해 보시길 바랍니다.

4. MicroC/OS-II 책 (labrosse)

Page 2 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

포팅에 대한 강좌를 읽으시는 분이라면, 당연히 uC/OS-II에 대한 다른 내용들을 이미 알고 있으리라 생각합니다. 만약 그렇지 않다면, 먼저 위에 언급한 책을 구해서 읽어 보시길 바랍니다. uC/OS-II에 관해서는 처음부터 끝까지를 모두 담고 있는 책입니다. 물론 포팅에 관한 내용도 책의 8장과 9장에 걸쳐 자세히 설명하고 있습니다.

제 강좌를 이해하시기 위해서는 우선 책의 3장에서 7장까지의 내용을 어느정도 이해하고 있어야 합니다. 그리고 8장과 9장을 한번쯤은 읽어 보시는게 강좌의 이해를 도울것이라 믿습니다.

제가 포팅을 하기 위해서 참조한 모든 부분은 해당 책의 내용이었습니다. 즉, 누구든 책을 통해서 포팅을 위한 모든 정보를 얻을 수 있을 것입니다. 물론 특정 CPU에 대한 부분은 스스로 추가

하셔야 하는 부분이고, 여기서는 바로 ARM7을 대상으로 했던 그런 저의 작업들을 설명하려 합니다.

5. ARM7 Data sheet와 EP7209 Data sheet

전에 ARM7 강좌를 연재할 때 얘기한 적이 있었지만, ARM7은 CPU의 핵심 코어에 대한 부분이고, 특정 CPU를 사용하기 위해서는 해당 칩의 Data Sheet를 따로 봐야 한다고 했었는데요... 그런 이유로 EP7209 Data Sheet를 참조해야 합니다. 타이머 및 IRQ, Serial 등의 장치들을 사용하기 위한 정보를 이 문서로 부터 얻을 수 있습니다. 혹시 ARM7에 아직 익숙하지 않다면, 해당 Core부터 공부하셔야 합니다. uC/OS 포팅의 핵심 부분은 어셈블리로 이루어져 있기 때문입니다.

이상 다섯가지 정도를 들었습니다. 오늘 하고자 했던 얘기는 다 한 셈인데요... 몇 가지 사족을 붙이고 마치려 합니다.

OS를 포팅하는 작업은 어떻게 생각하면 무척 단순한 작업일 수도 있습니다. OS자체를 설계하는 것이 아니므로 커널에 대해서 많은 것을 알지 못해도 상관이 없을 수도 있구요... 하지만 누군가는 꼭 해야 하는 일이고, 아무나 쉽게 할 수 있는일도 아니라는 것이 중요합니다.

점차 사용자 요구 수준이 높아감에 따라 OS를 채택하는 것이 추세가 되어가고 있는 요즘, OS 포팅 자체도 하나의 큰 사업일 수 있습니다.

특정 보드에 WinCE나 Embedded Linux, pSOS, VxWorks 등을 올려주는 전문 업체도 있다고 하더군요.

어떻든... uC/OS-II는 그렇게 뛰어난 OS는 아니지만, 소스를 볼 수 있다는 큰 장점을 가지고 있습니다. Linux도 소스가 공개되기는 하지만, 사실 너무 복잡해서 공부의 목적으로는 적당하지 않은 것 같습니다.

uC/OS를 공부한다는 것은, 물론 그것 자체로서 할 수 있는 일은 많지 않겠지만 아주 좋은 경험을 하는 것이라 생각합니다. 포팅도 마찬가지구요.

그럼 오늘 강좌는 이만 마치겠습니다.

(3) Stack 모양 만들기

제목이 좀 이상하다고 생각 하셨을런지 모르겠습니다. 하지만 이 부분이 uC/OS포팅에 있어 가장 기초가 되는 부분입니다. RTOS의 가장 중요한 특징으로 Multi-Tasking 환경을 지원하는 기능을 들 수 있습니다. 흔히 Time Sharing 방식이라고도 말하는데요, 좀 더 자세히 말하자면, CPU를 시분할 하여 여러개의 프로그램(혹은 타스크)을 동시에 돌리는 방법입니다. OS 수업시간에 귀에 못이 박히도록 들었던 얘기가 아닐까 싶네요.

그러면... 프로그램이 진행되던 중간에, 갑자기 다른 프로그램을 진행 시킬 수 있는 환경으로 바뀔 수 가 있는 걸까요? 이 과정을 Context Switching이라는 용어로 표현하는데요... 이 과정을 이해하

는게 중요합니다. 이 과정을 이해하기 위해 우리가 익히 알고 있는 인터럽트의 처리과정을 생각 해보도록 하죠. 인터럽트는 임의의 순간에 발생할 수 있죠. 인터럽트가 발생하면 CPU는 특정 번지로

Page 3 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

점프를 하게 됩니다. 해당 번지에는 인터럽트 핸들러 코드가 들어 있어야 하구요... 인트럽트 핸들러에서 가장 먼저 하는 일이 뭐였냐면, 인터럽트 핸들러 내부에서 사용하게될 레지스터들을 어딘가에 보관해 두는 일입니다. 그리고 핸들러 처리가 끝나면 저장해 두었던 레지스터들의 내용을 꺼내어 원상 복구를 시키죠. 그리고 인터럽트 이전의 위치로 돌아가게 됩니다.

이 과정을 살피면, 짧은 순간이나마 일종의 멀티 타스킹 비슷한 일을 했다고도 볼 수 있을 것 같네요. 8086 CPU를 기준으로 인터럽트에의 동작을 잠깐 정리해 보도록 하죠.

1. 인터럽트 발생 2. 복귀번지를 스택에 저장하고 인터럽트 핸들러로 분기 3. 핸들러 내부에서 사용하게될 레지스터들을 스택에 저장 4. 실제 인터럽트 처리(레지스터들을 사용- 값이 변함) 5. 저장되었던 레지스터들을 스택에서 복구 6. 스택에 저장된 인터럽트가 걸리기 전의 복귀번지로 복귀

정확하지는 않을 수도 있겠지만, 대충 위와 같을 것입니다. 위의 과정을 이해 하시는게 중요합니다. 그리고 이 과정에서 스택이 하는 역할을 되세겨 보십시요.

자, 그러면 다시 Multi-Tasking 문제로 돌아와 보겠습니다. 멀티 타스킹을 위해서는 어떤 순간에 CPU의 동작 상태를 완전히 저장할 수 있어야 합니다. 이게 무슨 얘기냐면... 예를 들어 칼로 사과를 깎는 경우를 생각해 보죠. 칼 하나로 사과 10개를 깎는 다고 할 때, 첫번째 방법은 하나씩 깎는 경우입니다. 칼을 CPU로 보고 사과를 프로그램(타스크)으로 생각하면 사과 하나를 끝까지 깎는 것이므

로 싱글 Tasking 에 해당하겠군요. 멀티 타스킹은 사과를 1초씩 번갈아 깎는 것입니다. 1번 사과를 1초동안 깎다가... 2번 사과를 다시 깎고, 다시 3번, 4번.. 이런식으로 말이죠. 문제는 10초가 지나

서 다시 1번 사과를 깎아야 할 시점이 왔을때 입니다. 칼로 사과를 어디까지 깎았는지 기억해 두지 않는다면...(물론 사람은 눈으로 보고 확인할 수 있지만... CPU는 사람만큼 영리하지 않으니까요) 처음부터 다시 깎거나, 뭐 그래야 겠죠. 즉, 어떤 작업을 하다가 다른 작업으로 전환하기 위해서는 작업 중이던 내용을 그대로 어딘가에 보관할 필요가 있고, 나중에 다시 그대로 가져올 수 있다면 다시 작업을 진행하는데 문제가 없겠죠.

위의 인터럽트 처리 과정에서 3번에 해당하는 것이 바로 작업중이던 내용을 스택에 저장하는 것입니다. 즉, 작업중인 내용들은 CPU의 레지스터에 해당하고, 저장되는 공간은 스택이 되는 것이죠.

Multi-Tasking 과정에서 스택은 사실 보다 큰 의미를 지닙니다. 무슨 얘기냐면, 스택은 사용중이던 레지스터를 보관하는 공간의 의미를 지님과 동시에, 보관되어야 할 중요한 작업 내용 자체의 의미

도 지닙니다. 이건 또 무슨 얘길까요?

자... 그럼 앞서의 예를 조금 확장해서 10개의 사과와 10개의 쟁반을 상상해 보도록 하죠. 사과를 깎아서 쟁반에 올려 놓기도 하고, 사과 껍데기를 쟁반에 버리기도 하고... 암튼 쟁반 역시 각 사과마다 따로 따로 있는 것이 편할 것입니다. 손에 칼을 들고 1번 쟁반을 가져다가 쟁반위에 놓여있는 사과를 들어서 깎고, 또 껍데기는 쟁반위에 버리고... 반쯤 깎다가 이쁘게 한조각 잘라서 쟁반에 올려놓기

도 하고... 여기선 쟁반이 바로 스택에 해당합니다. CPU는 동작을 하며 계속 스택을 사용하게 됩니다. C의 내부 변수를 위한 공간으로도 사용하고, 어떤 루틴을 호출했을 경우 복귀번지를 넣는 목적으

로도 사용합니다.

Single-Tasking 의 경우는 바로 이 쟁반이 하나이고 가끔 인터럽트가 걸려, 옆에서 배좀 깎아달라고 하면 사과를 쟁반위에 올려 놓고 배를 깎아 준 다음 다시 쟁반 위에서 사과를 가져다가 깎게 되겠

죠.

Multi-Tasking과정에서... 위에서 언급했듯이 여러개의 스택을 사용한다면 어떨까요? 즉, 각 Task마다 자신의 Stack공간을 두고 어떤 Task를 실행하기 위해서는 해당 스택을 가져다가 그 안에서 실행해야 할 프로그램 포인터도 꺼내고, 그리고 전에 실행하다가 저장 해 두었을 작업상태인 레지스터들도 꺼내어 원래 위치로 보내고... 그리고 실행하면... 간단하겠죠?

즉, 인터럽트와 Multi-Tasking과정의 가장 큰 차이는, 각 Task마다 자신의 Stack을 따로 가진다는 것입니다. 그리고 스케쥴링이라는 것은, 다음에 칼로 어떤 사과를 깎아야 할런지를 결정하는 것이

고, Context Switching 이라는 것은 사과가 결정되면 지금까지 깎고 있던 사과를 쟁반위에 내려놓고, 작업 중이던 내용들도 같이 쟁반에 보관한 다음, 깎아야 할 새로운 쟁반을 가져다가 그안에서 작업내용과 사과를 꺼내어 칼이 다른 사과를 깎을 수 있도록 해 주는 과정입니다.

제가 들은 비유가 좀 맘에 안드실런지 모르겠는데요... 어떻든 이제 좀더 학술적인 용어로 현실화 시켜 보겠습니다.

Page 4 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

RTOS에서 일반적으로 말하는 Multi-Tasking의 대상을 Task라고 합니다. Task는 Process나 Thread와도 비슷한 개념인데요... 어떻든 동시에 실행될 수 있는 어떤 함수(?) 혹은 CPU흐름 정도로 생각해도 좋을 것입니다.

RTOS 커널에서는 Task를 관리하기 위해, 각 Task마다 관련 정보들을 저장할 수 있는 구조체 자료구조를 사용하는데요... 이 구조체를 TCB라고 부릅니다. 풀어서 표시하면 Task Control Block 의 약자입니다. 위에서 들었던 쟁반정도로 생각할 수도 있겠구요. TCB 는 하나의 구조체로 그 안에는 위에서 말했던 Task가 사용하게 될 Stack 공간을 가리키는 포인터 필드가 있습니다. 물론 Task 스케쥴링에 사용하는 Task의 Priority와 같은 정보나 기타의 것들도 들어가죠.

여기까지 Multi-Tasking을 위해 Stack이 얼마나 중요한지와, Context Switching이 무엇을 의미하는지, 그리고 각 Task 마다 Stack을 따로 두는 이유 등을 설명했습니다. 잘 이해가 안 되신다면... 아무래도 OS 에 대한 지식을 좀더 갖추어야 할 것 같군요. 한 학기 내내 가르치는 내용을 몇 줄의 글로 설명한다는 것 자체가 무리이니까요. 하지만 이해가 안되도 어쩔 수는 없습니다. 애초에 이 강좌

가 포팅에 대한 것이지 OS에 대한 것은 아니니까요. 다시 본론으로 돌아와서, uC/OS 의 대부분은 CPU에 상관없는 C 코드로 되어 있습니다. 하지만 Context Switcher부분은 각 시스템에 따라 Stack운영 방식이 다르고, 인터럽트 관련 처리도 해야하며, 또 고속의 성능이 요구되므로 어셈블리어로 씌여 졌으며, 그런 이유로 포팅의 주된 작업이 바로 이 부분을 구현하는 작업이 됩니다.

Linux나 pSOS등의 다른 OS의 경우에 비하면 uC/OS에서의 포팅은 상당히 어려운 편이라고 하겠습니다. 적어도 이런 중요한 코드는 각 CPU 코어별로 제공이 되니까요.

아뭏든... CPU마다 레지스터의 개수도 다르고, 인터럽트 동작 방식도 다르며, 스택을 사용하는 용도도 조금씩 차이가 납니다.

하지만 앞서 얘기했듯이, 우리는 Task의 Context Switching 기능을 구현하기 위해, 어떤 모양으로 레지스터들을 스택에 저장할지를 약속해야 합니다. 그래야 그Task를 다시 실행시키기 위해 어떤 식으로 레지스터들을 꺼내 올 지를 알 수 있기 때문입니다.

* ARM7에서의 Context

Context라는 용어는 바로 작업 상태를 의미합니다. 스택을 포함한 CPU 레지스터들이 그것입니다. 그러면 ARM의 Context는 무엇이 될까요?

ARM은 r0-r12까지를 범용 레지스터로 사용하고, r13을 스택포인터(sp), r14를 Link 레지스터로 사용하며 r15는 프로그램 카운터로 사용합니다.

그래서 기본적으로 r0-r12 는 항상 저장이 되어야 하구요, 나머지 레지스터들은 적절한 고려가 필요합니다. 자... 먼저 제가 그린 Stack의 그림을 보여드리고 나서 설명을 계속 하도록 하겠습

니다. 참고로 8086의 Stack모양에 대한 그림은 226Page에 나와있습니다.

   ----------------------------------------            Stack (High Memory)    ----------------------------------------            15            r15(pc)            14            r14(lr)            13            r12            12            r11            11            r10            10            r9            9             r8            8             r7            7             r6            6             r5            5             r4            4             r3            3             r2            2             r1            1             r0

Page 5 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

           0             CPSR     <-        pTCB->OSTCBStkPtr    ----------------------------------------            Stack (Low Memory)    ----------------------------------------

위의 그림을 보면 이해가 되나요? 어떤 Task A 가 있다고 한다면 해당 Task 의 정보를 담고 있는 TCB가 메모리상에 존재할 것입니다. 그 TCB의 포인터를 pTCB라고 할때 pTCB->OSTCBStkPtr에는 해당 Task의 스택 포인터가 담겨있게 됩니다. 그리고 그 스택 안에는 위의 그림과 같은 순서로 CPU의 레지스터들이 저장되도록 하려고 합니다. 이건 순전히 제가 정한 약속입니다. 만약 여러분들이 위의 배치가 맘에 안드신다면 바꾸셔도 상관 없습니다.

CPSR은 잘 아시겠지만 ARM7에서 Flag 레지스터에 해당하는 것입니다.

그런데 자세히 살피니... r13이 없군요. r13은 ARM7에서 스택 포인터로 사용이 된다고 말씀 드렸죠? 즉, 어떤 Task를 실행 시킬 때 가장 먼저 하는 일이, 위에서 언급한 pTCB->OSTCBStkPtr 값을 r13에 넣는 것입니다. 즉, r13은 TCB내에 저장할 수 있는 공간이 따로 있는 것이죠.

그렇게 넣고 나서, r13을 사용해서 스택에서 하나 하나씩 꺼내면서 원래의 레지스터로 넣어주면 됩니다.

한가지 재미있는 것은... r15의 경우입니다. r15는 프로그램 카운터이므로, 해당 값이 바뀌면, 마치 JUMP 와 같은 효과를 나타내게 됩니다. 즉, r15에는 Task가 중단된 지점의 위치를 넣어주

면 되겠군요. 그러면 자연스럽게 중단되었던 위치로 다시 돌아가게 되는 것입니다.

자... 앞에 사설이 길었지만... 제가 하고 싶었던 얘기는 바로 위의 그림 하나입니다. 위의 스택 모양을 기초로 나머지 부분들을 완성해 나갈 것입니다. 되도록이면 이 그림을 크게 그려서 모니

터 옆에라도 붙여 놓으시길 바랍니다. 수시로 확인하고 참조하게 될테니까요...

그럼 오늘 강좌는 이만 마치도록 하겠습니다.

(제가 쓴 글을 다시 읽어보니... Linux나 pSOS 포팅에 비해 uC/OS 포팅이 더 어렵다는 식으로 쓴거 같네요... 그건 그냥 단편적인 하나의 측면에 대한 얘기구요... 규모 면에서나 다른 부분에 있어

서... 상용 OS나 Linux에 비할 바가 못되죠. 그저 핵심 코드를 직접 짜 줘야 한다는 의미였으므로... 이해해 주시길 바랍니다.)

(4) OSTaskStkInit 함수

오늘 강좌는 uC/OS 포팅 부분 중에서 C 소스 코드로 되어 있는 부분에 대한 내용입니다. 제 경우엔 책에 포함되어 있는 8086 포팅 코드를 시작점으로 했는데요, lx86l 이라는 디렉토리에 8086용 포팅 코드가 들어있습니다.

그 중 Os_cpu_c.c 라는 소스 파일이 바로 오늘 강좌의 대상입니다.

해당 파일을 열어보면... 기대와는 달리 달랑 OSTaskStkInit 함수 하나만 들어있음을 알게 되실 겁니다. 물론 이밖에 몇몇 Hook 함수들이 있는데, 함수 틀만 만 들어져 있고, 실제 내용은 비어 있으

므로 포팅과는 관련이 없습니다.

결론적으로 C 소스에서는 OSTaskStkInit함수만 만들어주면 됩니다.

Page 6 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

자.. 그럼 본격적으로 OSTaskStkInit 함수에 대해서 알아보도록 하죠.

*. OSTaskStkInit 함수의 역할

이름을 풀어보면 Task의 스택을 초기화한다는 의미를 지니고 있습니다. 스택을 초기화 한다는게 어떤 의미를 지니고 있을까요? 지난 강좌에서 들었던 비유를 다시 한번 써먹기로 하죠. 지난

번 강좌에서 Task에는 TCB가 존재하고, 그 TCB에는 Task가 사용하게될 Stack 을 가리키는 포인터를 가지고 있다고 말씀 드렸습니다. 비유로 바꾸어 얘기하면, 사과를 깎는 작업 자체가 Task가 되고, 그 작업을 위한 작업 공간이 쟁반이 되며, 이를 TCB 혹은 Stack으로 생각할 수 있겠군요. OSTaskStkInit함수는 이 Task를 처음 생성할 때와 관련이 있는 함수입니다.

uC/OS를 들여다 보면, Task를 생성하기 위해 OSTaskCreate 라는 함수를 사용합니다. RTOS에서 Task는 실행중에 임의로 생성하거나 삭제할 수 있습니다. 흔히 사용하는 Visual C 나 Java에서 Thread를 생성하거나 삭제할 수 있는 것과 같습니다. 이 때 사용하는 시스템 콜 서비스가 OSTaskCreate라는 함수인 것입니다. 해당 함수의 소스는 OS_Task.c 소스안에 포함되

어 있습니다. 한번 들여다보는 것도 좋겠죠.

타스크를 생성하기 위해 OSTaskCreate을 호출할 때 넘겨주는 인수를 살펴보도록 하죠.

INT8U OSTaskCreate (void (*task)(void *pd),   -> 1                     void *pdata,              -> 2                     OS_STK *ptos,             -> 3                     INT8U prio)               -> 4

인수를 살피면, 우선 첫번째 인수는 Task 로서, 첫째 인수는 Task의 시작번지를 의미하며, 두번째 인수는 Task 시작 시에 건내어지게 될 인수를 의미합니다. 세번째 인수가 바로 Task가 사용하게 될 스택 공간의 포인터이고, 마지막인수가 Task의 우선순위입니다.

즉, Task를 생성할때 단순히 비어있는 어떤 공간의 주소를 넘겨줌으로서, 해당 타스크가 해당 공간을 그 Task의 스택으로 사용하도록 하고 있는 것입니다.

문제는... 단순히 비어있다는 사실입니다. 당연하겠죠... 처음 Task를 만들기 위해 임의의 비어있는 공간(혹은 배열)의 주소를 넘겨줬을 뿐이니, 해당 내용은 비어 있거나 의미 없는 값들이 들어 있을 것입니다.

즉, 아무것도 안들어 있는 쟁반을 하나 넘겨준다는 의미입니다. 근데 이게 왜 문제냐구요? 지난 강좌에서 언급했지만, Context Switching과정을 다시한번 짧게 언급해 보죠.

"Context Switching 이라는 것은 사과가 결정되면 지금까지 깎고 있던 사과를 쟁반위에 내려놓고, 작업 중이던 내용들도 같이 쟁반에 보관한 다음, 깎아야 할 새로운 쟁반을 가져다가 그 안에서 작업내용과 사과를 꺼내어 칼이 다른 사과를 깎을 수 있도록 해 주는 과정입니다."

라고 되어 있군요. 즉, Task를 만든다는 것은 쟁반위에 사과를 올려놓고 다른 쟁반들과 동등한 상태로 만들어 두는 것을 의미합니다. 즉, 언제라도 스케쥴러에 의해 선택이 된다면 즉시 실행

될 수 있는 대기 상태로 만들어 져야 하는 것이지요. 쟁반들을 넣어두는 캐비넷 쯤으로 생각해도 좋겠군요. 그 케비넷 안에는, 아까 작업을 하다 중단시켜 놓은 다른 쟁반들이 들어 있구요... Task를 만듦으로서 새로운 사과를 하나 쟁반위에 올려서 그 안에 넣어 두는 것이 Task의 생성이라고 생각해도 좋겠습니다.

그러면 쟁반을 초기화 한다는것은? 즉, 스택을 초기화한다는 것은 무슨 의미를 지니고 있을까요? 계속 힌트를 말했습니다만... 캐비넷에서 하나의 쟁반을 꺼내어 깎는 과정은 항상 동일합니

다. 즉, 위에 써 놓은 것처럼... 우선 현재 작업하던 작업 내용을 Stack에 저장해 놓고 작업 중이던 TASK는 Ready Q 로 넣은 다음, 새롭게 실행될 Task의 TCB를 가져다가 그 안에 저장되어 있으리라고 예상되는 작업 내용을 복구하여 CPU를 실행하는 것입니다.

하지만 처음 생성된 Task의 경우엔 Stack에 '작업하던 내용'이 저장되어 있을리가 없겠죠? 물론 중단된 위치도 저장되어 있을리가 없구요....

Page 7 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

그래서 OSTaskStkInit 함수를 통해 적당한 값으로 비어있는 Stack 을 채워주는 것입니다. 따라서 Task 생성과정에서 이 함수가 호출 되게 됩니다.

* OSTaskStkInit 의 실체

자.. 역할을 이해하셨다면, 이제 어떻게 Stack을 초기화 시켜야 하는지의 문제로 넘어가 보겠습니다.

여기서 지난 강좌에 사용한 Stack의 모양을 사용해야 합니다. 어제 정했던 스택의 모양을 바로 Ready Q에 들어있는(혹은 대기중인 Task의) 스택모양의 '표준'으로 정했기 때문입니다.

          ----------------------------------------                   Stack (High Memory)           ----------------------------------------                   15            r15(pc)                   14            r14(lr)                   13            r12                   12            r11                   11            r10                   10            r9                   9             r8                   8             r7                   7             r6                   6             r5                   5             r4                   4             r3                   3             r2                   2             r1                   1             r0                   0             CPSR     <-        pTCB->OSTCBStkPtr           ----------------------------------------                   Stack (Low Memory)           ----------------------------------------

자... 위의 모양을 만들어 주면 되겠군요. 그럼 구체적으로 OSTaskStkInit의 소스 코드를 살펴보도록 하겠습니다.

        01:     void * OSTaskStkInit(void (*task)(void *pd),void *pdata,         02:                          void *ptos,INT16U opt)         03:     {         04:         INT32U *stk;         05:         opt    = opt;         06:         stk    = (INT32U *)ptos;         07:         *(--stk) = (INT32U)task;        // r15         08:         *(--stk) = (INT32U)0;           // r14         09:         *(--stk) = (INT32U)0;           // r12         10:         *(--stk) = (INT32U)0;           // r11         11:         *(--stk) = (INT32U)0;           // r10         12:         *(--stk) = (INT32U)0;           // r9         13:         *(--stk) = (INT32U)0;           // r8         14:         *(--stk) = (INT32U)0;           // r7         15:         *(--stk) = (INT32U)0;           // r6         16:         *(--stk) = (INT32U)0;           // r5         17:         *(--stk) = (INT32U)0;           // r4         18:         *(--stk) = (INT32U)0;           // r3         19:         *(--stk) = (INT32U)0;           // r2         20:         *(--stk) = (INT32U)0;           // r1         21:         *(--stk) = (INT32U)pdata;       // r0

Page 8 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

        22:         *(--stk) = (INT32U)0;           // CPSR         23:             return ((void *)stk);         24:     }

단지 24줄에 불과한 소스를 설명하려고, 그렇게 많은 사설을 늘어놨다는 사실이 놀랍지 않나요?

먼저 OSTaskStkInit 함수의 인수를 살펴보겠습니다. 첫째 인수는 Task의 시작번지입니다. 즉, Task가 CPU에 의해 실행될 때 Program Counter 값으로 지정될 주소를 의미합니다. 두번째 인수는 Task의 인수이구요... 세번째가 Stack의 위치, 마지막은 현재 사용하지 않는 옵션입니다.

우선 4번 줄에서 8086에서는 INT16U *stk; 으로 정의 되어 있던 것을 INI32U로 바꿨습니다. 이유는 ARM7에서 레지스터의 크기가 32비트 이기 때문이구요, 5번줄은 그냥 무시하면 되구

요, 6번에서 스택으로 지정받은 공간의 번지를 stk라는 변수로 받는 부분입니다.

다음 7번 부터 22번까지 스택을 채우게 되는 것이죠. 우선 *(--stk)라고 쓴 이유는, 제가 Full & Decrement Stack을 사용했기 때문입니다.

그리고 r15부터 r0, CPSR까지 순서대로 스택에 넣습니다. 여기서 인수를 어떻게 사용했는지 살펴보십시요.

맨 먼저 r15의 자리에, 인수로 건내받은 Task의 시작번지를 넣어 줍니다. 그럼으로서 r15가 복구되며 Task가 시작될 수 있도록 하는 것입니다. r14부터 r1까지는 그냥 쭉 넣어 주는데 r13이 없는 이유는 지난 강좌에서 말씀 드렸습니다. sp로 사용되기 때문이죠... r0의 위치를 보시면 pdata 변수를 넣어준 것을 볼 수 있습니다. 이 부분은 8086의 소스와 차이가 있는데요, C 에서 인수를 어떻게 넘겨 주는가의 문제와 관련이 있습니다.

8086에서는 함수 호출시에 Stack을 통해 인수를 넘깁니다. 하지만 ARM 컴파일러는 적은 개수의 인수에 대해서는 레지스터를 사용해 인수를 넘깁니다. 즉, 첫째 인수는 r0를 통해 넘기게 됩니다.

그런 이유로 pdata를 r0의 위치에 넣어주게 됩니다.

이 부분은 C 가 어떤식으로 어셈으로 변환되는지에 대한 지식이 어느정도 있어야 이해를 하실 수 있을 것 같은데요... 그 얘기는 건너 뛰기로 하지요.

마지막으로 완성된 스택의 현재 주소를 리턴하면 됩니다.

자. 여기까지 입니다. 간단한 코드를 길게 설명하느라... 몹시 힘들군요. 앞으로도 비슷한 과정을 겪게 될 것 같습니다. 대부분의 코드가 그다지 복잡하지는 않습니다. 그저... 그 배경을 설명해야 한다

는 강박 관념 때문에...

어떻든... 오늘 강좌는 이쯤에서 마칠까 합니다. 그러기 전에 몇가지 개인적인 얘기를 하겠습니다.

음... 강좌의 저작권에 대한 문제인데요... 지난번 ARM7강좌를 연재할 적에, 겪었던 일입니다만, 제 강좌의 내용을 제 동의도 없이 모 유명 잡지의 기사에 일부 포함하고, 또 대부분의 내용을 '이달의 디스켓'의 내용으로 배포 했었던 적이 있었습니다. 강좌를 그대로 옮긴 것도 아니고, 토시만 바꿔서... 이해하시나요?

사실 강좌를 쓰며 뭔가 댓가를 바라고 하는 것은 아닙니다. 그저, 이렇게 함으로써, 제 이름을 얻기 위해서겠지요. 그런데, 그걸 가져다가... 악용하는 것은, 참 당하는 입장에서는 불쾌한 일입니다.

지금 이런 얘기를 꺼내는 것은... 다시는 그런 일이 없었으면 하는 바램에서 입니다. 제 글이 필요하신 분들이 메일로 허락을 구하면, 항상 허락을 해 드렸었습니다. 적어도 양해 정도는 구하는게 예의

라고 생각합니다.

Page 9 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

자... 분위기가 별로 안좋아진 것 같은데요, 그저 상식을 넘는 행동만 안 해 주셨으면 합니다. 그럼 다음 강좌에 뵙겠습니다.

(5) IRQ 핸들러와 OSTickISR 함수

오늘 강좌에서는 가장 많은 머리 회전을 필요로 했던 IRQ핸들러 부분에 대한 내용입니다.

uC/OS를 동작시키기 위해서는 타이머 인터럽트를 사용해야 합니다. RTOS에서 가장 중요하게 생각되는 시간 개념을 바로 타이머 인터럽트를 사용하여 구현하기 때문입니다.

오늘은 되도록 요점만 설명하도록 노력해 보겠습니다. 자칫 하다가는, RTOS의 원론 적인 부분분터 전부 언급해야 될지도 모르니까요.

어떻든 RTOS 가 시간을 중요시 하는 만큼, 약 1/100 초 마다 커널의 특정 함수를 호출해 줄 필요가 있습니다. 이 함수가 OSTimeTick 이라는 함수이구요, 이를 위한 타임 인터럽트 핸들러가 OSTickISR 함수입니다.

즉, 1/100 초마다 타이머 인터럽트가 발생하도록 칩의 타이머를 초기화 시키고, 인터럽트가 발생하면 OSTimeTick 함수를 한번 호출해 주는 것입니다.

사실은 말처럼 그렇게 쉽지만은 않은데요, 여러가지 문제들이 있기 때문입니다.

문제는 Timer 인터럽트 종료 시점에 Context Switching 이 일어날 수 있다는 점입니다. 무슨 얘긴고 하니... 우선순이가 높은 TaskA가 있어서 실행되다가, 스스로 100 Tick 동안 Block 되는 시스

템 서비스를 호출하여, Block 모드로 바뀌었다면, 그리고 그 다음으로 우선순위가 높은 TaskB가 Ready 상태에 존재했다면 그 시점에서 TaskB가 실행될 것입니다. TaskB가 쭉 실행되다가 100 Tick이 지나면, TaskA가 깨어나야 할 시점이 되겠지요... 그러면 그 순간에 TaskB에서 TaskA로 Context Switching 이 일어나야 합니다.

쓰고나니 전문용어를 많이 사용한 것 같네요. 우선 Tick은 RTOS에서 말하는 시간개념으로, 보통 1회의 Timer 인터럽트를 의미합니다. 따라서 1/100초로 인터럽트 주기를 맞췄다면, 100 Tick이 지나면 1초가 지난 것이 되겠죠... 나머지 용어는 대충 감으로 때려 맞추시길...

암튼... 핵심은, 100 Tick이 지났을 때 Context Switching이 일어나야 한다는 것입니다. 앞서도 말했지만 Tick이란 타이머 인터럽트가 걸리는 것을 의미하므로, 다시 말하자면 100회의 타이머 인터

럽트 이후에는, 다른 프로그램이 실행되어야 한다는 의미입니다.

그게 무슨 문제냐구요?

또다시 Stack 얘기를 해야겠군요. 쟁반 A를 가지고 사과 A를 깎던 도중에 인터럽트가 걸렸습니다. 그럼 어떻게 하죠? 당연히 현재 작업을 쟁반(Stack)에 저장하고 인터럽트 핸들러로 분기를 해야 겠죠. 인터럽트 핸들러에서 별 일이 없었다면 갔다 와서 저장된 내용을 꺼내어서 다시 실행을 하면 됩니다. 그러나 만약, 위에서처럼... Context Switching이 일어나야 한다면, 즉... 지금까지 하던 작업을 그만 두고 새로운 작업을 해야 한다면... 어떨까요?

계속 말했지만, Context Switching과정은 기존 것의 저장, 그리고 새로운 것의 복귀... 과정으로 간추려서 생각할 수 있습니다. Context Switching이 일어나려고 보았더니... 작업하던 내용은 인터

럽트가 걸리는 시점에서 이미 저장을 했겠군요. 그러면, 새로운 것을 가져다가 실행만 시키면 Context Switching이 되는 것입니다.

개념상으로는 아무런 문제가 없고, 아주 자연스럽게 이루어 질 수 있을 것 같은데요... 그렇게 자연스럽게 이루어지도록 해줘야 하는것은 우리들의 몫입니다.

Page 10 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

!!! 결론은 !!!

우리가 처음 정했던 Stack모양을... 인터럽트에도 적용 시켜 줘야 한다는 것입니다.

그래야 Context Switching이 일어났을 때 자연스럽게 진행이 될 수 있기 때문입니다.

자... 그 모양이란 것이 뭐였죠? Stack에 r15부터 r0까지, 그리고 CPSR까지를 쭉 넣어 주면 되는 것이었는데요...

문제는 ARM7이 여러개의 동작 모드를 갖고 있다는 것입니다. 잘 모르시는 분은 ARM7 Data Sheet 를 참조하십시요. EP7209 보드에서는 평상시에 6개의 동작 모드 중에서 Supervisor 모드로 동작이 되도록 하였습니다. - 일부러 그런 것은 아니고, 제가 보드 개발사로 부터 다운 받은 예제 프로그램이 그렇게 되어 있었습니다. 그러다가... IRQ가 걸리면, CPU 동작 모드가 바뀌어 버립니다. IRQ 모드로..

그렇게 되면, r13과 r14 두개의 레지스터가 IRQ 전용 레지스터로 바뀌게 되는 것이죠. r13은 Stack Pointer이므로, 인터럽트가 걸리는 순간... 핸들러 내부에서는 IRQ 전용 Stack을 사용하게 되는 것입니다. 우리의 목적은 뭐였죠? 인터럽트가 걸리면, 사용하던 레지스터들을 해당 Task의 Stack에 보관하는 것이었습니다.

그런데, IRQ모드로 들어오면서 기존에 사용하던 SVC 모드의 r13을 접근할 수 없게 되고, 따라서 레지스터들을 그 안에 넣을 수도 없게 되어 버립니다.

잘 이해가 안될 수도 있을 것 같습니다만... 8086의 인터럽트 핸들러 부분을 가져다 놓고, ARM 용으로 바꾼다고 생각해 보십시요. 쉽지 않음을 알게 되실 겁니다.

자... 그래서, 이제 실전 시간이 돌아왔습니다. 물론 코드는 ARM 어셈 코드입니다.

---------------------------------------------------------------------------   01:     IRQHandler 02:             stmfd   sp!,{r0-r3} 03:             mov     r1,#REGISTER_BASE 04:             ldr     r2,=INTSR1 05:             ldr     r0,[r1,r2] 06:             tst     r0,#0x100 07:             bne     TimerIRQ                ; Check Timer IRQ   08:             ldmfd   sp!,{r0-r3} 09:             subs    pc,lr,#4   10:     TimerIRQ 11:             ldr     r2,=TC1EOI              ; Timer 1 Interrupt Clear 12:             str     r0,[r1,r2] 13: 14:             mov     r2,sp                   ; copy IRQ's sp -> r2 15:             add     sp,sp,#16               ; recover IRQ's sp 16:             sub     r3,lr,#4                ; copy return address -> r3 17: 18:             LDR     r0,=IRQ_2 19:             MOVS    pc,r0 20:     IRQ_2 21:             stmfd   sp!,{r3}                ; push SVC's pc 22:             stmfd   sp!,{r4-r12,lr}         ; push SVC's r14, r12-r4 23:             mov     r4,r2 24:             ldmfd   r4!,{r0-r3} 25:             stmfd   sp!,{r0-r3}             ; push SVC's r3-r0

Page 11 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

26:             mrs     r5,cpsr 27:             stmfd   sp!,{r5}                ; push SVC's PSR   28:             B       OSTickISR               ; Real Body...   ---------------------------------------------------------------------------

코드를 한줄 한줄 따라가 보죠... 먼저 Timer IRQ가 걸리면, Vector Table에 의해 01번 부분으로 진입하게 됩니다.

이때, IRQ의 특성상...

1. CPU 동작 모드는 IRQ 모드가 되고, 2. r13과 r14는 IRQ 전용 레지스터로 바뀌며, 3. IRQ이전의 CPSR은 SPSR로 복사가 되고, CPSR에는 IRQ금지 플래그가 설정됩니다. 4. irq 전용 모드의 r14에는 '복귀번지+4' 값이 들어갑니다.

위의 특징은 ARM7의 Exception 의 특징입니다. 이해가 안되시면 꼭 확인하고 넘어가 주십시요.

2번에서 stmfd sp!,{r0-r3} 명령으로 r0에서 r3까지 4개의 레지스터를 스택에 보관합니다. 하지만 이때 사용되는 스택은? 예! 그렇습니다. IRQ 전용 스택입니다. 즉, SVC모드의 Task의 스택과는 상관이 없습니다.

다음 3줄 부터 6번줄 까지는, 현재 걸린 IRQ가 Timer IRQ인지를 체크하는 부분입니다. EP7209에 종속적인 부분입니다. 5번에서 r0에 IRQ 상태 레지스터 값을 읽어서, 6번에서 Timer IRQ인지 체크하고, 맞다면 10번으로 점프하게 됩니다.

8번,9번은 우리가 원하는 IRQ가 아닌 경우 그냥 Return 하는 코드입니다.

타이머 IRQ가 맞다면 10번으로 넘어가게 되는데요, 11번,12번은 타이머 인터럽트를 클리어 해 주는 부분입니다. 인터럽트 클리어는 아시겠지만, 해당 인터럽트가 정상적으로 처리되었음을 CPU에 알려 줌으로서, 다시 IRQ가 걸리지 않도록 해 주는 것입니다. 보통 인터럽트 핸들러 끝 부분에 두는게 일반적인 방법인데요, 왜 시작 부분에 넣었는지는... 곧 말씀드리겠습니다. 괜히 그런것은 아니

구요...

14번 부터는 Stack 모양을 만들기 위한 트릭 부분입니다. 핸들러 시작 부분에서 r0부터 r3까지를 IRQ스택에 보관했었습니다. IRQ체크 등의 작업을 위해서 해당 레지스터들을 사용해야 했기 때문인

데요, 즉, 14번 시점에서 보면 저장되어야 할 레지스터 중, r0-r3까지는 IRQ 스택에 들어 있는 상태입니다.

14번에서 mov r2,sp 명령을 통해 r2에 IRQ 스택 포인터를 넘겨줍니다. 이것은 잠시 후에 IRQ모드에서 SVC 모드로 전환한 다음, r0부터 r3까지를 꺼내기 위해서 입니다.

15번에서 add sp,sp,#16 명령을 실행하면, IRQ 스택 포인터를 초기상태로 만들어 주는 역할을 합니다. 이것은, IRQ 모드의 sp를 더이상 사용하지 않을 것이기 때문인데요, 지금 이해가 잘 안되면, 나중에 전체적으로 코드의 역할에 대해 고민해 보도록 하세요.

16번을 살펴보죠. sub r3,lr,#4 명령을 수행할때... lr, 즉 r14_irq에는 무엇이 들어있을까요? '복귀번지+4' 값이 들어 있다고 말씀드렸었습니다. 그러면... 명령 후에 r3에는? 예... 인터럽트 종료후

에 돌아갈 번지 값이 들어있게 됩니다.

왜 이 값을 r3로 옮기냐면... 곧 IRQ모드를 SVC모드로 전환할 것이기 때문입니다. 모드가 바뀌면 IRQ모드의 r14는 더이상 참조할 수 없기 때문이죠... 같은 이유로 r13_irq(sp)역시 r2로 옮겼었습

니다.

Page 12 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

        18:             LDR     r0,=IRQ_2         19:             MOVS    pc,r0         20:     IRQ_2

위의 세줄은 IRQ 모드에서 SVC 모드로 빠져 나오는 코드입니다. MOVS를 썼지요.. pc에 IRQ_2를 넣었으므로, 어딘가로 점프를 하는 것은 아니고, 단지 IRQ 모드에서 SVC 모드로 전환되는 것입니

다. 즉, IRQ종료 방법을 여기서 사용했습니다.

자... 그럼 이때 어떤 일이 일어날지 생각해 보죠.

1. SPSR에 들어있던 IRQ이전의 PSR이 CPSR로 복구됩니다. 2. r13과 r14는 SVC모드용 r13과 r14로 바뀌게 됩니다.

자.. (1)에서 이전 CPSR값이 복구되면서 IRQ금지도 풀리게 됩니다. 여기서... 만약 위에서 IRQ 클리어를 해 주지 않았었다면... 다시 IRQ가 발생하여 무한 루프에 빠지게 됩니다. 그래서 윗 부분에

서 IRQ 클리어를 넣어 준 것이지요...

(2)번 과정을 보면, r13의 경우... add sp,sp,#16 명령으로 맨 처음 했던 stmfd 명령에 의한 스택 포인터 변화를 원상태로 해 놓은 것을 볼 수 있습니다.

21번 줄로 넘어가지요..

stmfd sp!,{r3} 명령으로 스택에 r3를 저장하는군요... 이번의 Stack은 어떤 Stack일까요??? 방금 IRQ모드에서 SVC모드로 빠져나왔으므로... Task의 쟁반이군요!!! 그럼... 우리가 그렸던 스택 모양에 따르면, 가장 위에 들어가야 하는 것은 r15였는데요... 코드에서는 r3를 넣습니다. 왜냐구요? 위에서 sub r3,lr,#4명령을 통해 인터럽트 복귀 번지를 r3에 넣어 두었었으니까요. (점점 머리가 복잡해 지고 계실것 같군요... 인내를 가지고...!!!)

그럼 이번에는 나머지 레지스터들을 저장할 차례입니다. 22번 줄이지요.

stmfd sp!,{r4-r12,lr} 명령입니다. ARM의 멀티 레지스터 전송 기능입니다. 막강하죠? 보시다시피... r14와 r4부터 r12까지를 하나의 명령으로 스택에 넣습니다. 들어가는 순서는 항상 높은 레지

스터 번호가 높은 주소에 들어가게 되므로, 우리들의 Stack모양과 일치합니다.

이제는 ? r0부터 r3까지를 저장해야 합니다. 그런데... 해당 값은 아까 irq 스택에 넣었었습니다... 기억하시나요? IRQ 스택은... 이미 IRQ 모드가 아니므로 접근할 수 없을텐데... 하고 걱정이 되시지

는 않나요?

그걸 위해서 r2에 IRQ모드의 sp를 넣어 두었더랬습니다. 그 포인터를 이용해서 r0부터 r3까지를 IRQ스택에서 꺼내는 부분이 바로 23,24번 줄입니다.

그리고 25번에서는 Task의 스택에 r0-r3을 저장하는 부분이구요... 자... 이제 하나 남았습니다. 그게 뭐였죠? CPSR입니다.

CPSR은 바로 Stack으로 저장할 수 없으므로, r5로 옮겨서 Stack으로 넣기로 하지요. 바로 26, 27번 줄입니다.

예!!! 여기까지가 IRQ를 위한 Stack 모양 만들기였습니다.

너무 복잡하죠? 제가 썼지만 쓰면서도 복잡했습니다... 물론 처음 코딩할 때도 복잡했구요... 이해가 안되시더라도... 차근차근 따져 보시길 바랍니다. 무슨 일이든... 노력이 필요할 때도 있는 것이니

까요. 제가 전부 알아듣기 쉽게 설명 드릴 수 있으면 좋겠지만, 어쩌겠습니까... 제 능력의 한계가...T.T

Page 13 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

이쯤되면... 아마도 실제 포팅을 시도해보지 않으신 분들은, 거의 포기 단계에 이르렀을 것 같군요. 그래두 별 수 없습니다. 오직 끈기!!! 기분전환을 위해 8086 포팅 코드나, 책의 관련 부분을 읽어 보시는 것도 좋겠구요...

28번에서 OSTickISR 함수로 넘어가게 되는데요... 여기서 하는 일은 비교적 간단합니다. 책에 나온 Pseudocode를 보면요.. OSTaskISR에서 해야 할 일은...

1. 레지스터들을 저장한다. 2. OSIntEnter() 함수를 호출하거나, OSIntNesting변수를 증가시킨다. 3. OSTimeTick() 함수를 호출한다. 4. OSIntExit() 함수를 호출한다. 5. 레지스터들을 복구한다. 6. 인터럽트 종료...

이중에서 (1)번과정은 IRQ 핸들러 시작 부분에서 처리했습니다. 그래서... (2)번 이후의 부분이 OSTickISR 함수 안에 구현되면 됩니다.

자... 이제 마지막으로 해당 소스를 살펴보고 끝내도록 하겠습니다.

--------------------------------------------------------------------------- 01:     OSTickISR 02:             LDR     r0,=OSIntNesting ; Notify uC/OS-II of ISR 03:             LDRB    r1,[r0] 04:             ADD     r1,r1,#1 05:             STRB    r1,[r0]   06:             BL      OSTimeTick       ; Process system tick 07:             BL      OSIntExit        ; Notify uC/OS-II of end of ISR   08:             LDMFD   sp!,{r0} 09:             MSR     CPSR_xsf,r0 10:             LDMFD   sp!,{r0 - r12, lr , pc} ---------------------------------------------------------------------------

2-5 번줄 부분이 OSIntNesting++ 에 해당하는 부분입니다. 6번은 OSTimeTick()호출, 7번은 OSIntExit호출 입니다.

그리고 8번 부터 10번까지가 우리가 만들었던..(너무도 힘들게..) Stack 모양에서 내용들을 꺼내어 실행시키는 부분인데요...

8번에서 스택의 첫번째 항목을 꺼내어 CPSR에 넣어주고...(9번), 10번 줄에서는 한큐에... 나머지를 다 꺼내어 넣어줍니다.

휴... 이제 다 끝났군요. 오늘 설명했던 부분이 아마도 가장 힘든 부분이 아니었을까 싶네요.... IRQ 처리가 ARM7 uC/OS-II 포팅의 가장 큰 난관이었습니다.

아무쪼록... 앞으로 더 힘든 부분은 없습니다. 오늘 내용만 잘 이해하신다면... ARM7에서의 uC/OS-II 포팅의 70% 이상을 이해하신 것이라 생각합니다. 힘 내시길 바랍니다.

그럼 다음 강좌에서 뵙겠습니다.

Page 14 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

(6) OSStartHighRdy 함수

오늘 강조는 OSStartHighRdy 함수에 대한 부분입니다. 지난 강좌에서 지나치게 무리를 한 덕에... 앞으로 다룰 부분은 그다지 분량이 많지 않을 듯 합니다.

uC/OS 책 198 Page를 보시면 OS_CPU_A.ASM에 대한 수도 코드들이 들어 있습니다. 지금 우리는 기본적인 사항들을 모두 마친 상태이므로... 수도 코드에 따라 쭉 코드를 구현하면 됩니다. 추가

로 설명할 것도 별로 없을 것 같아요.

먼저 OSStartHighRdy 의 역할을 살펴보도록 하죠.

이 함수는 uC/OS 가 처음 초기화 되고 시작되어 최초로 Task를 실행 시키는 시점에서 호출되는 함수 입니다. 여기서 잠깐 uC/OS 용 예제 프로그램의 앞 부분을 확인해 볼까요?

01:     void main (void) 02:     { 03:         PC_DispClrScr(DISP_FGND_WHITE + DISP_BGND_BLACK); 04:         OSInit(); 05:         PC_DOSSaveReturn(); 06:         PC_VectSet(uCOS, OSCtxSw); 07:         RandomSem = OSSemCreate(1); 08:         OSTaskCreate(TaskStart, (void *)0, 09:             (void *)&TaskStartStk[TASK_STK_SIZE - 1], 0); 10:         OSStart(); 11:     }

위의 소스는 8086 포팅을 사용하는 예제 프로그램의 main 소스입니다. 대충 보시면... 8번에서 Task를 하나 생성하고, 10번에서 OSStart라는 함수를 호출하는데요, 이 함수 안에서 OSStartHighRdy 함수가 호출되게 됩니다. 그리고 다시는 돌아오지 않습니다.

즉, OSStartHighRdy 함수가 실행되는 시점은... 다른 Context Switching 함수와는 달리, 현재 Task가 실행되고 있지 않은 상태입니다. 일반적인 Context Switching 과정은 현재 어떤 Task가 실행되고 있는 상태에서 호출되며, 따라서 현재 작업을 해당 Task의 Stack으로 저장해 두는 과정이 포함되는데, 그에 비해 OSStartHighRdy 함수가 실행되는 시점에서는 아직 아무런 Task도 실행되

고 있지 않으므로, 현재 상태의 저장 과정이 생략되게 됩니다. 간단하죠?

그러면 OSStartHighRdy 코드안에서 해야할 일에 대해 살펴보도록 하죠. 책의 저자에 의하면 OSStartHighRdy 내에서 해야 할 일은 5단계로 이루어 집니다.

1. OSTaskSwHook() 함수 호출 2. 실행시키고자 하는 Task의 Stack Point를 얻어와서 sp에 설정 3. OSRunning 값을 TRUE로 설정 4. 설정된 sp에서 레지스터 값을 모두 꺼내옴 5. 새로운 Task를 실행

위 작업 자체에 대해서는 별 문제가 없으리라 생각하고, 바로 소스 코드를 살피겠습니다.

--------------------------------------------------------------------------- 01:     OSStartHighRdy 02:             BL      OSTaskSwHook   03:             LDR     r0,=OSRunning

Page 15 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

04:             MOV     r1,#1 05:             STRB    r1,[r0]   06:             LDR     r0,=OSTCBHighRdy 07:             LDR     r0,[r0] 08:             LDR     sp,[r0]   09:             LDMFD   sp!,{r0} 10:             MSR     CPSR_xsf,r0 11:             LDMFD   sp!,{r0 - r12, lr , pc} ---------------------------------------------------------------------------

2번은 유저가 작성한 Hook 함수를 호출해 주는 부분이고, 3-4번까지는 OSRunning값을 TRUE로 만드는 부분입니다. 6-8번이 실행되어야 할 TCB포인터로 부터 Task의 Stack 포인터 값을 sp로 옮기는 작업이구요... 9-11번은 지난 강좌에서 보았던 Task의 Stack으로부터 레지스터를 복구하고 Task를 실행시키는 부분입니다.

앞에서 말했던 순서와는 조금 다르긴 하지만... 대충 위에 나온 것을 모두 만족시키고 있음을 볼 수 있습니다.

이번 강좌는 참 간단해서 좋네요... 5회를 먼저 이해하셨다면, 오늘 것은 식은죽먹기 일것 같습니다.

자.. 그럼 이번 강좌는 이만 줄이겠습니다.

(7) OSCtxSw함수

오늘 강좌에서도 하나의 함수만 설명드리면 될 듯 하군요. 오늘은 OSCtxSw함수입니다. 역시 그다지 어렵지 않습니다.

먼저 OSCtxSw() 함수의 역할을 살펴보록 하지요.

이 함수는 os_cpu.h 파일 안에서 OS_TASK_SW() 라는 이름으로 정의되어 있습니다. 그리고 OS_TASK_SW() 는 OSSched() 함수 안에서 호출됩니다.

8086 포팅 소스에서는

#define  OS_TASK_SW()         asm  INT   uCOS

로 정의하여 소프트웨어 인터럽트를 사용하도록 처리했습니다만, ARM7의 경우엔 INT와 대응하는 명령어로 SWI가 있긴 하지만, 8086의 경우와 구현상의 다른 문제점이 있어서 그냥 OSCtxSw함수

를 직접 호출하도록 하였습니다.

OSSched() 함수는 uC/OS의 스케쥴러에 대항하는 함수입니다. 예를 들어 새로운 Task를 하나 생성하였다던가 하면, 해당 Task의 우선순위를 비교하여 기존의 실행하던 Task와 교체할 것인가를 결정하는 함수이지요.

어떻든... 중요한 점은 OSCtxSw함수는 시스템 서비스를 수행하다가 Context Switching이 일어날 경우 호출되는 함수라는 점입니다.

Page 16 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

따라서 내부적으로 하는 일은 현재 작업을 저장하고, 새로운 Task 정보를 가져다가 실행 시켜주는 부분까지 입니다. 상대적으로 OSIntCtxSw함수와 비교한다면, 후자가 인터럽트 발생 시점에 이미 레지스터의 내용을 스택에 저장한 상태이므로 그 부분이 생략되는데 반해, OSCtxSw함수는 현재 작업레지스터의 저장 과정을 처리해 주어야 합니다.

또, OSStartHighRdy 함수에 비교한다면, 해당 함수는 기존에 실행하던 Task가 없이 처음 시작하는 상태이므로, 마찬가지로 작업하던 레지스터를 저장하지 않는다는 차이가 있습니다.

책 201페이지에 나와있는 OSCtxSw함수 내에서 처리해 주어야 할 일을 살펴보겠습니다.

1. 사용중인 Register들을 Task의 Stack에 저장 2. 현재 Stack Pointer를 TCB의 OSTCBStkPtr 변수에 저장 3. OSTaskSwHook() 호출 4. OSTCBCur = OSTCBHighRdy 5. OSPrioCur = OSPrioHighRdy 6. 실행될 Task의 Stack Pointer를 sp로 설정 7. 해당 스택에서 레지스터들을 복구 8. 새로운 Task 실행

위의 내용을 보면 (1),(2)번이 현재 작업 내용을 마무리 하는 부분입니다. 깍던 사과를 쟁반위에 갈무리하는 부분입니다. (3)번은 User 의 Hook함수를 호출해 주는 부분이고, (4)번과 (5)번은 전역

변수 값을 조정하는 부분입니다.

OSTCBCur이란 전역변수는 현재 실행되고 있는 Task의 TCB를 가리키는 포인터이고 OSTCBHighRdy라는 전역변수는 ReadyQ내의 가장 우선순위가 높은 Task의 TCB를 가리키는 포인터 입니다. 즉, uC/OS의 스케쥴링 정책상, 가장 높은 우선순위의 Task가 항상 실행되어야 하므로, OSSched()함수에서 이 값을 비교하고, 다르다면 OS_TASK_SW()를 호출하도록 되어 있습니다. 즉, Context Switching이 일어나도록 하는 것이죠. OSCtxSw함수에서 Context Switching을 처리하므로, 처리되었다는 의미로 (4)번과 (5)번에서 해당 값들을 바꿔 줍니다.

(6)번 부터 (8)번까지는 이전 OSStartHighRdy() 함수 등에서 다루었던 내용과 다르지 않습니다. 선택된 Task를 실행시키는 부분입니다.

자.. 그럼 코드를 보겠습니다.

--------------------------------------------------------------------------- 01:     OSCtxSw 02:             STMFD   sp!,{lr} 03:             STMFD   sp!,{r0 - r12, lr} 04:             MRS     r0,CPSR 05:             STMFD   sp!,{r0}   06:             LDR     r0,=OSTCBCur 07:             LDR     r0,[r0] 08:             STR     sp,[r0]   09:             BL      OSTaskSwHook   10:             LDR     r0,=OSTCBCur 11:             LDR     r1,=OSTCBHighRdy   12:             LDR     r2,[r1] 13:             STR     r2,[r0]   14:             LDR     r0,=OSPrioCur 15:             LDR     r1,=OSPrioHighRdy  

Page 17 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

16:             LDRB    r3,[r1] 17:             STRB    r3,[r0]   18:             LDR     sp,[r2]   19:             LDMFD   sp!,{r0} 20:             MSR     CPSR_xsf,r0 21:             LDMFD   sp!,{r0 - r12, lr , pc} ---------------------------------------------------------------------------

1번줄부터 5번줄 까지가 (1)번에 해당하는 부분입니다. 6번줄부터 8번 줄까지는 (2)번에 해당하는 부분으로 OSTCBCur->OSTCBStkPtr=sp를 의미합니다.

9번줄에서 (3)번에 언급한 Hook함수를 호출해 주구요... 10번줄 부터 13번줄까지는 (4)번, (5)번에 해당하는 OSTCBCur = OSTCBHighRdy 와 OSPrioCur = OSPrioHighRdy 를 처리하는 부분

입니다.

18번에서 (6)번에 해당하는 처리를 합니다. 즉 새로운 Task의 Stack포인터를 가져다가 sp에 설정하는 것이죠.

19번,20번,21번은 자주 봐왔던 새로운 Stack에서의 레지스터 복구와 동시에 Task 실행을 의미합니다.

자... 오늘 강좌도 간단히 끝이 났군요.

이제 중요한 함수 하나만 더 설명을 하면 핵심 내용은 마치는 셈입니다. 마지막 강좌로, 그동안 언급하지 않았던 자잘한 부분들을 언급하고 강좌를 마칠 생각입니다. 2회 더 남았군요.

이번 강좌는 별로 인기가 없나봐요... 끝끝내... 피드백이 없어서.. 그냥 제 맘대로 강좌를 진행해 버리고 말았네요..

그럼 다음 강좌에서 뵙겠습니다.

(8) OSIntCtxSw함수

오늘 강좌에서는 핵심 함수중의 마지막 함수인 OSIntCtxSw 함수를 다루려고 합니다. 이부분만 말씀 드리면 uC/OS 포팅을 위한 핵심 코딩 부분들은 끝나게 되는 셈입니다. 나머지는, 헤더파일의 미세한 조정 및 환경 설정 정도 입니다.

오늘 내용도 그다지 어렵지 않습니다. 돌이켜 생각해 보건데... 첫 부분이 가장 힘들었던 것 같군요... 하긴, 그랬기에 지금은 쉽게 쓰고 있습니다.

먼저, 늘 그랬듯이 해당 함수의 역할에 대해 살펴 보겠습니다.

uC/OS에서 Context Switching을 수행하는 함수는 두개가 있습니다. OSCtxSw 함수와 OSIntCtxSw 함수인데, OSCtxSw 함수가 시스템 서비스 내부에서 호출되는데 반해, OSIntCtxSw 함수는 항상 인터럽트 종료 시점에 호출된다는 차이가 있습니다.

결정적인 차이는 OSCtxSw함수의 경우, 시스템 Call 에 의해 스케쥴러에 진입하게 되고, 스케쥴러를 빠져 나올 때 호출되어 Context Switching을 일으킵니다. 이때 Context Switching 을 위해

Page 18 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

OSCtxSw 함수가 호출되고, 이 함수 내에서 현재 시점의 CPU상태를 저장하기 때문에, 다시 해당 Task가 실행될 경우엔, 해당 시스템 Call을 종료하는 위치로 돌아가서 계속 실행을 하게 됩니다.

이에 반해 OSIntCtxSw 함수의 경우엔, 인터럽트가 걸리면 현재 상태를 저장하고 인터럽트 핸들러로 진입하며, 인터럽트 처리가 끝나면 OSIntExit 함수를 통해 필요에 따라 Context Switching을 위한 OSIntCtxSw 함수를 호출하게 됩니다. 그리고 OSIntCtxSw 함수 내부적으로는 이미 진행하던 Task의 내용은 인터럽트가 처음 걸렸을 때 이미 저장했으므로..(이 부분이 중요합니다.) 다시 CPU레지스터들을 저장할 필요가 없습니다. 그저, Context Switching을 위한 마무리 작업들만 간단히 해 주고 새로운 Task를 실행합니다. 따라서 해당 Task가 깨어날 때에는 인터럽트가 중단된 그 지점으로 가서 실행을 재개합니다.

그러면 책의 201페이지에 나와있는 해당 함수에서 처리해야 할 일들을 정의해 보지요.

1. 스택 포인터를 조정(필요없는 부분을 삭제) 2. 현재 Stack Pointer를 TCB의 OSTCBStkPtr 변수에 저장 3. OSTaskSwHook 함수를 호출 4. OSTCBCur = OSTCBHighRdy 5. OSPrioCur = OSPrioHighRdy 6. 실행될 Task의 Stack Pointer를 sp로 설정 7. 해당 스택에서 레지스터들을 복구 8. 새로운 Task 실행

위와 같습니다. 지난 강좌에서 OSCtxSw 함수에 대해 다루었던 내용을 비교해 보면, (1)번이 다른 것을 알 수 있습니다. OSCtxSw 함수에서는

1. 사용중인 Register들을 Task의 Stack에 저장

이었었죠. 2번 부터는 지난번 강좌의 내용과 같으므로 다시 언급할 필요가 없을것 같구요... (1)번에 대해서만 말씀 드리기로 하죠.

4 회때 강좌로 돌아가보면.. 인터럽트가 걸렸을 때 스택 모양을 어떻게 처리했던 가를 떠올릴 수 있을 것입니다. 즉, 인터럽트가 걸리면 인터럽트 핸들러에 들어가기 이전에 이미 스택에 CPU의 레지

스터들을 모두 저장한 상태를 만듭니다.

이 모양은 3회 강좌에서 정했었죠..

문제는... 인터럽트 핸들러 내부에 있습니다. 인터럽트 핸들러 내부에서도 계속 스택을 사용한다는 것이죠...

즉, 스택 모양을

          ----------------------------------------                   Stack (High Memory)           ----------------------------------------                   15            r15(pc)                   14            r14(lr)                   13            r12                   12            r11                   11            r10                   10            r9                   9             r8                   8             r7                   7             r6

Page 19 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

                  6             r5                   5             r4                   4             r3                   3             r2                   2             r1                   1             r0                   0             CPSR     <-        pTCB->OSTCBStkPtr           ----------------------------------------                   Stack (Low Memory)           ----------------------------------------

위와 같이 만들어 놓고, 인터럽트 핸들러에 진입을 하게 되는데요, 그것으로 끝나는게 아니라 인터럽트 핸들러로 진입을 하게 되면, sp 포인터가 계속 사용되어 OSIntCtxSw 함수가 실행될 시점에서 sp포인터가 위와 같이 CPSR의 위치를 가리키는 것이 아니라 좀 더 아래 부분을 가리키게 됩니다.

sp 값은 OSIntCtxSw 함수의 (2)번 부분에서 TCB의 OSTCBStkPtr 변수에 저장됩니다. 따라서 저장하기 전에 적당한 값으로 조정해 줄 필요가 있습니다. 그게 바로 (1)번이 하는 역할 입니다.

그럼 좀 더 구체적으로 살펴보도록 하겠습니다.

OSTickISR           LDR     r0,=OSIntNesting         LDRB    r1,[r0]         ADD     r1,r1,#1         STRB    r1,[r0]           BL      OSTimeTick         BL      OSIntExit       <- 이 부분에서 OSIntCtxSw를호출하게됩니다           LDMFD   sp!,{r0}         MSR     CPSR_xsf,r0         LDMFD   sp!,{r0 - r12, lr , pc}

위의 소스는 4회 강좌때 살펴 보았던 인터럽트 핸들러 소스입니다. 소스에 표시한 부분까지는 여전히 스택 모양을 유지하고 있을 것입니다. 그러나 OSIntExit를 호출하면, 스택 모양이 바뀔 것을 예상할 수 있습니다.

OS_Core.c 소스를 참조하시길 바랍니다. OSIntExit함수를 살피면, 내부에서 결국 OSIntExit 함수를 호출하는 것을 볼 수 있는데, 우리가 알아야 할 것은 OSIntCtxSw 함수가 호출된 시점에서 얼마나 스택이 사용되고 있느냐는 것입니다.

약간은 실험적이기도 하지만, 제가 알아낸 바에 의하면...

OSIntExit에서 OSIntCtxSw 로 호출을 하면 스택에는 별 영향이 없고, 그저 다시 OSIntExit로 돌아오기 위한 번지를 lr 에 저장을 합니다. 그러나 문제는 같은 방식으로 OSTickISR에서 OSIntExit를 호출할 때 lr에 저장 번지를 넣어 두었으므로 그대로라면 lr값은 망가지게 됩니다. 그래서 OSIntExit에서는 시작할 때 내부적으로 lr값을 사용하지만, 해당 값을 보존해야 하므로 스택에 저장을 합니다. 이 부분이 유일한 스택의 사용처 입니다.

자, 그럼 내용을 알았으니 그걸 어떻게 소스로 구현했는지 소스를 살펴보도록 하죠.

01:     OSIntCtxSw 02:             ADD     sp,sp,#4  

Page 20 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

03:             LDR     r0,=OSTCBCur 04:             LDR     r0,[r0] 05:             STR     sp,[r0]   06:             BL      OSTaskSwHook   07:             LDR     r0,=OSTCBCur 08:             LDR     r1,=OSTCBHighRdy   09:             LDR     r2,[r1] 10:             STR     r2,[r0]   11:             LDR     r0,=OSPrioCur 12:             LDR     r1,=OSPrioHighRdy   13:             LDRB    r3,[r1] 14:             STRB    r3,[r0]   15:             LDR     sp,[r2] 16:             LDMFD   sp!,{r0} 17:             MSR     CPSR_xsf,r0 18:             LDMFD   sp!,{r0 - r12, lr , pc}

이상이 OSIntCtxSw함수에 대한 소스의 전부입니다. 사실 3번 이후로는 지난 강좌에서 다루었던 내용과 다르지 않습니다. 의심나면 확인해 보시죠.. OSCtxSw함수의 6번 라인 이후와 OSIntCtxSw함수의 3번 라인 이후가 완전히 일치함을 확인할 수 있을 것입니다.

그렇다면 설명드릴 부분은 달랑 ADD sp,sp,#4 한줄이군요... ARM은 32비트 레지스터를 사용하므로... 위에서 언급한, OSTickISR로 돌아가기 위한 주소가 들어 있을 스택의 마지막 4바이트를 삭제하는 것이죠.

너무 시시해서... 재미없었을 수도 있겠군요.

어떻든... 제가 말씀드릴 중요한 부분은 다 말씀드렸습니다.

이제... 다음 강좌에서 몇가지 마무리를 하고 강좌를 마치도록 하겠습니다.

(9) 마무리

오늘은 드디어 마지막 강좌를 쓰려고 합니다. 그 동안 uC/OS 포팅에 필요한 핵심적인 함수들을 어떻게 구현하는가에 대해 언급했습니다만... 오늘은 그 밖에 설정해 주어야 할 몇몇 사항들을 간단히 언급하고 전체 강좌를 마무리 지으려고 합니다.

* OS_CPU.H 수정

해당 파일에는 각종 CPU정보들을 정의하도록 되어 있습니다. 그냥 대충 따라가면서 ARM 용으로 바꾸시면 됩니다.

Page 21 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

- Type 선언

예를 들어 INT16U라는 타입은 16비트 unsigned 형 정수를 의미하는데, 86에서는 unsigned int로 정의되어 있지만, ARM에서는 unsigned short로 정의되어야 합니다.

- OS_STK_GROWTH

다음으로는 OS_STK_GROWTH라는 중요한 상수가 있는데요, 이 값은 ARM의 경우엔 1로 정의하여 스택 포인터가 아래로 자라나는 Decrement Stack을 사용함을 명시합니다. 이 값이 잘못되면, IDLE Task생성시에 스택 공간 지정시에 문제가 발생합니다.

- OS_ENTER_CRITICAL(), OS_EXIT_CRITICAL()

인터럽트 금지 해제를 통해 Critical Section을 구현합니다. 인터럽트 금지를 시키는 방법은 여러가지가 있겠지만, 여기서는 CPSR의 I 비트를 1로 만들어서 금지하는 방법을 사용하였습니다. 그리고 해당 역할을 하는 함수를 splx()라고 어셈모듈에 만들어 포함시켰습니다.

- OS_TASK_SW()

86 에서는 asm INT uCOS를 사용하여 소프트웨어 인터럽트 기능을 썼지만, 여기서는 그냥 OSCtxSw()라는 함수 호출로 대치했습니다.

* EP7209의 타이머 설정

EP7209에서 타이머를 사용하기 위해서 사용하는 레지스터는 SYSCON1,TC1D,INTMR1,TC1EOI 정도가 있습니다. SYSCON1의 비트 조작을 통해 타이머 1을 Prescale 모드로 설정하고, 또 클럭

소스를 2KHz로 설정합니다. 해당 내용은 EP7209 Data Sheet 76 Page를 참조하시기 바랍니다.

TC1D가 실제로 타이머의 주기를 설정하는 레지스터입니다. 그리고 INTMR1은 타이머 IRQ를 Enable시키는 레지스터이고, TC1EOI 레지스터는 인터럽트 클리어 레지스터입니다.

사실 EP7209용 보드를 대상으로 하였지만... 강좌를 끝내고 보니, 겱국 그냥 ARM7을 위한 강좌가 되었군요.

별로 길지 않았던 강좌를 이쯤에서 끝내려고 하구요... 해당 소스는 www.ucos-ii.com 사이트에 올렸습니다. uCOS-II 포팅중에 ARM 분류에 포함되었습니다.

언젠간 uC/OS의 포팅뿐만이 아니라 다른 부분에 대한 무언가(?)도 하게될 날이 올 것 같습니다. 짧은 강좌였지만 아무쪼록 여러분들에게 조금이라도 도움이 되었으면 합니다.

참고자료

위의 글에서 언급한 Cirrus Logic社의 EP7209 Processor는 이미 단종된 제품입니다... 따라서 현재 Cirrus Logic의 Website에서 관련 자료를 다운로드 하실수는 없습니다... 대신 아래 제가 모아

논 자료를 참고하시기 바랍니다...

Page 22 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm

EP7209 Product Data Sheet (DEC '99 , DS453PP2 : 2.12 Kb) EP7209 Development Kit Product Bulletin (JAN '00 , DK453PP04 : 322 Kb) EP7209 Product Bulletin (DEC '99 , PB453PP2 : 970 Kb) EP7209 Development Kit Quick Start User's Guide (SEP '01 , DS453DKQS-1 : 463 Kb) Errata: EP7209 Data Sheet Change (JAN '00 , ER453C1 : 6 Kb) Errata: EP7209 Rev. D Silicon Change (JUN '00 , ER453D1 : 12Kb)

Send to a colleague | Print this document

-----------------------------------------------------------

Copyleft © 1998~2003,2004 Chang-woo YANG Last Modified: 09/30/2004 05:30:31

Page 23 of 23Here is a "PLDWorld.com"... // EXCALIBUR... // Additional Resources // µC/OS-II 포팅강좌...

2004-09-30http://www.pldworld.com/_altera/html/_excalibur/ucos-ii/ucos-ii_porting.htm