IoTLabs

Nghiên cứu, Sáng tạo và Thử nghiệm

ESP32-S3-DevKitC N16R8 CAM: Bài 04 – Setup Arduino IDE v2 “đúng chuẩn” (PSRAM / Partition / USB-CDC)

Giới thiệu

Sau khi đã nhận diện đúng board (Bài 01), kiểm tra phần cứng (Bài 02)phân biệt COM vs USB Native (Bài 03), bước quan trọng tiếp theo là cấu hình Arduino IDE v2 sao cho nạp chắc chắn, ổn định và không phát sinh lỗi vặt.

ESP32-S3-DevKitC N16R8 CAM có Flash 16MB + PSRAM 8MB, lại thêm USB Native, nên nếu cấu hình IDE sai, bạn sẽ gặp các lỗi rất phổ biến như:

  • Không nhận PSRAM
  • Camera chạy crash
  • USB CDC không hiện Serial
  • Nạp lúc được lúc không

Bài 04 sẽ giúp bạn setup Arduino IDE v2 đúng ngay từ đầu, theo hướng an toàn cho người mới và đủ sâu cho dự án thực tế.

Hình ảnh board thực tế

1. Cài đặt Arduino IDE v2 & ESP32 core

Phiên bản khuyến nghị

  • Arduino IDE: v2.x (mới nhất)
  • ESP32 Board Manager: Espressif Systems ≥ 2.0.11

Cài ESP32 core

  • Mở Boards Manager
  • Tìm esp32 by Espressif Systems
  • Cài đặt

⚠️ Không dùng core quá cũ → dễ lỗi USB & PSRAM

2. Chọn đúng board cho ESP32-S3-DevKitC N16R8 CAM

Board name: ESP32S3 Dev Module

Đây là lựa chọn an toàn nhất cho các board ESP32-S3 CAM dạng DevKitC


3. Thiết lập PSRAM (BẮT BUỘC cho bản CAM)

Vì sao PSRAM quan trọng?

  • Camera buffer cần rất nhiều RAM
  • Không bật PSRAM → camera crash hoặc không chạy

Cấu hình

  • PSRAM: Enabled
  • PSRAM Type: OPI PSRAM (nếu có)

Dấu hiệu đúng

  • Serial log có dòng: PSRAM found

4. Partition Scheme phù hợp cho N16R8

Khuyến nghị

  • Partition Scheme: Huge APP (3MB No OTA/1MB SPIFFS) hoặc
  • Huge APP (No OTA)

Vì sao?

  • Firmware camera + SD + WiFi rất lớn
  • Tránh lỗi Sketch too big

5. USB CDC On Boot – bật hay tắt?

Dùng cổng COM (USB-UART)

  • USB CDC On Boot: Disabled

Dùng USB Native (OTG)

  • USB CDC On Boot: Enabled

⚠️ Nếu bật sai → máy không hiện Serial hoặc không nhận USB

6. Upload Mode & tốc độ nạp

Upload Mode

  • Dùng COM: UART0 / USB-UART
  • Dùng USB Native: USB

Upload Speed

  • 115200 (khuyến nghị)
  • Giảm xuống 460800 nếu lỗi

7. Cấu hình IDE mẫu (an toàn nhất)

Khuyến nghị cho người mới:

  • Board: ESP32S3 Dev Module
  • PSRAM: Enabled
  • Flash Size: 16MB (do board đang dùng 16MB)
  • Partition Scheme: 16M Flash / Huge APP
  • USB CDC On Boot: Disabled
  • Upload Mode: UART0
  • Upload Speed: 115200 (khi serial monitor cũng chọn cùng speed này để hiển thị đúng)
  • Port: COM (USB-UART)

? Dùng cấu hình này để test LED, SD, camera trước

8. Code kiểm tra Board

Test 1 — Kiểm tra Serial + Flash + PSRAM

Nạp code này trước:

/**
 * Test 1 — Kiểm tra Serial + Flash + PSRAM
 * Board: ESP32-S3-DevKitC N16R8 CAM
*/
void setup() {
  Serial.begin(115200);
  delay(2000);

  Serial.println();
  Serial.println("ESP32-S3 N16R8 CAM Test");

  Serial.print("Chip Model: ");
  Serial.println(ESP.getChipModel());

  Serial.print("Chip Revision: ");
  Serial.println(ESP.getChipRevision());

  Serial.print("CPU Freq MHz: ");
  Serial.println(ESP.getCpuFreqMHz());

  Serial.print("Flash Size: ");
  Serial.print(ESP.getFlashChipSize() / (1024 * 1024));
  Serial.println(" MB");

  Serial.print("PSRAM Size: ");
  Serial.print(ESP.getPsramSize() / (1024 * 1024));
  Serial.println(" MB");

  Serial.print("Free Heap: ");
  Serial.println(ESP.getFreeHeap());

  Serial.print("Free PSRAM: ");
  Serial.println(ESP.getFreePsram());
}

