Renesas Engineer School 내용을 기반으로 공부한 내용을 정리했습니다.
https://www.renesas.com/en/support/engineer-school/mcu-03-software-development-environment
들어가며
1편에서 CPU는 메모리에 저장된 명령어를 순서대로 읽어서 실행한다고 설명했습니다. 그렇다면 그 명령어는 어떤 형태로 메모리에 저장되어 있을까요? 그리고 우리가 작성한 코드는 어떤 과정을 거쳐 MCU 안에서 실행되는 걸까요?
이번 편에서는 기계어부터 C언어, 그리고 IDE까지 소프트웨어 개발 환경 전반을 정리합니다.
기계어 — CPU가 이해하는 유일한 언어
CPU는 0과 1로 이루어진 이진 코드, 즉 기계어(Machine Language) 만 이해합니다. 예를 들어 레지스터 A에 숫자 2를 저장하는 명령어는 기계어로 이렇게 생겼습니다.
01010001
00000010
이게 전부입니다. CPU는 이 비트열을 읽고 해석해서 동작합니다. 사람이 이걸 직접 작성하면 어떨까요? 수백 줄의 0과 1을 보면서 "이게 덧셈인지 메모리 읽기인지" 파악하는 건 사실상 불가능합니다.
어셈블리어 — 기계어를 사람이 읽을 수 있게
기계어의 불편함을 해결하기 위해 등장한 것이 어셈블리어(Assembly Language) 입니다. 기계어와 1:1로 대응되지만, 0과 1 대신 사람이 읽을 수 있는 문자열을 씁니다. 위의 기계어는 어셈블리어로 이렇게 표현됩니다.
MOV A, #02
"A 레지스터에 2를 넣어라"는 뜻입니다. 기계어보다 훨씬 읽기 쉽습니다. 그런데 어셈블리어에는 치명적인 단점이 있습니다. CPU 아키텍처마다 문법이 완전히 다릅니다. RL78용 어셈블리 코드는 ARM Cortex-M에서 동작하지 않습니다. MCU를 바꿀 때마다 코드를 처음부터 다시 작성해야 합니다.
C언어 — MCU 프로그래밍의 사실상 표준
이 문제를 해결한 것이 C언어입니다. C언어로 작성한 코드는 어떤 CPU 아키텍처에서도 거의 수정 없이 동작합니다. 같은 동작을 어셈블리어와 C언어로 비교하면 차이가 명확합니다.
어셈블리어
MOV A, #00H
MOV B, #00H
loop:
INC A
MOV B, A
CMP A, #0AH
JNZ loop
C언어
int i;
for (i = 0; i < 10; i++) {
result = i;
}
C언어 코드가 훨씬 간결하고 직관적입니다. MCU를 바꿔도 C코드는 대부분 그대로 재사용할 수 있습니다. 그런데 CPU는 C언어를 직접 이해하지 못합니다. C코드를 기계어로 변환하는 과정이 필요합니다.
컴파일러와 링커 — 코드를 MCU가 실행할 수 있는 형태로
컴파일러 (Compiler)
컴파일러는 C언어 소스 코드를 기계어 오브젝트 파일로 변환하는 프로그램입니다.
main.c → [컴파일러] → main.o (오브젝트 파일)
컴파일러는 단순히 번역만 하는 게 아닙니다. 최적화도 수행합니다. 예를 들어 루프를 전개하거나, 사용하지 않는 변수를 제거하거나, 레지스터 활용을 최적화해서 실행 속도를 높입니다.
링커 (Linker)
현대 프로그램은 여러 개의 소스 파일로 나뉘어 작성됩니다. 컴파일러는 각 파일을 개별적으로 오브젝트 파일로 변환하는데, 이 파일들을 하나로 합쳐서 MCU가 실행할 수 있는 최종 바이너리를 만드는 것이 링커의 역할입니다.
main.o ─┐
sensor.o ─┤ → [링커] → firmware.hex (최종 바이너리)
uart.o ─┘
링커는 각 오브젝트 파일에 흩어져 있는 함수 호출과 변수 참조를 연결하고, 메모리 주소를 할당합니다. 최종 결과물은 MCU의 Flash에 기록됩니다.
디버거 — 버그를 잡는 도구
코드를 작성하면 반드시 버그가 생깁니다. 디버거는 프로그램 실행을 중간에 멈추거나, 변수 값을 실시간으로 확인하거나, 한 줄씩 실행하면서 문제를 찾는 도구입니다. MCU 디버거는 세 종류가 있습니다.
- ICE (In-Circuit Emulator): 실제 MCU 대신 에뮬레이터 칩을 회로에 꽂아서 디버깅하는 방식입니다. 가장 강력한 디버깅 기능을 제공하지만, 장비 가격이 비싸다는 단점이 있습니다.
- JTAG 에뮬레이터: MCU 칩 내부에 디버깅 회로가 내장되어 있고, JTAG 인터페이스를 통해 PC와 연결해서 디버깅합니다. 실제 MCU를 그대로 쓰면서 디버깅할 수 있어서 ICE보다 저렴하고, 현재 가장 널리 쓰이는 방식입니다. Renesas에서는 이를 온칩 디버깅 에뮬레이터라고 부릅니다.
- Debug Monitor: MCU 위에서 디버깅 소프트웨어를 같이 실행하는 방식입니다. 별도 하드웨어가 필요 없어서 가장 저렴하지만, 디버깅 소프트웨어가 MCU 자원을 일부 차지하므로 기능이 제한됩니다.
💡 AI를 MCU에서 돌리면 — 개발 환경 관점
문제
MCU에서 AI 모델을 추론하는 코드를 직접 작성하는 것은 현실적으로 매우 어렵습니다. 모델 파라미터를 메모리에 배치하고, 각 레이어의 행렬 연산을 C코드로 구현하고, SRAM을 효율적으로 재사용하는 코드까지 작성하려면 상당한 공수가 필요합니다. 여기에 양자화까지 적용하면 복잡도가 훨씬 올라갑니다
.
해결 방향
이 문제를 해결하는 프레임워크들이 있습니다.
- TensorFlow Lite for Microcontrollers (TFLM) 은 Google이 MCU용으로 만든 경량 추론 엔진입니다. PC에서 학습한 모델을 .tflite 포맷으로 변환하고, 이를 TFLM 라이브러리와 함께 MCU에 올리면 됩니다. INT8 양자화와 In-place Inference가 기본으로 지원됩니다.
- Edge Impulse 는 데이터 수집부터 모델 학습, MCU 배포까지 파이프라인 전체를 웹에서 처리할 수 있는 플랫폼입니다. 지원하는 MCU 타겟에 맞춰 최적화된 C++ 코드를 자동으로 생성해줍니다. 음성 인식, 제스처 감지, 이미지 분류 같은 엣지 AI 태스크를 빠르게 프로토타이핑할 때 유용합니다.
- STM32Cube.AI 는 STMicroelectronics의 툴로, Keras나 ONNX 모델을 STM32 MCU에 최적화된 C코드로 변환해줍니다. IDE인 STM32CubeIDE와 통합되어 있어서 변환부터 배포까지 한 환경에서 처리할 수 있습니다.
공통적인 흐름은 이렇습니다. PC에서 모델을 학습하고 → 경량화(양자화, Pruning) 후 → MCU용 포맷으로 변환하고 → 추론 엔진 라이브러리와 함께 MCU에 배포합니다. 직접 C코드로 모델을 구현하는 경우는 거의 없습니다.
정리
- CPU는 기계어만 이해합니다. 사람이 직접 기계어를 작성하는 것은 비현실적이기 때문에 어셈블리어와 C언어가 등장했습니다.
- C언어는 어떤 CPU 아키텍처에서도 재사용 가능한 이식성이 핵심 장점입니다. MCU 프로그래밍의 사실상 표준입니다.
- 컴파일러는 C코드를 기계어 오브젝트 파일로 변환하고, 링커는 여러 오브젝트 파일을 하나의 실행 바이너리로 합칩니다.
- 디버거는 ICE, JTAG 에뮬레이터, Debug Monitor 세 종류가 있으며, 현재는 JTAG 방식이 가장 널리 쓰입니다.
- MCU에서 AI 추론을 구현할 때는 TFLM, Edge Impulse, STM32Cube.AI 같은 프레임워크를 활용하는 것이 현실적입니다.
'Model Lightening > MCU' 카테고리의 다른 글
| MCU 입문 [4] - 주변장치 제어와 인터럽트 (0) | 2026.03.02 |
|---|---|
| MCU 입문 [2] - MCU를 작동시키는 주변 하드웨어(전원, 클럭, 리셋) (0) | 2026.03.02 |
| MCU 입문 [1] - MCU란 무엇인가? (0) | 2026.02.27 |