image-20210724152917592 image-20210724152857345

说在前面

  • 主要参考了稚晖君的HoloCubic和旺仔爸爸造物社的透明手表教程。https://mp.weixin.qq.com/s/Pkq7gmrbVfeZjG7krhSlTg
  • 第一次在B站发视频,视频难免有瑕疵,如果有口误不正确的地方,请大家多多包涵,有什么错误或疑问都可以在评论区指出。

1. 材料

  • ESP32 + TFT + 分光棱镜材料:

    | 器件 | 尺寸 |
    | :————————————————-: | :—————————-: |
    | ESP32开发板(乐鑫ESP-WROOM-32模组) | 54.9mm×27.9mm |
    | 高清SPI TFT彩屏(8引脚) | 1.44寸 26.5mm×25.5mm |
    | 分光棱镜 半透半反1:1 | 25.4mm×25.4mm |

  • 某宝链接:

    https://m.tb.cn/h.4zJX7F9?sm=4420ad Goouuu-ESP32模块开发板 无线WiFi+蓝牙 2合1 双核CPU
    https://m.tb.cn/h.4zJXVqe?sm=704883 高清SPI1.44寸 TFT显示彩屏 OLED液晶屏 st7735
    分光棱镜: https://m.tb.cn/h.4zFqoKL?sm=9cef79 分光棱镜 25.4mm实验教学仪器 半透半反1:1分光 影像分光投射棱镜,注意一定要是1:1

  • ESP8266 + OLED + 分光棱镜材料:

    | 器件 | 尺寸 |
    | :———————————————: | :——————-: |
    | ESP8266开发板(推荐ESP-01S) | 24.82mm×14.81mm |
    | OLED屏幕单色(四引脚) | 23.45mm×23.45mm |
    | 分光棱镜 半透半反1:1 | 25mm×25mm |
    | ESP8266串口下载器 | —— |
    | 3.7V锂电池 300mAh | —— |
    | 1A锂电池充电保护板(Type-C充电口) | —— |
    | 开关 | —— |

image-20210724091931181

  • ESP32和ESP8266是性价比非常高的Wi-Fi模块,非常适合物联网领域的DIY项目,两款芯片都属于32位处理器。ESP32是双核CPU,而ESP8266是单核处理器,可以通过Wi-Fi或蓝牙(ESP32)轻松地远程控制和监控设备。
  • ESP32是ESP8266继任者。它增加了额外的CPU内核,更快的Wi-Fi,更多的GPIO,并支持蓝牙4.2和蓝牙低功耗。此外,ESP32配有触摸感应针脚。

2. EPS32 硬件连接方式

  • EPS32 和 TFT彩屏接线图:

    image-20210724095057482

    image-20210724094918405

3. Arduino + ESP32开发环境的搭建

  • 官网下载Arduino1.8.15 https://www.arduino.cc/en/software (建议不要下载2.0 beta 版,可能会有问题)。

  • 安装ESP32开发包,文件-> 首选项->附加开发板管理器 输入https://dl.espressif.com/dl/package_esp32_index.json

    image-20210724101953258

    然后重启Arduino,工具-> 开发板->开发板管理器,搜索ESP32下载

    image-20210724102224386

    这种方法下载速度很慢一般用下述方法:

  • 将文件夹下Espressif文件考到Arduino安装目录/hardware下,可以直接完成ESP32的配置。

    image-20210724100814799

  • 工具->开发板选择 ESP32的开发板,并且设置下载速度为921600,频率为80MHZ。

    image-20210724102443337

  • 至此Arduino 和 ESP32 开发环境已经搭建好了。


下面采用官方例程验证是否有问题,能否下载程序。

  • 打开文件官方示例:ChipID,获取开发板芯片ID号。

image-20210724102651671

  • 点击烧写按钮,在下方看到烧写完成100%。注意下方提示Connect 时,此时开发板需要手动按下boot按钮才可烧写(踩坑记录,刚开始还以为板子坏了QAQ)

    image-20210724102926723

  • 点击右上角串口监视器,可以看到ESP32芯片的ID号。证明环境搭建成功!

    image-20210724103019210

