[作品提交] 【得捷电子Follow me第2期】任务4:分任务1:日历&时钟

aramy   2023-10-10 09:44 楼主

【得捷电子Follow me第2期】 任务4:分任务1:日历&时钟——完成一个可通过互联网更新的万年历时钟,并显示当地的天气信息。

开发工具:Vscode+platformio 。使用arduino进行进行编程。

配置文件:platformio.ini

[env:adafruit_feather_esp32s3_tft]
platform = espressif32
board = adafruit_feather_esp32s3_tft
framework = arduino

monitor_speed = 115200
upload_speed = 1500000
board_build.f_cpu = 240000000L
board_build.f_flash = 80000000L
board_build.flash_mode = dio
build_flags = 
	-DCORE_DEBUG_LEVEL=3
	-Iinclude

lib_deps =
  adafruit/Adafruit GFX Library @ ^1.11.8
  adafruit/Adafruit ST7735 and ST7789 Library @ ^1.10.3
  arduino-libraries/NTPClient @ ^3.2.1
  bblanchon/ArduinoJson @ ^6.21.3
  adafruit/Adafruit BMP280 Library @ ^2.6.8

玩了好久的CPY,想再尝试一下Arduino。板子上缺失的BMP280芯片,从淘宝上购买了一块焊上了,手艺不好,搞得屏幕黄了一块。既然装上了BMP280就做个简单的日历/时钟+天气展示吧!

作为时钟+天气,那么首先就要联网。通过互联网获取这些内容最为容易。通过一个单独的任务处理连接wifi的功能。连接完成wif后,与互联网进行校时。这里自动校时使用了NTPClient这个库。

#include "wifi_conn.h"
/**
 * Task: monitor the WiFi connection and keep it alive!
 *
 * When a WiFi connection is established, this task will check it every 10 seconds
 * to make sure it's still alive.
 *
 * If not, a reconnect is attempted. If this fails to finish within the timeout,
 * the ESP32 will wait for it to recover and try again.
 */
WiFiUDP ntpUDP;
// NTPClient timeClient(ntpUDP);
NTPClient timeClient(ntpUDP, "ntp1.aliyun.com", 60 * 60 * 8, 30 * 60 * 1000);
// NTPClient timeClient(ntpUDP,"ntp.tuna.tsinghua.edu.cn");

void keepWiFiAlive(void *parameter)
{
    for (;;)
    {
        if (WiFi.status() == WL_CONNECTED)
        {
            // Serial.print(WiFi.localIP());
            // Serial.print("    ");
            // Serial.println(timeClient.getFormattedTime());
            vTaskDelay(10000 / portTICK_PERIOD_MS);
            continue;
        }
        ESP_LOGE(TAG, "[WIFI] Connecting");
        WiFi.mode(WIFI_STA);
        WiFi.begin(WIFI_NETWORK, WIFI_PASSWORD);

        unsigned long startAttemptTime = millis();

        // Keep looping while we're not connected and haven't reached the timeout
        while (WiFi.status() != WL_CONNECTED &&
               millis() - startAttemptTime < WIFI_TIMEOUT_MS)
        {
        }

        // When we couldn't make a WiFi connection (or the timeout expired)
        // sleep for a while and then retry.
        if (WiFi.status() != WL_CONNECTED)
        {
            ESP_LOGE(TAG, "[WIFI] FAILED");
            vTaskDelay(WIFI_RECOVER_TIME_MS / portTICK_PERIOD_MS);
            continue;
        }

        timeClient.begin();
        timeClient.setTimeOffset(28800); //+1区,偏移3600,+8区,偏移3600*8
        timeClient.update();
        log_i("[WIFI] Connected: %s    %s", (WiFi.localIP()).toString(), timeClient.getFormattedTime());
        Serial.print(WiFi.localIP());
        Serial.print("    ");
        Serial.println(timeClient.getFormattedTime());
    }
}

联网后,需要获取天气信息。天气信息使用独立的任务来循环进行。每5分钟获取一次。天气信息从心知天气获取。心知天气获取到的是json数据,这里json数据解析学习了论坛中老师的帖子!然后还有个BMP280芯片,这个芯片能够获得当前环境的温度和气压。通过BMP280的库,很方便地通过IIC总线方式获得了温度(摄氏度),气压(帕斯卡)。该库还提供了一个计算高度的方法,也拿来用用,不过计算出的高度有点怪,貌似是用气压直接换算出来的。

有了天气信息还需要展示信息,这里使用了第三方的st7789的库进行展示。使用第三方库驱动屏幕简单了很多,不用去写麻烦的寄存器了!

image.png  

最后是将所有收集到到的信息一股脑地展示到tft屏幕上去,从上到下依次是 :

第一行:日历信息:年月日 时分秒。

第二行:当前气压(千帕),和温度(摄氏度)。

第三行:当前高度(米)(这里测出来我这里是低于海平面

第四行:所处的城市。和当前天气预报的温度值(摄氏度)。

第五行:天气情况。

 

image.png  

这里代码还是有些乱,有空了需要将功能模块化!

#include <Arduino.h>
#include "WiFi.h"
#include <Adafruit_GFX.h> // Core graphics library
#include "Adafruit_ST7789.h"
#include "wifi_conn.h"
#include <ArduinoJson.h>
#include <Adafruit_BMP280.h>

Adafruit_BMP280 bmp; // I2C

// 心知天气HTTP请求所需信息
String reqUserKey = "xxxx"; // 私钥
String reqLocation = "dongguan";        // 城市
String reqUnit = "c";                   // 摄氏/华氏
uint32_t weather_query_delay = 0;
//心知天气
const char *host = "api.seniverse.com"; // 将要连接的服务器地址
const int httpPort = 80;                // 将要连接的服务器端口

Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
void Tft_Init(void)
{
  // 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("TFT ST7789 240* 135 Initialized"));

  tft.setCursor(20, 50);
  tft.setTextColor(ST77XX_BLUE);
  tft.setTextSize(3);
  tft.println("Follow me 2");
}

// 利用ArduinoJson库解析心知天气响应信息
String results_0_now_text_str;
int results_0_now_temperature_int;
void parseInfo(WiFiClient client)
{
  const size_t capacity = JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(1) + 2 * JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(6) + 230;
  DynamicJsonDocument doc(capacity);

  deserializeJson(doc, client);

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

  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"]; 

  results_0_now_text_str = results_0_now["text"].as<String>();
  int results_0_now_code_int = results_0_now["code"].as<int>();
  results_0_now_temperature_int = results_0_now["temperature"].as<int>();

  Serial.println(F("======Weahter Now======="));
  Serial.print(F("Weather Now: "));
  Serial.print(results_0_now_text_str);
  Serial.print(F(" "));
  Serial.println(results_0_now_code_int);
  Serial.print(F("Temperature: "));
  Serial.print(results_0_now_temperature_int);
  Serial.print("C    ");
  Serial.println(timeClient.getFormattedTime());
  Serial.println(F("========================"));

  tft.fillScreen(ST77XX_BLACK);
  tft.setCursor(0, 74);
  tft.setTextColor(ST77XX_ORANGE);
  tft.setTextSize(2);
  tft.println("DONG GUAN");
  tft.setCursor(0, 100);
  tft.setTextColor(ST77XX_RED);
  tft.print(results_0_now_text_str);
  tft.print(" ");
  tft.setTextSize(3);
  tft.setTextColor(ST77XX_GREEN);
  tft.setCursor(150, 80);
  tft.print(String(results_0_now_temperature_int));
  tft.println("C");

  tft.setCursor(10, 24);            //显示BMP280信息
  tft.setTextSize(2);
  float f = bmp.readTemperature(); //摄氏度
  float P = bmp.readPressure() / 1000.0; // kPa
  float A = bmp.readAltitude(1013.25);   //米
  char strbuf[64];
  sprintf(strbuf,"P:%.fkPa  T:%.1fC ",P,f);
  tft.println(strbuf);
  sprintf(strbuf,"H:%.fM",A);
  tft.setCursor(10, 48); 
  tft.println(strbuf);
}

// 向心知天气服务器服务器请求信息并对信息进行解析
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();
}

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");
    vTaskDelay(weather_query_delay / portTICK_PERIOD_MS);
  }
}

void setup(void)
{
  Serial.begin(115200);
  Tft_Init();
  if (!bmp.begin())
  {
    Serial.println(F("Could not find   a valid BMP280 sensor, check wiring!"));
    while (1)
      ;
  }

  /* Default   settings from datasheet. */
  bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,     /*   Operating Mode. */
                  Adafruit_BMP280::SAMPLING_X2,     /* Temp.   oversampling */
                  Adafruit_BMP280::SAMPLING_X16,    /* Pressure   oversampling */
                  Adafruit_BMP280::FILTER_X16,      /* Filtering.   */
                  Adafruit_BMP280::STANDBY_MS_500); /* Standby time. */

  xTaskCreatePinnedToCore(keepWiFiAlive, "keepWiFiAlive", 1024 * 4, NULL, 2, NULL, ARDUINO_RUNNING_CORE);
  vTaskDelay(5000 / portTICK_PERIOD_MS);
  xTaskCreatePinnedToCore(Task_Weather_Get, "Task_Weather_Get", 1024 * 4, NULL, 1, NULL, 0);
}

void loop(void)
{
  timeClient.update();
  unsigned long epochTime = timeClient.getEpochTime();
  //将epochTime换算成年月日
  struct tm *ptm = gmtime((time_t *)&epochTime);
  // int monthDay = ptm->tm_mday;
  // Serial.print(ptm->tm_year);
  // Serial.print(" ");
  // Serial.print(ptm->tm_wday);
  // Serial.print(" ");
  char strbuf[64];
  sprintf(strbuf, "%d/%02d/%02d %s", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, timeClient.getFormattedTime());
  tft.fillRect(0, 0, 240, 16, ST77XX_BLACK);
  tft.setCursor(5, 1);
  tft.setTextColor(ST77XX_CYAN);
  tft.setTextSize(2);
  tft.print(strbuf);
  // tft.setCursor(136, 0);
  // tft.print(timeStamp);
  // delay(1000);
  delay(1000);
}

在使用Arduino做开发过程中,freertos还是挺有意思的。ESP32集成了freertos操作系统,这样在开发过程中不同的功能模块使用不同的任务去调用。并且Arduino中的loop方法,本质上也是一个freertos任务。

到了活动到期倒数2天时知道了个噩耗。凑单的模块是不返的!纳尼!哭死在马桶上!哎!汇总一下帖子吧!

任务1:Adafruit ESP32-S3 TFT Feather 控制屏幕显示中文 

任务2:网络功能使用

任务3:控制WS2812B 

任务4:WS2812B效果控制

 

本帖最后由 aramy 于 2023-10-13 08:31 编辑

回复评论 (1)

很多地方啊海拔是通过大气压力测算的,有可能不准  

在爱好的道路上不断前进,在生活的迷雾中播撒光引
点赞  2023-10-13 16:43
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复