news 2026/10/28 7:29:25

基于STM32F103 SPI驱动SX1278 LoRa模块软件设计源代码详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于STM32F103 SPI驱动SX1278 LoRa模块软件设计源代码详解

简介:这份资源面向嵌入式开发初学者与物联网无线通信方向的工程师,提供基于STM32F103单片机通过SPI接口驱动SX1278 LoRa模块的完整软件设计源代码,帮助读者快速搭建远距离、低功耗的无线数据传输方案,适用于智能监控、远程数据采集等场景。压缩包共442个文件,约14.89MB,以c与h源码文件为主,配合o、d、crf等编译中间文件及uvprojx、uvoptx工程配置、hex烧录文件、map映射文件等,覆盖从工程配置到固件输出的完整链路。目前已有106人学习下载。代码围绕SPI初始化、SX1278频率与扩频因子等参数配置、数据收发、中断响应及错误恢复展开,并包含电源管理、通信协议封装与状态机等进阶模块,可作为可复用的软件框架,缩短研发周期并降低调试难度。

1. 从一块 SX1278 说起:为什么 STM32F103 的 SPI 驱动值得你亲手写一遍

手里捏着一块 SX1278 模块,STM32F103 最小系统板已经跑起来了,但把两者用杜邦线连上之后,寄存器读出来全是 0x00 或者 0xFF——这个场景做 LoRa 通信的人多半都遇到过。标题里的“基于 STM32F103 单片机 SPI 驱动 SX1278 LoRa 无线模块软件设计源代码”,说的就是把这套硬件链路从寄存器层面打通:STM32F103 通过 SPI 总线访问 SX1278 的射频寄存器,配置频点、扩频因子、带宽、编码率,最终实现收发数据包。它解决的是“模块能上电但通不了信”的问题,适合已经会点 GPIO 和串口、想往无线通信方向走一步的嵌入式开发者。源代码的价值不在于复制粘贴,而在于你能看懂每一行 SPI 读写背后对应的时序和寄存器操作,出了问题知道从哪查。

2. SX1278 与 STM32F103 的 SPI 链路:先搞懂时序再动手接线

2.1 SX1278 的 SPI 接口有什么特殊之处

SX1278 的 SPI 接口和常见的 W25Q64 Flash 不太一样。它支持标准 SPI 模式 0(CPOL=0,CPHA=0),但寄存器访问分两步:先发一个地址字节,最高位表示读或写,接着才是数据字节。地址字节的 bit7 为 0 表示写,为 1 表示读,bit6 到 bit0 是寄存器地址。这意味着你不能像操作普通 SPI 从设备那样直接连续读写,每次操作都要重新发起片选。

时序上,SX1278 要求 NSS 在传输期间保持低电平,SCK 空闲为低,数据在 SCK 上升沿采样。STM32F103 的硬件 SPI1 挂在 APB2 总线上,最高时钟 72MHz,经过分频后 SPI 时钟建议先设到 4MHz 左右,等调通后再往上提。SX1278 数据手册标称 SPI 时钟最高 10MHz,但实际布线不好时 8MHz 以上就容易出玄学问题,血泪经验是先用低速跑通再逐步加。

还有一个容易翻车的点:SX1278 的 MISO 在片选无效时是高阻态,如果 STM32 的 MISO 引脚没有外部上拉,读出来的数据可能随机跳变。很多模块内部已经处理了,但自己画板子时最好加一个 10k 上拉到 3.3V。

2.2 硬件连接与最小系统检查清单

接线之前先确认 STM32F103 最小系统的供电。SX1278 模块工作电压典型 3.3V,峰值发射电流能到 120mA,而 STM32F103 最小系统板上的 AMS1117-3.3 如果输入是 5V,输出 3.3V 给 MCU 和模块共用,发射瞬间可能把电压拉低导致复位。稳妥做法是模块的 3.3V 单独从一路 LDO 取电,或者至少在模块 VCC 和 GND 之间并一个 100uF 电解电容加 0.1uF 陶瓷电容。

SPI 连线对应关系如下:

STM32F103 引脚SPI 功能SX1278 模块引脚备注
PA5SPI1_SCKSCK时钟
PA6SPI1_MISOMISO主入从出
PA7SPI1_MOSIMOSI主出从入
PA4GPIO 输出NSS硬件片选或软件片选
PA3GPIO 输入DIO0中断输出,收发完成指示
PA2GPIO 输出RESET复位,低有效

NSS 用硬件 SPI 的 NSS 引脚还是普通 GPIO 软件控制,这是 SPI 硬件片选与软件片选的老话题。SX1278 的片选逻辑要求每次寄存器操作独立拉低再拉高,用 STM32 硬件 NSS 反而不好控制时序,我一般直接用普通 GPIO 软件控制,代码里手动拉低拉高,逻辑清晰。

2.3 用 STM32CubeMX 生成 SPI 初始化代码

如果你用 HAL 库开发,STM32CubeMX 可以省掉很多寄存器配置的力气。打开 CubeMX,选好 STM32F103C8T6,配置 SPI1 为 Full-Duplex Master,参数如下:

  • Clock Polarity: Low
  • Clock Phase: 1 Edge
  • Data Size: 8 Bits
  • First Bit: MSB First
  • Prescaler: 16(72MHz/16=4.5MHz)
  • NSS: Software

生成代码后,SPI 初始化函数会自动生成。但要注意,HAL 库的HAL_SPI_TransmitReceive在每次调用时会自动控制 NSS 吗?不会,软件 NSS 模式下需要你自己在调用前后拉低拉高片选。下面是一个封装好的 SX1278 寄存器读写函数:

// sx1278_port.c #include "main.h" #include "sx1278_port.h" extern SPI_HandleTypeDef hspi1; // 片选控制宏,NSS 接 PA4 #define SX1278_NSS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define SX1278_NSS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET) // 写寄存器:地址 bit7=0 uint8_t SX1278_WriteReg(uint8_t addr, uint8_t data) { uint8_t txBuf[2]; uint8_t rxBuf[2]; txBuf[0] = addr & 0x7F; // 写操作,bit7 清零 txBuf[1] = data; SX1278_NSS_LOW(); HAL_SPI_TransmitReceive(&hspi1, txBuf, rxBuf, 2, 100); SX1278_NSS_HIGH(); return rxBuf[1]; } // 读寄存器:地址 bit7=1 uint8_t SX1278_ReadReg(uint8_t addr) { uint8_t txBuf[2]; uint8_t rxBuf[2]; txBuf[0] = addr | 0x80; // 读操作,bit7 置一 txBuf[1] = 0x00; // dummy byte SX1278_NSS_LOW(); HAL_SPI_TransmitReceive(&hspi1, txBuf, rxBuf, 2, 100); SX1278_NSS_HIGH(); return rxBuf[1]; }

逻辑说明:SX1278 的 SPI 帧格式是“地址字节 + 数据字节”,地址字节的 bit7 区分读写。HAL_SPI_TransmitReceive同时发送和接收,因为 SPI 是全双工,读操作时第二个字节发什么无所谓,接收到的第二个字节就是从机返回的数据。超时参数 100ms 是保险值,正常几微秒就完成。

参数说明:addr & 0x7F确保 bit7 为 0 表示写;addr | 0x80确保 bit7 为 1 表示读。片选拉低和拉高之间必须完整包含两个字节的传输,中间不能插入其他操作。

2.4 验证 SPI 链路是否打通

写一个最简单的测试:读 SX1278 的版本寄存器RegVersion,地址 0x42,正常应该返回 0x12。如果读出来是 0x00 或 0xFF,说明 SPI 通信没建立。

void SX1278_CheckVersion(void) { uint8_t ver = SX1278_ReadReg(0x42); printf("SX1278 Version: 0x%02X\r\n", ver); if (ver == 0x12) { printf("SPI link OK\r\n"); } else { printf("SPI link FAIL\r\n"); } }

这个步骤是分水岭:版本号读对了,后面寄存器配置才有意义;读不对,先查接线、供电、片选逻辑,别急着调射频参数。

3. 寄存器配置与 LoRa 模式初始化:从 Sleep 到 RX 的完整流程

3.1 SX1278 的工作模式切换逻辑

SX1278 上电后处于 Sleep 模式,要进入 LoRa 通信需要按顺序切到 Standby,再配置射频参数,最后切到 TX 或 RX。模式由RegOpMode(地址 0x01)控制,低三位决定模式:000 是 Sleep,001 是 Standby,011 是 TX,101 是 RX 连续接收。

关键点:从 Sleep 切到其他模式时,芯片内部需要时间稳定,数据手册建议在模式切换后加 1ms 以上延时。另外,LoRa 模式和 FSK 模式通过RegOpMode的 bit7 选择,置 1 为 LoRa。很多人初始化失败就是因为忘了设这一位,芯片还在 FSK 模式,自然收不到 LoRa 包。

初始化顺序我一般这样走:复位硬件 → 读版本确认 SPI → 切 Standby → 设 LoRa 模式 → 配频点 → 配扩频因子和带宽 → 配编码率 → 设前导码长度 → 设同步字 → 切 RX 或 TX。

3.2 频点、扩频因子、带宽的参数计算

频点由三个寄存器决定:RegFrfMsb、RegFrfMid、RegFrfLsb,地址 0x06 到 0x08。计算公式是Frf = (Fstep * FrfValue),其中 Fstep = 32MHz / 2^19 ≈ 61.035Hz。以 433MHz 为例,FrfValue = 433000000 / 61.035 ≈ 7094272,转成十六进制是 0x6C4000,所以三个寄存器分别写 0x6C、0x40、0x00。

扩频因子 SF 由RegModemConfig2的高四位控制,取值 6 到 12。SF 越大,通信距离越远但速率越低。带宽 BW 由RegModemConfig1的低四位控制,常见 125kHz 对应 0x07。编码率 CR 由RegModemConfig1的 bit3 到 bit1 控制,4/5 对应 0x01。

下面是一段初始化代码:

// sx1278_init.c void SX1278_Init(void) { // 硬件复位 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET); HAL_Delay(10); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); HAL_Delay(10); // 确认 SPI 链路 uint8_t ver = SX1278_ReadReg(0x42); if (ver != 0x12) { printf("SX1278 not found!\r\n"); return; } // 切到 Sleep 再切 Standby,确保状态干净 SX1278_WriteReg(0x01, 0x00); // Sleep HAL_Delay(10); SX1278_WriteReg(0x01, 0x01); // Standby HAL_Delay(10); // 设 LoRa 模式 SX1278_WriteReg(0x01, 0x81); // LoRa + Standby HAL_Delay(10); // 设频点 433MHz SX1278_WriteReg(0x06, 0x6C); SX1278_WriteReg(0x07, 0x40); SX1278_WriteReg(0x08, 0x00); // 设带宽 125kHz,编码率 4/5,显式报头 SX1278_WriteReg(0x1D, 0x72); // 设扩频因子 SF7,CRC 开启 SX1278_WriteReg(0x1E, 0x74); // 设前导码长度 8 SX1278_WriteReg(0x20, 0x08); // 设同步字 0x12(私有网络) SX1278_WriteReg(0x39, 0x12); // 设 FIFO 基地址 SX1278_WriteReg(0x0E, 0x00); SX1278_WriteReg(0x0F, 0x00); printf("SX1278 init done\r\n"); }

逻辑说明:先硬件复位确保芯片状态确定,再读版本号验证 SPI。切模式时先 Sleep 再 Standby 是为了清除之前的残留配置。RegModemConfig1写 0x72 表示带宽 125kHz、编码率 4/5、显式报头模式。RegModemConfig2写 0x74 表示 SF7、CRC 开启。同步字设为 0x12 是私有网络常用值,收发双方必须一致。

参数说明:RegOpMode的 0x81 表示 LoRa 模式加 Standby。频点寄存器值根据目标频率计算,433MHz 用 0x6C4000,470MHz 用 0x758000。SF 和 BW 的取舍是距离和速率的平衡,SF12 加 BW125 灵敏度最高但空中速率只有 293bps,SF7 加 BW125 速率约 5.5kbps。

3.3 发送与接收的 FIFO 操作

发送数据时,先切 Standby,把数据写入 FIFO 寄存器RegFifo(地址 0x00),然后设RegPayloadLength(地址 0x22)为数据长度,最后切 TX 模式。DIO0 引脚会在发送完成后拉高,代码里可以轮询RegIrqFlags(地址 0x12)的 TxDone 位,也可以配中断。

接收时,切 RX 连续模式,等 DIO0 拉高表示收到数据,读RegIrqFlags确认 RxDone,然后从RegFifo读数据,读RegRxNbBytes(地址 0x13)获取长度。

void SX1278_SendPacket(uint8_t *buf, uint8_t len) { // 切 Standby SX1278_WriteReg(0x01, 0x81); HAL_Delay(1); // 写 FIFO 指针到 0 SX1278_WriteReg(0x0D, 0x00); // 写数据到 FIFO for (uint8_t i = 0; i < len; i++) { SX1278_WriteReg(0x00, buf[i]); } // 设长度 SX1278_WriteReg(0x22, len); // 清中断标志 SX1278_WriteReg(0x12, 0xFF); // 切 TX SX1278_WriteReg(0x01, 0x83); // 等 TxDone while ((SX1278_ReadReg(0x12) & 0x08) == 0); SX1278_WriteReg(0x12, 0xFF); // 清标志 }

逻辑说明:发送前必须切 Standby,因为 TX 模式下不能写配置寄存器。FIFO 指针寄存器RegFifoAddrPtr设为 0 表示从 FIFO 头部开始写。每次写RegFifo后指针自动递增。切 TX 后轮询RegIrqFlags的 bit3(TxDone),完成后清标志。

参数说明:RegIrqFlags的 bit3 是 TxDone,bit6 是 RxDone,bit5 是 CRC 错误。清标志时写 1 清除对应位,写 0xFF 清除所有。轮询方式简单但阻塞 CPU,实际项目建议用 DIO0 中断。

4. 调试 SX1278 时最容易翻车的五个地方

4.1 读版本号返回 0x00 或 0xFF

现象:SPI 初始化完成,但读RegVersion始终是 0x00 或 0xFF。

原因:0x00 通常是 MISO 一直低,0xFF 是 MISO 一直高。可能是片选没拉低、SPI 模式不对、接线松动,或者模块供电不足。

解决:先用示波器或逻辑分析仪看 SCK 和 MOSI 有没有波形。没有波形查 SPI 初始化;有波形但 MISO 不变,查片选和模块供电。万用表量模块 VCC 对 GND 电压,发射瞬间如果掉到 3.0V 以下就是供电问题。

4.2 能读版本号但发不出数据

现象:RegVersion返回 0x12,但切 TX 后 DIO0 不拉高,或者接收端收不到。

原因:频点、扩频因子、同步字收发双方不一致,或者天线没接。

解决:先把收发双方所有射频参数打印出来逐项对比。同步字最容易漏,RegSyncWord地址 0x39,双方必须相同。天线必须接,不接天线发射可能损坏功放,而且辐射效率极低。

4.3 通信距离远低于预期

现象:空旷环境只能通几十米,远达不到模块标称的公里级。

原因:扩频因子设太小、带宽设太大、天线匹配不好,或者发射功率没设对。

解决:RegPaConfig(地址 0x09)控制发射功率,PA_BOOST 模式下最大 +20dBm,对应值 0x80 | 0x0F 再加RegPaDac设 0x87。先用 SF12、BW125 试,这是最远距离配置。天线用 433MHz 专用弹簧天线或 SMA 天线,别用杜邦线当天线。

4.4 接收数据出现 CRC 错误

现象:能收到包但RegIrqFlags的 CRC 错误位置位,数据不可信。

原因:空中速率太高、干扰大、或者收发双方编码率不一致。

解决:降低 SPI 时钟到 1MHz 试试,排除 SPI 读写时序问题。然后检查RegModemConfig1的编码率位,双方必须一致。如果环境干扰大,把带宽从 250kHz 降到 125kHz,牺牲速率换抗干扰。

4.5 模块发热严重

现象:上电一段时间后 SX1278 芯片烫手。

原因:长期处于 TX 模式,或者发射功率设到最大但天线不匹配,功率反射回芯片。

解决:检查代码是否在发送完成后切回了 Standby 或 Sleep。如果不是持续发射场景,发完就切回 Standby。天线驻波比要合格,自己绕的弹簧天线长度按 433MHz 四分之一波长约 17.3cm 裁剪。

5. 把驱动封装成可复用的源代码结构

5.1 分层设计:硬件抽象与协议逻辑分离

一套能复用的 SX1278 驱动代码,不应该把 SPI 读写和 LoRa 协议逻辑混在一起。我一般分三层:最底层是sx1278_port.c/h,只负责 SPI 读写寄存器和 GPIO 控制;中间层是sx1278.c/h,实现初始化、发送、接收、模式切换;最上层是应用层,处理具体的数据包格式和业务逻辑。

这样分层的好处是换 MCU 平台时只需要改sx1278_port.c,协议逻辑不动。比如从 STM32F103 换到 GD32F303,SPI 外设寄存器不同但 HAL 库接口相似,改 port 层就行。

5.2 用状态机管理收发流程

轮询方式在简单测试里够用,但实际项目里阻塞等待会浪费 CPU。更好的做法是用 DIO0 中断加状态机:

typedef enum { SX1278_STATE_IDLE, SX1278_STATE_TX, SX1278_STATE_RX, SX1278_STATE_ERROR } sx1278_state_t; volatile sx1278_state_t g_state = SX1278_STATE_IDLE; // DIO0 中断回调 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == GPIO_PIN_3) { // DIO0 接 PA3 uint8_t flags = SX1278_ReadReg(0x12); if (flags & 0x08) { // TxDone g_state = SX1278_STATE_IDLE; } else if (flags & 0x40) { // RxDone g_state = SX1278_STATE_RX; } SX1278_WriteReg(0x12, 0xFF); } }

逻辑说明:DIO0 在 TxDone 或 RxDone 时拉高,中断回调里读RegIrqFlags判断事件类型,更新状态机。主循环根据状态决定下一步操作,不再阻塞等待。

参数说明:DIO0 映射到哪个事件由RegDioMapping1(地址 0x40)控制,默认 00 表示 RxDone,01 表示 TxDone。如果同时用收发,可以在发送前改映射,或者用 DIO0 和 DIO1 分别映射。

5.3 源代码文件组织与移植要点

一个干净的 SX1278 驱动包通常包含这些文件:

文件名作用
sx1278_port.h定义 SPI 句柄、片选和复位引脚宏
sx1278_port.c实现寄存器读写、复位、延时
sx1278.h定义寄存器地址、模式枚举、API 声明
sx1278.c实现初始化、发送、接收、睡眠
sx1278_app.c应用层收发逻辑示例

移植到新平台时,只需要改sx1278_port.h里的引脚定义和sx1278_port.c里的 SPI 收发函数。如果新平台没有 HAL 库,把HAL_SPI_TransmitReceive换成对应的底层 SPI 收发即可,注意保持全双工模式,因为 SX1278 读操作需要同时发 dummy 字节。

最后一句话习惯:我调 SX1278 的时候,先把版本号读通,再把频点和同步字对齐,最后才动扩频因子和带宽,这个顺序能省掉很多来回折腾。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 7:28:41

Matlab LBM多孔介质流动仿真:D2Q9模型与边界条件实现

简介&#xff1a;这份资源提供了一套基于 Matlab 的 LBM 流体仿真代码&#xff0c;用于模拟流体流经多孔介质的流动过程&#xff0c;面向计算机、电子信息工程、数学等专业的大学生及科研人员&#xff0c;可服务于课程设计、期末大作业与毕业设计等场景。资源包共 11 个文件&am…

作者头像 李华
网站建设 2026/10/28 7:27:36

OpenCV+QT人脸识别考勤系统C++实战指南

简介&#xff1a;这是一套面向计算机专业本科生及初学者的C毕业设计级项目资源&#xff0c;基于OpenCV与Qt双框架实现端到端的人脸识别考勤系统&#xff0c;解决课程设计、毕业设计及实训项目中图像处理GUI集成实际业务逻辑落地的典型需求。压缩包共134个文件&#xff0c;涵盖4…

作者头像 李华
网站建设 2026/10/28 7:27:06

AI搜索流量红利,江西中小商家如何以小博大?

“江西中小商家现在抢AI搜索流量&#xff0c;真的太晚了吗&#xff1f;”这个问题我最近被问了不下十次。我的回答一直很直接&#xff1a;晚的不是入场时间&#xff0c;而是大多数人还没看懂AI搜索到底怎么分配流量。本地生活服务、电商、工厂转型&#xff0c;这三类商家在AI搜…

作者头像 李华
网站建设 2026/10/28 7:27:05

AI进前端课堂三天实录:从教代码到教决策

上周我在自己的Web前端课上做了一个实验&#xff1a;连续三天&#xff0c;把学生练习和作业环节里所有“代码不会写”的问题&#xff0c;全部交给AI来解答&#xff0c;我退到一边只做规则制定、结果验收和问题点评。三天后课堂面貌的变化速度远超我的预期——学生开始给AI“下需…

作者头像 李华
网站建设 2026/10/28 7:26:53

AI搜索时代如何应对“引用缺失”:从抢排名到抢引用的获客新打法

最近几个月&#xff0c;身边做AI赛道获客的朋友普遍都在抱怨同一个现象&#xff1a;广告预算没少花&#xff0c;独立站内容更新得也特别勤&#xff0c;但用户就是“搜不到了”。注意&#xff0c;不是被降权&#xff0c;也不是被屏蔽&#xff0c;而是用户根本不怎么打开搜索引擎…

作者头像 李华
网站建设 2026/10/28 7:26:52

GUI-MCP架构:模块化设计在GUI自动化测试与RPA中的应用

1. GUI-MCP架构概览&#xff1a;当GUI智能体遇上模块化设计在自动化测试和RPA&#xff08;机器人流程自动化&#xff09;领域&#xff0c;GUI智能体&#xff08;GUI-Agent&#xff09;正经历着从脚本录制回放向认知智能的跃迁。阶跃星辰团队开源的GUI-MCP架构&#xff0c;为这一…

作者头像 李华