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:
| NRF24L01 | ESP32 DevKit 38-pin | Ghi chú |
|---|---|---|
| GND | GND | GND chung giữa ESP32 và NRF |
| VCC | 3V3 | Chỉ dùng 3.3V, không cấp 5V |
| CE | GPIO 4 | Chân CE điều khiển mode TX/RX |
| CSN (CS) | GPIO 5 | Chân Chip Select SPI |
| SCK | GPIO 18 | VSPI SCK |
| MOSI | GPIO 23 | VSPI MOSI |
| MISO | GPIO 19 | VSPI 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
RF24cho NRF24L01.DHTcho 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. nodeIdgiú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
- Nạp code TX cho ESP32 có gắn DHT.
- Nạp code RX cho ESP32 còn lại.
- Mở hai Serial Monitor (TX và RX) ở Baud
115200. - 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.
- Đảm bảo
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ó
nodeIdriê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!



Leave a Reply