NB-Iot烟感07:NB-IOT 无线通讯程序开发

一、搭建程序的开发环境:

NB-Iot 无线通讯板我们选择的主控芯片是 我们选择的是华大半导体的HC32F005C6PA, 这个芯片是基于M0的32位低功耗单片机。

开发环境我们选择的是Keil MDK, 这个非常通用,在这里我就不给大家介绍了。

二、准备工作:

NB-Iot通讯的主要功能是为了将烟感的数据上次到云平台。 

目前的NB-Iot 的云平台主要中国移动的OneNet 和中国电信cwing。今天无际单片机编程主要给大家介绍一下BC26和OneNet平台的通讯。

为什么不选择中国电信平台? 中国电信也是可以的,只不过中国电信平台需要企业注册认证才可以注册账号,有点麻烦。

1.我们首先要注册一个OneNet 账号,OneNet个体就可以注册。

打开网页,大家在右上脚,注册账号,然后再登陆。

我们登录以后需要新建自己的项目, 注意要选择旧版本。

新建项目如下,可以根据自己的需求 填写项目名称,选择项目标签。点增加即可。

项目新建完成后如下图所示:

接下来,我们需要添加产品:

添加设备的时候,需要和我们的产品设备对应 ,每个NB-IOT 模块都一个对应的IMEI号。IMSI是设备安装的SIM卡的卡号 也是15位。

以上就是我们平台准备的工作,接下来,我们就开始我们程序的开发

三、产品NB-Iot 通讯指令流程

NB-IOT 是基于运营商的基站通讯的,所有需要准备一张有效的NB-Iot SIM卡。我们选择的是贴片SIM卡,可以直接使用。

我们先研究一下NB-IOT 的初始化过程。(参考BC26软件开发相关资料)

1.NB-Iot的初始化指令:

获取模块的IMEI

指令: AT + CGSN = 1;

OneNet平台注册需要

490154203237511 就是模块的IMEI序号,平台注册要用。

程序处理:

如果串口发送了 AT + CGSN = 1;NB模块没有回复,就需要确定是否模块开机失败?或者模块硬件有故障等。(如果模块没有回复, 需要尝试发送3次,连续3次失败表示他获取失败)

如果获取到了有效值,则执行下一条指令

获取产品SIM卡的IMSI                OneNet平台注册需要

 指令: AT+CIMI

460001357924680 SIM卡的IMSI序号

程序处理:

获取失败: 表示检测SIM卡失败,设备异常,通过提示灯报故障,

获取成功:执行下一条指令

查询信号值

指令: AT+CSQ

从指令可以获取 当前的CSQ值为22,表示为有效值。CSQ的其他取值的含义。

0  -113 dBm 或以下

1  -111 dBm

2~30 -109 至-53 dBm

31 -51 dBm 或以上

程序初始化过程中,我们需要判断CSQ的值 需要大于10,且小于32.才能有效,负责后期会严重影响产品的稳定性,需要提示生产者 产看是否天线没有安装好,或其他问题。

程序处理:

CSQ异常: 表示检测SIM卡失败,设备异常,通过提示灯报故障,

CSQ的值有效:执行下一条指令

注意:CSQ 的值需要系统每隔2秒,连续循环查询20次,等待模块入网。

查询模块网络是否已经注册。

指令: AT+CEREG?

本条指令说明:

+CEREG: 1,1  

第一个1,表示允许上报网络注册状态,如果=0,表示禁止上报网络注册状态 URC

第二个1,表示EPS 注册状态。其他取值说明。

0 未注册,MT 当前未搜索网络

1 已注册,归属网络

2 未注册,但 MT 当前正在尝试附着或搜索网络以进行注册

3 注册被拒绝

4 未知(例如:超出 E-UTRAN 覆盖范围)

5 已注册,漫游状态

程序处理:

发送 AT+CEREG?,模块需要回复+CEREG: 1,1,或+CEREG: 0,1 表示网络已注册。

回复失败:循环等待查询20,间隔2秒。 超时提示故障,需要查询模块的硬件故障

回复成功:   执行下一条指令。

