IoTLabs

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

Lập trình ESP32 & NRF – Phần 4: Gửi dữ liệu không dây qua NRF24L01

Giới thiệu series “Lập trình ESP32 38 Pin & NRF” – Phần 4

Phần 3, bạn đã tìm hiểu chi tiết về module NRF24L01 và cách kết nối SPI với ESP32 38 pin, đồng thời dùng radio.begin() để kiểm tra module hoạt động.

Trong Phần 4 này, chúng ta sẽ bước vào phần thú vị hơn:

  • Cấu hình 2 bộ ESP32 + NRF24L01.
  • Một bộ làm TX (bộ phát) và một bộ làm RX (bộ nhận).
  • Gửi chuỗi “Hello World” không dây từ TX sang RX.
  • Xem dữ liệu trên Serial Monitor và kiểm tra hệ thống.

Đây là bước nền tảng trước khi xây dựng mạng cảm biến nhiều node và các ứng dụng IoT phức tạp hơn trong các phần sau.

1. Mục tiêu bài học

Sau khi hoàn thành bài này, bạn sẽ:

  • Biết cách thiết lập địa chỉ truyền cho NRF24L01 (pipe address).
  • Biết cách cấu hình một bộ là bộ phát (TX), một bộ là bộ nhận (RX).
  • Hiểu cách dùng các hàm chính của thư viện RF24:
    • openWritingPipe(), openReadingPipe(), startListening(), stopListening(), radio.write(), radio.read().
  • Tự chạy thử demo “Hello World” không dây giữa 2 bộ ESP32.
  • Biết một số lỗi thường gặp và cách khắc phục khi không nhận được dữ liệu.

2. Chuẩn bị phần cứng

Để thực hiện bài 4, bạn cần chuẩn bị:

2.1. Danh sách linh kiện

  • 2 x ESP32 DevKit 38 pin (2 board riêng).
  • 2 x Module NRF24L01 loại thường (8 chân).
  • 2 x Breadboard nhỏ (khuyến khích).
  • Dây Dupont đực–cái để nối ESP32 với NRF.
  • 2 tụ điện 10µF – 47µF (tụ hóa hoặc tantalum) gắn gần mỗi module NRF để lọc nguồn.
  • 2 cáp USB để kết nối mỗi ESP32 với máy tính (2 cổng USB).

2.2. Nhắc lại sơ đồ kết nối ESP32 ↔ NRF24L01

Chúng ta sẽ dùng chung một kiểu wiring cho cả bộ TX và RX như sau:

NRF24L01ESP32 DevKit 38-pinGhi chú
GNDGNDGND chung giữa ESP32 và NRF
VCC3V3Chỉ dùng 3.3V, không cấp 5V
CEGPIO 4Chân CE điều khiển mode TX/RX
CSN (CS)GPIO 5Chân Chip Select SPI
SCKGPIO 18VSPI SCK
MOSIGPIO 23VSPI MOSI
MISOGPIO 19VSPI MISO
IRQ(Không nối)Có thể bỏ trống trong giai đoạn cơ bản

Gợi ý: Bạn có thể định nghĩa các chân này trong một file pins_nrf24_esp32.h dùng chung cho các bài sau.

3. Khái niệm TX/RX và địa chỉ truyền trên NRF24L01

3.1. Bộ phát (TX) và bộ nhận (RX)

  • TX (Transmitter): chỉ gửi dữ liệu ra không gian.
  • RX (Receiver): chỉ nghe và nhận dữ liệu.

Trong ví dụ của chúng ta:

  • ESP32 số 1 + NRF24L01 → đóng vai bộ phát (TX).
  • ESP32 số 2 + NRF24L01 → đóng vai bộ nhận (RX).

3.2. Địa chỉ pipe (pipe address)

NRF24L01 sử dụng địa chỉ 5 byte để phân biệt các “đường truyền” (pipe). Cả TX và RX phải dùng cùng một địa chỉ thì mới nói chuyện được.

Ví dụ, ta chọn 1 địa chỉ đơn giản:

const byte address[6] = "00001"; // 5 byte chu + ky tu ket thuc chuoi

Hoặc bạn có thể dùng dạng mảng ký tự rõ ràng:

const byte address[5] = {'N', 'O', 'D', 'E', '1'};

Trong bài này, để đồng bộ với nhiều ví dụ RF24 phổ biến, ta sẽ dùng:

const byte address[6] = "00001";

4. Thiết lập bộ phát (TX) – ESP32 gửi “Hello World”

4.1. Ý tưởng

  • Mỗi 1 giây, ESP32 (TX) sẽ gửi một chuỗi dạng: "Hello World #n" qua NRF24L01.
  • Chuỗi này được gửi tới địa chỉ pipe chung address.
  • Bộ nhận (RX) sẽ đọc và in ra Serial.

