테마 선택

📚 지식 노트

AI·하드웨어·주변기기·게임·컴퓨터 시스템·이론 컴퓨터 과학·수학·개발과 운영·보안·응용 분야에 대해 알아 두면 좋은 내용을 정리한 노트다. 위에서 분야를 고르고 아래에서 노트를 고른다.

임베디드와 하드웨어

전자레인지·키보드·자동차 속에서 도는 작은 컴퓨터, 마이크로컨트롤러가 레지스터·핀·타이머·버스로 바깥 세상과 주고받는 방법과, 실시간 스케줄링·저전력·디지털 논리·개발 도구까지 실제 수치를 계산한 그림으로 정리한다.

목차 (14)
  1. MCU와 MPU·SoC
  2. 메모리 맵과 레지스터
  3. GPIO: 핀 하나로 켜고 읽기
  4. ADC: 전압을 숫자로
  5. 타이머와 PWM
  6. 통신 버스: UART · I²C · SPI · CAN
  7. 인터럽트와 폴링
  8. RTOS와 실시간 스케줄링
  9. 전력 관리: 잠들어 있는 것이 일
  10. 디지털 논리: 게이트, 플립플롭, 클럭
  11. FPGA와 MCU
  12. 개발 흐름: 크로스 컴파일, 굽기, 디버깅
  13. 많이 쓰는 보드
  14. 핵심 자료

MCU와 MPU·SoC

임베디드 시스템은 정해진 일 하나를 하려고 기계 안에 넣은 컴퓨터다. 그 중심이 마이크로컨트롤러(MCU)다. CPU 코어와 프로그램을 담는 플래시, 변수를 담는 SRAM, 그리고 타이머·ADC·통신 장치 같은 주변장치가 칩 하나에 다 들어 있다. 전원만 넣으면 바로 내 코드가 돈다. 클럭은 수십~수백 MHz, RAM은 KB 단위이고, 잠들면 마이크로암페어(μA) 단위 전류만 쓴다.

반대쪽 끝은 스마트폰이나 Raspberry Pi에 들어가는 SoC(애플리케이션 프로세서, MPU라고도 한다)다. GHz급 코어 여럿과 GPU, 가상 메모리를 위한 MMU가 있어 Linux·Android 같은 범용 OS를 올린다. 대신 GB 단위 DRAM과 eMMC·SD 같은 저장장치를 칩 밖에 붙여야 하고, 부팅에 몇 초가 걸리며, 전력도 훨씬 많이 쓴다. "μs 안에 반드시 반응", "전지 하나로 몇 년"이 필요하면 MCU, 화면·네트워크·파일이 많은 복잡한 소프트웨어가 필요하면 SoC다. CPU 자체의 구조는 CPU 노트에 있다.

MCU 한 칩 안의 코어·메모리·주변장치와, SoC 칩 밖에 DRAM과 저장장치를 둔 구성을 나란히 비교MCU — 칩 하나로 끝CPU 코어플래시SRAM타이머·ADCGPIO·UART·SPI·I²C수십~수백 MHz · KB 단위 RAM · μA 대기 전류전원 켜면 바로 내 코드 실행(OS 없거나 RTOS)SoC / MPU — 바깥 부품이 필요CPU 코어 ×4GPUMMU·캐시USB·PCIe메모리 컨트롤러DRAMGBeMMCSDGHz · GB 단위 RAM · Linux 같은 OS를 올림예: Raspberry Pi 5의 BCM2712, 스마트폰 AP
MCU는 칩 하나(색칠)로 완결되고, SoC는 빠르지만 바깥 메모리·저장장치가 함께 있어야 돈다.

메모리 맵과 레지스터

MCU에서 하드웨어를 다루는 방법은 놀랄 만큼 단순하다. 주변장치의 설정과 상태가 모두 레지스터이고, 레지스터는 정해진 주소에 있다. 그 주소에 값을 쓰면 핀이 켜지고, 읽으면 타이머 값이 나온다. 이를 메모리 맵 입출력이라 한다. Arm Cortex-M 계열은 32비트 주소 공간(4 GiB)을 용도별 구역으로 나눠 두었고, 칩 회사는 그 안에서 실제 주소를 정해 데이터시트의 "주소 맵" 표에 적는다.

