一、前言
嵌入式開發中我們要時刻保持代碼的高效與整潔。目前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
由於目前手上沒有網關,所有調試代碼一直處於入網狀態中。官網代碼只是很簡單的例程,想要實際項目中使用還要修改很多東西,增加很多容錯機制。