目录
一、NB-IOT简介
实际上,就是NB-Iot彻底引爆了物联网的,大概2018年左右,NB推广如火如荼,同时广域网、低功耗的LPWAN网络也逐渐传开,现在回头来看,还是有些过火了,实际上还是得结合市场的。目前NB的主要市场还是在市政领域,水表、气表、停车场地磁等等,基本上是政企推动的;技术上,NB和LoRa有交集,但是更多的互补关系。NB在推广上主要的是电信,移动也算还好,联通就比较少看到了,因为NB基站需要重新部署,涉及到投入产出的问题,就目前来看,电信应该县级城市都有覆盖了。
在使用上,我之前主要还是使用电信的,电信刚开始只有电信IOT平台,开账号得企业,比较麻烦,说实话也不好用,后面电信又出了个AEP平台,这个整体体验还不错,所以我们这里也以AEP为主,驱动程序也主要还是框架,像移动的onenet和阿里平台还没具体实现,有时间再慢慢完善。AEP网址在这儿,自己去注册https://sso.ctwing.cn/login?service=https%3A%2F%2Fwww.ctwing.cn%2Flogin%2Fcas#/
二、NB-IOT要素
NB要素主要有以下几个:
IMEI--模块识别码,平台一般当作身份信息;
ICCID--20位的卡号,装NB卡了才有;
NB卡--这个需要联系客户经理给你开卡,跟我们平时的4G卡外形一样,但有本质区别,不能通用,其中电信NB卡一般是按次收费,一般2万次/年,算下来差不多半小时发送一次,够用了,移动一般是一年360M,具体套餐由费用决定,要跟客户经理商谈;
模块费用:我们这里的模块型号是移远的BC260Y,支持电信跟移动,模组+外设元器件估计也是要20+的,我这里购买成品模块,36块,NB卡10块,购买可以参考这里https://item.taobao.com/item.htm?_u=kpfmfmg80d4&id=675137321790&skuId=5031188443830&spm=a1z09.2.0.0.67002e8d7mF2cG
低功耗:这里暂时没有涉及低功耗,因为这个最好用具体项目讲解比较好理解,NB的低功耗比较特别,不仅跟模块配置有关,还跟NB卡有关,不同的APN对应不同的低功耗水平,简单讲就是唤醒后多久进入睡眠是卡决定的,不过这里不同厂家可能不一样,仅做参考。
三、代码详解
整个流程跟4G类似,差别在于,NB一般对接中间平台,如电信AEP、移动onenet,在平台上编辑插件对设备消息进行解析,解析完后推送给你自己的平台,整个流程也没有很轻松,刚开始电信的NB只能对接电信IOT平台,刚开始bug又很多,使用起来确实不太畅快。
硬件连接上,使用串口2,波特率是9600,我采购的模块电源是3.3V的,STM32的PA2接模块RX,PA3接模块TX。
这里的流程需要根据不同的平台分开定义,我们先看AEP的,在网络注册流程中,只要卡没问题,模块会自己注册,我们只要不断查询注册状态即可,注册成功一般会返回
+CEREG: 0,1
主要是后面的1这个状态值,有时候是5,是这两个值就可以认为注册成功了。
网络没问题之后,就要根据不同平台走连接流程了,AEP相对比较简单,如下图所示,IP和端口都是固定的,这里用的是LWM2M协议,COAP的端口是5682,连接成功后就可以进行数据收发了。
cpp
/*
================================================================================
描述 : 注册流程
输入 :
输出 :
================================================================================
*/
void drv_bc260_reg_process(void)
{
static u32 last_sec_time=0, wait_time=2;
u32 now_sec_time=drv_get_sec_counter();
if(now_sec_time-last_sec_time>wait_time)
{
switch(g_sBc260Work.state)
{
case BC260_STATE_START:
{
drv_bc260_send_at("QRST=1");//复位模块
g_sBc260Work.state=BC260_STAT_INIT;
wait_time=5;
break;
}
case BC260_STAT_INIT:
{
drv_bc260_send_at("CSQ");
delay_os(200);
drv_bc260_send_at("QSCLK=0");//禁用休眠模式
delay_os(200);
drv_bc260_send_at("CSCON=1");//使能信令连接状态上报
g_sBc260Work.state=BC260_STAT_CEREG;
wait_time=2;
break;
}
case BC260_STAT_CEREG://网络注册状态
{
drv_bc260_send_at("CEREG?");//查询
wait_time=2;
break;
}
case BC260_STAT_AEP_INIT:
{
drv_bc260_send_at("NNMI=1");//将数据接收模式设置为直吐模式
delay_os(200);
drv_bc260_send_at("NCFG=0,86400");//配置生命周期时间,秒
g_sBc260Work.state=BC260_STAT_AEP_CONNECT;
wait_time=2;
break;
}
case BC260_STAT_AEP_CONNECT://连接到服务器
{
drv_bc260_send_at("NCDPOPEN=\"221.229.214.202\",5683");
wait_time=3;
break;
}
case BC260_STAT_OK:
{
if(g_sBc260Work.imei_buff[0]==0)
{
drv_bc260_send_at("CGSN=1");
delay_os(200);
}
else if(g_sBc260Work.iccid_buff[0]==0)
{
drv_bc260_send_at("QCCID");
delay_os(200);
}
else
{
drv_bc260_send_at("CSQ");
}
wait_time=5;
break;
}
}
last_sec_time=drv_get_sec_counter();
}
}
数据解析也是匹配关键字,具体如下:
cpp
/*
================================================================================
描述 : 十六进制字符串转字节数组
输入 :
输出 :
================================================================================
*/
void hex_str_to_array(char *hex_str, char *out_array, u16 array_len)
{
for(u16 i=0; i<array_len; i++)
{
sscanf(hex_str+i*2, "%02hhX", &out_array[i]);
}
}
/*
================================================================================
描述 : 接收处理
输入 :
输出 :
================================================================================
*/
void drv_bc260_recv_process(void)
{
u16 recv_len;
char *pData=NULL;
if(g_sBc260Work.pUART->iRecv>0)
{
recv_len=0;
while(recv_len<g_sBc260Work.pUART->iRecv)
{
recv_len=g_sBc260Work.pUART->iRecv;
delay_ms(5);
}
char *pBuff=(char*)g_sBc260Work.pUART->pBuff;
printf("bc260 recv=%s\n", g_sBc260Work.pUART->pBuff);
if( (pData=strstr(pBuff, "+NNMI: "))!=NULL )//AEP接收到数据
{
pData+=strlen("+NNMI: ");
u16 data_len=atoi(pData);
if( (pData=strstr(pData, ","))!=NULL && data_len<100)
{
pData+=1;
char data_buff[100]={0};
hex_str_to_array(pData, data_buff, data_len);
printf_hex("data=", (u8*)data_buff, data_len);
if(g_sBc260Work.fun_recv_parse!=NULL)
{
g_sBc260Work.fun_recv_parse((u8*)data_buff, data_len);
}
}
}
else if((pData=strstr(pBuff, "+QLWEVTIND: "))!=NULL )
{
pData+=strlen("+QLWEVTIND: ");
u8 type=atoi(pData);
if(type==3)//AEP服务器订阅成功
{
g_sBc260Work.state=BC260_STAT_OK;
}
}
else if((pData=strstr(pBuff, "+CEREG: "))!=NULL )
{
pData+=strlen("+CEREG: ");
pData+=2;
u8 state=atoi(pData);
if(state==1 || state==5)//网络已注册
{
switch(g_sBc260Work.server_type)
{
case BC260_SERVER_ONENET://移动OneNet
{
break;
}
case BC260_SERVER_CHINANET_IOT://电信IOT
{
break;
}
case BC260_SERVER_CHINANET_AEP://电信AEP
{
g_sBc260Work.state=BC260_STAT_AEP_INIT;//进入AEP的初始化
break;
}
case BC260_SERVER_ALI://阿里平台
{
break;
}
}
}
}
else if((pData=strstr(pBuff, "+CSQ: "))!=NULL )
{
pData+=strlen("+CSQ: ");
g_sBc260Work.rssi=atoi(pData);
printf("***rssi=%d\n", g_sBc260Work.rssi);
}
else if((pData=strstr(pBuff, "+CGSN: "))!=NULL )
{
pData+=strlen("+CGSN: ");
memcpy(g_sBc260Work.imei_buff, pData, 15);
printf("***imei=%s\n", g_sBc260Work.imei_buff);
}
else if((pData=strstr(pBuff, "+QCCID: "))!=NULL )
{
pData+=strlen("+QCCID: ");
memcpy(g_sBc260Work.iccid_buff, pData, 20);
printf("***iccid=%s\n", g_sBc260Work.imei_buff);
}
UART_Clear(g_sBc260Work.pUART);
}
}
这里比较特殊的是,跟ESP8266和AIR780相比,NB返回的数据是十六进制的字符串,需要转换,以上的hex_str_to_array()函数可以看下,对于发送也是如此,要把字节数组转成字符串,具体如下:
cpp
/*
================================================================================
描述 :
输入 :
输出 :
================================================================================
*/
void drv_bc260_send_data(u8 *buff, u16 len)
{
if(g_sBc260Work.state!=BC260_STAT_OK)
return;
static char send_buff[200]={0};
if(len*2+10<sizeof(send_buff))
{
memset(send_buff, 0, sizeof(send_buff));
sprintf(send_buff, "AT+NMGS=%d,", len);
for(u16 i=0; i<len; i++)
{
char temp_buff[5]={0};
sprintf(temp_buff, "%02X", buff[i]);
strcat(send_buff, temp_buff);
}
strcat(send_buff, "\r\n");
// printf("send data=%s\n", send_buff);
drv_bc260_uart_send(send_buff);
}
}
应用数据接收后依然采用回调函数的方式进行处理,自己应用层去定义和注册,跟之前的WIFI和4G都差不多。
软件方面基本上就这些了,对于AEP平台没有很复杂,后面有时间再继续完善Onenet等其它平台的流程。应用层其实就是初始化+主程序循环了。
这里我在user_app.c里加了点测试代码,这样就可以随时发送测试了。
四、平台端
平台端注册完后要创建产品,具体流程可以参考文档,
注意点是协议要选LWM2M,然后添加设备的IMEI要是你测试模块的IMEI,这样才能收得到数据,整体页面如下:
设备上线后指示灯会由灰转绿色,数据查看和指令设置如下图所示,点击后会有响应显示。
数据格式可以选择16进制,便于观察;指令下发直接输入就行了,但是下发后模块一般不会马上收到,需要在模块主动上报数据后一个区间内才收的到,这也是低功耗的代价了,以后具体项目再深入讲解。
由上图可知,主动发送后大概过了10秒才收到刚才下发的指令。
NB设备端的整个开发流程大概就是这样了,平台端还有个物模型,就是解析插件,用来解析数据,具体可以看教程https://www.ctwing.cn/sbgl/539#see,这里贴出以前做过的示例:
驱动程序:https://download.csdn.net/download/ypp240124016/89180826
文档资料:https://download.csdn.net/download/ypp240124016/89181042
本项目的交流QQ群:701889554