Cortex-M 주소 공간을 코드, SRAM, 주변장치, 외부 RAM, 외부 장치, 시스템 여섯 구역으로 나눈 표시작 주소구역들어 있는 것0x0000_0000코드플래시: 프로그램·벡터 표0x2000_0000SRAM변수·스택·힙0x4000_0000주변장치GPIO·UART·타이머 레지스터0x6000_0000외부 RAM칩 밖 메모리(있다면)0xA000_0000외부 장치칩 밖 장치0xE000_0000시스템NVIC·SysTick·디버그주변장치 구역의 주소에 쓰면 메모리가 아니라 하드웨어가 바뀐다(메모리 맵 입출력)
Cortex-M의 주소 구역. 플래시와 SRAM, 주변장치 레지스터(색칠)가 모두 같은 주소 공간 안에 있어 같은 load·store 명령으로 다룬다.

레지스터 하나는 보통 32비트이고, 비트 하나하나가 서로 다른 기능을 맡는다(예: 출력 레지스터의 5번 비트 = 5번 핀). 그래서 한 비트만 바꾸려면 값을 읽어 그 비트만 고쳐 다시 쓴다(읽기-수정-쓰기). 켤 때는 OR, 끌 때는 AND NOT, 뒤집을 때는 XOR을 쓴다. 인터럽트가 그 사이에 같은 레지스터를 고치면 값이 엉키므로, 많은 칩이 "이 비트만 켜기/끄기" 전용 레지스터(STM32의 BSRR, RP2040의 SET·CLR 별칭 주소)를 따로 둔다. C 코드에서 레지스터 포인터에 volatile을 붙이는 이유는, 컴파일러가 "같은 주소를 두 번 읽을 필요 없다"며 읽기를 없애 버리지 못하게 하려는 것이다.

8비트 레지스터 값이 비트 켜기, 끄기, 뒤집기를 거치며 바뀌는 네 줄76543210처음 값101000010xA1REG |= (1 << 3)101010010xA9REG &= ~(1 << 0)101010000xA8REG ^= (1 << 7)001010000x28
8비트 레지스터에 세 연산을 차례로 적용했다. 색칠한 비트만 바뀌고 나머지는 그대로다. 값은 비트 연산 함수로 계산했다.

GPIO: 핀 하나로 켜고 읽기

GPIO(범용 입출력)는 소프트웨어로 쓰임을 정하는 핀이다. 출력으로 두면 1을 쓸 때 전원 전압(3.3 V 등), 0을 쓸 때 0 V를 낸다. LED는 전류를 제한하는 저항과 함께 단다. 핀이 낼 수 있는 전류는 보통 수~수십 mA라서, 모터·릴레이처럼 큰 부하는 트랜지스터나 드라이버 칩을 거쳐 켠다. 입력으로 두면 핀 전압이 문턱보다 높은지 낮은지를 1·0으로 읽는다.

아무것도 연결되지 않은 입력 핀은 전압이 떠서(floating) 잡음에 따라 1과 0을 오간다. 그래서 저항으로 평소 전압을 정해 둔다. 전원 쪽으로 끌어올리면 풀업, 접지 쪽으로 끌어내리면 풀다운이다. 대부분의 MCU에는 이 저항이 칩 안에 있어 레지스터로 켠다. 버튼은 누르는 순간 접점이 몇 ms 동안 튀어(bounce) 여러 번 눌린 것처럼 읽히므로, 일정 시간 같은 값이 유지될 때만 받아들이는 디바운스를 한다. 마이크로폰·가속도계 같은 센서 쪽 원리는 센싱 노트, 커넥터와 전기 규격은 포트 노트에 있다.

출력 핀에서 저항과 LED를 거쳐 접지로, 입력 핀은 풀업 저항과 접지로 가는 버튼에 연결된 회로MCUPA5 출력PB0 입력330 ΩLED1을 쓰면 3.3 V → LED 켜짐3.3 V풀업 10 kΩ버튼평소: 풀업이 끌어올려 1누르면: GND로 당겨져 0버튼은 몇 ms 동안 튄다 → 디바운스
출력 핀은 저항을 거쳐 LED를 켜고, 입력 핀은 풀업 저항 덕분에 평소 1이었다가 버튼을 누르면 GND에 연결돼 0이 된다.

