LoRa节点开发——代码详解LoRaWAN发送与接收数据

2026-02-23 12:45:00

1、数据包类型

LoRaWAN规范中有不同的数据包,通过MType字段区分,MType是3位的,总共可以表示8种不同类型的数据,其中前六种不同的数据包,分别是“入网请求”、“入网回复”、“不需要确认上行数据包”、“不需要确认下行数据包”、“需要确认上行数据包”、“需要确认下行数据包”,后面两个一个是预留(RFU),一个开放给用户自定义(Proprietary)。

其中“入网请求”、“入网回复”,主要是用于OTAA入网的,在前面的LoRa节点开发——LoRaWAN节点入网代码详解文章已经分析过了。

“不需要确认上行数据包”、“需要确认上行数据包”:主要用于用户上报数据。这里说一下不需要确认和需要确认,“需要确认”:就是发送数据后需要服务器回复一个ack,表明已经收到数据了,如果没有回复ack,那么还会重复发,一般用于紧急重要的数据上报;“不需要确认”:就是不管服务器有没有收到数据,发一次就不管了,一般用于非紧急不重要数据上报。

“不需要确认下行数据包”、“需要确认下行数据包”:主要服务器下发数据。不需要确认和需要确认同上面。服务器发送“需要确认”数据包时,需要节点回复ack给服务器。

2、源码分析

2.1、上行数据

代码语言:javascript复制/*!

* \brief Prepares the payload of the frame

*/

static void PrepareTxFrame( uint8_t port )

{

switch( port )

{

case 2:

{

AppDataSizeBackup = AppDataSize = 1;

AppDataBuffer[0] = AppLedStateOn;

}

break;

case 224:

if( ComplianceTest.LinkCheck == true )

{

ComplianceTest.LinkCheck = false;

AppDataSize = 3;

AppDataBuffer[0] = 5;

AppDataBuffer[1] = ComplianceTest.DemodMargin;

AppDataBuffer[2] = ComplianceTest.NbGateways;

ComplianceTest.State = 1;

}

else

{

switch( ComplianceTest.State )

{

case 4:

ComplianceTest.State = 1;

break;

case 1:

AppDataSize = 2;

AppDataBuffer[0] = ComplianceTest.DownLinkCounter >> 8;

AppDataBuffer[1] = ComplianceTest.DownLinkCounter;

break;

}

}

break;

default:

break;

}

}可以看到,在PrepareTxFrame这个函数中,应用只需要在AppDataBuffer中填充相应的数据以及设置数据长度AppDataSize即可。

代码语言:javascript复制static bool SendFrame( void )

{

McpsReq_t mcpsReq;

LoRaMacTxInfo_t txInfo;

if( LoRaMacQueryTxPossible( AppDataSize, &txInfo ) != LORAMAC_STATUS_OK )

{

// Send empty frame in order to flush MAC commands

mcpsReq.Type = MCPS_UNCONFIRMED;

mcpsReq.Req.Unconfirmed.fBuffer = NULL;

mcpsReq.Req.Unconfirmed.fBufferSize = 0;

mcpsReq.Req.Unconfirmed.Datarate = LORAWAN_DEFAULT_DATARATE;

}

else

{

if( IsTxConfirmed == false )

{

mcpsReq.Type = MCPS_UNCONFIRMED;

mcpsReq.Req.Unconfirmed.fPort = AppPort;

mcpsReq.Req.Unconfirmed.fBuffer = AppDataBuffer;

mcpsReq.Req.Unconfirmed.fBufferSize = AppDataSize;

mcpsReq.Req.Unconfirmed.Datarate = LORAWAN_DEFAULT_DATARATE;

}

else

{

mcpsReq.Type = MCPS_CONFIRMED;

mcpsReq.Req.Confirmed.fPort = AppPort;

mcpsReq.Req.Confirmed.fBuffer = AppDataBuffer;

mcpsReq.Req.Confirmed.fBufferSize = AppDataSize;

mcpsReq.Req.Confirmed.NbTrials = 8;

mcpsReq.Req.Confirmed.Datarate = LORAWAN_DEFAULT_DATARATE;

}

}

// Update global variable

AppData.MsgType = ( mcpsReq.Type == MCPS_CONFIRMED ) ? LORAMAC_HANDLER_CONFIRMED_MSG : LORAMAC_HANDLER_UNCONFIRMED_MSG;

AppData.Port = mcpsReq.Req.Unconfirmed.fPort;

AppData.Buffer = mcpsReq.Req.Unconfirmed.fBuffer;

AppData.BufferSize = mcpsReq.Req.Unconfirmed.fBufferSize;

LoRaMacStatus_t status;

status = LoRaMacMcpsRequest( &mcpsReq );

printf( "\r\n###### ===== MCPS-Request ==== ######\r\n" );

printf( "STATUS : %s\r\n", MacStatusStrings[status] );

if( status == LORAMAC_STATUS_OK )

{

return false;

}

return true;

}一些紧急重要数据可以发送“需要确认数据包”,从SendFrame这个函数中,可以看出需要发送“需要确认数据包”的时候,只需把IsTxConfirmed这个参数设置true即可。

