打印
[通用8051核FLASH系列]

【芯圣SDK-HC89F0541测评】 万年历+网络时钟同步

[复制链接]
5471|22
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
Liyj336|  楼主 | 2020-9-13 21:59 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 芯圣电子官方QQ 于 2023-7-20 10:47 编辑

首先很感谢圣芯MCU送的SDK-HC89F0541,之前一直忙于工作把测评的事耽搁了一下。
SDK-HC89F0541开发板是由两部分组成:HC89F0541微控制器的主控板、HC-LINKV4.0 仿真烧录器。所以只需一条microUSB就可以完成应用程序的烧录和验证,不需要另外的烧录器,使用十分方便。
这次我测评的内容是,SDK-HC89F0541+ESP8266+OLED12864实现万年历时钟的显示和同步。用到HC89F0541微控制器内部的外设主要有UART1、UART2、TIM1、TIM2、TIM3和两个模拟I2C的GPIO脚。程序编写难度不大,跟平常51内核单片机编程差不多,都是直接配置寄存器编程。官网有例程,可以直接使用例程进行软件开发,很是方便。
一、ESP8266获取网络时间
首先说说怎么获取网络时钟。现在很多NTP时间服务器,从服务器中就能获取网络时间。可以通过ESP8266连接到时间服务器,再从服务器中获取时间。我所使用的时间服务器为time.windows.com,IP地址为52.231.114.183。发送AT命令AT+CIPSTART="UDP","52.231.114.183",123\r\n,就可以让ESP8266连接上该服务器。可以使用串口调试助手先进行调试,看看ESP8266能否获得NTP时间数据。
如果你的ESP8266已经连上一个无线热点,发送AT命令。
AT+CIPSTART="UDP","time.windows.com",123\r\n
AT+CIPMODE=1\r\n
AT+CIPSEND\r\n
之后再发送48个字节的十六进制数(不用回车加换行):
0b 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 d6 6d d9 00 00 00 00 00
然后就可以看到时间服务器返回来的数据,下图是我调试获得的数据,正常情况下一共会收到48个字节数据,第41-44字节的数据是我们所需要的时间数据。


二、UART外设使用
我使用了两个UART外设,UART1用来跟ESP8266进行数据交互。我的数据方案接收采用循环队列+定时器超时+UART接收中断的方案。UART2用做向外打印信息。
1、UART1初始化
void UartInit()
{
/************************************系统初始化****************************************/
        CLKSWR = 0x51;                                                //选择内部高频RC为系统时钟,内部高频RC 2分频,Fosc=16MHz
        CLKDIV = 0x01;                                                //Fosc 1分频得到Fcpu,Fcpu=16MHz
/**********************************UART配置初始化**************************************/
        P1M6 = 0xC2;                                        //P16设置为推挽输出
        P1M7 = 0x62;                                        //P17设置为上拉输出
        TXD_MAP = 0x16;                                                //TXD映射P16
        RXD_MAP = 0x17;                                                //RXD映射P17               
        T4CON = 0x06;                                                //T4工作模式:UART1波特率发生器
  TH4 = 0xFF;
        TL4 = 0xF7;                                                        //波特率115200
        SCON2 = 0x02;                                                //8位UART,波特率可变
        SCON = 0x10;                                                //允许串行接收
        IE |= 0x10;                                                        //使能串口中断
        EA = 1;                                                                //使能总中断

        RingBuff_Init(&ringBuff, RevBuff, MAX_LEN);
}


2、UART1中断处理
void UART1_Rpt(void) interrupt UART1_VECTOR
{
    u8 ch;
    inum = 0;
    if(SCON & 0x01)                        //判断接收中断标志位
    {
        ch = SBUF;
        if((Rev_Len<MAX_LEN)&&(Revflag!=1))
        {
        RingBuff_PutLen(&ringBuff, &ch, 1);  //数据压入循环队列
        Rev_Len ++;
        Rev_Status = 1;
        }
        SCON &=~ 0x01;                    //清除接收中断标志位
    }                                   
}



3、UART2初始化
void Uart2_Init(void)
{
/************************************系统初始化****************************************/
        CLKSWR = 0x51;                                                //选择内部高频RC为系统时钟,内部高频RC 2分频,Fosc=16MHz
        CLKDIV = 0x01;                                                //Fosc 1分频得到Fcpu,Fcpu=16MHz
/**********************************UART配置初始化**************************************/
        P0M4 = 0xC2;                                        //P16设置为推挽输出
        P0M5 = 0x62;                                        //P17设置为上拉输出
        TXD2_MAP = 0x04;                                        //TXD映射P16
        RXD2_MAP = 0x05;                                        //RXD映射P17               
        RCAP5H = 0xFF;
        RCAP5L = 0xF7;
        T5CON = 0x06;                                                //T5工作模式:UART2波特率发生器
        S2CON2 = 0x00;                                                //8位UART,波特率可变
        S2CON = 0x10;                                                //允许串行接收
        IE |= 0x40;                                                        //使能串口中断
        EA = 1;                                                                //使能总中断
}
4、编写UART2打印数据函数
void LOG_ch(uchar c)
{
        S2BUF = c;                //发送8位串口数据
        while(!(S2CON & 0x02));
        S2CON &=~ 0x02;                        //清除发送中断标志位
}
void LOG(uchar *s)
{
        while(*s)//收到结束符跳出循环
  {
          LOG_ch(*s);//发送S地址里面的字符
          s++;//S地址加一,发送下一个字符
  }
}
void co_printf(const char *fmt,...)
{
    va_list ap;  
    char data string[50];
    va_start(ap,fmt);  
    vsprintf(string,fmt,ap);//此处也可以使用sprintf函数,用法差不多,稍加修改即可,此处略去  
                LOG(string);
    va_end(ap);  
}
三、TIM的使用
使用了3个TIM,TIM0用作接收数据超时定时器,TIM1用作1ms延时定时器,TIM2用作1秒中断定时器用来增加时间数值。
1、TIM0定时器初始化与中断
void Tim0_Init(void)
{
        /************************************系统初始化****************************************/
        CLKSWR = 0x51;                                                //选择内部高频RC为系统时钟,内部高频RC 2分频,Fosc=16MHz
        CLKDIV = 0x01;                                                //Fosc 1分频得到Fcpu,Fcpu=16MHz
/**********************************相关配置初始化**************************************/
/**********************************TIM0配置初始化**************************************/
        TCON1 = 0x00;                                                //Tx0定时器时钟为Fosc
        TMOD = 0x00;                                                //16位重装载定时器/计数器
        TH0=0xCB;
        TL0=0xEB;
        IE |= 0x02;                                                        //打开T0中断
        TCON |= 0x10;                                                //使能T0
        EA = 1;                                                                //打开总中断
}

void TIMER0_Rpt(void) interrupt TIMER0_VECTOR
{
        inum++;
        if(inum > TimNum && Rev_Status)
        {
                memset(RevData,'\0',MAX_LEN);
                RingBuff_GetLen(&ringBuff,RevData, Rev_Len);  //数据接收超时,循环队列的数据放到数组中,待使用。
                Rev_Status = 0;
                Data_Len=Rev_Len;
                Rev_Len=0;
                Revflag=1;
        }
}
2、TIM1初始化与中断
void Tim1_Init(void)
{
/************************************系统初始化****************************************/
        CLKSWR = 0x51;                                                //选择内部高频RC为系统时钟,内部高频RC 2分频,Fosc=16MHz
        CLKDIV = 0x01;                                                //Fosc 1分频得到Fcpu,Fcpu=16MHz
        TCON1 = 0x00;                                                //T1定时器时钟为Fosc
        TMOD = 0x00;                                                //16位重装载定时器/计数器
        TH1 = 0xFF;
        TL1 = 0xF3;                //10us
        IE |= 0x08;                                                        //打开T1中断
        TCON &=(~0x40);                                                //失能T1
        EA = 1;                                                                //打开总中断
}

