LoRa 是LPWAN通信技術(shù)中的一種,是美國Semtech公司采用和推廣的一種基于擴頻技術(shù)的超遠距離無線傳輸方案。這一方案改變了以往關(guān)于傳輸距離與功耗的折衷考慮方式為用戶提供一種簡單的能實現(xiàn)遠距離、長電池壽命、大容量的系統(tǒng),進而擴展傳感網(wǎng)絡(luò)。目前,LoRa 主要在全球免費頻段運行,各個國家和地區(qū)不一樣,中國區(qū)運行在470MHZ和779MHZ。
LoRaWAN是一個開放標(biāo)準,它定義了基于LoRa芯片的LPWAN技術(shù)的通信協(xié)議。 LoRaWAN在數(shù)據(jù)鏈路層定義媒體訪問控制(MAC),專為具有單一運營商的大型公共網(wǎng)絡(luò)而設(shè)計,具體而言,每個節(jié)點將數(shù)據(jù)傳輸?shù)骄W(wǎng)關(guān)或多個網(wǎng)關(guān)。然后網(wǎng)關(guān)將數(shù)據(jù)轉(zhuǎn)發(fā)到網(wǎng)絡(luò)服務(wù)器,在網(wǎng)絡(luò)服務(wù)器上執(zhí)行冗余檢測,安全檢查和消息調(diào)度,LoRaWAN現(xiàn)在由LoRa聯(lián)盟維護(link)。
總體而言,LoRa僅包含鏈路層協(xié)議,并且非常適用于節(jié)點間的P2P通信;同時,LoRa模塊(立創(chuàng)商城上20塊左右)也比LoRaWAN(某寶30到40塊)便宜一點;
LoRaWAN包含網(wǎng)絡(luò)層,因此可以將信息發(fā)送到任何已連接到云平臺的基站。只需將正確的天線連接到其插座,LoRaWAN模塊就可以以不同的頻率工作。
one picture wins thoustands words(如圖所示)
LoRaWAN = MAC Layer
LoRa = PHY Layer
LoRa + LoRaWAN = LPWAN
正是因為lorawan,成千上萬個節(jié)點的連網(wǎng)變得可能,本次移植的STM32 節(jié)點所連接的網(wǎng)關(guān)(SX1301)理論上能連接62500個節(jié)點。
開發(fā)環(huán)境的準備
Nucleo-F746ZG Board and ST Nucleo LoRa GW Module 如下圖
Nucleo-L073R8 Board and ST Nucleo LoRa Sensor V2
因為 ST Nucleo LoRa Sensor V2 上的RHF0M003 模塊已經(jīng)集成了lorawan 協(xié)議了,只需MCU通過UART發(fā)送AT指令就能實現(xiàn)lorawan通訊(上文所說較貴的一類模塊)
安信可ra-02(sx1278)
模塊通過杜邦線連接開發(fā)板的SPI1 GPIO(PA0-reset腳, PA10-中斷腳) ,VCC和地,連接好如下圖所示:
PC通過串口連接網(wǎng)關(guān),通過AT指令連接騰訊云物聯(lián)網(wǎng)開發(fā)平臺,指令如下
AT+PKTFWD=loragw.things.qcloud.com,1700,1700
AT+CH=0,486.3,A
AT+CH=1,486.5,A
AT+CH=2,486.7,A
AT+CH=3,486.9,A
AT+CH=4,487.1,B
AT+CH=5,487.3,B
AT+CH=6,487.5,B
AT+CH=7,487.7,B
AT+CH=8,OFF
AT+CH=9,OFF
AT+log=on (此條很重要,可以看到網(wǎng)關(guān)與云平臺,與節(jié)點的通訊情況)
AT+Reset 復(fù)位網(wǎng)關(guān),則開始服務(wù)器連接了。
以上就是節(jié)點移植前準備工作了
正文
初始化
void LORA_Init (LoRaMainCallback_t *callbacks, LoRaParam_t* LoRaParam )
{
?uint8_t devEui[] = LORAWAN_DEVICE_EUI;
? uint8_t joinEui[] = LORAWAN_JOIN_EUI; ? ?//連接騰訊云平臺用不到這個參數(shù)
??
? /* init the Tx Duty Cycle*/
? LoRaParamInit = LoRaParam;
??
? /* init the main call backs*/
? LoRaMainCallbacks = callbacks;
??
#if (STATIC_DEVICE_EUI != 1)
? LoRaMainCallbacks->BoardGetUniqueId( devEui ); ?
#endif
??
#if( OVER_THE_AIR_ACTIVATION != 0 )
? PPRINTF( "OTAA\n\r");?
? PPRINTF( "DevEui= %02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X\n\r", HEX8(devEui));
? PPRINTF( "AppEui= %02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X\n\r", HEX8(joinEui));
? PPRINTF( "AppKey= %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n\r", HEX16(AppKey));
#else
#if (STATIC_DEVICE_ADDRESS != 1)
? // Random seed initialization
? srand1( LoRaMainCallbacks->BoardGetRandomSeed( ) );
? // Choose a random device address
? DevAddr = randr( 0, 0x01FFFFFF );
#endif
? PPRINTF( "ABP\n\r");?
? PPRINTF( "DevEui= %02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X\n\r", HEX8(devEui));
? PPRINTF( "DevAdd= ?%08X\n\r", DevAddr) ;
? PPRINTF( "NwkSKey= %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n\r", HEX16(NwkSEncKey));
? PPRINTF( "AppSKey= %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n\r", HEX16(AppSKey));
#endif
.
.
.
#elif defined( REGION_CN470 )
? LoRaMacInitialization( &LoRaMacPrimitives, &LoRaMacCallbacks, LORAMAC_REGION_CN470 );
?.
?.
?.
? mibReq.Param.DevEui = devEui;
? mibReq.Param.AppKey = AppKey;
? mibReq.Param.NwkKey = NwkKey; //這幾個參數(shù)很重要一定要設(shè)對,我就 ?
? mibReq.Param.Class= CLASS_A; ?// 因為沒設(shè)Nwkkey 導(dǎo)致入不網(wǎng),
? //Lorawan 1.0.x 也要設(shè)置,具體原因在下文會詳細分析
? .
? .
? .
? LoRaMacStart( );
}
可以看出,初始化就是根據(jù)我們設(shè)置的一些宏 如入網(wǎng)方式,使用地區(qū)等等進行初始化。
入網(wǎng)
void LORA_Join( void)
{
? ? MlmeReq_t mlmeReq;
??
? ? mlmeReq.Type = MLME_JOIN;
? ? mlmeReq.Req.Join.Datarate = LoRaParamInit->TxDatarate;
??
? ? JoinParameters = mlmeReq.Req.Join;
#if( OVER_THE_AIR_ACTIVATION != 0 )
? ? LoRaMacMlmeRequest( &mlmeReq ); ? ?//騰訊云物聯(lián)網(wǎng)平臺要求空中入網(wǎng)的方式,所以定義了這個宏為1,于是調(diào)用了這個函數(shù); ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
#else
.
.
.
#endif
}
LoRaMacStatus_t LoRaMacMlmeRequest( MlmeReq_t* mlmeRequest )
{
? ? LoRaMacStatus_t status = LORAMAC_STATUS_SERVICE_UNKNOWN;
? ? MlmeConfirmQueue_t queueElement;
? ? uint8_t macCmdPayload[2] = { 0x00, 0x00 };
? ? if( mlmeRequest == NULL )
? ? {
? ? ? ? return LORAMAC_STATUS_PARAMETER_INVALID;
? ? }
? ? if( LoRaMacIsBusy( ) == true )
? ? {
? ? ? ? return LORAMAC_STATUS_BUSY;
? ? }
? ? if( LoRaMacConfirmQueueIsFull( ) == true )
? ? {
? ? ? ? return LORAMAC_STATUS_BUSY;
? ? }
? ? .
? ? .
? ? .
? ? switch( mlmeRequest->Type ) ? //通過入?yún)砼袛辔覀冞@是是入網(wǎng)請求MLME_JOIN
? ? {
? ? ? ? case MLME_JOIN:
? ? ? ? {
? ? ? ? ? ? if( ( MacCtx.MacState & LORAMAC_TX_DELAYED ) == LORAMAC_TX_DELAYED )
? ? ? ? ? ? {
? ? ? ? ? ? ? ? return LORAMAC_STATUS_BUSY;
? ? ? ? ? ? }
? ? ? ? ? ? ResetMacParameters( );
? ? ? ? ? ? MacCtx.NvmCtx->MacParams.ChannelsDatarate = RegionAlternateDr( MacCtx.NvmCtx->Region, mlmeRequest->Req.Join.Datarate, ALTERNATE_DR );
? ? ? ? ? ? queueElement.Status = LORAMAC_EVENT_INFO_STATUS_JOIN_FAIL;
? ? ? ? ? ? status = SendReJoinReq( JOIN_REQ ); ? ? //我們再進去看這個函數(shù)
? ? ? ? ? ? if( status != LORAMAC_STATUS_OK ) ? ?
? ? ? ? ? ? {
?? ??? ??? ??? ??? ?PPRINTF( "joinreq ok\n\r");
? ? ? ? ? ? ? ? // Revert back the previous datarate ( mainly used for US915 like regions )
? ? ? ? ? ? ? ? MacCtx.NvmCtx->MacParams.ChannelsDatarate = RegionAlternateDr( MacCtx.NvmCtx->Region, mlmeRequest->Req.Join.Datarate, ALTERNATE_DR_RESTORE );
? ? ? ? ? ? }
?? ??? ??? ??? ??? ??? ?else
?? ??? ??? ??? ??? ??? ?{
?? ??? ??? ??? ??? ??? ??? ?PPRINTF( "joinreq not ok\n\r");
?? ??? ??? ??? ??? ??? ?}
? ? ? ? ? ? break;
? ? ? ? }
? ? ? ? .
? ? ? ? .
? ? ? ? .
? ? ? ? return status;
}
LoRaMacStatus_t SendReJoinReq( JoinReqIdentifier_t joinReqType )
{
? ? LoRaMacStatus_t status = LORAMAC_STATUS_OK;
? ? LoRaMacHeader_t macHdr;
? ? macHdr.Value = 0;
? ? bool allowDelayedTx = true;
? ? // Setup join/rejoin message
? ? switch( joinReqType )
? ? {
? ? ? ? case JOIN_REQ:
? ? ? ? {
? ? ? ? ?.
? ? ? ? ?.
? ? ? ? ?.
? ? ? ? }
? ? }
? ? // Schedule frame
? ? status = ScheduleTx( allowDelayedTx ); ?//再看這個函數(shù)
? ? return status;
}
//謎底快解開了....
static LoRaMacStatus_t ScheduleTx( bool allowDelayedTx )
{
? ?LoRaMacStatus_t status = LORAMAC_STATUS_PARAMETER_INVALID;
? ? TimerTime_t dutyCycleTimeOff = 0;
? ? NextChanParams_t nextChan;
? ? size_t macCmdsSize = 0;
? ? // Update back-off
? ? CalculateBackOff( MacCtx.NvmCtx->LastTxChannel );
? ? .
? ? .
? ? .
? ? ?if( MacCtx.NvmCtx->NetworkActivation == ACTIVATION_TYPE_NONE )
? ? {
? ? ? ? MacCtx.RxWindow1Delay = MacCtx.NvmCtx->MacParams.JoinAcceptDelay1 + ? MacCtx.RxWindow1Config.WindowOffset;
? ? ? ? MacCtx.RxWindow2Delay = MacCtx.NvmCtx->MacParams.JoinAcceptDelay2 + MacCtx.RxWindow2Config.WindowOffset;
?? ? ? ?PPRINTF( "MacCtx.RxWindow1Delay is %d\n\r",MacCtx.RxWindow1Delay);
? ? }
? ? ?//這里也重點說一下,很多人入不了網(wǎng)的原因是因為接收窗口的時間不對,從發(fā)出入?
? ? //請求到從網(wǎng)關(guān)接收入網(wǎng)應(yīng)答這個時間間隔是5秒,這個和騰訊云物聯(lián)網(wǎng)平臺的工程師確
? ? //確認過
?.
?.
?.
? ? ?// Secure frame
? ? ?//謎底就在這個函數(shù)里
? ? LoRaMacStatus_t retval = SecureFrame( MacCtx.NvmCtx->MacParams.ChannelsDatarate, MacCtx.Channel );?
? ? if( retval != LORAMAC_STATUS_OK )
? ? {
? ? ? ? return retval;
? ? }
? ? // Try to send now
? ? return SendFrameOnChannel( MacCtx.Channel ); ?
}
== 之前一直入網(wǎng)不成功,聯(lián)系騰訊云的夏云飛老師,得到的回復(fù)是MIC錯誤,夏老師說MIC錯誤只有兩個原因,一是key錯了,二是算法錯了。 ==
我確信算法不會錯,因為我沒有改過源碼,所以我再次確認了AppKey 和 devEui,沒錯。經(jīng)過了一段時間的折騰和騰訊云的兩位大神夏云飛老師和twowinter(真名不知道啊,哈哈)的指導(dǎo)和提示,再次去看代碼,答案如下
static LoRaMacStatus_t SecureFrame( uint8_t txDr, uint8_t txCh )
{
? ? LoRaMacCryptoStatus_t macCryptoStatus = LORAMAC_CRYPTO_ERROR;
? ? uint32_t fCntUp = 0;
? ? switch( MacCtx.TxMsg.Type )
? ? {
? ? ? ? case LORAMAC_MSG_TYPE_JOIN_REQUEST: ?
? ? ? ? ? ?//我們來看看下面的函數(shù) LoRaMacCryptoPrepareJoinRequest
? ? ? ? ? ? macCryptoStatus = LoRaMacCryptoPrepareJoinRequest( &MacCtx.TxMsg.Message.JoinReq );
? ? ? ? ? ? if( LORAMAC_CRYPTO_SUCCESS != macCryptoStatus )
? ? ? ? ? ? {
? ? ? ? ? ? ? ? return LORAMAC_STATUS_CRYPTO_ERROR;
? ? ? ? ? ? }
? ? ? ? ? ? MacCtx.PktBufferLen = MacCtx.TxMsg.Message.JoinReq.BufSize;
? ? ? ? ? ? break;
? ? ? .
? ? ? .
? ? ? .
? ? return LORAMAC_STATUS_OK;
}
LoRaMacCryptoStatus_t LoRaMacCryptoPrepareJoinRequest( LoRaMacMessageJoinRequest_t* macMsg ) ? ??
{
? ? if( macMsg == 0 )
? ? {
? ? ? ? return LORAMAC_CRYPTO_ERROR_NPE;
? ? }
? ? //這里加密用的是NWK_KEY, ?但是我沒有設(shè)置,所以加密錯誤,這就是原因,我也打印再次確認過,就是nwk_key。 破案了
? ? KeyIdentifier_t micComputationKeyID = NWK_KEY; ?
? ? // Add device nonce
#if ( USE_RANDOM_DEV_NONCE == 1 )
? ? uint32_t devNonce = 0;
? ? SecureElementRandomNumber( &devNonce );
? ? CryptoCtx.NvmCtx->DevNonce = devNonce;
#else
? ? CryptoCtx.NvmCtx->DevNonce++;
#endif
? ? CryptoCtx.EventCryptoNvmCtxChanged( );
? ? macMsg->DevNonce = CryptoCtx.NvmCtx->DevNonce;
#if( USE_LRWAN_1_1_X_CRYPTO == 1 ) ? //這里是USE_LRWAN_1_1_X 的宏,但是我的是1_0_X, 所以為零
? ? // Derive lifetime session keys
? ? if( DeriveLifeTimeSessionKey( J_S_INT_KEY, macMsg->DevEUI ) != LORAMAC_CRYPTO_SUCCESS )
? ? {
? ? ? ? return LORAMAC_CRYPTO_ERROR;
? ? }
? ? if( DeriveLifeTimeSessionKey( J_S_ENC_KEY, macMsg->DevEUI ) != LORAMAC_CRYPTO_SUCCESS )
? ? {
? ? ? ? return LORAMAC_CRYPTO_ERROR;
? ? }
#endif
? ? // Serialize message
? ? if( LoRaMacSerializerJoinRequest( macMsg ) != LORAMAC_SERIALIZER_SUCCESS )
? ? {
? ? ? ? return LORAMAC_CRYPTO_ERROR_SERIALIZER;
? ? }
? ? // Compute mic ? 這里計算用到了上面的nwk_key,破案了
? ? if( SecureElementComputeAesCmac( NULL, macMsg->Buffer, ( LORAMAC_JOIN_REQ_MSG_SIZE - LORAMAC_MIC_FIELD_SIZE ), micComputationKeyID, &macMsg->MIC ) != SECURE_ELEMENT_SUCCESS )
? ? {
? ? ? ? return LORAMAC_CRYPTO_ERROR_SECURE_ELEMENT_FUNC;
? ? }
? ? // Reserialize message to add the MIC
? ? if( LoRaMacSerializerJoinRequest( macMsg ) != LORAMAC_SERIALIZER_SUCCESS )
? ? {
? ? ? ? return LORAMAC_CRYPTO_ERROR_SERIALIZER;
? ? }
? ? return LORAMAC_CRYPTO_SUCCESS;
}
發(fā)送與接收
發(fā)送和接收因為沒遇到什么困難,直接調(diào)用發(fā)送函數(shù)就行,因為是class A 設(shè)備,會在發(fā)送后,打開接收窗口接收;貼一個發(fā)送函數(shù)吧文章來源:http://www.zghlxwxcb.cn/news/detail-693469.html
bool LORA_send(lora_AppData_t* AppData, LoraConfirm_t IsTxConfirmed)
{
? ? McpsReq_t mcpsReq;
? ? LoRaMacTxInfo_t txInfo;
??
? ? /*if certification test are on going, application data is not sent*/
? ? if (certif_running() == true)
? ? {
?? ??? ??? ?PPRINTF("certif_run\r\n");
? ? ? return false;
? ? }
? ??
? ? if( LoRaMacQueryTxPossible( AppData->BuffSize, &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 = LoRaParamInit->TxDatarate;
? ? }
? ? else
? ? {
? ? ? ? if( IsTxConfirmed == LORAWAN_UNCONFIRMED_MSG )
? ? ? ? {
? ? ? ? ? ? mcpsReq.Type = MCPS_UNCONFIRMED;
? ? ? ? ? ? mcpsReq.Req.Unconfirmed.fPort = AppData->Port;
? ? ? ? ? ? mcpsReq.Req.Unconfirmed.fBufferSize = AppData->BuffSize;
? ? ? ? ? ? mcpsReq.Req.Unconfirmed.fBuffer = AppData->Buff;
? ? ? ? ? ? mcpsReq.Req.Unconfirmed.Datarate = LoRaParamInit->TxDatarate;
? ? ? ? }
? ? ? ? else
? ? ? ? {
? ? ? ? ? ? mcpsReq.Type = MCPS_CONFIRMED;
? ? ? ? ? ? mcpsReq.Req.Confirmed.fPort = AppData->Port;
? ? ? ? ? ? mcpsReq.Req.Confirmed.fBufferSize = AppData->BuffSize;
? ? ? ? ? ? mcpsReq.Req.Confirmed.fBuffer = AppData->Buff;
? ? ? ? ? ? mcpsReq.Req.Confirmed.NbTrials = 8;
? ? ? ? ? ? mcpsReq.Req.Confirmed.Datarate = LoRaParamInit->TxDatarate;
? ? ? ? }
? ? }
? ? if( LoRaMacMcpsRequest( &mcpsReq ) == LORAMAC_STATUS_OK )
? ? {
? ? ? ? return false;
? ? }
? ? return true;
}文章來源地址http://www.zghlxwxcb.cn/news/detail-693469.html
到了這里,關(guān)于STM32的lorawan協(xié)議棧的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!