☰
STM32F103+SX1278 LoRa点对点通信最小可行工程
2026/10/27 3:56:02 网站建设 项目流程

简介:本资源是一个基于STM32F103C8T6微控制器与LoRa模块的嵌入式无线数据传输完整项目,面向嵌入式开发初学者及物联网实践者,解决传感器数据采集、本地处理与远距离(理论可达3km)低功耗无线上传的核心问题,适用于环境监测、智能农业等典型IoT场景。压缩包共189个文件,主体为34个.h头文件与32个.c源码文件,涵盖STM32标准外设库(如stm32f10x_gpio.__i、stm32f10x_usart.__i等预编译依赖)、LoRa驱动、ADC采样、串口通信及系统初始化等关键模块;另有.o目标文件、.axf/.hex可执行镜像、.uvprojx工程配置及少量说明文档,整体1.96MB,结构完整,开箱即编译。目前已有4197人学习下载,读者可直接获取可运行的Keil工程框架、清晰的模块化代码组织、LoRa与STM32的SPI接口实现细节,以及从硬件抽象层到应用逻辑的完整链路参考,是掌握LoRa嵌入式集成开发的高实用性入门范例。

1. LORA完成.rar:一个基于STM32F103C8T6的LoRa点对点通信最小可行工程包,能直接烧录、改频段、发数据,适合嵌入式新手快速验证物理层连通性

你手上有块蓝 pill(STM32F103C8T6),买了SX1278模块,接好线却卡在“串口有输出但对方收不到”——不是代码没跑,是时钟没配准、寄存器没写全、SPI时序压根没对上。这个名为LORA完成.rar的压缩包,就是某开发者在调试 SX1278 + STM32F103C8T6 点对点通信时,把从 HAL 库初始化、LoRa 模式配置、载波频率校准、显式报头开关、CRC 校验使能、到实际发送/接收中断回调的全部可运行逻辑,打包成一个「删掉 main.c 就不能编译,烧进去就能 ping 通」的完整工程。它不讲 LoRaWAN 协议栈,不带 OTA 或网关功能,只做一件事:让两块板子在 433MHz 或 868MHz 频段下,用固定扩频因子 SF7、带宽 125kHz、编码率 4/5,稳定互传 16 字节 payload。如果你正被HAL_SPI_TransmitReceive()返回 HAL_BUSY 卡死、或RegIrqFlags读出来全是 0x00 不触发 RX_DONE 中断,这个包里的寄存器配置顺序和延时位置,就是你缺的那张「硬件行为说明书」。

2. 工程结构与核心模块拆解:从 .rar 解压到 Keil 工程可编译的六步落地路径

2.1 解压后目录树与关键文件定位

解压LORA完成.rar后,你会看到如下结构(已去平台化命名,无作者/时间戳干扰):

LORA完成/ ├── Core/ │ ├── Inc/ │ │ ├── main.h │ │ ├── stm32f1xx_hal_conf.h ← 关键:启用了 SPI1 和 EXTI0 │ │ └── sx1278.h ← 封装了所有寄存器宏定义与基础函数声明 │ └── Src/ │ ├── main.c ← 主循环:按键触发发送,中断触发接收打印 │ ├── sx1278.c ← 核心驱动:初始化、写寄存器、读状态、发送/接收函数 │ └── sx1278_reg.h ← 头文件:RegOpMode, RegFifoTxBaseAddr 等 0x01~0x70 寄存器地址定义 ├── Drivers/ │ └── STM32F1xx_HAL_Driver/ ← 标准 HAL 库(v1.8.4),含 spi.c / exti.c / delay.c ├── MDK-ARM/ │ └── LORA.uvprojx ← Keil v5 工程文件,Target 为 STM32F103C8Tx,Flash 64KB └── README.txt ← 仅一行:“433Mhz, SF7, BW125kHz, CR4/5, Explicit Header”

提示:该工程未使用 CMSIS-RTOS 或 FatFS,所有延时靠HAL_Delay()实现,无阻塞式等待;SPI 时钟分频设为SPI_BAUDRATEPRESCALER_4(即 PCLK2/4 = 36MHz/4 = 9MHz),这是 SX1278 数据手册明确要求的最高安全速率。

2.2 sx1278.c 中最关键的四段寄存器配置逻辑

SX1278 是个状态机黑匣子,必须按严格顺序写寄存器,跳过任意一步都会导致RegOpMode读回 0x00 或RegIrqFlags始终为 0。以下是SX1278_Init()函数中不可删减的四段(已提取为独立代码块,可直接复用):

