MCU는 CPU나 GPU와 다르게 낯설지만, 가장 많이 사용되는 반도체 중 하나다. ST 시스템 솔루션 랩의 유지 카와노 매니저에게 MCU의 활용성과 가능성이 얼마나 큰지 들어보자.
MCU의 언어, ‘C’
CPU와 관계없이 작성, 매우 편리
PC 컴파일러 C 기계어 자동 번역
[편집자주]일반적으로 반도체라 하면 컴퓨터의 CPU와 메모리처럼 일반인에게 익숙한 반도체를 떠올리기 마련이다. 반면에 전자제품 구동을 위해서 핵심 반도체로 쓰이는 MCU(Micro Controller Unit)의 경우 일반적으로 우리가 쉽게 접하는 모든 전자제품에서 사용되고 있으면서도 일반인에게는 아직 낯선 반도체다. 이런 MCU가 최근 반도체 부족 사태로 인해 언론에 오르내리며, 일반인들에게 주목받기 시작했다. 이에 본지는 MCU 반도체 전문기업인 ST마이크로일렉트로닉스의 유지 카와노 매니저의 연재기고를 통해 MCU에 대해 전문적으로 알아보는 자리를 마련했다.
■ 이진 패턴 대신할 수 있는 프로그래밍 언어 완벽하게 익혀야
MCU는 메모리(예: ROM)에 이진(0/1) 패턴으로 저장되어 있는 명령어들을 실행한다. 그러나 이진 명령어들은 사용자 친화적이지 못하다. 무슨 뜻인지 사람이 이해하기 어렵기 때문이다. 따라서 특정 이진 패턴을 대신할 수 있는 언어 시스템으로 프로그램을 작성해야만 한다. 이런 언어 시스템을 프로그래밍 언어라고 부른다. 프로그래머가 MCU의 프로그램 작성을 위해 프로그래밍 언어를 완벽하게 익히는 것이 반드시 필요하다. 프로그래밍 언어에는 여러 가지 종류가 있는데, 본고에서는 이러한 언어들 중 몇 가지를 살펴보도록 하겠다.
■ 모든 MCU에 사용할 수 있는 공통 언어 ‘C’
‘3부. CPU란 무엇인가’에서 설명했듯이 CPU에 작동을 지시하는 명령어는 ROM에서 0과 1로 이루어진 이진 패턴의 형태로 읽혀진다. 궁극적으로는 어떻게든 이러한 이진(0/1) 패턴들을 생성해내야만 하지만 어떤 패턴이 어떤 명령어인지 한 눈에 파악하기란 매우 어렵다. 따라서 개별 이진 패턴에 이름을 정해주고 이러한 이름을 사용하여 프로그램을 작성하게 된다. 이진 패턴으로 구성된 명령어들을 기계어라고 부르며, 기계어에 사용된 이진 패턴들에 할당된 이름들을 어셈블리 언어(또는 니모닉(mnemonic) 언어)라고 한다. 예를 들어, 덧셈은 ‘3부 CPU란 무엇인가’에서 0000 0001로 정의되었는데, 이 패턴을 여기서는 ADD라고 부른다면, 0000 0001은 기계어로 작성된 덧셈 명령어가 되며, ADD는 이를 어셈블리 언어로 작성된 명령어가 된다.
MCU 개발자는 각자의 MCU를 위한 기계어와 어셈블리 언어를 결정한다. 따라서 MCU마다 사용되는 언어가 다를 수 있고, 사용하려는 MCU의 어셈블리 언어를 반드시 배워야 한다. MCU가 다른 MCU로 바뀔 때마다 프로그래머는 새로운 MCU의 어셈블리 언어를 배우지 않으면 안 된다.
프로그래머들에게는 시간 소모가 너무 큰 일이 아닐 수 없다. 고급 컴퓨터 프로그래밍 언어(예: 포트란, 베이직)를 조사한 결과 모든 MCU에 사용할 수 있는 공통 언어로 C가 선택됐다.
C는 처음에는 그 서술형 방식이 MCU용 언어로서 적합하기 때문에 채택되었지만 지금은 PC, MCU 및 메인프레임 컴퓨터에서도 널리 사용되고 있다.
▲그림 1 : 기계어의 예
ST의 STM32의 Cortex-M3 CPU를 이용하여 ‘레지스터 8에 있는 값에 1을 더한 뒤 그 합산 결과를 레지스터 8에 저장하라’는 명령어를 기계어로는 어떻게 나타내는 지 살펴보자. 이 같은 명령어들을 나타내는 데 사용되는 패턴은 [1111 0001]로서, 이는 ‘범용 레지스터의 값을 12비트의 수치 값에 더한 뒤 그 합계를 범용 레지스터에 저장하라’는 뜻이다. 여기에 이진수 [0000 1000]이 뒤따르는데, 이는 합산 결과를 저장하게 될 범용 레지스터의 번호이다. 이 경우의 번호는 8(레지스터 8)이다. 연산 대상인 수치는 레지스터 8에 있기 때문에 또 다른 [0000 1000]이 뒤따르게 된다. 마지막으로 추가될 패턴은 수치 ‘1’([0000 0001])이다. 이 모든 패턴들을 더하면 [1111 0001 0000 1000 0000 1000 0000 0001]이 된다. 이 이진 패턴이 ‘레지스터 8에 있는 값에 1을 더한 뒤 그 합산 결과를 레지스터 8에 저장하라’는 기계어 명령어인 것이다(그림 1). 메모리(예컨대, ROM)에 저장된 기계어 패턴을 객체라고 부른다.
프로그래밍 언어로 작성된 프로그램들은 기계어로 번역되어야만 MCU가 이를 실행할 수 있다.
▲그림 2 : 어셈블리 언어의 예
기계어는 이진 패턴들로 구성되어 있으므로 사람이 이러한 포맷으로 조작하기는 매우 어려울 수 밖에 없다. 따라서 기계어 코드에 대략 일대일로 대응되는 니모닉 언어를 사용해 프로그램을 작성하게 된다. 니모닉 언어를 어셈블리 언어라고 한다.
그러면 어셈블리 언어 몇 가지를 살펴보자. 위에 나온 ‘레지스터 8에 있는 값에 1을 더한 뒤 그 합산 결과를 레지스터 8에 저장하라’는 명령어에서 처음의 [1111 0001]은 덧셈을 나타내는데, 이를 ADD.W로 명명할 수 있다. 이런 식의 표현이 니모닉이다. 범용 레지스터인 레지스터 8은 R8으로 표시된다. 가수(더해지는 수)는 #1으로 표시된다(#는 일반적으로 수를 십진법으로 표시하는 데 사용된다). 이 모든 니모닉을 더하면 ADD.W R8 R8 #1 이 되는데, 이것이 우리가 처음에 살펴보았던 기계어의 어셈블리 언어 버전이다(그림 2).
Cortex-M3가 사용하는 네 가지 산술 연산을 위한 어셈블리 언어는 ADD(덧셈), SUB(뺄셈), MUL(곱셈) 및 SDIV(나눗셈)이다. 논리 연산을 위한 어셈블리 언어는 AND(논리곱), ORR(논리합) 및 EOR(배타적 논리합)이다. 프로그램 내에서 ‘점프’ 하라는 명령어에는 B.W(무조건 분기)와 BL(조건부 분기)이 있다. 또한 아무런 연산도 수행하지 말라는 명령어는 NOP("no operation"의 축약어)이다.
수행될 연산을 명시하는 기계어 명령어의 니모닉 부분을 ‘연산 코드(operation code : 줄여서 "opcode")’라고 한다. 연산 코드에 이어지는 기계어 명령어의 주소 값 및 데이터 값 부분을 ‘피연산자(operand)’라고 한다. 끝으로, 어셈블리 언어를 기계어로 번역하는 과정을 ‘어셈블링(assembling)’이라고 한다.
니모닉을 이용하면 프로그래밍이 어느 정도까지는 간단해진다. 그러나 니모닉과 기계어 명령어 간에는 일대일 대응 관계가 있기 때문에 여전히 사용하기가 쉽지는 않다. 이런 점을 고려하여 컴퓨터에 사용되는 고급 언어인 C가 MCU 프로그래밍에 채택되었다.
어셈블리 언어와 기계어는 특정 CPU용이므로 이처럼 특정 CPU용의 언어로 작성된 프로그램들은 다른 CPU에 사용할 수가 없다. 그러나 C는 CPU와 관계 없이 작성할 수 있으므로 매우 편리하다. 이런 이유 때문에 C는 현재 CPU언어에 흔히 사용되고 있다.
C로 작성된 명령어에 대한 보다 상세한 설명은 시판되고 있는 수많은 교재들을 참조하기 바란다.
▲그림 3 : C와 기계어 및 어셈블리 언어의 비교
그 다음에는 C와 어셈블리 언어의 관계를 살펴보자. 하지만 그 전에 먼저 C의 while 문장을 이용하여 NOP를 500번 반복하기 위한 기술 방법을 살펴보자. 그림 3을 보기 바란다. 여기에서 볼 수 있는 프로그램은 먼저 변수 m을 정의한 뒤 m을 0으로 치환하고, NOP가 실행될 때마다(__asm("nop");) m에 1을 더한다(++m). 그러다가 마침내 m이 500에 이르면 while 루프에서 벗어난다(while (m<500)). 그림 3은 좌측에서 우측 방향 순서대로 다음 사항들을 보여준다: 이 프로그램을 C로 표현하는 방법, 기계어 명령어들이 저장되는 ROM 주소, 기계어 명령어(16진수), 어셈블리 언어 명령어, 각 명령어들에 대한 설명. 여기서 ??main_5는 주소 0x80001d4에 주어진 이름이다. 이와 유사하게, ??main_4는 주소 0x80001e0의 이름이다. 어셈블리 언어는 주소를 직접 다루지 않고 주소에 주어진 이름을 이용하여 주소를 쓴다.
먼저 int에 의해 정의되는 m이 R8에 할당된다. 그러면 m=0가 R8에 0을 넣는다. CMP.W는 R8을 500과 비교한다. R8이 500 이상이면 BGE.N은 프로그램이 ??main_4로 분기하도록 만든다. 그러나 여기서는 R8이 0이므로 분기 없이 ??main_5로 간다. ??main_5는 R8에 1을 더한 뒤 NOP를 실행한다. 그 다음에 CMP.W는 R8을 다시 500과 비교한다. 이번에는 R8이 500 미만이면 BLT.N이 프로그램으로 하여금 ??main_5로 분기하도록 만든다. 따라서 R8은 0에서 시작하여 1, 2, 3 식으로 1씩 증가해나간다. m이 500에 이르면 프로그램은 ??main_5로 분기하는 대신에 ??main_4로 간다. 이 프로그램은 NOP 명령을 500번 반복한다.
이 그림은 C를 읽기가 얼마나 쉬운지를 보여준다. 어셈블리 언어는 기계어보다 훨씬 읽기가 쉽긴 하지만 그래도 이를 이해하기 위해서는 니모닉을 알아야 한다. 그러나 PC 컴파일러는 C를 어셈블리 언어로 자동 번역한 뒤 다시 기계어로 번역해 주므로 프로그래머는 프로그램을 C로 작성하기만 하면 된다.
C만 알면 MCU용 프로그램을 작성할 수 있다고까지 감히 말할 수 있을 것이다. 그렇다고 해도 엔지니어라면 MCU가 주어진 프로그램들을 실제로 어떻게 실행하는지 알아야 한다.