void loop() {
  delay(1000);
}

Mở Serial Monitor:

Baud: 115200

Kết quả mong muốn:

Flash Size: 16 MB
PSRAM Size: 8 MB

Nếu PSRAM hiện 0 MB, kiểm tra lại:

Tools → PSRAM → OPI PSRAM

Camera cần PSRAM hoạt động ổn định, đặc biệt khi stream/chụp ảnh độ phân giải cao.

Test 2 — Test LED onboard / WS2812

Nhiều board ESP32-S3 CAM dùng LED RGB WS2812 ở GPIO48.

Cài thư viện:

Tools → Manage Libraries → tìm Adafruit NeoPixel → Install

Code test:

#include <Adafruit_NeoPixel.h>

#define LED_PIN 48
#define LED_COUNT 1

Adafruit_NeoPixel pixel(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

void setup() {
pixel.begin();
pixel.setBrightness(30);
}

void loop() {
pixel.setPixelColor(0, pixel.Color(255, 0, 0));
pixel.show();
delay(500);

pixel.setPixelColor(0, pixel.Color(0, 255, 0));
pixel.show();
delay(500);

pixel.setPixelColor(0, pixel.Color(0, 0, 255));
pixel.show();
delay(500);

pixel.clear();
pixel.show();
delay(500);
}

Nếu LED không sáng, có thể board của bạn dùng GPIO khác hoặc không có WS2812.

Test 3 — Test WiFi Scan

Code này kiểm tra WiFi có hoạt động không:

#include <WiFi.h>

void setup() {
Serial.begin(115200);
delay(2000);

WiFi.mode(WIFI_STA);
WiFi.disconnect();
delay(100);

Serial.println("Scanning WiFi...");

int n = WiFi.scanNetworks();

if (n == 0) {
Serial.println("No networks found");
} else {
Serial.print(n);
Serial.println(" networks found");

for (int i = 0; i < n; ++i) {
Serial.print(i + 1);
Serial.print(": ");
Serial.print(WiFi.SSID(i));
Serial.print(" RSSI: ");
Serial.println(WiFi.RSSI(i));
delay(10);
}
}
}

void loop() {
}

Nếu chạy tới WiFi thì board reset liên tục, thường là do nguồn USB yếu. Với board CAM, không nên dùng hub USB rẻ tiền khi test WiFi/Camera.

Test 4 — Test Camera bằng ví dụ có sẵn

Trong Arduino IDE mở:

File → Examples → ESP32 → Camera → CameraWebServer

Sau đó chọn model gần đúng hoặc tự cấu hình pin.

Với ESP32-S3-DevKitC N16R8 CAM, phần quan trọng nhất là camera pin mapping phải đúng theo board. Các board ESP32 camera khác nhau có thể dùng GPIO khác nhau cho XCLK, SIOD, SIOC, D0-D7, VSYNC, HREF, PCLK.

Trong code CameraWebServer, sửa WiFi:

const char* ssid = "YOUR_WIFI_NAME";
const char* password = "YOUR_WIFI_PASSWORD";

Sau khi nạp, mở Serial Monitor, bạn sẽ thấy IP dạng:

Camera Ready! Use 'http://192.168.x.x' to connect

Mở IP đó trên trình duyệt.

Test 4 — Ví dụ Test Camera

Test bằng code tối giản này:

#include "esp_camera.h"
#include <WiFi.h>

const char* ssid = "YOUR_WIFI";
const char* password = "YOUR_PASSWORD";

#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 15
#define SIOD_GPIO_NUM 4
#define SIOC_GPIO_NUM 5

#define Y2_GPIO_NUM 11
#define Y3_GPIO_NUM 9
#define Y4_GPIO_NUM 8
#define Y5_GPIO_NUM 10
#define Y6_GPIO_NUM 12
#define Y7_GPIO_NUM 18
#define Y8_GPIO_NUM 17
#define Y9_GPIO_NUM 16

#define VSYNC_GPIO_NUM 6
#define HREF_GPIO_NUM 7
#define PCLK_GPIO_NUM 13

void setup() {
Serial.begin(115200);
delay(3000);

Serial.println();
Serial.println("ESP32-S3 N16R8 OV5640 Camera Test");

Serial.print("Flash MB: ");
Serial.println(ESP.getFlashChipSize() / 1024 / 1024);

Serial.print("PSRAM MB: ");
Serial.println(ESP.getPsramSize() / 1024 / 1024);

camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;

config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;

config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;

config.pin_sccb_sda = SIOD_GPIO_NUM;
config.pin_sccb_scl = SIOC_GPIO_NUM;

config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;

config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;

if (psramFound()) {
config.frame_size = FRAMESIZE_VGA;
config.jpeg_quality = 12;
config.fb_count = 2;
config.fb_location = CAMERA_FB_IN_PSRAM;
config.grab_mode = CAMERA_GRAB_LATEST;
} else {
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 15;
config.fb_count = 1;
config.fb_location = CAMERA_FB_IN_DRAM;
}

esp_err_t err = esp_camera_init(&config);

if (err != ESP_OK) {
Serial.printf("Camera init failed: 0x%x\n", err);
return;
}

Serial.println("Camera init OK");

camera_fb_t *fb = esp_camera_fb_get();

if (!fb) {
Serial.println("Camera capture failed");
return;
}

Serial.print("Captured image size: ");
Serial.print(fb->len);
Serial.println(" bytes");

esp_camera_fb_return(fb);

Serial.println("Camera test done");
}

void loop() {
delay(1000);
}

Kết quả mong muốn

Serial Monitor 115200 sẽ hiện:

Flash MB: 16
PSRAM MB: 8
Camera init OK
Captured image size: xxxxx bytes
Camera test done

Nếu báo lỗi camera

Nếu thấy:

Camera init failed: 0x105

thường là sai pin mapping hoặc camera chưa giao tiếp SCCB/I2C được.

Nếu thấy:

Camera capture failed

thường là camera init được nhưng dữ liệu D0-D7/PCLK/VSYNC/HREF sai, nguồn yếu, hoặc OV5640 cần giảm frame size.

Với OV5640, khi test lần đầu nên để:

FRAMESIZE_QVGA hoặc FRAMESIZE_VGA
JPEG quality: 12–15
XCLK: 20MHz

Sau khi camera chạy ổn mới tăng độ phân giải.

Test 5: Camera Stream Webserver

#include "esp_camera.h"
#include <WiFi.h>
#include "esp_http_server.h"

// =====================================================
// ESP32-S3 N16R8 CAM + OV5640 Optimized Web Server
// Goal: cooler, more stable, less load, acceptable quality
// =====================================================

// ---------- WiFi ----------
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASSWORD = "YOUR_PASSWORD";

// ---------- Camera Pins: ESP32-S3 N16R8 CAM ----------
#define PWDN_GPIO_NUM     -1
#define RESET_GPIO_NUM    14

#define XCLK_GPIO_NUM     15
#define SIOD_GPIO_NUM     4
#define SIOC_GPIO_NUM     5

#define Y2_GPIO_NUM       11
#define Y3_GPIO_NUM       9
#define Y4_GPIO_NUM       8
#define Y5_GPIO_NUM       10
#define Y6_GPIO_NUM       12
#define Y7_GPIO_NUM       18
#define Y8_GPIO_NUM       17
#define Y9_GPIO_NUM       16

#define VSYNC_GPIO_NUM    6
#define HREF_GPIO_NUM     7
#define PCLK_GPIO_NUM     13

// ---------- Stream optimization ----------
#define STREAM_DELAY_MS   120     // Increase to 150-200 if board is hot
#define WIFI_TX_POWER     WIFI_POWER_8_5dBm

// JPEG quality: lower number = better image, more heat/load
// Good stable range: 15-20
#define JPEG_QUALITY      18

// Start with QVGA for cooler operation. Try VGA after stable.
#define CAMERA_FRAME_SIZE FRAMESIZE_QVGA

#define PART_BOUNDARY "123456789000000000000987654321"
static const char* STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char* STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";

httpd_handle_t camera_httpd = NULL;

// ---------- Simple HTML page ----------
static const char INDEX_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>ESP32-S3 OV5640 Camera</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      background: #101010;
      color: white;
      text-align: center;
      margin: 0;
      padding: 16px;
    }
    .card {
      max-width: 920px;
      margin: auto;
    }
    img {
      width: 100%;
      max-width: 900px;
      border-radius: 12px;
      background: #000;
      border: 1px solid #333;
    }
    a, button {
      display: inline-block;
      margin: 8px;
      padding: 10px 14px;
      border-radius: 8px;
      border: 0;
      background: #0e8578;
      color: white;
      text-decoration: none;
      font-size: 15px;
      cursor: pointer;
    }
    .hint {
      color: #bbb;
      font-size: 14px;
    }
  </style>
