0x6A Logbook

0x6A Logbook
Shi6a的筆記本
  1. 首頁
  2. 程式開發
  3. 正文

LoRa 長距離通訊完整教學:從 ESP32 + SX1278 到 LoRaWAN 協定實作

2026 年 6 月 12 日 615點熱度 0人點贊 0條評論

LoRa 長距離通訊完整教學封面

LoRa 是什麼?一句話:拿速率換距離,而且換得很兇

如果你只想要「一顆電池撐好幾年、資料送到幾公里外」,那 LoRa 幾乎是標準答案。它是 LPWAN(低功耗廣域網路)裡最紅的技術之一,智慧城市、農業監控、工業感測網路都在用。

先講清楚它的取捨:LoRa 用很低的速率(0.3~50 kbps)換到數公里的距離,同時功耗低到可以撐數年。對一個每分鐘才回報一次溫度的感測器來說,這個速率完全夠用 —— 反正你也不需要用它來看影片。

我自己第一次接觸時最驚訝的不是距離,而是它居然可以在沒有電信商的情況下自己架網路。這件事在後面談到 LoRaWAN 時會變得很關鍵。

LoRa 技術原理:核心是 CSS,不是「低頻訊號」

很多人以為 LoRa 傳得遠是因為頻率低。不是,關鍵是調變方式:CSS(Chirp Spread Spectrum,線性調頻展頻)。

它的做法是把一小段資料攤開到很寬的頻譜上(展頻),代價是速率變慢,好處是抗干擾、抗都卜勒效應。四個你一定要記住的參數:

  • Chirp:頻率隨時間線性變化的訊號(往上掃 = up-chirp,往下掃 = down-chirp)
  • 展頻增益:訊號攤在寬頻譜上,對抗干擾與都卜勒效應
  • SF(Spreading Factor):展頻因子(7~12),SF 越高 → 距離越遠、速率越慢
  • BW(Bandwidth):頻寬(125/250/500 kHz),BW 越高 → 速率越快、靈敏度越低

這兩個參數的關係可以直接算出來,公式就這三行:

符碼率 Rs = BW / 2^SF
位元率 Rb = SF × Rs × (4 / (4 + CR))

範例(SF=12, BW=125kHz, CR=1):
Rs = 125000 / 4096 ≈ 30.5 symbols/s
Rb = 12 × 30.5 × 0.8 ≈ 293 bps

看到那個 293 bps 了嗎?SF12 加上 125 kHz,速率就是這麼低。但換來的是最遠的距離 —— 這筆交易划不划算是你自己決定的。

LoRa 封包格式:前導碼在幫你「對時脈」

封包長得像這樣,五段各司其職:

LoRa 封包五個欄位:Preamble、SFD、PHDR、Payload、Payload CRC

整串資料裡,最容易被忽略但最重要的是 Preamble(前導碼):它是一串連續的 up-chirp,接收端靠它偵測「有訊號來了」並且同步時脈。前導碼預設 8 個 symbols(長度可設 6~65535)。

接著 SFD 用一個固定模式的 down-chirp 宣告「前導碼結束、資料開始」。後面才是 PHDR + PHDR_CRC(帶 Payload 長度、編碼率、CRC 是否啟用)、Payload(最大 255 bytes)與 Payload CRC(16-bit 錯誤檢測)。

實務上我會這樣看:PHDR 只有隱式模式才能省掉,而省掉它你就得自己保證兩邊參數完全一致 —— 除非你在壓那幾個 byte,不然別自找麻煩。

LoRaWAN 通訊協定:LoRa 是實體層,LoRaWAN 才是規矩

這裡一定要分清楚:LoRa 只是調變技術(實體層),LoRaWAN 是蓋在它上面的 MAC 層協定,由 LoRa Alliance 標準化。你自己拿兩顆 SX1278 對傳叫做「LoRa 點對點」,那不叫 LoRaWAN。

裝置類型

LoRaWAN 把裝置分成三類,差別只有一件事:什麼時候醒著聽下行。

Class 說明 下行時機 功耗
Class A 所有 LoRaWAN 裝置必備 上行後開啟 RX1/RX2 視窗 最低
Class B 增加 Beacon 定期接收視窗 Beacon 時間槽 中等
Class C 持續監聽下行 隨時 最高

