基于ESP32-S2-Mini-1模组的本地气象台与网页遥控
本项目利用ESP32-S2 Audio V2.2开发板制作一个本地气象台,能够联网更新实时天气与时间,同时融入远程遥控的功能,通过任意红外遥控器控制网页上的按钮
标签
嵌入式系统
ESP32
物联网
2022寒假在家练
网页
SDeron
更新2022-03-01
金陵科技学院
1379

一、项目介绍

本项目利用ESP32-S2 Audio V2.2开发板制作一个本地气象台,能够联网更新实时天气与时间,同时融入远程遥控的功能,通过任意红外遥控器控制网页上的按钮。

开发板运行实物图:

FhFovWTAQnJ2bsUHG1oyVl4B9pN5

远程遥控网页:

FkzEU5SNTTvJFWpoD1aHgAdwDhKQ

二、设计思路

此次我完成了2个项目,分别是项目6和项目8,我们先看下题目要求:

项目6 制作一个本地气象台/温度计

  • 利用OLED显示
  • 显示当前本地的时间、温度和气象信息

项目8 远程遥控

  • 利用板上的红外接收器,用遥控器控制网页界面上的按钮

题目看起来很简短,我们逐个分析一下。

关于项目6气象台方面:

由于开发板没有焊接温湿度传感器,但我们制作气象台的话需要想办法获取这些信息,因为ESP32-S2-Mini-1模组半载PCB天线,能够连接网络,所以气象数据和温湿度数据的获取可以通过心知天气API获取,随后我们把得到的数据进行解析,再通过OLED屏幕显示出来。

关于项目8远程遥控方面:

开发板上有红外接收器,我们通过Arduino库里的IRremote.h可以轻松驱动,并且把接受的数据解码为16进制。涉及网页的部分需要学习基本的HTML语法以及部分的JavaScript和CSS,这里我使用网页是从网上搜集的,原本CSS和HTML分开的,我对他进行了整合,并把全部代码放进了ESP32的flash内,我们可以根据接收到的不同红外型号,对网页请求返回不同的界面,从而达到开关切换的结果。

三、ESP32-Audio V2.2开发板硬件介绍

1.主控芯片模组

本平台使用了乐鑫公司的ESP32-S2-Mini-1模块,ESP32-S2-MINI-1是一颗通用型Wi-Fi MCU模组,功能强大,具有丰富的外设接口,可用于可穿戴电子设备、智能家居等场景。

ESP32-S2-MINI-1采用PCB板载天线,模组配置了4MB SPI flash,采用的是 ESP32-S2FN4 芯片。该芯片搭载了Xtensa® 32 位LX7 单核处理器,工作频率高达 240 MHz。

2.模组内部构成

description?diagram=50

3.核心功能介绍:

  • 基于ESP32-S2 WiFi核心模块
  • 128*64 OLED显示,SPI接口,显示信息、参数、波形
  • 4个按键,用于参数控制、菜单选择
  • 1路Mic音频输入 - 模拟电路,通过电位计可以调节增益0-40dB调节范围,并有带通滤波器
  • 1路耳机插座音频输入 - 模拟电路,通过电位计可以调节增益 0-40dB调节范围,并有带通滤波器
  • 2路音频输出,并有功率放大,可以驱动喇叭和耳机插座
  • 一个FM接收模块,ESP32通过I2C接口对其进行参数设置,调节FM电台以及设置音量大小
  • 一个模拟开关切换来自ESP32产生的音频还是FM输出的音频,模块开关的输出送到喇叭或耳机输出

4.开发板实物图

Fij4yA2gO6e9uXhtVvh_JjmI24oC

5.系统框图

Fq2gVivaeYL4QOi_9N579bAz5rJA

四、开发环境的部署

我使用的Arduino IDE 1.8.19作为编译工具,VSCode作为代码编辑器。

Arduino IDE下载地址:https://www.arduino.cc/en/software

VSCode下载地址:https://code.visualstudio.com/

Arduino开发ESP32的方法可参考espressif官方文档:https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html

五、程序流程图与代码解析

Fl1DRNxVb_9R8jV-26mcy1ePPt6R

首先对设备进行联网,若是连接局域网的话,路由器会分配给设备一个内网IP。

//连接网络
void linkNetwork()
{
    Serial.println();
    Serial.println();
    Serial.print("正在连接WIFI");

    u8g2.clearBuffer();
    u8g2.setCursor(0, 15);
    u8g2.print("联网中");
    u8g2.sendBuffer();

    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) 
    {
        delay(1000);
        Serial.print(".");
        u8g2.print(".");
        u8g2.sendBuffer();
    }
    Serial.println("成功");
    Serial.print("IP:");
    Serial.println(WiFi.localIP());
    u8g2.print(" OK");
    u8g2.sendBuffer();

    //OLED显示IP信息
    u8g2.setCursor(0, 31);
    u8g2.print(WiFi.localIP());
    u8g2.sendBuffer();
}

