赞
踩
一、实现效果
WeChat_20221109203218
二、开发说明
几个月前就实现了效果,一直没有整理发布博客。开发工具:visual studio code 平台:platformio。visual studio code 安装以及platformio插件 配置可百度,就是使用platformio插件项目开始下载慢的问题,这个需要在早上(网络不好需更换wifi)下载,这样项目基本都新建成功,我一开始白天新建项目下载esp8266相关的文件都一直卡着不动,后来都是一大早新建项目都成功了,由于屏幕较小,布局改了几次。使用的库:
TFT_eSPI、TJpg_Decoder、ArduinoJson、TimeLib(下载的别人写好的)以及esp8266wifi连接相关。
三、实现过程
(1)TFT_eSPI配置
引脚请自行配置tft_espi库中的 User_Setup.h文件。在User_Setup.h文件中使用st7735驱动
以及高度、宽度、RGB等配置
(2)屏幕引脚插线
具体接线对应如下:
TFT屏幕 nodemcu
GND GND
VCC 3V3
SCL D5
SDA D7
RES D4
DC D3
CS D8
BLK 可以不接(控制屏幕背光)
(3)利用python将太空人gif转为多个图片以及数据文件
最终使用space.h文件引入适合的帧数据,不能都引入,都引入就大了。
- from PIL import Image
- import sys
- import os
- from io import BytesIO
- import binascii
- import traceback
-
-
- curdir = "./"
- os.chdir(curdir)
-
- def processImage(in_file, saveImg=True):
- try:
- im = Image.open(in_file)
- except IOError:
- print("Cant load", in_file)
- sys.exit(1)
-
- # 截取文件名
- filename = in_file.split('.')[0]
-
- i = 0
- mypalette = im.getpalette()
-
- arr_name_all = '' # 存取数组
- arr_size_all = '' # 存储数组容量
-
- try:
- with open(filename + '.h', 'w', encoding='utf-8') as f: # 写入文件
- f.write('#include <pgmspace.h> \n\n')
- while 1:
- print('.', end="")
- im.putpalette(mypalette)
- new_im = Image.new("RGB", im.size)
- new_im.paste(im)
-
- # 缩放图像,
- width = new_im.size[0] # 获取原始图像宽度
- height = new_im.size[1] # 获取原始图像高度
- new_height = 82 # 等比例缩放后的图像高度,根据实际需要调整
- # print(width, " ", height)
- if height > new_height:
- ratio = round(new_height / height, 3) # 缩放系数
- new_im = new_im.resize((int(width * ratio), int(height * ratio)), Image.ANTIALIAS)
-
- # 获取图像字节流,转16进制格式
- img_byte = BytesIO() # 获取字节流
- new_im.save(img_byte, format='jpeg')
- # print(img_byte.getvalue())
-
- # 16进制字符串
- img_hex = binascii.hexlify(img_byte.getvalue()).decode('utf-8')
-
- arr_name = filename + '_' + str(i)
- arr_size = 0 # 记录数组长度
- arr_name_all += arr_name + ','
-
- # 将ac --> 0xac
- f.write('const uint8_t ' + arr_name + '[] PROGMEM = { \n') # 写前
- for index, x in zip(range(len(img_hex)), range(0, len(img_hex), 2)):
- temp_hex = '0x' + img_hex[x:x + 2] + ', '
- # 30个数据换行
- if (index + 1) % 30 == 0:
- temp_hex += '\n'
-
- f.write(temp_hex) # 写入文件
- arr_size += 1
- f.write('\n};\n\n') # 写结尾
- i += 1
- arr_size_all += str(arr_size) + ','
-
- # 保存一帧帧图像
- if saveImg:
- if not os.path.exists('./out_img'):
- os.mkdir('./out_img')
- if not os.path.exists('./out_img/' + filename):
- os.mkdir('./out_img/' + filename)
- new_im.save('./out_img/' + filename + '/' + str(i) + '.jpg')
-
- try:
- im.seek(im.tell() + 1)
- except EOFError:
- # 动图读取结束
- f.write('const uint8_t *' + filename + '[' + str(i) + '] PROGMEM { ' + arr_name_all + '};\n')
- f.write('const uint32_t ' + filename + '_size[' + str(i) + '] PROGMEM { ' + arr_size_all + '};')
- print("成功保存文件为:" + filename + '.h')
- break
-
- except EOFError as e:
- print(e.args)
- print(traceback.format_exc())
- pass # end of sequence
-
-
- if __name__ == '__main__':
- processImage("space.gif", True)
- # im=Image.open("foo0.bmp")
- # print ("img info:",im.format,im.size)
-
(4)使用processing 软件制作字体
使用processing打开Create_font.pde文件(https://processing.org/ 下载processing软件,并且安装)。只需修改几个地方就可以,如下所示:
每个汉字对应的unicode码值可以通过在线转换工具获取,然后将转换后的/u替换为0x即可。完成修改后,点击运行,弹出对话框显示自定义库中的所有字符,同时在FontFiles文件夹中生成一个.vlw格式的文件,存放我们制作出来的字库文件。通过https://tomeko.net/online_tools/file_to_hex.php?lang=zh,将vlw文件转换成Arduin使用的字库文件xxxFont.h
将生成的16进制数据按照下列各式存放在自定义的.h格式文件中
- #include <pgmspace.h>
- const uint8_t font_10[] PROGMEM = {
- 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x52, 0xA0, 0x00, 0x00, 0x00, 0x1F,
- 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x02,
- ...
- 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6C, 0xB9, 0x00, 0x00, 0x00, 0x26,
- 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x07,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x96, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x29,
- };
(5) 完整代码
- #include <TFT_eSPI.h>
- #include <Arduino.h>
- #include <ESP8266WiFi.h>
- #include <ESP8266WiFiMulti.h>
- #include <ESP8266HTTPClient.h>
- #include <TJpg_Decoder.h>
- #include <WiFiUdp.h>
- #include <TimeLib.h>
- #include <ArduinoJson.h>
- // #include <NTPClient.h>
-
- #include "img/space.h" // 太空人
- #include "font/date_18.h" // 时间
- #include "font/time_20.h" // 日期
-
- TFT_eSPI tft = TFT_eSPI(); // 引脚请自行配置tft_espi库中的 User_Setup.h文件
- TFT_eSprite clk = TFT_eSprite(&tft);
- WiFiClient espClient;
- ESP8266WiFiMulti wifis;
- WiFiUDP Udp;
-
- // NTP Servers:
- // static const char ntpServerName[] = "cn.ntp.org.cn";
- static const char ntpServerName[] = "ntp1.aliyun.com";
- const int timeZone = 8; // 时区
- unsigned int localPort = 1337; // local port to listen for UDP packets
-
- unsigned long weatherTime = 0;
- struct WeatherData
- {
- char city[16]; //城市名称
- char weather[32]; //天气介绍(多云...)
- char temp[16]; //温度
- char udate[32]; //更新时间
- };
-
- WeatherData weatherData;
- void sendNTPpacket(IPAddress &address);
- time_t getNtpTime();
- void getCityWeater();
-
- /**********************************************
- * 加载进度条
- *
- ***********************************************/
- int loadNum = 1; // 进度条长度 初始1
- int maxLoad = 147; // 进度条最大长度
- void loading(int num)
- {
- clk.setColorDepth(8);
-
- clk.createSprite(160, 80); // 创建布局大小 宽x高 0.96寸ttf 80x160 tft.int() 后设置方向
- tft.setRotation(1);
- clk.fillSprite(TFT_BLACK); // 布局背景颜色
-
- clk.drawRoundRect(5, 40, 150, 16, 6, TFT_WHITE); // 画进度条外边 (x方向5, y方向40, 长度150(左右边距5,总长度160), 高度16 , 圆角6 , 白色)
- clk.fillRoundRect(7, 42, loadNum, 12, 5, TFT_WHITE); // 画进度条填充 (x方向7, y方向42, 长度loadNum, 高度12 , 圆角5 , 白色)设置的坐标位置和长度在外边内
- clk.setTextColor(TFT_GREEN, TFT_BLACK); // 设置字体颜色背景
- clk.drawCentreString("Connecting to WiFi", 80, 20, 1); // 设置字体居中显示
- clk.pushSprite(0, 0); // 布局坐标
- clk.deleteSprite();
- if (loadNum < maxLoad) // 值小于最大值 +1
- loadNum += 1;
- delay(1);
- if (loadNum < num) // 值小于设定值 继续执行
- loading(num);
- }
- // wifi连接
- void wifiConnect()
- {
- Serial.print("Connecting");
- // while (WiFi.status() != WL_CONNECTED)
- while (wifis.run() != WL_CONNECTED)
- {
- digitalWrite(D0, LOW);
- delay(250);
- Serial.print(".");
- digitalWrite(D0, HIGH);
- delay(250);
- }
- Serial.println();
- Serial.print("Connected, IP address: ");
- Serial.println(WiFi.localIP());
- }
-
- /**************************
- * 太空人动画
- * x x轴,默认0
- * y y轴,默认0
- * dt 延时,默认60ms
- *
- * ***********************/
- void spaceAnimation(int x = 0, int y = 30, int dt = 60)
- {
- // TJpgDec.setJpgScale(2);
- TJpgDec.drawJpg(x, y, space_0, sizeof(space_0));
- delay(dt);
- TJpgDec.drawJpg(x, y, space_4, sizeof(space_4));
- delay(dt);
- TJpgDec.drawJpg(x, y, space_8, sizeof(space_8));
- delay(dt);
- TJpgDec.drawJpg(x, y, space_12, sizeof(space_12));
- delay(dt);
- TJpgDec.drawJpg(x, y, space_16, sizeof(space_16));
- delay(dt);
- TJpgDec.drawJpg(x, y, space_20, sizeof(space_20));
- delay(dt);
- TJpgDec.drawJpg(x, y, space_24, sizeof(space_24));
- delay(dt);
- TJpgDec.drawJpg(x, y, space_28, sizeof(space_28));
- delay(dt);
- TJpgDec.drawJpg(x, y, space_32, sizeof(space_32));
- delay(dt);
- TJpgDec.drawJpg(x, y, space_36, sizeof(space_36));
- delay(dt);
- TJpgDec.drawJpg(x, y, space_40, sizeof(space_40));
- delay(dt);
- TJpgDec.drawJpg(x, y, space_44, sizeof(space_44));
- delay(dt);
- TJpgDec.drawJpg(x, y, space_47, sizeof(space_47));
- delay(dt);
- }
- bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap)
- {
- if (y >= tft.height())
- return 0;
- tft.pushImage(x, y, w, h, bitmap);
- // Return 1 to decode next block
- return 1;
- }
- void digitalClockDisplay()
- {
-
- clk.setColorDepth(8);
-
- /***中间时间区***/
- byte xpos = 20;
- byte ypos = 4;
- //时分
- clk.createSprite(90, 32);
- clk.fillSprite(TFT_WHITE);
- clk.loadFont(TIME_32);
- clk.setTextColor(TFT_BLACK, TFT_WHITE);
- int hh = hour();
- if (hh < 10)
- xpos += clk.drawString("0", xpos, ypos);
- xpos += clk.drawNumber(hh, xpos, ypos);
- xpos += clk.drawString(":", xpos, ypos);
- int mm = minute();
- if (mm < 10)
- xpos += clk.drawString("0", xpos, ypos);
- clk.drawNumber(mm, xpos, ypos); //绘制时和分
- clk.unloadFont();
- clk.pushSprite(31, 25);
- clk.deleteSprite();
-
- //秒
- clk.createSprite(40, 20);
- clk.fillSprite(TFT_WHITE);
- clk.loadFont(TIME_20);
- clk.setTextColor(TFT_BLACK, TFT_WHITE);
- int seconds = second();
- String secondStr = (seconds < 10 ? "0" : "") + String(seconds);
- clk.drawString(secondStr, 5, 0);
- clk.unloadFont();
- clk.pushSprite(120, 36);
- clk.deleteSprite();
- /***中间时间区***/
-
- /***顶部***/
- clk.loadFont(DATE_18);
- String weeks[7] = {"日", "一", "二", "三", "四", "五", "六"};
- String week = " 周" + weeks[weekday() - 1];
-
- //年月日 星期
- clk.createSprite(160, 22);
- clk.fillSprite(TFT_WHITE);
- clk.setTextColor(TFT_BLACK, TFT_WHITE);
- String str = String(year()) + "年" + String(month()) + "月" + String(day()) + "日" + week;
- clk.drawString(str, 2, 4);
- clk.unloadFont();
- clk.pushSprite(0, 0);
- clk.deleteSprite();
-
- /***顶部***/
- }
- void setup()
- {
- Serial.begin(9600);
- tft.init();
- tft.setRotation(1); // 屏幕旋转方向0-3 镜像 4-7
- tft.fillScreen(TFT_BLACK);
- pinMode(D0, OUTPUT);
- digitalWrite(D0, HIGH);
- // wifi 配置 可添加多个
- wifis.addAP("wifi名称", "密码");
- wifis.addAP("wifi名称", "密码");
- loading(60); // 进度条加载到60
- if (wifis.run() == WL_CONNECTED)
- {
- Serial.println("connected wifi");
- loading(100); //进度条加载完成
- Udp.begin(localPort);
- setSyncProvider(getNtpTime);
- setSyncInterval(300);
- loading(maxLoad); //进度条加载完成
- tft.fillScreen(TFT_WHITE);
- TJpgDec.setJpgScale(1);
- TJpgDec.setSwapBytes(true);
- TJpgDec.setCallback(tft_output);
- tft.drawLine(30, 22, 30, 80, TFT_BLACK);
- tft.drawFastHLine(0, 22, 160, TFT_BLACK);
- tft.drawFastHLine(30, 57, 130, TFT_BLACK);
- weatherTime = millis();
- getCityWeater();
- }
- else
- {
- }
- }
- void loop()
- {
- digitalClockDisplay();
- if (millis() - weatherTime > 300000)
- { // 5分钟更新一次天气
- weatherTime = millis();
- getCityWeater();
- }
- spaceAnimation();
- // scale.power_up();
- }
-
- /*-------- NTP code ----------*/
-
- const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
- byte packetBuffer[NTP_PACKET_SIZE]; // buffer to hold incoming & outgoing packets
-
- time_t getNtpTime()
- {
- IPAddress ntpServerIP; // NTP server's ip address
- while (Udp.parsePacket() > 0)
- ; // discard any previously received packets
- Serial.println("Transmit NTP Request");
- // get a random server from the pool
- WiFi.hostByName(ntpServerName, ntpServerIP);
- Serial.print(ntpServerName);
- Serial.print(": ");
- Serial.println(ntpServerIP);
- sendNTPpacket(ntpServerIP);
- uint32_t beginWait = millis();
- while (millis() - beginWait < 1500)
- {
- int size = Udp.parsePacket();
- if (size >= NTP_PACKET_SIZE)
- {
- Serial.println("Receive NTP Response");
- Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
- unsigned long secsSince1900;
- // convert four bytes starting at location 40 to a long integer
- secsSince1900 = (unsigned long)packetBuffer[40] << 24;
- secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
- secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
- secsSince1900 |= (unsigned long)packetBuffer[43];
- return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
- }
- }
- Serial.println("No NTP Response :-(");
- return 0; // return 0 if unable to get the time
- }
- // send an NTP request to the time server at the given address
- void sendNTPpacket(IPAddress &address)
- {
- // set all bytes in the buffer to 0
- memset(packetBuffer, 0, NTP_PACKET_SIZE);
- // Initialize values needed to form NTP request
- // (see URL above for details on the packets)
- packetBuffer[0] = 0b11100011; // LI, Version, Mode
- packetBuffer[1] = 0; // Stratum, or type of clock
- packetBuffer[2] = 6; // Polling Interval
- packetBuffer[3] = 0xEC; // Peer Clock Precision
- // 8 bytes of zero for Root Delay & Root Dispersion
- packetBuffer[12] = 49;
- packetBuffer[13] = 0x4E;
- packetBuffer[14] = 49;
- packetBuffer[15] = 52;
- // all NTP fields have been given values, now
- // you can send a packet requesting a timestamp:
- Udp.beginPacket(address, 123); // NTP requests are to port 123
- Udp.write(packetBuffer, NTP_PACKET_SIZE);
- Udp.endPacket();
- }
- bool parseUserData(String content, struct WeatherData *weatherData)
- {
- // -- 根据我们需要解析的数据来计算JSON缓冲区最佳大小
- // 如果你使用StaticJsonBuffer时才需要
- // const size_t BUFFER_SIZE = 1024;
- // 在堆栈上分配一个临时内存池
- // StaticJsonBuffer<BUFFER_SIZE> jsonBuffer;
- // -- 如果堆栈的内存池太大,使用 DynamicJsonBuffer jsonBuffer 代替
- DynamicJsonDocument doc(1024);
- auto error = deserializeJson(doc, content);
- if (error)
- {
- Serial.print(F("deserializeJson() failed with code "));
- Serial.println(error.c_str());
- return false;
- }
- JsonObject obj = doc.as<JsonObject>();
- //复制我们感兴趣的字符串
- strcpy(weatherData->city, obj["results"][0]["location"]["name"]);
- strcpy(weatherData->weather, obj["results"][0]["now"]["text"]);
- strcpy(weatherData->temp, obj["results"][0]["now"]["temperature"]);
- strcpy(weatherData->udate, obj["results"][0]["last_update"]);
- // -- 这不是强制复制,你可以使用指针,因为他们是指向“内容”缓冲区内,所以你需要确保
- // 当你读取字符串时它仍在内存中
- return true;
- }
- // 获取城市天气
- void getCityWeater()
- {
- //创建 HTTPClient 对象
- HTTPClient httpClient;
- // https 请求报400
- httpClient.begin(espClient, "api.seniverse.com", 80, "/v3/weather/now.json?key=Sy_3POubsgptOeUau&location=wenzhou&language=zh-Hans&unit=c", false);
- //启动连接并发送HTTP请求
- int httpCode = httpClient.GET();
- Serial.print("request weather data:");
- //如果服务器响应OK则显示
- if (httpCode == HTTP_CODE_OK)
- {
- Serial.println("request weather data success");
- String response = httpClient.getString();
- if (parseUserData(response, &weatherData))
- { //解析响应内容
- clk.createSprite(125, 22);
- clk.loadFont(WEATHER_16);
- clk.fillSprite(TFT_WHITE);
- clk.setTextColor(TFT_BLACK, TFT_WHITE);
- byte xpos = 3, ypos = 2;
- xpos += clk.drawString(weatherData.city, xpos, ypos);
- xpos += 4;
- xpos += clk.drawString(weatherData.weather, xpos, ypos);
- xpos += 5;
- clk.setTextColor(TFT_RED, TFT_WHITE);
- String temp = String(weatherData.temp) + "℃";
- xpos += clk.drawString(temp, xpos, ypos);
- clk.pushSprite(31, 62);
- clk.deleteSprite();
- }
- }
- else
- {
- Serial.print("request weather data fail:");
- Serial.println(httpCode);
- }
- httpClient.end(); //关闭ESP8266与服务器连接
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。