VEDA 주간 학습 정리
[5주차] 임베디드 시스템
이번 주 학습을 한 문장으로 정리하면: 임베디드 시스템의 특징, 마이크로컴퓨터 구조, CPU 실행 흐름, 빌드 과정, ARM32 어셈블리 기초를 한 흐름으로 이해한 주간
작성일: 2026.04.25
관련 링크
◀ 이전 주 블로그: [4주차] Qt 프레임워크
▶ 일정관리 프로젝트: [5주차] 일정관리 앱 프로젝트
▶ 일정관리 프로젝트: [6주차] STM32 개발환경과 라즈베리파이 GPIO 이해
이번 주 학습 목표
- 임베디드 시스템이 일반 PC와 무엇이 다른지 NTCR 관점에서 이해하기
- 마이크로컴퓨터의 구성 요소와 CPU 중심 데이터 흐름 정리하기
- 임베디드 빌드 과정과 ARM32 어셈블리 기초를 연결해서 보기
1. 임베디드 시스템이란?
임베디드 시스템은 특정 목적을 수행하기 위해 하드웨어와 소프트웨어가 결합된 시스템이다. 데스크톱처럼 범용 작업을 폭넓게 처리하기보다는, 센서 입력을 읽고 정해진 규칙에 따라 동작을 제어하는 식으로 목표가 분명한 경우가 많다.
환경 준비 메모
이번 주는 라즈베리파이 4B를 이용한 환경설정과 실습이 포함되어 있었습니다.
임베디드 시스템의 특징: NTCR
| 항목 | 의미 | 예시 |
|---|---|---|
| Nature | 현실 세계의 변화를 입력으로 받고, 처리 후 다시 현실에 출력한다는 점 | 센서 값을 읽어 모터를 제어하거나 화면에 상태를 표시하는 흐름 |
| Time | 정해진 시간 안에 반응해야 하는 실시간성 요구 | 의료기기, 자동차 제어, 오디오 재생 |
| Constraint | 전력, 크기, 발열, 비용 등 제한된 자원 안에서 동작해야 함 | 배터리 기반 장치, 소형 센서 보드 |
| Reliability | 오랜 시간 안정적으로 같은 기능을 수행해야 함 | 산업 설비, 가전 제품, 네트워크 장비 |
핵심 정리
임베디드 시스템은 단순히 작은 컴퓨터가 아니라, 외부 세계와 연결되어 있고 시간 제한과 자원 제약을 함께 고려해야 하는 시스템이라는 점이 중요했다.
2. 마이크로컴퓨터의 구성
- CPU: 명령을 해석하고 계산을 수행하는 중심 장치
- ROM: 부팅 절차나 고정된 프로그램을 저장하는 읽기 전용 메모리
- RAM: 실행 중 필요한 값을 임시로 담는 작업 공간
- 주변장치: 센서, PWM 컨트롤러, UART, 오디오 같은 입출력 장치
- 버스: CPU와 메모리, 주변장치 사이에서 주소와 데이터, 제어 신호를 전달하는 통로
메인 버스는 CPU와 ROM, RAM을 직접 연결하는 핵심 경로이고, 주소 버스·데이터 버스·컨트롤 버스로 나누어 이해할 수 있다.
로컬 버스는 USB 허브같이 UART나 각종 주변장치처럼 더 많은 장치를 비교적 낮은 속도로 확장 연결하는 역할에 가깝다.
3. CPU는 어떻게 명령을 실행할까?
| 구성 요소 | 역할 |
|---|---|
| PC (Program Counter) | 다음에 실행할 명령어의 주소를 가리킨다 |
| Decoder | 이진수 명령을 해석해 어떤 동작을 할지 결정한다 |
| ALU | 산술 및 논리 연산을 실제로 수행한다 |
| 레지스터 | 연산에 필요한 값과 상태를 빠르게 저장한다 |
- 명령 패치 사이클: ROM이나 인터럽트 벡터에서 실행할 명령을 가져온다.
- 명령 디코드 사이클: 명령의 의미를 해석하고 필요한 자원을 결정한다.
- 실행 사이클: ALU 연산, 메모리 접근, 분기 같은 실제 동작을 수행한다.
- Write back cycle: 결과를 레지스터나 메모리에 반영한다.
4. 임베디드 시스템 빌드 과정
| 단계 | 입력 | 출력 | 설명 |
|---|---|---|---|
| 전처리 | .cpp, .h |
.i |
매크로 확장, 헤더 포함 처리 |
| 컴파일 | .i |
.s |
고급 언어를 어셈블리로 변환 |
| 어셈블 | .s |
.o |
어셈블리 코드를 기계어 조각으로 변환 |
| 링크 | .o, 라이브러리 |
.elf, .out |
실행 가능한 하나의 이미지로 묶음 |
| 바이너리 변환 | .elf |
.bin |
헤더를 제외한 순수 기계어 데이터 추출 |
| 최종 포맷 변환 | .elf 또는 .bin |
.hex |
하드웨어 프로그래머가 다루기 쉬운 형식으로 변환 |
g++ -E main.cpp -o main.i
g++ -S main.i -o main.s
g++ -c main.s -o main.o
g++ main.o -o main.elf
objcopy -O binary main.elf main.bin
objcopy -O ihex main.elf main.hex
5. ARM32 어셈블리 기초
어셈블리 언어는 하드웨어에 가까운 수준에서 프로그램을 기술하는 언어이며, C 같은 고급 언어가 실제로 어떤 명령들로 바뀌는지 이해하는 데 중요한 도구다. 이번 주에는 ARM32 기준으로 레지스터 구조, 기본 디렉티브, 명령어 분류, 제어문 변환, 함수 호출 규약까지 함께 살펴보았다.
개요
ARM 아키텍처는 Load/Store 구조를 사용하는 비교적 단순한 RISC 계열이다. 메모리 접근은 전용 명령으로만 수행하고, 대부분의 연산은 레지스터에서 처리한다는 점이 핵심이다.
ARM 레지스터 구조
| 레지스터 | 의미 |
|---|---|
R0-R12 |
범용 레지스터 |
R13 (SP) |
스택 포인터 |
R14 (LR) |
함수 복귀 주소 저장 |
R15 (PC) |
현재 또는 다음 명령어 주소 |
특히 SP, LR, PC는 함수 호출과 복귀, 스택 관리, 제어 흐름을 이해할 때 계속 등장하는 핵심 레지스터다.
기본 프로그램 틀
.arm
.text
.global main
.type main, %function
main:
/* 프로그램 코드 위치 */
BX LR
.end
주요 디렉티브
.arm: ARM 32비트 어셈블리 코드임을 지정.text: 코드를 포함하는 섹션 시작.data: 전역 데이터 섹션 시작.global: 외부에서 참조할 전역 식별자 선언.type: 심볼의 종류를 함수 등으로 명시.end: 어셈블리 소스 종료
명령어 분류
- 산술-논리 명령어: 레지스터 값을 계산하고 논리 연산을 수행한다.
- 분기 명령어: 프로그램 카운터를 바꾸어 제어 흐름을 이동시킨다.
- Load/Store 명령어: 메모리와 레지스터 사이에서 데이터를 옮긴다.
기본 산술 및 논리 명령어
| 명령어 | 동작 | 설명 |
|---|---|---|
ADD Rd, Rn, Rm/#imm |
Rd = Rn + Rm/#imm | 덧셈 |
SUB Rd, Rn, Rm/#imm |
Rd = Rn - Rm/#imm | 뺄셈 |
MUL Rd, Rn, Rm |
Rd = (Rn × Rm) % 2³² | 곱셈 |
MOV Rd, Rm/#imm |
Rd = Rm/#imm | 값 이동 |
AND Rd, Rn, Rm/#imm |
Rd = Rn & Rm/#imm | 비트 AND |
ORR Rd, Rn, Rm/#imm |
Rd = Rn | Rm/#imm | 비트 OR |
EOR Rd, Rn, Rm/#imm |
Rd = Rn ^ Rm/#imm | 비트 XOR |
MVN Rd, Rm/#imm |
Rd = ~Rm/#imm | 비트 NOT |
표기 읽는 법
MVN(Move Negative): 값을 옮기되, 비트를 반전시켜서 옮기라는 뜻이다. 즉 Logical NOT에 해당한다.Rd(Destination Register): 결과값이 저장될 대상 레지스터이다. 예:R0,R1Rm(Source Register): 연산에 사용할 값이 들어 있는 원본 레지스터이다.#imm(Immediate Value): 레지스터가 아니라10,0xFF처럼 명령어에 직접 적는 상수값이다.
비교 명령어
CMP Rn, Rm/#imm는 두 값을 비교해 상태 레지스터에 결과를 남긴다. 이후 BEQ, BNE, BGT, BLE 같은 조건부 분기 명령이 이 비교 결과를 바탕으로 실행된다.
분기 명령어
| 명령어 | 의미 |
|---|---|
B label |
무조건 분기 |
BL label |
함수 호출, 복귀 주소를 LR에 저장 |
BX LR |
함수 복귀 |
| 조건부 분기 | 조건 |
|---|---|
BEQ |
같으면 분기 |
BNE |
다르면 분기 |
BGT |
크면 분기 |
BGE |
크거나 같으면 분기 |
BLT |
작으면 분기 |
BLE |
작거나 같으면 분기 |
제어문이 어셈블리에서 보이는 방식
C 코드
if (x > 0) {
y = 1;
} else {
y = 2;
}
ARM 어셈블리
CMP R4, #0 @ x > 0 확인
BLE else_label @ 조건 불만족시 else로
MOV R5, #1 @ y = 1
B end_label @ else 건너뜀
else_label:
MOV R5, #2 @ y = 2
end_label:
C 코드
y = 1;
while (x > 0) {
y *= x;
x--;
}
ARM 어셈블리
MOV R5, #1 @ y = 1
loop:
CMP R4, #0 @ x > 0 확인
BLE end_label @ 조건 불만족시 종료
MUL R6, R5, R4 @ tmp = y * x
MOV R5, R6 @ y = tmp
SUB R4, R4, #1 @ x = x - 1
B loop
end_label:
함수 구현 예제: factorial
factorial:
PUSH {R5, R6} /* Callee-save 레지스터 저장 */
MOV R5, #1 /* y = 1 */
loop:
CMP R0, #0 /* x > 0 확인 */
BLE end_label
MUL R6, R5, R0 /* tmp = y * x */
MOV R5, R6 /* y = tmp */
SUB R0, R0, #1 /* x = x - 1 */
B loop
end_label:
MOV R0, R5 /* 반환값 설정 */
POP {R5, R6} /* 레지스터 복원 */
BX LR /* 호출자로 복귀 */
main:
BL factorial
ARM32 어셈블리 학습은 문법을 정확하게 이해하기보다 CPU가 코드를 실제로 어떻게 실행하는지 이해하는 과정에 가깝다.
6. 예제
주차 예제 실행
아래 예제 페이지에는 마이크로컴퓨터 구성 요소 탐색과 ARM 조건 분기 시뮬레이션을 넣어 두었다. 티스토리 본문에서도 바로 확인할 수 있도록 iframe으로 연결했다.