【开发记录】esp32获取天气数据并在tft显示屏上显示
前言
开发原因:就是懒的翻手机看天气()
基本思路:esp32联网调用相关天气api,得到结果后对数据处理后在tft显示屏输出显示
一、硬件
1.esp32开发板
esp32-DevKitC-32E
引脚定义:

2.tft显示屏
240*320的SPI显示屏,驱动芯片:ili9341


引脚定义:

3.引脚连接

二、开发环境
开发环境我直接使用ArduinoIDE对esp32进行开发,微软商店和官网均有提供下载,安装完毕后在主界面选择工具---->开发板---->搜索esp32---->安装,如有问题可百度,相关开发环境搭建的文章有很多

三、代码
1.api获取
我使用的是高德提供的天气预报api,需要在高德开放平台注册后才可以使用,官网地址:https://lbs.amap.com/
如果使用其他api,流程差不多,但得到的数据结构可能会有差别,之后会进行说明

点击右上角登陆,没有账号可以注册,注册信息填写完成后回到首页点右上角控制台---->左边应用管理---->我的应用---->右上角创建新应用,名称和类型随便填

在新创建好的应用中点击右边的添加,key名称随便填,服务平台选择web服务,同意条款后提交

key添加完毕后记住key的值,现在就可以调用api了:
http://restapi.amap.com/v3/weather/weatherInfo?city=(你的城市编码)&key=(你的key)=(实时天气填base,预报天气填all)
其中城市编码可以在官方文档查询:https://lbs.amap.com/api/webservice/download
在浏览器地址栏输入后如果正常会返回一些数据

返回的数据结果在官方文档中也有说明:https://lbs.amap.com/api/webservice/guide/api/weatherinfo/#t1
注:调用不同的api返回的数据结构可能不一样,如高德返回的数据中有几个数组,但其他api可能就没有,根据返回数据的结构不一样,后面需要处理数据相应的代码也不一样
2.安装所需要的库和创建中文字库
获取的数据需要用到ArduinoJson进行处理,而后在tft显示屏上显示,但tft显示不支持中文显示,所以需要自定义中文字库
2.1 ArduinoJson
在Arduino主界面选择项目---->加载库---->管理库中搜索ArduinoJson,点击安装

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)
此时已经可以尝试测试tft_esp中的示例代码编译上传了
2.3安装中文字库
首先要将需要的文字挑选出来(以降低存储压力,当然你也可以试试全部汉字),因为我需要的是天气数据显示,所以只挑选气象相关汉字即可,通过查询高德的天气对照表可知https://lbs.amap.com/api/webservice/guide/tools/weather-code(其他api可能在描述细节方面不一样,若使用其他api建议不要照搬):
晴少云晴间多云多云阴有风平静微风和风清风强风/劲风疾风大风烈风风暴狂爆风飓风热带风暴霾中度霾重度霾严重霾阵雨雷阵雨雷阵雨并伴有冰雹小雨中雨大雨暴雨大暴雨特大雨强阵雨强雷阵雨极端降雨毛毛雨/细雨雨小雨-中雨中雨-大雨大雨-暴雨暴雨-大暴雨大暴雨-特大暴雨雨雪天气雨夹雪阵雨夹雪冻雨雪阵雪小雪中雪大雪暴雪小雪-中雪中雪-大雪大雪-暴雪浮尘扬沙沙尘暴强沙尘暴龙卷风雾浓雾强浓雾轻雾大雾特强浓雾热冷未知-1234567890°C东南西北风
因为后面要将Unicode编码转为16进制值存储,所以使用编辑器(word、记事本等)优化去除重复字可以进一步减轻存储压力
在汉字转Unicode编码网站中转换http://www.jsons.cn/unicode
转换结果如下:

使用编辑器将结果中的“\u”替换为“,0x”,再将开头的“,”删去

接下来需要软件processing和字体文件,processing可以在官网或者野鸡软件网下载,字体可以直接到C:\Windows\Fonts中挑选或直接下载一个
将字体文件放入...\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再导入Create_font.pde可能导致字体文件路径出错)

找到String fontName和String fontType所在行,fontName后面的名字改为字体名字,fontType为ttf或者otf,若字体文件为其他格式可以在字体转换网站转换为otf或ttf,int fontSize为生成字体本身的大小,int displayFontSize为生成字体的预览大小,两个值可以根据需要自行修改

注释掉0x0021, 0x007E所在行,这是英文字母的Unicode块所以不需要
之后找到static final int[] specificUnicodes所在行,在此函数中添加之前在编辑器中编辑好的汉字Unicode编码

点击运行按钮无问题则正常显示预览字体,同时生成vlw文件

此时还需将vlw文件转换为16进制编码
进入下面的网站https://tomeko.net/online_tools/file_to_hex.php?lang=zh
点击选择文件,将刚刚生成的vlw文件选中便自动生成16进制编码

接下来将所有16进制码复制到编辑器加头加尾:
#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
};
此时字库以制作完成,然后保存为.h文件到和项目同目录下,改代码时直接加上#include "你的h文件.h"即可
3.修改Arduino示例代码

打开后建议先将项目另存为一个新项目文件
3.1 添加所需要头文件和调用tft相关函数

注:这里做了3个字库对应不同大小字体
3.1 修改示例中需要连接的wifi名称密码、请求链接以及添加tft初始化


3.3 解析数据并在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();//获取返回结果并赋给payload
USE_SERIAL.println(payload);//串口显示结果
ArduinoJson_sw(payload);//调用解析函数并传递参数
}
}
else
{USE_SERIAL.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());}
http.end();
}
delay(5000);
}
/*解析函数*/
void ArduinoJson_sw(String httpdata)
{
/*为设备分配动态内存*/
const size_t capacity = 9*JSON_ARRAY_SIZE(9) + 9*JSON_OBJECT_SIZE(1) + 60;
DynamicJsonDocument doc(capacity);
/*解析数据*/
deserializeJson(doc,httpdata);
JsonObject obj = doc["lives"][0];
/*获取数组中的数据并赋值*/
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>();
/*串口显示结果*/
//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);//调用显示函数并传递参数
delay(3000);
void clear();
}
/*******************************************/
/*显示函数*/
void display(String tmp,String whr,String wdd,String wdp)
{
tft.fillScreen(TFT_BLACK);//刷新屏幕
tft.loadFont(font_tmp_80); //指定tft屏幕对象载入字库
tft.setCursor(10, 25);//设置位置
tft.print(tmp);tft.println("°C");//显示数据
tft.unloadFont();//卸载字库
tft.loadFont(font_wt_50); //指定tft屏幕对象载入字库
tft.setCursor(10, 130);//设置位置
tft.println(whr);//显示数据
tft.unloadFont();//卸载字库
tft.loadFont(font_wd_30); //指定tft屏幕对象载入字库
tft.setCursor(10, 215);//设置位置
tft.print(wdd);tft.print("风");tft.print(wdp);tft.print("级");//显示数据
tft.unloadFont();//卸载字库
delay(1000);
}
/*****************************************/