모듈끼리 이을 때 가장 흔한 사고는 전압 수준이다. Arduino Uno는 5 V로, Pico·ESP32 같은 요즘 칩은 3.3 V로 동작하고, 3.3 V 칩의 핀은 대개 5 V를 견디지 못한다. 5 V 출력을 3.3 V 입력에 바로 넣으면 핀이나 칩이 상할 수 있으니 데이터시트의 최대 입력 전압을 보고, 넘으면 저항 분압이나 레벨 시프터를 끼운다. 또 두 보드가 신호를 주고받으려면 GND를 서로 이어 기준(접지)을 공유해야 한다. 모터·릴레이 같은 코일 부하는 끌 때 역방향 전압이 튀므로 코일 양끝에 다이오드(플라이백 다이오드)를 단다.

ADC: 전압을 숫자로

온도 센서·마이크·가변 저항처럼 연속적인 전압을 내는 장치는 ADC(아날로그-디지털 변환기)로 읽는다. ADC는 0 V부터 기준 전압까지를 2N칸으로 나눠, 입력이 몇 번째 칸인지를 정수로 돌려준다. N이 분해능(비트 수)이고, 한 칸의 크기가 LSB다.

  • 기준 전압 3.3 V, 10비트면 LSB = 3.3 / 1024 = 3.22 mV, 12비트면 3.3 / 4096 = 0.81 mV다.
  • 12비트 ADC에 1.2 V가 들어오면 코드는 1489다. 거꾸로 코드 × LSB로 전압을 되돌린다.
  • 양자화 때문에 오차가 최대 한 칸(반올림하는 ADC는 반 칸) 생긴다. 실제로는 잡음과 기준 전압의 오차 때문에 쓸 만한 비트(유효 비트 수, ENOB)가 표시된 비트보다 한두 개 적은 경우가 많다.

얼마나 자주 재는지(표본화 주파수)도 중요하다. 신호에 든 가장 높은 주파수의 두 배보다 빨리 재야 원래 모양을 되살릴 수 있다(나이퀴스트). 표본화와 앨리어싱 자체는 신호 처리 노트에서 다룬다. 반대 방향인 DAC는 숫자를 전압으로 바꾼다.

매끈한 사인파와 3비트 ADC가 읽은 계단 모양 값을 겹친 그래프0 (0.00 V)1 (0.41 V)2 (0.82 V)3 (1.24 V)4 (1.65 V)5 (2.06 V)6 (2.47 V)7 (2.89 V)점선: 실제 전압 · 색 계단: 3비트 코드 · 한 눈금 = 3.3 V / 8 = 0.412 V
0~3.3 V 사인파를 3비트 ADC로 쟀다. 칸이 8개뿐이라 계단이 크게 보인다. 비트가 하나 늘 때마다 칸은 두 배로 촘촘해진다.

타이머와 PWM

타이머는 클럭에 맞춰 수를 세는 하드웨어 카운터다. 클럭을 분주기(prescaler)로 나눈 속도로 0부터 정해 둔 TOP까지 세고, TOP에 닿으면 0으로 돌아가며 인터럽트를 걸 수 있다. 그래서 넘침 주파수 = 클럭 / (분주비 × (TOP + 1))이다. 16 MHz 클럭을 64로 나누고 TOP을 249로 두면 1000 Hz, 곧 1 ms마다 한 번 넘친다. CPU가 다른 일을 하는 동안에도 정확한 박자를 지키고, 들어오는 펄스의 폭을 재거나(입력 캡처) 정해진 순간에 핀을 바꾸는 데 쓴다.

PWM(펄스 폭 변조)은 타이머로 만든다. 카운터가 비교값보다 작은 동안 핀을 켜고, 큰 동안 끈다. 켜진 시간의 비율이 듀티비다. 위 타이머에 비교값 64을 주면 듀티비 25.6%인 1000 Hz 신호가 된다. 디지털 핀은 켜짐·꺼짐밖에 못 내지만, 충분히 빠르게 깜빡이면 LED 밝기·모터 속도는 평균 전압(전원 × 듀티비)을 따라간다. 서보 모터는 반대로 펄스 폭 자체(보통 1~2 ms)를 각도 명령으로 읽는다.

톱니 모양으로 세는 카운터와 비교값 선, 그리고 듀티비 30%인 PWM 출력카운터0 ~ 9비교 3출력듀티비 = 3 / (9 + 1) = 30% → 평균 전압 3.3 V × 0.3 = 0.99 V
TOP = 9, 비교값 3인 PWM. 카운터(톱니)가 비교선 아래에 있는 동안 출력이 높다. 비교값만 바꾸면 주파수는 그대로 두고 듀티비가 바뀐다.

