十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SX1268 LoRa驱动开发:从SPI初始化到收发闭环

SX1268 LoRa驱动开发:从SPI初始化到收发闭环 简介面向SX1268射频收发器与STM8单片机联调的参考工程来自CSDN下载频道的demo代码包适合正在开发LoRa远距离传输、FSK/OOK物联网节点的嵌入式开发者。SX1268支持扩频调制与最高22dBm发射功率代码中涉及SPI接口配置、初始化参数设置、数据包收发与中断处理等实用环节能够帮助理解芯片寄存器操作与状态机管理进而快速移植到自有硬件平台。包内共37个文件约114KB以C源码、头文件、编译中间文件o/ls及工程配置为主包含编译生成的s19、map与调试信息结构虽精简但功能闭环可在STM8环境中直接评估。目前已有570人学习浏览特别适合初次接触SX1268的开发者按实际需求裁剪复用缩短无线驱动开发周期。1. SX1268 demo code 是给主控单片机的 client 驱动骨架拿到一块 SX1268 的射频小板第一件事往往不是画板而是让单片机把它“开口说话”。很多人会把标题里的 SX1268 demo code 当成芯片自带固件实际上 SX1268 是一颗 sub-GHz 收发器所有调制、收发的动作都要由外部主控单片机通过 SPI 下发命令来控制。这种代码通常叫 client 侧代码跑在 STM32、STC、51 这类单片机上负责初始化芯片、拼装数据包、处理中断。如果你正在从 SX1276 迁移或者第一次调试 LoRa 收发链路这套骨架能帮你避开最容易被文档带偏的几个坑BUSY 时序、校准顺序、频率字换算。下面按我平时调 SX1268 的顺序把初始化、收发闭环和验证方法一次说清。2. SX1268 的 SPI 命令集与上电初始化流程SX1268 不像 51 单片机或 STM32 的外设那样有一大片可直接访问的寄存器它对外只有一个命令接口。所谓“写寄存器”在 SX1268 上实际是发一条命令帧命令帧由操作码 opcode 和固定长度的参数组成。这个设计让芯片内部逻辑更简单但也要求驱动代码在发每条命令前都检查 BUSY 引脚BUSY 拉高时芯片还在处理上一条命令此时不能发下一条。很多第一次调 SX1268 的人把驱动写成“连续 SPI 写”结果命令被吞掉频率、中断配置看起来写了但实际没生效。2.1 读懂 SX1268 的四类 SPI 命令要写 client 侧代码不需要背全部命令但下面这四类必须分清配置类命令负责设置调制、频率、数据包格式操作类命令负责启动发射、进入接收、休眠查询类命令负责读中断状态、RSSI、CRC 错误数缓冲区命令负责读写待发送或刚收到的 payload。用惯了传统寄存器映射的工程师可以把它理解成“一个可以收发数据的串口外设”先配置再操作操作完成后查状态。命令类别典型命令opcode作用配置类SetStandby / SetPacketType / SetModulationParams0x80 / 0x8A / 0x8B设置时钟、调制方式、扩频参数配置类SetRfFrequency / SetPacketParams0x86 / 0x8C设置载波频率、帧格式操作类SetTx / SetRx / SetSleep0x83 / 0x82 / 0x84发射、接收、休眠查询类GetIrqStatus / GetPacketStatus / GetStats0x12 / 0x14 / 0x10读中断标志、RSSI、统计信息缓冲类WriteBuffer / ReadBuffer / SetBufferBaseAddress0x0E / 0x1E / 0x8F读写内部 FIFO校准类Calibrate / CalibrateImage0x89 / 0x98频率校准和镜像频段校准这里最常被忽略的是 ClearIrqStatusopcode 0x02。中断标志不会自动清零必须在处理完数据后手动清除否则 DIO1 一直保持高电平下一次中断会被吞掉。我一般把 ClearIrqStatus 放在中断服务函数的最后一步而不是进中断就清避免清了标志但数据还没读出来。2.2 上电校准与最小初始化 shell拿到新板子我习惯先跑一遍最小初始化不接射频天线只看 SPI 通信和中断引脚是否正常。下面这段骨架代码可以在 STM32 上直接套用换成 STC 或 51 时只需要改底层的 SPI 读写函数/* client_sx1268.c 最小初始化骨架主控为 STM32 或同类单片机 */ void sx1268_init(void) { /* 1. 硬件复位让芯片回到已知状态 */ HAL_GPIO_WritePin(RST_PORT, RST_PIN, GPIO_PIN_RESET); delay_ms(5); HAL_GPIO_WritePin(RST_PORT, RST_PIN, GPIO_PIN_SET); delay_ms(10); /* 2. 每次发命令前先等 BUSY 拉低 */ sx126x_wait_busy(); /* 3. 进入待机模式选用 RC 时钟 */ sx126x_set_standby(STDBY_RC); /* opcode 0x80 */ /* 4. 全频段校准 */ sx126x_calibrate(0x7F); /* 0x7F 表示校准所有模块 */ /* 5. 切到晶体振荡器后续收发以 TCXO 为基准 */ sx126x_set_standby(STDBY_XOSC); /* 6. 如果板载 TCXO必须让 DIO3 输出供电 */ sx126x_set_dio3_as_tcxo_ctrl(0x01); /* 7. 如果板上有射频收发切换开关让 DIO2 代为控制 */ sx126x_set_dio2_as_rf_switch_ctrl(0x01); /* 8. 选中 LoRa 调制 */ sx126x_set_packet_type(LORA); /* opcode 0x8A参数 0x01 */ /* 9. 设频率433.92MHz */ sx126x_set_rf_frequency(433920000UL); /* 10. 发射功率取 20dBm留余量避免谐波超标 */ sx126x_set_tx_params(20, RADIO_RAMP_200_US); /* 11. LoRa 调制参数SF9 / 125kHz / CR4/5 */ sx126x_set_modulation_params(SF9, BW_125_KHZ, CR_4_5, 1); /* 12. 数据包参数显式抬头可变长度CRC 开启 */ sx126x_set_packet_params(8, LORA_PKT_VARIABLE_LEN, 0, LORA_CRC_ON, LORA_IQ_NORMAL); /* 13. 打开需要的中断源并全部映射到 DIO1 */ sx126x_set_dio_irq_params(IRQ_TX_DONE | IRQ_RX_DONE | IRQ_RX_TIMEOUT, IRQ_TX_DONE | IRQ_RX_DONE | IRQ_RX_TIMEOUT, 0, 0); }这段代码有两点值得说明。第一第 6 步和第 7 步的顺序不能颠倒DIO3 供电必须在 DIO2 设置为射频开关控制之前做完否则 TCXO 可能在上电瞬间工作在不稳定状态。第二第 11 步的最后一个参数是低速率优化开关在 SF11、SF12 且带宽 125kHz 时必须置 1否则接收灵敏度会明显下降。SF9 / 125kHz 组合下该参数可设 0 也可设 1标准做法是设为 1 以获得更好的抗频偏能力。2.3 频率字、调制参数与 packet 参数的换算细节SX1268 的频率寄存器不是直接填 MHz而要按公式转换频率字 目标频率 × 2^25÷ 32MHz。32MHz 是芯片内部参考晶振频率。很多人直接把 433920000 拆成三个字节填进去结果载波偏了几十 kHz收发双方都收不到。正确做法是用 64 位变量计算void sx126x_set_rf_frequency(uint32_t freq_hz) { /* 用 64 位避免 32 位乘法溢出 */ uint64_t rf ((uint64_t)freq_hz 25) / 32000000ULL; uint8_t b[3]; b[0] (uint8_t)(rf 16); b[1] (uint8_t)(rf 8); b[2] (uint8_t)(rf); /* opcode 0x86后跟 3 字节频率字 */ sx126x_cmd(0x86, b, 3); }这套换算逻辑对 SX1261、SX1262、SX1268 都适用也是标题里client_sx126*这套命名能复用的原因。调制参数里 SF 可设 5 到 12带宽可选 7.8kHz 到 500kHz但并非所有组合都合理工程上常用的是 125kHz 带宽与 SF7 到 SF12 的组合。带宽越窄、SF 越高灵敏度越好但空中时间成倍拉长对晶振频偏也更敏感。参数组合灵敏度典型值单字节空中时间适用场景SF7 / 125kHz约 -123dBm短数据量大、节点密集SF9 / 125kHz约 -129dBm中通用传感器上报SF12 / 125kHz约 -137dBm长远距离、低速率SF7 / 500kHz约 -116dBm极短高速透传packet 参数里最容易混淆的是显式抬头和隐式抬头。显式抬头模式下每一帧前面会附带 SF、CRC 等信息接收端无需预先知道负载长度适合可变长度数据隐式抬头模式不发送这些信息接收端必须提前知道负载长度否则会一直等下去。大多数 demo code 用显式抬头能少踩很多坑。2.4 初始化顺序里最容易漏的两项配置TCXO 配置漏掉后果是发射功率正常但接收灵敏度差或者频率漂移导致两个模块明明写的是同一频率却互相收不到。判断方法是读 GetStats 里的 CRC 错误数如果错误率很高但 RSSI 又正常优先怀疑 DIO3 没有正确给 TCXO 供电。DIO2 配置漏掉的影响则更隐蔽板子上如果有射频收发切换开关而 DIO2 没有接管控制发射时开关可能仍停留在接收通路射频能量被开关吃掉大半表现为“发得出但对方收不到”。3. 从发送到接收client 代码的最小收发闭环初始化只是让芯片“醒着”真正的闭环是发一包、对方收到、再回一包。这个闭环里最核心的不是 SPI 读写而是中断标志的处理顺序。SX1268 内部只有一个中断状态寄存器DIO1 只是它的映射出口所以 driver 必须自己维护一个状态变量记录当前是等 TX_DONE 还是 RX_DONE这样才能在同一个中断回调里区分事件。3.1 发送一帧 LoRa 数据的完整流程发送一个 payload 需要四步把 FIFO 写指针定位到 0写入数据设置 packet 参数最后启动发射。下面是一段可在阻塞式 demo 里直接跑的发送函数uint8_t lora_buf[64]; int sx1268_client_send(uint8_t *data, uint8_t len) { /* 1. 定位 FIFO 写入位置一般固定从 0 开始 */ sx126x_set_buffer_base_address(0x00, 0x00); /* 2. 写入 payload */ sx126x_write_buffer(0x00, data, len); /* 3. 数据包参数8 字节前导码可变长度CRC on */ sx126x_set_packet_params(8, LORA_PKT_VARIABLE_LEN, len, LORA_CRC_ON, LORA_IQ_NORMAL); /* 4. 启动发射timeout0 表示不限时 */ sx126x_set_tx(0); /* 5. 阻塞等待 DIO1产品代码里应改为中断 超时 */ while (HAL_GPIO_ReadPin(DIO1_PORT, DIO1_PIN) GPIO_PIN_RESET); /* 6. 读中断状态并清除 */ uint16_t irq sx126x_get_irq_status(); sx126x_clear_irq_status(irq); return (irq IRQ_TX_DONE) ? 0 : -1; }第 3 步的 len 参数只对可变长度模式有效如果初始化时选的是隐式抬头这里填的 len 会被忽略接收端必须在 own 的 packet 参数里预先配置同样长度。第 4 步的 SetTx 参数是超时时间单位是 15.625 微秒0 代表单次发送不超时。对 demo 来说发送是瞬时动作用 0 没问题但在电池供电的场景里为了不让 MCU 死等我一般给 500ms 量级的超时超时后清中断并返回失败。3.2 接收状态机与 RX 超时参数接收比发送复杂因为接收可以是单次、超时或连续三种模式。SetRx 的超时参数三字节单位同样是 15.625 微秒0 表示单次接收收到一帧就自动回到 standby非 0 表示接收持续指定时间后触发 RX_TIMEOUT。连续接收要指定RX_CONTINUOUS宏它会忽略超时值一直监听。实际项目中不建议在中断里做复杂逻辑而是用一个简单的状态机把收发事件串起来typedef enum { ST_IDLE, ST_TX_WAIT, ST_RX_WAIT } radio_state_t; radio_state_t radio_state ST_IDLE; void on_dio1_irq(void) { uint16_t irq sx126x_get_irq_status(); if (irq IRQ_TX_DONE) { radio_state ST_IDLE; tx_done_flag 1; } else if (irq IRQ_RX_DONE) { /* 收到数据后先读 FIFO再清标志 */ sx126x_read_buffer(0x00, lora_buf, rx_len); rx_done_flag 1; radio_state ST_IDLE; } else if (irq IRQ_RX_TIMEOUT) { radio_state ST_IDLE; rx_timeout_flag 1; } /* 最后统一清中断 */ sx126x_clear_irq_status(irq); }这里的关键点是“先读 FIFO 再清中断”。如果先执行 ClearIrqStatusIRQ_RX_DONE 虽然清除但 FIFO 里的数据还在所以读数据本身没问题问题在于如果下一次接收很快到来新数据会覆盖 FIFO而驱动还没把上一次数据读走。对低速传感器网络这可能几年来不了一次但一旦发生就是定位很痛苦的偶发抖动。3.3 把收发包装成一个 client API为了让上层应用不感知 SX1268 的存在我一般会把上面的发送、接收、状态查询封装成四个函数sx1268_client_init()、sx1268_client_send()、sx1268_client_recv()、sx1268_client_get_rssi()。这个封装层就是标题里 client 的含义——它是跑在主控单片机上的驱动接口不是 SX1268 自身的东西。接收函数通常采用“启动接收后立刻返回由中断置标志”的方式主循环里轮询标志。这样做的好处是单片机在等待无线数据的同时还能处理按键、采集 ADC、刷新显示。3.4 中断标志读取与清除顺序的坑SX1268 的中断标志是累积式的TX_DONE 和 RX_DONE 可能同时置位。比如在连续接收模式下上一帧刚处理完下一帧已经到达DIO1 会一直保持高电平。如果中断服务函数入口处先去 GetIrqStatus 再 ClearIrqStatusDIO1 会短暂拉低后又被新中断拉高看起来像中断风暴。正确的做法是每次只处理自己关心的那一位处理完保留其他位不清除。ClearIrqStatus 的参数是 mask传入哪位就清哪位不是“写入的全部标志都清”。4. 调制参数选择与功耗权衡SX1268 的 LoRa 调制参数直接影响通信距离和电池寿命而且这些参数不是孤立的。很多工程师拿到 demo code 后只改频率SF、带宽、CRC 全部保持默认结果要么距离不够要么一包数据在空中占了几百毫秒把低功耗设计全毁了。这一节把参数之间的关系和落地选择讲清楚。4.1 SF、BW、CR 对灵敏度和空中时间的影响LoRa 的扩频因子 SF 决定每个符号承载的比特数SF 每增加 1接收灵敏度提高约 2 到 3dB但数据速率下降一半空中时间近似翻倍。带宽 BW 则决定频谱占用和接收机噪声底带宽减半灵敏度提高约 3dB数据速率同样减半。编码率 CR 是前向纠错冗余从 4/5 提高到 4/8 能增强抗干扰能力但有效数据速率降到原来的 60%。一个粗略的估算关系是数据速率约等于BW × SF ÷ 2^SFbps空中时间 帧符号数 × 符号时间。同样是 10 字节 payloadSF7 / 125kHz 大约 30 毫秒SF12 / 125kHz 则超过 600 毫秒。对电池供电的传感器节点这个差距意味着同样 duty cycle 限制下SF12 的可上报次数只有 SF7 的 1/20。所以低功耗设计里SF9 往往是比 SF12 更优的选择——距离损失不大但功耗和控制信道占用好得多。4.2 低功耗SX1268 的休眠与周期唤醒SX1268 的休眠模式通过 SetSleep 命令进入opcode 0x84参数里可以配置唤醒方式为 GPIO 或 SPI 命令。进入休眠后芯片功耗降到 0.6 微安左右只有 RTC 和唤醒逻辑保持工作。如果节点需要周期上报一种成熟方案是让单片机也进入低功耗然后用 SX1268 的定时唤醒功能在指定时间拉高 DIO1 把单片机唤醒。这套机制在 demo code 里通常不会写全因为它依赖外部中断和休眠协议的配合但系统集成时非常关键。/* 进入休眠保留 RC 时钟支持定时唤醒 */ void sx1268_enter_sleep(void) { uint8_t cfg 0x04; /* 保留 RC 用于唤醒定时器 */ sx126x_cmd(0x84, cfg, 1); }4.3 实际项目中常见的三种误用第一种是发射功率写满 22dBm。SX1268 标称最大输出 22dBm但很多模块的匹配电路和电源纹波处理并不支持长期满功率发射表现为电压跌落导致复位或频率牵引。我一般在 demo 里默认写 20dBm留出 2dB 的余量。第二种是低频段没做镜像校准SX1268 的高频段校准在 430MHz 附近可能偏差较大需要调用 CalibrateImage 指定频段。第三种是在声明SetModulationParams时把低速率优化位固定写成 0导致 SF11 / SF12 在 125kHz 带宽下灵敏度劣化。5. 验证 SX1268 client 代码是否跑通的三个技巧5.1 回环测试两块板子对发验证 client 驱动最直接的方法是拿两块板子一块运行发送函数另一块运行接收函数发送端每 500ms 发一包递增序号接收端把序号和接收时间打印到串口。先在同一张桌子上测确认能连续收到 100 包不乱序再拉开距离测。如果近距离正常、远距离丢包问题大概率在射频匹配或天线而不是 SPI 驱动。5.2 用 RSSI 和 SNR 定位链路劣化接收完成后调用 GetPacketStatus 可以读出最后一包的 RSSI 和 SNR。RSSI 表示信号强度SNR 表示信噪比。两者同时偏低说明信号本身弱RSSI 偏高但 SNR 很低说明附近有同频干扰或频谱占用严重。对 433MHz 频段RSSI 在 -120dBm 以下还能正确收包说明接收链路没有大问题。void sx1268_read_packet_snr_rssi(int16_t *rssi, int8_t *snr) { uint8_t buf[4]; /* opcode 0x14: GetPacketStatus */ sx126x_cmd_read(0x14, buf, 4); *rssi -buf[2] / 2; /* RSSI 单位为 0.5dBm */ *snr (int8_t)buf[1] / 4; /* SNR 单位为 0.25dB */ }5.3 用芯片统计信息判断天线问题还是配置问题GetStats 命令能读出接收 CRC 错误数、接收字节数、前导码错误数。如果 CRC 错误数很高而 RX_DONE 也很多说明芯片收到了完整 LoRa 帧但数据被破坏优先检查天线和干扰。如果前导码错误数高但 RX_DONE 很少说明双方的 SF、带宽或频率不一致这是配置问题而不是信号问题。调 SX1268 时我最常用这个统计量因为它能直接区分“没收到”和“收到但坏了”两种情况省去盲调参数的功夫。本文还有配套的精品资源点击获取
返回列表