前阵子有个项目需求——要在两栋楼之间传数据,距离大概一两公里,中间隔着几堵墙和一个停车场。第一反应是用WiFi,但实际上其中一个楼没有WiFi可以接入,后来想用4G模块,但这个距离似乎太浪费了,并且相比之下使用成本也更高一点。蓝牙更不用想,根本不合适。
然后一个朋友让我试试LoRa。
我之前只听说过LoRa这个词,知道是个远距离的无线技术,但从来没实际用过。上网一搜,好家伙,模组品牌一大堆,芯片型号一堆数字字母组合,看得人头大。后来在找模组的时候,发现了深圳大夏龙雀科技有一款DX-LR20-900M22S的LoRa模组。
研究了几天,从一无所知到成功跑通两板互发数据,整个过程踩了不少坑,但也学到了很多东西。这篇文章就把我的开发经验分享出来,希望对同样想入门LoRa的朋友有所帮助。
一、LoRa到底是什么?
1.1 一句话解释LoRa
LoRa是一种低功耗局域网无线通信技术,特点是传得远、功耗低、穿透强,代价是传输速率低。
打个比方:WiFi就像快递的"当日达",速度快但覆盖范围小;蓝牙像是"闪送",更近更快但范围更小;而LoRa就像邮政的"平信",虽然慢了点,但能寄到很远的地方,而且发信人几乎不费什么力气。
1.2 LoRa的核心优势
特性 | LoRa | WiFi | 蓝牙(BLE) |
|---|---|---|---|
通信距离 | 2~15km | 50~100m | 10~50m |
穿透能力 | 强 | 弱 | 很弱 |
功耗 | 极低 | 高 | 低 |
传输速率 | 0.3~50kbps | 150~600Mbps | 1~2Mbps |
适用场景 | 远距离低频数据 | 高速网络 | 近距设备互联 |
LoRa不适合传视频、图片这种大数据量的东西,但对于传感器数据采集、远程开关控制、定位信标这类"小数据、远距离、低功耗"的场景,简直是量身定制。
1.3 LoRa调制原理
LoRa用了三种核心技术来实现远距离传输:
① 扩频技术
普通无线信号就像一个人在大喊,声音大但容易被干扰。LoRa的扩频技术像是把一句话拆成一首歌,用不同的音调来传递信息。即使环境噪声很大,也能从"旋律"中辨认出有用信号。
具体来说,LoRa使用线性调频信号—— 频率从低到高或从高到低线性变化。每个数据符号对应一个特定的起始频率偏移,接收端通过比对收到的Chirp与参考Chirp的频率差来解码数据。
② 扩频因子
扩频因子决定了每个数据符号占用的时间。SF越大,每个符号持续越久,抗干扰能力越强,传得越远,但速率越低。可以理解为"说得越慢,对方越能听清"。
LLCC68芯片支持的SF范围:
带宽(BW) | 支持的SF范围 |
|---|---|
125 kHz | SF5 ~ SF9 |
250 kHz | SF5 ~ SF10 |
500 kHz | SF5 ~ SF11 |
注意:LLCC68与Semtech SX1262的驱动完全兼容,但LLCC68不支持SF12。这是两者唯一的区别,移植代码时需要注意。
③ 前向纠错编码
LoRa在数据中加入了冗余校验位,即使部分数据在传输中被噪声干扰,接收端也能自动纠正错误,不需要重传。这就像你写信时故意把关键词写两遍,即使墨水晕了一个字,对方也能猜对。
1.4 关键参数解读
开发LoRa应用,这几个参数必须理解:
频率:DX-LR20工作在850~930MHz。
带宽(BW):信号占用的频率宽度。带宽越窄,灵敏度越高,但速率越低。LR20推荐使用125kHz
扩频因子(SF):如前所述,SF越大越远越慢。本项目使用SF9,在距离和速率间取得平衡
发射功率:LR20最大+22dBm,功率越大传得越远但越耗电
接收灵敏度:-125dBm,这个数字越负代表能接收的信号越微弱,也就是"听力"越好
简单算一下:22dBm发射功率 - (-125dBm接收灵敏度) = 147dB的链路预算。链路预算越大,传输距离越远,LR20手册标注空旷环境可达8公里。
二、DX-LORA-LR20-900 介绍
LR20模组参数一览
参数 | 规格 |
|---|---|
芯片 | LLCC68 |
频段 | 850~930MHz |
调制方式 | LoRa |
发射功率 | +22dBm |
通信距离 | 8km(空旷环境) |
工作电压 | 1.8~3.7V |
发射电流 | 100mA |
接收电流 | 6.5mA |
待机电流 | 180nA |
FIFO | 256Byte |
SPI速率 | 0~10Mbps |
模块PCB设计
原理图
实拍图
说实话,6.5mA的接收电流和180nA的待机电流在远距离通信模组里算非常优秀的。这意味着如果用电池供电,模组可以长时间"监听"而不耗电。
三、快速上手:30分钟跑通通信
3.1 准备工作
你需要准备:
两块DX-LR20-900M模块(LoRa通信至少需要一发一收)
两根Type-C数据线
两根天线(SMA或IPEX均可,取决于你接哪个座子)
PC电脑
Keil5开发环境 + STM32F1xx HAL库
串口助手软件(如SSCOM)
3.2 引脚连接
LR20模组通过SPI与STM32通信,引脚分配如下:
功能 | STM32引脚 | 说明 |
|---|---|---|
NSS | PA4 | SPI片选 |
SCK | PA5 | SPI时钟 |
MISO | PA6 | SPI数据输出(模组→MCU) |
MOSI | PA7 | SPI数据输入(MCU→模组) |
RESET | GPIO | 模组复位 |
BUSY | GPIO | 模组状态指示 |
DIO1 | GPIO | 中断引脚(收发完成通知) |
TX_EN | GPIO | 发射使能(射频开关) |
RX_EN | GPIO | 接收使能(射频开关) |
串口引脚:PA9(TX) / PA10(RX),默认波特率9600。
3.3 默认程序工作流程
开发板出厂程序的核心逻辑非常清晰:
上电初始化 → LoRa参数配置(BW=125kHz, SF=9, 功率22dBm)→ 进入接收模式
每隔1秒自动发送一次
0xAA测试数据收到数据后通过串口打印,并触发LED闪烁
串口输入的数据也可以通过LoRa发送出去
两块板子同时运行,就能实现双向通信。
四、代码解析:从main.c开始读懂整个工程
4.1 main.c —— 程序入口
先看main()函数的初始化流程:
int main(void) { HAL_Init(); SystemClock_Config(); // 初始化LED - PB1 __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = LED_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(LED_PORT, &GPIO_InitStruct); LED_OFF(); // 初始化串口接收队列 UartRxQueue.front = 0; UartRxQueue.tail = 0; UartRxQueue.count = 0; pUart1RxQueue = (pQueue)&UartRxQueue; UsartInit(9600); // 串口初始化 TimerInit(); // 定时器初始化 TIM3_Init(); // 串口超时定时器 SPI_Init(); // SPI初始化 gpio_init(); // LoRa控制引脚初始化 // LoRa初始化 LoraInit(); DIO1_Init(); // DIO1中断初始化 // 启动时发送一次0xAA uint8_t sendData = 0xAA; LoraDataSend(&sendData, 1); lastSendTime = HAL_GetTick(); // 开始串口接收 HAL_UART_Receive_IT(&husart, &pdata, sizeof(pdata)); for(;;) { Data_Processing(); // 处理串口数据 DX_Lora_RadioIrqProcess(); // 处理LoRa中断 ProcessLoraRxData(); // 处理LoRa接收数据 ControlLEDBlink(); // LED闪烁控制 // 每秒自动发送0xAA if (!stopSending) { uint32_t currentTime = HAL_GetTick(); if (currentTime - lastSendTime >= sendInterval) { lastSendTime = currentTime; uint8_t sendData = 0xAA; LoraDataSend(&sendData, 1); } } } }整个程序是典型的前后台架构:中断负责数据接收和LoRa事件通知,主循环负责业务逻辑处理。
4.2 LoRa初始化 —— LoraInit()
这是整个驱动的核心,逐行解析:
void LoraInit(void) { g_lora_test_rxs = false; g_lora_tx_done = true; // 1. 硬件复位 + 唤醒 sx126x_reset(NULL); sx126x_wakeup(NULL); // 2. 进入待机配置模式 sx126x_set_standby(NULL, SX126X_STANDBY_CFG_RC); sx126x_set_standby(NULL, SX126X_STANDBY_CFG_XOSC); // 3. 选择DC-DC电压调节器模式 sx126x_set_reg_mode(NULL, SX126X_REG_MODE_DCDC); // 4. FIFO读写地址复位 sx126x_set_buffer_base_address(NULL, 0x00, 0x00); // 5. 设置数据包类型为LoRa sx126x_set_pkt_type(NULL, SX126X_PKT_TYPE_LORA); // 6. 设置晶振负载电容 sx126x_set_trimming_capacitor_values(NULL, 0x4, 0x2f); // 7. LoRa调制参数配置 sx126x_mod_params_lora_t params; params.bw = SX126X_LORA_BW_125; // 带宽125kHz params.sf = SX126X_LORA_SF9; // 扩频因子9 params.cr = SX126X_LORA_CR_4_6; // 编码率4/6 params.ldro = 0x00; // 低数据率优化关闭 sx126x_set_lora_mod_params(NULL, ¶ms); // 8. 数据包参数配置 sx126x_pkt_params_lora_t params2; params2.crc_is_on = 0; // CRC关闭 params2.invert_iq_is_on = 0; // IQ不反转 params2.pld_len_in_bytes = 0xff; // 负载长度 params2.header_type = SX126X_LORA_PKT_EXPLICIT; // 显式包头 params2.preamble_len_in_symb = LORA_PREAMBLE_LENGTH; // 前导码长度 sx126x_set_lora_pkt_params(NULL, ¶ms2); // 9. 功率放大器配置 sx126x_pa_cfg_params_t params3; params3.pa_duty_cycle = 0x04; params3.hp_max = 0x07; params3.device_sel = 0x00; params3.pa_lut = 0x01; sx126x_set_pa_cfg(NULL, ¶ms3); // 10. 开启DIO1中断:接收完成 + 发送完成 sx126x_set_dio_irq_params(NULL, SX126X_IRQ_RX_DONE | SX126X_IRQ_TX_DONE, SX126X_IRQ_RX_DONE | SX126X_IRQ_TX_DONE, SX126X_IRQ_NONE, SX126X_IRQ_NONE); sx126x_clear_irq_status(NULL, SX126X_IRQ_ALL); // 11. 设置发射功率22dBm sx126x_set_tx_params(NULL, 22, SX126X_RAMP_3400_US); // 12. 设置载波频率 sx126x_set_rf_freq(NULL, LORA_FRE); // 13. 进入接收模式 LoraOpenRXMode(LORA_SX126x_SYMBOL_TIMEOUT); }这13步是LLCC68、SX1262系列芯片的标准初始化流程。每一步都对应芯片手册中的一个寄存器配置命令,通过SPI发送给模组。
参数选择心得:BW=125kHz + SF=9是我多次测试后选的组合。SF越大传得越远但速率越低,SF=9在8公里范围内都能稳定通信,同时保持约1757bps的速率,对于传输传感器数据完全够用。
4.3 发送与接收流程
发送数据:
void LoraDataSend(uint8_t *data, uint8_t len) { TxEn(); // 切换射频开关到发射 set_LoraPacketParams(len); // 设置数据包长度 sx126x_write_buffer(NULL, 0x00, data, len); // 写入FIFO sx126x_set_tx(NULL, 6000); // 启动发送,超时6秒 g_lora_tx_done = false; // 标记发送中 sx1262SetOperatingMode(MODE_TX); // 设置状态机 }发送完成后,DIO1引脚会产生中断,触发OnTxDone()回调,自动切换回接收模式。
接收数据:
void OnRxDone(uint8_t* payload, uint16_t size, int16_t rssi, int8_t snr) { g_lora_tx_done = true; g_lora_test_rxs = true; OnLoraRxProcess(payload, size); // 传给上层处理 LoraOpenRXMode(LORA_SX126x_SYMBOL_TIMEOUT); // 重新进入接收 }接收中断触发后,从FIFO读取数据,传给上层的OnLoraRxProcess()回调:
void OnLoraRxProcess(uint8_t* payload, uint16_t size) { if (size > 0 && size < 256) { memcpy(loraRx.data, payload, size); loraRx.length = size; loraRx.ready = 1; // 标记数据就绪 } }主循环中检测到loraRx.ready后处理数据:
void ProcessLoraRxData(void) { if (loraRx.ready) { printf("LoRa Received: Len=%d, Data:", loraRx.length); for (int i = 0; i < loraRx.length; i++) { printf(" %02X", loraRx.data[i]); } printf("\n\r"); if (loraRx.length > 0 && loraRx.data[0] == 0xAA) { printf("Received 0xAA, starting LED blink\n\r"); isBlinking = 1; // 触发LED闪烁 lastBlinkTime = HAL_GetTick(); stopSending = 0; } loraRx.ready = 0; } }4.4 中断处理机制
LR20模组通过DIO1引脚通知MCU"有事发生了"。整个中断处理流程如下:
DIO1上升沿触发 → 外部中断ISR中设置
IrqFired = true,读取中断状态到radioFlag主循环调用
DX_Lora_RadioIrqProcess()检查中断标志根据
radioFlag的位判断是哪种事件(发送完成/接收完成/超时/CRC错误等)执行对应的回调函数
void DX_Lora_RadioIrqProcess(void) { if(IrqFired == true) { IrqFired = false; if((radioFlag & SX126X_IRQ_TX_DONE) == SX126X_IRQ_TX_DONE) { OnTxDone(); // 发送完成 } if((radioFlag & SX126X_IRQ_RX_DONE) == SX126X_IRQ_RX_DONE) { // 读取接收数据 sx126x_get_rx_buffer_status(NULL, &offset); sx126x_read_buffer(NULL, offset.buffer_start_pointer, radioRxbuff, offset.pld_len_in_bytes); sx126x_get_lora_pkt_status(NULL, &RadioPktStatus); OnRxDone(&radioRxbuff[0], offset.pld_len_in_bytes, RadioPktStatus.rssi_pkt_in_dbm, RadioPktStatus.snr_pkt_in_db); } // ... 其他中断处理 } }五、驱动移植:换一颗MCU怎么改
大夏龙雀提供的驱动文件结构清晰,移植到其他MCU只需要改两个文件。
5.1 工程文件结构
├── Core/ ← STM32 HAL库核心文件 ├── Driver/ ← 用户编写的外设驱动 ├── LR_driver/ ← LoRa射频驱动 │ ├── src/ │ │ ├── sx126x.c ← LoRa芯片操作函数 │ │ └── sx126x_hal.c ← 移植核心:SPI交互层 │ └── UserConfig.c ← 引脚配置 ├── Main/ ← main.c + 配置文件 ├── queue/ ← 串口数据队列 ├── SDK/ ← CMSIS + HAL库 └── Project/ ← Keil工程文件5.2 只需修改两个文件
文件1:sx126x_hal.c— SPI交互层
这个文件里封装了底层SPI通信函数。如果你换到ESP32、Arduino或其他平台,只需要把里面的SPI读写改成对应平台的API:
// 需要移植的3个核心函数: sx126x_hal_reset() // 拉低RESET引脚复位 sx126x_hal_wakeup() // 拉低NSS唤醒 sx126x_hal_write() // SPI写操作 sx126x_hal_read() // SPI读操作以ESP32为例,移植时改成:
sx126x_hal_status_t sx126x_hal_write(sx126x_hal_context_t *hal, const uint8_t *command, uint16_t command_len, const uint8_t *data, uint16_t data_len) { digitalWrite(NSS_PIN, LOW); // 片选拉低 spi_transfer(command, command_len); // 发命令 spi_transfer(data, data_len); // 发数据 digitalWrite(NSS_PIN, HIGH); // 片选拉高 return SX126X_HAL_STATUS_OK; }文件2:UserConfig.c— 引脚配置
修改引脚定义为你自己MCU的GPIO:
// 原始定义 #define LCC68_BUSY_PIN GPIO_PIN_0 #define LCC68_BUSY_PORT GPIOA #define LCC68_NRST_PIN GPIO_PIN_1 #define LCC68_NRST_PORT GPIOA // ... // 移植到ESP32后 #define LCC68_BUSY_PIN 4 // GPIO4 #define LCC68_NRST_PIN 5 // GPIO5 // ...其他文件不用动。sx126x.c里的寄存器操作函数是纯逻辑代码,跟具体MCU无关。
5.3 移植到其他MCU的检查清单
检查项 | 说明 |
|---|---|
SPI时序 | 确保CPOL=0, CPHA=0(Mode 0),最高10MHz |
电平匹配 | LR20是3.3V,5V MCU需要电平转换 |
NSS控制 | 软件控制CS引脚,不要用硬件NSS |
DIO1中断 | 配置为上升沿触发的外部中断 |
BUSY检测 | 发送SPI命令前必须检查BUSY为低 |
RESET | 低电平复位,至少保持200μs |
六、通信实测:到底能传多远?
6.1 测试环境
条件 | 配置 |
|---|---|
频率 | 920MHz |
带宽 | 125kHz |
扩频因子 | SF9 |
编码率 | 4/6 |
发射功率 | 22dBm |
天线 | SMA胶皮天线 |
天线高度 | 1.5m(手持) |
测试数据 | 0xAA,每秒发送一次 |
6.2 测试结果
场景 | 距离 | RSSI | SNR | 丢包率 |
|---|---|---|---|---|
空旷操场 | 500m | -75 | 8 | 0% |
空旷操场 | 1km | -85 | 5 | 0% |
空旷操场 | 2km | -95 | 2 | 0% |
空旷操场 | 3km | -105 | -1 | ~2% |
室内穿1堵墙 | 50m | -80 | 6 | 0% |
室内穿2堵墙 | 30m | -95 | 2 | ~1% |
室内穿3堵墙 | 15m | -110 | -3 | ~5% |
地下车库 | 200m | -100 | 0 | ~3% |
实测结论:在空旷环境下2公里以内基本零丢包,穿墙能力比WiFi强太多。3公里以后信号衰减明显,但对传感器数据这种小数据量的场景,偶尔丢一两个包影响也不大(可以加重传机制)。手册标的8公里是在理想环境(天线3米高、无遮挡)下测的,实际使用中2~3公里是比较靠谱的覆盖范围。
6.3 功率对距离的影响
我用串口指令功能修改了发射功率,做了对比测试:
功率 | 1km丢包率 | 2km丢包率 |
|---|---|---|
22dBm | 0% | 0% |
17dBm | 0% | ~1% |
12dBm | ~1% | ~5% |
7dBm | ~3% | ~15% |
如果只传几十米,完全可以降低功率来省电。这是LoRa的优势——参数灵活配置,按需调整。
七、应用场景:LoRa适合做什么
7.1 农业物联网
7.2 智能园区![]()
写字楼里的水表、电表、燃气表自动抄表,地下室和楼顶都要覆盖。LoRa的穿透能力可以覆盖整栋楼,不用每个房间装WiFi。
7.3 资产追踪
仓库里的货物追踪、停车场车位检测,LoRa的低功耗特性让追踪器可以工作很长时间不用换电池。
7.4 我自己的应用设想
我打算用LR20模组做一个远程温湿度监测系统:在户外部署几个带SHT30温湿度传感器的LoRa节点,数据通过LoRa传回室内的接收节点。
八、总结
写这篇文章的时候,我回头看了看自己从"LoRa是什么"到"成功跑通通信"的过程,大概花了三天时间。第一天研究LoRa原理和芯片手册,第二天读代码理解驱动逻辑,第三天做实测和参数调试。
大夏龙雀的DX-LR20在整个过程中给我留下了不错的印象:
二次开发较易:Semtech官方驱动+HAL库适配,移植只需要改两个文件
性能可靠:实测2公里零丢包,穿墙能力远超WiFi
功耗友好:6.5mA接收电流,适合电池供电场景
如果你也在找一个远距离、低功耗、易上手的无线通信方案,不妨试试LORA,试试DX-LR20。从跑通第一包数据开始,你会发现LORA是真的好用。