先簡單說一下實驗?zāi)康陌伞F綍r做項目或做一些小作品的時候需要用到時間,時間用的是STM32內(nèi)部的RTC,在精度要求不是特別高時這樣省去接外設(shè)時鐘模塊,省時省力。但我們都知道,RTC在斷電后數(shù)據(jù)是不保存的,也就是說如果沒有電源如電池之類一直給后備寄存器供電的話數(shù)據(jù)是會丟失的,下次開機(jī)時時間就會恢復(fù)初始化時設(shè)置的那個時間,想要時間正確就要重新設(shè)置時間,這就很不實用。所以就想通過網(wǎng)絡(luò)獲取時間的方式來自動校正時間。又恰好用到SIM900A這個模塊,所以查了下資料,發(fā)現(xiàn)已經(jīng)有前輩做過了。看了https://blog.csdn.net/ludaoyi88/article/details/51757664這位博主的文章,獲取時間部分用了這位前輩提供的代碼,在他提供的代碼上進(jìn)行測試和改進(jìn)(直接用不修改的話是不行的。如果大家仔細(xì)對比的話會發(fā)現(xiàn)其實我改進(jìn)后的代碼跟原版的還是有蠻多小細(xì)節(jié)不同的),最終得以達(dá)到目的,即可以通過服務(wù)器獲取到網(wǎng)絡(luò)的時間并自動校正到STM32內(nèi)部RTC中,這里再次感謝 ludaoyi123這位前輩。大家可以先去看看這位前輩的博客,也就是上面那個鏈接,獲取時間和處理時間數(shù)據(jù)都是源于他的那篇文章。好了,接下來就說一下怎么獲取時間的吧。 此次實驗用的單片機(jī)是STM32F103C8T6核心板,串口2控制SIM900A模塊數(shù)據(jù)的收發(fā),串口1用于在串口調(diào)試助手打印相關(guān)信息。下面是我的硬件平臺:STM32F103C8T6核心板和SIM900A模塊。
硬件.jpg (323.42 KB, 下載次數(shù): 156)
下載附件
硬件平臺
2019-6-21 00:28 上傳
獲取網(wǎng)絡(luò)時間的第一種方法是連接到國際授時服務(wù)器,IP為:time點nist點gov,端口為:13,我用的連接方式是TCP連接。當(dāng)客戶端連接到此服務(wù)器后,服務(wù)器會立刻發(fā)送一串格式為“58646 19-06-12 16:05:36 50 0 0 668.3 UTC(NIST) * ”這樣的字符串返回給客戶端并主動斷開連接。這一串字符串中就包含有日期和時間,我們所要做的,就是把相關(guān)的日期和時間提取出來轉(zhuǎn)換成數(shù)字就好了,這也是最最重要的部分。這里貼出我修改后的代碼:`- _nowtime_obj NowTime; //現(xiàn)在時間日期結(jié)構(gòu)體
- ///*******************************************************************************
- //* 函數(shù)名 : Get_Sever_Time
- //* 描述 : 獲取Time信息(連接服務(wù)器成功情況下)
- //* 輸入 :
- //* 輸出 :
- //* 返回 :
- //* 注意 :服務(wù)器返回的數(shù)據(jù)形式如下:58646 19-06-12 16:05:36 50 0 0 668.3 UTC(NIST) *
- //*******************************************************************************/
- void Get_Sever_Time(void)
- {
- u8 i =0;
- char timestr1[200]={0};
- char *timestr = timestr1;//指向timestr1地址
- printf("\r\n獲取時間日期中...\r\n");
- while(1)
- {
- if(strstr((const char*)USART2_RX_BUF , "-") != NULL || strstr((const char*)USART2_RX_BUF , "5") != NULL )
- {
- timestr = strstr((const char*)USART2_RX_BUF,"5");//58646//USART2_RX_BUF 為接收緩存數(shù)組
- break;
- }
-
- }
-
- printf("\r\n時間數(shù)組timestr的數(shù)據(jù)為");
- printf((char *)timestr);
- printf("\r\n");
- delay_ms(5);
- //提取UTC世界時間
- for(i = 0 ; i <50 ; i++)
- {
- if(timestr[i] == '-')
- {
- NowTime.year = (timestr[i-2]-'0')*10 + (timestr[i-1]-'0') + 2000;
- NowTime.moon = (timestr[i+1]-'0')*10 + (timestr[i+2]-'0');
- NowTime.day = (timestr[i+4]-'0')*10 + (timestr[i+5]-'0');
- NowTime.hour = (timestr[i+7]-'0')*10 + (timestr[i+8]-'0');//時差相差8
- if(NowTime.hour >= 16)//北京時間新的一天
- {
- NowTime.hour = (timestr[i+7]-'0')*10 + (timestr[i+8]-'0') + 8 - 24;
- NowTime.day = NowTime.day + 1;
- if(NowTime.hour == 24)
- NowTime.hour = 0;
- }
-
- else
- NowTime.hour = (timestr[i+7]-'0')*10 + (timestr[i+8]-'0') + 8 ;//時差相差8
-
- NowTime.minu = (timestr[i+10]-'0')*10 + (timestr[i+11]-'0');
- NowTime.sec = (timestr[i+13]-'0')*10 + (timestr[i+14]-'0') + 2;//2是返回數(shù)據(jù)到處理處結(jié)果的誤差
- sprintf((char*)TimeRTC,"AT+CCLK=\"%d/%02d/%02d,%02d:%02d:%02d+08\"\r\n",NowTime.year,NowTime.moon,NowTime.day,NowTime.hour,NowTime.minu,NowTime.sec);
- //轉(zhuǎn)換成RTC時間格式
- break;
- }
- }
-
- printf("\r\n當(dāng)前時間:%d年%02d月%02d日%02d時%02d分%02d秒\r\n",NowTime.year,NowTime.moon,NowTime.day,NowTime.hour,NowTime.minu,NowTime.sec);
-
- printf("\r\n寫入SIM900A設(shè)置時間的字符串為:");
- printf((char *)TimeRTC);
- printf("\r\n");
- if(NowTime.year!=0&&NowTime.moon!=0&&NowTime.day!=0&&NowTime.hour!=0&&NowTime.minu!=0)//獲取到正確數(shù)據(jù)
- {
- RTC_Set(NowTime.year,NowTime.moon,NowTime.day,NowTime.hour,NowTime.minu,NowTime.sec); //設(shè)置STM32單片機(jī)內(nèi)部RTC時間
- Set_SIM900A_RTCtime();//給GSM模塊設(shè)置從網(wǎng)絡(luò)獲取來的時間
- }
- else
- printf("\r\n沒能在服務(wù)器獲取到正確時間,復(fù)位重試一下\r\n");
-
- delay_ms(500);
- Get_GSM_RTCtime();//用串口查看一下GSM模塊時間正常了沒有
- sim900a_send_cmd("AT+CIPSHUT\r\n","SHUT OK",2); //關(guān)閉連接
- delay_ms(300);
- AT_DataInit();//清除接收數(shù)組
- CLR_Buf2();
- delay_ms(200);
- }
復(fù)制代碼 代碼內(nèi)容很簡單,就是連接上服務(wù)器后用strstr函數(shù)和字符“-”或者“5”比較串口2接收數(shù)組中的“58646 19-06-12 16:05:36 50 0 0 668.3 UTC(NIST) * ”這串字符串,如果串口二接收數(shù)組中接收到了這一串字符串,那個就將這串字符串保存到數(shù)組timestr中并跳出死循環(huán)。實驗過程中發(fā)現(xiàn)最容易出現(xiàn)問題的就是這一塊,也就是有時候串口2沒能收到完整的字符串導(dǎo)致程序死在死循環(huán)里面,應(yīng)該是串口2中斷服務(wù)函數(shù)沒寫好。接到完整的字符串后,接下來接著把年月日時分秒提取出來,把字符變成數(shù)字并做判斷轉(zhuǎn)換成北京時間,當(dāng)服務(wù)器中的小時大于或等于16時當(dāng)?shù)匦r+8-24,且天數(shù)+1,否則小時只是單純地+8,這個8就是時差。數(shù)據(jù)處理好后就給STM32內(nèi)部的RTC設(shè)置時間和給GSM模塊的RTC設(shè)置時間,后續(xù)如果要重新校正時間的話只需要在SIM900A內(nèi)部獲取RTC的時間就可以了。實驗過程中相關(guān)信息在串口調(diào)試助手顯示,結(jié)果如下圖所示:
20190614.png (79.32 KB, 下載次數(shù): 137)
下載附件
測試結(jié)果一
2019-6-21 00:03 上傳
一次成功不代表什么,所以又在不同時間段多次測試:
20190617174835.jpg (65.26 KB, 下載次數(shù): 162)
下載附件
測試結(jié)果二
2019-6-21 00:04 上傳
20190618023712.png (33.04 KB, 下載次數(shù): 149)
下載附件
測試三
2019-6-21 00:04 上傳
20190618122218.png (38.58 KB, 下載次數(shù): 140)
下載附件
測試四
2019-6-21 00:04 上傳
測試結(jié)果表明,實驗是成功的,也就是在SIM900A連接到服務(wù)器后可以獲取到正確時間并校正到RTC中。
但測試的過程中發(fā)現(xiàn)上面連接服務(wù)器的方法并不是最佳的,原因是連接服務(wù)器不是很好用,有時候需要六七秒就能連上,有時候要半分多鐘才能連上,感覺不可靠,就像刷臉一樣,而且,有時候辛辛苦苦連上了,串口2還不能完完整整接收到那串字符串。。。所以,提供了第二個獲取時間的方法,也就是直接從SIM900A內(nèi)部獲取時間日期,下面貼上代碼: - _rtctime_obj RTCTime; //SIM900A時間日期結(jié)構(gòu)體
- /*******************************************************************************
- * 函數(shù)名 : Get_GSM_RTCtime(void)
- * 描述 : 獲取SIM900A模塊中RTC的時間
- * 輸入 :
- * 輸出 :
- * 返回 :RTCTime的時間結(jié)構(gòu)體--年月日時分秒
- * 注意: 如:"19/06/18,13:11:52+08";
- *******************************************************************************/
- void Get_GSM_RTCtime(void)//直接從GSM模塊內(nèi)部獲取時間,初次上電時需要手動復(fù)位
- {
- u8 i = 0;
- char timestr1[50]={0};
- char *timestr = timestr1;//指向timestr1地址
- AT_DataInit();//清除接收數(shù)組
- sim900a_send_cmd("AT+CCLK?\r\n","OK",5);
- delay_ms(1000);
- printf("\r\n獲取SIM900A內(nèi)部時間日期中...\r\n");
- while(1)
- {
- if(strstr((const char*)USART2_RX_BUF , "+CCLK") != NULL )
- {
- timestr = strstr((const char*)USART2_RX_BUF , "+CCLK");
- break;
- }
- }
- delay_ms(500);
- for(i = 0 ; i <50 ; i++)
- {
- if(timestr[i] == '/')
- {
- RTCTime.year = (timestr[i-2]-'0')*10 + (timestr[i-1]-'0') + 2000;
- RTCTime.moon = (timestr[i+1]-'0')*10 + (timestr[i+2]-'0');
- RTCTime.day= (timestr[i+4]-'0')*10 + (timestr[i+5]-'0');
- RTCTime.hour = (timestr[i+7]-'0')*10 + (timestr[i+8]-'0');
- RTCTime.minu = (timestr[i+10]-'0')*10 + (timestr[i+11]-'0');
- RTCTime.sec = (timestr[i+13]-'0')*10 + (timestr[i+14]-'0');
- break;
- }
- }
- printf("\r\nGSM內(nèi)部時間:%d年%02d月%02d日%02d時%02d分%02d秒\r\n",RTCTime.year,RTCTime.moon,RTCTime.day,RTCTime.hour,RTCTime.minu,RTCTime.sec);
- RTC_Set(RTCTime.year,RTCTime.moon,RTCTime.day,RTCTime.hour,RTCTime.minu,RTCTime.sec); //設(shè)置STM32單片機(jī)內(nèi)部RTC時間
- AT_DataInit();//清除接收數(shù)組
- memset(USART2_RX_BUF,0,USART2_MAX_RECV_LEN);
- }
復(fù)制代碼 這代碼跟上面的差不多,這里就不重復(fù)解釋了,也很容易看懂。用法是直接在主函數(shù)調(diào)用就行了。需要注意的是,板子初次上電時需要手動復(fù)位一下單片機(jī),不然串口發(fā)送AT+CCLK?命令給SIM900A模塊時它不會返回字符串。這里的代碼也是上面提到的那位博主提供我再進(jìn)行小小修改的,我只是代碼搬運工。然后發(fā)現(xiàn)用AT+CCLK?指令查詢時間并沒有網(wǎng)上說的那樣不能用,反而覺得更好用,個人比較推薦這種方法。其效果圖如下所示:
直接獲取GSM內(nèi)部時間.png (44.52 KB, 下載次數(shù): 144)
下載附件
2019-6-21 00:11 上傳
下面給出main.c和串口2配置及中斷服務(wù)函數(shù)代碼,請各路大神批評指正,提出寶貴意見。- /**
- ******************************************************************************
- * @file main.c
- * @author GXNU_LPK
- * @version V1.0
- * @date 2019-06-17
- * @brief 用3.5.0版本庫建的工程模板
- ******************************************************************************
- * @attention
- *
- * 實驗平臺: STM32F103CT6核心板
- * 實驗內(nèi)容:GSM(SIM900A)模塊通過服務(wù)器實現(xiàn)網(wǎng)絡(luò)授時
- * 實驗作者:廣西師范大學(xué)電子工程學(xué)院2015LPK
- * 備 注:Get_net_time.c這部分的代碼來源于https://blog.csdn.net/ludaoyi88/article/details/51757664 ,
- * 根據(jù)此ludaoyi123博主提供的思路和代碼進(jìn)行測試和修改而來,經(jīng)實驗測試和改善后目前已初步達(dá)到實驗?zāi)康�,�?br />
- * 通過服務(wù)器獲取時間那種方法 穩(wěn)定性方面略微存在一些欠缺,請學(xué)習(xí)者自行改善,僅供學(xué)習(xí),不得用于其他用途
- ******************************************************************************
- */
-
- #include "stm32f10x.h"
- #include "GSM.h"
- #include "AT_Cmd.h"
- #include "usart2.h"
- #include "usart1.h"
- #include "delay.h"
- #include "string.h"
- #include "rtc.h"
- #include "Get_net_time.h"
- /**
- * @brief 主函數(shù)
- * @param 無
- * @retval 無
- */
- int main(void)
- {
- u8 res;
-
- delay_init();
- Usart2_Init(115200); //初始化串口2
- delay_ms(3);
- Usart1_Init(115200); //初始化串口1
-
- if(RTC_Init()==0)
- printf("RTC初始化成功\r\n");
- else
- printf("RTC初始化失敗\r\n");
-
- printf("初始化SIM900A中...\r\n");
-
- res=1;
- while(res)
- {
- res=GSM_Dect();
- delay_ms(2000);
- }
- res=1;
- while(res)
- {
- res=SIM900A_CONNECT_SERVER_SEND_INFOR((u8*)"time.nist.gov",(u8*)"13");//連接授時服務(wù)器(國外)
- }
- Get_Sever_Time();//提取獲取到的時間并存入STM32和GSM模塊內(nèi)部RTC中
- // Get_GSM_RTCtime();//直接從GSM模塊內(nèi)部獲取時間,初次上電時需要手動復(fù)位
-
- printf("\r\n系統(tǒng)初始化完成\r\n");
- while(1)
- {
- display_time();//顯示STM32內(nèi)部RTC的時間
- delay_ms(1000);
- }
-
- }
- /*********************************************END OF FILE**********************/
復(fù)制代碼 下面是串口2配置及中斷服務(wù)函數(shù)代碼:
- #include "usart2.h"
- #include "stdio.h"
- #include "string.h"
- #include "stdarg.h"
- //串口接收緩存區(qū)
- u8 USART2_RX_BUF[USART2_MAX_RECV_LEN]; //接收緩沖,最大USART2_MAX_RECV_LEN個字節(jié).
- u8 USART2_TX_BUF[USART2_MAX_SEND_LEN]; //發(fā)送緩沖,最大USART2_MAX_SEND_LEN字節(jié)
- u16 USART2_RX_STA=0;
- /*
- 功能描述: 發(fā)送一個字節(jié)
- 函數(shù)參數(shù): byte —— 要發(fā)送的字節(jié)
- 返回說明: 無
- */
- void UART2_SendByte(unsigned char byte)
- {
- USART_SendData(USART2,byte);//向串口2發(fā)送數(shù)據(jù)
- while(USART_GetFlagStatus(USART2,USART_FLAG_TC)!=SET);//等待發(fā)送結(jié)束
- }
- /*
- 功能描述: 串口發(fā)送字符串
- 函數(shù)參數(shù): s —— 指向字符串的指針(字符串以'\0'結(jié)尾)
- 返回說明: 無
- 注:如果在字符串結(jié)尾有'\n',則會發(fā)送一個回車換行
- */
- void UART2_SendStr(char *s)
- {
- while( *s != '\0')
- {
- UART2_SendByte( *s );
- s ++;
- }
- }
- void Usart2_Init(unsigned int bps)
- {
- USART_InitTypeDef USART_InitStructure;
- GPIO_InitTypeDef GPIO_InitStructure;
- NVIC_InitTypeDef NVIC_InitStructure;
-
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
- USART_DeInit(USART2);
-
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; //TX PA2
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz;
- GPIO_Init(GPIOA,&GPIO_InitStructure );
-
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3; //RX PA3
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_2MHz;
- GPIO_Init(GPIOA,&GPIO_InitStructure );
-
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
-
- // NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;
- NVIC_InitStructure.NVIC_IRQChannel=USART2_IRQn;
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 0x01 ;
- NVIC_InitStructure.NVIC_IRQChannelSubPriority= 1 ;
- NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
- NVIC_Init(&NVIC_InitStructure);
-
- USART_InitStructure.USART_BaudRate=bps;
- USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
- USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
- USART_InitStructure.USART_Parity=USART_Parity_No;
- // USART_InitStructure.USART_StopBits=USART_StopBits_1;
- USART_InitStructure.USART_StopBits=USART_StopBits_2;
- USART_InitStructure.USART_WordLength=USART_WordLength_8b;
- USART_Init(USART2,&USART_InitStructure );
-
- // USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
- USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
-
- USART_Cmd(USART2,ENABLE);
- }
- //串口2,printf 函數(shù)
- //確保一次發(fā)送數(shù)據(jù)不超過USART2_MAX_SEND_LEN字節(jié)
- void u2_printf(char* fmt,...)
- {
- u16 i,j;
- va_list ap;
- va_start(ap,fmt);
- vsprintf((char*)USART2_TX_BUF,fmt,ap);
- va_end(ap);
- i=strlen((const char*)USART2_TX_BUF); //此次發(fā)送數(shù)據(jù)的長度
- for(j=0;j<i;j++) //循環(huán)發(fā)送數(shù)據(jù)
- {
- while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET); //循環(huán)發(fā)送,直到發(fā)送完畢
- USART_SendData(USART2,USART2_TX_BUF[j]);
- }
- }
- extern void AT_RecvProcess(unsigned char byte);
- //void USART2_IRQHandler(void) //串口2中斷服務(wù)程序
- //{
- // u8 Res;
- // if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收中斷(接收到的數(shù)據(jù)必須是0x0d 0x0a結(jié)尾)
- // {
- // USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除中斷標(biāo)志位
- // Res =USART_ReceiveData(USART2);//(USART2->DR); //讀取接收到的數(shù)據(jù)
- // AT_RecvProcess(Res);
- // if(USART2_RX_STA < USART2_MAX_RECV_LEN) //還可以接收數(shù)據(jù)
- // {
- // USART2_RX_BUF[USART2_RX_STA]=Res; //記錄接收到的值
- // USART2_RX_STA++;
- // }
- // else
- // {
- // USART2_RX_STA=USART2_MAX_RECV_LEN;//強制標(biāo)記接收完成
- // }
- // }
- //}
- void USART2_IRQHandler(void)
- {
- u8 clear=clear;
- USART_ClearFlag(USART2,USART_FLAG_TC);
- if(USART_GetITStatus(USART2,USART_IT_RXNE)!=Bit_RESET)
- {
- AT_RecvProcess(USART2->DR);
- USART2_RX_BUF[USART2_RX_STA++]=USART2->DR;
- USART_ClearFlag(USART2,USART_FLAG_ORE);//讀SR
- }
-
- else if(USART_GetFlagStatus(USART2,USART_FLAG_IDLE)!=Bit_RESET)
- {
- clear=USART2->SR;
- clear=USART2->DR;
- USART2_RX_STA=0;
- }
- }
復(fù)制代碼 以上就是SIM900A獲取網(wǎng)絡(luò)時間的兩種小方法,不足之處請批評指正,不喜勿噴,謝謝~
沒有黑幣或黑幣不足的的完整工程下載鏈接:https://download.csdn.net/download/qq_36112455/11247378 有黑幣的可以從附件直接下載。
工程資源簡介:用STM32F103單片機(jī)控制SIM900A模塊通過連接國外的授時服務(wù)器或者訪問SIM900A內(nèi)部獲取網(wǎng)絡(luò)時間,把獲得的時間設(shè)置到STM32內(nèi)部的RTC中,實現(xiàn)單片機(jī)上電自動校正時間。時間在串口上顯示出來。資源是完整的工程,里面包含了SIM900A的驅(qū)動和常用的撥打電話發(fā)短信連接到服務(wù)器等等功能;另工程里面也有STM32 RTC的驅(qū)動,可通過編譯等獲取電腦上的時間,也可下載下來學(xué)習(xí)一下。程序測試多次可用,歡迎大家留言評論,一起探討,共同進(jìn)步。 版權(quán)所有,轉(zhuǎn)載請注明出處,謝謝!代碼僅供學(xué)習(xí),不得用于其他用途。
|