先簡單說一下實驗目的吧。平時做項目或做一些小作品的時候需要用到時間,時間用的是STM32內部的RTC,在精度要求不是特別高時這樣省去接外設時鐘模塊,省時省力。但我們都知道,RTC在斷電后數據是不保存的,也就是說如果沒有電源如電池之類一直給后備寄存器供電的話數據是會丟失的,下次開機時時間就會恢復初始化時設置的那個時間,想要時間正確就要重新設置時間,這就很不實用。所以就想通過網絡獲取時間的方式來自動校正時間。又恰好用到SIM900A這個模塊,所以查了下資料,發現已經有前輩做過了。看了https://blog.csdn.net/ludaoyi88/article/details/51757664這位博主的文章,獲取時間部分用了這位前輩提供的代碼,在他提供的代碼上進行測試和改進(直接用不修改的話是不行的。如果大家仔細對比的話會發現其實我改進后的代碼跟原版的還是有蠻多小細節不同的),最終得以達到目的,即可以通過服務器獲取到網絡的時間并自動校正到STM32內部RTC中,這里再次感謝 ludaoyi123這位前輩。大家可以先去看看這位前輩的博客,也就是上面那個鏈接,獲取時間和處理時間數據都是源于他的那篇文章。好了,接下來就說一下怎么獲取時間的吧。 此次實驗用的單片機是STM32F103C8T6核心板,串口2控制SIM900A模塊數據的收發,串口1用于在串口調試助手打印相關信息。下面是我的硬件平臺:STM32F103C8T6核心板和SIM900A模塊。
硬件平臺
獲取網絡時間的第一種方法是連接到國際授時服務器,IP為:time點nist點gov,端口為:13,我用的連接方式是TCP連接。當客戶端連接到此服務器后,服務器會立刻發送一串格式為“58646 19-06-12 16:05:36 50 0 0 668.3 UTC(NIST) * ”這樣的字符串返回給客戶端并主動斷開連接。這一串字符串中就包含有日期和時間,我們所要做的,就是把相關的日期和時間提取出來轉換成數字就好了,這也是最最重要的部分。這里貼出我修改后的代碼:`- _nowtime_obj NowTime; //現在時間日期結構體
- ///*******************************************************************************
- //* 函數名 : Get_Sever_Time
- //* 描述 : 獲取Time信息(連接服務器成功情況下)
- //* 輸入 :
- //* 輸出 :
- //* 返回 :
- //* 注意 :服務器返回的數據形式如下: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 為接收緩存數組
- break;
- }
-
- }
-
- printf("\r\n時間數組timestr的數據為");
- 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是返回數據到處理處結果的誤差
- 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);
- //轉換成RTC時間格式
- break;
- }
- }
-
- printf("\r\n當前時間:%d年%02d月%02d日%02d時%02d分%02d秒\r\n",NowTime.year,NowTime.moon,NowTime.day,NowTime.hour,NowTime.minu,NowTime.sec);
-
- printf("\r\n寫入SIM900A設置時間的字符串為:");
- printf((char *)TimeRTC);
- printf("\r\n");
- if(NowTime.year!=0&&NowTime.moon!=0&&NowTime.day!=0&&NowTime.hour!=0&&NowTime.minu!=0)//獲取到正確數據
- {
- RTC_Set(NowTime.year,NowTime.moon,NowTime.day,NowTime.hour,NowTime.minu,NowTime.sec); //設置STM32單片機內部RTC時間
- Set_SIM900A_RTCtime();//給GSM模塊設置從網絡獲取來的時間
- }
- else
- printf("\r\n沒能在服務器獲取到正確時間,復位重試一下\r\n");
-
- delay_ms(500);
- Get_GSM_RTCtime();//用串口查看一下GSM模塊時間正常了沒有
- sim900a_send_cmd("AT+CIPSHUT\r\n","SHUT OK",2); //關閉連接
- delay_ms(300);
- AT_DataInit();//清除接收數組
- CLR_Buf2();
- delay_ms(200);
- }
復制代碼 代碼內容很簡單,就是連接上服務器后用strstr函數和字符“-”或者“5”比較串口2接收數組中的“58646 19-06-12 16:05:36 50 0 0 668.3 UTC(NIST) * ”這串字符串,如果串口二接收數組中接收到了這一串字符串,那個就將這串字符串保存到數組timestr中并跳出死循環。實驗過程中發現最容易出現問題的就是這一塊,也就是有時候串口2沒能收到完整的字符串導致程序死在死循環里面,應該是串口2中斷服務函數沒寫好。接到完整的字符串后,接下來接著把年月日時分秒提取出來,把字符變成數字并做判斷轉換成北京時間,當服務器中的小時大于或等于16時當地小時+8-24,且天數+1,否則小時只是單純地+8,這個8就是時差。數據處理好后就給STM32內部的RTC設置時間和給GSM模塊的RTC設置時間,后續如果要重新校正時間的話只需要在SIM900A內部獲取RTC的時間就可以了。實驗過程中相關信息在串口調試助手顯示,結果如下圖所示:
測試結果一
一次成功不代表什么,所以又在不同時間段多次測試:
測試結果二
測試三
測試四
測試結果表明,實驗是成功的,也就是在SIM900A連接到服務器后可以獲取到正確時間并校正到RTC中。
但測試的過程中發現上面連接服務器的方法并不是最佳的,原因是連接服務器不是很好用,有時候需要六七秒就能連上,有時候要半分多鐘才能連上,感覺不可靠,就像刷臉一樣,而且,有時候辛辛苦苦連上了,串口2還不能完完整整接收到那串字符串。。。所以,提供了第二個獲取時間的方法,也就是直接從SIM900A內部獲取時間日期,下面貼上代碼: - _rtctime_obj RTCTime; //SIM900A時間日期結構體
- /*******************************************************************************
- * 函數名 : Get_GSM_RTCtime(void)
- * 描述 : 獲取SIM900A模塊中RTC的時間
- * 輸入 :
- * 輸出 :
- * 返回 :RTCTime的時間結構體--年月日時分秒
- * 注意: 如:"19/06/18,13:11:52+08";
- *******************************************************************************/
- void Get_GSM_RTCtime(void)//直接從GSM模塊內部獲取時間,初次上電時需要手動復位
- {
- u8 i = 0;
- char timestr1[50]={0};
- char *timestr = timestr1;//指向timestr1地址
- AT_DataInit();//清除接收數組
- sim900a_send_cmd("AT+CCLK?\r\n","OK",5);
- delay_ms(1000);
- printf("\r\n獲取SIM900A內部時間日期中...\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內部時間:%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); //設置STM32單片機內部RTC時間
- AT_DataInit();//清除接收數組
- memset(USART2_RX_BUF,0,USART2_MAX_RECV_LEN);
- }
復制代碼 這代碼跟上面的差不多,這里就不重復解釋了,也很容易看懂。用法是直接在主函數調用就行了。需要注意的是,板子初次上電時需要手動復位一下單片機,不然串口發送AT+CCLK?命令給SIM900A模塊時它不會返回字符串。這里的代碼也是上面提到的那位博主提供我再進行小小修改的,我只是代碼搬運工。然后發現用AT+CCLK?指令查詢時間并沒有網上說的那樣不能用,反而覺得更好用,個人比較推薦這種方法。其效果圖如下所示:
下面給出main.c和串口2配置及中斷服務函數代碼,請各路大神批評指正,提出寶貴意見。 - /**
- ******************************************************************************
- * @file main.c
- * @author GXNU_LPK
- * @version V1.0
- * @date 2019-06-17
- * @brief 用3.5.0版本庫建的工程模板
- ******************************************************************************
- * @attention
- *
- * 實驗平臺: STM32F103CT6核心板
- * 實驗內容:GSM(SIM900A)模塊通過服務器實現網絡授時
- * 實驗作者:廣西師范大學電子工程學院2015LPK
- * 備 注:Get_net_time.c這部分的代碼來源于https://blog.csdn.net/ludaoyi88/article/details/51757664 ,
- * 根據此ludaoyi123博主提供的思路和代碼進行測試和修改而來,經實驗測試和改善后目前已初步達到實驗目的,但
- * 通過服務器獲取時間那種方法 穩定性方面略微存在一些欠缺,請學習者自行改善,僅供學習,不得用于其他用途
- ******************************************************************************
- */
-
- #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 主函數
- * @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");//連接授時服務器(國外)
- }
- Get_Sever_Time();//提取獲取到的時間并存入STM32和GSM模塊內部RTC中
- // Get_GSM_RTCtime();//直接從GSM模塊內部獲取時間,初次上電時需要手動復位
-
- printf("\r\n系統初始化完成\r\n");
- while(1)
- {
- display_time();//顯示STM32內部RTC的時間
- delay_ms(1000);
- }
-
- }
- /*********************************************END OF FILE**********************/
復制代碼 下面是串口2配置及中斷服務函數代碼:
- #include "usart2.h"
- #include "stdio.h"
- #include "string.h"
- #include "stdarg.h"
- //串口接收緩存區
- u8 USART2_RX_BUF[USART2_MAX_RECV_LEN]; //接收緩沖,最大USART2_MAX_RECV_LEN個字節.
- u8 USART2_TX_BUF[USART2_MAX_SEND_LEN]; //發送緩沖,最大USART2_MAX_SEND_LEN字節
- u16 USART2_RX_STA=0;
- /*
- 功能描述: 發送一個字節
- 函數參數: byte —— 要發送的字節
- 返回說明: 無
- */
- void UART2_SendByte(unsigned char byte)
- {
- USART_SendData(USART2,byte);//向串口2發送數據
- while(USART_GetFlagStatus(USART2,USART_FLAG_TC)!=SET);//等待發送結束
- }
- /*
- 功能描述: 串口發送字符串
- 函數參數: s —— 指向字符串的指針(字符串以'\0'結尾)
- 返回說明: 無
- 注:如果在字符串結尾有'\n',則會發送一個回車換行
- */
- 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 函數
- //確保一次發送數據不超過USART2_MAX_SEND_LEN字節
- 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); //此次發送數據的長度
- for(j=0;j<i;j++) //循環發送數據
- {
- while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET); //循環發送,直到發送完畢
- USART_SendData(USART2,USART2_TX_BUF[j]);
- }
- }
- extern void AT_RecvProcess(unsigned char byte);
- //void USART2_IRQHandler(void) //串口2中斷服務程序
- //{
- // u8 Res;
- // if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收中斷(接收到的數據必須是0x0d 0x0a結尾)
- // {
- // USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除中斷標志位
- // Res =USART_ReceiveData(USART2);//(USART2->DR); //讀取接收到的數據
- // AT_RecvProcess(Res);
- // if(USART2_RX_STA < USART2_MAX_RECV_LEN) //還可以接收數據
- // {
- // USART2_RX_BUF[USART2_RX_STA]=Res; //記錄接收到的值
- // USART2_RX_STA++;
- // }
- // else
- // {
- // USART2_RX_STA=USART2_MAX_RECV_LEN;//強制標記接收完成
- // }
- // }
- //}
- 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;
- }
- }
復制代碼 以上就是SIM900A獲取網絡時間的兩種小方法,不足之處請批評指正,不喜勿噴,謝謝~
沒有黑幣或黑幣不足的的完整工程下載鏈接:https://download.csdn.net/download/qq_36112455/11247378 有黑幣的可以從附件直接下載。
工程資源簡介:用STM32F103單片機控制SIM900A模塊通過連接國外的授時服務器或者訪問SIM900A內部獲取網絡時間,把獲得的時間設置到STM32內部的RTC中,實現單片機上電自動校正時間。時間在串口上顯示出來。資源是完整的工程,里面包含了SIM900A的驅動和常用的撥打電話發短信連接到服務器等等功能;另工程里面也有STM32 RTC的驅動,可通過編譯等獲取電腦上的時間,也可下載下來學習一下。程序測試多次可用,歡迎大家留言評論,一起探討,共同進步。 版權所有,轉載請注明出處,謝謝!代碼僅供學習,不得用于其他用途。
|