应用只需设置以上3个参数即可发送,数据准备好之后,就是协议栈组包了,LoRaMacMcpsRequest( &mcpsReq )这个函数正是发送数据组包的函数,组包之后就是加密,最后就是射频发送了。

2.2、下行数据

过程刚好和发送数据相反(上行数据),先是射频接收,接收到数据之后解密,用户应用数据处理。

查看static void ProcessRadioRxDone( void )函数,可以看到使用switch case语句,通过macHdr.Bits.MType字段对接收到的数据包进行了区分,FRAME_TYPE_DATA_CONFIRMED_DOWN和FRAME_TYPE_DATA_UNCONFIRMED_DOWN正是服务器的下行数据。

LoRaWAN协议栈在处理的时候,使用了设置标志位,然后回调函数的方法来处理。若有下发数据,则将MacCtx.MacFlags.Bits.McpsInd 设置为1,如下:

代码语言:javascript复制 // Provide always an indication, skip the callback to the user application,

// in case of a confirmed downlink retransmission.

MacCtx.MacFlags.Bits.McpsInd = 1;在LoRaWAN协议栈初始化的时候,注册了几个函数,然后在满足条件的时候回调。

代码语言:javascript复制int main( void )

{

…………//代码过长,部分代码未截取

macPrimitives.MacMcpsConfirm = McpsConfirm;

macPrimitives.MacMcpsIndication = McpsIndication;

macPrimitives.MacMlmeConfirm = MlmeConfirm;

macPrimitives.MacMlmeIndication = MlmeIndication;

macCallbacks.GetBatteryLevel = BoardGetBatteryLevel;

macCallbacks.GetTemperatureLevel = NULL;

macCallbacks.NvmContextChange = NvmCtxMgmtEvent;

macCallbacks.MacProcessNotify = OnMacProcessNotify;

LoRaMacInitialization( &macPrimitives, &macCallbacks, ACTIVE_REGION );

…………//代码过长,部分代码未截取

}其中,MlmeIndication就是下发回调函数。

查看MlmeIndication函数,如下:

代码语言:javascript复制static void McpsIndication( McpsIndication_t *mcpsIndication )

