STM32的lorawan协议栈

LoRa 是LPWAN通信技术中的一种,是美国Semtech公司采用和推广的一种基于扩频技术的超远距离无线传输方案。这一方案改变了以往关于传输距离与功耗的折衷考虑方式为用户提供一种简单的能实现远距离、长电池寿命、大容量的系统,进而扩展传感网络。目前,LoRa 主要在全球免费频段运行,各个国家和地区不一样,中国区运行在470MHZ和779MHZ。

LoRaWAN是一个开放标准,它定义了基于LoRa芯片的LPWAN技术的通信协议。 LoRaWAN在数据链路层定义媒体访问控制(MAC),专为具有单一运营商的大型公共网络而设计,具体而言,每个节点将数据传输到网关或多个网关。然后网关将数据转发到网络服务器,在网络服务器上执行冗余检测,安全检查和消息调度,LoRaWAN现在由LoRa联盟维护(link)。

总体而言,LoRa仅包含链路层协议,并且非常适用于节点间的P2P通信;同时,LoRa模块(立创商城上20块左右)也比LoRaWAN(某宝30到40块)便宜一点;
LoRaWAN包含网络层,因此可以将信息发送到任何已连接到云平台的基站。只需将正确的天线连接到其插座,LoRaWAN模块就可以以不同的频率工作。


one picture wins thoustands words(如图所示)
LoRaWAN = MAC Layer
LoRa = PHY Layer
LoRa + LoRaWAN = LPWAN

正是因为lorawan,成千上万个节点的连网变得可能,本次移植的STM32 节点所连接的网关(SX1301)理论上能连接62500个节点。

开发环境的准备
Nucleo-F746ZG Board and ST Nucleo LoRa GW Module 如下图

Nucleo-L073R8 Board and ST Nucleo LoRa Sensor V2

因为 ST Nucleo LoRa Sensor V2 上的RHF0M003 模块已经集成了lorawan 协议了,只需MCU通过UART发送AT指令就能实现lorawan通讯(上文所说较贵的一类模块)

安信可ra-02(sx1278)

模块通过杜邦线连接开发板的SPI1 GPIO(PA0-reset脚, PA10-中断脚) ,VCC和地,连接好如下图所示:

PC通过串口连接网关,通过AT指令连接腾讯云物联网开发平台,指令如下
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 (此条很重要,可以看到网关与云平台,与节点的通讯情况)
AT+Reset 复位网关,则开始服务器连接了。
以上就是节点移植前准备工作了