4. TFT_eSPI库的安装和基本使用

  • 本文件的ESP32包已经有了TFT_eSPI库,如果自行下载的,需要到库管理中下载。

  • 打开.\Arduino\hardware\espressif\esp32\libraries\TFT_eSPI下的文件,在众多的驱动文件中,选择驱动ST7735,其他用不到的注释掉。 (文件夹中的是修改好的,如果TFT屏幕驱动和我的一样就选择ST7735)

    image-20210724104800558

  • 接着设置屏幕显示的颜色和屏幕的尺寸大小,我们设置屏幕的宽度为128×128,屏幕颜色有RGB和BGR两种类型。TFT默认的是BGR类型,我们图片一般是RGB,后面在显示图片教程中会详细说明。

  • 下面这行代码取消注释,是解决屏幕显示偏移的问题,主要是针对7735驱动的,出现屏幕下方和右方一条花纹线,没有颜色显示,原因就是屏幕原点(0,0)发生了偏移,把这行代码取消注释就可以了。(踩坑记录,找了好久才知道是这个问题,还以为TFT屏幕坏了QAQ

    image-20210724105342971

  • 接下来是SPI引脚的设置,按照下图中的引脚设置即可,和前面的ESP32连线保持一致。

    image-20210724105037348

  • 下面是频率的设置,按照下图设置即可。

    image-20210724105208229

  • 新建文件写入如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #include <SPI.h> //导入库
    #include <TFT_eSPI.h>

    TFT_eSPI tft = TFT_eSPI();

    void setup() {
    // put your setup code here, to run once:
    tft.init(); //初始化
    tft.fillScreen(TFT_BLACK); //屏幕颜色
    tft.setCursor(10, 10, 1); //设置起始坐标(10, 10),2 号字体
    tft.setTextColor(TFT_YELLOW); //设置文本颜色为白色
    tft.setTextSize(2); //设置文字的大小 (1~7)
    tft.println("TFT_Text"); //显示文字
    tft.fillCircle(30,30,10,TFT_BLUE); //画圆
    tft.drawLine(10,50, 118, 50, TFT_WHITE); //画线
    tft.drawPixel(70,70,TFT_RED); //画点
    tft.setTextColor(TFT_WHITE,TFT_BLUE); //设置文字颜色和背景颜色
    tft.setCursor(10, 80, 1); //设置起始坐标(10, 10),2 号字体
    tft.println("TFT_Text"); //显示文字
    }
    void loop() {
    // put your main code here, to run repeatedly:
    }
  • 按之前步骤进行烧写,结果如下图所示:

    image-20210724111531391

  • TFT常用函数原型:

    tft.init(); tft 初始化

    tft.fillScreen(Color); color 可用内置的也可以自己设置屏幕颜色

    tft.setCursor(X, Y, L);设置起始坐标(X, Y),L号字体 L:0~6

    tft.setTextColor(Color);设置文本颜色

    tft.setTextSize(S); 设置文字的大小 S:(1~7)

    tft.println(Str); 显示文字字符串,仅可显示英文字符,中文字符后续会出详细教程(需要自建汉字库显示)

    tft.fillCircle(X,Y,R,Color); 画圆 以(X,Y)为圆心,R为半径。

    tft.drawLine(X1,Y1, X2, Y2, Color);画线,起点(X1,Y1),终点(X2,Y2)。

    tft.drawPixel(X,Y,Color);画点 (X,Y)

    tft.setTextColor(fg,bg); 设置文字颜色fg和背景颜色bg。

5. TFT显示中文汉字

  • 新建一个字库文件MyFont.h用来存储汉字字模数据,将每个汉字转换成对应的16进制数,就可以显示任意中文词语或者句子,但其实显示汉字或图片这种操作是比较占用单片机运行内存的,我们需要将这些不会变化的数据定义为不可变的const类型,因为用const修饰的变量,在硬件上会被保存到ROM中即“程序存储器”,而用于计算的“随机存储器”RAM空间比ROM小很多。

  • 定义每个汉字的显示大小为16×16像素,在MyFont.h中写入如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    #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/*"周",0*/
    };
    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/*"一",1*/
    };

    ...
    ...
    ... // 省略其余字模数据

    struct FNT_HZ // 汉字字模数据结构
    {
    char Index[4]; // 汉字内码索引,如"中",在UTF-8编码下,每个汉字占3个字节,第四个是结束符0
    const unsigned char* hz_Id; // 点阵码数据,存放内码后对应的点阵序列每个字需要32个字节的点阵序列
    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}
    };

  • 打开字体取模软件PCtoLCD2002.exe,选项进行如下设置:

    image-20210725174446722

  • 然后在下方打入需要取的汉字,点击生成字模,就可以复制下来按照上述方式定义汉字数组,存储这些字模就可以使用了。

    image-20210725174926505

  • 下面是导入字模,在TFT屏幕上显示汉字的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /*******************单个汉字显示****************/
    void showMyFont(int32_t x, int32_t y, const char c[3], uint32_t color) {
    for (int k = 0; k < 25; 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;
    }
    }

    showMyFonts(40, 50, "周日晴", TFT_YELLOW); //在(40,50)处显示“周日晴”