{

printf( "\r\n###### ===== MCPS-Indication ==== ######\r\n" );

printf( "STATUS : %s\r\n", EventInfoStatusStrings[mcpsIndication->Status] );

if( mcpsIndication->Status != LORAMAC_EVENT_INFO_STATUS_OK )

{

return;

}

switch( mcpsIndication->McpsIndication )

{

case MCPS_UNCONFIRMED:

{

break;

}

case MCPS_CONFIRMED:

{

break;

}

case MCPS_PROPRIETARY:

{

break;

}

case MCPS_MULTICAST:

{

break;

}

default:

break;

}

// Check Multicast

// Check Port

// Check Datarate

// Check FramePending

if( mcpsIndication->FramePending == true )

{

// The server signals that it has pending data to be sent.

// We schedule an uplink as soon as possible to flush the server.

OnTxNextPacketTimerEvent( NULL );

}

// Check Buffer

// Check BufferSize

// Check Rssi

// Check Snr

// Check RxSlot

if( ComplianceTest.Running == true )

{

ComplianceTest.DownLinkCounter++;

}

if( mcpsIndication->RxData == true )

{

switch( mcpsIndication->Port )

{

case 1: // The application LED can be controlled on port 1 or 2

case 2:

if( mcpsIndication->BufferSize == 1 )

{

AppLedStateOn = mcpsIndication->Buffer[0] & 0x01;

}

break;

case 224:

if( ComplianceTest.Running == false )

{

// Check compliance test enable command (i)

if( ( mcpsIndication->BufferSize == 4 ) &&

( mcpsIndication->Buffer[0] == 0x01 ) &&

( mcpsIndication->Buffer[1] == 0x01 ) &&

( mcpsIndication->Buffer[2] == 0x01 ) &&

( mcpsIndication->Buffer[3] == 0x01 ) )

{

IsTxConfirmed = false;

AppPort = 224;

AppDataSizeBackup = AppDataSize;

AppDataSize = 2;

ComplianceTest.DownLinkCounter = 0;

ComplianceTest.LinkCheck = false;

ComplianceTest.DemodMargin = 0;

ComplianceTest.NbGateways = 0;

ComplianceTest.Running = true;

ComplianceTest.State = 1;

MibRequestConfirm_t mibReq;

mibReq.Type = MIB_ADR;

mibReq.Param.AdrEnable = true;

LoRaMacMibSetRequestConfirm( &mibReq );

#if defined( REGION_EU868 ) || defined( REGION_RU864 ) || defined( REGION_CN779 ) || defined( REGION_EU433 )

LoRaMacTestSetDutyCycleOn( false );

#endif

}

}

else

{

ComplianceTest.State = mcpsIndication->Buffer[0];

switch( ComplianceTest.State )

{

case 0: // Check compliance test disable command (ii)

IsTxConfirmed = LORAWAN_CONFIRMED_MSG_ON;

AppPort = LORAWAN_APP_PORT;

AppDataSize = AppDataSizeBackup;

ComplianceTest.DownLinkCounter = 0;

ComplianceTest.Running = false;

MibRequestConfirm_t mibReq;

mibReq.Type = MIB_ADR;

mibReq.Param.AdrEnable = LORAWAN_ADR_ON;

LoRaMacMibSetRequestConfirm( &mibReq );

#if defined( REGION_EU868 ) || defined( REGION_RU864 ) || defined( REGION_CN779 ) || defined( REGION_EU433 )

LoRaMacTestSetDutyCycleOn( LORAWAN_DUTYCYCLE_ON );

#endif

break;

case 1: // (iii, iv)

AppDataSize = 2;

break;

case 2: // Enable confirmed messages (v)

IsTxConfirmed = true;

ComplianceTest.State = 1;

break;

case 3: // Disable confirmed messages (vi)

IsTxConfirmed = false;

ComplianceTest.State = 1;

break;

case 4: // (vii)

AppDataSize = mcpsIndication->BufferSize;

AppDataBuffer[0] = 4;

for( uint8_t i = 1; i < MIN( AppDataSize, LORAWAN_APP_DATA_MAX_SIZE ); i++ )

{

AppDataBuffer[i] = mcpsIndication->Buffer[i] + 1;

}

break;

case 5: // (viii)

{

MlmeReq_t mlmeReq;

mlmeReq.Type = MLME_LINK_CHECK;

LoRaMacStatus_t status = LoRaMacMlmeRequest( &mlmeReq );

printf( "\r\n###### ===== MLME-Request - MLME_LINK_CHECK ==== ######\r\n" );

printf( "STATUS : %s\r\n", MacStatusStrings[status] );

}

break;

case 6: // (ix)

{

// Disable TestMode and revert back to normal operation

IsTxConfirmed = LORAWAN_CONFIRMED_MSG_ON;

AppPort = LORAWAN_APP_PORT;

AppDataSize = AppDataSizeBackup;

ComplianceTest.DownLinkCounter = 0;

ComplianceTest.Running = false;

MibRequestConfirm_t mibReq;

mibReq.Type = MIB_ADR;

mibReq.Param.AdrEnable = LORAWAN_ADR_ON;

LoRaMacMibSetRequestConfirm( &mibReq );

#if defined( REGION_EU868 ) || defined( REGION_RU864 ) || defined( REGION_CN779 ) || defined( REGION_EU433 )

LoRaMacTestSetDutyCycleOn( LORAWAN_DUTYCYCLE_ON );

#endif

JoinNetwork( );

}

break;

case 7: // (x)

{

if( mcpsIndication->BufferSize == 3 )

{

MlmeReq_t mlmeReq;

mlmeReq.Type = MLME_TXCW;

mlmeReq.Req.TxCw.Timeout = ( uint16_t )( ( mcpsIndication->Buffer[1] << 8 ) | mcpsIndication->Buffer[2] );

LoRaMacStatus_t status = LoRaMacMlmeRequest( &mlmeReq );

printf( "\r\n###### ===== MLME-Request - MLME_TXCW ==== ######\r\n" );

printf( "STATUS : %s\r\n", MacStatusStrings[status] );

}

else if( mcpsIndication->BufferSize == 7 )

{

MlmeReq_t mlmeReq;

mlmeReq.Type = MLME_TXCW_1;

mlmeReq.Req.TxCw.Timeout = ( uint16_t )( ( mcpsIndication->Buffer[1] << 8 ) | mcpsIndication->Buffer[2] );

mlmeReq.Req.TxCw.Frequency = ( uint32_t )( ( mcpsIndication->Buffer[3] << 16 ) | ( mcpsIndication->Buffer[4] << 8 ) | mcpsIndication->Buffer[5] ) * 100;

mlmeReq.Req.TxCw.Power = mcpsIndication->Buffer[6];

LoRaMacStatus_t status = LoRaMacMlmeRequest( &mlmeReq );

printf( "\r\n###### ===== MLME-Request - MLME_TXCW1 ==== ######\r\n" );

printf( "STATUS : %s\r\n", MacStatusStrings[status] );

}

ComplianceTest.State = 1;

}

break;

case 8: // Send DeviceTimeReq

{

MlmeReq_t mlmeReq;

mlmeReq.Type = MLME_DEVICE_TIME;

LoRaMacStatus_t status = LoRaMacMlmeRequest( &mlmeReq );

printf( "\r\n###### ===== MLME-Request - MLME_DEVICE_TIME ==== ######\r\n" );

printf( "STATUS : %s\r\n", MacStatusStrings[status] );

}

break;

default:

break;

}

}

break;

default:

break;

}

}

// Switch LED 2 ON for each received downlink

GpioWrite( &Led2, 1 );

TimerStart( &Led2Timer );

const char *slotStrings[] = { "1", "2", "C", "C Multicast", "B Ping-Slot", "B Multicast Ping-Slot" };

printf( "\r\n###### ===== DOWNLINK FRAME %lu ==== ######\r\n", mcpsIndication->DownLinkCounter );

printf( "RX WINDOW : %s\r\n", slotStrings[mcpsIndication->RxSlot] );

printf( "RX PORT : %d\r\n", mcpsIndication->Port );

if( mcpsIndication->BufferSize != 0 )

{

printf( "RX DATA : \r\n" );

PrintHexBuffer( mcpsIndication->Buffer, mcpsIndication->BufferSize );

}

printf( "\r\n" );

printf( "DATA RATE : DR_%d\r\n", mcpsIndication->RxDatarate );

printf( "RX RSSI : %d\r\n", mcpsIndication->Rssi );

printf( "RX SNR : %d\r\n", mcpsIndication->Snr );

printf( "\r\n" );

}应用可以在这里获取服务器下发的数据,也可以获取到下发信号的RSSI和SNR等。

至此,如何上报数据,下发接收数据分析完成。

——————END——————