STM32F103驱动SX1278 LoRa模块:代码整理与移植实战

📅 发布时间:2026/9/9 13:47:38
STM32F103驱动SX1278 LoRa模块:代码整理与移植实战
简介一份面向STM32F103的LORA通信精简代码包整理自正点原子LORA例程作者去掉了屏显等与通信无关的模块只保留LORA收发核心代码并做了重写让通信逻辑更加突出适合正在学习F103平台无线通信或者需要快速把指定LORA模块接入自己项目的开发者。压缩包内文件不多共5个——2个C源文件、2个H头文件和1个txt说明文档整个包仅11KB非常轻量其中C源文件负责数据收发、寄存器配置和底层驱动相关逻辑H头文件给出对外接口与常用参数宏txt文档则对代码使用方式做了简要说明整体结构清晰几乎不用再整理目录按文件名称即可快速定位。已有4164人学习关注度较高对有STM32基础、希望快速完成LORA通信验证的读者来说很实用。拿到这份精简代码后不需要在大工程中筛选无关部分可直接对照说明文档理解通信流程在此基础上做二次修改同时也能借鉴作者在时钟初始化、接口配置、收发状态处理上的代码组织方式缩短嵌入式无线项目的开发调试时间。整体来看是一份小而精、可直接上手的入门参考。 做LoRa无线透传的时候经常有朋友翻出正点原子的LoRa例程点开一看里面文件不少注释也比较“原生态”直接往自己的F103工程里塞又觉得到处是冲突和冗余。我当初整理这份正点原子LoRa代码时就是冲着“拿来能用、能看懂、能改”这三个目标去的。这篇博文就围绕这份整理过的代码把F103与LoRa模块以SX1278为核心的驱动梳理、移植思路、调试经验和踩坑记录都摊开讲清楚。如果你手头有正点原子的LoRa模块或SX1278兼容模块用的芯片是STM32F103系列不管是做环境监测、远程开关、还是小数据量透传这篇内容可以直接照着落地。即使你用的是其他STM32型号只要换一下SPI和GPIO的映射思路完全通用。1. 项目背景与整理思路1.1 LoRa在F103项目里解决什么问题项目里要用LoRa大多数是看中它两点远距离和低功耗。所谓“远”指的是在同样的发射功率下LoRa的灵敏度比FSK、OOK这类传统调制方案高很多SX1278的灵敏度能做到-137dBm左右配合合适的扩频因子和带宽视距环境下几公里很常见。所谓“低功耗”是因为LoRa的接收电流也只有10mA上下对电池供电的采集节点很友好。至于为什么偏偏是F103原因更简单这是最普及的Cortex-M3芯片资料多、库存足、价格便宜。做传感器采集、继电器控制这类产品F103的72MHz主频和丰富外设绰绰有余搭配LoRa模块做一个低成本的远距离链路是很多工程师的第一选择。1.2 原厂代码为什么要“整理”正点原子提供的LoRa例程核心功能是能跑的但代码结构偏教学风格有几个明显问题所有功能堆在几个大文件里没有按驱动层、应用层划分。寄存器操作和业务逻辑混在一起想改成自己的协议栈得先花时间理清哪些是初始化、哪些是收发。错误处理和状态判断分散发送和接收的“握手流程”不够直观仿照它写应用代码容易漏掉关键分支。注释虽然多但更适合“演示”不适合直接裁剪到产品里。整理的目标不是重写驱动而是把SX1278这条主线抽出来形成清晰的三层结构硬件抽象层SPI和GPIO操作、SX1278驱动层寄存器读写、初始化、收发状态机、应用层封装成Send/Receive事件通知。这样无论你是想快速验证通信还是在上面跑自定义协议都有清晰的切入点。2. 核心器件与通信机制解析2.1 SX1278与F103的连接关系先看模块连接。正点原子LoRa模块把SX1278以及必要的匹配电路、天线座都做好了对外引出SPI接口、复位引脚、DIO0部分模块还有DIO1/DIO2和供电引脚。F103这边用硬件SPI1就能驱动引脚分配通常是功能F103引脚说明NSSPA4SPI片选软件控制SCKPA5SPI时钟MOSIPA7主机输出连接模块SIMISOPA6主机输入连接模块SORSTPA3模块复位低电平有效DIO0PA2发完成/收完成中断引脚这里特别提醒一个点正点原子有些例程里片选用的是软件拉低拉高方式没问题但要注意你选的GPIO是否与板子上其他外设冲突尤其如果工程里还挂了SD卡、LCD等外设它们可能都要抢占SPI1的引脚。这种情况下要么改用SPI2要么用GPIO模拟SPI驱动层做好接口分离改起来会容易很多。2.2 寄存器读写与时序关键点SX1278是典型的SPI从设备寄存器地址8位数据8位。读取时把NSS拉低先发送寄存器地址最高位置1表示读然后连续读数据写入时先发送地址最高位为0再写数据。代码里对应两个基础函数// 写寄存器addr为8位寄存器地址value为要写入的数据 static void SX1278_WriteReg(uint8_t addr, uint8_t value) { LORA_NSS_LOW(); SPI1_ReadWriteByte(addr 0x7F); // 写命令最高位为0 SPI1_ReadWriteByte(value); LORA_NSS_HIGH(); } // 读寄存器返回该地址的当前值 static uint8_t SX1278_ReadReg(uint8_t addr) { uint8_t value; LORA_NSS_LOW(); SPI1_ReadWriteByte(addr | 0x80); // 读命令最高位为1 value SPI1_ReadWriteByte(0x00); LORA_NSS_HIGH(); return value; }实际调试中很多人遇到“写进去读出来不对”的情况大多数不是函数问题而是SPI时钟相位极性和速率设置不对。SX1278的SPI模式要求CPOL0、CPHA0也就是空闲时时钟低电平第二个边沿采样。如果你的初始化函数里把SPI配置成Mode 3寄存器读写会时好时坏而且很难定位因为初始化那几步接收状态居多问题往往延迟到通信时才暴露。2.3 LoRa配置参数理解LoRa关键参数其实就那么几个载波频率、扩频因子SF、信号带宽BW、编码率CR、发射功率、前导码长度、同步字。整理代码时我把这些参数全部集中在lora_config.h里方便一处修改而不是散落在初始化函数里。参数常见值对性能影响频率433MHz / 470MHz / 868MHz不同地区合法频段不同买模块时要注意扩频因子SF7~12越大灵敏度越高、传输更远但速率降低信号带宽BW125kHz / 250kHz / 500kHz越大速率越高但灵敏度下降编码率CR4/5 ~ 4/8越高抗干扰越强但有冗余开销发射功率最大20dBm功率越大越耗电不是所有模块都能跑满功率展开讲一下扩频因子这个最容易让人纠结的参数。SF7相比SF12有效数据率能差出好几倍但SF12在有干扰和多径的场景下接收灵敏度更好。拿田间地头的环境来说对速率不敏感但希望少用中继那SF12配合125kHz带宽是很稳的搭配如果是楼宇抄表、需要在几百米内把采集数据按时上报那SF9/SF10相对均衡。我平时测试第一版固件固定用SF10、125kHz、CR4/5、发射功率14dBm先把链路跑通再看余量有需要才调。3. 核心代码模块与移植要点3.1 整理后的代码目录结构整理后的代码不再是正点原子那份“堆砌式”结构而是这样分文件Project/ ├── lora/ │ ├── sx1278.c │ ├── sx1278.h │ └── lora_config.h ├── app/ │ ├── lora_app.c │ └── lora_app.h └── bsp/ ├── spi1.c └── gpio.csx1278.c负责纯驱动寄存器读写、芯片初始化、进入待机/发射/接收模式、读取IRQ标志。lora_app.c则负责具体应用决策比如启动一次发送、收到一帧数据后怎么处理、超时重发策略。bsp目录里是硬件初始化代码。这样拆完以后如果你用的是标准外设库那sx1278.c根本不用动换芯片平台只需要改bsp和配置头文件。3.2 初始化流程的整理逻辑SX1278初始化流程我建议按下面顺序走复位RST引脚拉低10ms再拉高。读取RegVersion寄存器确认SPI通信正常芯片型号正确。设置RF参数频率、扩频因子、带宽、编码率等。设置收发模式需要的寄存器前导码、同步字、CRC开关。配置DIO0映射到发送完成和接收完成中断。进入待机模式。这里面第一步和第二步很多人会省略但恰恰是它们能帮你区分“硬件没焊好”和“软件配置不对”。整理代码时我把版本号读出来后在串口打印一行类似“SX1278 detected, version 0x12”上电就能看到模块是否正常响应。别小看这一句日志前期联调省的时间比写整段驱动还多。初始化代码核心片段大致是uint8_t SX1278_Init(void) { // 复位模块 LORA_RST_LOW(); HAL_Delay(20); LORA_RST_HIGH(); HAL_Delay(20); // 检查SPI读写是否正常 uint8_t version SX1278_ReadReg(SX1278_REG_VERSION); if (version ! 0x12) { return 1; // 返回非0表示初始化失败 } // 配置LoRa模式 SX1278_WriteReg(SX1278_REG_OP_MODE, SX1278_MODE_SLEEP); SX1278_WriteReg(SX1278_REG_OP_MODE, SX1278_MODE_STANDBY); // 设置载波频率以433MHz为例 SX1278_SetFrequency(433000000UL); // 设置调制参数 SX1278_WriteReg(SX1278_REG_MODEM_CONFIG_1, (SX1278_BW_125KHZ 4) | (SX1278_CR_4_5 1)); SX1278_WriteReg(SX1278_REG_MODEM_CONFIG_2, (SX1278_SF_10 4) | (SX1278_CRC_ON 2)); // 设置同步字默认0x12国内模块一般不改 SX1278_WriteReg(SX1278_REG_SYNC_WORD, 0x12); // 设置发射功率14dBm比较稳妥 SX1278_SetTxPower(14); // 配置DIO0为发送完成/接收完成中断 SX1278_WriteReg(SX1278_REG_DIO_MAPPING_1, 0x00); return 0; }3.3 发送与接收状态机LoRa发送的过程比大家想的要简单因为SX1278内部会自行完成数据加前导码、CRC计算、调制发送的流程单片机只需要把数据填进FIFO然后触发发送再等DIO0中断通知即可。发送路径整理完基本是这个样子uint8_t SX1278_SendPacket(uint8_t *buf, uint8_t len) { // 等待上一次发送或接收动作彻底结束 SX1278_WriteReg(SX1278_REG_OP_MODE, SX1278_MODE_STANDBY); // 写FIFO指针 SX1278_WriteReg(SX1278_REG_FIFO_TX_BASE_ADDR, 0x00); SX1278_WriteReg(SX1278_REG_FIFO_ADDR_PTR, 0x00); // 写入一帧数据 SX1278_WriteReg(SX1278_REG_PAYLOAD_LENGTH, len); for (uint8_t i 0; i len; i) { SX1278_WriteReg(SX1278_REG_FIFO, buf[i]); } // 切换为发送模式写入该寄存器自动触发发送 SX1278_WriteReg(SX1278_REG_OP_MODE, SX1278_MODE_TX); return 0; }发送完成标志和接收完成标志都会映射到DIO0引脚上在中断服务函数里读取IRQ标志位来区分是收发哪种事件void EXTI_IRQHandler(void) { if (LORA_DIO0_READ()) { uint8_t irq SX1278_ReadReg(SX1278_REG_IRQ_FLAGS); if (irq SX1278_IRQ_TX_DONE) { SX1278_ClearIRQ(SX1278_IRQ_TX_DONE); // 通知应用层发送完成 lora_event | LORA_EVENT_TX_DONE; } else if (irq SX1278_IRQ_RX_DONE) { SX1278_ClearIRQ(SX1278_IRQ_RX_DONE); SX1278_ReadPacket(rx_buf, rx_len); // 通知应用层收到数据 lora_event | LORA_EVENT_RX_DONE; } } }接收侧的FIFO读取同样要先把读指针定位到当前接收起始地址void SX1278_ReadPacket(uint8_t *buf, uint8_t *len) { uint8_t current SX1278_ReadReg(SX1278_REG_FIFO_RX_CURRENT_ADDR); SX1278_WriteReg(SX1278_REG_FIFO_ADDR_PTR, current); *len SX1278_ReadReg(SX1278_REG_RX_NB_BYTES); for (uint8_t i 0; i *len; i) { buf[i] SX1278_ReadReg(SX1278_REG_FIFO); } }这里有一个细节接收完成和发送完成的判断不能只靠DIO0引脚的高低电平因为不同模式映射不同。建议在中断里读RegIRQFlags用IRQ标志位判断避免DIO0在低功耗模式下出现误触发。4. 实操过程与测试调优4.1 从串口到无线的最小验证流程拿到整理好的代码我习惯先做一个“串口转发”的最小验证板子A接串口串口发什么LoRa就发什么板子B收到LoRa数据后发到本地串口打印。这个方案能在一小时内验证整条链路分为三步两块F103都烧录同一份工程工程里包含LoRa驱动和串口收发逻辑。串口波特率配置为115200LoRa参数配置一致频率、SF、BW、同步字必须相同。把板子A的串口接到电脑USB转TTL板子B的串口也接到电脑两个串口助手互发消息。验证通过后再开始改自己的应用层协议。比如在发送的数据前加一字节“目标地址”接收端判断地址匹配才处理这就是最简单的点对点协议原型。4.2 距离和丢包率调优实测收到“距离不够”这类反馈时我第一反应是先别动代码检查天线。SX1278模块上用的天线如果是弹簧天线对安装环境极其敏感周围有大面积金属或者紧贴地平面驻波比会变差距离衰减非常严重。把手持模块离开金属桌面50cm以上往往距离翻倍。排除天线因素后再讨论调参。拿我一个实测数据举个例子两块F103配SX1278天线相同位置固定在一个室内楼宇的走廊两端直线距离约280米中间有两堵剪力墙。使用SF7、125kHz、发射功率14dBm时丢包率大约15%把SF从7改为10其他不变丢包率降到接近0。代价是理论空速从约5.5kbps降到约1.4kbps但对温湿度采集这种低频上报场景来说完全够用。整理代码时我专门在lora_config.h里留下了“调试模式”开关。打开后会通过串口把当前设置和每次收发的空速估算值打印出来方便在现场快速对照参数调整效果。4.3 低功耗考虑与配置如果你的节点是电池供电不能一直开着接收。标准的做法是定期唤醒节点先进入睡眠模式定时器每隔几秒唤醒一次切换为接收模式并持续一段时间没收到指令就再次入睡。SX1278本身的睡眠电流在0.2uA级别但F103的睡眠电流取决于低功耗模式配置。我在整理代码时保留了RTC唤醒的接口。节点默认进入STOP模式RTC定时器到达后唤醒然后LoRa模块从Sleep切到Standby再切到RX监听一个预设的时间窗口比如500ms如果收到指令就处理没收到就重新睡下去。这套逻辑在lora_app.c里封装成Lora_NodeSleep()和Lora_NodeListen()做电池类产品时可以直接调用。低功耗调试有一个比较容易踩的坑F103进入STOP模式前要把SPI的NSS引脚拉高否则模块可能误以为片选拉低在接收模式下白耗电。另外DIO0引脚要配置为外部中断输入并且打开上升沿触发这样才能在模块收到数据时把MCU从STOP模式唤醒。5. 常见问题与排查技巧实录5.1 双方都收不到数据这是最常遇到的情况。按我平时的排查顺序优先级从高到低依次是排查项操作现象说明SPI通信是否正常读RegVersion正常应为0x12如果读到0xFF多半是SPI时序、引脚定义或模块供电异常频率是否一致双方打印当前频率偏差哪怕几十kHz也可能导致收不到同步字是否一致确认双方RegSyncWord相同很多模块出厂默认0x12如果被改过而代码里没同步必然收不到CRC是否一致确认双方都开或者都关发送端开CRC接收端关CRC会导致接收中断不触发DIO0是否接对用示波器或逻辑分析仪看DIO0在收发时有无脉冲如果硬件没接DIO0接收完成通知不会触发其中SPI通信不正常是翻车最多的。常见原因有三个一是SPI用的不是硬件SPI而是GPIO模拟但时序写错了二是STM32的SPI初始化配置成了Mode 3三是模块的MOSI/MISO接反了。Module上标注的SI对应MCU的MOSISO对应MCU的MISO别搞反。5.2 距离短到只有十几米先怀疑天线再怀疑射频参数接着怀疑电源。SX1278的PA供电引脚VCC_RF通常需要单独供电不要和逻辑供电用同一个LDO还能吃掉瞬态电流。如果模块的VCC_RF输入端没有加钽电容或大容量陶瓷电容发射功率上去后电压跌落严重距离自然上不去。还有一个容易忽略的点发射功率设置值可能超出模块实际承受范围。正点原子LoRa模块最大支持20dBm但如果模块在板上的散热条件不好连续长时间大功率发射芯片温度升高后发射功率下降距离也会缩水。我一般测试距离时把发射功率设为14~17dBm留出余量也避免频繁触发过温保护。5.3 数据出现乱码或错帧LoRa本身带CRC正常情况下不会出现“随机错几个字节”的情况。如果频繁出现乱码重点查两件事数据长度字段是否匹配。发送时RegPayloadLength写的是长的那一包接收端读RegRxNbBytes得到的是实际接收长度如果两边长度处理逻辑不对称FIFO读出来自然错位。FIFO读指针是否正确。很多问题出在没把FIFO_ADDR_PTR设置成当前接收地址就开读直接读到缓存区历史数据。另外提醒一下LoRa数据包的“最大长度”取决于寄存器配置中的Payload Length设置和FIFO大小SX1278是256字节。如果你的一帧业务数据超过255字节要么拆包要么用连续模式分块传输不要把整帧塞进去否则模块会拒绝发送或者只发前一段。5.4 与正点原子其他例程的比照如果你拿到的整理代码是基于正点原子官方例程改的注意正点原子F103板子上的PA2/PA3通常已经焊了串口跳帽或接了CH340芯片而LoRa模块的DIO0如果恰好也复用这两个引脚请先确认板子上的外设冲突。我见过不止一次有人把DIO0接到PA3上同时又开着一路串口打印结果模块接收完成中断一直被串口引脚电平干扰。整理代码时最好先查原理图给LoRa外设分配一组不和其他板载外设打架的引脚。6. 写在最后的几点体会这套代码整理完我自己又从里面拆了几份变体出来一份做成了RTC低功耗节点一份做成了带RSSI信号强度回传的巡检测试工具。改动最多的反而不是SX1278驱动而是lora_app.c里的业务状态机。这恰恰说明驱动层整理得足够干净应用层才有条件随便折腾。如果你打算把这个LoRa链路用在产品上我再提三个建议频率参数留成可配置项最好在应用层通过串口命令修改并存入Flash。同一片板子发到不同地区运营商或者客户对频段有不同要求硬件不用改改配置文件就行。每一帧都预留2~4字节的扩展字段用来放序列号、电源电压、RSSI等状态信息。简单报站功能可以用后期排查干扰想拉数据看的时候这些字段能救急。接收端如果总是最后一个节点在保持接收模式会一直消耗电流能设计成ACK确认机制就尽量加。LoRa天然有“发完才能收”的时分特性把ACK放在发送完成后的空闲窗口里配合超时重发链路可靠性会有一个明显提升。整理代码这件事表面上是在移动文件、拆分函数实际上是在建立自己对硬件行为的掌控感。SX1278这款芯片成熟稳定F103又是多年的老朋友把它们之间的代码整理顺了后面加功能、做变体都会顺手很多。本文还有配套的精品资源点击获取