</head>
<body>
  <div class="card">
    <h2>ESP32-S3 N16R8 CAM - OV5640</h2>
    <p class="hint">Optimized low-heat stream mode</p>
    <a href="/capture" target="_blank">Capture</a>
    <a href="/stream" target="_blank">Open Stream Only</a>
    <br />
    <img src="/stream" />
  </div>
</body>
</html>
)rawliteral";

static esp_err_t index_handler(httpd_req_t *req) {
  httpd_resp_set_type(req, "text/html");
  httpd_resp_set_hdr(req, "Cache-Control", "no-store");
  return httpd_resp_send(req, INDEX_HTML, strlen(INDEX_HTML));
}

static esp_err_t capture_handler(httpd_req_t *req) {
  camera_fb_t *fb = esp_camera_fb_get();

  if (!fb) {
    Serial.println("Capture failed");
    httpd_resp_send_500(req);
    return ESP_FAIL;
  }

  httpd_resp_set_type(req, "image/jpeg");
  httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
  httpd_resp_set_hdr(req, "Cache-Control", "no-store");

  esp_err_t res = httpd_resp_send(req, (const char *)fb->buf, fb->len);
  esp_camera_fb_return(fb);
  return res;
}

static esp_err_t stream_handler(httpd_req_t *req) {
  camera_fb_t *fb = NULL;
  esp_err_t res = ESP_OK;
  char part_buf[80];

  res = httpd_resp_set_type(req, STREAM_CONTENT_TYPE);
  if (res != ESP_OK) return res;

  httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
  httpd_resp_set_hdr(req, "Cache-Control", "no-store");

  while (true) {
    fb = esp_camera_fb_get();

    if (!fb) {
      Serial.println("Stream capture failed");
      res = ESP_FAIL;
      break;
    }

    if (fb->format != PIXFORMAT_JPEG) {
      Serial.println("Frame is not JPEG");
      esp_camera_fb_return(fb);
      res = ESP_FAIL;
      break;
    }

    res = httpd_resp_send_chunk(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));

    if (res == ESP_OK) {
      size_t hlen = snprintf(part_buf, sizeof(part_buf), STREAM_PART, fb->len);
      res = httpd_resp_send_chunk(req, part_buf, hlen);
    }

    if (res == ESP_OK) {
      res = httpd_resp_send_chunk(req, (const char *)fb->buf, fb->len);
    }

    esp_camera_fb_return(fb);
    fb = NULL;

    if (res != ESP_OK) {
      Serial.println("Client disconnected or stream error");
      break;
    }

    // Important: reduce CPU/WiFi load and heat
    delay(STREAM_DELAY_MS);
  }

  return res;
}