통신 버스: UART · I²C · SPI · CAN

MCU는 센서·화면·메모리·다른 MCU와 몇 가닥 선으로 이야기한다. 핀 수, 속도, 연결할 장치 수, 거리·잡음에 따라 버스를 고른다.

버스선클럭 선연결속도주 용도
UARTTX·RX (+GND)없음(보율 약속)1:1보통 9600 bit/s~수 Mbit/sGPS·블루투스 모듈, 디버그 로그
I²CSDA·SCL있음여럿이 공유, 7비트 주소100 k · 400 k · 1 Mbit/s센서, 작은 화면, EEPROM
SPISCK·MOSI·MISO + 장치마다 CS있음주 1 : 부 여럿수~수십 MHz플래시 메모리, 화면, SD 카드
CANCAN_H·CAN_L (차동)없음다중 마스터 버스최대 1 Mbit/s(CAN FD는 데이터 구간 더 빠름)자동차, 산업 장비
  • UART: 클럭 선 없이 양쪽이 미리 같은 속도(보율)를 약속한다. 쉬는 동안 선은 높고, 0인 시작 비트로 "지금부터"를 알린 뒤 데이터를 LSB부터 보내고, 1인 정지 비트로 끝낸다. 가장 흔한 8N1(데이터 8, 패리티 없음, 정지 1)은 바이트 하나에 10비트라, 115,200 보율이면 1초에 11,520바이트다. PC의 USB 시리얼 포트도 이것이다.
  • I²C: 데이터(SDA)·클럭(SCL) 두 선을 여러 장치가 함께 쓴다. 장치마다 7비트 주소가 있어서, 주 장치가 주소를 먼저 보내면 그 장치만 대답(ACK)한다. 선은 저항으로 끌어올려 두고 누구든 0으로 당기기만 해서(오픈 드레인), 두 장치가 동시에 말해도 합선되지 않는다.
  • SPI: 클럭(SCK), 주→부(MOSI), 부→주(MISO) 세 선을 공유하고, 장치마다 고르는 선(CS)을 하나씩 둔다. 주소도 ACK도 없이 클럭마다 한 비트씩 양방향으로 동시에 주고받아 빠르다. 대신 장치가 늘 때마다 핀이 하나씩 든다.
  • CAN: 꼬인 두 선의 전압 차로 신호를 보내(차동) 자동차 같은 잡음 많은 곳에서도 수십 m를 간다. 주 장치가 따로 없고, 여럿이 동시에 보내면 메시지 ID가 작은 쪽이 이겨 계속 보낸다(중재). 버스 양 끝에 120 Ω 종단 저항을 단다.
UART, I²C, SPI, CAN의 배선 방식을 네 칸에 그린 그림UART — 1:1, 선 2개MCUGPSTXRXTXRX클럭 없음: 양쪽이 보율을 미리 맞춤I²C — 두 선을 주소로 공유SDASCLMCU0x48 온도0x3C 화면풀업 저항 + 오픈 드레인: 누구든 0으로만 당긴다SPI — 공유 3선 + 장치마다 CSMCUSCKMOSIMISOCS1CS2플래시화면전이중 · 수십 MHz까지CAN — 꼬인 두 선, 차동 신호HL120 Ω120 Ω엔진브레이크계기판메시지 ID로 중재 · 잡음에 강해 자동차·산업용
네 버스의 배선. 선이 적을수록 핀은 아끼지만, 주소·중재 같은 약속이 더 필요하다.
UART로 0x41을 보내는 10비트 파형과 SPI로 0xA5를 보내는 클럭·데이터 파형UART 8N1 · 'A' = 0x41TX시작0D01D10D20D30D40D50D61D70정지1SPI 모드 0 · 0xA5 · 상승 에지에서 읽음SCKMOSI10100101UART는 선 하나에 시작·정지 비트로 박자를 맞추고, SPI는 클럭 선이 박자를 따로 준다
선 위의 실제 비트. UART는 'A'(0x41)를 시작 비트 뒤 LSB부터 보내고, SPI는 0xA5를 MSB부터 클럭 상승 에지에 맞춰 보낸다. 비트는 프레임 함수로 계산했다.

인터럽트와 폴링

