Cấu hình LoRa trên ESP32: SF, băng thông, tỉ lệ mã, công suất và cách thử tầm xa

Giải thích bốn tham số cấu hình LoRa (SF, băng thông, tỉ lệ mã, công suất), cách tính thời gian phát một gói và hướng dẫn làm bộ thử tầm xa bằng ESP32, SX1278 và màn hình OLED.

Hai module LoRa giống nhau có thể chỉ liên lạc được vài trăm mét hoặc đi xa hơn nhiều lần, tùy cách đặt bốn tham số: hệ số trải phổ (SF), băng thông (BW), tỉ lệ mã (CR) và công suất phát. Bài viết giải thích từng tham số, cách tính thời gian phát một gói, rồi hướng dẫn làm một bộ thử tầm xa bằng ESP32 và module SX1278: mạch phát đặt cố định, mạch thu cầm tay có màn hình OLED hiện RSSI, SNR và số gói mất.

Bài dùng lại cách nối dây và thư viện của bài LoRa cơ bản với module SX1278.

1. Bốn tham số cấu hình

Bốn tham số cấu hình LoRa trên ESP32: hệ số trải phổ SF, băng thông BW, tỉ lệ mã CR và công suất phát, kèm lệnh đặt và sự đánh đổi của từng tham số
Bốn tham số và lệnh đặt tương ứng trong thư viện LoRa (hình vẽ minh họa).
  • Hệ số trải phổ (SF), từ 7 đến 12: mỗi ký hiệu mang SF bit và kéo dài 2SF / BW giây. SF lớn thì ký hiệu dài, máy thu gom được nhiều năng lượng hơn nên nhận được tín hiệu yếu hơn.
  • Băng thông (BW): thường dùng 125 kHz, ngoài ra có 250 kHz và 500 kHz. Băng thông hẹp cho độ nhạy tốt hơn nhưng gửi chậm hơn.
  • Tỉ lệ mã (CR), từ 4/5 đến 4/8: số bit sửa lỗi thêm vào. 4/5 là ít nhất, 4/8 là nhiều nhất.
  • Công suất phát: thư viện cho đặt từ 2 đến 20 dBm. Tăng 3 dB là gấp đôi công suất. Công suất phải nằm trong giới hạn cho phép của quy định vô tuyến nơi sử dụng.

Mạch phát và mạch thu phải đặt giống nhau SF, BW và CR. Công suất phát là việc riêng của mạch phát.

2. SF ảnh hưởng thế nào?

Bảng so sánh các hệ số trải phổ LoRa từ SF7 đến SF12 ở băng thông 125 kHz: tốc độ dữ liệu, thời gian phát gói 20 byte, độ nhạy máy thu và SNR tối thiểu
SF7 đến SF12 ở băng thông 125 kHz, tỉ lệ mã 4/5 (số liệu tính theo công thức; độ nhạy và SNR là số xấp xỉ).
SFTốc độ dữ liệuThời gian phát gói 20 byteĐộ nhạy (xấp xỉ)SNR tối thiểu
75469 bit/s56,6 ms-123 dBm-7,5 dB
83125 bit/s102,9 ms-126 dBm-10 dB
91758 bit/s185,3 ms-129 dBm-12,5 dB
10977 bit/s370,7 ms-132 dBm-15 dB
11537 bit/s741,4 ms-133 dBm-17,5 dB
12293 bit/s1318,9 ms-136 dBm-20 dB

Từ SF7 lên SF12, máy thu nhạy hơn khoảng 13 dB nhưng một gói 20 byte phát lâu hơn 23 lần. Thời gian phát dài đồng nghĩa tốn pin hơn và chiếm kênh lâu hơn, nên chỉ tăng SF đến mức vừa đủ cho khoảng cách cần dùng.

3. Tính thời gian phát một gói

Thời gian phát (time on air) tính theo công thức trong tài liệu của Semtech:

Tsym = 2^SF / BW
Nsym = 8 + max( ceil( (8 x PL - 4 x SF + 28 + 16) / (4 x (SF - 2 x DE)) ) x (CR + 4), 0 )
Tgoi = (8 + 4,25) x Tsym + Nsym x Tsym
  • PL là số byte dữ liệu; CR là 1 đến 4 ứng với tỉ lệ mã 4/5 đến 4/8.
  • DE bằng 1 khi một ký hiệu dài hơn 16 ms (SF11 và SF12 ở băng thông 125 kHz), còn lại bằng 0. Thư viện tự bật chế độ này.
  • Công thức ứng với gói có 8 ký hiệu mở đầu, có tiêu đề và có CRC, đúng với cấu hình của chương trình trong bài.

Chương trình phát ở mục 5 có hàm thoiGianPhatMs() tính đúng công thức này và in cạnh thời gian đo được để đối chiếu.

4. Chuẩn bị và nối dây

Sơ đồ nối mạch thu LoRa: ESP32 DevKit V1 với module LoRa SX1278 qua SPI và bảng nối màn hình OLED 0.96 inch vào D21, D22
Sơ đồ nối mạch thu (hình vẽ minh họa). Mạch phát chỉ cần phần module LoRa.
Linh kiệnChânESP32 DevKit V1
Module LoRa3.3V3V3
GNDGND
MOSID23
MISOD19
SCKD18
NSSD5
RSTD4
DIO0RX2 (GPIO16)
Màn hình OLED (chỉ mạch thu)VCC3V3
GNDGND
SCLD22
SDAD21

5. Chương trình cho mạch phát

Mạch phát gửi một gói ngắn mỗi 3 giây. Bốn hằng số ở đầu chương trình là nơi đổi cấu hình cho mỗi lần thử:

/*
  PYWORLD.VN - Thử tầm xa LoRa: mạch PHÁT đặt cố định
  Phát đều đặn gói có số thứ tự; đổi SF, băng thông, tỉ lệ mã, công suất ở các hằng số bên dưới
  Nối dây: NSS = D5, SCK = D18, MISO = D19, MOSI = D23, RST = D4, DIO0 = RX2 (GPIO16), nguồn 3V3
  Thư viện: LoRa (Sandeep Mistry)
*/

#include <SPI.h>
#include <LoRa.h>

#define LORA_NSS  5
#define LORA_RST  4
#define LORA_DIO0 16
#define LED_PIN   2

const long TAN_SO_HZ = 433E6;
const int  TU_DONG_BO = 0xF3;

// Bốn tham số quyết định tầm xa và tốc độ. Mạch thu phải đặt giống ba tham số đầu
const int  HE_SO_TRAI_PHO = 9;         // SF: 7 đến 12
const long BANG_THONG_HZ  = 125E3;     // BW: 125E3, 250E3 hoặc 500E3
const int  TI_LE_MA       = 5;         // CR 4/5 đến 4/8: ghi 5, 6, 7 hoặc 8
const int  CONG_SUAT_DBM  = 14;        // 2 đến 20 dBm; tuân theo quy định về công suất phát

const unsigned long CHU_KY_PHAT_MS = 3000;

unsigned long soThuTu = 0;
unsigned long lanPhatTruoc = 0;

