【開發(fā)記錄】esp32獲取天氣數(shù)據(jù)并在tft顯示屏上顯示
前言
開發(fā)原因:就是懶的翻手機(jī)看天氣()
基本思路:esp32聯(lián)網(wǎng)調(diào)用相關(guān)天氣api,得到結(jié)果后對數(shù)據(jù)處理后在tft顯示屏輸出顯示
一、硬件
1.esp32開發(fā)板
esp32-DevKitC-32E
引腳定義:

2.tft顯示屏
240*320的SPI顯示屏,驅(qū)動(dòng)芯片:ili9341


引腳定義:

3.引腳連接

二、開發(fā)環(huán)境
開發(fā)環(huán)境我直接使用ArduinoIDE對esp32進(jìn)行開發(fā),微軟商店和官網(wǎng)均有提供下載,安裝完畢后在主界面選擇工具---->開發(fā)板---->搜索esp32---->安裝,如有問題可百度,相關(guān)開發(fā)環(huán)境搭建的文章有很多

三、代碼
1.api獲取
我使用的是高德提供的天氣預(yù)報(bào)api,需要在高德開放平臺(tái)注冊后才可以使用,官網(wǎng)地址:https://lbs.amap.com/
如果使用其他api,流程差不多,但得到的數(shù)據(jù)結(jié)構(gòu)可能會(huì)有差別,之后會(huì)進(jìn)行說明

點(diǎn)擊右上角登陸,沒有賬號可以注冊,注冊信息填寫完成后回到首頁點(diǎn)右上角控制臺(tái)---->左邊應(yīng)用管理---->我的應(yīng)用---->右上角創(chuàng)建新應(yīng)用,名稱和類型隨便填

在新創(chuàng)建好的應(yīng)用中點(diǎn)擊右邊的添加,key名稱隨便填,服務(wù)平臺(tái)選擇web服務(wù),同意條款后提交

key添加完畢后記住key的值,現(xiàn)在就可以調(diào)用api了:
http://restapi.amap.com/v3/weather/weatherInfo?city=(你的城市編碼)&key=(你的key)=(實(shí)時(shí)天氣填base,預(yù)報(bào)天氣填all)
其中城市編碼可以在官方文檔查詢:https://lbs.amap.com/api/webservice/download
在瀏覽器地址欄輸入后如果正常會(huì)返回一些數(shù)據(jù)

返回的數(shù)據(jù)結(jié)果在官方文檔中也有說明:https://lbs.amap.com/api/webservice/guide/api/weatherinfo/#t1
注:調(diào)用不同的api返回的數(shù)據(jù)結(jié)構(gòu)可能不一樣,如高德返回的數(shù)據(jù)中有幾個(gè)數(shù)組,但其他api可能就沒有,根據(jù)返回?cái)?shù)據(jù)的結(jié)構(gòu)不一樣,后面需要處理數(shù)據(jù)相應(yīng)的代碼也不一樣
2.安裝所需要的庫和創(chuàng)建中文字庫? ?
獲取的數(shù)據(jù)需要用到ArduinoJson進(jìn)行處理,而后在tft顯示屏上顯示,但tft顯示不支持中文顯示,所以需要自定義中文字庫
2.1? ArduinoJson
在Arduino主界面選擇項(xiàng)目---->加載庫---->管理庫中搜索ArduinoJson,點(diǎn)擊安裝

2.2? TFT_eSPI
和2.1中一樣,搜索TFT_eSPI后安裝

