基于ESP32-S2实现本地气象计
本项目使用ESP32-S2以及arduino框架,由于本地无气象传感器,因此采用腾讯位置服务的IP定位、“心知”天气获取当地天气的思路实现气象数据的获取,再通过ntp网络授时完成时间同步,最后使用U8g2显示。
标签
嵌入式系统
显示
ESP32-S2
2022寒假在家练
zeki
更新2022-03-01
北京邮电大学
1381
一、项目介绍

      本项目为硬禾学堂的2022寒假在家练ESP32-S2物联网平台的项目六,主要的要求为不使用板外资源硬件资源的情况下使用板载的OLED显示屏显示本地时间、温度和气象信息。该项目主要锻炼了物联网系统的开发思路以及大致流程。

二、设计思路

      由于需要时间、温度和气象等数据,并且无法使用外部硬件资源,因此选择网络进行各种信息的获取。详细思路如下:

FqTmNfmmtLzKbVuULPxdEXef_Fvx

FtUFj_z0JD52LPhSRDWz5X4YLQJI
1、OLED驱动部分:

      由于本项目使用Arduino平台进行开发,因此可以调用丰富的显示驱动库,比如:`U8g2`、`adafruit`等等。本项目采用`U8g2`库进行OLED地驱动。

​       Arduino中提供了U8g2库的“傻瓜式”下载方式:

​       在工具中选择工具——选择“管理库”——在界面中搜索“U8g2”后下载安装即可。

FibD_goCzv0HwxKo7uh-snAU0DAR

FgGDM-mUlqUlQxAEFUN1zW-wEMQz

      安装完毕后,根据OLED的驱动芯片SSD1306以及分辨率128X64选择相应的初始化函数。之后便可根据自身的需要显示出相应的数据即可。

2、WiFi连接以及数据获取

      由于需要从网络中获取数据,因此需要使用ESP32S2的WiFi库连接附近WiFi后再调用各种API获取公网IP、位置以及天气等数据。

      调用API需要使用HTTPClient库的函数发起HTTP的GET请求,服务器以JSON格式返回请求结果,因此程序中还会涉及JSON格式数据的解析。但Arduino中自带了解析JSON数据的ArduinoJson库,因此可以方便的提取各种请求结果。

      但为了提前测试各种API的调用方法以及查看数据格式,可以使用Postman提前测试相应的API。

FsJv_OVcgDrYILtMhJPP-FAYqFZ7

      基本思路是先提取IP,再利用IP进行定位,最后利用位置获取当地天气。

      (1)公网IP

Fo_rO_Nloy_Ag1cqFuM3Dj393bgT

      (2)位置

FmYfCw0j66PbkQRz9dZxGs_AGdBF

      (3)天气

FvCzrVO4pVuevjK8urTunnScu37-

三、硬件介绍

      ESP32-S2 是一款高度集成、高性价比、低功耗、主打安全的单核 Wi-Fi SoC,具备强大的功能和丰富的 IO 接口。ESP32-S2-MINI-1采用PCB板载天线,模组配置了4MB SPI flash,采用的是 ESP32-S2FN4 芯片。该芯片搭载了Xtensa® 32 位LX7 单核处理器,工作频率高达 240 MHz。用户可以关闭 CPU 的电源,利用低功耗协处理器监测外设的状态变化或某些模拟量是否超出阈值。ESP32-S2-FH4 还集成了丰富的外设接口。

FvlrWGqHjJp3bKiSdLF3g9GGLueF

      本项目平台在ESP32-S2的基础上又增加了128X64OLED、4个按键、1路Mic音频输入 - 模拟电路、1路耳机插座音频输入 - 模拟电路、2路音频输出、一个FM接收模块和一个模拟开关。

Fij4yA2gO6e9uXhtVvh_JjmI24oC

四、实现的功能及效果展示 1、资源占用

项目使用了 1043042 字节,占用了 (79%) 程序存储空间。最大为 1310720 字节。

全局变量使用了35544字节,(10%)的动态内存,余留292136字节局部变量。最大为327680字节。

2、硬件效果

Fgy0DIPApxfczEu78yduNuGej7QQ

3、打印日志

ESP-ROM:esp32s2-rc4-20191025

Build:Oct 25 2019

rst:0x1 (POWERON),boot:0xb (SPI_FAST_FLASH_BOOT)

SPIWP:0xee

mode:DIO, clock div:1

load:0x3ffe6100,len:0x510

load:0x4004c000,len:0xa50

load:0x40050000,len:0x28bc

entry 0x4004c18c

UART Initialized

WiFi connected!

IP:192.168.xxx.xxx

[HTTP] begin...

[HTTP] GET...

[HTTP] GET... code: 200{"ip":"112.9.xxx.xxx","country":"China","cc":"CN"}

[HTTP] begin...

[HTTP] GET...

[HTTP] GET... code: 200{

   "status": 0,

   "message": "query ok",

   "result": {

      "ip": "112.9.xxx.xxx",

      "location": {

         "lat": xxx,

         "lng": xxx

      },

      "ad_info": {

         "nation": "中国",

         "province": "山东省",

         "city": "德州市",

         "district": "xxx",

         "adcode": xxx

      }

   }

}