버튼이 눌렸는지 알아내는 방법은 두 가지다. 메인 루프에서 핀을 계속 읽는 폴링은 단순하지만, 루프가 다른 일로 바쁘면 짧은 신호를 놓치고, 할 일이 없을 때도 CPU가 깨어 있어야 한다. 인터럽트는 핀 변화·타이머 넘침·수신 완료 같은 사건이 생기면 하드웨어가 하던 일을 멈추고 미리 등록한 함수(ISR, 인터럽트 서비스 루틴)를 부른다. Cortex-M은 인터럽트 컨트롤러(NVIC)가 우선순위를 관리하고, 레지스터 저장을 하드웨어가 해 주어 수십 클럭 안에 ISR로 들어간다.

ISR은 메인 코드 아무 곳에나 끼어들므로 짧아야 한다. 깃발을 세우거나 데이터를 버퍼에 넣는 정도만 하고, 무거운 처리는 메인 루프나 태스크에 넘긴다. ISR과 메인 코드가 함께 쓰는 변수는 volatile로 선언하고, 여러 바이트짜리 값은 읽는 동안 인터럽트를 잠깐 막아 반쯤 바뀐 값을 읽지 않게 한다. 같은 개념이 PC 운영체제에서 어떻게 쓰이는지는 운영체제 노트의 입출력 절에 있다.

메인 루프 실행 중 인터럽트가 들어와 ISR이 짧게 돌고 메인 루프로 돌아오는 시간 축메인 루프ISR센서 읽기 · 화면 갱신깃발 보고 버튼 처리ISR버튼 눌림(핀 하강 에지)하드웨어가 레지스터 저장복귀(레지스터 복원)ISR 규칙: 짧게, 기다리지 않고, 공유 변수는 volatile·원자적으로 다룬다.무거운 일은 깃발이나 큐로 메인 루프·태스크에 넘긴다.
인터럽트. 버튼 신호가 오면 메인 루프가 멈추고 ISR(빨간 테두리)이 짧게 돈 뒤 돌아온다. 메인 루프는 나중에 깃발을 보고 실제 처리를 한다.

RTOS와 실시간 스케줄링

일이 몇 가지뿐이면 "무한 루프 + 인터럽트"(superloop)로 충분하다. 통신·화면·센서·제어가 서로 다른 박자로 돌기 시작하면 RTOS(실시간 운영체제)를 쓴다. FreeRTOS·Zephyr·ThreadX 같은 RTOS는 일을 태스크로 나누고, 큐·세마포어·뮤텍스로 서로 주고받게 한다. 리눅스와 달리 가상 메모리·파일 시스템이 기본으로는 없고, 커널이 수 KB에 불과하다.

"실시간"은 빠르다는 뜻이 아니라 마감을 지킨다는 뜻이다. 그래서 RTOS 스케줄러는 대개 고정 우선순위 선점이다. 준비된 태스크 가운데 우선순위가 가장 높은 것이 늘 돈다. 공평하게 나누는 PC의 스케줄러(운영체제 노트의 CFS·EEVDF)와 달리, 낮은 태스크가 굶더라도 높은 태스크의 반응 시간이 예측 가능해야 한다. 주기가 짧은 태스크에 높은 우선순위를 주는 규칙(rate-monotonic)이 고전적인 배치 방법이다.

여기에 고전적인 함정이 있다. 우선순위 역전은 높은 태스크 H가 낮은 태스크 L이 쥔 뮤텍스를 기다리는 동안, 뮤텍스와 상관없는 중간 태스크 M이 L을 밀어내 H가 M보다도 늦어지는 현상이다. 1997년 화성 탐사선 Mars Pathfinder가 이 문제로 거듭 재부팅됐고, 지구에서 그 뮤텍스의 우선순위 상속을 켜는 설정 변경으로 고쳤다. 우선순위 상속은 뮤텍스를 쥔 태스크가 기다리는 태스크 가운데 가장 높은 우선순위를 잠시 물려받게 해, 임계 구역을 빨리 끝내고 놓게 한다. FreeRTOS의 뮤텍스는 이를 기본으로 한다.

상속이 없을 때와 있을 때 L, M, H 세 태스크의 실행 시간 축을 한 칸씩 채우는 단계 그림상속 없음H (3)M (2)L (1)H 끝 14우선순위 상속H (3)M (2)L (1)H 끝 90123456789101112131415
1 / 16

L(낮음)이 0초에 시작해 1초부터 뮤텍스를 쥔다. H(높음)는 2초, M(중간)은 3초에 도착한다. 색칠된 칸은 뮤텍스를 쥐고 하는 일이다.