// Thời gian phát một gói (time on air), tính theo công thức của Semtech
// tiLeMa là mẫu số của tỉ lệ mã: 5, 6, 7 hoặc 8
float thoiGianPhatMs(int sf, long bw, int tiLeMa, int soByte) {
  float tKyHieu = (float)(1UL << sf) / bw * 1000.0;            // Thời gian một ký hiệu, ms
  int toiUuTocDoThap = (tKyHieu > 16.0) ? 1 : 0;               // Thư viện tự bật khi ký hiệu dài hơn 16 ms
  float tu = 8.0 * soByte - 4.0 * sf + 28 + 16;                // 16: có CRC; header tường minh
  float mau = 4.0 * (sf - 2 * toiUuTocDoThap);
  float soKyHieu = 8 + max(ceilf(tu / mau) * tiLeMa, 0.0f);
  return (8 + 4.25) * tKyHieu + soKyHieu * tKyHieu;            // 8 ký hiệu mở đầu
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);

  LoRa.setPins(LORA_NSS, LORA_RST, LORA_DIO0);
  if (!LoRa.begin(TAN_SO_HZ)) {
    Serial.println("Khong tim thay module LoRa, kiem tra day noi va nguon 3V3");
    while (true) {
      delay(1000);
    }
  }
  LoRa.setSyncWord(TU_DONG_BO);
  LoRa.setSpreadingFactor(HE_SO_TRAI_PHO);
  LoRa.setSignalBandwidth(BANG_THONG_HZ);
  LoRa.setCodingRate4(TI_LE_MA);
  LoRa.setTxPower(CONG_SUAT_DBM);
  LoRa.enableCrc();

  Serial.printf("Mach phat: SF%d, BW %ld Hz, CR 4/%d, %d dBm\n",
                HE_SO_TRAI_PHO, BANG_THONG_HZ, TI_LE_MA, CONG_SUAT_DBM);
}

void loop() {
  if (millis() - lanPhatTruoc < CHU_KY_PHAT_MS) {
    return;
  }
  lanPhatTruoc = millis();
  soThuTu++;

  char goi[24];
  int soByte = snprintf(goi, sizeof(goi), "T;%lu", soThuTu);

  digitalWrite(LED_PIN, HIGH);
  unsigned long batDau = millis();
  LoRa.beginPacket();
  LoRa.write((const uint8_t *)goi, soByte);
  LoRa.endPacket();
  unsigned long daMat = millis() - batDau;
  digitalWrite(LED_PIN, LOW);

  Serial.printf("Phat goi %lu (%d byte): do duoc %lu ms, tinh theo cong thuc %.1f ms\n",
                soThuTu, soByte, daMat, thoiGianPhatMs(HE_SO_TRAI_PHO, BANG_THONG_HZ, TI_LE_MA, soByte));
}

6. Chương trình cho mạch thu

Mạch thu đếm số gói nhận được, dùng số thứ tự để suy ra số gói mất, rồi hiện kết quả lên màn hình. Nếu không gắn màn hình, chương trình vẫn chạy và in ra Serial Monitor.

/*
  PYWORLD.VN - Thử tầm xa LoRa: mạch THU cầm tay có màn hình OLED
  Hiện RSSI, SNR, số gói nhận được và số gói mất để mang đi thử ở nhiều vị trí
  LoRa: NSS = D5, SCK = D18, MISO = D19, MOSI = D23, RST = D4, DIO0 = RX2 (GPIO16), nguồn 3V3
  OLED SSD1306 I2C: VCC = 3V3, GND = GND, SCL = D22, SDA = D21
  Thư viện: LoRa (Sandeep Mistry), Adafruit SSD1306, Adafruit GFX
*/

#include <SPI.h>
#include <Wire.h>
#include <LoRa.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define LORA_NSS  5
#define LORA_RST  4
#define LORA_DIO0 16
#define DIA_CHI_OLED 0x3C

const long TAN_SO_HZ = 433E6;
const int  TU_DONG_BO = 0xF3;

// Ba tham số này phải giống mạch phát
const int  HE_SO_TRAI_PHO = 9;
const long BANG_THONG_HZ  = 125E3;
const int  TI_LE_MA       = 5;

Adafruit_SSD1306 manHinh(128, 64, &Wire, -1);
bool coManHinh = false;

unsigned long soNhan = 0;
unsigned long soMat = 0;
unsigned long soThuTuTruoc = 0;
int rssi = 0;
float snr = 0;

