ปลุกชีพจอมอนิเตอร์เก่า! DIY การ์ดจอ PICO VGA BOARD ด้วย Raspberry Pi Pico 🖥️✨

Pico VGA Board 1.0

สวัสดีชาว Maker และสาย Tech ทุกคนครับ! วันนี้เรามีโปรเจกต์สุดเนิร์ดและน่าตื่นเต้นมานำเสนอ นั่นคือการสร้าง "Pico VGA Board"

หัวใจหลักของโปรเจกต์นี้คือการจับคู่บอร์ด Raspberry Pi PICO W เข้ากับพอร์ต D-SUB15 (พอร์ต VGA สมัยก่อน) เพื่อเนรมิตให้จอมอนิเตอร์ TFT ธรรมดาๆ กลายเป็นจอแสดงผลขนาดใหญ่ที่คุณจะเอาไปรันกราฟิก โชว์ข้อมูล หรือแม้แต่เล่นเกมเรโทรก็ทำได้สบายๆ!

โปรเจกต์นี้ดัดแปลงมาจาก VGA Library ดั้งเดิม ที่ออกแบบมาสำหรับจอ CRT จอตู้สมัยก่อน แต่ผู้สร้างได้ปรับจูนโค้ดใหม่ให้สามารถใช้งานร่วมกับจอ LCD VGA ในยุคปัจจุบันได้อย่างลื่นไหล ว่าแล้วเราไปดูสเตปการสร้างและโค้ดของโปรเจกต์นี้กันเลยครับ!

ไอเทมที่ต้องมี (Supplies)

เตรียมอุปกรณ์บนโต๊ะช่างของคุณให้พร้อมตามลิสต์นี้เลยครับ:

  • แผ่นวงจร Custom PCB Board
  • พอร์ต D-SUB 15 VGA Connector
  • DC Barrel jack (พอร์ตเสียบอะแดปเตอร์)
  • ตัวต้านทาน 330 Ohms (แพ็กเกจ 1206)
  • ตัวต้านทาน 10k (แพ็กเกจ 0805)
  • ตัวต้านทาน 1K (แพ็กเกจ 1206)
  • LED สีเขียว (แพ็กเกจ 0603)
  • พินเฮดเดอร์ตัวเมีย (Female header Pins CON20 x 2)
  • บอร์ด PICO 1 หรือ PICO W (ชิป RP2040 ตัวไหนก็ได้)

💡 Maker's Tip: เห็นลิสต์ชิ้นส่วนอิเล็กทรอนิกส์แล้วอยากเริ่มทำเลยใช่ไหม? ถ้าคุณไม่อยากเปลือยบอร์ดให้ฝุ่นเกาะ หรืออยากทำเคสสไตล์เรโทรแบบเครื่องเกมคอนโซล แนะนำให้ใช้ เครื่อง 3D Printer ปริ้นท์เคสครอบบอร์ดดูสิครับ!

หากเพื่อนๆ กำลังมองหา บอร์ด Raspberry Pi Pico, ชิ้นส่วนอิเล็กทรอนิกส์, และเส้นพลาสติก 3D Print (Filament) คุณภาพสูง แวะไปช้อปปิ้งของแท้พร้อมส่งได้ที่ Globalbyte เลยครับ พร้อมลุยทุกโปรเจกต์แน่นอน!

Step 1: พอร์ต VGA มันทำงานยังไง? (How VGA Port Works)

ก่อนจะเริ่มทำ เรามาเข้าใจหลักการของ VGA (Video Graphics Array) กันก่อนครับ มันคืออินเทอร์เฟซแบบอนาล็อกที่ต่างจาก HDMI หรือ DisplayPort ที่ส่งข้อมูลเป็นแพ็กเกจดิจิทัล เพราะ VGA จะส่ง "แรงดันไฟฟ้า (Voltages)" อย่างต่อเนื่องเพื่อบอกสีและจังหวะเวลาครับ

