第一次在产品中使用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指令中关于双引号"的处理
多数指令关于双引号是可有可无的,例如设置订阅主题的指令: 可以有双引号括住订阅主题:
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";
2.通过AT指令控制air724的代码详解
2.1 发送AT指令的函数
int SendATCmdAndWaitReply(int tryTimes,const char * cmdStr, const char* replyStr,int waitTime)
{
int rx_len,ret;
u32 lastTime;
for(int i=0;i<tryTimes;i++){
if(cmdStr != NULL)
tls_uart_write(LTE_4G_COM,(u8 *) cmdStr,strlen(cmdStr));
lastTime = xTaskGetTickCount();
vTaskDelay(50);
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);
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);
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;
}
}
}
}while((xTaskGetTickCount()-lastTime )< waitTime);
}
printf("wait %s fail\n",replyStr);
return 0;
}
2.2 控制air724模块的状态机流程
状态机的状态码 :
enum _Stat_4G{
Stat4G_WaitReady=0,
Stat4G_CloseEcho=1,
Stat4G_CGREG,
Stat4G_CGATT,
Stat4G_CSTT,
Stat4G_CIICR,
Stat4G_CIFSR,
Stat4G_Mconfig,
Stat4G_Mipstart,
Stat4G_Mconnect,
Stat4G_Msub,
Stat4G_MQuerySubMsg,
Stat4G_4GIdle,
Stat4G_4GPowerReset,
};
状态机相关源码(使用W801的串口3控制4G模块):
const char * StrNect="NECT";
const char * StrAct="ACK";
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";
const char * StrGATT="AT+CGATT?\r\n";
const char * StrCSTT="AT+CSTT\r\n";
const char * StrIICR="AT+CIICR\r\n";
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;
if(Stat4G_MQuerySubMsg == stat4G){
tls_uart_write(LTE_4G_COM,(u8 *) StrMconnectQry,strlen(StrMconnectQry));
printf("PING\n");
}
}
vTaskDelay(10);
if(xQueueReceive(demo_uart3->demo_uart_q,&(cmdmsg),0)){
if((cmdmsg == DEMO_MSG_4G_RESTART)){
tls_gpio_write(POWER_4G_CTRL_PIN,1);
stat4G = Stat4G_WaitReady;
}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:
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:
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:
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:
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:
printf("%s\n",StrIICR);
if(SendATCmdAndWaitReply(TRY_TIME_MAX,StrIICR,StrOK,500)){
stat4G++;
}
break;
case Stat4G_CIFSR:
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:
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:
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:
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:
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);
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;
p = strstr(demo_uart3->rx_buf, "byte,");
if (p != NULL)
{
p = p+5;
rx_len =p - demo_uart3->rx_buf;
if((ret -rx_len) < 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:
case NETIF_WIFI_JOIN_FAILED:
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;
}
}
}
}
3.2 air724正常工作中,wifi连上网,需要关闭4G模块的电源,并使4G模块任务进入空转
上图的代码块2就是wifi连上了网络产生的事件,此时要发送4G模块进入空转的消息 DEMO_MSG_4G_ENTER_IDLE 。并启动W801的MQTT任务(如果已经启动过,只需要重连MQTT) 代码块2:
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");
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电源,进入空转:
4.切断air724电源后,串口引脚会有漏电流,使得4G模块的电源灯微亮
针对这种情况,切断4G模块电源后,要把串口引脚稍作处理,可以有2种方法:
1.引脚配置为输出模式,然后输出低电平; 2.配置为输出,浮空。
不知道那种好,我使用了第二种。上图的程序修改为:
if(xQueueReceive(demo_uart3->demo_uart_q,&(cmdmsg),0)){
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;
}else if(cmdmsg == DEMO_MSG_4G_ENTER_IDLE){
tls_io_cfg_set(WM_IO_PA_05, WM_IO_OPT5_GPIO);
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);
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;
}
}
|