3、LoRa点对点通信基础:SX1278寄存器配置、发送模式与接收模式、数据包结构设计、CRC校验实现、通信距离测试

好,咱们今天来啃一块硬骨头——LoRa点对点通信。说实话,SX1278这颗芯片我用了快十年了,从最早的抄板调试到现在量产,踩过的坑能写满一本笔记本。但你别怕,只要把寄存器配置、收发模式、数据包结构这几个核心点吃透,剩下的就是调参数、跑测试了。

3.1 SX1278寄存器配置:从复位到初始化

SX1278的寄存器有上百个,但真正需要你手写的,其实就十几个。我个人习惯先把芯片复位,再按顺序配置频率、调制参数、功率和IO模式。

先看一段初始化代码,我加了详细注释:

// 复位SX1278
SX1278_Write(REG_OP_MODE, 0x80);  // 进入睡眠模式
delay_ms(10);
SX1278_Write(REG_OP_MODE, 0x00);  // 退出睡眠

// 配置频率:433MHz
uint64_t frf = (uint64_t)433000000 * (1 << 19) / 32000000;
SX1278_Write(REG_FRF_MSB, (frf >> 16) & 0xFF);
SX1278_Write(REG_FRF_MID, (frf >> 8) & 0xFF);
SX1278_Write(REG_FRF_LSB, frf & 0xFF);

// 配置LoRa调制参数
SX1278_Write(REG_MODEM_CONFIG_1, 0x72);  // BW=125kHz, CR=4/5, 隐式模式关
SX1278_Write(REG_MODEM_CONFIG_2, 0x74);  // SF=7, CRC使能, 发送单次
SX1278_Write(REG_MODEM_CONFIG_3, 0x04);  // AGC自动增益控制

// 配置发射功率:20dBm
SX1278_Write(REG_PA_CONFIG, 0xFF);  // PA_BOOST + 最大功率
SX1278_Write(REG_PA_DAC, 0x84);     // 开启+20dBm模式

// 配置DIO0映射到发送完成/接收完成中断
SX1278_Write(REG_DIO_MAPPING_1, 0x00);  // DIO0: TxDone/RxDone

这里有个坑,我当年第一次调的时候,忘了配置REG_PA_DAC,结果发射功率只有17dBm,死活连不上。你想想看,差3dB就是差一倍的距离啊。

注意:配置频率时,晶振频率一定要精确。SX1278内部用的是32MHz晶振,如果偏差超过30ppm,通信距离会明显缩短。我建议用温补晶振(TCXO),贵几块钱但省心。

3.2 发送模式与接收模式:切换的艺术

LoRa的收发模式切换,说白了就是改一个寄存器位。但时机很重要,搞不好就会丢包。

发送模式流程:

  1. 清空FIFO:写REG_OP_MODE进入待机模式
  2. 写入数据:把要发的字节依次写入REG_FIFO
  3. 设置长度:写REG_PAYLOAD_LENGTH
  4. 触发发送:写REG_OP_MODE进入发送模式
  5. 等待完成:轮询REG_IRQ_FLAGS的TxDone位,或等DIO0中断

接收模式更简单:

  1. 进入待机模式
  2. REG_OP_MODE进入连续接收模式
  3. 等DIO0中断,读REG_IRQ_FLAGS确认RxDone
  4. 从FIFO读出数据

嗯,这里要注意:发送完成后,芯片会自动回到待机模式。但接收完成后,它还在接收模式。如果你想连续收,就不用管;如果收完要发,得先切回待机再切发送。

我的经验:收发切换之间,至少留1ms的延时。我曾经在快速切换时丢过包,后来发现是芯片内部状态机还没稳定。加个2ms延时,问题就解决了。

3.3 数据包结构设计:别让协议成为瓶颈

LoRa的物理层数据包很简单:前导码 + 可选报头 + 有效载荷 + CRC。但应用层怎么设计,学问就大了。

我常用的数据包结构是这样的:

// 数据包格式(总长不超过255字节)
typedef struct {
    uint8_t  preamble;      // 固定0xAA, 用于同步
    uint8_t  dest_addr;     // 目标地址
    uint8_t  src_addr;      // 源地址
    uint8_t  msg_id;        // 消息序号, 用于去重
    uint8_t  cmd;           // 命令类型: 0x01=开阀, 0x02=关阀, 0x03=查询
    uint8_t  payload_len;   // 有效数据长度
    uint8_t  payload[64];   // 有效数据
    uint16_t crc16;         // CRC16校验, 覆盖从dest_addr到payload
} LoRaPacket;

为什么要有msg_id?我在项目里遇到过重复执行命令的问题——接收端收到了两次相同的开阀指令,结果阀门开了又关。加上msg_id后,接收端只处理最新序号的消息,老消息直接丢弃。

地址字段也很关键。点对点通信虽然只有两个节点,但万一以后扩展呢?我习惯把地址0x00保留为广播地址,所有节点都能收到。

3.4 CRC校验实现:别让噪声骗了你

SX1278硬件自带CRC,但那是物理层的,只校验有效载荷。我建议应用层再加一层CRC,覆盖整个数据包(包括地址和命令)。

下面是我常用的CRC16实现,查表法,速度快:

// CRC16查表法, 多项式0x8005
uint16_t crc16_update(uint16_t crc, uint8_t data) {
    crc ^= (uint16_t)data << 8;
    for (int i = 0; i < 8; i++) {
        if (crc & 0x8000)
            crc = (crc << 1) ^ 0x8005;
        else
            crc <<= 1;
    }
    return crc;
}

// 计算整个包的CRC
uint16_t calc_packet_crc(LoRaPacket *pkt) {
    uint16_t crc = 0xFFFF;
    uint8_t *ptr = (uint8_t *)&pkt->dest_addr;
    uint16_t len = 3 + 1 + 1 + pkt->payload_len;  // dest + src + msg_id + cmd + payload
    for (uint16_t i = 0; i < len; i++) {
        crc = crc16_update(crc, ptr[i]);
    }
    return crc;
}

接收端收到包后,先算CRC,再跟包尾的CRC比较。不一致就直接丢弃,不处理。我曾经遇到过一种情况:电磁干扰导致数据包中间某位翻转,但硬件CRC没检测出来(因为硬件CRC只覆盖部分字段)。加上应用层CRC后,误码率从万分之一降到了百万分之一以下。

核心原则:永远不要信任无线信道。CRC校验是最后一道防线,宁可丢包,不能错包。

3.5 通信距离测试:理论 vs 现实

好了,代码写完了,该拉出去溜溜了。通信距离测试,我建议分三步走:

  • 第一步:室内摸底。同一房间,距离5米,看能不能正常收发。这一步主要是验证硬件和代码有没有低级错误。
  • 第二步:室外视距。找个空旷的公园或操场,从50米开始,每次增加50米,直到丢包率超过10%。记录下每个距离的RSSI和SNR。
  • 第三步:非视距测试。找有建筑物遮挡的环境,比如小区里隔两栋楼。这时候你会发现,距离可能只有视距的十分之一。

我整理了一份典型测试数据,供你参考:

距离(米) 环境 RSSI(dBm) SNR(dB) 丢包率
100 视距 -85 12 0%
500 视距 -105 6 1%
1000 视距 -118 2 5%
200 非视距(隔1栋楼) -110 4 3%
500 非视距(隔2栋楼) -125 -2 20%

看到没?非视距下,500米就20%丢包率了。所以实际部署时,我建议留出50%的余量。比如你需要控制500米外的阀门,那至少保证视距下能通1公里。

避坑指南:测试时别忘了带个笔记本,每测一个点就记下来。我曾经偷懒没记,结果第二天忘了哪个点测过了,又跑了一遍。还有,天线方向也很重要——SX1278用的是1/4波长单极子天线,垂直放置效果最好。

好了,这一章的内容就到这儿。寄存器配置、收发模式、数据包结构、CRC校验、距离测试,这五块你都掌握了,LoRa点对点通信就算入门了。下一章咱们聊聊怎么把多个阀门组网,实现一对多控制。