brunch

You can make anything
by writing

C.S.Lewis

by 이경종 Jul 29. 2020

Ch14-1. 임베디드 소프트웨어 개발환경

사실 이 부분은 임베디드 소프트웨어를 개발함에 있어 가장 먼저 다뤄야 할 부분중 하나입니다.

삽을 사용하는 방법을 알아야 흙을 퍼낼수 있듯이, 소프트웨어 개발 환경을 이해해야 소프트웨어를 만들 수 있겠죠.(너무 당연한 얘기를 당연하지 않은 듯 이야기하니 좀 그렇군요...)


임베디드 소프트웨어 개발은 일반 소프트웨어 개발과는 다른 부분이 있습니다.


작성한 프로그램의 결과를 타겟이 되는 임베디드 디바이스(이하 타겟 보드로 지칭)에서 확인하기 위해서는 부가적인 단계가 더 필요합니다. 프로그램을 작성하는 단계에서부터 차근차근 알아가 보도록 하겠습니다.


임베디드 소프트웨어의 어느 부분, 즉 어플리케이션을 작성하느냐, 아니면 운영체제 부분인지, 디바이스 드라이버를 작성하느냐에 따라 사용되는 언어나 컴파일러가 달라집니다. 여기서는 C 언어를 사용한다고 가정하겠습니다.


시리얼 콘솔에 "Hello World" 라는 문장을 출력하는 프로그램 main.c를 작성했다고 칩시다.


#include <stdio.h>


void main(void)

{

    printf("Hello World");

}


오옷... 너무 간단합니다. 원래 다 여기서부터 시작을 하죠.


자, 이제 이 프로그램을 빌드해야겠죠. 

툴체인(Toolchain)이라는게 필요합니다. 툴체인은 쉽게 말해 컴파일러와 같은 빌드툴입니다. 

툴체인은 다음 글에서 다룰 예정입니다.


여기서부터 임베디드 소프트웨어와 일반 소프트웨어의 차별점이 발생합니다.


임베디드 소프트웨어의 컴파일러를 크로스 컴파일러(Cross Compiler)라고 합니다. 우리말로 교차 컴파일러라고 해석하기도 하는데, 그런 말은 구닥다리 일본 전산학 책에서나 나오는 말이고, 현업에서는 그런 말은 쓰지 않습니다. 크로스 컴파일은 프로그램을 작성한 호스트(Host), 즉 PC가 아닌 다른 타겟 하드웨어에서 구동하기 위해 사용되는 컴파일러를 말합니다. 그래서 임베디드 소프트웨어 개발환경 및 타겟 디바이스를 통칭하여 흔히 크로스 플랫폼으로 부릅니다.


통상 크로스 컴파일러는 해당 임베디드 디바이스에 집적된 CPU에 따라 달라집니다. ARM계열의 프로세서라면 ARM용 컴파일러를 써야겠죠, MIPS 프로세서라면 MIPS용 컴파일러가 필요합니다. 각 프로세서마다 Instruction Set(Ch2-1. 컴퓨터시스템 개론 참조)이 다르기 때문이죠. JAVA든 C언어든 뭐든지간에 만들어진 소프트웨어의 종착지는 결국 프로세서의 명령어 이진코드(machine code)입니다.


크로스 개발환경은  특정한 언어 프로그램을 오브젝트 코드(object code)로 바꿔주는 C, C++, JAVA등 상위 언어 컴파일러 뿐만 아니라, 링커(Linker), 어셈블러(Assembler)와 같은 일련의 툴체인을 포함합니다. 


또한 플랫폼에 따라 개발환경은 전용 에디터, 디버거, 프로파일러, 시뮬레이터 등과 같이 소프트웨어 툴과 하드웨어 장비까지 포함될 수 있습니다. 어떤 임베디드 디바이스인지, 그리고 사용되는 프로세서와, SoC가 무엇인지에 따라 다양한 개발환경에 제공되고 구성가능하다는 점 알아 두시기 바랍니다. 임베디드 쪽은 대부분 SoC업체에서 제공하는 SDK(Software Development Kit)이나 IDE(Integrated Development Kit)에 거의 의존한다고 보시면 됩니다.


다시 빌드 단계로 돌아오겠습니다. 빌드단계를 거쳐 나온 main 이라는 바이너리를 타겟보드에서 실행시키면 시리얼 콘솔에 "Hello World"라는 글자가 나오는 것을 볼 수 있겠죠.

그럼 main 이라는 바이너리를 타겟 보드에서 어떻게 실행을 시키느냐?

방법은 천차만별입니다. 

소프트웨어 바이너리/코드를 전송매체가 무엇이든지간에, 타겟보드의 메모리(DRAM) 영역에 해당 바이너리를 복사하고, 부트로더등의 상위 소프트웨어에서 해당 메모리의 맨처음 명령어가 위치한 주소로 분기하면 프로그램이 수행됩니다.


이것이 가장 보편적인 타겟 보드에 실행파일을 구동시키는 방법입니다.


타겟보드의 메모리에 실행파일을 올리는 방법은 플랫폼별로 제각각 다릅니다. 

가장 간단히는 부트로더에서 RS232C로 호스트 PC에 있는 프로그램을 읽어들여서 실행시키는 방식이 있습니다. 작은 크기의 프로그램에서나 쓸 수 있는 방법이겠죠. 

SDK에서 제공하는 플래쉬툴(Flash tool)을 사용해서 타겟보드의 ROM에 직접 소프트웨어 바이너리를 구운(burning) 다음 실행시키는 방법은 비효율적이므로 실제 개발과정에서는 적절치 못합니다만, 다른 방법이 없다면 어쩔수 없겠죠.


통상의 임베디드 디바이스에서는 더 빠른 전송매체(이더넷이나 USB와 같은)를 통해 프로그램을 실행시키거나, JTAG장비나 에뮬레이터 같은 하드웨어 장비를 연결해서 프로그램을 실행시키는게 더 보편적인 방법입니다.


나중에 디버깅을 다루는 글에서 살펴보겠지만, 실제 디버깅은 얼마나 빨리 빌드를 할 수있는지, 그리고 얼마나 빨리 타겟보드에 빌드된 소프트웨어를 내릴수 있는지에 따라 개발작업효율이 크게 차이가 납니다. 빌드하는데 2시간 걸리고, 매번 타겟 보드의 ROM에 구운 다음에 소프트웨어를 구동하는 것이 가능하다면 개발이나 디버깅 작업의 효율은 거의 바닥이라고 봐야겠죠. 그래서 사전에 스피디하고 효율적인 개발환경을 갖추는 것이 매우 중요합니다.


정리하자면 아래와 같은 그림으로 설명할 수 있겠네요.

Implmentation Phase는 빌드 단계를 말합니다. 

Verification Phase는 실행 및 디버깅 단계를 말합니다. 

이 모든 것을 합쳐서 임베디드 소프트웨어 개발환경이라고 할 수 있겠습니다. 정적으로 보면, 툴체인으로 볼 수도 있습니다.

다음 글에서는 툴체인, 그리고 더욱 자세한 임베디드 소프트웨어의 타겟 구동 및 디버깅에 대해 알아보기로 하겠습니다.



작가의 이전글 Ch12-2. 인터럽트(Interrupt)
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari