
简介面向嵌入式物联网单片机开发者这份实战资源以STM32F103为核心围绕LORA无线收发通讯实验展开重点解决节点间无线数据传输的工程实现问题适合有一定单片机基础、希望学习标准库开发及无线通信的初学者或项目实战人员。工程基于KEIL标准库编写代码注释完整模块接线在源码中有清晰定义兼容STM32F103系列其他型号只需调整芯片型号与Flash容量即可运行便于快速移植到实际项目中。压缩包共153个文件包含大量头文件与源文件、编译生成的中间文件、烧录文件、工程配置文件及脚本说明等整体约2.96MB结构清晰便于查阅。目前已有383人学习使用。通过本资源可系统掌握LORA模块与STM32的硬件连接、初始化配置、无线收发流程及调试方法亦可作为物联网节点通信、环境监测等场景的快速参考模板。1. 嵌入式物联网项目里真正决定通信距离的往往不是MCU而是射频链路STM32F103这边负责采集和控制LORA那边负责把数据送到几百米甚至几公里外这个实验的主线就是用SPI把这两半接起来跑通一条完整的无线收发链路。为什么选F103做节点外设简单、库齐全用最小系统板就能带起来而且后续换低功耗MCU时这套SPI加寄存器的驱动逻辑几乎原样保留。适合照着做的人是对GPIO、串口、中断都不陌生但还没亲手调过射频参数的开发者做过多年嵌入式的可以直接跳到后面看参数和排错。LORA的名字有“广域网”的含义但这类实验本质上先解决点对点链路组网是后话。2. LORA调制、SX1278寄存器与STM32F103的SPI接线拿到模块先看清楚芯片型号再写代码。市面上STM32F103实验最常用的是SX1278芯片常见封装形式叫Ra-02也有国产兼容型号SX1262/SX1268是另一代寄存器模型不能直接套同一套底层。判断方法很简单模块丝印带SX1278的直接按本文走带SX126x的要把寄存器地址全部换掉。实验里最常见的组合是STM32F103最小系统板加SX1278模块用杜邦线就能搭起来但发射功率在20dBm档次上时线长超过20cm容易掉压跑通后建议改成母对母排线或直接插模块。2.1 从FSK的灵敏度瓶颈说起为什么物联网节点爱用LORAFSK接收机做到-112dBm已是工程极限而LORA在同样频段、相同发射功率下可以做到-137dBm的灵敏度下限这25dB的差距对应距离上大约三四倍的差距。它的手段不是把天线做大而是用扩频调制换取信噪比发送端把一个bit拆成一段扩频序列接收端用同一个序列做相关解调干扰和噪声被平均掉。代价是数据速率低但物联网环境监测、表计抄表这类应用本来一包就几十字节速率低不是问题链路余量才是。需要说清楚的是LORA只解决物理层的无线收发不负责组网和可靠确认。所谓“无线收发通讯实验”就是把物理层的发送和接收跑通重传、应答、编号这些属于应用层可以放在MCU里自己写。这也解释了为什么标题里的实验几乎全是两个节点对发一主一从一个发一个收验证调制链路有没有建立起来。2.2 SX1278的寄存器模型FIFO、IRQ与模式切换SX1278内部有256字节FIFO、一组状态寄存器和一组配置寄存器MCU通过SPI访问它们。LORA模式下MCU要做的事只有三件把要发的内容写进FIFO配置调制参数然后切模式。发送时数据从FIFO出来经过调制后上射频接收时射频解调后的数据落回FIFOMCU再读出来。这个框架固定下来之后换任何SX1276/SX1278模块代码都一样。实验里必须熟悉的寄存器不多整理成表地址寄存器作用0x00RegFifoFIFO读写口payload从这里进出0x01RegOpModeSleep/Standby/TX/RX模式切换bit7选LORA或FSK0x06~0x08RegFrf频率字3字节直接决定载波频率0x09RegPaConfig发射功率bit7选择PA_BOOST路径0x0DRegFifoAddrPtrFIFO地址指针读写payload前要设置0x12RegIrqFlags中断标志TX_DONE和RX_DONE都在这里0x13RegRxNbBytes收到的一包数据字节数0x1D/0x1ERegModemConfig1/2带宽、编码率、扩频因子、CRC开关0x22RegPayloadLength发送模式下的payload长度模式切换要按状态机走上电默认Sleep配置寄存器要等芯片完全醒来再写发送完必须回Standby不回去会出现下一包发不出去的现象。这是新手查半小时查不出来、老手看一眼就知道的原因。2.3 接线图和引脚选择STM32F103最小系统配SX1278模块用STM32F103的SPI1连接SX1278引脚分配如下这也是大多数实验例程的默认排布STM32F103引脚SX1278模块引脚说明PA4NSSSPI片选低电平有效PA5SCKSPI时钟主模式输出PA6MISO模块数据输出PA7MOSI模块数据输入PA3NRST复位低电平复位PA2DIO0中断输出接外部中断或轮询读取供电用3.3V模块的SPI电平上限是3.6VF103工作在3.3V时可以直接对接不需要电平转换。NRST建议用GPIO控制不要直接接RC复位电路否则软件没法初始化芯片。DIO0在发送和接收时都会产生跳变实验阶段可以只用轮询寄存器不接DIO0也能跑通但建议一开始就接上后面改中断驱动时不用重新飞线。3. 用STM32F103跑通LORA发送与接收的最小代码这一章给完整可编译的最小代码基于标准外设库V3.5风格。代码刻意精简到只剩链路必须的部分OLED显示、串口打印、按键触发全部剥掉方便移植和排查。3.1 SPI1配置和LORA寄存器读写SPI1初始化用标准外设库注意SX1278要求SPI模式0即CPOL低电平、CPHA第一个沿采样时钟分频选择2.25MHz足够稳定不必追求高速void SPI1_Init(void) { GPIO_InitTypeDef gpio; SPI_InitTypeDef spi; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); gpio.GPIO_Mode GPIO_Mode_AF_PP; // SCK、MOSI 复用推挽 gpio.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); gpio.GPIO_Mode GPIO_Mode_IN_FLOATING; // MISO 浮空输入 gpio.GPIO_Pin GPIO_Pin_6; GPIO_Init(GPIOA, gpio); gpio.GPIO_Mode GPIO_Mode_Out_PP; // NSS 软件控制 gpio.GPIO_Pin GPIO_Pin_4; GPIO_Init(GPIOA, gpio); GPIO_SetBits(GPIOA, GPIO_Pin_4); // NSS 默认拉高 spi.SPI_Direction SPI_Direction_2Lines_FullDuplex; spi.SPI_Mode SPI_Mode_Master; spi.SPI_DataSize SPI_DataSize_8b; spi.SPI_CPOL SPI_CPOL_Low; // 模式0 spi.SPI_CPHA SPI_CPHA_1Edge; spi.SPI_NSS SPI_NSS_Soft; spi.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_32; // 2.25MHz spi.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, spi); SPI_Cmd(SPI1, ENABLE); }SPI工作在2.25MHz远低于SX1278的10MHz上限实际用杜邦线时也够稳定。NSS用普通GPIO控制不启用SPI硬件片选因为模块要求NSS在整个读写事务期间全程拉低。寄存器读写函数是全部驱动的地基uint8_t LORA_ReadReg(uint8_t reg) { uint8_t val; GPIO_ResetBits(GPIOA, GPIO_Pin_4); // NSS 拉低 SPI1_ReadWriteByte(reg 0x7F); // 读指令最高位为0 val SPI1_ReadWriteByte(0x00); // 读回数据 GPIO_SetBits(GPIOA, GPIO_Pin_4); // NSS 拉高 return val; } void LORA_WriteReg(uint8_t reg, uint8_t val) { GPIO_ResetBits(GPIOA, GPIO_Pin_4); SPI1_ReadWriteByte(reg | 0x80); // 写指令最高位为1 SPI1_ReadWriteByte(val); GPIO_SetBits(GPIOA, GPIO_Pin_4); }SX1278的SPI约定是寄存器地址最高位区分读写1写0读所以同一份地址表操作方向不同时指令字节不同。这里的SPI1_ReadWriteByte是标准外设库的SPI读写函数发送一个字节的同时接收一个字节返回值即MISO上采到的数据。3.2 发送把数据写进FIFO并等待TX_DONE初始化函数先做复位再切到LORA模式并配置频点。433MHz频点的频率字计算方式是硬件固定值void LORA_Reset(void) { GPIO_ResetBits(GPIOA, GPIO_Pin_3); // NRST 拉低 delay_ms(10); GPIO_SetBits(GPIOA, GPIO_Pin_3); delay_ms(10); } void LORA_SetFrequency(uint32_t freq) { uint32_t frf (uint32_t)((uint64_t)freq * 524288 / 32000000); LORA_WriteReg(0x06, (frf 16) 0xFF); LORA_WriteReg(0x07, (frf 8) 0xFF); LORA_WriteReg(0x08, frf 0xFF); }频率字公式用整数运算代替浮点SX1278内部参考时钟32MHzFSTEP等于32MHz除以2的19次方即61.03515625Hz433000000除以这个数得到0x6C8000对应三个寄存器分别是0x6C、0x80、0x00。公式里524288就是2的19次方直接用乘法避开浮点误差。初始化配置带宽、编码率、扩频因子和发送模式void LORA_Init(void) { LORA_Reset(); LORA_WriteReg(0x01, 0x80); // Sleep LoRa模式 delay_ms(5); LORA_SetFrequency(433000000UL); LORA_WriteReg(0x1D, 0x70); // BW125kHz, CR4/5, 显式头 LORA_WriteReg(0x1E, 0x74); // SF7, CRC开启 LORA_WriteReg(0x0C, 0x23); // LNA最大增益 LORA_WriteReg(0x09, 0xFF); // PA_BOOST路径20dBm LORA_WriteReg(0x01, 0x81); // 切回Standby等待发送 }0x1D写0x70是带宽125kHz加编码率4/5加显式头0x1E写0x74是扩频因子SF7加CRC开启。网上不少代码这里写0x72实际上那已经是编码率4/6了两端一致也能通但调试时要对得上含义别照抄后发现链路预算和预期差一截。发送函数把payload写入FIFO设置长度切TX模式轮询等待TX_DONEvoid LORA_Send(uint8_t *data, uint8_t len) { uint32_t timeout 0; uint8_t i; LORA_WriteReg(0x01, 0x81); // 回Standby LORA_WriteReg(0x0E, 0x00); // TX基地址 LORA_WriteReg(0x0D, 0x00); // FIFO指针 0 LORA_WriteReg(0x22, len); // payload长度 GPIO_ResetBits(GPIOA, GPIO_Pin_4); SPI1_ReadWriteByte(0x80); // 写FIFO指令 for (i 0; i len; i) SPI1_ReadWriteByte(data[i]); GPIO_SetBits(GPIOA, GPIO_Pin_4); LORA_WriteReg(0x01, 0x83); // 切TX模式 while ((LORA_ReadReg(0x12) 0x08) 0) // 等TX_DONE { if (timeout 100000) break; // 超时保护 } LORA_WriteReg(0x12, 0x08); // 清TX_DONE LORA_WriteReg(0x01, 0x81); // 回Standby }发送的关键顺序先回Standby再操作FIFO避免在TX发射过程中改FIFO长度寄存器必须在切TX前写否则FIFO不知道要发几个字节等待TX_DONE后必须清中断不清会导致下一次中断组合异常。超时保护写在循环里正常情况下几十个周期内就退出。3.3 接收轮询RX_DONE读FIFO以及中断写法接收端先开启连续接收模式然后轮询中断标志。收到一包后从FIFO读出注意读之前要把FIFO指针设置到当前包的首地址uint8_t LORA_RxPoll(uint8_t *buf) { uint8_t irq, len, i; if ((LORA_ReadReg(0x12) 0x40) 0) // 无RX_DONE return 0; irq LORA_ReadReg(0x12); // 先读再清 len LORA_ReadReg(0x13); // 实际字节数 LORA_WriteReg(0x0D, LORA_ReadReg(0x10)); // FIFO指针 当前包地址 GPIO_ResetBits(GPIOA, GPIO_Pin_4); SPI1_ReadWriteByte(0x00); // 读FIFO for (i 0; i len; i) buf[i] SPI1_ReadWriteByte(0x00); GPIO_SetBits(GPIOA, GPIO_Pin_4); LORA_WriteReg(0x12, irq); // 写1清中断 return len; }写1清中断是SX1278的约定读到的irq原样写回即可只清当前已置位的标志不误伤其他位。连续接收模式下清完中断后芯片自动回到侦听状态整个主循环里反复调用这个函数就行如果用的是单次接收模式清完中断还要再写一次0x85重新开启。用DIO0中断时把PA2配置为下降沿触发的外部中断中断服务函数里只做一件事调用读包函数把数据放在全局缓冲区退出中断后再由主循环处理。不要在中断里做串口打印或OLED刷新RX_DONE标志不快点清掉下一包来了就会覆盖FIFO。4. LORA通信参数怎么选频率、扩频因子SF、带宽BW与编码率CR代码跑通只是第一步两个模块能不能稳定通信、距离能到多远取决于四个参数的搭配。这四个参数不独立它们共同决定链路余量、速率和占用频谱的时间参数改错了表现为“模块明明能发但接收端永远等不到数据”。4.1 四个参数决定链路余量速率、灵敏度、空中时间LORA的空中速率近似公式是SF乘带宽除以2的SF次方再乘编码率。以125kHz带宽为例几个典型组合如下扩频因子带宽编码率空中速率灵敏度参考值适合场景SF7125kHz4/5约5.5kbps-123dBm近距离高频率上报SF9125kHz4/5约1.8kbps-129dBm园区内一般覆盖SF12125kHz4/5约293bps-134dBm长距离、强遮挡SF12125kHz4/8约260bps-137dBm链路余量优先注意速率值是对空中的码片速率不是应用层的吞吐量。实际有效吞吐还要扣掉前导码、同步和头部的开销实验里一包20字节SF7一秒钟能发差不多10包SF12则要接近1秒一包时间差异非常大。这里最容易踩的坑是只关注频点一致忽略SF、BW、CR也必须一致。接收端解调和发射端调制用的是同一组参数任何一位不对RX_DONE都不会置位表现就是静默无响应不是数据错乱。4.2 不同物联网场景的参数组合与改动位置实验阶段我一般先用SF7加125kHz带宽因为调试速度快串口刷新频率高肉眼能直接判断通不通。真实项目里再按业务改环境监测传感器一小时一包数据无线链路要穿过几堵墙直接用SF12加125kHz把灵敏度压到极限。这里有个反直觉的点SF12的单包发送时间更长单次功耗确实更高但因为发送频次极低平均功耗反而没有劣势选高SF不亏。表计抄表场景设备装在表箱里金属外壳对信号衰减很大用470MHz频点加SF9或SF10编码率提到4/6CRC必须开。对时延敏感但距离短的场景比如仓储分拣线SF7加250kHz带宽一包几十字节的数据几百毫秒内发完抗干扰能力够用就行。低速率优化也要注意当SF11或SF12配合125kHz带宽时需要在寄存器0x26的bit3开启LowDataRateOptimize否则长符号时间下PLL漂移会导致灵敏度下降这个细节很多移植代码漏了表现为盲区特别大。4.3 发射功率与电流为什么20dBm的模块比想象中费电SX1278的发射功率通过0x09寄存器配置。bit7置1选择PA_BOOST引脚此时输出功率能做到20dBm对应电流约120mAbit7清走普通RFO路径输出功率被限制在17dBm以下很多电流和距离都会明显下降。实验刚开始调通时建议先用0x8F即PA_BOOST路径加中等功率等天线和匹配都确认了再改成0xFF跑满20dBm。原因很简单长时间满功率发射时小模块和杜邦线的压降会造成供电波动MCU可能莫名其妙复位先排除发射功率这个变量。模块供电不要直接从F103的3.3V LDO拉SX1278发射瞬间的电流尖峰容易把MCU电源拉塌模块电源引脚旁就近放一个100uF电解电容再并0.1uF瓷片电容。5. LORA收发实验怎么验证SPI回环、RSSI/SNR上报与3个高频坑跑到最后一步重点不是再写一段新功能而是把验证手段补齐。这一章给一个可以直接抄的验证链路。5.1 第一步验证不是拉距离而是确认寄存器回读上电后第一件事读0x42版本寄存器SX1278应返回0x12返回其他值就是SPI链路坏了。先用示波器或逻辑分析仪看NSS拉低后SCK上有没有时钟再看MISO上有没有数据如果SCK波形正常但读回全0xFF多半是MOSI和MISO接反。没有逻辑分析仪时先把SPI1的MOSI和MISO用杜邦线短接自发自收一个字节能读回原值说明MCU这边SPI正常问题在模块侧供电或焊点。5.2 高频坑对照表现象第一优先检查第二优先检查TX_DONE永远等不到0x22长度寄存器是否写了实际字节数PA供电跌落导致芯片复位发送成功但对端收不到两端的频率、SF、BW是否完全一致天线是否焊好是否用了433MHz匹配天线收到一包后不再接收RX_DONE标志有没有清干净接收中断里处理时间过长距离比预期近很多0x09的bit7是不是1天线高度和周边障碍物丢包率时高时低电源纹波发射时是否复位低速率优化位是否该开没开排错顺序固定为寄存器版本、频率字、调制参数、天线、电源从内向外排查。不要一上来就怀疑模块硬件LORA模块坏的概率远低于配置错。5.3 把RSSI和SNR打印出来看链路是否真的健康接收完一包后读取0x1A和0x19两个寄存器能得到这包的接收信号强度和信噪比int rssi -137 LORA_ReadReg(0x1A); // 接收信号强度 int snr (int8_t)LORA_ReadReg(0x19) / 4; // 信噪比0.25dB为单位 printf(RSSI%d dBm, SNR%d dB\r\n, rssi, snr);0x19是有符号数必须转成int8_t再除以4直接按无符号数读会出现负信噪比变成大正数的怪事。RSSI在-100dBm以上说明接收余量充足SNR大于0说明信号高于噪声底SNR接近0或为负时通信就不稳定了。用两块板子A板每秒发一包带递增序号的包B板统计连续收到的序号和RSSI/SNR连续跑几百包链路质量就有了量化结论。本文还有配套的精品资源点击获取