安裝完成后還需修改頭文件的引腳定義,不過要先找到添加的庫文件的位置
如果是win10的app商店里安裝的話:
C:\Users\<用戶名>\Documents\Arduino\libraries\TFT_eSPI
如果是你使用的是綠色版 Arduino 或者exe安裝的話,該庫的安裝目錄一般為:
<Arduino安裝目錄>\Arduino\portable\sketchbook\libraries\TFT_eSPI
找到User_Setup.h打開,將TFT_CS、TFT_DC、TFT_RST的所在行注釋掉,并加入以下代碼,保存退出
#define TFT_MISO 19#define TFT_MOSI 23 ?// fixed pin, SDA -> MOSI (IO23)
#define TFT_SCLK 18 ?// fixed pin, SCL -> SCK (IO18)
#define TFT_CS ? 27 ?// Chip select control pin D4 (IO27)
#define TFT_DC ? 25 ?// pin of your choice D2 (IO25)
#define TFT_RST ?26 ?// pin of your choice D3 (IO26)
此時(shí)已經(jīng)可以嘗試測試tft_esp中的示例代碼編譯上傳了
2.3安裝中文字庫
首先要將需要的文字挑選出來(以降低存儲(chǔ)壓力,當(dāng)然你也可以試試全部漢字),因?yàn)槲倚枰氖翘鞖鈹?shù)據(jù)顯示,所以只挑選氣象相關(guān)漢字即可,通過查詢高德的天氣對照表可知https://lbs.amap.com/api/webservice/guide/tools/weather-code(其他api可能在描述細(xì)節(jié)方面不一樣,若使用其他api建議不要照搬):
晴少云晴間多云多云陰有風(fēng)平靜微風(fēng)和風(fēng)清風(fēng)強(qiáng)風(fēng)/勁風(fēng)疾風(fēng)大風(fēng)烈風(fēng)風(fēng)暴狂爆風(fēng)颶風(fēng)熱帶風(fēng)暴霾中度霾重度霾嚴(yán)重霾陣雨雷陣雨雷陣雨并伴有冰雹小雨中雨大雨暴雨大暴雨特大雨強(qiáng)陣雨強(qiáng)雷陣雨極端降雨毛毛雨/細(xì)雨雨小雨-中雨中雨-大雨大雨-暴雨暴雨-大暴雨大暴雨-特大暴雨雨雪天氣雨夾雪陣雨夾雪凍雨雪陣雪小雪中雪大雪暴雪小雪-中雪中雪-大雪大雪-暴雪浮塵揚(yáng)沙沙塵暴強(qiáng)沙塵暴龍卷風(fēng)霧濃霧強(qiáng)濃霧輕霧大霧特強(qiáng)濃霧熱冷未知-1234567890°C東南西北風(fēng)
因?yàn)楹竺嬉獙nicode編碼轉(zhuǎn)為16進(jìn)制值存儲(chǔ),所以使用編輯器(word、記事本等)優(yōu)化去除重復(fù)字可以進(jìn)一步減輕存儲(chǔ)壓力
在漢字轉(zhuǎn)Unicode編碼網(wǎng)站中轉(zhuǎn)換http://www.jsons.cn/unicode
轉(zhuǎn)換結(jié)果如下:

使用編輯器將結(jié)果中的“\u”替換為“,0x”,再將開頭的“,”刪去

接下來需要軟件processing和字體文件,processing可以在官網(wǎng)或者野雞軟件網(wǎng)下載,字體可以直接到C:\Windows\Fonts中挑選或直接下載一個(gè)
將字體文件放入...\libraries\TFT_eSPI\Tools\Create_Smooth_Font\Create_font\data文件夾中打開processing,將...\libraries\TFT_eSPI\Tools\Create_Smooth_Font\Create_font中的Create_font.pde使用processing打開(先打開processing再導(dǎo)入Create_font.pde可能導(dǎo)致字體文件路徑出錯(cuò))

找到String fontName和String fontType所在行,fontName后面的名字改為字體名字,fontType為ttf或者otf,若字體文件為其他格式可以在字體轉(zhuǎn)換網(wǎng)站轉(zhuǎn)換為otf或ttf,int fontSize為生成字體本身的大小,int displayFontSize為生成字體的預(yù)覽大小,兩個(gè)值可以根據(jù)需要自行修改

注釋掉0x0021, 0x007E所在行,這是英文字母的Unicode塊所以不需要
之后找到static final int[] specificUnicodes所在行,在此函數(shù)中添加之前在編輯器中編輯好的漢字Unicode編碼

點(diǎn)擊運(yùn)行按鈕無問題則正常顯示預(yù)覽字體,同時(shí)生成vlw文件

此時(shí)還需將vlw文件轉(zhuǎn)換為16進(jìn)制編碼
進(jìn)入下面的網(wǎng)站https://tomeko.net/online_tools/file_to_hex.php?lang=zh
點(diǎn)擊選擇文件,將剛剛生成的vlw文件選中便自動(dòng)生成16進(jìn)制編碼

