当前位置:   article > 正文

基于ESP32的透明电视网络相册(可网页配置WIFI)_esp32电子相册

esp32电子相册

1 小电视目前的功能有:

  1. 显示当地今天和明天的天气、温度
  2. 显示年月日星期及时间
  3. 循环播放128*128像素的照片
  4. 通过网页切换连接不同的wifi
  5. 通过网页上传功能3中的照片

小电视网络相册主要通过ESP32模组+TFT显示屏+分光棱镜+SD卡模块+三轴加速度计陀螺仪实现以上功能。其中ESP32模组用于数据处理,TFT+分光棱镜用于立体显示图像,SD卡模块用于存储要显示的图片,三轴加速度计陀螺仪用于实现主要功能的切换。

主要参考了稚晖君的HoloCubic、DID迪的透明小电视教程以及哔哩哔哩@退役熬夜选手的WIFI天气+相册,代码在各位大佬的基础上进行了修改并增加新功能,感谢各位大佬的资料。

本文将分为硬件部分和软件部分来介绍此网络小电视。

2 硬件部分

本项目所包含的硬件有:Goouuu-ESP32模块开发板、分光棱镜 25.4mm实验教学仪器半透半反1:1、高清OLED液晶屏 st7735 1.44寸、基于SPI的SD卡读写模块、闪迪SD卡、MPU-6050模块 三轴加速度陀螺仪、带帽大按键轻触开关模块以及连接杜邦导线,这里由于我没有3D打印装置,因此我在淘宝上买了一种类似乐高的玩具积木打造了一个外壳,会3D打印的朋友可以通过3D打印来打造外壳。

下面上某宝链接

分光棱镜https://item.taobao.com/item.htm?spm=a1z09.2.0.0.61692e8dh4MbaE&id=583924549232&_u=jvq1ceff198

ESP32模块开发板

https://item.taobao.com/item.htm?spm=a1z09.2.0.0.61692e8dh4MbaE&id=547082402418&_u=jvq1cef91de

开关模块

https://item.taobao.com/item.htm?spm=a1z09.2.0.0.61692e8dh4MbaE&id=651180760420&_u=jvq1cef0353

OLED显示屏

https://detail.tmall.com/item.htm?id=618484375951&spm=a1z09.2.0.0.61692e8dh4MbaE&_u=jvq1cefdd7f

SD卡模块

https://item.taobao.com/item.htm?spm=a1z09.2.0.0.61692e8dh4MbaE&id=580583745767&_u=jvq1cef076f

三轴陀螺仪

https://item.taobao.com/item.htm?spm=a1z09.2.0.0.61692e8dh4MbaE&id=16630417522&_u=jvq1cef37b8

其他物品某宝都有卖的,大家可以随便买。

2.1 电子硬件连接介绍

连接关系如下图所示:

这里的连接方式和软件以及芯片驱动程序强相关的,如果大家想要更改管脚,需要更改芯片驱动程序及代码即可。

OLED屏的驱动芯片st7735以及SD卡模块用的是SPI传输,mpu6050用的是I2C传输。之前看到网上说SPI可以分时复用,如果分时复用可以节约一些管脚,我的模块里并没有这样做,因为怕读写SD卡会影响屏幕显示,如果有大佬可以做分时复用的接口可以教一下小弟,非常感谢!

2.2 分光棱镜介绍

分光棱镜是一种用于分离光线的水平偏振和垂直偏振的光学元件,是由两个三棱镜组成,中间镀制了多层膜结构,其中透射和反射是1:1,这样可以保证人眼看到的光线一半是反射过来的光线,一半是投射过来的光线,可以达到透明显示的效果。

 由于我们的屏幕在下方,因此分光棱镜对我们屏幕的投影是上下颠倒的,因此需要在程序中设置镜像显示,在第3章软件部分我会详细介绍。

3 软件部分

3.1 软件环境搭建

1.官网下载Arduino 1.8.15 https://www.arduino.cc/en/software

2.安装ESP32开发包 文件->首选项->附加开发板管理器输入

https://dl.espressif.com/dl/package_esp32_index.json

3.重启Arduino 工具->开发板->开发板管理 搜索ESP32下载

也可以下载https://pan.baidu.com/s/1DQ2MfChzsLiTKjYmowqEXA?pwd=edcv 提取码edcv

放到Arduino安装目录/hardware下

4. 工具-> 下载速度配置成115200 频率为80MHz.

此时开发环境就搭建好了。

3.2 硬件模块测试

此时我们把我们需要的硬件模块买回来,在淘宝商铺那里都有对应的测试程序,下载下来跑一下,看看各个模块能不能正常运转,如果可以,那你很幸运,可以开始编写代码了,如果有问题要联系商家第一时间更换模块。(PS:不然等调代码的时候才发现模块坏了就比较麻烦,也浪费时间)

另外也要注意管脚约束的问题,定义的管脚和实际连接的管脚一定要一样。

3.3 代码说明

代码框架如图所示:

模块初始化步骤用于初始化ESP32、SD卡、mpu6050、TFT显示屏以及网络。

开机动画是自己找的GIF拆分成单张图片,再保存在SD卡中并通过TFT屏显示。

配置WIFI和上传照片是通过简单的网页实现的。

天气界面我用的是心知天气的URL。

