# ESP32 - The Cube

- Một dự án "out of the box" - dự án đầu tiên trong hành trình tìm hiểu về **lập trình nhúng (embedded programming)**
- Dự án đầu tiên này sẽ phát triển một pet robot để bàn tương tự như [Dasai Mochi Robot](https://dasai.com.au/collections/mochi-dasai)
- Tính năng chính: 
    - Hiển thị biểu cảm ngẫu nhiên
    - Hiển thị biểu cảm khi tương tác chạm
    - Phát âm thanh, nhạc
    - Tương tác với GenAI bằng giọng nói (cập nhật sau)

# Chuẩn bị

[![20251011_151947~2[1].jpg](https://knowledge.thanhdv.com/uploads/images/gallery/2025-10/scaled-1680-/20251011-15194721.jpg)](https://knowledge.thanhdv.com/uploads/images/gallery/2025-10/scaled-1680-/20251011-15194721.jpg)

- Linh kiện cần thiết
- **ESP32-C3 SuperMini**<span style="white-space:pre-wrap;"> (</span>[ESP32-C3 SuperMini datasheet.pdf](https://knowledge.thanhdv.com/attachments/1)<span style="white-space:pre-wrap;">). Ban đầu linh kiện này được sử dụng do </span>**nhỏ gọn**<span style="white-space:pre-wrap;"> và </span>**đủ mạnh**<span style="white-space:pre-wrap;"> để có thể nhét vào trong khung in 3D.</span>
- <span style="white-space:pre-wrap;">Module thẻ nhớ </span>**TF V474**<span style="white-space:pre-wrap;"> và thẻ nhớ </span>**MicroSD 2GB**. Dùng để lưu trữ dữ liệu vì bộ nhớ trong của ESP32 C3 là rất nhỏ (4MB).
- <span style="white-space:pre-wrap;">Module khuếch đại </span>**MAX98357A** <span style="white-space:pre-wrap;">và </span>**loa 4ohm 3W**<span style="white-space:pre-wrap;">. Chắc chắn là dùng để phát âm thanh rồi. Nhưng cần dùng </span>**MAX98357A** vì tín hiệu điện đầu ra của ESP32 là rất nhỏ không đủ để phát âm thanh qua loa.
- <span style="white-space:pre-wrap;">Pin 3.7v và mạch sạc </span>**HW-373.**<span style="white-space:pre-wrap;"> Chắc chắn là để cấp nguồn chứ còn làm gì nữa.</span>
- <span style="white-space:pre-wrap;">Cảm biến chạm </span>**TTP223**. Để người dùng có thể tương tác với robot (pet mà :)))))
- <span style="white-space:pre-wrap;">Màn hình </span>**TFT 1.3in 240\*240 IC ST7789VW.** Để hiển thị biểu cảm của robot. Ban đầu sử dụng màn hình OLED đơn sắc 0.96in nhưng gặp 2 vấn đề:
    - Giao tiếp I2C tốc độ chậm hiển thị hình ảnh không được mượt.
    - Màn hình đơn sắc hiển thị hình ảnh không có chi tiết, xấu.

### Phần mềm cần thiết

- **VSCode** <span style="white-space:pre-wrap;">và plugin </span>**PlatformIO.** <span style="white-space:pre-wrap;"> Chắc chắn là dùng để lập trình rồi.</span>
    - VSCode được sử dụng do mình đã quen dùng từ trước
    - Mạnh hơn Ardruino IDE. Có gợi ý code...
    - Nhẹ hơn Clion nhưng vẫn đầy đủ tính năng cần thiết

### Một vài kiến thức khác với ESP32

#### Giao tiếp với ESP32

<table id="bkmrk-ti%C3%AAu-ch%C3%ADi2c-%28inter-i" style="border-style:dotted;"><colgroup><col></col><col></col><col style="width:216px;"></col><col></col><col></col></colgroup><tbody><tr><td>**Tiêu chí**

</td><td>**I2C (Inter-Integrated Circuit)**

</td><td>**I2S (Inter-IC Sound)**

</td><td>**SPI (Serial Peripheral Interface)**

</td><td>**UART (Universal Asynchronous Receiver/Transmitter)**

</td></tr><tr><td>**Số dây cần**

</td><td>2 (SDA, SCL)

</td><td>3 (BCLK, LRCLK/WS, DIN/DOUT) có thể có thêm MCLK

</td><td>4+ (SCK, MOSI, MISO, CS/SS)

</td><td>2 (TX/RX)

</td></tr><tr><td>**Kiểu giao tiếp**

</td><td>Đồng bộ (có clock)

</td><td>Đồng bộ (có clock)

</td><td>Đồng bộ (có clock)

</td><td>Không đồng bộ (không cần clock)

</td></tr><tr><td>**Số thiết bị**

</td><td>+++ (thiết bị có địa chỉ riêng)

</td><td>1 master vs 1 slave hoặc 1 master nhiều slave với thiết bị chia

</td><td>+++ (mỗi thiết bị cần một dây CS riêng)

</td><td>1 master vs 1 slave

</td></tr><tr><td>**Tốc độ**

</td><td>Thấp (100kHz - vài MHz)

</td><td>Cao (vài MHz tùy cấu hình)

</td><td>Rất cao (vài MHz đến vài chục MHz)

</td><td>Thấp (9.6kbps - vài Mbps)

</td></tr><tr><td>**Độ phức tạp**

</td><td>Ít dây =&gt; dễ nối

</td><td>Phức tạp, cần cấu hình và định dạng dữ liệu đúng

</td><td>Phức tạp, tốn chân GPIO

</td><td>Ít dây =&gt; dễ nối

</td></tr><tr><td>**Ưu điểm**

</td><td>Tiết kiệm chân  
Dễ thêm thiết bị

</td><td>Chuẩn hóa cho audio  
Chất lượng cao  
<span style="white-space:pre-wrap;">Hỗ trợ DMA </span>

</td><td>Tốc độ cao  
Truyền được dữ liệu lớn

</td><td>Đơn giản

</td></tr><tr><td>**Nhược điểm**

</td><td>Tốc độ hạn chế  
Dễ xung đột

</td><td>Chỉ dùng cho Audio  
Khó quản lý với buffer audio lớn  
Dễ nhiễu trên dây dài

</td><td>Thiết kế phức tạp  
Tốn chân

</td><td>Chỉ có thể kết nối 1 - 1

</td></tr><tr><td>**Ứng dụng**

</td><td>Cảm biến, màn hình OLED nhỏ....

</td><td>Xử lý âm thanh (thu, phát, truyền...)

</td><td>Màn hình TFT, thẻ nhớ,...

</td><td>Giao tiếp với PC, GPS, GSM...

</td></tr></tbody></table>

#### Các chân trên ESP32

Trên ESP32 có nhiều chân sẽ được dùng chung với nhiều thiết bị khác nhau. Nên cân nhắc để hàn chân sao cho hợp lý.

##### Dùng chung cho tất cả các module

Tất cả các module đều cần sử dụng đến nguồn (có thể trên mạch ESP32 hoặc nguồn cấp ngoài). Cần cân nhắc để nối chung hoặc chọn dây nguồn phù hợp.

- 5V
- 3.3V
- GND

##### Giao tiếp

Các chân giao tiếp này nằm chung với các chân GPIO có đánh số. Cần check datasheet để biết chính xác nó nằm ở đâu

- I2C (SDA, SCL)
- SPI (SCK. MOSI, MISO, CS/SS)
- UART (RX/TX)

# Chương 1: Phần cứng

# Phần 1: Những mối hàn đầu tiên

<span style="white-space: pre-wrap;">Bắt đầu luôn với chút kinh nghiệm được rút ra từ </span><span style="color: rgb(248, 202, 198);">đau thương.</span><span style="white-space: pre-wrap;"></span>

- <span style="white-space: pre-wrap;">Nên đọc kỹ </span>****thông tin****<span style="white-space: pre-wrap;">, tìm </span>****datasheet**** với mỗi linh kiện để đảm bảo có thể phục vụ nhu cầu và tính tương thích với ESP32 cũng như những linh kiện khác.
- ESP32 sẽ có nhiều chân được dùng chung với nhau 3.3V, 5V, GND, I2C, SPI... chần chú ý những chân này để hàm dây cho phù hợp.
- <span style="white-space: pre-wrap;">Nếu có thể nên sử dụng </span>****pin header****<span style="white-space: pre-wrap;"> trên bo mạch kết hợp với </span>****breadboard**** <span style="white-space: pre-wrap;">và các </span>****jumper wire**** để thuận tiện linh hoạt trong quá trình phát triển
- <span style="white-space: pre-wrap;">Trên mạch ESP32 có những linh kiện rất nhỏ rất nhạy cảm, nếu quá nhiệt rất dễ bong ra (đã chết một chiếc ESP32-C3-SuperMini vì lý do này T.T) vậy nên </span>****hết sức cẩn thận khi hàn.****

# Phần 2: "Hello World!!!"

Giống như lập trình phần mềm. Lập trình nhúng cũng có những bài "Hello world!!!".

Trong phần này ngoài thực hiện log lên màn hình dòng "Hello world!!!" chúng ta sẽ thêm một chút liên quan đến phần cứng là điều khiển đèn led trên ESP32

### Cấu hình Dự án

<span style="white-space: pre-wrap;">Để cấu hình chúng ta sử dụng file </span>`<span class="editor-theme-code">platformio.ini</span>`<span style="white-space: pre-wrap;"> </span>

```ini
[env:esp32-c3-devkitm-1]
platform = espressif32                    ; Khai báo nền tảng sử dụng
board = esp32-c3-devkitm-1                ; Khai báo board, thay đổi với những board khác nhau
framework = arduino                       ; Khai báo framework
lib_deps =                                ; Khai báo thư viện
	SPI                                   ;  với "hello world" thì chưa cần đến thư viện nào
	SD                                    ;  có thể xóa bỏ lib_deps nếu chưa dùng đến
monitor_speed = 115200                    ; Khai báo tốc độ giao tiếp Serial Monitor
build_flags =                             ; Tương tự như Scripting Define Symbols trong Unity
	-D ARDUINO_USB_MODE=1                 ;  cần 2 báo 2 trường tương tự như trong ví dụ 
	-D ARDUINO_USB_CDC_ON_BOOT=1          ;  để có thể xem được log
```

### Các hàm cơ bản

```c++
// Một file main.cpp cơ bản khi làm việc với ESP32

#include <Arduino.h> // Khai báo thư viện

// Tương tự như Awake trong Unity.
// Hàm này được gọi 1 lần khi ESP32 khởi động.
// Dùng để khởi tạo, setup...
void setup() 
{
}

// Tương tự như hàm Update trong Unity
// Lặp lại mỗi khi các lệnh bên trong loop được thực hiện hết
void loop()
{
}
```

### "Hello World!!!"

```c++
#include <Arduino.h>

// Khai báo chân giao tiếp với đèn led
const int LED_PIN = 8;

void setup() 
{
  // Setup chân led là output
  pinMode(LED_PIN, OUTPUT);

  // Khởi tạo serial có tốc độ trùng với tốc độ đã khai báo ở platformio.ini
  Serial.begin(115200);
  // Chờ đến khi serial khởi tạo xong hoặc hết thời gian chờ
  unsigned long start = millis();
  while (!Serial && millis() - start < 2000)
  {
    delay(1);
  }

  // Delay 1s để cửa sổ Serial Monitor kịp bật lên và nhìn thấy log
  // Nếu không có thì code vẫn chạy được nhưng của sổ Serial Monitor bật lên sau sẽ không nhìn thấy log
  delay(1000);

  // Hello world đây rồi =)))
  Serial.println("Hello world!!!");
}

void loop()
{
  digitalWrite(LED_PIN, LOW);   // Bật đèn LED
  delay(100);                   // Chờ 0.1 giây
  digitalWrite(LED_PIN, HIGH);  // Tắt đèn LED
  delay(100);                   // Chờ 0.1 giây
}
```

<span style="white-space: pre-wrap;">Sau khi code xong thì thực hiện </span>

- Kết nối ESP32 với PC bằng cáp USB
- Upload and Monitor (trong VSCode &gt; tab PlatformIO trên Activity Bar &gt; Upload and Monitor)

[![image.png](https://knowledge.thanhdv.com/uploads/images/gallery/2025-10/scaled-1680-/image.png)](https://knowledge.thanhdv.com/uploads/images/gallery/2025-10/image.png)

Chờ đến khi code được nạp xong. Nhìn cửa sổ terminal và mong chờ kết quả:

- Dòng text "Hello world!!!" được in ra 1 lần
- Trên board ESP32 led xanh nháy liên tục

### Hành trình fix bug đầu tiên

#### Vấn đề

<span style="white-space: pre-wrap;">Mặc dù đã nạp code thành công. Đèn nguồn có sáng nhưng tôi không nhận được bất kỳ dòng log nào dù có viết </span>`<span class="editor-theme-code">Serial.println("Hello world!!!");</span>`<span style="white-space: pre-wrap;"> ở </span>`<span class="editor-theme-code">setup()</span>`<span style="white-space: pre-wrap;"> hay </span>`<span class="editor-theme-code">loop()</span>`

#### Quá trình debug

##### Kiểm tra chip

Để đảm bảo ESP32 này không chết trong lúc mình hàn. Nên mình đã dùng code điều khiển led để kiểm tra xem nó có còn hoạt động bình thường không.

Rất mừng là nó còn sống. Trước đấy vừa chết một con xong (T.T)

**=&gt; Vấn đề có thể do giao tiếp Serial qua USB**

##### Kiểm tra các lỗi cấu hình

<span style="white-space: pre-wrap;">Đảm bảo tốc độ </span>`<span class="editor-theme-code">monitor_speed = 115200</span>`<span style="white-space: pre-wrap;"> là trùng nhau trong </span>`<span class="editor-theme-code">platformio.ini</span>`<span style="white-space: pre-wrap;"> và </span>`<span class="editor-theme-code">main.cpp</span>`

<span style="white-space: pre-wrap;">Thử delay trước khi khởi tạo </span>`<span class="editor-theme-code">Serial.begin(115200);</span>`

Nhưng vấn đề vẫn không được giải quyết.

**=&gt; Có thể vấn đề do driver**

##### Kiểm tra các môi trường khác

Chuyển sang macOS và sử dụng CLion với PlatformIO =&gt; Vẫn không được!!!

Dùng Ardruino IDE để code và nạp code =&gt; Code đã chạy và monitor đã hiện log.

**=&gt; Vấn đề có thể do cấu hình**

##### Mấu chốt

<span style="white-space: pre-wrap;">So sánh 2 cách cấu hình thì trên Ardruino IDE có </span>`<span class="editor-theme-code">USB CDC On Boot: "Enabled"</span>`.

<span style="white-space: pre-wrap;">Tương tự thêm </span>`<span class="editor-theme-code">build_flags</span>`<span style="white-space: pre-wrap;"> </span>`<span class="editor-theme-code">-D ARDUINO_USB_CDC_ON_BOOT=1</span>`<span style="white-space: pre-wrap;"> vào </span>`<span class="editor-theme-code">platformio.ini</span>`<span style="white-space: pre-wrap;"> thì sẽ gặp lỗi biên dịch của Serial. Cần phải thêm cả cờ </span>`<span class="editor-theme-code">-D ARDUINO_USB_MODE=1.</span>`

**Lúc này vấn đề đã được giải quyết.**

#### Giải thích

Mạch ESP32 - C3 SuperMini đang dùng sử dụng Native USB. Nghĩa là con chip sẽ tự mình xử lú giao tiếp USB. Để tính năng này hoạt động đúng thì trình biên dịch cần thực hiện 2 việc:

- `<span class="editor-theme-code">-D ARDUINO_USB_MODE=1</span>`: Chuyển chế độ hoạt động của cổng USB sang chế độ CDC
- `<span class="editor-theme-code">-D ARDUINO_USB_CDC_ON_BOOT=1</span>`: Kích hoạt chế độ CDC ngay khi khởi động.

# Phần 3: Màn hình

Ok, đầu tiên mình muốn chọn lắp màn hình trước.

<span style="white-space: pre-wrap;">Màn hình là phần giúp hiển thị trực quan nhất. Mình có thể thấy được thành quả của mình hiển thị ngay trên màn hình chứ không phải chỉ ở mỗi </span>`<span class="editor-theme-code">Serial.print</span>`

<span style="white-space: pre-wrap;">Màn hình cũng giúp mình kiểm tra được những lỗi phần cứng mà chỉ dùng </span>`<span class="editor-theme-code">Serial.print</span>`<span style="white-space: pre-wrap;"> thì không thể tìm ra hết được.</span>

### Lắp đặt

<table id="bkmrk-m%C3%A0n-h%C3%ACnhesp32-c3ch%E1%BA%AFc"><colgroup><col></col><col style="width: 88px;"></col><col></col><col></col><col></col></colgroup><tbody><tr><td>**Màn hình**

</td><td>**ESP32-C3**

</td><td>**Chắc năng**

</td><td>**Lý do**

</td><td>**Ghi Chú**

</td></tr><tr><td>VCC

</td><td>**3.3V** hoặc 5V

</td><td>Cấp nguồn cho màn hình.

</td><td>Nguồn vào từ USB qua ESP32 có thể đẩy ra thành 2 nguồn 3.3V hoặc 5V.

</td><td>Với những thiết bị nhẹ điện. Có thể sử dụng nguồn từ ESP32.

Chọn nguồn phù hợp với thiết bị

</td></tr><tr><td>GND

</td><td>GND

</td><td>Nối đất

</td><td>Tạo một điểm tham chiếu chung cho toàn bộ thiết bị trên hệ thống

</td><td>Toàn bộ thiết bị đều cần kết nối GND

</td></tr><tr><td>SCK

</td><td>GPIO4

</td><td>Clock (SPI)

</td><td>Màn hình sử dụng giao thức SPI nên cần sử dụng chân clock  
GPIO4 là chân clock của mạch ESP32-C3 này

</td><td><span style="white-space: pre-wrap;">Chân </span>**clock** có thể thay đổi với những board mạch khác nhau. Kiểm tra datasheet để biết thêm chi tiết.

</td></tr><tr><td>SDA

</td><td>GPIO6

</td><td>Data out (MOSI)

</td><td>Màn hình nhận dữ liệu từ cổng ra của ESP32

</td><td>MOSI cũng là một chân đặc biệt trên ESP32. Chú ý kiểm tra datasheet

</td></tr><tr><td>RES

</td><td>GPIO1

</td><td>Reset

</td><td>Là chân điều khiển không yêu cầu tốc độ cao. Chọn bất kỳ chân GPIO còn trống thuận tiện cho đi dây

</td><td>Tránh chọn các chân đặc biệt  
Cần chú ý khai báo chính xác trong code

</td></tr><tr><td>DC

</td><td>GPIO2

</td><td>Data/Command

</td><td>Tương tự như RES. Có thể chọn chân GPIO thuận tiện

</td><td></td></tr><tr><td>BLK

</td><td>GPIO3

</td><td>Điều khiển nền của màn hình

</td><td>Tương tự như RES và DC. Có thể chọn chân thuận tiện cho việc đi dây

</td><td></td></tr></tbody></table>

### Kiểm tra

#### Cài đặt thư viện TFT\_eSPI

> Arduino and PlatformIO IDE compatible TFT library optimised for the Raspberry Pi Pico (RP2040), STM32, ESP8266 and ESP32 that supports different driver chips

Để thêm thư viện chúng ta có thể thực hiện 1 trong 2 cách sau:

- <span style="white-space: pre-wrap;"> Truy cập PIO Home → Library → Search TFT\_eSPI và cài đặt library TFT\_eSPI của Bodmer</span>
- Hoặc thêm`<span class="editor-theme-code"> bodmer/TFT_eSPI</span>`<span style="white-space: pre-wrap;"> vào phần </span>`<span class="editor-theme-code">lib_deps</span>`<span style="white-space: pre-wrap;"> trong file </span>`<span class="editor-theme-code">platformio.ini</span>`

<span style="white-space: pre-wrap;">Khi đó file </span>`<span class="editor-theme-code">platformio.ini</span>`<span style="white-space: pre-wrap;"> sẽ trông như thế này</span>

```c++
[env:esp32-c3-devkitm-1]
platform = platformio/espressif32 @ 6.6.0
board = esp32-c3-devkitm-1
framework = arduino
platform_packages =
  framework-arduinoespressif32 @ ~3.20014.0
lib_deps = 
	SPI
	bodmer/TFT_eSPI@^2.5.43
monitor_speed = 115200
build_flags = 
	-D ARDUINO_USB_MODE=1
	-D ARDUINO_USB_CDC_ON_BOOT=1
```

Tiếp theo cần cấu hình thư viện để nó tương thích với đúng phần cứng.

<span style="white-space: pre-wrap;">Truy cập </span>`<span class="editor-theme-code">.pio → libdeps → esp32-c3-devkitm-1 → TFT_eSPI → User_Setup.h</span>`<span style="white-space: pre-wrap;"> và thực hiện config tương ứng với cấu hình phần cứng.</span>

```c#
// Driver của màn hình
#define ST7789_DRIVER
#define ESP32_C3_DMA_ERROR_WORKAROUND
#define TFT_RGB_ORDER TFT_RGB // Nếu màu in ra bị sai Blue và Red thì đổi lại thành TFT_BGR

// Kích thước của màn hình
#define TFT_WIDTH 240
#define TFT_HEIGHT 240

// Các chân kết nối đang được sử dụng
#define TFT_MOSI 6
#define TFT_SCLK 4
#define TFT_CS   -1

#define TFT_DC   2
#define TFT_RST  1
#define TFT_BL   3

// Tín hiệu bật/tắt đèn nền
#define TFT_BACKLIGHT_ON HIGH

// Các font sẽ được compile
#define LOAD_GLCD    // Font 1 (built-in), required for basic text
#define LOAD_FONT2   // Small 16 px font
#define LOAD_FONT4   // Medium 26 px font
#define LOAD_FONT6
#define LOAD_FONT7
#define LOAD_FONT8
#define LOAD_GFXFF  // FreeFonts (optional)
#define SMOOTH_FONT // Anti-aliased fonts (needs FS)

// Tốc độ SPI
#define SPI_FREQUENCY       40000000
#define SPI_READ_FREQUENCY  20000000
```

<p class="callout warning"><span style="white-space: pre-wrap;">Cách này có điểm yếu là sẽ bị </span>**reset** <span style="white-space: pre-wrap;">khi </span>`<span class="editor-theme-code">Full Clean</span>`<span style="white-space: pre-wrap;"> hoặc </span>`<span class="editor-theme-code">Update/Re-install</span>`<span style="white-space: pre-wrap;"> thư viện. Nhưng trong quá trình test các các setup khác đều gặp lỗi bootloop do config sai. Nên hiện tại vẫn sử dụng cách này để setup.</span></p>

#### "Hello World!"

Tiếp theo để test xem màn hình đã được setup đúng và phần cứng còn hoạt động tốt hay không. Chúng ta sẽ thử với đoạn code hiển thị "Hello world!" trên màn hình.

Truy cập`<span class="editor-theme-code"> src → main.cpp</span>`<span style="white-space: pre-wrap;"> và sử dụng đoạn code dưới đây:</span>

```c++
#include <Arduino.h>
#include <TFT_eSPI.h>

// Tạo đối tượng tft từ thư viện
TFT_eSPI tft = TFT_eSPI();

void setup() {
  tft.init(); // Khởi tại tft controller

  tft.setRotation(0); // Set hướng xoay màn hình // Sử dụng các tham số 0, 1, 2, 3
  tft.fillScreen(TFT_BLACK); // Fill toàn bộ màn hình bằng màu đen
  tft.setTextColor(TFT_GREEN); // Set màu chữ
  tft.setTextSize(2); // Set cỡ chữ
  tft.setCursor(0, 0); // Set tọa độ điểm bắt đầu viết chữ
  tft.println("Hello World!"); // Hiển thị chữ
}

void loop() {}
```

Sau đó thực hiện Upload. Quá trình Upload có thể có warning (console màu vàng) nhưng không ảnh hưởng gì.

<span style="white-space: pre-wrap;">Sau khi upload xong kiểm tra màn hình nếu hiển thị như hình dưới là đã chạy </span>**thành công** sẵn sàng cho bước tiếp theo thôi.[![1000048881.jpg](https://knowledge.thanhdv.com/uploads/images/gallery/2025-10/scaled-1680-/1000048881.jpg)](https://knowledge.thanhdv.com/uploads/images/gallery/2025-10/scaled-1680-/1000048881.jpg)<span style="white-space: pre-wrap;"> </span>

Trong trường hợp chưa hiển thị được "Hello World!" trên màn hình hãy kiểm tra cửa sổ terminal trên VSCode:

- <span style="white-space: pre-wrap;">Nếu </span>**có** log chạy liên tục thì rất có thể bạn bị bootloop.
- <span style="white-space: pre-wrap;">Nếu </span>**không** <span style="white-space: pre-wrap;">có hãy nhần nút </span>**Reset** trên ESP32 để ép ESP32 khởi động lại.

Nếu vẫn không được thực hiện check list sau:

- Kiểm tra các chân kết nối đảm bảo không chân nào bị lỏng.
- <span style="white-space: pre-wrap;">Kiểm tra lại file </span>`<span class="editor-theme-code">User_Setup.h</span>`<span style="white-space: pre-wrap;"> đảm bảo đã config đúng file, đúng tham số.</span>
- <span style="white-space: pre-wrap;">Đảm bảo đã khai báo đúng và đủ file </span>`<span class="editor-theme-code">platformio.ini</span>`
- Thực hiện Clean và Upload lại.

Nếu vẫn không được nữa. Thì nên thử với một cái màn hình khác để đảm bảo phần cứng không bị hỏng.

# Phần 4: Thẻ nhớ

Ok tiếp theo là lắp đặt thẻ nhớ.

Trong dự án này thẻ nhớ sẽ là nơi lưu trữ dữ liệu chính như hình gif, audio...

<span style="color: rgb(241, 196, 15);">Trước khi bắt đầu thì có một điều cần chú ý</span>

### Xử lý conflict phần cứng

Thẻ nhớ sử dụng giao tiếp SPI. Với ESP32 khi sử dụng nhiều thiết bị giao tiếp chung SPI thì cần có chân CS để điều khiển.

Trong trường hợp hiện tại chúng ta có 2 thiết bị giao tiếp SPI là thẻ nhớ và màn hình. Khi cần giao tiếp với thẻ nhớ thì tín hiệu CS trên thẻ nhớ được bật và tắt đối với màn hình.

Thế nhưng trong phần trước. Màn hình chúng ta sử dụng có 7 pin và không có chân CS. Điều này là do chân CS được nhà sản xuất nối trực tiếp với GND. Tín hiệu CS trên màn hình sẽ luôn ở trạng thái bật. Vì vậy chúng ta có 2 hướng giải quyết:

- Kiểu người có tiền: Thay màn mới cho đẹp
- Kiểu người hết tiền chúng ta có những bước sau:
    - Xác định chân CS đã bị hàn với GND trên màn hình. Là chân thứ 8 trên dây nối màn hình với PCB
    - Rã hàn để thấy được phần kết nối CS với GND
    - Ngắt kết nối của chân CS với GND bằng cách cắt qua lớp đồng trên PCB
    - Hàn lại dây nối màn hình với PCB
    - Hàn chân CS ra một chân mới và kết nối với ESP32.
    - <span style="white-space: pre-wrap;">Trong phần khai báo thư viện TFT\_eSPI phần </span>`<span class="editor-theme-code">#define TFT_CS -1</span>`<span style="white-space: pre-wrap;"> sửa thành </span>`<span class="editor-theme-code"> #define TFT_CS   <pin đã kết nối trên ESP32></span>`

[![ESP32_TheCube_Phan4_1.png](https://knowledge.thanhdv.com/uploads/images/gallery/2025-10/scaled-1680-/BB4esp32-thecube-phan4-1.png)](https://knowledge.thanhdv.com/uploads/images/gallery/2025-10/BB4esp32-thecube-phan4-1.png)[![ESP32_TheCube_Phan4_2.png](https://knowledge.thanhdv.com/uploads/images/gallery/2025-10/scaled-1680-/jl6esp32-thecube-phan4-2.png)](https://knowledge.thanhdv.com/uploads/images/gallery/2025-10/jl6esp32-thecube-phan4-2.png)[![ESP32_TheCube_Phan4_3.png](https://knowledge.thanhdv.com/uploads/images/gallery/2025-10/scaled-1680-/WDeesp32-thecube-phan4-3.png)](https://knowledge.thanhdv.com/uploads/images/gallery/2025-10/WDeesp32-thecube-phan4-3.png)

Ok vậy là xong. Đừng làm chết màn hình là được :)))

### Lắp đặt

<table id="bkmrk-m%C3%A0n-h%C3%ACnhesp32-c3ch%E1%BA%AFc"><colgroup><col></col><col></col><col></col><col></col></colgroup><tbody><tr><td>**Màn hình**

</td><td>**ESP32-C3**

</td><td>**Chắc năng**

</td><td>**Ghi chú**

</td></tr><tr><td>VCC (3.3)

</td><td>**3.3V** hoặc 5V

</td><td>Cấp nguồn cho màn hình.

</td><td>Nguồn vào từ USB qua ESP32 có thể đẩy ra thành 2 nguồn 3.3V hoặc 5V.

</td></tr><tr><td>GND

</td><td>GND

</td><td>Nối đất

</td><td>Tạo một điểm tham chiếu chung cho toàn bộ thiết bị trên hệ thống

</td></tr><tr><td>SCK

</td><td>GPIO4

</td><td>Clock (SPI)

</td><td>Dùng chung với Màn hình

</td></tr><tr><td>MOSI

</td><td>GPIO6

</td><td>Data out

</td><td>Dùng chung với Màn hình

</td></tr><tr><td>MISO

</td><td>GPIO5

</td><td>Data in

</td><td>Dùng chân chuyên dụng của ESP32

</td></tr><tr><td>CS

</td><td>GPIO7

</td><td>Điều khiển khi giao tiếp SPI

</td><td>Chọn bất kỳ chân GPIO còn trống thuận tiện cho đi dây

</td></tr></tbody></table>

### Kiểm tra

#### `<span class="editor-theme-code">platformio.ini</span>`

<span style="white-space: pre-wrap;">Trong phần thiết lập này chúng ta cần khai báo thêm thư viện SD trong </span>`<span class="editor-theme-code">lib_deps</span>`

```c++
[env:esp32-c3-devkitm-1]
platform = platformio/espressif32 @ 6.6.0
board = esp32-c3-devkitm-1
framework = arduino
platform_packages =
  framework-arduinoespressif32 @ ~3.20014.0
lib_deps = 
	SD // Khai báo thêm thư viện này
	SPI
	bodmer/TFT_eSPI@^2.5.43
monitor_speed = 115200
build_flags = 
	-D ARDUINO_USB_MODE=1
	-D ARDUINO_USB_CDC_ON_BOOT=1
```

#### Code and Test

```c++
#include <Arduino.h>
#include <TFT_eSPI.h>
#include <SD.h> // Thêm khai báo thư viện

// Tạo đối tượng tft từ thư viện // Để hiển thị kết quả test
TFT_eSPI tft = TFT_eSPI();

// Khai báo phần cứng cho SDCard
const int SDCARD_PIN_CS = 7;
const int SDCARD_PIN_SCK = 4;
const int SDCARD_PIN_MISO = 5;
const int SDCARD_PIN_MOSI = 6;

// Khai báo hàm
void initTFT(); // Khởi tạo màn hình
void initSD(); // Khởi tạo SDCard
void listDir(fs::FS &fs, const char * dirname, uint8_t levels); // Hàm hiển thị các tệp trong SD // Dùng để test chưa cần quan tâm nhiều

void setup() {
  initTFT();
  initSD();
}

int count = 0;
void loop() {}

void initTFT()
{
  tft.init(); 
  tft.setRotation(0);
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_GREEN);
  tft.setTextSize(1);
  tft.setCursor(0, 0);
  tft.println("Init TFT success!!!");
}

void initSD()
{
  SPI.end();
  SPI.begin(SDCARD_PIN_SCK, SDCARD_PIN_MISO, SDCARD_PIN_MOSI, SDCARD_PIN_CS); // Khởi tạo giao tiếp SPI

  tft.print("Initializing SD card using CS pin: ");
  tft.println(SDCARD_PIN_CS);

  if (!SD.begin(SDCARD_PIN_CS, SPI)) {
    tft.setTextColor(TFT_RED);
    tft.println("SD mount failed (wiring/freq).");
    return;
  }

  tft.setTextColor(TFT_GREEN);
  tft.println("SD card initialized.");

  uint8_t type = SD.cardType();
  if (type == CARD_NONE) {
    tft.setTextColor(TFT_RED);
    tft.println("No SD card detected.");
    return;
  }
  tft.printf("Type: %s\n", type == CARD_MMC ? "MMC" :
                         type == CARD_SD  ? "SDSC" :
                         type == CARD_SDHC? "SDHC/SDXC" : "UNKNOWN");
  tft.printf("Size: %llu MB\n", SD.cardSize() / (1024ULL * 1024ULL));

  tft.println("Filesystem Content:");
  listDir(SD, "/", 0);
  tft.println("\n--- Test Complete ---");
}

// Hàm liệt kê nội dung thư mục (lấy từ ví dụ của thư viện SD)
void listDir(fs::FS &fs, const char * dirname, uint8_t levels) {
  tft.printf("Listing directory: %s\n", dirname);

  File root = fs.open(dirname);
  if (!root) {
    tft.println("Failed to open directory");
    return;
  }
  if (!root.isDirectory()) {
    tft.println("Not a directory");
    return;
  }

  File file = root.openNextFile();
  while (file) {
    if (file.isDirectory()) {
      tft.print("  DIR : ");
      tft.println(file.name());
      if (levels) {
        listDir(fs, file.path(), levels - 1);
      }
    } else {
      tft.print("  FILE: ");
      tft.print(file.name());
      tft.print("\tSIZE: ");
      tft.println(file.size());
    }
    file = root.openNextFile();
  }
}
```

Upload and Monitor

Kết quả hiển thị như hình dưới (hoặc tương tự toàn màu green) là OK

Nếu chỉ hiển thị đến "Initializing SD card using CS pin: " thì cần check thêm terminal để kiểm tra lỗi

- Có thể conflict phần cứng nhắc tới ở trên chưa được xử lý
- Thẻ nhớ chưa được format đúng (ESP32)
- Lỗi thẻ nhớ hoặc module thẻ nhớ

[![1000048897.jpg](https://knowledge.thanhdv.com/uploads/images/gallery/2025-10/scaled-1680-/1000048897.jpg)](https://knowledge.thanhdv.com/uploads/images/gallery/2025-10/scaled-1680-/1000048897.jpg)