void veManHinh() {
  if (!coManHinh) {
    return;
  }
  manHinh.clearDisplay();
  manHinh.setTextSize(1);
  manHinh.setCursor(0, 4);
  manHinh.printf("LoRa SF%d BW%ldk", HE_SO_TRAI_PHO, BANG_THONG_HZ / 1000);
  manHinh.setTextSize(2);
  manHinh.setCursor(0, 18);
  manHinh.printf("%d dBm", rssi);
  manHinh.setTextSize(1);
  manHinh.setCursor(0, 40);
  manHinh.printf("SNR %.1f dB", snr);
  manHinh.setCursor(0, 52);
  manHinh.printf("Nhan %lu  Mat %lu", soNhan, soMat);
  manHinh.display();
}

void setup() {
  Serial.begin(115200);

  coManHinh = manHinh.begin(SSD1306_SWITCHCAPVCC, DIA_CHI_OLED);
  if (coManHinh) {
    manHinh.setTextColor(SSD1306_WHITE);
    manHinh.clearDisplay();
    manHinh.setCursor(0, 4);
    manHinh.print("Dang cho goi tin...");
    manHinh.display();
  } else {
    Serial.println("Khong tim thay OLED, chi in ra Serial");
  }

  LoRa.setPins(LORA_NSS, LORA_RST, LORA_DIO0);
  if (!LoRa.begin(TAN_SO_HZ)) {
    Serial.println("Khong tim thay module LoRa, kiem tra day noi va nguon 3V3");
    while (true) {
      delay(1000);
    }
  }
  LoRa.setSyncWord(TU_DONG_BO);
  LoRa.setSpreadingFactor(HE_SO_TRAI_PHO);
  LoRa.setSignalBandwidth(BANG_THONG_HZ);
  LoRa.setCodingRate4(TI_LE_MA);
  LoRa.enableCrc();
  Serial.printf("Mach thu: SF%d, BW %ld Hz, CR 4/%d\n", HE_SO_TRAI_PHO, BANG_THONG_HZ, TI_LE_MA);
}

void loop() {
  int kichThuoc = LoRa.parsePacket();
  if (kichThuoc == 0) {
    return;
  }

  char goi[24];
  int n = 0;
  while (LoRa.available() && n < (int)sizeof(goi) - 1) {
    goi[n++] = (char)LoRa.read();
  }
  goi[n] = '\0';

  unsigned long soThuTu = 0;
  if (sscanf(goi, "T;%lu", &soThuTu) != 1) {
    return;   // Không phải gói thử tầm xa
  }

  // Số thứ tự nhảy cóc nghĩa là có gói bị mất ở giữa
  if (soNhan > 0 && soThuTu > soThuTuTruoc + 1) {
    soMat += soThuTu - soThuTuTruoc - 1;
  }
  soThuTuTruoc = soThuTu;
  soNhan++;
  rssi = LoRa.packetRssi();
  snr = LoRa.packetSnr();

  float tiLeMat = 100.0 * soMat / (soNhan + soMat);
  Serial.printf("Goi %lu | RSSI %d dBm | SNR %.1f dB | nhan %lu, mat %lu (%.1f %%)\n",
                soThuTu, rssi, snr, soNhan, soMat, tiLeMat);
  veManHinh();
}

7. Kết quả mong đợi

Hình minh họa màn hình OLED của mạch thu LoRa cầm tay: cấu hình SF9 BW125k, RSSI -97 dBm, SNR 7.5 dB, số gói nhận và số gói mất
Màn hình của mạch thu cầm tay (hình minh họa, số đo là ví dụ).
Hình minh họa Serial Monitor của mạch phát và mạch thu khi thử tầm xa LoRa: thời gian phát đo được và tính theo công thức, RSSI, SNR, tỉ lệ gói mất
Kết quả mong đợi trên Serial Monitor (hình minh họa).

Ở mạch phát, thời gian đo được thường lớn hơn số tính theo công thức vài mili giây vì có thêm thời gian trao đổi qua SPI.