初始化代码如下所示(代码都有注释我就不一一介绍了):

  1. void setup() {
  2. mpu6050_setup();//6050陀螺仪初始化
  3. TFT_setup();//TFT初始化
  4. SD_setup();//SD初始化
  5. magic(); //开机动画
  6. WIFI_setup();//WIFI初始化
  7. client.setTimeout(5000);//设置天气服务器连接超时时间
  8. //EEPROM
  9. EEPROM.begin(512);
  10. tft_num = EEPROM.read(20);
  11. Serial.print("the tft number is");Serial.println(tft_num);
  12. WEB_setup();//网页初始化
  13. timeClient.begin();
  14. timeClient.setTimeOffset(28800); //设置偏移时间(以秒为单位)以调整时区
  15. }
  16. void mpu6050_setup(){
  17. #define LED_PIN 13
  18. Wire.begin();
  19. Serial.begin(115200);
  20. Serial.println("Initializing I2C devices...");
  21. accelgyro.initialize();
  22. Serial.println("Testing device connections...");
  23. Serial.println(accelgyro.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed");
  24. pinMode(LED_PIN, OUTPUT);
  25. }
  26. // TFT初始化
  27. //####################################################################################################
  28. void TFT_setup(){
  29. tft.init(); //初始化显示寄存器
  30. tft.fillScreen(TFT_WHITE); //屏幕颜色
  31. tft.setTextColor(TFT_BLACK); //设置字体颜色黑色
  32. tft.setCursor(15, 100, 1); //设置文字开始坐标(15,30)及1号字体
  33. tft.setTextSize(1);
  34. tft.setSwapBytes(true);
  35. tft.setRotation(4);//屏幕内容镜像显示或者旋转屏幕0-4 ST7735_Rotation中设置
  36. }
  37. // SD初始化
  38. //####################################################################################################
  39. void SD_setup(){
  40. sdSPI.begin(SD_SCLK, SD_MISO, SD_MOSI, SD_CS);
  41. if (!SD.begin(SD_CS, sdSPI))
  42. {
  43. Serial.println("存储卡挂载失败");
  44. return;
  45. }
  46. uint8_t cardType = SD.cardType();
  47. if (cardType == CARD_NONE)
  48. {
  49. Serial.println("未连接存储卡");
  50. return;
  51. }
  52. else if (cardType == CARD_MMC)
  53. {
  54. Serial.println("挂载了MMC卡");
  55. }
  56. else if (cardType == CARD_SD)
  57. {
  58. Serial.println("挂载了SDSC卡");
  59. }
  60. else if (cardType == CARD_SDHC)
  61. {
  62. Serial.println("挂载了SDHC卡");
  63. }
  64. else
  65. {
  66. Serial.println("挂载了未知存储卡");
  67. }
  68. //打印存储卡信息
  69. Serial.printf("存储卡总大小是: %lluMB \n", SD.cardSize() / (1024 * 1024)); // "/ (1024 * 1024)"可以换成">> 20"
  70. Serial.printf("文件系统总大小是: %lluB \n", SD.totalBytes());
  71. Serial.printf("文件系统已用大小是: %lluB \n", SD.usedBytes());
  72. }
  73. /*******************开机画面****************/
  74. int image_num = 1;
  75. void magic() {
  76. //播放magic,共128帧,每秒30帧
  77. while(image_num<=128)
  78. {
  79. drawSdJpeg(image_num, 0, 0, 2); // This draws a jpeg pulled off the SD Card
  80. image_num=image_num+1;
  81. }
  82. }
  83. // WIFI初始化
  84. //####################################################################################################
  85. void wifi_Config()
  86. {
  87. Serial.println("scan start");
  88. // 扫描附近WiFi
  89. int n = WiFi.scanNetworks();
  90. Serial.println("scan done");
  91. if (n == 0) {
  92. Serial.println("no networks found");
  93. scanNetworksID = "no networks found";
  94. } else {
  95. Serial.print(n);
  96. Serial.println(" networks found");
  97. for (int i = 0; i < n; ++i) {
  98. // Print SSID and RSSI for each network found
  99. Serial.print(i + 1);
  100. Serial.print(": ");
  101. Serial.print(WiFi.SSID(i));
  102. Serial.print(" (");
  103. Serial.print(WiFi.RSSI(i));
  104. Serial.print(")");
  105. Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? " " : "*");
  106. scanNetworksID += "<P>" + WiFi.SSID(i) + "</P>";
  107. delay(10);
  108. }
  109. }
  110. Serial.println("");
  111. WiFi.mode(WIFI_AP);//配置为AP模式
  112. boolean result = WiFi.softAP(AP_SSID, AP_PASS); //开启WIFI热点
  113. if (result)
  114. {
  115. IPAddress myIP = WiFi.softAPIP();
  116. //打印相关信息
  117. Serial.println("");
  118. Serial.print("Soft-AP IP address = ");
  119. Serial.println(myIP);
  120. Serial.println(String("MAC address = ") + WiFi.softAPmacAddress().c_str());
  121. Serial.println("waiting ...");
  122. } else { //开启热点失败
  123. Serial.println("WiFiAP Failed");
  124. delay(3000);
  125. ESP.restart(); //复位esp32
  126. }
  127. if (MDNS.begin("esp32")) {
  128. Serial.println("MDNS responder started");
  129. }
  130. //首页
  131. server.on("/", []() {
  132. server.send(200, "text/html", ROOT_HTML + scanNetworksID + "</body></html>");
  133. });
  134. //连接
  135. server.on("/connect", []() {
  136. server.send(200, "text/html", "<html><body><font size=\"10\">successd,wifi connecting...<br />Please close this page manually.</font></body></html>");
  137. WiFi.softAPdisconnect(true);
  138. //获取输入的WIFI账户和密码
  139. wifi_ssid = server.arg("ssid");
  140. wifi_pass = server.arg("pass");
  141. city_location = server.arg("city");
  142. server.close();
  143. WiFi.softAPdisconnect();
  144. Serial.println("WiFi Connect SSID:" + wifi_ssid + " PASS:" + wifi_pass);
  145. //设置为STA模式并连接WIFI
  146. WiFi.mode(WIFI_STA);
  147. WiFi.begin(wifi_ssid.c_str(), wifi_pass.c_str());
  148. uint8_t Connect_time = 0; //用于连接计时,如果长时间连接不成功,复位设备
  149. while (WiFi.status() != WL_CONNECTED) { //等待WIFI连接成功
  150. delay(500);
  151. Serial.print(".");
  152. Connect_time ++;
  153. if (Connect_time > 80) { //长时间连接不上,复位设备
  154. Serial.println("Connection timeout, check input is correct or try again later!");
  155. delay(3000);
  156. ESP.restart();
  157. }
  158. }
  159. Serial.println("");
  160. Serial.println("WIFI Config Success");
  161. Serial.printf("SSID:%s", WiFi.SSID().c_str());
  162. Serial.print(" LocalIP:");
  163. Serial.print(WiFi.localIP());
  164. Serial.println("");
  165. tft.fillScreen(TFT_WHITE);
  166. tft.setCursor(20, 100, 1); //设置文字开始坐标(20,30)及1号字体
  167. tft.setTextSize(1);
  168. tft.println("WiFi Connected!");
  169. drawSdJpeg(6, 0, 0, 5);
  170. });
  171. }
  172. //用于上电自动连接WiFi
  173. bool AutoConfig()
  174. {
  175. WiFi.begin();
  176. for (int i = 0; i < 20; i++)
  177. {
  178. int wstatus = WiFi.status();
  179. uint8_t wifi_image_num = 1;
  180. if (wstatus == WL_CONNECTED)
  181. {
  182. Serial.println("WIFI SmartConfig Success");
  183. Serial.printf("SSID:%s", WiFi.SSID().c_str());
  184. Serial.printf(", PSW:%s\r\n", WiFi.psk().c_str());
  185. Serial.print("LocalIP:");
  186. Serial.print(WiFi.localIP());
  187. Serial.print(" ,GateIP:");
  188. Serial.println(WiFi.gatewayIP());
  189. tft.fillScreen(TFT_WHITE);
  190. tft.println("Connecting Wifi...");
  191. tft.setSwapBytes(true); //使图片颜色由RGB->BGR
  192. while(wifi_image_num<=5)
  193. {
  194. drawSdJpeg(wifi_image_num, 0, 0, 5); // This draws a jpeg pulled off the SD Card
  195. wifi_image_num=wifi_image_num+1;
  196. delay(400);
  197. }
  198. tft.fillScreen(TFT_WHITE);
  199. tft.setCursor(20, 100, 1); //设置文字开始坐标(20,30)及1号字体
  200. tft.setTextSize(1);
  201. tft.println("WiFi Connected!");
  202. drawSdJpeg(6, 0, 0, 5);
  203. delay(600);
  204. return true;
  205. }
  206. else
  207. {
  208. Serial.print("WIFI AutoConfig Waiting......");
  209. Serial.println(wstatus);
  210. delay(1000);
  211. }
  212. }
  213. Serial.println("WIFI AutoConfig Faild!" );
  214. return false;
  215. }
  216. void WIFI_setup() {
  217. pinMode(RESET_PIN, INPUT_PULLUP);
  218. // 连接WiFi
  219. if (!AutoConfig())
  220. {
  221. wifi_Config();
  222. }
  223. //用于删除已存WiFi
  224. if (digitalRead(RESET_PIN) == LOW) {
  225. delay(1000);
  226. esp_wifi_restore();
  227. delay(10);
  228. ESP.restart(); //复位esp32
  229. }
  230. //WiFi.mode(WIFI_STA);
  231. //WiFi.begin(wifi_ssid.c_str(), wifi_pass.c_str());
  232. }
  233. // web服务初始化
  234. //####################################################################################################
  235. void WEB_setup(){
  236. server.on("/", HTTP_GET, handleRoot);//发送开始获取
  237. //把上传的数据保存到spiffs
  238. server.on("/", HTTP_POST,[](){uplaodFinish();}, handleFileUpload);//下载文件
  239. //访问的url没有在找spiffs中找到回复404
  240. server.onNotFound([](){if(!handleFileRead(server.uri()))server.send(404, "text/plain", "FileNotFound");});
  241. server.begin();//网络服务开启
  242. }

