STM32 应用之 LoRa:从基础知识到工程实战
一、为什么选择 LoRa在物联网场景中Wi-Fi 覆盖短、功耗高蓝牙距离有限NB-IoT 依赖运营商网络。LoRaLong Range作为 Sub-GHz 频段的低功耗广域网LPWAN物理层技术具备远距离、低功耗、抗干扰强、无需授权频段等特点非常适合抄表、环境监测、农业传感、资产追踪等小数据量、低频次上报的业务。LoRa 只定义了物理层其上层组网协议是 LoRaWAN。理解这一点很关键LoRa 是调制技术LoRaWAN 是网络协议两者不能混为一谈。二、LoRa 基础知识2.1 线性调频扩频CSSLoRa 采用 Chirp Spread Spectrum线性调频扩频调制载波频率随时间线性变化形成“啁啾”信号。接收端通过解扩把窄带噪声摊开、把有用信号能量集中因此能在低于噪声底的信噪比下正确解调这是 LoRa 远距离能力的根本来源。2.2 四个关键参数参数含义典型取值影响SF扩频因子每个符号携带的比特数SF7 ~ SF12SF 越大距离越远、速率越低、空中时间越长BW带宽信道带宽125 / 250 / 500 kHz带宽越大速率越高但灵敏度下降CR编码率前向纠错冗余比例4/5 ~ 4/8冗余越多抗干扰越强有效速率越低TP发射功率射频输出功率2 ~ 20 dBm功率越高距离越远功耗与法规限制越大2.3 空中时间与占空比空中时间Time on Air由 SF、BW、CR、前导码长度和负载长度共同决定。SF 每增加 1符号时间翻倍空中时间近似翻倍。国内 470~510 MHz 频段对占空比有约束工程上必须控制发送频次避免长时间占用信道。Tsym 2^SF / BW Tpreamble (Npreamble 4.25) * Tsym Tpayload ≈ Tsym * (8 max(ceil((8*PL - 4*SF 28 16*CRC) / (4*(SF - 2*DE))) * (CR 4), 0))2.4 链路预算与灵敏度链路预算 发射功率 − 接收灵敏度。SX1278 在 SF12、BW125 下灵敏度可达约 −137 dBm配合 20 dBm 发射功率链路预算约 157 dB理论上可覆盖数公里到十几公里视环境而定。面试常问为什么 LoRa 能传这么远答案要落到“扩频增益 低速率 高灵敏度 Sub-GHz 绕射能力强”这几点上。三、STM32 上的 LoRa 应用所需知识3.1 硬件层模块与接口常见芯片有 SX1278433/470 MHz、SX1276868/915 MHz、SX1262新一代功耗更低。芯片与 STM32 之间通常通过SPI通信另外还需要若干 GPIONSS片选可用硬件 SPI 的 NSS 或软件 GPIO 控制。SCK / MOSI / MISO标准 SPI 四线中的三线。RESET复位引脚上电需拉低再释放。DIO0 ~ DIO5中断输出DIO0 常用于 TxDone / RxDone 中断。BUSYSX126x忙标志操作前需等待其拉低。注意SX127x 的 SPI 时钟极性为CPOL0、CPHA0最高时钟约 10 MHzSX126x 支持更高时钟但同样要遵守时序。也可以直接使用搭载相关芯片的成品模块比如E32-433T20D这是一款基于 SX1278 射频芯片的无线串口模块TTL 电平。3.1.1 E32-433T20D 模块使用介绍E32-433T20D 把 SX1278 的 SPI 寄存器操作全部封装在模块内部STM32 侧不再需要写 SPI 驱动只需要通过 UART 收发数据、通过 M0/M1 选择工作模式、通过 AUX 判断模块是否空闲。对于不想深入寄存器细节、只想快速把 LoRa 链路跑起来的项目这是最省事的方案。主要参数项目参数射频芯片SX1278工作频段410~441 MHz默认 433 MHz信道步进 1 MHz共 32 个信道调制方式LoRa 扩频基于 CSS最大发射功率20 dBm约 100 mW通信接口UARTTTL 电平默认 9600 8N1空中速率0.3k ~ 19.2k bps可配置供电电压2.3 ~ 5.5 VTTL 电平版本传输方式透明传输发什么收什么引脚连接VCC / GND供电注意发射瞬间电流可达 100 mA 以上电源要留余量并就近加去耦电容。TXD / RXD接 STM32 的 UART RX / TX注意交叉连接。M0 / M1工作模式选择普通 GPIO 输出即可模块内部有上拉悬空时为默认模式。AUX模块状态输出接 STM32 的 GPIO 输入可配外部中断用于判断模块是否忙、是否有数据待取。四种工作模式由 M0、M1 电平决定M1M0模式说明00一般模式正常收发串口与射频双向透明传输01唤醒模式发送数据时自动加唤醒前导码用于唤醒处于省电模式的接收方10省电模式串口关闭射频处于监听状态收到数据后通过 AUX 唤醒 MCU11休眠模式可进行参数配置功耗最低参数配置流程把 M0、M1 都拉高进入休眠配置模式等待 AUX 输出高电平。通过串口发送 6 字节配置命令C0 起始地址 长度 参数...例如C0 00 00 1A 17 44表示写寄存器 0x00 开始的 2 个字节。等待 AUX 拉高表示配置完成。把 M0、M1 拉低回到一般模式配置立即生效。透明传输的注意点模块内部有缓冲区发送方连续写入过快会溢出必须用 AUX 做流控AUX 为低表示模块忙此时不要继续写串口。接收方在一般模式下收到数据会直接从串口输出MCU 侧建议用 DMA 空闲中断接收避免丢包。同一网络内所有模块的信道、地址、空中速率必须一致否则互相收不到。模块的空中速率与串口波特率是两回事串口 9600 不代表空中 9600两者要分别配置。STM32 侧最小驱动示例#include stm32f1xx_hal.h extern UART_HandleTypeDef huart2; #define M0_HIGH() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET) #define M0_LOW() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET) #define M1_HIGH() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET) #define M1_LOW() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET) #define AUX_READ() HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) /* 等待模块空闲超时返回 0 */ static uint8_t e32_wait_aux_idle(uint32_t timeout_ms) { uint32_t tick HAL_GetTick(); while (AUX_READ() GPIO_PIN_RESET) { if (HAL_GetTick() - tick timeout_ms) { return 0; } } return 1; } /* 进入配置模式并写入 6 字节命令 */ uint8_t e32_config(const uint8_t *cmd, uint16_t len) { M0_HIGH(); M1_HIGH(); HAL_Delay(10); if (!e32_wait_aux_idle(100)) { return 0; } HAL_UART_Transmit(huart2, (uint8_t *)cmd, len, 100); if (!e32_wait_aux_idle(200)) { return 0; } M0_LOW(); M1_LOW(); HAL_Delay(10); return 1; } /* 一般模式下发送数据 */ uint8_t e32_send(const uint8_t *data, uint16_t len) { if (!e32_wait_aux_idle(100)) { return 0; } return HAL_UART_Transmit(huart2, (uint8_t *)data, len, 500) HAL_OK; }提示E32 系列不同型号如 E32-433T20D 与 E32-433T30D的配置寄存器定义略有差异动手前务必对照对应型号的数据手册不要直接套用别的型号的配置表。3.2 驱动层SPI 读写与寄存器操作SX127x 的寄存器地址为 7 位最高位表示读写方向写为 0读为 1。因此读寄存器时地址需| 0x80。#include stm32f1xx_hal.h extern SPI_HandleTypeDef hspi1; #define LORA_NSS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define LORA_NSS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET) static void sx127x_write_reg(uint8_t addr, uint8_t value) { uint8_t tx[2] { addr 0x7F, value }; LORA_NSS_LOW(); HAL_SPI_Transmit(hspi1, tx, 2, 100); LORA_NSS_HIGH(); } static uint8_t sx127x_read_reg(uint8_t addr) { uint8_t tx addr | 0x80; uint8_t rx 0; LORA_NSS_LOW(); HAL_SPI_Transmit(hspi1, tx, 1, 100); HAL_SPI_Receive(hspi1, rx, 1, 100); LORA_NSS_HIGH(); return rx; }实际工程中建议把 SPI 收发合并为一次HAL_SPI_TransmitReceive减少片选翻转次数提高稳定性。3.3 配置层进入 LoRa 模式与参数设置SX127x 上电默认是 FSK/OOK 模式必须先切换到 LoRa 模式再配置频率、SF、BW、CR、前导码、同步字和发射功率。#define REG_OP_MODE 0x01 #define REG_FRF_MSB 0x06 #define REG_FRF_MID 0x07 #define REG_FRF_LSB 0x08 #define REG_MODEM_CONFIG1 0x1D #define REG_MODEM_CONFIG2 0x1E #define REG_MODEM_CONFIG3 0x26 #define REG_PREAMBLE_MSB 0x20 #define REG_PREAMBLE_LSB 0x21 #define REG_SYNC_WORD 0x39 #define REG_PA_CONFIG 0x09 #define REG_PA_DAC 0x4D #define MODE_SLEEP 0x00 #define MODE_STDBY 0x01 #define MODE_TX 0x03 #define MODE_RX_CONTINUOUS 0x05 #define LONG_RANGE_MODE 0x80 static void sx127x_set_frequency(uint32_t freq_hz) { /* Frf freq * 2^19 / 32MHz */ uint32_t frf (uint32_t)(((uint64_t)freq_hz 19) / 32000000UL); sx127x_write_reg(REG_FRF_MSB, (frf 16) 0xFF); sx127x_write_reg(REG_FRF_MID, (frf 8) 0xFF); sx127x_write_reg(REG_FRF_LSB, frf 0xFF); } static void sx127x_init_lora(void) { /* 1. 进入睡眠模式并切换到 LoRa */ sx127x_write_reg(REG_OP_MODE, MODE_SLEEP | LONG_RANGE_MODE); HAL_Delay(10); /* 2. 设置频率 470MHz */ sx127x_set_frequency(470000000UL); /* 3. BW125kHz, CR4/5, 显式报头 */ sx127x_write_reg(REG_MODEM_CONFIG1, 0x72); /* 4. SF9, CRC 使能 */ sx127x_write_reg(REG_MODEM_CONFIG2, 0x92); /* 5. 低数据率优化 AGC */ sx127x_write_reg(REG_MODEM_CONFIG3, 0x04); /* 6. 前导码长度 8 */ sx127x_write_reg(REG_PREAMBLE_MSB, 0x00); sx127x_write_reg(REG_PREAMBLE_LSB, 0x08); /* 7. 同步字 0x12私有网络 */ sx127x_write_reg(REG_SYNC_WORD, 0x12); /* 8. 发射功率 17dBm */ sx127x_write_reg(REG_PA_CONFIG, 0x8F); /* 9. 进入待机 */ sx127x_write_reg(REG_OP_MODE, MODE_STDBY | LONG_RANGE_MODE); }3.4 收发流程与中断处理发送时把数据写入 FIFO0x00设置负载长度0x22切到 TX 模式等待 DIO0 的 TxDone 中断。接收时切到连续接收模式DIO0 触发 RxDone 后读取负载长度和 FIFO 数据并检查 CRC 与 RSSI。#define REG_FIFO 0x00 #define REG_FIFO_ADDR_PTR 0x0D #define REG_FIFO_TX_BASE 0x0E #define REG_FIFO_RX_BASE 0x0F #define REG_IRQ_FLAGS 0x12 #define REG_PAYLOAD_LENGTH 0x22 #define REG_PKT_RSSI_VALUE 0x1A #define IRQ_TX_DONE 0x08 #define IRQ_RX_DONE 0x40 #define IRQ_PAYLOAD_CRC_ERR 0x20 void sx127x_send(const uint8_t *data, uint8_t len) { sx127x_write_reg(REG_OP_MODE, MODE_STDBY | LONG_RANGE_MODE); sx127x_write_reg(REG_FIFO_ADDR_PTR, 0x00); for (uint8_t i 0; i len; i) { sx127x_write_reg(REG_FIFO, data[i]); } sx127x_write_reg(REG_PAYLOAD_LENGTH, len); sx127x_write_reg(REG_OP_MODE, MODE_TX | LONG_RANGE_MODE); } void sx127x_start_rx(void) { sx127x_write_reg(REG_FIFO_ADDR_PTR, 0x00); sx127x_write_reg(REG_OP_MODE, MODE_RX_CONTINUOUS | LONG_RANGE_MODE); } /* 在 DIO0 外部中断回调中调用 */ void sx127x_on_dio0_irq(void) { uint8_t flags sx127x_read_reg(REG_IRQ_FLAGS); sx127x_write_reg(REG_IRQ_FLAGS, 0xFF); /* 清中断 */ if (flags IRQ_RX_DONE) { if (flags IRQ_PAYLOAD_CRC_ERR) { return; /* CRC 错误丢弃 */ } uint8_t len sx127x_read_reg(REG_PAYLOAD_LENGTH); uint8_t rssi sx127x_read_reg(REG_PKT_RSSI_VALUE); uint8_t buf[64]; sx127x_write_reg(REG_FIFO_ADDR_PTR, sx127x_read_reg(REG_FIFO_RX_BASE)); for (uint8_t i 0; i len i sizeof(buf); i) { buf[i] sx127x_read_reg(REG_FIFO); } /* 处理 buf / len / rssi */ } }3.5 低功耗设计要点STM32 侧使用STOP 模式 RTC 唤醒在两次上报之间深度休眠。LoRa 模块在空闲时切到 SLEEP 模式电流可降到微安级。发送前再上电或唤醒射频发送完成后立即回到睡眠。合理选择 SF距离够用即可SF 越大功耗越高。注意 DIO 中断唤醒 MCU 的配置避免漏包。3.6 天线与射频布局天线匹配网络通常为 π 型必须按模块手册调试射频走线要做 50 Ω 阻抗控制远离晶振和高速数字信号电源需加去耦电容避免发射瞬间电压跌落导致复位。四、面试高频考点清单4.1 LoRa 原理类LoRa 和 LoRaWAN 的区别LoRa 是物理层调制技术LoRaWAN 是建立在 LoRa 之上的 MAC 层与网络协议。LoRa 为什么能远距离通信扩频增益、低速率、高接收灵敏度、Sub-GHz 频段绕射能力强。SF 增大带来什么变化灵敏度提升约 3 dB/级速率减半空中时间翻倍功耗上升。带宽对性能的影响BW 增大速率提高但灵敏度下降、抗噪能力变弱。编码率 CR 的作用前向纠错冗余越多抗干扰越强有效速率越低。链路预算怎么算发射功率减去接收灵敏度单位 dB。什么是空中时间如何估算由 SF、BW、CR、前导码和负载长度决定可用公式或在线计算器估算。LoRa 的占空比限制是什么部分地区法规限制单信道占用时间比例需控制发送频次。4.2 STM32 工程类SX127x 与 STM32 如何连接SPI 四线加 RESET、DIO0 等 GPIO注意片选和中断引脚。SPI 读写寄存器的地址规则7 位地址最高位为读写标志读操作地址需或 0x80。为什么上电后要先进 SLEEP 再切 LoRa 模式只有睡眠模式下才能可靠切换调制模式。DIO0 中断的作用TxDone、RxDone、CadDone 等事件通知避免轮询。如何判断接收数据是否有效检查 PayloadCrcError 标志并读取 RSSI/SNR 评估链路质量。如何降低整体功耗MCU 用 STOP 模式射频空闲进 SLEEP减少发送频次按需选择 SF。发射时 MCU 复位怎么办多为电源跌落需加大电容、优化供电路径、检查天线驻波。如何提高通信可靠性加 CRC、应用层重传与 ACK、合理设置前导码、避开干扰频点。4.3 E32-433T20D 模块类E32-433T20D 和直接用 SX1278 芯片有什么区别E32 是封装好的串口透传模块内部 MCU 已完成 SX1278 的 SPI 配置与收发管理对外只暴露 UART 和 M0/M1/AUX直接用芯片则需要自己写 SPI 驱动、寄存器配置和中断收发流程。前者开发快、灵活性低后者可控性强、能精细调参。M0、M1 两个引脚的作用是什么用于选择模块工作模式M00/M10 为一般模式M01/M10 为唤醒模式M00/M11 为省电模式M01/M11 为休眠配置模式。模式切换后需要等待 AUX 拉高才表示切换完成。AUX 引脚有什么用为什么必须处理AUX 是模块的状态指示低电平表示模块忙正在收发或配置高电平表示空闲。发送前必须等 AUX 变高否则数据会丢接收时可用 AUX 下降沿中断通知 MCU 有数据到来。不做 AUX 流控是新手最常见的丢包原因。如何修改模块的信道和地址把 M0、M1 都拉高进入休眠模式通过串口发送C0开头的配置命令写入对应寄存器等待 AUX 拉高后把 M0、M1 拉低配置生效。同一网络中所有模块的信道、地址、空中速率必须完全一致。透明传输模式下模块会不会把数据分包会。模块内部有固定大小的缓冲区单次发送长度受缓冲区限制超过部分会被拆分或丢弃。工程上建议单包控制在几十字节以内并在应用层加帧头、长度和校验。串口波特率和空中速率是一回事吗不是。串口波特率是模块与 MCU 之间的通信速率空中速率是射频链路的传输速率两者独立配置。空中速率越低灵敏度越高、距离越远但空中时间越长、功耗越大。模块发射时 MCU 复位或串口异常可能是什么原因多为电源问题发射瞬间电流突增导致电压跌落。应加大电源去耦电容、缩短供电走线、避免与 MCU 共用细线供电同时检查天线是否匹配良好驻波过大也会引起电流异常。两个 E32 模块通信不上如何排查按顺序检查供电是否正常、M0/M1 模式是否正确、串口波特率是否一致、信道与地址是否相同、空中速率是否相同、天线是否接好、距离与遮挡是否超出链路预算最后用 AUX 波形确认模块是否真的在收发。4.3 易错点提醒① 忘记清 IRQ 标志导致中断反复触发② FIFO 指针未复位导致读到旧数据③ 频率计算未用 64 位导致溢出④ 同步字不一致导致双方收不到⑤ 显式/隐式报头配置不一致。五、小结在 STM32 上使用 LoRa核心是把三件事做扎实理解扩频参数对距离、速率和功耗的权衡写好 SPI 与中断驱动做好低功耗与射频布局。把本文第四节的考点过一遍基本可以覆盖 LoRa 与 STM32 结合的中初级面试问题。