先简单说一下实验目的吧。平时做项目或做一些小作品的时候需要用到时间,时间用的是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的驱动,可通过编译等获取电脑上的时间,也可下载下来学习一下。程序测试多次可用,欢迎大家留言评论,一起探讨,共同进步。 版权所有,转载请注明出处,谢谢!代码仅供学习,不得用于其他用途。
|