我正在陆续对《LoRaWAN102》即LoRaWAN协议规范 V1.0.2 版本(2016年7月定稿)协议的各个章节进行翻译。译文之外还对LoRaWAN协议和源码进行了解析,可点此查看帖子LoRa学习笔记_汇总。
欢迎同行朋友们留言交流。
本文作者twowinter,转载请注明作者:http://blog.csdn.net/iotisan/
LoRaWAN第4章,主要讲述了MAC帧格式,对所有涉及的字段都做了解释。
千言万语汇成一句话,哦不,汇成一个表。
数据帧头 | DevAddr | FCtrl | FCnt | FOpts | ||||||||
数据帧 | Preamble | PHDR | PHDR_CRC | MHDR | FHDR | FPort | FRMPayload | MIC | CRC | |||
MAC层 | Preamble | PHDR | PHDR_CRC | MHDR | MACPayload | MIC | CRC | |||||
PHY层 | Preamble | PHDR | PHDR_CRC | PHYPayload | CRC |
好了,帧格式是大家随手都能看到的东西,本尊作为IoT小能手,如果不能提出一些稍有深度的信息增量,就对不起这个称号了。所以,有些协议设计层面的心得要分享下:
特别酷的ADR(速率自适应)机制
这个章节中最亮眼的莫过于速率自适应机制,简直是为LoRa网络量身定做的:一旦使能了FCtrl中的ADR位,距离近信号好的节点用高速率,距离远信号弱的节点用低速率,不小心被调高了速率,则自动降下来。这样,尽可能地提高了传输速率,也有效提高了网络容量。我已经见过不少厂家,拿这个协议的公知特点当产品卖点了。
可同时携带数据和命令的MAC帧
一般来说,应用除了数据,出于管理需要,肯定还会涉及命令。比如基站要查询节点状态,或者节点要请求变更信道等。所以LoRaWAN协议设计上利用FOpts把数据和命令揉在一个MAC帧里,这样可以提高交互效率,有效地降低功耗。这在寸土寸金,哦不,寸库仑(电量单位)寸金的物联网应用中,是一个很有必要的设计。
这章的处理基本都在 \src\mac\LoRaMac.c 中,下面按照MAC帧格式的字段逐个解析下。
在LoRaWAN的数据API中处理了MHDR,这个字段内容比较少,就按需选择了消息类型是confirm还是unconfirm。
另外在管理API中的Join-Req的消息类型。
具体可见 LoRaMacMcpsRequest() 和 LoRaMacMlmeRequest() 这两个函数。
MACPayload 的组帧都在 PrepareFrame() 这个函数中处理,将macHdr和macPayload的fCtrl、FPort、FRMPayload都传递进去,完成整个MAC层的数据组帧。
LoRaMacBuffer就存放了MACPayload的数据,这个变量的组帧和协议字段定义是一一对应。MACPayload的组帧处理,在大流程上是对join和数据两种类型的帧分别处理,用两个case分开。
为了方便阅览,我把函数代码框架提炼了出来。
LoRaMacStatus_t PrepareFrame( LoRaMacHeader_t *macHdr, LoRaMacFrameCtrl_t *fCtrl, uint8_t fPort, void *fBuffer, uint16_t fBufferSize )
{
switch( macHdr->Bits.MType )
{
case FRAME_TYPE_JOIN_REQ:
...// 省略
break;
case FRAME_TYPE_DATA_CONFIRMED_UP:
NodeAckRequested = true;
//Intentional falltrough
case FRAME_TYPE_DATA_UNCONFIRMED_UP:
...
fCtrl->Bits.AdrAckReq = AdrNextDr( fCtrl->Bits.Adr, true, &LoRaMacParams.ChannelsDatarate );
...
if( SrvAckRequested == true )
{
SrvAckRequested = false;
fCtrl->Bits.Ack = 1;
}
LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr ) & 0xFF;
LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr >> 8 ) & 0xFF;
LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr >> 16 ) & 0xFF;
LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr >> 24 ) & 0xFF;
LoRaMacBuffer[pktHeaderLen++] = fCtrl->Value;
LoRaMacBuffer[pktHeaderLen++] = UpLinkCounter & 0xFF;
LoRaMacBuffer[pktHeaderLen++] = ( UpLinkCounter >> 8 ) & 0xFF;
// Copy the MAC commands which must be re-send into the MAC command buffer
memcpy1( &MacCommandsBuffer[MacCommandsBufferIndex], MacCommandsBufferToRepeat, MacCommandsBufferToRepeatIndex );
MacCommandsBufferIndex += MacCommandsBufferToRepeatIndex;
if( ( payload != NULL ) && ( payloadSize > 0 ) )
{
if( ( MacCommandsBufferIndex <= LORA_MAC_COMMAND_MAX_LENGTH ) && ( MacCommandsInNextTx == true ) )
{
fCtrl->Bits.FOptsLen += MacCommandsBufferIndex;
// Update FCtrl field with new value of OptionsLength
LoRaMacBuffer[0x05] = fCtrl->Value;
for( i = 0; i < MacCommandsBufferIndex; i++ )
{
LoRaMacBuffer[pktHeaderLen++] = MacCommandsBuffer[i];
}
}
}
else
{
if( ( MacCommandsBufferIndex > 0 ) && ( MacCommandsInNextTx ) )
{
payloadSize = MacCommandsBufferIndex;
payload = MacCommandsBuffer;
framePort = 0;
}
}
MacCommandsInNextTx = false;
// Store MAC commands which must be re-send in case the device does not receive a downlink anymore
MacCommandsBufferToRepeatIndex = ParseMacCommandsToRepeat( MacCommandsBuffer, MacCommandsBufferIndex, MacCommandsBufferToRepeat );
if( MacCommandsBufferToRepeatIndex > 0 )
{
MacCommandsInNextTx = true;
}
MacCommandsBufferIndex = 0;
if( ( payload != NULL ) && ( payloadSize > 0 ) )
{
LoRaMacBuffer[pktHeaderLen++] = framePort;
if( framePort == 0 )
{
LoRaMacPayloadEncrypt( (uint8_t* ) payload, payloadSize, LoRaMacNwkSKey, LoRaMacDevAddr, UP_LINK, UpLinkCounter, LoRaMacPayload );
}
else
{
LoRaMacPayloadEncrypt( (uint8_t* ) payload, payloadSize, LoRaMacAppSKey, LoRaMacDevAddr, UP_LINK, UpLinkCounter, LoRaMacPayload );
}
memcpy1( LoRaMacBuffer + pktHeaderLen, LoRaMacPayload, payloadSize );
}
LoRaMacBufferPktLen = pktHeaderLen + payloadSize;
LoRaMacComputeMic( LoRaMacBuffer, LoRaMacBufferPktLen, LoRaMacNwkSKey, LoRaMacDevAddr, UP_LINK, UpLinkCounter, &mic );
LoRaMacBuffer[LoRaMacBufferPktLen + 0] = mic & 0xFF;
LoRaMacBuffer[LoRaMacBufferPktLen + 1] = ( mic >> 8 ) & 0xFF;
LoRaMacBuffer[LoRaMacBufferPktLen + 2] = ( mic >> 16 ) & 0xFF;
LoRaMacBuffer[LoRaMacBufferPktLen + 3] = ( mic >> 24 ) & 0xFF;
LoRaMacBufferPktLen += LORAMAC_MFR_LEN;
break;
case FRAME_TYPE_PROPRIETARY:
...// 省略
break;
default:
return LORAMAC_STATUS_SERVICE_UNKNOWN;
}
return LORAMAC_STATUS_OK;
}
Join-request的组帧处理对应协议第6章 6.2.4 Join-request message。
数据帧的组帧处理则稍微复杂些,尤其是FHDR,下面逐个字段讲解下FHDR。
1.FHDR中的DevAddr
LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr ) & 0xFF;
LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr >> 8 ) & 0xFF;
LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr >> 16 ) & 0xFF;
LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr >> 24 ) & 0xFF;
2.FHDR中的FCtrl
首先 ADR 位段 是在传入 PrepareFrame() 之前,就做了处理。
fCtrl.Bits.Adr = AdrCtrlOn;
接着 AdrAckReq 位段,在长期失联情况下会发送AdrAckReq确认链路。
fCtrl->Bits.AdrAckReq = AdrNextDr( fCtrl->Bits.Adr, true, &LoRaMacParams.ChannelsDatarate );
最后 F0ptsLen 位段,会在下面计算完FOpts之后更新。
3.FHDR中的FCnt
LoRaMacBuffer[pktHeaderLen++] = UpLinkCounter & 0xFF;
LoRaMacBuffer[pktHeaderLen++] = ( UpLinkCounter >> 8 ) & 0xFF;
这个UpLinkCounter会在物理层发送完成后会按照协议进行累加。可以看到这是个32位计数器,按照协议规定,“如果采用32位帧计数,FCnt就对应计数器32位的16个低有效位”。
这是上行的,另外下行的也类似。
把MAC命令放入F0pts中,并且更新F0ptsLen。MAC命令,要么使用非零的FPort来和数据一起传输,要么使用FPort0来单独传输。
// Copy the MAC commands which must be re-send into the MAC command buffer
memcpy1( &MacCommandsBuffer[MacCommandsBufferIndex], MacCommandsBufferToRepeat, MacCommandsBufferToRepeatIndex );
MacCommandsBufferIndex += MacCommandsBufferToRepeatIndex;
if( ( payload != NULL ) && ( payloadSize > 0 ) )
{
if( ( MacCommandsBufferIndex <= LORA_MAC_COMMAND_MAX_LENGTH ) && ( MacCommandsInNextTx == true ) )
{
fCtrl->Bits.FOptsLen += MacCommandsBufferIndex;
// Update FCtrl field with new value of OptionsLength
LoRaMacBuffer[0x05] = fCtrl->Value;
for( i = 0; i < MacCommandsBufferIndex; i++ )
{
LoRaMacBuffer[pktHeaderLen++] = MacCommandsBuffer[i];
}
}
}
else
{
if( ( MacCommandsBufferIndex > 0 ) && ( MacCommandsInNextTx ) )
{
payloadSize = MacCommandsBufferIndex;
payload = MacCommandsBuffer;
framePort = 0;
}
}
这个是在应用层一直传递进去的,协议栈默认是用了端口2。这个是后期大家在应用时要调整的,类似于IP端口,不同的端口对应不同的服务。
在函数 PrepareFrame()的最后是调用LoRaMacComputeMic() 计算出整个MAC层的校验码。应用层这边基本不用改这边就暂时不细究了。
That’s all.