写在最前面
注意!附件中的工程不能拿来直接用,需要修改ssid,password,和链接中的[your key]。[your key]为高德开发平台创建应用产生的key id。
项目介绍
基于ESP32-S2模块的物联网/音频信号处理平台实现本地气象台,能够在OLED显示当前本地的时间、温度和气象信息,系统能够自动校时,开机后自动调节到准确的时间(年、月、日、时、分、秒)。
设计思路
总体的思路是,将处理平台连接wifi,通过api获取时间、日期、位置和气象等数据,经过解析后,通过OLED显示出来。
软件流程图
硬件介绍
基于ESP32-S2模块的物联网/音频信号处理平台是硬禾学堂推出的一款开发平台。平台使用了乐鑫公司的ESP32-S2模块作为MCU模组,支持WiFi连接,128*64的SPI接口OLED,4路按键,以及其他音频输入输出接口,可以用来开发物联网应用和音频信号处理。
实现的功能
1.OLED显示及连接WiFi
待平台上电以后,OLED显示等待联网界面,直到网络连接成功。
2.显示本地日期、时间和星期
在联网后,OLED将界面跳转为本地日期界面,显示本地日期、时间和星期。
3.显示位置和天气实况
按动下翻键(sw2),OLED界面跳转到天气实况界面,显示本地位置,实时温度,实时湿度和气象情况。
4.显示天气预报
再次按动下翻键,OLED界面跳转到天气预报界面,显示今日白天温度与夜间温度、明日白天温度与夜间温度和后日白天温度与夜间温度。
5.显示标语
再次按动下翻键,OLED界面跳转到标语界面,这里的标语可以自定义。此时按动下翻键将回到本地日期界面。上翻键也是类似的操作逻辑。
主要代码片段及说明
0. 软件环境的搭建
ESP32的开发方式主要分为两种,第一种是采用乐鑫提供的ESP32 IDF进行开发,另外一种是采用Arduino进行开发。前者需要较为复杂的环境安装,后者的话,软件的安装比较简单。由于在安装ESP32 IDF的时候出现了比较多的错误,所以最终选择了Arduino的开发方式。这也是我第一次系统采用Arduino进行开发,也是一次很好的学习过程。
关于Arduino的环境搭建,网上的教程有很多,在此不再赘述。需要一提的是,采用Arduino IDE编译和上传的速度较慢,本来计划采用VSCode+PlatformIO+Arduino的方式进行开发,可是PlatformIO对ESP32S2的支持不太好,所以最终选择了VSCode+Arduino的方式。
1.oled的显示
OLED的显示一开始采用的是Adafruit_SSD1306的库,后续为了能够较好的支持中文,选择了u8g2的库。示例来自PrintUTF8.ino。
void setup(void) {
u8g2.begin();
u8g2.enableUTF8Print(); // enable UTF8 support for the Arduino print() function
}
void loop(void) {
u8g2.setFont(u8g2_font_unifont_t_chinese2); // use chinese2 for all the glyphs of "你好世界"
u8g2.setFontDirection(0);
u8g2.clearBuffer();
u8g2.setCursor(0, 15);
u8g2.print("Hello World!");
u8g2.setCursor(0, 40);
u8g2.print("你好世界"); // Chinese "Hello World"
u8g2.sendBuffer();
delay(1000);
}
通过示例可以较为清楚的了解到u8g2库的使用方式。后续其他的显示本质上也是采用的上述的方式。
2. Wifi
连接wifi是非常关键的一步。示例来自WiFiClientBasic.ino。
void setup()
{
Serial.begin(115200);
delay(10);
// We start by connecting to a WiFi network
WiFiMulti.addAP("SSID", "passpasspass");
Serial.println();
Serial.println();
Serial.print("Waiting for WiFi... ");
while(WiFiMulti.run() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
delay(500);
}
输入wifi的名称和密码即可连接wifi。实际应用中,通过手机开热点,将ESP32接入WiFi须先给ESP32上电,之后再打开WIFI才可以。
3. 时间
时间的获取参考了示例NTPClient的Advance.ino。
NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000);
void setup(){
Serial.begin(115200);
WiFi.begin(ssid, password);
while ( WiFi.status() != WL_CONNECTED ) {
delay ( 500 );
Serial.print ( "." );
}
timeClient.begin();
}
void loop() {
timeClient.update();
Serial.println(timeClient.getFormattedTime());
delay(1000);
}
应当注意,中国位于东八区,这里默认的参数提供的时间是东一区的,所以需要将参数3600改为8*3600。
同时,我将域名从欧洲的服务器修改为了中国的服务器,理论上会有更好的稳定性。
4. 日期
虽然NTP也可以获取到日期,但是为了正式接触http的api,日期的获取我采用了api的方式。
网友perseverance52 的 Arduino ESP32 获取网络数据(HTTP GET方式)
https://blog.csdn.net/weixin_42880082/article/details/120904803
介绍了苏宁易购提供的api接口,接口为 http://quan.suning.com/getSysTime.do
需要注意,这个接口有事会出现 404 当前页面访问人数过多,请您稍后再试!的问题,所以我在程序中设置了连续3次进行读取以解决读取失败的问题。
接口的返回为
{"sysTime2":"2022-02-23 13:31:28","sysTime1":"20220223133128"}
关于如何从数据中解析出需要的内容,这部分将在json中进行介绍。
5. JSON
上一部分中,接口返回数据的格式是JSON。这时我们无法直接从返回数据中提取出我们想要的内容,通过arduinojson的库就可以快速地完成数据解析。
https://arduinojson.org/ 提供的quickstart可以帮助快速实现对数据的解析。
char json[] = "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}";
DynamicJsonDocument doc(1024);
deserializeJson(doc, json);
const char* sensor = doc["sensor"];
long time = doc["time"];
double latitude = doc["data"][0];
double longitude = doc["data"][1];
只要将输入输出进行修改,即可快速完成开发。
6. 位置
为了通过IP获得本地位置,在网络上进行了调查与研究。很多方案提供的方法是使用百度或者腾讯等企业提供的API。但是这些公司要求只有先登录个人账号才能够使用API,较为麻烦。
这里要感谢网友 夏时 整理的 分享几个IP获取地理位置的API接口
来自 https://cloud.tencent.com/developer/article/1152362
在其中,我选择了太平洋网络IP地址查询Web接口 ,因为这个网站的接口资料很详细,不需要登录,而且可以获得地区代码,方便下一步通过地区代码直接获得地区的气象信息。
https://whois.pconline.com.cn/ipJson.jsp?json=true
返回格式为
{
"ip": "255.255.255.255",
"pro": "江苏省",
"proCode": "320000",
"city": "南京市",
"cityCode": "320100",
"region": "",
"regionCode": "0",
"addr": "江苏省南京市 移动",
"regionNames": "",
"err": ""
}
ip地址部分为了保护隐私人为修改了。可以看到能够获得所在城市名称和城市代码,方便后续通过城市代码获取天气。
7. 气象
下一步是通过城市代码获得城市的气象信息,这里参考了网友 蒋川 的 最好的 6 个免费天气 API 接口对比测评
来自 https://kalacloud.com/blog/free-weather-api/
考虑到api的可靠性和实用性,选择了高德地图的天气API,具体过程可以参考上述链接。
实况天气和预报天气如下
https://restapi.amap.com/v3/weather/weatherInfo?key=[your_key]&city=320100&extensions=base
https://restapi.amap.com/v3/weather/weatherInfo?key=[your_key]&city=320100&extensions=all
返回值分别为
{
"status": "1",
"count": "1",
"info": "OK",
"infocode": "10000",
"lives": [
{
"province": "江苏",
"city": "南京市",
"adcode": "320100",
"weather": "多云",
"temperature": "7",
"winddirection": "南",
"windpower": "≤3",
"humidity": "24",
"reporttime": "2022-02-21 13:05:56"
}
]
}
{
"status": "1",
"count": "1",
"info": "OK",
"infocode": "10000",
"forecasts": [
{
"city": "南京市",
"adcode": "320100",
"province": "江苏",
"reporttime": "2022-02-21 15:32:29",
"casts": [
{
"date": "2022-02-21",
"week": "1",
"dayweather": "多云",
"nightweather": "多云",
"daytemp": "8",
"nighttemp": "0",
"daywind": "南",
"nightwind": "南",
"daypower": "≤3",
"nightpower": "≤3"
},
{
"date": "2022-02-22",
"week": "2",
"dayweather": "多云",
"nightweather": "阴",
"daytemp": "6",
"nighttemp": "2",
"daywind": "东南",
"nightwind": "东南",
"daypower": "≤3",
"nightpower": "≤3"
},
{
"date": "2022-02-23",
"week": "3",
"dayweather": "阴",
"nightweather": "多云",
"daytemp": "7",
"nighttemp": "-2",
"daywind": "东",
"nightwind": "东",
"daypower": "≤3",
"nightpower": "≤3"
},
{
"date": "2022-02-24",
"week": "4",
"dayweather": "多云",
"nightweather": "多云",
"daytemp": "9",
"nighttemp": "0",
"daywind": "东",
"nightwind": "东",
"daypower": "≤3",
"nightpower": "≤3"
}
]
}
]
}
其中,第一个为实况天气,第二个为预报天气。
需要注意,在api中填写在高德开发平台中创建应用的key id,这一部分,在上文提到的链接中有较为详细的介绍。
api中的城市编号是固定的,city=320100,但是在程序中,为了增加普适性,采用的是拼接字符串的方法,所以,只要你在中国境内,无论哪个城市,只要能够正确获取你的IP地址对应的位置,就可以提供你的气象信息。
8. 交互界面
交互界面采用了翻页式设计,第一页显示本地时间,包括日期,时间和星期;第二页显示天气实况,包括地区,实时温度,实时湿度,实时气象;第三页显示天气预报,包括,今日白天温度与夜间温度、明日白天温度与夜间温度和后日白天温度与夜间温度。第四页为标语页面,可自定义标语。
在平台刚上电,或者联网后断网时,显示等待上电,并且显示滚动动画。
翻页的方式通过按键进行控制,sw1为上翻页,sw2为下翻页,sw3未设置功能,sw4为手动同步数据按键。
按键逻辑参考了慕容流年的《Arduino》开发 之 基于 u8g2 库 的 OLED 菜单界面 https://blog.csdn.net/qq_41868901/article/details/105970873
9. 刷新方式
刷新方式上,每个页面都单独写了刷新函数。采用定时器的方式,每秒刷新一次屏幕。数据刷新上,每秒同步一次时间数据,每小时同步一次气象数据。
遇到的主要难题及解决方法
遇到的难题太多了,多到一时不知道从哪里讲起。持续时间最长的难题是WiFi的连接问题,无论是用手机开热点,或者是用电脑开热点,WiFi的连接都很玄学。在这其中也做过很多尝试,诸如设置手机的热点模式,设置2.4g与5g的频率,切换WiFi连接函数等等。最后经过大量的尝试以后,得到了一个较为稳健的连接方式,就是先让ESP32等待连接,再打开默认模式的2.4g的手机WiFi。虽然还是很麻烦,但是总是有了一个稳健的连接方式,可能通过路由器会简单很多,可惜手边没有。
第二个问题是OLED的刷新问题。如何刷新OLED,以什么样的频率刷新OLED都是问题。经过仔细研究,最终方案是,通过定时器产生标志信号,然后在循环中判断标志信号的状态来刷新屏幕。数据的刷新方式也是类似,不过由于天气的短期稳定性,所以每小时刷新一次,而不是和屏幕或者时间的每秒刷新一次。
第三个问题是按键的响应问题和屏幕的刷新问题,由于项目时间即将结束,这个问题要留到后续进行解决了。
未来的计划或建议等
后续有时间的话,首先要解决的是按键的响应问题。按键的判断是在循环中的,理论上每一轮循环都很快,按键的响应不应该出问题,然而实际上有时会存在按键响应丢失的问题。分析原因是有代码占据了控制器的时间,初步认为应该是网络连接判断函数占用了大量的时间。为了能够实时获取网络连接状态,每一轮都在读取网络连接,由于网络连接状态判断较为复杂,所以占用了较多时间。后续可以以时间变化为判断依据,若是未能更新时间,说明网络连接失败,这样就可以避免采用网络连接判断函数了。其余操作的话,诸如刷新屏幕,都无需占用时间。
解决方法二是采用外部中断的方式,把按键设为外部中断,就可以及时响应按键了。
屏幕刷新问题体现在,有时两秒钟才会刷新一次屏幕,这个问题的原因显然和上述问题一致,在于网络连接函数占用了大量的时间,可以采取上述问题的方法一解决。
除了解决现有问题以外,还有就是进一步开发平台的应用。ESP32模块在音频处理上有较大的优势,然而本项目未能体现出这些优势,下一步应该将这些优势挖掘出来。