本帖最后由 芯圣电子官方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小跑堂
|
|