简介:针对Semtech SX1268 LoRa射频芯片的SPI驱动源码包,面向嵌入式软硬件工程师及物联网开发者,解决基于STM32F10x平台远距离低功耗无线通信的移植与调试问题。压缩包共99个文件,以42个C源文件、44个头文件为核心,覆盖SX126xRadio、SX126xDriver等分层驱动代码,并配备STM32F103ZET6的Keil工程、启动文件、固件库中文手册PDF、说明文档等,整体约2.02MB,目录整齐便于检索。驱动实现了芯片初始化、工作模式切换、频率/功率/扩频因子/编码率配置,以及数据发送、接收缓冲区读取和中断回调等关键流程,代码中包含寄存器操作与状态机管理,注释较完整,便于二次开发和教学参考。开发者可基于附带Demo快速验证硬件连接与通信参数,并参考中文固件库手册理解STM32外设配置。已有1482人学习下载,适合正在评估或量产LoRa方案的团队参考。
1. SX1268驱动:LoRa 项目里最容易在沉默中翻车的一段代码
拆过的 LoRa 模块多了之后,你会发现 SX1268 驱动是最容易被当成“能跑就行”去抄的一段代码。它看起来就是几行 SPI 读写加寄存器填值,但它背后牵涉命令应答、BUSY 时序、DIO1 中断映射、TCXO 校准这些隐蔽环节,任何一环没对齐,板子就是不出信号。这份 sx126xdriver 源码包覆盖从 SPI 命令层到 radio 操作层的完整调用链,特别适合正在做 STM32 + LoRa 数传、想把 SX1268 从“买了模块”快速推进到“空中收发正常”的工程师。我拆过几份同类驱动,能直接编译的不多,能过频谱仪信号测试的更少,所以这篇把关键机制和坑位都摊开讲。
2. 驱动到底在驱动什么:SPI 命令层、状态机与寄存器机制
拿到驱动源码先别急着编译。SX1268 的驱动文件看着多,其实逻辑上就三层:最底下是物理层的 SPI 收发、GPIO 和延时;中间是命令层,每个函数对应 datasheet 上的一条命令,比如 SetRfFrequency、SetPacketType;最上层是操作层,负责把多条命令拼成一个完整动作,比如“发送一包数据”或者“进入连续接收”。大多数移植出问题,都不是 SPI 坏了,而是跨层调用时漏了某个前置命令。
2.1 三块积木:命令层、状态机与寄存器表
SX1268 是 SPI 从机,所有配置都要通过命令码完成。命令格式很固定:NSS 拉低,先发一字节命令码,再发该命令的参数字节,最后 NSS 拉高。对读写寄存器类的命令,参数里就是寄存器地址加数据。这样一个 SPI 周期只允许一条命令,不能把多条命令夹在同一个片选低电平里混着发。
static int sx126x_cmd(uint8_t cmd, uint8_t *param, uint8_t size) { // 常见做法:NSS拉低,先发命令码,再发参数字节 // 对读寄存器命令,param里是“地址+占位字节” hal_nss_low(); spi_write_byte(cmd); if (size) { spi_write_bytes(param, size); } hal_nss_high(); return 0; }这段代码是所有 SX1268 驱动的地基,逻辑上没什么玄学:NSS 拉低表示命令开始,拉高表示命令结束,中间时序必须完整。唯一容易踩的是参数长度,比如 SetStandby 只有一个参数字节 0x01,但 SetDioIrqParams 要带五组参数共 9 字节,写错了芯片不会报错,只是行为全都乱套。所以移植驱动时,我一般先对着 datasheet 把所有命令的参数长度抄一遍贴到函数注释里,方便后面对照。
状态机这块是初用者最容易忽略的黑匣子。SX1268 内部有 IDLE、STDBY_RC、STDBY_XOSC、RX、TX 等状态,改频率、改调制参数、写 FIFO 之前都要先用 SetStandby 回到 STDBY_RC。如果你的代码在连续收发几次后再配置参数偶尔失败,不用怀疑芯片坏了,大概率是上一次操作还没回 standby。
2.2 参数配置映射:频率、带宽、扩频因子对应哪几行
驱动里最常被原样抄走的是一段 LoRa 默认配置代码,它把频率、扩频因子、带宽、编码率、前导码都堆在几个函数调用里。很多人抄完不知道每一行在干什么,出了问题只能整段回退,所以我把关键参数和命令的对应关系列一下。
| 配置项 | 命令 | 关键参数 | 常见值 |
|---|---|---|---|
| 工作频率 | SetRfFrequency (0x86) | 4 字节 RF 频点 | 470000000 Hz |
| 调制方式 | SetPacketType (0x8A) | 1 字节包类型 | 0x01 (LoRa) |
| 调制参数 | SetModulationParams (0x8B) | SF、BW、CR、LDRO | SF7 / 125kHz / 4/5 |
| 包参数 | SetPacketParams (0x8C) | 前导码、载荷长度、CRC | 8 字节前导码,固定长度 |
| 中断映射 | SetDioIrqParams (0x08) | IRQ 掩码与 DIO1 掩码 | TX_DONE 映射到 DIO1 |
这里必须提一个容易翻车的细节:SetRfFrequency 的入参不是直接的 Hz 数,而是一个由晶振频率换算出来的频点值,公式是 Fstep = 晶振频率 / 2^25。SX1268 的评估板和绝大多数国产模块用的都是 32MHz 晶振,于是 Fstep 约等于 0.9537Hz,配置 470MHz 时要先把 470000000 除以 0.9537 再取整。驱动里如果写死 26MHz 晶振的换算系数,频谱仪上频率会偏出去好几兆。
void lora_cfg_default(sx126x_radio_t *r, uint32_t freq_hz) { sx126x_set_standby(SX126X_STDBY_RC); // 改配置前必须回 standby sx126x_set_packet_type(SX126X_PACKET_TYPE_LORA); /* freq = Fstep * RFfreq, Fstep = 32MHz / 2^25 */ uint32_t rf_freq = (uint32_t)(freq_hz / 0.9536743164 + 0.5); sx126x_set_rf_frequency(r, rf_freq); /* SF7、BW125、CR4/5、LDRO=0 */ sx126x_set_modulation_params(r, SF7, BW125K, CR_4_5, 0); /* 前导码 8、固定长度、载荷 64、双字节 CRC、IQ 不翻转 */ sx126x_set_packet_params(r, 8, SX126X_HEADER_FIXED_LEN, 64, SX126X_CRC_2_BYTE, 0); }逻辑说明:先 SetStandby 是因为频率和调制参数都不允许在 RX/TX 状态下修改,这是 SX1268 比老款 SX1276 更“矫情”的地方。SetPacketType(0x01) 确保芯片处于 LoRa 模式而不是 FSK 模式,如果上一手固件里跑过 FSK,漏掉这行会导致后面所有参数都按 FSK 解释。RFfreq 后面的加 0.5 只是四舍五入,避免频率偏几赫兹。
2.3 移植到 STM32:需要改的是四个挂点,不是整个驱动
上到实际项目里,驱动自带的工程模板往往和你的板子对不上。不要试图重写整个驱动,真正要动的只有四个挂点:SPI 单字节收发、NSS 片选控制、DIO1 外部中断回调、毫秒延时函数。这四个点改完,其余文件基本不用动。
/* STM32 HAL 版移植示例 */ uint8_t hal_spi_xfer(uint8_t data) { uint8_t rx; // 注意:SX1268 要求 SPI 模式 0,即 CPOL=0、CPHA=0 HAL_SPI_TransmitReceive(&hspi1, &data, &rx, 1, 10); return rx; } void sx1268_gpio_init(void) { // NSS、RESET、BUSY 配成推挽输出,DIO1 配成输入 // DIO1 对应的 EXTI 线配置为上升沿中断 HAL_NVIC_SetPriority(EXTI9_5_IRQn, 2, 0); HAL_NVIC_EnableIRQ(EXTI9_5_IRQn); }逻辑说明:SPI 模式 0 是硬性要求,模式和 SX1276 一样。很多驱动为了省事不检查 BUSY 引脚,直接用固定延时代替,这在低速操作时没问题,但连续收发时容易丢命令。我一般会把 BUSY 查询保留下来,宁可多写几行也不能让命令撞车。参数说明:hspi1 换成你工程里实际的 SPI 句柄;DIO1 的 EXTI 配置要看芯片引脚连到哪条 EXTI 线上,代码里的 EXTI9_5_IRQn 只是示例。
移植完成后,先用一个最简单的读寄存器操作验证通路:调用驱动里的读寄存器函数读 0x0740 的同步字寄存器,能读回默认值 0x1424,说明 SPI 通路和驱动层都通了,再进行收发调试。这一步能省掉后面一半的排查时间。
3. 把包收回来:发送、接收与中断链路的完整调用顺序
驱动移植通过后,下一步就是让数据真的从天线飞出去再收回来。SX1268 的收发链路比寄存器配置更依赖调用顺序,因为中断映射、FIFO 操作和超时参数是联动的。很多人在这里翻车,不是不懂 SPI,而是没搞清“先等 BUSY 拉低再发下一条命令”的节奏。
3.1 发送一条 LoRa 包:从 SetTx 到 TX_DONE 的路径
发送一包数据的标准顺序是:先回 standby,然后写 FIFO,再配置 DIO1 中断映射,最后调 SetTx 启动发射。代码看起来不长,但每一步都有隐含前提。
int radio_tx_packet(uint8_t *data, uint8_t len) { sx126x_set_standby(SX126X_STDBY_RC); sx126x_write_buffer(data, len); // 命令 0x0E,从 FIFO 偏移 0x00 写起 sx126x_set_pa_config(0x04, 0x07, 0x01, 0x01); sx126x_set_tx_params(22, SX126X_RAMP_TIME_200U); sx126x_set_dio_irq_params(SX126X_IRQ_TX_DONE, SX126X_IRQ_TX_DONE, 0, 0, 0); sx126x_set_tx(0xFFFFFF); // 超时单位 15.625us,最大即不超时 while (!dio1_flag); // DIO1 在 EXTI 回调里置位 uint16_t irq = sx126x_get_irq_status(); sx126x_clear_irq_status(); dio1_flag = 0; return (irq & SX126X_IRQ_TX_DONE) ? 0 : -1; }逻辑说明:WriteBuffer 命令必须先发偏移字节,再发数据,这里的偏移就是 FIFO 起始地址,通常固定 0x00。SetPaConfig 和 SetTxParams 这两行是很多人看不懂的:前者配置 PA 的通断占空比和高功率模式,后者设置发射功率与升压时间。功率 22 是 dBm 数值,但前提是 PA 配置和模块匹配网络一致,否则虚标。等待 TX_DONE 时不能用死循环,项目里我一般会加超时计数,比如设一个 500ms 的上限,超过就报错退出,避免模块异常时把整个系统卡死。
如果发送后 DIO1 一直没有拉高,先别查中断,用 GetIrqStatus 命令主动读一次中断状态,看有没有 TX_DONE 标志。如果读到的中断是别的位,说明芯片根本没进入发送状态,问题在状态机前置条件上,而不是中断配置。
3.2 接收一条 LoRa 包:连续 RX 与 DIO1 中断
接收比发送简单,但容易混淆的是两种接收模式:单次接收和连续接收。单次接收在收到一包或超时后芯片自动回到 standby,连续接收则一直停在 RX 状态,直到你手动调 SetStandby 才退出。低功耗场景建议用前者,主循环轮询用后者更省事。
void radio_start_rx(void) { sx126x_set_standby(SX126X_STDBY_RC); sx126x_set_dio_irq_params(SX126X_IRQ_RX_DONE | SX126X_IRQ_CRC_ERROR, SX126X_IRQ_RX_DONE | SX126X_IRQ_CRC_ERROR, 0, 0, 0); sx126x_set_rx(0xFFFFFF); // 连续接收,不超时 }收到 DIO1 中断后,在主循环里读中断状态、清标志、再读 FIFO,这个流程和发送是对称的。
void radio_poll(void) { if (!dio1_flag) { return; } dio1_flag = 0; uint16_t irq = sx126x_get_irq_status(); if (irq & SX126X_IRQ_RX_DONE) { uint8_t buf[256]; uint8_t len = 0; sx126x_read_buffer(buf, &len); sx126x_clear_irq_status(); // 到这里 len 是有效数据长度,buf 里是完整载荷 handle_rx_packet(buf, len); } else if (irq & SX126X_IRQ_CRC_ERROR) { sx126x_clear_irq_status(); // CRC 出错也要清中断,否则一直卡 } }逻辑说明:SX1268 在 CRC 错误时也会触发中断,如果不主动 ClearIrqStatus,中断标志会一直挂在状态寄存器里,导致后续包全部无法进入。读 FIFO 时驱动内部会用 ReadBuffer 命令先读回长度字节,再读载荷,这与你往 FIFO 写的字节顺序一致。参数说明:buf 大小至少 256 字节,SX1268 FIFO 总共就 256 字节,这是硬件上限,别指望单包超过这个长度。
关于 CAD 唤醒,它用于低功耗接收前先探测信道是否有 LoRa 前导码,命中后再切到 RX。连续接收也能用,但电流会一直保持在 RX 状态,电池供电时吃不消。驱动里一般会有 SetCad 命令,但先用连续 RX 调通链路,再去做 CAD 省电更稳妥。
3.3 用逻辑分析仪确认 SPI 时序和 DIO1 中断
收发链路调不通时,别急着猜射频问题,先用逻辑分析仪抓四根信号:SCK、NSS、DIO1、MISO。重点看三件事:一是 NSS 低电平期间 SCK 有没有多余脉冲,二是 MISO 上的回读字节是否出现在正确位置,三是 DIO1 在发送完成后是不是稳定拉高。
SX1268 的读命令有个特点:发完命令和地址后,MISO 上的有效数据会在随后的第三个 SCK 周期开始返回来,驱动里如果用 HAL 的 TransmitReceive 同步收发,逻辑分析仪上能看到一次完整的 8 位交互。如果 MISO 一直为高,说明 GPIO 配置不对或 SPI 速率过快;如果数据错位,优先查 SPI 模式的 CPOL/CPHA。
BUSY 信号的检查也值得专门抓一下。SX1268 在执行某些命令时会拉高 BUSY 引脚,常见表现是:连续调用多条命令时,第二条命令的返回数据全 FF。这不是芯片坏了,而是上一条命令还没执行完。我一般会在每条命令前加一个 busy_poll 函数,最多等 10ms,超时返回错误,比固定延时 100us 靠谱得多。
4. SX1268 驱动调试避坑:五个常见翻车现象与修法
下面这几条是我和同行调 SX1268 时反复踩过的血泪经验。每条都是实际遇到过的现象,原因和修法都按排掉后的结论写,照着顺序查能少烧不少头发。
4.1 现象:频谱仪上频率偏了 3MHz,或者完全没信号
原因:八个里面有七个是晶振频率和 Fstep 换算系数没对上。SX1268 驱动里 SetRfFrequency 的系数如果还是按 26MHz 晶振算的,那配置 470MHz 时实际输出频率会偏出去。还有一个隐蔽原因是 TCXO 没配好:DIO3 没有配置成 TCXO 供电脚,芯片内部 RC 震荡替代了晶振,频率就飘了。
解决:先确认板子上晶振型号,再看驱动里 32MHz 换算系数是否匹配。然后初始化里调用 SetDIO3AsTCXOCtrl,把 DIO3 电压设成模块实际需要的值,同时把 TCXO 启动延时设到 10ms 以上,给晶振留够稳定时间。修完再用频谱仪点频验证,频率落在目标频点附近才算过。
4.2 现象:DIO1 中断永远不来,代码卡死在等待发送完成
原因:SX1268 的 DIO1 引脚在芯片内部可以映射到任意中断事件,默认映射不包含 TX_DONE 或 RX_DONE。你调用了 SetTx 却没用 SetDioIrqParams 把对应事件映射到 DIO1,GPIO 口当然永远不会拉高。这是从 SX1276 迁移过来最容易踩的坑,因为 SX127x 的 DIO0 映射规则完全不同。
解决:发送前调用 SetDioIrqParams,把 IRQ 掩码和 DIO1 掩码都设成你要的事件,比如 TX_DONE。设置完用读寄存器的方式确认映射寄存器 0x0C 和 0x0D 的值已经写入。如果还不触发,就用逻辑分析仪看 DIO1 引脚是否有毛刺,确认后检查 EXTI 的中断触发边沿和 NVIC 优先级配置。
4.3 现象:能接收但不能发送,或者发送距离缩水到几十米
原因:两个点。第一是 PA 配置不对,SX1268 的高功率路径需要 SetPaConfig 设置 duty cycle、HP max 和 LUT,没设置就只能以低功率模式工作,发射功率上不去。第二是同步字或 IQ 翻转配置不一致,接收端和发送端若一个默认 0x1424 一个被改过,丢包率会高得离谱。
解决:发送初始化里按模块参考设计配 PA,常见参数是 paDutyCycle=0x04、paHpMax=0x07、paLut=0x01、deviceSel=0x01。同步字用 WriteRegister 写寄存器 0x0740,收发两端都写成 0x1424。IQ 翻转保持一致,绝大多数模块默认不翻转,不要乱开 InvertIQ 开关。
4.4 现象:同一套代码从 SX1276 换到 SX1268 后丢包严重
原因:两个芯片的 FIFO 操作差异很大。SX127x 的 FIFO 映射到寄存器空间,写前要设地址;SX1268 用独立的 WriteBuffer/ReadBuffer 命令,FIFO 偏移从 0x00 开始。老的写发送缓冲函数没换,数据根本没写进 SX1268 的 FIFO,或者写错位置了。
解决:发送和接收都改成新版驱动里的 WriteBuffer 和 ReadBuffer 函数,不要再操作寄存器 0x00 地址那种老办法。改完后用回环模式验证:发送前把接收模式打开,让同块板子自己收自己发的包,通了再上两块板联调。
4.5 现象:SPI 偶发超时、读状态寄存器读出来全是 0
原因:SPI 时钟太高,或者 NSS 释放后到下一次操作的间隔太短。SX1268 对 SPI 时序有上限要求,超过约 16MHz 后的读回数据会出现位错误。另一个常见原因是 BUSY 没等,命令还在执行你就发了下一条,芯片直接忽略新命令。
解决:把 SPI 预分频调到 8MHz 或更低,SPI Mode 0。每次命令前加 busy_poll 等待,超时设 10ms。如果项目用的 RTOS,注意在 SPI 传输期间不要被高优先级任务打断,否则波形会被切开,推荐用互斥锁保护整条 SPI 总线的访问。
5. 上板验证:回环自检流程与 LoRa 参数速查
驱动不是“编译过就完事”,上板后先用回环自检把软硬件边界划清,再去做空中距离测试。回环自检的逻辑很简单:同块板子上同时跑发送和接收,自己发自己收,能收到就说明 SPI、FIFO、调制、中断全链路是通的。
5.1 先做回环自检再上距离
void radio_loopback_test(void) { sx126x_set_standby(SX126X_STDBY_RC); sx126x_calibrate_image(0x75, 0x81); // 430~510MHz 频段校准 sx126x_set_packet_type(SX126X_PACKET_TYPE_LORA); sx126x_set_rf_frequency(470000000); /* 先开接收,再发一包,看能否收到自己的数据 */ radio_start_rx(); radio_tx_packet((uint8_t*)"ping", 4); }逻辑说明:CalibrateImage 是按频段校准镜像抑制,SX1268 每次上电运行一次即可,参数 0x75 和 0x81 覆盖 430~510MHz,正好是国产模块常用的频段。先开接收再发送,避免发送期间 chip 还停在 TX 状态导致丢包。回环通过后,再把发送和接收放到两块板子上做空中验证,判断标准是 RSSI 稳定且 CRC 错误计数不增长。
5.2 调距离时用的参数速查表
| 用途 | 命令 | 参数要点 | 备注 |
|---|---|---|---|
| 设置频率 | 0x86 | 4 字节 RFfreq | 要先回 STDBY_RC |
| 配置 LoRa 调制 | 0x8B | SF、BW、CR、LDRO | SF 越大灵敏度越高 |
| 配置包格式 | 0x8C | 前导码、CRC、IQ | 收发两端必须一致 |
| 映射中断 | 0x08 | IRQ 掩码、DIO1 掩码 | DIO1 没有默认映射 |
| 发送/接收 | 0x83 / 0x82 | 3 字节超时 | 0xFFFFFF 表示不超时 |
| 频段校准 | 0x98 | 0x75、0x81 | 换频段必须重新校准 |
调距离时优先拉大扩频因子,SF 从 7 提到 9、10,接收灵敏度提升明显,但数据速率也会降下来。带宽同理,125kHz 比 500kHz 更抗噪但速率低。这些参数在驱动里就是 SetModulationParams 的几个字节,改了之后重新跑一遍回环确认两端配置一致再出门测距离。
第一次用 SX1268 做 470MHz 模块时,我调好了频率,频谱仪上也能看到点频,但实际对传距离始终只有几十米,后来才发现是忘了做 CalibrateImage,接收灵敏度掉了好几个 dB。从那以后我每次换频段都强制走一遍 CalibrateImage 加频谱仪点频确认,再进联调,这套习惯帮我避开了后来好几次隐蔽问题。希望帮到你。
本文还有配套的精品资源,点击获取