查询 PDP 上下文激活状态

指令:AT+CGATT?

+CGATT:0:    

0  去附着或未附着

1  附着

程序处理:

发送 AT+CGATT?模块需要回复+CGATT:1  

回复失败:循环等待查询20,间隔2秒。 超时提示故障,需要查询模块的硬件故障

回复成功:   执行下一条指令。

查询工作频段

指令: AT+QBAND?

+QBAND:5 的有效值:1、3、5、8、20 等;

我们的是中国移动,取值需要是8.

程序处理:

发送 AT+CGATT?模块需要回复+QBAND:8

回复失败:执行AT+QBAND = 8 来设置网络

回复成功:   执行下一条指令。

2. NB-Iot注册网络:

网络注册,需要用户,根据NB模块的IMEI 和IMSI 再OneNet 平台上注册,再执行本段程序代码,否则会失败。

注册流程如下:

详细说明:

AT+MIPLCREATE指令

功能:创建 OneNET  通信套件实例

程序开发说明:

本条指令的响应时间最大为5秒,只能发送一次,等待5秒超时。

返回值: +MIPLCREATE:0

获取失败:需要执行AT+MIPLCLOSE=0 关闭实例,再执行AT+MIPLDELETE=0 删除实例,再执行AT+MIPLCREATE。

获取成功:执行下条指令。

AT+MIPLADDOBJ=0,3311,1,”1″,4,2指令 (重要)

功能: 添加 LwM2M 对象

说明: NB-IOT 是基于LwM2M 协议开发的,需要增加LwM2M才可以和平台通讯

指令说明:

(参数说明:省略,有疑问的 请找无际单片机编程。

程序开发说明:

本条指令的响应时间最大为5秒,只能发送一次,等待5秒超时。

返回值:OK

获取失败:需要执行AT+MIPLCLOSE=0 关闭实例,再执行AT+MIPLDELETE=0 删除实例,再从AT+MIPLCREATE开始执行。 本条指令一般不会出错。

获取成功:执行下条指令。

AT+MIPLOPEN=0,86400 指令(重要)

功能说明: 发送注册请求

指令说明:86400 设置设备的生命周期,实际生命周期为<lifetime> × 0.9。范围:

15~268435455,若设置为 0,则表示 3600 秒;单位:秒。

返回值:

+MIPLEVENT: 0,1  //开始连接到 Bootstrap 服务器。

+MIPLEVENT: 0,2  //成功连接到 Bootstrap 服务器。

+MIPLEVENT: 0,4  //成功连接到 OneNET 平台。

+MIPLEVENT: 0,6 //成功注册到 OneNET 平台。

如果返回:+MIPLEVENT: 0,3 表示OneNet 平台没有注册本设备

程序处理:

获取失败: 获取到 +MIPLEVENT: 0,4 表示注册到平台,否则注册失败,执行AT+MIPLCLOSE=0 关闭实例,再执行AT+MIPLDELETE=0 删除实例,再从AT+MIPLCREATE开始执行。

获取成功:执行下一条指令

AT+MIPLOBSERVE: 0,69234,1,3311,0,-1 指令(重要)

功能说明:响应订阅请求

指令说明:

返回值:

+MIPLDISCOVER: 0,26384,3311 //接收到发现资源请求。

程序处理:

本条指令的响应时间最大为5秒,只能发送一次,等待5秒超时。 获取到数据后,需要获取msgid的值,下条指令需要该值。

获取失败:因网络稳定型问题,会导致本条指令运行失败或错误。

如果失败,需要执行AT+MIPLCLOSE=0 关闭实例,再执行AT+MIPLDELETE=0 删除实例,再从AT+MIPLCREATE开始执行。

获取成功:执行下一条指令

AT+MIPLDISCOVERRSP=0,26384,1,19,”5850;5851;5706;5805″ 指令(重要)

功能说明:该命令用于响应来自 OneNET 平台的发现资源请求。

指令说明:

程序响应: OK

程序处理:

本条指令的响应时间最大为5秒,只能发送一次,等待5秒超时。 msgid的值需要从上一条指令获取。

获取失败:因网络稳定型问题,会导致本条指令运行失败或错误。

如果失败,需要执行AT+MIPLCLOSE=0 关闭实例,再执行AT+MIPLDELETE=0 删除实例,再从AT+MIPLCREATE开始执行。

获取成功:表示设备再OneNet平台注册成功。 需要获取。

3. NB-Iot上传数据。

指令说明:

程序处理:

如果发送成功:则返回OK。

返回失败: 需要先确定指令的数据格式是否正常。 如果正常则需要检测设备的网络是否异常,如果异常需要执行AT+MIPLCLOSE=0 关闭实例,再执行AT+MIPLDELETE=0 删除实例,再从AT+MIPLCREATE开始重新注册网络。

返回正常: 设备进入休眠,或等待处理其他数据。

4更新设备生命周期,如果确实这条指令,会导致设备离线。

 AT+MIPLOPEN=0,86400 设置设备的生命周期为86400秒,也就是说,再设备工作86400之前需要更新设备的生命周期,才能确保产品持续在线,否则会离线。

返回值:+MIPLEVENT: 0,11 //更新结果。

程序开发说明:

本条指令的响应时间最大为5秒,只能发送一次,等待5秒超时。负责就需要执行AT+MIPLCLOSE=0 关闭实例….

因为网络问题,执行本条指令需要等待5秒时间。需要再设备的生命周期结束前执行本条指令。

四、程序开发:

上面我们了解了NB-Iot的通讯流程和相关指令,下面我们开始程序的相关开发,再这里我给大家做个简单的介绍,因为产品的选择的单片机和平台都不一样。

程序开发逻辑:

单片机控制NB-Iot模块的PowerKey脚位拉低800毫秒开机,详细请参考BC26的硬件设计。

见上图,单片机控制MCU_PKEY 拉高500ms

开始NB-Iot 的指令控制:

 AT + CGSN = 1;

AT+CIMI

AT+CSQ

…..

以上已介绍。

备注:每条指令的超时时间,执行次数都不一样,都需要等待正确的回复才能执行下一条指令。

在这里大家需要做一个结构体数组:代码如下:

typedef struct  stCmdStr
{
    unsigned char  id;	                      //   发送ID号 
    unsigned char  const *str;             // 发送AT指令包 
    unsigned char  neddack;                //  是否需要检验应答
    unsigned short len;                          //发送数据长度
    unsigned char  ResenTimes;           //发送的次数
    unsigned short DelaySetTim;           // 发送前延时时间
    unsigned short SetInterDelayTim;     //  发送间隔时间  
    unsigned char  Next_SucIdx;           //  下个指针  成功操作后
    unsigned char  Next_FaiIdx;            //  下个指针  失败操作后   
}stCmdStrTy;

stCmdStrTy atCmdInMain;    ////

void SetCmdBuff(unsigned char scmid,stCmdStrTy * cmd)
{    				   
   atCmdBuff[scmid].id = cmd->id;
   atCmdBuff[scmid].str = cmd->str;
   atCmdBuff[scmid].neddack = cmd->neddack;
   atCmdBuff[scmid].len = cmd->len;
   atCmdBuff[scmid].DelaySetTim = cmd->DelaySetTim;
   atCmdBuff[scmid].SetInterDelayTim = cmd->SetInterDelayTim;  
   atCmdBuff[scmid].ResenTimes = cmd->ResenTimes;
   atCmdBuff[scmid].Next_FailOverTimID = cmd->Next_FailOverTimID;     
}

void NBIOT_SysteMode_ListInit(void)
{
    atCmdInMain.id = NB_MODE_RESET;    ///确认模块AT指令收发是否正常。 返回OK 表示正常
    atCmdInMain.str = &NBIOT_BC95_At_Com[NBIOM_ATCOM_RESET][0];
    atCmdInMain.neddack = TRUE;
    atCmdInMain.len=Fun_GetStrLen((unsigned char *)NBIOT_BC95_At_Com[NBIOM_ATCOM_RESET]);
    atCmdInMain.DelaySetTim = 0;
    atCmdInMain.SetInterDelayTim = 6000;
    atCmdInMain.ResenTimes = 3;
    atCmdInMain.Next_SucIdx = NB_MODE_AT;
    atCmdInMain.Next_FaiIdx = NB_MODE_RESET;
    SetCmdBuff(NB_MODE_RESET,&atCmdInMain);

    atCmdInMain.id = NB_MODE_AT;    ///确认模块AT指令收发是否正常。 返回OK 表示正常
    atCmdInMain.str = &NBIOT_BC95_At_Com[NBIOT_ATCOM_AT][0];
    atCmdInMain.neddack = TRUE;
    atCmdInMain.len=Fun_GetStrLen((unsigned char *)NBIOT_BC95_At_Com[NBIOT_ATCOM_AT]);
    atCmdInMain.DelaySetTim = 0;
    atCmdInMain.SetInterDelayTim = 200;
    atCmdInMain.ResenTimes = 20;
    atCmdInMain.Next_SucIdx = NB_MODE_CGSN;
    atCmdInMain.Next_FaiIdx = NB_MODE_RESET;
    SetCmdBuff(NB_MODE_AT,&atCmdInMain);
  .....
}
void TxThread(void)
{
  static unsigned short InterTimer=0; 
  switch(satCmdStatu)
  {
    case ATSTATUSEND:   //tx   数据发送
    {
		if(SystemModeIdx < NB_MODE_SLEEP)
		{
			if(SentReDelayT > 0)////发送数据前延时
			{
                            SentReDelayT--;
                            return;	 
			} 
                        satCmdAck = ATNULL;
                        UART1_SendStrLen((unsigned char *)atCmdBuff[SystemModeIdx].str,atCmdBuff[SystemModeIdx].len);//发送数据
			InterTimer = atCmdBuff[SystemModeIdx].SetInterDelayTim;  ///间隔时间
			if(atCmdBuff[SystemModeIdx].neddack)//判断本次发送是否需要等待应答
			{	
				satCmdStatu = ATSTATUCHECK; //checkack 检测应答
			}
			else
			{
				SystemModeIdx = atCmdBuff[SystemModeIdx].Next_SucIdx;////指针Next
				SentReDelayT = atCmdBuff[SystemModeIdx].DelaySetTim; ////更新发送数据前延时时间			
				return;
			}
			satCmdWaitCnt = 0;   //间隔时间复位  
			satCmdErrorCnt = 0;	        // 错误次数清零
			satCmdRepeatCnt = 0;            // 重发次数清零
		}
		else
		{
			//myprintf("NO CMD");
			satCmdStatu = ATSTATUSEND;
			return;
		}
    }
    break; 
    case ATSTATUCHECK:  //应答检测
    { 		switch(satCmdAck)
		{
			case ATNULL: ///if(ATNULL == j ) // 等待
			case ATERROR:// TCP准备好发送数据返回OK
			{////没有回复
				if(satCmdWaitCnt++ > InterTimer)// 等待次数超时
				{   								 
					satCmdStatu = ATSTATUREPEAT;// 重发
					satCmdWaitCnt = 0;
				}
			}
			break;
			case ATATOK:
                        {
			  satCmdStatu = ATSTATUSEND;	
			  satCmdErrorCnt = 0;	 
			  satCmdRepeatCnt =0;
			  atCmdTx =  ATTXSENDOK;	//TX 发送Ok
                          satCmdAck = ATNULL;  
			  SystemModeIdx = atCmdBuff[SystemModeIdx].Next_SucIdx;
			  SentReDelayT = atCmdBuff[SystemModeIdx].DelaySetTim;
                        }
			break;
			case ATRESTR:// TCP准备好发送数据返回OK
			{
			  satCmdStatu = ATSTATUSEND;	
			  satCmdErrorCnt = 0;	 
			  satCmdRepeatCnt =0;
			  atCmdTx =  ATTXSENDOK;	//TX 发送Ok
			}
			break;
			default:
			{
				 myprintf("NO Ack\n"); 

				//sleep(1);
				satCmdWaitCnt = 0;
				satCmdRepeatCnt=0;
                                 satCmdAck = ATNULL;  
				satCmdStatu = ATSTATUREPEAT; //其它状态重发  	
			}
			break;			
		}
    }
    break;
    case ATSTATUREPEAT:
    {
        satCmdRepeatCnt++;
        if(satCmdRepeatCnt >= atCmdBuff[SystemModeIdx].ResenTimes)// 重发计数超
        {
            if(NB_MODE_WAIT_SLEEP == atCmdBuff[SystemModeIdx].Next_FailOverTimID)
            {////系统进入延时
                SystemSleepTime = TIME_SYSTEM_SLEEP_ERROR;
                satCmdStatu = ATSTATUSEND;	
                satCmdErrorCnt = 0;	 
                satCmdRepeatCnt =0;
                SystemModeIdx =NB_MODE_RESET;
                 myprintf("模块进入间隔休眠状态中\r\n");
                  satCmdAck = ATNULL;  
                return;
            }
            else
            {
              satCmdStatu = ATSTATUSEND;// 发送状态复位  
              SystemModeIdx = atCmdBuff[SystemModeIdx].Next_FailOverTimID;
              SentReDelayT = atCmdBuff[SystemModeIdx].DelaySetTim;
            }   
        }
        else
        {
            satCmdStatu = ATSTATUCHECK;// 发送完后检测应答
        }	
        satCmdWaitCnt = 0;
        satCmdWaitCnt = 0;
        satCmdAck = ATNULL;
        UART1_SendStrLen((unsigned char *)atCmdBuff[SystemModeIdx].str,atCmdBuff[SystemModeIdx].len); 
    }
    break;
  }    
}

完整程序篇幅太长,这里就不放了,可以找无际单片机编程获取。

NB-Iot 接收程序:

为了防止串口接收数据丢失,我们订了一个256字节的队列,

 Queue256 Uart1RxMsg; 

串口接收主函数如下,使用到了回调函数:

void RxThread(void)
{
  if(satCmdAck == ATNULL)
  {
    satCmdAck = (unsigned char)NB_IOT_UartRec((Enum_NbIot_Mode)atCmdBuff[SystemModeIdx].id,Fuc_GetAtRespose);
  }
}
串口获取数据解析函数:
unsigned char Uart_GetDat(unsigned char *getbuf)
{
    unsigned char i;
    unsigned char len,dat,idx;
    static unsigned char lenx,GetDat_Delay;
    len = QueueDataLen(Uart1RxMsg);
    if(len)
    {
      if(len == lenx)
      {///接收数据延时
          GetDat_Delay++; 
      }
      else
      {///收到了新的数据
        lenx = len;
        GetDat_Delay = 0;
      } 
      if(GetDat_Delay > 100)
      {/////收到数据起,延时120毫秒,开始判断接收到的数据
          idx = 0xff;
          i = 0;
          do
          {
            i++;
            QueueDataOut(Uart1RxMsg,&dat);
            if(dat == 0x0A)   /////10   0A '\n'
            {
              if((idx == 0xff) || (idx < 2))
              {
                idx = 0; 
              }
            }
            else if(dat == 0x0D)////12  0D  '\r'
            {
              if(idx == 0)
                idx = 0;
              else if((idx > 1) && (idx != 0xff)) 
                return idx;
              else
                idx = 0xff;
            }              
            else
            {
              if(idx != 0xff)
              {
                getbuf[idx ++] = dat; 
                if(idx > 59)
                  return 0xff;                
              }
            }
          }while(i< len); 
      }
    }
    else
    {
      lenx = 0;
      GetDat_Delay = 0;
    }
    return 0xff;
}

Ok,那我们这节课就先讲到这里,到此整个项目就实现功能了,本节课较长,大家可以多看几遍加深理解,下篇文章我跟大家讲解这个产品的一些测试验证方法及注意问题

原创干货

NB-Iot烟感06:烟雾检测软件实现及详解

2021-6-30 17:56:47

原创干货

NB-Iot烟感08:NB-IOT烟感探测器测试验证方法

2021-6-30 18:39:29

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索