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