一、前言
嵌入式开发中我们要时刻保持代码的高效与整洁。目前LoRaWAN规范有两个版本V1.0.2和V1.0.3,相应的SDK也有两个:LoRaMac-node v4.0.0和LoRaMac-node v4.4.2。LoRaMac-node v4.4.2增加了classB的通信方式,但是LoRaMac-node v4.4.2的内存占用要比v4.0.0大一些,不过目前市面上买的LoRAWAN模组应该还是v1.0.2版本的。
二、代码分析v4.0.0
LoRaMac应该是纯软件的东西,但是却包含了硬件层的东西,全部读完代码就会发现官网的代码一点都不友好,用了很多全局变量,而且main.c搞那么大看着就很乱,也不知道官网是怎么想的。
int main( void )
{
LoRaMacPrimitives_t LoRaMacPrimitives;
LoRaMacCallback_t LoRaMacCallbacks;
MibRequestConfirm_t mibReq;
BoardInitMcu( );
BoardInitPeriph( );
DeviceState = DEVICE_STATE_INIT;
printf("LoRawan start\r\n");
while( 1 )
{
switch( DeviceState )
{
case DEVICE_STATE_INIT:
{
LoRaMacPrimitives.MacMcpsConfirm = McpsConfirm;
LoRaMacPrimitives.MacMcpsIndication = McpsIndication;
LoRaMacPrimitives.MacMlmeConfirm = MlmeConfirm;
// LoRaMacCallbacks.GetBatteryLevel = BoardGetBatteryLevel;
LoRaMacInitialization( &LoRaMacPrimitives, &LoRaMacCallbacks );
TimerInit( &TxNextPacketTimer, OnTxNextPacketTimerEvent );
TimerInit( &Led1Timer, OnLed1TimerEvent );
TimerSetValue( &Led1Timer, 25 );
TimerInit( &Led2Timer, OnLed2TimerEvent );
TimerSetValue( &Led2Timer, 25 );
TimerInit( &Led4Timer, OnLed4TimerEvent );
TimerSetValue( &Led4Timer, 25 );
mibReq.Type = MIB_ADR;
mibReq.Param.AdrEnable = LORAWAN_ADR_ON;
LoRaMacMibSetRequestConfirm( &mibReq );
mibReq.Type = MIB_PUBLIC_NETWORK;
mibReq.Param.EnablePublicNetwork = LORAWAN_PUBLIC_NETWORK;
LoRaMacMibSetRequestConfirm( &mibReq );
#if defined( USE_BAND_868 )
LoRaMacTestSetDutyCycleOn( LORAWAN_DUTYCYCLE_ON );
#if( USE_SEMTECH_DEFAULT_CHANNEL_LINEUP == 1 )
LoRaMacChannelAdd( 3, ( ChannelParams_t )LC4 );
LoRaMacChannelAdd( 4, ( ChannelParams_t )LC5 );
LoRaMacChannelAdd( 5, ( ChannelParams_t )LC6 );
LoRaMacChannelAdd( 6, ( ChannelParams_t )LC7 );
LoRaMacChannelAdd( 7, ( ChannelParams_t )LC8 );
LoRaMacChannelAdd( 8, ( ChannelParams_t )LC9 );
LoRaMacChannelAdd( 9, ( ChannelParams_t )LC10 );
mibReq.Type = MIB_RX2_DEFAULT_CHANNEL;
mibReq.Param.Rx2DefaultChannel = ( Rx2ChannelParams_t ){ 869525000, DR_3 };
LoRaMacMibSetRequestConfirm( &mibReq );
mibReq.Type = MIB_RX2_CHANNEL;
mibReq.Param.Rx2Channel = ( Rx2ChannelParams_t ){ 869525000, DR_3 };
LoRaMacMibSetRequestConfirm( &mibReq );
#endif
#endif
DeviceState = DEVICE_STATE_JOIN;
printf("DEVICE_STATE_INIT \r\n");
break;
}
case DEVICE_STATE_JOIN:
{
#if( OVER_THE_AIR_ACTIVATION != 0 )
MlmeReq_t mlmeReq;
// Initialize LoRaMac device unique ID
BoardGetUniqueId( DevEui );
mlmeReq.Type = MLME_JOIN;
mlmeReq.Req.Join.DevEui = DevEui;
mlmeReq.Req.Join.AppEui = AppEui;
mlmeReq.Req.Join.AppKey = AppKey;
mlmeReq.Req.Join.NbTrials = 3;
if( NextTx == true )
{
LoRaMacMlmeRequest( &mlmeReq );
}
DeviceState = DEVICE_STATE_SLEEP;
#else
// Choose a random device address if not already defined in Commissioning.h
if( DevAddr == 0 )
{
// Random seed initialization
srand1( BoardGetRandomSeed( ) );
// Choose a random device address
DevAddr = randr( 0, 0x01FFFFFF );
}
mibReq.Type = MIB_NET_ID;
mibReq.Param.NetID = LORAWAN_NETWORK_ID;
LoRaMacMibSetRequestConfirm( &mibReq );
mibReq.Type = MIB_DEV_ADDR;
mibReq.Param.DevAddr = DevAddr;
LoRaMacMibSetRequestConfirm( &mibReq );
mibReq.Type = MIB_NWK_SKEY;
mibReq.Param.NwkSKey = NwkSKey;
LoRaMacMibSetRequestConfirm( &mibReq );
mibReq.Type = MIB_APP_SKEY;
mibReq.Param.AppSKey = AppSKey;
LoRaMacMibSetRequestConfirm( &mibReq );
mibReq.Type = MIB_NETWORK_JOINED;
mibReq.Param.IsNetworkJoined = true;
LoRaMacMibSetRequestConfirm( &mibReq );
DeviceState = DEVICE_STATE_SEND;
#endif
printf("DEVICE_STATE_JOIN \r\n");
break;
}
case DEVICE_STATE_SEND:
{
if( NextTx == true )
{
PrepareTxFrame( AppPort );
NextTx = SendFrame( );
}
if( ComplianceTest.Running == true )
{
// Schedule next packet transmission
TxDutyCycleTime = 5000; // 5000 ms
}
else
{
// Schedule next packet transmission
TxDutyCycleTime = APP_TX_DUTYCYCLE + randr( -APP_TX_DUTYCYCLE_RND, APP_TX_DUTYCYCLE_RND );
}
DeviceState = DEVICE_STATE_CYCLE;
printf("DEVICE_STATE_SEND \r\n");
break;
}
case DEVICE_STATE_CYCLE:
{
DeviceState = DEVICE_STATE_SLEEP;
// Schedule next packet transmission
TimerSetValue( &TxNextPacketTimer, TxDutyCycleTime );
TimerStart( &TxNextPacketTimer );
printf("DEVICE_STATE_CYCLE \r\n");
break;
}
case DEVICE_STATE_SLEEP:
{
// Wake up through events
//printf("DEVICE_STATE_SLEEP \r\n");
TimerLowPowerHandler( );
break;
}
default:
{
DeviceState = DEVICE_STATE_INIT;
break;
}
}
// if( GpsGetPpsDetectedState( ) == true )
// {
// // Switch LED 4 ON
// GpioWrite( &Led4, 0 );
// TimerStart( &Led4Timer );
// }
}
}
主函数就是状态之间的切换,状态机采用的switch语句
static enum eDeviceState
{
DEVICE_STATE_INIT,
DEVICE_STATE_JOIN,
DEVICE_STATE_SEND,
DEVICE_STATE_CYCLE,
DEVICE_STATE_SLEEP
}DeviceState;
三、入网方式
如果是空中OTAA激活,则需要准备 DevEUI,AppEUI,AppKey 这三个参数,即设备自身MAC地址和要使用的应用(应用ID和密钥)。
如果是ABP激活,则直接配置 DevAddr,NwkSKey,AppSKey 这三个LoRaWAN最终通讯的参数,不再需要join流程。在这种情况下,这个设备是可以直接发应用数据的。
代码用一个宏(OVER_THE_AIR_ACTIVATION)分开两段,分别对应两种激活方式。OVER_THE_AIR_ACTIVATION !=0为OTAA空中激活方式。
case DEVICE_STATE_JOIN:
{
#if( OVER_THE_AIR_ACTIVATION != 0 )
MlmeReq_t mlmeReq;
// Initialize LoRaMac device unique ID
BoardGetUniqueId( DevEui );
mlmeReq.Type = MLME_JOIN;
mlmeReq.Req.Join.DevEui = DevEui;
mlmeReq.Req.Join.AppEui = AppEui;
mlmeReq.Req.Join.AppKey = AppKey;
mlmeReq.Req.Join.NbTrials = 3;
if( NextTx == true )
{
LoRaMacMlmeRequest( &mlmeReq );
}
DeviceState = DEVICE_STATE_SLEEP;
#else
// Choose a random device address if not already defined in Commissioning.h
if( DevAddr == 0 )
{
// Random seed initialization
srand1( BoardGetRandomSeed( ) );
// Choose a random device address
DevAddr = randr( 0, 0x01FFFFFF );
}
mibReq.Type = MIB_NET_ID;
mibReq.Param.NetID = LORAWAN_NETWORK_ID;
LoRaMacMibSetRequestConfirm( &mibReq );
mibReq.Type = MIB_DEV_ADDR;
mibReq.Param.DevAddr = DevAddr;
LoRaMacMibSetRequestConfirm( &mibReq );
mibReq.Type = MIB_NWK_SKEY;
mibReq.Param.NwkSKey = NwkSKey;
LoRaMacMibSetRequestConfirm( &mibReq );
mibReq.Type = MIB_APP_SKEY;
mibReq.Param.AppSKey = AppSKey;
LoRaMacMibSetRequestConfirm( &mibReq );
mibReq.Type = MIB_NETWORK_JOINED;
mibReq.Param.IsNetworkJoined = true;
LoRaMacMibSetRequestConfirm( &mibReq );
DeviceState = DEVICE_STATE_SEND;
#endif
printf("DEVICE_STATE_JOIN \r\n");
break;
四、数据包类型
LoRaWAN规范中有不同的数据包,通过MType字段区分,MType是3位的,总共可以表示8种不同类型的数据,其中前六种是不同的数据包,分别是“入网请求”、“入网回复”、“不需要确认上行数据包”、“需要确认上行数据包”、“不需要确认下行数据包”、“需要确认下行数据包”,后面两个一个是预留(RFU),一个开放给用户自定义(Proprietary)。
五、代码整理
目前国内用的最多的就是sx1278,加上官网给的SDK增感觉不是很清晰,所以重新建立工程。代码已经上传到CSDN;硬件使用的安信可RHF76-052
由于目前手上没有网关,所有调试代码一直处于入网状态中。官网代码只是很简单的例程,想要实际项目中使用还要修改很多东西,增加很多容错机制。