2732|3

155

帖子

1

TA的资源

一粒金砂(高级)

楼主
 

【Beetle ESP32-C3】九、OLED时钟和天气助手逻辑说明(Arduino) [复制链接]

       本篇测评承接上篇,介绍上篇完成案例的主要业务逻辑。

1、时钟功能相关说明

       案例利用网络授时,在初始化时配置了ESP32的RTC,之后就可以通过RTC来获取本地时间。其实,这个功能是本人前年开发过的,当时是基于ESP32 WROOM,这次改为C3了。不过当时在下错误的认为其仅是“获取网络时间”并保存到时间结构体变量struct tm t(案例定义变量名为“t”),所以完全是画蛇添足的搞了一个每秒种通过代码更新变量t的值,实际上直接访问RTC就可以随时刷新t值。

       在ESP32-C3的第七篇测评中,本人还延续了这种错误思路,所以编写了相关代码:

 

//两个宏定义用来标志“是否在一分钟内”
#define INMIN     0x60                //in 60s flag
#define ONEMIN    0x61                //one minute flag

//省略其它代码

//循环逻辑中,接收到信号量,说明1s到了,将t.tm_sec更新(即时间结构体秒钟数成员)
//如果t.tm_sec超过59则表示1分钟到了,标志ONEMIN即“不在一分钟内了”
//然后再次printLocalTime()
//其中的调用getLocalTime(&t)实际为读取RTC,被本人错认为“获取网络时间”
void loop() {  
//-----update time state per second
  if(xSemaphoreTake(timersem, 0) == pdTRUE) {
    if((t.tm_sec)++ >= 59)  timestate = ONEMIN;
  }
//-----print localtime per minute & weather info per 5 minutes
  if(timestate == ONEMIN) {
    timestate = INMIN;
    if(WiFi.status() == WL_CONNECTED) {
      printLocalTime();
      if(t.tm_min%5 == 0) getWeather();
    }   
  }
}

//省略其它代码

//定义一个1s定时器,这是定时器回调产生信号量timersem
void IRAM_ATTR onTimer() {
  xSemaphoreGiveFromISR(timersem, NULL);
}

//省略其它代码

//-----print local time function
void printLocalTime() {
  if(!getLocalTime(&t)) {
    Serial.println("Failed to obtain time");
    return;
  }
  Serial.println(&t, "%F %T %A");
}

 

       实际上,本人在写第七篇测评时就已经怀疑之前犯的错误了,不过也没有修改(就是懒得),直到完成上篇的OLED时钟案例出现Bug——时间显示有时会变成“HH:mm:60”,也就是秒钟数显示为60。于是,代码调整为:

 

void loop() {
//-----update time state per second
  if(xSemaphoreTake(timersem, 0) == pdTRUE) {
    getLocalTime(&t);
  }

//省略其它代码
}

 

       本人还尝试了直接在定时器回调中调用getLocalTime(&t),结果系统开始不断重启,并提示重启原因为:“rst:0x3 (RTC_SW_SYS_RST)”,只能改回使用信号量。

2、时钟显示相关说明

       开发阶段,本人也是脑子迷糊,把时间显示的代码也放到接收信号量的语句块中,心想“一秒钟刷新一次屏幕,没毛病啊”。

void loop() {
//-----update time state per second
  if(xSemaphoreTake(timersem, 0) == pdTRUE) {
    getLocalTime(&t);
  }

//省略其它代码
}

 

       不过这样写也出现了莫名其妙的bug,屏幕时不时就会定住。后来也是琢磨过来“当接收不到信号量时,任务本来就会阻塞,刷屏操作没必要放到语句块中”,所以语句块中只做时间刷新就好,刷屏代码放到外面就行。