主循环中有3个界面,时钟界面、照片界面和天气界面。当界面加载完成后需要查找陀螺仪是否有移动,检查上传的位置坐标来决定是否切换界面。同时,需要全程开启网页上传图片的网页服务,以便随时上传图片。上传图片有个超时计时器t,当超时之后会返回显示界面。

主功能代码如下所示:

  1. // 主循环
  2. //####################################################################################################
  3. void loop(){
  4. if(upload_flag == false)
  5. {
  6. if(flag_finsh == 0)//页面未加载
  7. {
  8. switch(flag_page % 3)
  9. {
  10. case 0://时钟界面
  11. {
  12. tft.fillScreen(0x0000);//背景颜色
  13. flag_finsh = 1;//完成加载
  14. while(mpu_update()==9)
  15. {display_time();}
  16. }
  17. break;
  18. case 1://天气界面
  19. {
  20. //tft.fillScreen(0x0000);//背景颜色
  21. flag_finsh = 1;//完成加载
  22. weather_api();
  23. }
  24. break;
  25. case 2://照片界面
  26. {
  27. flag_finsh = 1;//完成加载
  28. jpg_draw(flag_pic);
  29. limit();//形成循环
  30. while(timer<=2500)
  31. {
  32. timer++;
  33. if(mpu_update() >= 3 && mpu_update() <=6)
  34. {
  35. break;
  36. }
  37. }
  38. timer=0;
  39. }
  40. break;
  41. }
  42. }
  43. else if(flag_finsh == 1)//页面已加载,进入手势扫描 和网络服务处理
  44. {
  45. uint8_t num = 0;
  46. num = mpu_update();//扫描手势传感器
  47. if(num >= 3 && num <=6)
  48. {
  49. if(num == Right)
  50. {
  51. flag_page++;
  52. }
  53. else if(num == Left)
  54. {
  55. flag_page--;
  56. }
  57. if(flag_page % 3==2)//处在照片界面
  58. {
  59. if(num == Up)
  60. {
  61. flag_pic++;
  62. }
  63. else if(num == Down)
  64. {
  65. flag_pic--;
  66. }
  67. }
  68. server.handleClient();
  69. flag_finsh =0;
  70. Serial.print(flag_page);
  71. }
  72. else if(num == 9)
  73. {
  74. if (flag_page % 3==2)
  75. {
  76. flag_pic++;
  77. flag_finsh =0;
  78. }
  79. }
  80. }
  81. }
  82. server.handleClient();//上传图片服务处理
  83. if(upload_flag == true)//防止未上传超时
  84. {
  85. t++;
  86. delay(5);
  87. server.handleClient();//上传图片服务处理
  88. }
  89. else
  90. {
  91. t=0;
  92. }
  93. if(t > 6000)
  94. {
  95. upload_flag = false;
  96. t = 0;
  97. }
  98. //Serial.print(t);
  99. }

