AT指令(嵌入式+物联网)编程心得C语言

本文拿我当初做了一个共享设备为例,最开始用的硬件是stm32f1+sim800(2g),这两个应该是国内做共享设备最普遍的组合了,因为据说联通2G快淘汰了,如果想用4g的sim7600,但是这也不影响AT指令使用,大家也可以用NBIOT(SIM7000)或者移远的.

1,stm32f1串口使用(中断+DMA接受 与 DMA发送)
stm32的串口大家可能都会用,但是还是有很多小白不会使用接受不定长数据,接受的最好方式就是(中断+DMA),配置就是基础的GPIO口配置,串口配置,DMA配置 这里不做多说,网上有太多DEMO。
接受中断最好用空闲中断(USART_IT_IDLE),中断内部先DMA失能,然后再DMA使能,目的是为了获得发来数据及数据长度。 以串口3为例(因为本人串口1用来debug,串口2用来传数据帧了)。
建立 串口接受缓冲区(全局变量) u8 USART3_RX_BUF[USART3_MAX_RECV_LEN];

//刚才提到的配置
UART_DMA_Config(DMA1_Channel3,(u32)&USART3->DR,(u32)USART3_RX_BUF,DMA_DIR_PeripheralSRC);
if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)
{
DataLen=USART3_MAX_RECV_LEN-DMA_GetCurrDataCounter(DMA1_Channel3); 
/*
DataLen就是接受数据的长度,USART3_RX_BUF就是接受的缓冲区
*/
DMA1_Channel3->CNDTR = USART3_MAX_RECV_LEN;//充填
}

在中断函数末尾,别忘了USART3->SR与USART3->DR还有各种清楚故障标志位的。

2,数据接受处理(循坏队列)
AT指令最重要的就是提取有效并且最准确的数据,但是接受到的数据很有可能是不及时的,粘包的,快速的(还没处理完就发过来了),甚至我还遇见过一个指令在另一条指令的中间的奇葩事件,粘包和快速数据,粘包和快速数据的处理方法是采用循环队列,首先我选择了使用中断判断后进入两个不同的缓冲区,然后程序执行过程中轮询取结果即可。为什么取两个呢,因为一个用作与AT模块的AT指令通信,一个用作接受发送方的数据缓存。
(1)判断用if(strstr((char *)USART3_RX_BUF,"+IPD"))即可,“+IPD”的意思就是这个指令接下来的数据是发送方发的数据而并非是模块的简单AT交互,因为发送方(本项目是服务器),那么有这个“+IPD”的就是服务器数据,保留在新的缓存区,没有“+IPD”的继续放在USART3_RX_BUF里即可。
(2)与simcom模块AT的交互指令分为两种,第一种是及时接受(也就是你发送它就马上接受),第二种是等一会儿才能接受,第一种用硬延时即可,第二种需要定时器中断加上标志位,发送此指令,设立相应标志位,程序顺次执行其他命令,如果计时结束还没有在USART3_RX_BUF中发现此结果,采取重发机制。
(3)服务器发来的数据是最重要的,也就是"+IPD"数据,因为简单的模块AT交互指令没有接收到“OK”,继续发送即可,但是如果服务器数据没有接收到会影响很多,因为相当于控制失效,是个很严重的后果,所以确保服务器数据能及时发送并且解析成功,我采用了循环队列,判断是服务器数据入列EnQueue(ATtemp ,&ATline);,需要解析服务器数据时出列DeQueue(&ATtemp ,&ATline);。
附(C Primer Plus 队列代码)

