搜索

[应用方案] 【芯圣SDK-HC89F0541测评】 万年历+网络时钟同步

[复制链接]
2307|6
 楼主 | 2020-9-13 21:59 | 显示全部楼层 |阅读模式
本帖最后由 Liyj336 于 2020-9-13 22:05 编辑

首先很感谢圣芯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字节的数据是我们所需要的时间数据。
微信图片_20200913211313.jpg

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

  18.         RingBuff_Init(&ringBuff, RevBuff, MAX_LEN);
  19. }

复制代码

2、UART1中断处理
  1. void UART1_Rpt(void) interrupt UART1_VECTOR
  2. {
  3.     u8 ch;
  4.     inum = 0;
  5.     if(SCON & 0x01)                        //判断接收中断标志位
  6.     {
  7.         ch = SBUF;
  8.         if((Rev_Len<MAX_LEN)&&(Revflag!=1))
  9.         {
  10.         RingBuff_PutLen(&ringBuff, &ch, 1);  //数据压入循环队列
  11.         Rev_Len ++;
  12.         Rev_Status = 1;
  13.         }
  14.         SCON &=~ 0x01;                    //清除接收中断标志位
  15.     }                                   
  16. }
复制代码



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

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

  14. void Tim2_Init(void)
  15. {
  16. /************************************系统初始化****************************************/
  17.         CLKSWR = 0x51;                                                //选择内部高频RC为系统时钟,内部高频RC 2分频,Fosc=16MHz
  18.         CLKDIV = 0x01;                                                //Fosc 1分频得到Fcpu,Fcpu=16MHz
  19.         T3CON |=(0x34);                                                //使能T1
  20.         TH3 = 0x0B;
  21.         TL3 = 0xDC;                //10us
  22.         IE1 |= 0x04;                                                        //打开T1中断
  23.         
  24.         EA = 1;                                                                //打开总中断
  25. }
  26. void TIMER1_Rpt(void) interrupt TIMER1_VECTOR
  27. {
  28.         if(DelayCount != 0 )
  29.                 DelayCount--;
  30.         else
  31.         {
  32.                 TCON &=(~0x40);
  33.         }
  34. }
复制代码
3、Tim2初始化与中断
  1. void Tim2_Init(void)
  2. {
  3. /************************************系统初始化****************************************/
  4.     CLKSWR = 0x51;                        //选择内部高频RC为系统时钟,内部高频RC 2分频,Fosc=16MHz
  5.     CLKDIV = 0x01;                        //Fosc 1分频得到Fcpu,Fcpu=16MHz
  6.     T3CON |=(0x34);                        //使能T1
  7.     TH3 = 0x0B;
  8.     TL3 = 0xDC;        //10us
  9.     IE1 |= 0x04;                            //打开T1中断   
  10.     EA = 1;                                //打开总中断
  11. }
  12. void TIMER3_Rpt(void) interrupt T3_VECTOR
  13. {
  14.         T3CON &=~ 0x80;                                                //清除中断标志位
  15.   NTP_num++;
  16. }
复制代码
四、ESP8266控制
ESP8266使用UART串口发送AT命令进行控制。其中有些命令没采用检查返回数据机制。

  1. //配置ESP8266位sta模式,并连接到路由器
  2. uint8_t app_esp8266_wifista_config(char *ssid, char *pwd)
  3. {
  4.     uint8_t p[200];
  5.     uint8_t TryConnectTime = 0;
  6.    
  7.       co_printf("准备连接\r\n");
  8.     while (app_esp8266_send_cmd("AT", "OK", 20)) //检查WIFI模块是否在线
  9.         {
  10.                  static u8 i = 0;
  11.                  if(i > 5)
  12.                  {
  13.                     i = 0;
  14.                     break;
  15.                  }
  16.                  i++;
  17.                 Delay_1ms(1000);
  18.         co_printf("未检测到模块\r\n");
  19.     }
  20.         Delay_1ms(1000);
  21.     app_esp8266_send_cmd("AT+CWMODE=3", "OK", 50);     //设置WIFI STA模式
  22.       Delay_1ms(1000);
  23.     app_esp8266_send_cmd("AT+CIPMUX=0", "OK", 20);
  24.         Delay_1ms(1000);
  25.     sprintf((char *)p, "AT+CWJAP=\"%s\",\"%s\"", ssid, pwd);
  26.       
  27.         TimNum = 300;
  28.         app_esp8266_send_cmd(p, "WIFI GOT IP", 50);
  29.         while(1)
  30.         {
  31.             u16 waittime;
  32.             waittime = 1200;
  33.            while (--waittime)  //等待倒计时
  34.         {
  35.            Delay_1ms(10);
  36.             if (Revflag) //接收到期待的应答结果
  37.             {
  38.                 if (app_esp8266_check_cmd("OK"))
  39.                 {
  40.                     co_printf("发送:%s 回应:%s\r\n", "Wifi cnnect", "WIFI GOT IP");
  41.                     break;//得到有效数据
  42.                 }
  43.                Revflag = 0;
  44.             }

  45.         }
  46.                 break;
  47.         }
  48.         TimNum = 10;
  49.     co_printf("WiFi连接成功\r\n");
  50.         Delay_1ms(3000);
  51.     return 0;
  52. }

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

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

  59.                 Delay_1ms(1000);
  60.                 SendStr("AT+CIPSTART=\"UDP\",\"52.231.114.183\",123\r\n");
  61.     Delay_1ms(4000);
  62.                 RingBuff_Clear(&ringBuff);
  63.                 Revflag = 0;
  64.     app_esp8266_send_cmd("AT+CIPMODE=1", "OK", 100);    //传输模式为:透传
  65.                 Delay_1ms(1000);
  66.     app_esp8266_send_cmd("AT+CIPSEND", "OK", 100);       //开始透传
  67.                 Delay_1ms(1000);
  68.                 return 0;
  69. }