우선순위 역전. H가 L이 쥔 뮤텍스를 기다리는 동안, 뮤텍스와 상관없는 M이 L을 밀어내면 H는 M보다도 늦게 끝난다. 우선순위 상속이 이 구멍을 막는다.

전력 관리: 잠들어 있는 것이 일

전지로 도는 장치의 수명은 깨어 있을 때가 아니라 잠들어 있을 때 정해진다. MCU는 여러 단계의 슬립 모드를 둔다. 가벼운 슬립은 CPU 클럭만 멈추고 주변장치와 RAM은 살려 두어 금방 깨고, 깊은 슬립은 대부분의 전원을 끄고 RTC 타이머나 핀 하나만 깨어 있어 μA 이하까지 내려가지만 깨어날 때 다시 초기화해야 한다. ESP32라면 Light-sleep은 CPU·RAM 상태를 보존하고, Deep-sleep은 CPU와 대부분의 RAM을 끈다.

평균 전류 = (깨어 있는 전류 × 깨어 있는 시간 + 잠든 전류 × 잠든 시간) / 주기이고, 수명 ≈ 전지 용량 / 평균 전류다. 아래 예에서 잠든 전류가 10 μA가 아니라 100 μA(잊고 끄지 않은 LED 하나, 풀업 저항으로 새는 전류)라면 수명이 약 344일에서 79일로 준다. 그래서 저전력 설계는 쓰지 않는 주변장치의 클럭 끄기, 핀 상태 정리, 깨어 있는 시간 줄이기(한꺼번에 모아 보내기)의 싸움이다. 실제 수명은 전지의 자가 방전과 온도, 전압이 떨어지는 끝부분 때문에 계산보다 짧다.

잠깐 깨어 큰 전류를 쓰고 오래 잠드는 전류 그래프와 평균 전류·배터리 수명20 mA10 μA깨어 50 ms주기 60 s (그림은 비율대로가 아님)평균 = (20 mA × 50 ms + 0.01 mA × 59950 ms) / 60000 ms = 26.7 μA220 mAh 전지로 약 344일 — 늘 깨어 있으면 11시간
1분마다 50 ms 깨어 측정·전송하고 나머지는 잠드는 센서. 깨어 있는 시간은 0.1%도 안 되지만 평균 전류의 대부분을 차지한다.

전력과 함께 보는 안전장치가 둘 있다. 워치독 타이머는 프로그램이 정해진 시간 안에 "살아 있다"고 신호를 주지 않으면 칩을 다시 시작시켜, 펌웨어가 멈춰도 기기가 스스로 살아나게 한다. 브라운아웃 감지는 전지가 닳아 전압이 기준 밑으로 내려가면 칩을 리셋 상태로 붙잡아, 불안정한 전압에서 플래시를 잘못 쓰는 일을 막는다.

디지털 논리: 게이트, 플립플롭, 클럭

MCU 안의 모든 것은 결국 0과 1을 다루는 논리 게이트로 만든다. AND·OR·NOT·XOR 같은 게이트는 트랜지스터 몇 개로 만들고, NAND 하나만 있으면 다른 모든 게이트를 조합할 수 있다. 입력이 바뀌면 출력이 (아주 짧은 지연 뒤) 곧바로 따라 바뀌는 회로를 조합 논리라 한다. 덧셈기·비교기·멀티플렉서가 그 예이고, 반가산기를 여러 개 이어 붙인 것이 CPU 산술 장치의 바탕이다.

XOR 게이트와 AND 게이트로 만든 반가산기 회로와 진리표ABXORAND합 S올림 CABSC00000110101011011 + 1 = 10(2진수): 합 0, 올림 1. 이런 덧셈기를 이어 붙이면 CPU의 ALU가 된다
반가산기. 두 비트를 더해 합(XOR)과 올림(AND)을 낸다. 진리표는 게이트 함수로 계산했다.

기억하려면 순차 논리가 필요하다. 가장 흔한 부품인 D 플립플롭은 클럭이 0에서 1로 오르는 순간(상승 에지)의 입력 D를 붙잡아 다음 에지까지 출력 Q로 내보낸다. 플립플롭 32개를 나란히 두면 32비트 레지스터, 덧셈기와 이으면 카운터(타이머), 조건에 따라 다음 상태를 고르면 상태 기계가 된다. 모든 플립플롭이 같은 클럭에 맞춰 움직이기 때문에, 게이트 지연이 제각각이어도 다음 에지 전에만 값이 자리 잡으면 회로 전체가 질서 있게 돈다. 이 "다음 에지 전까지"가 최대 클럭 주파수를 정한다.

클럭, D, Q 세 파형. 클럭 상승 에지에서만 Q가 D를 따라간다CLKDQ빨간 점선 = 클럭 상승 에지. 그 순간의 D만 Q로 넘어간다
D 플립플롭. D가 클럭 사이에 흔들려도 Q는 상승 에지 순간의 값만 따라간다. Q 파형은 플립플롭 함수로 계산했다.

FPGA와 MCU

MCU는 이미 만들어진 CPU가 명령을 하나씩 실행한다. FPGA는 CPU가 아니라, 작은 진리표(LUT)와 플립플롭 수천~수백만 개를 배선으로 마음대로 이을 수 있는 칩이다. Verilog·VHDL 같은 하드웨어 기술 언어로 회로를 적으면, 도구가 그것을 LUT 배치와 배선으로 바꾼 비트스트림을 만든다. 칩에 올리면 그 회로가 실제로 생기고, 모든 부분이 같은 클럭에 동시에 돈다.

그래서 FPGA는 지연이 클럭 단위로 정확히 정해져야 하거나(모터 제어, 고속 통신 규격), 같은 계산을 대량으로 병렬 처리해야 할 때(영상 처리, 신호 처리, 칩을 만들기 전 시제품) 쓴다. 대신 같은 일을 MCU로 할 수 있다면 MCU가 훨씬 싸고, 전력이 적고, 개발이 쉽다. 아예 회로를 실리콘에 굳힌 것이 ASIC이다. RP2040의 PIO처럼, 작은 상태 기계를 MCU 안에 넣어 둘 사이를 메우는 칩도 있다.

왼쪽은 명령을 하나씩 실행하는 MCU, 오른쪽은 논리 칸을 배선으로 이어 동시에 동작하는 FPGAMCU — 명령을 차례로LOAD r0, [센서]MUL r0, r0, #3ADD r0, r0, #7STORE [출력], r0한 클럭에 한(또는 몇) 명령 — 바꾸기 쉬움FPGA — 회로를 만든다LUTLUTLUTLUTLUTLUTLUTLUTLUTLUTLUTLUTLUTLUTLUTLUTLUTLUTLUTLUT색칠한 칸이 모두 같은 클럭에 함께 동작비트스트림을 다시 올리면 회로가 바뀐다 — 정해진 지연·대량 병렬이 필요할 때 쓴다
MCU는 같은 하드웨어로 명령을 차례로, FPGA는 계산마다 회로를 따로 만들어 동시에 처리한다.

개발 흐름: 크로스 컴파일, 굽기, 디버깅

MCU용 프로그램(펌웨어)은 PC에서 만들어 칩에 넣는다. PC와 다른 CPU용 기계어를 만드는 크로스 컴파일러(Arm Cortex-M이면 arm-none-eabi-gcc나 Clang)를 쓰고, 링커 스크립트가 코드는 플래시 주소에, 변수는 SRAM 주소에 놓도록 정한다. OS가 없으니 main 전에 스택 포인터를 정하고 변수를 초기화하는 시작 코드와, 리셋·인터럽트 때 뛸 주소를 담은 벡터 표도 이미지에 함께 들어간다. 컴파일러와 링커 자체는 컴파일러 노트에서 다룬다.

만든 이미지를 플래시에 넣는 것을 굽기(flashing)라 한다. 가장 강력한 길은 디버그 프로브다. Arm 칩은 선 두 가닥의 SWD나 네 가닥의 JTAG로 칩 내부의 디버그 장치에 닿아, 플래시를 쓰고, CPU를 멈추고, 한 줄씩 실행하고, 레지스터와 메모리를 들여다볼 수 있다. PC에서는 OpenOCD 같은 프로그램이 프로브를 GDB와 이어 준다. 프로브가 없어도 Pico는 BOOTSEL 버튼을 누른 채 연결해 나타나는 USB 드라이브에 .uf2 파일을 끌어 놓고, ESP32는 USB 시리얼로 esptool을 써서, Arduino는 미리 들어 있는 시리얼 부트로더로 굽는다.

