1. 引言
用過幾款GPRS模塊,也從淘寶上買過多個(gè)GPRS模塊,一般的都會(huì)送一個(gè)驅(qū)動(dòng)程序和使用demo,但是代碼質(zhì)量都較低。
回頭看了下幾年前使用的GPRS代碼,從今天的角度來看,也就是買模塊贈(zèng)送一個(gè)免費(fèi)demo的那種水平,甚是汗顏。
GPRS模塊驅(qū)動(dòng)主要是串口驅(qū)動(dòng),其本質(zhì)是字符串處理,本文就從對比下幾種常見的驅(qū)動(dòng)方式。
2. 版本1--初學(xué)者的驅(qū)動(dòng)
思路:
1. 串口接收使用中斷,收到數(shù)據(jù)放到全局buffer。
2. 發(fā)送前清空接收buffer。
3. 拼接字符串,然后從串口發(fā)送出去。
4. 設(shè)定一個(gè)等待時(shí)間,然后while(1)不停的查看接收buffer里面是否有需要的字符串出現(xiàn),即是否得到需要的響應(yīng)。
5. 初始化過程使用一個(gè)簡單的狀態(tài)機(jī)輪轉(zhuǎn),一步通過再進(jìn)行下一步。
下面是一個(gè)我曾經(jīng)用過的例子,問題很明顯:
1. 難維護(hù),函數(shù)耦合度太高,簡單的堆功能,功能模塊沒有劃分。
2. 低效,發(fā)送需要CPU停下來一個(gè)一個(gè)字符的發(fā)送,接收還要延時(shí)一段時(shí)間等待GPRS模塊回復(fù)足夠多的數(shù)據(jù)。
3. 接收buffer只是簡單的共享全局變量,沒有雙buffer切換也沒有讀寫互斥。
比如每次發(fā)送前清空buffer然后發(fā)送命令,用來判別此次接收都是對本次發(fā)送的命令的響應(yīng)。
4. 不能精細(xì)控制,AT指令響應(yīng)檢查全部放到一個(gè)函數(shù)里面處理,必然造成有些AT響應(yīng)的回復(fù)無法區(qū)分對應(yīng)哪個(gè)指令。
網(wǎng)絡(luò)連接的驅(qū)動(dòng):
(gprs_start==) =uint8_t gprs_connect(uint8_t *= strBuf[, (gprs_mgr.stat == GPRS_GET_CSQ) (gprs_get_csq() > = (gprs_mgr.stat == GPRS_WAIT_REGNET) (!= (gprs_mgr.stat == GPRS_CONFIG_PARA) (!= (gprs_mgr.stat == GPRS_CONFIG_SOCKET) (!gprs_config_socket()) gprs_mgr.stat = (num=;num<;num++ (gprs_mgr.stat == GPRS_DATA_RW) =,len+=
發(fā)送函數(shù),串口輸出加上查詢式解析:
//---------------------------------------------------------// 函數(shù)名稱:uint8 gprs_send_cmd(char* pcmd)// 函數(shù)功能:gprs命令字發(fā)送函數(shù)// 輸入?yún)?shù): pcmd,要發(fā)送的命令// 返回參數(shù):// 0 ,命令發(fā)送成功// 1 ,命令發(fā)送失敗//---------------------------------------------------------uint8_t gprs_send_cmd(char* pcmd) { uint16_t i; uint8_t ret=0, *GSM_ReturnInfo; memset(GSM_info, 0, sizeof(GSM_info)); // 清除串口緩沖區(qū) GSM_Info_CNT=0; // 清除串口接收計(jì)數(shù) debug_print(pcmd); //發(fā)送的命令,調(diào)試輸出 while(*pcmd) // 發(fā)送命令 { while(USART_GetFlagStatus(GPRS_USART, USART_FLAG_TXE)==0); USART_SendData(GPRS_USART, *pcmd++); } delay_ms(1000); GSM_ReturnInfo=GPRS_Get_Info(); for (i = 0; i < 15; i++) //15s 等待 { delay_ms(500); if (strstr(GSM_ReturnInfo, "OK")) // 命令發(fā)送成功 { ret = 0; break; } else if (strstr(GSM_ReturnInfo, "CONNECT")) { ret = 0; break; } else if (strstr(GSM_ReturnInfo, "ERROR")) // 命令發(fā)送失敗 { ret = 1; break; } else ret = 1; delay_ms(500); } debug_print(GSM_ReturnInfo); // 打印調(diào)試信息 return ret; }
3. 版本2--有模塊化思想的驅(qū)動(dòng)
大體流程和第一種差別不大,但是在幾個(gè)關(guān)鍵點(diǎn)上有巨大改進(jìn),比如函數(shù)的模塊化和中斷的使用。
1. 發(fā)送和接收都用中斷提高效率,不再使用查詢方式。
2. 拼湊發(fā)送字符串處理和串口數(shù)據(jù)發(fā)送過程分開。
3. 初步的異常處理,如斷線重連、重啟等。
比上一版本進(jìn)化很多,但是也有問題:
1. 模塊已經(jīng)劃分,但是在邏輯層次上區(qū)分不明顯,如下面例子中的發(fā)送的AT指令的響應(yīng)處理,就和發(fā)送函數(shù)混在一起。
2.全局變量問題,比如記錄GPRS模塊當(dāng)前狀態(tài)標(biāo)識,可以多處進(jìn)行修改。
比較獨(dú)立的功能做一定的提取,比如注冊網(wǎng)絡(luò)、SIM卡檢查等功能函數(shù)封裝起來。
uint8_t gprs_reg_network(void) { uint8_t ret, *uart_buf; ret = gprs_send_cmd("AT+CGREG?\r\n"); if (ret == 0) //命令發(fā)送成功 { uart_buf = get_gprs_rsp(); ret = 1; if (strstr(uart_buf, "+CGREG: 0,5")) // 已注冊,本地網(wǎng) ret = 0; if (strstr(uart_buf, "+CGREG: 1,5")) // 已注冊,本地網(wǎng) ret = 0; if (strstr(uart_buf, "+CGREG: 0,1")) // 已注冊,漫游 ret = 0; if (strstr(uart_buf, "+CGREG: 1,1")) // 已注冊,漫游 ret = 0; return ret; } else return 1; }
或者接收響應(yīng)的buffer不使用全局變量,而在發(fā)送函數(shù)參數(shù)中直接傳入接收數(shù)組指針。
gprs_check_sim( err = rsp_buf[= gprs_send_atcmd(,rsp_buf,(strstr(rsp_buf,)== =(retry++ > =(err !=
4. 版本3--按邏輯層次劃分功能
分兩個(gè)層次來實(shí)現(xiàn)需求,先是邏輯層次劃分功能,然后在具體是實(shí)現(xiàn)層次按照功能單一原則編碼。
該方法可以用在產(chǎn)品中,驅(qū)動(dòng)代碼的思路清晰,高效且易維護(hù)。
1. 使用RTOS來,提升CPU利用率,尤其是等待AT指令回復(fù)的過程中,系統(tǒng)可以執(zhí)行其他任務(wù)。
2. GPRS操作的本質(zhì)是寫字符串(發(fā)AT指令),然后讀回復(fù)的字符串(讀指令響應(yīng)),那么可以從這個(gè)角度來設(shè)計(jì)驅(qū)動(dòng)。
3. 屏蔽硬件細(xì)節(jié),在寫GPRS驅(qū)動(dòng)和邏輯處理的過程中,硬件讀寫都抽象成一個(gè)字符處理函數(shù)。
例1:查詢SIM卡,發(fā)送AT指令,然后等待接收響應(yīng)字符串。
函數(shù)接口就負(fù)責(zé)填充期待的字符串,如果指定時(shí)間內(nèi)沒等到字符串出現(xiàn)就認(rèn)為出錯(cuò),具體怎么發(fā)出去怎么收到回復(fù)都是更加底層的處理。
具體的響應(yīng)由SIM800_WaitResponse函數(shù)來處理,該函數(shù)自動(dòng)匹配指定字符串,參數(shù)500是超時(shí)時(shí)間,如果匹配成功會(huì)提前退出,否則等待500ms然后回復(fù)超時(shí)。
由于使用的Free RTOS,那么該函數(shù)不是阻塞性的,不會(huì)影響CPU執(zhí)行其他的任務(wù)。如果模塊很快響應(yīng)了指令,那么還可以提前結(jié)束超時(shí)等待。
/******************************************************************************* * Function Name : SIM800_Check_SIM * Description : None * Input : None * Output : None * Return : 1-OK, 0-NG * Attention : None *******************************************************************************/
(SIM800_WaitResponse(,
例2:注冊GSM網(wǎng)絡(luò),發(fā)送AT指令,然后等待接收響應(yīng)字符串。
有些指令回復(fù)參數(shù)種類較多,如果寫成上面的形式可能比較臃腫,可以把接收到的數(shù)據(jù)先放到buffer,然后從中搜索需要的字符。
SIM800_ReadResponse完成這個(gè)功能,但該函數(shù)要一直等待直至最大超時(shí)時(shí)間。
/******************************************************************************* * Function Name : SIM800_GsmCheck * Description : 檢查是否注冊到GSM網(wǎng)絡(luò) * Input : None * Output : None * Return : 1—OK, 0-NG * Attention : None *******************************************************************************/uint8_t SIM800_GsmCheck(void) { uint8_t rtn = 0; SIM800_SendATCmd("AT+CREG?\r\n"); SIM800_ReadResponse(gprs_rsp_buffer, sizeof(gprs_rsp_buffer), 500); if (strstr(gprs_rsp_buffer, "+CREG: 0,1") != NULL) { rtn = 1; } else if (strstr(gprs_rsp_buffer, "+CREG: 0,5") != NULL) { rtn = 1; } else rtn = 0; vTaskDelay(200); return rtn; }
例3:AT指令的發(fā)送,不需要等待硬件的響應(yīng),啟動(dòng)硬件發(fā)送標(biāo)識即可,具體發(fā)送由中斷或DMA去操作,代碼更加高效。
剩下的發(fā)送和接收都是CPU硬件操作,在這一層次CPU并不識別數(shù)據(jù)的含義,僅是把數(shù)據(jù)從串口輸出或讀入。
SIM800_SendATCmd( uint8_t *= (uint8_t *==
例4:如果模塊已經(jīng)連接到服務(wù)器,那么需要應(yīng)用數(shù)據(jù)的發(fā)送。
常見的過程分3步:
1)應(yīng)用數(shù)據(jù)預(yù)處理打包;
2)GPRS模塊發(fā)送數(shù)據(jù)可能需要一個(gè)特殊的指令來啟動(dòng),作用是告訴模塊,下面發(fā)過來的是用戶數(shù)據(jù),不是控制字了;
3)啟動(dòng)數(shù)據(jù)包發(fā)送(實(shí)際是初始化發(fā)送過程邏輯控制相關(guān)的標(biāo)志和啟動(dòng)硬件發(fā)送標(biāo)志)。
下面驅(qū)動(dòng)函數(shù)處理了用戶數(shù)據(jù)的發(fā)送,在發(fā)送AT+CIPSEND后,2s內(nèi)收到">" 回復(fù)就可以開始發(fā)送數(shù)據(jù)。
http://www.cnblogs.com/pingwen/p/6681955.html