// 1. 软复位并等待就绪:必须先写 0x80 到 RegOpMode,再轮询 RegOpMode 直到 bit7=0 SX1278_WriteRegister(0x01, 0x80); // 进入 Sleep 模式并复位内部状态机 HAL_Delay(1); while ((SX1278_ReadRegister(0x01) & 0x80) == 0x80) { // 等待 bit7 清零 HAL_Delay(1); } // 2. 配置频率:433MHz → 0x6C, 0x80, 0x00(公式:FRF = (Freq * 2^19) / Fxtal) SX1278_WriteRegister(0x06, 0x6C); // RegFrMsb SX1278_WriteRegister(0x07, 0x80); // RegFrMid SX1278_WriteRegister(0x08, 0x00); // RegFrLsb // 3. 设置 LoRa 模式参数:SF7, BW125kHz, CR4/5, Explicit Header SX1278_WriteRegister(0x1D, 0x72); // RegModemConfig1: BW=125kHz, CR=4/5, Header=Explicit SX1278_WriteRegister(0x1E, 0x74); // RegModemConfig2: SF=7, TxCont=0, CRC=ON, SymTimeout=0 // 4. 配置 FIFO 与中断映射:TX_DONE 和 RX_DONE 必须映射到 DIO0 引脚 SX1278_WriteRegister(0x40, 0x00); // RegDioMapping1: DIO0=TX_DONE, DIO1=RX_DONE SX1278_WriteRegister(0x0E, 0x00); // RegFifoTxBaseAddr = 0x00(FIFO 写起始地址) SX1278_WriteRegister(0x0F, 0x00); // RegFifoRxBaseAddr = 0x00(FIFO 读起始地址)

参数说明:

  • RegModemConfig1(0x1D)的0x72拆解:bit7-6=0b11(BW=125kHz),bit5-3=0b010(CR=4/5),bit2-0=0b010(Header=Explicit);
  • RegModemConfig2(0x1E)的0x74拆解:bit7-4=0b0111(SF=7),bit3=0(TxContinuous=OFF),bit2=1(CRC=ON),bit1-0=0b00(SymTimeout=0);
  • RegDioMapping1(0x40)决定哪个 DIO 引脚触发哪个事件,此处 DIO0 接 STM32 的 PA0(EXTI0),故必须设为0x00(TX_DONE)或0x40(RX_DONE),否则中断永不触发。

2.3 Keil 工程配置三处硬性要求

该工程能在 Keil v5.38 下直接编译通过,但需确认以下三项(新手常在此翻车):

  1. Device 选择:Project → Options for Target → Device → STM32F103C8Tx(非 CBT6 或 CB6,Flash Size 必须为 64KB);
  2. Flash Download 算法:Utilities → Settings → Flash Download → Add… → 选择STM32F10x_64.FLM(若选错为 128KB 版本,烧录后程序不运行);
  3. C/C++ 宏定义:C/C++ → Define → 添加USE_HAL_DRIVER, STM32F103xB(缺STM32F103xB会导致HAL_GPIO_WritePin()编译报错)。

注意:工程未启用__weak重定向HAL_Delay(),所有延时依赖 SysTick,因此SystemCoreClock必须在system_stm32f1xx.c中正确设为 72000000(HSE=8MHz × PLL=9),否则HAL_Delay(1)实际延时偏差超 300%。

3. LoRa 物理层通信链路闭环:从发送端按键触发到接收端串口打印的全流程验证

3.1 发送端逻辑:按键触发一次 16 字节 payload 发送