코드가 크로스 컴파일러와 링커를 거쳐 이미지가 되고, GDB와 OpenOCD, 디버그 프로브를 거쳐 MCU에 굽고 디버깅하는 흐름빌드 (PC)C·C++·Rust 코드main.c크로스 컴파일러arm-none-eabi-gcc링커 스크립트플래시·RAM 주소 배치ELF · .bin · .uf2굽는 이미지굽기·디버깅GDB중단점·변수 보기OpenOCDGDB 서버디버그 프로브J-Link·CMSIS-DAPMCUSWD 2선 / JTAG 4선프로브 없이도: USB 부트로더(Pico는 BOOTSEL로 .uf2 끌어 놓기, Arduino는 시리얼 부트로더)printf 대신 UART 로그, 로직 분석기로 버스 파형 보기도 흔한 디버깅 수단
펌웨어 개발 흐름. 위 줄은 PC에서 이미지를 만드는 과정, 아래 줄은 프로브를 거쳐 칩(색칠)에 굽고 멈춰 보는 경로다.

많이 쓰는 보드

처음 시작하기 좋은 보드는 대개 아래 중 하나다. Arduino는 라이브러리와 예제가 가장 많고, Pico는 값싸고 데이터시트가 친절하며 MicroPython으로 바로 시작할 수 있고, ESP32는 Wi-Fi·Bluetooth가 칩에 들어 있어 IoT 장치에 많이 쓴다. 이 밖에 산업 현장에서 가장 흔한 것은 ST의 STM32 계열이고, Nordic nRF52(블루투스 저전력)도 많이 쓴다.

보드칩코어클럭SRAM플래시무선
Arduino Uno R3ATmega328PAVR 8비트16 MHz2 KB32 KB(내장)없음
Arduino Uno R4 MinimaRenesas RA4M1Arm Cortex-M448 MHz32 KB256 KB(내장)없음(WiFi 판은 ESP32-S3 추가)
Raspberry Pi PicoRP2040Cortex-M0+ ×2133 MHz264 KB2 MB(외장 QSPI)없음(Pico W는 Wi-Fi)
Raspberry Pi Pico 2RP2350Cortex-M33 ×2 또는 RISC-V Hazard3 ×2150 MHz520 KB4 MB(외장 QSPI)없음(Pico 2 W는 Wi-Fi)
ESP32 개발 보드ESP32Xtensa LX6 ×2240 MHz520 KB보통 4 MB(모듈 안 외장)Wi-Fi · Bluetooth
Arduino Uno R3, Uno R4, Pico, Pico 2, ESP32의 SRAM 크기와 클럭을 막대로 비교SRAM (로그 눈금)클럭Arduino Uno R32 KB16 MHzArduino Uno R4 Minima32 KB48 MHzRaspberry Pi Pico264 KB133 MHzRaspberry Pi Pico 2520 KB150 MHzESP32 개발 보드520 KB240 MHz
보드별 SRAM(로그 눈금)과 기본 클럭. 가장 작은 Uno R3와 가장 큰 Pico 2·ESP32는 RAM이 260배 차이 난다.

핵심 자료

칩 하나의 데이터시트를 처음부터 끝까지 읽어 보는 것이 가장 빠른 공부다. RP2040 데이터시트가 특히 읽기 좋다.

자료종류요지링크
RP2040 Datasheet데이터시트Raspberry Pi Pico의 칩. 주소 맵, GPIO·타이머·PWM·ADC·UART·SPI·I²C 레지스터, 독특한 PIO까지 한 문서에 다 있어 MCU 구조를 공부하기 좋다.Raspberry Pi
FreeRTOS — Mutexes공식 문서뮤텍스와 이진 세마포어의 차이, 우선순위 상속이 어떻게 역전을 줄이는지.freertos.org
What really happened on Mars? (Mars Pathfinder)사례화성 탐사선이 우선순위 역전으로 거듭 재부팅됐고, 지구에서 뮤텍스의 우선순위 상속을 켜 고친 이야기.Cornell
Understanding the I2C Bus응용 노트오픈 드레인과 풀업, 시작·정지 조건, ACK, 주소 지정을 파형으로 설명한다.Texas Instruments
Introduction to the Controller Area Network (CAN)응용 노트차동 신호, 우성·열성 비트, ID로 하는 중재와 프레임 구조.Texas Instruments
From Nand to Tetris무료 강의NAND 게이트 하나에서 시작해 가산기·메모리·CPU·컴파일러·OS까지 직접 쌓아 올린다.nand2tetris.org