IoTLabs

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

Lập trình ESP32 & NRF – Phần 5: Thiết kế cấu trúc gói tin: gửi số & dữ liệu cảm biến

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

Phần 4, bạn đã cấu hình thành công 2 bộ ESP32 + NRF24L01 để gửi chuỗi “Hello World” từ TX sang RX.

Trong Phần 5, chúng ta sẽ tiến thêm một bước quan trọng: thay vì chỉ gửi chuỗi text, ta sẽ thiết kế gói tin dạng struct để truyền dữ liệu cảm biến một cách gọn gàng và dễ mở rộng:

  • Gửi nodeId (mã thiết bị).
  • Gửi nhiệt độ, độ ẩm đọc từ cảm biến.
  • Gửi uptime (thời gian thiết bị đã chạy).

Kỹ thuật này là nền tảng cho:

  • Mạng nhiều node cảm biến → 1 gateway.
  • Các dự án IoT thực tế như giám sát môi trường, nhà thông minh, nông nghiệp thông minh.

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

Sau bài này, bạn sẽ:

  • Biết cách định nghĩa cấu trúc gói tin (struct) cho dữ liệu cảm biến.
  • Gửi và nhận nguyên gói struct qua NRF24L01.
  • Hiểu các giới hạn payload của NRF24L01 và cách thiết kế dữ liệu cho phù hợp.
  • In dữ liệu cảm biến ra Serial một cách rõ ràng để kiểm tra.

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

2.1. Danh sách linh kiện

  • 2 x ESP32 DevKit 38 pin.
  • 2 x Module NRF24L01 loại thường (8 chân).
  • 1 x Cảm biến nhiệt độ/độ ẩm (ví dụ: DHT11 hoặc DHT22).
  • 2 x Breadboard nhỏ + dây Dupont đực–cái.
  • 2 x tụ 10µF – 47µF gắn sát VCC–GND của mỗi module NRF.
  • 2 x cáp USB để nối 2 ESP32 với máy tính.

2.2. Kết nối lại ESP32 ↔ NRF24L01

Sơ đồ kết nối cho cả bộ phát và bộ nhận giống Bài 4:

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)Bỏ trống ở mức cơ bản

Bạn nên định nghĩa các chân này bằng #define hoặc trong file pins_nrf24_esp32.h dùng chung.

2.3. Kết nối ESP32 với cảm biến DHT11/DHT22

Ví dụ dùng DHT22 (tương tự cho DHT11):

  • VCC → 3V3 trên ESP32.
  • GND → GND.
  • DATA → GPIO 15 (bạn có thể chọn chân digital khác, miễn không trùng SPI).

Nhớ gắn điện trở kéo lên (pull-up) 10k giữa DATA và VCC nếu module DHT bạn dùng dạng “sensor rời” chưa có sẵn trở.

3. Thiết kế cấu trúc gói tin dữ liệu cảm biến

NRF24L01 hỗ trợ payload tối đa 32 byte cho mỗi gói tin. Vì vậy, bạn nên thiết kế gói tin vừa đủ – không quá cồng kềnh.

Ví dụ một cấu trúc gói tin đơn giản:

struct SensorPacket {
  uint8_t  nodeId;      // ID cua node (0-255)
  float    temperature; // Nhiet do (°C)
  float    humidity;    // Do am (%)
  uint32_t uptime;      // Thoi gian chay (giay)
};

Tổng kích thước ước tính:

  • uint8_t: 1 byte.
  • float: 4 byte (tùy kiến trúc, nhưng với ESP32 là 4 byte).
  • float: 4 byte.
  • uint32_t: 4 byte.

→ Tổng: 13 byte (chưa tính padding, nhưng vẫn < 32 byte, rất an toàn).

Lưu ý: Một số compiler có thể thêm byte đệm (padding) để căn lề, nhưng với cấu trúc này vẫn nằm trong giới hạn 32 byte.

Bạn có thể thêm trường khác như battery sau này, miễn sizeof(SensorPacket) <= 32.

4. Code bộ phát (TX) – Node cảm biến gửi struct

4.1. Thư viện cần dùng

  • RF24 cho NRF24L01.
  • DHT cho DHT11/DHT22.

Bạn cài thư viện DHT sensor library (by Adafruit) trong Arduino IDE nếu chưa có.

4.2. Code mẫu cho bộ phát (TX)

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

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

// Chan cam bien DHT
#define DHT_PIN      15
#define DHT_TYPE     DHT22  // hoac DHT11 neu ban dung DHT11

RF24 radio(PIN_NRF_CE, PIN_NRF_CSN); // CE, CSN
DHT dht(DHT_PIN, DHT_TYPE);

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

struct SensorPacket {
  uint8_t  nodeId;
  float    temperature;
  float    humidity;
  uint32_t uptime;
};

SensorPacket packet;

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

  dht.begin();

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

  radio.setChannel(108);         // Giong RX
  radio.setPALevel(RF24_PA_LOW);
  radio.setDataRate(RF24_1MBPS);

  radio.openWritingPipe(address);
  radio.stopListening(); // che do gui

  // Gan nodeId cho node nay (vd: 1)
  packet.nodeId = 1;

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

void loop() {
  float h = dht.readHumidity();
  float t = dht.readTemperature(); // mac dinh do C

  // Kiem tra loi doc cam bien
  if (isnan(h) || isnan(t)) {
    Serial.println(F("Loi: Khong doc duoc cam bien DHT!"));
    delay(2000);
    return;
  }

  packet.temperature = t;
  packet.humidity    = h;
  packet.uptime      = millis() / 1000; // giay

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

  if (ok) {
    Serial.print(F("Da gui - nodeId: "));
    Serial.print(packet.nodeId);
    Serial.print(F(", T: "));
    Serial.print(packet.temperature, 1);
    Serial.print(F(" °C, H: "));
    Serial.print(packet.humidity, 1);
    Serial.print(F(" %, uptime: "));
    Serial.print(packet.uptime);
    Serial.println(F(" s"));
  } else {
    Serial.println(F("Gui struct that bai!"));
  }

  delay(2000); // gui moi 2 giay
}

4.3. Điểm cần chú ý

  • SensorPacket packet; được dùng chung, mỗi vòng lặp cập nhật lại dữ liệu.
  • Gửi thẳng radio.write(&packet, sizeof(packet)); để chuyển toàn bộ struct.
  • nodeId giúp phân biệt nhiều node trong tương lai (Bài 6).

5. Code bộ nhận (RX) – Gateway nhận struct và in Serial

Bộ nhận sẽ:

  • Lắng nghe trên cùng địa chỉ address.
  • Mỗi khi có gói tin, đọc vào một biến SensorPacket.
  • In đầy đủ các trường ra Serial Monitor.

5.1. Code mẫu cho 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

const byte address[6] = "00001";

struct SensorPacket {
  uint8_t  nodeId;
  float    temperature;
  float    humidity;
  uint32_t uptime;
};

SensorPacket packet;

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

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

  radio.setChannel(108);
  radio.setPALevel(RF24_PA_LOW);
  radio.setDataRate(RF24_1MBPS);

  radio.openReadingPipe(1, address);
  radio.startListening();

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

void loop() {
  if (radio.available()) {
    radio.read(&packet, sizeof(packet));

    Serial.print(F("Nhan duoc tu node "));
    Serial.print(packet.nodeId);
    Serial.print(F(" - T: "));
    Serial.print(packet.temperature, 1);
    Serial.print(F(" °C, H: "));
    Serial.print(packet.humidity, 1);
    Serial.print(F(" %, uptime: "));
    Serial.print(packet.uptime);
    Serial.println(F(" s"));
  }

  delay(10);
}

5.2. Giải thích nhanh

  • radio.read(&packet, sizeof(packet)); đọc toàn bộ gói struct.
  • In chi tiết với packet.nodeId, packet.temperature, packet.humidity, packet.uptime.
  • Khi mở rộng nhiều node, bạn chỉ cần phân biệt theo nodeId.

6. Kiểm tra hoạt động và debug khi có lỗi

6.1. Các bước kiểm tra cơ bản

  1. Nạp code TX cho ESP32 có gắn DHT.
  2. Nạp code RX cho ESP32 còn lại.
  3. Mở hai Serial Monitor (TX và RX) ở Baud 115200.
  4. Quan sát:
    • Bên TX: log gửi struct, giá trị nhiệt độ/độ ẩm thay đổi.
    • Bên RX: log nhận struct với cùng giá trị.

Nếu tất cả hiển thị ổn định, bạn đã truyền thành công dữ liệu cảm biến bằng struct.

6.2. Một số lỗi thường gặp

  • Không đọc được cảm biến DHT:
    • Kiểm tra wiring VCC, GND, DATA.
    • Kiểm tra khai báo DHT_PIN, DHT_TYPE.
    • DHT cần vài giây đầu để ổn định sau khi cấp nguồn.
  • TX báo gửi thành công nhưng RX không nhận được:
    • Kiểm tra channel, data rate, address giống nhau.
    • Đảm bảo 2 module NRF không quá sát nhau (để cách tầm 1–2m).
    • Kiểm tra lại nguồn 3.3V và tụ lọc.
  • Giá trị nhận bị “NaN” hoặc sai lệch:
    • Đảm bảo sizeof(SensorPacket) giống nhau trên cả TX và RX (cùng code, cùng compiler).
    • Không sửa struct ở bên TX mà quên sửa ở bên RX.

7. Bài tập mở rộng với struct dữ liệu

Để sẵn sàng cho các phần nâng cao, bạn có thể thử:

7.1. Thêm mức pin vào gói tin

Thêm trường float batteryVoltage; vào SensorPacket và gửi điện áp pin đo ở TX.

7.2. Thiết kế nhiều loại gói tin

Thêm trường uint8_t packetType; để phân biệt:

  • 0: gói dữ liệu cảm biến.
  • 1: gói cấu hình.
  • 2: gói báo lỗi.

Ở RX, dựa trên packetType để xử lý khác nhau.

7.3. In log dạng “bảng” dễ đọc

  • Trên RX, in dữ liệu dạng:
Node | T(°C) | H(%) | Uptime(s)
  1  |  27.3 | 65.2 |    120
  1  |  27.4 | 65.1 |    122

Điều này giúp bạn dễ quan sát hơn khi có nhiều node.

8. Kết luận & chuẩn bị cho Phần 6

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

  • Thiết kế một struct gói tin dữ liệu cảm biến gọn nhẹ, phù hợp giới hạn 32 byte của NRF24L01.
  • Gửi và nhận thành công struct giữa 2 ESP32 + NRF24L01.
  • Hiển thị dữ liệu cảm biến (nhiệt độ, độ ẩm, uptime) trên Serial.
  • Nắm được cách debug những lỗi thường gặp khi làm việc với struct và NRF.

Đây là bước chuẩn bị quan trọng để bước sang mạng nhiều node.

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

Xây dựng mạng nhiều node cảm biến NRF24L01 → 1 gateway ESP32, mỗi node có nodeId riêng, gửi dữ liệu về một gateway trung tâm – tiền đề để đẩy dữ liệu lên MQTT/IoTLabs Cloud ở các phần sau.

Hẹn gặp bạn ở Phần 6 trên iotlabs.vn!

Comments

Leave a Reply

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