Mpu6050扫描的代码中定义了几个参数,用于表示位置到底是向左、向右、向上、向下还是没变,作为函数反馈值返回回去。

  1. // MPU6050传感器扫描
  2. //####################################################################################################
  3. uint8_t mpu_update()
  4. {
  5. uint8_t num = 0;
  6. uint8_t data = 0, successful;
  7. accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
  8. if(gy>5000)
  9. {
  10. data = GES_RIGHT_FLAG;
  11. }
  12. else if(gy<-5000)
  13. {
  14. data = GES_LEFT_FLAG;
  15. }
  16. else if(gx>5000)
  17. {
  18. data = GES_UP_FLAG;
  19. }
  20. else if(gx<-5000)
  21. {
  22. data = GES_DOWN_FLAG;
  23. }
  24. successful = accelgyro.testConnection();
  25. if (successful)
  26. {
  27. switch (data) // When different gestures be detected, the variable 'data' will be set to different values by paj7620ReadReg(0x43, 1, &data).
  28. {
  29. case GES_RIGHT_FLAG:
  30. {
  31. // Serial.println("Right");
  32. num = Right;
  33. }
  34. break;
  35. case GES_LEFT_FLAG:
  36. {
  37. //Serial.println("Left");
  38. num = Left;
  39. }
  40. break;
  41. case GES_UP_FLAG:
  42. {
  43. //Serial.println("Up");
  44. num = Up;
  45. }
  46. break;
  47. case GES_DOWN_FLAG:
  48. {
  49. //Serial.println("Down");
  50. num = Down;
  51. }
  52. break;
  53. default:
  54. //accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
  55. num=not_change;
  56. //Serial.println("num not change");
  57. break;
  58. }
  59. }
  60. return num;
  61. delay(100);
  62. }

