硬件介绍:
该平台使用了乐鑫公司的ESP32-S2-Mini-1模块,ESP32-S2-MINI-1是一颗通用型Wi-Fi MCU模组,功能强大,具有丰富的外设接口;还带有0.96寸OLED显示屏,与开发板采用SPI总线连接,可以用来显示信息参数等;以及4个独立按键。
项目要求:
制作一个本地气象站/温度计,利用OLED显示当前本地的时间、温度和气象信息。
设计思路:
首先初始化WiFi并设置WiFi模式为STA模式;成功连接WiFi后进入时间显示界面,按下按键2后进入天气显示界面,再次按下按键1后即可返回时间显示界面。时间显示功能利用“pool.ntp.org”网站进行网络校准时钟;天气显示功能利用“console.amap.com”高德开放平台上的天气查询api接口获取实时天气信息。下图为本系统程序流程图:
时间校准功能介绍:
NTP(Network Time Protocol)是指网络时间协议,用来同步网络中各个计算机的时间的协议,可以给计算机和其它网络设备授时。
NTP提供准确时间,首先要有准确的时间来源,这一时间应该是国际标准时间UTC,UTC时间是使用多种不同的方法得到的,包括无线电和卫星系统。我国的北京时间为世界东八时区,与国际标准时间相比增加8小时,配置网络时钟代码如下所示:
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 8*60*60; //北京时间为东八时区,与世界时间相比增加8小时
const int daylightOffset_sec = 0; //不使用夏令时
配置好网络时钟后,定义一个tm结构体的timeinfo即可,tm结构体中的变量具体如下所示:
struct tm
{
int tm_sec; //秒,取值区间为[0,59]
int tm_min; //分,取值区间为[0,59]
int tm_hour; //时,取值区间为[0,23]
int tm_mday; //一个月中的日期,取值区间为[1,31]
int tm_mon; //月份,取值区间为[0,11]
int tm_year; //年份,其值从1900开始
int tm_wday; //星期,取值区间为[0,6]
int tm_yday; //从每年的1月1日开始的天数,取值区间为[0,365]
int tm_isdst; //夏令时标识符
long int tm_gmtoff; //指定了日期变更线东面时区中UTC东部时区正秒数或UTC西部时区的负秒数
const char *tm_zone; //当前时区的名字(与环境变量TZ有关)
};
获取天气介绍:
网上有很多免费的天气查询api接口,这里我选择的是高德开放平台的天气查询api,如下图所示:
申请一个key后就可以进行天气信息查询,高德开放平台给出了有服务示例,我们将所要查询的地区的区域代码以及key替换后,再把这行网址输入网页地址栏中按下回车,就可以得到所在地区的天气信息,如下图所示:
上图中“restapi.amap.com”即为要填入代码中的host信息,“/v3/weather/weatherInfo?city=410103&key=e765135a5fd92ba0d7b0d7247f0efa57”为代码中的url信息。
得到如上图所示的两行信息后还不够,我们需要将其转化为arduino的json格式的代码。百度搜索“arduinojson”打开ArduinoJson官网,点击首页上方“Assistant”,将“Processor”选为“ESP32”,再将网页中的两行信息填入“Input”中即可得到所需的json代码,最后将结果复制粘贴至我们自己的代码中即可。
下图为天气查询中所能返回的天气现象表:
下图为天气查询中所能返回的风向表:
下图为天气查询中所能返回的风力级别表:
代码介绍:
本代码所需头文件如下所示:
#include <ETH.h>
#include <WiFi.h>
#include <WiFiAP.h>
#include <WiFiClient.h>
#include <WiFiGeneric.h>
#include <WiFiMulti.h>
#include <WiFiScan.h>
#include <WiFiServer.h>
#include <WiFiSTA.h>
#include <WiFiType.h>
#include <WiFiUdp.h>
#include <SPI.h>
#include <U8g2lib.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
其中u8g2库和arduinojson库均可以直接在库管理器中找到,如下图所示:
起始画面显示代码:
void Display_begin() { //显示起始画面
u8g2.enableUTF8Print();
u8g2.firstPage();
u8g2.clearBuffer(); //清除内部缓冲区
u8g2.setFontDirection(0); //设置字体方向
u8g2.setFont(u8g2_font_wqy16_t_gb2312a); //设置字体
u8g2.setCursor(0, 15);
u8g2.print("正在连接至WiFi:");
u8g2.setCursor(0, 35);
u8g2.print(ssid);
u8g2.sendBuffer();
}
setup()部分代码:
void setup() {
Serial.begin(115200); //初始化串口
u8g2.begin(); //显示器初始化
Display_begin(); //显示起始画面
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while(WiFi.status() != WL_CONNECTED) //未连接上
{
delay(400);
Serial.print(".");
}
u8g2.setCursor(0, 55);
u8g2.print("连接成功");
u8g2.sendBuffer();
IP_address = WiFi.localIP().toString();
Serial.println(IP_address);
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
num = 1;
}
按键判断部分代码:
if(digitalRead(KEY1) == LOW)
{
delay(100);
if(digitalRead(KEY1) == LOW)
{
num = 1;
Serial.println("1");
}
}
if(digitalRead(KEY2) == LOW)
{
delay(100);
if(digitalRead(KEY2) == LOW)
{
num = 2;
Serial.println("2");
}
}
在loop()循环中每次循环首先判断是否有按键被按下,然后执行switch语句,变量为num,num=1为时间显示界面,num=2为天气显示界面。开机后默认num=1;当按键1被按下后,num=1;当按键2被按下后,num=2。
时间显示部分代码如下所示:
struct tm timeinfo;
char str[64] = {0};
if(!getLocalTime(&timeinfo))
return;
if(oldsec == timeinfo.tm_sec)
return;
oldsec = timeinfo.tm_sec;
u8g2.setFont(u8g2_font_ncenB18_tr);
u8g2.setCursor(25, 24);
u8g2.print(&timeinfo, "%H:%M");
u8g2.setFont(u8g2_font_ncenB12_tr);
u8g2.setCursor(95, 24);
u8g2.print(&timeinfo, "%S");
u8g2.setFont(u8g2_font_wqy12_t_gb2312);
u8g2.setCursor(5, 42);
u8g2.print(&timeinfo, "%Y年%m月%d日");
u8g2.setCursor(95, 42);
switch(timeinfo.tm_wday)
{
case 1 : u8g2.print("周一"); break;
case 2 : u8g2.print("周二"); break;
case 3 : u8g2.print("周三"); break;
case 4 : u8g2.print("周四"); break;
case 5 : u8g2.print("周五"); break;
case 6 : u8g2.print("周六"); break;
case 0 : u8g2.print("周日"); break;
default : break;
}
天气显示部分代码如下所示:
WiFiClient client;
const int httpPort = 80;
if(!client.connect(host, httpPort))
{
return;
}
client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
delay(50);
String answer;
while(client.available())
{
String line = client.readStringUntil('\r');
answer += line;
}
client.stop();
String jsonAnswer;
int jsonIndex = 0;
int i = 0;
for(i = 0; i < answer.length(); i++)
{
if (answer[i] == '{')
{
jsonIndex = i;
break;
}
}
jsonAnswer = answer.substring(jsonIndex);
StaticJsonDocument<512> doc;
DeserializationError error = deserializeJson(doc, jsonAnswer);
if(error)
{
return;
}
const char* status = doc["status"]; //返回状态
const char* count = doc["count"]; //返回结果总数目
const char* info = doc["info"]; //返回的状态信息
const char* infocode = doc["infocode"]; //返回状态说明,10000代表正确
JsonObject lives_0 = doc["lives"][0];
const char* lives_0_weather = lives_0["weather"]; //天气现象(汉字描述)
const char* lives_0_temperature = lives_0["temperature"]; //实时气温,单位:摄氏度
const char* lives_0_winddirection = lives_0["winddirection"]; //风向描述
const char* lives_0_windpower = lives_0["windpower"]; //风力级别,单位:级
const char* lives_0_humidity = lives_0["humidity"]; //空气湿度
u8g2.setFont(u8g2_font_wqy12_t_gb2312);
u8g2.setCursor(10, 11); u8g2.print("天气现象:");
u8g2.setCursor(80, 11); u8g2.print(lives_0_weather);
u8g2.setCursor(10, 24); u8g2.print("实时气温:");
u8g2.setCursor(80, 24); u8g2.print(lives_0_temperature);
u8g2.setCursor(95, 24); u8g2.print("℃");
u8g2.setCursor(10, 37); u8g2.print("风向描述:");
u8g2.setCursor(80, 37); u8g2.print(lives_0_winddirection);
u8g2.setCursor(10, 50); u8g2.print("风力级别:");
u8g2.setCursor(80, 50); u8g2.print(lives_0_windpower);
u8g2.setCursor(10, 63); u8g2.print("空气湿度:");
u8g2.setCursor(80, 63); u8g2.print(lives_0_humidity);
u8g2.setCursor(95, 63); u8g2.print("%");
功能展示:
上电后进入起始画面显示界面,显示WiFi名称,等待WiFi连接,如下图所示:
成功连接WiFi后屏幕会跳出“连接成功”字样,如下图所示:
成功连接WiFi后默认进入的是时间显示界面,显示网络时钟信息,如下图所示:
按下按键2后,即可进入天气信息显示界面,如下图所示:
再次按下按键1后即可返回时间显示界面,两界面可随意切换。
存在不足:
1、还可以为该系统加入气象图标功能,使得气象站更生动形象;
2、不知为何,我这块板子的按键3和按键4有些问题,采用跟按键1和2一样的按键检测代码后,会检测到按键3和4处于一直被按下的状态,故本次我并未采用按键3和4;
3、或许下次我会采用汉字取模来显示汉字中文的方法,这种方法会很好地节约内存。