VGA ใช้สายสัญญาณอนาล็อก 3 เส้นหลักคือ แดง (Red), เขียว (Green), และ น้ำเงิน (Blue) โดยแต่ละเส้นจะรับแรงดันตั้งแต่ 0 ถึง 0.7V

  • ที่ 0V คือไม่มีแสง (มืดสนิท)
  • ที่ 0.7V คือสว่างเต็มที่
  • ถ้าเส้นสีแดงรับไฟ 0.7V ส่วนสีอื่น 0V จอจะแสดงสีแดงล้วน แต่ถ้ามา 0.7V พร้อมกันสามเส้น จอจะเป็นสีขาวครับ!

นอกจากนี้ยังมีสัญญาณดิจิทัลอีก 2 เส้นคือ HSYNC (Horizontal Sync) บอกจอว่าให้เริ่มวาดพิกเซลบรรทัดใหม่ และ VSYNC (Vertical Sync) บอกว่าให้เริ่มวาดเฟรมใหม่ ซึ่งกระบวนการนี้เกิดขึ้นราวๆ 60 ครั้งต่อวินาที (60 Hz) ครับ (พอร์ต VGA 15 พิน จะใช้ Pin 1-3 เป็น RGB, Pin 13 เป็น HSYNC และ Pin 14 เป็น VSYNC)

VGA Connector
VGA Pinout Pico VGA Library GitHub

Step 2: จัดการ PICO VGA Library

โปรเจกต์นี้ใช้ Library ที่ดัดแปลงมาจากต้นฉบับ เพื่อให้มันเข้ากันได้กับจอ LCD ยุคใหม่ คุณสามารถเข้าไปดาวน์โหลดและติดตั้ง Custom Library ฉบับสมบูรณ์ได้จาก GitHub ลิงก์นี้เลยครับ: PICO-VGA-BOARD GitHub

Step 3: การเชื่อมต่อ D-SUB15 กับ Pico เบื้องต้น

ผังการต่อสายเข้ากับบอร์ด Raspberry Pi Pico มีดังนี้ครับ:

  • Pin 1 (Red) ต่อเข้า GPIO18 ขนานด้วยตัวต้านทาน 1kΩ 4 ตัว
  • Pin 2 (Green) ต่อเข้า GPIO19 ขนานด้วยตัวต้านทาน 1kΩ 4 ตัว
  • Pin 3 (Blue) ต่อเข้า GPIO20 ขนานด้วยตัวต้านทาน 1kΩ 4 ตัว
  • (ค่าความต้านทานจะเป็นตัวกำหนดความสว่าง ยิ่งโอห์มสูงภาพยิ่งมืด แนะนำให้รวมแล้วอยู่ที่ 200–400Ω)
  • Pin 13 (HSYNC) ต่อเข้า GPIO16 และ Pin 14 (VSYNC) ต่อเข้า GPIO17
  • Pin 5, 6, 7, 8 และ 10 ต่อลง GND ให้หมด
Basic Connection Diagram
Breadboard Setup View Terminal Output Demo

Step 4: ทดสอบบน Breadboard Demo

ก่อนจะทำบอร์ดจริง เราเอาของมาเสียบสายจัมเปอร์บน Breadboard ดูก่อนครับ แล้วอัปโหลดโค้ด "RobCo Terminal" ซึ่งได้แรงบันดาลใจจากเทอร์มินัลสีเขียวสุดคลาสสิกในเกม Fallout! ลองก๊อปปี้โค้ดด้านล่างไปรันดูได้เลยครับ

C++ (Fallout Terminal Demo)
#include "vga_graphics.h"

// ---------------- CONFIG ----------------
#define BORDER_THICKNESS 4
#define BORDER_MARGIN    14

// Fallout (big text)
#define BIG_CHAR_W 12
#define BIG_CHAR_H 16

// Linux (small text)
#define SMALL_CHAR_W 6
#define SMALL_CHAR_H 8

// Cursor
bool cursor_visible = true;
unsigned long last_blink = 0;

// ---------------- CURSORS ----------------
int big_col = 0;
int big_row = 0;

