一、概述:
C51内核的单片机接入NTP服务器,通过网络授时时钟,目的是让电子时钟与网络时间同步。在B站看到有同步网络时间的电子钟,便萌发了通过网络授时解决电子时间调节不方便,走时误差大的问题。ESP01s接入网络,使用UPD方式接入阿里云的NTP服务器(起初接入的是北京市中国教育和科研计算机网骨干网,未知原因授时有偶有1分钟的误差,有时无返回值,后来改成阿里云,自然解决),NTP服务器返回Unix时间戳,通过STC8H的解码计算得到日期与时间。过程其中的Unix时间戳的计算花销用时较长,本身算法不难,不好解决的是c51这个8位的单片机内核用来处理32位字长计算确实难为它了,解决的办法把32的数据分组成4个8位的数组,放入至数组中然后运用小学数学老师教的运算方法,计算出最终结果。
二、编程简述:
1、Esp01s接入ntp
模块的接入参考了https://www.cnblogs.com/Luad/p/10652644.html使用AT指令获取网络时间,其中实现原理大概是使用AT指令运用UDP方式接入ntp服务器,端口是123,然后向服务发送48位hex码,服务器响应返回58位的hex码,文中提及的4位unix戳码为40-43,实际上51-54位。返回的时间戳与标准的时间戳有一定的区别,区别在于起点ntp自1900开始,标准时间戳自1970开始,计算方法将之间的秒差减去,标准时间戳的计算年月日的方法大体上是将年月日分为4年一组进行取模和整除,取模的后第三个年份为闰年,先将整年整年的减去,秒值不足一年了加上年整除值的4倍即得到年,剩下的,取月的方法为一月一月减,减去时按月的天数来,闰年时需要补偿一天时间,天数不足时即为本月,剩下的天数即为日。小时的计算除3600再与24取模,最终结果加上时区8,分钟的计算除60再除模60,秒钟的计算直接模60。设定一个定时器0,每1秒钟刷新一下,将计时显示在0LED上,第4分钟向服务器获取一次时间,用来纠正定时器的计时误差。
三、实验平台搭建:
1、MCU:STC-打狗棒系列核心实验板 V2.3
2、实验板平台:德飞莱LY-51s
3、ESP-01s模块、0.96寸OLED 4脚IIC接口
4、硬件连接表:
ESP-01s接线表
3V3---------->3.3V
GND---------->GND
TX---------->P36
RX---------->P37
指示灯接线表
LED1---------->P01(执行器LED灯模拟)
LED2---------->P02(通信指示)
0.96寸4脚OLED接线图硬I2C接线表
SDL---------->P24
CLK---------->P25
VCC---------->+5V
GND---------->GND
四、测试源代码:
//main.c
#include <STC8H.h>
#include "intrins.h"
#include <stdio.h>
#include "UnixToBeijing.h"
#include "Uart.h"
#include "OLED.h"
sbit Led1=P0^0;
sbit Led2=P0^1;
sbit Led3=P0^2;
sbit Led4=P0^3;
unsigned int count=0;
unsigned char xdata a[4]={0x00,0x00,0x00,0x00};//ntp服务器Unix格式
unsigned char code b[4]={0x83,0xAA,0x7E,0x80};
unsigned char xdata c[4]={0x00,0x00,0x00,0x00};//标准Unix格式
unsigned int xdata d[6]={0x00,0x00,0x00,0x00,0x00,0x00};
unsigned char i;
unsigned int oled_y=0,oled_mo=0,oled_d=0,oled_h=0,oled_m=0,oled_s=0;
unsigned char idata oledBuf[8]="";
/******************AT指令***********************/
u8 code s1[]="AT+CIPCLOSE\r\n";
u8 code s2[]="AT+RST\r\n";
u8 code s3[]="AT+CIFSR\r\n";
u8 code s4[]="AT+CIPMUX=0\r\n";
u8 code s5[]="AT+CIPSTART=\"UDP\",\"ntp2.aliyun.com\",123\r\n";
u8 code s6[]="AT+CIPMODE=1\r\n";
u8 code s7[]="AT+CIPSEND=48\r\n";
u8 code s8[]={0xE3,0x00,0x06,0xEC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x31,0x4E,0x31,0x34,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
u8 code k1[]="0.0.0.0";
u8 code k2[]="CONNECT";
u8 code k3[]=">";
u8 code k4[]="apitag";
u8 code k5[]="ta\":0,";
u8 code k6[]="ta\":1,";
u8 code k7[]="WIFI GOT IP";
void init_IO();//初始化IO
void DispTime(unsigned int d[]);//显示当前时间
bit Findkey(u8 key[],u8 len);
void GetWifiNtp();
void GetDate();
void init_timer0();//定时器0的初始化
void main()
{
P_SW2 |= 0x80; //扩展寄存器XFR访问使能
init_IO();
init_Uart1();
init_Uart2();
EA=1;
printf("STC8H UnixTime Test!\n");
init_timer0();
init_IIC();//初始化硬IIC
OLED_Init();//初始化OLED
// sprintf((char *)oledBuf ,"Length:mm");//格式化输出
OLED_ShowString(0,0," MG LeiYang!");
// OLED_ShowString(0,4,oledBuf);
OLED_ShowString(0,2,"Get Ntp Time!");
//OLED_ShowString(0,6," 2023/08/09");
// U32sub(a,b,c);
//c[0]=0x64;c[1]=0xe7;c[2]=0x91;c[3]=0xde;
// UnixToBeiJinTime(c,d);
// DispTime(d);
ET0=0;
GetWifiNtp();
ET0=1;
while(1)
{
if(count>=40000)
{
count=0;
ET0=0;
GetDate();
ET0=1;
}
}
}
void DispTime(unsigned int d[])
{
printf("%02d-",d[0]);
printf("%02d-",d[1]);
printf("%02d ",d[2]);
printf("%02d:",d[3]);
printf("%02d:",d[4]);
printf("%02d",d[5]);
printf("\n");
}
void init_IO()
{
RSTCFG=0x50; //开启RST键进入ISP模式
P0M1 = 0x00; P0M0 = 0x00; //设置P0口为准双向口
P1M1 = 0x00; P1M0 = 0x00; //设置P0口为准双向口
P2M1 = 0x00; P2M0 = 0x00; //设置P1口为准双向口
P3M1 = 0x00; P3M0 = 0x00; //设置P3口为准双向口
P4M1 = 0x00; P4M0 = 0x00; //设置P4口为准双向口
P5M1 = 0x00; P5M0 = 0x00; //设置P5口为准双向口
}
void init_timer0()//定时器0的初始化10ms
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x33; //设置定时初始值
TH0 = 0xE3; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;//Timer0 开中断
}
void GetWifiNtp() //wifi连接上线
{
unsigned char i;
RX1_Buffer[RX1_Cnt]='\0';
while(Findkey(k7,10)==0);
SendToEspStr(s3);
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
RX1_Buffer[RX1_Cnt]='\0';
while(Findkey(k1,6))
{
SendToEspStr(s3);
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
RX1_Buffer[RX1_Cnt]='\0';
}
SendToEspStr(s4);
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
SendToEspStr(s5);
RX1_Buffer[RX1_Cnt]='\0';
while(Findkey(k2,6))
{
SendToEspStr(s5);
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
RX1_Buffer[RX1_Cnt]='\0';
}
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
SendToEspStr(s7);
RX1_Buffer[RX1_Cnt]='\0';
while(Findkey(k3,1))
{
SendToEspStr(s7);
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
RX1_Buffer[RX1_Cnt]='\0';
}
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
RX1_Cnt=0x00;
for(i=0;i<48;i++)
{
Uart1Send(s8[i]);
}
//SendToEspStr(s8);
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
for(i=78;i<82;i++)
{
//printf("0x%hhx ",RX1_Buffer[i]);
a[i-78]=RX1_Buffer[i];
}
Led2=0;
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
U32sub(a,b,c);
UnixToBeiJinTime(c,d);
DispTime(d);
oled_y=d[0];oled_mo=d[1];oled_d=d[2];
oled_h=d[3];oled_m=d[4];oled_s=d[5];
SendToEspStr(s1);
Led2=1;
}
void GetDate()
{
unsigned char i;
SendToEspStr(s5);
RX1_Buffer[RX1_Cnt]='\0';
while(Findkey(k2,6))
{
SendToEspStr(s5);
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
RX1_Buffer[RX1_Cnt]='\0';
}
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
SendToEspStr(s7);
RX1_Buffer[RX1_Cnt]='\0';
while(Findkey(k3,1))
{
SendToEspStr(s7);
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
RX1_Buffer[RX1_Cnt]='\0';
}
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
RX1_Cnt=0x00;
for(i=0;i<48;i++)
{
Uart1Send(s8[i]);
}
//SendToEspStr(s8);
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
for(i=78;i<82;i++)
{
//printf("0x%hhx ",RX1_Buffer[i]);
a[i-78]=RX1_Buffer[i];
}
Led2=0;
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
Delay1ms(200);Delay1ms(200);Delay1ms(200);Delay1ms(200);
U32sub(a,b,c);
UnixToBeiJinTime(c,d);
DispTime(d);
oled_y=d[0];oled_mo=d[1];oled_d=d[2];
oled_h=d[3];oled_m=d[4];oled_s=d[5];
SendToEspStr(s1);
Led2=1;
}
//接收到的字符串,从中查找关键字
bit Findkey(u8 key[],u8 len)
{
u8 i=0,j=0;
bit result=0;
for(i=0;i<RX1_Cnt;i++)
{
if(RX1_Buffer[i]==key[0])
{
for(j=1;j<len;j++)
{
if(RX1_Buffer[i+j]!=key[j])
break;
if(j==len-1) result=1;
}
}
}
return result;
}
void Timer0_isr() interrupt 1//Timer0中断入口
{
count++;
if(count%50==0)
Led1=~Led1;
if(count%100==0)
{
oled_s++;
if(oled_s>=60)
{
oled_s=0;
oled_m++;
}
if(oled_m>=60)
{
oled_m=0;
oled_h++;
}
if(oled_h>=24)
{
oled_h=0;
oled_d++;
}
//sprintf((char *)oledBuf ," ");//格式化输出
sprintf((char *)oledBuf ," %02d-%02d-%02d",oled_y,oled_mo,oled_d);
OLED_ShowString(0,4,oledBuf);
sprintf((char *)oledBuf ," %02d:%02d:%02d",oled_h,oled_m,oled_s);
OLED_ShowString(0,6,oledBuf);
//sprintf((char *)oledBuf ," ");//格式化输出
}
}
//C51计算unix时间戳
unsigned char U32sub(unsigned char a[],unsigned char b[],unsigned char c[]);//减法a-b>0 返回1,否则返回0,值存c
unsigned char U32add(unsigned char a[],unsigned char b[],unsigned char d[]);//加法a+b无溢出返回1,否则返回0,值存d
void U32Mul(unsigned char a[],unsigned char b,unsigned char c[]);//乘法
void U32Mul1(unsigned char a[],unsigned char b[],unsigned char c[]);//乘法
char U32Cmp(unsigned char a[],unsigned char b[]);//比较大小返回1,否则返回0,值存d
void U32Div(unsigned char a[],unsigned char b[],unsigned char c[]);//除法
void U32Div1(unsigned char a[],unsigned char b,unsigned char c[]);//除法,除数为u8
void U32Mod(unsigned char a[],unsigned char b,unsigned char c[]);//求余数
void U32ModMon(unsigned char a1[],unsigned char c1[]);//求固定1421*24的余数
void UnixToBeiJinTime(unsigned char a1[],unsigned int c1[]);//Unix时间转北京时间
void UnixToBeiJinTime(unsigned char c[],unsigned int c1[])
{
unsigned char i=0,flag=0;
unsigned int time=0,hours_per_year=0,Pass4year=0;
char code Days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
unsigned int month=0;//月
unsigned int days=0;//日
unsigned int hour=0;//小时
unsigned int minute=0;//分钟
unsigned int second=0;//分钟
unsigned int year=0;//年
unsigned char idata d[4]={0x01,0xE1,0x84,0x80};
unsigned char idata e[4]={0x00,0x00,0x00,0x00};
unsigned char idata f[4]={0x00,0x00,0x00,0x00};
unsigned char idata a[4]={0x00,0x00,0x00,0x00};
d[0]=0x00;d[1]=0x00;d[2]=0x00;d[3]=0x00;
U32Div1(c,60,d);
U32Div1(d,60,e);
d[0]=0x00;d[1]=0x00;d[2]=0x00;d[3]=0x00;
U32Mod(e,24,d);
hour=d[3]+8;
hour=hour%24;
d[0]=0x00;d[1]=0x00;d[2]=0x00;d[3]=0x00;
e[0]=0x00;e[1]=0x00;e[2]=0x00;e[3]=0x00;
U32Div1(c,60,e);
U32Mod(e,60,d);
minute=d[3];
d[0]=0x00;d[1]=0x00;d[2]=0x00;d[3]=0x00;
U32Mod(c,60,d);
second=d[3];
U32Div1(c,60,d);
U32Div1(d,60,a); // time/3600的值
d[0]=0x00;d[1]=0x00;d[2]=0x00;d[3]=0x01;
U32sub(a,d,f);//time-1
e[0]=0x00;e[1]=0x00;e[2]=0x00;e[3]=0x00;
U32Div1(f,162,e);
d[0]=0x00;d[1]=0x00;d[2]=0x00;d[3]=0x00;
U32Div1(e,216,d);
//顺便计算好年
year=d[3];
year=year*4;
d[0]=0x00;d[1]=0x00;d[2]=0x00;d[3]=0x00;
U32ModMon(c,d);
time=d[2]*256+d[3];
year=year+1970;
for (i=1;i<4;i++)
{
//一年的小时数
hours_per_year = 365 * 24;
//判断闰年
if(i==3)
{
//是闰年,一年则多24小时,即一天
hours_per_year += 24;
}
flag=i;
if(time < hours_per_year)
{
break;
}
year++;
time -= hours_per_year;
}
time=time/24;//剩下的天数
time++;
//校正闰年的误差,计算月份,日期
if((year & 3) == 0)
{
if(time > 60)
{
time--;
}
else
{
if(time == 60)
{
days = 29;
return ;
}
}
}
//计算月日
for(i = 0; Days[i] < time; i++)
{
if((i==1)&&(flag==3))
time-=29;
else
time -= Days[i];
}
month=i+1;
days = (int)(time);
c1[0]=year;c1[1]=month;c1[2]=days;c1[3]=hour;c1[4]=minute;c1[5]=second;
}