正文
初始化
void LORA_Init (LoRaMainCallback_t *callbacks, LoRaParam_t* LoRaParam )
{
 uint8_t devEui[] = LORAWAN_DEVICE_EUI;
  uint8_t joinEui[] = LORAWAN_JOIN_EUI;    //连接腾讯云平台用不到这个参数
  
  /* 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; //这几个参数很重要一定要设对,我就  
  mibReq.Param.Class= CLASS_A;  // 因为没设Nwkkey 导致入不网,
  //Lorawan 1.0.x 也要设置,具体原因在下文会详细分析
  .
  .
  .
  LoRaMacStart( );
}

可以看出,初始化就是根据我们设置的一些宏 如入网方式,使用地区等等进行初始化。

入网
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 );    //腾讯云物联网平台要求空中入网的方式,所以定义了这个宏为1,于是调用了这个函数;                                                                       
#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 )   //通过入参来判断我们这是是入网请求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 );     //我们再进去看这个函数

            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 );  //再看这个函数
    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);
    }
     //这里也重点说一下,很多人入不了网的原因是因为接收窗口的时间不对,从发出入 
    //请求到从网关接收入网应答这个时间间隔是5秒,这个和腾讯云物联网平台的工程师确
    //确认过
 .
 .
 .
     // Secure frame
     //谜底就在这个函数里
    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 );  
}

== 之前一直入网不成功,联系腾讯云的夏云飞老师,得到的回复是MIC错误,夏老师说MIC错误只有两个原因,一是key错了,二是算法错了。 ==

我确信算法不会错,因为我没有改过源码,所以我再次确认了AppKey 和 devEui,没错。经过了一段时间的折腾和腾讯云的两位大神夏云飞老师和twowinter(真名不知道啊,哈哈)的指导和提示,再次去看代码,答案如下

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:  
           //我们来看看下面的函数 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,  但是我没有设置,所以加密错误,这就是原因,我也打印再次确认过,就是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;
}

发送与接收
发送和接收因为没遇到什么困难,直接调用发送函数就行,因为是class A 设备,会在发送后,打开接收窗口接收;贴一个发送函数吧

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.mzph.cn/news/66035.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Open3D(C++) 点云格网分块

目录 一、算法概述二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、算法概述 点云格网分块是点云二维格网化的一个具体应用案例,与Open3D (C++) 使用点云创建数字高程模型DEM类似,对每个格…

【每日一题】657. 机器人能否返回原点

657. 机器人能否返回原点 - 力扣(LeetCode) 在二维平面上,有一个机器人从原点 (0, 0) 开始。给出它的移动顺序,判断这个机器人在完成移动后是否在 (0, 0) 处结束。 移动顺序由字符串 moves 表示。字符 move[i] 表示其第 i 次移动。…

设计模式之装饰者模式

文章目录 星巴克咖啡订单项目(咖啡馆)方案 1-解决星巴克咖啡订单项目方案 1-解决星巴克咖啡订单问题分析方案 2-解决星巴克咖啡订单(好点)方案 2-解决星巴克咖啡订单问题分析装饰者模式定义装饰者模式原理装饰者模式解决星巴克咖啡订单装饰者模式下的订单…

YOLOv5算法改进(13)— 替换主干网络之PP-LCNet

前言:Hello大家好,我是小哥谈。PP-LCNet是一个由百度团队针对Intel-CPU端加速而设计的轻量高性能网络。它是一种基于MKLDNN加速策略的轻量级卷积神经网络,适用于多任务,并具有提高模型准确率的方法。与之前预测速度相近的模型相比…

百度搜索清理大量低质量网站

我是卢松松,点点上面的头像,欢迎关注我哦! 据部分站长爆料:百度大规模删低质量网站的百度资源站长平台权限,很多网站都被删除了百度站长资源平台后台权限,以前在百度后台添加的网站大量被删除!…

MAC修改python3命令为py

1, 找到python3安装路径 2, vi ~/.bash_profile 3, 增加内容: alias py“/usr/bin/python3” 4, 重载source ~/.bash_profile 5,执行py

Java后端开发面试题——多线程

创建线程的方式有哪些? 继承Thread类 public class MyThread extends Thread {Overridepublic void run() {System.out.println("MyThread...run...");}public static void main(String[] args) {// 创建MyThread对象MyThread t1 new MyThread() ;MyTh…

Python语音识别处理详解

概要 人们对智能语音助手的需求不断提高,语音识别技术也随之迅速发展。在这篇文章中,我们将介绍如何使用Python的SpeechRecognition和pydub等库来实现语音识别和处理,从而打造属于自己的智能语音助手。 1. 什么是语音识别? 语音…

StartUp启动框架-Android启动性能

简述 当谈论Android应用程序的启动性能时,StartUp启动框架是一个不可忽视的关键工具。它旨在优化应用程序的启动过程,确保用户在打开应用时能够迅速获得流畅、高效的体验。让我们来深入了解StartUp框架的作用和重要性,以及它是如何改善Andro…

【项目】Reactor模式的服务器

目录 Reactor完整代码连接 前置知识: 1.普通的epoll读写有什么问题? 2.Connection内的回调函数是什么 3.服务器的初始化(Connection只是使用的一个结构体) 4.等待就绪事件:有事件就绪,对使用Connectio…

【Java核心知识】ThreadLocal相关知识

ThreadLocal 什么是ThreadLocal ThreadLoacal类可以为每个线程保存一份独有的变量,该变量对于每个线程都是独占的。实现原理为每个Thread类中包含一个ThreadHashMap,key为变量的对应的ThreadLocal对象,value为变量的值。 在日常使用中&…

python编写MQTT订阅程序

Download | Eclipse Mosquitto 1、下载: https://mosquitto.org/files/binary/win64/mosquitto-2.0.17-install-windows-x64.exe 2、安装: 3、conf配置 1)使用notepad打开“C:\Program Files\mosquitto\mosquitto.conf”另存为c:\myapp\msquitto\mo…

VueRouter使用详解(5000字通关大全)

Vue Router是一个官方的路由管理器,它可以让我们在Vue应用中实现单页面应用(SPA)的效果,即通过改变URL而不刷新页面来显示不同的内容。Vue Router可以让我们定义多个路由,每个路由对应一个组件,当URL匹配到…

RT-Thread 线程间同步

线程间同步 在多线程实时系统中,一项工作的完成往往可以通过多个线程协调的方式共同来完成,那么多个线程之间如何 “默契” 协作才能使这项工作无差错执行?下面举个例子说明。 例如一项工作中的两个线程:一个线程从传感器中接收…

菜鸟教程《Python 3 教程》笔记(12):推导式

菜鸟教程《Python 3 教程》笔记(12) 12 推导式12.1 列表推导式12.2 字典推导式12.3 集合推导式12.4 元组推导式(生成器表达式) 笔记带有个人侧重点,不追求面面俱到。 12 推导式 出处: 菜鸟教程 - Python3 …

nodejs中如何使用Redis

Redis介绍: Redis 是一个开源的内存数据结构存储器,一般可以用于数据库、缓存、消息代理等,我们常在项目中用redis解决高并发、高可用、高可扩展、大数据存储等问题; 它本质上是一个NoSql(非关系型数据库)…

Linux开机启动Tomcat

需求背景 Linux重启后要手动执行"startup.sh"启动Tomcat&#xff0c;比较麻烦&#xff0c;想要Linux开机启动Tomcat。 开机启动 #---------------------------------------------------------- sudo tee /usr/bin/tomcat.sh <<-EOF #! /bin/bash nohup /opt/to…

Compose学习 - remember、mutableStateOf的使用

一、需求 在显示界面中&#xff0c;数据变动&#xff0c;界面刷新是非常常见的操作&#xff0c;所以使用compose该如何实现呢&#xff1f; 二、remember、mutableStateOf的使用 我们可以借助标题的两个概念 remember、mutableStateOf来完成。这里先不写定义&#xff0c;定义…

C#基础知识点记录

目录 课程一、C#基础1.C#编译环境、基础语法2.Winform-后续未学完 课程二、Timothy C#底层讲解一、类成员0常量1字段2属性3索引器5方法5.1值参数&#xff08;创建副本&#xff0c;方法内对值的操作&#xff0c;不会影响原来变量的值&#xff09;5.2引用参数&#xff08;传的是地…

Unix System V BSD POSIX 究竟是什么?

学习Linux系统,很多同学对这些单词概念很模糊、一脸懵逼! 黄老师觉得,了解了历史,才会真正明白这些单词的含义,坐稳、黄老师发车了!!! 首先介绍一下什么是Unix? UNIX(非复用信息和计算机服务,英语:Uniplexed Information and Computing Service,UnICS)取“UNI…