Добавлено тестировние платы в меню:
- Тест Tx Оптики - Тест Rx Оптики - Тест АЦП
This commit is contained in:
@@ -17,7 +17,7 @@ namespace {
|
||||
const char *uiFailName(FailReason reason);
|
||||
|
||||
const char *appStateName(AppState state) {
|
||||
static const char *names[] = {"IDLE", "MENU", "SOLO_MEASURE", "SOLO_DRIVER", "MASTER_DISCOVER",
|
||||
static const char *names[] = {"IDLE", "MENU", "BOARD_TEST", "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);
|
||||
@@ -145,6 +145,30 @@ const char *uiTestName(TestKind kind) {
|
||||
? UiText::TEST_NAMES[index] : "?";
|
||||
}
|
||||
|
||||
const char *uiTestGroupName(TestGroup group) {
|
||||
const uint8_t index = static_cast<uint8_t>(group);
|
||||
return index < sizeof(UiText::TEST_GROUP_NAMES) / sizeof(UiText::TEST_GROUP_NAMES[0])
|
||||
? UiText::TEST_GROUP_NAMES[index] : "?";
|
||||
}
|
||||
|
||||
const char *uiBoardTestName(BoardTest test) {
|
||||
const uint8_t index = static_cast<uint8_t>(test);
|
||||
return index < sizeof(UiText::BOARD_TEST_NAMES) / sizeof(UiText::BOARD_TEST_NAMES[0])
|
||||
? UiText::BOARD_TEST_NAMES[index] : "?";
|
||||
}
|
||||
|
||||
bool boardTestAvailable(BoardTest test) {
|
||||
if (test == BoardTest::PWM_OUTPUT) return true;
|
||||
if (test == BoardTest::ADC) return BOARD_RX_USES_ADC && BOARD_ADC_AVAILABLE;
|
||||
if (test == BoardTest::RX_INPUT) return !BOARD_RX_USES_ADC;
|
||||
return false;
|
||||
}
|
||||
|
||||
BoardTest defaultBoardTest() {
|
||||
return BOARD_RX_USES_ADC && BOARD_ADC_AVAILABLE
|
||||
? BoardTest::ADC : BoardTest::RX_INPUT;
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -290,7 +314,8 @@ void App::finishInitialization(bool factoryReset) {
|
||||
lastUserActivityMs_ = millis();
|
||||
setActivePerformance(false);
|
||||
printConfiguration();
|
||||
if (static_cast<Role>(settings_.role) == Role::SLAVE) armSlave();
|
||||
if (static_cast<TestGroup>(settings_.testGroup) == TestGroup::OPTICS &&
|
||||
static_cast<Role>(settings_.role) == Role::SLAVE) armSlave();
|
||||
else showIdle();
|
||||
}
|
||||
|
||||
@@ -313,7 +338,15 @@ void App::update() {
|
||||
else if (now - bootCheckStartedMs_ >= FACTORY_RESET_HOLD_MS) finishInitialization(true);
|
||||
return;
|
||||
}
|
||||
serviceRxPinStateLog();
|
||||
if (state_ == AppState::BOARD_TEST) {
|
||||
if (startEvent == ButtonEvent::LONG) stopBoardTest();
|
||||
else {
|
||||
if (modeEvent != ButtonEvent::NONE)
|
||||
Log::event("ACTION", "MODE ignored while board test is active");
|
||||
updateBoardTest(now);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (state_ != AppState::IDLE && state_ != AppState::MENU && state_ != AppState::FINISHED &&
|
||||
startEvent == ButtonEvent::LONG) { abortTest(); return; }
|
||||
if (state_ != AppState::IDLE && state_ != AppState::MENU && state_ != AppState::FINISHED &&
|
||||
@@ -323,12 +356,16 @@ void App::update() {
|
||||
if (modeEvent == ButtonEvent::SHORT) {
|
||||
cycleRunMode(); sanitizeRange(); const bool saved = store_.save(settings_);
|
||||
params_ = store_.params(settings_);
|
||||
if (static_cast<Role>(settings_.role) == Role::SLAVE) armSlave();
|
||||
if (static_cast<TestGroup>(settings_.testGroup) == TestGroup::OPTICS &&
|
||||
static_cast<Role>(settings_.role) == Role::SLAVE) armSlave();
|
||||
else showIdle();
|
||||
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");
|
||||
if (static_cast<TestGroup>(settings_.testGroup) == TestGroup::BOARD)
|
||||
Log::printf("ACTION", "board test changed to %s, NVS=%s",
|
||||
boardTestName(static_cast<BoardTest>(settings_.boardTest)), saved ? "OK" : "FAILED");
|
||||
else
|
||||
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(); }
|
||||
@@ -351,13 +388,15 @@ void App::update() {
|
||||
}
|
||||
if (state_ == AppState::MENU) {
|
||||
if (modeEvent == ButtonEvent::SHORT) {
|
||||
menuItem_ = (menuItem_ + 1U) % 6U; Log::printf("ACTION", "menu item selected index=%u", menuItem_); showMenu();
|
||||
const uint8_t menuCount = static_cast<TestGroup>(settings_.testGroup) == TestGroup::BOARD ? 1U : 7U;
|
||||
menuItem_ = (menuItem_ + 1U) % menuCount; 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_);
|
||||
Log::printf("ACTION", "settings menu saved and closed, NVS=%s", saved ? "OK" : "FAILED");
|
||||
state_ = AppState::IDLE; printConfiguration();
|
||||
if (static_cast<Role>(settings_.role) == Role::SLAVE) armSlave();
|
||||
if (static_cast<TestGroup>(settings_.testGroup) == TestGroup::OPTICS &&
|
||||
static_cast<Role>(settings_.role) == Role::SLAVE) armSlave();
|
||||
else showIdle();
|
||||
} else if (startEvent == ButtonEvent::SHORT) changeMenu(+1);
|
||||
else if (startEvent == ButtonEvent::LONG || startEvent == ButtonEvent::REPEAT) changeMenu(-1);
|
||||
@@ -429,10 +468,17 @@ void App::showIdle() {
|
||||
setActivePerformance(false);
|
||||
setStandbyOpticalOutput();
|
||||
lastUserActivityMs_ = millis();
|
||||
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);
|
||||
char one[64];
|
||||
if (static_cast<TestGroup>(settings_.testGroup) == TestGroup::BOARD) {
|
||||
snprintf(one, sizeof(one), "%s: %s", uiTestGroupName(TestGroup::BOARD),
|
||||
uiBoardTestName(static_cast<BoardTest>(settings_.boardTest)));
|
||||
display_.show(one, UiText::BOARD_READY);
|
||||
} else {
|
||||
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() {
|
||||
@@ -464,6 +510,8 @@ void App::serviceSerialConsole() {
|
||||
void App::printSerialHelp() {
|
||||
Serial.println("COMMANDS (send with newline):");
|
||||
Serial.println(" help | status | start | stop | defaults");
|
||||
Serial.println(" set group optics|board");
|
||||
Serial.println(" set boardtest adc|pwm|rx");
|
||||
Serial.println(" set role solo|master|slave");
|
||||
Serial.println(" set test optical|driver");
|
||||
Serial.println(" set frequency 500|1000|2000|5000|10000|25000");
|
||||
@@ -480,8 +528,9 @@ void App::printSerialStatus() {
|
||||
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)),
|
||||
Serial.printf("STATUS state=%s group=%s boardtest=%s role=%s test=%s frequency=%luHz max=%luns min=%luns accuracy=%.2f%% time=%lums light=%s usb=%s\n",
|
||||
appStateName(state_), testGroupName(static_cast<TestGroup>(settings_.testGroup)),
|
||||
boardTestName(static_cast<BoardTest>(settings_.boardTest)), roleName(static_cast<Role>(settings_.role)),
|
||||
testKindName(static_cast<TestKind>(settings_.testKind)), params_.frequencyHz,
|
||||
params_.maxPulseNs, params_.minPulseNs, params_.accuracyPct, params_.testTimeMs,
|
||||
configuredLevelName(settings_),
|
||||
@@ -501,7 +550,8 @@ void App::finishSerialSettingsChange() {
|
||||
pwm_.configureActiveLight(configuredTxPulseLightOn(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();
|
||||
if (static_cast<TestGroup>(settings_.testGroup) == TestGroup::OPTICS &&
|
||||
static_cast<Role>(settings_.role) == Role::SLAVE) armSlave();
|
||||
else showIdle();
|
||||
printSerialStatus();
|
||||
}
|
||||
@@ -535,7 +585,10 @@ void App::handleSerialCommand(char *line) {
|
||||
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) {
|
||||
else if (state_ == AppState::BOARD_TEST) {
|
||||
Serial.println("OK board test stopped");
|
||||
stopBoardTest();
|
||||
} 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 {
|
||||
@@ -564,7 +617,22 @@ void App::handleSerialCommand(char *line) {
|
||||
|
||||
bool accepted = false;
|
||||
uint32_t numeric = 0;
|
||||
if (!strcmp(name, "role")) {
|
||||
if (!strcmp(name, "group")) {
|
||||
if (!strcmp(value, "optics") || !strcmp(value, "optical")) {
|
||||
settings_.testGroup = static_cast<uint8_t>(TestGroup::OPTICS); accepted = true;
|
||||
} else if (!strcmp(value, "board")) {
|
||||
settings_.testGroup = static_cast<uint8_t>(TestGroup::BOARD); accepted = true;
|
||||
}
|
||||
} else if (!strcmp(name, "boardtest") || !strcmp(name, "board")) {
|
||||
BoardTest selected = static_cast<BoardTest>(UINT8_MAX);
|
||||
if (!strcmp(value, "adc")) selected = BoardTest::ADC;
|
||||
else if (!strcmp(value, "pwm")) selected = BoardTest::PWM_OUTPUT;
|
||||
else if (!strcmp(value, "rx")) selected = BoardTest::RX_INPUT;
|
||||
if (boardTestAvailable(selected)) {
|
||||
settings_.boardTest = static_cast<uint8_t>(selected);
|
||||
accepted = true;
|
||||
}
|
||||
} else 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; }
|
||||
@@ -608,6 +676,13 @@ void App::handleSerialCommand(char *line) {
|
||||
}
|
||||
|
||||
void App::cycleRunMode() {
|
||||
if (static_cast<TestGroup>(settings_.testGroup) == TestGroup::BOARD) {
|
||||
do {
|
||||
settings_.boardTest = (settings_.boardTest + 1U) %
|
||||
(static_cast<uint8_t>(BoardTest::RX_INPUT) + 1U);
|
||||
} while (!boardTestAvailable(static_cast<BoardTest>(settings_.boardTest)));
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
@@ -625,6 +700,11 @@ void App::cycleRunMode() {
|
||||
}
|
||||
|
||||
void App::sanitizeRange() {
|
||||
if (settings_.testGroup > static_cast<uint8_t>(TestGroup::BOARD))
|
||||
settings_.testGroup = static_cast<uint8_t>(TestGroup::OPTICS);
|
||||
if (settings_.boardTest > static_cast<uint8_t>(BoardTest::RX_INPUT) ||
|
||||
!boardTestAvailable(static_cast<BoardTest>(settings_.boardTest)))
|
||||
settings_.boardTest = static_cast<uint8_t>(defaultBoardTest());
|
||||
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))
|
||||
@@ -654,29 +734,19 @@ void App::sanitizeRange() {
|
||||
}
|
||||
}
|
||||
|
||||
void App::serviceRxPinStateLog() {
|
||||
#ifdef RX_PIN_CHANGE_TEST
|
||||
const bool level = digitalRead(GPIO_RX) == HIGH;
|
||||
const bool outsideTest = state_ == AppState::IDLE || state_ == AppState::MENU ||
|
||||
state_ == AppState::SLAVE_READY || state_ == AppState::FINISHED;
|
||||
if (rxPinStateKnown_ && level != rxPinState_ && outsideTest)
|
||||
Log::printf("RX TEST", "GPIO=%u state=%s (%u)", GPIO_RX,
|
||||
level ? "HIGH" : "LOW", level ? 1U : 0U);
|
||||
rxPinState_ = level;
|
||||
rxPinStateKnown_ = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void App::changeMenu(int d) {
|
||||
sanitizeRange();
|
||||
if (menuItem_ == 1) {
|
||||
if (menuItem_ == 0) {
|
||||
settings_.testGroup = cycleIndex(settings_.testGroup, 0,
|
||||
static_cast<uint8_t>(TestGroup::BOARD), d);
|
||||
} else if (menuItem_ == 2) {
|
||||
const uint8_t last = lastValidMaxPulseIndex(
|
||||
PWM_FREQUENCY_OPTIONS_HZ[settings_.frequencyIndex]);
|
||||
const uint8_t first = firstMaxPulseIndexAtLeast(
|
||||
MIN_PULSE_OPTIONS_NS[settings_.minPulseIndex], last);
|
||||
settings_.maxPulseIndex = cycleIndex(settings_.maxPulseIndex,
|
||||
first, last, d);
|
||||
} else if (menuItem_ == 2) {
|
||||
} else if (menuItem_ == 3) {
|
||||
const uint8_t last = lastMinPulseIndexAtMost(
|
||||
MAX_PULSE_OPTIONS_NS[settings_.maxPulseIndex]);
|
||||
const uint8_t first = static_cast<TestKind>(settings_.testKind) == TestKind::DRIVER
|
||||
@@ -685,10 +755,10 @@ void App::changeMenu(int 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:
|
||||
case 1: value = &settings_.frequencyIndex; count = countOf(PWM_FREQUENCY_OPTIONS_HZ); break;
|
||||
case 4: value = &settings_.accuracyIndex; count = countOf(ACCURACY_OPTIONS_PCT); break;
|
||||
case 5: value = &settings_.timeIndex; count = countOf(TEST_TIME_OPTIONS_MS); break;
|
||||
case 6:
|
||||
if (static_cast<TestKind>(settings_.testKind) != TestKind::DRIVER) {
|
||||
showMenu();
|
||||
return;
|
||||
@@ -713,26 +783,31 @@ void App::showMenu() {
|
||||
Display::formatDuration(actualNominalTotalUs(), all, sizeof(all));
|
||||
switch (menuItem_) {
|
||||
case 0:
|
||||
snprintf(value, sizeof(value), "%s",
|
||||
uiTestGroupName(static_cast<TestGroup>(settings_.testGroup)));
|
||||
label = UiText::MENU_TEST_GROUP;
|
||||
break;
|
||||
case 1:
|
||||
Display::formatPwmFrequency(params_.frequencyHz, value, sizeof(value));
|
||||
label = UiText::MENU_FREQUENCY;
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
Display::formatPulse(params_.maxPulseNs, value, sizeof(value));
|
||||
label = UiText::MENU_MAX_PULSE;
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
Display::formatPulse(params_.minPulseNs, value, sizeof(value));
|
||||
label = UiText::MENU_MIN_PULSE;
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
snprintf(value, sizeof(value), "+/-%g%%", params_.accuracyPct);
|
||||
label = UiText::MENU_ACCURACY;
|
||||
break;
|
||||
case 4:
|
||||
case 5:
|
||||
snprintf(value, sizeof(value), "%.1fs", params_.testTimeMs / 1000.0f);
|
||||
label = UiText::MENU_TEST_TIME;
|
||||
break;
|
||||
case 5: {
|
||||
case 6: {
|
||||
if (static_cast<TestKind>(settings_.testKind) != TestKind::DRIVER) {
|
||||
snprintf(value, sizeof(value), "%s", UiText::LIGHT_AUTO);
|
||||
label = UiText::MENU_LIGHT_CODE;
|
||||
@@ -751,7 +826,10 @@ void App::showMenu() {
|
||||
default: return;
|
||||
}
|
||||
formatMenuLine(label, value, one, sizeof(one));
|
||||
formatMenuLine(UiText::MENU_TOTAL_TIME, all, total, sizeof(total));
|
||||
if (static_cast<TestGroup>(settings_.testGroup) == TestGroup::BOARD)
|
||||
snprintf(total, sizeof(total), "%s", UiText::BOARD_READY);
|
||||
else
|
||||
formatMenuLine(UiText::MENU_TOTAL_TIME, all, total, sizeof(total));
|
||||
display_.show(one, total);
|
||||
}
|
||||
|
||||
@@ -760,6 +838,10 @@ void App::startTest() {
|
||||
pwm_.stop();
|
||||
setActivePerformance(true);
|
||||
sanitizeRange();
|
||||
if (static_cast<TestGroup>(settings_.testGroup) == TestGroup::BOARD) {
|
||||
startBoardTest();
|
||||
return;
|
||||
}
|
||||
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;
|
||||
@@ -787,6 +869,98 @@ void App::startTest() {
|
||||
else { state_ = AppState::SLAVE_READY; Log::event("TEST", "Slave armed and waiting for Master"); display_.show(UiText::SLAVE_READY, UiText::WAIT_MASTER); }
|
||||
}
|
||||
|
||||
void App::startBoardTest() {
|
||||
const BoardTest test = static_cast<BoardTest>(settings_.boardTest);
|
||||
state_ = AppState::BOARD_TEST;
|
||||
boardTestUpdatedMs_ = boardDisplayUpdatedMs_ = 0;
|
||||
boardPwmLightOn_ = false;
|
||||
rxPinStateKnown_ = false;
|
||||
Log::printf("BOARD", "starting test=%s", boardTestName(test));
|
||||
|
||||
// ADC and RX checks need the optical transmitter continuously illuminated.
|
||||
// The PWM-output check drives the same pin itself and therefore replaces
|
||||
// the constant active level with its test waveform.
|
||||
if (test != BoardTest::PWM_OUTPUT) {
|
||||
pwm_.lightOn();
|
||||
Log::printf("BOARD", "optical output active GPIO=%u level=%s", GPIO_PWM,
|
||||
TX_LIGHT_ON_GPIO_LEVEL == HIGH ? "HIGH" : "LOW");
|
||||
}
|
||||
|
||||
if (test == BoardTest::ADC) {
|
||||
if (!BOARD_ADC_AVAILABLE) {
|
||||
Serial.println("BOARD ADC: unavailable in MAKETKA profile");
|
||||
display_.show(UiText::ADC_UNAVAILABLE, UiText::BOARD_STOP);
|
||||
return;
|
||||
}
|
||||
pinMode(GPIO_ANALOG_RX, INPUT);
|
||||
pinMode(GPIO_VBAT, INPUT);
|
||||
display_.show(UiText::BOARD_TEST_NAMES[static_cast<uint8_t>(BoardTest::ADC)],
|
||||
UiText::BOARD_STOP);
|
||||
} else if (test == BoardTest::PWM_OUTPUT) {
|
||||
boardPwmLightOn_ = true;
|
||||
boardTestUpdatedMs_ = millis();
|
||||
pwm_.lightOn();
|
||||
Serial.printf("BOARD PWM: GPIO=%u frequency=%luHz duty=50%% light=ON\n",
|
||||
GPIO_PWM, BOARD_PWM_TEST_FREQUENCY_HZ);
|
||||
display_.show(UiText::BOARD_TEST_NAMES[static_cast<uint8_t>(test)], UiText::BOARD_STOP);
|
||||
} else {
|
||||
pinMode(GPIO_RX, INPUT);
|
||||
display_.show(UiText::BOARD_TEST_NAMES[static_cast<uint8_t>(test)], UiText::BOARD_STOP);
|
||||
}
|
||||
}
|
||||
|
||||
void App::updateBoardTest(uint32_t now) {
|
||||
const BoardTest test = static_cast<BoardTest>(settings_.boardTest);
|
||||
if (test == BoardTest::ADC) {
|
||||
if (!BOARD_ADC_AVAILABLE || now - boardTestUpdatedMs_ < BOARD_ADC_PRINT_INTERVAL_MS) return;
|
||||
boardTestUpdatedMs_ = now;
|
||||
const uint16_t analogRx = analogRead(GPIO_ANALOG_RX);
|
||||
const uint16_t vbat = analogRead(GPIO_VBAT);
|
||||
Serial.printf("ADC analog_rx=%u vbat=%u\n", analogRx, vbat);
|
||||
if (now - boardDisplayUpdatedMs_ >= BOARD_TEST_DISPLAY_INTERVAL_MS) {
|
||||
boardDisplayUpdatedMs_ = now;
|
||||
char one[32], two[32];
|
||||
snprintf(one, sizeof(one), "ADC RX: %u", analogRx);
|
||||
snprintf(two, sizeof(two), "ADC VBAT: %u", vbat);
|
||||
display_.show(one, two);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (test == BoardTest::PWM_OUTPUT) {
|
||||
if (now - boardTestUpdatedMs_ < BOARD_PWM_TEST_HALF_PERIOD_MS) return;
|
||||
boardTestUpdatedMs_ = now;
|
||||
boardPwmLightOn_ = !boardPwmLightOn_;
|
||||
if (boardPwmLightOn_) pwm_.lightOn();
|
||||
else pwm_.stop();
|
||||
Serial.printf("BOARD PWM: light=%s\n", boardPwmLightOn_ ? "ON" : "OFF");
|
||||
char two[32];
|
||||
snprintf(two, sizeof(two), "%luHz 50%%: %s", BOARD_PWM_TEST_FREQUENCY_HZ,
|
||||
boardPwmLightOn_ ? "ON" : "OFF");
|
||||
display_.show(UiText::BOARD_TEST_NAMES[static_cast<uint8_t>(test)], two);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool level = digitalRead(GPIO_RX) == HIGH;
|
||||
if (!rxPinStateKnown_ || level != rxPinState_) {
|
||||
rxPinState_ = level;
|
||||
rxPinStateKnown_ = true;
|
||||
Serial.printf("RX GPIO=%u state=%s (%u)\n", GPIO_RX,
|
||||
level ? "HIGH" : "LOW", level ? 1U : 0U);
|
||||
char two[32];
|
||||
snprintf(two, sizeof(two), "GPIO %u: %s", GPIO_RX, level ? "HIGH" : "LOW");
|
||||
display_.show(UiText::BOARD_TEST_NAMES[static_cast<uint8_t>(test)], two);
|
||||
}
|
||||
}
|
||||
|
||||
void App::stopBoardTest() {
|
||||
Log::printf("BOARD", "stopping test=%s", boardTestName(static_cast<BoardTest>(settings_.boardTest)));
|
||||
pwm_.stop();
|
||||
state_ = AppState::IDLE;
|
||||
setActivePerformance(false);
|
||||
showIdle();
|
||||
}
|
||||
|
||||
bool App::armSlave(bool preserveDisplay) {
|
||||
setActivePerformance(false);
|
||||
pwm_.stop();
|
||||
@@ -1291,13 +1465,15 @@ bool App::usbHostPresent() const {
|
||||
void App::setStandbyOpticalOutput() {
|
||||
// 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 ||
|
||||
if (static_cast<TestGroup>(settings_.testGroup) == TestGroup::BOARD ||
|
||||
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 bool driverMode = initialized_ &&
|
||||
static_cast<TestGroup>(settings_.testGroup) == TestGroup::OPTICS &&
|
||||
static_cast<TestKind>(settings_.testKind) == TestKind::DRIVER;
|
||||
const uint32_t targetMhz = active ? (driverMode ? 240U : 160U) : 80U;
|
||||
if (getCpuFrequencyMhz() != targetMhz && !setCpuFrequencyMhz(targetMhz))
|
||||
@@ -1406,7 +1582,15 @@ 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/%s | light=%s\n", board,
|
||||
if (static_cast<TestGroup>(settings_.testGroup) == TestGroup::BOARD) {
|
||||
Serial.printf("\nOptical Channel Tester | %s | group=BOARD | test=%s\n", board,
|
||||
boardTestName(static_cast<BoardTest>(settings_.boardTest)));
|
||||
Serial.printf("GPIO PWM=%u RX=%u ANALOG_RX=%u VBAT=%u START=%u MODE=%u SDA=%u SCL=%u\n",
|
||||
GPIO_PWM, GPIO_RX, GPIO_ANALOG_RX, GPIO_VBAT, GPIO_BUTTON_START,
|
||||
GPIO_BUTTON_MODE, GPIO_SDA, GPIO_SCL);
|
||||
return;
|
||||
}
|
||||
Serial.printf("\nOptical Channel Tester | %s | group=OPTICS | mode=%s/%s | light=%s\n", board,
|
||||
roleName(static_cast<Role>(settings_.role)),
|
||||
testKindName(static_cast<TestKind>(settings_.testKind)),
|
||||
configuredLevelName(settings_));
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "SettingsStore.h"
|
||||
|
||||
enum class AppState : uint8_t {
|
||||
IDLE, MENU, SOLO_MEASURE, SOLO_DRIVER, MASTER_DISCOVER, MASTER_WAIT_READY,
|
||||
IDLE, MENU, BOARD_TEST, 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
|
||||
};
|
||||
@@ -26,6 +26,9 @@ class App {
|
||||
void cycleRunMode();
|
||||
void sanitizeRange();
|
||||
void startTest();
|
||||
void startBoardTest();
|
||||
void updateBoardTest(uint32_t now);
|
||||
void stopBoardTest();
|
||||
bool armSlave(bool preserveDisplay = false);
|
||||
bool prepareStage(bool showProgress = true);
|
||||
bool startLocalMeasurement(float hz, float duty);
|
||||
@@ -52,7 +55,6 @@ class App {
|
||||
void updateHeartbeat();
|
||||
bool packetForCurrent(const ProtocolPacket &p) const;
|
||||
void serviceIdlePowerSave();
|
||||
void serviceRxPinStateLog();
|
||||
void serviceSerialConsole();
|
||||
void handleSerialCommand(char *line);
|
||||
void printSerialHelp();
|
||||
@@ -100,6 +102,9 @@ class App {
|
||||
uint32_t lastOpticalWakeToggleMs_ = 0;
|
||||
bool opticalWakeActive_ = false;
|
||||
bool rxPinStateKnown_ = false, rxPinState_ = false;
|
||||
uint32_t boardTestUpdatedMs_ = 0;
|
||||
uint32_t boardDisplayUpdatedMs_ = 0;
|
||||
bool boardPwmLightOn_ = false;
|
||||
mutable uint32_t lastUsbHostSeenMs_ = 0;
|
||||
char serialLine_[96] = {};
|
||||
uint8_t serialLineLength_ = 0;
|
||||
|
||||
@@ -5,29 +5,43 @@
|
||||
// ------------------------- Hardware configuration -------------------------
|
||||
// Enabled for the hand-wired prototype. Comment out for the production PCB.
|
||||
// Both profiles map S3 signals by physical header position with 5V/GND aligned.
|
||||
#define MAKETKA
|
||||
//#define MAKETKA
|
||||
|
||||
// Additionally log raw GPIO_RX level changes while no test is running.
|
||||
//#define RX_PIN_CHANGE_TEST
|
||||
// Select exactly one populated receiver circuit. Use
|
||||
// BOARD_RX_INTERFACE_DIGITAL for MAKETKA and boards fitted with GPIO_RX.
|
||||
#define BOARD_RX_INTERFACE_ADC 1
|
||||
#define BOARD_RX_INTERFACE_DIGITAL 2
|
||||
#define BOARD_RX_INTERFACE BOARD_RX_INTERFACE_ADC
|
||||
|
||||
// Standalone PWM output check. While enabled, the normal application is not
|
||||
// started: GPIO_PWM continuously outputs the frequency and duty below.
|
||||
// Comment this define out after the hardware check.
|
||||
//#define PWM_OUTPUT_TEST
|
||||
constexpr uint32_t PWM_OUTPUT_TEST_FREQUENCY_HZ = 1000;
|
||||
constexpr uint32_t PWM_OUTPUT_TEST_SWEEP_PERIOD_MS = 2000;
|
||||
constexpr uint32_t PWM_OUTPUT_TEST_UPDATE_MS = 10;
|
||||
constexpr uint32_t PWM_OUTPUT_TEST_MIN_PULSE_NS = 1000;
|
||||
constexpr uint32_t PWM_OUTPUT_TEST_MAX_PULSE_NS = 10000;
|
||||
#if BOARD_RX_INTERFACE != BOARD_RX_INTERFACE_ADC && \
|
||||
BOARD_RX_INTERFACE != BOARD_RX_INTERFACE_DIGITAL
|
||||
#error "BOARD_RX_INTERFACE must select ADC or DIGITAL"
|
||||
#endif
|
||||
|
||||
#if defined(MAKETKA) && BOARD_RX_INTERFACE == BOARD_RX_INTERFACE_ADC
|
||||
#error "MAKETKA requires BOARD_RX_INTERFACE_DIGITAL"
|
||||
#endif
|
||||
|
||||
constexpr bool BOARD_RX_USES_ADC =
|
||||
BOARD_RX_INTERFACE == BOARD_RX_INTERFACE_ADC;
|
||||
|
||||
// Board-test parameters. These checks are selected at runtime from the UI;
|
||||
// no special diagnostic firmware build is required.
|
||||
constexpr uint32_t BOARD_PWM_TEST_FREQUENCY_HZ = 1;
|
||||
constexpr uint32_t BOARD_PWM_TEST_HALF_PERIOD_MS = 500;
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
constexpr bool TARGET_IS_C3 = true;
|
||||
constexpr uint8_t GPIO_PWM = 3;
|
||||
constexpr uint8_t GPIO_RX = 4;
|
||||
#ifdef MAKETKA
|
||||
constexpr bool BOARD_ADC_AVAILABLE = false;
|
||||
constexpr uint8_t GPIO_BUTTON_MODE = 0;
|
||||
constexpr uint8_t GPIO_BUTTON_START = 1;
|
||||
constexpr uint8_t GPIO_VBAT = UINT8_MAX;
|
||||
constexpr uint8_t GPIO_ANALOG_RX = UINT8_MAX;
|
||||
#else
|
||||
constexpr bool BOARD_ADC_AVAILABLE = true;
|
||||
constexpr uint8_t GPIO_BUTTON_MODE = 20;
|
||||
constexpr uint8_t GPIO_BUTTON_START = 10;
|
||||
constexpr uint8_t GPIO_VBAT = 2;
|
||||
@@ -38,6 +52,7 @@ constexpr uint8_t GPIO_SCL = 7;
|
||||
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||
constexpr bool TARGET_IS_C3 = false;
|
||||
#ifdef MAKETKA
|
||||
constexpr bool BOARD_ADC_AVAILABLE = false;
|
||||
// Same physical header contacts as the C3 MAKETKA profile when 5V/GND align.
|
||||
constexpr uint8_t GPIO_PWM = 12;
|
||||
constexpr uint8_t GPIO_RX = 13;
|
||||
@@ -45,7 +60,10 @@ constexpr uint8_t GPIO_BUTTON_MODE = 9;
|
||||
constexpr uint8_t GPIO_BUTTON_START = 10;
|
||||
constexpr uint8_t GPIO_SDA = 44;
|
||||
constexpr uint8_t GPIO_SCL = 1;
|
||||
constexpr uint8_t GPIO_VBAT = UINT8_MAX;
|
||||
constexpr uint8_t GPIO_ANALOG_RX = UINT8_MAX;
|
||||
#else
|
||||
constexpr bool BOARD_ADC_AVAILABLE = true;
|
||||
// The S3 SuperMini is fitted so its 5V and GND pins occupy the same PCB
|
||||
// contacts as on the C3 SuperMini. Signals therefore follow header position.
|
||||
constexpr uint8_t GPIO_PWM = 12;
|
||||
@@ -69,6 +87,8 @@ constexpr uint32_t SERIAL_BAUD = 115200;
|
||||
// Native USB CDC may keep a stale "connected" state after light sleep. Keep
|
||||
// logging non-blocking so a missing host can never delay button polling.
|
||||
constexpr uint32_t SERIAL_TX_TIMEOUT_MS = 2;
|
||||
constexpr uint32_t BOARD_ADC_PRINT_INTERVAL_MS = 100;
|
||||
constexpr uint32_t BOARD_TEST_DISPLAY_INTERVAL_MS = 250;
|
||||
constexpr bool SERIAL_ACTION_LOG = true;
|
||||
constexpr bool SERIAL_LOG_TIMESTAMPS = true;
|
||||
constexpr bool SERIAL_MINIMAL_LOG = true;
|
||||
|
||||
@@ -21,6 +21,14 @@ constexpr const char *TEST_NAMES[] = {
|
||||
"ОПТИКА", "ДРАЙВЕР"
|
||||
};
|
||||
|
||||
constexpr const char *TEST_GROUP_NAMES[] = {
|
||||
"ОПТИКА", "ПЛАТА"
|
||||
};
|
||||
|
||||
constexpr const char *BOARD_TEST_NAMES[] = {
|
||||
"АЦП", "ШИМ ВЫХОД", "RX ВХОД"
|
||||
};
|
||||
|
||||
constexpr const char *FAIL_NAMES[] = {
|
||||
"НЕТ ОШИБКИ",
|
||||
"НЕТ СИГНАЛА",
|
||||
@@ -44,6 +52,7 @@ constexpr const char *MODE_PREFIX = "РЕЖИМ: ";
|
||||
constexpr const char *START_RUN = "ГОТОВ К ЗАПУСКУ";
|
||||
|
||||
constexpr const char *MENU_FREQUENCY = "ЧАСТОТА ШИМ:";
|
||||
constexpr const char *MENU_TEST_GROUP = "ГРУППА ТЕСТОВ:";
|
||||
constexpr const char *MENU_MAX_PULSE = "МАКС. ИМПУЛЬС:";
|
||||
constexpr const char *MENU_MIN_PULSE = "МИН. ИМПУЛЬС:";
|
||||
constexpr const char *MENU_ACCURACY = "ТОЧНОСТЬ:";
|
||||
@@ -53,6 +62,9 @@ constexpr const char *LIGHT_CODE_FORMAT = "TX:%c, RX:%c";
|
||||
constexpr const char *LIGHT_AUTO = "АВТО";
|
||||
constexpr const char *LIGHT_AUTO_FORMAT = "TX/RX: АВТО";
|
||||
constexpr const char *MENU_TOTAL_TIME = "ОБЩЕЕ ВРЕМЯ:";
|
||||
constexpr const char *BOARD_READY = "START: ЗАПУСК";
|
||||
constexpr const char *BOARD_STOP = "УДЕРЖ START: СТОП";
|
||||
constexpr const char *ADC_UNAVAILABLE = "АЦП НЕТ НА МАКЕТКЕ";
|
||||
constexpr const char *FREQUENCY_UNIT = " Гц";
|
||||
|
||||
constexpr const char *SLAVE_READY = "СЛЕЙВ ГОТОВ";
|
||||
@@ -87,6 +99,14 @@ constexpr const char *TEST_NAMES[] = {
|
||||
"OPTICAL", "DRIVER"
|
||||
};
|
||||
|
||||
constexpr const char *TEST_GROUP_NAMES[] = {
|
||||
"OPTICS", "BOARD"
|
||||
};
|
||||
|
||||
constexpr const char *BOARD_TEST_NAMES[] = {
|
||||
"ADC", "PWM OUTPUT", "RX INPUT"
|
||||
};
|
||||
|
||||
constexpr const char *FAIL_NAMES[] = {
|
||||
"NONE",
|
||||
"NO SIGNAL",
|
||||
@@ -110,6 +130,7 @@ constexpr const char *MODE_PREFIX = "MODE: ";
|
||||
constexpr const char *START_RUN = "READY TO START";
|
||||
|
||||
constexpr const char *MENU_FREQUENCY = "PWM FREQUENCY:";
|
||||
constexpr const char *MENU_TEST_GROUP = "TEST GROUP:";
|
||||
constexpr const char *MENU_MAX_PULSE = "MAX PULSE:";
|
||||
constexpr const char *MENU_MIN_PULSE = "MIN PULSE:";
|
||||
constexpr const char *MENU_ACCURACY = "ACCURACY:";
|
||||
@@ -119,6 +140,9 @@ constexpr const char *LIGHT_CODE_FORMAT = "TX:%c, RX:%c";
|
||||
constexpr const char *LIGHT_AUTO = "AUTO";
|
||||
constexpr const char *LIGHT_AUTO_FORMAT = "TX/RX: AUTO";
|
||||
constexpr const char *MENU_TOTAL_TIME = "TOTAL TIME:";
|
||||
constexpr const char *BOARD_READY = "START TO RUN";
|
||||
constexpr const char *BOARD_STOP = "HOLD START TO STOP";
|
||||
constexpr const char *ADC_UNAVAILABLE = "ADC ABSENT ON PROTOTYPE";
|
||||
constexpr const char *FREQUENCY_UNIT = " Hz";
|
||||
|
||||
constexpr const char *SLAVE_READY = "SLAVE READY";
|
||||
|
||||
@@ -9,12 +9,24 @@ const char *roleName(Role r) {
|
||||
return i < 3 ? names[i] : "?";
|
||||
}
|
||||
|
||||
const char *testGroupName(TestGroup group) {
|
||||
static const char *names[] = {"OPTICS", "BOARD"};
|
||||
const uint8_t i = static_cast<uint8_t>(group);
|
||||
return i < 2 ? names[i] : "?";
|
||||
}
|
||||
|
||||
const char *testKindName(TestKind kind) {
|
||||
static const char *names[] = {"OPTICAL", "DRIVER"};
|
||||
const uint8_t i = static_cast<uint8_t>(kind);
|
||||
return i < 2 ? names[i] : "?";
|
||||
}
|
||||
|
||||
const char *boardTestName(BoardTest test) {
|
||||
static const char *names[] = {"ADC", "PWM OUTPUT", "RX INPUT"};
|
||||
const uint8_t i = static_cast<uint8_t>(test);
|
||||
return i < 3 ? names[i] : "?";
|
||||
}
|
||||
|
||||
const char *lightCodeName(LightCode code) {
|
||||
static const char *names[] = {"HH", "HL", "LH", "LL"};
|
||||
const uint8_t i = static_cast<uint8_t>(code);
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
#include <stddef.h>
|
||||
|
||||
enum class Role : uint8_t { SOLO, MASTER, SLAVE };
|
||||
enum class TestGroup : uint8_t { OPTICS, BOARD };
|
||||
enum class TestKind : uint8_t { OPTICAL, DRIVER };
|
||||
enum class BoardTest : uint8_t { ADC, PWM_OUTPUT, RX_INPUT };
|
||||
enum class LightCode : uint8_t { HH, HL, LH, LL };
|
||||
enum class FailReason : uint8_t {
|
||||
NONE, NO_SIGNAL, PERIOD_OUT, DUTY_OUT, EXTRA_EDGE, GLITCH, LOST_EDGE,
|
||||
@@ -13,7 +15,9 @@ enum class FailReason : uint8_t {
|
||||
};
|
||||
|
||||
const char *roleName(Role role);
|
||||
const char *testGroupName(TestGroup group);
|
||||
const char *testKindName(TestKind kind);
|
||||
const char *boardTestName(BoardTest test);
|
||||
const char *lightCodeName(LightCode code);
|
||||
const char *failName(FailReason reason);
|
||||
|
||||
@@ -27,7 +31,8 @@ struct Settings {
|
||||
uint8_t minPulseIndex;
|
||||
uint8_t accuracyIndex;
|
||||
uint8_t timeIndex;
|
||||
uint16_t reserved;
|
||||
uint8_t testGroup;
|
||||
uint8_t boardTest;
|
||||
uint32_t checksum;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,57 +1,6 @@
|
||||
#include "App.h"
|
||||
#include "Config.h"
|
||||
#include <math.h>
|
||||
|
||||
#ifdef PWM_OUTPUT_TEST
|
||||
PwmGenerator pwmOutputTest;
|
||||
uint32_t pwmOutputTestPulseNs = PWM_OUTPUT_TEST_MIN_PULSE_NS;
|
||||
uint32_t pwmOutputTestUpdatedMs = 0;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
delay(200);
|
||||
Serial.printf("\nPWM OUTPUT TEST: GPIO=%u requested=%luHz pulse=%lu..%luns sine=%lums light-off=%s\n",
|
||||
GPIO_PWM, PWM_OUTPUT_TEST_FREQUENCY_HZ,
|
||||
PWM_OUTPUT_TEST_MIN_PULSE_NS, PWM_OUTPUT_TEST_MAX_PULSE_NS,
|
||||
PWM_OUTPUT_TEST_SWEEP_PERIOD_MS,
|
||||
TX_LIGHT_OFF_GPIO_LEVEL == HIGH ? "HIGH" : "LOW");
|
||||
|
||||
pwmOutputTest.begin();
|
||||
ActualPwm actual = {};
|
||||
if (pwmOutputTest.start(PWM_OUTPUT_TEST_FREQUENCY_HZ,
|
||||
pwmOutputTestPulseNs, actual)) {
|
||||
Serial.printf("PWM OUTPUT TEST STARTED: actual=%luHz pulse=%luns bits=%u\n",
|
||||
actual.actualHz, actual.actualPulseNs, actual.bits);
|
||||
} else {
|
||||
Serial.println("PWM OUTPUT TEST FAILED");
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
const uint32_t now = millis();
|
||||
if (now - pwmOutputTestUpdatedMs < PWM_OUTPUT_TEST_UPDATE_MS) return;
|
||||
pwmOutputTestUpdatedMs = now;
|
||||
|
||||
constexpr float PWM_TWO_PI = 6.28318530718f;
|
||||
const float phase = PWM_TWO_PI * (now % PWM_OUTPUT_TEST_SWEEP_PERIOD_MS) /
|
||||
PWM_OUTPUT_TEST_SWEEP_PERIOD_MS;
|
||||
const float center = (PWM_OUTPUT_TEST_MIN_PULSE_NS + PWM_OUTPUT_TEST_MAX_PULSE_NS) * 0.5f;
|
||||
const float amplitude = (PWM_OUTPUT_TEST_MAX_PULSE_NS - PWM_OUTPUT_TEST_MIN_PULSE_NS) * 0.5f;
|
||||
const uint32_t pulseNs = static_cast<uint32_t>(center + amplitude * sinf(phase) + 0.5f);
|
||||
if (pulseNs == pwmOutputTestPulseNs) return;
|
||||
|
||||
ActualPwm actual = {};
|
||||
if (pwmOutputTest.start(PWM_OUTPUT_TEST_FREQUENCY_HZ, pulseNs, actual)) {
|
||||
pwmOutputTestPulseNs = pulseNs;
|
||||
} else {
|
||||
Serial.printf("PWM OUTPUT TEST UPDATE FAILED: pulse=%luns\n", pulseNs);
|
||||
delay(100);
|
||||
}
|
||||
}
|
||||
#else
|
||||
App app;
|
||||
|
||||
void setup() { app.begin(); }
|
||||
void loop() { app.update(); }
|
||||
#endif
|
||||
|
||||
|
||||
@@ -3,18 +3,23 @@
|
||||
#include "Log.h"
|
||||
#include <Preferences.h>
|
||||
|
||||
// testGroup/boardTest reuse the former zeroed reserved bytes, so version 11
|
||||
// settings remain binary-compatible and keep the user's optical parameters.
|
||||
namespace { constexpr uint16_t SETTINGS_VERSION = 11; constexpr char NAMESPACE[] = "opt-test"; }
|
||||
|
||||
void SettingsStore::defaults(Settings &s) const {
|
||||
// 2 kHz, 200 us .. 2 us, 5%, 1 s.
|
||||
s = {SETTINGS_VERSION, static_cast<uint8_t>(Role::SOLO),
|
||||
static_cast<uint8_t>(TestKind::OPTICAL), static_cast<uint8_t>(LightCode::HH),
|
||||
2, 6, 3, 2, 3, 0, 0};
|
||||
2, 6, 3, 2, 3, static_cast<uint8_t>(TestGroup::OPTICS),
|
||||
static_cast<uint8_t>(BoardTest::ADC), 0};
|
||||
s.checksum = settingsChecksum(s);
|
||||
}
|
||||
|
||||
bool SettingsStore::valid(const Settings &s) const {
|
||||
return s.version == SETTINGS_VERSION && s.role <= static_cast<uint8_t>(Role::SLAVE) &&
|
||||
s.testGroup <= static_cast<uint8_t>(TestGroup::BOARD) &&
|
||||
s.boardTest <= static_cast<uint8_t>(BoardTest::RX_INPUT) &&
|
||||
s.testKind <= static_cast<uint8_t>(TestKind::DRIVER) &&
|
||||
s.lightCode <= static_cast<uint8_t>(LightCode::LL) &&
|
||||
(s.testKind != static_cast<uint8_t>(TestKind::DRIVER) ||
|
||||
|
||||
42
README.md
42
README.md
@@ -160,13 +160,47 @@ MASTER <~~~~ ESP-NOW Wi-Fi channel 6 ~~~~> SLAVE
|
||||
|
||||
## Управление
|
||||
|
||||
В ожидании на S3:
|
||||
В ожидании в группе `ОПТИКА` на S3:
|
||||
|
||||
- MODE short: `SOLO ОПТИКА → SOLO ДРАЙВЕР → MASTER ОПТИКА → SLAVE ОПТИКА`;
|
||||
- MODE long: открыть настройки;
|
||||
- START short: начать тест;
|
||||
- START long во время теста: ABORT, PWM немедленно выключается.
|
||||
|
||||
В ожидании в группе `ПЛАТА`:
|
||||
|
||||
- MODE short для аналоговой платы: `АЦП ↔ ШИМ ВЫХОД`;
|
||||
- MODE short для цифровой платы: `RX ВХОД ↔ ШИМ ВЫХОД`;
|
||||
- MODE long: открыть настройки;
|
||||
- START short: запустить выбранный тест платы;
|
||||
- START long: остановить тест платы и вернуться к выбору.
|
||||
|
||||
При запуске `АЦП` или `RX ВХОД` оптический передатчик постоянно включается
|
||||
уровнем `TX_LIGHT_ON_GPIO_LEVEL`. При остановке теста свет выключается. В
|
||||
`ШИМ ВЫХОД` этот же вывод переключается с частотой 1 Гц и заполнением 50%:
|
||||
500 мс свет включён, 500 мс выключен, поэтому работу выхода видно глазом.
|
||||
|
||||
`АЦП` каждые 100 мс выводит в Serial текущие сырые значения входов
|
||||
`GPIO_ANALOG_RX` и `GPIO_VBAT`, а также периодически обновляет OLED. Этот тест
|
||||
доступен в профиле производственной PCB; в профиле `MAKETKA` прошивка явно
|
||||
сообщает об отсутствии этих входов. `ШИМ ВЫХОД` заменяет прежний
|
||||
`PWM_OUTPUT_TEST`, а `RX ВХОД` — прежний `RX_PIN_CHANGE_TEST`; отдельная сборка
|
||||
диагностической прошивки больше не нужна.
|
||||
|
||||
Тип распаянного приёмника выбирается в `Config.h` одним взаимоисключающим
|
||||
значением:
|
||||
|
||||
```cpp
|
||||
#define BOARD_RX_INTERFACE BOARD_RX_INTERFACE_ADC
|
||||
// или
|
||||
#define BOARD_RX_INTERFACE BOARD_RX_INTERFACE_DIGITAL
|
||||
```
|
||||
|
||||
По умолчанию выбрана плата с АЦП. Одновременно аналоговый и цифровой тесты
|
||||
приёмника в меню не появляются. Для `MAKETKA` следует выбрать
|
||||
`BOARD_RX_INTERFACE_DIGITAL`, поскольку аналоговые выводы в этом профиле
|
||||
отсутствуют.
|
||||
|
||||
В настройках:
|
||||
|
||||
- MODE short: следующий параметр;
|
||||
@@ -184,7 +218,11 @@ START и MODE будят устройство. Первое, пробуждаю
|
||||
|
||||
## Настройки теста
|
||||
|
||||
Меню содержит шесть общих параметров:
|
||||
Первый пункт меню выбирает группу `ОПТИКА` или `ПЛАТА`. Для группы `ПЛАТА`
|
||||
это единственная настройка: конкретный аппаратный тест выбирается коротким
|
||||
нажатием MODE в экране ожидания.
|
||||
|
||||
Для группы `ОПТИКА` меню дополнительно содержит шесть параметров:
|
||||
|
||||
1. частота ШИМ из `PWM_FREQUENCY_OPTIONS_HZ`;
|
||||
2. максимальная длительность импульса из отдельного `MAX_PULSE_OPTIONS_NS`;
|
||||
|
||||
Reference in New Issue
Block a user