好了,之前補了一定的基礎了,現在來到基本demo上。

SIM800C模塊也叫GSM/GPRS模塊。後面可能會有多種表述,希望大家能理解。

先來看最後的成果: 我這裡沒有使用花生殼去內網穿透,還是使用阿里雲的伺服器,直接自己監聽和返回。 然後單片機這邊是使用了兩個串口,一個是SIM800C模塊的串口通信,這個使用的是串口2,另一個是調試的時候使用的和電腦通信的串口,這個使用的是串口1.

下面是引腳連接(這裡是開發板,而不是實驗板)

GSM模塊:

GSM模塊的TTL串口與STM32的usart2介面相連。

GND<---->GND

PA2<---->RXD

PA3<---->TXD

這裡注意:GSM模塊需要使用DC電源獨立供電(我這裡把GSM模塊的V_MCU---------接到了單片機的3.3V,sim800c的說明書上說的)

串口(TTL-USB TO USART):

CH340的收發引腳與STM32的發收引腳相連。

RX<--->PA9

TX<--->PA10

下面是伺服器上的顯示(我用putty進行的遠程連接,1和2都是sim800c通過tcp上傳的,後面的jackhe和this is ok是我在伺服器端輸入的,看伺服器的數據能否正確返回客戶端上。這樣雙向通信就沒啥問題了)

下面是調試的串口輸出:

posttcp:是讀取回的從伺服器端的返回的數據。 後面由於時間較長沒有數據傳輸,自動斷開tcp。

大家仔細觀察一下就知道,其實就是把我們之前自己在電腦上直接發到GSM模塊上的,現在是通過單片機的程序來發送。所以這裡我們著重要學習的是火哥的程序是怎麼編的。這裡有很多細節的地方的。