[HTTP] begin...

[HTTP] GET...

[HTTP] GET... code: 200{"results":[{"location":{"id":"WWDTN3X443VR","name":"德州","country":"CN","path":"德州,德州,山东,中国","timezone":"Asia/Shanghai","timezone_offset":"+08:00"},"now":{"text":"多云","code":"4","temperature":"2"},"last_update":"2022-01-27T17:39:42+08:00"}]}

五、主要代码片段及说明    1、导入项目所需库
#include <Arduino.h>
#include <HTTPClient.h>
#include <WiFI.h>
#include <SPI.h>
#include <WiFiMulti.h>
#include <U8g2lib.h>
#include <ArduinoJson.h>
#include <time.h>
   2、定义引脚

      根据 esp32_audio_v2.2_sch.pdf 中的芯片引脚图的标记确定SPI通信的引脚以及四个按键的引脚。

int PIN_SCK = 36;//IO36
int PIN_SDA = 35;//IO35
int PIN_RST = 34;//IO34
int PIN_DC  = 33;//IO33
int PIN_CS  = 37;//IO37
int PIN_KEY1 = 1;//IO1
int PIN_KEY2 = 2;//IO2
int PIN_KEY3 = 3;//IO3
int PIN_KEY4 = 6;//IO6
   3、定义静态变量
const char* ssid     = "xxxx";////网络名称   
const char* password = "xxxx";//"2019210194";//网络密码   
const char *ntpServer = "cn.ntp.org.cn";//ntp服务器地址
const long gmtOffset_sec = 8*3600;
const int daylightOffset_sec =8*3600;//用于将时间校正到东八区
StaticJsonDocument<500> doc;		//存储IP定位的JSON数据
StaticJsonDocument<500> weatherDoc;	//存储天气的JSON数据
StaticJsonDocument<200> IPDoc;		//存储公网IP的JSON数据

struct tm timeinfo;		//存储当前时间

WiFiMulti wifiMulti;	//用于连接WiFi
   4、初始化U8g2驱动

      根据屏幕的相关信息选择合适的初始化函数。

      屏幕驱动为SSD2306,分辨率为128X64,保存一页的缓冲区,4线 (clock, data, cs 和dc) 软件模拟SPI,因此选择以下初始化函数。

U8G2_SSD1306_128X64_NONAME_1_4W_SW_SPI u8g2(U8G2_R0,PIN_SCK,PIN_SDA,PIN_CS,PIN_DC,PIN_RST);

其中U8G2_R0表示显示画面不旋转。

   5、获取时间
int GetTime()
{
  if (!getLocalTime(&timeinfo))
  {
    Serial.println("Failed to obtain time");
    return 0;
  }
//  Serial.println(&timeinfo, "%A, %Y-%m-%d %H:%M:%S");
  return 1;
  
}
   6、获取公网IP
void getPublicIP()
{
  HTTPClient http;	//初始化HTTP对象
  
  Serial.println("[HTTP] begin...");
  String url = "https://api.myip.com/";	//获取公网IP的API
  http.begin(url); //HTTP
  Serial.println("[HTTP] GET...");
  int httpCode = http.GET();
  if(httpCode > 0)
  {
    Serial.print("[HTTP] GET... code: ");
    
    Serial.print(httpCode);

    if(httpCode == HTTP_CODE_OK)	//若访问成功,将数据装载到相应的StaticJsonDocument中
    {
      String payload = http.getString();
      deserializeJson(IPDoc, payload);
      
      Serial.println(payload);		//若访问失败,打印状态码
    }else
    {
      Serial.print("[HTTP] GET... failed, error: ");
      Serial.print(http.errorToString(httpCode).c_str());
    }
    http.end();
  }  
}
   7、获取IP定位
void GetIPPos()
{
  HTTPClient http;

  Serial.println("[HTTP] begin...");
  String url;
  String str1 = "https://apis.map.qq.com/ws/location/v1/ip?ip=";
  String strIP = IPDoc["ip"];
  String str2 = "&key=xxxx";//IP定位API,为保护隐私,已抹去密钥
  url=str1 + strIP + str2;

  http.begin(url); 
  Serial.println("[HTTP] GET...");
  int httpCode = http.GET();
  if(httpCode > 0)
  {
    Serial.print("[HTTP] GET... code: ");
    Serial.print(httpCode);

    if(httpCode == HTTP_CODE_OK)
    {
      String payload = http.getString();
      deserializeJson(doc, payload);
      Serial.println(payload);
    }else
    {
      Serial.print("[HTTP] GET... failed, error: ");
      Serial.print(http.errorToString(httpCode).c_str());
    }
    http.end();
  }
}
   8、获取目前位置天气

      利用刚刚获取的位置信息,请求当地天气。

