добавлена бета проверка драйверов
This commit is contained in:
@@ -10,13 +10,14 @@
|
||||
#include <driver/gpio.h>
|
||||
#include <Wire.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace {
|
||||
const char *uiFailName(FailReason reason);
|
||||
|
||||
const char *appStateName(AppState state) {
|
||||
static const char *names[] = {"IDLE", "MENU", "SOLO_MEASURE", "MASTER_DISCOVER",
|
||||
static const char *names[] = {"IDLE", "MENU", "SOLO_MEASURE", "SOLO_DRIVER", "MASTER_DISCOVER",
|
||||
"MASTER_WAIT_READY", "MASTER_WAIT_RESULT", "MASTER_FINALIZE", "SLAVE_READY", "SLAVE_WAIT_START",
|
||||
"SLAVE_MEASURE", "SLAVE_WAIT_ACK", "FINISHED"};
|
||||
const uint8_t index = static_cast<uint8_t>(state);
|
||||
@@ -54,7 +55,7 @@ void formatMeasured(float hz, uint32_t pulseNs, char *out, size_t size) {
|
||||
void formatTestTarget(uint32_t hz, uint32_t pulseNs, char *out, size_t size) {
|
||||
char target[32];
|
||||
formatTarget(hz, pulseNs, target, sizeof(target));
|
||||
snprintf(out, size, "TEST: %s", target);
|
||||
snprintf(out, size, UiText::TEST_TARGET_FORMAT, target);
|
||||
}
|
||||
|
||||
void formatFailure(FailReason reason, uint32_t hz, uint32_t pulseNs,
|
||||
@@ -62,7 +63,16 @@ void formatFailure(FailReason reason, uint32_t hz, uint32_t pulseNs,
|
||||
(void)reason;
|
||||
char target[32];
|
||||
formatTarget(hz, pulseNs, target, sizeof(target));
|
||||
snprintf(out, size, "FAIL AT %s", target);
|
||||
snprintf(out, size, UiText::FAIL_TARGET_FORMAT, target);
|
||||
}
|
||||
|
||||
void formatElapsedNs(uint64_t ns, char *out, size_t size) {
|
||||
// Compact form keeps `T:... D:... P:...` within 21 OLED columns.
|
||||
// Exact nanoseconds remain available in the Serial diagnostic.
|
||||
if (ns < 1000ULL) snprintf(out, size, "%llun", ns);
|
||||
else if (ns < 1000000ULL) snprintf(out, size, "%.1fu", ns / 1000.0);
|
||||
else if (ns < 1000000000ULL) snprintf(out, size, "%.0fm", ns / 1000000.0);
|
||||
else snprintf(out, size, "%.2fs", ns / 1000000000.0);
|
||||
}
|
||||
|
||||
size_t utf8CharacterCount(const char *text) {
|
||||
@@ -116,6 +126,12 @@ uint32_t stageWallTimeMs(uint32_t testTimeMs, uint32_t frequencyHz) {
|
||||
return static_cast<uint32_t>((nominalStageUs(frequencyHz, testTimeMs, PWM_SETTLE_CYCLES) + 999ULL) / 1000ULL);
|
||||
}
|
||||
|
||||
const char *uiTestName(TestKind kind) {
|
||||
const uint8_t index = static_cast<uint8_t>(kind);
|
||||
return index < sizeof(UiText::TEST_NAMES) / sizeof(UiText::TEST_NAMES[0])
|
||||
? UiText::TEST_NAMES[index] : "?";
|
||||
}
|
||||
|
||||
uint8_t lastValidMaxPulseIndex(uint32_t frequencyHz) {
|
||||
uint8_t last = static_cast<uint8_t>(countOf(MAX_PULSE_OPTIONS_NS) - 1U);
|
||||
while (last && static_cast<uint64_t>(MAX_PULSE_OPTIONS_NS[last]) * frequencyHz >= 1000000000ULL)
|
||||
@@ -194,9 +210,41 @@ uint8_t cycleIndex(uint8_t value, uint8_t first, uint8_t last, int direction) {
|
||||
if (direction > 0) return value >= last ? first : static_cast<uint8_t>(value + 1U);
|
||||
return value <= first ? last : static_cast<uint8_t>(value - 1U);
|
||||
}
|
||||
|
||||
bool parseUnsigned(const char *text, uint32_t &value) {
|
||||
if (!text || !*text || *text == '-') return false;
|
||||
char *end = nullptr;
|
||||
const unsigned long parsed = strtoul(text, &end, 10);
|
||||
if (!end || *end) return false;
|
||||
value = static_cast<uint32_t>(parsed);
|
||||
return true;
|
||||
}
|
||||
|
||||
App::App() : startButton_(GPIO_BUTTON_START), modeButton_(GPIO_BUTTON_MODE), measurement_(receiver_) {}
|
||||
template <size_t N>
|
||||
int optionIndex(const uint32_t (&options)[N], uint32_t value) {
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
if (options[i] == value) return static_cast<int>(i);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int accuracyOptionIndex(const char *text) {
|
||||
if (!text || !*text) return -1;
|
||||
char *end = nullptr;
|
||||
const float value = strtof(text, &end);
|
||||
if (!end || *end) return -1;
|
||||
for (size_t i = 0; i < countOf(ACCURACY_OPTIONS_PCT); ++i)
|
||||
if (fabsf(ACCURACY_OPTIONS_PCT[i] - value) < 0.001f) return static_cast<int>(i);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void lowerAscii(char *text) {
|
||||
for (; text && *text; ++text)
|
||||
if (*text >= 'A' && *text <= 'Z') *text = static_cast<char>(*text - 'A' + 'a');
|
||||
}
|
||||
}
|
||||
|
||||
App::App() : startButton_(GPIO_BUTTON_START), modeButton_(GPIO_BUTTON_MODE),
|
||||
measurement_(receiver_), driverTest_(receiver_) {}
|
||||
|
||||
void App::begin() {
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
@@ -221,6 +269,7 @@ void App::finishInitialization(bool factoryReset) {
|
||||
}
|
||||
sanitizeRange();
|
||||
params_ = store_.params(settings_);
|
||||
pwm_.configureActiveLight(txActiveLightOn(settings_));
|
||||
if (!display_.begin()) Log::event("BOOT", "OLED unavailable; Serial UI remains fully operational");
|
||||
initialized_ = true;
|
||||
if (!receiver_.begin()) { Log::event("BOOT", "FATAL: capture peripheral init failed"); finish(false, FailReason::UNSUPPORTED); return; }
|
||||
@@ -233,6 +282,7 @@ void App::finishInitialization(bool factoryReset) {
|
||||
}
|
||||
|
||||
void App::update() {
|
||||
serviceSerialConsole();
|
||||
serviceIdlePowerSave();
|
||||
const uint32_t now = millis();
|
||||
const ButtonEvent startEvent = startButton_.update(now);
|
||||
@@ -258,11 +308,14 @@ void App::update() {
|
||||
|
||||
if (state_ == AppState::IDLE || state_ == AppState::FINISHED) {
|
||||
if (modeEvent == ButtonEvent::SHORT) {
|
||||
settings_.role = (settings_.role + 1U) % 3U; const bool saved = store_.save(settings_);
|
||||
cycleRunMode(); sanitizeRange(); const bool saved = store_.save(settings_);
|
||||
params_ = store_.params(settings_);
|
||||
if (static_cast<Role>(settings_.role) == Role::SLAVE) armSlave();
|
||||
else showIdle();
|
||||
Log::printf("ACTION", "role changed to %s, NVS=%s", roleName(static_cast<Role>(settings_.role)), saved ? "OK" : "FAILED");
|
||||
Log::printf("ACTION", "mode changed to %s/%s, NVS=%s",
|
||||
roleName(static_cast<Role>(settings_.role)),
|
||||
testKindName(static_cast<TestKind>(settings_.testKind)),
|
||||
saved ? "OK" : "FAILED");
|
||||
} else if (modeEvent == ButtonEvent::LONG) {
|
||||
state_ = AppState::MENU; menuItem_ = 0; Log::event("ACTION", "settings menu entered"); showMenu();
|
||||
} else if (startEvent == ButtonEvent::SHORT) { Log::event("ACTION", "test start requested"); startTest(); }
|
||||
@@ -273,7 +326,9 @@ void App::update() {
|
||||
if (state_ == AppState::SLAVE_READY && modeEvent != ButtonEvent::NONE) {
|
||||
radio_.end(); havePeer_ = false;
|
||||
if (modeEvent == ButtonEvent::SHORT) {
|
||||
settings_.role = static_cast<uint8_t>(Role::SOLO); const bool saved = store_.save(settings_);
|
||||
settings_.role = static_cast<uint8_t>(Role::SOLO);
|
||||
settings_.testKind = static_cast<uint8_t>(TestKind::OPTICAL);
|
||||
const bool saved = store_.save(settings_);
|
||||
params_ = store_.params(settings_); state_ = AppState::IDLE; showIdle();
|
||||
Log::printf("ACTION", "role changed to SOLO, NVS=%s", saved ? "OK" : "FAILED");
|
||||
} else if (modeEvent == ButtonEvent::LONG) {
|
||||
@@ -283,7 +338,7 @@ void App::update() {
|
||||
}
|
||||
if (state_ == AppState::MENU) {
|
||||
if (modeEvent == ButtonEvent::SHORT) {
|
||||
menuItem_ = (menuItem_ + 1U) % 5U; Log::printf("ACTION", "menu item selected index=%u", menuItem_); showMenu();
|
||||
menuItem_ = (menuItem_ + 1U) % 6U; Log::printf("ACTION", "menu item selected index=%u", menuItem_); showMenu();
|
||||
}
|
||||
else if (modeEvent == ButtonEvent::LONG) {
|
||||
sanitizeRange(); const bool saved = store_.save(settings_); params_ = store_.params(settings_);
|
||||
@@ -316,6 +371,39 @@ void App::update() {
|
||||
StageStats live = {};
|
||||
if (measurement_.statsSnapshot(live)) showStageResult(live);
|
||||
}
|
||||
} else if (state_ == AppState::SOLO_DRIVER) {
|
||||
if (static_cast<int32_t>(now - localMeasurementDeadlineMs_) >= 0)
|
||||
driverTest_.forceFail(FailReason::LOST_EDGE);
|
||||
const DriverState ds = driverTest_.update();
|
||||
if (ds == DriverState::SUBSAMPLE_DONE) {
|
||||
// Capture is already stopped. Update the OLED only in this quiet gap,
|
||||
// then restart the same PWM point and arm the next tenth of the sample.
|
||||
pwm_.stop();
|
||||
driverTest_.takeProgressUpdate();
|
||||
showDriverResult(driverTest_.stats());
|
||||
ActualPwm resumed = {};
|
||||
if (!pwm_.start(requestedHz_, requestedPulseNs_, resumed)) {
|
||||
driverTest_.forceFail(FailReason::RESOLUTION);
|
||||
} else {
|
||||
actual_ = resumed;
|
||||
if (!driverTest_.resumeSubsample()) {
|
||||
pwm_.stop();
|
||||
driverTest_.forceFail(FailReason::DATA_LOSS);
|
||||
}
|
||||
}
|
||||
} else if (ds == DriverState::FAIL) {
|
||||
pwm_.stop(); receiver_.stop();
|
||||
driverTest_.printSummary();
|
||||
driverTest_.printTrace();
|
||||
showDriverResult(driverTest_.stats());
|
||||
finish(false, driverTest_.stats().reason, true);
|
||||
} else if (ds == DriverState::PASS) {
|
||||
pwm_.stop();
|
||||
driverTest_.printSummary();
|
||||
const bool finalPoint = stageIndex_ + 1U >= stageCount_;
|
||||
showDriverResult(driverTest_.stats(), finalPoint);
|
||||
stagePassed();
|
||||
}
|
||||
} else if (state_ == AppState::MASTER_DISCOVER || state_ == AppState::MASTER_WAIT_READY ||
|
||||
state_ == AppState::MASTER_WAIT_RESULT || state_ == AppState::MASTER_FINALIZE) {
|
||||
handleRadio(); updateMaster();
|
||||
@@ -328,14 +416,207 @@ void App::showIdle() {
|
||||
setActivePerformance(false);
|
||||
setStandbyOpticalOutput();
|
||||
lastUserActivityMs_ = millis();
|
||||
char one[64]; snprintf(one, sizeof(one), "%s%s", UiText::MODE_PREFIX,
|
||||
uiRoleName(static_cast<Role>(settings_.role)));
|
||||
char one[64]; snprintf(one, sizeof(one), "%s: %s",
|
||||
uiRoleName(static_cast<Role>(settings_.role)),
|
||||
uiTestName(static_cast<TestKind>(settings_.testKind)));
|
||||
display_.show(one, UiText::START_RUN);
|
||||
}
|
||||
|
||||
void App::serviceSerialConsole() {
|
||||
while (Serial.available() > 0) {
|
||||
const int raw = Serial.read();
|
||||
if (raw < 0) break;
|
||||
const char c = static_cast<char>(raw);
|
||||
lastUserActivityMs_ = millis();
|
||||
leaveIdlePowerSave();
|
||||
|
||||
if (c == '\r') continue;
|
||||
if (c == '\n') {
|
||||
if (serialLineOverflow_) Serial.println("ERR command too long");
|
||||
else if (serialLineLength_) {
|
||||
serialLine_[serialLineLength_] = '\0';
|
||||
handleSerialCommand(serialLine_);
|
||||
}
|
||||
serialLineLength_ = 0;
|
||||
serialLineOverflow_ = false;
|
||||
continue;
|
||||
}
|
||||
if (c < ' ' || c > '~') continue;
|
||||
if (serialLineLength_ + 1U < sizeof(serialLine_))
|
||||
serialLine_[serialLineLength_++] = c;
|
||||
else serialLineOverflow_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void App::printSerialHelp() {
|
||||
Serial.println("COMMANDS (send with newline):");
|
||||
Serial.println(" help | status | start | stop | defaults");
|
||||
Serial.println(" set role solo|master|slave");
|
||||
Serial.println(" set test optical|driver");
|
||||
Serial.println(" set frequency 500|1000|2000|5000|10000|25000");
|
||||
Serial.println(" set max 2000|5000|10000|20000|50000|100000|200000|500000");
|
||||
Serial.println(" set min 250|500|1000|2000|5000|10000|50000");
|
||||
Serial.println(" set accuracy 1|2|5|10");
|
||||
Serial.println(" set time 100|250|500|1000|2000|5000 (ms)");
|
||||
Serial.println(" set light HH|HL|LH|LL");
|
||||
}
|
||||
|
||||
void App::printSerialStatus() {
|
||||
if (!initialized_) {
|
||||
Serial.println("STATUS initializing");
|
||||
return;
|
||||
}
|
||||
params_ = store_.params(settings_);
|
||||
Serial.printf("STATUS state=%s role=%s test=%s frequency=%luHz max=%luns min=%luns accuracy=%.2f%% time=%lums light=%s usb=%s\n",
|
||||
appStateName(state_), roleName(static_cast<Role>(settings_.role)),
|
||||
testKindName(static_cast<TestKind>(settings_.testKind)), params_.frequencyHz,
|
||||
params_.maxPulseNs, params_.minPulseNs, params_.accuracyPct, params_.testTimeMs,
|
||||
lightCodeName(static_cast<LightCode>(settings_.lightCode)),
|
||||
usbHostPresent() ? "connected" : "disconnected");
|
||||
}
|
||||
|
||||
bool App::serialSettingsMutable() const {
|
||||
return initialized_ && (state_ == AppState::IDLE || state_ == AppState::FINISHED ||
|
||||
state_ == AppState::MENU || state_ == AppState::SLAVE_READY);
|
||||
}
|
||||
|
||||
void App::finishSerialSettingsChange() {
|
||||
if (state_ == AppState::SLAVE_READY) radio_.end();
|
||||
state_ = AppState::IDLE;
|
||||
sanitizeRange();
|
||||
params_ = store_.params(settings_);
|
||||
pwm_.configureActiveLight(txActiveLightOn(settings_));
|
||||
const bool saved = store_.save(settings_);
|
||||
Serial.printf("OK settings saved=%s\n", saved ? "yes" : "no");
|
||||
if (static_cast<Role>(settings_.role) == Role::SLAVE) armSlave();
|
||||
else showIdle();
|
||||
printSerialStatus();
|
||||
}
|
||||
|
||||
void App::handleSerialCommand(char *line) {
|
||||
lowerAscii(line);
|
||||
char *save = nullptr;
|
||||
char *command = strtok_r(line, " \t", &save);
|
||||
char *name = strtok_r(nullptr, " \t", &save);
|
||||
char *value = strtok_r(nullptr, " \t", &save);
|
||||
char *extra = strtok_r(nullptr, " \t", &save);
|
||||
if (!command) return;
|
||||
|
||||
if ((!strcmp(command, "help") || !strcmp(command, "?")) && !name) {
|
||||
printSerialHelp();
|
||||
return;
|
||||
}
|
||||
if ((!strcmp(command, "status") || !strcmp(command, "get")) && !name) {
|
||||
printSerialStatus();
|
||||
return;
|
||||
}
|
||||
if (!strcmp(command, "start") && !name) {
|
||||
if (!initialized_) Serial.println("ERR still initializing");
|
||||
else if (state_ == AppState::IDLE || state_ == AppState::FINISHED) {
|
||||
Serial.println("OK test start requested");
|
||||
startTest();
|
||||
} else if (state_ == AppState::SLAVE_READY) Serial.println("OK slave already armed");
|
||||
else Serial.printf("ERR busy state=%s\n", appStateName(state_));
|
||||
return;
|
||||
}
|
||||
if ((!strcmp(command, "stop") || !strcmp(command, "abort")) && !name) {
|
||||
if (!initialized_) Serial.println("ERR still initializing");
|
||||
else if (state_ == AppState::IDLE || state_ == AppState::FINISHED) Serial.println("OK already stopped");
|
||||
else if (state_ == AppState::MENU) {
|
||||
state_ = AppState::IDLE; showIdle(); Serial.println("OK menu closed");
|
||||
} else if (state_ == AppState::SLAVE_READY) Serial.println("OK slave is armed; no test is running");
|
||||
else {
|
||||
Serial.println("OK abort requested");
|
||||
abortTest();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!strcmp(command, "defaults") && !name) {
|
||||
if (!serialSettingsMutable()) {
|
||||
Serial.printf("ERR settings locked state=%s\n", appStateName(state_));
|
||||
return;
|
||||
}
|
||||
store_.defaults(settings_);
|
||||
finishSerialSettingsChange();
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "set") || !name || !value || extra) {
|
||||
Serial.println("ERR unknown command; send 'help'");
|
||||
return;
|
||||
}
|
||||
if (!serialSettingsMutable()) {
|
||||
Serial.printf("ERR settings locked state=%s; stop the test first\n", appStateName(state_));
|
||||
return;
|
||||
}
|
||||
|
||||
bool accepted = false;
|
||||
uint32_t numeric = 0;
|
||||
if (!strcmp(name, "role")) {
|
||||
if (!strcmp(value, "solo")) { settings_.role = static_cast<uint8_t>(Role::SOLO); accepted = true; }
|
||||
else if (!strcmp(value, "master")) { settings_.role = static_cast<uint8_t>(Role::MASTER); accepted = true; }
|
||||
else if (!strcmp(value, "slave")) { settings_.role = static_cast<uint8_t>(Role::SLAVE); accepted = true; }
|
||||
} else if (!strcmp(name, "test")) {
|
||||
if (!strcmp(value, "optical")) { settings_.testKind = static_cast<uint8_t>(TestKind::OPTICAL); accepted = true; }
|
||||
else if (!strcmp(value, "driver") && !TARGET_IS_C3 &&
|
||||
static_cast<Role>(settings_.role) == Role::SOLO) {
|
||||
settings_.testKind = static_cast<uint8_t>(TestKind::DRIVER); accepted = true;
|
||||
}
|
||||
} else if ((!strcmp(name, "frequency") || !strcmp(name, "freq")) && parseUnsigned(value, numeric)) {
|
||||
const int index = optionIndex(PWM_FREQUENCY_OPTIONS_HZ, numeric);
|
||||
if (index >= 0) { settings_.frequencyIndex = static_cast<uint8_t>(index); accepted = true; }
|
||||
} else if ((!strcmp(name, "max") || !strcmp(name, "maxpulse")) && parseUnsigned(value, numeric)) {
|
||||
const int index = optionIndex(MAX_PULSE_OPTIONS_NS, numeric);
|
||||
if (index >= 0) { settings_.maxPulseIndex = static_cast<uint8_t>(index); accepted = true; }
|
||||
} else if ((!strcmp(name, "min") || !strcmp(name, "minpulse")) && parseUnsigned(value, numeric)) {
|
||||
const int index = optionIndex(MIN_PULSE_OPTIONS_NS, numeric);
|
||||
if (index >= 0) { settings_.minPulseIndex = static_cast<uint8_t>(index); accepted = true; }
|
||||
} else if (!strcmp(name, "accuracy")) {
|
||||
const int index = accuracyOptionIndex(value);
|
||||
if (index >= 0) { settings_.accuracyIndex = static_cast<uint8_t>(index); accepted = true; }
|
||||
} else if ((!strcmp(name, "time") || !strcmp(name, "duration")) && parseUnsigned(value, numeric)) {
|
||||
const int index = optionIndex(TEST_TIME_OPTIONS_MS, numeric);
|
||||
if (index >= 0) { settings_.timeIndex = static_cast<uint8_t>(index); accepted = true; }
|
||||
} else if (!strcmp(name, "light")) {
|
||||
if (!strcmp(value, "hh")) { settings_.lightCode = static_cast<uint8_t>(LightCode::HH); accepted = true; }
|
||||
else if (!strcmp(value, "hl")) { settings_.lightCode = static_cast<uint8_t>(LightCode::HL); accepted = true; }
|
||||
else if (!strcmp(value, "lh")) { settings_.lightCode = static_cast<uint8_t>(LightCode::LH); accepted = true; }
|
||||
else if (!strcmp(value, "ll")) { settings_.lightCode = static_cast<uint8_t>(LightCode::LL); accepted = true; }
|
||||
}
|
||||
|
||||
if (!accepted) {
|
||||
Serial.println("ERR invalid setting or value; send 'help'");
|
||||
return;
|
||||
}
|
||||
finishSerialSettingsChange();
|
||||
}
|
||||
|
||||
void App::cycleRunMode() {
|
||||
const Role role = static_cast<Role>(settings_.role);
|
||||
const TestKind kind = static_cast<TestKind>(settings_.testKind);
|
||||
if (role == Role::SOLO && kind == TestKind::OPTICAL && !TARGET_IS_C3) {
|
||||
settings_.testKind = static_cast<uint8_t>(TestKind::DRIVER);
|
||||
} else if (role == Role::SOLO) {
|
||||
settings_.role = static_cast<uint8_t>(Role::MASTER);
|
||||
settings_.testKind = static_cast<uint8_t>(TestKind::OPTICAL);
|
||||
} else if (role == Role::MASTER) {
|
||||
settings_.role = static_cast<uint8_t>(Role::SLAVE);
|
||||
settings_.testKind = static_cast<uint8_t>(TestKind::OPTICAL);
|
||||
} else {
|
||||
settings_.role = static_cast<uint8_t>(Role::SOLO);
|
||||
settings_.testKind = static_cast<uint8_t>(TestKind::OPTICAL);
|
||||
}
|
||||
}
|
||||
|
||||
void App::sanitizeRange() {
|
||||
if (settings_.role > static_cast<uint8_t>(Role::SLAVE))
|
||||
settings_.role = static_cast<uint8_t>(Role::SOLO);
|
||||
if (settings_.testKind > static_cast<uint8_t>(TestKind::DRIVER))
|
||||
settings_.testKind = static_cast<uint8_t>(TestKind::OPTICAL);
|
||||
if (settings_.lightCode > static_cast<uint8_t>(LightCode::LL))
|
||||
settings_.lightCode = static_cast<uint8_t>(LightCode::HH);
|
||||
if (settings_.role != static_cast<uint8_t>(Role::SOLO) ||
|
||||
(TARGET_IS_C3 && settings_.testKind == static_cast<uint8_t>(TestKind::DRIVER)))
|
||||
settings_.testKind = static_cast<uint8_t>(TestKind::OPTICAL);
|
||||
settings_.frequencyIndex %= countOf(PWM_FREQUENCY_OPTIONS_HZ);
|
||||
settings_.maxPulseIndex %= countOf(MAX_PULSE_OPTIONS_NS);
|
||||
settings_.minPulseIndex %= countOf(MIN_PULSE_OPTIONS_NS);
|
||||
@@ -346,9 +627,15 @@ void App::sanitizeRange() {
|
||||
if (settings_.maxPulseIndex > lastValid) settings_.maxPulseIndex = lastValid;
|
||||
const uint8_t lastMin = lastMinPulseIndexAtMost(MAX_PULSE_OPTIONS_NS[settings_.maxPulseIndex]);
|
||||
if (settings_.minPulseIndex > lastMin) settings_.minPulseIndex = lastMin;
|
||||
const uint8_t firstMin = firstMinPulseIndexAtLeast(
|
||||
minimumPulseForAccuracy(hz, ACCURACY_OPTIONS_PCT[settings_.accuracyIndex]), lastMin);
|
||||
if (settings_.minPulseIndex < firstMin) settings_.minPulseIndex = firstMin;
|
||||
if (settings_.testKind == static_cast<uint8_t>(TestKind::DRIVER)) {
|
||||
const uint8_t driverLastMin = lastMinPulseIndexAtMost(
|
||||
MAX_PULSE_OPTIONS_NS[settings_.maxPulseIndex]);
|
||||
const uint8_t firstDriverMin = firstMinPulseIndexAtLeast(
|
||||
DRIVER_MIN_INPUT_PULSE_NS, driverLastMin);
|
||||
if (settings_.minPulseIndex < firstDriverMin)
|
||||
settings_.minPulseIndex = firstDriverMin;
|
||||
settings_.lightCode = static_cast<uint8_t>(LightCode::HL);
|
||||
}
|
||||
}
|
||||
|
||||
void App::serviceRxPinStateLog() {
|
||||
@@ -376,25 +663,26 @@ void App::changeMenu(int d) {
|
||||
} else if (menuItem_ == 2) {
|
||||
const uint8_t last = lastMinPulseIndexAtMost(
|
||||
MAX_PULSE_OPTIONS_NS[settings_.maxPulseIndex]);
|
||||
const uint8_t first = firstMinPulseIndexAtLeast(
|
||||
minimumPulseForAccuracy(PWM_FREQUENCY_OPTIONS_HZ[settings_.frequencyIndex],
|
||||
ACCURACY_OPTIONS_PCT[settings_.accuracyIndex]), last);
|
||||
settings_.minPulseIndex = cycleIndex(settings_.minPulseIndex,
|
||||
first, last, d);
|
||||
const uint8_t first = static_cast<TestKind>(settings_.testKind) == TestKind::DRIVER
|
||||
? firstMinPulseIndexAtLeast(DRIVER_MIN_INPUT_PULSE_NS, last) : 0U;
|
||||
settings_.minPulseIndex = cycleIndex(settings_.minPulseIndex, first, last, d);
|
||||
} else {
|
||||
uint8_t *value = nullptr; size_t count = 0;
|
||||
switch (menuItem_) {
|
||||
case 0: value = &settings_.frequencyIndex; count = countOf(PWM_FREQUENCY_OPTIONS_HZ); break;
|
||||
case 3: value = &settings_.accuracyIndex; count = countOf(ACCURACY_OPTIONS_PCT); break;
|
||||
case 4: value = &settings_.timeIndex; count = countOf(TEST_TIME_OPTIONS_MS); break;
|
||||
case 5: value = &settings_.lightCode; count = 4; break;
|
||||
default: return;
|
||||
}
|
||||
*value = cycleIndex(*value, 0, static_cast<uint8_t>(count - 1U), d);
|
||||
}
|
||||
sanitizeRange(); params_ = store_.params(settings_);
|
||||
Log::printf("ACTION", "menu item=%u changed direction=%+d frequency=%u max-pulse=%u min-pulse=%u accuracy=%u time=%u",
|
||||
pwm_.configureActiveLight(txActiveLightOn(settings_));
|
||||
Log::printf("ACTION", "menu item=%u changed direction=%+d frequency=%u max-pulse=%u min-pulse=%u accuracy=%u time=%u light=%s",
|
||||
menuItem_, d, settings_.frequencyIndex, settings_.maxPulseIndex,
|
||||
settings_.minPulseIndex, settings_.accuracyIndex, settings_.timeIndex);
|
||||
settings_.minPulseIndex, settings_.accuracyIndex, settings_.timeIndex,
|
||||
lightCodeName(static_cast<LightCode>(settings_.lightCode)));
|
||||
showMenu();
|
||||
}
|
||||
|
||||
@@ -423,6 +711,15 @@ void App::showMenu() {
|
||||
snprintf(value, sizeof(value), "%.1fs", params_.testTimeMs / 1000.0f);
|
||||
label = UiText::MENU_TEST_TIME;
|
||||
break;
|
||||
case 5: {
|
||||
const char *code = lightCodeName(static_cast<LightCode>(settings_.lightCode));
|
||||
snprintf(value, sizeof(value), "%s", code);
|
||||
label = UiText::MENU_LIGHT_CODE;
|
||||
formatMenuLine(label, value, one, sizeof(one));
|
||||
snprintf(total, sizeof(total), UiText::LIGHT_CODE_FORMAT, code[0], code[1]);
|
||||
display_.show(one, total);
|
||||
return;
|
||||
}
|
||||
default: return;
|
||||
}
|
||||
formatMenuLine(label, value, one, sizeof(one));
|
||||
@@ -434,23 +731,29 @@ void App::startTest() {
|
||||
leaveIdlePowerSave();
|
||||
pwm_.stop();
|
||||
setActivePerformance(true);
|
||||
sanitizeRange();
|
||||
params_ = store_.params(settings_); stageCount_ = pulseWidthPointCount(params_.maxPulseNs, params_.minPulseNs);
|
||||
stageIndex_ = 0; requestedHz_ = params_.frequencyHz; requestedPulseNs_ = 0; pendingReason_ = FailReason::NONE;
|
||||
havePeer_ = false; lastHeartbeatMs_ = 0; lastPeerSeenMs_ = 0;
|
||||
if (!stageCount_) { finish(false, FailReason::UNSUPPORTED); return; }
|
||||
Log::printf("TEST", "starting role=%s stages=%lu", roleName(static_cast<Role>(settings_.role)), stageCount_);
|
||||
Log::printf("TEST", "starting role=%s test=%s light=%s stages=%lu",
|
||||
roleName(static_cast<Role>(settings_.role)),
|
||||
testKindName(static_cast<TestKind>(settings_.testKind)),
|
||||
lightCodeName(static_cast<LightCode>(settings_.lightCode)), stageCount_);
|
||||
if (SERIAL_MINIMAL_LOG) {
|
||||
Log::printf("CONFIG", "mode=%s frequency=%luHz pulse=%lu..%luns accuracy=%.2f%% time=%lums TX=%s RX=AUTO stages=%lu",
|
||||
roleName(static_cast<Role>(settings_.role)), params_.frequencyHz,
|
||||
Log::printf("CONFIG", "mode=%s/%s frequency=%luHz pulse=%lu..%luns accuracy=%.2f%% time=%lums LIGHT=%s stages=%lu",
|
||||
roleName(static_cast<Role>(settings_.role)),
|
||||
testKindName(static_cast<TestKind>(settings_.testKind)), params_.frequencyHz,
|
||||
params_.maxPulseNs, params_.minPulseNs, params_.accuracyPct, params_.testTimeMs,
|
||||
PWM_ACTIVE_LEVEL == HIGH ? "HIGH" : "LOW",
|
||||
lightCodeName(static_cast<LightCode>(settings_.lightCode)),
|
||||
stageCount_);
|
||||
}
|
||||
printConfiguration();
|
||||
const Role role = static_cast<Role>(settings_.role);
|
||||
if (role == Role::SOLO) {
|
||||
if (!prepareStage()) return;
|
||||
state_ = AppState::SOLO_MEASURE;
|
||||
state_ = static_cast<TestKind>(settings_.testKind) == TestKind::DRIVER
|
||||
? AppState::SOLO_DRIVER : AppState::SOLO_MEASURE;
|
||||
} else if (!radio_.begin()) finish(false, FailReason::LINK_LOST);
|
||||
else if (role == Role::MASTER) startMasterDiscovery();
|
||||
else { state_ = AppState::SLAVE_READY; Log::event("TEST", "Slave armed and waiting for Master"); display_.show(UiText::SLAVE_READY, UiText::WAIT_MASTER); }
|
||||
@@ -498,24 +801,31 @@ bool App::prepareStage(bool showProgress) {
|
||||
params_.accuracyPct);
|
||||
finish(false, FailReason::RESOLUTION); return false;
|
||||
}
|
||||
const uint32_t plannedRxHz = receiver_.plannedTickHz(actual_.actualHz, actual_.actualDutyPct);
|
||||
const uint32_t plannedPulseRxHz = receiver_.plannedPulseTickHz(
|
||||
actual_.actualHz, actual_.actualDutyPct);
|
||||
const FailReason resolution = validateResolution(actual_.actualHz, actual_.actualDutyPct, params_.accuracyPct,
|
||||
plannedRxHz, plannedPulseRxHz, actual_.bits,
|
||||
MEASUREMENT_AVERAGING_PERIODS);
|
||||
if (resolution != FailReason::NONE) {
|
||||
Log::printf("PWM", "resolution rejected: actual=%luHz duty=%.3f%% bits=%u period-capture=%luHz pulse-capture=%luHz tolerance=%.3f%%",
|
||||
actual_.actualHz, actual_.actualDutyPct, actual_.bits, plannedRxHz, plannedPulseRxHz,
|
||||
effectiveTolerancePct(params_.accuracyPct));
|
||||
finish(false, resolution); return false;
|
||||
const bool driverMode = static_cast<TestKind>(settings_.testKind) == TestKind::DRIVER;
|
||||
if (!driverMode) {
|
||||
const uint32_t plannedRxHz = receiver_.plannedTickHz(actual_.actualHz, actual_.actualDutyPct);
|
||||
const uint32_t plannedPulseRxHz = receiver_.plannedPulseTickHz(
|
||||
actual_.actualHz, actual_.actualDutyPct);
|
||||
const FailReason resolution = validateResolution(actual_.actualHz, actual_.actualDutyPct,
|
||||
params_.accuracyPct, plannedRxHz, plannedPulseRxHz, actual_.bits,
|
||||
MEASUREMENT_AVERAGING_PERIODS);
|
||||
if (resolution != FailReason::NONE) {
|
||||
Log::printf("PWM", "resolution rejected: actual=%luHz duty=%.3f%% bits=%u period-capture=%luHz pulse-capture=%luHz tolerance=%.3f%%",
|
||||
actual_.actualHz, actual_.actualDutyPct, actual_.bits, plannedRxHz,
|
||||
plannedPulseRxHz, effectiveTolerancePct(params_.accuracyPct));
|
||||
finish(false, resolution); return false;
|
||||
}
|
||||
} else if (!receiver_.highRateBackend()) {
|
||||
finish(false, FailReason::UNSUPPORTED); return false;
|
||||
}
|
||||
Log::printf("PWM", "stage=%lu/%lu requested=%luHz/%luns actual=%luHz/%luns duty=%.3f%% bits=%u STARTED",
|
||||
stageIndex_ + 1, stageCount_, requestedHz_, requestedPulseNs_, actual_.actualHz,
|
||||
actual_.actualPulseNs, actual_.actualDutyPct, actual_.bits);
|
||||
if (showProgress) showStageProgress();
|
||||
if (static_cast<Role>(settings_.role) == Role::SOLO && !startLocalMeasurement(actual_.actualHz, actual_.actualDutyPct)) {
|
||||
finish(false, FailReason::UNSUPPORTED); return false;
|
||||
if (static_cast<Role>(settings_.role) == Role::SOLO) {
|
||||
const bool started = driverMode ? startDriverMeasurement() :
|
||||
startLocalMeasurement(actual_.actualHz, actual_.actualDutyPct);
|
||||
if (!started) { finish(false, FailReason::UNSUPPORTED); return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -526,7 +836,7 @@ bool App::startLocalMeasurement(float hz, float duty) {
|
||||
receiver_.plannedPulseTickHz(static_cast<uint32_t>(hz + 0.5f), duty),
|
||||
PWM_SETTLE_CYCLES, params_.testTimeMs);
|
||||
const bool ok = measurement_.start(hz, duty, params_.accuracyPct, params_.testTimeMs,
|
||||
MEASUREMENT_AVERAGING_PERIODS, PWM_SETTLE_CYCLES);
|
||||
MEASUREMENT_AVERAGING_PERIODS, PWM_SETTLE_CYCLES, rxActiveLightOn(settings_));
|
||||
const uint32_t nominalMs = stageWallTimeMs(params_.testTimeMs,
|
||||
static_cast<uint32_t>(hz + 0.5f));
|
||||
const uint64_t watchdogMs = static_cast<uint64_t>(nominalMs) * 2ULL + 2000ULL;
|
||||
@@ -536,6 +846,18 @@ bool App::startLocalMeasurement(float hz, float duty) {
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool App::startDriverMeasurement() {
|
||||
const bool ok = driverTest_.start(actual_.actualHz, actual_.actualPulseNs,
|
||||
params_.accuracyPct, params_.testTimeMs, PWM_SETTLE_CYCLES,
|
||||
txActiveLightOn(settings_), rxActiveLightOn(settings_));
|
||||
const uint32_t nominalMs = stageWallTimeMs(params_.testTimeMs, actual_.actualHz);
|
||||
const uint64_t watchdogMs = static_cast<uint64_t>(nominalMs) * 2ULL + 2000ULL;
|
||||
localMeasurementDeadlineMs_ = millis() + static_cast<uint32_t>(
|
||||
watchdogMs > UINT32_MAX ? UINT32_MAX : watchdogMs);
|
||||
if (!ok) Log::event("DRIVER", "response test start FAILED");
|
||||
return ok;
|
||||
}
|
||||
|
||||
void App::stagePassed() {
|
||||
Log::printf("TEST", "stage %lu/%lu PASS; PWM stopping", stageIndex_ + 1, stageCount_);
|
||||
pwm_.stop();
|
||||
@@ -543,8 +865,16 @@ void App::stagePassed() {
|
||||
// queue for the next pulse width. Never reset a FreeRTOS queue concurrently
|
||||
// with the capture ISR.
|
||||
if (static_cast<Role>(settings_.role) == Role::SOLO) receiver_.stop();
|
||||
if (++stageIndex_ >= stageCount_) { finish(true, FailReason::NONE); return; }
|
||||
if (static_cast<Role>(settings_.role) == Role::SOLO) { if (prepareStage()) state_ = AppState::SOLO_MEASURE; }
|
||||
if (++stageIndex_ >= stageCount_) {
|
||||
const bool preserveDriverMeasurements =
|
||||
static_cast<TestKind>(settings_.testKind) == TestKind::DRIVER;
|
||||
finish(true, FailReason::NONE, preserveDriverMeasurements);
|
||||
return;
|
||||
}
|
||||
if (static_cast<Role>(settings_.role) == Role::SOLO) {
|
||||
if (prepareStage()) state_ = static_cast<TestKind>(settings_.testKind) == TestKind::DRIVER
|
||||
? AppState::SOLO_DRIVER : AppState::SOLO_MEASURE;
|
||||
}
|
||||
else if (static_cast<Role>(settings_.role) == Role::MASTER) {
|
||||
requestedHz_ = params_.frequencyHz;
|
||||
requestedPulseNs_ = pulseWidthAt(params_.maxPulseNs, params_.minPulseNs, stageIndex_);
|
||||
@@ -561,7 +891,7 @@ void App::startMasterDiscovery() {
|
||||
requestedPulseNs_ = 0; havePeer_ = false; radio_.flush();
|
||||
opticalWakeActive_ = true;
|
||||
lastOpticalWakeToggleMs_ = millis();
|
||||
pwm_.active();
|
||||
pwm_.lightOn();
|
||||
pendingPacket_ = makePacket(MessageType::DISCOVER); radio_.sendBroadcast(pendingPacket_);
|
||||
lastSendMs_ = millis(); retries_ = 0;
|
||||
state_ = AppState::MASTER_DISCOVER; Log::printf("ESP-NOW", "discovery started session=%08lX", session_);
|
||||
@@ -575,7 +905,8 @@ ProtocolPacket App::makePacket(MessageType type) const {
|
||||
p.requestedHz = requestedHz_; p.requestedPulseNs = requestedPulseNs_;
|
||||
p.actualHz = actual_.actualHz; p.actualPulseNs = actual_.actualPulseNs;
|
||||
p.testTimeMs = params_.testTimeMs;
|
||||
p.accuracyX100 = static_cast<uint16_t>(params_.accuracyPct * 100.0f + 0.5f); p.settleCycles = PWM_SETTLE_CYCLES;
|
||||
p.accuracyX100 = static_cast<uint16_t>(params_.accuracyPct * 100.0f + 0.5f);
|
||||
p.lightCode = settings_.lightCode;
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -663,6 +994,8 @@ void App::handleRadio() {
|
||||
state_ = AppState::SLAVE_WAIT_START;
|
||||
params_.testTimeMs = r.packet.testTimeMs;
|
||||
params_.accuracyPct = r.packet.accuracyX100 / 100.0f;
|
||||
if (r.packet.lightCode <= static_cast<uint8_t>(LightCode::LL))
|
||||
settings_.lightCode = r.packet.lightCode;
|
||||
requestedHz_ = r.packet.requestedHz; requestedPulseNs_ = r.packet.requestedPulseNs;
|
||||
stageCount_ = r.packet.stageCount;
|
||||
actual_ = {};
|
||||
@@ -753,7 +1086,7 @@ void App::updateMaster() {
|
||||
if (state_ == AppState::MASTER_DISCOVER) {
|
||||
if (now - lastOpticalWakeToggleMs_ >= OPTICAL_WAKE_HALF_PERIOD_MS) {
|
||||
opticalWakeActive_ = !opticalWakeActive_;
|
||||
if (opticalWakeActive_) pwm_.active();
|
||||
if (opticalWakeActive_) pwm_.lightOn();
|
||||
else pwm_.stop();
|
||||
lastOpticalWakeToggleMs_ = now;
|
||||
}
|
||||
@@ -836,7 +1169,10 @@ void App::sendAbort(FailReason reason) {
|
||||
|
||||
void App::abortTest() {
|
||||
Log::event("ACTION", "abort requested: sending ABORT, stopping receiver and PWM");
|
||||
sendAbort(FailReason::ABORTED); measurement_.abort(); finish(false, FailReason::ABORTED);
|
||||
sendAbort(FailReason::ABORTED);
|
||||
measurement_.abort();
|
||||
driverTest_.abort();
|
||||
finish(false, FailReason::ABORTED);
|
||||
}
|
||||
|
||||
void App::finish(bool pass, FailReason reason, bool preserveDisplay) {
|
||||
@@ -903,17 +1239,39 @@ bool App::idlePowerSaveAllowed() const {
|
||||
// Never enter blocking light sleep while the settings screen is open. A
|
||||
// wake-up press is deliberately consumed by the button state machine, which
|
||||
// is useful in IDLE but makes menu navigation appear frozen.
|
||||
return initialized_ && (state_ == AppState::IDLE ||
|
||||
return !usbHostPresent() && initialized_ && (state_ == AppState::IDLE ||
|
||||
state_ == AppState::FINISHED || state_ == AppState::SLAVE_READY);
|
||||
}
|
||||
|
||||
bool App::usbHostPresent() const {
|
||||
#if ARDUINO_USB_MODE && ARDUINO_USB_CDC_ON_BOOT && SOC_USB_SERIAL_JTAG_SUPPORTED
|
||||
// This is driven by USB SOF packets, not by CDC traffic: an enumerated host
|
||||
// keeps the board awake even if COM is closed and no bytes are exchanged.
|
||||
// Retain the state across short SOF/driver glitches.
|
||||
const uint32_t now = millis();
|
||||
if (Serial.isPlugged()) {
|
||||
lastUsbHostSeenMs_ = now ? now : 1U;
|
||||
return true;
|
||||
}
|
||||
return lastUsbHostSeenMs_ &&
|
||||
now - lastUsbHostSeenMs_ <= USB_HOST_DISCONNECT_GRACE_MS;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void App::setStandbyOpticalOutput() {
|
||||
if (static_cast<Role>(settings_.role) == Role::SLAVE) pwm_.stop();
|
||||
// A gate driver must never be held enabled while the tester is idle or
|
||||
// showing a result. DRIVER is SOLO-only, so force real light OFF here.
|
||||
if (static_cast<Role>(settings_.role) == Role::SLAVE ||
|
||||
static_cast<TestKind>(settings_.testKind) == TestKind::DRIVER) pwm_.stop();
|
||||
else pwm_.active();
|
||||
}
|
||||
|
||||
void App::setActivePerformance(bool active) {
|
||||
const uint32_t targetMhz = active ? 160U : 80U;
|
||||
const bool driverMode = initialized_ &&
|
||||
static_cast<TestKind>(settings_.testKind) == TestKind::DRIVER;
|
||||
const uint32_t targetMhz = active ? (driverMode ? 240U : 160U) : 80U;
|
||||
if (getCpuFrequencyMhz() != targetMhz && !setCpuFrequencyMhz(targetMhz))
|
||||
Log::printf("POWER", "CPU frequency change to %luMHz FAILED", targetMhz);
|
||||
}
|
||||
@@ -1020,13 +1378,18 @@ void App::printConfiguration() {
|
||||
if (SERIAL_MINIMAL_LOG) return;
|
||||
const char *board = TARGET_IS_C3 ? "ESP32-C3" : "ESP32-S3";
|
||||
uint8_t mac[6] = {}; esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
||||
Serial.printf("\nOptical Channel Tester | %s | mode=%s\n", board, roleName(static_cast<Role>(settings_.role)));
|
||||
Serial.printf("\nOptical Channel Tester | %s | mode=%s/%s | light=%s\n", board,
|
||||
roleName(static_cast<Role>(settings_.role)),
|
||||
testKindName(static_cast<TestKind>(settings_.testKind)),
|
||||
lightCodeName(static_cast<LightCode>(settings_.lightCode)));
|
||||
Serial.printf("MAC=%02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
Serial.printf("GPIO PWM=%u RX=%u START=%u MODE=%u SDA=%u SCL=%u\n", GPIO_PWM, GPIO_RX,
|
||||
GPIO_BUTTON_START, GPIO_BUTTON_MODE, GPIO_SDA, GPIO_SCL);
|
||||
Serial.printf("Test %lu Hz, pulse %lu..%lu ns, accuracy %.2f%%, %lums, RX AUTO\n",
|
||||
Serial.printf("Test %lu Hz, pulse %lu..%lu ns, accuracy %.2f%%, %lums, TX light=%c RX active light=%c\n",
|
||||
params_.frequencyHz, params_.maxPulseNs, params_.minPulseNs,
|
||||
params_.accuracyPct, params_.testTimeMs);
|
||||
params_.accuracyPct, params_.testTimeMs,
|
||||
lightCodeName(static_cast<LightCode>(settings_.lightCode))[0],
|
||||
lightCodeName(static_cast<LightCode>(settings_.lightCode))[1]);
|
||||
stageCount_ = pulseWidthPointCount(params_.maxPulseNs, params_.minPulseNs);
|
||||
Serial.printf("Pulse widths descending (%lu): ", stageCount_);
|
||||
for (uint32_t i = 0; i < stageCount_; ++i)
|
||||
@@ -1093,6 +1456,66 @@ void App::showStageResult(const StageStats &s) {
|
||||
overallProgressTotal(stageCount_));
|
||||
}
|
||||
|
||||
void App::showDriverResult(const DriverStats &s, bool testPassed) {
|
||||
char one[64], two[64];
|
||||
const bool haveResponse = s.responses || s.lastResponseTicks;
|
||||
const uint64_t delaySumTicks =
|
||||
s.turnOn.delaySumTicks + s.turnOff.delaySumTicks;
|
||||
const uint64_t responseSumTicks =
|
||||
s.turnOn.responseSumTicks + s.turnOff.responseSumTicks;
|
||||
const uint64_t displayedDelayTicks = s.responses ?
|
||||
delaySumTicks / s.responses : s.lastDelayTicks;
|
||||
const uint64_t displayedResponseTicks = s.responses ?
|
||||
responseSumTicks / s.responses : s.lastResponseTicks;
|
||||
const uint32_t delayNs = static_cast<uint32_t>(
|
||||
(displayedDelayTicks * 1000000000ULL +
|
||||
driverTest_.tickHz() / 2U) / driverTest_.tickHz());
|
||||
const uint32_t responseNs = static_cast<uint32_t>(
|
||||
(displayedResponseTicks * 1000000000ULL +
|
||||
driverTest_.tickHz() / 2U) / driverTest_.tickHz());
|
||||
char delay[12] = "---", response[12] = "---";
|
||||
if (haveResponse) {
|
||||
Display::formatPulse(delayNs, delay, sizeof(delay));
|
||||
Display::formatPulse(responseNs, response, sizeof(response));
|
||||
}
|
||||
if (testPassed) {
|
||||
snprintf(one, sizeof(one), "%s", UiText::PASS_WORD);
|
||||
snprintf(two, sizeof(two), UiText::DRIVER_MEASUREMENT_FORMAT,
|
||||
delay, response);
|
||||
} else if (s.reason != FailReason::NONE) {
|
||||
snprintf(one, sizeof(one), "%s", uiFailName(s.reason));
|
||||
char elapsed[12] = "---", errorDelay[12] = "---", errorPulse[12] = "---";
|
||||
if (s.errorElapsedTicks) {
|
||||
const uint64_t elapsedNs =
|
||||
(s.errorElapsedTicks * 1000000000ULL + driverTest_.tickHz() / 2U) /
|
||||
driverTest_.tickHz();
|
||||
formatElapsedNs(elapsedNs, elapsed, sizeof(elapsed));
|
||||
}
|
||||
if (s.errorDelayValid) {
|
||||
const uint64_t errorDelayNs =
|
||||
(static_cast<uint64_t>(s.errorDelayTicks) * 1000000000ULL +
|
||||
driverTest_.tickHz() / 2U) / driverTest_.tickHz();
|
||||
formatElapsedNs(errorDelayNs, errorDelay, sizeof(errorDelay));
|
||||
}
|
||||
if (s.errorPulseValid) {
|
||||
const uint64_t errorPulseNs =
|
||||
(static_cast<uint64_t>(s.errorPulseTicks) * 1000000000ULL +
|
||||
driverTest_.tickHz() / 2U) / driverTest_.tickHz();
|
||||
formatElapsedNs(errorPulseNs, errorPulse, sizeof(errorPulse));
|
||||
}
|
||||
snprintf(two, sizeof(two), "T:%s D:%s P:%s",
|
||||
elapsed, errorDelay, errorPulse);
|
||||
} else {
|
||||
formatTestTarget(requestedHz_, requestedPulseNs_, one, sizeof(one));
|
||||
snprintf(two, sizeof(two), UiText::DRIVER_MEASUREMENT_FORMAT,
|
||||
delay, response);
|
||||
}
|
||||
display_.show(one, two,
|
||||
overallProgress(stageIndex_, driverTest_.progressStep()),
|
||||
overallProgressTotal(stageCount_), s.reason == FailReason::NONE ? nullptr :
|
||||
roleCorner(Role::SOLO));
|
||||
}
|
||||
|
||||
void App::showRemoteResult(const ProtocolPacket &packet) {
|
||||
const FailReason reason = packet.reason <= static_cast<uint8_t>(FailReason::ABORTED)
|
||||
? static_cast<FailReason>(packet.reason) : FailReason::UNSUPPORTED;
|
||||
@@ -1135,6 +1558,15 @@ void App::fillMeasuredResult(ProtocolPacket &packet, const StageStats &stats) co
|
||||
}
|
||||
|
||||
void App::showStageProgress() {
|
||||
if (static_cast<TestKind>(settings_.testKind) == TestKind::DRIVER) {
|
||||
char one[64], two[64];
|
||||
formatTestTarget(requestedHz_, requestedPulseNs_, one, sizeof(one));
|
||||
snprintf(two, sizeof(two), UiText::DRIVER_MEASUREMENT_FORMAT,
|
||||
"---", "---");
|
||||
display_.show(one, two, overallProgress(stageIndex_, 0),
|
||||
overallProgressTotal(stageCount_));
|
||||
return;
|
||||
}
|
||||
char one[64];
|
||||
formatTestTarget(requestedHz_, requestedPulseNs_, one, sizeof(one));
|
||||
display_.show(one, UiText::NO_MEASUREMENT, overallProgress(stageIndex_, 0),
|
||||
|
||||
Reference in New Issue
Block a user