實務上九成的感測器都是 Class A:送完上行資料 → 開 RX1(上行後 1 秒)和 RX2(2 秒)兩個視窗 → 收完就回睡眠。它的下載反應慢,但省電到極致。如果你的應用需要「伺服器隨時叫你」,那你要付的代價就是電。

OTAA 啟用流程

裝置要加入網路有兩種方式:OTAA(Over-The-Air Activation)和 ABP(Activation By Personalization)。沒有懸念,用 OTAA —— ABP 是把 Session Key 直接寫死在程式裡,裝置遺失或被複製就是安全問題,而且換金鑰得一台一台重燒。

LoRaWAN OTAA 加入流程:裝置、Gateway 與 Network Server 的訊息交換

流程拆開來看是這樣:

  1. Join Request:裝置廣播,內容包含 DevEUI(裝置唯一 ID)、AppEUI(應用唯一 ID)與 DevNonce(隨機數)
  2. Gateway 轉發:LoRa Gateway 收到後,透過 TCP/IP 轉發給 Network Server
  3. Network Server 驗證:檢查 DevNonce 是否重複,並用 AppKey 計算 MIC 驗證完整性
  4. Join Accept:Server 回傳 AppNonce、NetID、DevAddr 與 RX 參數,並用 AppKey 加密
  5. 裝置解密:裝置用 AppKey 解開 Join Accept,取得 DevAddr 與 Session Keys(NwkSKey + AppSKey)

DevNonce 那個「不能重複」的規則很容易踩:如果你每次重開機都拿到同一組 DevNonce,Server 會直接拒絕你。所以別把 DevNonce 寫死,也別在重開機後從頭數。

ESP32 + SX1278 硬體接線:八條線,照著接就好

SX1278 ESP32 說明
3.3V 3.3V 電源(注意電流 > 100mA)
GND GND 共地
NSS GPIO 5 SPI Chip Select
SCK GPIO 18 SPI 時脈
MOSI GPIO 23 SPI 資料輸入
MISO GPIO 19 SPI 資料輸出
DIO0 GPIO 2 中斷(封包接收完成)
RST GPIO 14 重置(低電位觸發)

幾個我會特別提醒的地方:DIO0 接 GPIO 2 當中斷源,這樣封包收完它會直接通知你,不用在 loop 裡一直問「收到了沒」。另外 3.3V 那條線不能省 —— SX1278 發射瞬間的電流會超過 100 mA,用開發板上的 LDO 硬撐,很容易遇到「近距離正常、一加大功率就重置」的怪現象。

Arduino 程式碼:LoRa 點對點通訊

用 LoRa.h 函式庫(底層就是 SX1278),這是最短的兩個範例。先看發送端:

// ESP32 LoRa 發送端(Sender)
#include 
#include 

// SPI 腳位定義(ESP32 預設 VSPI)
#define SS    5
#define RST   14
#define DIO0  2

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

    LoRa.setPins(SS, RST, DIO0);

    if (!LoRa.begin(915E6)) {  // 頻率:915 MHz(北美)/ 868 MHz(歐盟)/ 923 MHz(亞洲)
        Serial.println("LoRa 初始化失敗");
        while (1);
    }

    LoRa.setSpreadingFactor(12);    // SF12:最遠距離
    LoRa.setSignalBandwidth(125E3); // BW 125 kHz
    LoRa.setCodingRate4(5);         // CR = 4/5
    LoRa.setTxPower(20);            // 發射功率 20 dBm(最大值)

    Serial.println("LoRa Sender 啟動");
}

void loop() {
    float temp = 25.0 + random(-50, 50) / 10.0;
    float hum  = 60.0 + random(-30, 30) / 10.0;

    LoRa.beginPacket();
    LoRa.print("TEMP:");
    LoRa.print(temp, 1);
    LoRa.print(",HUM:");
    LoRa.print(hum, 1);
    LoRa.endPacket();

    Serial.printf("已發送:T=%.1f H=%.1f\n", temp, hum);
    delay(60000);  // 每分鐘發送一次
}

設定那幾行值得多看一眼:SF12 + BW 125 kHz + CR 4/5 + 20 dBm,這是「最遠距離」的組合,代價就是速率慢、空傳時間長(下一節會算給你看)。

然後是接收端 —— 重點是它會回報 RSSI 與 SNR,這兩個數字是你在調天線跟判斷距離時唯一的依據:

// ESP32 LoRa 接收端(Receiver)
#include 

void setup() {
    Serial.begin(115200);
    LoRa.setPins(5, 14, 2);

    if (!LoRa.begin(915E6)) {
        Serial.println("LoRa 初始化失敗");
        while (1);
    }

    LoRa.setSpreadingFactor(12);
    LoRa.setSignalBandwidth(125E3);
    LoRa.setCodingRate4(5);

    Serial.println("LoRa Receiver 啟動,等待資料...");
}

void loop() {
    int packetSize = LoRa.parsePacket();
    if (packetSize) {
        String data = "";
        while (LoRa.available()) {
            data += (char)LoRa.read();
        }

        int rssi = LoRa.packetRssi();
        float snr = LoRa.packetSnr();

        Serial.printf("收到: %s | RSSI: %d dBm | SNR: %.1f dB\n",
                      data.c_str(), rssi, snr);
    }
}

使用 LoRaWAN 協定(MCCI LoRaWAN Library)

如果你想接 TTN 或 ChirpStack,就得走 LoRaWAN,程式碼會變成事件驅動的寫法:

// ESP32 LoRaWAN Class A 範例(MCCI LoRaWAN 函式庫)
#include 
#include <hal/hal.h>
#include 

// OTTA 憑證(來自 TTN Console)
static const u1_t PROGMEM APPEUI[8]  = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
static const u1_t PROGMEM DEVEUI[8]  = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
static const u1_t PROGMEM APPKEY[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8); }
void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8); }
void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16); }

// SPI 腳位
const lmic_pinmap lmic_pins = {
    .nss = 5,
    .rxtx = LMIC_UNUSED_PIN,
    .rst = 14,
    .dio = {2, LMIC_UNUSED_PIN, LMIC_UNUSED_PIN},
};

static uint8_t txData[] = "Hello LoRaWAN!";
static osjob_t sendjob;

void do_send(osjob_t* j) {
    if (LMIC.opmode & OP_TXRXPEND) {
        Serial.println("上一個傳送還未完成");
    } else {
        LMIC_setTxData2(1, txData, sizeof(txData)-1, 0);
        Serial.println("傳送中...");
    }
    os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(60), do_send);
}

void onEvent(ev_t ev) {
    switch (ev) {
        case EV_JOINED:
            Serial.println("已加入 LoRaWAN 網路");
            LMIC_setLinkCheckMode(0);
            break;
        case EV_TXCOMPLETE:
            Serial.println("傳送完成");
            if (LMIC.dataLen) {
                Serial.printf("收到下行: %s\n", LMIC.frame + LMIC.dataBeg);
            }
            break;
        default:
            break;
    }
}

void setup() {
    Serial.begin(115200);
    LMIC_reset();
    LMIC_startJoining();
    do_send(&sendjob);
}

void loop() {
    os_runloop_once();
}

這段跟前面的差別不只是「換一套函式庫」:LoRaWAN 把「什麼時候可以發送」的決定權交給了 LMIC 與伺服器。你的工作變成處理事件(EV_JOINED、EV_TXCOMPLETE),而不是自己數 delay。一開始會覺得彆扭,但這正是它能跟別人的網路共存的理由。

LoRa 參數調校指南:這三個場景選一個就好

參數不用背,記住「距離、速率、空傳時間是同一件事的三個面」就夠了:

場景 SF BW CR TP (dBm) 範圍 空傳時間
極遠距離 12 125 4/5 20 ~15 km ~2.3s (51 bytes)
平衡 9 125 4/5 17 ~5 km ~0.3s
高速 7 250 4/8 14 ~1 km ~0.04s

我自己調參數的順序是:先決定要多遠,再決定要多快,最後才看空傳時間。極遠距離那組的 51 bytes 要佔掉 2.3 秒的無線電時間,這在佔空比有法規限制的地區(例如歐盟的 1%)會非常痛。

天線設計注意事項:這是最容易被當成「玄學」的一段

先講結論:天線是類比電路,不是插上去就會動的零件。幾個關鍵:

  • 匹配阻抗:SX1278 輸出阻抗 50Ω,天線也必須是 50Ω,不匹配就是功率反射
  • 天線長度:λ/4 單極天線最簡單(915 MHz → 約 8.2 cm,868 MHz → 約 8.6 cm)
  • 地平面:單極天線需要接地平面,PCB 上要預留足夠面積
  • SWR 測試:用網路分析儀調整匹配網路(L/C 元件),不要憑感覺換電容
  • 法規限制:各國對 ISM 頻段的發射功率規定不同(台灣 915 MHz ≤ 1W EIRP)

我個人的看法是:花在 SWR 上的時間,報酬率遠高於換一顆更貴的模組。天線沒調好,你在軟體上怎麼調參數都是在補洞。

LoRa vs 其他 IoT 通訊技術

選技術之前,先把這個對照表看一遍:

特性 LoRa/LoRaWAN NB-IoT Sigfox WiFi BLE
頻段 Sub-GHz ISM LTE 授權頻段 Sub-GHz ISM 2.4/5 GHz 2.4 GHz
範圍(市區) 2~5 km 1~3 km 3~10 km ~50 m ~100 m
資料速率 0.3~50 kbps ~200 kbps ~100 bps Mbps~Gbps ~2 Mbps
電池壽命 ~10 年 ~5 年 ~10 年 數天~數週 數月
雙向 ✓(受限) ✓ ✗(上行為主) ✓ ✓
建置成本 低(自建 Gateway) 高(需 SIM) 中(年費) 低 低
適合場景 感測器網路 車聯網、電表 極簡感測器 串流、網頁 穿戴裝置

我的選型順序是:先看範圍,再看資料速率,最後問「我要不要自己顧這個網路」。LoRa 的位置很明確 —— 免費頻段加自建 Gateway,代價是速率低、而且你得自己維運。要雙向又懶得顧網路,那 NB-IoT 走電信商更省事,只是成本高。

實務經驗:LoRa 通訊距離測試

參數講再多,不如實際量一次。下面這段程式就是持續發送測試封包,並把 RSSI 印出來:

// LoRa 距離測試 — 持續發送 RSSI 封包
void loop() {
    LoRa.beginPacket();
    LoRa.print("RSSI_TEST");
    LoRa.endPacket();

    delay(5000);

    // 每秒檢查是否收到回應
    if (LoRa.parsePacket()) {
        Serial.printf("收到回應 | RSSI: %d SNR: %.1f\n",
                      LoRa.packetRssi(), LoRa.packetSnr());
    }
}

實測條件:SF12、BW125、20 dBm、空曠環境。結果長這樣:

  • 500m:RSSI -85~-95 dBm,SNR 8~12 dB(穩定連線)
  • 1km:RSSI -100~-110 dBm,SNR 5~8 dB(偶爾掉封包)
  • 2km:RSSI -115~-125 dBm,SNR 0~3 dB(需重傳機制)
  • 5km:RSSI -130 dBm 以下,僅視距可見時可收到

實測距離與 RSSI 的關係:500m、1km、2km、5km

這張圖給我的結論是:500 公尺到 2 公里之間,才是 LoRa 真正好用的區間。超過 2 公里,SNR 已經掉到 0~3 dB,你必須在協定層加上重傳與確認機制;5 公里那種數字是「視距可見 + 天線調好」才成立的實驗室成績,市區不要當常態。

總結

LoRa 用極低功耗換到數公里通訊距離,配合 ESP32 + SX1278,從點對點到 LoRaWAN 都有一條完整的實作路徑。整篇的重點收斂成四句:

  • 原理:CSS 展頻,SF 越高越遠越慢,BW 越高越快越不靈敏
  • 協定:LoRa 是實體層,LoRaWAN 是 MAC 層;用 OTAA,裝置用 Class A
  • 調校:先決定距離,再決定速率,最後才看空傳時間與佔空比
  • 硬體:3.3V 要夠力、DIO0 接中斷、天線一定要匹配 50Ω

如果你只是要玩點對點通訊,LoRa.h 半天就能上手;要正式部署,直接上 MCCI LoRaWAN + TTN/ChirpStack,別自己發明輪詢機制。剩下的,就交給天線了。

標籤: 教學
最後更新:2026 年 9 月 30 日

shi6a

這個人很懶,什麼都沒留下

點贊
< 上一篇
下一篇 >

文章評論

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回覆

COPYRIGHT © 2026 0x6A Logbook. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang