【得捷电子Follow me第2期】基于ESP32S3-FHR的联网天气时钟
[复制链接]
本帖最后由 慕容雪花 于 2023-9-21 09:58 编辑
基于ESP32S3-FHR的联网天气时钟
Adafruit 是国外一家非常知名的创客公司。本次FOLLOW-ME活动提供的Adafruit ESP32-S3 TFT Feather是一款富有特色的开源硬件,开发板使用乐鑫ESP32-S3芯片,支持WiFi和蓝牙能,自带高清TFT彩色显示屏。板载丰富的资源与外设接口。
- 搭载 Xtensa® 32 位 LX7 双核处理器,主频高达 240 MHz,内置 512 KB SRAM (TCM),具有 45 个可编程 GPIO 管脚和丰富的通信接口,集成 2.4 GHz Wi-Fi 和 Bluetooth 5 (LE),配备原生 USB,可用作键盘/鼠标、MIDI 设备、磁盘驱动器等;
- 获得FCC / CE认证,带有4 MB闪存和2 MB PSRAM;
- 彩色 1.14“ IPS TFT,240x135 像素——采用 ST7789 芯片组的高清彩色显示屏,可从任何角度观看;
- 电源选项 - USB C 型或锂聚合物电池,通过 USB-C 供电时可为内置电池充电;
- LiPoly 电池监控器 - LC709203 芯片主动监控电池的电压和充电状态/通过 I2C 报告电池电量百分比;
- 重置和DFU(BOOT0)按钮以进入ROM引导加载程序;
- 内置原生USB串行端口,不需要单独的电缆,串行调试输出引脚(可选,用于检查硬件串行调试控制台);
- 用于 I2C 设备的 STEMMA QT 连接器,具有可切换电源,可进入低功耗模式;
- 板载 NeoPixel 具有引脚控制电源,可实现低功耗,用于多种状态显示;
- 在深度睡眠模式下,可从Lipoly连接获得80~100uA的电流消耗;
- 可与Arduino或CircuitPython一起使用。
这款板卡的实物图如下:
这款板卡的引脚图如下:
电路原理图如下:
Arduino环境搭建:
ESP32S3使用自带的USB替代传统的串口芯片进行代码烧录,本文提供了简短视频:
任务0-Arduino环境起步
任务1:控制屏幕显示中文(要求:完成屏幕的控制,并且能显示中文)
首先是屏幕的驱动,本文选择的是Arduino开发平台,在库管理器里面找到本开发板的屏幕驱动库。
之后在源代码中包含相应的头文件:
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <SPI.h>
接着进行屏幕初始化:
void setup(void) {
Serial.begin(9600);
Serial.println("***************************************************");
Serial.println(F("Hello! Feather TFT Test. Today is August 31th."));
Serial.println("***************************************************");
// turn on backlite
pinMode(TFT_BACKLITE, OUTPUT);
digitalWrite(TFT_BACKLITE, HIGH);
// turn on the TFT / I2C power supply
pinMode(TFT_I2C_POWER, OUTPUT);
digitalWrite(TFT_I2C_POWER, HIGH);
delay(10);
// initialize TFT
tft.init(135, 240); // Init ST7789 240x135
tft.setRotation(3);
tft.fillScreen(ST77XX_BLACK);
Serial.println(F("Initialized"));
}
其次是中文显示,参考了论坛网友[1]的字模提取方案,针对要显示的中文,提前准备好16*16大小的字模,
在源代码中,
const uint8_t PROGMEM str_f_DIAN[]={0x00,0x00,0x00,0x00,0x03,0x00,0x02,0x00,0x02,0x70,0x3F,0xB0,0x12,0x30,0x17,0xA0,0x1A,0x20,0x13,0xC0,0x0E,0x00,0x02,0x04,0x02,0x04,0x01,0xFC,0x00,0x00,0x00,0x00};/*"电",0*/
const uint8_t PROGMEM str_f_ZI[]={0x00,0x00,0x00,0x00,0x00,0xE0,0x0F,0x20,0x00,0x40,0x01,0x80,0x00,0x9C,0x0F,0xE0,0x30,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x04,0x80,0x03,0x00,0x00,0x00};/*"子",1*/
const uint8_t PROGMEM str_f_GONG[]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x0F,0xC0,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x01,0x00,0x01,0x00,0x01,0x7E,0x7E,0x80,0x00,0x00,0x00,0x00,0x00,0x00};/*"工",2*/
const uint8_t PROGMEM str_f_CHENG[]={0x00,0x00,0x00,0x00,0x02,0x08,0x04,0xFC,0x0C,0x48,0x04,0x58,0x1C,0x20,0x68,0x08,0x16,0x70,0x1A,0x20,0x24,0xF0,0x44,0x20,0x04,0x3E,0x0B,0xC0,0x00,0x00,0x00,0x00};/*"程",3*/
const uint8_t PROGMEM str_f_SHI[]={0x00,0x00,0x00,0x00,0x00,0x30,0x01,0x30,0x01,0x20,0x09,0x20,0x09,0x2E,0x0F,0xF0,0x39,0x20,0x09,0x20,0x09,0xC0,0x08,0x00,0x08,0x78,0x0F,0x80,0x00,0x00,0x00,0x00};/*"世",4*/
const uint8_t PROGMEM str_f_JIE[]={0x00,0x00,0x00,0x00,0x17,0xF0,0x09,0x10,0x0F,0xE0,0x09,0x20,0x0F,0xC0,0x06,0x40,0x04,0x20,0x0C,0x58,0x14,0x44,0x64,0x40,0x04,0x40,0x08,0x40,0x10,0x40,0x00,0x00};/*"界",5*/
const uint8_t PROGMEM str_f_DE[]={0x00,0x00,0x00,0x00,0x04,0x78,0x09,0x88,0x10,0xC8,0x24,0xB0,0x08,0x40,0x19,0xF0,0x10,0x20,0x30,0xFE,0x53,0x10,0x10,0x90,0x10,0x10,0x10,0x10,0x00,0x70,0x00,0x20};/*"得",6*/
const uint8_t PROGMEM str_f_JIE2[]={0x00,0x00,0x00,0x40,0x08,0x40,0x08,0x70,0x08,0xC0,0x0C,0x78,0x38,0xDC,0x0D,0xF0,0x0A,0x70,0x18,0xC0,0x69,0x78,0x09,0xC0,0x0A,0x40,0x1C,0x38,0x00,0x0E,0x00,0x00};/*"捷",7*/
const uint8_t PROGMEM str_f_WANG[]={0x00,0x00,0x00,0x00,0x00,0x10,0x1F,0xF8,0x10,0x08,0x11,0x28,0x12,0x48,0x16,0xC8,0x12,0xA8,0x15,0x88,0x18,0x08,0x10,0x08,0x10,0x28,0x10,0x18,0x00,0x00,0x00,0x00};/*"网",10*/
const uint8_t PROGMEM str_f_LUO[]={0x00,0x00,0x00,0x00,0x00,0x40,0x08,0x40,0x10,0x80,0x14,0xF0,0x25,0x20,0x18,0xE0,0x10,0x60,0x39,0x98,0x02,0x24,0x0D,0xD8,0x71,0x10,0x01,0x30,0x00,0xC0,0x00,0x00};/*"络",11*/
const uint8_t PROGMEM str_f_LIAN[]={0x00,0x00,0x00,0x80,0x00,0xC0,0x10,0xB0,0x0B,0xC0,0x01,0x40,0x01,0x50,0x13,0xE0,0x10,0x44,0x17,0xF8,0x10,0x40,0x10,0x40,0x7C,0x40,0x03,0xFE,0x00,0x38,0x00,0x00};/*"连",12*/
const uint8_t PROGMEM str_f_JIE3[]={0x00,0x00,0x08,0x00,0x08,0x60,0x08,0x20,0x08,0x38,0x0C,0xD0,0x38,0xA0,0x08,0xAC,0x0D,0xD0,0x18,0x46,0x6F,0xF8,0x08,0xA0,0x08,0x70,0x08,0xC8,0x0B,0x04,0x00,0x00};/*"接",13*/
const uint8_t PROGMEM str_f_ZHONG[]={0x00,0x00,0x01,0x00,0x01,0x80,0x01,0x00,0x01,0x38,0x1F,0xD8,0x11,0x08,0x11,0x10,0x09,0xF0,0x0F,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x00,0x00};/*"中",14*/
最后可以通过调用tft.drawBitmap()像绘制图片一下把想要显示的中文显示出来。
tft.fillScreen(ST77XX_BLACK);
tft.setCursor(0, 0);
static uint8_t i, j;
tft.drawBitmap(0, 0, str_f_DIAN, 16, 16, ST77XX_GREEN);
tft.drawBitmap(16, 0, str_f_ZI, 16, 16, ST77XX_GREEN);
tft.drawBitmap(16*2, 0, str_f_GONG, 16, 16, ST77XX_GREEN);
tft.drawBitmap(16*3, 0, str_f_CHENG, 16, 16, ST77XX_GREEN);
tft.drawBitmap(16*4, 0, str_f_SHI, 16, 16, ST77XX_GREEN);
tft.drawBitmap(16*5, 0, str_f_JIE, 16, 16, ST77XX_GREEN);
tft.drawBitmap(16*7, 0, str_f_DE, 16, 16, ST77XX_GREEN);
tft.drawBitmap(16*8, 0, str_f_JIE2, 16, 16, ST77XX_GREEN);
tft.drawBitmap(16*9, 0, str_f_DIAN, 16, 16, ST77XX_GREEN);
tft.drawBitmap(16*10, 0, str_f_ZI, 16, 16, ST77XX_GREEN);
tft.drawBitmap(16*0, 32, str_f_WANG, 16, 16, ST77XX_GREEN);
tft.drawBitmap(16*1, 32, str_f_LUO, 16, 16, ST77XX_GREEN);
tft.drawBitmap(16*2, 32, str_f_LIAN, 16, 16, ST77XX_GREEN);
tft.drawBitmap(16*3, 32, str_f_JIE3, 16, 16, ST77XX_GREEN);
tft.drawBitmap(16*4, 32, str_f_ZHONG, 16, 16, ST77XX_GREEN);
效果展示:
任务2:网络功能使用(任务要求:完成网络功能的使用,能够创建热点和连接到WiFi)
要使用Wi-Fi相关功能,首先要包含如下头文件:
#include <WiFi.h>
针对STA与AP混合模式,需要定义各自的SSID与PASSWORD:
// Set WiFi credentials
#define WIFI_SSID "TP-LINK_XXX"
#define WIFI_PASS "XXXXXXXXXXX"
// Set AP credentials
#define AP_SSID "Adafruit-S3-FTHR"
#define AP_PASS "followme"
接着启动AP与STA模式:
// Begin Access Point
WiFi.mode(WIFI_AP_STA);
WiFi.softAP(AP_SSID, AP_PASS);
// Begin WiFi
WiFi.begin(WIFI_SSID, WIFI_PASS);
// Connecting to WiFi...
Serial.print("Connecting to ");
Serial.print(WIFI_SSID);
while (WiFi.status() != WL_CONNECTED)
{
delay(100);
Serial.print(".");
}
STA模式启动成功后,开发板连接到指定的AP即家里的路由器。可以看到S3已经有了自己对IP地址,并且电脑也连接到了路由器,可以PING通S3开发板。
接下来测试一下AP接入点模式,即电脑或者手机可以连接到S3开发板。
任务2-Wi-Fi-AP-STA
可以看到电脑已经连接成功到S3开发板提供的AP。
实物展示:
任务3:控制WS2812B(要求:使用按键控制板载Neopixel LED的显示和颜色切换)
本节介绍了如何在Arduino环境下驱动本次活动板卡自带的一颗WS2812 RGB灯珠(即Adafruit称之为NeoPixel)
板载的这颗NeoPixel与S3的IO33引脚相连接,可以从电路图看出:
首先在库管理器中下载“Adafruit NeoPixel”库:
之后包含该库的头文件:
#include <Adafruit_NeoPixel.h>
//NeoPixel Related:
// Which pin on the Arduino is connected to the NeoPixels?
// On a Trinket or Gemma we suggest changing this to 1:
#define LED_PIN 33
// How many NeoPixels are attached to the Arduino?
#define LED_COUNT 1
// NeoPixel brightness, 0 (min) to 255 (max)
#define BRIGHTNESS 50 // Set BRIGHTNESS to about 1/5 (max = 255)
// Declare our NeoPixel strip object:
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRBW + NEO_KHZ800);
题目要求按键控制NeoPixel,板载一个RST按键,一个BOOT按键。BOOT连接到GPIO0,在程序运行期间可以拿来当作普通的GPIO使用。并且使能上拉,配置中断输入源。
Setup(){
pinMode(BUTTON_BOOT, INPUT_PULLUP);
attachInterrupt( digitalPinToInterrupt(BUTTON_BOOT), onButtonPressed, FALLING);
}
void onButtonPressed(void){
colorSelector = buttonPressedNum%4;
buttonPressedNum++;
Serial.println("Inside ISR. buttonPressedNum = " + String(buttonPressedNum));
}
void loop() {
switch(colorSelector){
case 0:
tft.fillRect(16*7,64,32,16,ST77XX_BLACK);
tft.drawBitmap(16*7, 64, str_f_HONG, 16, 16, ST77XX_RED);
tft.drawBitmap(16*8, 64, str_f_SE, 16, 16, ST77XX_RED);
tft.fillCircle(16*11, 64, 20, ST77XX_RED);
colorWipe(strip.Color(255, 0, 0) , 1000); // Red
break;
case 1:
tft.fillRect(16*7,64,32,16,ST77XX_BLACK);
tft.drawBitmap(16*7, 64, str_f_LV, 16, 16, ST77XX_GREEN);
tft.drawBitmap(16*8, 64, str_f_SE, 16, 16, ST77XX_GREEN);
tft.fillCircle(16*11, 64, 20, ST77XX_GREEN);
colorWipe(strip.Color( 0, 255, 0) , 1000); // Green
break;
case 2:
tft.fillRect(16*7,64,32,16,ST77XX_BLACK);
tft.drawBitmap(16*7, 64, str_f_LV, 16, 16, ST77XX_BLUE);
tft.drawBitmap(16*8, 64, str_f_SE, 16, 16, ST77XX_BLUE);
tft.fillCircle(16*11, 64, 20, ST77XX_BLUE);
colorWipe(strip.Color( 0, 0, 255) , 1000); // Blue
break;
case 3:
tft.fillRect(16*7,64,32,16,ST77XX_BLACK);
tft.fillCircle(16*11, 64, 20, ST77XX_BLACK);
rainbowFade2White(3, 3, 1);
break;
default:
break;
}
效果展示:
任务4:分任务1:日历&时钟——完成一个可通过互联网更新的万年历时钟,并显示当地的天气信息
之前注册过心知天气,所以任务4的天气部分继续使用这个免费的天气API服务。如果是第一次使用心知天气,需要去注册然后获取key。
本任务我选择的API是:https://seniverse.yuque.com/hyper_data/api_v3/nyiu3t。获取指定城市的天气实况。付费用户可获取全部数据,免费用户只返回天气现象文字、代码和气温 3 项数据。
//心知天气
const char *host = "api.seniverse.com";
const char *privateKey = "替换成自己的KEY哦";
const char *city = "shanghai";
const char *language = "en";
struct WetherData{
char city[32];
char weather[64];
char high[32];
char low[32];
char humi[32];
};
在Arduino环境下,需要使用到ArduinoJson库来解析心知天气返回的JSON格式的数据,因此先要安装一下这个库。
#include <ArduinoJson.h>
在尝试FreeRTOS的过程中,遇到了问题,板卡反复重启。后来把线程的Stack从1024字节调整到4096字节,问题解决。
对于天气和时间,分别需要与心知天气服务器与NTP时间服务器进行交互,因此在setup()里面分别创建两个线程来对应处理。
xTaskCreatePinnedToCore(
Task_Weather_Get
, "Task_Weather_Get"
, 4096 // Stack size
, NULL
, 1 // Priority
, NULL
, 0);
xTaskCreatePinnedToCore(
Task_NTP_Time_Get
, "Task_NTP_Time_Get"
, 4096 // Stack size
, NULL
, 1 // Priority
, NULL
, 0);
天气获取核心代码:
void Task_Weather_Get(void *pvParameters){
(void) pvParameters;
// 建立心知天气API当前天气请求资源地址
String reqRes = "/v3/weather/now.json?key=" + reqUserKey +
+ "&location=" + reqLocation +
"&language=en&unit=" +reqUnit;
for(;;){
// 向心知天气服务器服务器请求信息并对信息进行解析
httpRequest(reqRes);
Serial.println("Next Weather Query will be " + String(weather_query_delay/1000/60) + " min later");
delay(weather_query_delay);
}
}
// 向心知天气服务器服务器请求信息并对信息进行解析
void httpRequest(String reqRes){
WiFiClient client;
// 建立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.");
}
weather_query_delay = 1000*60*5;
// 利用ArduinoJson库解析心知天气响应信息
parseInfo(client);
} else {
weather_query_delay = 3000;
Serial.println(" connection failed!");
}
//断开客户端与服务器连接工作
client.stop();
}
心知天气返回的是Json格式的数据,因此需要用ArduinoJson来解析,提取感兴趣的天气内容。
NTP时间获取需要先初始化NTP Server,国内还是选择阿里云的NTP服务稳定些,端口号123
接着初始化Ntp服务器
void Ntp_Init(void){
// Initialize a NTPClient to get time
timeClient.begin();
// Set offset time in seconds to adjust for your timezone, for example:
// GMT +1 = 3600
// GMT +8 = 28800
// GMT -1 = -3600
// GMT 0 = 0
timeClient.setTimeOffset(28800);
}
void Task_NTP_Time_Get(void *pvParameters){
(void) pvParameters;
// 通过NTP Server获取时间,日期
for(;;){
while(!timeClient.update()) {
timeClient.forceUpdate();
}
// The formattedDate comes with the following format:
// 2018-05-28T16:00:13Z
// We need to extract date and time
formattedDate = timeClient.getFormattedDate();
Serial.println(formattedDate);
// Extract date
int splitT = formattedDate.indexOf("T");
dayStamp = formattedDate.substring(0, splitT);
Serial.print("DATE: ");
Serial.println(dayStamp);
// Extract time
timeStamp = formattedDate.substring(splitT+1, formattedDate.length()-1);
Serial.print("HOUR: ");
Serial.println(timeStamp);
tft.fillRect(0,0,240,16,ST77XX_BLACK);
tft.setCursor(0, 0);
tft.setTextColor(ST77XX_WHITE);
tft.setTextSize(2);
tft.print(dayStamp);
tft.setCursor(136, 0);
tft.print(timeStamp);
delay(1000);
}
}
实物展示:
任务4-时间-天气
总结:
非常感谢这次的Follow-Me活动。学习到了国外优秀的开源团队Adafruit的ESP32S3作品,设计非常精美。在Arduino环境里面也学会了如何显示汉字,如何联网,如何控制WS2812灯珠以及获取网络时间等,最终实现了一个具有天气和时间功能的小作品。
参考网友:
1. Lucheni 【得捷电子Follow me第2期】Arduino速通教程 bbs.eeworld.com.cn/thread-1254565-1-1.html
2. ESP32 BLE Server and Client (Bluetooth Low Energy) https://randomnerdtutorials.com/esp32-ble-server-client/
3. ESP32 WebSocket Server: Display Sensor Readings https://randomnerdtutorials.com/esp32-websocket-server-sensor/
4. ESP32 Useful Wi-Fi Library Functions (Arduino IDE) https://randomnerdtutorials.com/esp32-useful-wi-fi-functions-arduino/
5. 太极创客 物联网平台 www.taichi-maker.com/homepage/iot-development/iot-platform/
6. Getting Date and Time with ESP32 on Arduino IDE (NTP Client) https://randomnerdtutorials.com/esp32-ntp-client-date-time-arduino-ide/
补充内容 (2023-10-14 13:54):
审核老师您好:任务3,控制neo-pixel部分增加了旋转编码器,详细内容在评论区。原帖已经无法编辑。谢谢理解。
|