void Tim2_Init(void)
{
/************************************系统初始化****************************************/
        CLKSWR = 0x51;                                                //选择内部高频RC为系统时钟,内部高频RC 2分频,Fosc=16MHz
        CLKDIV = 0x01;                                                //Fosc 1分频得到Fcpu,Fcpu=16MHz
        T3CON |=(0x34);                                                //使能T1
        TH3 = 0x0B;
        TL3 = 0xDC;                //10us
        IE1 |= 0x04;                                                        //打开T1中断
        
        EA = 1;                                                                //打开总中断
}
void TIMER1_Rpt(void) interrupt TIMER1_VECTOR
{
        if(DelayCount != 0 )
                DelayCount--;
        else
        {
                TCON &=(~0x40);
        }
}
3、Tim2初始化与中断
void Tim2_Init(void)
{
/************************************系统初始化****************************************/
    CLKSWR = 0x51;                        //选择内部高频RC为系统时钟,内部高频RC 2分频,Fosc=16MHz
    CLKDIV = 0x01;                        //Fosc 1分频得到Fcpu,Fcpu=16MHz
    T3CON |=(0x34);                        //使能T1
    TH3 = 0x0B;
    TL3 = 0xDC;        //10us
    IE1 |= 0x04;                            //打开T1中断   
    EA = 1;                                //打开总中断
}
void TIMER3_Rpt(void) interrupt T3_VECTOR
{
        T3CON &=~ 0x80;                                                //清除中断标志位
  NTP_num++;
}
四、ESP8266控制
ESP8266使用UART串口发送AT命令进行控制。其中有些命令没采用检查返回数据机制。

//配置ESP8266位sta模式,并连接到路由器
uint8_t app_esp8266_wifista_config(char *ssid, char *pwd)
{
    uint8_t p[200];
    uint8_t TryConnectTime = 0;
   
      co_printf("准备连接\r\n");
    while (app_esp8266_send_cmd("AT", "OK", 20)) //检查WIFI模块是否在线
        {
                 static u8 i = 0;
                 if(i > 5)
                 {
                    i = 0;
                    break;
                 }
                 i++;
                Delay_1ms(1000);
        co_printf("未检测到模块\r\n");
    }
        Delay_1ms(1000);
    app_esp8266_send_cmd("AT+CWMODE=3", "OK", 50);     //设置WIFI STA模式
      Delay_1ms(1000);
    app_esp8266_send_cmd("AT+CIPMUX=0", "OK", 20);
        Delay_1ms(1000);
    sprintf((char *)p, "AT+CWJAP=\"%s\",\"%s\"", ssid, pwd);
      
        TimNum = 300;
        app_esp8266_send_cmd(p, "WIFI GOT IP", 50);
        while(1)
        {
            u16 waittime;
            waittime = 1200;
           while (--waittime)  //等待倒计时
        {
           Delay_1ms(10);
            if (Revflag) //接收到期待的应答结果
            {
                if (app_esp8266_check_cmd("OK"))
                {
                    co_printf("发送:%s 回应:%s\r\n", "Wifi cnnect", "WIFI GOT IP");
                    break;//得到有效数据
                }
               Revflag = 0;
            }

        }
                break;
        }
        TimNum = 10;
    co_printf("WiFi连接成功\r\n");
        Delay_1ms(3000);
    return 0;
}