#include "Ringqueue.h"
#include "malloc.h"
static void CopyToNode(Item item, Node *pn);
static void CopyToItem(Node *pn, Item *pi);
void InitializeQueue(Queue *pq)
{pq->front = pq->rear = NULL;pq->items = 0;
}
bool QueueIsFull(const Queue *pq)
{return pq->items == MAXQUEUE;
}	
bool QueueIsEmpty(const Queue *pq)
{return pq->items ==0;
}int QueueItemCount(const Queue *pq)
{return  pq->items;
}	bool EnQueue(Item item, Queue *pq)
{Node *pnew;if(QueueIsFull(pq))return false;pnew = (Node*)mymalloc(sizeof(Node));CopyToNode(item, pnew);pnew->next = NULL;if (QueueIsEmpty(pq))pq->front = pnew;       /*项位于队列的首端*/elsepq->rear->next=pnew;    /*项位于队列的尾端*/pq->rear = pnew;            /*记录队列尾端的位置*/pq->items++;                /*队列项数加1*/return true;}
/*出队列应该判断是否粘包*/
bool DeQueue(Item *pitem, Queue *pq)
{Node* pt;if(QueueIsEmpty(pq))return false;CopyToItem(pq->front, pitem); // *pitem=pq->front->itempt = pq->front;pq->front =pq->front->next;myfree(pt);pq->items--;if(pq->items == 0)pq->rear = NULL;return true;}
void EmptyTheQueue(Queue *pq)
{Item dummy;while(!QueueIsEmpty(pq))DeQueue(&dummy, pq);}static void CopyToNode(Item item, Node *pn)
{pn->item = item;
//	  memcpy(pn->item.item_buf,item.item_buf,200);
//	  pn->item.item_sendlen= item.item_sendlen;}
static void CopyToItem(Node *pn, Item *pi)
{*pi =pn->item;
}

这个是利用链表方式组成循环队列,因为服务器粘包最多为两包,所以当判断为粘包时,将粘包的前包进行处理,后包截取到另一个数据帧即可,利用标志位先判断是否发生粘包,下一次处理数据时先判断粘包标志位,如果发生粘包就先处理上一次粘包截取后的后包。(因为这段代码涉及公司业务,就不粘贴出来了)。
还有一种方式是利用大数组来组成循环队列,这个数组方式其实更适合管理服务器发来的数据,只要根据帧头及帧的数据长度即可。

3,发送AT指令方法
通常AT指令的发送函数都是三个形参,AT命令(u8 *cmd),AT应答(u8 *ack),等待时间(u16 waittime)
类似u8 SIM800_Send_Cmd(u8 *cmd,u8 *ack,u16 waittime);其实还可以设置两个AT应答ack1 和ack2,判断的话用接受到的缓冲区是否同时含有这两个字符串,这个也不难在下面demo改改就可以。

u8 SIM800_Send_Cmd(u8 *cmd,u8 *ack,u16 waittime)
{u8 ret = CMD_ACK_NONE; //放在下面还是这里合适???//dev.msg_recv &= ~MSG_DEV_ACK;	if(ack!=NULL){//新的一次发送开始,需要把之前recv 的ack 状态清除掉//dev.msg_recv = 0;dev.msg_expect |= MSG_DEV_ACK;memset(dev.atcmd_ack, 0, sizeof(dev.atcmd_ack));strcpy(dev.atcmd_ack, (char *)ack);}	//Clear_Usart3();	  //放下面还是放在这里合适if((u32)cmd <= 0XFF){while((USART3->SR&0X40)==0);//等待上一次数据发送完成  USART3->DR = (u32)cmd;}else {u3_printf("%s\r\n",cmd);//发送命令}//Clear_Usart3();	//放上面还是放在这里合适if(ack&&waittime)		//需要等待应答{while(waittime!=0)	//等待倒计时{ delay_ms(10);	//if(dev.msg_recv & MSG_DEV_RESET)if(dev.need_reset != ERR_NONE){ret = CMD_ACK_DISCONN;break;}//IDLE 是指串口同时收到"SEND OK" + "正确的服务器回文",在//定时器处理中已经将设备状态转换为IDLE 状态//else if((dev.msg_recv & MSG_DEV_ACK) && ((dev.status == CMD_IDLE) || (dev.status == CMD_OPEN_DEVICE)))else if(dev.msg_recv & MSG_DEV_ACK){ret = CMD_ACK_OK;dev.msg_recv &= ~MSG_DEV_ACK;break;}				waittime--;	}}else   //不需要等待应答,这里暂时不添加相关的处理代码{;}return ret;
} void u3_printf(char* fmt,...)  
{  u16 i,j; va_list ap; va_start(ap,fmt);memset(USART3_TX_BUF, 0, USART3_MAX_SEND_LEN);	vsprintf((char*)USART3_TX_BUF,fmt,ap);va_end(ap);i=strlen((const char*)USART3_TX_BUF);		//此次发送数据的长度BSP_Printf("S: %s\r\n", USART3_TX_BUF);for(j=0;j<i;j++)							//循环发送数据{while(USART_GetFlagStatus(USART3,USART_FLAG_TC)==RESET); //循环发送,直到发送完毕   USART_SendData(USART3,USART3_TX_BUF[j]); } 
}

建立个USART3_TX_BUF[]即可,剩下的就是把 USART3_RX_BUF接收到的数据,经过处理再用USART3_TX_BUF发出去即可。处理逻辑时,我用的是状态机,状态机可以自行百度一下。因为这种物联设备都需要OTA固件升级,stm32正好也带IAP功能,当时就用http协议下载程序调试成功了,还有mqtt功能,这些以后有空补上吧。邮箱:1543323154@qq.com

Published by

风君子

独自遨游何稽首 揭天掀地慰生平