无意间在网上看到开源的使用墨水屏打造的桌面时钟,当个桌面小摆件可谓是十分优雅,于是就萌生出了自己DIY一个的想法。这个墨水屏时钟具有以下特点
目前已经实现软件功能,但是硬件上没有画板做成一体化的,只是开发板和模块之间使用杜邦线连接的试验版本。
硬件由STM32主控、ESP8266模块、墨水屏驱动电路和墨水屏主体组成。STM32通过串口给ESP8266发送AT指令控制其连接WIFI和获取信息等操作;通过SPI控制墨水屏。
为了节约成本墨水屏使用的是电子价签上拆下来的2.13寸汉朔墨水屏,驱动电路可以参照微雪电子官方的提供的驱动电路,单片机通过SPI与其通信即可。
驱动程序可以参考微雪电子官网的,只需把STM32版本的demo下载下来,找到RTS,CS,DC,BUSY,SCL,SDA,USART宏定义,并改成自己实际接的GPIO,最后将main函数里的EPD_2in13_test()注释打开,编译烧录就能看到墨水屏显示图片了。
能刷新墨水屏就说明硬件没有问题了,接下来就要精简出项目需要的墨水屏驱动代码即可。我用到了gpio,spi,DEV_Config,存储图片数据的imageData,存储字库数据的Font,负责控制的EPD_2in13,画图接口的GUI_Paint。
墨水屏时钟的时钟来源是STM32内部的RTC时钟,因为晶振的差异,导致RTC时钟走时并不十分准确,所以使用网络校准每隔一个小时获取一次网络时间写入RTC寄存器中。如何配置RTC实时时钟和ESP8266连接WIFI可以参考我的另外两篇博客
STM32F1系列HAL库开发——RTC实时时钟
STM32使用ESP8266模块AT指令连接心知天气API获取天气信息
获取网络时间可以使用ESP8266的AT+CIPSNTPCFG设置时域和 SNTP 服务器和AT+CIPSNTPTIME—查询 SNTP 时间。
//设置SNTP服务器时域,参数1:使能,参数2:设置时域
AT+CIPSNTPCFG=1,8rn
//获取SNTP服务器时间
AT+CIPSNTPTIME?rn
//获取到的时间格式如下
+CIPSNTPTIME:Thu Aug 04 14:48:05 2016
//解析获取到的数据
char result[16];
char *p = strstr((const char*)USART2_RX_BUF,"+CIPSNTPTIME:");
p += 17; //跳到月份的字符串//设置月份for(i=0; (*p != ' ') && (*p != 't'); i++){result[i] = *p++;}if(strcmp(result,"Jan") == 0)RTC_DataStruct.Month = RTC_MONTH_JANUARY;else if(strcmp(result,"Feb") == 0)RTC_DataStruct.Month = RTC_MONTH_FEBRUARY;else if(strcmp(result,"Mar") == 0)RTC_DataStruct.Month = RTC_MONTH_MARCH;else if(strcmp(result,"Apr") == 0)RTC_DataStruct.Month = RTC_MONTH_APRIL;else if(strcmp(result,"May") == 0)RTC_DataStruct.Month = RTC_MONTH_MAY;else if(strcmp(result,"Jun") == 0)RTC_DataStruct.Month = RTC_MONTH_JUNE;else if(strcmp(result,"Jul") == 0)RTC_DataStruct.Month = RTC_MONTH_JULY;else if(strcmp(result,"Aug") == 0)RTC_DataStruct.Month = RTC_MONTH_AUGUST;else if(strcmp(result,"Sept") == 0)RTC_DataStruct.Month = RTC_MONTH_SEPTEMBER;else if(strcmp(result,"Oct") == 0)RTC_DataStruct.Month = RTC_MONTH_OCTOBER;else if(strcmp(result,"Nov") == 0)RTC_DataStruct.Month = RTC_MONTH_NOVEMBER;else if(strcmp(result,"Dec") == 0)RTC_DataStruct.Month = RTC_MONTH_DECEMBER;p++; //跳过空格//设置日for(i=0; *p != ' ' && *p != 't'; i++){result[i] = *p++;}RTC_DataStruct.Date = (result[0]-'0')*16 + (result[1]-'0');p++; //跳过空格//设置小时for(i=0; *p != ':'; i++){result[i] = *p++;}RTC_TimeStruct.Hours = (result[0]-'0')*10 + (result[1]-'0');p++; //跳过://设置分钟for(i=0; *p != ':'; i++){result[i] = *p++;}RTC_TimeStruct.Minutes = (result[0]-'0')*10 + (result[1]-'0');p++; //跳过://设置秒for(i=0; *p != ' ' && *p != 't'; i++){result[i] = *p++;}RTC_TimeStruct.Seconds = (result[0]-'0')*10 + (result[1]-'0'); p++; //跳过空格//设置年份for(i=0; i<4; i++){result[i] = *p++;}RTC_DataStruct.Year = (result[2]-'0')*16 + (result[3]-'0');RTC_DataStruct.WeekDay = RTC_WEEKDAY_TUESDAY;HAL_RTC_SetTime(&hrtc,&RTC_TimeStruct,RTC_FORMAT_BIN); //设置时间HAL_RTC_SetDate(&hrtc,&RTC_DataStruct,RTC_FORMAT_BCD); //设置日期
上电后获取完了网络时间并设置到RTC时钟后就设置RTC的闹钟时间为当前时间+1分钟,让STM32进入停止模式来降低功耗,设置的RTC闹钟中断就会让STM32从低功耗中唤醒,在闹钟中断里我们可以执行墨水屏刷新时间的操作后再重新设置闹钟时间和进入低功耗。这样就可以实现每隔一分钟墨水屏刷新时间。当走过一个小时后就再获取一次网络时间和天气信息。
//设置下一次闹钟时间if(RTC_TimeStruct.Minutes == 59){if(RTC_TimeStruct.Hours == 23)RTC_SetAlarm(0,0,0);elseRTC_SetAlarm(RTC_TimeStruct.Hours+1,0,0); }else{RTC_SetAlarm(RTC_TimeStruct.Hours,RTC_TimeStruct.Minutes+1,0);}while(1){if(Alarm_Flag == 1){Alarm_Flag = 0; //清除闹钟标志位RTC_Alarm(); //闹钟事件处理if(Updata_Flag == EPD_PART) //局部刷新{Updata_Flag = 0; //清除刷新标志位GUI_Display_Part();}else if(Updata_Flag == EPD_FULL) //全局刷新{Updata_Flag = 0; //清除刷新标志位GUI_Display_All();}else if(Updata_Flag == EPD_SLEEP) //休眠页面{Updata_Flag = 0;GUI_Display_Sleep();}printf("%d-%02d-%02d %drn",RTC_DataStruct.Year+2000,RTC_DataStruct.Month,RTC_DataStruct.Date,RTC_DataStruct.WeekDay);printf("%02d:%02d:%02drn",RTC_TimeStruct.Hours,RTC_TimeStruct.Minutes,RTC_TimeStruct.Seconds);//设置下一次闹钟时间if(RTC_TimeStruct.Hours>=1 && RTC_TimeStruct.Hours<=6)RTC_SetAlarm(6,59,55);elseRTC_SetAlarm(RTC_TimeStruct.Hours, RTC_TimeStruct.Minutes, RTC_TimeStruct.Seconds+55);Sys_Enter_Stop(); //系统进入停止模式}}//RTC闹钟事件处理
void RTC_Alarm(void)
{printf("WK_UPrn");do{HAL_RTC_GetTime(&hrtc,&RTC_TimeStruct,RTC_FORMAT_BIN); //获取RTC时间HAL_Delay(250);}while(Paint_time.Min == RTC_TimeStruct.Minutes); //等待分钟更新if(Paint_time.Hour != RTC_TimeStruct.Hours) //每小时全局刷新一次{//凌晨时间,墨水屏休眠if(RTC_TimeStruct.Hours>=1 && RTC_TimeStruct.Hours<=6){Updata_Flag = EPD_SLEEP;}else{Updata_Flag = EPD_FULL; //全局刷新标志位Get_Net_Time(); //每小时获取网络时间 Get_Weather(); //每小时更新天气信息}}else{Updata_Flag = EPD_PART; //局部刷新标志位}
}//系统进入停止模式
void Sys_Enter_Stop(void)
{printf("go to sleeprn");__HAL_RCC_PWR_CLK_ENABLE(); //使能PWR时钟__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU); //清除唤醒标志HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_STOPENTRY_WFI); //进入待机模式
}//RTC闹钟中断服务函数
void RTC_Alarm_IRQHandler(void)
{HAL_RTC_AlarmIRQHandler(&hrtc);
}//RTC闹钟唤醒中断回调函数
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *RTC_Handle)
{SystemClock_Config(); //唤醒后要初始化时钟配置Alarm_Flag = 1; //标志位置一__HAL_RTC_ALARM_EXTI_CLEAR_FLAG();
}
利用ESP8266TCP连接来天气网站API获取天气信息,怎么连接天气API还是参考上面提到的另一篇博客。这里还是介绍怎么解析数据,并把解析到的天气代码,温度湿度放到一个结构体中管理,方便后续读出数据进行显示。
//存储天气信息的结构体
typedef struct{uint8_t icon;char Tempera[4];char Humidity[4];
} PAIN_WEATHER;//建立TCP连接,并开启透传,进行HTTP请求
uint8_t TCP_Connect(char *IP,char *URL)
{ uint8_t res = 0;USART2_RX_STA = 0;u2_printf("AT+CIPSTART="TCP","%s",80rn",IP);HAL_Delay(500);if(Send_Command("AT+CIPSTATUS","TCP",20)) //检查TCP是否连接{res = 1;Send_Command("AT+CIPMODE=1","OK",20); //开启透传模式Send_Command("AT+CIPSEND",">",20); //开始传输HAL_Delay(200);USART2_RX_STA = 0;u2_printf("GET %srn",URL);HAL_Delay(200);strcpy(Rcv_Str,(const char*)USART2_RX_BUF);}return res;
}//关闭透传并断开TCP连接
void TCP_Disconnect(void)
{//退出发送模式while((USART2->SR&0X40)==0); //等待发送空USART2->DR='+'; HAL_Delay(15); //大于串口组帧时间(10ms)while((USART2->SR&0X40)==0); //等待发送空USART2->DR='+'; HAL_Delay(15); //大于串口组帧时间(10ms)while((USART2->SR&0X40)==0); //等待发送空USART2->DR='+'; HAL_Delay(1000); //等待1s//while(!Send_Command("AT","OK",20));//退出透传判断.//关闭透传模式Send_Command("AT+CIPMODE=0","OK",20);//断开TCP连接Send_Command("AT+CIPCLOSE","OK",20);
}//解析GET请求返回的数据
void Weather_DataParsing(char *reqRes, char *keywords, char *keyval)
{char *p1 = NULL;char *p2 = NULL;if(strstr(reqRes,"Sucess") != NULL){p1 = strstr(reqRes, keywords); //查找关键词if(p1){p1 += strlen(keywords) + 3;p2 = strstr(p1, """); //查找末端的 " strncpy(keyval, p1, p2 - p1); //拷贝数据}}
}void Get_Weather(void)
{char result[16] = "NULL";//连接天气API获取天气信息if(!TCP_Connect(ianqi","=CH281101&key=72r8t4knnk32g8s9rn")){Paint_weather.Tempera[0] = '?';Paint_weather.Tempera[1] = '?';Paint_weather.Tempera[2] = 'C';Paint_weather.Humidity[0] = '?';Paint_weather.Humidity[1] = '?';Paint_weather.Humidity[2] = '%';}//获取气温Weather_DataParsing(Rcv_Str,"qw",result);if(result[1] == 'U') //气温低于10度处理{Paint_weather.Tempera[0] = result[0];Paint_weather.Tempera[1] = 'C';Paint_weather.Tempera[2] = '