int small_col = 0;
int small_row = 18;   // start lower on screen

// ---------------- BORDER ----------------
void drawBorder() {
  for (int t = 0; t < BORDER_THICKNESS; t++) {
    drawHLine(t, t, screenWidth - 2*t, GREEN);
    drawHLine(t, screenHeight - 1 - t, screenWidth - 2*t, GREEN);
    drawVLine(t, t, screenHeight - 2*t, GREEN);
    drawVLine(screenWidth - 1 - t, t, screenHeight - 2*t, GREEN);
  }
}

// ---------------- TEXT HELPERS ----------------
void big_print(const char* s) {
  while (*s) {
    if (*s == '\n') {
      big_col = 0;
      big_row++;
    } else {
      drawChar(
        BORDER_MARGIN + big_col * BIG_CHAR_W,
        BORDER_MARGIN + big_row * BIG_CHAR_H,
        *s,
        GREEN,
        BLACK,
        2
      );
      big_col++;
    }
    s++;
  }
}

void small_print(const char* s) {
  while (*s) {
    if (*s == '\n') {
      small_col = 0;
      small_row++;
    } else {
      drawChar(
        BORDER_MARGIN + small_col * SMALL_CHAR_W,
        BORDER_MARGIN + small_row * SMALL_CHAR_H,
        *s,
        GREEN,
        BLACK,
        1
      );
      small_col++;
    }
    s++;
  }
}

// ---------------- BLINKING CURSOR ----------------
void draw_cursor() {
  int x = BORDER_MARGIN + big_col * BIG_CHAR_W;
  int y = BORDER_MARGIN + big_row * BIG_CHAR_H;

  if (cursor_visible) {
    fillRect(x, y + BIG_CHAR_H - 2, BIG_CHAR_W, 2, GREEN);
  } else {
    fillRect(x, y + BIG_CHAR_H - 2, BIG_CHAR_W, 2, BLACK);
  }
}

// ---------------- SETUP ----------------
void setup() {
  initVGA();
  clearScreen();
  drawBorder();

  // Fallout header (BIG)
  big_print("ROBCO INDUSTRIES UNIFIED OS\n");
  big_print("COPYRIGHT 2075-2077 ROBCO\n");
  big_print("- VAULT TERMINAL -\n\n");

  big_print("> INITIALIZING SYSTEM...\n");
  big_print("> MEMORY BANKS: OK\n");
  big_print("> VGA INTERFACE: OK\n");
  big_print("> RP2040 STATUS: NOMINAL\n\n");
  big_print("> ");

  // Linux / RPi info (SMALL)
  small_print("pico@rp2040:~$ neofetch\n\n");
  small_print("OS: Pico Linux (demo)\n");
  small_print("Host: Raspberry Pi Pico W\n");
  small_print("Kernel: 0.1-retro\n");
  small_print("Uptime: just now\n");
  small_print("Resolution: 640x480 VGA\n");
  small_print("Shell: bash (fake)\n");
}

// ---------------- LOOP ----------------
void loop() {
  unsigned long now = millis();
  if (now - last_blink > 500) {
    last_blink = now;
    cursor_visible = !cursor_visible;
    draw_cursor();
  }
}

สรุปการทำงานของโค้ด: เราใช้ initVGA() เปิดการเชื่อมต่อจอ กำหนดขนาดฟอนต์ (Scale 1 และ 2) และวาดกรอบสีเขียวด้วยฟังก์ชัน drawBorder() โค้ดส่วนใหญ่จะอยู่ในฟังก์ชัน setup() เพื่อให้พิมพ์ข้อความโชว์ตอนแรก ส่วนใน loop() จะปล่อยให้ตัว Cursor กระพริบสลับไปมาครับ

Step 5: ออกแบบ PCB (PCB DESIGN)