void GetWeather()
{
  HTTPClient http;

  Serial.println("[HTTP] begin...");
  String url;
  String str1 = "https://api.seniverse.com/v3/weather/now.json?key=xxxx&location=";
  String str2 = "&language=zh-Hans&unit=c";
  String strCity = doc["result"]["ad_info"]["city"];	//天气查询API,为保护隐私,已抹去密钥
  url=str1 + strCity + str2;
  http.begin(url); //HTTP
  Serial.println("[HTTP] GET...");
  int httpCode = http.GET();
  if(httpCode > 0)
  {
    Serial.print("[HTTP] GET... code: ");
    
    Serial.print(httpCode);

    if(httpCode == HTTP_CODE_OK)
    {
      String payload = http.getString();
      deserializeJson(weatherDoc, payload);
      
      Serial.println(payload);
    }else
    {
      Serial.print("[HTTP] GET... failed, error: ");
      Serial.print(http.errorToString(httpCode).c_str());
    }
    http.end();
  }
}
   9、初始化配置
void setup() {
  // put your setup code here, to run once:
  u8g2.begin();//初始化oled驱动
  u8g2.enableUTF8Print();
  Serial.begin(115200);//初始化串口,波特率为115200
  pinMode(PIN_KEY1,INPUT);
  pinMode(PIN_KEY2,INPUT);
  pinMode(PIN_KEY3,INPUT);//配置三个按钮为输入
  Serial.println("UART Initialized");

  wifiMulti.addAP(ssid, password);
  while(wifiMulti.run()!=WL_CONNECTED)	//尝试连接WiFi
  {
    delay(500);
    Serial.println(".");
  }
  Serial.println("WiFi connected!");
  Serial.print("IP:");
  Serial.println(WiFi.localIP());

  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);		//连接ntp服务器,同步时间
  
  GetTime();
  
  if(wifiMulti.run()==WL_CONNECTED)		//初次执行完整流程
  {
    getPublicIP();
    GetIPPos();
    GetWeather();
  }
}
   10、循环程序
void loop() {
  // put your main code here, to run repeatedly:
  String PublicIP = IPDoc["ip"];
  u8g2.firstPage();
  do//显示数据
  {
      GetTime();
      String resPos = doc["result"]["ad_info"]["city"];
      String resWeather = weatherDoc["results"][0]["now"]["text"];
      String resTemp = weatherDoc["results"][0]["now"]["temperature"];
      u8g2.setFont(u8g2_font_wqy14_t_gb2312);	//设置字库,字号为14
//      u8g2.drawStr(0,15,"Hello World");
      u8g2.setCursor(0,15);u8g2.print(&timeinfo,"%Y/%m/%d %H:%M:%S");//规定显示格式
      u8g2.setCursor(0,40);u8g2.print(resPos);//位置
      u8g2.setCursor(0,60);u8g2.print("天气:");
      u8g2.setCursor(40,60);u8g2.print(resWeather);//天气
      u8g2.setCursor(80,60);u8g2.print(resTemp);//温度
      u8g2.setCursor(95,60);u8g2.print("℃");
      
  }while(u8g2.nextPage());
  int buttonState = digitalRead(PIN_KEY1);
  int buttonState2 = digitalRead(PIN_KEY2);
  int buttonState3 = digitalRead(PIN_KEY3);//读取三个按钮的状态
  if(buttonState == LOW)//按键1:更新天气
  {
    GetWeather();
    Serial.println("updating weather...");
    delay(50);
  }
  if(buttonState2 == LOW)//按键2:打印IP信息
  {
    Serial.print("IP:");
    Serial.println(WiFi.localIP());
    Serial.print("PublicIP:");
    Serial.println(PublicIP);
    delay(50);
  }
  if(buttonState3 == LOW)//按键3:更新时间
  {
    Serial.println("Getting Time...");
    GetTime();
    delay(50);
  }
}
六、遇到的主要难题及解决办法    1、SPI通信时,CS引脚已经默认接地

      SPI通信时,CS引脚已经默认接地,但U8g2库的初始化函数需要此参数。因此,在调用初始化函数时,引入一个冗余引脚来代替CS。

   2、局域网IP无法定位

      使用IP定位服务时,若查询局域网IP,会提示无法查询。因此,我们需要先获取本机的公网IP,之后再去获取公网IP的位置,这样才能准确地获取地理位置信息。

   3、“霾”字无法加载

      之前使用的字库大小较小,字库中不包含“霾”等字,因此选择更大的字库,一是可以正常显示部分较为生僻的汉字,二是提高了程序的扩展性。

七、未来的计划或者建议

      之后如果有时间会尝试移植LVGL,实现多页面显示,并且加入更多流畅的动画效果。

 

 

 

 

 

 

 

软硬件
元器件
ESP32-S2-MINI-1
2.4GHz Wi­Fi (802.11 b/g/n) 模组, 内置ESP32­S2系列芯片,Xtensa® 单核32位LX7微处理器, 内置芯片叠封4MB flash,可叠封2MB PSRAM, 37个GPIO,丰富的外设, 板载PCB天线或外部天线连接器
附件下载
2022寒假在家一起练——ESP32制作本地气象计.zip
团队介绍
团队成员
zeki
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号