//配置连接时间服务器
uint8_t build_tcp_connect(char *type, char *ip, char *port)
{
    char str_tmp[200];
    uint8_t TryConnectTime = 0;

    app_esp8266_send_cmd("AT+CIPMUX=0", "OK", 100);

                Delay_1ms(1000);
                SendStr("AT+CIPSTART=\"UDP\",\"52.231.114.183\",123\r\n");
    Delay_1ms(4000);
                RingBuff_Clear(&ringBuff);
                Revflag = 0;
    app_esp8266_send_cmd("AT+CIPMODE=1", "OK", 100);    //传输模式为:透传
                Delay_1ms(1000);
    app_esp8266_send_cmd("AT+CIPSEND", "OK", 100);       //开始透传
                Delay_1ms(1000);
                return 0;
}
NTP数据处理
从NTP服务器获取得的时间数据很Unix时间戳很类似,不过Unix时间戳从1970年开始,而NTP时间数值从1900年开始。NTP时间数值转换为北京时间,可以参考Unix时间戳转换为北京时间的例程。
//主函数
void main()
{
    UartInit();
    Uart2_Init();
  Tim0_Init();
    Tim2_Init();
  Delay_Init();
    Delay_1ms(3000);
    IIC_Init();
    OLED_Init();
    Delay_1ms(100);
    OLED_Clear();
    Delay_1ms(100);
    NTP_num = 1599923854;
    OLED_ShowCHinese(32,3,10);//正
    OLED_ShowCHinese(48,3,11);//在
    OLED_ShowCHinese(64,3,12);//启
    OLED_ShowCHinese(80,3,13);//动
    Delay_1ms(4000);
    Wifi_Link = app_esp8266_wifista_config(WIFI_SSID, WIFI_PWD);
    UDP_Link = build_tcp_connect(data_cip_type, data_api_ip, data_api_port);
    if(Wifi_Link == 0)
        co_printf("Wifi is successfully Connected !\n");
    if(UDP_Link == 0)
        co_printf("UDP is successfully Connected !\n");
    while(1)
    {
        u8 i = 0;
     if(UDP_Link == 0)
        {
        RingBuff_Clear(&ringBuff);
        SendStr_Len(NTP_GetData,48);   
        Delay_1ms(2000);
        }
     if(Revflag)
        {
                    i++;
              NTP_num = ((u32)RevData[40]<<24)|((u32)RevData[41]<<16)|((u32)RevData[42]<<8)|((u32)RevData[43]);
                co_printf("NTP_num is %ld\n", (u32)NTP_num);
                    LOG_Len(RevData,48);
                    RingBuff_Clear(&ringBuff);
                    Revflag=0;
                if((NTP_num >3597523200)||(i>10))
                    {
                     break;
                    }
        }        
   
    }   
    OLED_Clear();
    Delay_1ms(100);
    while(1)
    {

        
        covUnixTimeStp2Beijing(NTP_num,&Data);
        sprintf((char *)LCD_TimeChar,"%d-%02d-%02d %02d:%02d:%02d",(u16)Data.ui8Year,(u16)Data.ui8Month,
        (u16)Data.ui8DayOfMonth,(u16)Data.ui8Hour,(u16)Data.ui8Minute,(u16)Data.ui8Second);
        sprintf((char *)LCD_DataChar,"%d-%02d-%02d",(u16)Data.ui8Year,(u16)Data.ui8Month,(u16)Data.ui8DayOfMonth);
        OLED_ShowString(16,0,LCD_DataChar,16);
        sprintf((char *)LCD_SJChar,"%02d:%02d:%02d",(u16)Data.ui8Hour,(u16)Data.ui8Minute,(u16)Data.ui8Second);
        OLED_ShowString(20,3,LCD_SJChar,16);
        Delay_1ms(500);
        if(Revflag)
        {
                    LOG_Len(RevData,48);
                    RingBuff_Clear(&ringBuff);
                    Revflag=0;
        }        
            
    }
}

//NTP时间数值转换
void covUnixTimeStp2Beijing(uint32_t unixTime, rtc_time_t *tempBeijing)
{
    uint32_t totleDaynum=0, totleSecNum=0;
    uint16_t remainDayofYear, tempDay=0;
    uint8_t *pr, tempYear=0;
   
        co_printf("unixtime is %ld",unixTime);
    totleDaynum = (uint32_t)unixTime/(((uint32_t)24*60*60)); //总天数(注意加括号)
    totleSecNum = (uint32_t)unixTime%((uint32_t)24*60*60); //当天剩余的秒速
    memset(tempBeijing, 0x00, sizeof(rtc_time_t));
   // 1.计算哪一年
        tempBeijing->ui8Year = 1900 + (totleDaynum/((uint32_t)FOURYEARDAY))*4;  //从1900开始
    remainDayofYear = totleDaynum%((uint32_t)FOURYEARDAY)+1;
        //Delay_1ms(1000);
    while((u16)remainDayofYear >= dayPerYear[tempYear])
        {
        remainDayofYear -= dayPerYear[tempYear];
        tempBeijing->ui8Year++;
        tempYear++;
    }
    //2.计算哪一月的哪一天
    pr = isLeapYear(tempBeijing->ui8Year)?Leap_month_day:month_day;
    while(remainDayofYear > *(pr+tempBeijing->ui8Month))
    {
                remainDayofYear -= *(pr+tempBeijing->ui8Month);
        tempBeijing->ui8Month++;
    }
    tempBeijing->ui8Month++; //month
    tempBeijing->ui8DayOfMonth = remainDayofYear; //day
        co_printf("Year is %d\n",tempBeijing->ui8Year);
        co_printf("Month is %d\n",(u16)tempBeijing->ui8Month);
        co_printf("Data is %d\n",(u16)tempBeijing->ui8DayOfMonth);
    //3.计算当天时间
    tempBeijing->ui8Hour = ((u16)totleSecNum)/3600;
    tempBeijing->ui8Minute = ((u16)totleSecNum%3600)/60; //error:变量搞错
    tempBeijing->ui8Second = ((u16)totleSecNum%3600)%60;
        
    //4.时区调整
    tempBeijing->ui8Hour +=TIMEZONE;
    if(tempBeijing->ui8Hour>23){
        tempBeijing->ui8Hour -= 24;
        tempBeijing->ui8DayOfMonth++;
    }
      co_printf("Hour is %d\n",(u16)tempBeijing->ui8Hour);
        co_printf("Minute is %d\n",(u16)tempBeijing->ui8Minute);
        co_printf("Seconed is %d\n",(u16)tempBeijing->ui8Second );
}