6. TFT显示图片(包含动态图片)

  • 同样新建一个图片文件pic.h,将图片取模的的数据用相同的方式存进去,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #include <pgmspace.h>

    const uint16_t weather0[0x1000] PROGMEM ={
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0010 (16)
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    ...
    ...
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0FE0 (4064)
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0FF0 (4080)
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1000 (4096)
    };

    const uint16_t weather1[0x1000] PROGMEM ={...};

    const uint16_t weather2[0x1000] PROGMEM ={...};

    const uint16_t weather3[0x1000] PROGMEM ={...};
  • 图片取模方法:打开图片取模软件ImageConverter565.exe,Open image选择要转换的图片文件,可以看到像素大小,选择Arduino,保存为.c文件。

  • 将其改为const uint16_t Name[ ] PROGMEM ={...}方式,和字体一样存到程序存储器中。

  • 下面就可以采用以下程序在TFT屏幕上显示图片了:

    1
    2
    tft.setSwapBytes(true);              // RGB->BGR 加上这句才能正确显示颜色。
    tft.pushImage(4, 4, 120, 120, Name); // 在(4,4)处显示Name图片 128×128像素
  • 对于动态照片的显示,将动态照片分解成几帧,在loop函数中,定义一个全局变量i控制显示的帧数,每次运行loop显示一帧照片并延时,就可以完成动态照片的显示。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #include <pgmspace.h>

    //每一帧太空人的照片,一共九帧
    const uint16_t astronaut1[0x1000] PROGMEM ={
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0010 (16)
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0020 (32)
    ...
    ...
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}
    const uint16_t astronaut2[0x1000] PROGMEM ={...};
    const uint16_t astronaut3[0x1000] PROGMEM ={...};
    const uint16_t astronaut4[0x1000] PROGMEM ={...};
    const uint16_t astronaut5[0x1000] PROGMEM ={...};
    const uint16_t astronaut6[0x1000] PROGMEM ={...};
    const uint16_t astronaut7[0x1000] PROGMEM ={...};
    const uint16_t astronaut8[0x1000] PROGMEM ={...};
    const uint16_t astronaut9[0x1000] PROGMEM ={...};

    //定义太空人照片的指针数组
    const uint16_t* Astronaut [] PROGMEM = {astronaut1,astronaut2,astronaut3,astronaut4,astronaut5,astronaut6,astronaut7,astronaut8,astronaut9};

    int i = 0;
    void loop{
    tft.setSwapBytes(true); //使图片颜色由RGB->BGR
    tft.pushImage(10, 55, 64, 64, Astronaut[i]); // (10,55) 显示 64 × 64 像素的图片
    delay(100); //延时
    i+=1; //下一帧
    if(i>8){i=0;}
    }

