4. LoRaWAN MAC层帧结构与消息类型
LoRaWAN 的物理层只负责把比特可靠地送出去,真正决定“谁能发、什么时候发、发完要不要回执、网络怎么下发参数”的,是 MAC 层。理解 MAC 帧结构,是后面计算网络开销、评估容量的前提:每一字节的 MAC 头、MIC、FOpts 都会占用空中时间,而空中时间是 LoRaWAN 最稀缺的资源。
一、PHYPayload 的整体结构
LoRaWAN 上行和下行报文在 MAC 层统称 PHYPayload,由三部分组成:MHDR(1 字节)、MACPayload(可变长)、MIC(4 字节)。MHDR 的高 3 位是 MType,低 3 位是 Major 版本号(当前为 0),中间保留。MType 直接决定了这条消息是入网请求、数据上行、确认下行还是 MAC 命令,是解析一切帧的入口。
MACPayload 内部又分三段:FHDR(帧头)、FPort(端口,可省略)、FRMPayload(应用载荷)。FHDR 包含 4 字节 DevAddr、1 字节 FCtrl、2 字节 FCnt 以及 0~15 字节的 FOpts。FCtrl 是控制核心:ADR 位表示是否启用自适应速率,ADRACKReq 用于请求网络下发下行确认,ACK 位表示本帧是对上一条确认帧的应答,FPending 位(仅下行)表示网络还有数据待发,最后 4 位 FOptsLen 指明 FOpts 长度。
这里有一个必须记住的约束:FOpts 与 FRMPayload 不能同时非空。若 FOptsLen>0,则 FPort 必须为 0,FRMPayload 为空;若 FPort>0,则 FOptsLen 必须为 0。原因是二者共用同一套加密与完整性保护机制,混用会导致密钥和计数器语义冲突。这条规则直接影响 MAC 命令的发送方式——要么搭在数据帧的 FOpts 里“顺路”下发,要么单独发一条 FPort=0 的 MAC 命令帧。
二、上行与下行帧的差异
上行帧(Unconfirmed/Confirmed Uplink)的 FHDR 中,FCtrl 的 FPending 位无意义,ADRACKReq 由终端置位以“催”网络回一条下行;FCnt 是终端自己的上行帧计数器,32 位,但空中只传低 16 位,高 16 位由两端各自维护,靠 MIC 校验来发现失步。下行帧(Unconfirmed/Confirmed Downlink)的 FCtrl 中 FPending 有效,表示网络还有后续下行排队;FCnt 是网络侧的下行计数器,与上行计数器相互独立。
帧计数器是安全与容量的双重关键。每次发送 FCnt 递增,接收端拒绝重复计数,从而防重放。但 32 位计数器在高速率下会耗尽:若终端以 SF7、每分钟 1 帧持续发送,约 2^32 分钟(约 8000 年)才回绕,实际可忽略;真正的问题是 16 位空中字段在丢帧后容易失步,需要靠 MIC 校验和重同步机制恢复。
三、MAC 命令与 FOpts 的取舍
MAC 命令(MACCommand)用于网络与终端之间的控制交互,常见的有 LinkCheckReq/Ans、LinkADRReq/Ans、DutyCycleReq/Ans、RXParamSetupReq/Ans、DevStatusReq/Ans、NewChannelReq/Ans、RXTimingSetupReq/Ans、TxParamSetupReq/Ans、DlChannelReq/Ans 等。每条命令由 1 字节 CID 加若干参数字节组成。
MAC 命令有两种承载方式:搭在数据帧的 FOpts 中,或单独作为 FPort=0 的帧发送。前者不额外消耗一次发送机会,但 FOpts 最多 15 字节,且会挤占应用载荷空间;后者灵活但增加一次完整的空中传输。工程上的经验是:把 LinkADRReq、RXParamSetupReq 这类“顺路”命令尽量塞进 FOpts,把需要长参数的 NewChannelReq 单独发。
下面给出一个 FOpts 承载 MAC 命令的示例,展示 LinkADRReq 的 4 字节参数如何拼装:
// LinkADRReq 载荷(CID=0x03,共 4 字节参数)
// DataRate_TXPower: 高4位=DR,低4位=TXPower
// ChMask: 16位信道掩码
// Redundancy: 高4位=ChMaskCntl,低4位=NbTrans
uint8_t linkAdrReq[5];
linkAdrReq[0] = 0x03; // CID
linkAdrReq[1] = (dr << 4) | txp; // 例: DR=5, TXP=0 -> 0x50
linkAdrReq[2] = chMask & 0xFF; // ChMask 低字节
linkAdrReq[3] = (chMask >> 8) & 0xFF;
linkAdrReq[4] = (chMaskCntl << 4) | nbTrans; // 例: 0x01
// 放入上行帧 FOpts,FCtrl.FOptsLen = 5
// 此时 FPort 必须为 0,FRMPayload 为空
四、确认帧与入网流程
Confirmed 帧要求接收方回一条 ACK=1 的帧。注意:ACK 不是独立消息类型,而是搭在反向帧的 FCtrl.ACK 位上。终端发 Confirmed Uplink 后,网络在下一个下行窗口回一条 ACK=1 的下行;若终端在 RX1/RX2 都没收到,会按 NbTrans 重传。重传次数直接放大网络开销,因此 Confirmed 帧应谨慎使用,通常只用于关键配置或告警。
入网流程分 OTAA 与 ABP。OTAA 的 Join-Request 由 JoinEUI(8B)+DevEUI(8B)+DevNonce(2B) 组成,用 AppKey 做 MIC;Join-Accept 由 AppNonce(3B)+NetID(3B)+DevAddr(4B)+DLSettings(1B)+RxDelay(1B)+可选 CFList 组成,用 AppKey 加密。一次 OTAA 至少消耗 1 次上行 + 1 次下行,且 Join-Request 使用固定 DR,是网络启动阶段的主要开销来源。
五、对网络开销与容量的影响
把上述机制折算成容量账:假设某网关在 SF7 下每小时可容纳约 5 万条 29 字节上行(仅为示例量级,实际取决于占空比与并发),若其中 10% 使用 Confirmed 帧并平均重传 2 次,等效上行量增加约 10%;若大量终端在入网阶段集中发 Join-Request,则短时间内会挤占数据信道。因此调优的三个抓手是:控制 Confirmed 帧比例、把 MAC 命令尽量并入 FOpts、合理设置 NbTrans 与 ADR。ADR 让靠近网关的终端升到高速率,是提升整网容量最有效的手段,而 ADRACKReq 与 LinkADRReq 正是通过 FCtrl 和 MAC 命令协同完成的。