main.c中的HAL_GPIO_EXTI_Callback()绑定到 KEY_Pin(PC13,板载按键),按下后执行:

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == KEY_Pin) { uint8_t tx_data[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}; SX1278_SetTxMode(); // 写 RegOpMode = 0x8C(LoRa + TX) SX1278_WriteFifo(tx_data, 16); // 将数据写入 FIFO(地址 0x00 开始) SX1278_StartTransmit(); // 写 RegOpMode = 0x8C(保持 TX 模式,自动触发发射) HAL_Delay(500); // 等待发射完成(SF7@125kHz 约需 120ms) SX1278_SetSleepMode(); // 发完切回 Sleep,省电 } }

关键点:SX1278_StartTransmit()并非启动 DMA,而是向RegOpMode写0x8C,由 SX1278 硬件自动从 FIFO 取数发射;HAL_Delay(500)是保守值,实际可通过监听 DIO0 上升沿(TX_DONE)精确同步,但此工程为简化未启用。

3.2 接收端逻辑:中断驱动 FIFO 读取与串口回显

接收端使用EXTI0_IRQHandler响应 DIO0(PA0)上升沿(RX_DONE),进入中断后:

void EXTI0_IRQHandler(void) { HAL_EXTI_IRQHandler(&ExtiHandle); // 清 EXTI 挂起位 if (SX1278_GetIrqFlags() & IRQ_RX_DONE_MASK) { // 读 RegIrqFlags = 0x40 uint8_t rx_data[16]; uint8_t rx_len = SX1278_ReadFifo(rx_data, 16); // 从 FIFO 读出有效字节数 printf("RX OK, Len=%d: ", rx_len); for (int i = 0; i < rx_len; i++) { printf("%02X ", rx_data[i]); } printf("\r\n"); SX1278_SetRxMode(); // 写 RegOpMode = 0x85(LoRa + RX continuous),持续监听 } }

验证方法:用 USB-TTL 连接收端串口(PA9/PA10),波特率 115200,按下发送端按键,串口应立即打印:
RX OK, Len=16: 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10
若打印Len=0,说明 FIFO 为空——大概率是发送端没真正发出,或频率/扩频因子不匹配。

3.3 频段与参数修改实操指南

该工程默认为 433MHz,如需改为 868MHz(欧洲频段),只需改三处:

文件行号原值新值说明
sx1278.c1270x6C, 0x80, 0x000xD9, 0x00, 0x00868MHz 计算:(868e6 * 2^19) / 32e6 = 0xD90000
README.txt1433Mhz868Mhz同步更新文档
main.c42SX1278_SetFrequency(433000000)SX1278_SetFrequency(868000000)若封装了频率设置函数则需同步

提示:修改后必须重新编译烧录,且两块板子频率、SF、BW、CR 必须完全一致,否则表现为「发送端 TX_DONE 触发,接收端 RX_DONE 永不触发」——这是 LoRa 物理层最典型的失步现象。

4. 避坑:SX1278+STM32F103C8T6 通信失败的五个血泪现场与当场修复方案

4.1 现象:串口打印 "RX OK, Len=0",但发送端确认 TX_DONE 触发

原因:接收端未正确配置RegFifoRxBaseAddr(0x0F),导致 SX1278 从 FIFO 地址 0x00 开始读,但实际数据写入地址偏移了;或RegPayloadLength(0x22)未设为 16,SX1278 默认按隐式头模式解析长度字段。
解决:检查SX1278_Init()中是否执行了SX1278_WriteRegister(0x0F, 0x00),并在SX1278_SetRxMode()后追加SX1278_WriteRegister(0x22, 16)(显式头模式下此寄存器无效,但部分固件版本会误读)。

4.2 现象:Keil 编译报错 "undefined reference toHAL_GPIO_WritePin"

原因:stm32f1xx_hal_conf.h中#define HAL_GPIO_MODULE_ENABLED被注释,或HAL_GPIO_MODULE_ENABLED宏未在 C/C++ define 中声明。
解决:打开Core/Inc/stm32f1xx_hal_conf.h,取消第 127 行/* #define HAL_GPIO_MODULE_ENABLED */的注释;同时在 Keil C/C++ → Define 中添加HAL_GPIO_MODULE_ENABLED。

4.3 现象:发送端按键无反应,串口无任何输出

原因:HAL_GPIO_EXTI_Callback()未被正确注册,常见于MX_GPIO_Init()中未调用HAL_GPIO_EnableIRQ()使能 EXTI 中断,或EXTI->IMR寄存器未置位。
解决:检查gpio.c中MX_GPIO_Init()函数末尾是否有HAL_GPIO_EnableIRQ(KEY_EXTI_IRQn);若无,手动添加;同时确认KEY_EXTI_IRQn对应的中断服务函数名与startup_stm32f103xb.s中定义一致(应为EXTI15_10_IRQHandler,因 PC13 映射到 EXTI13,属 15_10 组)。

4.4 现象:接收端串口乱码,或打印内容错位(如 "RX OK, Len=16: 00 00 00...")

原因:printf()使用了半主机模式(_sys_write),而工程未勾选Use MicroLIB,导致 printf 输出到未初始化的 ITM 或 SWO;或huart1初始化时huart1.Init.WordLength错设为UART_WORDLENGTH_9B。
解决:Project → Options for Target → Target → Use MicroLIB(打钩);或改用HAL_UART_Transmit(&huart1, buf, len, 100)替代printf。

4.5 现象:两块板子距离 >2 米就丢包,RSSI 值极低(<-120dBm)

原因:SX1278 的 PA 输出功率未配置,RegPaConfig(0x09)默认为0x00(PA_OFF),或天线未接 50Ω 匹配。
解决:在SX1278_Init()末尾添加:

SX1278_WriteRegister(0x09, 0xC0); // RegPaConfig: PA_BOOST=1, OutputPower=15dBm (0xC0 = 0b11000000) SX1278_WriteRegister(0x0A, 0x0F); // RegPaRamp: Ramp time = 4.5ms (for PA_BOOST)

注意:PA_BOOST 模式需外接 3.3V 电源至 PA_BOOST 引脚,且天线必须为 433/868MHz 专用陶瓷贴片或弹簧天线,普通导线天线衰减超 20dB。

5. 进阶技巧:用逻辑分析仪抓取 SPI 时序,定位 SX1278 寄存器写入失败的根本原因

5.1 为什么必须用逻辑分析仪?

HAL 库的HAL_SPI_TransmitReceive()返回HAL_OK只代表 DMA 传输完成,不代表 SX1278 真正接收到了指令。例如:SPI SCK 电平不稳导致某 bit 采样错误,SX1278 内部状态机卡死,此时SX1278_ReadRegister(0x01)仍返回 0x00,但示波器看 SPI 波形一切正常——只有逻辑分析仪能解码出实际传输的字节流,确认0x01 0x80是否真的发了出去。

5.2 四通道接线与触发设置

将逻辑分析仪四通道按如下方式接入(以常见 Saleae Logic Pro 8 为例):

通道接线点信号类型触发条件
CH0PA5 (SCK)Clock上升沿
CH1PA7 (MOSI)Data无(自动解码)
CH2PA4 (NSS)Enable下降沿(NSS 低电平期间为有效传输)
CH3PA0 (DIO0)Interrupt上升沿(TX_DONE/RX_DONE)

提示:NSS 必须接 CH2,因为 SX1278 要求 NSS 下降沿后至少 5μs 才开始采样 SCK,逻辑分析仪需以此为窗口截取 MOSI 数据。

5.3 解码 SPI 后的关键判据表

在 Logic 软件中启用 SPI 解码(Settings → Protocol Analyzer → SPI),设置 CPOL=0, CPHA=0(SX1278 要求 mode 0),然后观察解码结果:

场景解码显示问题定位
正常写寄存器[0x01] [0x80]寄存器地址 0x01 写入值 0x80,成功
NSS 提前释放[0x01] [0x80] [0xFF]NSS 在第二个字节未发完时拉高,SX1278 放弃接收,需检查HAL_SPI_TransmitReceive()的Size参数是否为 2
SCK 频率超限[0x01] [0x80]但波形 SCK 周期 <111ns(>9MHz)STM32 SPI 分频设置错误,需改SPI_BAUDRATEPRESCALER_8
MOSI 电平异常[0x01] [0x00](第二字节恒为 0)STM32 MOSI 引脚未配置为 Alternate Function Push-Pull,检查GPIO_InitStruct.Mode = GPIO_MODE_AF_PP

5.4 一个真实案例:某开发者发现SX1278_WriteRegister(0x1D, 0x72)总是失败

他用逻辑分析仪抓到波形解码为[0x1D] [0x00],排查发现HAL_SPI_TransmitReceive()的pTxData指针指向了一个局部数组,函数返回后内存被覆盖。修正方式:

// ❌ 错误:局部数组生命周期短于 SPI 传输 void bad_func() { uint8_t tx_buf[2] = {0x1D, 0x72}; HAL_SPI_TransmitReceive(&hspi1, tx_buf, rx_buf, 2, 100); } // ✅ 正确:静态分配或全局变量 static uint8_t tx_buf[2]; void good_func() { tx_buf[0] = 0x1D; tx_buf[1] = 0x72; HAL_SPI_TransmitReceive(&hspi1, tx_buf, rx_buf, 2, 100); }

从那以后我每次写 SPI 驱动,都强制走一遍逻辑分析仪抓波形 —— 不是信不过 HAL 库,是信不过自己对时序边界的直觉。寄存器写不进去,从来不是芯片坏了,而是某个HAL_Delay(1)少写了,或者NSS拉高时机差了 200ns。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询