时间界面的程序,显示汉字是通过字模程序来实现的。

  1. /*******************时间界面显示****************/
  2. void showtext(int16_t x,int16_t y,uint8_t font,uint8_t s,uint16_t fg,uint16_t bg,const String str)
  3. {
  4. //设置文本显示坐标,和文本的字体,默认以左上角为参考点,
  5. tft.setCursor(x, y, font);
  6. // 设置文本颜色为白色,文本背景黑色
  7. tft.setTextColor(fg,bg);
  8. //设置文本大小,文本大小的范围是1-7的整数
  9. tft.setTextSize(s);
  10. // 设置显示的文字,注意这里有个换行符 \n 产生的效果
  11. tft.println(str);
  12. }
  13. /*******************单个汉字显示****************/
  14. void showMyFont(int32_t x, int32_t y, const char c[3], uint32_t color) {
  15. for (int k = 0; k < 32; k++)// 根据字库的字数调节循环的次数
  16. if (hanzi[k].Index[0] == c[0] && hanzi[k].Index[1] == c[1] && hanzi[k].Index[2] == c[2])
  17. { tft.drawBitmap(x, y, hanzi[k].hz_Id, hanzi[k].hz_width, 16, color);
  18. }
  19. }
  20. /*******************整句汉字显示****************/
  21. void showMyFonts(int32_t x, int32_t y, const char str[], uint32_t color) { //显示整句汉字,字库比较简单,上下、左右输出是在函数内实现
  22. int x0 = x;
  23. for (int i = 0; i < strlen(str); i += 3) {
  24. showMyFont(x0, y, str+i, color);
  25. x0 += 17;
  26. }
  27. }
  28. void show_time(uint16_t fg,uint16_t bg, String currentTime, String currentDate, int tm_Year,const char* week)
  29. {
  30. //tft.fillRect(10, 55, 64, 64, bg);
  31. tft.setSwapBytes(true); //使图片颜色由RGB->BGR
  32. drawSdJpeg(face_num,0,55,3);//加载今天天气
  33. delay(100);
  34. face_num++;
  35. if(face_num>58){face_num=1;}
  36. tft.drawFastHLine(10, 53, 108, tft.alphaBlend(0, bg, fg));
  37. showtext(15,5,2,3,fg,bg,currentTime);
  38. showtext(75,60,1,2,fg,bg, String(tm_Year));
  39. showtext(75,80,1,2,fg,bg, currentDate);
  40. showMyFonts(80, 100, week, TFT_YELLOW);
  41. }
  42. void display_time()
  43. {
  44. timeClient.update();
  45. unsigned long epochTime = timeClient.getEpochTime();
  46. currentSec = epochTime;
  47. String formattedTime = timeClient.getFormattedTime();
  48. int tm_Hour = timeClient.getHours();
  49. int tm_Minute = timeClient.getMinutes();
  50. int tm_Second = timeClient.getSeconds();
  51. String weekDay = weekDays[timeClient.getDay()];
  52. char week[weekDay.length() + 1];
  53. weekDay.toCharArray(week,weekDay.length() + 1);
  54. struct tm *ptm = gmtime ((time_t *)&epochTime);
  55. int monthDay = ptm->tm_mday;
  56. int tm_Month = ptm->tm_mon+1;
  57. String currentMonthName = months[tm_Month-1];
  58. int tm_Year = ptm->tm_year+1900;
  59. String currentDate = String(tm_Month) + "/" + String(monthDay);
  60. String currentTime, hour, minute;
  61. if (tm_Hour < 10)
  62. hour = "0" + String(tm_Hour);
  63. else
  64. hour = String(tm_Hour);
  65. if (tm_Minute < 10)
  66. minute = "0" + String(tm_Minute);
  67. else
  68. minute = String(tm_Minute);
  69. currentTime = hour + ":" + minute;
  70. tft.setSwapBytes(true);
  71. if(epochTime - currentSec >= 5)
  72. {currentSec = timeClient.getEpochTime();}
  73. else
  74. {
  75. show_time(TFT_WHITE, TFT_BLACK, currentTime, currentDate, tm_Year, week); // 显示时间界面
  76. }
  77. delay(50);
  78. }