8. Cách thử tầm xa

  1. Đặt mạch phát cố định ở chỗ cao và thoáng, anten dựng thẳng đứng. Ghi lại cấu hình đang dùng.
  2. Cầm mạch thu đứng cạnh mạch phát vài mét để chắc chắn hai mạch đang nhận được nhau, rồi nhấn nút EN trên mạch thu để đưa bộ đếm về 0.
  3. Đi ra xa dần. Ở mỗi điểm dừng lại khoảng một phút, ghi khoảng cách, RSSI, SNR và số gói mất.
  4. Điểm xa nhất còn dùng được là nơi tỉ lệ mất gói còn chấp nhận được với ứng dụng, ví dụ dưới 10 %.
  5. Đổi một tham số ở cả hai mạch (ví dụ SF9 lên SF11) rồi thử lại đúng các điểm cũ để so sánh.

Đọc kết quả: ở cự ly xa, SNR cho biết nhiều hơn RSSI. Khi SNR xuống gần mức tối thiểu của SF đang dùng (cột cuối trong bảng ở mục 2) thì gói bắt đầu mất. Lúc đó tăng SF sẽ nối lại được liên lạc.

9. Các cách tăng tầm xa

  • Anten và vị trí: dùng anten đúng băng 433 MHz, đặt cao, giữ tầm nhìn thẳng giữa hai mạch. Nâng anten lên vài mét thường hiệu quả hơn tăng công suất.
  • Tăng SF trước: mỗi bậc SF thêm khoảng 2,5 đến 3 dB mà không tăng công suất phát.
  • Giảm băng thông: từ 125 kHz xuống 62,5 kHz thêm khoảng 3 dB độ nhạy, nhưng băng thông càng hẹp thì càng đòi hỏi tần số hai mạch khớp nhau.
  • Tăng tỉ lệ mã: dùng 4/8 khi môi trường có nhiễu ngắt quãng.
  • Công suất phát: là lựa chọn sau cùng vì tốn điện và bị giới hạn bởi quy định.
  • Gói ngắn: gửi dạng nhị phân thay vì chuỗi chữ để giảm số byte và thời gian phát.

10. Xử lý lỗi thường gặp

Hiện tượngNguyên nhân thường gặpCách xử lý
Đổi SF xong thì không nhận được gói nàoChỉ đổi ở một mạchĐặt SF, BW, CR giống nhau ở cả hai chương trình rồi nạp lại cả hai
Màn hình đứng yên ở dòng Dang cho goi tinMạch phát chưa chạy, khác tần số, khác từ đồng bộ hoặc đã ra ngoài tầmĐưa hai mạch lại gần để kiểm tra trước; xem Serial Monitor của mạch phát
Số gói mất tăng vọt một lần rồi dừngMạch phát vừa khởi động lại nên số thứ tự quay về 1, hoặc mạch thu vừa ra khỏi vùng khuấtKhởi động lại mạch thu để đếm lại từ đầu
Ở SF11, SF12 mạch phát in thời gian phát rất dàiĐúng với công thức: mỗi gói mất khoảng 0,7 đến 1,3 giâyTăng CHU_KY_PHAT_MS để kênh không bị chiếm liên tục
RSSI tốt nhưng vẫn mất góiCó nguồn nhiễu cùng tần số, SNR thấpXem SNR; đổi tần số trong dải cho phép hoặc tăng SF
Không thấy màn hình, Serial in Khong tim thay OLEDNối nhầm SDA với SCL hoặc địa chỉ khác 0x3CKiểm tra dây; quét địa chỉ theo bài giao tiếp I2C trên ESP32

11. Xem thêm


Tin tức liên quan

Phased array siêu âm 40 kHz với ESP32 (phần 2): mạch đệm 12 V, đầu thu và quét búp sóng
Phased array siêu âm 40 kHz với ESP32 (phần 2): mạch đệm 12 V, đầu thu và quét búp sóng