7. TFT镜像设置+分光棱镜

  • 分光棱镜是一种用于分离光线的水平偏振和垂直偏振的光学元件,是由两个三棱镜组成,中间镀制了多层膜结构,其中透射和反射是1:1。

    image-20210726090804948

    image-20210726090921476

  • 而它所成的像是上下颠倒的,这就需要我们在程序中把屏幕设置为镜像显示。

    image-20210726094234640

  • 打开文件E:\Arduino\hardware\espressif\esp32\libraries\TFT_eSPI\TFT_Drivers\ST7735_Rotation.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    rotation = m % 5; // 修改 Limit the range of values to 0-4

    writecommand(TFT_MADCTL);
    switch (rotation) {
    case 0:
    if (tabcolor == INITR_BLACKTAB) {
    writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_COLOR_ORDER);
    } else if(tabcolor == INITR_GREENTAB2) {
    ...
    ...
    ...
    _width = _init_height;
    _height = _init_width;
    break;

    /****添加case4**********/
    case 4:
    writedata(0x39); // 设置镜像旋转
    _width = _init_width;
    _height = _init_height;
    break;
    }

  • 之后在代码中Setup()函数设置镜像就可以了,这样TFT屏幕就显示镜像。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void setup()
    {
    tft.init(); //初始化显示寄存器
    tft.fillScreen(TFT_WHITE); //屏幕颜色
    tft.setTextColor(TFT_BLACK); //设置字体颜色黑色
    tft.setCursor(15, 30, 1); //设置文字开始坐标(15,30)及1号字体
    tft.setTextSize(1);

    tft.setRotation(4); //设置显示镜像 + 旋转90°
    }

8. NTP获取时间

  • 网络时间协议(Network Time Protocol,NTP)是用来使计算机时间同步化的一种协议,它可以使计算机对其服务器或时钟源(如石英钟,GPS等等)做同步化,它可以提供高精准度的时间校正,与标准时间的误差小于1ms。标准时间来源于原子钟,卫星,天文台等。

  • 我们就是要从NTP服务器上获取当前日期和时间,常用的NTP服务器如下:

    1
    pool.ntp.org     		 # 国外的NTP服务器,国内地址为:cn.pool.ntp.orgntp.aliyun.com   		 # 阿里云time1.cloud.tencent.com  # 腾讯time.google.com 		 # 谷歌ntp.tuna.tsinghua.edu.cn # 清华大学ntp.sjtu.edu.cn  		 # 上海交通大学ntp.fudan.edu.cn		 # 复旦大学 
  • 在Arduino安装NTPClientWifi库,在工具->库管理工具中搜索,如果当时用的我分享的ESP32包,里面自带的有就不需要下载了。

    image-20210728102800071

  • 下面打开官方例程,文件->例程->NTPClient->Basic,进行如下修改:

    1
    #include <NTPClient.h>#include <WiFi.h> // for WiFi shield#include <WiFiUdp.h>const char *ssid    = "   ";  //wifi账号const char *password = "   ";  //wifi密码WiFiUDP ntpUDP;NTPClient timeClient(ntpUDP,"ntp.aliyun.com");  //NTP服务器地址void setup(){  Serial.begin(115200);  //连接wifi  WiFi.begin(ssid, password);  while ( WiFi.status() != WL_CONNECTED ) {    delay ( 500 );    Serial.print ( "." );  }  timeClient.begin();}void loop() {  timeClient.update();  //打印时间  Serial.println(timeClient.getFormattedTime());  delay(1000);}
  • 下载后我们通过串口监视器查看打印结果,发现打印出来的时间和电脑中显示的时间并不同步,基本相差8个小时。原因在于时区,我们所处的位置是东8区,所以要与东8区时间同步,进行如下修改:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void setup(){
    Serial.begin(115200);
    //连接wifi
    WiFi.begin(ssid, password);
    while ( WiFi.status() != WL_CONNECTED ) {
    delay ( 500 );
    Serial.print ( "." );
    }
    timeClient.begin();
    timeClient.setTimeOffset(28800); // + 1区 偏移3600, +8区 :3600×8 = 28800
    }
  • 下面是获取具体年、月、日、星期、小时、分钟的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    timeClient.update();
    unsigned long epochTime = timeClient.getEpochTime();
    Serial.print("Epoch Time: ");
    Serial.println(epochTime);
    //打印时间
    int currentHour = timeClient.getHours();
    Serial.print("Hour: ");
    Serial.println(currentHour);
    int currentMinute = timeClient.getMinutes();
    Serial.print("Minutes: ");
    Serial.println(currentMinute);
    int weekDay = timeClient.getDay();
    Serial.print("Week Day: ");
    Serial.println(weekDay);
    //将epochTime换算成年月日
    struct tm *ptm = gmtime ((time_t *)&epochTime);
    int monthDay = ptm->tm_mday;
    Serial.print("Month day: ");
    Serial.println(monthDay);
    int currentMonth = ptm->tm_mon+1;
    Serial.print("Month: ");
    Serial.println(currentMonth);
    delay(1000);

    image-20210728105635342

  • Epoch Time: 其实它指的是一个特定的时间:1970-01-01 00:00:00 ,也就是世界标准时间1970年1月1日0时0分0秒,以这个时间为起点,每过去一秒,数值加1。对应的就可以算出公历时间日期(不算闰秒)。

  • 可以利用NTP库文件中提供的方法直接计算出年月日、星期等数据,获取到时间数据后只需要按照前面介绍的显示文本与汉字的方法就可以在TFT彩屏显示时间数据了。

