1. 程式人生 > >AT指令(嵌入式+物聯網)程式設計心得C語言

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;       /*項位於佇列的首端*/
		else
        pq->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->item
		pt = 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功能,這些以後有空補上吧。郵箱:[email protected]