無意間在網(wǎng)上看到開源的使用墨水屏打造的桌面時鐘,當(dāng)個桌面小擺件可謂是十分優(yōu)雅,于是就萌生出了自己DIY一個的想法。這個墨水屏?xí)r鐘具有以下特點(diǎn)
- 時間日期的顯示和自動校準(zhǔn)
- 自動獲取實(shí)時天氣
- 半夜自動進(jìn)入休眠
- 支持微信智能配網(wǎng)
目前已經(jīng)實(shí)現(xiàn)軟件功能,但是硬件上沒有畫板做成一體化的,只是開發(fā)板和模塊之間使用杜邦線連接的試驗(yàn)版本。
硬件設(shè)計(jì)
硬件由STM32主控、ESP8266模塊、墨水屏驅(qū)動電路和墨水屏主體組成。STM32通過串口給ESP8266發(fā)送AT指令控制其連接WIFI和獲取信息等操作;通過SPI控制墨水屏。
為了節(jié)約成本墨水屏使用的是電子價(jià)簽上拆下來的2.13寸漢朔墨水屏,驅(qū)動電路可以參照微雪電子官方的提供的驅(qū)動電路,單片機(jī)通過SPI與其通信即可。
墨水屏驅(qū)動
驅(qū)動程序可以參考微雪電子官網(wǎng)的,只需把STM32版本的demo下載下來,找到RTS,CS,DC,BUSY,SCL,SDA,USART宏定義,并改成自己實(shí)際接的GPIO,最后將main函數(shù)里的EPD_2in13_test()注釋打開,編譯燒錄就能看到墨水屏顯示圖片了。
能刷新墨水屏就說明硬件沒有問題了,接下來就要精簡出項(xiàng)目需要的墨水屏驅(qū)動代碼即可。我用到了gpio,spi,DEV_Config,存儲圖片數(shù)據(jù)的imageData,存儲字庫數(shù)據(jù)的Font,負(fù)責(zé)控制的EPD_2in13,畫圖接口的GUI_Paint。
獲取網(wǎng)絡(luò)時間
墨水屏?xí)r鐘的時鐘來源是STM32內(nèi)部的RTC時鐘,因?yàn)榫д竦牟町悾瑢?dǎo)致RTC時鐘走時并不十分準(zhǔn)確,所以使用網(wǎng)絡(luò)校準(zhǔn)每隔一個小時獲取一次網(wǎng)絡(luò)時間寫入RTC寄存器中。如何配置RTC實(shí)時時鐘和ESP8266連接WIFI可以參考我的另外兩篇博客
STM32F1系列HAL庫開發(fā)——RTC實(shí)時時鐘
STM32使用ESP8266模塊AT指令連接心知天氣API獲取天氣信息
獲取網(wǎng)絡(luò)時間可以使用ESP8266的AT+CIPSNTPCFG設(shè)置時域和 SNTP 服務(wù)器和AT+CIPSNTPTIME—查詢 SNTP 時間。
//設(shè)置SNTP服務(wù)器時域,參數(shù)1:使能,參數(shù)2:設(shè)置時域
AT+CIPSNTPCFG=1,8\r\n
//獲取SNTP服務(wù)器時間
AT+CIPSNTPTIME?\r\n
//獲取到的時間格式如下
+CIPSNTPTIME:Thu Aug 04 14:48:05 2016
//解析獲取到的數(shù)據(jù)
char result[16];
char *p = strstr((const char*)USART2_RX_BUF,"+CIPSNTPTIME:");
p += 17; //跳到月份的字符串
//設(shè)置月份
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++; //跳過空格
//設(shè)置日
for(i=0; *p != ' ' && *p != '\t'; i++)
{
result[i] = *p++;
}
RTC_DataStruct.Date = (result[0]-'0')*16 + (result[1]-'0');
p++; //跳過空格
//設(shè)置小時
for(i=0; *p != ':'; i++)
{
result[i] = *p++;
}
RTC_TimeStruct.Hours = (result[0]-'0')*10 + (result[1]-'0');
p++; //跳過:
//設(shè)置分鐘
for(i=0; *p != ':'; i++)
{
result[i] = *p++;
}
RTC_TimeStruct.Minutes = (result[0]-'0')*10 + (result[1]-'0');
p++; //跳過:
//設(shè)置秒
for(i=0; *p != ' ' && *p != '\t'; i++)
{
result[i] = *p++;
}
RTC_TimeStruct.Seconds = (result[0]-'0')*10 + (result[1]-'0');
p++; //跳過空格
//設(shè)置年份
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); //設(shè)置時間
HAL_RTC_SetDate(&hrtc,&RTC_DataStruct,RTC_FORMAT_BCD); //設(shè)置日期
上電后獲取完了網(wǎng)絡(luò)時間并設(shè)置到RTC時鐘后就設(shè)置RTC的鬧鐘時間為當(dāng)前時間+1分鐘,讓STM32進(jìn)入停止模式來降低功耗,設(shè)置的RTC鬧鐘中斷就會讓STM32從低功耗中喚醒,在鬧鐘中斷里我們可以執(zhí)行墨水屏刷新時間的操作后再重新設(shè)置鬧鐘時間和進(jìn)入低功耗。這樣就可以實(shí)現(xiàn)每隔一分鐘墨水屏刷新時間。當(dāng)走過一個小時后就再獲取一次網(wǎng)絡(luò)時間和天氣信息。
//設(shè)置下一次鬧鐘時間
if(RTC_TimeStruct.Minutes == 59)
{
if(RTC_TimeStruct.Hours == 23)
RTC_SetAlarm(0,0,0);
else
RTC_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; //清除鬧鐘標(biāo)志位
RTC_Alarm(); //鬧鐘事件處理
if(Updata_Flag == EPD_PART) //局部刷新
{
Updata_Flag = 0; //清除刷新標(biāo)志位
GUI_Display_Part();
}
else if(Updata_Flag == EPD_FULL) //全局刷新
{
Updata_Flag = 0; //清除刷新標(biāo)志位
GUI_Display_All();
}
else if(Updata_Flag == EPD_SLEEP) //休眠頁面
{
Updata_Flag = 0;
GUI_Display_Sleep();
}
printf("%d-%02d-%02d %d\r\n",RTC_DataStruct.Year+2000,RTC_DataStruct.Month,RTC_DataStruct.Date,RTC_DataStruct.WeekDay);
printf("%02d:%02d:%02d\r\n",RTC_TimeStruct.Hours,RTC_TimeStruct.Minutes,RTC_TimeStruct.Seconds);
//設(shè)置下一次鬧鐘時間
if(RTC_TimeStruct.Hours>=1 && RTC_TimeStruct.Hours<=6)
RTC_SetAlarm(6,59,55);
else
RTC_SetAlarm(RTC_TimeStruct.Hours, RTC_TimeStruct.Minutes, RTC_TimeStruct.Seconds+55);
Sys_Enter_Stop(); //系統(tǒng)進(jìn)入停止模式
}
}
//RTC鬧鐘事件處理
void RTC_Alarm(void)
{
printf("WK_UP\r\n");
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; //全局刷新標(biāo)志位
Get_Net_Time(); //每小時獲取網(wǎng)絡(luò)時間
Get_Weather(); //每小時更新天氣信息
}
}
else
{
Updata_Flag = EPD_PART; //局部刷新標(biāo)志位
}
}
//系統(tǒng)進(jìn)入停止模式
void Sys_Enter_Stop(void)
{
printf("go to sleep\r\n");
__HAL_RCC_PWR_CLK_ENABLE(); //使能PWR時鐘
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU); //清除喚醒標(biāo)志
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_STOPENTRY_WFI); //進(jìn)入待機(jī)模式
}
//RTC鬧鐘中斷服務(wù)函數(shù)
void RTC_Alarm_IRQHandler(void)
{
HAL_RTC_AlarmIRQHandler(&hrtc);
}
//RTC鬧鐘喚醒中斷回調(diào)函數(shù)
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *RTC_Handle)
{
SystemClock_Config(); //喚醒后要初始化時鐘配置
Alarm_Flag = 1; //標(biāo)志位置一
__HAL_RTC_ALARM_EXTI_CLEAR_FLAG();
}
獲取實(shí)時天氣
利用ESP8266TCP連接來天氣網(wǎng)站API獲取天氣信息,怎么連接天氣API還是參考上面提到的另一篇博客。這里還是介紹怎么解析數(shù)據(jù),并把解析到的天氣代碼,溫度濕度放到一個結(jié)構(gòu)體中管理,方便后續(xù)讀出數(shù)據(jù)進(jìn)行顯示。
//存儲天氣信息的結(jié)構(gòu)體
typedef struct{
uint8_t icon;
char Tempera[4];
char Humidity[4];
} PAIN_WEATHER;
//建立TCP連接,并開啟透傳,進(jìn)行HTTP請求
uint8_t TCP_Connect(char *IP,char *URL)
{
uint8_t res = 0;
USART2_RX_STA = 0;
u2_printf("AT+CIPSTART=\"TCP\",\"%s\",80\r\n",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 %s\r\n",URL);
HAL_Delay(200);
strcpy(Rcv_Str,(const char*)USART2_RX_BUF);
}
return res;
}
//關(guān)閉透傳并斷開TCP連接
void TCP_Disconnect(void)
{
//退出發(fā)送模式
while((USART2->SR&0X40)==0); //等待發(fā)送空
USART2->DR='+';
HAL_Delay(15); //大于串口組幀時間(10ms)
while((USART2->SR&0X40)==0); //等待發(fā)送空
USART2->DR='+';
HAL_Delay(15); //大于串口組幀時間(10ms)
while((USART2->SR&0X40)==0); //等待發(fā)送空
USART2->DR='+';
HAL_Delay(1000); //等待1s
//while(!Send_Command("AT","OK",20));//退出透傳判斷.
//關(guān)閉透傳模式
Send_Command("AT+CIPMODE=0","OK",20);
//斷開TCP連接
Send_Command("AT+CIPCLOSE","OK",20);
}
//解析GET請求返回的數(shù)據(jù)
void Weather_DataParsing(char *reqRes, char *keywords, char *keyval)
{
char *p1 = NULL;
char *p2 = NULL;
if(strstr(reqRes,"Sucess") != NULL)
{
p1 = strstr(reqRes, keywords); //查找關(guān)鍵詞
if(p1)
{
p1 += strlen(keywords) + 3;
p2 = strstr(p1, "\""); //查找末端的 "
strncpy(keyval, p1, p2 - p1); //拷貝數(shù)據(jù)
}
}
}
void Get_Weather(void)
{
char result[16] = "NULL";
//連接天氣API獲取天氣信息
if(!TCP_Connect("api.yytianqi.com",
"http://api.yytianqi.com/observe?city=CH281101&key=72r8t4knnk32g8s9\r\n"))
{
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] = '\0';
}
else
{
Paint_weather.Tempera[0] = result[0];
Paint_weather.Tempera[1] = result[1];
Paint_weather.Tempera[2] = 'C';
}
//獲取濕度
Weather_DataParsing(Rcv_Str,"sd",result);
Paint_weather.Humidity[0] = result[0];
Paint_weather.Humidity[1] = result[1];
Paint_weather.Humidity[2] = '%';
//獲取天氣代碼
Weather_DataParsing(Rcv_Str,"numtq",result);
Paint_weather.icon = (result[0]-'0')*10 + (result[1]-'0');
printf("Rcv:%s\r\n",Rcv_Str);
printf("%sC\t%s\r\n",Paint_weather.Tempera,Paint_weather.Humidity);
HAL_Delay(1000);
TCP_Disconnect(); //斷開TCP連接
}
智能配網(wǎng)
ESP8266的智能配網(wǎng)指由外部設(shè)備(如手機(jī))向 外部廣播含有 SSID 和密碼(PSW)的WiFi信息的報(bào)文,ESP8266獲取到該報(bào)文就可以連接到WiFi了。使用AT+CWSTARTSMART開啟 SmartConfig,使用AT+CWSTOPSMART停止 SmartConfig 。需要注意的是,在開啟智能配網(wǎng)時不要執(zhí)行其他的AT指令,無論配置成功與否都要及時停止智能配網(wǎng)。而且在實(shí)際使用中發(fā)現(xiàn),如果上一次未配置成功即使重新上電也會影響下一次智能配網(wǎng)的開啟,所以每次使用完都要調(diào)用停止智能配網(wǎng)。
具體操作流程是,系統(tǒng)上電后檢測是否連接WiFi,未連接則顯示智能配網(wǎng)的二維碼,同時開啟智能配網(wǎng),監(jiān)聽報(bào)文。當(dāng)用戶輸入WiFi的信息后,并且接收到報(bào)文就會連接WiFi,連接成功就會返回smartconfig connected wifi等一系列數(shù)據(jù),就是通過判斷是否接收到smartconfig connected wifi的應(yīng)答來判斷是否成功連接。
//檢查WIFI是否連接
uint8_t Check_WIFI(void)
{
if(Send_Command("AT+CWJAP_DEF?","No AP",20))
{
printf("WIFI未連接\r\n");
return 0;
}
else
{
printf("WIFI已連接\r\n");
return 1;
}
}
uint8_t WIFI_SmartConfig(uint32_t waittime)
{
uint8_t res = 0;
//開啟station模式
if(!Send_Command("AT+CWMODE?","1",20))
{
Send_Command("AT+CWMODE=1","OK",20);
}
//退出smartconfig,防止上一次未退出
Send_Command("AT+CWSTOPSMART", "OK", 50);
//開啟smartconfig
if(Send_Command("AT+CWSTARTSMART", "OK", 50))
{
//printf("%s\r\n",USART2_RX_BUF);
while(waittime--)
{
HAL_Delay(10);
if(USART2_RX_STA & 0x8000)
{
printf("%s\r\n",USART2_RX_BUF);
if(Check_Command("smartconfig connected wifi"))
{
res = 1;
HAL_Delay(500);
break;
}
USART2_RX_STA=0;
}
if(waittime == 0)
printf("Timeout\r\n");
}
if(Send_Command("AT+CWSTOPSMART", "OK", 50))
{
printf("Stop smartconfig\r\n");
}
else
{
printf("smartconfig stop error\r\n");
}
}
else
{
printf("smartconfig open error\r\n");
}
return res;
}
墨水屏的顯示
墨水屏的顯示關(guān)鍵在于調(diào)用GUI_Paint里的各種畫圖接口來顯示圖片,數(shù)字,字母,中文等。這里用墨水屏的局部刷新為例子。
首先需要事先申請開辟一塊空間用于存儲畫點(diǎn)的數(shù)據(jù),調(diào)用Paint_NewImage
函數(shù)創(chuàng)建一幅新的圖像。文章來源:http://www.zghlxwxcb.cn/news/detail-492013.html
uint8_t *BlackImage;
uint16_t Imagesize = ((EPD_WIDTH % 8 == 0)? (EPD_WIDTH / 8 ): (EPD_WIDTH / 8 + 1)) * EPD_HEIGHT;//申請空間
if((BlackImage = (uint8_t *)malloc(Imagesize)) == NULL)
return -1;
Paint_NewImage(BlackImage, EPD_WIDTH, EPD_HEIGHT, 270, WHITE); //創(chuàng)建新的圖像
然后調(diào)用DEV_Module_Init
初始化模塊,EPD_Init
確定刷新墨水屏的模式是局部刷新還是全局刷新,Paint_SelectImage
選擇剛剛申請的內(nèi)存空間為刷新數(shù)據(jù)。然后就可以調(diào)用具體的畫圖函數(shù)去顯示自己想要顯示的內(nèi)容即可,最后調(diào)用EPD_Display
顯示圖片,調(diào)用EPD_Sleep
和DEV_Module_Exit
讓墨水屏進(jìn)入休眠即可。文章來源地址http://www.zghlxwxcb.cn/news/detail-492013.html
//屏幕部分刷新
void GUI_Display_Part(void)
{
DEV_Module_Init();
EPD_Init(EPD_PART);
Paint_SelectImage(BlackImage);
RTC_GetTime(); //獲取RTC時間
//刷新時間
Paint_ClearWindows(5, 50, 5 + FontNum.Width * 5-5, 50 + FontNum.Height, WHITE);
Paint_DrawTime(5, 50, &Paint_time, &FontNum, WHITE, BLACK);
//刷新WIFI連接狀態(tài)
Paint_ClearWindows(90,30,90 + 24,30 + 24,WHITE);
if(Check_WIFI())
Paint_DrawBitMap_Paste(gImage_WIFI_24,90,30,24,24,1);
else
Paint_DrawBitMap_Paste(gImage_NoWIFI_24,90,30,24,24,1);
EPD_Display(BlackImage);
EPD_Sleep();
DEV_Module_Exit();
}
到了這里,關(guān)于物聯(lián)網(wǎng)小項(xiàng)目——墨水屏?xí)r鐘(STM32+ESP8266實(shí)現(xiàn))的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!