เมื่อ Breadboard ผ่านฉลุย ก็เข้าสู่การทำ PCB ครับ เราเดินวงจรเชื่อม VGA เข้ากับ Pico และใส่ตัวต้านทาน 330Ω จำนวน 3 ตัวขนานกันในแต่ละเส้นสี (RGB) เพื่อให้ปรับค่าความสว่างได้ง่ายๆ นอกจากนี้ยังเพิ่ม IC LM317 (ตัวแปลงแรงดันไฟฟ้า) เพื่อแปลงไฟจากอะแดปเตอร์ 12V ให้เป็นไฟ 5V นิ่งๆ ไปเลี้ยงบอร์ด Pico ด้วยครับ (ดู Datasheet ของ LM317 ที่นี่)

PCB Design Schematic
PCB Routing PCB 3D View

Step 6-8: สั่งผลิต PCB, เช็ค DFM, และประกอบวงจร!

ผู้สร้างใช้บริการจาก NextPCB ในการสั่งทำแผ่น PCB สีแดงสุดจี๊ด และใช้เครื่องมือ HQDFM (โปรแกรมตรวจสอบ Design for Manufacturing) เพื่อเช็คก่อนว่าแผ่นวงจรที่วาดมามีจุดผิดพลาดตรงไหนก่อนส่งโรงงานหรือไม่ ถือเป็นทริคที่ดีมากๆ สำหรับการทำโปรดักชันครับ

ส่วนการประกอบ (PCB Assembly) ก็เริ่มตั้งแต่การหยอดตะกั่วเหลว (Solder paste) วางชิ้นส่วน SMD ลงไป แล้วเอาไปวางบน Hotplate เพื่อละลายตะกั่ว จากนั้นก็บัดกรีพอร์ต VGA, พอร์ตไฟ และประกอบบอร์ด Pico ลงไปเป็นอันเสร็จ!

Manufactured PCB
HQDFM Software
PCB Assembly Detail Fully Assembled PCB

Step 9-10: เสียบปลั๊ก ลุย Demo 1 - Hello World

เตรียมสาย VGA เสียบเข้ากับจอมอนิเตอร์ขนาด 24 นิ้ว แล้วจ่ายไฟ 12V ผ่านแจ็ค DC เรามาทดสอบด้วยโค้ด "Hello World" แบบมีเอฟเฟกต์ CRT Scanline ให้ดูเหมือนจอตู้สมัยก่อนกันครับ!

C++ (Hello World CRT Demo)
#include "vga_graphics.h"
#include 
/* ── Screen ───────────────── */
#define SW 640
#define SH 480
/* ── Text scale ──────────── */
#define SCALE 4
/* ── Centered print ───────── */
void printCentered(const char* text, int y, char color) {
int len = strlen(text);
int textWidth = len * 6 * SCALE;
int x = (SW - textWidth) / 2;
for (int i = 0; i < len; i++) {
drawChar(x + i * 6 * SCALE, y, text[i], color, BLACK, SCALE);
}
}
/* ── CRT Scanlines ───────── */
void drawScanlines() {
for (int y = 0; y < SH; y += 2) {
drawHLine(0, y, SW, BLACK);  // dark line every other row
}
}
/* ── Setup ─────────────── */
void setup() {
initVGA();
clearScreen();
int lineHeight = 8 * SCALE;
int totalHeight = lineHeight * 3 + 20; // spacing included
int startY = (SH - totalHeight) / 2;
// Line 1 (RED)
printCentered("HELLO WORLD 1", startY, RED);
// Line 2 (GREEN)
printCentered("HELLO WORLD 2", startY + lineHeight + 10, GREEN);
// Line 3 (BLUE)
printCentered("HELLO WORLD 3", startY + (lineHeight + 10) * 2, BLUE);
// CRT overlay
drawScanlines();
}
/* ── Loop ─────────────── */
void loop() {
}

Step 11: Demo 2 - เกมงูในตำนาน (Snake Game)

ลองเอาโค้ดเกมงู (Snake Game Console) มาดัดแปลงลงบอร์ด Pico ดูครับ ในเวอร์ชันนี้เราเขียนให้มันเป็น AI งูเลื้อยสุ่มหาอาหารเองไปเรื่อยๆ (แต่ถ้าใครอยากต่อปุ่มบังคับเองก็ทำได้นะ)

