Коррекция уровня оптики, вкл по умолчанию, выкл в сне

плюс коррекции по отображению
This commit is contained in:
2026-08-12 08:25:56 +03:00
parent f56595f767
commit 862781fa6a
10 changed files with 124 additions and 26 deletions

View File

@@ -53,6 +53,12 @@ const char *uiFailName(FailReason reason) {
? UiText::FAIL_NAMES[index] : "UNKNOWN"; ? UiText::FAIL_NAMES[index] : "UNKNOWN";
} }
const char *roleCorner(Role role) {
static const char *markers[] = {"O", "M", "S"};
const uint8_t index = static_cast<uint8_t>(role);
return index < sizeof(markers) / sizeof(markers[0]) ? markers[index] : "?";
}
void formatMenuLine(const char *label, const char *value, char *out, size_t size) { void formatMenuLine(const char *label, const char *value, char *out, size_t size) {
constexpr size_t OLED_TEXT_COLUMNS = 21; constexpr size_t OLED_TEXT_COLUMNS = 21;
const size_t labelLength = utf8CharacterCount(label); const size_t labelLength = utf8CharacterCount(label);
@@ -198,6 +204,7 @@ void App::update() {
void App::showIdle() { void App::showIdle() {
setActivePerformance(false); setActivePerformance(false);
setStandbyOpticalOutput();
lastUserActivityMs_ = millis(); lastUserActivityMs_ = millis();
char one[64]; snprintf(one, sizeof(one), "%s%s", UiText::MODE_PREFIX, char one[64]; snprintf(one, sizeof(one), "%s%s", UiText::MODE_PREFIX,
uiRoleName(static_cast<Role>(settings_.role))); uiRoleName(static_cast<Role>(settings_.role)));
@@ -272,6 +279,7 @@ void App::showMenu() {
void App::startTest() { void App::startTest() {
leaveIdlePowerSave(); leaveIdlePowerSave();
pwm_.stop();
setActivePerformance(true); setActivePerformance(true);
params_ = store_.params(settings_); stageCount_ = frequencyPointCount(params_.startHz, params_.endHz); params_ = store_.params(settings_); stageCount_ = frequencyPointCount(params_.startHz, params_.endHz);
stageIndex_ = 0; requestedHz_ = 0; pendingReason_ = FailReason::NONE; stageIndex_ = 0; requestedHz_ = 0; pendingReason_ = FailReason::NONE;
@@ -298,6 +306,7 @@ void App::startTest() {
bool App::armSlave(bool preserveDisplay) { bool App::armSlave(bool preserveDisplay) {
setActivePerformance(false); setActivePerformance(false);
pwm_.stop();
lastUserActivityMs_ = millis(); lastUserActivityMs_ = millis();
params_ = store_.params(settings_); params_ = store_.params(settings_);
stageIndex_ = 0; stageCount_ = frequencyPointCount(params_.startHz, params_.endHz); stageIndex_ = 0; stageCount_ = frequencyPointCount(params_.startHz, params_.endHz);
@@ -306,7 +315,8 @@ bool App::armSlave(bool preserveDisplay) {
if (!radio_.begin()) { if (!radio_.begin()) {
state_ = AppState::FINISHED; pendingReason_ = FailReason::LINK_LOST; state_ = AppState::FINISHED; pendingReason_ = FailReason::LINK_LOST;
slaveRearmAtMs_ = millis() + LINK_HEARTBEAT_TIMEOUT_MS; slaveRearmAtMs_ = millis() + LINK_HEARTBEAT_TIMEOUT_MS;
display_.show(UiText::LINK_FAILED, UiText::RADIO_ERROR); display_.show(UiText::LINK_FAILED, UiText::RADIO_ERROR,
0, 0, roleCorner(Role::SLAVE));
return false; return false;
} }
radio_.setWindowedReceive(true); radio_.setWindowedReceive(true);
@@ -373,6 +383,9 @@ void App::stagePassed() {
void App::startMasterDiscovery() { void App::startMasterDiscovery() {
session_ = esp_random(); if (!session_) session_ = 1; session_ = esp_random(); if (!session_) session_ = 1;
sequence_ = 1; stageIndex_ = 0; requestedHz_ = 0; havePeer_ = false; radio_.flush(); sequence_ = 1; stageIndex_ = 0; requestedHz_ = 0; havePeer_ = false; radio_.flush();
opticalWakeActive_ = true;
lastOpticalWakeToggleMs_ = millis();
pwm_.active();
pendingPacket_ = makePacket(MessageType::DISCOVER); radio_.sendBroadcast(pendingPacket_); pendingPacket_ = makePacket(MessageType::DISCOVER); radio_.sendBroadcast(pendingPacket_);
lastSendMs_ = millis(); retries_ = 0; lastSendMs_ = millis(); retries_ = 0;
state_ = AppState::MASTER_DISCOVER; Log::printf("ESP-NOW", "discovery started session=%08lX", session_); state_ = AppState::MASTER_DISCOVER; Log::printf("ESP-NOW", "discovery started session=%08lX", session_);
@@ -435,6 +448,7 @@ void App::handleRadio() {
if ((state_ == AppState::SLAVE_READY || state_ == AppState::SLAVE_WAIT_START) && if ((state_ == AppState::SLAVE_READY || state_ == AppState::SLAVE_WAIT_START) &&
type == MessageType::DISCOVER && (!havePeer_ || !memcmp(peer_, r.mac, 6))) { type == MessageType::DISCOVER && (!havePeer_ || !memcmp(peer_, r.mac, 6))) {
leaveIdlePowerSave(); leaveIdlePowerSave();
pwm_.stop();
setActivePerformance(true); setActivePerformance(true);
radio_.setWindowedReceive(false); radio_.setWindowedReceive(false);
memcpy(peer_, r.mac, 6); havePeer_ = true; session_ = r.packet.session; stageIndex_ = 0; sequence_ = r.packet.sequence; memcpy(peer_, r.mac, 6); havePeer_ = true; session_ = r.packet.session; stageIndex_ = 0; sequence_ = r.packet.sequence;
@@ -443,6 +457,8 @@ void App::handleRadio() {
state_ = AppState::SLAVE_WAIT_START; display_.show(UiText::MASTER_SEEN, UiText::ACK_SENT); continue; state_ = AppState::SLAVE_WAIT_START; display_.show(UiText::MASTER_SEEN, UiText::ACK_SENT); continue;
} }
if (state_ == AppState::MASTER_DISCOVER && type == MessageType::DISCOVER_ACK && r.packet.session == session_) { if (state_ == AppState::MASTER_DISCOVER && type == MessageType::DISCOVER_ACK && r.packet.session == session_) {
opticalWakeActive_ = false;
pwm_.stop();
memcpy(peer_, r.mac, 6); havePeer_ = true; lastPeerSeenMs_ = lastHeartbeatMs_ = millis(); memcpy(peer_, r.mac, 6); havePeer_ = true; lastPeerSeenMs_ = lastHeartbeatMs_ = millis();
requestedHz_ = frequencyAt(params_.startHz, params_.endHz, stageIndex_); requestedHz_ = frequencyAt(params_.startHz, params_.endHz, stageIndex_);
sendCurrent(MessageType::PREPARE); state_ = AppState::MASTER_WAIT_READY; retries_ = 0; deadlineMs_ = millis() + LINK_REPLY_TIMEOUT_MS; sendCurrent(MessageType::PREPARE); state_ = AppState::MASTER_WAIT_READY; retries_ = 0; deadlineMs_ = millis() + LINK_REPLY_TIMEOUT_MS;
@@ -551,6 +567,12 @@ void App::handleRadio() {
void App::updateMaster() { void App::updateMaster() {
const uint32_t now = millis(); const uint32_t now = millis();
if (state_ == AppState::MASTER_DISCOVER) { if (state_ == AppState::MASTER_DISCOVER) {
if (now - lastOpticalWakeToggleMs_ >= OPTICAL_WAKE_HALF_PERIOD_MS) {
opticalWakeActive_ = !opticalWakeActive_;
if (opticalWakeActive_) pwm_.active();
else pwm_.stop();
lastOpticalWakeToggleMs_ = now;
}
if (now - lastSendMs_ >= DISCOVERY_RETRY_INTERVAL_MS) { if (now - lastSendMs_ >= DISCOVERY_RETRY_INTERVAL_MS) {
if (++retries_ % 50U == 0U) Log::event("ESP-NOW", "DISCOVER burst continues"); if (++retries_ % 50U == 0U) Log::event("ESP-NOW", "DISCOVER burst continues");
radio_.sendBroadcast(pendingPacket_); radio_.sendBroadcast(pendingPacket_);
@@ -651,6 +673,7 @@ void App::finish(bool pass, FailReason reason, bool preserveDisplay) {
if (!pass && masterActive && havePeer_ && reason != FailReason::ABORTED) sendAbort(reason); if (!pass && masterActive && havePeer_ && reason != FailReason::ABORTED) sendAbort(reason);
if (state_ != AppState::IDLE && state_ != AppState::MENU) radio_.end(); if (state_ != AppState::IDLE && state_ != AppState::MENU) radio_.end();
state_ = AppState::FINISHED; pendingReason_ = reason; state_ = AppState::FINISHED; pendingReason_ = reason;
setStandbyOpticalOutput();
setActivePerformance(false); setActivePerformance(false);
lastUserActivityMs_ = millis(); lastUserActivityMs_ = millis();
if (slaveLinkLost) { if (slaveLinkLost) {
@@ -658,7 +681,8 @@ void App::finish(bool pass, FailReason reason, bool preserveDisplay) {
Display::formatTestFrequency(actual_.actualHz ? actual_.actualHz : requestedHz_, target, sizeof(target)); Display::formatTestFrequency(actual_.actualHz ? actual_.actualHz : requestedHz_, target, sizeof(target));
snprintf(one, sizeof(one), UiText::FAIL_FORMAT, target, snprintf(one, sizeof(one), UiText::FAIL_FORMAT, target,
actual_.actualDutyPct > 0.0f ? actual_.actualDutyPct : params_.dutyPct); actual_.actualDutyPct > 0.0f ? actual_.actualDutyPct : params_.dutyPct);
display_.show(one, uiFailName(reason), stageIndex_ + 1, stageCount_); display_.show(one, uiFailName(reason), stageIndex_ + 1, stageCount_,
roleCorner(Role::SLAVE));
armSlave(true); armSlave(true);
return; return;
} }
@@ -675,9 +699,11 @@ void App::finish(bool pass, FailReason reason, bool preserveDisplay) {
Display::formatTestFrequency(actual_.actualHz ? actual_.actualHz : requestedHz_, frequency, sizeof(frequency)); Display::formatTestFrequency(actual_.actualHz ? actual_.actualHz : requestedHz_, frequency, sizeof(frequency));
snprintf(one, sizeof(one), UiText::FAIL_FORMAT, frequency, snprintf(one, sizeof(one), UiText::FAIL_FORMAT, frequency,
actual_.actualDutyPct > 0.0f ? actual_.actualDutyPct : params_.dutyPct); actual_.actualDutyPct > 0.0f ? actual_.actualDutyPct : params_.dutyPct);
display_.show(one, uiFailName(reason), stageIndex_ + 1, stageCount_); display_.show(one, uiFailName(reason), stageIndex_ + 1, stageCount_,
roleCorner(static_cast<Role>(settings_.role)));
} else { } else {
display_.show(UiText::TEST_FAILED, uiFailName(reason)); display_.show(UiText::TEST_FAILED, uiFailName(reason), 0, 0,
roleCorner(static_cast<Role>(settings_.role)));
} }
} }
@@ -686,6 +712,11 @@ bool App::idlePowerSaveAllowed() const {
state_ == AppState::FINISHED || state_ == AppState::SLAVE_READY); state_ == AppState::FINISHED || state_ == AppState::SLAVE_READY);
} }
void App::setStandbyOpticalOutput() {
if (static_cast<Role>(settings_.role) == Role::SLAVE) pwm_.stop();
else pwm_.active();
}
void App::setActivePerformance(bool active) { void App::setActivePerformance(bool active) {
const uint32_t targetMhz = active ? 160U : 80U; const uint32_t targetMhz = active ? 160U : 80U;
if (getCpuFrequencyMhz() != targetMhz && !setCpuFrequencyMhz(targetMhz)) if (getCpuFrequencyMhz() != targetMhz && !setCpuFrequencyMhz(targetMhz))
@@ -699,6 +730,15 @@ void App::leaveIdlePowerSave(bool wakeDisplay) {
} }
idlePowerSave_ = false; idlePowerSave_ = false;
lastUserActivityMs_ = millis(); lastUserActivityMs_ = millis();
setStandbyOpticalOutput();
if (idleSleepRadioStopped_) {
idleSleepRadioStopped_ = false;
if (radio_.begin()) {
radio_.setWindowedReceive(true);
radio_.flush();
Log::event("POWER", "Slave ESP-NOW restored after external wake");
} else Log::event("POWER", "Slave ESP-NOW restore FAILED after external wake");
}
if (wakeDisplay) display_.setPower(true); if (wakeDisplay) display_.setPower(true);
Log::event("POWER", "idle light sleep ended"); Log::event("POWER", "idle light sleep ended");
} }
@@ -716,6 +756,11 @@ void App::serviceIdlePowerSave() {
return; return;
} }
idlePowerSave_ = true; idlePowerSave_ = true;
pwm_.stop();
if (state_ == AppState::SLAVE_READY) {
radio_.end();
idleSleepRadioStopped_ = true;
}
display_.setPower(false); display_.setPower(false);
Log::event("POWER", "idle timeout; OLED off and light sleep started"); Log::event("POWER", "idle timeout; OLED off and light sleep started");
} }
@@ -724,8 +769,16 @@ void App::serviceIdlePowerSave() {
BUTTON_ACTIVE_LEVEL == LOW ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL); BUTTON_ACTIVE_LEVEL == LOW ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL);
gpio_wakeup_enable(static_cast<gpio_num_t>(GPIO_BUTTON_MODE), gpio_wakeup_enable(static_cast<gpio_num_t>(GPIO_BUTTON_MODE),
BUTTON_ACTIVE_LEVEL == LOW ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL); BUTTON_ACTIVE_LEVEL == LOW ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL);
if (static_cast<Role>(settings_.role) == Role::SLAVE) {
// Light-sleep GPIO wake is level-triggered in ESP-IDF. Arm the level
// opposite to the one sampled immediately before sleep, which makes a
// transition (either edge) necessary and prevents a steady RX level from
// waking Slave continuously.
const bool currentRxHigh = gpio_get_level(static_cast<gpio_num_t>(GPIO_RX)) != 0;
gpio_wakeup_enable(static_cast<gpio_num_t>(GPIO_RX),
currentRxHigh ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL);
} else gpio_wakeup_disable(static_cast<gpio_num_t>(GPIO_RX));
esp_sleep_enable_gpio_wakeup(); esp_sleep_enable_gpio_wakeup();
esp_sleep_enable_timer_wakeup(IDLE_LIGHT_SLEEP_SLICE_US);
const esp_err_t result = esp_light_sleep_start(); const esp_err_t result = esp_light_sleep_start();
if (result != ESP_OK) { if (result != ESP_OK) {
delay(1); delay(1);
@@ -733,12 +786,19 @@ void App::serviceIdlePowerSave() {
} }
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_GPIO) { if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_GPIO) {
// The wake-up press is deliberately consumed. Holding or releasing it const bool buttonWake = digitalRead(GPIO_BUTTON_START) == BUTTON_ACTIVE_LEVEL ||
// must not later turn into a SHORT, LONG, or REPEAT event. digitalRead(GPIO_BUTTON_MODE) == BUTTON_ACTIVE_LEVEL;
startButton_.suppressUntilRelease(); if (buttonWake) {
modeButton_.suppressUntilRelease(); // The wake-up press is deliberately consumed. Holding or releasing it
leaveIdlePowerSave(); // must not later turn into a SHORT, LONG, or REPEAT event.
Log::event("POWER", "button wake consumed; next press will perform the action"); startButton_.suppressUntilRelease();
modeButton_.suppressUntilRelease();
leaveIdlePowerSave();
Log::event("POWER", "button wake consumed; next press will perform the action");
} else if (static_cast<Role>(settings_.role) == Role::SLAVE) {
leaveIdlePowerSave();
Log::event("POWER", "optical input woke Slave");
}
} }
} }
@@ -794,7 +854,7 @@ void App::showStageResult(const StageStats &s) {
snprintf(two, sizeof(two), "%s", uiFailName(s.reason)); snprintf(two, sizeof(two), "%s", uiFailName(s.reason));
} }
display_.show(one, two, overallProgress(stageIndex_, measurement_.progressStep()), display_.show(one, two, overallProgress(stageIndex_, measurement_.progressStep()),
overallProgressTotal(stageCount_)); overallProgressTotal(stageCount_), roleCorner(static_cast<Role>(settings_.role)));
return; return;
} }
char stage[12]; snprintf(stage, sizeof(stage), "%lu/%lu", stageIndex_ + 1, stageCount_); char stage[12]; snprintf(stage, sizeof(stage), "%lu/%lu", stageIndex_ + 1, stageCount_);
@@ -841,7 +901,8 @@ void App::showRemoteResult(const ProtocolPacket &packet) {
snprintf(two, sizeof(two), "%s", uiFailName(reason)); snprintf(two, sizeof(two), "%s", uiFailName(reason));
} }
display_.show(one, two, overallProgress(stageIndex_, packet.progressStep), display_.show(one, two, overallProgress(stageIndex_, packet.progressStep),
overallProgressTotal(stageCount_)); overallProgressTotal(stageCount_), reason == FailReason::NONE ? nullptr :
roleCorner(static_cast<Role>(settings_.role)));
} }
void App::fillMeasuredResult(ProtocolPacket &packet, const StageStats &stats) const { void App::fillMeasuredResult(ProtocolPacket &packet, const StageStats &stats) const {

View File

@@ -51,6 +51,7 @@ class App {
void serviceRxPinStateLog(); void serviceRxPinStateLog();
void leaveIdlePowerSave(bool wakeDisplay = true); void leaveIdlePowerSave(bool wakeDisplay = true);
bool idlePowerSaveAllowed() const; bool idlePowerSaveAllowed() const;
void setStandbyOpticalOutput();
void setActivePerformance(bool active); void setActivePerformance(bool active);
Button startButton_, modeButton_; Button startButton_, modeButton_;
@@ -82,5 +83,8 @@ class App {
bool stageStartConfirmed_ = false; bool stageStartConfirmed_ = false;
uint32_t lastUserActivityMs_ = 0; uint32_t lastUserActivityMs_ = 0;
bool idlePowerSave_ = false; bool idlePowerSave_ = false;
bool idleSleepRadioStopped_ = false;
uint32_t lastOpticalWakeToggleMs_ = 0;
bool opticalWakeActive_ = false;
bool rxPinStateKnown_ = false, rxPinState_ = false; bool rxPinStateKnown_ = false, rxPinState_ = false;
}; };

View File

@@ -71,8 +71,14 @@ constexpr bool SERIAL_LOG_TIMESTAMPS = true;
constexpr bool SERIAL_MINIMAL_LOG = true; constexpr bool SERIAL_MINIMAL_LOG = true;
#define BUTTON_ACTIVE_LEVEL LOW #define BUTTON_ACTIVE_LEVEL LOW
#define RX_SIGNAL_INVERTED false // Raw GPIO_RX level that means the optical receiver is active.
#define RX_ACTIVE_LEVEL LOW
// PWM_SAFE_LEVEL must switch the optical transmitter fully off and is used
// during tests whenever PWM is stopped, and while the controller sleeps.
// PWM_ACTIVE_LEVEL intentionally keeps the transmitter active while the
// controller is awake and no test is in progress.
#define PWM_SAFE_LEVEL HIGH #define PWM_SAFE_LEVEL HIGH
#define PWM_ACTIVE_LEVEL LOW
#define PWM_SETTLE_CYCLES 5U #define PWM_SETTLE_CYCLES 5U
constexpr uint32_t BUTTON_DEBOUNCE_MS = 30; constexpr uint32_t BUTTON_DEBOUNCE_MS = 30;
@@ -85,6 +91,9 @@ constexpr uint32_t LINK_REPLY_TIMEOUT_MS = 1500;
constexpr uint8_t LINK_PACKET_RETRIES = 10; constexpr uint8_t LINK_PACKET_RETRIES = 10;
constexpr uint32_t LINK_RETRY_INTERVAL_MS = 1000; constexpr uint32_t LINK_RETRY_INTERVAL_MS = 1000;
constexpr uint32_t DISCOVERY_RETRY_INTERVAL_MS = 20; constexpr uint32_t DISCOVERY_RETRY_INTERVAL_MS = 20;
// During discovery Master alternates PWM_ACTIVE_LEVEL and PWM_SAFE_LEVEL to
// wake a sleeping Slave through the optical channel.
constexpr uint32_t OPTICAL_WAKE_HALF_PERIOD_MS = 50;
constexpr uint32_t LINK_HEARTBEAT_INTERVAL_MS = 500; constexpr uint32_t LINK_HEARTBEAT_INTERVAL_MS = 500;
constexpr uint32_t LINK_HEARTBEAT_TIMEOUT_MS = 2500; constexpr uint32_t LINK_HEARTBEAT_TIMEOUT_MS = 2500;
constexpr uint32_t FINAL_ACK_RETRY_INTERVAL_MS = 50; constexpr uint32_t FINAL_ACK_RETRY_INTERVAL_MS = 50;
@@ -99,7 +108,6 @@ constexpr uint8_t MEASUREMENT_PROGRESS_STEPS = 10;
constexpr uint32_t OLED_PROGRESS_UPDATE_MS = 15; constexpr uint32_t OLED_PROGRESS_UPDATE_MS = 15;
constexpr uint32_t IDLE_POWER_SAVE_TIMEOUT_MS = 60000; constexpr uint32_t IDLE_POWER_SAVE_TIMEOUT_MS = 60000;
constexpr uint32_t IDLE_LIGHT_SLEEP_SLICE_US = 10000;
constexpr uint16_t SLAVE_LISTEN_INTERVAL_MS = 100; constexpr uint16_t SLAVE_LISTEN_INTERVAL_MS = 100;
constexpr uint16_t SLAVE_LISTEN_WINDOW_MS = 20; constexpr uint16_t SLAVE_LISTEN_WINDOW_MS = 20;
static_assert(SLAVE_LISTEN_WINDOW_MS < SLAVE_LISTEN_INTERVAL_MS, static_assert(SLAVE_LISTEN_WINDOW_MS < SLAVE_LISTEN_INTERVAL_MS,

View File

@@ -53,8 +53,8 @@ void Display::setPower(bool enabled) {
Log::printf("OLED", "display power %s", enabled ? "ON" : "OFF"); Log::printf("OLED", "display power %s", enabled ? "ON" : "OFF");
} }
void Display::drawTextLine(const char *text, int16_t y) { void Display::drawTextLine(const char *text, int16_t y, int16_t startX) {
int16_t x = 0; int16_t x = startX;
while (text && *text && x + CyrillicFont::WIDTH <= oled_.width()) { while (text && *text && x + CyrillicFont::WIDTH <= oled_.width()) {
const uint32_t codepoint = nextUtf8Codepoint(text); const uint32_t codepoint = nextUtf8Codepoint(text);
const uint8_t *glyph = CyrillicFont::glyph(codepoint); const uint8_t *glyph = CyrillicFont::glyph(codepoint);
@@ -75,7 +75,8 @@ void Display::drawTextLine(const char *text, int16_t y) {
} }
} }
void Display::show(const char *a, const char *b, uint32_t progress, uint32_t progressTotal) { void Display::show(const char *a, const char *b, uint32_t progress,
uint32_t progressTotal, const char *topRight) {
char one[64], two[64]; char one[64], two[64];
snprintf(one, sizeof(one), "%s", a ? a : ""); snprintf(two, sizeof(two), "%s", b ? b : ""); snprintf(one, sizeof(one), "%s", a ? a : ""); snprintf(two, sizeof(two), "%s", b ? b : "");
// Serial is the primary UI mirror and remains available when OLED is absent. // Serial is the primary UI mirror and remains available when OLED is absent.
@@ -85,6 +86,11 @@ void Display::show(const char *a, const char *b, uint32_t progress, uint32_t pro
oled_.clearDisplay(); oled_.clearDisplay();
drawTextLine(one, 3); drawTextLine(one, 3);
drawTextLine(two, 19); drawTextLine(two, 19);
if (topRight && *topRight) {
oled_.fillRect(oled_.width() - CyrillicFont::ADVANCE, 0,
CyrillicFont::ADVANCE, CyrillicFont::HEIGHT + 3, SSD1306_BLACK);
drawTextLine(topRight, 3, oled_.width() - CyrillicFont::ADVANCE);
}
if (progressTotal) { if (progressTotal) {
if (progress > progressTotal) progress = progressTotal; if (progress > progressTotal) progress = progressTotal;
const uint16_t width = static_cast<uint16_t>( const uint16_t width = static_cast<uint16_t>(

View File

@@ -8,7 +8,8 @@ class Display {
Display(); Display();
bool begin(); bool begin();
void show(const char *line1, const char *line2, void show(const char *line1, const char *line2,
uint32_t progress = 0, uint32_t progressTotal = 0); uint32_t progress = 0, uint32_t progressTotal = 0,
const char *topRight = nullptr);
void setPower(bool enabled); void setPower(bool enabled);
bool available() const { return ok_; } bool available() const { return ok_; }
bool powered() const { return powered_; } bool powered() const { return powered_; }
@@ -16,7 +17,7 @@ class Display {
static void formatTestFrequency(uint32_t hz, char *out, size_t size); static void formatTestFrequency(uint32_t hz, char *out, size_t size);
static void formatDuration(uint64_t us, char *out, size_t size); static void formatDuration(uint64_t us, char *out, size_t size);
private: private:
void drawTextLine(const char *text, int16_t y); void drawTextLine(const char *text, int16_t y, int16_t startX = 0);
Adafruit_SSD1306 oled_; Adafruit_SSD1306 oled_;
bool ok_ = false; bool ok_ = false;
bool powered_ = false; bool powered_ = false;

View File

@@ -187,3 +187,18 @@ void PwmGenerator::stop() {
#endif #endif
running_ = false; running_ = false;
} }
void PwmGenerator::active() {
// First detach/stop the PWM peripheral, then select the independently
// configured active level. The active and safe levels may be equal.
stop();
#if CONFIG_IDF_TARGET_ESP32C3
digitalWrite(GPIO_PWM, PWM_ACTIVE_LEVEL);
#elif CONFIG_IDF_TARGET_ESP32S3
if (mcpwmGenerator) mcpwm_generator_set_force_level(mcpwmGenerator, PWM_ACTIVE_LEVEL, true);
else {
pinMode(GPIO_PWM, OUTPUT);
digitalWrite(GPIO_PWM, PWM_ACTIVE_LEVEL);
}
#endif
}

View File

@@ -7,6 +7,7 @@ class PwmGenerator {
public: public:
void begin(); void begin();
bool start(uint32_t frequencyHz, uint8_t dutyPct, ActualPwm &actual); bool start(uint32_t frequencyHz, uint8_t dutyPct, ActualPwm &actual);
void active();
void stop(); void stop();
bool running() const { return running_; } bool running() const { return running_; }
private: private:

View File

@@ -56,6 +56,7 @@ void Radio::end() {
if (heartbeatQueue_) xQueueReset(heartbeatQueue_); if (heartbeatQueue_) xQueueReset(heartbeatQueue_);
if (instance_ == this) instance_ = nullptr; if (instance_ == this) instance_ = nullptr;
windowedReceive_ = false; windowedReceive_ = false;
if (wasActive) WiFi.mode(WIFI_OFF);
Log::event("ESP-NOW", "stopped"); Log::event("ESP-NOW", "stopped");
} }

View File

@@ -58,7 +58,8 @@ bool PulseReceiver::configureRmt(uint32_t resolutionHz) {
rmt_rx_channel_config_t cfg = {}; rmt_rx_channel_config_t cfg = {};
cfg.clk_src = RMT_CLK_SRC_DEFAULT; cfg.resolution_hz = resolutionHz; cfg.clk_src = RMT_CLK_SRC_DEFAULT; cfg.resolution_hz = resolutionHz;
cfg.gpio_num = static_cast<gpio_num_t>(GPIO_RX); cfg.gpio_num = static_cast<gpio_num_t>(GPIO_RX);
cfg.flags.invert_in = RX_SIGNAL_INVERTED; // Internally the measurement code always treats HIGH as the active phase.
cfg.flags.invert_in = RX_ACTIVE_LEVEL == LOW;
#if CONFIG_IDF_TARGET_ESP32S3 #if CONFIG_IDF_TARGET_ESP32S3
cfg.mem_block_symbols = 512; cfg.mem_block_symbols = 512;
cfg.flags.with_dma = true; cfg.flags.with_dma = true;
@@ -205,7 +206,7 @@ size_t PulseReceiver::readPeriods(PulsePeriod *periods, size_t capacity, TickTyp
void IRAM_ATTR PulseReceiver::onGpio(void *ctx) { void IRAM_ATTR PulseReceiver::onGpio(void *ctx) {
PulseReceiver *self = static_cast<PulseReceiver *>(ctx); PulseReceiver *self = static_cast<PulseReceiver *>(ctx);
bool level = gpio_get_level(static_cast<gpio_num_t>(GPIO_RX)); bool level = gpio_get_level(static_cast<gpio_num_t>(GPIO_RX));
if (RX_SIGNAL_INVERTED) level = !level; if (RX_ACTIVE_LEVEL == LOW) level = !level;
Edge e = {esp_cpu_get_cycle_count(), static_cast<uint8_t>(level)}; Edge e = {esp_cpu_get_cycle_count(), static_cast<uint8_t>(level)};
BaseType_t wake = pdFALSE; BaseType_t wake = pdFALSE;
if (xQueueSendFromISR(self->queue_, &e, &wake) != pdTRUE) if (xQueueSendFromISR(self->queue_, &e, &wake) != pdTRUE)

File diff suppressed because one or more lines are too long