9. 天气信息获取

  • 采用的是心知天气seniverse.com官方API,选择这个是因为对于用户免费提供天气实况及未来三天的天气,这对于我们的项目完全够用了。首先注册登录,点击控制台->免费版。

    image-20210728114143590

  • 在基本信息中可以查看自己的公钥和私钥,私钥用于API的访问。

    image-20210728114259323

  • 点击文档-> V3版本文档->天气实况,这里有具体的接口地址、参数说明和返回结果说明。

    image-20210728114454737

  • 天气实况API:https://api.seniverse.com/v3/weather/now.json?key=你的私钥&location=查询城市的拼音&language=zh-Hans&unit=c

  • 当天+未来3天天气API: https://api.seniverse.com/v3/weather/daily.json?key=你的私钥&location=查询城市的拼音&language=zh-Hans&unit=c&start=0&days=5

  • 生活指数API :https://api.seniverse.com/v3/life/suggestion.json?key=你的私钥&location=查询城市的拼音&language=zh-Hans

  • 下面以查询上海的天气实况为例,直接在网站输入上述网址。返回结果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    {
    "results":[
    {
    "location":
    {
    "id":"WTW3SJ5ZBJUY",
    "name":"上海",
    "country":"CN",
    "path":"上海,上海,国",
    "timezone":"Asia/Shanghai",
    "timezone_offset":"+08:00"},
    "now":
    {
    "text":"阴",
    "code":"9",
    "temperature":"28"
    },
    "last_update":"2021-07-28T11:25:27+08:00"
    }
    ]
    }
  • 可以看到返回的是json数据格式,我们在程序中采用Http协议去访问这个API获得json数据,在Arduino中安装httpclient库用来发送请求,建立连接。安装arduino json库用于json数据的解析。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    #include <WiFi.h> //wifi库
    #include <ArduinoJson.h> //Json库
    #include <HTTPClient.h> //HTTP库
    const char* ssid = " "; //wifi账号
    const char* password = " "; //wifi密码
    const char* host = "api.seniverse.com"; //心知天气服务器地址
    String now_address="",now_time="",now_temperature="";//用来存储报文得到的字符串
    void setup()
    {
    Serial.begin(115200);
    // 连接网络
    WiFi.begin(ssid, password);
    //等待wifi连接
    while (WiFi.status() != WL_CONNECTED)
    {
    delay(500);
    Serial.print(".");
    }
    Serial.println("");
    Serial.println("WiFi connected"); //连接成功
    Serial.print("IP address: "); //打印IP地址
    Serial.println(WiFi.localIP());
    }
    void loop()
    {
    //创建TCP连接
    WiFiClient client;
    const int httpPort = 80;
    if (!client.connect(host, httpPort))
    {
    Serial.println("connection failed"); //网络请求无响应打印连接失败
    return;
    }
    //URL请求地址
    String url ="/v3/weather/now.json?key=SeAeuMjbRTJo4I_ZN&location=shanghai&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");
    delay(5000);
    //定义answer变量用来存放请求网络服务器后返回的数据
    String answer;
    while(client.available())
    {
    String line = client.readStringUntil('\r');
    answer += line;
    }
    //断开服务器连接
    client.stop();
    Serial.println();
    Serial.println("closing connection");
    //获得json格式的数据
    String jsonAnswer;
    int jsonIndex;
    //找到有用的返回数据位置i 返回头不要
    for (int i = 0; i < answer.length(); i++) {
    if (answer[i] == '{') {
    jsonIndex = i;
    break;
    }
    }
    jsonAnswer = answer.substring(jsonIndex);
    Serial.println();
    Serial.println("JSON answer: ");
    Serial.println(jsonAnswer);
    }

    image-20210728120157491

  • 下面是处理json数据的代码,如何从json数据中提取到我们需要的天气字符串,采用官方的json处理网站:arduinojson.org 点击Assistant,进行如下设置:

    image-20210728120652863

  • 点击Next -> json,将刚刚获取的json代码复制过来:

    image-20210728120748427

  • 然后一直下一步,给出解析json数据的代码如下:

    image-20210728120848532

  • 将其复制到主程序中,其中第二行input 修改为之前返回的json数据,可以把判断error的代码去掉,因为我们前面得到的肯定是正确的json数据。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    #include <WiFi.h> //wifi库
    #include <ArduinoJson.h> //Json库
    #include <HTTPClient.h> //HTTP库

    const char *ssid = "HUAWEI-86BPKD";
    const char *password = "changgebaitong";

    const char* host = "api.seniverse.com"; //心知天气服务器地址
    String now_address="",now_weather="",now_temperature="";//用来存储报文得到的字符串
    void setup()
    {
    Serial.begin(115200);
    // 连接网络
    WiFi.begin(ssid, password);
    //等待wifi连接
    while (WiFi.status() != WL_CONNECTED)
    {
    delay(500);
    Serial.print(".");
    }
    Serial.println("");
    Serial.println("WiFi connected"); //连接成功
    Serial.print("IP address: "); //打印IP地址
    //Serial.println(WiFi.localIP());
    }
    void loop()
    {
    //创建TCP连接
    WiFiClient client;
    const int httpPort = 80;
    if (!client.connect(host, httpPort))
    {
    Serial.println("connection failed"); //网络请求无响应打印连接失败
    return;
    }
    //URL请求地址
    String url ="/v3/weather/now.json?key=SeAeuMjbRTJo4I_ZN&location=shanghai&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");
    delay(5000);
    //定义answer变量用来存放请求网络服务器后返回的数据
    String answer;
    while(client.available())
    {
    String line = client.readStringUntil('\r');
    answer += line;
    }
    //断开服务器连接
    client.stop();
    Serial.println();
    Serial.println("closing connection");
    //获得json格式的数据
    String jsonAnswer;
    int jsonIndex;
    //找到有用的返回数据位置i 返回头不要
    for (int i = 0; i < answer.length(); i++) {
    if (answer[i] == '{') {
    jsonIndex = i;
    break;
    }
    }
    jsonAnswer = answer.substring(jsonIndex);
    Serial.println();
    Serial.println("JSON answer: ");
    Serial.println(jsonAnswer);


    StaticJsonDocument<512> doc;

    deserializeJson(doc, jsonAnswer);

    JsonObject results_0 = doc["results"][0];

    JsonObject results_0_location = results_0["location"];

    const char* results_0_location_name = results_0_location["name"]; // "上海"

    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"]; // "9"
    const char* results_0_now_temperature = results_0_now["temperature"]; // "28"
    now_address = results_0_location_name;
    now_weather = results_0_now_text;
    now_temperature = results_0_now_temperature;
    Serial.print("Now address:");
    Serial.println(now_address);
    Serial.print("Now weather:");
    Serial.println(now_weather);
    Serial.print("Now temperature:");
    Serial.println(now_temperature);
    }

    image-20210728121521274

  • 得到了这些字符串之后结合之前教程TFT显示中文汉字,就可以在屏幕任意位置显示天气信息了。