复制代码
NTP数据处理
从NTP服务器获取得的时间数据很Unix时间戳很类似,不过Unix时间戳从1970年开始,而NTP时间数值从1900年开始。NTP时间数值转换为北京时间,可以参考Unix时间戳转换为北京时间的例程。
  1. //主函数
  2. void main()
  3. {
  4.     UartInit();
  5.     Uart2_Init();
  6.   Tim0_Init();
  7.     Tim2_Init();
  8.   Delay_Init();
  9.     Delay_1ms(3000);
  10.     IIC_Init();
  11.     OLED_Init();
  12.     Delay_1ms(100);
  13.     OLED_Clear();
  14.     Delay_1ms(100);
  15.     NTP_num = 1599923854;
  16.     OLED_ShowCHinese(32,3,10);//正
  17.     OLED_ShowCHinese(48,3,11);//在
  18.     OLED_ShowCHinese(64,3,12);//启
  19.     OLED_ShowCHinese(80,3,13);//动
  20.     Delay_1ms(4000);
  21.     Wifi_Link = app_esp8266_wifista_config(WIFI_SSID, WIFI_PWD);
  22.     UDP_Link = build_tcp_connect(data_cip_type, data_api_ip, data_api_port);
  23.     if(Wifi_Link == 0)
  24.         co_printf("Wifi is successfully Connected !\n");
  25.     if(UDP_Link == 0)
  26.         co_printf("UDP is successfully Connected !\n");
  27.     while(1)
  28.     {
  29.         u8 i = 0;
  30.      if(UDP_Link == 0)
  31.         {
  32.         RingBuff_Clear(&ringBuff);
  33.         SendStr_Len(NTP_GetData,48);   
  34.         Delay_1ms(2000);
  35.         }
  36.      if(Revflag)
  37.         {
  38.                     i++;
  39.               NTP_num = ((u32)RevData[40]<<24)|((u32)RevData[41]<<16)|((u32)RevData[42]<<8)|((u32)RevData[43]);
  40.                 co_printf("NTP_num is %ld\n", (u32)NTP_num);
  41.                     LOG_Len(RevData,48);
  42.                     RingBuff_Clear(&ringBuff);
  43.                     Revflag=0;
  44.                 if((NTP_num >3597523200)||(i>10))
  45.                     {
  46.                      break;
  47.                     }
  48.         }        
  49.    
  50.     }   
  51.     OLED_Clear();
  52.     Delay_1ms(100);
  53.     while(1)
  54.     {

  55.         
  56.         covUnixTimeStp2Beijing(NTP_num,&Data);
  57.         sprintf((char *)LCD_TimeChar,"%d-%02d-%02d %02d:%02d:%02d",(u16)Data.ui8Year,(u16)Data.ui8Month,
  58.         (u16)Data.ui8DayOfMonth,(u16)Data.ui8Hour,(u16)Data.ui8Minute,(u16)Data.ui8Second);
  59.         sprintf((char *)LCD_DataChar,"%d-%02d-%02d",(u16)Data.ui8Year,(u16)Data.ui8Month,(u16)Data.ui8DayOfMonth);
  60.         OLED_ShowString(16,0,LCD_DataChar,16);
  61.         sprintf((char *)LCD_SJChar,"%02d:%02d:%02d",(u16)Data.ui8Hour,(u16)Data.ui8Minute,(u16)Data.ui8Second);
  62.         OLED_ShowString(20,3,LCD_SJChar,16);
  63.         Delay_1ms(500);
  64.         if(Revflag)
  65.         {
  66.                     LOG_Len(RevData,48);
  67.                     RingBuff_Clear(&ringBuff);
  68.                     Revflag=0;
  69.         }        
  70.             
  71.     }
  72. }

  73. //NTP时间数值转换
  74. void covUnixTimeStp2Beijing(uint32_t unixTime, rtc_time_t *tempBeijing)
  75. {
  76.     uint32_t totleDaynum=0, totleSecNum=0;
  77.     uint16_t remainDayofYear, tempDay=0;
  78.     uint8_t *pr, tempYear=0;
  79.    
  80.         co_printf("unixtime is %ld",unixTime);
  81.     totleDaynum = (uint32_t)unixTime/(((uint32_t)24*60*60)); //总天数(注意加括号)
  82.     totleSecNum = (uint32_t)unixTime%((uint32_t)24*60*60); //当天剩余的秒速
  83.     memset(tempBeijing, 0x00, sizeof(rtc_time_t));
  84.    // 1.计算哪一年
  85.         tempBeijing->ui8Year = 1900 + (totleDaynum/((uint32_t)FOURYEARDAY))*4;  //从1900开始
  86.     remainDayofYear = totleDaynum%((uint32_t)FOURYEARDAY)+1;
  87.         //Delay_1ms(1000);
  88.     while((u16)remainDayofYear >= dayPerYear[tempYear])
  89.         {
  90.         remainDayofYear -= dayPerYear[tempYear];
  91.         tempBeijing->ui8Year++;
  92.         tempYear++;
  93.     }
  94.     //2.计算哪一月的哪一天
  95.     pr = isLeapYear(tempBeijing->ui8Year)?Leap_month_day:month_day;
  96.     while(remainDayofYear > *(pr+tempBeijing->ui8Month))
  97.     {
  98.                 remainDayofYear -= *(pr+tempBeijing->ui8Month);
  99.         tempBeijing->ui8Month++;
  100.     }
  101.     tempBeijing->ui8Month++; //month
  102.     tempBeijing->ui8DayOfMonth = remainDayofYear; //day
  103.         co_printf("Year is %d\n",tempBeijing->ui8Year);
  104.         co_printf("Month is %d\n",(u16)tempBeijing->ui8Month);
  105.         co_printf("Data is %d\n",(u16)tempBeijing->ui8DayOfMonth);
  106.     //3.计算当天时间
  107.     tempBeijing->ui8Hour = ((u16)totleSecNum)/3600;
  108.     tempBeijing->ui8Minute = ((u16)totleSecNum%3600)/60; //error:变量搞错
  109.     tempBeijing->ui8Second = ((u16)totleSecNum%3600)%60;
  110.         
  111.     //4.时区调整
  112.     tempBeijing->ui8Hour +=TIMEZONE;
  113.     if(tempBeijing->ui8Hour>23){
  114.         tempBeijing->ui8Hour -= 24;
  115.         tempBeijing->ui8DayOfMonth++;
  116.     }
  117.       co_printf("Hour is %d\n",(u16)tempBeijing->ui8Hour);
  118.         co_printf("Minute is %d\n",(u16)tempBeijing->ui8Minute);
  119.         co_printf("Seconed is %d\n",(u16)tempBeijing->ui8Second );
  120. }
复制代码



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

1.jpg

@21小跑堂



使用特权

评论回复
| 2020-9-14 08:38 | 显示全部楼层
谢谢分享【 万年历+网络时钟同步】

使用特权

评论回复
| 2020-9-16 09:25 | 显示全部楼层
写的很好,逻辑很清晰,手动点赞

使用特权

评论回复
| 2020-9-16 09:34 | 显示全部楼层
很清晰,参考价值很大,不错

使用特权

评论回复
| 2020-9-16 21:07 | 显示全部楼层

使用特权

评论回复
| 2020-9-17 12:34 | 显示全部楼层
感谢楼主分享,不错的演示代码,我也进行搭建一下,看看如何。

使用特权

评论回复
| 2020-9-18 15:22 | 显示全部楼层
做的不错

使用特权

评论回复
扫描二维码,随时随地手机跟帖
您需要登录后才可以回帖 登录 | 注册

本版积分规则

我要发帖 投诉建议 创建版块 申请版主

快速回复

您需要登录后才可以回帖
登录 | 注册
高级模式

论坛热帖

在线客服 快速回复 返回顶部 返回列表