嵌入式开发 LoRa 远距离通信实战:SX1278 模块组网实践
写在前面: 做物联网项目的兄弟们都遇到过这个问题:WiFi 够不着,蓝牙太短,4G 又太贵。今天我们来聊聊 LoRa——这个能让你的设备在郊区传 10 公里、市区传 3 公里的”神奇无线电”。
上周有个做农业监控的朋友找我,说要在果园里部署土壤湿度传感器,最远的离机房 2 公里。WiFi?想都别想。4G?每个节点每月 20 块流量费,100 个节点就是 2000 块/月,吃不消。最后我们用 LoRa 方案,8 个网关覆盖整个 500 亩果园,每个月电费不到 50 块。
今天就把这个方案完整分享给你。
需要准备什么?
| 物品 | 规格 | 参考价格 | 数量 | 小计 |
|---|---|---|---|---|
| LoRa 模块 SX1278 | 868MHz | ¥35 | 2 | ¥70 |
| Arduino Nano | ATmega328P | ¥15 | 2 | ¥30 |
| 868MHz 弹簧天线 | SMA 接口 | ¥12 | 2 | ¥24 |
| 面包板 + 杜邦线 | 20cm | ¥5 | 10 | ¥50 |
| USB 数据线 | Mini USB | ¥5 | 2 | ¥10 |
| USB-TTL 下载线 | CH340 | ¥8 | 1 | ¥8 |
| 合计 | ¥192 |
💡 省钱提示: 淘宝搜”LoRa 套件”,买集成好的 SX1278+Arduino 一体板,两套下来大概 ¥150,更划算。我这次为了演示接线细节,分开买的。
步骤 1:硬件连接
SX1278 模块有 8 个引脚,我们只需要接 7 个。来,跟着我接:
SX1278 → Arduino Nano
--------- ------------
VCC → 3.3V ⚠️ 不要接 5V,会烧模块!
GND → GND
NSS (CSN) → D10
MOSI → D11
MISO → D12
SCK → D13
RST → D9
DIO0 → D2 (中断引脚,必须接!)
⚠️ 踩坑预警:
- SX1278 是 3.3V 模块,绝对不能接 5V,否则直接烧掉。Arduino Nano 的 3.3V 引脚输出电流有限(约 50mA),但 LoRa 发射瞬间需要约 120mA,长距离发射时建议从外部 3.3V 稳压供电。
- NSS 片选引脚可以根据需要调整,不一定要用 D10,但代码里要和
Module(10, 2, 9, 3)第一个参数对应。 - DIO0 必须接中断引脚(Arduino Nano 的 D2 或 D3),否则接收端的
setDio0Action()回调不会触发,你会永远收不到数据。
建议接线后用万用表通断档逐根复核一遍,LoRa 对 SPI 信号质量比较敏感,虚焊或接触不良会导致初始化失败。
步骤 2:安装库文件
我们用 RadioLib 库,比老牌的 RadioHead 更新更活跃,支持芯片更多。
# 打开 Arduino IDE
# 工具 → 管理库 → 搜索 "RadioLib" → 安装 (v6.5.0 或更新)
或者手动下载:
cd ~/Arduino/libraries
git clone https://github.com/jgromes/RadioLib.git
步骤 3:编写发射端代码
新建一个 sketch,命名为 LoRa_Transmitter:
#include <RadioLib.h>
// SX1278 引脚定义
SX1278 lora = new Module(10, 2, 9, 3); // NSS, DIO0, RST, DIO1
int counter = 0;
void setup() {
Serial.begin(9600);
// 初始化 LoRa
int state = lora.begin();
if (state == RADIOLIB_ERR_NONE) {
Serial.println("LoRa 初始化成功!");
} else {
Serial.print("初始化失败,错误码:");
Serial.println(state);
while (true);
}
// 配置参数 (可根据距离调整)
state = lora.setFrequency(868.0); // 频率 868MHz
state = lora.setBandwidth(125.0); // 带宽 125kHz
state = lora.setSpreadingFactor(9); // 扩频因子 7-12 (越大距离越远,速度越慢)
state = lora.setCodingRate(5); // 编码率 5-8 (容错能力)
state = lora.setSyncWord(0x12); // 同步字 (同一网络必须相同)
state = lora.setOutputPower(17); // 发射功率 10-17dBm
Serial.println("配置完成,开始发送...");
}
void loop() {
String message = "Hello LoRa #" + String(counter) +
" | Temp: " + String(random(20, 35)) +
"°C | Humi: " + String(random(40, 80)) + "%";
Serial.print("发送:");
Serial.println(message);
int state = lora.transmit(message);
if (state == RADIOLIB_ERR_NONE) {
Serial.println("✓ 发送成功!");
} else {
Serial.print("✗ 发送失败,错误码:");
Serial.println(state);
}
counter++;
delay(5000); // 每 5 秒发送一次
}
步骤 4:编写接收端代码
另一块板子刷这个 LoRa_Receiver:
#include <RadioLib.h>
SX1278 lora = new Module(10, 2, 9, 3);
// 中断标志
volatile bool receivedFlag = false;
void setFlag(void) {
receivedFlag = true;
}
void setup() {
Serial.begin(9600);
int state = lora.begin();
if (state != RADIOLIB_ERR_NONE) {
Serial.print("初始化失败:");
Serial.println(state);
while (true);
}
// 配置必须和发射端一致!
lora.setFrequency(868.0);
lora.setBandwidth(125.0);
lora.setSpreadingFactor(9);
lora.setCodingRate(5);
lora.setSyncWord(0x12);
// 设置中断回调
lora.setDio0Action(setFlag);
// 开始监听
state = lora.startReceive();
if (state == RADIOLIB_ERR_NONE) {
Serial.println("开始监听...");
} else {
Serial.print("监听失败:");
Serial.println(state);
}
}
void loop() {
if (receivedFlag) {
receivedFlag = false;
String message;
int state = lora.readData(message);
if (state == RADIOLIB_ERR_NONE) {
Serial.println("✓ 收到数据:");
Serial.println(message);
// 打印 RSSI (信号强度)
float rssi = lora.getRSSI();
Serial.print("信号强度:");
Serial.print(rssi);
Serial.println(" dBm");
} else {
Serial.print("✗ 读取失败:");
Serial.println(state);
}
// 继续监听
lora.startReceive();
}
}
步骤 5:测试验证
两块板子都刷好代码后:
- 先给发射端上电,打开串口监视器(9600 波特率),观察是否每隔 5 秒打印一条 “发送成功”。
- 再给接收端上电,观察串口是否显示收到的数据以及对应的 RSSI 信号强度。
- 如果两端都正常,尝试逐步拉开距离,测试实际通信范围。
我的测试结果:
发送:Hello LoRa #0 | Temp: 28°C | Humi: 65%
✓ 发送成功!
✓ 收到数据:
Hello LoRa #0 | Temp: 28°C | Humi: 65%
信号强度:-42 dBm
🏃 远距离测试:
- 我把发射端放到楼顶,接收端在室内
- 直线距离约 800 米,隔了 3 堵墙
- RSSI 降到 -95 dBm,但依然能稳定接收
- 根据官方数据,SX1278 在开阔地能达到 10km+
参数调优指南
不同场景需要不同配置,记住这个 trade-off:
| 场景 | SF | BW | CR | 功率 | 特点 |
|---|---|---|---|---|---|
| 市区楼宇 | SF9 | 125kHz | 4/5 | 17dBm | 平衡型,兼顾距离和速率 |
| 郊区开阔 | SF11 | 125kHz | 4/7 | 17dBm | 距离优先,最远可达 5km+ |
| 室内短距 | SF7 | 500kHz | 4/5 | 10dBm | 速度优先,延迟低 |
| 电池供电 | SF10 | 125kHz | 4/8 | 14dBm | 低功耗,牺牲速率换可靠性 |
我的经验配置:
-
市区楼宇: SF9 / BW125 / CR5 / 17dBm (平衡型)
-
郊区开阔: SF11 / BW125 / CR7 / 17dBm (距离优先)
-
室内短距: SF7 / BW500 / CR5 / 10dBm (速度优先)
常见问题排查
问题 1:初始化失败,错误码 -16
现象: 串口打印 初始化失败,错误码:-16
原因: SPI 通信失败,通常是接线问题
解决:
- 检查 VCC 是否接 3.3V(不是 5V),用万用表量一下电压。
- 用万用表蜂鸣档测 SPI 四根线(MOSI/MISO/SCK/NSS)是否连通,排除虚焊。
- 确认杜邦线没有松动,面包板触点接触良好;换一个接口孔位试试。
问题 2:能初始化但发不出去
现象: 初始化成功,但 transmit() 返回错误码 -702
原因: DIO0 引脚没接或接错
解决:
- 检查 DIO0 到 D2 的杜邦线是否接好,重新插拔一下。
- 确认代码里
new Module(10, 2, 9, 3)第二个参数(DIO0)和实际接线一致。 - 尝试重新上电重启模块,某些情况下 RST 引脚的电容会导致初始化不完整。
问题 3:接收端收不到数据
现象: 发射端显示发送成功,接收端没反应
排查步骤:
- 检查两端参数是否完全一致:频率、SF、BW、CR、SyncWord,任何一个不匹配都收不到。
- 确认两端代码的
SyncWord相同(默认0x12,不同网络必须用不同值隔离)。 - 尝试把两端拉近到 1 米内测试,排除距离过远或天线问题。
- 检查天线是否接好;天线断路或虚焊会导致信号极弱,把天线拆了重新接。
问题 4:RSSI 值波动大
现象: 同样位置,信号强度忽高忽低
原因: 多径效应(无线电反射干扰)
解决:
- 使用外置弹簧天线或胶棒天线代替 PCB 天线,辐射方向图更稳定。
- 避免天线靠近金属物体或人体(人体含水量高,对 868MHz 信号衰减明显)。
- 多次采样取平均值,建议取 5-10 次 RSSI 读数的中位数作为最终值。
进阶:组建星型网络
上面的例子是点对点,实际项目中我们常用星型拓扑:
[网关]
/ | \
/ | \
[节点 1] [节点 2] [节点 3]
实现思路:
- 网关端:编写接收程序,循环监听并解析不同
nodeId的数据,按 ID 分组存储。 - 节点端:每个节点在代码里分配唯一
nodeId(1-254),定时采集传感器数据并上报。 - 数据包结构:在 payload 开头加上
nodeId字节标识,网关据此区分数据来源(比如"1,28.5,65"代表节点 1,温度 28.5℃,湿度 65%)。 - 可选 ACK 机制:节点发送后等待网关 200ms 内回复一个 ACK 字节,超时则重发(最多 3 次),提高可靠性。
数据包结构示例:
struct SensorData {
uint8_t nodeId; // 节点 ID (1-254)
float temperature; // 温度
float humidity; // 湿度
uint16_t battery; // 电池电压 (mV)
uint8_t crc; // 校验和
};
这个架构我用在果园项目里,8 个网关带 120 个节点,跑了一年很稳定。
总结
LoRa 不是万能药,但在特定场景下真的香:
✅ 适合用 LoRa:
-
传输距离 1-10km
-
数据量小(每次几十字节)
-
低频发送(几分钟一次)
-
电池供电(待机电流<1μA)
❌ 别用 LoRa:
-
要传图片/音频
-
需要高带宽(>10kbps)
-
密集城区(干扰严重)
-
高速移动场景
今天这个 SX1278 方案,192 块钱就能玩转,比买成品开发板便宜多了。拿去改改,做个智能停车传感器、仓库温湿度监控、甚至山地自行车追踪器,都挺好使。
下一步可以试试:
- 加个 GPS 模块做资产追踪
- 用太阳能板实现永久续航
- 接入 Home Assistant 做智能家居
有问题评论区见,我看到都会回。
相关资源:
硬件购买链接:
- SX1278 模块:淘宝搜索”Ra-02 868MHz”
- Arduino Nano:淘宝搜索”Arduino Nano CH340”