结尾

  • 至此所有知识都讲完了,将以上所有内容进行整合就可以完成该项目,具体代码可以参考文件中的TransparentScreen.ino , 有什么疑问或者问题都可以在评论区提出,谢谢大家。

补充

ESP32 获取B站粉丝数和关注数

API : https://api.bilibili.com/x/relation/stat?vmid=270875564&jsonp=jsonp

其中:270875564 为要查看用户的UID,在个人资料的右下角查看:

  • 和获取天气信息一样,在网站上输入该链接可以返回相应的json数据:
  • 在源代码中添加以下函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    const char* host2 = "api.bilibili.com"; // B站服务器地址
    String UID = "270875564"; // B站用户UID数字号
    int following = 0; // 关注数
    int follower = 0; // 粉丝数

    void get_Bstation_follow()
    {
    //创建TCP连接
    WiFiClient client;
    const int httpPort = 80;
    if (!client.connect(host2, httpPort))
    {
    Serial.println("Connection failed"); //网络请求无响应打印连接失败
    return;
    }
    //URL请求地址
    String url ="/x/relation/stat?vmid="+ UID + "&jsonp=jsonp"; // B站粉丝数链接,注意修改UID
    //发送网络请求
    client.print(String("GET ") + url + " HTTP/1.1\r\n" +
    "Host: " + host2 + "\r\n" +
    "Connection: close\r\n\r\n");
    delay(2000);
    //定义answer变量用来存放请求网络服务器后返回的数据
    String answer;
    while(client.available())
    {
    String line = client.readStringUntil('\r');
    answer += line;
    }
    //断开服务器连接
    client.stop();
    Serial.println();
    Serial.println("closing connection");
    //获得json格式的数据

    String jsonAnswer;
    int jsonIndex;
    //找到有用的返回数据位置i 返回头不要
    for (int i = 0; i < answer.length(); i++) {
    if (answer[i] == '{') {
    jsonIndex = i;
    break;
    }
    }
    jsonAnswer = answer.substring(jsonIndex);
    Serial.println();
    Serial.println("JSON answer: ");
    Serial.println(jsonAnswer);
    StaticJsonDocument<256> doc;

    deserializeJson(doc, jsonAnswer);
    JsonObject data = doc["data"];
    following = data["following"]; // 59
    follower = data["follower"]; // 411

    Serial.println("Following: ");
    Serial.println(following);
    Serial.println("follower: ");
    Serial.println(follower);
    }
    void show_Bstation(uint16_t fg,uint16_t bg)
    {
    tft.setSwapBytes(true); //使图片颜色由RGB->BGR
    tft.pushImage(4, 20, 120, 50, Bili);

    showMyFonts(20, 80, "关注数", fg);
    showMyFonts(20, 100, "粉丝数", fg);
    showtext(85,80,1,2,TFT_YELLOW,bg, String(following));
    showtext(85,100,1,2,TFT_YELLOW,bg, String(follower));
    }

外接锂电池+充电保护板供电

  • 锂电池:3.7V,300mAh,1A充电保护板-TypeC接口充电。
  • 充电时负载要断开,所以负载处用了一个开关进行控制,充满电时充电保护板蓝色指示灯亮,这里提醒一下,如果锂电池出现膨胀,一定不要再使用了,具有安全隐患
  • 开关用的是拨动开关,中间引脚是公共端,使用只需两个引脚,波动到哪边,哪边与中间引脚相通,可控制负载。