IoTLabs

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

Lập trình ESP32 & NRF – Phần 6: Mạng cảm biến nhiều node → 1 gateway ESP32


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

Ở các phần trước của series:

  • Phần 4: Bạn đã gửi chuỗi “Hello World” giữa 2 ESP32 + NRF24L01.
  • Phần 5: Bạn đã thiết kế gói tin struct và gửi dữ liệu cảm biến (nhiệt độ, độ ẩm, uptime) từ 1 node sang 1 gateway.

Trong Phần 6, chúng ta sẽ nâng cấp lên mạng nhiều node cảm biến cùng gửi dữ liệu về 1 gateway ESP32:

  • Mỗi node cảm biến có nodeId riêng.
  • Tất cả sử dụng cùng một địa chỉ pipe hoặc nhiều pipe nếu muốn.
  • Gateway nhận gói tin, đọc nodeId và hiển thị dữ liệu theo từng node.

Đây là mô hình thường gặp trong thực tế: nhiều điểm đo (sensors) → 1 trạm trung tâm (gateway).

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

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

  • Hiểu kiến trúc mạng nhiều node NRF24L01 → 1 gateway.
  • Biết cách phân biệt nhiều node bằng nodeId trong struct.
  • Viết được code dùng chung cho nhiều node cảm biến (chỉ khác nodeId).
  • Viết code cho gateway ESP32 để nhận dữ liệu từ nhiều node và in log rõ ràng.

2. Kiến trúc mạng nhiều node → 1 gateway

2.1. Mô hình tổng quan

  • Node cảm biến: ESP32 (hoặc vi điều khiển khác) + NRF24L01 + cảm biến (DHT, MQ-x, v.v.).
  • Gateway: 1 ESP32 + NRF24L01 làm trung tâm, đặt tại vị trí có WiFi/MQTT.

Sơ đồ logic:

Node 1 (ESP32 + NRF + DHT)
Node 2 (ESP32 + NRF + DHT)
Node 3 (ESP32 + NRF + ...)
             ↓
       NRF24L01 (Gateway)
             ↓
        ESP32 Gateway
             ↓
      Serial / OLED / MQTT

2.2. Cách phân biệt node

Có 2 cách phổ biến:

  1. Dùng nhiều pipe trên gateway:
    • Mỗi node dùng một địa chỉ pipe riêng (addressNode1, addressNode2, …).
    • Gateway mở nhiều openReadingPipe() tương ứng.
  2. Dùng chung 1 địa chỉ pipe, phân biệt theo nodeId trong struct:
    • Tất cả node TX dùng cùng một address.
    • Gói tin struct chứa trường nodeId (1, 2, 3,…).
    • Gateway đọc nodeId rồi xử lý.

Trong bài này, để đơn giản và dễ mở rộng, chúng ta dùng cách 2: chung địa chỉ pipe, phân biệt bằng nodeId.

3. Thiết kế gói tin cho mạng nhiều node

Ta tiếp tục sử dụng struct từ Phần 5, có thêm nodeId:

struct SensorPacket {
  uint8_t  nodeId;      // ID cua node (1, 2, 3,...)
  float    temperature; // Nhiet do
  float    humidity;    // Do am
  uint32_t uptime;      // Thoi gian chay (giay)
};

Kích thước gói tin vẫn nhỏ hơn 32 byte, phù hợp với giới hạn payload của NRF24L01.

Các node khác nhau chỉ cần:

  • Dùng chung struct.
  • Gán packet.nodeId khác nhau (1, 2, 3…).

4. Code dùng chung cho nhiều node cảm biến (TX)

Giả sử bạn muốn có 3 node cảm biến gần giống nhau:

  • Node 1: nodeId = 1.
  • Node 2: nodeId = 2.
  • Node 3: nodeId = 3.

Mỗi node sẽ chạy gần như cùng một chương trình, chỉ khác hằng số nodeId (và có thể khác loại cảm biến nếu bạn muốn).

4.1. Định nghĩa chung cho node TX

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

// Chon nodeId cho board nay
#define NODE_ID    1    // Thay thanh 2, 3 cho cac node khac

// 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

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

// Dia chi chung cho tat ca node
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.print(F("=== Node TX (nodeId="));
  Serial.print(NODE_ID);
  Serial.println(F(") khoi dong ==="));

  dht.begin();

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

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

  radio.openWritingPipe(address);
  radio.stopListening();

  packet.nodeId = NODE_ID;

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

void loop() {
  float h = dht.readHumidity();
  float t = dht.readTemperature();

  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;

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

  if (ok) {
    Serial.print(F("["));
    Serial.print(packet.nodeId);
    Serial.print(F("] Gui: 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 du lieu that bai!"));
  }

  delay(3000); // moi 3 giay gui 1 lan
}

4.2. Cách triển khai nhiều node

  • Copy cùng một project sang 3 board ESP32 khác nhau.
  • Với mỗi board:
    • Đổi #define NODE_ID thành 1, 2 hoặc 3.
    • Nạp code tương ứng.

Kết quả: Mỗi node sẽ đều đặn gửi struct dữ liệu với nodeId riêng về cùng một địa chỉ.

5. Gateway ESP32 – nhận dữ liệu từ nhiều node

Gateway vẫn là ESP32 + NRF24L01, nhưng code logic sẽ:

  • Lắng nghe trên địa chỉ address dùng chung.
  • Mỗi lần nhận struct, đọc packet.nodeId để biết đến từ node nào.
  • In dữ liệu dạng bảng hoặc log gọn gàng.

5.1. Code gateway (RX) nhận nhiều node

#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);

const byte address[6] = "00001";

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

SensorPacket packet;

void printHeader() {
  Serial.println(F("Node |  T(°C)  |  H(%)  | Uptime(s)"));
  Serial.println(F("----------------------------------"));
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println();
  Serial.println(F("=== ESP32 NRF24L01 Gateway - Multi-node RX ==="));

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

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

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

  printHeader();
}

unsigned long lastHeaderTime = 0;

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

    Serial.print("  ");
    Serial.print(packet.nodeId);
    Serial.print(F("  |  "));
    Serial.print(packet.temperature, 1);
    Serial.print(F("   |  "));
    Serial.print(packet.humidity, 1);
    Serial.print(F("   |  "));
    Serial.println(packet.uptime);

    lastHeaderTime = millis();
  }

  // Thinh thoang in lai header cho de doc
  if (millis() - lastHeaderTime > 15000) {
    printHeader();
    lastHeaderTime = millis();
  }

  delay(10);
}

5.2. Cách đọc log

Trên Serial Monitor của gateway, bạn sẽ thấy dạng log như:

Node |  T(°C)  |  H(%)  | Uptime(s)
----------------------------------
  1  |  27.5   |  60.3  |  120
  2  |  28.1   |  58.7  |  130
  3  |  26.9   |  62.1  |  140
  1  |  27.6   |  60.2  |  123
  2  |  28.3   |  58.4  |  133
  ...

Mỗi dòng tương ứng với một gói struct gửi từ một node về gateway.

6. Mở rộng: Giới hạn số node và lưu ý thực tế

6.1. NRF24L01 chịu được bao nhiêu node?

  • Về lý thuyết, bạn có thể có rất nhiều node nếu:
    • Mỗi node gửi với chu kỳ đủ thưa (ví dụ 2–5 giây/lần).
    • Dữ liệu gói nhỏ (< 32 byte).
  • Trên thực tế, vài chục node là khả thi nếu thiết kế chu kỳ gửi hợp lý.

6.2. Tránh xung đột khi nhiều node gửi cùng lúc

  • Nếu tất cả node cùng gửi đúng một thời điểm, xung đột có thể xảy ra, mất gói tin.
  • Một số cách giảm xung đột:
    • Mỗi node dùng một chu kỳ khác nhau (ví dụ: node 1 gửi mỗi 3s, node 2 mỗi 4s, node 3 mỗi 5s).
    • Thêm một khoảng trễ ngẫu nhiên nhỏ (random) trước khi gửi.

Ví dụ thêm jitter:

// Truoc khi gui
delay( random(0, 500) ); // delay ngau nhien 0-500ms

6.3. Vị trí đặt node và gateway

  • Gateway nên đặt ở vị trí trung tâm, cao và thoáng.
  • Hạn chế vật cản kim loại và tường dày giữa node và gateway.
  • Nếu dùng loại NRF24L01 + PA + LNA, chú ý nguồn phải đủ dòng và có tản nhiệt nếu cần.

7. Bài tập mở rộng cho mạng nhiều node

7.1. Thêm node ảo để test tải

  • Dùng nhiều ESP32 hoặc mô phỏng thêm node bằng cách cho một node gửi liên tục với nodeId khác nhau.
  • Quan sát gateway xem có bỏ sót gói tin không.

7.2. Lưu dữ liệu mỗi node vào cấu trúc riêng trên gateway

  • Trên gateway, tạo một mảng lưu trạng thái mới nhất của mỗi node:
struct NodeState {
  bool     active;
  float    temperature;
  float    humidity;
  uint32_t lastUpdate;
};

NodeState nodes[10]; // toi da 10 node
  • Mỗi khi nhận packet, cập nhật nodes[packet.nodeId].
  • Có thể định kỳ in ra bảng tổng quan: node nào còn sống, node nào đã lâu không gửi.

7.3. Kết hợp với OLED hoặc web/MQTT

  • Hiển thị dữ liệu tóm tắt lên màn OLED/TFT.
  • Hoặc dùng ESP32 gateway để đẩy dữ liệu lên MQTT/IoTLabs Cloud, kết nối với dashboard web.

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

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

  • Hiểu kiến trúc mạng nhiều node cảm biến → 1 gateway.
  • Dùng nodeId trong struct để phân biệt các node.
  • Viết code node TX dùng chung (chỉ khác nodeId) và gateway RX nhận cùng lúc nhiều node.
  • Nắm được các lưu ý thực tế về xung đột, số lượng node và vị trí lắp đặt.

Đây là nền tảng quan trọng để bước sang phần tiếp theo trong series.

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

Kết nối gateway ESP32 với MQTT/IoTLabs Cloud, đẩy dữ liệu cảm biến từ mạng NRF24L01 lên server, từ đó hiển thị trên dashboard hoặc mobile app.

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