void loop() {
//-----update time state per second
  if(xSemaphoreTake(timersem, 0) == pdTRUE) {
    getLocalTime(&t);
  }
//-----refresh screen 暂时省略其它代码
  drawLocalTimePage();

}

 

       drawLocalTimePage()是自己编写的显示时间函数,感觉屏幕还有富裕空间,面包板上还插接着AHT10,所以剩余屏幕空间显示传感器温湿度。

       时间显示画面中,第一行(下述代码注释line1处)是“HH:MM:SS”时间显示和星期缩写——即语句:“u8g2.print(&t, "%H:%M:%S  %a");”,struct tm在C++中有定义好的格式化字符,具体可以参考:https://baike.baidu.com/item/strftime/9569073?fr=aladdin 。第一行选择字体是u8g2_font_ncenB14_tr,其具体像素值没找到,结合字体名和目测,本人猜想是7x14的。时间+星期共14个字符(中间两个空格),横向也就是98个像素点,不过设置顶格显示,感觉也挺美观,就没有再修改。

       第二行(下述代码注释line2处)是“YYYY-mm-dd”日期显示,共10个字符,采用字体u8g2_font_8x13_mf(看名字字符像素应该是8*13的),横向占据80个像素,居中显示左右留空24像素(OLED屏128*64),所以设置显示坐标从[24, 18]开始——第一行高度14像素加上行间距定为y坐标18。

       第三、四行显示湿度和温度,以湿度为例,“Humi: ”共六字符(加一个空格),湿度值占六字符(通过dtostrf()函数转换字符串,限定长度为6),后接单位“% rH”占据四字符,这样正好16字符(字体还是u8g2_font_8x13_mf)。因此,顶格显示,湿度行y坐标为32,温度行y坐标为48。

       另外,温湿度值是浮点数,如果使用u8g2.print()输出会出现bug,好像是ESP32-Arduino的Printable接口(框架中的一个类)一直处理不了浮点数,所以这里使用dtostrf()做转换,再用drawStr()显示。不过温度单位“°”是UTF字符,只能用u8g2.print()显示才会成功。

//-----draw local time function
void drawLocalTimePage(void) {
  char str[6] = {0};
  u8g2.clearBuffer();
  //line1. set time & weekday
  u8g2.setFont(u8g2_font_ncenB14_tr);
  u8g2.setCursor(0, 0);           //第一行字从屏幕左上顶点开始
  u8g2.print(&t, "%H:%M:%S  %a");
  //line2. set date
  u8g2.setCursor(24, 18);
  u8g2.setFont(u8g2_font_8x13_mf);
  u8g2.print(&t, "%Y-%m-%d");
  //line3. set aht10 humidity
  u8g2.drawStr(0, 32, "Humi: ");
  memset(str, 0, 6);
  dtostrf(aht_humi.relative_humidity, 6, 2, str);
  u8g2.drawStr(48, 32, str);
  u8g2.drawStr(96,32, "% rH");
  //line4. set aht10 temperature
  u8g2.drawStr(0, 48, "Temp: ");
  memset(str, 0, 6);
  dtostrf(aht_temp.temperature, 6, 2, str);
  u8g2.drawStr(48, 48, str);
  u8g2.setCursor(96, 48);
  u8g2.print("°  C");
  //draw screen
  u8g2.sendBuffer();
}

 

3、天气显示相关说明

       天气显示单独为一个画面,借鉴了u8g2案例“buffer/weather”。u8g2提供的一些字体库中包含天气图标,案例中提供了“晴、多云、阴、雨、雷”五个例子。本人也是在网上没有找到其它介绍,因此本例也只用了上述五个图标。

//u8g2案例,设置固定字体,并给编号67,就可以显示“雷”图标
//本人随意修改67这个编号值,还找到了齿轮和五角星图标
u8g2.setFont(u8g2_font_open_iconic_embedded_6x_t);
u8g2.drawGlyph(x, y, 67);

 

       u8g2的天气案例除了显示天气图标和温度值,还在最后一行显示流水字串,本例将最后一行用于显示城市名——即“Tianjin”。

       本例先通过自定义的getWeather()函数从心知天气请求到实时天气信息,因为刷屏逻辑为了保证时间显示正确,也就是1s刷新一次,而天气每正5分钟点时才请求一次,所以定义了全局变量用来存储天气编号(心知网站定义)、气温值。

 

int code = 0;                         //weather code
int degree = 0;                       //weather temperature

//-----request now weather function
void getWeather() {
  if((WiFi.status() == WL_CONNECTED)) {
    HTTPClient http;           //create HTTPClient instance
    http.begin(url);           //begin HTTP request
    int httpCode = http.GET(); //get response code - normal:200
    if(httpCode > 0) {
      Serial.printf("[HTTP] GET... code: %d\n", httpCode);
      if(httpCode == HTTP_CODE_OK) {
        String payload = http.getString();       
        Serial.println(payload);
        //parse response string  to DynamicJsonDocument instance
        deserializeJson(doc, payload);
        //get top level Json-Object
        JsonObject obj = doc.as<JsonObject>();
        //get "results" property then parse the value to Json-Array
        JsonVariant resultsvar = obj["results"];
        JsonArray resultsarr = resultsvar.as<JsonArray>();
        //get array index 0 which is now weather info
        JsonVariant resultselementvar = resultsarr[0];
        JsonObject resultselementobj = resultselementvar.as<JsonObject>();
        /* get values of city, temp, code, update */
        //get city
        JsonVariant namevar = resultselementobj["location"]["name"];
        String namestr = namevar.as<String>();
        Serial.println(namestr);
        //get temperature,变量degree是全局的
        JsonVariant temperaturevar = resultselementobj["now"]["temperature"];
        String temperaturestr = temperaturevar.as<String>();
        degree = temperaturevar.as<int>();
        Serial.println(temperaturestr);
        //get weather code,变量code是全局的
        JsonVariant codevar = resultselementobj["now"]["code"];
        code = codevar.as<int>();
        Serial.println(code);
        //get last_update time
        JsonVariant last_updatevar = resultselementobj["last_update"];
        String last_updatestr = last_updatevar.as<String>();
        Serial.println(last_updatestr);
      }
    } else {
      Serial.printf("[HTTP] GET... failed, error: %s\n", 
      http.errorToString(httpCode).c_str());
    }
    http.end(); //end http connectiong
  }
}

 

       心知天气对各种天气状态进行了编号(请查看:https://seniverse.yuque.com/books/share/e52aa43f-8fe9-4ffa-860d-96c0f3cf1c49/yev2c3 ),案例只引用了u8g2的五种图标,因而对解析到的心知天气状态码(code全局量)做了判断,以决定要显示的图标样式,天气编号在21以上的就显示一个“五角星”了。

 

图9-1 心知天气现象代码对照表页面截图

 

//-----set weather icon function
void drawWeatherSymbol(u8g2_uint_t x, u8g2_uint_t y, uint8_t symbol) {
  if(symbol>=0 && symbol<4) {         //Sun
    u8g2.setFont(u8g2_font_open_iconic_weather_6x_t);
    u8g2.drawGlyph(x, y, 69);
  } else if(symbol>=4 && symbol<9) {  //Cloudy
    u8g2.setFont(u8g2_font_open_iconic_weather_6x_t);
    u8g2.drawGlyph(x, y, 65); 
  } else if(symbol==9) {              //Overcast
    u8g2.setFont(u8g2_font_open_iconic_weather_6x_t);
    u8g2.drawGlyph(x, y, 64);
  } else if(symbol>=10 && symbol<13) {//Thunder
    u8g2.setFont(u8g2_font_open_iconic_embedded_6x_t);
    u8g2.drawGlyph(x, y, 67);
  } else if(symbol>=13 && symbol<21) {//Rain
    u8g2.setFont(u8g2_font_open_iconic_weather_6x_t);
    u8g2.drawGlyph(x, y, 67);
  } else {                            //Others show star
    u8g2.setFont(u8g2_font_open_iconic_weather_6x_t);
    u8g2.drawGlyph(x, y, 68);
  }
}
//-----set weather temperature degree function
void drawWeatherDegree(uint8_t symbol, int degree) {
  drawWeatherSymbol(0, 0, symbol);
  u8g2.setFont(u8g2_font_logisoso32_tf);
  u8g2.setCursor(48+6, 10);
  u8g2.print(degree);
  u8g2.print("°C"); //requires enableUTF8Print()
}
//-----draw now weather function
void drawNowWeatherPage(int code, int temp) {
  u8g2.clearBuffer();
  drawWeatherDegree(code, temp);
  u8g2.setCursor(32, 48);
  u8g2.setFont(u8g2_font_8x13_mf);
  u8g2.print("Tianjin");
  u8g2.sendBuffer();
}

 

      对应的,loop()中的代码就是:

void loop() {
//-----update time state per second
  if(xSemaphoreTake(timersem, 0) == pdTRUE) {
    getLocalTime(&t);
  }
//-----refresh screen 
  if(pagestate == TIMEPAGE)
    drawLocalTimePage();    
  else if(pagestate == WEATHERPAGE) 
    drawNowWeatherPage(code, degree);  
//-----update weather info per 5 minutes 5分钟检测一次温湿度和请求一次天气
  if(t.tm_min%5==0 && t.tm_sec==0) {
    getAHT10();
    if(WiFi.status() == WL_CONNECTED) {
      getWeather();
    }
  }
//暂时省略其它代码
}

 

4、按键翻页相关说明

       最后就是按键翻页了,这里开始考虑使用按键中断,但是触发中断后也不时出现重启,个人猜测可能是ISR和信号量之间有冲突,所以将翻页判断逻辑放到了loop()中。

       首先,定义了一个枚举类型,表示不同的显示页,后续如果还想增加显示页面,可以再扩展枚举成员。

       接着,就是定义记录页面编号的全局量,并初始化为“TIMEPAGE”,也就是开机后默认显示时钟画面。

//-----show page enum
typedef enum {
  TIMEPAGE = 0,
  WEATHERPAGE,
} AITA_PAGE_INDEX;

//-----global variable of page No
int pagestate = TIMEPAGE;

 

       然后,就是宏定义按键IO——本例用IO7,并作IO初始化。

#define KEY             7


void setup() {
//-----initialize BSP
  Serial.begin(115200);
  pinMode(KEY, INPUT_PULLUP);

//省略其它代码

}

 

       最后,由于按键中断的不给力,只能在loop()中进行按键判断了。

void loop() {
//-----update time state per second
  if(xSemaphoreTake(timersem, 0) == pdTRUE) {
    getLocalTime(&t);
  }
//-----refresh screen 
  if(pagestate == TIMEPAGE)
    drawLocalTimePage();    
  else if(pagestate == WEATHERPAGE) 
    drawNowWeatherPage(code, degree);  
//-----update weather info per 5 minutes
  if(t.tm_min%5==0 && t.tm_sec==0) {
    getAHT10();
    if(WiFi.status() == WL_CONNECTED) {
      getWeather();
    }
  }
//-----read KEY 按键判断逻辑,采用枚举在另赠页面时可以不修改此处的逻辑
  if(digitalRead(KEY) == 0) {
    delay(30);
    if(digitalRead(KEY) == 0) {
      if(pagestate == WEATHERPAGE) pagestate = TIMEPAGE;
      else                         pagestate++;
    }
  }
}

 

此帖出自无线连接论坛

最新回复

想看看效果,有视频的话就好了~   详情 回复 发表于 2022-9-6 13:11
点赞 关注(1)
 

回复
举报

7047

帖子

11

TA的资源

版主

沙发
 

楼主的U8g2用得非常好,建议除上图片+视频,让大家一睹为快!

此帖出自无线连接论坛

点评

好的,我再努力  详情 回复 发表于 2022-9-5 12:15
 
 

回复

155

帖子

1

TA的资源

一粒金砂(高级)

板凳
 
lugl4313820 发表于 2022-9-5 12:03 楼主的U8g2用得非常好,建议除上图片+视频,让大家一睹为快!

好的,我再努力

此帖出自无线连接论坛
 
 
 

回复

7244

帖子

2

TA的资源

版主

4
 

想看看效果,有视频的话就好了~

此帖出自无线连接论坛
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
快速回复 返回顶部 返回列表