LoRaWAN协议解析 第4章 MAC帧格式

1 前言

我正在陆续对《LoRaWAN102》即LoRaWAN协议规范 V1.0.2 版本(2016年7月定稿)协议的各个章节进行翻译。译文之外还对LoRaWAN协议和源码进行了解析,可点此查看帖子LoRa学习笔记_汇总。

欢迎同行朋友们留言交流。

本文作者twowinter,转载请注明作者:http://blog.csdn.net/iotisan/

2 梳理解析

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小能手,如果不能提出一些稍有深度的信息增量,就对不起这个称号了。所以,有些协议设计层面的心得要分享下:

  1. 特别酷的ADR(速率自适应)机制
    这个章节中最亮眼的莫过于速率自适应机制,简直是为LoRa网络量身定做的:一旦使能了FCtrl中的ADR位,距离近信号好的节点用高速率,距离远信号弱的节点用低速率,不小心被调高了速率,则自动降下来。这样,尽可能地提高了传输速率,也有效提高了网络容量。我已经见过不少厂家,拿这个协议的公知特点当产品卖点了。

  2. 可同时携带数据和命令的MAC帧
    一般来说,应用除了数据,出于管理需要,肯定还会涉及命令。比如基站要查询节点状态,或者节点要请求变更信道等。所以LoRaWAN协议设计上利用FOpts把数据和命令揉在一个MAC帧里,这样可以提高交互效率,有效地降低功耗。这在寸土寸金,哦不,寸库仑(电量单位)寸金的物联网应用中,是一个很有必要的设计。

3 源码解析

这章的处理基本都在 \src\mac\LoRaMac.c 中,下面按照MAC帧格式的字段逐个解析下。

3.1 MAC层MHDR

在LoRaWAN的数据API中处理了MHDR,这个字段内容比较少,就按需选择了消息类型是confirm还是unconfirm。
另外在管理API中的Join-Req的消息类型。

具体可见 LoRaMacMcpsRequest() 和 LoRaMacMlmeRequest() 这两个函数。

3.2 MACPayload

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。

3.2.1 MACPayload中的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个低有效位”。

这是上行的,另外下行的也类似。

  • 4.FHDR中的FOpts

把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;
	}
}

3.2.2 MACPayload中的FPort

这个是在应用层一直传递进去的,协议栈默认是用了端口2。这个是后期大家在应用时要调整的,类似于IP端口,不同的端口对应不同的服务。

3.3 MIC解析

在函数 PrepareFrame()的最后是调用LoRaMacComputeMic() 计算出整个MAC层的校验码。应用层这边基本不用改这边就暂时不细究了。

End

That’s all.


你可能感兴趣的:(联,-,LoRa)