基于Esp32+TFT_LCD的网络天气时钟
2022-02-08,esp32学习笔记
说在前面
主要参考了WD的【ESP32+TFT+分光棱镜实现透明小电视】教程。 难免有瑕疵,如果有口误不正确的地方,请大家多多包涵,有什么错误或疑问都可以在评论区指出。
视频展示
【ESP32+TFT 天气时钟-哔哩哔哩】
硬件部分
材料
器件 | 尺寸 |
---|
ESP32开发板(乐鑫ESP-WROOM-32模组) | 54.9mm×27.9mm | 高清SPI TFT彩屏(8引脚) | 1.44寸 26.5mm×25.5mm |
连接方式
TFT_LCD彩屏 | ESP32开发板 |
---|
GND | GND | VCC | 3V3 | SCL | IO14 | SDA | IO15 | RES | IO33 | DC | IO27 | CS | IO5 | BL | IO22 |
软件部分
开发平台
VScode + platfrom IO
主程序
#include <SPI.h>
#include <TFT_eSPI.h>
#include <MyFont.h>
#include <pic.h>
#include <NTPClient.h>
#include <WiFi.h>
#include <WiFiUdp.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
TFT_eSPI tft = TFT_eSPI();
void showMyFont(int32_t x, int32_t y, const char c[3], uint32_t color) {
for (int k = 0; k <= 55; k++)
if (hanzi[k].Index[0] == c[0] && hanzi[k].Index[1] == c[1] && hanzi[k].Index[2] == c[2])
{
tft.drawBitmap(x, y, hanzi[k].hz_Id, hanzi[k].hz_width, 16, color);
}
}
void showMyFonts(int32_t x, int32_t y, const char str[], uint32_t color) {
int x0 = x;
for (int i = 0; i < strlen(str); i += 3) {
showMyFont(x0, y, str+i, color);
x0 += 17;
}
}
const char *ssid = "wifiname";
const char *password = "password";
const char *host = "api.seniverse.com";
String now_address="",now_temperature="",now_weather="",now_weather_code="";
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP,"ntp.aliyun.com");
void setup() {
tft.init();
tft.fillScreen(TFT_BLACK);
Serial.begin(115200);
WiFi.begin(ssid, password);
while ( WiFi.status() != WL_CONNECTED ) {
delay ( 500 );
Serial.print ( "." );
}
tft.println("");
tft.println("WiFi connected");
tft.print("IP address: \n");
tft.println(WiFi.localIP());
tft.println("Initializing...");
delay(2000);
tft.fillScreen(TFT_BLACK);
tft.drawLine(0,23, 128, 23, TFT_WHITE);
tft.drawLine(0,60, 128, 60, TFT_WHITE);
timeClient.begin();
timeClient.setTimeOffset(28800);
tft.setSwapBytes(true);
}
void Display_Time() {
timeClient.update();
unsigned long epochTime = timeClient.getEpochTime();
int currentSec = timeClient.getSeconds();
int currentMinute = timeClient.getMinutes();
int currentHour = timeClient.getHours();
int weekDay = timeClient.getDay();
struct tm *ptm = gmtime ((time_t *)&epochTime);
int monthDay = ptm->tm_mday;
int currentMonth = ptm->tm_mon+1;
int currentYear = ptm->tm_year+1900;
tft.fillRect(102,42,22,14,TFT_BLACK);
tft.setCursor(102, 42, 1);
tft.setTextColor(TFT_RED);
tft.setTextSize(2);
if (currentSec < 10){
tft.println(0);
tft.setCursor(114, 42, 1);
tft.setTextColor(TFT_RED);
tft.setTextSize(2);
tft.println(currentSec);
}
else{
tft.println(currentSec);
}
if (currentSec==0){
tft.fillRect(55,28,44,28,TFT_BLACK);
}
tft.setCursor(55, 28, 1);
tft.setTextColor(TFT_CYAN);
tft.setTextSize(4);
if (currentMinute < 10) {
tft.println(0);
tft.setCursor(79, 28, 1);
tft.setTextColor(TFT_CYAN);
tft.setTextSize(4);
tft.println(currentMinute);
}
else{
tft.println(currentMinute);
}
if (currentMinute==0 && currentSec==0){
tft.fillRect(1,28,44,28,TFT_BLACK);
}
tft.setCursor(1, 28, 1);
tft.setTextColor(TFT_CYAN);
tft.setTextSize(4);
if (currentHour < 10) {
tft.setCursor(25, 28, 1);
tft.setTextColor(TFT_CYAN);
tft.setTextSize(4);
tft.println(currentHour);
}
else{
tft.println(currentHour);
}
tft.setCursor(40, 28, 1);
tft.setTextColor(TFT_CYAN);
tft.setTextSize(4);
tft.println(":");
tft.setCursor(89, 5, 1);
tft.setTextColor(TFT_WHITE);
tft.setTextSize(2);
tft.println("/");
if (currentHour==0 && currentMinute==0 && currentSec==0){
tft.fillRect(102,5,22,14,TFT_BLACK);
tft.fillRect(5,5,32,16,TFT_BLACK);
}
tft.setCursor(102, 5, 1);
tft.setTextColor(TFT_YELLOW);
tft.setTextSize(2);
if (monthDay < 10) {
tft.setCursor(114, 5, 1);
tft.setTextColor(TFT_YELLOW);
tft.setTextSize(2);
tft.println(monthDay);
}
else {
tft.println(monthDay);
}
switch(weekDay){
case 0: showMyFonts(5, 5, "周日", TFT_GREENYELLOW);break;
case 1: showMyFonts(5, 5, "周一", TFT_GREENYELLOW);break;
case 2: showMyFonts(5, 5, "周二", TFT_GREENYELLOW);break;
case 3: showMyFonts(5, 5, "周三", TFT_GREENYELLOW);break;
case 4: showMyFonts(5, 5, "周四", TFT_GREENYELLOW);break;
case 5: showMyFonts(5, 5, "周五", TFT_GREENYELLOW);break;
case 6: showMyFonts(5, 5, "周六", TFT_GREENYELLOW);break;
default: break;
}
if (monthDay==1 && currentHour==0 && currentMinute==0 && currentSec==0){
tft.fillRect(65,5,22,14,TFT_BLACK);
}
tft.setCursor(65, 5, 1);
tft.setTextColor(TFT_YELLOW);
tft.setTextSize(2);
if (currentMonth <10) {
tft.setCursor(77, 5, 1);
tft.setTextColor(TFT_YELLOW);
tft.setTextSize(2);
tft.println(currentMonth);
}
else {
tft.println(currentMonth);
}
if (currentMonth==1 && monthDay==1 && currentHour==0 && currentMinute==0 && currentSec==0){
tft.fillRect(102,28,23,7,TFT_BLACK);
}
tft.setCursor(102, 28, 1);
tft.setTextColor(TFT_RED);
tft.setTextSize(1);
tft.println(currentYear);
}
void Display_Weather() {
WiFiClient client;
const int httpPort = 80;
if (!client.connect(host, httpPort))
{
Serial.println("connection failed");
return;
}
String url ="/v3/weather/now.json?key=your_kpi_password=xuchang&language=zh-Hans&unit=c";
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
for(int i = 0; i < 5; i++) {
Display_Time();
delay(1000);
}
String answer;
while(client.available())
{
String line = client.readStringUntil('\r');
answer += line;
}
client.stop();
String jsonAnswer;
int jsonIndex;
for (int i = 0; i < answer.length(); i++) {
if (answer[i] == '{') {
jsonIndex = i;
break;
}
}
jsonAnswer = answer.substring(jsonIndex);
StaticJsonDocument<512> doc;
DeserializationError error = deserializeJson(doc, jsonAnswer);
if (error) {
Serial.print("deserializeJson() failed: ");
Serial.println(error.c_str());
return;
}
JsonObject results_0 = doc["results"][0];
JsonObject results_0_location = results_0["location"];
const char* results_0_location_id = results_0_location["id"];
const char* results_0_location_name = results_0_location["name"];
const char* results_0_location_country = results_0_location["country"];
const char* results_0_location_path = results_0_location["path"];
const char* results_0_location_timezone = results_0_location["timezone"];
const char* results_0_location_timezone_offset = results_0_location["timezone_offset"];
JsonObject results_0_now = results_0["now"];
const char* results_0_now_text = results_0_now["text"];
const char* results_0_now_code = results_0_now["code"];
const char* results_0_now_temperature = results_0_now["temperature"];
const char* results_0_last_update = results_0["last_update"];
now_address = results_0_location_name;
now_weather = results_0_now_text;
now_weather_code = results_0_now_code;
now_temperature = results_0_now_temperature;
tft.fillRect(5,65,32,16,TFT_BLACK);
if(now_address=="许昌") {
showMyFonts(5,65,"许昌" , TFT_GOLD);
}
#define X 4
#define Y 110
#define pX 68
#define pY 68
tft.fillRect(X,Y,64,16,TFT_BLACK);
tft.fillRect(68,68,60,60,TFT_BLACK);
switch (std::atoi(now_weather_code.c_str())) {
case 0: showMyFonts(X,Y,"晴" , TFT_GREEN); tft.pushImage(pX,pY, 51,51,p0_5151);break;
case 1: showMyFonts(X,Y,"夜晚晴" , TFT_GREEN); tft.pushImage(pX,pY, 51,52,p1_5152);break;
case 2: showMyFonts(X,Y,"晴" , TFT_GREEN); tft.pushImage(pX,pY, 51,51,p0_5151);break;
case 3: showMyFonts(X,Y,"夜晚晴" , TFT_GREEN); tft.pushImage(pX,pY, 51,52,p1_5152);break;
case 4: showMyFonts(X,Y,"多云" , TFT_GREEN); tft.pushImage(pX,pY, 60,47,p4_6047);break;
case 5: showMyFonts(X,Y,"晴间多云" , TFT_GREEN); tft.pushImage(pX,pY, 60,44,p5_6044);break;
case 6: showMyFonts(X,Y,"晴间多云" , TFT_GREEN); tft.pushImage(pX,pY, 60,51,p6_6051);break;
case 7: showMyFonts(X,Y,"大部多云" , TFT_GREEN); tft.pushImage(pX,pY, 60,42,p7_6042);break;
case 8: showMyFonts(X,Y,"大部多云" , TFT_GREEN); tft.pushImage(pX,pY, 56,49,p8_5649);break;
case 9: showMyFonts(X,Y,"阴" , TFT_GREEN); tft.pushImage(pX,pY, 60,40,p9_6040);break;
case 10: showMyFonts(X,Y,"阵雨" , TFT_GREEN); tft.pushImage(pX,pY, 60,59,p10_6059);break;
case 11: showMyFonts(X,Y,"雷阵雨" , TFT_GREEN); tft.pushImage(pX,pY, 56,56,p11_5656);break;
case 12: {
tft.pushImage(pX,pY, 56,56,p12_5656);
showMyFonts(X,Y,"雷阵雨伴" , TFT_GREEN);delay(500);
tft.fillRect(X,Y,64,16,TFT_BLACK);
showMyFonts(X,Y,"阵雨伴有" , TFT_GREEN);delay(500);
tft.fillRect(X,Y,64,16,TFT_BLACK);
showMyFonts(X,Y,"雨伴有冰" , TFT_GREEN);delay(500);
tft.fillRect(X,Y,64,16,TFT_BLACK);
showMyFonts(X,Y,"伴有冰雹" , TFT_GREEN);delay(500);
tft.fillRect(X,Y,64,16,TFT_BLACK);
showMyFonts(X,Y,"阵雨冰雹" , TFT_GREEN);break;
}
case 13: showMyFonts(X,Y,"小雨" , TFT_GREEN); tft.pushImage(pX,pY, 56,54,p13_5654);break;
case 14: showMyFonts(X,Y,"中雨" , TFT_GREEN); tft.pushImage(pX,pY, 56,54,p14_5654);break;
case 15: showMyFonts(X,Y,"大雨" , TFT_GREEN); tft.pushImage(pX,pY, 56,54,p15_5654);break;
case 16: showMyFonts(X,Y,"暴雨" , TFT_GREEN); tft.pushImage(pX,pY, 56,54,p16_5654);break;
case 17: showMyFonts(X,Y,"大暴雨" , TFT_GREEN); tft.pushImage(pX,pY, 57,54,p17_5754);break;
case 18: showMyFonts(X,Y,"特大暴雨" , TFT_GREEN); tft.pushImage(pX,pY, 57,54,p18_5754);break;
case 19: showMyFonts(X,Y,"冻雨" , TFT_GREEN); tft.pushImage(pX,pY, 56,57,p19_5657);break;
case 20: showMyFonts(X,Y,"雨夹雪" , TFT_GREEN); tft.pushImage(pX,pY, 56,55,p20_5655);break;
case 21: showMyFonts(X,Y,"阵雪" , TFT_GREEN); tft.pushImage(pX,pY, 56,56,p21_5656);break;
case 22: showMyFonts(X,Y,"小雪" , TFT_GREEN); tft.pushImage(pX,pY, 56,53,p22_5653);break;
case 23: showMyFonts(X,Y,"中雪" , TFT_GREEN); tft.pushImage(pX,pY, 56,53,p23_5653);break;
case 24: showMyFonts(X,Y,"大雪" , TFT_GREEN); tft.pushImage(pX,pY, 56,53,p24_5653);break;
case 25: showMyFonts(X,Y,"暴雪" , TFT_GREEN); tft.pushImage(pX,pY, 56,56,p25_5656);break;
case 26: showMyFonts(X,Y,"浮尘" , TFT_GREEN); tft.pushImage(pX,pY, 53,45,p26_5345);break;
case 27: showMyFonts(X,Y,"扬沙" , TFT_GREEN); tft.pushImage(pX,pY, 53,45,p26_5345);break;
case 28: showMyFonts(X,Y,"沙尘暴" , TFT_GREEN); tft.pushImage(pX,pY, 58,34,p28_5834);break;
case 29: showMyFonts(X,Y,"强沙尘暴" , TFT_GREEN); tft.pushImage(pX,pY, 58,34,p28_5834);break;
case 30: showMyFonts(X,Y,"雾" , TFT_GREEN); tft.pushImage(pX,pY, 54,50,p30_5450);break;
case 31: showMyFonts(X,Y,"霾" , TFT_GREEN); tft.pushImage(pX,pY, 56,50,p31_5650);break;
case 32: showMyFonts(X,Y,"风" , TFT_GREEN); tft.pushImage(pX,pY, 56,44,p32_5644);break;
case 33: showMyFonts(X,Y,"大风" , TFT_GREEN); tft.pushImage(pX,pY, 56,44,p32_5644);break;
case 34: showMyFonts(X,Y,"飓风" , TFT_GREEN); tft.pushImage(pX,pY, 56,56,p34_5656);break;
case 35: showMyFonts(X,Y,"热带风暴" , TFT_GREEN); tft.pushImage(pX,pY, 56,56,p34_5656);break;
case 36: showMyFonts(X,Y,"龙卷风" , TFT_GREEN); tft.pushImage(pX,pY, 56,55,p36_5655);break;
case 37: showMyFonts(X,Y,"冷" , TFT_GREEN); tft.pushImage(pX,pY, 51,58,p37_5158);break;
case 38: showMyFonts(X,Y,"热" , TFT_GREEN); tft.pushImage(pX,pY, 51,51,p38_5151);break;
case 99: showMyFonts(X,Y,"未知" , TFT_GREEN); tft.pushImage(pX,pY, 53,23,p99_5323);break;
default: break;
}
tft.fillRect(5,87,63,16,TFT_BLACK);
tft.setCursor(5, 88, 1);
tft.setTextColor(TFT_SKYBLUE);
tft.setTextSize(2);
tft.println(now_temperature);
if ( (( std::atoi(now_temperature.c_str()) ) < 10) && (( std::atoi(now_temperature.c_str()) ) >= 0)) {
showMyFonts(24,87,"℃",TFT_SKYBLUE);
}
else{
showMyFonts(40,87,"℃",TFT_SKYBLUE);
}
}
void loop() {
Display_Weather();
for(int i = 0; i < 115; i++) {
Display_Time();
delay(1000);
}
}
汉字库
使用汉字取模软件将图片获得汉字数组,创建汉字库 代码如下:
#include <pgmspace.h>
const unsigned char hz_zhou PROGMEM[] =
{
0x00,0x00,0x3F,0xF8,0x21,0x08,0x21,0x08,0x2F,0xE8,0x21,0x08,0x21,0x08,0x3F,0xF8,
0x20,0x08,0x27,0xC8,0x24,0x48,0x24,0x48,0x27,0xC8,0x40,0x08,0x40,0x28,0x80,0x10
};
const unsigned char hz_Mon PROGMEM[] =
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFE,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
struct FNT_HZ
{
char Index[4];
const unsigned char* hz_Id;
unsigned char hz_width;
};
PROGMEM const FNT_HZ hanzi[] =
{
{"周", hz_zhou,16}, {"一", hz_Mon,16}, {"二", hz_Tue,16}, {"三", hz_Wed,16}, {"四", hz_Thu,16},
{"五", hz_Fri,16}, {"六", hz_Sat,16}, {"日", hz_Sunday,16}, {"晴", hz_sun,16}, {"阴", hz_cloudy,16},
{"雨", hz_rain,16}, {"雪", hz_snow,16}, {"多", hz_duo,16}, {"云", hz_yun,16}, {"许",hz_xu,16}, {"昌", hz_chang, 16},
{"东", hz_dong,16}, {"西", hz_xi,16}, {"南", hz_nan,16}, {"北",hz_bei,16}, {"风", hz_feng, 16},
{"间", hz_jian,16},{"特", hz_super,16},{"大", hz_Heavy,16},{"中", hz_Moderate,16},{"小", hz_Light,16},
{"暴", hz_Violent,16},{"部", hz_bu,16},{"阵", hz_zhen,16},{"雷", hz_Thunder,16},{"伴", hz_With},
{"有", hz_Have,16},{"冰", hz_Ice,16},{"雹", hz_Hail,16},{"冻", hz_Freeze,16},{"夹", hz_jia,16},
{"浮", hz_fu,16},{"尘", hz_Dust,16},{"扬", hz_Raise,16},{"沙", hz_Sand,16},{"强", hz_Strong,16},
{"雾", hz_Fog,16},{"霾", hz_Haze,16},{"飓", hz_Hurricane,16},{"热", hz_Hot,16},{"带", hz_Band},
{"白", hz_White,16},{"天", hz_Day,16},{"夜", hz_Night,16},{"晚", hz_Evening,16},{"℃", hz_duC,16}
};
图片库
这里使用的是【心知天气】的图标库,每一个天气现象也对应了一个图标,详见天气现象代码说明 使用图片取模软件将图片获得图片数组,创建图片库 代码如下:
#include <pgmspace.h>
#include <stdint.h>
const uint16_t p0_5151[0xA29] PROGMEM ={
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xFEE3, 0xF6C2, 0xF6A2, 0xF6A2, 0xF6A2, 0xF6A2, 0xF6A2, 0xF6A2, 0xF6A2, 0xF6A2, 0xF6A2, 0xF6A2, 0xF6A2, 0xF6C2,
...
...
...
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFEE0, 0xFE20, 0xFE20, 0xFE20, 0xFE20, 0xFE20, 0xFE20, 0xFE20,
0xFE20, 0xFE20, 0xFE20, 0xFE20, 0xFE20, 0xFE20, 0xFEE3, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
};
const uint16_t p1_5151[0xA29] PROGMEM ={
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xFEE3, 0xF6C2, 0xF6A2, 0xF6A2, 0xF6A2, 0xF6A2, 0xF6A2, 0xF6A2, 0xF6A2, 0xF6A2, 0xF6A2, 0xF6A2, 0xF6A2, 0xF6C2,
...
...
...
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFEE0, 0xFE20, 0xFE20, 0xFE20, 0xFE20, 0xFE20, 0xFE20, 0xFE20,
0xFE20, 0xFE20, 0xFE20, 0xFE20, 0xFE20, 0xFE20, 0xFEE3, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
};
相关资源
本文相关资源包含MyFont.h、pic.h、取模软件等
特别感谢
博客作者 WD
写在最后
本文仅为个人学习笔记,诸多纰漏,欢迎斧正!
|