works
This commit is contained in:
parent
6fdbf1d258
commit
04afd13532
6 changed files with 685 additions and 0 deletions
14
exercises/24_nvs/lib/tbeam_display/library.json
Normal file
14
exercises/24_nvs/lib/tbeam_display/library.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "tbeam_display",
|
||||
"version": "0.1.0",
|
||||
"description": "Reusable SH1106 OLED display service for LilyGO T-Beam Supreme exercises.",
|
||||
"frameworks": "arduino",
|
||||
"platforms": "espressif32",
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "U8g2",
|
||||
"owner": "olikraus",
|
||||
"version": "^2.36.4"
|
||||
}
|
||||
]
|
||||
}
|
||||
204
exercises/24_nvs/lib/tbeam_display/src/TBeamDisplay.cpp
Normal file
204
exercises/24_nvs/lib/tbeam_display/src/TBeamDisplay.cpp
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
#include "TBeamDisplay.h"
|
||||
|
||||
#ifndef OLED_SDA
|
||||
#define OLED_SDA 17
|
||||
#endif
|
||||
|
||||
#ifndef OLED_SCL
|
||||
#define OLED_SCL 18
|
||||
#endif
|
||||
|
||||
#ifndef OLED_ADDR
|
||||
#define OLED_ADDR 0x3C
|
||||
#endif
|
||||
|
||||
namespace tbeam {
|
||||
|
||||
TBeamDisplay::TBeamDisplay(TwoWire& wire) : wire_(wire) {}
|
||||
|
||||
bool TBeamDisplay::begin(const DisplayConfig& config) {
|
||||
config_ = config;
|
||||
clearError();
|
||||
|
||||
if (config_.sda < 0) {
|
||||
config_.sda = OLED_SDA;
|
||||
}
|
||||
if (config_.scl < 0) {
|
||||
config_.scl = OLED_SCL;
|
||||
}
|
||||
if (config_.address == 0) {
|
||||
config_.address = OLED_ADDR;
|
||||
}
|
||||
|
||||
if (config_.beginWire) {
|
||||
wire_.begin(config_.sda, config_.scl);
|
||||
}
|
||||
|
||||
oled_.setI2CAddress(config_.address << 1);
|
||||
if (!oled_.begin()) {
|
||||
ready_ = false;
|
||||
setError("OLED begin failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
ready_ = true;
|
||||
setPowerSave(config_.powerSave);
|
||||
setFont(DisplayFont::NORMAL);
|
||||
clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void TBeamDisplay::update() {
|
||||
}
|
||||
|
||||
void TBeamDisplay::clear() {
|
||||
if (!ready_) {
|
||||
return;
|
||||
}
|
||||
oled_.clearBuffer();
|
||||
oled_.sendBuffer();
|
||||
}
|
||||
|
||||
void TBeamDisplay::clearBuffer() {
|
||||
for (uint8_t i = 0; i < kMaxLines; ++i) {
|
||||
lines_[i][0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void TBeamDisplay::setPowerSave(bool enabled) {
|
||||
if (!ready_) {
|
||||
return;
|
||||
}
|
||||
oled_.setPowerSave(enabled ? 1 : 0);
|
||||
powerSave_ = enabled;
|
||||
}
|
||||
|
||||
void TBeamDisplay::setFont(DisplayFont font) {
|
||||
font_ = font;
|
||||
if (ready_) {
|
||||
oled_.setFont(fontFor(font_));
|
||||
}
|
||||
}
|
||||
|
||||
void TBeamDisplay::showLines(const char* l1,
|
||||
const char* l2,
|
||||
const char* l3,
|
||||
const char* l4,
|
||||
const char* l5,
|
||||
const char* l6) {
|
||||
setLine(0, l1);
|
||||
setLine(1, l2);
|
||||
setLine(2, l3);
|
||||
setLine(3, l4);
|
||||
setLine(4, l5);
|
||||
setLine(5, l6);
|
||||
renderLines();
|
||||
}
|
||||
|
||||
void TBeamDisplay::setLine(uint8_t index, const char* text) {
|
||||
if (index >= kMaxLines) {
|
||||
return;
|
||||
}
|
||||
strlcpy(lines_[index], text ? text : "", sizeof(lines_[index]));
|
||||
}
|
||||
|
||||
void TBeamDisplay::renderLines(uint8_t lineCount) {
|
||||
if (!ready_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lineCount > kMaxLines) {
|
||||
lineCount = kMaxLines;
|
||||
}
|
||||
|
||||
oled_.clearBuffer();
|
||||
oled_.setFont(fontFor(font_));
|
||||
|
||||
uint8_t yStart = 12;
|
||||
uint8_t yStep = 12;
|
||||
if (font_ == DisplayFont::SMALL) {
|
||||
yStart = 10;
|
||||
yStep = 10;
|
||||
} else if (font_ == DisplayFont::LARGE) {
|
||||
yStart = 15;
|
||||
yStep = 16;
|
||||
if (lineCount > 4) {
|
||||
lineCount = 4;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < lineCount; ++i) {
|
||||
if (lines_[i][0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
oled_.drawUTF8(0, yStart + (i * yStep), lines_[i]);
|
||||
}
|
||||
oled_.sendBuffer();
|
||||
}
|
||||
|
||||
void TBeamDisplay::appendLine(const char* text) {
|
||||
for (uint8_t i = 0; i < kMaxLines - 1; ++i) {
|
||||
strlcpy(lines_[i], lines_[i + 1], sizeof(lines_[i]));
|
||||
}
|
||||
setLine(kMaxLines - 1, text);
|
||||
renderLines();
|
||||
}
|
||||
|
||||
void TBeamDisplay::showBoot(const char* title, const char* subtitle, const char* detail) {
|
||||
setFont(DisplayFont::NORMAL);
|
||||
showLines(title ? title : "T-Beam", subtitle, detail);
|
||||
}
|
||||
|
||||
void TBeamDisplay::showStatus(const char* title, const char* left, const char* right, const char* footer) {
|
||||
if (!ready_) {
|
||||
return;
|
||||
}
|
||||
|
||||
oled_.clearBuffer();
|
||||
oled_.setFont(u8g2_font_6x10_tf);
|
||||
if (title) {
|
||||
oled_.drawUTF8(0, 10, title);
|
||||
}
|
||||
oled_.drawHLine(0, 13, 128);
|
||||
|
||||
oled_.setFont(u8g2_font_7x14B_tf);
|
||||
if (left) {
|
||||
oled_.drawUTF8(0, 34, left);
|
||||
}
|
||||
if (right) {
|
||||
const int width = oled_.getUTF8Width(right);
|
||||
int x = 128 - width;
|
||||
if (x < 0) {
|
||||
x = 0;
|
||||
}
|
||||
oled_.drawUTF8(x, 34, right);
|
||||
}
|
||||
|
||||
oled_.setFont(u8g2_font_6x10_tf);
|
||||
if (footer) {
|
||||
oled_.drawUTF8(0, 60, footer);
|
||||
}
|
||||
oled_.sendBuffer();
|
||||
}
|
||||
|
||||
void TBeamDisplay::setError(const char* message) {
|
||||
strlcpy(lastError_, message ? message : "", sizeof(lastError_));
|
||||
}
|
||||
|
||||
void TBeamDisplay::clearError() {
|
||||
lastError_[0] = '\0';
|
||||
}
|
||||
|
||||
const uint8_t* TBeamDisplay::fontFor(DisplayFont font) const {
|
||||
switch (font) {
|
||||
case DisplayFont::SMALL:
|
||||
return u8g2_font_5x8_tf;
|
||||
case DisplayFont::LARGE:
|
||||
return u8g2_font_7x14B_tf;
|
||||
case DisplayFont::NORMAL:
|
||||
default:
|
||||
return u8g2_font_6x10_tf;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace tbeam
|
||||
70
exercises/24_nvs/lib/tbeam_display/src/TBeamDisplay.h
Normal file
70
exercises/24_nvs/lib/tbeam_display/src/TBeamDisplay.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <U8g2lib.h>
|
||||
#include <Wire.h>
|
||||
|
||||
namespace tbeam {
|
||||
|
||||
struct DisplayConfig {
|
||||
int sda = -1;
|
||||
int scl = -1;
|
||||
uint8_t address = 0x3C;
|
||||
bool beginWire = true;
|
||||
bool powerSave = false;
|
||||
};
|
||||
|
||||
enum class DisplayFont : uint8_t {
|
||||
SMALL = 0,
|
||||
NORMAL,
|
||||
LARGE
|
||||
};
|
||||
|
||||
class TBeamDisplay {
|
||||
public:
|
||||
static constexpr uint8_t kMaxLines = 6;
|
||||
static constexpr uint8_t kLineBytes = 32;
|
||||
|
||||
explicit TBeamDisplay(TwoWire& wire = Wire);
|
||||
|
||||
bool begin(const DisplayConfig& config = DisplayConfig{});
|
||||
void update();
|
||||
|
||||
bool ready() const { return ready_; }
|
||||
bool powerSave() const { return powerSave_; }
|
||||
const char* lastError() const { return lastError_; }
|
||||
|
||||
void clear();
|
||||
void clearBuffer();
|
||||
void setPowerSave(bool enabled);
|
||||
void setFont(DisplayFont font);
|
||||
void showLines(const char* l1,
|
||||
const char* l2 = nullptr,
|
||||
const char* l3 = nullptr,
|
||||
const char* l4 = nullptr,
|
||||
const char* l5 = nullptr,
|
||||
const char* l6 = nullptr);
|
||||
void setLine(uint8_t index, const char* text);
|
||||
void renderLines(uint8_t lineCount = kMaxLines);
|
||||
void appendLine(const char* text);
|
||||
void showBoot(const char* title, const char* subtitle = nullptr, const char* detail = nullptr);
|
||||
void showStatus(const char* title, const char* left, const char* right = nullptr, const char* footer = nullptr);
|
||||
|
||||
U8G2& raw() { return oled_; }
|
||||
|
||||
private:
|
||||
void setError(const char* message);
|
||||
void clearError();
|
||||
const uint8_t* fontFor(DisplayFont font) const;
|
||||
|
||||
TwoWire& wire_;
|
||||
DisplayConfig config_{};
|
||||
U8G2_SH1106_128X64_NONAME_F_HW_I2C oled_{U8G2_R0, U8X8_PIN_NONE};
|
||||
bool ready_ = false;
|
||||
bool powerSave_ = false;
|
||||
DisplayFont font_ = DisplayFont::NORMAL;
|
||||
char lines_[kMaxLines][kLineBytes] = {};
|
||||
char lastError_[96] = {};
|
||||
};
|
||||
|
||||
} // namespace tbeam
|
||||
77
exercises/24_nvs/platformio.ini
Normal file
77
exercises/24_nvs/platformio.ini
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
; 20260423 Codex
|
||||
; Exercise 24_nvs
|
||||
|
||||
[platformio]
|
||||
default_envs = cy
|
||||
|
||||
[env]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp32-s3-devkitc-1
|
||||
board_build.partitions = default_8MB.csv
|
||||
monitor_speed = 115200
|
||||
extra_scripts = pre:scripts/set_build_epoch.py
|
||||
lib_extra_dirs =
|
||||
../lib
|
||||
lib_deps =
|
||||
Wire
|
||||
olikraus/U8g2@^2.36.4
|
||||
|
||||
build_flags =
|
||||
-I ../lib/tbeam_display/src
|
||||
-I ../../shared/boards
|
||||
-D BOARD_MODEL=BOARD_TBEAM_S_V1
|
||||
-D OLED_SDA=17
|
||||
-D OLED_SCL=18
|
||||
-D OLED_ADDR=0x3C
|
||||
-D ARDUINO_USB_MODE=1
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
|
||||
[env:amy]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D BOARD_ID=\"AMY\"
|
||||
-D NODE_LABEL=\"Amy\"
|
||||
|
||||
[env:bob]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D BOARD_ID=\"BOB\"
|
||||
-D NODE_LABEL=\"Bob\"
|
||||
|
||||
[env:cy]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D BOARD_ID=\"CY\"
|
||||
-D NODE_LABEL=\"Cy\"
|
||||
|
||||
[env:dan]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D BOARD_ID=\"DAN\"
|
||||
-D NODE_LABEL=\"Dan\"
|
||||
|
||||
[env:ed]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D BOARD_ID=\"ED\"
|
||||
-D NODE_LABEL=\"Ed\"
|
||||
|
||||
[env:flo]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D BOARD_ID=\"FLO\"
|
||||
-D NODE_LABEL=\"Flo\"
|
||||
|
||||
[env:guy]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D BOARD_ID=\"GUY\"
|
||||
-D NODE_LABEL=\"Guy\"
|
||||
13
exercises/24_nvs/scripts/set_build_epoch.py
Normal file
13
exercises/24_nvs/scripts/set_build_epoch.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import time
|
||||
Import("env")
|
||||
|
||||
epoch = int(time.time())
|
||||
utc_tag = time.strftime("%Y%m%d_%H%M%S", time.gmtime(epoch))
|
||||
|
||||
env.Append(
|
||||
CPPDEFINES=[
|
||||
("BUILD_EPOCH", str(epoch)),
|
||||
("FW_BUILD_EPOCH", str(epoch)),
|
||||
("FW_BUILD_UTC", '\"%s\"' % utc_tag),
|
||||
]
|
||||
)
|
||||
307
exercises/24_nvs/src/main.cpp
Normal file
307
exercises/24_nvs/src/main.cpp
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
#include <Arduino.h>
|
||||
#include <Preferences.h>
|
||||
#include <U8g2lib.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "TBeamDisplay.h"
|
||||
|
||||
namespace {
|
||||
|
||||
#ifndef BOARD_ID
|
||||
#define BOARD_ID "CY"
|
||||
#endif
|
||||
|
||||
#ifndef NODE_LABEL
|
||||
#define NODE_LABEL "Cy"
|
||||
#endif
|
||||
|
||||
#ifndef BUILD_EPOCH
|
||||
#define BUILD_EPOCH 0
|
||||
#endif
|
||||
|
||||
using tbeam::DisplayConfig;
|
||||
using tbeam::DisplayFont;
|
||||
using tbeam::TBeamDisplay;
|
||||
|
||||
static constexpr const char* kExerciseTitle = "Exercise 24";
|
||||
static constexpr const char* kExerciseSubtitle = "NVS Persistence Demo";
|
||||
static constexpr const char* kExerciseVersion = "Version 1";
|
||||
static constexpr const char* kNamespace = "mag";
|
||||
static constexpr const char* kBlobKey = "magcal_blob";
|
||||
static constexpr const char* kKeyX = "mag_x";
|
||||
static constexpr const char* kKeyY = "mag_y";
|
||||
static constexpr const char* kKeyZ = "mag_z";
|
||||
static constexpr const char* kKeyEpoch = "mag_epoch";
|
||||
static constexpr uint32_t kSplashMs = 15000;
|
||||
static constexpr uint32_t kInitMessageMs = 2000;
|
||||
|
||||
struct __attribute__((packed)) MagCal {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t z;
|
||||
uint64_t epoch;
|
||||
uint16_t version;
|
||||
};
|
||||
|
||||
static_assert(sizeof(MagCal) == 16, "MagCal layout changed");
|
||||
|
||||
MagCal magcalibration = {-654, 1129, 464, BUILD_EPOCH, 1};
|
||||
|
||||
enum class ValueState : uint8_t {
|
||||
Missing = 0,
|
||||
NullValue,
|
||||
Valid,
|
||||
};
|
||||
|
||||
struct DisplayField {
|
||||
ValueState state = ValueState::Missing;
|
||||
int16_t value = 0;
|
||||
};
|
||||
|
||||
struct EpochField {
|
||||
ValueState state = ValueState::Missing;
|
||||
uint64_t value = 0;
|
||||
};
|
||||
|
||||
struct CalibrationView {
|
||||
DisplayField x{};
|
||||
DisplayField y{};
|
||||
DisplayField z{};
|
||||
EpochField epoch{};
|
||||
};
|
||||
|
||||
Preferences g_preferences;
|
||||
TBeamDisplay g_display;
|
||||
char g_displayLines[4][64] = {};
|
||||
uint32_t g_scrollStartMs = 0;
|
||||
|
||||
bool formatEpochUtc(uint64_t epoch, char* out, size_t outSize) {
|
||||
if (out == nullptr || outSize == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (epoch > static_cast<uint64_t>(INT32_MAX)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
time_t raw = static_cast<time_t>(epoch);
|
||||
struct tm tmUtc;
|
||||
if (gmtime_r(&raw, &tmUtc) == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return strftime(out, outSize, "%Y%m%d_%H%M%S", &tmUtc) > 0;
|
||||
}
|
||||
|
||||
bool isInvalidCalibration(const MagCal& cal) {
|
||||
if (cal.version == 0 || cal.epoch == 0) {
|
||||
return true;
|
||||
}
|
||||
return cal.x == 0 && cal.y == 0 && cal.z == 0 && cal.epoch == 0 && cal.version == 0;
|
||||
}
|
||||
|
||||
void assignMissing(CalibrationView& view) {
|
||||
view = CalibrationView{};
|
||||
}
|
||||
|
||||
void assignNull(CalibrationView& view) {
|
||||
view.x.state = ValueState::NullValue;
|
||||
view.y.state = ValueState::NullValue;
|
||||
view.z.state = ValueState::NullValue;
|
||||
view.epoch.state = ValueState::NullValue;
|
||||
}
|
||||
|
||||
void assignValid(const MagCal& cal, CalibrationView& view) {
|
||||
view.x.state = ValueState::Valid;
|
||||
view.x.value = cal.x;
|
||||
view.y.state = ValueState::Valid;
|
||||
view.y.value = cal.y;
|
||||
view.z.state = ValueState::Valid;
|
||||
view.z.value = cal.z;
|
||||
view.epoch.state = ValueState::Valid;
|
||||
view.epoch.value = cal.epoch;
|
||||
}
|
||||
|
||||
void formatField(const DisplayField& field, char* out, size_t outSize) {
|
||||
if (field.state == ValueState::Missing) {
|
||||
strlcpy(out, "not found", outSize);
|
||||
return;
|
||||
}
|
||||
if (field.state == ValueState::NullValue) {
|
||||
strlcpy(out, "NULL", outSize);
|
||||
return;
|
||||
}
|
||||
snprintf(out, outSize, "%d", field.value);
|
||||
}
|
||||
|
||||
void formatEpochField(const EpochField& field, char* out, size_t outSize) {
|
||||
if (field.state == ValueState::Missing) {
|
||||
strlcpy(out, "not found", outSize);
|
||||
return;
|
||||
}
|
||||
if (field.state == ValueState::NullValue) {
|
||||
strlcpy(out, "NULL", outSize);
|
||||
return;
|
||||
}
|
||||
if (!formatEpochUtc(field.value, out, outSize)) {
|
||||
strlcpy(out, "NULL", outSize);
|
||||
}
|
||||
}
|
||||
|
||||
bool writeCalibration(const MagCal& cal) {
|
||||
const size_t written = g_preferences.putBytes(kBlobKey, &cal, sizeof(cal));
|
||||
if (written != sizeof(cal)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t xWritten = g_preferences.putShort(kKeyX, cal.x);
|
||||
const size_t yWritten = g_preferences.putShort(kKeyY, cal.y);
|
||||
const size_t zWritten = g_preferences.putShort(kKeyZ, cal.z);
|
||||
const size_t epochWritten = g_preferences.putULong64(kKeyEpoch, cal.epoch);
|
||||
return xWritten > 0 && yWritten > 0 && zWritten > 0 && epochWritten > 0;
|
||||
}
|
||||
|
||||
CalibrationView loadCalibration(bool& initializedDefaults) {
|
||||
CalibrationView view;
|
||||
assignMissing(view);
|
||||
initializedDefaults = false;
|
||||
|
||||
const size_t len = g_preferences.getBytesLength(kBlobKey);
|
||||
if (len == sizeof(MagCal)) {
|
||||
MagCal cal{};
|
||||
const size_t read = g_preferences.getBytes(kBlobKey, &cal, sizeof(cal));
|
||||
if (read == sizeof(cal)) {
|
||||
if (isInvalidCalibration(cal)) {
|
||||
assignNull(view);
|
||||
} else {
|
||||
assignValid(cal, view);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
if (writeCalibration(magcalibration)) {
|
||||
initializedDefaults = true;
|
||||
assignValid(magcalibration, view);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
void showSplash() {
|
||||
if (!g_display.ready()) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_display.setFont(DisplayFont::NORMAL);
|
||||
g_display.showLines(kExerciseTitle, kExerciseSubtitle, kExerciseVersion);
|
||||
delay(kSplashMs);
|
||||
}
|
||||
|
||||
void showMessage(const char* line1, const char* line2 = nullptr) {
|
||||
if (!g_display.ready()) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_display.setFont(DisplayFont::NORMAL);
|
||||
g_display.showLines(line1, line2);
|
||||
}
|
||||
|
||||
void showCalibration(const CalibrationView& view) {
|
||||
char xValue[24];
|
||||
char yValue[24];
|
||||
char zValue[24];
|
||||
char epochValue[24];
|
||||
|
||||
formatField(view.x, xValue, sizeof(xValue));
|
||||
formatField(view.y, yValue, sizeof(yValue));
|
||||
formatField(view.z, zValue, sizeof(zValue));
|
||||
formatEpochField(view.epoch, epochValue, sizeof(epochValue));
|
||||
|
||||
snprintf(g_displayLines[0], sizeof(g_displayLines[0]), "magnet calibration.x = %s", xValue);
|
||||
snprintf(g_displayLines[1], sizeof(g_displayLines[1]), "magnet calibration.y = %s", yValue);
|
||||
snprintf(g_displayLines[2], sizeof(g_displayLines[2]), "magnet calibration.z = %s", zValue);
|
||||
snprintf(g_displayLines[3], sizeof(g_displayLines[3]), "magnet calibration.date = %s", epochValue);
|
||||
g_scrollStartMs = millis();
|
||||
}
|
||||
|
||||
void renderCalibrationScreen() {
|
||||
if (!g_display.ready()) {
|
||||
return;
|
||||
}
|
||||
|
||||
U8G2& oled = g_display.raw();
|
||||
oled.clearBuffer();
|
||||
oled.setFont(u8g2_font_4x6_tf);
|
||||
|
||||
const uint32_t elapsed = millis() - g_scrollStartMs;
|
||||
const int16_t scrollStep = static_cast<int16_t>(elapsed / 175U);
|
||||
const uint8_t yPositions[4] = {8, 22, 36, 50};
|
||||
|
||||
for (uint8_t i = 0; i < 4; ++i) {
|
||||
const int width = oled.getUTF8Width(g_displayLines[i]);
|
||||
int16_t x = 0;
|
||||
if (width > 128) {
|
||||
const int travel = width - 128 + 8;
|
||||
x = -static_cast<int16_t>(scrollStep % travel);
|
||||
}
|
||||
oled.drawUTF8(x, yPositions[i], g_displayLines[i]);
|
||||
}
|
||||
|
||||
oled.sendBuffer();
|
||||
}
|
||||
|
||||
void logCalibration(const CalibrationView& view) {
|
||||
char xValue[24];
|
||||
char yValue[24];
|
||||
char zValue[24];
|
||||
char epochValue[24];
|
||||
|
||||
formatField(view.x, xValue, sizeof(xValue));
|
||||
formatField(view.y, yValue, sizeof(yValue));
|
||||
formatField(view.z, zValue, sizeof(zValue));
|
||||
formatEpochField(view.epoch, epochValue, sizeof(epochValue));
|
||||
|
||||
Serial.printf("magnet calibration.x = %s\n", xValue);
|
||||
Serial.printf("magnet calibration.y = %s\n", yValue);
|
||||
Serial.printf("magnet calibration.z = %s\n", zValue);
|
||||
Serial.printf("magnet calibration.date = %s\n", epochValue);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(200);
|
||||
|
||||
DisplayConfig displayConfig;
|
||||
displayConfig.powerSave = false;
|
||||
g_display.begin(displayConfig);
|
||||
|
||||
showSplash();
|
||||
|
||||
if (!g_preferences.begin(kNamespace, false)) {
|
||||
showMessage("NVS open failed", kNamespace);
|
||||
Serial.println("Failed to open Preferences namespace");
|
||||
return;
|
||||
}
|
||||
|
||||
bool initializedDefaults = false;
|
||||
const CalibrationView view = loadCalibration(initializedDefaults);
|
||||
|
||||
if (initializedDefaults) {
|
||||
showMessage("Calibration initialized");
|
||||
delay(kInitMessageMs);
|
||||
}
|
||||
|
||||
showCalibration(view);
|
||||
renderCalibrationScreen();
|
||||
logCalibration(view);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
renderCalibrationScreen();
|
||||
delay(250);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue