目录
一、4G模块简介
二、AIR780E驱动程序
三、AIR780使用注意事项
四、结合MQTT传输测试
一、4G模块简介
4G应该是我们日常生活最常见的一种互联网通讯方式了,每个智能手机都配置了,不过手机的4G跟我们物联网领域要用的4G有点区别。首先是物联网采用模块化4G,手机是CPU内部直接集成了4G基带,技术水平肯定高很多了;其次是网速,手机为了满足日常看视频的需求,速度比较快,理论下载速率是150M,采用CAT4标准;而我们要用的物联网4G模块采用CAT1标准,下载速率10M左右,从成本和需求上来讲比较适合。
基于CAT1标准的4G模组没出来之前,4G模块一般是作为网关与服务器的通讯链路,因为成本比较高,一个都要150左右;后来基于CAT1的4G模块出来后价格来到了60元左右,随着用量的增长以及技术的更新,成本在不断地降低,前两年AIR720差不多40元左右,如今我们的测试型号AIR780E单纯模组TB价格也就差不多20块左右。就这4G成本,用在设备端作为通讯链路应该没什么问题了,比如充电宝、共享单车、按摩椅等等,而且AIR780E还有一定的低功耗水平,根据手册(目前还没测试过),极致低功耗可以达到2uA,已经差不多SX1278的水平了。
总体来讲,基于CAT1的4G模组可以满足绝大多数的应用需求,所以需要好好学习,我们这里以合宙的AIR780E为例,写个驱动程序。
二、AIR780E驱动程序
AIR780的驱动跟之前的esp8266 WiFi驱动很类似,可以参考。物联网实战--入门篇之(六)嵌入式-WIFI驱动(ESP8266)-CSDN博客
以及AT指令手册Luat社区
首先来了解下4G模块的入网流程,模块上电后首先会进行网络注册,就是看下有没有信号了,这时候属于基站跟4G模组底层之间的事情,有没有4G卡暂时不影响;第二步就是PDP上下文移动场景创建,简单理解就是创建一个可以用于互联网通讯的身份,这时候需要你的4G模组里有可用的4G卡;第三步是网络附着,4G模组和基站相互交换认证信息,只有附着了网络,后续才能分配IP地址,进行网络通讯。到这一步为止,只要你的卡能用,每个步骤都是自动完成的,驱动程序只要间隔几秒查询下各个步骤的执行情况就行了。
接下来就是配置一些网络参数了,比如多路连接、快发模式和APN,这里的APN相当于VIP等级了,跟开的卡有关系,有的卡保密等级比较高,会有专门的APN账户密码,普通卡就默认就行了。
剩下就是最后一步,激活移动场景,获取IP地址就行了,其它网络连接部分跟esp8266是一样的,对于4G模组一般不作为服务端的,所以不必关系服务端的问题,都是作为客户端连接。接下来先从头文件开始:
基本上都有注释了,AIR780E支持6个客户端连接,基本够用了。状态值就是刚才说明的那些了,入网成功后就可以进行连接操作了。客户端的定义跟esp8266的一样,整体工作的结构体有些区别,首先这里少了服务器相关的内容,其次是多了信号强度、模块序列号IMEI、卡号ICCID和定位信息,其中定位信息其他型号的4G模组不一定有,这个型号有的,我们主要采用了经纬度和海拔,其他暂时忽略。
下面主要看下注册流程和接收处理,代码如下:
/*
================================================================================
描述 : 网络注册函数
输入 :
输出 :
================================================================================
*/
void drv_air780_reg_process(void)
{
static u32 last_sec_time=0, wait_time=2;
// static char cmd_buff[100]={0};
u32 now_sec_time=drv_get_sec_counter();
if(now_sec_time-last_sec_time>wait_time)
{
switch(g_sAir780Work.state)
{
case AIR780_STATE_START:
{
drv_air780_uart_send("ATE0\r\n");
delay_os(200);
drv_air780_send_at("RESET");//复位模块
g_sAir780Work.state=AIR780_STAT_INIT;
wait_time=5;
break;
}
case AIR780_STAT_INIT:
{
drv_air780_uart_send("ATE0\r\n");
delay_os(200);
drv_air780_send_at("CSQ");//查询信号强度
delay_os(200);
drv_air780_send_at("CGNSPWR=1");//打开GPS
delay_os(200);
drv_air780_send_at("CGNSAID=31,1,1,1");//使能位置辅助定位
g_sAir780Work.state=AIR780_STAT_CGREG;
break;
}
case AIR780_STAT_CGREG://查询网络注册状态
{
drv_air780_send_at("CGREG?");//查询网络连接信息
wait_time=3;
break;
}
case AIR780_STAT_CGACT://查询PDP状态
{
drv_air780_send_at("CGACT?");//查询网络连接信息
wait_time=3;
break;
}
case AIR780_STAT_CGATT0://查询附着状态
{
drv_air780_send_at("CGATT?");//查询网络连接信息
wait_time=3;
break;
}
case AIR780_STAT_CGATT1://已附着
{
drv_air780_send_at("CIPMUX=1");//多路连接
delay_os(200);
drv_air780_send_at("CIPQSEND=0");//快发模式
delay_os(200);
drv_air780_send_at("CSTT");//设置APN =\"cmnet\",\"\",\"\"
wait_time=2;
g_sAir780Work.state=AIR780_STAT_CIICR;
break;
}
case AIR780_STAT_CIICR:
{
drv_air780_send_at("CIICR");激活移动场景,获取IP地址
delay_os(1000);
drv_air780_send_at("CIFSR");//查询IP地址
g_sAir780Work.state=AIR780_STAT_OK;
wait_time=3;
break;
}
case AIR780_STAT_OK://入网成功
{
static u8 counts=0;
if(counts++%2==0)
{
if(g_sAir780Work.imei_buff[0]==0)
{
drv_air780_send_at("CGSN");
}
else if(g_sAir780Work.iccid_buff[0]==0)
{
drv_air780_send_at("ICCID");
}
else
{
static u8 flag=0;
if(flag++%2==0)
{
drv_air780_send_at("CSQ");//查询信号强度
}
else
{
drv_air780_send_at("CGNSINF");//查询GPS信息
}
}
}
else
{
drv_air780_connect_process();
}
wait_time=3;
break;
}
}
last_sec_time=drv_get_sec_counter();
}
}
在起始状态下我们直接复位了模块,这样可以避免与之前的状态冲突,这个模块的启动速度很快,2~3秒就完成了;在初始化阶段,把回显关掉,避免影响接收解析,同时打开定位的相关功能,定位功能要几十秒甚至几分钟才能有效果,而且最好天线要靠窗户边或者户外才行;接下去几步都是查询状态,接收处理等等,这里先跳到入网成功后的状态。分别分时进行了网络连接、请求IMEI、请求ICCID、查询信号强度和查询定位信息等任务,其中IMEI和ICCID只要请求到后就不再重复执行了,剩下就是信号强度和定位信息不断更新。
接下来是接收处理的代码:
/*
================================================================================
描述 : 接收处理
输入 :
输出 :
================================================================================
*/
void drv_air780_recv_process(void)
{
u16 recv_len;
char *pData=NULL;
if(g_sAir780Work.pUART->iRecv>0)
{
recv_len=0;
while(recv_len<g_sAir780Work.pUART->iRecv)
{
recv_len=g_sAir780Work.pUART->iRecv;
delay_ms(5);
}
char *pBuff=(char*)g_sAir780Work.pUART->pBuff;
printf("air780 recv=%s\n", g_sAir780Work.pUART->pBuff);
if( (pData=strstr(pBuff, "+RECEIVE,"))!=NULL )//接收到数据
{
pData=pBuff;
while((pData=strstr(pData, "+RECEIVE,"))!=NULL)
{
pData+=strlen("+RECEIVE,");
u8 sock_id=atoi(pData);
// printf("sock_id=%d\n", sock_id);
if(sock_id<AIR780_MAX_LINK_NUM && (pData=strstr(pData, ","))!=NULL )
{
pData+=1;
u16 data_len=atoi(pData);
if( (pData=strstr(pData, ":"))!=NULL )
{
pData+=3;//: 0D 0A
g_sAir780Work.client_list[sock_id].keep_time=drv_get_sec_counter();
printf_hex("air780 data buff= ", (u8*)pData, data_len);
if(g_sAir780Work.fun_recv_parse!=NULL)
{
g_sAir780Work.fun_recv_parse(sock_id, (u8*)pData, data_len);//接收处理
}
pData+=data_len;
}
}
}
}
else if( (pData=strstr(pBuff, "+CSQ: "))!=NULL )//信号强度
{
pData+=strlen("+CSQ: ");
g_sAir780Work.rssi=atoi(pData);
printf("***rssi=%d\n", g_sAir780Work.rssi);
}
else if( (pData=strstr(pBuff, "+CGNSINF:"))!=NULL )//定位信息
{
pData+=strlen("+CGNSINF:");
// printf("GPS=%s", pBuff);
for(u8 i=0; i<5; i++)
{
if( (pData=strstr(pData, ","))!=NULL )
{
pData+=1;
switch(i)
{
case 2:
{
g_sAir780Work.deg_sn=atof(pData);//纬度
break;
}
case 3:
{
g_sAir780Work.deg_we=atof(pData);//经度
break;
}
case 4:
{
g_sAir780Work.hight=atof(pData);//海拔
break;
}
}
}
else
{
break;
}
}
printf("GPS: WE=%.3f deg, SN=%.3f deg, H=%.3f m\n", g_sAir780Work.deg_we, g_sAir780Work.deg_sn, g_sAir780Work.hight);
}
else if((pData=strstr((char*)pBuff, "+CGEV: "))!=NULL)//PDP变化
{
pData+=strlen("+CGEV: ");
if(strstr(pData, "DEACT")!=NULL)//移动场景去活
{
drv_air780_send_at("CIPSHUT");//去激活
delay_os(100);
g_sAir780Work.state=AIR780_STATE_START;
printf("===RDY net_state=AIR780_STATE_START\n");
}
else if(strstr(pData, "ACT")!=NULL)//移动场景激活
{
if(g_sAir780Work.state==AIR780_STAT_CIICR)
{
g_sAir780Work.state=AIR780_STAT_OK;
}
}
}
else if((pData=strstr((char*)pBuff, "+CGREG:"))!=NULL)//注册查询
{
pData+=strlen("+CGREG:");
if((pData=strstr((char*)pBuff, ",1"))!=NULL || (pData=strstr((char*)pBuff, ",5"))!=NULL)//注册成功
{
g_sAir780Work.state=AIR780_STAT_CGACT;
printf("=== +CGREG:1\n");
}
}
else if((pData=strstr((char*)pBuff, "+CGACT: "))!=NULL && (pData=strstr((char*)pBuff, ",1"))!=NULL)//PDP查询
{
g_sAir780Work.state=AIR780_STAT_CGATT0;
printf("=== AIR780_STAT_CGATT0\n");
}
else if((pData=strstr((char*)pBuff, "+CGATT: 1"))!=NULL)//附着查询
{
g_sAir780Work.state=AIR780_STAT_CGATT1;
printf("=== +CGATT: 1\n");
}
else if((pData=strstr((char*)pBuff, ", CONNECT OK"))!=NULL)//连接检测 || (pData=strstr((char*)pBuff, ", ALREADY CONNECT"))!=NULL
{
u8 sock_id=0;
pData--;
printf("### pData=%s\n", pData);
if(pData[0]>='0' && pData[0]<='9')
{
sock_id=pData[0]-'0';
printf("=== air780 sock_id=%d connect ok!\n", sock_id);
if(sock_id<AIR780_MAX_LINK_NUM)
{
g_sAir780Work.client_list[sock_id].conn_state=true;
g_sAir780Work.client_list[sock_id].keep_time=drv_get_sec_counter();
}
}
}
else if((pData=strstr((char*)pBuff, ",CLOSE OK"))!=NULL)//关闭
{
u8 sock_id=0;
pData--;
if(pData[0]>='0' && pData[0]<='9')
{
sock_id=pData[0]-'0';
printf("=== sock_id=%d close ok!\n", sock_id);
if(sock_id<AIR780_MAX_LINK_NUM)
{
g_sAir780Work.client_list[sock_id].conn_state=false;
}
}
}
else if((pData=strstr((char*)pBuff, "+PDP: DEACT"))!=NULL)//PDP失效
{
drv_air780_send_at("CIPSHUT");//去激活
delay_os(100);
g_sAir780Work.state=AIR780_STATE_START;
printf("===RDY net_state=AIR780_STATE_START\n");
}
else if(pBuff[2]>='0' && pBuff[2]<='9' && g_sAir780Work.imei_buff[0]==0)//IMEI检测
{
bool is_number=true;
pBuff+=2;
for(u8 i=0; i<15; i++)
{
if(pBuff[i]<'0' || pBuff[i]>'9')
{
is_number=false;
break;
}
}
if(is_number==true)
{
memset(g_sAir780Work.imei_buff, 0, sizeof(g_sAir780Work.imei_buff));
memcpy(g_sAir780Work.imei_buff, pBuff, 15);
printf("imei=%s\n", g_sAir780Work.imei_buff);
}
}
else if((pData=strstr((char*)pBuff, "+ICCID: "))!=NULL)//ICCID检测
{
pData+=strlen("+ICCID: ");
memset(g_sAir780Work.iccid_buff, 0, sizeof(g_sAir780Work.iccid_buff));
memcpy(g_sAir780Work.iccid_buff, pData, 20);
printf("iccid=%s\n", g_sAir780Work.iccid_buff);
}
UART_Clear(g_sAir780Work.pUART);
}
}
接收处理的基本思路是根据AT指令返回的关键字进行匹配,得到对应动作的状态值并解析。这里要注意的点是,数据接收"+RECEIVE,"要放在第一个,这样才不会被数据区的内容干扰;定位信息要根据AT指令手册去对应解析,我们只获取了经度、纬度和海拔3个信息,有需要详细的自行添加;还有一个比较重要的场景去激活,简单讲就是信号比较差,4G模组跟基站暂时断开了,这时候是需要重新走一遍流程注册的,所以这个状态要检测,不然数据发送和网络连接都会发生错误的,在产品应用的时候如果忽略这个问题那么很可能导致的后果是大面积失联,客户投诉,市场丢失等问题,这个事件在一些共享充电宝厂商身上发生过的。
其它也没有什么大问题了,模块的功能很丰富,我们这里只是基本应用,有需要可以根据AT指令手册自己扩展,剩下的就是回调函数注册、初始化等常规操作了。
三、AIR780使用注意事项
最基本的是天线要插,而且要4G的,不要433M的天线插一根,没啥用的,很多胶棒天线长得都一样,自己要记得标识;然后就是4G卡的问题,很多物联网卡是专用的,不能放在手机使用,不能换设备使用,跟第一个4G模块是绑定的,换设备是不能用的,需要联系开卡的客户经理解除绑定,这么做主要是怕卡被拿去非法使用。
电源问题也是容易被忽略的,模块在网络注册的时候峰值电流比较大,如果电源芯片功率不够的话会导致模块一直重启,在电路设计的时候要严格对照手册进行。
我这里使用的是串口2进行驱动,自己接线要注意,PA2接模块的RX,PA3接模块的RX,我购买的模块供电是5V的,接线就是5V-TX-RX-GND 4根线就行了,很简单,具体购买链接如下,可作参考。https://item.taobao.com/item.htm?_u=apfmfmg56fd&id=720428359893&spm=a1z09.2.0.0.20d42e8dQqVw5W&skuId=5332410429413
四、结合MQTT传输测试
之前的净化器项目是用WiFi作为网络链路的,我们现在改成4G,操作很简单,这也是为什么我们要模块化封装驱动程序的原因,分级分层,实际做项目的时候根据用户需求随时可以切换方案,维护起来不容易出错,下面看下代码,只要改几行就足够了。
就是把app_mqtt.c文件中的WIFI参数配置改成4G的配置就行了,4G的配置更为简单,不要账户密码什么的。这里因为4G模块必须连接互联网,所以MQTT的服务器用EMQ官方的测试服务器,域名是broker.emqx.io,端口1883,MQTT账户密码随便设置一个就行了,服务器不做检查。用户端我们就不用手机了,直接用客户端工具看下有没有数据发过来就行了。
很明显这些数据就是从设备端发送出来的。
4G模组AIR780E的驱动程序暂时就这些了,驱动代码在这里下载:https://download.csdn.net/download/ypp240124016/89110607
本项目的交流QQ群:701889554