随后与网络校准时间,并把当前时间在屏幕上。

//校准时间
void calibrateTime() 
{
    //东八区 8 * 3600,使用夏令时 
    configTime(8 * 3600, 0, "2.cn.pool.ntp.org", "time.nist.gov","3.cn.pool.ntp.org");
    Serial.print(F("校时中"));
    u8g2.setCursor(0, 47);
    u8g2.print("校时中");
    u8g2.sendBuffer();
    time_t nowSecs = time(nullptr);
    while (nowSecs < 8 * 3600 * 2) {
    delay(1000);
    Serial.print(F("."));
    u8g2.print(".");
    u8g2.sendBuffer();
    yield();
    nowSecs = time(nullptr);
    }
  
    if (!getLocalTime(&timeinfo))
    {
        Serial.println("获取时间失败");
        return;
    }
    Serial.println("成功");
    u8g2.print(" OK");
    u8g2.sendBuffer();
    //串口输出时间信息
    Serial.print("当前日期:");
    Serial.println(&timeinfo, "%F"); // 格式化输出
    Serial.print("当前时间:");
    Serial.println(&timeinfo, "%T"); // 格式化输出
    //OLED输出时间信息
    u8g2.setCursor(0, 63);
    u8g2.print(&timeinfo, "%T");
    u8g2.sendBuffer();
    delay(500);
    u8g2.clear();
}

利用心知天气的API获得温度与气象数据,并把显示在屏幕上。

//获取天气信息
void getWeather()
{
  WiFiClient client;
  const int httpPort = 80;
    if (!client.connect(host, httpPort)) 
    {
        Serial.println("连接断开");
        return;
    }  
    client.print(String("GET ") + url + " HTTP/1.1\r\n" +
                "Host: " + host + "\r\n" + 
                "Connection: close\r\n\r\n");
    Serial.println("正在获取天气数据");
    u8g2.setCursor(0, 15);
    u8g2.print("获取天气数据");
    u8g2.sendBuffer();
    
    while(!client.available())
    {
        u8g2.print(".");
        u8g2.sendBuffer();
        delay(1000);
    }
    String answer;
    while(client.available())
    {
        String line = client.readStringUntil('\r');
        answer += line;
    }
    //断开连接
    client.stop();
    u8g2.print(" OK");
    u8g2.sendBuffer();

    //解析json
    String jsonAnswer;
    int jsonIndex;
    for (int i = 0; i < answer.length(); i++) 
    {
        if (answer[i] == '{') 
        {
            jsonIndex = i;
            break;
        }
    }
    jsonAnswer = answer.substring(jsonIndex);
    Serial.println();
    Serial.print("返回JSON:");
    Serial.println(jsonAnswer);

    //访问下列链接即可得到天气的数据
    //https://api.seniverse.com/v3/weather/now.json?key=SjO80gPUTg9VAFgHr&location=nanjing&language=zh-Hans&unit=c
    //返回数据为下列格式
    //{"results":[{"location":{"id":"WTSQQYHVQ973","name":"南京","country":"CN","path":"南京,南京,江苏,中国",
    //"timezone":"Asia/Shanghai","timezone_offset":"+08:00"},"now":{"text":"阴","code":"9","temperature":"1"},
    //"last_update":"2022-02-17T21:24:05+08:00"}]}

    const size_t capacity = JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(1) + 2*JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(6) + 210;
    DynamicJsonDocument doc(capacity);
    deserializeJson(doc, jsonAnswer);

    JsonObject results_0 = doc["results"][0];

    JsonObject results_0_location = results_0["location"];
    const char* results_0_location_id = results_0_location["id"];
    const char* results_0_location_name = results_0_location["name"]; 
    const char* results_0_location_country = results_0_location["country"]; 
    const char* results_0_location_path = results_0_location["path"]; 
    const char* results_0_location_timezone = results_0_location["timezone"]; 
    const char* results_0_location_timezone_offset = results_0_location["timezone_offset"];

    JsonObject results_0_now = results_0["now"];
    const char* results_0_now_text = results_0_now["text"];
    const char* results_0_now_code = results_0_now["code"];
    const char* results_0_now_temperature = results_0_now["temperature"];

    const char* results_0_last_update = results_0["last_update"];

    roll_str = (char *) malloc(100);
    sprintf(roll_str, "今日%s天气%s 当前体感温度为%s摄氏度", results_0_location_name, results_0_now_text, results_0_now_temperature);

    //串口输出天气信息 
    Serial.print("城市:");
    Serial.println(results_0_location_name);
    Serial.print("气温:");
    Serial.println(results_0_now_temperature);
    Serial.print("天气:");
    Serial.println(results_0_now_text);
    Serial.println("初始化完成");

    //OLED输出天气信息
    u8g2.setCursor(0, 31);
    u8g2.print(results_0_location_name);
    u8g2.print(" ");
    u8g2.print(results_0_now_temperature);
    u8g2.print("度 ");
    u8g2.print(results_0_now_text);
    u8g2.setCursor(0, 47);
    u8g2.print("初始化完成");
    u8g2.setCursor(0, 63);
    u8g2.print("3 秒后进入系统");
    u8g2.sendBuffer();
    delay(1000);
    u8g2.setCursor(0, 63);
    u8g2.print("2 秒后进入系统");
    u8g2.sendBuffer();
    delay(1000);
    u8g2.clearBuffer();         // 清除内部缓冲区
    u8g2.setCursor(0, 63);
    u8g2.print("1 秒后进入系统");
    u8g2.sendBuffer();
    delay(1000);
    u8g2.clear();
}

关于气象数据的显示,由于长度太长并且我使用的是中文,屏幕一行128个像素点显示不完全,我们可以通过下列方法滚动显示,就像常见的广告点阵屏那样从左向右滚动。

//显示滚动字幕 方法2
void showRollStrInterrupt(char str[])
{
    if(roll_str_move_dir == 0)
    {
        u8g2.setCursor(30-roll_str_move_x, 63);
        u8g2.print(str);
        if(roll_str_move_x == 220)
        {
            roll_str_move_dir = 1;
        }
        else
        {
            roll_str_move_x += 2 ;
        }
    }
    else if(roll_str_move_dir == 1)
    {
        u8g2.setCursor(30-roll_str_move_x, 63);
        u8g2.print(str);
        if(roll_str_move_x == 0)
        {
            roll_str_move_dir = 0;
        }
        else
        {
            roll_str_move_x -= 4;
        }
    }
}

红外解码以及对网页的处理如下,通过不同数据改变请求的网页。

//红外读取解码
void irRead()
{
    if (IrReceiver.decode(&ir_decode_results))          //解码   
    {
        Serial.print("红外解码:0x"); 
        Serial.println(ir_decode_results.value, HEX);   //以16进制输出红外解码值
        if((ir_decode_results.value == 0x36480002) || (ir_decode_results.value == 0xD410BC59))       //空调遥控 0x36480002 手机遥控 0xD410BC59
        {
            switch_state = 0;
            u8g2.setFont(u8g2_font_weather);            //设定字体
            u8g2.setCursor(95, 43);                     //选定坐标
            u8g2.print("OFF");                          //OLED显示OFF
            Serial.println("状态:OFF");                      //串口输出OFF 
            server.close();
            server.on("/", handleRoot);
            server.begin();
        }
        else if((ir_decode_results.value == 0x36480000) || (ir_decode_results.value == 0x8F0C8B3A))  //空调遥控 0x36480000 手机遥控 0x8F0C8B3A
        {
            switch_state = 1;
            u8g2.setFont(u8g2_font_weather);            //设定字体
            u8g2.setCursor(95, 43);                     //选定坐标
            u8g2.print("ON");                           //OLED显示ON
            Serial.println("状态:ON");                       //串口输出ON
            server.close();
            server.on("/", handleRoot);
            server.begin();
        }
        IR.resume();                                    //继续接收下一个值
    }
}

六、难点及注意要点

1.难点

  • U8G2屏幕刷新和字符重叠问题,若字符重叠可以尝试写入屏幕数据后清除缓存。
  • 网页遥控部分需要HTML、CSS和JavaScript基础。

2.注意要点

  • 由于本次开发使用Arduino,在Arduino IDE中添加ESP32开发板需要较好的网络环境,一直刷不出来的话可以尝试使用手机热点。
  • 由于U8G2自带的中文库不全,我自己建立了16像素高度的2500常用汉字的中文字库,建立方法可以参考这篇博客:https://blog.csdn.net/weixin_44395581/article/details/108608141
  • 若设备管理器无法检测到开发板串口,可尝试安装CH340的驱动CH341SER.EXE。

七、未来计划

  • 学习IDF开发环境,编译速度会比Arduino快很多,优化开发效率。
  • 把板子所有功能用上,比如音频输入、运放、扬声器、FM模块这些。
软硬件
元器件
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天线或外部天线连接器
电路图
附件下载
ESP32_eetree.zip
团队介绍
机电工程学院大四学生
团队成员
SDeron
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号