Phần 2 của loạt phased array siêu âm: thêm mạch đệm 12 V bằng hai module L298N, lắp mạch thu dùng LM358, đo biên độ 40 kHz bằng ADC của ESP32 và chạy chương trình tự quét góc lái để xem búp sóng.

Phased array siêu âm 40 kHz với ESP32 (phần 1): nguyên lý lái búp sóng và tạo 8 kênh lệch pha
Phased array siêu âm 40 kHz với ESP32 (phần 1): nguyên lý lái búp sóng và tạo 8 kênh lệch pha

Nguyên lý phased array và cách dùng ESP32 tạo 8 kênh xung 40 kHz lệch pha để lái búp sóng siêu âm: công thức độ trễ, giới hạn do khoảng cách đầu phát, cấu hình LEDC với hpoint, sơ đồ nối và code.

Phát hiện chuyển động bằng sóng Wi-Fi (CSI) trên ESP32: không cần cảm biến
Phát hiện chuyển động bằng sóng Wi-Fi (CSI) trên ESP32: không cần cảm biến

Giải thích Wi-Fi CSI và hướng dẫn dùng ESP32 phát hiện người di chuyển trong phòng chỉ bằng sóng Wi-Fi từ router: lấy dữ liệu CSI, tính điểm chuyển động, đặt ngưỡng và xem trên Serial Plotter.

Phát hiện chuyển động bằng cảm biến PIR HC-SR501 với ESP32: tự bật đèn qua relay
Phát hiện chuyển động bằng cảm biến PIR HC-SR501 với ESP32: tự bật đèn qua relay

Hướng dẫn dùng cảm biến chuyển động PIR HC-SR501 với ESP32: nguyên lý, cách chỉnh module, đọc tín hiệu bằng ngắt và ghép relay làm đèn tự bật khi có người, tự tắt sau 60 giây.

Làm gateway LoRa sang MQTT bằng ESP32: node cảm biến chạy pin gửi dữ liệu về broker
Làm gateway LoRa sang MQTT bằng ESP32: node cảm biến chạy pin gửi dữ liệu về broker

Hướng dẫn làm hệ thống LoRa hoàn chỉnh với ESP32 và SX1278: node chạy pin đo điện áp, gửi gói rồi ngủ sâu; gateway nhận gói LoRa và publish dữ liệu JSON lên broker MQTT qua Wi-Fi.

Hướng dẫn dùng LoRa SX1278 (Ra-02) với ESP32: nối dây SPI, gửi và nhận gói tin đầu tiên
Hướng dẫn dùng LoRa SX1278 (Ra-02) với ESP32: nối dây SPI, gửi và nhận gói tin đầu tiên

Hướng dẫn nối module LoRa SX1278 Ra-02 433 MHz với ESP32 qua SPI, cài thư viện LoRa trên Arduino IDE, viết chương trình gửi và nhận gói tin, đọc RSSI, SNR và xử lý lỗi thường gặp.

ESP-NOW nhiều node với ESP32: nhiều mạch gửi về một trạm thu, hiển thị trên OLED
ESP-NOW nhiều node với ESP32: nhiều mạch gửi về một trạm thu, hiển thị trên OLED

Hướng dẫn dựng mô hình ESP-NOW nhiều node: tối đa bốn ESP32 gửi số đo về một trạm thu, trạm thu hiện điện áp, RSSI và tình trạng từng node trên màn hình OLED 0.96 inch.

Điều khiển relay từ xa bằng ESP-NOW: remote ESP32 một nút nhấn có phản hồi trạng thái
Điều khiển relay từ xa bằng ESP-NOW: remote ESP32 một nút nhấn có phản hồi trạng thái

Hướng dẫn làm bộ điều khiển relay từ xa bằng hai mạch ESP32 và ESP-NOW hai chiều: remote gửi lệnh, mạch relay báo lại trạng thái, lọc theo địa chỉ MAC và bật mã hóa.


Bình luận
Đã thêm vào giỏ hàng