|
|
분기 예측기 (Branch Predictor) — "다음에 어떤 명령이 올지" 미리 예측
비순서 실행 엔진 (Out-of-Order Execution) — 순서를 바꿔 더 빠르게 처리
대용량 캐시 (L1/L2/L3) — 자주 쓰는 데이터를 근처에 보관
복잡한 제어 로직 — 운영체제·프로그램의 복잡한 명령 처리
이 복잡한 구조 때문에 CPU 코어 하나가 칩 면적을 약 9mm² 씩 차지합니다.
GPU 코어는 일부러 단순하게 만듭니다
GPU 코어는 딱 두 가지만 잘하면 됩니다.
덧셈
곱셈
분기 예측도 없고, 복잡한 제어 로직도 없습니다. 덕분에 GPU 코어 하나의 면적이 약 0.037mm², CPU 코어의 1/250 수준입니다. 같은 칩 면적에 단순한 코어를 수천~수만 개 빼곡히 넣을 수 있는 이유입니다.
AI 활용
비유로 정리하면
CPU = 회의실·도서관·응접실이 갖춰진 초호화 사무실 24개
GPU = 책상만 달랑 있는 단순 작업실 16,384개
같은 건물 면적이라면 공장이 훨씬 많은 사람을 수용할 수 있는 것과 같은 원리입니다.
단, GPU의 한계도 있습니다
GPU 코어는 단순한 대신 복잡한 판단이 필요한 작업은 처리하지 못합니다. 운영체제 실행, 웹 브라우저, 복잡한 게임 AI 로직은 여전히 CPU가 담당해야 합니다. 그래서 컴퓨터에는 CPU와 GPU가 항상 함께 존재하는 것입니다.
▶ 3.2 역사적인 우연 — AI와 GPU의 극적인 만남
2010년대, 인공지능(AI) 과학자들은 딥러닝 연구를 하다 거대한 장벽에 부딪힙니다.
| 🔍 AI 학습의 핵심 연산 AI가 사람처럼 학습하려면 수십억 개의 데이터를 동시에 처리(행렬 곱셈)해야 합니다. 예) '고양이 사진' 100만 장으로 학습 → 각 픽셀 × 가중치 × 수백만 번 반복 이 연산량이 너무 방대해 CPU로는 몇 달~몇 년이 걸렸습니다. |
그때 누군가 엄청난 사실을 깨달았습니다. "AI가 학습할 때 쓰는 행렬 계산이, 화면에 픽셀을 그릴 때 쓰는 수학과 완전히 똑같잖아?!"
| ⚡ 혁명의 순간 (2012년, AlexNet) 과학자들이 그래픽용 GPU에 AI 학습 데이터를 집어넣어 실험했습니다. 결과: CPU로 몇 달이 걸리던 AI 학습을 GPU는 단 며칠 만에 완료! 2012년 NVIDIA GPU 2개로 학습한 'AlexNet'이 이미지 인식 대회에서 압도적 1위를 차지하며 AI 시대의 서막을 열었습니다. 그래픽을 부드럽게 만들기 위해 발전한 기술이 잠들어 있던 인공지능을 깨우는 마법의 지팡이가 된 것입니다. |
📐 왜 GPU의 덧셈·곱셈이 AI에 딱 맞을까?
AI 학습이 복잡해 보이지만, 내부를 들여다보면 결국 덧셈과 곱셈의 폭발적인 반복입니다.
AI가 고양이 사진을 학습할 때 일어나는 일을 극도로 단순화하면 이렇습니다.
픽셀값 × 가중치 + 픽셀값 × 가중치 + 픽셀값 × 가중치 … = 결과값
이것이 수십억 번 반복됩니다. 수학 용어로 행렬 곱셈(Matrix Multiplication)이라고 합니다.
그런데 모니터에 픽셀 하나를 그릴 때의 연산을 보면 어떨까요?
R × 밝기 + G × 밝기 + B × 밝기 = 최종 색상값
형태가 완전히 똑같습니다. 둘 다 덧셈과 곱셈의 반복, 그것뿐입니다.
GPU는 복잡한 판단 없이 덧셈·곱셈만 수만 개 코어로 동시에 처리하도록 설계되어 있습니다. AI 학습에 필요한 연산과 수학적 구조가 완전히 일치했던 것입니다.
그래픽을 위해 수십 년간 갈고닦은 GPU의 병렬 연산 능력이, 어느 날 잠들어 있던 AI를 깨우는 열쇠가 된 것은 우연이 아니라 수학적 필연이었습니다.
▶ 3.3 HBM — AI 시대가 만든 초고속 데이터 고속도로
AI 시대가 열리며 GPU는 쉴 새 없이 데이터를 처리하기 시작했습니다. 그런데 새로운 문제가 생겼습니다.
| 🚧 메모리 병목 현상 (Memory Bottleneck) GPU 코어: 수만 명의 일꾼이 초고속으로 계산 준비 완료! 일반 메모리: 재료(데이터)를 실어 나르는 수레가 10대뿐... GPU는 놀고, 메모리에서 데이터가 오기만 기다리는 상황 발생. 이것이 'Memory Bandwidth Bottleneck' 문제입니다. |
이를 해결하기 위해 등장한 것이 HBM(고대역폭 메모리, High Bandwidth Memory)입니다.
| 🏢 HBM의 원리 — 아파트처럼 쌓는다! 기존 방식: 메모리 칩을 옆으로 나란히 배치 → 연결 선이 좁음 HBM 방식: 메모리 칩을 수직으로 아파트처럼 높이 쌓기 (3D 스태킹) + 칩과 칩 사이에 수천 개 미세한 구멍(TSV) 뚫기 → 데이터를 수천 개 통로로 동시에 쏟아부음! • 기존 GDDR 메모리 대역폭: 약 500GB/s • HBM3E 메모리 대역폭: 약 1,280GB/s (약 2.5배) 대한민국 SK하이닉스·삼성전자가 전 세계 HBM 시장을 주도하고 있습니다. |
| 🔗 RGB LED 점 하나에서 시작된 거대한 연결고리 RGB LED 점(Pixel) → 화면 구성 → 방대한 데이터 처리 문제 ↓ GPU 탄생 (그래픽 병렬 처리용) ↓ AI 연구자들이 GPU의 행렬 연산 능력 발견 (2012년) ↓ AI 시대 개막 → GPU 수요 폭발 → NVIDIA 시가총액 세계 1위 ↓ GPU 메모리 병목 문제 → HBM 탄생 → 한국 반도체 기업 세계 주도 브레드보드에 꽂은 이 작은 RGB LED 점 하나가 인류의 미래를 바꾸는 기술의 씨앗이었습니다. |
4장. 브레드보드 회로 배선
▶ 4.1 부품 목록
브레드보드 배선도 (Wokwi) — OLED + RGB LED
브레드보드 실물 배선 — OLED + RGB LED
| 부품 | 수량 | 비고 |
| 아두이노 Uno R3 | 1 | 또는 호환 보드 |
| OLED 128x64 I2C (SSD1306) | 1 | 1편부터 계속 사용 |
| RGB LED 모듈 (Common Anode) | 1 | 이번 편 신규 추가 |
| 저항 330Ω | 3 | R·G·B 핀 각각 1개 |
| 브레드보드 | 1 | 대형 권장 (830홀) |
| 점퍼 선 | 다수 | M-M 타입 |
▶ 4.2 RGB LED 핀 연결표
Common Anode 방식: VCC는 5V 공통, R·G·B 핀은 330Ω 저항을 통해 PWM 핀에 연결합니다.
| RGB LED 핀 | 연결 | 아두이노 핀 | 비고 |
| VCC (공통 양극) | → 직접 | 5V | 전원 레일 빨간선 |
| R (빨강) | → 330Ω → | D11 | PWM 핀 (~) |
| G (초록) | → 330Ω → | D10 | PWM 핀 (~) |
| B (파랑) | → 330Ω → | D9 | PWM 핀 (~) |
| 📌 330Ω 저항이 필요한 이유 LED에 전류를 직접 흘리면 과전류로 즉시 소손됩니다. 330Ω 저항이 전류를 약 10~15mA로 제한하여 LED를 보호합니다. 각 색상(R·G·B)마다 독립적으로 저항을 연결해야 합니다. 계산: (5V - 2V) / 330Ω ≈ 9mA → LED 안전 동작 범위 |
| ⚠️ Common Anode — Low Active 핵심 정리 VCC(5V)가 공통으로 연결되어 있어서 신호가 반전됩니다. • analogWrite(pin, 0) → 전압차 5V → 최대 밝기 (ON) • analogWrite(pin, 255) → 전압차 0V → 완전히 꺼짐 (OFF) 코드에서 항상 255에서 빼주어야 합니다: analogWrite(R_PIN, 255 - r_value); 반대로 Common Cathode 모듈은 analogWrite 값 그대로 사용합니다. 모듈 구입 시 사양을 반드시 확인하세요. |
5장. 라이브러리 설치
RGB LED는 아두이노 기본 함수 analogWrite()만으로 동작합니다. OLED는 1편에서 설치한 라이브러리를 그대로 사용합니다. 추가 설치가 필요 없습니다.
| 라이브러리 | 용도 | 설치 상태 |
| Wire.h | I2C 통신 (OLED) | 아두이노 기본 내장 |
| Adafruit_GFX.h | OLED 그래픽 | 1편에서 설치 완료 |
| Adafruit_SSD1306.h | SSD1306 드라이버 | 1편에서 설치 완료 |
| 없음 (추가 불필요) | RGB LED (analogWrite) | 추가 설치 불필요 ✅ |
6장. 소스코드
▶ 6.1 동작 설명
| 항목 | 내용 |
| RGB 색상 순환 | millis()로 무지개 7색 자동 순환 (비블로킹, 1초 간격) |
| OLED 수치 표시 | 현재 R·G·B 값(0~255)을 숫자로 실시간 표시 |
| OLED 바 그래프 | R·G·B 각각 독립적인 수평 바 그래프 표시 |
| 색상 이름 표시 | 현재 색상 이름(RED/GREEN/BLUE/YELLOW 등) OLED 표시 |
| millis() 비블로킹 | 색상 전환(1초), OLED 갱신(200ms) 독립 동작 |
▶ 6.2 OLED 화면 레이아웃
| Y 위치 | 크기 | 표시 내용 |
| y=0 | TextSize(1) | RGB LED MONITOR 타이틀 |
| y=10 | ───── | 구분선 |
| y=14 | TextSize(1) | COLOR: [색상 이름] |
| y=23 | ───── | 구분선 |
| y=27 | TextSize(1) | R: 수치 + 바 그래프 |
| y=38 | TextSize(1) | G: 수치 + 바 그래프 |
| y=49 | TextSize(1) | B: 수치 + 바 그래프 |
▶ 6.3 전체 소스코드
//================================================ // 민선생의 전자공방 - 브레드보드 IO 인터페이스 7편 // RGB LED 기본 제어 + OLED 수치/바 그래프 표시 // 보드: Arduino Uno R3 // OLED: SSD1306 128x64 I2C (A4=SDA, A5=SCL, 0x3C) // RGB LED (Common Anode): R=D11, G=D10, B=D9 //================================================ #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> // ── OLED 설정 ──────────────────────────────── #define SCREEN_W 128 #define SCREEN_H 64 #define OLED_ADDR 0x3C #define OLED_RESET -1 // ── RGB LED 핀 (Common Anode = Low Active) ── #define RGB_R 11 // PWM 핀 (~) #define RGB_G 10 // PWM 핀 (~) #define RGB_B 9 // PWM 핀 (~) // ── 색상 전환 주기 ─────────────────────────── #define COLOR_INTERVAL 1000 // 1초마다 색상 전환 #define OLED_INTERVAL 200 // 0.2초마다 OLED 갱신 // ── 무지개 7색 정의 ────────────────────────── // {R, G, B, 색상이름} const int COLORS[][3] = { {255, 0, 0}, // 0: 빨강 RED {255, 127, 0}, // 1: 주황 ORANGE {255, 255, 0}, // 2: 노랑 YELLOW { 0, 255, 0}, // 3: 초록 GREEN { 0, 0, 255}, // 4: 파랑 BLUE { 75, 0, 130}, // 5: 남색 INDIGO {148, 0, 211}, // 6: 보라 VIOLET {255, 255, 255}, // 7: 흰색 WHITE { 0, 0, 0}, // 8: 꺼짐 OFF }; const char* COLOR_NAMES[] = { "RED","ORANGE","YELLOW","GREEN", "BLUE","INDIGO","VIOLET","WHITE","OFF" }; #define NUM_COLORS 9 // ── 객체 생성 ──────────────────────────────── Adafruit_SSD1306 display(SCREEN_W, SCREEN_H, &Wire, OLED_RESET); // ── 전역 변수 ──────────────────────────────── unsigned long prevColor = 0; // 색상 전환 타이머 unsigned long prevOLED = 0; // OLED 갱신 타이머 int colorIdx = 0; // 현재 색상 인덱스 int curR = 0, curG = 0, curB = 0; // 현재 RGB 값 // ── RGB LED 출력 함수 ──────────────────────── // Common Anode: 255에서 빼서 출력 void setRGB(int r, int g, int b) { curR = r; curG = g; curB = b; analogWrite(RGB_R, 255 - r); // 반전 출력 analogWrite(RGB_G, 255 - g); analogWrite(RGB_B, 255 - b); } // ── OLED 대시보드 표시 ─────────────────────── void showDashboard() { display.clearDisplay(); display.setTextColor(SSD1306_WHITE); // 타이틀 display.setTextSize(1); display.setCursor(15, 0); display.print("RGB LED MONITOR"); display.drawLine(0, 10, 127, 10, SSD1306_WHITE); // 색상 이름 display.setCursor(0, 13); display.print("COLOR: "); display.print(COLOR_NAMES[colorIdx]); display.drawLine(0, 23, 127, 23, SSD1306_WHITE); // ── R 수치 + 바 그래프 ────────────────── display.setCursor(0, 26); display.print("R:"); display.setCursor(12, 26); // 3자리 고정 출력 (자리 흔들림 방지) if(curR < 100) display.print(" "); if(curR < 10) display.print(" "); display.print(curR); // R 바 그래프 display.drawRect(34, 25, 93, 8, SSD1306_WHITE); int rBar = map(curR, 0, 255, 0, 91); display.fillRect(35, 26, rBar, 6, SSD1306_WHITE); // ── G 수치 + 바 그래프 ────────────────── display.setCursor(0, 37); display.print("G:"); display.setCursor(12, 37); if(curG < 100) display.print(" "); if(curG < 10) display.print(" "); display.print(curG); display.drawRect(34, 36, 93, 8, SSD1306_WHITE); int gBar = map(curG, 0, 255, 0, 91); display.fillRect(35, 37, gBar, 6, SSD1306_WHITE); // ── B 수치 + 바 그래프 ────────────────── display.setCursor(0, 48); display.print("B:"); display.setCursor(12, 48); if(curB < 100) display.print(" "); if(curB < 10) display.print(" "); display.print(curB); display.drawRect(34, 47, 93, 8, SSD1306_WHITE); int bBar = map(curB, 0, 255, 0, 91); display.fillRect(35, 48, bBar, 6, SSD1306_WHITE); // ── 하단: 순서 표시 ───────────────────── display.setCursor(0, 57); display.print(colorIdx+1); display.print("/"); display.print(NUM_COLORS); display.print(" ["); for(int i=0; i<NUM_COLORS; i++){ display.print(i==colorIdx ? "*" : "-"); } display.print("]"); display.display(); } // ───────────────────────────────────────────── void setup() { Serial.begin(9600); pinMode(RGB_R, OUTPUT); pinMode(RGB_G, OUTPUT); pinMode(RGB_B, OUTPUT); // 시작 시 LED 끄기 (Common Anode → 255 출력) analogWrite(RGB_R, 255); analogWrite(RGB_G, 255); analogWrite(RGB_B, 255); // OLED 초기화 if(!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) { while(1); // 연결 실패 시 정지 } display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(20, 25); display.print("RGB LED Ready!"); display.display(); delay(1500); // 첫 번째 색상 적용 setRGB(COLORS[0][0], COLORS[0][1], COLORS[0][2]); } // ───────────────────────────────────────────── void loop() { unsigned long now = millis(); // ── 색상 전환: 1초마다 ────────────────── if (now - prevColor >= COLOR_INTERVAL) { prevColor = now; colorIdx = (colorIdx + 1) % NUM_COLORS; // 순환 setRGB(COLORS[colorIdx][0], COLORS[colorIdx][1], COLORS[colorIdx][2]); Serial.print(COLOR_NAMES[colorIdx]); Serial.print(" R:"); Serial.print(curR); Serial.print(" G:"); Serial.print(curG); Serial.print(" B:"); Serial.println(curB); } // ── OLED 갱신: 0.2초마다 ──────────────── if (now - prevOLED >= OLED_INTERVAL) { prevOLED = now; showDashboard(); } }
7장. 핵심 코드 해설
▶ 7.1 setRGB() — Common Anode 반전 처리
Common Anode 방식의 핵심은 출력값을 255에서 빼는 것입니다. 이 함수 하나에 모든 반전 처리를 담아두면 나머지 코드는 일반적인 방식(0=끔, 255=최대)으로 작성할 수 있습니다.
| void setRGB(int r, int g, int b) { curR = r; curG = g; curB = b; // 원본값 저장 (OLED 표시용) analogWrite(RGB_R, 255 - r); // 반전: r=255 → 0 출력 → 최대 밝기 analogWrite(RGB_G, 255 - g); // 반전: g=0 → 255 출력 → 꺼짐 analogWrite(RGB_B, 255 - b); } // 사용 예 — 값은 항상 '정상적인' 0~255로 입력 setRGB(255, 0, 0); // 빨강 최대 setRGB( 0, 255, 0); // 초록 최대 setRGB(255, 255, 0); // 노랑 (R+G 혼합) setRGB( 0, 0, 0); // 완전히 꺼짐 |
▶ 7.2 2차원 배열로 색상 관리
여러 색상을 switch-case나 if-else로 관리하면 코드가 길어집니다. 2차원 배열로 색상표를 만들면 인덱스 하나로 색상을 선택할 수 있습니다.
| // 색상표: {R, G, B} const int COLORS[][3] = { {255, 0, 0}, // 0: RED {255, 127, 0}, // 1: ORANGE {255, 255, 0}, // 2: YELLOW // ... 이하 생략 }; // 인덱스로 색상 선택 — 코드가 매우 간결! setRGB(COLORS[colorIdx][0], // R값 COLORS[colorIdx][1], // G값 COLORS[colorIdx][2]); // B값 // 다음 색상으로 순환 (마지막에서 다시 처음으로) colorIdx = (colorIdx + 1) % NUM_COLORS; |
▶ 7.3 숫자 자릿수 고정 — 화면 흔들림 방지
analogRead 값이 100→99→98로 줄어들 때 3자리→2자리→2자리가 되면 뒤에 오는 내용이 밀려 화면이 흔들립니다. 자릿수를 고정하면 깔끔합니다.
| // ❌ 흔들리는 방식 display.print(curR); // 100→99→9 로 자리수 변하면 화면 흔들림 // ✅ 3자리 고정 방식 if(curR < 100) display.print(" "); // 100 미만: 앞에 공백 1개 if(curR < 10) display.print(" "); // 10 미만: 앞에 공백 2개 display.print(curR); // 결과: '255', ' 99', ' 9' → 항상 3자리 고정 |
▶ 7.4 OLED 진행 표시 바 — 현재 순서 시각화
숫자(1/9)만으로는 진행 상황이 직관적이지 않습니다. 별표(*)로 현재 위치를 표시하면 한눈에 파악됩니다.
| // 예: colorIdx=3 일 때 → [---*-----] display.print("["); for(int i=0; i<NUM_COLORS; i++){ display.print(i == colorIdx ? "*" : "-"); } display.print("]"); // 출력: [---*-----] ← 4번째 색상임을 직관적으로 표시 |
▶ 7.5 millis() 비블로킹 — 2개 타이머 독립 동작
색상 전환(1초)과 OLED 갱신(0.2초)이 각자 독립적으로 동작합니다. OLED는 색상보다 5배 빠르게 갱신되어 수치가 부드럽게 업데이트됩니다.
| unsigned long prevColor = 0; // 색상 전환 전용 unsigned long prevOLED = 0; // OLED 갱신 전용 void loop() { unsigned long now = millis(); if(now - prevColor >= 1000) { // 1초마다 색상 전환 prevColor = now; // 다음 색상으로... } if(now - prevOLED >= 200) { // 0.2초마다 OLED 갱신 prevOLED = now; showDashboard(); } // loop()가 계속 돌며 두 조건을 독립적으로 체크! } |
▶ 7.6 주요 함수 정리
| 함수/개념 | 설명 |
| setRGB(r,g,b) | RGB 색상 출력. Common Anode 반전(255-값) 처리 |
| analogWrite(pin,val) | PWM 출력 0~255. RGB_R에 0 → LED 최대 밝기 |
| COLORS[][3] | 2차원 배열 색상표. [인덱스][0~2] = R·G·B |
| colorIdx % NUM_COLORS | 나머지 연산으로 인덱스 순환 (마지막→처음) |
| map(val, 0,255, 0,91) | RGB 수치 0~255를 바 그래프 픽셀 0~91로 변환 |
| 3자리 고정 출력 | 공백 패딩으로 자릿수 고정 → 화면 흔들림 방지 |
8장. 동작 확인 및 테스트
▶ 8.1 동작 확인 순서
| 단계 | 확인 내용 | 정상 동작 |
| 1 | 업로드 직후 | 'RGB LED Ready!' OLED 표시 |
| 2 | 1.5초 후 | 대시보드 진입, 첫 색상(RED) 표시 |
| 3 | 1초마다 | ORANGE→YELLOW→GREEN→BLUE→INDIGO→VIOLET→WHITE→OFF 순환 |
| 4 | OLED 바 그래프 | 색상 변할 때마다 R·G·B 바 그래프 즉시 갱신 |
| 5 | 진행 표시 바 | [*--------] → [-*-------] → 순서대로 이동 |
| 6 | 시리얼 모니터 | 1초마다 색상 이름 + R·G·B 수치 출력 |
아두이노와 RGB LED 무지개 순환 + OLED 바 그래프
▶ 8.2 시리얼 모니터 출력 예시
| RGB LED Ready! RED R:255 G:0 B:0 ORANGE R:255 G:127 B:0 YELLOW R:255 G:255 B:0 GREEN R:0 G:255 B:0 BLUE R:0 G:0 B:255 INDIGO R:75 G:0 B:130 VIOLET R:148 G:0 B:211 WHITE R:255 G:255 B:255 OFF R:0 G:0 B:0 RED R:255 G:0 B:0 ← 다시 처음으로 |
| 🎁 보너스 노트 — RGB와 16진수 색상 코드의 관계 웹/앱 개발에서 자주 보이는 #FF5733 같은 색상 코드의 정체는 R·G·B를 16진수로 표현한 것입니다. #FF5733 = R:0xFF(255) G:0x57(87) B:0x33(51) • #000000 = R:0 G:0 B:0 → 검정 • #FFFFFF = R:255 G:255 B:255 → 흰색 • #1A6FA3 = R:26 G:111 B:163 → 민선생의 전자공방 블로그 파란색! RGB LED로 색을 직접 섞어보면 HTML 색상 코드가 훨씬 친숙하게 느껴집니다. |
| 다음 편 예고 — 8편 ▶ 8편: MPU-6050 자이로·가속도 센서 + OLED 기울기 대시보드 • MPU-6050 I2C 통신 원리와 가속도·자이로 원시 데이터(Raw Data) 읽기 • Roll·Pitch 각도 계산 + 상보필터(Complementary Filter)로 노이즈 제거 • OLED에 기울기 각도 + 수평 인디케이터 바 실시간 표시 • 인버티드 펜듈럼 최종 프로젝트의 핵심 센서 — 각도 제어의 첫걸음! |
민선생의 전자공방 | blog.naver.com/hansikmin
태그#아두이노#RGB#LED#RGBLED#Common#Anode#PWM제어#가법혼색#아두이노LED#OLED#SSD1306#아두이노OLED#millis비블로킹#analogWrite#픽셀#GPU#AI반도체#HBM#고대역폭메모리#NVIDIA#AlexNet#딥러닝#브레드보드실습#아두이노프로젝트#민선생의전자공방#전자공방#피지컬코딩#아두이노입문#24비트트루컬러#2차원배열 태그수정