赞
踩
第一次在产品中使用iot功能,完全没有经验,可能会存在很多bug,请各位大侠不吝赐教,指点一二。
我这次使用的是合宙的air724ug模组,成品是飞思创的FS-MCore-A724UG。
实物图片:
这个成品有2个软件版本:
1.(AT)
AT版本是直接使用合宙的AT指令。
2.(YunDTU)
YunDTU版本功能完善,覆盖绝大多数应用场景,用户只需通过简单的配置,即可实现产品联网。支持 TCP Client、UDP Client、MQTT、HTTP、阿里云、OneNET、百度云、腾讯云和华为云等多种工作模式,并支持 HTTP、FTP 他升级以及 FOTA 自升级。
以MQTT应用为例,说明一下如何通过简单配置,使用YunDTU功能:
下面的配置界面提供了MQTT通信所需要的全部配置,不过只能有一个订阅主题和一个发布主题,配置好之后,上电就会自动连接MQTT服务器,此时设备只管接收消息和发布消息。
我用的是AT版本。在优信电子购买,店家提供了详细的资料,包括STM32使用AT指令的例程。
模块插上中国移动SIM卡,通电后,通过SIM卡,注册网络后会从网络自动获取并激活一个PDP上下文。下面是串口观察到的打印信息:
AT指令必须以回车符(0x0D)结束,以回车+换行(0xA)结束也行。
多数指令关于双引号是可有可无的,例如设置订阅主题的指令:
可以有双引号括住订阅主题:
AT+MSUB="/subtop/1",0
也可以不用双引号:
AT+MSUB=/subtop/1,0
唯一的例外就是发布消息的指令 AT+MPUB 中的消息部分,一定要双引号。
我们一般发送消息使用json格式,比如{“name”:“tom”},这个消息放到AT指令该怎样表示呢?一般的话,我们用C语言字符串表达方法是这样(发布主题可以不用双引号):
char * strPub="AT+MPUB=/pubtopic/1,0,0,\"{\"name\":\"tom\"}\"\r\n";
上面的表达方法是无法正确被AT指令解析的。C语言的双引号使用转义字符反斜杠+双引号来表示,json内嵌的双引号只能使用反斜杠字符+十六进制表示,而C语言字符串中反斜杠需要两个反斜杠表示。
上面4个红框要被 \\22 替代,要写成下面这样,才能准确地被AT指令正确解析:
char * strPub="AT+MPUB=/pubtopic/1,0,0,\"{\\22name\\22:\\22tom\\22}\"\r\n";
/** 发送AT指令并等待应答 * * 指定尝试的次数和每次发送后等待应答的时间 * * @param tryTimes 尝试次数 * @param cmdStr AT命令字符串 * @param replyStr 期望应答字符串 * @param waitTime 每次发送后等待应答的时间 * * @retval 0-没有接收到期望的字符串 * @retval 1-成功接收到期望的字符串 */ int SendATCmdAndWaitReply(int tryTimes,const char * cmdStr, const char* replyStr,int waitTime) { int rx_len,ret; u32 lastTime; // 1.尝试的次数 for(int i=0;i<tryTimes;i++){ if(cmdStr != NULL) tls_uart_write(LTE_4G_COM,(u8 *) cmdStr,strlen(cmdStr)); // 2.记录发送时的时间戳,并做100ms的延时 lastTime = xTaskGetTickCount(); vTaskDelay(50); // 一个时间片是2ms ,这里就是100ms do{ rx_len = tls_uart_try_read(LTE_4G_COM,1); vTaskDelay(10); if(rx_len > 0){ rx_len = tls_uart_try_read(LTE_4G_COM,1); memset(demo_uart3->rx_buf,0,100); // 如果接收到的数据量大于接收缓存 (256 bytes),数据丢弃 // 因为串口中断的缓存有4K Byte,但是demo_uart3->rx_buf的空间只给了256 Byte if(rx_len >= DEMO_UART3_RX_BUF_SIZE){ do{ rx_len = 200; ret = tls_uart_read(LTE_4G_COM, (u8 *) demo_uart3->rx_buf, rx_len); }while(ret > 100); continue; } // 3.判断接收的数据是否有期望的字符串 ret = tls_uart_read(LTE_4G_COM, (u8 *) demo_uart3->rx_buf, rx_len); printf("%s\r\n",demo_uart3->rx_buf); if(ret > 2){ demo_uart3->rx_buf[ret] = 0; if(strstr(demo_uart3->rx_buf,replyStr) != NULL){ printf("find %s\n",replyStr); return 1; } } } // 4.在等待的时间内,每20ms查询一次串口;超时则重新发送AT指令 }while((xTaskGetTickCount()-lastTime )< waitTime); } // 5.如果执行到这里,说明失败了 printf("wait %s fail\n",replyStr); return 0; }
状态机的状态码 :
enum _Stat_4G{ Stat4G_WaitReady=0, Stat4G_CloseEcho=1, // 1.关闭回显命令 Stat4G_CGREG, // 2.查询网络状态 Stat4G_CGATT, // 3.查询是否附着上数据网络 Stat4G_CSTT, // 4.设置接入点 APN,可无参数 Stat4G_CIICR, // 5.激活移动场景 Stat4G_CIFSR, // 6.获取IP地址 Stat4G_Mconfig, // 7.设置MQTT接入密码 Stat4G_Mipstart, // 8.设置MQTT服务器IP 或域名 Stat4G_Mconnect, // 9.设置 心跳时间 Stat4G_Msub, // 10.设置 订阅主题 Stat4G_MQuerySubMsg, // 11.在这里等待消息,死循环 Stat4G_4GIdle, // 12.有了wifi,4G关闭电源 Stat4G_4GPowerReset, // 13.出现错误,关闭电源50ms后再次上电,进入重连状态 WaitReady };
状态机相关源码(使用W801的串口3控制4G模块):
const char * StrNect="NECT"; // 命令 ipstart 的应答 const char * StrAct="ACK"; // 命令 connect 的应答 订阅消息成功后的应答 const char * StrReady="READY"; const char * StrSMS="SMS"; const char * StrOK="OK"; const char * Strdot="."; const char * StrATE0="ATE0\r\n"; const char * StrGREG="AT+CGREG?\r\n"; // 2.查询网络状态 const char * StrGATT="AT+CGATT?\r\n";// 3.查询是否附着上数据网络 const char * StrCSTT="AT+CSTT\r\n"; // 4.设置接入点 APN,可无参数 const char * StrIICR="AT+CIICR\r\n"; // 5.激活移动场景 const char * StrIFSR="AT+CIFSR\r\n"; const char * StrMconnectQry="AT+MCONNECT=?\r\n"; stat4G = Stat4G_WaitReady; for (;;) { ticks20ms++; if(ticks20ms > 50*20){ ticks20ms = 0; // 0.不知道4G模块有没有MQTT的心跳,这里使用查询MQTT状态来模拟心跳 if(Stat4G_MQuerySubMsg == stat4G){ tls_uart_write(LTE_4G_COM,(u8 *) StrMconnectQry,strlen(StrMconnectQry)); printf("PING\n"); } } // 1. 20ms运行一次 vTaskDelay(10); // 有2种消息 if(xQueueReceive(demo_uart3->demo_uart_q,&(cmdmsg),0)){ // 消息1. 4G模块处于断电状态,如果 wifi断开了,重连一次失败,会发送重启4G的消息 if((cmdmsg == DEMO_MSG_4G_RESTART)){ tls_gpio_write(POWER_4G_CTRL_PIN,1); stat4G = Stat4G_WaitReady; // 消息2. 本来4G模块处于正常通信中 ,wifi连上网络了,优先使用wifi,要断开4G电源,任务进入空闲等待 }else if(cmdmsg == DEMO_MSG_4G_ENTER_IDLE){ tls_gpio_write(POWER_4G_CTRL_PIN,0); stat4G = Stat4G_4GIdle; } } switch ( stat4G) { case Stat4G_WaitReady: printf("wait %s\n",StrSMS); if(SendATCmdAndWaitReply(60,NULL,StrSMS,1000)){ stat4G++; vTaskDelay(5*1000); }else{ stat4G = Stat4G_4GPowerReset; } break; case Stat4G_CloseEcho:// 1.关闭回显命令 printf("StrATE0\n"); if(SendATCmdAndWaitReply(TRY_TIME_MAX,StrATE0,StrOK,500)){ stat4G++; }else{ SendATCmdAndWaitReply(2,StrRESET,StrOK,200); stat4G = Stat4G_WaitReady; } break; case Stat4G_CGREG:// 2.查询网络状态 --OK printf("%s\n",StrGREG); if(SendATCmdAndWaitReply(TRY_TIME_MAX,StrGREG,StrOK,500)){ stat4G++; }else{ SendATCmdAndWaitReply(2,StrRESET,StrOK,200); stat4G = Stat4G_WaitReady; } break; case Stat4G_CGATT: // 3.查询是否附着上数据网络 printf("%s\n",StrGATT); if(SendATCmdAndWaitReply(TRY_TIME_MAX,StrGATT,StrOK,500)){ stat4G++; }else{ SendATCmdAndWaitReply(2,StrRESET,StrOK,200); stat4G = Stat4G_WaitReady; } break; case Stat4G_CSTT: // 4.设置接入点 APN,可无参数 printf("%s\n",StrCSTT); if(SendATCmdAndWaitReply(TRY_TIME_MAX,StrCSTT,StrOK,500)){ stat4G++; }else{ SendATCmdAndWaitReply(2,StrRESET,StrOK,200); stat4G = Stat4G_WaitReady; } break; case Stat4G_CIICR: // 5.激活移动场景 StrIICR printf("%s\n",StrIICR); if(SendATCmdAndWaitReply(TRY_TIME_MAX,StrIICR,StrOK,500)){ stat4G++; } break; case Stat4G_CIFSR: // 6.获取IP地址 printf("%s\n",StrIFSR); if(SendATCmdAndWaitReply(TRY_TIME_MAX,StrIFSR,Strdot,500)){ stat4G++; }else{ SendATCmdAndWaitReply(2,StrRESET,StrOK,200); stat4G = Stat4G_WaitReady; } break; case Stat4G_Mconfig: // 7.设置MQTT接入密码 printf("set MQTT ID,pwd\n"); sprintf(Buf,StrMconfig,MAC2STR(g_macBuf)); if(SendATCmdAndWaitReply(TRY_TIME_MAX,Buf,StrOK,1000)){ stat4G++; }else{ SendATCmdAndWaitReply(2,StrRESET,StrOK,200); stat4G = Stat4G_WaitReady; } break; case Stat4G_Mipstart: // 8.设置MQTT服务器IP 或域名 printf("set MQTT server\n"); if(SendATCmdAndWaitReply(TRY_TIME_MAX,StrMipstart,StrNect,1000)){ stat4G++; }else{ SendATCmdAndWaitReply(2,StrRESET,StrOK,200); stat4G = Stat4G_WaitReady; } break; case Stat4G_Mconnect: // 9.设置 心跳时间 printf("set heart beat\n"); if(SendATCmdAndWaitReply(TRY_TIME_MAX,StrMconnect,StrAct,2000)){ stat4G++; }else{ SendATCmdAndWaitReply(2,StrRESET,StrOK,200); stat4G = Stat4G_WaitReady; } break; case Stat4G_Msub: // 10.设置 订阅主题 printf("set sub topic\n"); sprintf(Buf,StrMsubTop,MAC2STR(g_macBuf)); if(SendATCmdAndWaitReply(TRY_TIME_MAX,Buf,StrAct,1000)){ stat4G++; g_internetStat = internetStat_4GConnect; }else{ SendATCmdAndWaitReply(2,StrRESET,StrOK,200); stat4G = Stat4G_WaitReady; } break; case Stat4G_MQuerySubMsg: // 查询消息 -- 大部分时间在这里 rx_len = tls_uart_try_read(LTE_4G_COM,1); if(rx_len > 0){ vTaskDelay(5); rx_len = tls_uart_try_read(LTE_4G_COM,1); // 如果接收到的数据量大于接收缓存 (256 bytes),数据丢弃 if(rx_len >= DEMO_UART3_RX_BUF_SIZE){ do{ rx_len = 200; ret = tls_uart_read(LTE_4G_COM, (u8 *) demo_uart3->rx_buf, rx_len); }while(ret > 100); continue; } ret = tls_uart_read(LTE_4G_COM, (u8 *) demo_uart3->rx_buf, rx_len); demo_uart3->rx_buf[ret] = 0; printf("Msg: %s\n",demo_uart3->rx_buf); // 处理订阅数据 char *p = NULL;//, *q = NULL; p = strstr(demo_uart3->rx_buf, "byte,"); if (p != NULL) { p = p+5; rx_len =p - demo_uart3->rx_buf; // json数据首地址相对于整串数据的偏移量 if((ret -rx_len) < 50) { // json数据的长度不应该大于50 mqtt_msg_process(p); } } 异常情况判断 p = strstr(demo_uart3->rx_buf, "ERROR"); if(p != NULL){ stat4G = Stat4G_4GPowerReset; break; } p = strstr(demo_uart3->rx_buf, "DEACT"); if(p != NULL){ stat4G = Stat4G_4GPowerReset; break;// } } break; case Stat4G_4GIdle: break; case Stat4G_4GPowerReset: tls_gpio_write(POWER_4G_CTRL_PIN,0); vTaskDelay(25); tls_gpio_write(POWER_4G_CTRL_PIN,1); stat4G = Stat4G_WaitReady; break; default: break; } }
一开机,同时启动wifi连接(没有配过网就进入配网)和4G连接;如果wifi连上了,就切断4G电源;wifi断线后重连一次失败(观察过 W801从wifi断线到重连失败的时间约22秒),启动4G连接。
在下图的代码块1中,发送4G重启消息,关闭W801的MQTT心跳。
在状态机中查询到重启消息,4G模块通电并启动连接:
代码块1:
case NETIF_WIFI_JOIN_FAILED:
// 已经连上wifi后,又断网,重连1次都失败后,启动4G
if(internetStat_WifiConnect == g_internetStat){
g_internetStat = internetStat_TryWifi;
if(demo_uart3 != NULL)
tls_os_queue_send(demo_uart3->demo_uart_q, (void *) DEMO_MSG_4G_RESTART, 0);
if(flagMqttIsStart ){
tls_os_queue_send(mqtt_demo_task_queue, (void *)MQTT_DEMO_CMD_STOP_TIMER, 0);
}
printf("WIFI_JOIN_FAILED,start 4G\n");
}else{
printf("NETIF_WIFI_JOIN_FAILED,%d\n",failTimes);
}
break;
在《wm_mqtt_demo.c》的函数 mqtt_demo_task 增加 MQTT_DEMO_CMD_STOP_TIMER 的消息处理:
static void mqtt_demo_task(void *p) { int ret; void *msg; struct tls_ethif *ether_if = tls_netif_get_ethif(); if (ether_if->status) { wm_printf("sta ip: %v\n", ether_if->ip_addr.addr); tls_os_queue_send(mqtt_demo_task_queue, (void *)MQTT_DEMO_CMD_START, 0); } for ( ; ; ) { ret = tls_os_queue_receive(mqtt_demo_task_queue, (void **)&msg, 0, 0); if (!ret) { switch((u32)msg) { case MQTT_DEMO_CMD_START: do { ret = mqtt_demo_init(); if (ret) break; tls_os_queue_send(mqtt_demo_task_queue, (void *)MQTT_DEMO_CMD_LOOP, 0); } while (0); break; case MQTT_DEMO_CMD_HEART: wm_printf("send heart ping\r\n"); mqtt_ping(&mqtt_demo_mqtt_broker); break; case MQTT_DEMO_CMD_LOOP: mqtt_demo_loop(); break; case MQTT_DEMO_CMD_STOP_TIMER: wm_printf("delete heart timer\r\n"); tls_os_timer_stop(mqtt_demo_heartbeat_timer); default: break; } } } }
上图的代码块2就是wifi连上了网络产生的事件,此时要发送4G模块进入空转的消息 DEMO_MSG_4G_ENTER_IDLE 。并启动W801的MQTT任务(如果已经启动过,只需要重连MQTT)
代码块2:
// 此时要切断4G电源,告诉4G任务进入等待 if((internetStat_4GConnect == g_internetStat) ||(internetStat_Try4G == g_internetStat) ||(internetStat_TryWifi == g_internetStat) ||(internetStat_WifiOneshot == g_internetStat)){ if(demo_uart3 != NULL) tls_os_queue_send(demo_uart3->demo_uart_q, (void *) DEMO_MSG_4G_ENTER_IDLE, 0); printf("JOIN_SUCCESS when 4G connect,4G off\n"); // 启动 wifi MQTT if(flagMqttIsStart == 0){ mqtt_demo(); flagMqttIsStart = 1; }else{ tls_os_queue_send(mqtt_demo_task_queue, (void *)MQTT_DEMO_CMD_START, 0); printf("wifi mqtt retart\n"); } g_internetStat = internetStat_WifiConnect;
4G模块的任务查询到 DEMO_MSG_4G_ENTER_IDLE 消息后,关闭4G电源,进入空转:
针对这种情况,切断4G模块电源后,要把串口引脚稍作处理,可以有2种方法:
1.引脚配置为输出模式,然后输出低电平;
2.配置为输出,浮空。
不知道那种好,我使用了第二种。上图的程序修改为:
if(xQueueReceive(demo_uart3->demo_uart_q,&(cmdmsg),0)){ // 消息1. 4G模块处于断电状态,如果 wifi断开了,重连一次失败,会发送重启4G的消息 if((cmdmsg == DEMO_MSG_4G_RESTART)){ wm_uart3_tx_config(WM_IO_PA_05); wm_uart3_rx_config(WM_IO_PA_06); tls_gpio_write(POWER_4G_CTRL_PIN,1); stat4G = Stat4G_WaitReady; // 消息2. 本来4G模块处于正常通信中 ,wifi连上网络了,优先使用wifi,要断开4G电源,任务进入空闲等待 }else if(cmdmsg == DEMO_MSG_4G_ENTER_IDLE){ tls_io_cfg_set(WM_IO_PA_05, WM_IO_OPT5_GPIO); // 关闭电源时,把IO口设置为输入高阻 tls_gpio_cfg(WM_IO_PA_05, WM_GPIO_DIR_INPUT, WM_GPIO_ATTR_FLOATING); tls_io_cfg_set(WM_IO_PA_06, WM_IO_OPT5_GPIO); // 关闭电源时,把IO口设置为输入高阻 tls_gpio_cfg(WM_IO_PA_06, WM_GPIO_DIR_INPUT, WM_GPIO_ATTR_FLOATING); tls_gpio_write(POWER_4G_CTRL_PIN,0); stat4G = Stat4G_4GIdle; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。