心知天气API获取今明天气,最高温度,最低温度,通过图片和数字显示天气和温度。

  1. // 显示数字
  2. //####################################################################################################
  3. void tft_showstring(int x,int y,int c,String z){
  4. tft.setCursor(x, y, c);
  5. tft.setTextSize(1);
  6. tft.setTextColor(TFT_WHITE);
  7. tft.println(z);
  8. }
  9. // JOSN解析函数
  10. //####################################################################################################
  11. void parseUserData(String content){ // Json数据解析并串口打印.可参考https://www.bilibili.com/video/av65322772
  12. int weather_num[2];
  13. const size_t capacity = JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(1) + 2*JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(6) + 210;
  14. DynamicJsonBuffer jsonBuffer(1660);
  15. JsonObject& root = jsonBuffer.parseObject(content);
  16. JsonObject& results_0 = root["results"][0];
  17. JsonObject& results_daily0 = results_0["daily"][0];
  18. JsonObject& results_daily1 = results_0["daily"][1];
  19. JsonObject& results_daily2 = results_0["daily"][2];
  20. const char* results_0_now_data = results_daily0["date"];//天气时间
  21. const char* results_0_now_text = results_daily0["text_day"];//天气时间
  22. const char* results_0_now_code = results_daily0["code_day"];//天气现象数值
  23. const char* results_1_now_code = results_daily1["code_day"];//天气现象数值
  24. String high_tem_0= results_daily0["high"];//天气温度最高数值
  25. String high_tem_1= results_daily1["high"];//天气温度最高数值
  26. String low_tem_0= results_daily0["low"];//天气温度最低数值
  27. String low_tem_1= results_daily1["low"];//天气温度最低数值
  28. String hum= results_daily0["humidity"];//天气湿度数值
  29. const char* wind_speed0 = results_daily0["wind_speed"];//天气风速数值
  30. const char* wind_speed1 = results_daily1["wind_speed"];//天气风速数值
  31. const char* rain= results_daily0["rainfall"];//天气降雨量数值
  32. //atoi()函数将字符转换为数字
  33. weather_num[0] = atoi(results_0_now_code);
  34. weather_num[1] = atoi(results_1_now_code);
  35. drawSdJpeg(weather_num[0],0,0,1);//加载今天天气
  36. drawSdJpeg(weather_num[1],35,88,4);//加载明天天气
  37. tft_showstring(5,19,4,high_tem_0+"C");
  38. tft_showstring(10,45,2,low_tem_0+"C");//显示今日温度
  39. tft_showstring(90,90,2,high_tem_1+"C");
  40. tft_showstring(90,112,1,"-"+low_tem_1+"C");//显示明日温度
  41. }
  42. // 天气获取并显示
  43. //####################################################################################################
  44. void weather_api(){//天气API获取
  45. if(client.connect("api.seniverse.com",80)==1)//连接服务器并判断是否连接成功,若成功就发送GET 请求数据下发
  46. { //换成你自己在心知天气申请的私钥//改成你所在城市的拼音
  47. //client.print("GET /v3/weather/now.json?key=*********&location=beijing&language=zh-Hans&unit=c HTTP/1.1\r\n"); //心知天气的URL格式
  48. client.print("Host:api.seniverse.com\r\n");
  49. client.print("Accept-Language:zh-cn\r\n");
  50. client.print("Connection:close\r\n\r\n"); //向心知天气的服务器发送请求。
  51. String status_code = client.readStringUntil('\r'); //读取GET数据,服务器返回的状态码,若成功则返回状态码200
  52. //Serial.println(status_code);
  53. /*
  54. * {"results":
  55. * [
  56. * {"location":
  57. * {"id":"WX4FBXXFKE4F","name":"北京","country":"CN","path":"北京,北京,中国","timezone":"Asia/Shanghai","timezone_offset":"+08:00"}
  58. * ,"now":{"text":"晴","code":"1","temperature":"10"},"last_update":"2020-04-04T23:10:00+08:00"}
  59. * ]
  60. * }
  61. */
  62. if(client.find("{")==1)//跳过返回的数据头,直接读取后面的JSON数据
  63. //if(client.available())
  64. {
  65. String json_from_server=client.readStringUntil(']'); //读取返回的JSON数据
  66. json_from_server = "{"+json_from_server+"]}]}";
  67. Serial.println(json_from_server);
  68. parseUserData(json_from_server); //将读取的JSON数据,传送到JSON解析函数中进行显示。
  69. }
  70. else
  71. {
  72. Serial.println("Not find.");
  73. }
  74. }
  75. else
  76. {
  77. Serial.println("connection failed this time");
  78. delay(500); //请求失败等5秒
  79. }
  80. client.stop(); //关闭HTTP客户端,采用HTTP短链接,数据请求完毕后要客户端要主动断开
  81. }