五、OLED12864控制
OLED12864采用模拟I2C方式进行控制。因为SDK-HC89F0541微控制器芯片拥有强大的引脚映射功能,把哪两个引脚配置为SDA和SCL引脚都行。
该OLED12864程序从其他例程中移植过来的,在网上也有很多例程可以参考。
六、演示效果
开机过程,因为开机后,需要ESP8266联网获取时间数据,ESP8266初始化联网需要一定的时间所以开机后时间比较长。

时钟获取成功,并且显示成功。



@21小跑堂



使用特权

评论回复
沙发
zeshoufx| | 2020-9-14 08:38 | 只看该作者
谢谢分享【 万年历+网络时钟同步】

使用特权

评论回复
板凳
grhr| | 2020-9-16 09:25 | 只看该作者
写的很好,逻辑很清晰,手动点赞

使用特权

评论回复
地板
帅气的菜鸟| | 2020-9-16 09:34 | 只看该作者
很清晰,参考价值很大,不错

使用特权

评论回复
5
cdwujinshan| | 2020-9-16 21:07 | 只看该作者

使用特权

评论回复
6
数据采集存储| | 2020-9-17 12:34 | 只看该作者
感谢楼主分享,不错的演示代码,我也进行搭建一下,看看如何。

使用特权

评论回复
7
一路向北lm| | 2020-9-18 15:22 | 只看该作者
做的不错

使用特权

评论回复
8
八层楼| | 2020-10-11 17:28 | 只看该作者
介绍的非常详细

使用特权

评论回复
9
观海| | 2020-10-11 17:29 | 只看该作者
效果很不错啊

使用特权

评论回复
10
guanjiaer| | 2020-10-11 17:30 | 只看该作者
调试过程顺利吗

使用特权

评论回复
11
heimaojingzhang| | 2020-10-11 17:32 | 只看该作者
怎么样 好上手吗

使用特权

评论回复
12
keaibukelian| | 2020-10-11 17:34 | 只看该作者
这是串口屏吗

使用特权

评论回复
13
labasi| | 2020-10-11 17:35 | 只看该作者
抗干扰能力如何

使用特权

评论回复
14
电子学长| | 2020-10-22 10:31 | 只看该作者
介绍的很详细

使用特权

评论回复
15
isageko| | 2020-10-23 17:29 | 只看该作者
牛啊楼主 这也太详细了 借你代码调试一下谢谢

使用特权

评论回复
16
Liyj336|  楼主 | 2020-10-27 11:54 | 只看该作者

不是串口屏,OLED12864 需要配合取模软件使用

使用特权

评论回复
17
Liyj336|  楼主 | 2020-10-27 11:56 | 只看该作者

开发跟普通的51单片机类似,官方提供各种外设例程,很容易上手

使用特权

评论回复
18
Liyj336|  楼主 | 2020-10-27 12:00 | 只看该作者

还好,开机有时候网络会连不上,就是网络连接那一块不是很完善,没加网络断开重连

使用特权

评论回复
19
Liyj336|  楼主 | 2020-10-27 12:01 | 只看该作者
labasi 发表于 2020-10-11 17:35
抗干扰能力如何

这个没有仪器具体测试过,但是官方说这款芯片抗干扰能力挺好的

使用特权

评论回复
20
萌沐兮兮| | 2020-10-27 16:23 | 只看该作者
很详细了,感谢楼主分享

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

8

主题

39

帖子

0

粉丝