C++ (Snake Game AI Demo)
#include "vga_graphics.h"
/* ── Screen ───────────────── */
#define SW 640
#define SH 480
/* ── Grid ───────────────── */
#define CELL 12
#define GRID_W (SW / CELL)
#define GRID_H (SH / CELL)
/* ── Snake ─────────────── */
struct Segment {
int x;
int y;
};
Segment snake[200];
int snakeLength = 5;
int dx = 1, dy = 0;
int foodX, foodY;
int prevTailX, prevTailY;
/* ── Random food ───────── */
void placeFood() {
foodX = random(2, GRID_W - 2);
foodY = random(2, GRID_H - 2);
}
/* ── Reset ─────────────── */
void resetGame() {
clearScreen();
snakeLength = 5;
dx = 1; dy = 0;
for (int i = 0; i < snakeLength; i++) {
snake[i].x = GRID_W / 2 - i;
snake[i].y = GRID_H / 2;
}
placeFood();
}
/* ── Random movement ───── */
void randomMove() {
if (random(0, 10) < 3) {  // 30% chance
int dir = random(0, 4);
if (dir == 0 && dy == 0) { dx = 0; dy = -1; } // up
if (dir == 1 && dy == 0) { dx = 0; dy = 1; }  // down
if (dir == 2 && dx == 0) { dx = -1; dy = 0; } // left
if (dir == 3 && dx == 0) { dx = 1; dy = 0; }  // right
}
}
/* ── Collision ─────────── */
bool selfCollision() {
for (int i = 1; i < snakeLength; i++) {
if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) {
return true;
}
}
return false;
}
/* ── Draw one cell ─────── */
void drawCell(int gx, int gy, char color) {
fillRect(gx * CELL, gy * CELL, CELL - 1, CELL - 1, color);
}
/* ── Setup ─────────────── */
void setup() {
initVGA();
clearScreen();
randomSeed(analogRead(26)); // Pico randomness
resetGame();
}
/* ── Loop ─────────────── */
void loop() {
// random AI movement
randomMove();
// store tail
prevTailX = snake[snakeLength - 1].x;
prevTailY = snake[snakeLength - 1].y;
// move body
for (int i = snakeLength - 1; i > 0; i--) {
snake[i] = snake[i - 1];
}
// move head
snake[0].x += dx;
snake[0].y += dy;
// wrap screen
if (snake[0].x >= GRID_W) snake[0].x = 0;
if (snake[0].x < 0) snake[0].x = GRID_W - 1;
if (snake[0].y >= GRID_H) snake[0].y = 0;
if (snake[0].y < 0) snake[0].y = GRID_H - 1;
// self collision
if (selfCollision()) {
delay(500);
resetGame();
return;
}
// food
if (snake[0].x == foodX && snake[0].y == foodY) {
snakeLength++;
placeFood();
}
// erase tail
drawCell(prevTailX, prevTailY, BLACK);
// draw snake
for (int i = 0; i < snakeLength; i++) {
drawCell(snake[i].x, snake[i].y, GREEN);
}
// draw food
drawCell(foodX, foodY, WHITE);
delay(120);
}

Step 12: Demo 3 - Conway's Game of Life

แถมอีกหนึ่งเดโมสุดคลาสสิก นั่นคือ Game of Life ซึ่งเป็นการจำลองวิวัฒนาการการเกิด-ดับของเซลล์ (จุดสีเหลือง) ตามเงื่อนไขทางคณิตศาสตร์ครับ

