
简介面向 STM8 单片机开发者的 SX1268 无线收发器参考例程聚焦 LoRa 与 FSK/OOK 通信场景中 SPI 配置、初始化参数设置、数据包收发和中断处理等关键环节。压缩包共 37 个文件、约 114KB以 c/h 源文件、lkf 链接脚本、s19 烧录文件及 STVD 工程文件为主并附带 map、elf 等编译中间产物便于核对构建流程与调试。代码基于 STM8S103F3 平台包含主程序、SPI 驱动、SX1268 操作封装、中断向量表等模块目录结构简洁可直接用 STVD 打开工程编译下载。项目内还保留 readme 说明与 TAGS 索引文件有助于快速定位文件职责配套的 sx1276_7_8 命名与 SX1268 同系列 API 兼容也可为同类芯片开发作参考。目前已有 570 人学习/下载适合正在调试射频驱动、希望缩短 LoRa 节点开发周期的嵌入式工程师。1. 一份指向 SX1268 的 Demo工程路径却写着 SX127x拿到SX1268demo code for client压缩包解压后第一眼看到的是sx127x_demo_code_for_client.wed、sx1276_7_8.c、sx1276_7_8.h这套文件。名字指向 SX1276/77/78标题却写着 SX1268很容易让人以为资源传错了。实际拆完工程目录和驱动代码就能发现这套例程的价值不在芯片型号匹配而在 SPI 驱动、寄存器读写封装、LoRa 收发状态机这套骨架——SX1268 与 SX127x 同属 Semtech LoRa 收发器家族命令集和设备寄存器有清晰的继承关系基于 STM8S103F3 把这套代码读通移植到 SX1268 上的成本远低于从零写驱动。适合两类人刚开始接触 SX1268、需要一份能跑通的参考实现的开发者以及手里已有 SX127x 工程、想迁到 SX1268 的工程师。后面的内容全部围绕 demo 里的spi.c、sx1276_7_8.c、main.c展开。2. 工程解剖STM8S103F3 的 SPI 时序与寄存器读写封装2.1 从工程文件列表反推硬件与工具链压缩包内.stw与.stp是 ST Visual DevelopSTVD的工程文件后缀Release 目录下的.elf、.map、.s19是链接与烧录产物。.s19是 Motorola S-record 格式ST-Link 烧录器可直接加载所以拿到代码后第一步不是打开工程而是确认sx127x_demo_code_for_client_pro.s19能否直接烧进目标板做基础验证。文件列表里stm8s_flash.c、stm8s_flash.h、stm8_interrupt_vector.c属于 STM8S 标准外设库而spi.c、sx1276_7_8.c、main.c才是本项目自己写的部分。sx127x_demo_code_for_client_pro.s19 // 编译产物直接烧录 main.c // 应用层初始化 主循环 sx1276_7_8.c / .h // LoRa 驱动寄存器操作与收发封装 spi.c / .h // 硬件适配SPI 底层读写 stm8s_flash.c / stm8_interrupt_vector.c // STM8 标准库从文件组织能看出一个常见分层思路main.c只负责调用不直接操作硬件sx1276_7_8.c不关心 MCU 型号所有对外硬件访问收敛到spi.c。这个结构保证了当你想把代码移植到 STM32 或其它 MCU 时90% 的改动只发生在spi.c一个文件里。实际动手改板子前先确认模块的 SDOMISO、SDIMOSI、SCLK、NSS、DIO1、RST、BUSY 这些引脚与 STM8S103F3 的哪个端口对应建议把所有映射写进一个头文件不要散落在各.c文件里。2.2 SPI 主模式读写函数与 SX1268 命令帧格式SX1268 与 MCU 之间只走四条线SCLK、MOSI、MISO、NSS外加一条 BUSY 输出用于流控。SPI 工作模式固定为模式 0CPOL0CPHA0时钟频率建议起步跑 1MHz稳定后再往上调。下面是从 demo 中提炼的 SPI 底层的常见写法参数与实现贴近 STM8S103F3 的 SPI1 外设// spi.c —— 以寄存器直操作方式驱动 STM8S103F3 的 SPI1 void SPI_Init_LoRa(void) { // CLK 频率: 默认 16MHz 主频, BR[2:0]010 即 4 分频 - 4MHz SPI 时钟 SPI1-CR1 0x02; // BR010, CPOL0, CPHA0, MSB first SPI1-CR2 0x02; // 主机模式使能 SSM1, SSI1 SPI1-ICR 0x00; SPI1-CR1 | 0x40; // SPE1, 使能 SPI } uint8_t SPI_ReadWriteByte(uint8_t byte) { while ((SPI1-SR 0x02) 0); // 等待 TXE: 发送缓冲为空 SPI1-DR byte; while ((SPI1-SR 0x01) 0); // 等待 RXNE: 接收缓冲非空 return (uint8_t)SPI1-DR; }这段代码里CR10x02的关键是把波特率预分频定为 4 分频SSM1与SSI1表示软件管理 NSS否则 SPI 接口在主机模式下产生 MODF 错误。ReadWriteByte是全双工读写合一发送一个字节的同时会收到一个字节这个特性在后续读 SX1268 寄存器时非常关键因为读操作也是在 MOSI 上写地址字节、同时在 MISO 上读回数据。寄存器读写封装在sx1276_7_8.c内SX1268 的寄存器空间通过 SPI 命令访问不同于用地址线直连。SX1268 官方命令集里与寄存器读写相关的有ReadRegister0x1D与WriteRegister0x0E但实际命令码要以你拿到的芯片数据手册为准因为 SX1268 与 SX1262 的底层一致命令格式却以 Semtech 官方驱动为准。常见实现如下// sx1276_7_8.c —— 寄存器读写封装以 SX1268 命令格式为例 void SX126x_WriteRegister(uint16_t address, uint8_t value) { NSS_LOW(); SPI_ReadWriteByte(0x0E); // WriteRegister 命令码 SPI_ReadWriteByte((address 8) 0xFF); // 地址高字节 SPI_ReadWriteByte(address 0xFF); // 地址低字节 SPI_ReadWriteByte(value); // 写入数据 NSS_HIGH(); }命令码、地址高字节、地址低字节、数据字节的发送顺序是固定的NSS 拉低期间所有字节必须连续发出中间不能有任何 GPIO 操作插入。SX1268 在 SPI 命令行上要求 MCU 在发起命令前等待 BUSY 引脚回落否则命令可能被丢弃demo 里如果没做 BUSY 等待常见的替代做法是固定延时 100us但代价是每次操作慢 100us批量写缓冲时吞吐差距明显。2.3 移植到其它 MCU 的引脚适配与速率调整从 STM8S103F3 迁到 STM32 平台时外设库函数名要换SPI 初始化逻辑骨架不变。下表是移植时最常用的引脚映射建议其中 BUSY 和 DIO1 在不同库函数版本里名称可能为BusyPin与DIO1注意对应关系即可。功能STM8S103F3 推荐引脚说明SCKPA5SPI 时钟模式 0MOSIPA7主出从入MISOPA6主入从出NSSPA3软件控制普通 GPIO 即可RSTPA2低电平复位 SX1268BUSYPA1高电平表示芯片忙必须等它拉低再发命令DIO1PA0中断输出可配置映射到 EXTI速率方面SX1268 的 SPI 最高支持 16MHz但实际模块厂商会在 PCB 上串电阻限流跑太高容易误码。建议先固定在 1M~2MHz 验证通信收发吞吐要求上来了再逐步提高每次提高后都要重跑一整轮ping-pong回环验证确认没有因为时钟过快导致随机丢字节。3. LoRa 长距参数落位SX1268 频率、SF、BW 与编码率的配置顺序3.1 频率与调制参数背后的硬件机制SX1268 内部的核心是一个分数分频锁相环输出频率由SetRfFrequency命令配合一个 32 位频率字决定。频率字计算式为freq_word (target_freq * 2^25) / 32MHz32MHz 是 SX1268 内部参考时钟不要与 MCU 主频混淆。以 433MHz 为例433 * 33554432 / 32000000 ≈ 453685换算成十六进制就是写入寄存器的值。demo 的sx1276_7_8.c里通常把这个计算做成一个宏或函数你换频段只需要改目标频率这一处不需要动寄存器地址。扩频因子SF、带宽BW、编码率CR这三个参数共同决定了 LoRa 的链路预算和数据速率。SX1268 支持 SF5 到 SF12BW 支持 7.8kHz 到 500kHzCR 支持 4/5 到 4/8。三个参数不是独立的SF 每增加 1空中速率减半灵敏度提升约 2~3dBBW 加倍速率翻倍但灵敏度下降约 3dBCR 增大带来更强的抗突发干扰能力代价是有效载荷吞吐降低。在 demo 代码中调制参数不会拆成单个寄存器赋值而是通过一组命令包一次下发。常见初始化序列是清中断 - 设置待机模式 - 设置包类型LoRa- 设置频率 - 设置调制参数 - 设置包参数 - 设置发射功率 - 配置 DIO 中断映射。顺序不能乱因为后一条命令依赖前一阶段的模式状态。3.2 初始化序列与关键命令的代码实现下面这段配置函数将 SX1268 设为 LoRa 模式、433MHz、SF9、125kHz 带宽、编码率 4/5工作在连续接收状态// sx1276_7_8.c —— SX1268 LoRa 初始化示例 void SX126x_Init_LoRa(void) { uint32_t freq 433000000UL; uint32_t freq_word (uint32_t)((freq * 33554432UL) / 32000000UL); SX126x_Reset(); // 复位芯片, 拉低 RST 引脚至少 100us SX126x_SetStandby(0x01); // 进入 RC 待机模式 SX126x_SetPacketType(0x01); // 0x01 LoRa, 0x00 GFSK SX126x_SetRfFrequency(freq_word); // 写入 32 位频率字 SX126x_SetModulationParams( // 扩频因子. 带宽. 编码率 0x09, // SF9 0x02, // BW125kHz, 配置值 0x02 对应 125k 0x01, // CR4/5 0x00, // 低数据率优化关闭 0x00 // 保留位 ); SX126x_SetPacketParams( 0x01, // 固定包头模式 32, // 默认负载长度 32 字节 0x08, // 前导码 8 个符号 0x00, // 无 CRC 反转 0x00 // 无 IQ 反转 ); SX126x_SetDIOIrqParams(0xFFFF, 0x0001, 0x0000, 0x0000); // 仅映射 RX_DONE 到 DIO1 SX126x_SetRx(0xFFFFFF); // 连续接收模式, 超时参数置极大值 }SetStandby的参数0x01表示使用 RC 振荡器待机0x00表示使用晶振待机如果模块外接 TCXO这里必须选晶振模式否则后续频率精度受影响。SetModulationParams的四个参数顺序是固定的配置值对应的带宽表在数据手册里有专门表格125kHz 对应的枚举值是0x02不要凭直觉用数值 125。SetPacketParams里定义的是包级属性负载长度在固定包头模式下是全局设置在可变包头模式下则为最大长度限制。3.3 SF/BW/CR 组合的取舍表格与功耗权衡不同参数组合对链路的影响可以直接在 demo 里做对比测试把两块板子拉开距离逐步降 SF 和升 BW观察 RSSI 变化。这里整理了一组典型参数下的特性对照可作为工程选型的出发点组合灵敏度参考空中速率适用场景SF12 / BW125 / CR4/8约 -148dBm约 250bps 级需要极限距离量小、实时性要求低SF9 / BW125 / CR4/5约 -135dBm接近 1.8kbps平衡通信距离与速率SF7 / BW250 / CR4/5约 -125dBm接近 11kbps高速数传距离相对短上表的速率只是参考量级SX1268 数据手册中空中速率与 SF、BW 的精确关系需要查计算曲线。工程上我的做法是先用 SF9/BW125 跑通 demo再根据现场的链路余量往下调 SF。别忘了 SX1268 需要先执行校准Calibrate命令常规做法是在上电初始化后先等 3.5ms让 32MHz 晶振稳定然后触发校准再把结果写入CalibParam相关寄存器。跳过校准这步接收灵敏度会显著劣化百思不得其解的时候先怀疑它。4. 收发状态机main.c 里的发送流程与 DIO1 中断处理4.1 发送路径的完整调用链发送一包数据在 demo 里的主流程是这样的先切到待机模式清空上一次的中断状态把 FIFO 写指针和读指针复位到 0x00然后往 FIFO 里写负载数据最后调SetTx让芯片进入发射态。发送完成事件通过 DIO1 引脚拉高通知 MCU。核心代码部分参考如下// main.c —— 发送一帧数据到 SX1268 FIFO 并触发发射 void LoRa_SendPacket(uint8_t *buf, uint8_t len) { // 1. 回到待机模式, 此时 SPI 可以对 FIFO 做写操作 SX126x_SetStandby(0x01); // 2. 清掉上次发送遗留的 IRQ 标志位 SX126x_ClearIrqStatus(0xFFFF); // 3. FIFO 读写指针复位到 0 SX126x_SetBufferBaseAddress(0x00, 0x00); // 4. 把负载写入 FIFO, 起始地址 0x00 SX126x_WriteBuffer(0x00, buf, len); // 5. 设置包参数: 固定头, 负载长度 len, 前导码 8, 无翻转 SX126x_SetPacketParams(0x01, len, 0x08, 0x00, 0x00); // 6. 触发发送, 超时时间 1000ms (单位 15.625us) SX126x_SetTx(0x0640); }第 6 步的0x0640是超时值单位是 15.625us换算下来约 100ms。这里有个容易踩的细节SetTx的超时计时是从命令发出后开始的不是在数据真正发完才开始所以超时值要留足空中传输时间的余量。在 433MHz、SF9、BW125 的配置下32 字节负载的实际空中时间在几百毫秒级超时给到 1000ms 比较稳妥。4.2 接收路径与中断标志的判断接收端的核心差异在于 DIO1 中断的映射。SetDIOIrqParams可以分别配置 DIO1、DIO2、DIO3 各自映射哪些中断源。demo 里如果只期望在收到完整数据包时触发 MCU就把RX_DONE映射到 DIO1主循环只需轮询 DIO1 引脚电平或者把它接到外部中断引脚。收到中断后的处理函数模板如下// main.c —— DIO1 中断或轮询处理入口 void LoRa_HandleIrq(void) { uint16_t irq SX126x_GetIrqStatus(); // 读回中断标志 if (irq 0x01) { // RX_DONE 置位 SX126x_ReadBuffer(0x00, rx_buf, rx_len); // 从 FIFO 读走数据 SX126x_ClearIrqStatus(0x01); // 清掉 RX_DONE rx_len 0; // 准备下一包 } if (irq 0x02) { // TX_DONE 置位 SX126x_ClearIrqStatus(0x02); tx_busy 0; } }irq各 bit 的定义在数据手册里有专门表格0x01是RX_DONE0x02是TX_DONE0x04是CRC_ERROR。重点提醒读GetIrqStatus不会自动清除状态必须显式调ClearIrqStatus才能把对应位清掉否则下一次中断不会重新触发或产生重复判断。CRC 校验失败的包硬件上同样会产生中断但映射位是CRC_ERROR开发初期建议把它也映射到 DIO1 并单独打印计数作为链路质量的参考指标。4.3 用状态机替代阻塞式收发demo 的main.c主循环里大量使用while (IRQ TX_DONE 0)这种阻塞等待对调试阶段的逻辑验证没有大碍但真正接入业务后要改成状态驱动。我通常维护一个枚举状态变量主循环里每轮根据当前状态决定动作typedef enum { IDLE, TX_START, TX_WAIT, RX_WAIT } LoraState;IDLE状态下检查是否有数据要发送有则切到TX_START在TX_START里调用LoRa_SendPacket并立即切到TX_WAITTX_WAIT每轮循环只查tx_busy标志这个标志在中断处理函数里被清零。这样主循环不会因为等待射频事件而卡死其它业务逻辑按键扫描、传感器采集才能和射频收发共存。5. 从 SX127x 迁移到 SX1268 的寄存器差异与 BUSY 脚排错5.1 SX127x 与 SX1268 的架构差异对照demo 目录名为sx127x_demo_code_for_client但目标芯片是 SX1268这种名实不符恰好说明两个芯片族的驱动底子是相通的。SX127x 把调制参数拆在几十个寄存器里逐位配置SX1268 则把所有操作收敛为命令加参数的形式整体更接近控制器风格。迁移时最大的认知转变是不用再记RegOpMode的哪个 bit 表示 TX 模式而是调SetTx命令。操作SX127x 做法SX1268 做法迁移注意事项切换模式写RegOpModeSetStandby/SetRx/SetTx命令SX1268 发命令前必须查 BUSYFIFO 寻址RegFifoAddrPtr单指针SetBufferBaseAddress双指针读写指针独立复位中断源RegIrqFlags寄存器GetIrqStatus命令读后显式ClearIrqStatus射频开关控制DIO2 常作接收指示DIO2 常配为 RF switch 控制差异主要在匹配电路校准时机复位后自动校准需显式调Calibrate跳过后灵敏度明显下降BUSY脚是 SX1268 与 SX127x 最直观的差异。SX1268 内部的状态机从待机切到发射、接收或者执行频率计算时SPI 接口不会实时响应新命令此时 BUSY 输出高电平。严谨的驱动里每次发命令前都要等待 BUSY 拉低demo 中若省略了这一步大概率靠延时兜底。排错时发现写命令偶尔不生效第一件事就是拿逻辑分析仪同时抓 NSS 和 BUSY确认 BUSY 高电平期间是否发起了 SPI 传输。5.2 迁移过程中最容易翻车的三个点第一个坑是 SPI 波特率。SX1268 的 SPI 极限是 16MHz但很多模块的参考设计会省掉串阻或加较大的滤波电容导致高速率下 MISO 上升沿变缓。现象是初始化偶尔成功、偶尔读寄存器全是 0xFF。遇到这种情况先把 SPI 时钟降到 1MHz 试跑能稳定通过再逐档提升。第二个坑是SetPacketParams中包头模式与负载长度的组合。SX1268 在可变包头模式下负载长度字段指定的是可选的最大值实际长度从前导码后的包头字节里解析如果发端用可变包头、收端用固定包头收端把长度值当配置值用会造成 CRC 校验永远失败或收到超长包。demo 里收发两端最好统一用固定包头模式负载长度写死同一个值。第三个坑是低功耗模式的进入与唤醒。SX1268 进入 Sleep 模式前必须处理 DIO3 对 TCXO 的供电如果是 TCXO 版本Sleep 模式下 DIO3 会继续供电从 Sleep 唤醒后要重新执行校准并等待晶振稳定如果是普通晶振版本DIO3 配置不同。很多工程师把发送间隔拉到 5 分钟以上结果发现平均电流远高于预期排查方向就是 DIO3 的配置有没有跟随芯片状态切换。6. 回环对拷与 SPI 波形验证SX1268 驱动是否正常的实测两步法拿到 demo 后不建议直接去调距离和灵敏度先完成两个 10 分钟的验证确认驱动本身没有底层问题。第一步准备两块同型号 SX1268 模块分别刷入发送程序和接收程序距离 1 米以内发送端每 500ms 发一帧 16 字节递增序列接收端把收到的数据和 RSSI 值通过串口打印出来。如果接收端 RSSI 在合理范围且数据连续正确说明 SPI 通信、调制参数、包参数、中断路径全部正常如果收不到优先检查 BUSY 等待与SetDIOIrqParams的映射配置断开逻辑分析仪抓 DIO1 是否拉高。第二步是用逻辑分析仪抓一帧完整的 SPI 写入过程。把 NSS、SCLK、MOSI 三路接上触发条件设为 NSS 下降沿抓取发送一包数据时的完整波形。重点看三处NSS 拉低前 BUSY 是否已为低SetTx命令发出后到 DIO1 拉高的时间间隔是否与理论空中时间接近写完 FIFO 后有没有多余的 SCK 脉冲。这组波形能一次性暴露位序错乱、命令格式错误、SPI 速率过高三个问题。接收端调不通时用同样的方法抓SetRx发出后的 FIFO 写入过程确认SetBufferBaseAddress的读写指针没有把 FIFO 覆盖。两个验证通过后再回头动SetModulationParams的 SF/BW 参数每次只改一个变量用接收端打印的 RSSI 与 CRC 错误计数做回归对比而不是凭感觉调一堆参数后归因于环境干扰。这套验证流程同样适用后续把 demo 迁到 STM32 或其它平台换芯片不换方法。本文还有配套的精品资源点击获取