在显示照片的过程中,加入了计时器timer可以让照片每8s自动切换到下一张,limit函数用于用于控制照片显示当显示到最后一张时又会回到第一张。同时照片的切换也可以手动控制,当检测到陀螺仪的位置信息为朝上时,照片会切换至下一张,当检测到陀螺仪的位置信息为朝下时,照片会切换至上一张,照片存放的位置参考drawSdJpeg函数。

  1. void jpg_draw(int bmp_screen_num){
  2. //tft.setRotation(2); //设置旋转
  3. tft.fillScreen(0x0000);//背景颜色
  4. drawSdJpeg(bmp_screen_num,0,0,0); // This draws a jpeg pulled off the SD Card
  5. //delay(5000);
  6. }
  7. void drawSdJpeg(int bmp_screen_num, int xpos, int ypos,int mode_pic) {
  8. char filename1[20];
  9. int mode_ = 0;
  10. switch(mode_pic){
  11. case 0://加载照片
  12. //filename = "/loge"+String(bmp_screen_num)+".jpg";
  13. sprintf(filename1,"/loge%d.jpg",bmp_screen_num);
  14. mode_ =1;
  15. break;
  16. case 1://加载128x128天气图片
  17. //filename = "/img/64x64/"+String(bmp_screen_num)+".jpg";//重定向文件
  18. sprintf(filename1,"/img/128x128/%d.jpg",bmp_screen_num);
  19. break;
  20. case 2://开机画面
  21. //filename = "/magic/"+String(bmp_screen_num)+".jpg";
  22. sprintf(filename1,"/magic/%d.jpg",bmp_screen_num);
  23. break;
  24. case 3://加载face图片
  25. sprintf(filename1,"/face/%d.jpg",bmp_screen_num);
  26. break;
  27. case 4://加载40x40天气图片
  28. //filename = "/img/40x40/"+String(bmp_screen_num)+".jpg";//重定向文件
  29. sprintf(filename1,"/img/40x40/%d.jpg",bmp_screen_num);
  30. break;
  31. case 5://WIFI连接
  32. //filename = "/wifi/"+String(bmp_screen_num)+".jpg";//重定向文件
  33. sprintf(filename1,"/wifi/%d.jpg",bmp_screen_num);
  34. break;
  35. }
  36. const char *filename = filename1;
  37. File jpegFile = SD.open(filename1,FILE_READ); // or, file handle reference for SD library
  38. if ( !jpegFile ) {
  39. Serial.print("ERROR: File \""); Serial.print(filename); Serial.println ("\" not found!");
  40. return;
  41. }
  42. Serial.println("===========================");
  43. Serial.print("Drawing file: "); Serial.println(filename);
  44. Serial.println("===========================");
  45. // Use one of the following methods to initialise the decoder:
  46. boolean decoded = JpegDec.decodeSdFile(jpegFile); // Pass the SD file handle to the decoder,
  47. //boolean decoded = JpegDec.decodeSdFile(filename); // or pass the filename (String or character array)
  48. if (decoded) {
  49. // print information about the image to the serial port
  50. jpegInfo();
  51. // render the image onto the screen at given coordinates
  52. jpegRender(xpos, ypos,mode_);
  53. }
  54. else {
  55. Serial.println("Jpeg file format not supported!");
  56. }
  57. }
  58. //####################################################################################################
  59. // 在TFT上绘图片
  60. //####################################################################################################
  61. void jpegRender(int xpos, int ypos,int mode_) {
  62. //jpegInfo(); // Print information from the JPEG file (could comment this line out)
  63. uint16_t *pImg;
  64. uint32_t mcu_w = JpegDec.MCUWidth;
  65. uint32_t mcu_h = JpegDec.MCUHeight;
  66. uint32_t max_x = JpegDec.width;
  67. uint32_t max_y = JpegDec.height;
  68. //调整选转角度并且居中显示
  69. if(mode_ == 1)
  70. {
  71. if(max_x > max_y)
  72. {
  73. //tft.setRotation(1);
  74. xpos = (128-max_x)/2; //居中显示
  75. ypos = (128-max_y)/2; //居中显示
  76. if(xpos < 0 ||xpos > 128)
  77. xpos = 0;
  78. if(ypos < 0 || ypos >128)
  79. ypos = 0;
  80. }
  81. else if(max_x <= max_y)
  82. {
  83. //tft.setRotation(2);
  84. xpos = (128-max_x)/2; //居中显示
  85. ypos = (128-max_y)/2; //居中显示
  86. if(xpos < 0 ||xpos > 128)
  87. xpos = 0;
  88. if(ypos < 0 || ypos >128)
  89. ypos = 0;
  90. }
  91. }
  92. else
  93. {
  94. //tft.setRotation(2);
  95. }
  96. bool swapBytes = tft.getSwapBytes();
  97. tft.setSwapBytes(true);
  98. // Jpeg images are draw as a set of image block (tiles) called Minimum Coding Units (MCUs)
  99. // Typically these MCUs are 16x16 pixel blocks
  100. // Determine the width and height of the right and bottom edge image blocks
  101. uint32_t min_w = min(mcu_w, max_x % mcu_w);
  102. uint32_t min_h = min(mcu_h, max_y % mcu_h);
  103. // save the current image block size
  104. uint32_t win_w = mcu_w;
  105. uint32_t win_h = mcu_h;
  106. // record the current time so we can measure how long it takes to draw an image
  107. uint32_t drawTime = millis();
  108. // save the coordinate of the right and bottom edges to assist image cropping
  109. // to the screen size
  110. max_x += xpos;
  111. max_y += ypos;
  112. // Fetch data from the file, decode and display
  113. while (JpegDec.read()) { // While there is more data in the file
  114. pImg = JpegDec.pImage ; // Decode a MCU (Minimum Coding Unit, typically a 8x8 or 16x16 pixel block)
  115. // Calculate coordinates of top left corner of current MCU
  116. int mcu_x = JpegDec.MCUx * mcu_w + xpos;
  117. int mcu_y = JpegDec.MCUy * mcu_h + ypos;
  118. // check if the image block size needs to be changed for the right edge
  119. if (mcu_x + mcu_w <= max_x) win_w = mcu_w;
  120. else win_w = min_w;
  121. // check if the image block size needs to be changed for the bottom edge
  122. if (mcu_y + mcu_h <= max_y) win_h = mcu_h;
  123. else win_h = min_h;
  124. // copy pixels into a contiguous block
  125. if (win_w != mcu_w)
  126. {
  127. uint16_t *cImg;
  128. int p = 0;
  129. cImg = pImg + win_w;
  130. for (int h = 1; h < win_h; h++)
  131. {
  132. p += mcu_w;
  133. for (int w = 0; w < win_w; w++)
  134. {
  135. *cImg = *(pImg + w + p);
  136. cImg++;
  137. }
  138. }
  139. }
  140. // calculate how many pixels must be drawn
  141. uint32_t mcu_pixels = win_w * win_h;
  142. // draw image MCU block only if it will fit on the screen
  143. if (( mcu_x + win_w ) <= tft.width() && ( mcu_y + win_h ) <= tft.height())
  144. tft.pushImage(mcu_x, mcu_y, win_w, win_h, pImg);
  145. else if ( (mcu_y + win_h) >= tft.height())
  146. JpegDec.abort(); // Image has run off bottom of screen so abort decoding
  147. }
  148. tft.setSwapBytes(swapBytes);
  149. }

这个是加载网页和上传照片的函数,打开html文件后上传照片会给照片命名并存放到SD卡中。

  1. // 将上传的文件发送回SD卡
  2. //####################################################################################################
  3. bool handleFileRead(String path){
  4. int upload_ = tft_num - 1;
  5. if(upload_<0)
  6. upload_ = 0;
  7. path = "/loge"+String(upload_)+".jpg";//上传JPG文件
  8. Serial.println("handleFileRead: " + path);
  9. if(path.endsWith("/")) path += "index.htm";
  10. String contentType = getContentType(path);
  11. String pathWithGz = path + ".gz";
  12. if(SD.exists(pathWithGz) || SD.exists(path)){
  13. if(SD.exists(pathWithGz))
  14. path += ".gz";
  15. File file = SD.open(path, "r");
  16. size_t sent = server.streamFile(file, contentType);
  17. file.close();
  18. upload_flag = false;//完成一次写入
  19. return true;
  20. }
  21. upload_flag = false;//完成一次写入
  22. return false;
  23. }
  24. //####################################################################################################
  25. // 文件上传SD卡
  26. //####################################################################################################
  27. void handleFileUpload(){//网络服务处理函数
  28. upload_flag = true;//正在进行上传
  29. if(server.uri() != "/") return;
  30. HTTPUpload& upload = server.upload();
  31. String filename;
  32. char *file_sd;
  33. if(upload.status == UPLOAD_FILE_START){//开启下载上传的文件
  34. filename = upload.filename;
  35. if(!filename.startsWith("/"))
  36. {
  37. filename = "/loge"+String(tft_num)+".jpg";//如果文件开头没有/则添加/ 并且对该图片添加计数尾缀
  38. tft_num++;//文件数+1
  39. EEPROM.write(20,tft_num);//将数据保存
  40. EEPROM.commit();
  41. }
  42. Serial.print("handleFileUpload Name: "); Serial.println(filename);//打印文件名
  43. SD.remove(filename);
  44. fsUploadFile = SD.open(filename, "w");//将上传的文件保存
  45. filename = String();
  46. } else if(upload.status == UPLOAD_FILE_WRITE){
  47. if(fsUploadFile)
  48. fsUploadFile.write(upload.buf, upload.currentSize);//将上传文件写入SD卡
  49. } else if(upload.status == UPLOAD_FILE_END){
  50. if(fsUploadFile)
  51. fsUploadFile.close();
  52. }
  53. }
  54. //####################################################################################################
  55. // 加载网页
  56. //####################################################################################################
  57. void handleRoot(){
  58. upload_flag = true;//进入上传就绪状态(打开了网页)
  59. server.send(200, "text/html", mainPageString);
  60. server.client().stop();
  61. }
  62. //####################################################################################################
  63. // 上传完成函数
  64. //####################################################################################################
  65. void uplaodFinish(){
  66. server.send(200, "text/html", uploadString);//重新发送网页
  67. upload_flag = true;//上传完成,但是网页回复仍未完成
  68. }
  69. //####################################################################################################
  70. // 限幅函数
  71. //####################################################################################################
  72. uint8_t limit(){
  73. if(flag_pic >= tft_num)
  74. {
  75. flag_pic = flag_pic - tft_num;
  76. }
  77. else if(flag_pic < 0)
  78. {
  79. flag_pic = flag_pic + tft_num;
  80. }
  81. }

以上就是代码讲解部分。。。

代码调试过程遇到的BUG以及解决办法:

1.软件定时器的使用问题

在WIFI配置的功能中,一开始我使用软件定时回调函数ATimerCallback( TimerHandle_t xTimer );来回调上传WIFI名称和密码的函数,后面发现软件定时回调函数只会在指定的时间点才会执行函数,而上传WIFI名称和密码功能在这段时间必须一直打开,而且一直回调程序不会继续下去,非常占用CPU资源,因此我放弃了使用软件回调函数,改成目前的实现方式。

2.在加载SD卡图片时串口总是打印找不到SD卡中的图片

主要原因是SD卡模块供电应该是5V,而我用成了3.3V,因此导致SD卡读取不正常,另外用于存储SD卡中文件路径的变量char filename1[20]设置过短,无法存储那么长的字符串。

3.镜像显示的问题

打开文件

E:\Arduino\hardware\espressif\esp32\libraries\TFT_eSPI\TFT_Drivers\ST7735_Rotation.h

  1. case 4:
  2. writedata(0x48);
  3. _width = _init_height;
  4. _height = _init_width;
  5. break;

并将最前面改为rotation = m % 5; // Limit the range of values to 0-4

至于为什么是0x48大家可以去看ST7735的数据手册,这里就不再赘述了。

最后在你的TFT初始化函数中添加:

tft.setRotation(4);//屏幕内容镜像显示或者旋转屏幕0-4  ST7735_Rotation中设置

写在后面

此外还有分光棱镜的安装问题,如何将分光棱镜安装在屏幕上呢?

  1. 我用了热熔胶枪,先将分光棱镜固定好(这一步很重要,因为分光棱镜比较贵,所以固定不好粘上去了之后又要重新买)
  2. 在积木和分光棱镜中间我特意留了空隙,将热熔胶打入空隙中,这一步可以慢点
  3. 最后用热熔胶将空隙填满,这样就固定好了

我看有的同学用的是粘手机屏幕的干胶,但是这种胶需要专门的工具,我没有这种工具,所以就没用,有同学如果会用的话可以用这种,效果能好一些。

源码连接: https://download.csdn.net/download/qq_42542307/86505031icon-default.png?t=M7J4https://download.csdn.net/download/qq_42542307/86505031

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/471247
推荐阅读
相关标签
  

闽ICP备14008679号