4.2. Code bộ phát (TX)

#include <Arduino.h>
#include <SPI.h>
#include <RF24.h>

// Chan NRF24L01
#define PIN_NRF_CE   4
#define PIN_NRF_CSN  5

RF24 radio(PIN_NRF_CE, PIN_NRF_CSN); // CE, CSN

// Dia chi pipe chung giua TX va RX
const byte address[6] = "00001"; // 5 ky tu + ky tu ket thuc

unsigned long counter = 0;

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println();
  Serial.println(F("=== ESP32 NRF24L01 TX - Hello World Demo ==="));

  if (!radio.begin()) {
    Serial.println(F("Loi: Khong khoi tao duoc NRF24L01 (TX)."));
    while (1) {
      delay(1000);
    }
  }

  // Cau hinh thong so truyen
  radio.setChannel(108);        // Kienh 108 (2.508 GHz), co the thay doi
  radio.setPALevel(RF24_PA_LOW); // Cong suat thap cho test gan
  radio.setDataRate(RF24_1MBPS); // Toc do 1Mbps, on dinh

  // Mo pipe ghi
  radio.openWritingPipe(address);

  // TX chi gui -> dung nghe
  radio.stopListening();

  Serial.println(F("TX san sang gui du lieu..."));
}

void loop() {
  char text[32];
  snprintf(text, sizeof(text), "Hello World #%lu", counter++);

  bool ok = radio.write(&text, sizeof(text));

  if (ok) {
    Serial.print(F("Da gui: "));
    Serial.println(text);
  } else {
    Serial.println(F("Gui that bai!"));
  }

  delay(1000); // gui moi 1 giay
}

4.3. Giải thích nhanh

  • radio.setChannel(108);
    • Thiết lập kênh tần số. Bạn có thể dùng giá trị 76–120 tùy môi trường, miễn TX & RX giống nhau.
  • radio.setPALevel(RF24_PA_LOW);
    • Tùy chọn mức công suất phát. Khi test gần, nên để LOW hoặc MIN để ổn định.
  • radio.setDataRate(RF24_1MBPS);
    • Thiết lập tốc độ truyền. 1MBPS thường ổn định hơn 2MBPS.
  • radio.openWritingPipe(address);
    • Mở pipe dùng để ghi (gửi) dữ liệu tới địa chỉ address.
  • radio.stopListening();
    • Chuyển module về chế độ chỉ gửi.

5. Thiết lập bộ nhận (RX) – ESP32 nhận “Hello World”

5.1. Ý tưởng

  • ESP32 (RX) sẽ mở một pipe để lắng nghe tại cùng địa chỉ address.
  • Mỗi khi có dữ liệu tới, NRF24L01 sẽ báo có dữ liệu.
  • ESP32 đọc dữ liệu và in ra Serial Monitor.

5.2. Code bộ nhận (RX)

#include <Arduino.h>
#include <SPI.h>
#include <RF24.h>

// Chan NRF24L01
#define PIN_NRF_CE   4
#define PIN_NRF_CSN  5

RF24 radio(PIN_NRF_CE, PIN_NRF_CSN); // CE, CSN

// Dia chi pipe chung giua TX va RX
const byte address[6] = "00001";

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println();
  Serial.println(F("=== ESP32 NRF24L01 RX - Hello World Demo ==="));

  if (!radio.begin()) {
    Serial.println(F("Loi: Khong khoi tao duoc NRF24L01 (RX)."));
    while (1) {
      delay(1000);
    }
  }

  radio.setChannel(108);         // Phai trung voi TX
  radio.setPALevel(RF24_PA_LOW); // Cong suat giong TX
  radio.setDataRate(RF24_1MBPS); // Toc do giong TX

  // Mo pipe 1 o che do doc (so 1 la index pipe)
  radio.openReadingPipe(1, address);

  // Bat che do nghe
  radio.startListening();

  Serial.println(F("RX dang lang nghe du lieu..."));
}

void loop() {
  if (radio.available()) {
    char text[32] = {0};
    radio.read(&text, sizeof(text));

    Serial.print(F("Nhan duoc: "));
    Serial.println(text);
  }

  // Nho delay nhe, tranh spam CPU
  delay(10);
}

5.3. Giải thích nhanh

  • radio.openReadingPipe(1, address);
    • Mở pipe số 1 để nhận dữ liệu trên địa chỉ address.
    • Có thể có nhiều pipe (0–5) với địa chỉ khác nhau.
  • radio.startListening();
    • Chuyển module sang chế độ nghe, chờ dữ liệu đến.
  • radio.available():
    • Trả về true nếu có gói tin trong buffer.
  • radio.read(&text, sizeof(text));
    • Đọc dữ liệu vào buffer text.

6. Cách nạp code và chạy thử demo Hello World