bool initCamera() {
  camera_config_t config;
  memset(&config, 0, sizeof(config));

  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;

  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;

  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;

  config.pin_sccb_sda = SIOD_GPIO_NUM;
  config.pin_sccb_scl = SIOC_GPIO_NUM;

  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;

  // Lower XCLK helps stability and heat for OV5640
  config.xclk_freq_hz = 10000000;
  config.pixel_format = PIXFORMAT_JPEG;

  config.frame_size = CAMERA_FRAME_SIZE;
  config.jpeg_quality = JPEG_QUALITY;
  config.fb_count = 1;
  config.fb_location = CAMERA_FB_IN_PSRAM;
  config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;

  Serial.println("Initializing camera...");
  esp_err_t err = esp_camera_init(&config);

  if (err != ESP_OK) {
    Serial.printf("Camera init failed: 0x%x\n", err);
    return false;
  }

  sensor_t *s = esp_camera_sensor_get();
  if (!s) {
    Serial.println("Failed to get camera sensor");
    return false;
  }

  Serial.printf("Camera PID: 0x%04x\n", s->id.PID);

  // Optimized basic image settings
  s->set_framesize(s, CAMERA_FRAME_SIZE);
  s->set_quality(s, JPEG_QUALITY);
  s->set_brightness(s, 0);
  s->set_contrast(s, 0);
  s->set_saturation(s, 0);

  // Keep auto controls enabled for normal indoor use
  s->set_whitebal(s, 1);
  s->set_awb_gain(s, 1);
  s->set_exposure_ctrl(s, 1);
  s->set_gain_ctrl(s, 1);

  // Uncomment these if the image direction is wrong
  // s->set_vflip(s, 1);
  // s->set_hmirror(s, 1);

  Serial.println("Camera init OK");
  return true;
}

void startCameraServer() {
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  config.server_port = 80;
  config.ctrl_port = 32768;
  config.max_open_sockets = 4;
  config.stack_size = 8192;
  config.recv_wait_timeout = 5;
  config.send_wait_timeout = 5;

  httpd_uri_t index_uri = {
    .uri = "/",
    .method = HTTP_GET,
    .handler = index_handler,
    .user_ctx = NULL
  };

  httpd_uri_t capture_uri = {
    .uri = "/capture",
    .method = HTTP_GET,
    .handler = capture_handler,
    .user_ctx = NULL
  };

  httpd_uri_t stream_uri = {
    .uri = "/stream",
    .method = HTTP_GET,
    .handler = stream_handler,
    .user_ctx = NULL
  };

  Serial.println("Starting web server...");

  if (httpd_start(&camera_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(camera_httpd, &index_uri);
    httpd_register_uri_handler(camera_httpd, &capture_uri);
    httpd_register_uri_handler(camera_httpd, &stream_uri);
    Serial.println("Camera web server started");
  } else {
    Serial.println("Failed to start camera web server");
  }
}

void connectWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.setSleep(true);          // Lower heat when possible
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  WiFi.setTxPower(WIFI_TX_POWER);

  Serial.println();
  Serial.println("WiFi connected");
  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());
}

void printSystemInfo() {
  Serial.println();
  Serial.println("ESP32-S3 N16R8 OV5640 Optimized Web Server");

  Serial.print("Flash MB: ");
  Serial.println(ESP.getFlashChipSize() / 1024 / 1024);

  Serial.print("PSRAM MB: ");
  Serial.println(ESP.getPsramSize() / 1024 / 1024);

  Serial.print("Free Heap: ");
  Serial.println(ESP.getFreeHeap());

  Serial.print("Free PSRAM: ");
  Serial.println(ESP.getFreePsram());
}

void setup() {
  Serial.begin(115200);
  delay(3000);

  printSystemInfo();

  if (!psramFound()) {
    Serial.println("PSRAM not found. Stop.");
    return;
  }

  if (!initCamera()) {
    Serial.println("Camera setup failed. Stop.");
    return;
  }

  connectWiFi();

  Serial.print("Open: http://");
  Serial.println(WiFi.localIP());
  Serial.print("Capture: http://");
  Serial.print(WiFi.localIP());
  Serial.println("/capture");
  Serial.print("Stream: http://");
  Serial.print(WiFi.localIP());
  Serial.println("/stream");

  startCameraServer();
}

void loop() {
  // Keep loop light to reduce heat
  delay(10000);

  Serial.print("Free Heap: ");
  Serial.print(ESP.getFreeHeap());
  Serial.print(" | Free PSRAM: ");
  Serial.println(ESP.getFreePsram());
}

Điểm đã tối ưu để giảm nóng và ổn định hơn:

Frame size: QVGA
XCLK: 10MHz
JPEG quality: 18
fb_count: 1
Stream delay: 120ms
WiFi TX Power: 8.5dBm
WiFi sleep: enabled
HTTP server socket giới hạn thấp hơn

Sau khi nạp, mở Serial Monitor 115200, rồi test theo thứ tự:

1. http://IP/capture
2. http://IP/stream
3. http://IP

Nếu thấy camera nóng, tăng:

#define STREAM_DELAY_MS 150

hoặc:

#define STREAM_DELAY_MS 200

Nếu ảnh quá xấu, giảm nhẹ:

#define JPEG_QUALITY 15

Lưu ý: số JPEG_QUALITY càng thấp thì ảnh càng đẹp nhưng camera sẽ nóng hơn.

Điều chỉnh độ phân giải:

FRAMESIZE_QVGA   // 320x240, mát, ổn định
FRAMESIZE_VGA    // 640x480, cân bằng
FRAMESIZE_SVGA   // 800x600, đẹp hơn nhưng nóng hơn
FRAMESIZE_XGA    // 1024x768, dễ nóng/lag

8. Các lỗi thường gặp & cách xử lý

Không thấy PSRAM

  • Kiểm tra PSRAM Enabled
  • Kiểm tra đúng board

Camera chạy crash

  • Partition quá nhỏ
  • PSRAM chưa bật

USB Native không nhận

  • Bật USB CDC On Boot
  • Nạp lại bằng COM

Chuẩn bị cho bài tiếp theo

Bài 05, chúng ta sẽ:

  • Học cách đọc pinout theo nhóm chức năng
  • Phân biệt chân an toàn và chân dễ xung đột