文章目录
- 1.air724ug-AT版本的使用
- 1.1 air724简介
- 1.2 模块上电上报信息
- 1.3 AT指令使用流程
- 1.4 MQTT相关的AT指令中关于双引号"的处理
- 2.通过AT指令控制air724的代码详解
- 2.1 发送AT指令的函数
- 2.2 控制air724模块的状态机流程
- 3. wifi和4G并存策略
- 3.1wifi断网后,启动4G模块,如果W801的MQTT已经启动,则要关闭W801的MQTT心跳:
- 3.2 air724正常工作中,wifi连上网,需要关闭4G模块的电源,并使4G模块任务进入空转
- 4.切断air724电源后,串口引脚会有漏电流,使得4G模块的电源灯微亮
第一次在产品中使用iot功能,完全没有经验,可能会存在很多bug,请各位大侠不吝赐教,指点一二。
1.air724ug-AT版本的使用
1.1 air724简介
我这次使用的是合宙的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指令的例程。
1.2 模块上电上报信息
模块插上中国移动SIM卡,通电后,通过SIM卡,注册网络后会从网络自动获取并激活一个PDP上下文。下面是串口观察到的打印信息:
1.3 AT指令使用流程
AT指令必须以回车符(0x0D)结束,以回车+换行(0xA)结束也行。
1.4 MQTT相关的AT指令中关于双引号"的处理
多数指令关于双引号是可有可无的,例如设置订阅主题的指令:
可以有双引号括住订阅主题:
1
2AT+MSUB="/subtop/1",0
也可以不用双引号:
1
2AT+MSUB=/subtop/1,0
唯一的例外就是发布消息的指令 AT+MPUB 中的消息部分,一定要双引号。
我们一般发送消息使用json格式,比如{“name”:“tom”},这个消息放到AT指令该怎样表示呢?一般的话,我们用C语言字符串表达方法是这样(发布主题可以不用双引号):
1
2char * strPub="AT+MPUB=/pubtopic/1,0,0,"{"name":"tom"}"rn";
上面的表达方法是无法正确被AT指令解析的。C语言的双引号使用转义字符反斜杠+双引号来表示,json内嵌的双引号只能使用反斜杠字符+十六进制表示,而C语言字符串中反斜杠需要两个反斜杠表示。
上面4个红框要被 \22 替代,要写成下面这样,才能准确地被AT指令正确解析:
1
2char * strPub="AT+MPUB=/pubtopic/1,0,0,"{\22name\22:\22tom\22}"rn";
2.通过AT指令控制air724的代码详解
2.1 发送AT指令的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63/** 发送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("%srn",demo_uart3->rx_buf); if(ret > 2){ demo_uart3->rx_buf[ret] = 0; if(strstr(demo_uart3->rx_buf,replyStr) != NULL){ printf("find %sn",replyStr); return 1; } } } // 4.在等待的时间内,每20ms查询一次串口;超时则重新发送AT指令 }while((xTaskGetTickCount()-lastTime )< waitTime); } // 5.如果执行到这里,说明失败了 printf("wait %s failn",replyStr); return 0; }
2.2 控制air724模块的状态机流程
状态机的状态码 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30enum _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模块):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204const 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="ATE0rn"; const char * StrGREG="AT+CGREG?rn"; // 2.查询网络状态 const char * StrGATT="AT+CGATT?rn";// 3.查询是否附着上数据网络 const char * StrCSTT="AT+CSTTrn"; // 4.设置接入点 APN,可无参数 const char * StrIICR="AT+CIICRrn"; // 5.激活移动场景 const char * StrIFSR="AT+CIFSRrn"; const char * StrMconnectQry="AT+MCONNECT=?rn"; 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("PINGn"); } } // 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 %sn",StrSMS); if(SendATCmdAndWaitReply(60,NULL,StrSMS,1000)){ stat4G++; vTaskDelay(5*1000); }else{ stat4G = Stat4G_4GPowerReset; } break; case Stat4G_CloseEcho:// 1.关闭回显命令 printf("StrATE0n"); 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("%sn",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("%sn",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("%sn",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("%sn",StrIICR); if(SendATCmdAndWaitReply(TRY_TIME_MAX,StrIICR,StrOK,500)){ stat4G++; } break; case Stat4G_CIFSR: // 6.获取IP地址 printf("%sn",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,pwdn"); 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 servern"); 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 beatn"); 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 topicn"); 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: %sn",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; } }
3. wifi和4G并存策略
一开机,同时启动wifi连接(没有配过网就进入配网)和4G连接;如果wifi连上了,就切断4G电源;wifi断线后重连一次失败(观察过 W801从wifi断线到重连失败的时间约22秒),启动4G连接。
3.1wifi断网后,启动4G模块,如果W801的MQTT已经启动,则要关闭W801的MQTT心跳:
在下图的代码块1中,发送4G重启消息,关闭W801的MQTT心跳。
在状态机中查询到重启消息,4G模块通电并启动连接:
代码块1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16case 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 4Gn"); }else{ printf("NETIF_WIFI_JOIN_FAILED,%dn",failTimes); } break;
在《wm_mqtt_demo.c》的函数 mqtt_demo_task 增加 MQTT_DEMO_CMD_STOP_TIMER 的消息处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44static 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: %vn", 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 pingrn"); 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 timerrn"); tls_os_timer_stop(mqtt_demo_heartbeat_timer); default: break; } } } }
3.2 air724正常工作中,wifi连上网,需要关闭4G模块的电源,并使4G模块任务进入空转
上图的代码块2就是wifi连上了网络产生的事件,此时要发送4G模块进入空转的消息 DEMO_MSG_4G_ENTER_IDLE 。并启动W801的MQTT任务(如果已经启动过,只需要重连MQTT)
代码块2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 此时要切断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 offn"); // 启动 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 retartn"); } g_internetStat = internetStat_WifiConnect;
4G模块的任务查询到 DEMO_MSG_4G_ENTER_IDLE 消息后,关闭4G电源,进入空转:
4.切断air724电源后,串口引脚会有漏电流,使得4G模块的电源灯微亮
针对这种情况,切断4G模块电源后,要把串口引脚稍作处理,可以有2种方法:
1.引脚配置为输出模式,然后输出低电平;
2.配置为输出,浮空。
不知道那种好,我使用了第二种。上图的程序修改为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22if(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; } }
最后
以上就是友好乌龟最近收集整理的关于联盛德W801系列9-wifi和4G模块(air724ug)并存使用MQTT总结1.air724ug-AT版本的使用2.通过AT指令控制air724的代码详解3. wifi和4G并存策略4.切断air724电源后,串口引脚会有漏电流,使得4G模块的电源灯微亮的全部内容,更多相关联盛德W801系列9-wifi和4G模块(air724ug)并存使用MQTT总结1.air724ug-AT版本的使用2.通过AT指令控制air724的代码详解3.内容请搜索靠谱客的其他文章。
发表评论 取消回复