6.1. Nạp code cho bộ TX

  1. Cắm ESP32 số 1 vào máy tính.
  2. Mở Arduino IDE.
  3. Mở file code TX.
  4. Chọn đúng Board ESP32cổng COM.
  5. Upload chương trình.

6.2. Nạp code cho bộ RX

  1. Cắm ESP32 số 2 vào máy tính (hoặc một cổng USB khác).
  2. Mở một cửa sổ Arduino IDE khác (hoặc dùng VS Code + PlatformIO tạo 2 môi trường).
  3. Mở file code RX.
  4. Chọn đúng Board ESP32cổng COM tương ứng.
  5. Upload chương trình.

6.3. Mở Serial Monitor

  • Mở 2 Serial Monitor cho 2 ESP32 (TX & RX), cùng tốc độ baud 115200.
  • Trên cửa sổ TX, bạn sẽ thấy:
    • Da gui: Hello World #0
    • Da gui: Hello World #1
  • Trên cửa sổ RX, bạn sẽ thấy:
    • Nhan duoc: Hello World #0
    • Nhan duoc: Hello World #1

Nếu bạn thấy chuỗi “Hello World #n” xuất hiện đều đặn ở cả 2 bên, điều đó có nghĩa là hệ thống truyền không dây NRF24L01 giữa 2 ESP32 đã hoạt động tốt.

7. Một số lỗi thường gặp và cách khắc phục

Trong quá trình thử nghiệm, bạn có thể gặp một số vấn đề sau:

7.1. radio.begin() báo lỗi, chương trình đứng

  • Kiểm tra lại dây:
    • VCC có đúng 3.3V không?
    • GND đã nối chung chưa?
    • Các chân SCK, MOSI, MISO, CE, CSN có đúng như mapping không?
  • Gắn thêm tụ lọc 10µF – 47µF giữa VCC–GND gần module NRF.
  • Thử đổi module NRF sang cái khác để loại trừ khả năng hỏng phần cứng.

7.2. TX báo “Da gui” nhưng RX không nhận được gì

  • Kiểm tra:
    • setChannel() trên TX và RX có giống nhau không?
    • setDataRate() trên TX và RX có cùng cấu hình không?
    • Địa chỉ address của TX & RX có giống y hệt không?
  • Thử để 2 module cách nhau 1–2m, tránh để quá sát.
  • Đảm bảo 2 board đều được cấp nguồn ổn định (cắm USB vào máy tính hoặc adapter chất lượng).

7.3. Dữ liệu nhận bị lỗi ký tự

  • Đảm bảo kích thước buffer nhận (text[32]) không nhỏ hơn kích thước dữ liệu gửi.
  • Khi gửi chuỗi, bạn có thể gửi full sizeof(text) để tránh lỗi:
radio.write(&text, sizeof(text));
// va ben RX: radio.read(&text, sizeof(text));
  • Nếu muốn tiết kiệm băng thông hơn, có thể gửi strlen(text) + 1, nhưng phải chắc chắn hai bên dùng cùng cách.

8. Bài tập mở rộng sau demo Hello World

Để luyện thêm và chuẩn bị cho các phần tiếp theo, bạn có thể thử:

8.1. Gửi số nguyên hoặc cấu trúc dữ liệu

  • Thay vì gửi chuỗi, hãy gửi một biến int, float hoặc một struct đơn giản chứa:
    • nodeId
    • temperature
    • humidity

8.2. Thêm LED báo trạng thái

  • Trên bộ RX, mỗi khi nhận được dữ liệu, hãy nháy LED trên một chân GPIO.
  • Điều này rất hữu ích khi bạn đưa hệ thống ra khỏi bàn test (không có Serial Monitor).

8.3. Tự đổi kênh, tốc độ, công suất

  • Thử thay đổi setChannel(), setPALevel(), setDataRate() để xem sự khác biệt về độ ổn định khi truyền.

9. Kết luận & chuẩn bị cho Phần 5

Trong Phần 4 của series “Lập trình ESP32 38 Pin & NRF”, bạn đã:

  • Cấu hình thành công 2 bộ ESP32 + NRF24L01.
  • Thiết lập một bộ làm TX và bộ còn lại làm RX.
  • Gửi và nhận chuỗi “Hello World #n” qua đường truyền không dây.
  • Biết cách debug các lỗi thường gặp khi NRF24L01 không hoạt động như mong đợi.

Đây là bước đệm quan trọng để chúng ta tiến tới các ứng dụng thực tế hơn.

Trong Phần 5, chúng ta sẽ:

Định nghĩa cấu trúc gói tin (struct) để gửi dữ liệu cảm biến – ví dụ: nhiệt độ, độ ẩm, nodeId, uptime – từ nhiều node về một gateway ESP32.

Hẹn gặp bạn ở Phần 5 trong series “Lập trình ESP32 38 Pin & NRF” trên iotlabs.vn!

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *