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就是差一倍的距离啊。
3.2 发送模式与接收模式:切换的艺术
LoRa的收发模式切换,说白了就是改一个寄存器位。但时机很重要,搞不好就会丢包。
发送模式流程:
- 清空FIFO:写
REG_OP_MODE进入待机模式 - 写入数据:把要发的字节依次写入
REG_FIFO - 设置长度:写
REG_PAYLOAD_LENGTH - 触发发送:写
REG_OP_MODE进入发送模式 - 等待完成:轮询
REG_IRQ_FLAGS的TxDone位,或等DIO0中断
接收模式更简单:
- 进入待机模式
- 写
REG_OP_MODE进入连续接收模式 - 等DIO0中断,读
REG_IRQ_FLAGS确认RxDone - 从FIFO读出数据
嗯,这里要注意:发送完成后,芯片会自动回到待机模式。但接收完成后,它还在接收模式。如果你想连续收,就不用管;如果收完要发,得先切回待机再切发送。
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后,误码率从万分之一降到了百万分之一以下。
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公里。
好了,这一章的内容就到这儿。寄存器配置、收发模式、数据包结构、CRC校验、距离测试,这五块你都掌握了,LoRa点对点通信就算入门了。下一章咱们聊聊怎么把多个阀门组网,实现一对多控制。