C++ (Game of Life Demo)
#include "vga_graphics.h"
/* ── Screen ───────────────── */
#define SW 640
#define SH 480
/* ── Grid ───────────────── */
#define CELL 8
#define GRID_W (SW / CELL)
#define GRID_H (SH / CELL)
/* ── Buffers ───────────── */
bool grid[GRID_W][GRID_H];
bool nextGrid[GRID_W][GRID_H];
/* ── Draw cell ─────────── */
void drawCell(int x, int y, bool alive) {
fillRect(
x * CELL,
y * CELL,
CELL - 1,
CELL - 1,
alive ? YELLOW : BLACK
);
}
/* ── Random init ───────── */
void randomizeGrid() {
for (int x = 0; x < GRID_W; x++) {
for (int y = 0; y < GRID_H; y++) {
grid[x][y] = random(0, 2);
drawCell(x, y, grid[x][y]);
}
}
}
/* ── Count neighbors ───── */
int countNeighbors(int x, int y) {
int count = 0;
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
if (dx == 0 && dy == 0) continue;
int nx = x + dx;
int ny = y + dy;
// wrap around edges
if (nx < 0) nx = GRID_W - 1;
if (nx >= GRID_W) nx = 0;
if (ny < 0) ny = GRID_H - 1;
if (ny >= GRID_H) ny = 0;
if (grid[nx][ny]) count++;
}
}
return count;
}
/* ── Update simulation ─── */
void updateGrid() {
for (int x = 0; x < GRID_W; x++) {
for (int y = 0; y < GRID_H; y++) {
int neighbors = countNeighbors(x, y);
if (grid[x][y]) {
// alive
nextGrid[x][y] = (neighbors == 2 || neighbors == 3);
} else {
// dead
nextGrid[x][y] = (neighbors == 3);
}
}
}
// apply + redraw only changes
for (int x = 0; x < GRID_W; x++) {
for (int y = 0; y < GRID_H; y++) {
if (grid[x][y] != nextGrid[x][y]) {
drawCell(x, y, nextGrid[x][y]);
}
grid[x][y] = nextGrid[x][y];
}
}
}
/* ── Setup ─────────────── */
void setup() {
initVGA();
clearScreen();
randomSeed(analogRead(26)); // Pico randomness
randomizeGrid();
}
/* ── Loop ─────────────── */
void loop() {
updateGrid();
delay(80);  // speed control
}

บทสรุป (Conclusion)

เป็นไงบ้างครับกับ Pico VGA Board 1.0! โปรเจกต์นี้ตอบโจทย์มากสำหรับใครที่อยากเอาบอร์ดไมโครคอนโทรลเลอร์ตัวเล็กๆ ไปต่อขึ้นจอใหญ่ๆ เพื่อทำ Sensor Interface, Dashboard หรือแม้แต่เครื่องเกมคอนโซลทำเอง เพียงแค่ลง Library ที่แจกไว้ให้ แล้วประกอบวงจรตามนี้ คุณก็สามารถเนรมิตทุกอย่างออกจอมอนิเตอร์ได้ง่ายๆ เลยครับ!

อ้างอิงข้อมูลจาก: Globalbyteshop Blog

ต้นฉบับโปรเจกต์โดย: Arnov Sharma | Original Link

ดาวน์โหลดเอกสารเพิ่มเติม: คู่มือ PICO VGA BOARD 1.0 (PDF)

โปรเจกต์ที่เกี่ยวข้อง: Original VGA Library | Custom PICO-VGA-BOARD GitHub | Snake Game Console

*คำเตือน: เนื้อหานี้เป็นการสรุปและเรียบเรียงแนวคิดจากบทความโปรเจกต์ต้นฉบับภาษาอังกฤษ ข้อมูลฉบับภาษาไทยและกระบวนการทำงานบางส่วนอาจถูกปรับให้เข้าใจง่ายและเห็นภาพรวมชัดเจนขึ้น โปรดตรวจสอบรายละเอียดเชิงวิศวกรรมการต่อวงจรและการเชื่อมต่อพอร์ตอย่างรัดกุมได้ที่ เว็บไซต์ต้นฉบับ ก่อนนำไปทดลองจริง

 

แท็ก


Blog posts

เข้าสู่ระบบ

ลืมรหัสผ่านใช่ไหม?

ยังไม่มีบัญชีใช่ไหม?
สร้างบัญชี