7 Commits

Author SHA1 Message Date
Jerry
21dc3ff831 1.1.3 2025-10-03 11:14:19 +08:00
Jerry
6e3d9cd06d 1.1.3 2025-10-03 11:08:10 +08:00
Jerry
0865a9492a 1.1.3 2025-10-03 11:04:55 +08:00
Jerry
d085750073 1.1.2 2025-09-29 18:39:59 +08:00
Jerry
64e5980d3d 1.1.1 2025-09-21 22:32:06 +08:00
Jerry
f631e8632a 1.1.0 2025-09-18 12:20:10 +08:00
Jerry
d1511d7035 1.1.0 2025-09-18 12:10:10 +08:00
14 changed files with 242 additions and 80 deletions

View File

@@ -2,6 +2,7 @@
墨水屏日历采用三色4.2寸墨水屏,展示基本月历信息,支持农历、公共假期、倒计日、天气(实时天气、每日天气)展示。<br>
项目以低难度、低成本的方式,方便爱好者实现属于自己的低功耗月历。<br>
<img src="./assets/img/sample.jpg" width="60%"><br>
<img src="./assets/img/sample2.jpg" width="60%"><br>
Bilibili连接https://www.bilibili.com/video/BV1wHDhYoE3G/<br>
注:固件仅供个人下载免费使用,禁止商用。
@@ -28,8 +29,11 @@ Bilibili连接https://www.bilibili.com/video/BV1wHDhYoE3G/<br>
* 其他
* 按钮->(PIN_14, GND)
* LED->22(板载)
* 电池ADC->32
2. 三色墨水屏排线插入时注意针脚方向,屏幕排线和驱动板排线1号针脚均是悬空,注意对齐。
3. 电池接口需要是ph2.0,且注意正负极(开发板上有标注),如果电池的正负极反了,可以用镊子调整电池插头。
1. 可选电池电压检测电路需要通过两个相同的大电阻建议500K以上串联通过分压进行检测电池电压。如图
<img src="./assets/img/battery_adc.png" width="30%"><br>
4. 烧录固件<br>
使用ESP32的烧录工具flash_download_tool烧录固件. [Flash Download Tools](https://www.espressif.com/en/support/download/other-tools?keys=flash+download+tools)
1. 选择烧录的文件和烧录地址bootloader.bin与partitions.bin烧录过一次后就不需要重复烧录了
@@ -83,7 +87,7 @@ Bilibili连接https://www.bilibili.com/video/BV1wHDhYoE3G/<br>
输入格式,分多段,每段用“;”隔开。
1. 课程数段。"000"三位数字分别代表上午、下午、晚上课程数不可省略。如“430”代表上午4课下午三课晚上无课。
2. 每日课程(多段)。首位为星期数,后接当日的课程,以“,”分隔。如:“二,数学,语文,英语,体育,音乐,德法,”,表示周二的课程为数学、语文...等。
3.: 430;一,语文,数学,体育,美术,科学,综合;二,数学,语文,英语,体育,音乐,德法;三,数学,英语,科学,语文,体育,书法,音乐;四,语文,数学,信息,劳动,德法,体育;五,英语,语文,数学,美术,心理,体育;
3. 例: 430;一,语文,数学,体育,美术,科学,综合;二,数学,语文,英语,体育,音乐,德法;三,数学,英语,科学,语文,体育,书法,音乐;四,语文,数学,信息,劳动,德法,体育;五,英语,语文,数学,美术,心理,体育;
4. 注意:分隔用的“,”“;”,必须为英文符号,不可用中文符号。
* 保存配置后,系统自动重启。
3. Update. OTA升级
@@ -122,10 +126,16 @@ A: 2025年3月1日后注册的和风天气账户有API Host限制请下载1.0
A: 在系统运行状态下(状态灯常亮),单击按键,稍等即可切换课程表的展示。
## Releases
### 1.1.3
* Fix: some bugs.
### 1.1.2
* Refine: 功耗
### 1.1.1
* New Featue: 增加对电池的测量和显示。
### 1.1.0
* New Feature: 课程表。
* Refine: 对针脚统一配置。
* Fix: Some bugs.
* Fix: some bugs.
### 1.0.27
* Fix: 闰月错误。bug from nongli lib
### 1.0.26

BIN
assets/img/battery_adc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
assets/img/sample2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 KiB

6
include/battery.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef __BATTERY_H__
#define __BATTERY_H__
int readBatteryVoltage();
#endif

View File

@@ -10,4 +10,6 @@ void si_weather();
int si_screen_status();
void si_screen();
void print_status();
void print_status();
void si_warning(const char* str);

View File

@@ -5,13 +5,11 @@
#define LOLIN32_LITE
#ifdef LOLIN32_LITE
#ifdef LOLIN32_LITE
// Output PIN
#define SPI_MOSI GPIO_NUM_23
#define SPI_MISO GPIO_NUM_19
#define SPI_MISO GPIO_NUM_19 // Reserved
#define SPI_SCK GPIO_NUM_18
#define SPI_CS GPIO_NUM_5
#define SPI_DC GPIO_NUM_17
#define SPI_RST GPIO_NUM_16
@@ -20,8 +18,10 @@
#define I2C_SDA GPIO_NUM_21
#define I2C_SCL GPIO_NUM_22
// Other PIN
#define PIN_BUTTON GPIO_NUM_14 // 注意由于此按键负责唤醒因此需要选择支持RTC唤醒的PIN脚。
#define PIN_LED GPIO_NUM_22
#define KEY_M GPIO_NUM_14 // 注意由于此按键负责唤醒因此需要选择支持RTC唤醒的PIN脚。
#define PIN_LED_R GPIO_NUM_22
#define PIN_ADC GPIO_NUM_32 // ADC
#endif
#endif

View File

@@ -25,6 +25,7 @@ lib_deps =
mathertel/OneButton@^2.6.1
https://github.com/JADE-Jerry/nongli.git
https://github.com/tignioj/ArduinoUZlib
https://github.com/Wh1teRabbitHU/RX8010SJ
[env:z98]
build_type = release

35
src/battery.cpp Normal file
View File

@@ -0,0 +1,35 @@
/*
电压 (V) 近似剩余电量 状态说明
4.20 100% 刚刚充满,充电器断开
4.10 ~90% 电量非常充足
4.00 ~80% 电量充足
3.90 ~60% 中等电量
3.80 ~50% 中等电量(接近标称电压)
3.75 ~40% 电量偏低
3.70 ~30% 标称电压点,但电量已不多
3.65 ~20% 低电量
3.50 ~10% 极低电量,应立即充电
3.30 0% 放电截止电压,继续放电将损坏电池
充电截止电压 4.2
放电截止电压 3.3
*/
#include "driver/adc.h"
#include "esp_adc_cal.h"
/**
* 获取电池电压mV
*/
int readBatteryVoltage() {
esp_adc_cal_characteristics_t adc_chars;
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_11db, ADC_WIDTH_BIT_12, 1100, &adc_chars);
static const adc1_channel_t channel = ADC1_CHANNEL_4; // GPIO32
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(channel, ADC_ATTEN_11db);
int adc_val = adc1_get_raw(channel);
int voltage = esp_adc_cal_raw_to_voltage(adc_val, &adc_chars);
voltage *= 2;
return voltage;
}

View File

@@ -8,7 +8,7 @@ int8_t BLINK_TYPE;
void led_init()
{
pinMode(PIN_LED, OUTPUT);
pinMode(PIN_LED_R, OUTPUT);
}
void task_led(void *param)
@@ -18,42 +18,42 @@ void task_led(void *param)
switch(BLINK_TYPE)
{
case 0:
digitalWrite(PIN_LED, HIGH); // Off
digitalWrite(PIN_LED_R, HIGH); // Off
vTaskDelay(pdMS_TO_TICKS(1000));
break;
case 1:
digitalWrite(PIN_LED, LOW); // On
digitalWrite(PIN_LED_R, LOW); // On
vTaskDelay(pdMS_TO_TICKS(1000));
break;
case 2:
digitalWrite(PIN_LED, LOW); // On
digitalWrite(PIN_LED_R, LOW); // On
vTaskDelay(pdMS_TO_TICKS(1000));
digitalWrite(PIN_LED, HIGH); // Off
digitalWrite(PIN_LED_R, HIGH); // Off
vTaskDelay(pdMS_TO_TICKS(1000));
break;
case 3:
digitalWrite(PIN_LED, LOW); // On
digitalWrite(PIN_LED_R, LOW); // On
vTaskDelay(pdMS_TO_TICKS(200));
digitalWrite(PIN_LED, HIGH); // Off
digitalWrite(PIN_LED_R, HIGH); // Off
vTaskDelay(pdMS_TO_TICKS(200));
break;
case 4:
vTaskDelay(pdMS_TO_TICKS(200));
digitalWrite(PIN_LED, LOW); // On
digitalWrite(PIN_LED_R, LOW); // On
vTaskDelay(pdMS_TO_TICKS(200));
digitalWrite(PIN_LED, HIGH); // Off
digitalWrite(PIN_LED_R, HIGH); // Off
vTaskDelay(pdMS_TO_TICKS(200));
digitalWrite(PIN_LED, LOW); // On
digitalWrite(PIN_LED_R, LOW); // On
vTaskDelay(pdMS_TO_TICKS(200));
digitalWrite(PIN_LED, HIGH); // Off
digitalWrite(PIN_LED_R, HIGH); // Off
vTaskDelay(pdMS_TO_TICKS(200));
digitalWrite(PIN_LED, LOW); // On
digitalWrite(PIN_LED_R, LOW); // On
vTaskDelay(pdMS_TO_TICKS(200));
digitalWrite(PIN_LED, HIGH); // Off
digitalWrite(PIN_LED_R, HIGH); // Off
vTaskDelay(pdMS_TO_TICKS(1000));
break;
default:
digitalWrite(PIN_LED, HIGH); // Off
digitalWrite(PIN_LED_R, HIGH); // Off
vTaskDelay(pdMS_TO_TICKS(1000));
}
}

View File

@@ -7,6 +7,8 @@
#include <wiring.h>
#include "battery.h"
#include "led.h"
#include "_sntp.h"
#include "weather.h"
@@ -16,7 +18,7 @@
#include "version.h"
#include "OneButton.h"
OneButton button(PIN_BUTTON, true);
OneButton button(KEY_M, true);
void IRAM_ATTR checkTicks() {
button.tick();
@@ -42,8 +44,22 @@ void print_wakeup_reason() {
Serial.println("Wakeup caused by external signal using RTC_IO");
break;
case ESP_SLEEP_WAKEUP_EXT1:
{
Serial.println("Wakeup caused by external signal using RTC_CNTL");
uint64_t status = esp_sleep_get_ext1_wakeup_status();
if (status == 0) {
Serial.println(" *None of the configured pins woke us up");
} else {
Serial.print(" *Wakeup pin mask: ");
Serial.printf("0x%016llX\r\n", status);
for (int i = 0; i < 64; i++) {
if ((status >> i) & 0x1) {
Serial.printf(" - GPIO%d\r\n", i);
}
}
}
break;
}
case ESP_SLEEP_WAKEUP_TIMER:
Serial.println("Wakeup caused by timer");
break;
@@ -54,7 +70,7 @@ void print_wakeup_reason() {
Serial.println("Wakeup caused by ULP program");
break;
default:
Serial.printf("Wakeup was not caused by deep sleep.\n");
Serial.printf("Wakeup was not caused by deep sleep.\r\n");
}
}
@@ -73,7 +89,7 @@ void setup() {
Serial.begin(115200);
Serial.println(".");
print_wakeup_reason();
Serial.println("\r\n\r\n\r\n");
Serial.println("\r\n\r\n");
delay(10);
button.setClickMs(300);
@@ -82,7 +98,7 @@ void setup() {
button.attachDoubleClick(buttonDoubleClick, &button);
// button.attachMultiClick()
button.attachLongPressStop(buttonLongPressStop, &button);
attachInterrupt(digitalPinToInterrupt(PIN_BUTTON), checkTicks, CHANGE);
attachInterrupt(digitalPinToInterrupt(KEY_M), checkTicks, CHANGE);
Serial.printf("***********************\r\n");
Serial.printf(" J-Calendar\r\n");
@@ -91,8 +107,24 @@ void setup() {
Serial.printf("Copyright © 2022-2025 JADE Software Co., Ltd. All Rights Reserved.\r\n\r\n");
led_init();
led_fast();
led_on();
delay(1000);
int voltage = readBatteryVoltage();
Serial.printf("Battery: %d mV\r\n", voltage);
if(voltage < 1000) {
Serial.println("[WARN]无电池。");
} else if(voltage < 3000) {
Serial.println("[WARN]电量低于3v系统休眠。");
go_sleep();
} else if (voltage < 3300) {
// 低于3.3v,电池电量用尽,屏幕给警告,然后关机。
Serial.println("[WARN]电量低于3.3v,警告并系统休眠。");
si_warning("电量不足,请充电!");
go_sleep();
}
Serial.println("Wm begin...");
led_fast();
wm.setHostname("J-Calendar");
wm.setEnableConfigPortal(false);
wm.setConnectTimeout(10);
@@ -280,7 +312,6 @@ void buttonDoubleClick(void* oneButton) {
_idle_millis = millis();
}
// 重置系统,并重启
void buttonLongPressStop(void* oneButton) {
Serial.println("Button long press.");
@@ -298,76 +329,62 @@ void buttonLongPressStop(void* oneButton) {
#define TIMEOUT_TO_SLEEP 10 // seconds
time_t blankTime = 0;
void go_sleep() {
// 设置唤醒时间为下个偶数整点。
time_t now = time(NULL);
struct tm tmNow = { 0 };
// Serial.printf("Now: %ld -- %s\n", now, ctime(&now));
localtime_r(&now, &tmNow); // 时间戳转化为本地时间结构
uint64_t p;
// 根据配置情况来刷新如果未配置qweather信息则24小时刷新否则每2小时刷新
Preferences pref;
pref.begin(PREF_NAMESPACE);
String _qweather_key = pref.getString(PREF_QWEATHER_KEY, "");
pref.end();
time_t now;
time(&now);
struct tm local;
localtime_r(&now, &local);
if (_qweather_key.length() == 0 || weather_type() == 0) { // 没有配置天气或者使用按日天气,则第二天刷新。
Serial.println("Sleep to next day.");
now += 3600 * 24;
localtime_r(&now, &tmNow); // 将新时间转成tm
// Serial.printf("Set1: %ld -- %s\n", now, ctime(&now));
struct tm tmNew = { 0 };
tmNew.tm_year = tmNow.tm_year;
tmNew.tm_mon = tmNow.tm_mon; // 月份从0开始
tmNew.tm_mday = tmNow.tm_mday; // 日期
tmNew.tm_hour = 0; // 小时
tmNew.tm_min = 0; // 分钟
tmNew.tm_sec = 10; // 秒, 防止离线时出现时间误差所以延后10s
time_t set = mktime(&tmNew);
p = (uint64_t)(set - time(NULL));
Serial.printf("Sleep time: %ld seconds\n", p);
} else {
if (tmNow.tm_hour % 2 == 0) { // 将时间推后两个小时,偶整点刷新。
now += 7200;
} else {
now += 3600;
// Sleep to next day
int secondsToNextDay = (24 - local.tm_hour) * 3600 - local.tm_min * 60 - local.tm_sec;
if (secondsToNextDay <= 0) {
secondsToNextDay += 24 * 3600;
}
localtime_r(&now, &tmNow); // 将新时间转成tm
// Serial.printf("Set1: %ld -- %s\n", now, ctime(&now));
struct tm tmNew = { 0 };
tmNew.tm_year = tmNow.tm_year;
tmNew.tm_mon = tmNow.tm_mon; // 月份从0开始
tmNew.tm_mday = tmNow.tm_mday; // 日期
tmNew.tm_hour = tmNow.tm_hour; // 小时
tmNew.tm_min = 0; // 分钟
tmNew.tm_sec = 10; // 秒, 防止离线时出现时间误差所以延后10s
time_t set = mktime(&tmNew);
p = (uint64_t)(set - time(NULL));
Serial.printf("Sleep time: %ld seconds\n", p);
Serial.printf("Seconds to next day: %d seconds.\n", secondsToNextDay);
p = (uint64_t)(secondsToNextDay);
} else {
// Sleep to next even hour.
int secondsToNextHour = (60 - local.tm_min) * 60 - local.tm_sec;
if (secondsToNextHour <= 0) {
secondsToNextHour += 3600;
}
if ((local.tm_hour % 2) == 0) { // 如果是奇数点则多睡1小时
secondsToNextHour += 3600;
}
Serial.printf("Seconds to next even hour: %d seconds.\n", secondsToNextHour);
p = (uint64_t)(secondsToNextHour);
}
p += 10; // 额外增加10秒避免过早唤醒
esp_sleep_enable_timer_wakeup(p * (uint64_t)uS_TO_S_FACTOR);
#ifdef CONFIG_IDF_TARGET_ESP32
esp_sleep_enable_ext0_wakeup(PIN_BUTTON, 0);
#elif CONFIG_IDF_TARGET_ESP32C3
esp_deep_sleep_enable_gpio_wakeup(PIN_BUTTON, ESP_GPIO_WAKEUP_GPIO_HIGH);
gpio_set_direction(PIN_BUTTON, GPIO_MODE_INPUT);
#endif
esp_sleep_enable_ext0_wakeup(KEY_M, LOW);
// 省电考虑关闭RTC外设和存储器
// esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); // RTC IO, sensors and ULP, 注意由于需要按键唤醒所以不能关闭否则会导致RTC_IO唤醒失败
// esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); // RTC IO, sensors and ULP, 注意由于需要按键唤醒所以不能关闭否则会导致RTC_IO唤醒(ext0)失败
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF); //
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_OFF);
gpio_deep_sleep_hold_dis(); // 解除所有引脚的保持状态
// 省电考虑重置gpio平均每针脚能省8ua。
gpio_reset_pin(PIN_LED); // 减小deep-sleep电流
gpio_reset_pin(PIN_LED_R); // 减小deep-sleep电流
gpio_reset_pin(SPI_CS); // 减小deep-sleep电流
gpio_reset_pin(SPI_DC); // 减小deep-sleep电流
gpio_reset_pin(SPI_RST); // 减小deep-sleep电流
gpio_reset_pin(SPI_BUSY); // 减小deep-sleep电流
gpio_reset_pin(SPI_MOSI); // 减小deep-sleep电流
gpio_reset_pin(SPI_MISO); // 减小deep-sleep电流
gpio_reset_pin(SPI_SCK); // 减小deep-sleep电流
gpio_reset_pin(PIN_ADC); // 减小deep-sleep电流
gpio_reset_pin(I2C_SDA); // 减小deep-sleep电流
gpio_reset_pin(I2C_SCL); // 减小deep-sleep电流
delay(10);
Serial.println("Deep sleep...");

View File

@@ -1,10 +1,14 @@
#include "screen_ink.h"
#include <weather.h>
#include <API.hpp>
#include "holiday.h"
#include "nongli.h"
#include "battery.h"
int voltage;
#include <_preference.h>
#include <U8g2_for_Adafruit_GFX.h>
@@ -71,6 +75,11 @@ struct
int16_t cdDayX;
int16_t cdDayY;
int16_t statusX;
int16_t statusY;
int16_t statusW;
int16_t statusH;
int16_t weatherX;
int16_t weatherY;
int16_t weatherW;
@@ -115,6 +124,11 @@ void init_cal_layout_size() {
calLayout.tW = 60;
calLayout.tH = calLayout.topH / 2;
calLayout.statusX = 300;
calLayout.statusY = 0;
calLayout.statusW = display.width() - calLayout.weatherX;
calLayout.statusH = 14;
calLayout.weatherX = 300;
calLayout.weatherY = calLayout.topY;
calLayout.weatherW = display.width() - calLayout.weatherX;
@@ -575,6 +589,7 @@ void draw_cd_day(String label, String date) {
}
}
void draw_special_day() {
String str = "Special Days!!!";
@@ -802,6 +817,38 @@ void draw_err(bool partial) {
}
}
void draw_status(bool partial) {
if (partial) {
display.setPartialWindow(calLayout.statusX, calLayout.statusY, calLayout.statusW, calLayout.statusH);
display.firstPage();
display.fillScreen(GxEPD_WHITE);
}
u8g2Fonts.setFontMode(1);
u8g2Fonts.setFontDirection(0);
u8g2Fonts.setBackgroundColor(GxEPD_WHITE);
u8g2Fonts.setForegroundColor(GxEPD_BLACK);
u8g2Fonts.setFont(u8g2_font_siji_t_6x10);
// 电池icon
String iconStr = "";
if(voltage >= 4100) { // 满电
iconStr = "\ue24b";
} else if (voltage >= 3900) { // 多电
iconStr = "\ue249";
} else if (voltage >= 3700) { // 中电量
iconStr = "\ue247";
} else if (voltage >= 3500) { // 低电量
iconStr = "\ue245";
} else { // 空
iconStr = "\ue242";
}
u8g2Fonts.drawUTF8(400 - 12 - 4, 10, iconStr.c_str());
if (partial) {
display.nextPage();
}
}
void drawStudySchedule() {
int i = _study_schedule.substring(0, 3).toInt();
Serial.printf("%d\r\n", i);
@@ -1000,12 +1047,17 @@ int si_calendar_status() {
void task_screen(void* param) {
Serial.println("[Task] screen update begin...");
voltage = readBatteryVoltage();
delay(100);
display.init(115200); // 串口使能 初始化完全刷新使能 复位时间 ret上拉使能
display.setRotation(ROTATION); // 设置屏幕旋转1和3是横向 0和2是纵向
u8g2Fonts.begin(display);
init_cal_layout_size();
display.setFullWindow();
display.firstPage();
display.fillScreen(GxEPD_WHITE);
do {
if (_si_type == 1) {
@@ -1022,8 +1074,9 @@ void task_screen(void* param) {
if (weather_status() == 1) {
draw_weather(false);
} else if (weather_status() == 2) {
draw_err(false);
}
if (voltage > 1000 && voltage < 4300) {
draw_status(false);
}
} while (display.nextPage());
@@ -1035,6 +1088,7 @@ void task_screen(void* param) {
pref.end();
display.powerOff(); // !!!important!!!, 关闭屏幕否则会多0.5ma的空载电流(全屏刷新的话会自动关闭,局部刷新必须手动关闭)
display.hibernate();
Serial.println("[Task] screen update end...");
_screen_status = 1;
@@ -1067,3 +1121,40 @@ void print_status() {
Serial.printf("Calendar: %d\n", si_calendar_status());
Serial.printf("Screen: %d\n", si_screen_status());
}
void si_warning(const char* str) {
Serial.println("Screen warning...");
display.init(115200); // 串口使能 初始化完全刷新使能 复位时间 ret上拉使能
display.setRotation(ROTATION); // 设置屏幕旋转1和3是横向 0和2是纵向
u8g2Fonts.begin(display);
display.setFullWindow();
display.fillScreen(GxEPD_WHITE);
do {
u8g2Fonts.setFontMode(1);
u8g2Fonts.setFontDirection(0);
u8g2Fonts.setBackgroundColor(GxEPD_WHITE);
u8g2Fonts.setForegroundColor(GxEPD_BLACK);
u8g2Fonts.setFont(u8g2_font_open_iconic_all_4x_t);
int space = 8;
int w = u8g2Fonts.getUTF8Width("\u0118") + space;
u8g2Fonts.setFont(FONT_TEXT);
w += u8g2Fonts.getUTF8Width(str);
u8g2Fonts.setForegroundColor(GxEPD_RED);
u8g2Fonts.setFont(u8g2_font_open_iconic_all_4x_t);
u8g2Fonts.setCursor((display.width() - w) / 2, (display.height() + 32) / 2);
u8g2Fonts.print("\u0118");
u8g2Fonts.setForegroundColor(GxEPD_BLACK);
u8g2Fonts.setCursor(u8g2Fonts.getCursorX() + space, u8g2Fonts.getCursorY() - 5);
u8g2Fonts.setFont(FONT_TEXT);
u8g2Fonts.print(str);
} while (display.nextPage());
display.powerOff();
display.hibernate();
}