所以,接下來我們看看火哥的程序是怎麼寫的吧(可惜的是我沒有從網上找到火哥講解這個demo的視頻或者pdf。。感覺這些編程細節很重要,源碼 :

鏈接:pan.baidu.com/s/1lFEK5d

提取碼:p1k7

複製這段內容後打開百度網盤手機App,操作更方便哦 這個是從網上找的,火哥共享出來的

首先看:

bsp_debug_usart這個文件

它是負責封裝用於調試的串口1的函數的,比如初始化,配置中斷之類的。

這裡重寫了fputc,fgetc,這樣調用printf就可以直接列印數據到串口1了。

這裡配置的串口中斷是:DEBUG_USART_IRQ

然後在stm32f4xx_it.c裏找到DEBUG_USART_IRQHandler函數

void DEBUG_USART_IRQHandler(void)
{
#if 0
bsp_DEBUG_USART_IRQHandler();
#else
uint8_t ch;

if(USART_GetITStatus(DEBUG_USART, USART_IT_RXNE) != RESET)
{
//ch = USART1->DR;
ch = USART_ReceiveData(DEBUG_USART);
printf( "%c", ch );
}
#endif

}

這裡有個c的小技巧 #if 0 :可以參考:blog.csdn.net/wwwsssZhe 進行了解 。總之這裡就是不會運行bsp_DEBUG_USART_IRQHandler這個函數,而是運行下面else中的 收到什麼數據就返回什麼數據這部分功能,所以,如果你在調試的時候通過電腦往串口發送數據,那麼就立即返回同樣的數據。 所以,在這裡,我們主要是使用這個串口用來觀察的。

然後我們看看沒有使用的bsp_DEBUG_USART_IRQHandler這個函數有啥用的

#if 0
#define UART_BUFF_SIZE 255
volatile uint8_t uart_p = 0;
uint8_t uart_buff[UART_BUFF_SIZE];

void bsp_DEBUG_USART_IRQHandler(void)
{
if(uart_p<UART_BUFF_SIZE)
{
if(USART_GetITStatus(DEBUG_USART, USART_IT_RXNE) != RESET)
{
uart_buff[uart_p] = USART_ReceiveData(DEBUG_USART);
uart_p++;
}
}
}

char *get_rebuff(uint8_t *len)
{
*len = uart_p;
return (char *)&uart_buff;
}

void clean_rebuff(void)
{
uart_p = 0;
}

#endif

觀察一下就知道,這個不是串口一收到數據就馬上返回了,而是先把接收到的數據存放到一個數組當中,當小於這個長度就一直存著,大於的沒寫,是讓我們自己去擴展的。

哦,對了,大家別看那個debug usart以為單片機的底層就是這樣定義的串口啊,我們看一下它的頭文件就知道它是預定義好了的,這樣方便我們顧名思義,火哥考慮得周到啊。如下

#define DEBUG_USART USART1
#define DEBUG_USART_CLK RCC_APB2Periph_USART1

#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_CLK RCC_AHB1Periph_GPIOA
#define DEBUG_USART_RX_PIN GPIO_Pin_10
#define DEBUG_USART_RX_AF GPIO_AF_USART1
#define DEBUG_USART_RX_SOURCE GPIO_PinSource10

#define DEBUG_USART_TX_GPIO_PORT GPIOA
#define DEBUG_USART_TX_GPIO_CLK RCC_AHB1Periph_GPIOA
#define DEBUG_USART_TX_PIN GPIO_Pin_9
#define DEBUG_USART_TX_AF GPIO_AF_USART1
#define DEBUG_USART_TX_SOURCE GPIO_PinSource9

#define DEBUG_USART_IRQ USART1_IRQn
#define DEBUG_USART_IRQHandler USART1_IRQHandler

然後調試串口這裡就沒啥注意的了。

然後看bsp_gsm_usart 這個GSM串口的文件:

首先注意到的是,這裡把這個串口的中斷優先順序設為了0,我記得這是最高優先順序分組。然後也是各種配置中斷和串口IO之類的。然後我們再來看看後面封裝好的函數:

itoa這個函數是將整型數據轉換位字元串,給後面的列印函數使用。

然後看這裡的預定義:

#if 1
#define UART_BUFF_SIZE 255
volatile uint8_t uart_p = 0;
uint8_t uart_buff[UART_BUFF_SIZE];

void bsp_GSM_USART_IRQHandler(void)
{
if(uart_p<UART_BUFF_SIZE)
{
if(USART_GetITStatus(GSM_USARTx, USART_IT_RXNE) != RESET)
{
uart_buff[uart_p] = USART_ReceiveData(GSM_USARTx);
uart_p++;
}
}
}
char *get_rebuff(uint8_t *len)
{
*len = uart_p;
return (char *)&uart_buff;
}

void clean_rebuff(void)
{
uint16_t i=UART_BUFF_SIZE+1;
uart_p = 0;
while(i)
uart_buff[--i]=0;
}

#endif

這個函數是在中斷函數裏直接調用。這裡就我們前面說的不直接返回GSM返回的數據了。我們都知道GSM那邊返回的不是OK,就是error之類的。不過這裡也沒做什麼處理,可能在其它地方處理了吧,這裡先暫時不理它。

然後就是串口發送數據到GSM模塊的封裝函數了。鑒於知乎這裡各種亂碼,代碼框裏也不好寫注釋,所以就不貼全部代碼了,說幾個注意的點吧。

GSM_USART_printf 函數就是往GSM模塊發送字元串的,下面是有預定義GSM_TX,後面再看看它都是用來發送串口數據的GSM的。簡單概述一下這個GSM_USART_printf函數的功能就是識別 /r/n和字元串結束符 ,然後每次發送一個位元組數據,到GSM當中。這個就是核心的函數了。

然後到最關鍵的 bsp_gsm_gprs 文件。這裡封裝的就是所有操作GSM模塊的函數了。

我先看的是發送AT指令的函數,也就是gsm_cmd這個函數:

(這裡提示一個小技巧,keil中滑鼠指針定位在函數名上,F12就可以找到函數定位的地方了)

先看這個函數是怎麼使用的:

if(gsm_cmd("AT+CNUM
","OK", 100) != GSM_TRUE)
{
return GSM_FALSE;
}

然後再來看函數代碼:

uint8_t gsm_cmd(char *cmd, char *reply,uint32_t waittime )

{

GSM_DEBUG_FUNC(); //這個沒看懂是什麼意思。。好像是由於這裡使用了FATFS文件系統的原因,我以前沒接觸過,好像是文件讀寫的,現在先不理它,以後看需要進行學習吧。

GSM_CLEAN_RX(); //這個是清空接收緩衝區中的內容,也就是前面的clean_rebuff中功能,這裡注意啊,這也就代表著,你每一次在發送串口數據到GSM的時候,都會把原來的緩存數據全部清除,這也就保障了後面接收到的數據都是針對這次發送數據的應答。

GSM_TX(cmd);

//查找它定義的地方:#define GSM_TX(cmd) GSM_USART_printf("%s",cmd) 原來在這裡直接調用了這個函數進行串口發送數據,說明這個函數還擁有其它功能。

GSM_DEBUG("Send cmd:%s",cmd);//這個好像也是和fatfs文件系統有關,瞭解了一下,應該類似於一種日誌系統,記載了32的運行日誌似的東西,不過影響不大。

if(reply == 0) //如果不需要接收數據,就直接返回ok

{

return GSM_TRUE;

}

GSM_DELAY(waittime); //延時一段時間,這裡好像都是延時100ms,一般來說100ms內GSM模塊都會進行返回,所以後面就可以去串口上取出數據。

return gsm_cmd_check(reply); // 如果需要確認接收的數據,就是確認返回的是ok這類的字元串。然後用另一個函數去確認。

}

然後繼續看下去:

uint8_t gsm_cmd_check(char *reply)

{

uint8_t len;

uint8_t n;

uint8_t off;

char *redata

GSM_DEBUG_FUNC();

redata = GSM_RX(len); //這裡看預定義,可以知道這裡也是前面處理接收串口中斷裡面的函數,然後用一個數組接收這個數據。現在,len存儲的是數組的長度,redata存儲的是數組的首地址。(幸好我對指針地址之類的熟悉一些,兩年前我肯定比較懵逼)

*(redata+len) = ; //這裡是確認後面沒其它字元串了,也就是直接讓長度後面的都去掉。

GSM_DEBUG("Reply:%s",redata); //列印一次日誌(但就是沒找到他列印在哪裡。。),也就是列印串口返回的是啥

// GSM_DEBUG_ARRAY((uint8_t *)redata,len);

//然後下面就是比較核心的地方了,先看看

n = 0;
off = 0;
while((n + off)<len)
{
if(reply[n] == 0) //數據為空或者數據比較完畢
{
return GSM_TRUE;
}
if(redata[ n + off]== reply[n])
{
n++; //移動到下一個接收數據
}
else
{
off++; //進行下一輪匹配
n=0; //重來
}
//n++;
}

if(reply[n]==0) //剛好匹配完畢
{
return GSM_TRUE;
}

我感覺這是防止錯誤數據產生之類的,比如 aOKb之類的 ,像這樣的話,可能前面的a是由於電平錯誤之類產生的,或者什麼其它意外產生的(按理說每次都進行清空寄存器數據,應該是不會出現這類錯誤的,但是這是一個穩準的吧)。現在,即使出現這類錯誤也可以正常匹配。這個也叫 樸素字元串匹配演算法(大家可以去了解一下這個演算法,有個更快的叫KMP字元串匹配演算法 ,但是寫起來比較麻煩)。off是偏移量,n是要匹配的字元串的個數。大家看一下應該就能懂了。感覺得到火哥實力的強勁了。。

這樣我們就實現了對GSM模塊返回的數據的確認了。

return GSM_FALSE; //跳出循環表示比較完畢後都沒有相同的數據,因此跳出

}

好了,到上面這裡,我感覺最核心的東西就講得車不多了。

然後後面封裝的函數就不一一去解釋了。現在是看main函數使用到了哪個就看哪個,畢竟火哥封裝得太好了,把打電話,發簡訊什麼的都封裝好了。

先看:gsm_init

//返回0表示成功,1表示失敗
//初始化並檢測模塊
uint8_t gsm_init(void)
{
char *redata;
uint8_t len;

GSM_DEBUG_FUNC();

GSM_CLEAN_RX(); //????á??óê??o3???êy?Y
GSM_USART_Config(); //3?ê??ˉ′??ú

//下面這個AT指令時返回模塊的名稱。如果沒有返回,說明模塊檢測失敗,後面時兼容sim900的檢測
if(gsm_cmd("AT+CGMM
","OK", 100) != GSM_TRUE)
{
return GSM_FALSE;
}

redata = GSM_RX(len);
if(len == 0)
{
return GSM_FALSE;
}
//這個函數時檢測返回的字元串中是否含有對應的字元串,大家而可以百度瞭解一下。有點類似於字元串匹配演算法了。。
if (strstr(redata,"SIMCOM_GSM900A") != 0)
{
return GSM_TRUE;
}
else if(strstr(redata,"SIMCOM_SIM800") != 0)
{
return GSM_TRUE;
}
else
return GSM_FALSE;

}

經過這個函數之後,就表示模塊正常啟動了,沒啥問題。

然後是:IsInsertCard 函數

uint8_t IsInsertCard(void)
{
GSM_DEBUG_FUNC();

GSM_CLEAN_RX();
return gsm_cmd("AT+CNUM
","OK",200);

}

很明顯,這個我們之前介紹過了,是返回是否插有手機卡的AT指令,前面我們也設置過了。返回ok的話就表示電話卡插入沒問題了,這裡用了200ms的延時。

下面很多都是和前面這兩個類似的了,就不解釋具體代碼了,大家看看AT指令和前面的文章聯繫起來就很好理解了。

gsm_gprs_link_close(); 關閉之前的gprs服務

gsm_gprs_shut_close 關閉之前的場景

這裡開機運行後就運行這部分代碼,估計是考慮到可能之前就一直開啟者gprs服務,所以先關閉,然後再打開。

gsm_gprs_tcp_link(char *localport,char * serverip,char * serverport) 封裝的是tcp的連接,這裡有個sprintf函數應該是fatfs中用來列印日誌的,我不確定,有學習過的大佬可以來交流一下。

沒錯,截圖就不會有亂碼了。。 這裡大家應該都懂了,如果tcp沒有連接成功,就關閉連接,然後重啟單片機,就不斷地循環這個過程。注意一下的是soft_reset這個函數

它直接讓系統複位了。

然後

這裡是連接成功後,發送數據到伺服器上。

下面是最好的程序:

PostGPRS就是讀取伺服器返回給GSM模塊,然後GSM模塊再返回給單片機串口的數據。

然後這裡是10ms一次timecount,50次timecount一次timestop,也就是0.5s一次timestop,120次timestop就是60s 。如果60s伺服器都沒有數據返回,就認定此次tcp連接已經斷開,然後正常結束此次通信,即結束此次程序。

沒錯,整個程序就到這裡了。沒啥錯誤的話就會出現文章開頭那樣的現象了。

火哥居然不用中斷來檢測伺服器的數據,,,咦~,,偷懶了。。看來這部分還得我自己得另外寫。。

這裡要注意,串口和sim800c模塊進行通信時,sim800c可以自動識別它的波特率然後進行接收的。

沒錯,今天學習的代碼還是很重要的。這個fatfs以前沒接觸過就很難受。。。看看後面有沒有必要學吧,感覺把日誌記錄下來還是很有必要的啊,這樣調試找問題什麼的都畢竟方便。。

歡迎交流討論。


推薦閱讀:
相關文章