内容介绍
内容介绍
基于ESP32-S2模块的本地气象计/温度仪
一、ESP32开发环境搭建
1.下载 arduino1.xx 版本 (Arduino下载)
2.直接点击下载附件中hardware文件并替换掉自己安装目录下的hardware文件夹
(这样子99%可以成功)
3.这样就成功了
二、项目要求分析
制作一个本地气象台/温度计
-
利用OLED显示
-
显示当前本地的时间、温度和气象信息
具体实现
- 读取网络时间并显示
- 读取当地实时天气信息并显示
- 读取当地天气预报信息并显示
三、硬件分析
读取网络主要是通过ESP32S2模块实现的,显示主要是通过OLED显示屏来实现的。所以只需要注意ESP32S2模块和OLED显示屏模块之间的连接和个别按键即可。
对于oled显示屏显示可以使用u8g2来适配(需要以下硬件相关定义)
/****************************OLED显示定义********************************/
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif
// 使用硬件SPI,只需要定义CS(该板载显示屏幕内部置低,引脚未引出) DC(33) RST(34)即可
U8G2_SSD1306_128X64_NONAME_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 26, /* dc=*/ 33, /* reset=*/ 34);
/**********************************************************************/
按键需要以下定义(只需要使用一个按键)
const int buttonPin = 1;
四、程序流程分析
五、关键内容具体实现
1.使用u8g2库实现在oled屏幕上的显示(参考arduino中 u8g2 详解)
/*
* 当前天气显示 液晶屏幕显示布局
*/
#include <Arduino.h>
#include <U8g2lib.h>
#include "bmp.h"
/****************************OLED显示定义********************************/
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif
// 使用硬件SPI,只需要定义CS(该板载显示屏幕内部置低,引脚未引出) DC(33) RST(34)即可
U8G2_SSD1306_128X64_NONAME_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 26, /* dc=*/ 33, /* reset=*/ 34);
/**********************************************************************/
static const unsigned char bmp0[] U8X8_PROGMEM = { /* 白天晴 (32 X 32 )*/
0x00,0x00,0x00,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,
0x00,0x80,0x01,0x04,0x30,0x80,0x01,0x06,0x60,0x80,0x01,0x03,0xC0,0x00,0x80,0x01,
0x80,0xE1,0xC7,0x00,0x00,0x79,0x1E,0x00,0x00,0x0C,0x38,0x00,0x00,0x06,0x20,0x00,
0x00,0x02,0x60,0x00,0x00,0x03,0x40,0x00,0x00,0x03,0xC0,0x00,0x7E,0x01,0xC0,0x7E,
0x7E,0x01,0xC0,0x7E,0x00,0x03,0xC0,0x00,0x00,0x03,0x40,0x00,0x00,0x06,0x60,0x00,
0x00,0x06,0x30,0x00,0x00,0x1C,0x18,0x00,0x00,0xF1,0xCF,0x00,0x80,0xC1,0x83,0x01,
0xC0,0x00,0x00,0x03,0x60,0x80,0x01,0x06,0x30,0x80,0x01,0x04,0x00,0x80,0x01,0x00,
0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x00,0x00,0x00
};
void setup(void) {
u8g2.begin();
}
void loop(void) {
u8g2.clearBuffer();
u8g2.drawXBMP( 12,12, 32, 32, bmp0);
u8g2.setFont(u8g2_font_helvR18_tr);
u8g2.drawStr( 54,24,"26 C");
u8g2.drawCircle(88,9,3,U8G2_DRAW_ALL);
u8g2.setFont(u8g2_font_helvR08_tr);
u8g2.drawStr( 54,40,"Humi:12%");
u8g2.drawStr( 54,56,"Wind:5");
u8g2.sendBuffer();
delay(3000);
}
之后的显示都建立在这个代码的基础上。
2.获取网络时间并显示(可以参考ESP32获取网络时间)
注:以下代码有一个不易发现的BUG,在周一到周六正常,在周日硬件会连续重启(本人实际遇到 说多了都是泪)
#include <U8g2lib.h>
#include <Arduino.h>
#include <WiFi.h>
/****************************OLED显示定义********************************/
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif
// 使用硬件SPI,只需要定义CS(该板载显示屏幕内部置低,引脚未引出) DC(33) RST(34)即可
U8G2_SSD1306_128X64_NONAME_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 26, /* dc=*/ 33, /* reset=*/ 34);
/**********************************************************************/
/******************************WiFi连接*********************************/
/*
* WiFi初始化和连接
*/
void WiFi_Connect()
{
Serial.print("Connecting...");
WiFi.begin("TP-LINK_2013", "ylp812787442");
while (WiFi.status() != WL_CONNECTED){ //这里是阻塞程序,直到连接成功
delay(300);
Serial.print(".");
}
Serial.println("WiFi connect successed");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
Serial.println();
}
/*********************************************************************/
/***************************3.获取并且显示当前网络时间***************************/
const String WDAY_NAMES[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; //星期
const char *ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 8 * 3600;
const int daylightOffset_sec = 0;
// 获取成功后芯片会使用RTC时钟保持时间的更新
void printLocalTime()
{
struct tm timeinfo;
if (!getLocalTime(&timeinfo))
{
Serial.println("Failed to obtain time");
return;
}
//Serial.println(&timeinfo, "%F %T %A"); // 格式化输出
Serial.print(asctime(&timeinfo)); //默认打印格式:Mon Oct 25 11:13:29 2021
String date = WDAY_NAMES[timeinfo.tm_wday - 1];
Serial.println(date.c_str()); // 星期的字符输出
//String year = String(timeinfo.tm_year + 1900); //年
//String month = String(timeinfo.tm_mon + 1); //月
//String date = String(timeinfo.tm_mday); //日
//String hour = String(timeInfo.tm_hour); //时
//String min = String(timeInfo.tm_min); //分
//String sec = String(timeInfo.tm_sec); //秒
u8g2.setCursor(15, 40);
u8g2.print(&timeinfo, "%T"); //显示 时:分:秒
u8g2.setFont(u8g2_font_ncenB08_tr);
u8g2.setCursor(0, 10);
u8g2.print(&timeinfo, "%F"); //显示 年:月:日
// u8g2.setCursor(80, 10);
// u8g2.print(&timeinfo, "%A"); //显示 星期
u8g2.drawStr(90,10,date.c_str());
}
/************************************************************************/
void setup(void) {
Serial.begin(115200); // open the serial port at 115200 bps;
delay(100);
WiFi_Connect();
u8g2.begin();
u8g2.enableUTF8Print();
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}
void loop(void) {
u8g2.setFontDirection(0);
u8g2.clearBuffer(); // 清除内部缓冲区
u8g2.setFont(u8g2_font_ncenB18_tr); // choose a suitable font
// u8g2.drawStr(0,20,"21:12:56"); // write something to the internal memory
printLocalTime();
u8g2.sendBuffer(); // transfer internal memory to the display
delay(1000);
}
获取网络时间不能每时每刻都向时间服务器发出请求,可以将读取到的网络时间同步到自身外设RTC,读取RTC实时时间。
3.获取实时的天气信息(参考arduino环境心知天气使用教程)
注:密钥需要填写自己的账号私钥(心知天气官网可以自己注册并获取)
/*********************************2.获取并显示当前天气信息****************************/
const char* host = "api.seniverse.com"; // 将要连接的服务器地址
const int httpPort = 80; // 将要连接的服务器端口
// 心知天气HTTP请求所需信息
String reqUserKey = "xxxxxxxxxxxxxxxxx"; // 私钥
String reqLocation = "Taiyuan"; // 城市
String reqUnit = "c"; // 摄氏/华氏
// 获取当前天气状况
void getNowWeather()
{
WiFiClient client;
// 建立心知天气API当前天气请求资源地址
String reqRes = "/v3/weather/now.json?key=" + reqUserKey +
+ "&location=" + reqLocation +
"&language=en&unit=" +reqUnit;
// 建立http请求信息
String httpRequest = String("GET ") + reqRes + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n";
// 尝试连接服务器
if (!client.connect(host, httpPort))
{
Serial.println("weather host connection failed");
return;
}
Serial.println("weather host connection successed");
// 向服务器发送http请求信息
client.print(httpRequest);
//Serial.println("Sending request: ");
//Serial.println(httpRequest);
// 获取并显示服务器响应状态行 200/404
String status_response = client.readStringUntil('\n');
//Serial.print("status_response: ");
//Serial.println(status_response);
// 使用find跳过HTTP响应头
if (client.find("\r\n\r\n"))
{
//Serial.println("Found Header End. Start Parsing.");
}
// 以下为解析JSON天气数据
const size_t capacity = JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(1) + 2*JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(6) + 230;
DynamicJsonDocument doc(capacity);
deserializeJson(doc, client);
JsonObject results_0 = doc["results"][0];
JsonObject results_0_now = results_0["now"];
const char* results_0_now_text = results_0_now["text"]; // "Sunny"
const char* results_0_now_code = results_0_now["code"]; // "0"
const char* results_0_now_temperature = results_0_now["temperature"]; // "32"
const char* results_0_last_update = results_0["last_update"]; // "2020-06-02T14:40:00+08:00"
// 通过串口监视器显示以上信息
String results_0_now_text_str = results_0_now["text"].as<String>();
int results_0_now_code_int = results_0_now["code"].as<int>();
int results_0_now_temperature_int = results_0_now["temperature"].as<int>();
String results_0_last_update_str = results_0["last_update"].as<String>();
Serial.println(F("======Weahter Now======="));
Serial.print(F("Weather Now: "));
Serial.print(results_0_now_text_str);
Serial.print(F(" "));
Serial.println(results_0_now_code_int);
Serial.print(F("Temperature: "));
Serial.println(results_0_now_temperature_int);
Serial.println(F("========================"));
int temp = results_0_now_temperature_int;
switch(results_0_now_code_int){
case 0:
case 38: u8g2.drawXBMP( 12,12, 32, 32, bmp0); break; // 白天晴
case 1: u8g2.drawXBMP( 12,12, 32, 32, bmp1); break; // 夜晚晴
case 4:
case 7:
case 8: u8g2.drawXBMP( 12,12, 32, 32, bmp2); break; // 多云
case 5: u8g2.drawXBMP( 12,12, 32, 32, bmp3); break; // 白天晴间多云
case 6: u8g2.drawXBMP( 12,12, 32, 32, bmp4); break; // 晚上晴间多云
case 9: u8g2.drawXBMP( 12,12, 32, 32, bmp5); break; // 阴天
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
case 16:
case 17:
case 18:
case 19:
case 20: u8g2.drawXBMP( 12,12, 32, 32, bmp6); break; // 下雨
case 21:
case 22:
case 23:
case 24:
case 25:
case 37: u8g2.drawXBMP( 12,12, 32, 32, bmp7); break; // 下雪
case 26:
case 27:
case 28:
case 29:
case 32:
case 33:
case 34:
case 35:
case 36: u8g2.drawXBMP( 12,12, 32, 32, bmp8); break; // 风
case 30:
case 31: u8g2.drawXBMP( 12, 12, 32, 32, bmp9); break; // 雾霾
default: ;
}
u8g2.setFont(u8g2_font_helvR18_tr);
// u8g2.drawStr( 54,24,temp);
u8g2.setCursor(54, 24);
u8g2.printf("%d C", temp); //显示 时:分:秒
if(temp>=0){
if(temp/10!=0)
u8g2.drawCircle(88,9,3,U8G2_DRAW_ALL);
else
u8g2.drawCircle(72,9,3,U8G2_DRAW_ALL);
}
else{
if(temp/10!=0)
u8g2.drawCircle(98,9,3,U8G2_DRAW_ALL);
else
u8g2.drawCircle(82,9,3,U8G2_DRAW_ALL);
}
//断开客户端与服务器连接工作
client.stop();
Serial.println("closing connection");
Serial.println();
}
/*********************************************************************************/
4.获取天气预报信息并显示
/*********************************3.获取并显示三天天气预报信息****************************/
int getForecastWeather(){
int a;
WiFiClient client;
// 建立心知天气API当前天气请求资源地址
String reqRes = "/v3/weather/daily.json?key=" + reqUserKey +
+ "&location=" + reqLocation + "&language=en&unit=" +
reqUnit + "&start=0&days=3";
// 建立http请求信息
String httpRequest = String("GET ") + reqRes + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n";
Serial.println("");
Serial.print("Connecting to "); Serial.print(host);
// 尝试连接服务器
if (client.connect(host, 80)){
Serial.println(" Success!");
// 向服务器发送http请求信息
client.print(httpRequest);
Serial.println("Sending request: ");
Serial.println(httpRequest);
// 获取并显示服务器响应状态行
String status_response = client.readStringUntil('\n');
Serial.print("status_response: ");
Serial.println(status_response);
// 使用find跳过HTTP响应头
if (client.find("\r\n\r\n")) {
Serial.println("Found Header End. Start Parsing.");
}
// 利用ArduinoJson库解析心知天气响应信息
const size_t capacity = JSON_ARRAY_SIZE(1) + JSON_ARRAY_SIZE(3) + JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(6) + 3*JSON_OBJECT_SIZE(14) + 860;
DynamicJsonDocument doc(capacity);
deserializeJson(doc, client);
JsonObject results_0 = doc["results"][0];
JsonArray results_0_daily = results_0["daily"];
JsonObject results_0_daily_0 = results_0_daily[0];
const char* results_0_daily_0_date = results_0_daily_0["date"];
const char* results_0_daily_0_text_day = results_0_daily_0["text_day"];
const char* results_0_daily_0_code_day = results_0_daily_0["code_day"];
const char* results_0_daily_0_text_night = results_0_daily_0["text_night"];
const char* results_0_daily_0_code_night = results_0_daily_0["code_night"];
const char* results_0_daily_0_high = results_0_daily_0["high"];
const char* results_0_daily_0_low = results_0_daily_0["low"];
const char* results_0_daily_0_rainfall = results_0_daily_0["rainfall"];
const char* results_0_daily_0_precip = results_0_daily_0["precip"];
const char* results_0_daily_0_wind_direction = results_0_daily_0["wind_direction"];
const char* results_0_daily_0_wind_direction_degree = results_0_daily_0["wind_direction_degree"];
const char* results_0_daily_0_wind_speed = results_0_daily_0["wind_speed"];
const char* results_0_daily_0_wind_scale = results_0_daily_0["wind_scale"];
const char* results_0_daily_0_humidity = results_0_daily_0["humidity"];
JsonObject results_0_daily_1 = results_0_daily[1];
const char* results_0_daily_1_date = results_0_daily_1["date"];
const char* results_0_daily_1_text_day = results_0_daily_1["text_day"];
const char* results_0_daily_1_code_day = results_0_daily_1["code_day"];
const char* results_0_daily_1_text_night = results_0_daily_1["text_night"];
const char* results_0_daily_1_code_night = results_0_daily_1["code_night"];
const char* results_0_daily_1_high = results_0_daily_1["high"];
const char* results_0_daily_1_low = results_0_daily_1["low"];
const char* results_0_daily_1_rainfall = results_0_daily_1["rainfall"];
const char* results_0_daily_1_precip = results_0_daily_1["precip"];
const char* results_0_daily_1_wind_direction = results_0_daily_1["wind_direction"];
const char* results_0_daily_1_wind_direction_degree = results_0_daily_1["wind_direction_degree"];
const char* results_0_daily_1_wind_speed = results_0_daily_1["wind_speed"];
const char* results_0_daily_1_wind_scale = results_0_daily_1["wind_scale"];
const char* results_0_daily_1_humidity = results_0_daily_1["humidity"];
JsonObject results_0_daily_2 = results_0_daily[2];
const char* results_0_daily_2_date = results_0_daily_2["date"];
const char* results_0_daily_2_text_day = results_0_daily_2["text_day"];
const char* results_0_daily_2_code_day = results_0_daily_2["code_day"];
const char* results_0_daily_2_text_night = results_0_daily_2["text_night"];
const char* results_0_daily_2_code_night = results_0_daily_2["code_night"];
const char* results_0_daily_2_high = results_0_daily_2["high"];
const char* results_0_daily_2_low = results_0_daily_2["low"];
const char* results_0_daily_2_rainfall = results_0_daily_2["rainfall"];
const char* results_0_daily_2_precip = results_0_daily_2["precip"];
const char* results_0_daily_2_wind_direction = results_0_daily_2["wind_direction"];
const char* results_0_daily_2_wind_direction_degree = results_0_daily_2["wind_direction_degree"];
const char* results_0_daily_2_wind_speed = results_0_daily_2["wind_speed"];
const char* results_0_daily_2_wind_scale = results_0_daily_2["wind_scale"];
const char* results_0_daily_2_humidity = results_0_daily_2["humidity"];
const char* results_0_last_update = results_0["last_update"];
// 从以上信息中摘选几个通过串口监视器显示
day0_date = results_0_daily_0["date"].as<String>();
day0_code = results_0_daily_0["code_day"].as<int>();
day0_high = results_0_daily_0["high"].as<int>();
day0_low = results_0_daily_0["low"].as<int>();
humi = results_0_daily_0["humidity"].as<int>();
wind = results_0_daily_0["wind_scale"].as<int>();
day1_date = results_0_daily_1["date"].as<String>();
day1_code = results_0_daily_1["code_day"].as<int>();
day1_high = results_0_daily_1["high"].as<int>();
day1_low = results_0_daily_1["low"].as<int>();
day2_date = results_0_daily_2["date"].as<String>();
day2_code = results_0_daily_2["code_day"].as<int>();
day2_high = results_0_daily_2["high"].as<int>();
day2_low = results_0_daily_2["low"].as<int>();
day0_date =day0_date.substring(5,10); //截取指定位置字符串
day1_date =day1_date.substring(5,10);
day2_date =day2_date.substring(5,10);
Serial.println(F("======Today Weahter ======="));
Serial.print(F("DATE: "));
Serial.println(day0_date);
Serial.print(F("Weather code: "));
Serial.println(day0_code);
Serial.print(F("Highest tempeture:"));
Serial.println(day0_high);
Serial.print(F("Lowest tempeture:"));
Serial.println(day0_low);
Serial.print(F("DATE: "));
Serial.println(day1_date);
Serial.print(F("Weather code: "));
Serial.println(day1_code);
Serial.print(F("Highest tempeture:"));
Serial.println(day1_high);
Serial.print(F("Lowest tempeture:"));
Serial.println(day1_low);
Serial.print(F("DATE: "));
Serial.println(day2_date);
Serial.print(F("Weather code: "));
Serial.println(day2_code);
Serial.print(F("Highest tempeture:"));
Serial.println(day2_high);
Serial.print(F("Lowest tempeture:"));
Serial.println(day2_low);
Serial.println(F("=============================="));
a = 1;
}
else {
a = 0;
Serial.println(" connection failed!");
}
//断开客户端与服务器连接工作
client.stop();
return a;// 1:获取成功 0:获取失败
}
以上两个代码块主要涉及有
- 建立HTTP请求信息
- 向服务器发送请求
- 获取服务器响应状态和响应信息
- 解析响应信息(arduinoison在线解析网站)
以上1,2,3 三点都是固定方法,关键是数据获取和处理。
五、程序整合及实现关键点
- 将天气信息的获取和显示分开为两个函数。
- 按键只负责切换显示,不负责刷新数据。
- loop使用delay会影响按键扫描,所以1s和1min定时使用非阻塞方式。
六、可改进地方
- 屏幕布局不够好看,字体选择观感欠佳。
- 多个页面可以添加倒计时自动切换效果
- 连接wifi时候可以添加动态显示效果
- 由于心知天气网站服务器限制,实时天气无法读取更多内容
七、心得体会
- arduino不是单纯的C编程,而是混合了部分C++,很多库无法直观的观看,很多函数也是arduino所特有的。
- arduino编译速度及其缓慢,严重影响使用体验。(前期使用vs_code+platformio感觉不错,但是没有S2的arduino开发框架)
- 本人尝试过micropython开发,资料较少,且ESP32的部分外设功能适配性不好。封装程度更高。
- 本人搭建esp-idf开发环境n多次才成功,但是后期由于网上相关教程较少,才放弃。我觉得乐鑫或者安信可可以开发在不同系统的适配效果好的ide。
软硬件
元器件
ESP32-S2-MINI-1
2.4GHz WiFi (802.11 b/g/n) 模组, 内置ESP32S2系列芯片,Xtensa® 单核32位LX7微处理器, 内置芯片叠封4MB flash,可叠封2MB PSRAM, 37个GPIO,丰富的外设, 板载PCB天线或外部天线连接器
附件下载
06_final_version_v1.ino.esp32s2.bin
可以直接烧写的二进制文件(WiFi账号和密码是个人的,不改的话无法使用)
06_final_version_v1.zip
源工程文件(若要验证功能,请修改36行的WiFi账号和密码)
hardware.zip
esp32的硬件支持包(在arduino安装目录中直接粘贴替换)
团队介绍
中北大学信息与通信工程学院
团队成员
董殿国
中北大学信息与通信工程学院研究生二年级在读
评论
0 / 100
查看更多
猜你喜欢
基于ESP32-S2制作的本地气象台/温度计本项目基于ESP32-S2模块的物联网平台,制作了一个本地气象台及温度计,项目内容包括通过wifi获取本地时间、天气和温度,并利用OLED屏进行显示。
yushui
1200
基于ESP32-S2制作的本地气象台温度计本项目采用乐鑫官方模块ESP32-S2-Mini-1主控,制作一个本地气象台温度计。功能为通过网络获取天气数据且自动校时,在12864oled屏上显示地区、天气、温度、时间和星期,并且绘制相应的天气图标。
co1
1341
基于ESP32-S2模块制作一个本地气象台/温度计本项目使用了乐鑫公司的ESP-S2-Mini-1模块,主要利用了其三个核心功能:WiFi连接、OLED显示信息和按键控制参数,以此制作了一个本地的气象台/温度计。
lgem
1586