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
nodeIdvà 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
nodeIdtrong 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:
- 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.
- Mỗi node dùng một địa chỉ pipe riêng (
- Dùng chung 1 địa chỉ pipe, phân biệt theo
nodeIdtrong 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
nodeIdrồi xử lý.
- Tất cả node TX dùng cùng một
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.nodeIdkhá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_IDthành1,2hoặc3. - Nạp code tương ứng.
- Đổi
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ỉ
addressdù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
nodeIdkhá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
nodeIdtrong 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!