接下來將所有16進(jìn)制碼復(fù)制到編輯器加頭加尾:
#include <pgmspace.h>
const uint8_t 自定義字庫名字[] PROGMEM =?
{
0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00,?
0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x01,?
...
...
...
0x61, 0x48, 0x65, 0x69, 0x2D, 0x42, 0x6F, 0x6C, 0x64, 0x01
};
此時(shí)字庫以制作完成,然后保存為.h文件到和項(xiàng)目同目錄下,改代碼時(shí)直接加上#include "你的h文件.h"即可
3.修改Arduino示例代碼

打開后建議先將項(xiàng)目另存為一個(gè)新項(xiàng)目文件
3.1? 添加所需要頭文件和調(diào)用tft相關(guān)函數(shù)

注:這里做了3個(gè)字庫對應(yīng)不同大小字體
3.1? 修改示例中需要連接的wifi名稱密碼、請求鏈接以及添加tft初始化


3.3? 解析數(shù)據(jù)并在tft上顯示



3.4? 完整代碼展示
/**
?* BasicHTTPClient.ino
?*
?*? Created on: 24.05.2015
?*
?*/
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <SPI.h>
#include <TFT_eSPI.h>
#include "font_tmp_80.h"
#include "font_wt_50.h"
#include "font_wd_30.h"
#define USE_SERIAL Serial
TFT_eSPI tft = TFT_eSPI();
WiFiMulti wifiMulti;
/*
const char* ca = \?
"-----BEGIN CERTIFICATE-----\n" \??
"MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/\n" \??
"MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \??
"DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\n" \??
"SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT\n" \??
"GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC\n" \??
"AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF\n" \??
"q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8\n" \??
"SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYuCV9bTyWaN8jKkKQDIZ0\n" \??
"Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA\n" \??
"a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj\n" \??
"/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T\n" \??
"AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG\n" \??
"CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv\n" \??
"bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k\n" \??
"c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw\n" \??
"VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC\n" \??
"ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz\n" \??
"MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu\n" \??
"Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF\n" \??
"AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo\n" \??
"uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/\n" \??
"wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu\n" \??
"X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG\n" \??
"PfZ+G6Z6h7mjem0Y+iWlkYCV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6\n" \??
"KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==\n" \??
"-----END CERTIFICATE-----\n";
*/
void setup() {
? ? USE_SERIAL.begin(115200);
? ? USE_SERIAL.println();
? ? USE_SERIAL.println();
? ? USE_SERIAL.println();
? ? for(uint8_t t = 4; t > 0; t--) {
? ? ? ? USE_SERIAL.printf("[SETUP] WAIT %d...\n", t);
? ? ? ? USE_SERIAL.flush();
? ? ? ? delay(1000);
? ? }
? ? wifiMulti.addAP("HOW DARE YOU", "PPX9XMFE");
? ??
? ? ? // 初始化彩屏
? ? tft.init();
? ? tft.setRotation(0);
}
void loop() {
? ? // wait for WiFi connection
? ? if((wifiMulti.run() == WL_CONNECTED)) {
? ? ? ? HTTPClient http;
? ? ? ? USE_SERIAL.print("[HTTP] begin...\n");
? ? ? ? // configure traged server and url
? ? ? ? //http.begin("https://restapi.amap.com/v3/weather/weatherInfo?city=152921&key=5fed1fcd87dc58a354fd19d50f8b2060&extensions=base", ca); //HTTPS
? ? ? ? http.begin("http://restapi.amap.com/v3/weather/weatherInfo?city=152921&key=5fed1fcd87dc58a354fd19d50f8b2060&extensions=base"); //HTTP
? ? ? ? USE_SERIAL.print("[HTTP] GET...\n");
? ? ? ? // start connection and send HTTP header
? ? ? ? int httpCode = http.GET();
? ? ? ? // httpCode will be negative on error
? ? ? ? if(httpCode > 0)?
? ? ? ? {
? ? ? ? ? ? // HTTP header has been send and Server response header has been handled
? ? ? ? ? ? USE_SERIAL.printf("[HTTP] GET... code: %d\n", httpCode);
? ? ? ? ? ? // file found at server
? ? ? ? ? ? if(httpCode == HTTP_CODE_OK)?
? ? ? ? ? ? {
? ? ? ? ? ? ? ? String payload = http.getString();//獲取返回結(jié)果并賦給payload
? ? ? ? ? ? ? ? USE_SERIAL.println(payload);//串口顯示結(jié)果?
? ? ? ? ? ? ? ? ArduinoJson_sw(payload);//調(diào)用解析函數(shù)并傳遞參數(shù)
? ? ? ? ? ? }
? ? ? ? }?
? ? ? ? else?
? ? ? ? {USE_SERIAL.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());}
? ? ? ? http.end();
? ? }
? ? delay(5000);
}
/*解析函數(shù)*/
void ArduinoJson_sw(String httpdata)
{
? ? /*為設(shè)備分配動(dòng)態(tài)內(nèi)存*/
? ? const size_t capacity = 9*JSON_ARRAY_SIZE(9) + 9*JSON_OBJECT_SIZE(1) + 60;
? ? DynamicJsonDocument doc(capacity);
? ??
? ? /*解析數(shù)據(jù)*/
? ? deserializeJson(doc,httpdata);
? ? JsonObject obj = doc["lives"][0];
? ??
? ? /*獲取數(shù)組中的數(shù)據(jù)并賦值*/? ? ? ? ? ?
? ? String province = obj["province"].as<String>();
? ? String city = obj["city"].as<String>();
? ? String adcode = obj["adcode"].as<String>();
? ? String weather = obj["weather"].as<String>();
? ? String temperature = obj["temperature"].as<String>();
? ? String winddirection = obj["winddirection"].as<String>();
? ? String windpower = obj["windpower"].as<String>();
? ? String humidity = obj["humidity"].as<String>();
? ? String reporttime = obj["reporttime"].as<String>();
? ? /*串口顯示結(jié)果*/? ? ? ? ? ??
? ? //USE_SERIAL.print("[ArduinoJson]");USE_SERIAL.println(province);delay(100);
? ? //USE_SERIAL.print("[ArduinoJson]");USE_SERIAL.println(city);delay(100);
? ? //USE_SERIAL.print("[ArduinoJson]");USE_SERIAL.println(adcode);delay(100);
? ? USE_SERIAL.print("[ArduinoJson]");USE_SERIAL.println(weather);delay(100);
? ? USE_SERIAL.print("[ArduinoJson]");USE_SERIAL.println(temperature);delay(100);
? ? USE_SERIAL.print("[ArduinoJson]");USE_SERIAL.println(winddirection);delay(100);
? ? USE_SERIAL.print("[ArduinoJson]");USE_SERIAL.println(windpower);delay(100);
? ? USE_SERIAL.print("[ArduinoJson]");USE_SERIAL.println(humidity);delay(100);
? ? USE_SERIAL.print("[ArduinoJson]");USE_SERIAL.println(reporttime);
? ??
? ? display(temperature,weather,winddirection,windpower);//調(diào)用顯示函數(shù)并傳遞參數(shù)
? ??
? ? delay(3000);
? ? void clear();
}
/*******************************************/
/*顯示函數(shù)*/
void display(String tmp,String whr,String wdd,String wdp)
{
? ? tft.fillScreen(TFT_BLACK);//刷新屏幕
??
? ? tft.loadFont(font_tmp_80); //指定tft屏幕對象載入字庫
? ? tft.setCursor(10, 25);//設(shè)置位置
? ? tft.print(tmp);tft.println("°C");//顯示數(shù)據(jù)
? ? tft.unloadFont();//卸載字庫
??
? ? tft.loadFont(font_wt_50); //指定tft屏幕對象載入字庫
? ? tft.setCursor(10, 130);//設(shè)置位置
? ? tft.println(whr);//顯示數(shù)據(jù)
? ? tft.unloadFont();//卸載字庫
? ? tft.loadFont(font_wd_30); //指定tft屏幕對象載入字庫
? ? tft.setCursor(10, 215);//設(shè)置位置
? ? tft.print(wdd);tft.print("風(fēng)");tft.print(wdp);tft.print("級");//顯示數(shù)據(jù)
? ? tft.unloadFont();//卸載字庫
? ? delay(1000);
}
/*****************************************/