Compare commits

...

28 Commits

Author SHA1 Message Date
Razvalyaev
dda98860d1 добавлен субмодуль с зарядкой и мультисборка с ней 2026-08-26 12:59:57 +03:00
Razvalyaev
30264698b8 подправлен разъем для зарядника, но ещше не окончаительно 2026-08-25 18:31:21 +03:00
Razvalyaev
e1fcd0b7fe гербер для завода 2026-08-24 18:50:54 +03:00
Razvalyaev
a2de62f61d Возвращены отверстия для заказа платы, а не чпу
добавлен дюпонт разъем для самодельного аккума
2026-08-23 22:16:51 +03:00
Razvalyaev
bff0294a22 в pcb добавлен потенциометр для настройки мощности оптики 2026-08-23 21:34:40 +03:00
Razvalyaev
8699f8e4eb Сделана настройка мощности передающей оптики 2026-08-23 18:32:47 +03:00
Razvalyaev
3907cbcf77 Добавлено тестировние платы в меню:
- Тест Tx Оптики
- Тест Rx Оптики
- Тест АЦП
2026-08-22 20:03:12 +03:00
Razvalyaev
49332aa028 доработки по интерфейсу 2026-08-14 15:41:09 +03:00
Razvalyaev
035792aedd Доработки по тесту драйвера 2026-08-14 14:42:46 +03:00
Razvalyaev
037bb37e62 добавлена бета проверка драйверов 2026-08-14 12:01:07 +03:00
Razvalyaev
a17e8962b4 Глобальная переделка. тест сделан по длине импульса и заданной частоте шим, а не меандру 2026-08-12 18:10:05 +03:00
Razvalyaev
1db89fca79 доработки всякие 2026-08-12 12:11:55 +03:00
Razvalyaev
862781fa6a Коррекция уровня оптики, вкл по умолчанию, выкл в сне
плюс коррекции по отображению
2026-08-12 08:25:56 +03:00
Razvalyaev
f56595f767 добавлены дефайны для теста разводки оптики
запущено все на s3
скорректированны пины и шим на s3
2026-08-11 21:43:43 +03:00
Razvalyaev
9d474eb62b обновлены пины под pcb 2026-08-10 00:41:18 +03:00
Razvalyaev
7db164b979 настройки для чпу 2026-08-09 19:00:18 +03:00
Razvalyaev
eaddf277ea +gerber и чпу 2026-08-09 15:28:44 +03:00
Razvalyaev
706ae4de82 плата сделана и под аналог и под цифру 2026-08-09 13:57:26 +03:00
Razvalyaev
cec997d1d2 Перепиновка под будущий аналог
+скрипт для конвертации падов под CNC
2026-08-09 10:19:12 +03:00
Razvalyaev
61d3aeaba6 минор фиксы pcb 2026-08-08 20:02:14 +03:00
Razvalyaev
34581913ff Пины и ШИМ для S3 (не проверено) 2026-08-08 19:50:34 +03:00
Razvalyaev
be1a90afac Добавлена разведенная PCB плата 2026-08-08 19:49:35 +03:00
Razvalyaev
70fe3566b4 Сделан русский текст 2026-08-08 11:15:20 +03:00
Razvalyaev
c952c165f8 Добавлено энергосбережение для увеличения работы от аккума:
- через 60 сек экран выключается и есп уходит в сон
- слейв в ожидании мастера прослушивает не постоянно 20мс/100мс
2026-08-08 10:42:59 +03:00
Razvalyaev
21f8fe8e13 Улучшения
- улушчено отображение на OLED
- убраны настройки шага частоты и количества повторов
- сделан перебор только реализуемых частот
- увеличена точность, на 1МГц 1.25%, в остальных до 1%
- точное измерение TOTAL TIME
2026-08-08 10:18:50 +03:00
Razvalyaev
a8099bf2b8 запущен тест между есп с перемычкой 2026-08-07 16:04:30 +03:00
Razvalyaev
4b88ac6a42 Merge remote-tracking branch 'OptoTest/master'
# Conflicts:
#	OpticalChannelTester/App.cpp
2026-08-07 09:59:53 +03:00
Razvalyaev
f52f65686d работает с перемычкой на 1МГц 2% 2026-08-06 15:53:32 +03:00
87 changed files with 22439 additions and 549 deletions

7
.gitignore vendored
View File

@@ -1 +1,8 @@
/tests/ /tests/
__Previews/
History
Project Logs*/
Project Outputs*/
/.build/

4
.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "PCB_Charger"]
path = PCB_Charger
url = https://git.rd12.ru/Razvalyaev/Charger.git
branch = master

View File

@@ -1,15 +1,26 @@
#include "App.h" #include "App.h"
#include "Config.h" #include "Config.h"
#include "Config_Text.h"
#include "Log.h" #include "Log.h"
#include <WiFi.h> #include <WiFi.h>
#include <esp_mac.h> #include <esp_mac.h>
#include <esp_sleep.h>
#include <esp_system.h> #include <esp_system.h>
#include <esp32-hal-cpu.h>
#include <driver/gpio.h>
#include <Wire.h>
#include <math.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
namespace { namespace {
constexpr uint8_t MENU_OPTICAL_CALIBRATION_ITEM = 7;
const char *uiFailName(FailReason reason);
const char *appStateName(AppState state) { const char *appStateName(AppState state) {
static const char *names[] = {"IDLE", "MENU", "SOLO_MEASURE", "MASTER_DISCOVER", static const char *names[] = {"IDLE", "MENU", "BOARD_TEST", "SOLO_MEASURE", "SOLO_DRIVER", "MASTER_DISCOVER",
"MASTER_WAIT_READY", "MASTER_WAIT_RESULT", "SLAVE_READY", "SLAVE_WAIT_START", "MASTER_WAIT_READY", "MASTER_WAIT_RESULT", "MASTER_FINALIZE", "SLAVE_READY", "SLAVE_WAIT_START",
"SLAVE_MEASURE", "SLAVE_WAIT_ACK", "FINISHED"}; "SLAVE_MEASURE", "SLAVE_WAIT_ACK", "FINISHED"};
const uint8_t index = static_cast<uint8_t>(state); const uint8_t index = static_cast<uint8_t>(state);
return index < sizeof(names) / sizeof(names[0]) ? names[index] : "UNKNOWN"; return index < sizeof(names) / sizeof(names[0]) ? names[index] : "UNKNOWN";
@@ -20,12 +31,272 @@ const char *buttonEventName(ButtonEvent event) {
const uint8_t index = static_cast<uint8_t>(event); const uint8_t index = static_cast<uint8_t>(event);
return index < sizeof(names) / sizeof(names[0]) ? names[index] : "UNKNOWN"; return index < sizeof(names) / sizeof(names[0]) ? names[index] : "UNKNOWN";
} }
uint32_t pulseFromDuty(float hz, float dutyPct) {
return hz > 0.0f ? static_cast<uint32_t>(lroundf(dutyPct * 10000000.0f / hz)) : 0U;
} }
App::App() : startButton_(GPIO_BUTTON_START), modeButton_(GPIO_BUTTON_MODE), measurement_(receiver_) {} float dutyFromPulse(uint32_t hz, uint32_t pulseNs) {
return static_cast<float>(static_cast<double>(hz) * pulseNs / 10000000.0);
}
bool configuredTxPulseLightOn(const Settings &settings) {
return static_cast<TestKind>(settings.testKind) == TestKind::DRIVER
? txActiveLightOn(settings) : true;
}
const char *configuredLevelName(const Settings &settings) {
return static_cast<TestKind>(settings.testKind) == TestKind::DRIVER
? lightCodeName(static_cast<LightCode>(settings.lightCode)) : "AUTO";
}
void formatTarget(uint32_t hz, uint32_t pulseNs, char *out, size_t size) {
char frequency[16], pulse[12];
Display::formatPwmFrequency(hz, frequency, sizeof(frequency));
Display::formatPulse(pulseNs, pulse, sizeof(pulse));
snprintf(out, size, UiText::TEST_FORMAT, frequency, pulse);
}
void formatMeasured(float hz, uint32_t pulseNs, char *out, size_t size) {
char frequency[12], pulse[12];
Display::formatFrequency(hz, frequency, sizeof(frequency));
Display::formatPulse(pulseNs, pulse, sizeof(pulse), true);
snprintf(out, size, "F:%s, P:%s", frequency, pulse);
}
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, UiText::TEST_TARGET_FORMAT, target);
}
void formatFailure(FailReason reason, uint32_t hz, uint32_t pulseNs,
char *out, size_t size) {
(void)reason;
char target[32];
formatTarget(hz, pulseNs, target, sizeof(target));
snprintf(out, size, UiText::FAIL_TARGET_FORMAT, target);
}
void formatElapsedNs(uint64_t ns, char *out, size_t size) {
// Compact form keeps error timing 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) {
size_t count = 0;
while (text && *text) {
const uint8_t byte = static_cast<uint8_t>(*text++);
// Continuation bytes (10xxxxxx) belong to the preceding UTF-8 character.
if ((byte & 0xC0U) != 0x80U) ++count;
}
return count;
}
const char *uiRoleName(Role role) {
const uint8_t index = static_cast<uint8_t>(role);
return index < sizeof(UiText::ROLE_NAMES) / sizeof(UiText::ROLE_NAMES[0])
? UiText::ROLE_NAMES[index] : "?";
}
const char *uiFailName(FailReason reason) {
const uint8_t index = static_cast<uint8_t>(reason);
return index < sizeof(UiText::FAIL_NAMES) / sizeof(UiText::FAIL_NAMES[0])
? 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) {
constexpr size_t OLED_TEXT_COLUMNS = 21;
const size_t labelLength = utf8CharacterCount(label);
const size_t valueLength = utf8CharacterCount(value);
const size_t usedColumns = labelLength + valueLength;
const int padding = static_cast<int>(
usedColumns < OLED_TEXT_COLUMNS ? OLED_TEXT_COLUMNS - usedColumns : 0U);
snprintf(out, size, "%s%*s%s", label, padding, "", value);
}
uint32_t overallProgress(uint32_t stageIndex, uint8_t step,
uint8_t stepsPerStage = MEASUREMENT_PROGRESS_STEPS) {
if (step > stepsPerStage) step = stepsPerStage;
return stageIndex * stepsPerStage + step;
}
uint32_t overallProgressTotal(
uint32_t stageCount,
uint8_t stepsPerStage = MEASUREMENT_PROGRESS_STEPS) {
return stageCount * stepsPerStage;
}
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] : "?";
}
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)
--last;
return last;
}
uint8_t firstMaxPulseIndexAtLeast(uint32_t pulseNs, uint8_t last) {
for (uint8_t i = 0; i <= last; ++i)
if (MAX_PULSE_OPTIONS_NS[i] >= pulseNs) return i;
return last;
}
uint8_t lastMinPulseIndexAtMost(uint32_t pulseNs) {
for (size_t i = countOf(MIN_PULSE_OPTIONS_NS); i > 0; --i)
if (MIN_PULSE_OPTIONS_NS[i - 1U] <= pulseNs) return static_cast<uint8_t>(i - 1U);
return 0;
}
uint32_t plannedPulseCaptureHz(uint32_t frequencyHz, float dutyPct) {
if (!frequencyHz || dutyPct <= 0.0f || dutyPct >= 100.0f) return 0;
return MCPWM_CAPTURE_RESOLUTION_HZ;
}
uint32_t plannedCaptureHz(uint32_t frequencyHz, float dutyPct) {
// One 32-bit S3 MCPWM capture timer measures period and pulse at 80 MHz.
return plannedPulseCaptureHz(frequencyHz, dutyPct);
}
bool pulsePointHasResolution(uint32_t hz, uint32_t pulseNs, float accuracyPct) {
uint32_t actualHz = 0, actualPulseNs = 0;
uint8_t bits = 0;
if (TARGET_IS_C3) {
IntegerPwmConfig config = {};
if (!choosePwmConfig(hz, pulseNs, LEDC_SOURCE_CLOCK_HZ, LEDC_MAX_BITS, config)) return false;
actualHz = config.actualHz;
actualPulseNs = config.actualPulseNs;
bits = config.bits;
} else {
if (!hz || MCPWM_RESOLUTION_HZ % hz) return false;
const uint32_t periodTicks = MCPWM_RESOLUTION_HZ / hz;
if (periodTicks < 2U || periodTicks > MCPWM_MAX_PERIOD_TICKS) return false;
uint32_t activeTicks = static_cast<uint32_t>(
(static_cast<uint64_t>(pulseNs) * MCPWM_RESOLUTION_HZ + 500000000ULL) / 1000000000ULL);
if (!activeTicks || activeTicks >= periodTicks) return false;
actualHz = hz;
actualPulseNs = static_cast<uint32_t>(
(static_cast<uint64_t>(activeTicks) * 1000000000ULL + MCPWM_RESOLUTION_HZ / 2U) /
MCPWM_RESOLUTION_HZ);
bits = 1;
for (uint32_t ticks = periodTicks; ticks > 1U; ticks >>= 1U) ++bits;
}
if (!periodWithin(actualHz, hz, accuracyPct) ||
!periodWithin(actualPulseNs, pulseNs, accuracyPct)) return false;
const float dutyPct = dutyFromPulse(actualHz, actualPulseNs);
const uint32_t captureHz = plannedCaptureHz(actualHz, dutyPct);
const uint32_t pulseCaptureHz = plannedPulseCaptureHz(actualHz, dutyPct);
return captureHz && pulseCaptureHz && validateResolution(actualHz, dutyPct, accuracyPct,
captureHz, pulseCaptureHz, bits, MEASUREMENT_AVERAGING_PERIODS) == FailReason::NONE;
}
uint32_t minimumPulseForAccuracy(uint32_t frequencyHz, float accuracyPct) {
for (uint32_t pulseNs : TEST_PULSE_WIDTHS_NS)
if (pulsePointHasResolution(frequencyHz, pulseNs, accuracyPct)) return pulseNs;
return UINT32_MAX;
}
uint8_t firstMinPulseIndexAtLeast(uint32_t pulseNs, uint8_t last) {
for (uint8_t i = 0; i <= last; ++i)
if (MIN_PULSE_OPTIONS_NS[i] >= pulseNs) return i;
return last;
}
uint8_t cycleIndex(uint8_t value, uint8_t first, uint8_t last, int direction) {
if (first >= last) return first;
if (direction > 0) return value >= last ? first : static_cast<uint8_t>(value + 1U);
return value <= first ? last : static_cast<uint8_t>(value - 1U);
}
uint8_t nextMenuItem(uint8_t current, TestGroup group) {
if (group == TestGroup::BOARD)
return current == 0U ? MENU_OPTICAL_CALIBRATION_ITEM : 0U;
return current >= MENU_OPTICAL_CALIBRATION_ITEM ? 0U
: static_cast<uint8_t>(current + 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;
}
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() { void App::begin() {
Serial.begin(SERIAL_BAUD); Serial.begin(SERIAL_BAUD);
#if ARDUINO_USB_CDC_ON_BOOT
Serial.setTxTimeoutMs(SERIAL_TX_TIMEOUT_MS);
#endif
Log::printf("BOOT", "firmware start, Serial=%lu baud", SERIAL_BAUD); Log::printf("BOOT", "firmware start, Serial=%lu baud", SERIAL_BAUD);
startButton_.begin(); modeButton_.begin(); pwm_.begin(); startButton_.begin(); modeButton_.begin(); pwm_.begin();
bootCheckStartedMs_ = millis(); bootCheckStartedMs_ = millis();
@@ -42,17 +313,24 @@ void App::finishInitialization(bool factoryReset) {
} else if (!store_.load(settings_)) { } else if (!store_.load(settings_)) {
store_.save(settings_); Log::event("BOOT", "NVS invalid/missing: defaults loaded"); store_.save(settings_); Log::event("BOOT", "NVS invalid/missing: defaults loaded");
} }
sanitizeRange();
params_ = store_.params(settings_); params_ = store_.params(settings_);
pwm_.configureActiveLight(configuredTxPulseLightOn(settings_));
if (!display_.begin()) Log::event("BOOT", "OLED unavailable; Serial UI remains fully operational"); if (!display_.begin()) Log::event("BOOT", "OLED unavailable; Serial UI remains fully operational");
initialized_ = true; initialized_ = true;
if (!receiver_.begin()) { Log::event("BOOT", "FATAL: capture peripheral init failed"); finish(false, FailReason::UNSUPPORTED); return; } if (!receiver_.begin()) { Log::event("BOOT", "FATAL: capture peripheral init failed"); finish(false, FailReason::UNSUPPORTED); return; }
Log::printf("BOOT", "capture initialized: %s", receiver_.highRateBackend() ? "RMT DMA" : "RMT ping-pong"); Log::printf("BOOT", "capture initialized: %s", receiver_.highRateBackend() ? "MCPWM 80MHz" : "GPIO cycle counter");
lastUserActivityMs_ = millis();
setActivePerformance(false);
printConfiguration(); 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 showIdle();
} }
void App::update() { void App::update() {
serviceSerialConsole();
serviceIdlePowerSave();
const uint32_t now = millis(); const uint32_t now = millis();
const ButtonEvent startEvent = startButton_.update(now); const ButtonEvent startEvent = startButton_.update(now);
const ButtonEvent modeEvent = modeButton_.update(now); const ButtonEvent modeEvent = modeButton_.update(now);
@@ -60,11 +338,24 @@ void App::update() {
Log::printf("INPUT", "START %s state=%s", buttonEventName(startEvent), appStateName(state_)); Log::printf("INPUT", "START %s state=%s", buttonEventName(startEvent), appStateName(state_));
if (modeEvent != ButtonEvent::NONE) if (modeEvent != ButtonEvent::NONE)
Log::printf("INPUT", "MODE %s state=%s", buttonEventName(modeEvent), appStateName(state_)); Log::printf("INPUT", "MODE %s state=%s", buttonEventName(modeEvent), appStateName(state_));
if (startEvent != ButtonEvent::NONE || modeEvent != ButtonEvent::NONE) {
lastUserActivityMs_ = now;
leaveIdlePowerSave();
}
if (!initialized_) { if (!initialized_) {
if (!startButton_.pressed() || !modeButton_.pressed()) finishInitialization(false); if (!startButton_.pressed() || !modeButton_.pressed()) finishInitialization(false);
else if (now - bootCheckStartedMs_ >= FACTORY_RESET_HOLD_MS) finishInitialization(true); else if (now - bootCheckStartedMs_ >= FACTORY_RESET_HOLD_MS) finishInitialization(true);
return; return;
} }
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 && if (state_ != AppState::IDLE && state_ != AppState::MENU && state_ != AppState::FINISHED &&
startEvent == ButtonEvent::LONG) { abortTest(); return; } startEvent == ButtonEvent::LONG) { abortTest(); return; }
if (state_ != AppState::IDLE && state_ != AppState::MENU && state_ != AppState::FINISHED && if (state_ != AppState::IDLE && state_ != AppState::MENU && state_ != AppState::FINISHED &&
@@ -72,22 +363,31 @@ void App::update() {
if (state_ == AppState::IDLE || state_ == AppState::FINISHED) { if (state_ == AppState::IDLE || state_ == AppState::FINISHED) {
if (modeEvent == ButtonEvent::SHORT) { 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_); 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(); else showIdle();
Log::printf("ACTION", "role changed to %s, NVS=%s", roleName(static_cast<Role>(settings_.role)), 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) { } else if (modeEvent == ButtonEvent::LONG) {
state_ = AppState::MENU; menuItem_ = 0; Log::event("ACTION", "settings menu entered"); showMenu(); state_ = AppState::MENU; menuItem_ = 0; Log::event("ACTION", "settings menu entered"); showMenu();
} else if (startEvent == ButtonEvent::SHORT) { Log::event("ACTION", "test start requested"); startTest(); } } else if (startEvent == ButtonEvent::SHORT) { Log::event("ACTION", "test start requested"); startTest(); }
else if (state_ == AppState::FINISHED && static_cast<Role>(settings_.role) == Role::SLAVE && else if (state_ == AppState::FINISHED && static_cast<Role>(settings_.role) == Role::SLAVE &&
now >= slaveRearmAtMs_) armSlave(); now >= slaveRearmAtMs_) armSlave(pendingReason_ != FailReason::NONE);
return; return;
} }
if (state_ == AppState::SLAVE_READY && modeEvent != ButtonEvent::NONE) { if (state_ == AppState::SLAVE_READY && modeEvent != ButtonEvent::NONE) {
radio_.end(); havePeer_ = false; radio_.end(); havePeer_ = false;
if (modeEvent == ButtonEvent::SHORT) { 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(); params_ = store_.params(settings_); state_ = AppState::IDLE; showIdle();
Log::printf("ACTION", "role changed to SOLO, NVS=%s", saved ? "OK" : "FAILED"); Log::printf("ACTION", "role changed to SOLO, NVS=%s", saved ? "OK" : "FAILED");
} else if (modeEvent == ButtonEvent::LONG) { } else if (modeEvent == ButtonEvent::LONG) {
@@ -97,30 +397,83 @@ void App::update() {
} }
if (state_ == AppState::MENU) { if (state_ == AppState::MENU) {
if (modeEvent == ButtonEvent::SHORT) { if (modeEvent == ButtonEvent::SHORT) {
menuItem_ = (menuItem_ + 1U) % 7U; Log::printf("ACTION", "menu item selected index=%u", menuItem_); showMenu(); leaveOpticalCalibration();
menuItem_ = nextMenuItem(menuItem_,
static_cast<TestGroup>(settings_.testGroup));
Log::printf("ACTION", "menu item selected index=%u", menuItem_);
showMenu();
} }
else if (modeEvent == ButtonEvent::LONG) { else if (modeEvent == ButtonEvent::LONG) {
leaveOpticalCalibration();
sanitizeRange(); const bool saved = store_.save(settings_); params_ = store_.params(settings_); sanitizeRange(); const bool saved = store_.save(settings_); params_ = store_.params(settings_);
Log::printf("ACTION", "settings menu saved and closed, NVS=%s", saved ? "OK" : "FAILED"); Log::printf("ACTION", "settings menu saved and closed, NVS=%s", saved ? "OK" : "FAILED");
state_ = AppState::IDLE; printConfiguration(); 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 showIdle();
} else if (startEvent == ButtonEvent::SHORT) changeMenu(+1); } else if (menuItem_ != MENU_OPTICAL_CALIBRATION_ITEM &&
else if (startEvent == ButtonEvent::LONG || startEvent == ButtonEvent::REPEAT) changeMenu(-1); startEvent == ButtonEvent::SHORT) changeMenu(+1);
else if (menuItem_ != MENU_OPTICAL_CALIBRATION_ITEM &&
(startEvent == ButtonEvent::LONG || startEvent == ButtonEvent::REPEAT)) changeMenu(-1);
updateOpticalCalibration(now);
return; return;
} }
if (state_ == AppState::SOLO_MEASURE) { if (state_ == AppState::SOLO_MEASURE) {
const MeasureState ms = measurement_.update(); if (static_cast<int32_t>(now - localMeasurementDeadlineMs_) >= 0) {
if (ms == MeasureState::FAIL) { printStageStats(measurement_.stats(), requestedHz_); finish(false, measurement_.reason()); } Log::event("MEASURE", "local stage watchdog expired");
else if (ms == MeasureState::PASS) { measurement_.forceFail(FailReason::LOST_EDGE);
printStageStats(measurement_.stats(), requestedHz_);
if (measurement_.reason() == FailReason::DATA_LOST) {
sweepHadDataLoss_ = true; if (!firstDataLossHz_) firstDataLossHz_ = requestedHz_;
} }
const MeasureState ms = measurement_.update();
if (ms == MeasureState::FAIL) {
pwm_.stop();
printStageStats(measurement_.stats(), actual_.actualHz);
showStageResult(measurement_.stats());
finish(false, measurement_.reason(), true);
}
else if (ms == MeasureState::PASS) {
pwm_.stop();
printStageStats(measurement_.stats(), actual_.actualHz);
showStageResult(measurement_.stats());
stagePassed();
} else if (measurement_.takeProgressUpdate()) {
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(); stagePassed();
} }
} else if (state_ == AppState::MASTER_DISCOVER || state_ == AppState::MASTER_WAIT_READY || } else if (state_ == AppState::MASTER_DISCOVER || state_ == AppState::MASTER_WAIT_READY ||
state_ == AppState::MASTER_WAIT_RESULT) { state_ == AppState::MASTER_WAIT_RESULT || state_ == AppState::MASTER_FINALIZE) {
handleRadio(); updateMaster(); handleRadio(); updateMaster();
} else { } else {
handleRadio(); updateSlave(); handleRadio(); updateSlave();
@@ -128,137 +481,665 @@ void App::update() {
} }
void App::showIdle() { void App::showIdle() {
char one[24]; snprintf(one, sizeof(one), "MODE: %s", roleName(static_cast<Role>(settings_.role))); setActivePerformance(false);
display_.show(one, "START=RUN"); setStandbyOpticalOutput();
lastUserActivityMs_ = millis();
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() {
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 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");
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 (DRIVER only)");
}
void App::printSerialStatus() {
if (!initialized_) {
Serial.println("STATUS initializing");
return;
}
params_ = store_.params(settings_);
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_),
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();
leaveOpticalCalibration();
state_ = AppState::IDLE;
sanitizeRange();
params_ = store_.params(settings_);
pwm_.configureActiveLight(configuredTxPulseLightOn(settings_));
const bool saved = store_.save(settings_);
Serial.printf("OK settings saved=%s\n", saved ? "yes" : "no");
if (static_cast<TestGroup>(settings_.testGroup) == TestGroup::OPTICS &&
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::BOARD_TEST) {
Serial.println("OK board test stopped");
stopBoardTest();
} else if (state_ == AppState::MENU) {
leaveOpticalCalibration();
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, "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; }
} 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 (static_cast<TestKind>(settings_.testKind) != TestKind::DRIVER) {
Serial.println("ERR level setting is available only in DRIVER test");
return;
}
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() {
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) {
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() { void App::sanitizeRange() {
settings_.startIndex %= countOf(START_FREQ_OPTIONS_HZ); settings_.endIndex %= countOf(END_FREQ_OPTIONS_HZ); if (settings_.testGroup > static_cast<uint8_t>(TestGroup::BOARD))
if (END_FREQ_OPTIONS_HZ[settings_.endIndex] <= START_FREQ_OPTIONS_HZ[settings_.startIndex]) { settings_.testGroup = static_cast<uint8_t>(TestGroup::OPTICS);
size_t i = 0; if (settings_.boardTest > static_cast<uint8_t>(BoardTest::RX_INPUT) ||
while (i < countOf(END_FREQ_OPTIONS_HZ) && END_FREQ_OPTIONS_HZ[i] <= START_FREQ_OPTIONS_HZ[settings_.startIndex]) ++i; !boardTestAvailable(static_cast<BoardTest>(settings_.boardTest)))
if (i == countOf(END_FREQ_OPTIONS_HZ)) { settings_.startIndex = 0; i = countOf(END_FREQ_OPTIONS_HZ) - 1; } settings_.boardTest = static_cast<uint8_t>(defaultBoardTest());
settings_.endIndex = i; 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);
settings_.accuracyIndex %= countOf(ACCURACY_OPTIONS_PCT);
settings_.timeIndex %= countOf(TEST_TIME_OPTIONS_MS);
const uint32_t hz = PWM_FREQUENCY_OPTIONS_HZ[settings_.frequencyIndex];
const uint8_t lastValid = lastValidMaxPulseIndex(hz);
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;
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;
} }
} }
void App::changeMenu(int d) { void App::changeMenu(int d) {
sanitizeRange();
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_ == 3) {
const uint8_t last = lastMinPulseIndexAtMost(
MAX_PULSE_OPTIONS_NS[settings_.maxPulseIndex]);
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; uint8_t *value = nullptr; size_t count = 0;
switch (menuItem_) { switch (menuItem_) {
case 0: value = &settings_.startIndex; count = countOf(START_FREQ_OPTIONS_HZ); break; case 1: value = &settings_.frequencyIndex; count = countOf(PWM_FREQUENCY_OPTIONS_HZ); break;
case 1: value = &settings_.endIndex; count = countOf(END_FREQ_OPTIONS_HZ); break; case 4: value = &settings_.accuracyIndex; count = countOf(ACCURACY_OPTIONS_PCT); break;
case 2: value = &settings_.stepIndex; count = countOf(STEP_OPTIONS_HZ); break; case 5: value = &settings_.timeIndex; count = countOf(TEST_TIME_OPTIONS_MS); break;
case 3: value = &settings_.accuracyIndex; count = countOf(ACCURACY_OPTIONS_PCT); break; case 6:
case 4: value = &settings_.timeIndex; count = countOf(TEST_TIME_OPTIONS_MS); break; if (static_cast<TestKind>(settings_.testKind) != TestKind::DRIVER) {
case 5: value = &settings_.repeatIndex; count = countOf(REPEAT_OPTIONS); break; showMenu();
default: value = &settings_.dutyIndex; count = countOf(DUTY_OPTIONS_PCT); break; return;
} }
*value = static_cast<uint8_t>((*value + count + d) % count); value = &settings_.lightCode; count = 4; break;
Log::printf("ACTION", "menu item=%u changed direction=%+d new-index=%u", menuItem_, d, *value); default: return;
sanitizeRange(); params_ = store_.params(settings_); showMenu(); }
*value = cycleIndex(*value, 0, static_cast<uint8_t>(count - 1U), d);
}
sanitizeRange(); params_ = store_.params(settings_);
pwm_.configureActiveLight(configuredTxPulseLightOn(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,
configuredLevelName(settings_));
showMenu();
} }
void App::showMenu() { void App::showMenu() {
char one[22], two[22], all[12]; char one[64], value[24], total[64], all[12];
const char *label = nullptr;
Display::formatDuration(actualNominalTotalUs(), all, sizeof(all)); Display::formatDuration(actualNominalTotalUs(), all, sizeof(all));
switch (menuItem_) { switch (menuItem_) {
case 0: snprintf(one, sizeof(one), "START FREQ"); Display::formatFrequency(params_.startHz, two, sizeof(two)); break; case 0:
case 1: snprintf(one, sizeof(one), "END FREQ"); Display::formatFrequency(params_.endHz, two, sizeof(two)); break; snprintf(value, sizeof(value), "%s",
case 2: snprintf(one, sizeof(one), "FREQ STEP"); Display::formatFrequency(params_.stepHz, two, sizeof(two)); break; uiTestGroupName(static_cast<TestGroup>(settings_.testGroup)));
case 3: snprintf(one, sizeof(one), "ACCURACY"); snprintf(two, sizeof(two), "+/-%g%%", params_.accuracyPct); break; label = UiText::MENU_TEST_GROUP;
case 4: snprintf(one, sizeof(one), "TEST TIME"); snprintf(two, sizeof(two), "%.1fs", params_.testTimeMs / 1000.0f); break; break;
case 5: snprintf(one, sizeof(one), "REPEATS"); snprintf(two, sizeof(two), "%ux", params_.repeats); break; case 1:
default: snprintf(one, sizeof(one), "PWM DUTY"); snprintf(two, sizeof(two), "%u%%", params_.dutyPct); break; Display::formatPwmFrequency(params_.frequencyHz, value, sizeof(value));
label = UiText::MENU_FREQUENCY;
break;
case 2:
Display::formatPulse(params_.maxPulseNs, value, sizeof(value));
label = UiText::MENU_MAX_PULSE;
break;
case 3:
Display::formatPulse(params_.minPulseNs, value, sizeof(value));
label = UiText::MENU_MIN_PULSE;
break;
case 4:
snprintf(value, sizeof(value), "+/-%g%%", params_.accuracyPct);
label = UiText::MENU_ACCURACY;
break;
case 5:
snprintf(value, sizeof(value), "%.1fs", params_.testTimeMs / 1000.0f);
label = UiText::MENU_TEST_TIME;
break;
case 6: {
if (static_cast<TestKind>(settings_.testKind) != TestKind::DRIVER) {
snprintf(value, sizeof(value), "%s", UiText::LIGHT_AUTO);
label = UiText::MENU_LIGHT_CODE;
formatMenuLine(label, value, one, sizeof(one));
display_.show(one, UiText::LIGHT_AUTO_FORMAT);
return;
} }
const size_t used = strlen(two); snprintf(two + used, sizeof(two) - used, " ALL %s", all); display_.show(one, two); 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;
}
case MENU_OPTICAL_CALIBRATION_ITEM:
enterOpticalCalibration(millis());
return;
default: return;
}
formatMenuLine(label, value, one, sizeof(one));
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);
}
void App::enterOpticalCalibration(uint32_t now) {
if (opticalCalibrationActive_) return;
optical_current_begin();
pwm_.lightOn();
opticalCalibrationActive_ = true;
opticalCalibrationStartedMs_ = now;
opticalCalibrationUpdatedMs_ = 0;
display_.show("Idiode: --.- mA", "Vcc: --.-- V");
Log::printf("CALIB", "optical LED ON; sense GPIO=%u, VCC GPIO=%u, settling %lums",
GPIO_OPTICAL_CURRENT, GPIO_OPTICAL_VCC, OPTICAL_CURRENT_SETTLE_MS);
}
void App::updateOpticalCalibration(uint32_t now, bool force) {
if (!opticalCalibrationActive_ ||
now - opticalCalibrationStartedMs_ < OPTICAL_CURRENT_SETTLE_MS) return;
(void)force;
OpticalCurrentMeasurement reading = {};
if (!optical_current_poll(reading)) return;
opticalCalibrationUpdatedMs_ = now;
char current[32], vcc[24];
snprintf(current, sizeof(current), "Idiode: %.1f mA", reading.currentMa);
snprintf(vcc, sizeof(vcc), "Vcc: %.2f V", reading.vccVoltage);
display_.show(current, vcc);
Log::printf("CALIB",
"raw sense=%u vcc=%u | ADC sense=%.3fV vcc=%.3fV | Usense=%.3fV Vcc=%.3fV | Idiode=%.1fmA",
reading.senseRaw, reading.vccRaw, reading.senseAdcVoltage,
reading.vccAdcVoltage, reading.senseVoltage, reading.vccVoltage,
reading.currentMa);
}
void App::leaveOpticalCalibration() {
if (!opticalCalibrationActive_) return;
pwm_.stop();
opticalCalibrationActive_ = false;
Log::event("CALIB", "optical LED OFF; calibration menu left");
} }
void App::startTest() { void App::startTest() {
params_ = store_.params(settings_); stageCount_ = frequencyPointCount(params_.startHz, params_.endHz, params_.stepHz); leaveIdlePowerSave();
stageIndex_ = 0; requestedHz_ = 0; pendingReason_ = FailReason::NONE; sweepHadDataLoss_ = false; firstDataLossHz_ = 0; 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; havePeer_ = false; lastHeartbeatMs_ = 0; lastPeerSeenMs_ = 0;
if (!stageCount_) { finish(false, FailReason::UNSUPPORTED); return; } 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",
if (SERIAL_MINIMAL_LOG) roleName(static_cast<Role>(settings_.role)),
Log::printf("CONFIG", "mode=%s range=%lu..%luHz step=%luHz accuracy=%.2f%% time=%lums repeats=%u duty=%u%% stages=%lu", testKindName(static_cast<TestKind>(settings_.testKind)),
roleName(static_cast<Role>(settings_.role)), params_.startHz, params_.endHz, params_.stepHz, configuredLevelName(settings_), stageCount_);
params_.accuracyPct, params_.testTimeMs, params_.repeats, params_.dutyPct, stageCount_); if (SERIAL_MINIMAL_LOG) {
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,
configuredLevelName(settings_),
stageCount_);
}
printConfiguration(); printConfiguration();
const Role role = static_cast<Role>(settings_.role); const Role role = static_cast<Role>(settings_.role);
if (role == Role::SOLO) { if (role == Role::SOLO) {
if (!prepareStage()) return; 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 (!radio_.begin()) finish(false, FailReason::LINK_LOST);
else if (role == Role::MASTER) startMasterDiscovery(); else if (role == Role::MASTER) startMasterDiscovery();
else { state_ = AppState::SLAVE_READY; Log::event("TEST", "Slave armed and waiting for Master"); display_.show("SLAVE READY", "WAIT MASTER"); } else { state_ = AppState::SLAVE_READY; Log::event("TEST", "Slave armed and waiting for Master"); display_.show(UiText::SLAVE_READY, UiText::WAIT_MASTER); }
} }
bool App::armSlave() { 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();
lastUserActivityMs_ = millis();
params_ = store_.params(settings_); params_ = store_.params(settings_);
stageIndex_ = 0; stageCount_ = frequencyPointCount(params_.startHz, params_.endHz, params_.stepHz); stageIndex_ = 0; stageCount_ = pulseWidthPointCount(params_.maxPulseNs, params_.minPulseNs);
requestedHz_ = 0; session_ = 0; sequence_ = 0; havePeer_ = false; requestedHz_ = params_.frequencyHz; requestedPulseNs_ = 0; session_ = 0; sequence_ = 0; havePeer_ = false;
lastHeartbeatMs_ = 0; lastPeerSeenMs_ = 0; retries_ = 0; slaveRearmAtMs_ = 0; lastHeartbeatMs_ = 0; lastPeerSeenMs_ = 0; retries_ = 0; slaveRearmAtMs_ = 0;
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("LINK FAILED", "RADIO ERROR"); display_.show(UiText::LINK_FAILED, UiText::RADIO_ERROR,
0, 0, roleCorner(Role::SLAVE));
return false; return false;
} }
radio_.setWindowedReceive(true);
radio_.flush(); state_ = AppState::SLAVE_READY; radio_.flush(); state_ = AppState::SLAVE_READY;
Log::event("TEST", "Slave automatically armed and waiting for Master"); Log::event("TEST", "Slave automatically armed and waiting for Master");
display_.show("SLAVE READY", "WAIT MASTER"); if (!preserveDisplay) display_.show(UiText::SLAVE_READY, UiText::WAIT_MASTER);
return true; return true;
} }
bool App::prepareStage() { bool App::prepareStage(bool showProgress) {
requestedHz_ = frequencyAt(params_.startHz, params_.endHz, params_.stepHz, stageIndex_); requestedHz_ = params_.frequencyHz;
requestedPulseNs_ = pulseWidthAt(params_.maxPulseNs, params_.minPulseNs, stageIndex_);
actual_ = {};
const uint32_t maxHz = TARGET_IS_C3 ? C3_STRICT_MAX_HZ : const uint32_t maxHz = TARGET_IS_C3 ? C3_STRICT_MAX_HZ :
(receiver_.highRateBackend() ? S3_STRICT_MAX_HZ : C3_STRICT_MAX_HZ); (receiver_.highRateBackend() ? S3_STRICT_MAX_HZ : C3_STRICT_MAX_HZ);
if (requestedHz_ > maxHz) { finish(false, FailReason::UNSUPPORTED); return false; } if (requestedHz_ > maxHz) { finish(false, FailReason::UNSUPPORTED); return false; }
Log::printf("PWM", "starting GPIO=%u requested=%luHz duty=%u%%", GPIO_PWM, requestedHz_, params_.dutyPct); Log::printf("PWM", "starting GPIO=%u requested=%luHz pulse=%luns", GPIO_PWM, requestedHz_, requestedPulseNs_);
if (!pwm_.start(requestedHz_, params_.dutyPct, actual_)) { if (!pwm_.start(requestedHz_, requestedPulseNs_, actual_)) {
Log::printf("PWM", "START FAILED GPIO=%u requested=%luHz; LEDC attach/write/read failed", Log::printf("PWM", "START FAILED GPIO=%u requested=%luHz pulse=%luns; PWM setup failed",
GPIO_PWM, requestedHz_); GPIO_PWM, requestedHz_, requestedPulseNs_);
finish(false, FailReason::RESOLUTION); return false; finish(false, FailReason::RESOLUTION); return false;
} }
const FailReason resolution = validateResolution(actual_.actualHz, actual_.actualDutyPct, params_.accuracyPct, if (!periodWithin(actual_.actualHz, requestedHz_, params_.accuracyPct) ||
receiver_.tickHz(), actual_.bits); !periodWithin(actual_.actualPulseNs, requestedPulseNs_, params_.accuracyPct)) {
Log::printf("PWM", "requested point cannot be generated within tolerance: requested=%luHz/%luns actual=%luHz/%luns tolerance=%.2f%%",
requestedHz_, requestedPulseNs_, actual_.actualHz, actual_.actualPulseNs,
params_.accuracyPct);
finish(false, FailReason::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) { if (resolution != FailReason::NONE) {
Log::printf("PWM", "resolution rejected: actual=%luHz duty=%.3f%% bits=%u RXclock=%luHz tolerance=%.3f%%", 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, receiver_.tickHz(), params_.accuracyPct); actual_.actualHz, actual_.actualDutyPct, actual_.bits, plannedRxHz,
plannedPulseRxHz, effectiveTolerancePct(params_.accuracyPct));
finish(false, resolution); return false; finish(false, resolution); return false;
} }
Log::printf("PWM", "stage=%lu/%lu requested=%luHz actual=%luHz duty=%.2f%% bits=%u STARTED", } else if (!receiver_.highRateBackend()) {
stageIndex_ + 1, stageCount_, requestedHz_, actual_.actualHz, actual_.actualDutyPct, actual_.bits);
char f[12], one[24], two[24]; Display::formatFrequency(actual_.actualHz, f, sizeof(f));
snprintf(one, sizeof(one), "F %s D %.1f%%", f, actual_.actualDutyPct);
snprintf(two, sizeof(two), "%lu/%lu RUN", stageIndex_ + 1, stageCount_); display_.show(one, two);
if (static_cast<Role>(settings_.role) == Role::SOLO && !startLocalMeasurement(actual_.actualHz, actual_.actualDutyPct)) {
finish(false, FailReason::UNSUPPORTED); return false; 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) {
const bool started = driverMode ? startDriverMeasurement() :
startLocalMeasurement(actual_.actualHz, actual_.actualDutyPct);
if (!started) { finish(false, FailReason::UNSUPPORTED); return false; }
}
return true; return true;
} }
bool App::startLocalMeasurement(float hz, float duty) { bool App::startLocalMeasurement(float hz, float duty) {
Log::printf("MEASURE", "arming expected=%.3fHz duty=%.3f%% tolerance=%.3f%% settle=%u cycles window=%lums x%u; per-pulse logging suspended", Log::printf("MEASURE", "arming expected=%.3fHz duty=%.3f%% tolerance=%.3f%% period-capture=%luHz pulse-capture=%luHz settle=%u cycles window=%lums; every pulse validated",
hz, duty, params_.accuracyPct, PWM_SETTLE_CYCLES, params_.testTimeMs, params_.repeats); hz, duty, effectiveTolerancePct(params_.accuracyPct), receiver_.plannedTickHz(static_cast<uint32_t>(hz + 0.5f), duty),
const bool ok = measurement_.start(hz, duty, params_.accuracyPct, params_.testTimeMs, params_.repeats, PWM_SETTLE_CYCLES); receiver_.plannedPulseTickHz(static_cast<uint32_t>(hz + 0.5f), duty),
Log::printf("MEASURE", "receiver start %s, RMT chunk=%u symbols", ok ? "OK" : "FAILED", PWM_SETTLE_CYCLES, params_.testTimeMs);
receiver_.receiveChunkSymbols()); const bool ok = measurement_.start(hz, duty, params_.accuracyPct, params_.testTimeMs,
MEASUREMENT_AVERAGING_PERIODS, PWM_SETTLE_CYCLES, true);
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;
localMeasurementDeadlineMs_ = millis() + static_cast<uint32_t>(
watchdogMs > UINT32_MAX ? UINT32_MAX : watchdogMs);
Log::printf("MEASURE", "receiver start %s, continuous edge capture", ok ? "OK" : "FAILED");
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; return ok;
} }
void App::stagePassed() { void App::stagePassed() {
Log::printf("TEST", "stage %lu/%lu PASS; PWM stopping", stageIndex_ + 1, stageCount_); Log::printf("TEST", "stage %lu/%lu PASS; PWM stopping", stageIndex_ + 1, stageCount_);
pwm_.stop(); pwm_.stop();
// With PWM already quiet it is safe to stop capture before clearing its
// 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_) { if (++stageIndex_ >= stageCount_) {
if (sweepHadDataLoss_) { requestedHz_ = firstDataLossHz_; finish(false, FailReason::DATA_LOST); } const bool preserveDriverMeasurements =
else finish(true, FailReason::NONE); static_cast<TestKind>(settings_.testKind) == TestKind::DRIVER;
finish(true, FailReason::NONE, preserveDriverMeasurements);
return; return;
} }
if (static_cast<Role>(settings_.role) == Role::SOLO) { if (prepareStage()) state_ = AppState::SOLO_MEASURE; } 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) { else if (static_cast<Role>(settings_.role) == Role::MASTER) {
requestedHz_ = frequencyAt(params_.startHz, params_.endHz, params_.stepHz, stageIndex_); requestedHz_ = params_.frequencyHz;
requestedPulseNs_ = pulseWidthAt(params_.maxPulseNs, params_.minPulseNs, stageIndex_);
actual_ = {};
stageStartConfirmed_ = false;
pendingPacket_ = makePacket(MessageType::PREPARE); sendCurrent(MessageType::PREPARE); pendingPacket_ = makePacket(MessageType::PREPARE); sendCurrent(MessageType::PREPARE);
state_ = AppState::MASTER_WAIT_READY; retries_ = 0; deadlineMs_ = millis() + LINK_REPLY_TIMEOUT_MS; state_ = AppState::MASTER_WAIT_READY; retries_ = 0; deadlineMs_ = millis() + LINK_REPLY_TIMEOUT_MS;
} }
@@ -266,32 +1147,44 @@ 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_ = params_.frequencyHz;
requestedPulseNs_ = 0; havePeer_ = false; radio_.flush();
opticalWakeActive_ = true;
lastOpticalWakeToggleMs_ = millis();
pwm_.lightOn();
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_);
display_.show("MASTER SEARCH", "HOLD START=STOP"); display_.show(UiText::MASTER_SEARCH, UiText::HOLD_START_STOP);
} }
ProtocolPacket App::makePacket(MessageType type) const { ProtocolPacket App::makePacket(MessageType type) const {
ProtocolPacket p = {}; ProtocolPacket p = {};
p.type = static_cast<uint8_t>(type); p.session = session_; p.stage = stageIndex_; p.sequence = sequence_; p.type = static_cast<uint8_t>(type); p.session = session_; p.stage = stageIndex_;
p.requestedHz = requestedHz_; p.actualHz = actual_.actualHz; p.stageCount = static_cast<uint16_t>(stageCount_); p.sequence = sequence_;
p.actualDutyX100 = static_cast<uint16_t>(actual_.actualDutyPct * 100.0f + 0.5f); p.requestedHz = requestedHz_; p.requestedPulseNs = requestedPulseNs_;
p.testTimeMs = params_.testTimeMs; p.repeats = params_.repeats; p.actualHz = actual_.actualHz; p.actualPulseNs = actual_.actualPulseNs;
p.accuracyX100 = static_cast<uint16_t>(params_.accuracyPct * 100.0f + 0.5f); p.settleCycles = PWM_SETTLE_CYCLES; p.testTimeMs = params_.testTimeMs;
p.accuracyX100 = static_cast<uint16_t>(params_.accuracyPct * 100.0f + 0.5f);
p.lightCode = settings_.lightCode;
return p; return p;
} }
void App::sendCurrent(MessageType type) { void App::sendCurrent(MessageType type) {
++sequence_; pendingPacket_ = makePacket(type); ++sequence_; pendingPacket_ = makePacket(type);
const bool ok = radio_.sendBroadcast(pendingPacket_); lastSendMs_ = millis(); const bool ok = sendLinked(pendingPacket_); lastSendMs_ = millis();
if (!ok) Log::printf("ESP-NOW", "sendCurrent %s FAILED", messageName(type)); if (!ok) Log::printf("ESP-NOW", "sendCurrent %s FAILED", messageName(type));
} }
bool App::sendLinked(ProtocolPacket packet) {
return havePeer_ ? radio_.sendTo(peer_, packet) : radio_.sendBroadcast(packet);
}
void App::updateHeartbeat() { void App::updateHeartbeat() {
if (!havePeer_ || state_ == AppState::FINISHED) return; if (!havePeer_ || state_ == AppState::FINISHED) return;
const uint32_t now = millis(); const uint32_t now = millis();
const uint32_t radioRxMs = radio_.lastReceiveMs();
if (radioRxMs && now - radioRxMs < now - lastPeerSeenMs_) lastPeerSeenMs_ = radioRxMs;
if (now - lastPeerSeenMs_ >= LINK_HEARTBEAT_TIMEOUT_MS) { if (now - lastPeerSeenMs_ >= LINK_HEARTBEAT_TIMEOUT_MS) {
Log::event("ESP-NOW", "peer heartbeat timeout"); Log::event("ESP-NOW", "peer heartbeat timeout");
finish(false, FailReason::LINK_LOST); finish(false, FailReason::LINK_LOST);
@@ -301,7 +1194,7 @@ void App::updateHeartbeat() {
now - lastHeartbeatMs_ >= LINK_HEARTBEAT_INTERVAL_MS) { now - lastHeartbeatMs_ >= LINK_HEARTBEAT_INTERVAL_MS) {
ProtocolPacket heartbeat = makePacket(MessageType::HEARTBEAT); ProtocolPacket heartbeat = makePacket(MessageType::HEARTBEAT);
heartbeat.sequence = sequence_; heartbeat.sequence = sequence_;
radio_.sendBroadcast(heartbeat); sendLinked(heartbeat);
lastHeartbeatMs_ = now; lastHeartbeatMs_ = now;
} }
} }
@@ -314,57 +1207,136 @@ void App::handleRadio() {
ReceivedPacket r; ReceivedPacket r;
while (radio_.receive(r)) { while (radio_.receive(r)) {
const MessageType type = static_cast<MessageType>(r.packet.type); const MessageType type = static_cast<MessageType>(r.packet.type);
if (type != MessageType::HEARTBEAT && type != MessageType::HEARTBEAT_ACK && if (type != MessageType::HEARTBEAT && type != MessageType::HEARTBEAT_ACK && type != MessageType::PROGRESS &&
state_ != AppState::SLAVE_MEASURE) state_ != AppState::SLAVE_MEASURE)
Log::printf("ESP-NOW", "RX %s session=%08lX stage=%u seq=%u", Log::printf("ESP-NOW", "RX %s session=%08lX stage=%u seq=%u",
messageName(type), r.packet.session, r.packet.stage, r.packet.sequence); messageName(type), r.packet.session, r.packet.stage, r.packet.sequence);
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();
pwm_.stop();
setActivePerformance(true);
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;
lastPeerSeenMs_ = millis(); lastPeerSeenMs_ = millis();
ProtocolPacket ack = makePacket(MessageType::DISCOVER_ACK); ack.sequence = r.packet.sequence; radio_.sendBroadcast(ack); ProtocolPacket ack = makePacket(MessageType::DISCOVER_ACK); ack.sequence = r.packet.sequence; sendLinked(ack);
state_ = AppState::SLAVE_WAIT_START; display_.show("MASTER SEEN", "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, params_.stepHz, stageIndex_); requestedHz_ = params_.frequencyHz;
requestedPulseNs_ = pulseWidthAt(params_.maxPulseNs, params_.minPulseNs, 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;
char mac[20]; Radio::macText(peer_, mac, sizeof(mac)); Log::printf("ESP-NOW", "Slave selected %s", mac); continue; char mac[20]; Radio::macText(peer_, mac, sizeof(mac)); Log::printf("ESP-NOW", "Slave selected %s", mac); continue;
} }
if (havePeer_ && !memcmp(peer_, r.mac, 6) && r.packet.session == session_) lastPeerSeenMs_ = millis(); if (havePeer_ && !memcmp(peer_, r.mac, 6) && r.packet.session == session_) lastPeerSeenMs_ = millis();
if (havePeer_ && !memcmp(peer_, r.mac, 6) && r.packet.session == session_ && if (havePeer_ && !memcmp(peer_, r.mac, 6) && r.packet.session == session_ &&
type == MessageType::HEARTBEAT) { type == MessageType::HEARTBEAT) {
ProtocolPacket ack = makePacket(MessageType::HEARTBEAT_ACK); continue; // Radio's priority heartbeat task has already sent the ACK.
ack.sequence = r.packet.sequence; radio_.sendBroadcast(ack); continue;
} }
if (type == MessageType::HEARTBEAT_ACK) continue; if (type == MessageType::HEARTBEAT_ACK) continue;
if (havePeer_ && !memcmp(peer_, r.mac, 6) && type == MessageType::RESULT && if (havePeer_ && !memcmp(peer_, r.mac, 6) && type == MessageType::RESULT &&
r.packet.session == session_ && r.packet.stage < stageIndex_) { r.packet.session == session_ && r.packet.stage < stageIndex_) {
ProtocolPacket ack = {}; ack.type = static_cast<uint8_t>(MessageType::ACK); ProtocolPacket ack = {}; ack.type = static_cast<uint8_t>(MessageType::ACK);
ack.session = session_; ack.stage = r.packet.stage; ack.sequence = r.packet.sequence; ack.session = session_; ack.stage = r.packet.stage; ack.sequence = r.packet.sequence;
radio_.sendBroadcast(ack); continue; // idempotent ACK for a retried old result sendLinked(ack); continue; // idempotent ACK for a retried old result
}
const bool matchingPeer = havePeer_ && !memcmp(peer_, r.mac, 6) && r.packet.session == session_;
if (matchingPeer && type == MessageType::PREPARE) {
const bool expected = state_ == AppState::SLAVE_WAIT_START && r.packet.stage == stageIndex_;
const bool implicitAck = state_ == AppState::SLAVE_WAIT_ACK && pendingPacket_.passed &&
r.packet.stage == static_cast<uint16_t>(pendingPacket_.stage + 1U);
if (expected || implicitAck) {
stageIndex_ = r.packet.stage;
sequence_ = r.packet.sequence;
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_ = {};
ProtocolPacket ready = makePacket(MessageType::READY);
ready.sequence = r.packet.sequence; sendLinked(ready);
}
continue;
} }
if (!havePeer_ || memcmp(peer_, r.mac, 6) || !packetForCurrent(r.packet)) continue; if (!havePeer_ || memcmp(peer_, r.mac, 6) || !packetForCurrent(r.packet)) continue;
if (type == MessageType::ABORT) { finish(false, FailReason::ABORTED); continue; } if (type == MessageType::ABORT) {
const FailReason reason = r.packet.reason > static_cast<uint8_t>(FailReason::NONE) &&
r.packet.reason <= static_cast<uint8_t>(FailReason::ABORTED)
? static_cast<FailReason>(r.packet.reason) : FailReason::ABORTED;
if (r.packet.requestedHz) requestedHz_ = r.packet.requestedHz;
if (r.packet.requestedPulseNs) requestedPulseNs_ = r.packet.requestedPulseNs;
actual_.actualHz = r.packet.actualHz ? r.packet.actualHz : requestedHz_;
actual_.actualPulseNs = r.packet.actualPulseNs ? r.packet.actualPulseNs : requestedPulseNs_;
actual_.actualDutyPct = dutyFromPulse(actual_.actualHz, actual_.actualPulseNs);
measurement_.abort(); finish(false, reason); continue;
}
if (state_ == AppState::MASTER_WAIT_READY && type == MessageType::READY) { if (state_ == AppState::MASTER_WAIT_READY && type == MessageType::READY) {
if (!prepareStage()) continue; if (!prepareStage(false)) continue;
sendCurrent(MessageType::START_STAGE); state_ = AppState::MASTER_WAIT_RESULT; retries_ = 0; deadlineMs_ = millis() + sendCurrent(MessageType::START_STAGE); state_ = AppState::MASTER_WAIT_RESULT;
params_.testTimeMs * params_.repeats + LINK_REPLY_TIMEOUT_MS + (1000UL * PWM_SETTLE_CYCLES / actual_.actualHz) + 20; stageStartConfirmed_ = false; retries_ = 0;
deadlineMs_ = millis() + LINK_RETRY_INTERVAL_MS;
} else if (state_ == AppState::MASTER_WAIT_RESULT && type == MessageType::READY &&
r.packet.sequence == pendingPacket_.sequence) {
if (!stageStartConfirmed_) showStageProgress();
stageStartConfirmed_ = true;
deadlineMs_ = millis() + stageWallTimeMs(params_.testTimeMs, actual_.actualHz) +
LINK_REPLY_TIMEOUT_MS + 20;
} else if (state_ == AppState::MASTER_WAIT_RESULT && type == MessageType::PROGRESS) {
stageStartConfirmed_ = true;
deadlineMs_ = millis() + stageWallTimeMs(params_.testTimeMs, actual_.actualHz) +
LINK_REPLY_TIMEOUT_MS;
showRemoteResult(r.packet);
} else if (state_ == AppState::MASTER_WAIT_RESULT && type == MessageType::RESULT) { } else if (state_ == AppState::MASTER_WAIT_RESULT && type == MessageType::RESULT) {
ProtocolPacket ack = makePacket(MessageType::ACK); ack.sequence = r.packet.sequence; radio_.sendBroadcast(ack); pwm_.stop(); ProtocolPacket ack = makePacket(MessageType::ACK); ack.sequence = r.packet.sequence;
if (!r.packet.passed) finish(false, static_cast<FailReason>(r.packet.reason)); else stagePassed(); ack.passed = r.packet.passed && stageIndex_ + 1U >= stageCount_;
} else if (state_ == AppState::SLAVE_WAIT_START && type == MessageType::PREPARE) { sendLinked(ack); pwm_.stop();
params_.testTimeMs = r.packet.testTimeMs; params_.repeats = r.packet.repeats; showRemoteResult(r.packet);
params_.accuracyPct = r.packet.accuracyX100 / 100.0f; requestedHz_ = r.packet.requestedHz; if (!r.packet.passed) {
ProtocolPacket ready = makePacket(MessageType::READY); ready.sequence = r.packet.sequence; radio_.sendBroadcast(ready); finish(false, static_cast<FailReason>(r.packet.reason), true);
display_.show("SLAVE LINKED", "MASTER ONLINE"); } else if (ack.passed) {
pendingPacket_ = ack;
state_ = AppState::MASTER_FINALIZE; retries_ = 0;
deadlineMs_ = millis() + FINAL_ACK_RETRY_INTERVAL_MS;
} else stagePassed();
} else if (state_ == AppState::MASTER_FINALIZE && type == MessageType::RESULT) {
// The Slave did not receive the final ACK and repeated RESULT.
sendLinked(pendingPacket_);
} else if (state_ == AppState::SLAVE_WAIT_START && type == MessageType::START_STAGE) { } else if (state_ == AppState::SLAVE_WAIT_START && type == MessageType::START_STAGE) {
actual_.actualHz = r.packet.actualHz; actual_.actualDutyPct = r.packet.actualDutyX100 / 100.0f; sequence_ = r.packet.sequence;
actual_.actualHz = r.packet.actualHz; actual_.actualPulseNs = r.packet.actualPulseNs;
actual_.actualDutyPct = dutyFromPulse(actual_.actualHz, actual_.actualPulseNs);
if (!startLocalMeasurement(actual_.actualHz, actual_.actualDutyPct)) { finish(false, FailReason::UNSUPPORTED); continue; } if (!startLocalMeasurement(actual_.actualHz, actual_.actualDutyPct)) { finish(false, FailReason::UNSUPPORTED); continue; }
state_ = AppState::SLAVE_MEASURE; deadlineMs_ = millis() + params_.testTimeMs * params_.repeats + LINK_REPLY_TIMEOUT_MS; showStageProgress();
state_ = AppState::SLAVE_MEASURE;
deadlineMs_ = millis() + stageWallTimeMs(params_.testTimeMs, actual_.actualHz) + LINK_REPLY_TIMEOUT_MS;
ProtocolPacket started = makePacket(MessageType::READY);
started.sequence = r.packet.sequence; sendLinked(started);
} else if (state_ == AppState::SLAVE_MEASURE && type == MessageType::START_STAGE) {
// START_STAGE or its acknowledgement was lost. Do not restart the
// measurement; only confirm the already running stage again.
ProtocolPacket started = makePacket(MessageType::READY);
started.sequence = r.packet.sequence; sendLinked(started);
} else if (state_ == AppState::SLAVE_WAIT_ACK && type == MessageType::ACK && r.packet.sequence == pendingPacket_.sequence) { } else if (state_ == AppState::SLAVE_WAIT_ACK && type == MessageType::ACK && r.packet.sequence == pendingPacket_.sequence) {
if (pendingPacket_.passed) { ++stageIndex_; state_ = AppState::SLAVE_WAIT_START; display_.show("SLAVE READY", "WAIT PREPARE"); } if (pendingPacket_.passed) {
else finish(false, static_cast<FailReason>(pendingPacket_.reason)); // Master sends ACK only after stopping its PWM. Disable capture for
// every completed stage, so the next start can clear its queue without
// racing the ISR (not only after the final stage).
receiver_.stop();
if (r.packet.passed) {
radio_.end(); pendingReason_ = FailReason::NONE;
if (armSlave(true)) display_.show(UiText::PASS_WORD, UiText::WAIT_MASTER);
} else {
stageIndex_ = static_cast<uint32_t>(r.packet.stage) + 1U;
state_ = AppState::SLAVE_WAIT_START;
}
}
else finish(false, static_cast<FailReason>(pendingPacket_.reason), true);
} }
} }
} }
@@ -372,56 +1344,106 @@ 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 - lastSendMs_ >= LINK_RETRY_INTERVAL_MS) { if (now - lastOpticalWakeToggleMs_ >= OPTICAL_WAKE_HALF_PERIOD_MS) {
Log::event("ESP-NOW", "DISCOVER retry"); radio_.sendBroadcast(pendingPacket_); opticalWakeActive_ = !opticalWakeActive_;
if (opticalWakeActive_) pwm_.lightOn();
else pwm_.stop();
lastOpticalWakeToggleMs_ = now;
}
if (now - lastSendMs_ >= DISCOVERY_RETRY_INTERVAL_MS) {
if (++retries_ % 50U == 0U) Log::event("ESP-NOW", "DISCOVER burst continues");
radio_.sendBroadcast(pendingPacket_);
lastSendMs_ = now; lastSendMs_ = now;
} }
return; return;
} }
if (state_ == AppState::MASTER_FINALIZE) {
if (now < deadlineMs_) return;
if (retries_++ < FINAL_ACK_RETRIES) {
sendLinked(pendingPacket_);
deadlineMs_ = now + FINAL_ACK_RETRY_INTERVAL_MS;
} else finish(true, FailReason::NONE);
return;
}
updateHeartbeat(); updateHeartbeat();
if (state_ == AppState::FINISHED) return; if (state_ == AppState::FINISHED) return;
if (now < deadlineMs_) return; if (now < deadlineMs_) return;
if (retries_ >= LINK_PACKET_RETRIES) { finish(false, FailReason::LINK_LOST); return; } if (retries_ >= LINK_PACKET_RETRIES) { finish(false, FailReason::LINK_LOST); return; }
Log::printf("ESP-NOW", "%s retry=%u", messageName(static_cast<MessageType>(pendingPacket_.type)), retries_ + 1); Log::printf("ESP-NOW", "%s retry=%u", messageName(static_cast<MessageType>(pendingPacket_.type)), retries_ + 1);
radio_.sendBroadcast(pendingPacket_); ++retries_; sendLinked(pendingPacket_); ++retries_;
deadlineMs_ = now + (state_ == AppState::MASTER_WAIT_RESULT ? deadlineMs_ = now + (state_ == AppState::MASTER_WAIT_RESULT ?
params_.testTimeMs * params_.repeats + LINK_REPLY_TIMEOUT_MS : LINK_REPLY_TIMEOUT_MS); (stageStartConfirmed_ ? stageWallTimeMs(params_.testTimeMs, actual_.actualHz) + LINK_REPLY_TIMEOUT_MS : LINK_RETRY_INTERVAL_MS) :
LINK_REPLY_TIMEOUT_MS);
} }
void App::updateSlave() { void App::updateSlave() {
updateHeartbeat(); updateHeartbeat();
if (state_ == AppState::FINISHED) return; if (state_ == AppState::FINISHED) return;
if (state_ == AppState::SLAVE_MEASURE) { if (state_ == AppState::SLAVE_MEASURE) {
if (static_cast<int32_t>(millis() - localMeasurementDeadlineMs_) >= 0) {
Log::event("MEASURE", "Slave local stage watchdog expired");
measurement_.forceFail(FailReason::LOST_EDGE);
}
const MeasureState ms = measurement_.update(); const MeasureState ms = measurement_.update();
if (ms != MeasureState::PASS && ms != MeasureState::FAIL) return; if (measurement_.takeProgressUpdate()) {
printStageStats(measurement_.stats(), requestedHz_); StageStats live = {};
if (measurement_.statsSnapshot(live)) {
ProtocolPacket progress = makePacket(MessageType::PROGRESS);
progress.progressStep = measurement_.progressStep();
fillMeasuredResult(progress, live);
progress.sequence = sequence_; sendLinked(progress);
showStageResult(live);
}
return;
}
if (ms != MeasureState::PASS && ms != MeasureState::FAIL) {
return;
}
printStageStats(measurement_.stats(), actual_.actualHz);
showStageResult(measurement_.stats());
pendingPacket_ = makePacket(MessageType::RESULT); pendingPacket_ = makePacket(MessageType::RESULT);
pendingPacket_.progressStep = ms == MeasureState::PASS ?
MEASUREMENT_PROGRESS_STEPS : measurement_.progressStep();
pendingPacket_.passed = ms == MeasureState::PASS && measurement_.reason() == FailReason::NONE; pendingPacket_.passed = ms == MeasureState::PASS && measurement_.reason() == FailReason::NONE;
pendingPacket_.reason = static_cast<uint8_t>(measurement_.reason()); pendingPacket_.periods = measurement_.stats().periods; pendingPacket_.reason = static_cast<uint8_t>(measurement_.reason()); pendingPacket_.periods = measurement_.stats().periods;
fillMeasuredResult(pendingPacket_, measurement_.stats());
pendingPacket_.minPeriodTicks = measurement_.stats().minPeriod; pendingPacket_.maxPeriodTicks = measurement_.stats().maxPeriod; pendingPacket_.minPeriodTicks = measurement_.stats().minPeriod; pendingPacket_.maxPeriodTicks = measurement_.stats().maxPeriod;
pendingPacket_.sequence = ++sequence_; radio_.sendBroadcast(pendingPacket_); pendingPacket_.sequence = ++sequence_; sendLinked(pendingPacket_);
Log::printf("TEST", "Slave result prepared: %s reason=%s periods=%lu", Log::printf("TEST", "Slave result prepared: %s reason=%s periods=%lu",
pendingPacket_.passed ? "PASS" : "FAIL", failName(static_cast<FailReason>(pendingPacket_.reason)), pendingPacket_.periods); pendingPacket_.passed ? "PASS" : "FAIL", failName(static_cast<FailReason>(pendingPacket_.reason)), pendingPacket_.periods);
state_ = AppState::SLAVE_WAIT_ACK; retries_ = 0; deadlineMs_ = millis() + LINK_REPLY_TIMEOUT_MS; state_ = AppState::SLAVE_WAIT_ACK; retries_ = 0; deadlineMs_ = millis() + LINK_REPLY_TIMEOUT_MS;
} else if (state_ == AppState::SLAVE_WAIT_ACK && millis() >= deadlineMs_) { } else if (state_ == AppState::SLAVE_WAIT_ACK && millis() >= deadlineMs_) {
if (retries_++ >= LINK_PACKET_RETRIES) finish(false, FailReason::LINK_LOST); if (retries_++ >= LINK_PACKET_RETRIES) finish(false, FailReason::LINK_LOST);
else { Log::printf("ESP-NOW", "RESULT retry=%u", retries_); radio_.sendBroadcast(pendingPacket_); deadlineMs_ = millis() + LINK_REPLY_TIMEOUT_MS; } else { Log::printf("ESP-NOW", "RESULT retry=%u", retries_); sendLinked(pendingPacket_); deadlineMs_ = millis() + LINK_REPLY_TIMEOUT_MS; }
} }
} }
void App::sendAbort() { if (havePeer_) sendCurrent(MessageType::ABORT); } void App::sendAbort(FailReason reason) {
if (!havePeer_) return;
++sequence_;
ProtocolPacket packet = makePacket(MessageType::ABORT);
packet.reason = static_cast<uint8_t>(reason);
if (!packet.actualPulseNs) packet.actualPulseNs = requestedPulseNs_;
sendLinked(packet);
}
void App::abortTest() { void App::abortTest() {
Log::event("ACTION", "abort requested: sending ABORT, stopping receiver and PWM"); Log::event("ACTION", "abort requested: sending ABORT, stopping receiver and PWM");
sendAbort(); measurement_.abort(); finish(false, FailReason::ABORTED); sendAbort(FailReason::ABORTED);
measurement_.abort();
driverTest_.abort();
finish(false, FailReason::ABORTED);
} }
void App::finish(bool pass, FailReason reason) { void App::finish(bool pass, FailReason reason, bool preserveDisplay) {
Log::printf("TEST", "finishing result=%s reason=%s", pass ? "PASS" : "FAIL", failName(reason)); Log::printf("TEST", "finishing result=%s reason=%s", pass ? "PASS" : "FAIL", failName(reason));
const AppState failedState = state_; const AppState failedState = state_;
const bool masterLinkLost = reason == FailReason::LINK_LOST && const bool masterLinkLost = reason == FailReason::LINK_LOST &&
(failedState == AppState::MASTER_DISCOVER || failedState == AppState::MASTER_WAIT_READY || (failedState == AppState::MASTER_DISCOVER || failedState == AppState::MASTER_WAIT_READY ||
failedState == AppState::MASTER_WAIT_RESULT); failedState == AppState::MASTER_WAIT_RESULT || failedState == AppState::MASTER_FINALIZE);
const bool masterActive = failedState == AppState::MASTER_DISCOVER ||
failedState == AppState::MASTER_WAIT_READY || failedState == AppState::MASTER_WAIT_RESULT ||
failedState == AppState::MASTER_FINALIZE;
const bool slaveLinkLost = reason == FailReason::LINK_LOST && const bool slaveLinkLost = reason == FailReason::LINK_LOST &&
(failedState == AppState::SLAVE_READY || failedState == AppState::SLAVE_WAIT_START || (failedState == AppState::SLAVE_READY || failedState == AppState::SLAVE_WAIT_START ||
failedState == AppState::SLAVE_MEASURE || failedState == AppState::SLAVE_WAIT_ACK); failedState == AppState::SLAVE_MEASURE || failedState == AppState::SLAVE_WAIT_ACK);
@@ -431,36 +1453,222 @@ void App::finish(bool pass, FailReason reason) {
startMasterDiscovery(); startMasterDiscovery();
return; return;
} }
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);
lastUserActivityMs_ = millis();
if (slaveLinkLost) { if (slaveLinkLost) {
if (armSlave()) display_.show("MASTER LOST", "WAIT MASTER"); char target[32], one[64];
formatTarget(requestedHz_, requestedPulseNs_, target, sizeof(target));
snprintf(one, sizeof(one), UiText::FAIL_FORMAT, target);
display_.show(one, uiFailName(reason), stageIndex_ + 1, stageCount_,
roleCorner(Role::SLAVE));
armSlave(true);
return; return;
} }
if (static_cast<Role>(settings_.role) == Role::SLAVE) slaveRearmAtMs_ = millis() + 2000; // The result has already been acknowledged before a normal measurement
char one[24]; // failure reaches here. Re-arm ESP-NOW immediately so a quick retry from
if (pass) { snprintf(one, sizeof(one), "PASS %luHz-%lu", params_.startHz, params_.endHz); display_.show(one, "START=REPEAT"); } // Master is not hidden behind the former two-second delay; preserve the
else if (requestedHz_) { // failure screen while listening.
snprintf(one, sizeof(one), "FAIL AT %lu", requestedHz_); display_.show(one, failName(reason)); if (static_cast<Role>(settings_.role) == Role::SLAVE) {
} else { armSlave(true);
display_.show("TEST FAILED", failName(reason)); return;
} }
if (preserveDisplay) return;
char one[64];
if (pass) {
const Role role = static_cast<Role>(settings_.role);
snprintf(one, sizeof(one), "%s %s", uiRoleName(role), UiText::PASS_WORD);
display_.show(one, role == Role::SLAVE ? UiText::WAIT_MASTER : UiText::START_AGAIN);
}
else if (requestedHz_) {
char target[32];
formatTarget(requestedHz_, requestedPulseNs_, target, sizeof(target));
snprintf(one, sizeof(one), UiText::FAIL_FORMAT, target);
display_.show(one, uiFailName(reason), stageIndex_ + 1, stageCount_,
roleCorner(static_cast<Role>(settings_.role)));
} else {
display_.show(UiText::TEST_FAILED, uiFailName(reason), 0, 0,
roleCorner(static_cast<Role>(settings_.role)));
}
}
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 !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() {
// Calibration and tests may hold the transmitter active. Idle/result
// screens must always detach PWM and restore the physical light-OFF level.
pwm_.stop();
}
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))
Log::printf("POWER", "CPU frequency change to %luMHz FAILED", targetMhz);
}
void App::leaveIdlePowerSave(bool wakeDisplay) {
if (!idlePowerSave_) {
if (wakeDisplay) display_.setPower(true);
return;
}
idlePowerSave_ = false;
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);
Log::event("POWER", "idle light sleep ended");
}
void App::serviceIdlePowerSave() {
if (!idlePowerSaveAllowed()) {
leaveIdlePowerSave(false);
return;
}
const uint32_t now = millis();
if (!idlePowerSave_) {
if (now - lastUserActivityMs_ < IDLE_POWER_SAVE_TIMEOUT_MS) {
delay(1); // allow the FreeRTOS idle task to halt the CPU between UI polls
return;
}
idlePowerSave_ = true;
pwm_.stop();
if (state_ == AppState::SLAVE_READY) {
radio_.end();
idleSleepRadioStopped_ = true;
}
display_.setPower(false);
Log::event("POWER", "idle timeout; preparing light sleep");
Serial.flush();
delay(2);
}
gpio_wakeup_enable(static_cast<gpio_num_t>(GPIO_BUTTON_START),
BUTTON_ACTIVE_LEVEL == LOW ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL);
gpio_wakeup_enable(static_cast<gpio_num_t>(GPIO_BUTTON_MODE),
BUTTON_ACTIVE_LEVEL == LOW ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL);
if (static_cast<Role>(settings_.role) == Role::SLAVE) {
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();
const esp_err_t result = esp_light_sleep_start();
if (result != ESP_OK) {
leaveIdlePowerSave(true);
delay(1);
return;
}
const esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
const bool buttonWake = digitalRead(GPIO_BUTTON_START) == BUTTON_ACTIVE_LEVEL ||
digitalRead(GPIO_BUTTON_MODE) == BUTTON_ACTIVE_LEVEL;
if (buttonWake) {
startButton_.suppressUntilRelease();
modeButton_.suppressUntilRelease();
}
// GPIO wake worked, so disarm all level sources before peripherals and the
// button state machines are brought back up.
gpio_wakeup_disable(static_cast<gpio_num_t>(GPIO_BUTTON_START));
gpio_wakeup_disable(static_cast<gpio_num_t>(GPIO_BUTTON_MODE));
gpio_wakeup_disable(static_cast<gpio_num_t>(GPIO_RX));
// Native USB and I2C can retain stale driver state across light sleep even
// though their clocks have stopped. A full end/begin cycle prevents the
// several-second button stalls and restores Serial output after wake.
setActivePerformance(false);
Serial.end();
delay(2);
Serial.begin(SERIAL_BAUD);
#if ARDUINO_USB_CDC_ON_BOOT
Serial.setTxTimeoutMs(SERIAL_TX_TIMEOUT_MS);
#endif
Wire.end();
Wire.begin(GPIO_SDA, GPIO_SCL);
Wire.setClock(400000);
Wire.setTimeOut(30);
// This also restores ESP-NOW when Slave stopped it before sleeping.
leaveIdlePowerSave(true);
Log::printf("POWER", "light sleep wake cause=%u button=%s; peripherals restored",
static_cast<unsigned>(cause), buttonWake ? "YES" : "NO");
if (buttonWake)
Log::event("POWER", "wake button consumed; next press will perform the action");
} }
void App::printConfiguration() { void App::printConfiguration() {
if (SERIAL_MINIMAL_LOG) return; if (SERIAL_MINIMAL_LOG) return;
const char *board = TARGET_IS_C3 ? "ESP32-C3" : "ESP32-S3"; const char *board = TARGET_IS_C3 ? "ESP32-C3" : "ESP32-S3";
uint8_t mac[6] = {}; esp_read_mac(mac, ESP_MAC_WIFI_STA); 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))); 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_));
Serial.printf("MAC=%02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 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, 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); GPIO_BUTTON_START, GPIO_BUTTON_MODE, GPIO_SDA, GPIO_SCL);
Serial.printf("Test %lu..%lu step %lu Hz, accuracy %.2f%%, %lums x%u, duty %u%%\n", if (static_cast<TestKind>(settings_.testKind) == TestKind::DRIVER) {
params_.startHz, params_.endHz, params_.stepHz, params_.accuracyPct, params_.testTimeMs, params_.repeats, params_.dutyPct); const char *code = lightCodeName(static_cast<LightCode>(settings_.lightCode));
stageCount_ = frequencyPointCount(params_.startHz, params_.endHz, params_.stepHz); Serial.printf("Test %lu Hz, pulse %lu..%lu ns, accuracy %.2f%%, %lums, TX light=%c RX active light=%c\n",
Serial.printf("Frequencies (%lu): ", stageCount_); params_.frequencyHz, params_.maxPulseNs, params_.minPulseNs,
for (uint32_t i = 0; i < stageCount_; ++i) Serial.printf("%lu%s", frequencyAt(params_.startHz, params_.endHz, params_.stepHz, i), i + 1 == stageCount_ ? "\n" : ","); params_.accuracyPct, params_.testTimeMs, code[0], code[1]);
Serial.printf("ALL nominal: %llu us | RX=%s\n", actualNominalTotalUs(), receiver_.highRateBackend() ? "RMT DMA" : "RMT ping-pong"); } else {
Serial.printf("Test %lu Hz, pulse %lu..%lu ns, accuracy %.2f%%, %lums, optical polarity=AUTO\n",
params_.frequencyHz, params_.maxPulseNs, params_.minPulseNs,
params_.accuracyPct, params_.testTimeMs);
}
stageCount_ = pulseWidthPointCount(params_.maxPulseNs, params_.minPulseNs);
Serial.printf("Pulse widths descending (%lu): ", stageCount_);
for (uint32_t i = 0; i < stageCount_; ++i)
Serial.printf("%lu%s", pulseWidthAt(params_.maxPulseNs, params_.minPulseNs, i),
i + 1 == stageCount_ ? " ns\n" : ",");
Serial.printf("ALL nominal: %llu us | RX=%s\n", actualNominalTotalUs(),
receiver_.highRateBackend() ? "MCPWM 80MHz" : "GPIO cycle counter");
} }
uint64_t App::actualNominalTotalUs() { uint64_t App::actualNominalTotalUs() {
@@ -473,10 +1681,165 @@ uint64_t App::actualNominalTotalUs() {
void App::printStageStats(const StageStats &s, uint32_t hz) { void App::printStageStats(const StageStats &s, uint32_t hz) {
if (!s.periods) return; if (!s.periods) return;
const float measuredHz = static_cast<float>(receiver_.tickHz()) * s.periods / s.periodSum; const float measuredHz = static_cast<float>(receiver_.tickHz()) * s.periods / s.periodSum;
const float measuredDuty = 100.0f * s.activeSum / s.periodSum; const uint32_t measuredPulseNs = static_cast<uint32_t>(lround(
const char *status = s.reason == FailReason::NONE ? "PASS" : static_cast<double>(s.activeSum) * 1000000000.0 /
(s.reason == FailReason::DATA_LOST ? "DATA_LOST" : "FAIL"); (static_cast<uint64_t>(receiver_.pulseTickHz()) * s.periods)));
Log::printf("RESULT", "%luHz %s periods=%lu measured=%.2fHz duty=%.2f%% lost=%lu%s%s", char requestedText[12], measuredText[12];
hz, status, s.periods, measuredHz, measuredDuty, s.lostItems, Display::formatFrequency(hz, requestedText, sizeof(requestedText));
Display::formatFrequency(measuredHz, measuredText, sizeof(measuredText));
const char *status = s.reason == FailReason::NONE ? "PASS" : "FAIL";
Log::printf("RESULT", "%s/%luns %s periods=%lu measured=%s/%luns skipped=%lu%s%s",
requestedText, requestedPulseNs_, status, s.periods, measuredText, measuredPulseNs, s.droppedItems,
s.reason == FailReason::NONE ? "" : " reason=", s.reason == FailReason::NONE ? "" : failName(s.reason)); s.reason == FailReason::NONE ? "" : " reason=", s.reason == FailReason::NONE ? "" : failName(s.reason));
} }
void App::showStageResult(const StageStats &s) {
char one[64], two[64];
formatTestTarget(requestedHz_, requestedPulseNs_, one, sizeof(one));
if (s.reason != FailReason::NONE) {
formatFailure(s.reason, requestedHz_, requestedPulseNs_, one, sizeof(one));
if (s.reason == FailReason::PERIOD_OUT && s.badFrequency > 0.0f) {
char frequency[12];
Display::formatFrequency(s.badFrequency, frequency, sizeof(frequency));
snprintf(two, sizeof(two), UiText::PERIOD_OUT_FORMAT, frequency);
} else if (s.reason == FailReason::DUTY_OUT && s.badFrequency > 0.0f) {
char pulse[12];
Display::formatPulse(pulseFromDuty(s.badFrequency, s.badDuty), pulse,
sizeof(pulse), true);
snprintf(two, sizeof(two), UiText::DUTY_OUT_FORMAT, pulse);
} else {
snprintf(two, sizeof(two), "%s", uiFailName(s.reason));
}
display_.show(one, two, overallProgress(stageIndex_, measurement_.progressStep()),
overallProgressTotal(stageCount_), roleCorner(static_cast<Role>(settings_.role)));
return;
}
if (!s.periods || !s.periodSum) {
display_.show(one, UiText::NO_MEASUREMENT, overallProgress(stageIndex_, measurement_.progressStep()),
overallProgressTotal(stageCount_));
return;
}
const float measuredHz = static_cast<float>(receiver_.tickHz()) * s.periods / s.periodSum;
const uint32_t measuredPulseNs = static_cast<uint32_t>(lround(
static_cast<double>(s.activeSum) * 1000000000.0 /
(static_cast<uint64_t>(receiver_.pulseTickHz()) * s.periods)));
formatMeasured(measuredHz, measuredPulseNs, two, sizeof(two));
display_.show(one, two, overallProgress(stageIndex_, measurement_.progressStep()),
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] = "---", errorPulse[12] = "---";
if (s.errorTriggerValid) {
const uint64_t elapsedNs =
(static_cast<uint64_t>(s.errorTriggerTicks) * 1000000000ULL +
driverTest_.tickHz() / 2U) /
driverTest_.tickHz();
formatElapsedNs(elapsedNs, elapsed, sizeof(elapsed));
}
if (s.errorPulseValid) {
const uint64_t errorPulseNs =
(static_cast<uint64_t>(s.errorPulseTicks) * 1000000000ULL +
driverTest_.tickHz() / 2U) / driverTest_.tickHz();
formatElapsedNs(errorPulseNs, errorPulse, sizeof(errorPulse));
}
if (s.errorPulseValid)
snprintf(two, sizeof(two), "T:%s P:%s", elapsed, errorPulse);
else snprintf(two, sizeof(two), "T:%s", elapsed);
} else {
formatTestTarget(requestedHz_, requestedPulseNs_, one, sizeof(one));
snprintf(two, sizeof(two), UiText::DRIVER_MEASUREMENT_FORMAT,
delay, response);
}
const bool finished = testPassed || s.reason != FailReason::NONE;
const uint8_t progressSteps = driverTest_.progressSteps();
display_.show(one, two,
finished ? 0U : overallProgress(
stageIndex_, driverTest_.progressStep(), progressSteps),
finished ? 0U : overallProgressTotal(stageCount_, progressSteps),
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;
char one[64], two[64];
formatTestTarget(packet.requestedHz, packet.requestedPulseNs, one, sizeof(one));
if (reason == FailReason::NONE) {
if (packet.measuredHzX10) {
formatMeasured(packet.measuredHzX10 / 10.0f, packet.measuredPulseNs, two, sizeof(two));
} else snprintf(two, sizeof(two), "%s", UiText::NO_MEASUREMENT);
} else if (reason == FailReason::PERIOD_OUT && packet.measuredHzX10) {
char frequency[12];
Display::formatFrequency(packet.measuredHzX10 / 10.0f, frequency, sizeof(frequency));
snprintf(two, sizeof(two), UiText::PERIOD_OUT_FORMAT, frequency);
} else if (reason == FailReason::DUTY_OUT && packet.measuredPulseNs) {
char pulse[12];
Display::formatPulse(packet.measuredPulseNs, pulse, sizeof(pulse), true);
snprintf(two, sizeof(two), UiText::DUTY_OUT_FORMAT, pulse);
} else {
snprintf(two, sizeof(two), "%s", uiFailName(reason));
}
if (reason != FailReason::NONE)
formatFailure(reason, packet.requestedHz, packet.requestedPulseNs, one, sizeof(one));
display_.show(one, two, overallProgress(stageIndex_, packet.progressStep),
overallProgressTotal(stageCount_), reason == FailReason::NONE ? nullptr :
roleCorner(static_cast<Role>(settings_.role)));
}
void App::fillMeasuredResult(ProtocolPacket &packet, const StageStats &stats) const {
packet.reason = static_cast<uint8_t>(stats.reason);
packet.periods = stats.periods;
if (!stats.periods || !stats.periodSum) return;
const bool badPeriod = (stats.reason == FailReason::PERIOD_OUT || stats.reason == FailReason::DUTY_OUT) &&
stats.badFrequency > 0.0f;
const float measuredHz = badPeriod ? stats.badFrequency :
static_cast<float>(receiver_.tickHz()) * stats.periods / stats.periodSum;
packet.measuredHzX10 = static_cast<uint32_t>(lroundf(measuredHz * 10.0f));
packet.measuredPulseNs = badPeriod ? pulseFromDuty(measuredHz, stats.badDuty) :
static_cast<uint32_t>(lround(static_cast<double>(stats.activeSum) * 1000000000.0 /
(static_cast<uint64_t>(receiver_.pulseTickHz()) * stats.periods)));
}
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),
overallProgressTotal(stageCount_));
}

View File

@@ -1,14 +1,16 @@
#pragma once #pragma once
#include "Buttons.h" #include "Buttons.h"
#include "Display.h" #include "Display.h"
#include "DriverTest.h"
#include "Measurement.h" #include "Measurement.h"
#include "OpticalCurrent.h"
#include "Pwm.h" #include "Pwm.h"
#include "Radio.h" #include "Radio.h"
#include "SettingsStore.h" #include "SettingsStore.h"
enum class AppState : uint8_t { enum class AppState : uint8_t {
IDLE, MENU, SOLO_MEASURE, MASTER_DISCOVER, MASTER_WAIT_READY, IDLE, MENU, BOARD_TEST, SOLO_MEASURE, SOLO_DRIVER, MASTER_DISCOVER, MASTER_WAIT_READY,
MASTER_WAIT_RESULT, SLAVE_READY, SLAVE_WAIT_START, SLAVE_MEASURE, MASTER_WAIT_RESULT, MASTER_FINALIZE, SLAVE_READY, SLAVE_WAIT_START, SLAVE_MEASURE,
SLAVE_WAIT_ACK, FINISHED SLAVE_WAIT_ACK, FINISHED
}; };
@@ -22,26 +24,52 @@ class App {
void finishInitialization(bool factoryReset); void finishInitialization(bool factoryReset);
void showMenu(); void showMenu();
void changeMenu(int direction); void changeMenu(int direction);
void enterOpticalCalibration(uint32_t now);
void updateOpticalCalibration(uint32_t now, bool force = false);
void leaveOpticalCalibration();
void cycleRunMode();
void sanitizeRange(); void sanitizeRange();
void startTest(); void startTest();
bool armSlave(); void startBoardTest();
bool prepareStage(); void updateBoardTest(uint32_t now);
void stopBoardTest();
bool armSlave(bool preserveDisplay = false);
bool prepareStage(bool showProgress = true);
bool startLocalMeasurement(float hz, float duty); bool startLocalMeasurement(float hz, float duty);
bool startDriverMeasurement();
void startMasterDiscovery(); void startMasterDiscovery();
void handleRadio(); void handleRadio();
void updateMaster(); void updateMaster();
void updateSlave(); void updateSlave();
void stagePassed(); void stagePassed();
void finish(bool pass, FailReason reason); void finish(bool pass, FailReason reason, bool preserveDisplay = false);
void abortTest(); void abortTest();
void sendAbort(); void sendAbort(FailReason reason = FailReason::ABORTED);
void printConfiguration(); void printConfiguration();
void printStageStats(const StageStats &s, uint32_t hz); void printStageStats(const StageStats &s, uint32_t hz);
void showStageResult(const StageStats &s);
void showDriverResult(const DriverStats &s, bool testPassed = false);
void showRemoteResult(const ProtocolPacket &packet);
void fillMeasuredResult(ProtocolPacket &packet, const StageStats &stats) const;
void showStageProgress();
uint64_t actualNominalTotalUs(); uint64_t actualNominalTotalUs();
ProtocolPacket makePacket(MessageType type) const; ProtocolPacket makePacket(MessageType type) const;
bool sendLinked(ProtocolPacket packet);
void sendCurrent(MessageType type); void sendCurrent(MessageType type);
void updateHeartbeat(); void updateHeartbeat();
bool packetForCurrent(const ProtocolPacket &p) const; bool packetForCurrent(const ProtocolPacket &p) const;
void serviceIdlePowerSave();
void serviceSerialConsole();
void handleSerialCommand(char *line);
void printSerialHelp();
void printSerialStatus();
bool serialSettingsMutable() const;
void finishSerialSettingsChange();
void leaveIdlePowerSave(bool wakeDisplay = true);
bool idlePowerSaveAllowed() const;
bool usbHostPresent() const;
void setStandbyOpticalOutput();
void setActivePerformance(bool active);
Button startButton_, modeButton_; Button startButton_, modeButton_;
Display display_; Display display_;
@@ -51,11 +79,12 @@ class App {
PwmGenerator pwm_; PwmGenerator pwm_;
PulseReceiver receiver_; PulseReceiver receiver_;
Measurement measurement_; Measurement measurement_;
DriverTest driverTest_;
Radio radio_; Radio radio_;
AppState state_ = AppState::IDLE; AppState state_ = AppState::IDLE;
uint8_t menuItem_ = 0; uint8_t menuItem_ = 0;
uint32_t stageIndex_ = 0, stageCount_ = 0; uint32_t stageIndex_ = 0, stageCount_ = 0;
uint32_t requestedHz_ = 0; uint32_t requestedHz_ = 0, requestedPulseNs_ = 0;
ActualPwm actual_ = {}; ActualPwm actual_ = {};
FailReason pendingReason_ = FailReason::NONE; FailReason pendingReason_ = FailReason::NONE;
uint32_t session_ = 0; uint32_t session_ = 0;
@@ -63,12 +92,28 @@ class App {
uint8_t peer_[6] = {}; uint8_t peer_[6] = {};
bool havePeer_ = false; bool havePeer_ = false;
uint32_t deadlineMs_ = 0, lastSendMs_ = 0; uint32_t deadlineMs_ = 0, lastSendMs_ = 0;
uint32_t localMeasurementDeadlineMs_ = 0;
uint32_t lastHeartbeatMs_ = 0, lastPeerSeenMs_ = 0; uint32_t lastHeartbeatMs_ = 0, lastPeerSeenMs_ = 0;
uint8_t retries_ = 0; uint8_t retries_ = 0;
ProtocolPacket pendingPacket_ = {}; ProtocolPacket pendingPacket_ = {};
bool initialized_ = false, bootResetCandidate_ = false; bool initialized_ = false, bootResetCandidate_ = false;
bool sweepHadDataLoss_ = false;
uint32_t firstDataLossHz_ = 0;
uint32_t bootCheckStartedMs_ = 0; uint32_t bootCheckStartedMs_ = 0;
uint32_t slaveRearmAtMs_ = 0; uint32_t slaveRearmAtMs_ = 0;
bool stageStartConfirmed_ = false;
uint32_t lastUserActivityMs_ = 0;
bool idlePowerSave_ = false;
bool idleSleepRadioStopped_ = false;
uint32_t lastOpticalWakeToggleMs_ = 0;
bool opticalWakeActive_ = false;
bool rxPinStateKnown_ = false, rxPinState_ = false;
uint32_t boardTestUpdatedMs_ = 0;
uint32_t boardDisplayUpdatedMs_ = 0;
bool boardPwmLightOn_ = false;
bool opticalCalibrationActive_ = false;
uint32_t opticalCalibrationStartedMs_ = 0;
uint32_t opticalCalibrationUpdatedMs_ = 0;
mutable uint32_t lastUsbHostSeenMs_ = 0;
char serialLine_[96] = {};
uint8_t serialLineLength_ = 0;
bool serialLineOverflow_ = false;
}; };

View File

@@ -7,8 +7,21 @@ void Button::begin() {
changedAt_ = millis(); changedAt_ = millis();
} }
void Button::suppressUntilRelease() {
suppressed_ = true;
raw_ = stable_ = (digitalRead(pin_) == BUTTON_ACTIVE_LEVEL);
changedAt_ = millis();
longSent_ = true;
}
ButtonEvent Button::update(uint32_t now) { ButtonEvent Button::update(uint32_t now) {
const bool sample = (digitalRead(pin_) == BUTTON_ACTIVE_LEVEL); const bool sample = (digitalRead(pin_) == BUTTON_ACTIVE_LEVEL);
if (suppressed_) {
raw_ = stable_ = sample;
changedAt_ = now;
if (!sample) { suppressed_ = false; longSent_ = false; }
return ButtonEvent::NONE;
}
if (sample != raw_) { raw_ = sample; changedAt_ = now; } if (sample != raw_) { raw_ = sample; changedAt_ = now; }
if (raw_ != stable_ && now - changedAt_ >= BUTTON_DEBOUNCE_MS) { if (raw_ != stable_ && now - changedAt_ >= BUTTON_DEBOUNCE_MS) {
stable_ = raw_; stable_ = raw_;

View File

@@ -8,10 +8,12 @@ class Button {
explicit Button(uint8_t pin) : pin_(pin) {} explicit Button(uint8_t pin) : pin_(pin) {}
void begin(); void begin();
ButtonEvent update(uint32_t nowMs); ButtonEvent update(uint32_t nowMs);
void suppressUntilRelease();
bool pressed() const { return stable_; } bool pressed() const { return stable_; }
private: private:
uint8_t pin_; uint8_t pin_;
bool raw_ = false, stable_ = false, longSent_ = false; bool raw_ = false, stable_ = false, longSent_ = false;
bool suppressed_ = false;
uint32_t changedAt_ = 0, pressedAt_ = 0, nextRepeat_ = 0; uint32_t changedAt_ = 0, pressedAt_ = 0, nextRepeat_ = 0;
}; };

View File

@@ -3,42 +3,138 @@
#include <Arduino.h> #include <Arduino.h>
// ------------------------- Hardware configuration ------------------------- // ------------------------- 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
// 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
#ifndef BOARD_RX_INTERFACE
#define BOARD_RX_INTERFACE BOARD_RX_INTERFACE_DIGITAL
#endif
#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 #if CONFIG_IDF_TARGET_ESP32C3
constexpr bool TARGET_IS_C3 = true; constexpr bool TARGET_IS_C3 = true;
constexpr uint8_t GPIO_PWM = 3; constexpr uint8_t GPIO_PWM = 3;
constexpr uint8_t GPIO_RX = 4; 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_MODE = 0;
constexpr uint8_t GPIO_BUTTON_START = 1; constexpr uint8_t GPIO_BUTTON_START = 1;
constexpr uint8_t GPIO_OPTICAL_CURRENT = 2;
constexpr uint8_t GPIO_OPTICAL_VCC = 5;
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_OPTICAL_CURRENT = 1;
constexpr uint8_t GPIO_OPTICAL_VCC = 5;
constexpr uint8_t GPIO_VBAT = 2;
constexpr uint8_t GPIO_ANALOG_RX = 0;
#endif
constexpr uint8_t GPIO_SDA = 6; constexpr uint8_t GPIO_SDA = 6;
constexpr uint8_t GPIO_SCL = 7; constexpr uint8_t GPIO_SCL = 7;
#elif CONFIG_IDF_TARGET_ESP32S3 #elif CONFIG_IDF_TARGET_ESP32S3
constexpr bool TARGET_IS_C3 = false; constexpr bool TARGET_IS_C3 = false;
constexpr uint8_t GPIO_PWM = 4; #ifdef MAKETKA
constexpr uint8_t GPIO_RX = 5; constexpr bool BOARD_ADC_AVAILABLE = false;
constexpr uint8_t GPIO_BUTTON_MODE = 6; // Same physical header contacts as the C3 MAKETKA profile when 5V/GND align.
constexpr uint8_t GPIO_BUTTON_START = 7; constexpr uint8_t GPIO_PWM = 12;
constexpr uint8_t GPIO_SDA = 8; constexpr uint8_t GPIO_RX = 13;
constexpr uint8_t GPIO_SCL = 9; constexpr uint8_t GPIO_BUTTON_MODE = 9;
constexpr uint8_t GPIO_BUTTON_START = 10;
constexpr uint8_t GPIO_OPTICAL_CURRENT = 2;
constexpr uint8_t GPIO_OPTICAL_VCC = 6;
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;
constexpr uint8_t GPIO_RX = 13;
constexpr uint8_t GPIO_BUTTON_MODE = 5;
constexpr uint8_t GPIO_BUTTON_START = 4;
constexpr uint8_t GPIO_OPTICAL_CURRENT = 10;
constexpr uint8_t GPIO_OPTICAL_VCC = 6;
constexpr uint8_t GPIO_SDA = 44;
constexpr uint8_t GPIO_SCL = 1;
constexpr uint8_t GPIO_VBAT = 11;
constexpr uint8_t GPIO_ANALOG_RX = 9;
#endif
#else #else
#error "Only ESP32-C3 and ESP32-S3 are supported" #error "Only ESP32-C3 and ESP32-S3 are supported"
#endif #endif
static_assert(GPIO_OPTICAL_CURRENT != GPIO_PWM &&
GPIO_OPTICAL_CURRENT != GPIO_RX &&
GPIO_OPTICAL_CURRENT != GPIO_BUTTON_MODE &&
GPIO_OPTICAL_CURRENT != GPIO_BUTTON_START &&
GPIO_OPTICAL_CURRENT != GPIO_SDA &&
GPIO_OPTICAL_CURRENT != GPIO_SCL,
"Optical-current ADC GPIO conflicts with another board signal");
static_assert(GPIO_OPTICAL_VCC != GPIO_OPTICAL_CURRENT &&
GPIO_OPTICAL_VCC != GPIO_PWM &&
GPIO_OPTICAL_VCC != GPIO_RX &&
GPIO_OPTICAL_VCC != GPIO_BUTTON_MODE &&
GPIO_OPTICAL_VCC != GPIO_BUTTON_START &&
GPIO_OPTICAL_VCC != GPIO_SDA &&
GPIO_OPTICAL_VCC != GPIO_SCL,
"Optical-VCC ADC GPIO conflicts with another board signal");
constexpr uint8_t OLED_ROTATION = 0; constexpr uint8_t OLED_ROTATION = 0;
constexpr uint8_t OLED_ADDRESS = 0x3C; constexpr uint8_t OLED_ADDRESS = 0x3C;
constexpr uint8_t ESPNOW_WIFI_CHANNEL = 6; constexpr uint8_t ESPNOW_WIFI_CHANNEL = 6;
constexpr uint32_t SERIAL_BAUD = 115200; 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 float OPTICAL_VCC = 5.1f;
constexpr float OPTICAL_SENSE_R = 47.0f;
constexpr float OPTICAL_DIVIDER_RATIO = 2.0f;
constexpr uint32_t OPTICAL_CURRENT_SETTLE_MS = 20;
constexpr uint32_t OPTICAL_CURRENT_AVERAGING_MS = 200;
constexpr bool SERIAL_ACTION_LOG = true; constexpr bool SERIAL_ACTION_LOG = true;
constexpr bool SERIAL_LOG_TIMESTAMPS = true; 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 // Fixed PCB conversion between electrical GPIO levels and actual optical
#define PWM_SAFE_LEVEL LOW // light. User settings HH/HL/LH/LL operate only in the optical domain and
// never change these hardware facts.
#define TX_LIGHT_ON_GPIO_LEVEL LOW
#define RX_LIGHT_ON_GPIO_LEVEL LOW
#define TX_LIGHT_OFF_GPIO_LEVEL (TX_LIGHT_ON_GPIO_LEVEL == HIGH ? LOW : HIGH)
#define PWM_SETTLE_CYCLES 5U #define PWM_SETTLE_CYCLES 5U
constexpr uint32_t BUTTON_DEBOUNCE_MS = 30; constexpr uint32_t BUTTON_DEBOUNCE_MS = 30;
constexpr uint32_t BUTTON_LONG_PRESS_MS = 800; constexpr uint32_t BUTTON_LONG_PRESS_MS = 500;
constexpr uint32_t BUTTON_REPEAT_DELAY_MS = 600; constexpr uint32_t BUTTON_REPEAT_DELAY_MS = 600;
constexpr uint32_t BUTTON_REPEAT_MS = 180; constexpr uint32_t BUTTON_REPEAT_MS = 180;
constexpr uint32_t FACTORY_RESET_HOLD_MS = 1500; constexpr uint32_t FACTORY_RESET_HOLD_MS = 1500;
@@ -46,32 +142,92 @@ constexpr uint32_t FACTORY_RESET_HOLD_MS = 1500;
constexpr uint32_t LINK_REPLY_TIMEOUT_MS = 1500; 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;
// During discovery Master alternates actual optical light ON and OFF 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 uint8_t FINAL_ACK_RETRIES = 2;
constexpr uint8_t NO_SIGNAL_TIMEOUT_PERIODS = 8; constexpr uint8_t NO_SIGNAL_TIMEOUT_PERIODS = 8;
constexpr uint16_t RMT_MIN_RECEIVE_SYMBOLS = 48;
constexpr uint16_t RMT_MAX_RECEIVE_SYMBOLS = 512;
constexpr uint32_t RMT_TARGET_CHUNK_US = 5000;
constexpr uint8_t RMT_QUEUE_BLOCKS = 8;
constexpr uint16_t PERIOD_BATCH_SIZE = 128; constexpr uint16_t PERIOD_BATCH_SIZE = 128;
// Retained as the minimum statistical depth used by the hardware-resolution
// calculation and diagnostics. PASS/FAIL is evaluated for every complete
// pulse independently; accumulated values are used only for display.
constexpr uint16_t MEASUREMENT_AVERAGING_PERIODS = 100;
static_assert(MEASUREMENT_AVERAGING_PERIODS > 0,
"Averaging window must contain at least one period");
constexpr uint8_t MEASUREMENT_PROGRESS_STEPS = 10;
constexpr uint32_t OLED_PROGRESS_UPDATE_MS = 15;
constexpr uint8_t DRIVER_SHORT_SAMPLE_PROGRESS_STEPS = 10;
constexpr uint32_t DRIVER_PROGRESS_INTERVAL_MS = 100;
constexpr uint32_t IDLE_POWER_SAVE_TIMEOUT_MS = 60000;
// usb_serial_jtag_is_connected() needs no open COM port or CDC traffic, but a
// short SOF detection gap must not send the board to sleep.
constexpr uint32_t USB_HOST_DISCONNECT_GRACE_MS = 5000;
constexpr uint16_t SLAVE_LISTEN_INTERVAL_MS = 100;
constexpr uint16_t SLAVE_LISTEN_WINDOW_MS = 20;
static_assert(SLAVE_LISTEN_WINDOW_MS < SLAVE_LISTEN_INTERVAL_MS,
"Slave listen window must be shorter than its interval");
// Conservative sustained validation rate calibrated from real C3 logs.
constexpr uint32_t RX_PROCESSING_PERIODS_PER_SECOND = 300000;
constexpr uint32_t C3_STRICT_MAX_HZ = 1000000; constexpr uint32_t C3_STRICT_MAX_HZ = 1000000;
constexpr uint32_t S3_STRICT_MAX_HZ = 1000000; constexpr uint32_t S3_STRICT_MAX_HZ = 1000000;
constexpr uint32_t CAPTURE_RESOLUTION_HZ = 80000000; // S3 MCPWM Capture uses one 32-bit 80 MHz timer for both edges. Unlike RMT,
// Arduino-ESP32 uses the 40 MHz crystal as the default LEDC clock on C3/S3. // its width does not constrain long LOW/HIGH intervals, so capture precision
// stays at 12.5 ns for every selectable PWM frequency and pulse length.
constexpr uint32_t MCPWM_CAPTURE_RESOLUTION_HZ = 80000000;
// C3 uses the 40 MHz crystal as the LEDC clock.
// Keep this explicit so the resolution calculation never asks LEDC for an // Keep this explicit so the resolution calculation never asks LEDC for an
// impossible frequency/resolution combination. // impossible frequency/resolution combination.
constexpr uint32_t LEDC_SOURCE_CLOCK_HZ = 40000000; constexpr uint32_t LEDC_SOURCE_CLOCK_HZ = 40000000;
constexpr uint8_t LEDC_CHANNEL = 0; constexpr uint8_t LEDC_CHANNEL = 0;
constexpr uint8_t LEDC_MAX_BITS = 14; constexpr uint8_t LEDC_MAX_BITS = 14;
// S3 uses the dedicated MCPWM peripheral. A 20 MHz timer clock keeps the
// selectable 500 Hz period within the S3's 16-bit counter while retaining
// 50 ns pulse resolution and exact periods for every menu frequency.
constexpr uint32_t MCPWM_RESOLUTION_HZ = 20000000;
constexpr uint32_t MCPWM_MAX_PERIOD_TICKS = 65535;
// Concept 1SP0635 status acknowledgement, expressed in the optical domain.
constexpr uint32_t DRIVER_MIN_INPUT_PULSE_NS = 2000;
constexpr uint32_t DRIVER_ACK_DELAY_NS = 250;
constexpr uint32_t DRIVER_ACK_WIDTH_NS = 700;
constexpr uint32_t DRIVER_ACK_START_MAX_NS = 2000;
constexpr uint32_t DRIVER_ACK_MERGE_MARGIN_NS = 250;
// The first MCPWM TX end may belong to a pulse that was already active when
// capture was enabled. The following period also drains capture events that
// were pending independently in the rising/falling channels. Validation
// therefore begins at the third TX period.
constexpr uint8_t DRIVER_CAPTURE_SYNC_CYCLES = 2;
// Any response this long is a fault, not a normal acknowledgement.
constexpr uint32_t DRIVER_FAULT_MIN_NS = 1500;
constexpr uint32_t DRIVER_RX_STUCK_MIN_NS = 20000;
// Retained by the generic receiver backend; the driver test itself uses the
// stricter ACK start deadline above.
constexpr uint32_t DRIVER_RESPONSE_TIMEOUT_NS = 10000;
// -------------------------- Menu value arrays ----------------------------- // -------------------------- Menu value arrays -----------------------------
constexpr uint32_t START_FREQ_OPTIONS_HZ[] = {1000, 2000, 5000, 10000, 20000, 50000}; // The test uses one selected PWM frequency and walks the pulse-width list from
constexpr uint32_t END_FREQ_OPTIONS_HZ[] = {100000, 200000, 500000, 750000, 1000000}; // the selected maximum down to the selected minimum. Widths are stored in
constexpr uint32_t STEP_OPTIONS_HZ[] = {1000, 2000, 5000, 10000, 20000, 50000, 100000}; // nanoseconds so sub-microsecond pulses remain representable without floats.
constexpr uint32_t PWM_FREQUENCY_OPTIONS_HZ[] = {
500, 1000, 2000, 5000, 10000,
};
constexpr uint32_t MAX_PULSE_OPTIONS_NS[] = {
2000, 5000, 10000, 50000, 100000, 500000
};
constexpr uint32_t MIN_PULSE_OPTIONS_NS[] = {
250, 500, 1000, 2000, 5000, 10000, 50000
};
constexpr uint32_t TEST_PULSE_WIDTHS_NS[] = {
50, 100, 150, 200, 250, 500, 1000, 2000, 5000, 10000, 20000, 50000,
100000, 200000, 500000, 1000000
};
constexpr float ACCURACY_OPTIONS_PCT[] = {1.0f, 2.0f, 5.0f, 10.0f}; constexpr float ACCURACY_OPTIONS_PCT[] = {1.0f, 2.0f, 5.0f, 10.0f};
constexpr uint32_t TEST_TIME_OPTIONS_MS[] = {100, 250, 500, 1000, 2000, 5000}; constexpr uint32_t TEST_TIME_OPTIONS_MS[] = {100, 250, 500, 1000, 2000, 5000, 60000};
constexpr uint8_t REPEAT_OPTIONS[] = {1, 2, 3, 5, 10};
constexpr uint8_t DUTY_OPTIONS_PCT[] = {10, 25, 50, 75, 90};
template <typename T, size_t N> constexpr size_t countOf(const T (&)[N]) { return N; } template <typename T, size_t N> constexpr size_t countOf(const T (&)[N]) { return N; }

View File

@@ -0,0 +1,176 @@
#pragma once
// Select the language used by the OLED interface.
// Build with UI_LANGUAGE_EN for English or UI_LANGUAGE_RU for Russian.
#define UI_LANGUAGE_EN 0
#define UI_LANGUAGE_RU 1
#ifndef UI_LANGUAGE
#define UI_LANGUAGE UI_LANGUAGE_RU
#endif
namespace UiText {
#if UI_LANGUAGE == UI_LANGUAGE_RU
constexpr const char *ROLE_NAMES[] = {
"СОЛО", "МАСТЕР", "СЛЕЙВ"
};
constexpr const char *TEST_NAMES[] = {
"ОПТИКА", "ДРАЙВЕР"
};
constexpr const char *TEST_GROUP_NAMES[] = {
"ОПТИКА", "ПЛАТА"
};
constexpr const char *BOARD_TEST_NAMES[] = {
"АЦП", "ШИМ ВЫХОД", "RX ВХОД"
};
constexpr const char *FAIL_NAMES[] = {
"НЕТ ОШИБКИ",
"НЕТ СИГНАЛА",
"ПЕРИОД ВНЕ ДОПУСКА",
"ИМПУЛЬС ВНЕ ДОПУСКА",
"ЛИШНИЙ ФРОНТ",
"ИМПУЛЬСНАЯ ПОМЕХА",
"ПРОПУЩЕН ФРОНТ",
"ОШИБКА ПОТЕРИ ДАННЫХ",
"СВЯЗЬ ПОТЕРЯНА",
"РЕЖИМ НЕ ПОДДЕРЖИВ.",
"НЕ ХВАТАЕТ ТОЧНОСТИ",
"ТЕСТ ОСТАНОВЛЕН",
"НЕТ ОТВЕТА ACK",
"ТАЙМИНГ ACK",
"АВАРИЯ ДРАЙВЕРА",
"ОТВЕТЫ ACK СЛИЛИСЬ"
};
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 = "ТОЧНОСТЬ:";
constexpr const char *MENU_TEST_TIME = "ВРЕМЯ ВЫБОРКИ:";
constexpr const char *MENU_LIGHT_CODE = "АКТ. УРОВЕНЬ:";
constexpr const char *MENU_OPTICAL_CALIBRATION = "КАЛИБР. ОПТИКИ";
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 = "СЛЕЙВ ГОТОВ";
constexpr const char *WAIT_MASTER = "ОЖИДАНИЕ МАСТЕРА";
constexpr const char *LINK_FAILED = "СВЯЗЬ НЕ УСТАНОВЛЕНА";
constexpr const char *RADIO_ERROR = "ОШИБКА СВЯЗИ";
constexpr const char *MASTER_SEARCH = "ПОИСК СЛЕЙВА";
constexpr const char *HOLD_START_STOP = "УДЕРЖ. ПУСК ДЛЯ СТОП";
constexpr const char *MASTER_SEEN = "МАСТЕР ОБНАРУЖЕН";
constexpr const char *ACK_SENT = "ОТВЕТ ОТПРАВЛЕН";
constexpr const char *START_AGAIN = "ГОТОВ К ЗАПУСКУ";
constexpr const char *TEST_FAILED = "ТЕСТ НЕ ПРОЙДЕН";
constexpr const char *PASS_WORD = "ТЕСТ ПРОЙДЕН";
constexpr const char *FAIL_FORMAT = "СБОЙ %s";
constexpr const char *TEST_FORMAT = "%s, %s";
constexpr const char *TEST_TARGET_FORMAT = "ТЕСТ: %s";
constexpr const char *FAIL_TARGET_FORMAT = "СБОЙ: %s";
constexpr const char *PERIOD_OUT_FORMAT = "ЧАСТОТА: %s";
constexpr const char *DUTY_OUT_FORMAT = "ИМПУЛЬС: %s";
constexpr const char *NO_MEASUREMENT = "F:---, P:---";
constexpr const char *DRIVER_RESPONSE_FORMAT = "ACK:%lu D:%luns";
constexpr const char *DRIVER_MEASUREMENT_FORMAT = "D: %s, P: %s";
#elif UI_LANGUAGE == UI_LANGUAGE_EN
constexpr const char *ROLE_NAMES[] = {
"SOLO", "MASTER", "SLAVE"
};
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",
"PERIOD OUT",
"PULSE OUT",
"EXTRA EDGE",
"GLITCH",
"LOST EDGE",
"DATA LOSS ERROR",
"LINK LOST",
"UNSUPPORTED",
"RESOLUTION",
"ABORTED",
"ACK MISSING",
"ACK TIMING",
"DRIVER FAULT",
"ACK MERGED"
};
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:";
constexpr const char *MENU_TEST_TIME = "TEST TIME:";
constexpr const char *MENU_LIGHT_CODE = "ACTIVE LEVEL:";
constexpr const char *MENU_OPTICAL_CALIBRATION = "OPTICAL CALIBRATION";
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";
constexpr const char *WAIT_MASTER = "WAIT MASTER";
constexpr const char *LINK_FAILED = "LINK FAILED";
constexpr const char *RADIO_ERROR = "RADIO ERROR";
constexpr const char *MASTER_SEARCH = "MASTER SEARCH";
constexpr const char *HOLD_START_STOP = "HOLD START TO STOP";
constexpr const char *MASTER_SEEN = "MASTER SEEN";
constexpr const char *ACK_SENT = "ACK SENT";
constexpr const char *START_AGAIN = "READY TO START";
constexpr const char *TEST_FAILED = "TEST FAILED";
constexpr const char *PASS_WORD = "TEST PASS";
constexpr const char *FAIL_FORMAT = "FAIL %s";
constexpr const char *TEST_FORMAT = "%s, %s";
constexpr const char *TEST_TARGET_FORMAT = "TEST: %s";
constexpr const char *FAIL_TARGET_FORMAT = "FAIL AT %s";
constexpr const char *PERIOD_OUT_FORMAT = "FREQ OUT %s";
constexpr const char *DUTY_OUT_FORMAT = "PULSE OUT %s";
constexpr const char *NO_MEASUREMENT = "F:---, P:---";
constexpr const char *DRIVER_RESPONSE_FORMAT = "ACK:%lu D:%luns";
constexpr const char *DRIVER_MEASUREMENT_FORMAT = "D: %s, P: %s";
#else
#error "UI_LANGUAGE must be UI_LANGUAGE_EN or UI_LANGUAGE_RU"
#endif
} // namespace UiText

View File

@@ -1,4 +1,5 @@
#include "Core.h" #include "Core.h"
#include "Config.h"
#include <math.h> #include <math.h>
#include <string.h> #include <string.h>
@@ -8,10 +9,35 @@ const char *roleName(Role r) {
return i < 3 ? names[i] : "?"; 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);
return i < 4 ? names[i] : "??";
}
const char *failName(FailReason r) { const char *failName(FailReason r) {
static const char *names[] = {"NONE", "NO SIGNAL", "PERIOD OUT", "DUTY OUT", static const char *names[] = {"NONE", "NO SIGNAL", "PERIOD OUT", "PULSE OUT",
"EXTRA EDGE", "GLITCH", "LOST EDGE", "TOO FEW PERIODS", "LINK LOST", "EXTRA EDGE", "GLITCH", "LOST EDGE", "DATA LOSS ERROR", "LINK LOST",
"UNSUPPORTED", "RESOLUTION", "ABORTED", "DATA LOST"}; "UNSUPPORTED", "RESOLUTION", "ABORTED", "ACK MISSING", "ACK TIMING",
"DRIVER FAULT", "ACK MERGED"};
const uint8_t i = static_cast<uint8_t>(r); const uint8_t i = static_cast<uint8_t>(r);
return i < (sizeof(names) / sizeof(names[0])) ? names[i] : "UNKNOWN"; return i < (sizeof(names) / sizeof(names[0])) ? names[i] : "UNKNOWN";
} }
@@ -30,29 +56,52 @@ uint32_t settingsChecksum(const Settings &s) {
return hash; return hash;
} }
uint32_t frequencyPointCount(uint32_t startHz, uint32_t endHz, uint32_t stepHz) { bool txActiveLightOn(const Settings &s) {
if (!startHz || !stepHz || endHz <= startHz) return 0; return static_cast<uint8_t>(s.lightCode) < static_cast<uint8_t>(LightCode::LH);
const uint64_t span = static_cast<uint64_t>(endHz) - startHz;
return static_cast<uint32_t>(span / stepHz + 1U + ((span % stepHz) ? 1U : 0U));
} }
uint32_t frequencyAt(uint32_t startHz, uint32_t endHz, uint32_t stepHz, uint32_t index) { bool rxActiveLightOn(const Settings &s) {
const uint32_t count = frequencyPointCount(startHz, endHz, stepHz); return (static_cast<uint8_t>(s.lightCode) & 1U) == 0U;
if (!count || index >= count) return 0; }
if (index == count - 1) return endHz;
const uint64_t v = static_cast<uint64_t>(startHz) + static_cast<uint64_t>(stepHz) * index; uint32_t pulseWidthPointCount(uint32_t maxPulseNs, uint32_t minPulseNs) {
return v < endHz ? static_cast<uint32_t>(v) : endHz; if (!minPulseNs || maxPulseNs < minPulseNs) return 0;
uint32_t count = 0;
for (size_t i = 0; i < countOf(TEST_PULSE_WIDTHS_NS); ++i)
if (TEST_PULSE_WIDTHS_NS[i] >= minPulseNs && TEST_PULSE_WIDTHS_NS[i] <= maxPulseNs) ++count;
return count;
}
uint32_t pulseWidthAt(uint32_t maxPulseNs, uint32_t minPulseNs, uint32_t index) {
for (size_t i = countOf(TEST_PULSE_WIDTHS_NS); i > 0; --i) {
const uint32_t pulseNs = TEST_PULSE_WIDTHS_NS[i - 1U];
if (pulseNs < minPulseNs || pulseNs > maxPulseNs) continue;
if (!index--) return pulseNs;
}
return 0;
}
uint64_t nominalStageUs(uint32_t frequencyHz, uint32_t sampleTimeMs, uint32_t settleCycles) {
if (!frequencyHz || !sampleTimeMs) return 0;
const uint64_t sampleUs = static_cast<uint64_t>(sampleTimeMs) * 1000ULL;
// At high frequency the CPU needs longer than the requested sample window
// to validate every captured period. Use the measured sustained C3 rate.
const uint64_t processingUs =
(static_cast<uint64_t>(frequencyHz) * sampleTimeMs * 1000ULL +
RX_PROCESSING_PERIODS_PER_SECOND - 1U) / RX_PROCESSING_PERIODS_PER_SECOND;
const uint64_t samplingWallUs = processingUs > sampleUs ? processingUs : sampleUs;
const uint64_t settleUs =
(1000000ULL * settleCycles * MEASUREMENT_PROGRESS_STEPS + frequencyHz - 1U) / frequencyHz;
// Initial stage screen, nine intermediate screens and the final result.
const uint64_t displayUs = static_cast<uint64_t>(OLED_PROGRESS_UPDATE_MS) * 1000ULL *
(MEASUREMENT_PROGRESS_STEPS + 1U);
return samplingWallUs + settleUs + displayUs;
} }
uint64_t nominalTotalUs(const TestParams &p, uint32_t settleCycles) { uint64_t nominalTotalUs(const TestParams &p, uint32_t settleCycles) {
uint64_t total = 0; return static_cast<uint64_t>(pulseWidthPointCount(p.maxPulseNs, p.minPulseNs)) *
const uint32_t count = frequencyPointCount(p.startHz, p.endHz, p.stepHz); nominalStageUs(p.frequencyHz, p.testTimeMs, settleCycles);
for (uint32_t i = 0; i < count; ++i) {
const uint32_t f = frequencyAt(p.startHz, p.endHz, p.stepHz, i);
total += (1000000ULL * settleCycles + f - 1) / f;
total += static_cast<uint64_t>(p.testTimeMs) * 1000ULL * p.repeats;
}
return total;
} }
bool periodWithin(float measured, float expected, float tolerance) { bool periodWithin(float measured, float expected, float tolerance) {
@@ -63,6 +112,10 @@ bool dutyWithin(float measured, float expected, float tolerance) {
return fabsf(measured - expected) <= tolerance + 0.0001f; return fabsf(measured - expected) <= tolerance + 0.0001f;
} }
float effectiveTolerancePct(float configured) {
return configured;
}
uint8_t choosePwmResolution(uint32_t frequencyHz, uint32_t sourceClockHz, uint8_t choosePwmResolution(uint32_t frequencyHz, uint32_t sourceClockHz,
uint8_t maxBits) { uint8_t maxBits) {
if (!frequencyHz || !sourceClockHz || !maxBits) return 0; if (!frequencyHz || !sourceClockHz || !maxBits) return 0;
@@ -72,37 +125,140 @@ uint8_t choosePwmResolution(uint32_t frequencyHz, uint32_t sourceClockHz,
return bits; return bits;
} }
uint8_t chooseStablePwmResolution(uint32_t frequencyHz, uint32_t sourceClockHz,
uint8_t maxBits, uint8_t dutyPct) {
const uint8_t fallback = choosePwmResolution(frequencyHz, sourceClockHz, maxBits);
if (!fallback || dutyPct > 100U) return fallback;
for (uint8_t bits = fallback; bits > 0; --bits) {
const uint32_t levels = 1UL << bits;
const uint64_t denominator = static_cast<uint64_t>(frequencyHz) * levels;
if (denominator > sourceClockHz || sourceClockHz % denominator) continue;
const uint32_t divider = static_cast<uint32_t>(sourceClockHz / denominator);
if (!divider || divider > 1024U) continue;
if ((static_cast<uint32_t>(levels) * dutyPct) % 100U == 0U) return bits;
}
return fallback;
}
bool choosePwmConfig(uint32_t requestedHz, uint32_t requestedPulseNs,
uint32_t sourceClockHz, uint8_t maxBits,
IntegerPwmConfig &config) {
if (!requestedHz || !requestedPulseNs || !sourceClockHz || !maxBits) return false;
constexpr uint32_t FRACTION_SCALE = 256U;
constexpr uint32_t MAX_DIVIDER_RAW = 1024U * FRACTION_SCALE - 1U;
bool found = false;
uint32_t bestFrequencyError = UINT32_MAX;
uint32_t bestPulseError = UINT32_MAX;
for (uint8_t bits = 1; bits <= maxBits && bits < 31; ++bits) {
const uint32_t levels = 1UL << bits;
const uint64_t dividerNumerator = static_cast<uint64_t>(sourceClockHz) * FRACTION_SCALE;
const uint64_t dividerDenominator = static_cast<uint64_t>(requestedHz) * levels;
const uint32_t dividerFloor = static_cast<uint32_t>(dividerNumerator / dividerDenominator);
const uint32_t candidates[] = {dividerFloor, dividerFloor + 1U};
for (uint32_t dividerRaw : candidates) {
if (dividerRaw < FRACTION_SCALE || dividerRaw > MAX_DIVIDER_RAW) continue;
const uint64_t frequencyDenominator = static_cast<uint64_t>(levels) * dividerRaw;
const uint32_t actualHz = static_cast<uint32_t>(
(dividerNumerator + frequencyDenominator / 2U) / frequencyDenominator);
if (!actualHz) continue;
const uint64_t dutyNumerator = static_cast<uint64_t>(requestedPulseNs) *
sourceClockHz * FRACTION_SCALE;
const uint64_t dutyDenominator = static_cast<uint64_t>(dividerRaw) * 1000000000ULL;
uint32_t dutyCount = static_cast<uint32_t>((dutyNumerator + dutyDenominator / 2U) /
dutyDenominator);
if (!dutyCount) dutyCount = 1U;
if (dutyCount >= levels) dutyCount = levels - 1U;
if (!dutyCount) continue;
const uint32_t actualPulseNs = static_cast<uint32_t>(
(static_cast<uint64_t>(dutyCount) * dividerRaw * 1000000000ULL +
static_cast<uint64_t>(sourceClockHz) * FRACTION_SCALE / 2U) /
(static_cast<uint64_t>(sourceClockHz) * FRACTION_SCALE));
const uint32_t frequencyError = actualHz > requestedHz ? actualHz - requestedHz : requestedHz - actualHz;
const uint32_t pulseError = actualPulseNs > requestedPulseNs
? actualPulseNs - requestedPulseNs : requestedPulseNs - actualPulseNs;
if (found && (frequencyError > bestFrequencyError ||
(frequencyError == bestFrequencyError && pulseError > bestPulseError) ||
(frequencyError == bestFrequencyError && pulseError == bestPulseError && bits <= config.bits)))
continue;
config = {actualHz, dividerRaw, dutyCount, actualPulseNs, bits};
bestFrequencyError = frequencyError;
bestPulseError = pulseError;
found = true;
}
}
return found;
}
FailReason validateResolution(uint32_t frequencyHz, float dutyPct, float accuracyPct, FailReason validateResolution(uint32_t frequencyHz, float dutyPct, float accuracyPct,
uint32_t captureHz, uint8_t pwmBits) { uint32_t periodCaptureHz, uint32_t pulseCaptureHz,
if (!frequencyHz || !captureHz || !pwmBits) return FailReason::RESOLUTION; uint8_t pwmBits,
const float periodTicks = static_cast<float>(captureHz) / frequencyHz; uint16_t averagingPeriods) {
const float activeTicks = periodTicks * dutyPct / 100.0f; if (!frequencyHz || !periodCaptureHz || !pulseCaptureHz || !pwmBits || !averagingPeriods)
const float inactiveTicks = periodTicks - activeTicks; return FailReason::RESOLUTION;
if (periodTicks < 4.0f || activeTicks < 2.0f || inactiveTicks < 2.0f) return FailReason::RESOLUTION; const float periodTicks = static_cast<float>(periodCaptureHz) / frequencyHz;
const float activeTicks = static_cast<float>(pulseCaptureHz) * dutyPct /
(100.0f * frequencyHz);
if (periodTicks < 4.0f || activeTicks < 2.0f) return FailReason::RESOLUTION;
const float timerPeriodError = 100.0f / periodTicks; const float timerPeriodError = 100.0f / periodTicks;
const float timerDutyError = 100.0f / periodTicks; const float timerPulseError = 100.0f / activeTicks;
// Measurement uses the duty actually programmed into LEDC. A coarse PWM // Measurement uses the duty actually programmed into LEDC. A coarse PWM
// step is not itself an error when the requested value (e.g. 50%) is exactly // step is not itself an error when the requested value (e.g. 50%) is exactly
// representable; only the selected value's actual quantization matters. // representable; only the selected value's actual quantization matters.
return (timerPeriodError > accuracyPct || timerDutyError > accuracyPct) const float effectiveAccuracy = effectiveTolerancePct(accuracyPct);
return (timerPeriodError > effectiveAccuracy || timerPulseError > effectiveAccuracy)
? FailReason::RESOLUTION : FailReason::NONE; ? FailReason::RESOLUTION : FailReason::NONE;
} }
FailReason evaluatePeriod(const PulsePeriod &p, uint32_t tickHz, float expectedHz, FailReason evaluatePeriod(const PulsePeriod &p, uint32_t tickHz, float expectedHz,
float expectedDuty, float tolerance, uint8_t repeat, float expectedDuty, float tolerance, uint8_t repeat,
StageStats &s) { StageStats &s) {
if (!p.periodTicks || p.activeTicks >= p.periodTicks) return FailReason::EXTRA_EDGE; const uint32_t pulseTickHz = p.activeTickHz ? p.activeTickHz : tickHz;
if (!p.periodTicks || !p.activeTicks || !tickHz || !pulseTickHz || expectedHz <= 0.0f)
return FailReason::EXTRA_EDGE;
const float hz = static_cast<float>(tickHz) / p.periodTicks; const float hz = static_cast<float>(tickHz) / p.periodTicks;
const float duty = 100.0f * p.activeTicks / p.periodTicks; const float duty = static_cast<float>(100.0 * p.activeTicks * tickHz /
(static_cast<double>(pulseTickHz) * p.periodTicks));
++s.periods; ++s.periods;
s.periodSum += p.periodTicks; s.activeSum += p.activeTicks; s.periodSum += p.periodTicks; s.activeSum += p.activeTicks;
if (p.periodTicks < s.minPeriod) s.minPeriod = p.periodTicks; if (p.periodTicks < s.minPeriod) s.minPeriod = p.periodTicks;
if (p.periodTicks > s.maxPeriod) s.maxPeriod = p.periodTicks; if (p.periodTicks > s.maxPeriod) s.maxPeriod = p.periodTicks;
if (p.activeTicks < s.minActive) s.minActive = p.activeTicks; if (p.activeTicks < s.minActive) s.minActive = p.activeTicks;
if (p.activeTicks > s.maxActive) s.maxActive = p.activeTicks; if (p.activeTicks > s.maxActive) s.maxActive = p.activeTicks;
FailReason reason = FailReason::NONE; // Validate every complete period independently. A single capture tick is the
if (!periodWithin(hz, expectedHz, tolerance)) reason = FailReason::PERIOD_OUT; // unavoidable endpoint uncertainty, so only that one tick may be corrected
else if (!dutyWithin(duty, expectedDuty, tolerance)) reason = FailReason::DUTY_OUT; // toward the expected value. It cannot hide a larger isolated distortion.
bool frequencyOk = periodWithin(hz, expectedHz, tolerance);
if (!frequencyOk) {
uint32_t correctedPeriod = p.periodTicks;
if (hz > expectedHz) ++correctedPeriod;
else if (correctedPeriod > 1U) --correctedPeriod;
frequencyOk = periodWithin(static_cast<float>(tickHz) / correctedPeriod,
expectedHz, tolerance);
}
const double expectedPulseTicks = static_cast<double>(pulseTickHz) * expectedDuty /
(100.0 * expectedHz);
bool pulseOk = expectedPulseTicks > 0.0 &&
fabs(static_cast<double>(p.activeTicks) - expectedPulseTicks) * 100.0 /
expectedPulseTicks <= tolerance + 0.0001;
if (!pulseOk) {
uint32_t correctedActive = p.activeTicks;
if (correctedActive > expectedPulseTicks) {
if (correctedActive) --correctedActive;
} else {
++correctedActive;
}
pulseOk = fabs(static_cast<double>(correctedActive) - expectedPulseTicks) *
100.0 / expectedPulseTicks <= tolerance + 0.0001;
}
const FailReason reason = !frequencyOk ? FailReason::PERIOD_OUT :
(!pulseOk ? FailReason::DUTY_OUT : FailReason::NONE);
if (reason != FailReason::NONE && s.reason == FailReason::NONE) { if (reason != FailReason::NONE && s.reason == FailReason::NONE) {
s.reason = reason; s.firstBadPeriod = s.periods; s.firstBadRepeat = repeat; s.reason = reason; s.firstBadPeriod = s.periods; s.firstBadRepeat = repeat;
s.badFrequency = hz; s.badDuty = duty; s.badFrequency = hz; s.badDuty = duty;
@@ -162,3 +318,64 @@ FailReason evaluatePeriodFast(const PulsePeriod &p, uint32_t tickHz,
} }
return reason; return reason;
} }
FailReason evaluatePeriodWindow(uint64_t periodSum, uint64_t activeSum,
uint32_t periodCount, uint32_t tickHz,
float expectedHz, float expectedDuty,
float tolerance,
uint32_t minPeriod, uint32_t maxPeriod,
uint8_t repeat,
StageStats &s) {
if (!periodSum || !periodCount || activeSum >= periodSum || !tickHz)
return FailReason::EXTRA_EDGE;
const float hz = static_cast<float>(
static_cast<double>(tickHz) * periodCount / periodSum);
const float duty = static_cast<float>(
100.0 * static_cast<double>(activeSum) / periodSum);
bool frequencyOk = periodWithin(hz, expectedHz, tolerance);
if (!frequencyOk && maxPeriod == minPeriod + 1U) {
// At a tolerance boundary, alternating adjacent capture counts prove that the
// result is quantization-limited. Accept only when a one-tick correction
// toward the expected value returns the averaged frequency into tolerance.
// Consecutive periods telescope into one first-to-last edge interval, so
// the whole window has a one-tick endpoint uncertainty, not one tick per
// period.
uint64_t correctedPeriodSum = periodSum;
if (hz > expectedHz) ++correctedPeriodSum;
else if (periodSum > 1U) --correctedPeriodSum;
const float correctedHz = static_cast<float>(
static_cast<double>(tickHz) * periodCount / correctedPeriodSum);
frequencyOk = periodWithin(correctedHz, expectedHz, tolerance);
}
const double expectedPulseTicks = static_cast<double>(tickHz) * expectedDuty /
(100.0 * expectedHz);
const double measuredPulseTicks = static_cast<double>(activeSum) / periodCount;
bool pulseOk = expectedPulseTicks > 0.0 &&
fabs(measuredPulseTicks - expectedPulseTicks) * 100.0 / expectedPulseTicks <= tolerance + 0.0001;
if (!pulseOk) {
// Unlike full periods, active intervals do not telescope: every pulse is
// bounded by a different rising/falling edge pair. With slowly drifting
// asynchronous clocks an entire short window can therefore quantize to
// the same adjacent count (e.g. 41/80 for a true 50% duty). Apply one tick
// per active interval even when minActive == maxActive.
const bool dutyHigh = measuredPulseTicks > expectedPulseTicks;
const uint64_t correctedActive = dutyHigh
? (activeSum > periodCount ? activeSum - periodCount : 0U)
: activeSum + periodCount;
const double correctedPulseTicks = static_cast<double>(correctedActive) / periodCount;
pulseOk = fabs(correctedPulseTicks - expectedPulseTicks) * 100.0 /
expectedPulseTicks <= tolerance + 0.0001;
}
FailReason reason = !frequencyOk ? FailReason::PERIOD_OUT :
(!pulseOk ? FailReason::DUTY_OUT : FailReason::NONE);
if (reason != FailReason::NONE && s.reason == FailReason::NONE) {
s.reason = reason;
s.firstBadPeriod = s.periods >= periodCount ? s.periods - periodCount + 1U : 1U;
s.firstBadRepeat = repeat;
s.badFrequency = hz;
s.badDuty = duty;
}
return reason;
}

View File

@@ -4,41 +4,51 @@
#include <stddef.h> #include <stddef.h>
enum class Role : uint8_t { SOLO, MASTER, SLAVE }; 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 { enum class FailReason : uint8_t {
NONE, NO_SIGNAL, PERIOD_OUT, DUTY_OUT, EXTRA_EDGE, GLITCH, LOST_EDGE, NONE, NO_SIGNAL, PERIOD_OUT, DUTY_OUT, EXTRA_EDGE, GLITCH, LOST_EDGE,
TOO_FEW_PERIODS, LINK_LOST, UNSUPPORTED, RESOLUTION, ABORTED, DATA_LOST DATA_LOSS, LINK_LOST, UNSUPPORTED, RESOLUTION, ABORTED,
ACK_MISSING, ACK_TIMING, DRIVER_FAULT, ACK_MERGED
}; };
const char *roleName(Role role); 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); const char *failName(FailReason reason);
struct Settings { struct Settings {
uint16_t version; uint16_t version;
uint8_t role; uint8_t role;
uint8_t startIndex; uint8_t testKind;
uint8_t endIndex; uint8_t lightCode;
uint8_t stepIndex; uint8_t frequencyIndex;
uint8_t maxPulseIndex;
uint8_t minPulseIndex;
uint8_t accuracyIndex; uint8_t accuracyIndex;
uint8_t timeIndex; uint8_t timeIndex;
uint8_t repeatIndex; uint8_t testGroup;
uint8_t dutyIndex; uint8_t boardTest;
uint32_t checksum; uint32_t checksum;
}; };
struct TestParams { struct TestParams {
uint32_t startHz; uint32_t frequencyHz;
uint32_t endHz; uint32_t maxPulseNs;
uint32_t stepHz; uint32_t minPulseNs;
float accuracyPct; float accuracyPct;
uint32_t testTimeMs; uint32_t testTimeMs;
uint8_t repeats;
uint8_t dutyPct;
}; };
struct PulsePeriod { struct PulsePeriod {
uint64_t startTick; uint64_t startTick;
uint32_t periodTicks; uint32_t periodTicks;
uint32_t activeTicks; uint32_t activeTicks;
uint32_t activeTickHz;
}; };
struct StageStats { struct StageStats {
@@ -53,7 +63,7 @@ struct StageStats {
uint8_t firstBadRepeat; uint8_t firstBadRepeat;
float badFrequency; float badFrequency;
float badDuty; float badDuty;
uint32_t lostItems; uint32_t droppedItems;
FailReason reason; FailReason reason;
void reset(); void reset();
}; };
@@ -65,16 +75,35 @@ struct PeriodLimits {
uint32_t maxDutyX100; uint32_t maxDutyX100;
}; };
struct IntegerPwmConfig {
uint32_t actualHz;
uint32_t dividerRaw;
uint32_t dutyCount;
uint32_t actualPulseNs;
uint8_t bits;
};
uint32_t settingsChecksum(const Settings &s); uint32_t settingsChecksum(const Settings &s);
uint32_t frequencyPointCount(uint32_t startHz, uint32_t endHz, uint32_t stepHz); bool txActiveLightOn(const Settings &s);
uint32_t frequencyAt(uint32_t startHz, uint32_t endHz, uint32_t stepHz, uint32_t index); bool rxActiveLightOn(const Settings &s);
uint32_t pulseWidthPointCount(uint32_t maxPulseNs, uint32_t minPulseNs);
uint32_t pulseWidthAt(uint32_t maxPulseNs, uint32_t minPulseNs, uint32_t index);
uint64_t nominalStageUs(uint32_t frequencyHz, uint32_t sampleTimeMs, uint32_t settleCycles);
uint64_t nominalTotalUs(const TestParams &p, uint32_t settleCycles); uint64_t nominalTotalUs(const TestParams &p, uint32_t settleCycles);
bool periodWithin(float measuredHz, float expectedHz, float tolerancePct); bool periodWithin(float measuredHz, float expectedHz, float tolerancePct);
bool dutyWithin(float measuredPct, float expectedPct, float tolerancePct); bool dutyWithin(float measuredPct, float expectedPct, float tolerancePct);
float effectiveTolerancePct(float configuredPct);
uint8_t choosePwmResolution(uint32_t frequencyHz, uint32_t sourceClockHz, uint8_t choosePwmResolution(uint32_t frequencyHz, uint32_t sourceClockHz,
uint8_t maxBits); uint8_t maxBits);
uint8_t chooseStablePwmResolution(uint32_t frequencyHz, uint32_t sourceClockHz,
uint8_t maxBits, uint8_t dutyPct);
bool choosePwmConfig(uint32_t requestedHz, uint32_t requestedPulseNs,
uint32_t sourceClockHz, uint8_t maxBits,
IntegerPwmConfig &config);
FailReason validateResolution(uint32_t frequencyHz, float dutyPct, float accuracyPct, FailReason validateResolution(uint32_t frequencyHz, float dutyPct, float accuracyPct,
uint32_t captureResolutionHz, uint8_t pwmBits); uint32_t periodResolutionHz, uint32_t pulseResolutionHz,
uint8_t pwmBits,
uint16_t averagingPeriods);
FailReason evaluatePeriod(const PulsePeriod &period, uint32_t tickHz, float expectedHz, FailReason evaluatePeriod(const PulsePeriod &period, uint32_t tickHz, float expectedHz,
float expectedDuty, float tolerancePct, uint8_t repeat, float expectedDuty, float tolerancePct, uint8_t repeat,
StageStats &stats); StageStats &stats);
@@ -83,3 +112,10 @@ bool makePeriodLimits(uint32_t expectedHz, float expectedDuty, float tolerancePc
FailReason evaluatePeriodFast(const PulsePeriod &period, uint32_t tickHz, FailReason evaluatePeriodFast(const PulsePeriod &period, uint32_t tickHz,
const PeriodLimits &limits, uint8_t repeat, const PeriodLimits &limits, uint8_t repeat,
StageStats &stats); StageStats &stats);
FailReason evaluatePeriodWindow(uint64_t periodSum, uint64_t activeSum,
uint32_t periodCount, uint32_t tickHz,
float expectedHz, float expectedDuty,
float tolerancePct,
uint32_t minPeriod, uint32_t maxPeriod,
uint8_t repeat,
StageStats &stats);

View File

@@ -1,14 +1,32 @@
#include "Display.h" #include "Display.h"
#include "Config.h" #include "Config.h"
#include "Font_Cyrillic.h"
#include "Log.h" #include "Log.h"
#include <Wire.h> #include <Wire.h>
#include <esp_log.h> #include <esp_log.h>
#include <string.h> #include <string.h>
namespace {
uint32_t nextUtf8Codepoint(const char *&text) {
const uint8_t first = static_cast<uint8_t>(*text++);
if (first < 0x80U) return first;
if ((first & 0xE0U) == 0xC0U) {
const uint8_t second = static_cast<uint8_t>(*text);
if ((second & 0xC0U) == 0x80U) {
++text;
return ((first & 0x1FU) << 6) | (second & 0x3FU);
}
}
return '?';
}
}
Display::Display() : oled_(128, 32, &Wire, -1) {} Display::Display() : oled_(128, 32, &Wire, -1) {}
bool Display::begin() { bool Display::begin() {
Wire.begin(GPIO_SDA, GPIO_SCL); Wire.begin(GPIO_SDA, GPIO_SCL);
Wire.setClock(400000); // keeps a full 128x32 framebuffer update near 15 ms
Wire.setTimeOut(30); // a faulty/stretched I2C bus must not stall button polling for seconds
// An absent optional OLED produces a large burst of ESP-IDF NACK messages. // An absent optional OLED produces a large burst of ESP-IDF NACK messages.
// Probe it once and keep the I2C driver quiet when no display is connected. // Probe it once and keep the I2C driver quiet when no display is connected.
esp_log_level_set("i2c.master", ESP_LOG_NONE); esp_log_level_set("i2c.master", ESP_LOG_NONE);
@@ -23,39 +41,118 @@ bool Display::begin() {
oled_.setRotation(OLED_ROTATION); oled_.setRotation(OLED_ROTATION);
oled_.setTextColor(SSD1306_WHITE); oled_.setTextColor(SSD1306_WHITE);
oled_.setTextSize(1); oled_.setTextSize(1);
powered_ = true;
} }
Log::printf("OLED", "initialization %s, I2C address=0x%02X", ok_ ? "OK" : "FAILED", OLED_ADDRESS); Log::printf("OLED", "initialization %s, I2C address=0x%02X", ok_ ? "OK" : "FAILED", OLED_ADDRESS);
return ok_; return ok_;
} }
void Display::fit(char *s) { void Display::setPower(bool enabled) {
int16_t x, y; uint16_t w, h; if (!ok_ || powered_ == enabled) return;
while (*s) { oled_.ssd1306_command(enabled ? SSD1306_DISPLAYON : SSD1306_DISPLAYOFF);
oled_.getTextBounds(s, 0, 0, &x, &y, &w, &h); powered_ = enabled;
if (w <= 128) break; Log::printf("OLED", "display power %s", enabled ? "ON" : "OFF");
s[strlen(s) - 1] = '\0'; }
void Display::drawTextLine(const char *text, int16_t y, int16_t startX) {
int16_t x = startX;
while (text && *text && x + CyrillicFont::WIDTH <= oled_.width()) {
const uint32_t codepoint = nextUtf8Codepoint(text);
const uint8_t *glyph = CyrillicFont::glyph(codepoint);
if (glyph) {
for (uint8_t row = 0; row < CyrillicFont::HEIGHT; ++row) {
const uint8_t pixels = pgm_read_byte(glyph + row);
for (uint8_t column = 0; column < CyrillicFont::WIDTH; ++column)
if (pixels & (1U << (CyrillicFont::WIDTH - 1U - column)))
oled_.drawPixel(x + column, y + row, SSD1306_WHITE);
}
} else if (codepoint < 0x100U) {
oled_.drawChar(x, y, static_cast<unsigned char>(codepoint),
SSD1306_WHITE, SSD1306_BLACK, 1);
} else {
oled_.drawChar(x, y, '?', SSD1306_WHITE, SSD1306_BLACK, 1);
}
x += CyrillicFont::ADVANCE;
} }
} }
void Display::show(const char *a, const char *b) { void Display::show(const char *a, const char *b, uint32_t progress,
char one[32], two[32]; uint32_t progressTotal, const char *topRight) {
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.
Log::printf("UI", "%s | %s", one, two); Log::printf("UI", "%s | %s", one, two);
if (!ok_) return; if (!ok_) return;
fit(one); fit(two); setPower(true);
oled_.clearDisplay(); oled_.setCursor(0, 3); oled_.print(one); oled_.clearDisplay();
oled_.setCursor(0, 19); oled_.print(two); oled_.display(); drawTextLine(one, 3);
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 (progress > progressTotal) progress = progressTotal;
const uint16_t width = static_cast<uint16_t>(
(static_cast<uint64_t>(progress) * 128U + progressTotal - 1U) / progressTotal);
if (width) oled_.drawFastHLine(0, 31, width, SSD1306_WHITE);
}
oled_.display();
} }
void Display::formatFrequency(float hz, char *out, size_t n) { void Display::formatFrequency(float hz, char *out, size_t n) {
if (hz >= 1000000.0f) snprintf(out, n, "%.2fM", hz / 1000000.0f); float value = hz; const char *suffix = "Hz";
else if (hz >= 1000.0f) snprintf(out, n, "%.2fk", hz / 1000.0f); if (hz >= 999950.0f) { value = hz / 1000000.0f; suffix = "M"; }
else snprintf(out, n, "%.0fHz", hz); else if (hz >= 999.5f) { value = hz / 1000.0f; suffix = "k"; }
if (suffix[0] == 'M' && fabsf(value - roundf(value)) < 0.0005f)
snprintf(out, n, "%.0f%s", value, suffix);
else if (value >= 100.0f) snprintf(out, n, "%.1f%s", value, suffix);
else if (value >= 10.0f) snprintf(out, n, "%.2f%s", value, suffix);
else snprintf(out, n, "%.3f%s", value, suffix);
}
void Display::formatTestFrequency(uint32_t hz, char *out, size_t n) {
if (hz >= 1000000U && hz % 1000000U == 0)
snprintf(out, n, "%luM", hz / 1000000U);
else if (hz >= 1000U && hz % 1000U == 0)
snprintf(out, n, "%luk", hz / 1000U);
else if (hz >= 1000U) {
const float khz = hz / 1000.0f;
if (khz >= 100.0f) snprintf(out, n, "%.1fk", khz);
else if (khz >= 10.0f) snprintf(out, n, "%.2fk", khz);
else snprintf(out, n, "%.3fk", khz);
} else
snprintf(out, n, "%lu", hz);
}
void Display::formatPwmFrequency(uint32_t hz, char *out, size_t n) {
if (hz >= 1000000U && hz % 1000000U == 0U)
snprintf(out, n, "%luMHz", hz / 1000000U);
else if (hz >= 1000U && hz % 1000U == 0U)
snprintf(out, n, "%lukHz", hz / 1000U);
else if (hz >= 1000U)
snprintf(out, n, "%.3gkHz", hz / 1000.0f);
else
snprintf(out, n, "%luHz", hz);
}
void Display::formatPulse(uint32_t pulseNs, char *out, size_t n, bool measured) {
if (pulseNs >= 1000U) {
const float us = pulseNs / 1000.0f;
if (measured) snprintf(out, n, "%.2fu", us);
else if (pulseNs % 1000U == 0U) snprintf(out, n, "%luus", pulseNs / 1000U);
else snprintf(out, n, "%.2fus", us);
} else if (measured) {
snprintf(out, n, "%.3fu", pulseNs / 1000.0f);
} else {
snprintf(out, n, "%luns", pulseNs);
}
} }
void Display::formatDuration(uint64_t us, char *out, size_t n) { void Display::formatDuration(uint64_t us, char *out, size_t n) {
const uint64_t minutes = us / 60000000ULL; const uint64_t totalSeconds = (us + 999999ULL) / 1000000ULL;
if (minutes < 60) snprintf(out, n, "%02llu:%02llu", minutes, (us / 1000000ULL) % 60ULL); const uint64_t minutes = totalSeconds / 60ULL;
if (minutes < 60) snprintf(out, n, "%02llu:%02llu", minutes, totalSeconds % 60ULL);
else snprintf(out, n, "%llu:%02llu", minutes / 60ULL, minutes % 60ULL); else snprintf(out, n, "%llu:%02llu", minutes / 60ULL, minutes % 60ULL);
} }

View File

@@ -7,13 +7,20 @@ class Display {
public: public:
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,
const char *topRight = nullptr);
void setPower(bool enabled);
bool available() const { return ok_; } bool available() const { return ok_; }
bool powered() const { return powered_; }
static void formatFrequency(float hz, char *out, size_t size); static void formatFrequency(float hz, char *out, size_t size);
static void formatTestFrequency(uint32_t hz, char *out, size_t size);
static void formatPwmFrequency(uint32_t hz, char *out, size_t size);
static void formatPulse(uint32_t pulseNs, char *out, size_t size, bool measured = false);
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 fit(char *text); 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;
}; };

View File

@@ -0,0 +1,736 @@
#include "DriverTest.h"
#include "Config.h"
#include "Log.h"
#include <driver/gpio.h>
#include <esp_cpu.h>
#include <esp_task_wdt.h>
#include <esp_timer.h>
#include <esp32-hal-cpu.h>
#include <soc/gpio_struct.h>
#include <string.h>
static inline uint32_t IRAM_ATTR maskAllInterrupts() {
uint32_t state;
asm volatile("rsil %0, 15" : "=a"(state) :: "memory");
return state;
}
static inline void IRAM_ATTR restoreInterrupts(uint32_t state) {
asm volatile("wsr %0, ps\nrsync" :: "a"(state) : "memory");
}
void DriverEdgeStats::reset() {
memset(this, 0, sizeof(*this));
minDelayTicks = minResponseTicks = UINT32_MAX;
}
void DriverStats::reset() {
memset(this, 0, sizeof(*this));
minDelayTicks = minResponseTicks = UINT32_MAX;
turnOn.reset();
turnOff.reset();
reason = FailReason::NONE;
}
uint64_t DriverTest::nsToTicks(uint32_t ns) const {
return (static_cast<uint64_t>(ns) * captureHz_ + 999999999ULL) /
1000000000ULL;
}
uint64_t DriverTest::ticksToNs(uint64_t ticks) const {
return (ticks * 1000000000ULL + captureHz_ / 2U) / captureHz_;
}
bool DriverTest::start(uint32_t frequencyHz, uint32_t pulseNs,
float tolerancePct, uint32_t testTimeMs,
uint8_t settleCycles, bool activeTxLightOn,
bool activeRxLightOn) {
(void)tolerancePct;
(void)settleCycles;
if (!receiver_.highRateBackend() || !frequencyHz || !pulseNs ||
!testTimeMs || GPIO_PWM >= 32U || GPIO_RX >= 32U) return false;
requestCaptureStop();
if (!waitCaptureStopped(25U)) return false;
if (!pollTask_ && xTaskCreatePinnedToCore(pollTaskEntry, "driver-poll",
3072, this, configMAX_PRIORITIES - 1U, &pollTask_, 0) != pdPASS)
return false;
if (!analyzerTask_ && xTaskCreatePinnedToCore(analyzerTaskEntry,
"driver-analyze", 4096, this, 4, &analyzerTask_, 1) != pdPASS)
return false;
// The ACK edges can be less than 1 us apart. MCPWM capture delivers all
// channels through one group ISR and can overwrite an earlier channel
// timestamp before that ISR reaches it. During DRIVER test dedicate core 0
// to direct GPIO sampling; PWM itself remains fully hardware-generated.
captureHz_ = getCpuFrequencyMhz() * 1000000UL;
if (!captureHz_ || captureHz_ % frequencyHz) return false;
captureFrequencyHz_ = frequencyHz;
capturePulseNs_ = pulseNs;
captureTxLightOn_ = activeTxLightOn;
txPulseLightOn_ = activeTxLightOn;
pollPeriodCycles_ = captureHz_ / frequencyHz;
pollWindowBeforeCycles_ = captureHz_ / 200000U; // 5 us
const uint64_t periodNs = 1000000000ULL / frequencyHz;
uint64_t windowNs = pulseNs + 50000ULL;
const uint64_t maximumWindowNs = periodNs * 3ULL / 4ULL;
if (windowNs > maximumWindowNs) windowNs = maximumWindowNs;
pollWindowAfterCycles_ = static_cast<uint32_t>(
windowNs * captureHz_ / 1000000000ULL);
const uint8_t activeTxRaw = activeTxLightOn ? TX_LIGHT_ON_GPIO_LEVEL :
TX_LIGHT_OFF_GPIO_LEVEL;
pollTxStartRawHigh_ = activeTxRaw == HIGH;
rxActiveRawHigh_ =
((RX_LIGHT_ON_GPIO_LEVEL == HIGH) == activeRxLightOn);
ackStartMaxTicks_ = nsToTicks(DRIVER_ACK_START_MAX_NS);
faultLongTicks_ = nsToTicks(DRIVER_FAULT_MIN_NS);
stuckTicks_ = nsToTicks(DRIVER_RX_STUCK_MIN_NS);
testTicks_ = static_cast<uint64_t>(captureHz_) * testTimeMs / 1000ULL;
const uint32_t requestedSubsamples = testTimeMs < 1000U ?
DRIVER_SHORT_SAMPLE_PROGRESS_STEPS :
(testTimeMs + DRIVER_PROGRESS_INTERVAL_MS - 1U) /
DRIVER_PROGRESS_INTERVAL_MS;
subsampleCount_ = static_cast<uint8_t>(
requestedSubsamples > UINT8_MAX ? UINT8_MAX : requestedSubsamples);
subsampleTicks_ = testTicks_ / subsampleCount_;
if (!pollPeriodCycles_ || !pollWindowAfterCycles_ ||
!ackStartMaxTicks_ || !faultLongTicks_ || !stuckTicks_ ||
!testTicks_ || !subsampleTicks_)
return false;
clearCapture();
stats_.reset();
publishStats();
pendingCount_ = 0;
response_ = {};
measurementStartTick_ = deadlineTick_ = 0;
pointOriginTick_ = lastEventTick_ = lastActiveTxTick_ = 0;
// Skip two complete periods after the polling task synchronizes with TX.
settleCycles_ = DRIVER_CAPTURE_SYNC_CYCLES;
Log::printf("DRIVER", "capture=GPIO-%luMHz sync-periods=%u ACK-timeout=%luns",
static_cast<unsigned long>(captureHz_ / 1000000UL),
static_cast<unsigned>(settleCycles_),
static_cast<unsigned long>(DRIVER_ACK_START_MAX_NS));
const uint64_t periodUs =
(1000000ULL + frequencyHz - 1ULL) / frequencyHz;
settlingTimeoutUs_ = periodUs *
(static_cast<uint64_t>(DRIVER_CAPTURE_SYNC_CYCLES) + 2ULL) + 1000ULL;
settlingDeadlineUs_ = 0;
settledCycles_ = 0;
completedSubsamples_ = 0;
measurementClosed_ = false;
havePointOrigin_ = false;
haveLastActiveTx_ = false;
rxActive_ = (gpio_get_level(static_cast<gpio_num_t>(GPIO_RX)) != 0) ==
rxActiveRawHigh_;
currentStep_ = 0;
traceWrite_ = traceCount_ = 0;
__atomic_store_n(&progressUpdatePending_, false, __ATOMIC_RELEASE);
state_ = DriverState::SETTLING;
return armCapture();
}
bool DriverTest::armCapture() {
// Never wait for USB/Serial here: a disconnected or slow host must not
// delay a subsample or consume the test's global timeout.
if (!__atomic_load_n(&core0WdtDisabled_, __ATOMIC_ACQUIRE)) {
TaskHandle_t idle0 = xTaskGetIdleTaskHandleForCore(0);
const bool watched = idle0 && esp_task_wdt_status(idle0) == ESP_OK;
const bool disabled = watched && disableCore0WDT();
__atomic_store_n(&core0WdtDisabled_, disabled, __ATOMIC_RELEASE);
// If IDLE0 is not watched there is nothing to remove or restore.
}
__atomic_store_n(&captureReady_, false, __ATOMIC_RELEASE);
__atomic_store_n(&captureActive_, true, __ATOMIC_RELEASE);
xTaskNotifyGive(pollTask_);
const uint32_t readyDeadline = millis() + 25U;
while (!__atomic_load_n(&captureReady_, __ATOMIC_ACQUIRE) &&
static_cast<int32_t>(millis() - readyDeadline) < 0) delay(0);
if (!__atomic_load_n(&captureReady_, __ATOMIC_ACQUIRE)) {
requestCaptureStop();
waitCaptureStopped(25U);
state_ = DriverState::IDLE;
return false;
}
settlingDeadlineUs_ = static_cast<uint64_t>(esp_timer_get_time()) +
settlingTimeoutUs_;
xTaskNotifyGive(analyzerTask_);
return true;
}
bool DriverTest::resumeSubsample() {
if (state_ != DriverState::SUBSAMPLE_DONE) return false;
if (!waitCaptureStopped(25U)) {
fail(FailReason::DATA_LOSS, lastEventTick_);
return false;
}
clearCapture();
pendingCount_ = 0;
response_ = {};
measurementStartTick_ = deadlineTick_ = 0;
lastActiveTxTick_ = 0;
settlingDeadlineUs_ = 0;
settledCycles_ = 0;
measurementClosed_ = false;
haveLastActiveTx_ = false;
rxActive_ = (gpio_get_level(static_cast<gpio_num_t>(GPIO_RX)) != 0) ==
rxActiveRawHigh_;
state_ = DriverState::SETTLING;
if (armCapture()) return true;
fail(FailReason::DATA_LOSS, lastEventTick_);
return false;
}
void DriverTest::pollTaskEntry(void *context) {
static_cast<DriverTest *>(context)->pollTaskLoop();
}
void IRAM_ATTR DriverTest::pollTaskLoop() {
constexpr uint32_t PIN_MASK = (1UL << GPIO_PWM) | (1UL << GPIO_RX);
for (;;) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
uint32_t levels = GPIO.in & PIN_MASK;
uint32_t nextStart = 0;
uint32_t windowEnd = 0;
uint32_t lastTxStart = 0;
RawEvent hotEvents[32] = {};
uint8_t hotCount = 0;
bool sawTxStart = false;
bool critical = false;
bool allInterruptsMasked = false;
uint32_t interruptState = 0;
auto sampleOnce = [&]() {
const uint32_t current = GPIO.in & PIN_MASK;
if (current == levels) return;
const uint32_t now = esp_cpu_get_cycle_count();
const uint32_t changed = current ^ levels;
if ((changed & (1UL << GPIO_PWM)) && hotCount < 32U)
hotEvents[hotCount++] = {now,
(current & (1UL << GPIO_PWM)) != 0U, Source::TX};
if ((changed & (1UL << GPIO_RX)) && hotCount < 32U)
hotEvents[hotCount++] = {now,
(current & (1UL << GPIO_RX)) != 0U, Source::RX};
if ((changed & (1UL << GPIO_PWM)) &&
((current & (1UL << GPIO_PWM)) != 0U) == pollTxStartRawHigh_) {
lastTxStart = now;
sawTxStart = true;
}
levels = current;
};
auto flushHot = [&]() {
for (uint8_t i = 0; i < hotCount; ++i)
recordRaw(hotEvents[i].tick, hotEvents[i].rising,
hotEvents[i].source);
hotCount = 0;
};
portENTER_CRITICAL(&pollMux_);
critical = true;
__atomic_store_n(&captureReady_, true, __ATOMIC_RELEASE);
while (__atomic_load_n(&captureActive_, __ATOMIC_ACQUIRE) && !sawTxStart)
for (uint8_t i = 0; i < 16U; ++i) sampleOnce();
if (sawTxStart) windowEnd = lastTxStart + pollWindowAfterCycles_;
const bool synchronized = sawTxStart;
if (synchronized) {
interruptState = maskAllInterrupts();
allInterruptsMasked = true;
}
while (__atomic_load_n(&captureActive_, __ATOMIC_ACQUIRE) && synchronized) {
while (__atomic_load_n(&captureActive_, __ATOMIC_ACQUIRE) &&
static_cast<int32_t>(esp_cpu_get_cycle_count() - windowEnd) < 0)
for (uint8_t i = 0; i < 16U; ++i) sampleOnce();
restoreInterrupts(interruptState);
allInterruptsMasked = false;
portEXIT_CRITICAL(&pollMux_);
critical = false;
flushHot();
// This marker is written only after every TX/RX edge from the completed
// sampling window. The analyzer may now safely decide that an ACK was
// absent without racing the producer that writes those edges.
recordRaw(esp_cpu_get_cycle_count(), false, Source::WINDOW_END);
if (!__atomic_load_n(&captureActive_, __ATOMIC_ACQUIRE)) break;
nextStart = lastTxStart + pollPeriodCycles_;
sawTxStart = false;
uint32_t outsideSpins = 0;
while (__atomic_load_n(&captureActive_, __ATOMIC_ACQUIRE) &&
static_cast<int32_t>(esp_cpu_get_cycle_count() -
(nextStart - pollWindowBeforeCycles_)) < 0) {
for (uint8_t i = 0; i < 16U; ++i) sampleOnce();
if (++outsideSpins >= 256U) {
outsideSpins = 0;
taskYIELD();
}
}
if (!__atomic_load_n(&captureActive_, __ATOMIC_ACQUIRE)) break;
portENTER_CRITICAL(&pollMux_);
critical = true;
interruptState = maskAllInterrupts();
allInterruptsMasked = true;
windowEnd = nextStart + pollWindowAfterCycles_;
}
if (allInterruptsMasked) restoreInterrupts(interruptState);
if (critical) portEXIT_CRITICAL(&pollMux_);
flushHot();
if (__atomic_exchange_n(&core0WdtDisabled_, false,
__ATOMIC_ACQ_REL)) enableCore0WDT();
__atomic_store_n(&captureReady_, false, __ATOMIC_RELEASE);
}
}
void DriverTest::analyzerTaskEntry(void *context) {
static_cast<DriverTest *>(context)->analyzerTaskLoop();
}
void DriverTest::analyzerTaskLoop() {
TimedEvent events[64] = {};
for (;;) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
while (state_ == DriverState::SETTLING || state_ == DriverState::RUNNING) {
const size_t count = readRaw(events, 64, pdMS_TO_TICKS(1));
for (size_t i = 0; i < count &&
(state_ == DriverState::SETTLING || state_ == DriverState::RUNNING);
++i) processEvent(events[i]);
const uint32_t dropped = takeDropped();
if (dropped) {
stats_.droppedItems += dropped;
fail(FailReason::DATA_LOSS, lastEventTick_);
}
if (!count && state_ == DriverState::SETTLING &&
static_cast<uint64_t>(esp_timer_get_time()) >=
settlingDeadlineUs_) {
fail(FailReason::ACK_MISSING, lastEventTick_);
}
}
}
}
void DriverTest::processEvent(const TimedEvent &event) {
lastEventTick_ = event.tick;
if (event.source == Source::WINDOW_END) {
if (state_ == DriverState::RUNNING) {
expirePending(event.tick);
if (state_ == DriverState::RUNNING) completeIfPossible(event.tick);
}
return;
}
if (!havePointOrigin_) {
pointOriginTick_ = event.tick;
havePointOrigin_ = true;
}
rememberTrace(event);
if (state_ == DriverState::SETTLING) processSettling(event);
else if (state_ == DriverState::RUNNING) processRunning(event);
}
void DriverTest::processSettling(const TimedEvent &event) {
if (event.source == Source::RX) {
rxActive_ = event.rising == rxActiveRawHigh_;
return;
}
const uint8_t rawLevel = event.rising ? HIGH : LOW;
const bool lightOn = rawLevel == TX_LIGHT_ON_GPIO_LEVEL;
if (lightOn != txPulseLightOn_) return;
if (settledCycles_ < settleCycles_) {
++settledCycles_;
return;
}
if (rxActive_) {
fail(FailReason::DRIVER_FAULT, event.tick);
return;
}
state_ = DriverState::RUNNING;
measurementStartTick_ = event.tick;
const uint64_t measuredBefore =
static_cast<uint64_t>(completedSubsamples_) * subsampleTicks_;
const uint64_t thisSubsampleTicks =
completedSubsamples_ + 1U == subsampleCount_ ?
testTicks_ - measuredBefore : subsampleTicks_;
deadlineTick_ = event.tick + thisSubsampleTicks;
processTx(event, lightOn);
}
void DriverTest::processRunning(const TimedEvent &event) {
if (response_.active && event.tick - response_.startTick >= stuckTicks_) {
const uint64_t delay = response_.associated ?
response_.startTick - response_.tx.tick : 0;
const uint64_t trigger = haveLastActiveTx_ &&
response_.startTick >= lastActiveTxTick_ ?
response_.startTick - lastActiveTxTick_ : delay;
fail(FailReason::DRIVER_FAULT, event.tick, delay,
event.tick - response_.startTick, trigger);
return;
}
if (event.source == Source::TX) {
expirePending(event.tick);
if (state_ != DriverState::RUNNING) return;
const uint8_t rawLevel = event.rising ? HIGH : LOW;
const bool lightOn = rawLevel == TX_LIGHT_ON_GPIO_LEVEL;
if (event.tick < deadlineTick_) processTx(event, lightOn);
else measurementClosed_ = true;
} else {
// A delayed fault indication can start after the normal ACK deadline.
// Measure the RX pulse before expiring its possible causal TX edge.
processRx(event, event.rising == rxActiveRawHigh_);
if (state_ != DriverState::RUNNING) return;
if (!response_.active) expirePending(event.tick);
}
if (state_ != DriverState::RUNNING) return;
completeIfPossible(event.tick);
}
bool DriverTest::addPending(uint64_t tick, bool lightOn) {
if (pendingCount_ >= MAX_PENDING) {
fail(FailReason::DATA_LOSS, tick);
return false;
}
pending_[pendingCount_++] = {tick, lightOn};
return true;
}
void DriverTest::processTx(const TimedEvent &event, bool lightOn) {
if (!addPending(event.tick, lightOn)) return;
if (lightOn == txPulseLightOn_) {
lastActiveTxTick_ = event.tick;
haveLastActiveTx_ = true;
}
++stats_.inputEdges;
}
int8_t DriverTest::matchingPending(uint64_t rxTick) const {
for (uint8_t i = 0; i < pendingCount_; ++i)
if (rxTick >= pending_[i].tick &&
rxTick - pending_[i].tick <= ackStartMaxTicks_)
return static_cast<int8_t>(i);
return -1;
}
void DriverTest::removePending(uint8_t index) {
if (index >= pendingCount_) return;
for (uint8_t i = index + 1U; i < pendingCount_; ++i)
pending_[i - 1U] = pending_[i];
--pendingCount_;
}
void DriverTest::processRx(const TimedEvent &event, bool activeNow) {
rxActive_ = activeNow;
if (activeNow) {
if (response_.active) {
fail(FailReason::DATA_LOSS, event.tick);
return;
}
response_ = {};
response_.active = true;
response_.startTick = event.tick;
const int8_t index = matchingPending(event.tick);
if (index >= 0) {
response_.associated = true;
response_.tx = pending_[index];
removePending(static_cast<uint8_t>(index));
} else ++stats_.unexpectedResponses;
return;
}
if (!response_.active) return;
const uint64_t width = event.tick - response_.startTick;
if (!response_.associated) {
const uint64_t trigger = haveLastActiveTx_ &&
response_.startTick >= lastActiveTxTick_ ?
response_.startTick - lastActiveTxTick_ : 0;
response_ = {};
fail(FailReason::DRIVER_FAULT, event.tick, trigger, width, trigger);
return;
}
const uint64_t guard = mergeGuardTicks();
for (uint8_t i = 0; i < pendingCount_; ++i) {
if (pending_[i].tick > response_.startTick &&
event.tick - pending_[i].tick >= guard) {
const uint64_t delay = response_.startTick - response_.tx.tick;
const uint64_t trigger = event.tick - pending_[i].tick;
response_ = {};
fail(FailReason::ACK_MERGED, event.tick, delay, width, trigger);
return;
}
}
if (width >= faultLongTicks_) {
const uint64_t delay = response_.startTick - response_.tx.tick;
const uint64_t trigger = haveLastActiveTx_ &&
response_.startTick >= lastActiveTxTick_ ?
response_.startTick - lastActiveTxTick_ : delay;
response_ = {};
fail(FailReason::DRIVER_FAULT, event.tick, delay, width, trigger);
return;
}
const uint64_t delay = response_.startTick - response_.tx.tick;
const bool lightOn = response_.tx.lightOn;
response_ = {};
acceptAcknowledgement(delay, width, lightOn);
}
uint64_t DriverTest::mergeGuardTicks() const {
uint32_t observedMax = stats_.maxDelayTicks;
const uint64_t baseline = observedMax ? observedMax :
nsToTicks(DRIVER_ACK_DELAY_NS + 500U);
return baseline + nsToTicks(DRIVER_ACK_MERGE_MARGIN_NS);
}
void DriverTest::acceptAcknowledgement(uint64_t delay, uint64_t width,
bool lightOn) {
const uint32_t delay32 = delay > UINT32_MAX ? UINT32_MAX :
static_cast<uint32_t>(delay);
const uint32_t width32 = width > UINT32_MAX ? UINT32_MAX :
static_cast<uint32_t>(width);
stats_.lastDelayTicks = delay32;
stats_.lastResponseTicks = width32;
++stats_.responses;
if (delay32 < stats_.minDelayTicks) stats_.minDelayTicks = delay32;
if (delay32 > stats_.maxDelayTicks) stats_.maxDelayTicks = delay32;
if (width32 < stats_.minResponseTicks) stats_.minResponseTicks = width32;
if (width32 > stats_.maxResponseTicks) stats_.maxResponseTicks = width32;
DriverEdgeStats &edge = lightOn ? stats_.turnOn : stats_.turnOff;
++edge.responses;
edge.delaySumTicks += delay32;
edge.responseSumTicks += width32;
if (delay32 < edge.minDelayTicks) edge.minDelayTicks = delay32;
if (delay32 > edge.maxDelayTicks) edge.maxDelayTicks = delay32;
if (width32 < edge.minResponseTicks) edge.minResponseTicks = width32;
if (width32 > edge.maxResponseTicks) edge.maxResponseTicks = width32;
publishStats();
}
void DriverTest::expirePending(uint64_t now) {
for (uint8_t i = 0; i < pendingCount_; ++i) {
if (now < pending_[i].tick + ackStartMaxTicks_) continue;
// The failure belongs to the ACK deadline itself. A later TX edge or the
// end-of-window marker is only the safe moment when absence is confirmed.
fail(FailReason::ACK_MISSING,
pending_[i].tick + ackStartMaxTicks_, ackStartMaxTicks_);
return;
}
}
void DriverTest::completeIfPossible(uint64_t now) {
if (state_ != DriverState::RUNNING) return;
if (!measurementClosed_ && now >= deadlineTick_) measurementClosed_ = true;
if (!measurementClosed_ || response_.active || pendingCount_) return;
if (!stats_.turnOn.responses || !stats_.turnOff.responses) {
fail(FailReason::ACK_MISSING, now);
return;
}
++completedSubsamples_;
currentStep_ = completedSubsamples_;
publishStats();
requestCaptureStop();
if (!waitCaptureStopped(25U)) {
fail(FailReason::DATA_LOSS, lastEventTick_);
return;
}
if (completedSubsamples_ >= subsampleCount_) {
__atomic_store_n(&progressUpdatePending_, false, __ATOMIC_RELEASE);
state_ = DriverState::PASS;
} else {
__atomic_store_n(&progressUpdatePending_, true, __ATOMIC_RELEASE);
state_ = DriverState::SUBSAMPLE_DONE;
}
}
void DriverTest::fail(FailReason reason, uint64_t tick, uint64_t delay,
uint64_t pulseWidth, uint64_t triggerAfterTx) {
if (state_ == DriverState::FAIL || state_ == DriverState::PASS) return;
if (stats_.reason == FailReason::NONE) {
stats_.reason = reason;
if (tick && havePointOrigin_ && tick >= pointOriginTick_)
stats_.errorElapsedTicks = tick - pointOriginTick_;
const uint64_t trigger = triggerAfterTx ? triggerAfterTx : delay;
if (trigger) {
stats_.errorTriggerTicks = trigger > UINT32_MAX ? UINT32_MAX :
static_cast<uint32_t>(trigger);
stats_.errorTriggerValid = true;
}
if (delay) {
stats_.errorDelayTicks = delay > UINT32_MAX ? UINT32_MAX :
static_cast<uint32_t>(delay);
stats_.errorDelayValid = true;
}
if (pulseWidth) {
stats_.errorPulseTicks = pulseWidth > UINT32_MAX ? UINT32_MAX :
static_cast<uint32_t>(pulseWidth);
stats_.errorPulseValid = true;
}
}
publishStats();
__atomic_store_n(&progressUpdatePending_, false, __ATOMIC_RELEASE);
requestCaptureStop();
state_ = DriverState::FAIL;
}
void DriverTest::publishStats() {
portENTER_CRITICAL(&statsMux_);
publishedStats_ = stats_;
portEXIT_CRITICAL(&statsMux_);
}
void DriverTest::forceFail(FailReason reason) {
if (state_ == DriverState::SETTLING || state_ == DriverState::RUNNING ||
state_ == DriverState::SUBSAMPLE_DONE)
fail(reason, lastEventTick_);
}
void DriverTest::abort() {
if (state_ == DriverState::SETTLING || state_ == DriverState::RUNNING ||
state_ == DriverState::SUBSAMPLE_DONE)
fail(FailReason::ABORTED, lastEventTick_);
else {
requestCaptureStop();
state_ = DriverState::IDLE;
}
}
bool DriverTest::takeProgressUpdate() {
return __atomic_exchange_n(&progressUpdatePending_, false,
__ATOMIC_ACQ_REL);
}
void DriverTest::requestCaptureStop() {
__atomic_store_n(&captureActive_, false, __ATOMIC_RELEASE);
}
bool DriverTest::waitCaptureStopped(uint32_t timeoutMs) {
const uint32_t deadline = millis() + timeoutMs;
while (__atomic_load_n(&captureReady_, __ATOMIC_ACQUIRE) &&
static_cast<int32_t>(millis() - deadline) < 0) delay(0);
if (__atomic_load_n(&captureReady_, __ATOMIC_ACQUIRE)) return false;
if (__atomic_exchange_n(&core0WdtDisabled_, false,
__ATOMIC_ACQ_REL)) enableCore0WDT();
return true;
}
void DriverTest::clearCapture() {
const uint16_t write = __atomic_load_n(&ringWrite_, __ATOMIC_ACQUIRE);
__atomic_store_n(&ringRead_, write, __ATOMIC_RELEASE);
__atomic_store_n(&droppedItems_, 0U, __ATOMIC_RELEASE);
haveRawTick_ = false;
lastRawTick_ = 0;
tickEpoch_ = 0;
}
void IRAM_ATTR DriverTest::recordRaw(uint32_t tick, bool rising,
Source source) {
const uint16_t write = ringWrite_;
const uint16_t next = static_cast<uint16_t>(
(write + 1U) & (RING_CAPACITY - 1U));
if (next == ringRead_) {
++droppedItems_;
return;
}
ring_[write] = {tick, rising, source};
asm volatile("memw" ::: "memory");
ringWrite_ = next;
}
size_t DriverTest::readRaw(TimedEvent *events, size_t capacity,
TickType_t waitTicks) {
if (!events || !capacity) return 0;
uint16_t read = __atomic_load_n(&ringRead_, __ATOMIC_RELAXED);
if (read == __atomic_load_n(&ringWrite_, __ATOMIC_ACQUIRE) && waitTicks) {
vTaskDelay(waitTicks);
read = __atomic_load_n(&ringRead_, __ATOMIC_RELAXED);
}
const uint16_t write = __atomic_load_n(&ringWrite_, __ATOMIC_ACQUIRE);
size_t count = 0;
while (read != write && count < capacity) {
const RawEvent raw = ring_[read];
read = static_cast<uint16_t>((read + 1U) & (RING_CAPACITY - 1U));
if (haveRawTick_ && raw.tick < lastRawTick_ &&
lastRawTick_ - raw.tick > 0x80000000UL) tickEpoch_ += 1ULL << 32U;
lastRawTick_ = raw.tick;
haveRawTick_ = true;
events[count++] = {tickEpoch_ + raw.tick, raw.rising, raw.source};
}
__atomic_store_n(&ringRead_, read, __ATOMIC_RELEASE);
return count;
}
uint32_t DriverTest::takeDropped() {
return __atomic_exchange_n(&droppedItems_, 0U, __ATOMIC_ACQ_REL);
}
void DriverTest::rememberTrace(const TimedEvent &event) {
trace_[traceWrite_] = {event.tick, static_cast<uint8_t>(event.source),
static_cast<uint8_t>(event.rising), static_cast<uint8_t>(state_),
pendingCount_};
traceWrite_ = static_cast<uint8_t>((traceWrite_ + 1U) % TRACE_CAPACITY);
if (traceCount_ < TRACE_CAPACITY) ++traceCount_;
}
void DriverTest::printSummary() const {
auto printEdge = [&](const char *name, const DriverEdgeStats &edge) {
if (!edge.responses) {
Log::printf("DRIVER", "%s ACK=0", name);
return;
}
Log::printf("DRIVER",
"%s ACK=%lu D=%lluns/%lluns/%lluns P=%lluns/%lluns/%lluns",
name, static_cast<unsigned long>(edge.responses),
static_cast<unsigned long long>(ticksToNs(edge.minDelayTicks)),
static_cast<unsigned long long>(ticksToNs(
edge.delaySumTicks / edge.responses)),
static_cast<unsigned long long>(ticksToNs(edge.maxDelayTicks)),
static_cast<unsigned long long>(ticksToNs(edge.minResponseTicks)),
static_cast<unsigned long long>(ticksToNs(
edge.responseSumTicks / edge.responses)),
static_cast<unsigned long long>(ticksToNs(edge.maxResponseTicks)));
};
Log::printf("DRIVER", "TX edges=%lu responses=%lu dropped=%lu unexpected=%lu result=%s",
static_cast<unsigned long>(publishedStats_.inputEdges),
static_cast<unsigned long>(publishedStats_.responses),
static_cast<unsigned long>(publishedStats_.droppedItems),
static_cast<unsigned long>(publishedStats_.unexpectedResponses),
failName(publishedStats_.reason));
if (publishedStats_.reason != FailReason::NONE) {
auto formatOptional = [&](bool valid, uint32_t ticks,
char *out, size_t size) {
if (!valid) snprintf(out, size, "---");
else snprintf(out, size, "%lluns",
static_cast<unsigned long long>(ticksToNs(ticks)));
};
char trigger[24], pulse[24];
formatOptional(publishedStats_.errorTriggerValid,
publishedStats_.errorTriggerTicks, trigger, sizeof(trigger));
formatOptional(publishedStats_.errorPulseValid,
publishedStats_.errorPulseTicks, pulse, sizeof(pulse));
if (publishedStats_.errorPulseValid)
Log::printf("DRIVER", "error timing: T=%s P=%s", trigger, pulse);
else Log::printf("DRIVER", "error timing: T=%s", trigger);
}
printEdge("ON", publishedStats_.turnOn);
printEdge("OFF", publishedStats_.turnOff);
}
void DriverTest::printTrace() const {
if (!traceCount_) return;
const uint8_t first = static_cast<uint8_t>(
(traceWrite_ + TRACE_CAPACITY - traceCount_) % TRACE_CAPACITY);
const uint64_t origin = trace_[first].tick;
Log::printf("DRIVER", "RAM trace: %u events, tick=%luHz", traceCount_,
static_cast<unsigned long>(captureHz_));
for (uint8_t i = 0; i < traceCount_; ++i) {
const TraceEvent &event = trace_[(first + i) % TRACE_CAPACITY];
Log::printf("DRIVER", "E%02u +%lluns %s/%s state=%u pending=%u", i,
static_cast<unsigned long long>(ticksToNs(event.tick - origin)),
event.source == static_cast<uint8_t>(Source::TX) ? "TX" : "RX",
event.rising ? "rise" : "fall", event.state, event.pending);
}
}

View File

@@ -0,0 +1,180 @@
#pragma once
#include <Arduino.h>
#include "Receiver.h"
enum class DriverState : uint8_t {
IDLE, SETTLING, RUNNING, SUBSAMPLE_DONE, PASS, FAIL
};
struct DriverEdgeStats {
uint32_t responses;
uint32_t minDelayTicks;
uint32_t maxDelayTicks;
uint64_t delaySumTicks;
uint32_t minResponseTicks;
uint32_t maxResponseTicks;
uint64_t responseSumTicks;
void reset();
};
struct DriverStats {
uint32_t inputEdges;
uint32_t responses;
uint32_t minDelayTicks;
uint32_t maxDelayTicks;
uint32_t minResponseTicks;
uint32_t maxResponseTicks;
uint32_t lastDelayTicks;
uint32_t lastResponseTicks;
uint32_t droppedItems;
uint32_t unexpectedResponses;
uint64_t errorElapsedTicks;
uint32_t errorTriggerTicks;
uint32_t errorDelayTicks;
uint32_t errorPulseTicks;
bool errorTriggerValid;
bool errorDelayValid;
bool errorPulseValid;
DriverEdgeStats turnOn;
DriverEdgeStats turnOff;
FailReason reason;
void reset();
};
class DriverTest {
public:
explicit DriverTest(PulseReceiver &receiver) : receiver_(receiver) {}
bool start(uint32_t frequencyHz, uint32_t pulseNs, float tolerancePct,
uint32_t testTimeMs, uint8_t settleCycles,
bool activeTxLightOn, bool activeRxLightOn);
DriverState update() const { return state_; }
void abort();
void forceFail(FailReason reason);
bool resumeSubsample();
bool takeProgressUpdate();
void printSummary() const;
void printTrace() const;
uint8_t progressStep() const { return currentStep_; }
uint8_t progressSteps() const { return subsampleCount_; }
uint32_t tickHz() const { return captureHz_; }
const DriverStats &stats() const { return publishedStats_; }
private:
enum class Source : uint8_t { TX, RX, WINDOW_END };
struct RawEvent { uint32_t tick; bool rising; Source source; };
struct TimedEvent { uint64_t tick; bool rising; Source source; };
struct PendingTx {
uint64_t tick;
bool lightOn;
};
struct Response {
bool active;
bool associated;
uint64_t startTick;
PendingTx tx;
};
struct TraceEvent {
uint64_t tick;
uint8_t source;
uint8_t rising;
uint8_t state;
uint8_t pending;
};
static void analyzerTaskEntry(void *context);
static void pollTaskEntry(void *context);
void analyzerTaskLoop();
void IRAM_ATTR pollTaskLoop();
void processEvent(const TimedEvent &event);
void processSettling(const TimedEvent &event);
void processRunning(const TimedEvent &event);
void processTx(const TimedEvent &event, bool lightOn);
void processRx(const TimedEvent &event, bool activeNow);
void expirePending(uint64_t now);
void completeIfPossible(uint64_t now);
bool addPending(uint64_t tick, bool lightOn);
int8_t matchingPending(uint64_t rxTick) const;
void removePending(uint8_t index);
uint64_t mergeGuardTicks() const;
void acceptAcknowledgement(uint64_t delay, uint64_t width, bool lightOn);
void fail(FailReason reason, uint64_t tick = 0, uint64_t delay = 0,
uint64_t pulseWidth = 0, uint64_t triggerAfterTx = 0);
void publishStats();
void rememberTrace(const TimedEvent &event);
bool armCapture();
void requestCaptureStop();
bool waitCaptureStopped(uint32_t timeoutMs);
void clearCapture();
void recordRaw(uint32_t tick, bool rising, Source source);
size_t readRaw(TimedEvent *events, size_t capacity, TickType_t waitTicks);
uint32_t takeDropped();
uint64_t nsToTicks(uint32_t ns) const;
uint64_t ticksToNs(uint64_t ticks) const;
static constexpr uint8_t MAX_PENDING = 8;
static constexpr uint16_t RING_CAPACITY = 2048;
static constexpr uint8_t TRACE_CAPACITY = 32;
static_assert((RING_CAPACITY & (RING_CAPACITY - 1U)) == 0,
"driver ring capacity must be a power of two");
PulseReceiver &receiver_;
TaskHandle_t analyzerTask_ = nullptr;
TaskHandle_t pollTask_ = nullptr;
volatile DriverState state_ = DriverState::IDLE;
DriverStats stats_ = {};
DriverStats publishedStats_ = {};
mutable portMUX_TYPE statsMux_ = portMUX_INITIALIZER_UNLOCKED;
RawEvent ring_[RING_CAPACITY] = {};
volatile uint16_t ringWrite_ = 0;
volatile uint16_t ringRead_ = 0;
volatile uint32_t droppedItems_ = 0;
volatile bool captureActive_ = false;
volatile bool captureReady_ = false;
volatile bool core0WdtDisabled_ = false;
uint32_t captureHz_ = 0;
uint32_t captureFrequencyHz_ = 0;
uint32_t capturePulseNs_ = 0;
uint32_t pollPeriodCycles_ = 0;
uint32_t pollWindowBeforeCycles_ = 0;
uint32_t pollWindowAfterCycles_ = 0;
bool pollTxStartRawHigh_ = false;
bool captureTxLightOn_ = true;
portMUX_TYPE pollMux_ = portMUX_INITIALIZER_UNLOCKED;
PendingTx pending_[MAX_PENDING] = {};
uint8_t pendingCount_ = 0;
Response response_ = {};
uint64_t ackStartMaxTicks_ = 0;
uint64_t faultLongTicks_ = 0;
uint64_t stuckTicks_ = 0;
uint64_t testTicks_ = 0;
uint64_t subsampleTicks_ = 0;
uint64_t settlingTimeoutUs_ = 0;
uint64_t settlingDeadlineUs_ = 0;
uint64_t measurementStartTick_ = 0;
uint64_t deadlineTick_ = 0;
uint64_t pointOriginTick_ = 0;
uint64_t lastEventTick_ = 0;
uint64_t lastActiveTxTick_ = 0;
uint8_t settleCycles_ = 0;
uint8_t settledCycles_ = 0;
uint8_t subsampleCount_ = DRIVER_SHORT_SAMPLE_PROGRESS_STEPS;
uint8_t completedSubsamples_ = 0;
bool rxActiveRawHigh_ = true;
bool txPulseLightOn_ = true;
bool rxActive_ = false;
bool measurementClosed_ = false;
bool havePointOrigin_ = false;
bool haveLastActiveTx_ = false;
bool haveRawTick_ = false;
uint32_t lastRawTick_ = 0;
uint64_t tickEpoch_ = 0;
volatile uint8_t currentStep_ = 0;
volatile bool progressUpdatePending_ = false;
TraceEvent trace_[TRACE_CAPACITY] = {};
uint8_t traceWrite_ = 0;
uint8_t traceCount_ = 0;
};

View File

@@ -0,0 +1,57 @@
#pragma once
#include <Arduino.h>
// Compact 5x7 uppercase Cyrillic font. Lowercase UTF-8 letters are deliberately
// rendered with the same uppercase glyphs: at 5x7 pixels this is considerably
// clearer than trying to preserve lowercase shapes.
namespace CyrillicFont {
constexpr uint8_t WIDTH = 5;
constexpr uint8_t HEIGHT = 7;
constexpr uint8_t ADVANCE = 6;
// Rows are stored top-to-bottom; the low five bits are pixels left-to-right.
const uint8_t GLYPHS[][HEIGHT] PROGMEM = {
{0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11}, // А
{0x1F, 0x10, 0x10, 0x1E, 0x11, 0x11, 0x1E}, // Б
{0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E}, // В
{0x1F, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10}, // Г
{0x0E, 0x0A, 0x0A, 0x0A, 0x11, 0x1F, 0x11}, // Д
{0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F}, // Е
{0x15, 0x15, 0x0E, 0x04, 0x0E, 0x15, 0x15}, // Ж
{0x0E, 0x11, 0x01, 0x06, 0x01, 0x11, 0x0E}, // З
{0x11, 0x13, 0x15, 0x15, 0x19, 0x11, 0x11}, // И
{0x0A, 0x11, 0x13, 0x15, 0x19, 0x11, 0x11}, // Й
{0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11}, // К
{0x07, 0x09, 0x09, 0x09, 0x11, 0x11, 0x11}, // Л
{0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11}, // М
{0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11}, // Н
{0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}, // О
{0x1F, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, // П
{0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10}, // Р
{0x0F, 0x10, 0x10, 0x10, 0x10, 0x10, 0x0F}, // С
{0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}, // Т
{0x11, 0x11, 0x11, 0x0F, 0x01, 0x11, 0x0E}, // У
{0x04, 0x0E, 0x15, 0x15, 0x0E, 0x04, 0x04}, // Ф
{0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11}, // Х
{0x12, 0x12, 0x12, 0x12, 0x12, 0x1E, 0x01}, // Ц
{0x11, 0x11, 0x11, 0x0F, 0x01, 0x01, 0x01}, // Ч
{0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x1F}, // Ш
{0x15, 0x15, 0x15, 0x15, 0x15, 0x1E, 0x01}, // Щ
{0x18, 0x08, 0x08, 0x0E, 0x09, 0x09, 0x0E}, // Ъ
{0x11, 0x11, 0x11, 0x1D, 0x15, 0x15, 0x1D}, // Ы
{0x10, 0x10, 0x10, 0x1E, 0x11, 0x11, 0x1E}, // Ь
{0x0E, 0x11, 0x01, 0x07, 0x01, 0x11, 0x0E}, // Э
{0x12, 0x15, 0x15, 0x1D, 0x15, 0x15, 0x12}, // Ю
{0x0F, 0x11, 0x11, 0x0F, 0x05, 0x09, 0x11} // Я
};
inline const uint8_t *glyph(uint32_t codepoint) {
if (codepoint >= 0x0430U && codepoint <= 0x044FU) codepoint -= 0x20U;
if (codepoint == 0x0401U || codepoint == 0x0451U) codepoint = 0x0415U; // Ё -> Е
if (codepoint < 0x0410U || codepoint > 0x042FU) return nullptr;
return GLYPHS[codepoint - 0x0410U];
}
} // namespace CyrillicFont

View File

@@ -7,7 +7,8 @@ namespace Log {
void event(const char *component, const char *message) { void event(const char *component, const char *message) {
if (!SERIAL_ACTION_LOG) return; if (!SERIAL_ACTION_LOG) return;
if (SERIAL_MINIMAL_LOG && strcmp(component, "INPUT") && strcmp(component, "UI") && if (SERIAL_MINIMAL_LOG && strcmp(component, "INPUT") && strcmp(component, "UI") &&
strcmp(component, "CONFIG") && strcmp(component, "RESULT") && strcmp(component, "ESP-NOW")) return; strcmp(component, "CONFIG") && strcmp(component, "RESULT") && strcmp(component, "CAPTURE") &&
strcmp(component, "DRIVER") && strcmp(component, "ESP-NOW")) return;
if (SERIAL_LOG_TIMESTAMPS) Serial.printf("[%10lu][%-8s] %s\n", millis(), component, message); if (SERIAL_LOG_TIMESTAMPS) Serial.printf("[%10lu][%-8s] %s\n", millis(), component, message);
else Serial.printf("[%-8s] %s\n", component, message); else Serial.printf("[%-8s] %s\n", component, message);
} }

View File

@@ -3,87 +3,156 @@
#include <string.h> #include <string.h>
bool Measurement::start(float hz, float duty, float tolerance, uint32_t timeMs, bool Measurement::start(float hz, float duty, float tolerance, uint32_t timeMs,
uint8_t repeats, uint8_t settleCycles) { uint16_t averagingPeriods, uint8_t settleCycles,
bool activeRxLightOn) {
if (!task_ && xTaskCreate(taskEntry, "optical-rx", 4096, this, 4, &task_) != pdPASS) return false;
expectedHz_ = static_cast<uint32_t>(hz + 0.5f); expectedHz_ = static_cast<uint32_t>(hz + 0.5f);
if (!expectedHz_ || !timeMs || !repeats || repeats > 10 || expectedDutyPct_ = duty;
!makePeriodLimits(expectedHz_, duty, tolerance, receiver_.tickHz(), limits_) || tolerance = effectiveTolerancePct(tolerance);
!receiver_.start(expectedHz_)) return false; if (!expectedHz_ || !timeMs || !averagingPeriods ||
timeMs_ = timeMs; repeats_ = repeats; settleLeft_ = settleCycles; !receiver_.start(expectedHz_, expectedDutyPct_, activeRxLightOn)) return false;
stats_.reset(); memset(repeatPeriods_, 0, sizeof(repeatPeriods_)); settleCycles_ = settleCycles; settleLeft_ = settleCycles;
tolerancePct_ = tolerance;
stepTimeMs_ = (timeMs + MEASUREMENT_PROGRESS_STEPS - 1U) / MEASUREMENT_PROGRESS_STEPS;
stepTicks_ = static_cast<uint64_t>(receiver_.tickHz()) * timeMs /
(1000ULL * MEASUREMENT_PROGRESS_STEPS);
if (!stepTicks_) stepTicks_ = 1;
currentStep_ = 0;
__atomic_store_n(&progressUpdatePending_, false, __ATOMIC_RELEASE);
stats_.reset();
publishStats();
measurementStartTick_ = deadlineTick_ = 0; startedMs_ = millis(); measurementStartTick_ = deadlineTick_ = 0; startedMs_ = millis();
measurementStartMs_ = lastPeriodMs_ = 0; measurementStartMs_ = lastPeriodMs_ = 0;
currentRepeat_ = 0;
expectedPeriodMs_ = static_cast<uint32_t>((1000ULL + expectedHz_ - 1U) / expectedHz_); expectedPeriodMs_ = static_cast<uint32_t>((1000ULL + expectedHz_ - 1U) / expectedHz_);
if (!expectedPeriodMs_) expectedPeriodMs_ = 1; if (!expectedPeriodMs_) expectedPeriodMs_ = 1;
state_ = MeasureState::SETTLING; return true; state_ = MeasureState::SETTLING;
xTaskNotifyGive(task_);
return true;
}
void Measurement::taskEntry(void *context) {
static_cast<Measurement *>(context)->taskLoop();
}
void Measurement::taskLoop() {
for (;;) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
while (state_ == MeasureState::SETTLING || state_ == MeasureState::RUNNING) processOnce();
}
} }
void Measurement::fail(FailReason reason) { void Measurement::fail(FailReason reason) {
if (stats_.reason == FailReason::NONE) stats_.reason = reason; if (stats_.reason == FailReason::NONE) stats_.reason = reason;
receiver_.stop(); state_ = MeasureState::FAIL; __atomic_store_n(&progressUpdatePending_, false, __ATOMIC_RELEASE);
publishStats();
// App stops PWM first and only then disables capture. Disabling MCPWM from
// this RX task while input edges are still arriving can race its ISR.
state_ = MeasureState::FAIL;
} }
void Measurement::completeWindow() { void Measurement::completeMeasurement() {
receiver_.stop(); const uint32_t dropped = receiver_.takeDroppedItems();
stats_.lostItems += receiver_.takeDroppedItems(); stats_.droppedItems += dropped;
if (receiver_.overflowed()) { fail(FailReason::GLITCH); return; } if (dropped) { fail(FailReason::DATA_LOSS); return; }
for (uint8_t i = 0; i < repeats_; ++i) if (!repeatPeriods_[i]) { publishStats();
fail(FailReason::TOO_FEW_PERIODS); return; if (++currentStep_ < MEASUREMENT_PROGRESS_STEPS) {
// Capture and validation continue while the main task draws OLED. Pausing
// here would overflow the edge queue at higher PWM frequencies; stopping
// MCPWM Capture can race an edge ISR. Publish a snapshot, then advance the
// edge-based window without interrupting the RX pipeline.
__atomic_store_n(&progressUpdatePending_, true, __ATOMIC_RELEASE);
measurementStartTick_ = deadlineTick_;
deadlineTick_ += stepTicks_;
measurementStartMs_ = lastPeriodMs_ = millis();
return;
} }
if (stats_.lostItems && stats_.reason == FailReason::NONE) stats_.reason = FailReason::DATA_LOST; if (!stats_.periods) {
fail(FailReason::DATA_LOSS); return;
}
__atomic_store_n(&progressUpdatePending_, false, __ATOMIC_RELEASE);
state_ = MeasureState::PASS; state_ = MeasureState::PASS;
} }
MeasureState Measurement::update() { void Measurement::publishStats() {
portENTER_CRITICAL(&statsMux_);
publishedStats_ = stats_;
portEXIT_CRITICAL(&statsMux_);
}
bool Measurement::statsSnapshot(StageStats &out) const {
portENTER_CRITICAL(&statsMux_);
out = publishedStats_;
portEXIT_CRITICAL(&statsMux_);
return out.periods && out.periodSum;
}
MeasureState Measurement::processOnce() {
if (state_ != MeasureState::SETTLING && state_ != MeasureState::RUNNING) return state_; if (state_ != MeasureState::SETTLING && state_ != MeasureState::RUNNING) return state_;
if (receiver_.overflowed()) { fail(FailReason::GLITCH); return state_; }
bool receivedPeriod = false; bool receivedPeriod = false;
for (;;) { for (;;) {
const size_t periodCount = receiver_.readPeriods(periodBatch_, PERIOD_BATCH_SIZE); const size_t periodCount = receiver_.readPeriods(periodBatch_, PERIOD_BATCH_SIZE, pdMS_TO_TICKS(2));
stats_.lostItems += receiver_.takeDroppedItems(); const uint32_t dropped = receiver_.takeDroppedItems();
stats_.droppedItems += dropped;
if (dropped) { fail(FailReason::DATA_LOSS); return state_; }
if (!periodCount) break; if (!periodCount) break;
receivedPeriod = true; receivedPeriod = true;
for (size_t periodIndex = 0; periodIndex < periodCount; ++periodIndex) { for (size_t periodIndex = 0; periodIndex < periodCount; ++periodIndex) {
if (state_ != MeasureState::SETTLING && state_ != MeasureState::RUNNING) return state_;
const PulsePeriod &period = periodBatch_[periodIndex]; const PulsePeriod &period = periodBatch_[periodIndex];
if (state_ == MeasureState::SETTLING) { if (state_ == MeasureState::SETTLING) {
if (settleLeft_) --settleLeft_; if (settleLeft_) --settleLeft_;
if (!settleLeft_) { if (!settleLeft_) {
measurementStartTick_ = period.startTick + period.periodTicks; measurementStartTick_ = period.startTick + period.periodTicks;
repeatTicks_ = static_cast<uint64_t>(receiver_.tickHz()) * timeMs_ / 1000ULL; deadlineTick_ = measurementStartTick_ + stepTicks_;
deadlineTick_ = measurementStartTick_ + repeatTicks_ * repeats_; measurementStartMs_ = lastPeriodMs_ = millis(); state_ = MeasureState::RUNNING;
nextRepeatTick_ = measurementStartTick_ + repeatTicks_;
stats_.reset(); measurementStartMs_ = lastPeriodMs_ = millis(); state_ = MeasureState::RUNNING;
} }
continue; continue;
} }
const uint64_t endTick = period.startTick + period.periodTicks;
if (period.startTick < measurementStartTick_) continue; // leading incomplete period if (period.startTick < measurementStartTick_) continue; // leading incomplete period
if (endTick > deadlineTick_) { completeWindow(); return state_; } // trailing incomplete period while (period.startTick >= deadlineTick_) {
while (currentRepeat_ + 1U < repeats_ && period.startTick >= nextRepeatTick_) { // Progress boundaries never discard a pulse. A complete period is
++currentRepeat_; nextRepeatTick_ += repeatTicks_; // assigned by its start edge, then validated exactly once. The nine
// intermediate boundaries only publish UI snapshots.
completeMeasurement();
if (state_ != MeasureState::RUNNING) return state_;
} }
++repeatPeriods_[currentRepeat_]; if (!period.periodTicks || !period.activeTicks || !period.activeTickHz) {
const FailReason r = evaluatePeriodFast(period, receiver_.tickHz(), limits_, currentRepeat_ + 1, stats_); fail(FailReason::EXTRA_EDGE); return state_;
if (r != FailReason::NONE) { fail(r); return state_; } }
const FailReason result = evaluatePeriod(period, receiver_.tickHz(),
expectedHz_, expectedDutyPct_, tolerancePct_, currentStep_ + 1U, stats_);
if (result != FailReason::NONE) { fail(result); return state_; }
} }
} }
if (receivedPeriod && state_ == MeasureState::RUNNING) lastPeriodMs_ = millis(); if (receivedPeriod && state_ == MeasureState::RUNNING) lastPeriodMs_ = millis();
const uint64_t edgeBasedTimeout = const uint64_t edgeBasedTimeout =
static_cast<uint64_t>(PWM_SETTLE_CYCLES + NO_SIGNAL_TIMEOUT_PERIODS) * expectedPeriodMs_ + 20; static_cast<uint64_t>(PWM_SETTLE_CYCLES + NO_SIGNAL_TIMEOUT_PERIODS) * expectedPeriodMs_ + 20;
const uint64_t rmtBatchTimeout = const uint64_t settleTimeout = edgeBasedTimeout;
static_cast<uint64_t>(RMT_MIN_RECEIVE_SYMBOLS + NO_SIGNAL_TIMEOUT_PERIODS) * expectedPeriodMs_ + 20;
const uint64_t settleTimeout = edgeBasedTimeout > rmtBatchTimeout ? edgeBasedTimeout : rmtBatchTimeout;
if (state_ == MeasureState::SETTLING && millis() - startedMs_ > settleTimeout) fail(FailReason::NO_SIGNAL); if (state_ == MeasureState::SETTLING && millis() - startedMs_ > settleTimeout) fail(FailReason::NO_SIGNAL);
if (state_ == MeasureState::RUNNING && measurementStartTick_) { if (state_ == MeasureState::RUNNING && measurementStartTick_) {
const uint32_t now = millis(); const uint32_t now = millis();
const uint32_t totalMs = timeMs_ * repeats_; const uint32_t edgeTimeoutMs = expectedPeriodMs_ * NO_SIGNAL_TIMEOUT_PERIODS + 2U;
const uint32_t edgeTimeoutMs = expectedPeriodMs_ * NO_SIGNAL_TIMEOUT_PERIODS + 2; if (now - measurementStartMs_ < stepTimeMs_ && now - lastPeriodMs_ > edgeTimeoutMs) {
if (now - measurementStartMs_ < totalMs && now - lastPeriodMs_ > edgeTimeoutMs) {
fail(FailReason::LOST_EDGE); return state_; fail(FailReason::LOST_EDGE); return state_;
} }
if (now - measurementStartMs_ > totalMs + expectedPeriodMs_ + 2) completeWindow(); if (now - measurementStartMs_ > stepTimeMs_ + expectedPeriodMs_ + 2) completeMeasurement();
} }
return state_; return state_;
} }
void Measurement::abort() { if (state_ == MeasureState::SETTLING || state_ == MeasureState::RUNNING) fail(FailReason::ABORTED); } MeasureState Measurement::update() { return state_; }
bool Measurement::takeProgressUpdate() {
return __atomic_exchange_n(&progressUpdatePending_, false, __ATOMIC_ACQ_REL);
}
void Measurement::abort() {
if (state_ == MeasureState::SETTLING || state_ == MeasureState::RUNNING)
fail(FailReason::ABORTED);
}
void Measurement::forceFail(FailReason reason) {
if (state_ == MeasureState::SETTLING || state_ == MeasureState::RUNNING)
fail(reason);
}

View File

@@ -7,25 +7,37 @@ class Measurement {
public: public:
explicit Measurement(PulseReceiver &receiver) : receiver_(receiver) {} explicit Measurement(PulseReceiver &receiver) : receiver_(receiver) {}
bool start(float expectedHz, float expectedDuty, float tolerancePct, bool start(float expectedHz, float expectedDuty, float tolerancePct,
uint32_t testTimeMs, uint8_t repeats, uint8_t settleCycles); uint32_t testTimeMs, uint16_t averagingPeriods,
uint8_t settleCycles, bool activeRxLightOn);
MeasureState update(); MeasureState update();
bool takeProgressUpdate();
void abort(); void abort();
void forceFail(FailReason reason);
MeasureState state() const { return state_; } MeasureState state() const { return state_; }
FailReason reason() const { return stats_.reason; } FailReason reason() const { return stats_.reason; }
const StageStats &stats() const { return stats_; } const StageStats &stats() const { return stats_; }
uint8_t progressStep() const { return currentStep_; }
bool statsSnapshot(StageStats &out) const;
private: private:
static void taskEntry(void *context);
void taskLoop();
MeasureState processOnce();
void fail(FailReason reason); void fail(FailReason reason);
void completeWindow(); void completeMeasurement();
void publishStats();
PulseReceiver &receiver_; PulseReceiver &receiver_;
MeasureState state_ = MeasureState::IDLE; volatile MeasureState state_ = MeasureState::IDLE;
TaskHandle_t task_ = nullptr;
StageStats stats_ = {}; StageStats stats_ = {};
PeriodLimits limits_ = {}; StageStats publishedStats_ = {};
mutable portMUX_TYPE statsMux_ = portMUX_INITIALIZER_UNLOCKED;
uint32_t expectedHz_ = 0; uint32_t expectedHz_ = 0;
uint32_t timeMs_ = 0; float expectedDutyPct_ = 0.0f, tolerancePct_ = 0.0f;
uint8_t repeats_ = 0, settleLeft_ = 0, currentRepeat_ = 0; uint8_t settleCycles_ = 0, settleLeft_ = 0;
uint64_t measurementStartTick_ = 0, deadlineTick_ = 0, repeatTicks_ = 0, nextRepeatTick_ = 0; uint64_t measurementStartTick_ = 0, deadlineTick_ = 0, stepTicks_ = 0;
uint32_t startedMs_ = 0, measurementStartMs_ = 0, lastPeriodMs_ = 0; uint32_t startedMs_ = 0, measurementStartMs_ = 0, lastPeriodMs_ = 0;
uint32_t expectedPeriodMs_ = 1; uint32_t stepTimeMs_ = 1, expectedPeriodMs_ = 1;
uint32_t repeatPeriods_[10] = {}; volatile uint8_t currentStep_ = 0;
volatile bool progressUpdatePending_ = false;
PulsePeriod periodBatch_[PERIOD_BATCH_SIZE] = {}; PulsePeriod periodBatch_[PERIOD_BATCH_SIZE] = {};
}; };

View File

@@ -1,5 +1,4 @@
#include "App.h" #include "App.h"
App app; App app;
void setup() { app.begin(); } void setup() { app.begin(); }

View File

@@ -0,0 +1,74 @@
#include "OpticalCurrent.h"
#include "Config.h"
namespace {
struct AdcAccumulator {
uint64_t senseRaw = 0;
uint64_t vccRaw = 0;
uint64_t senseMillivolts = 0;
uint64_t vccMillivolts = 0;
uint32_t samples = 0;
uint32_t startedMs = 0;
};
AdcAccumulator accumulator;
void resetAccumulator(uint32_t now) {
accumulator = {};
accumulator.startedMs = now;
}
OpticalCurrentMeasurement finishMeasurement() {
const float senseAdcVoltage =
accumulator.senseMillivolts / (1000.0f * accumulator.samples);
const float vccAdcVoltage =
accumulator.vccMillivolts / (1000.0f * accumulator.samples);
const float senseVoltage = senseAdcVoltage * OPTICAL_DIVIDER_RATIO;
const float vccVoltage = vccAdcVoltage * OPTICAL_DIVIDER_RATIO;
const float resistorVoltage = vccVoltage - senseVoltage;
const float currentMa = resistorVoltage * 1000.0f / OPTICAL_SENSE_R;
return {
static_cast<uint16_t>(accumulator.senseRaw / accumulator.samples),
static_cast<uint16_t>(accumulator.vccRaw / accumulator.samples),
senseAdcVoltage, vccAdcVoltage, senseVoltage, vccVoltage,
currentMa > 0.0f ? currentMa : 0.0f
};
}
}
void optical_current_begin(void) {
analogReadResolution(12);
pinMode(GPIO_OPTICAL_CURRENT, INPUT);
pinMode(GPIO_OPTICAL_VCC, INPUT);
// Both 10k/10k dividers can present about 2.5 V to their ADC inputs.
analogSetPinAttenuation(GPIO_OPTICAL_CURRENT, ADC_11db);
analogSetPinAttenuation(GPIO_OPTICAL_VCC, ADC_11db);
resetAccumulator(0);
}
bool optical_current_poll(OpticalCurrentMeasurement &measurement) {
const uint32_t now = millis();
if (!accumulator.startedMs) resetAccumulator(now ? now : 1U);
accumulator.senseRaw += analogRead(GPIO_OPTICAL_CURRENT);
accumulator.senseMillivolts += analogReadMilliVolts(GPIO_OPTICAL_CURRENT);
accumulator.vccRaw += analogRead(GPIO_OPTICAL_VCC);
accumulator.vccMillivolts += analogReadMilliVolts(GPIO_OPTICAL_VCC);
++accumulator.samples;
if (now - accumulator.startedMs < OPTICAL_CURRENT_AVERAGING_MS) return false;
measurement = finishMeasurement();
resetAccumulator(now);
return true;
}
OpticalCurrentMeasurement optical_get_led_measurement(void) {
OpticalCurrentMeasurement measurement = {};
resetAccumulator(millis());
while (!optical_current_poll(measurement)) delay(1);
return measurement;
}
float optical_get_led_current_ma(void) {
return optical_get_led_measurement().currentMa;
}

View File

@@ -0,0 +1,18 @@
#pragma once
#include <stdint.h>
struct OpticalCurrentMeasurement {
uint16_t senseRaw;
uint16_t vccRaw;
float senseAdcVoltage;
float vccAdcVoltage;
float senseVoltage;
float vccVoltage;
float currentMa;
};
void optical_current_begin(void);
bool optical_current_poll(OpticalCurrentMeasurement &measurement);
OpticalCurrentMeasurement optical_get_led_measurement(void);
float optical_get_led_current_ma(void);

View File

@@ -3,7 +3,7 @@
const char *messageName(MessageType type) { const char *messageName(MessageType type) {
static const char *names[] = {"DISCOVER", "DISCOVER_ACK", "PREPARE", "READY", static const char *names[] = {"DISCOVER", "DISCOVER_ACK", "PREPARE", "READY",
"START_STAGE", "RESULT", "ACK", "ABORT", "HEARTBEAT", "HEARTBEAT_ACK"}; "START_STAGE", "RESULT", "ACK", "ABORT", "HEARTBEAT", "HEARTBEAT_ACK", "PROGRESS"};
const uint8_t index = static_cast<uint8_t>(type); const uint8_t index = static_cast<uint8_t>(type);
return index < sizeof(names) / sizeof(names[0]) ? names[index] : "UNKNOWN"; return index < sizeof(names) / sizeof(names[0]) ? names[index] : "UNKNOWN";
} }
@@ -24,5 +24,5 @@ void finalizePacket(ProtocolPacket &p) {
bool validPacket(const ProtocolPacket &p) { bool validPacket(const ProtocolPacket &p) {
return p.magic == PROTOCOL_MAGIC && p.version == PROTOCOL_VERSION && return p.magic == PROTOCOL_MAGIC && p.version == PROTOCOL_VERSION &&
p.type <= static_cast<uint8_t>(MessageType::HEARTBEAT_ACK) && p.crc == packetCrc(p); p.type <= static_cast<uint8_t>(MessageType::PROGRESS) && p.crc == packetCrc(p);
} }

View File

@@ -2,11 +2,11 @@
#include "Core.h" #include "Core.h"
constexpr uint16_t PROTOCOL_MAGIC = 0x4F43; constexpr uint16_t PROTOCOL_MAGIC = 0x4F43;
constexpr uint8_t PROTOCOL_VERSION = 2; constexpr uint8_t PROTOCOL_VERSION = 12;
enum class MessageType : uint8_t { enum class MessageType : uint8_t {
DISCOVER, DISCOVER_ACK, PREPARE, READY, START_STAGE, RESULT, ACK, ABORT, DISCOVER, DISCOVER_ACK, PREPARE, READY, START_STAGE, RESULT, ACK, ABORT,
HEARTBEAT, HEARTBEAT_ACK HEARTBEAT, HEARTBEAT_ACK, PROGRESS
}; };
const char *messageName(MessageType type); const char *messageName(MessageType type);
@@ -18,24 +18,28 @@ struct ProtocolPacket {
uint8_t type; uint8_t type;
uint32_t session; uint32_t session;
uint16_t stage; uint16_t stage;
uint16_t stageCount;
uint16_t sequence; uint16_t sequence;
uint32_t requestedHz; uint32_t requestedHz;
uint32_t requestedPulseNs;
uint32_t actualHz; uint32_t actualHz;
uint16_t actualDutyX100; uint32_t actualPulseNs;
uint32_t testTimeMs; uint32_t testTimeMs;
uint8_t repeats;
uint16_t accuracyX100; uint16_t accuracyX100;
uint8_t settleCycles; uint8_t lightCode;
uint8_t progressStep;
uint8_t passed; uint8_t passed;
uint8_t reason; uint8_t reason;
uint32_t periods; uint32_t periods;
uint32_t measuredHzX10;
uint32_t measuredPulseNs;
uint32_t minPeriodTicks; uint32_t minPeriodTicks;
uint32_t maxPeriodTicks; uint32_t maxPeriodTicks;
uint16_t crc; uint16_t crc;
}; };
#pragma pack(pop) #pragma pack(pop)
static_assert(sizeof(ProtocolPacket) == 46, "Protocol layout changed"); static_assert(sizeof(ProtocolPacket) == 62, "Protocol layout changed");
uint16_t packetCrc(const ProtocolPacket &packet); uint16_t packetCrc(const ProtocolPacket &packet);
void finalizePacket(ProtocolPacket &packet); void finalizePacket(ProtocolPacket &packet);

View File

@@ -1,31 +1,248 @@
#include "Pwm.h" #include "Pwm.h"
#include "Config.h" #include "Config.h"
#include "Core.h" #include "Core.h"
#if CONFIG_IDF_TARGET_ESP32C3
#include <hal/ledc_ll.h>
#elif CONFIG_IDF_TARGET_ESP32S3
#include <driver/mcpwm_prelude.h>
#endif
namespace {
#if CONFIG_IDF_TARGET_ESP32C3
constexpr ledc_mode_t PWM_SPEED_MODE = LEDC_LOW_SPEED_MODE;
constexpr ledc_timer_t PWM_TIMER = LEDC_TIMER_0;
void setDivider(uint32_t dividerRaw) {
ledc_dev_t *hardware = LEDC_LL_GET_HW();
ledc_ll_timer_pause(hardware, PWM_SPEED_MODE, PWM_TIMER);
ledc_ll_set_clock_divider(hardware, PWM_SPEED_MODE, PWM_TIMER,
dividerRaw);
ledc_ll_timer_rst(hardware, PWM_SPEED_MODE, PWM_TIMER);
ledc_ll_ls_timer_update(hardware, PWM_SPEED_MODE, PWM_TIMER);
ledc_ll_timer_resume(hardware, PWM_SPEED_MODE, PWM_TIMER);
}
bool dividerIsSet(uint32_t expectedRaw) {
uint32_t rawDivider = 0;
ledc_ll_get_clock_divider(LEDC_LL_GET_HW(), PWM_SPEED_MODE, PWM_TIMER, &rawDivider);
return rawDivider == expectedRaw;
}
#elif CONFIG_IDF_TARGET_ESP32S3
mcpwm_timer_handle_t mcpwmTimer = nullptr;
mcpwm_oper_handle_t mcpwmOperator = nullptr;
mcpwm_cmpr_handle_t mcpwmComparator = nullptr;
mcpwm_gen_handle_t mcpwmGenerator = nullptr;
uint32_t mcpwmFrequencyHz = 0;
void releaseMcpwm() {
if (mcpwmGenerator) {
mcpwm_del_generator(mcpwmGenerator);
mcpwmGenerator = nullptr;
}
if (mcpwmComparator) {
mcpwm_del_comparator(mcpwmComparator);
mcpwmComparator = nullptr;
}
if (mcpwmOperator) {
mcpwm_del_operator(mcpwmOperator);
mcpwmOperator = nullptr;
}
if (mcpwmTimer) {
mcpwm_timer_disable(mcpwmTimer);
mcpwm_del_timer(mcpwmTimer);
mcpwmTimer = nullptr;
}
}
uint8_t periodResolutionBits(uint32_t periodTicks) {
uint8_t bits = 0;
while (periodTicks > 1U) {
periodTicks >>= 1U;
++bits;
}
return bits ? bits : 1U;
}
#endif
}
void PwmGenerator::begin() { void PwmGenerator::begin() {
#if CONFIG_IDF_TARGET_ESP32C3
// Match LEDC_SOURCE_CLOCK_HZ and make the timer calculation deterministic. // Match LEDC_SOURCE_CLOCK_HZ and make the timer calculation deterministic.
ledcSetClockSource(LEDC_USE_XTAL_CLK); ledcSetClockSource(LEDC_USE_XTAL_CLK);
pinMode(GPIO_PWM, OUTPUT); pinMode(GPIO_PWM, OUTPUT);
stop(); stop();
#elif CONFIG_IDF_TARGET_ESP32S3
mcpwm_timer_config_t timerConfig = {};
timerConfig.group_id = 0;
timerConfig.clk_src = MCPWM_TIMER_CLK_SRC_PLL160M;
timerConfig.resolution_hz = MCPWM_RESOLUTION_HZ;
timerConfig.count_mode = MCPWM_TIMER_COUNT_MODE_UP;
timerConfig.period_ticks = MCPWM_RESOLUTION_HZ / 1000U;
mcpwm_operator_config_t operatorConfig = {};
operatorConfig.group_id = 0;
mcpwm_comparator_config_t comparatorConfig = {};
mcpwm_generator_config_t generatorConfig = {};
generatorConfig.gen_gpio_num = GPIO_PWM;
bool ok = mcpwm_new_timer(&timerConfig, &mcpwmTimer) == ESP_OK;
ok = ok && mcpwm_new_operator(&operatorConfig, &mcpwmOperator) == ESP_OK;
ok = ok && mcpwm_operator_connect_timer(mcpwmOperator, mcpwmTimer) == ESP_OK;
ok = ok && mcpwm_new_comparator(mcpwmOperator, &comparatorConfig, &mcpwmComparator) == ESP_OK;
ok = ok && mcpwm_new_generator(mcpwmOperator, &generatorConfig, &mcpwmGenerator) == ESP_OK;
ok = ok && mcpwm_comparator_set_compare_value(mcpwmComparator,
timerConfig.period_ticks / 2U) == ESP_OK;
ok = ok && mcpwm_generator_set_action_on_timer_event(mcpwmGenerator,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP,
MCPWM_TIMER_EVENT_EMPTY, TX_LIGHT_ON_GPIO_LEVEL == HIGH ?
MCPWM_GEN_ACTION_HIGH : MCPWM_GEN_ACTION_LOW)) == ESP_OK;
ok = ok && mcpwm_generator_set_action_on_compare_event(mcpwmGenerator,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP,
mcpwmComparator, TX_LIGHT_OFF_GPIO_LEVEL == HIGH ?
MCPWM_GEN_ACTION_HIGH : MCPWM_GEN_ACTION_LOW)) == ESP_OK;
ok = ok && mcpwm_timer_enable(mcpwmTimer) == ESP_OK;
if (!ok) {
releaseMcpwm();
pinMode(GPIO_PWM, OUTPUT);
digitalWrite(GPIO_PWM, TX_LIGHT_OFF_GPIO_LEVEL);
return;
}
mcpwm_generator_set_force_level(mcpwmGenerator, TX_LIGHT_OFF_GPIO_LEVEL, true);
#endif
} }
bool PwmGenerator::start(uint32_t hz, uint8_t dutyPct, ActualPwm &a) { bool PwmGenerator::start(uint32_t hz, uint32_t pulseNs, ActualPwm &a) {
stop(); const uint8_t activeLevel = activeLightOn_ ? TX_LIGHT_ON_GPIO_LEVEL :
const uint8_t bits = choosePwmResolution(hz, LEDC_SOURCE_CLOCK_HZ, LEDC_MAX_BITS); TX_LIGHT_OFF_GPIO_LEVEL;
if (!bits) return false; const uint8_t inactiveLevel = activeLevel == HIGH ? LOW : HIGH;
if (!ledcAttachChannel(GPIO_PWM, hz, bits, LEDC_CHANNEL)) return false; #if CONFIG_IDF_TARGET_ESP32C3
IntegerPwmConfig config = {};
if (!choosePwmConfig(hz, pulseNs, LEDC_SOURCE_CLOCK_HZ, LEDC_MAX_BITS, config)) return false;
const uint8_t bits = config.bits;
const uint32_t levels = 1UL << bits; const uint32_t levels = 1UL << bits;
const uint32_t duty = (static_cast<uint64_t>(levels) * dutyPct + 50U) / 100U; const uint32_t duty = config.dutyCount;
if (!ledcWriteChannel(LEDC_CHANNEL, duty)) { ledcDetach(GPIO_PWM); return false; } for (uint8_t attempt = 0; attempt < 2; ++attempt) {
stop();
const bool attached = ledcAttachChannel(GPIO_PWM, config.actualHz, bits, LEDC_CHANNEL);
if (attached) {
// Native LEDC produces a HIGH pulse. Invert the GPIO matrix output when
// the configured active pulse level is LOW.
if (!ledcOutputInvert(GPIO_PWM, activeLevel == LOW)) {
ledcDetach(GPIO_PWM);
delay(2);
continue;
}
// Apply the selected Q10.8 divider explicitly. This lets pulse width,
// rather than duty percentage, drive the hardware quantization.
setDivider(config.dividerRaw);
}
if (attached && dividerIsSet(config.dividerRaw) && ledcWriteChannel(LEDC_CHANNEL, duty)) {
// On the first configuration after power-up the duty update is latched
// on a timer edge. Reading immediately can therefore return zero.
uint32_t settleUs = static_cast<uint32_t>((2000000ULL + hz - 1U) / hz);
if (settleUs > 2000U) settleUs = 2000U;
delayMicroseconds(settleUs);
const uint32_t actualHz = ledcReadFreq(GPIO_PWM); const uint32_t actualHz = ledcReadFreq(GPIO_PWM);
if (!actualHz) { ledcDetach(GPIO_PWM); return false; } if (actualHz == config.actualHz) {
a = {hz, actualHz, 100.0f * duty / levels, bits}; a = {hz, actualHz, pulseNs, config.actualPulseNs,
100.0f * duty / levels, bits};
running_ = true; running_ = true;
return true; return true;
}
}
if (attached) ledcDetach(GPIO_PWM);
delay(2);
}
pinMode(GPIO_PWM, OUTPUT); digitalWrite(GPIO_PWM, TX_LIGHT_OFF_GPIO_LEVEL);
return false;
#elif CONFIG_IDF_TARGET_ESP32S3
if (!mcpwmTimer || !mcpwmComparator || !mcpwmGenerator || !hz || !pulseNs ||
MCPWM_RESOLUTION_HZ % hz) return false;
const uint32_t periodTicks = MCPWM_RESOLUTION_HZ / hz;
if (periodTicks < 2U || periodTicks > MCPWM_MAX_PERIOD_TICKS) return false;
uint32_t activeTicks = static_cast<uint32_t>(
(static_cast<uint64_t>(pulseNs) * MCPWM_RESOLUTION_HZ + 500000000ULL) /
1000000000ULL);
if (activeTicks == 0U) activeTicks = 1U;
if (activeTicks >= periodTicks) activeTicks = periodTicks - 1U;
stop();
bool ok = mcpwm_timer_set_period(mcpwmTimer, periodTicks) == ESP_OK;
ok = ok && mcpwm_comparator_set_compare_value(mcpwmComparator, activeTicks) == ESP_OK;
ok = ok && mcpwm_generator_set_action_on_timer_event(mcpwmGenerator,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP,
MCPWM_TIMER_EVENT_EMPTY, activeLevel == HIGH ?
MCPWM_GEN_ACTION_HIGH : MCPWM_GEN_ACTION_LOW)) == ESP_OK;
ok = ok && mcpwm_generator_set_action_on_compare_event(mcpwmGenerator,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP,
mcpwmComparator, inactiveLevel == HIGH ?
MCPWM_GEN_ACTION_HIGH : MCPWM_GEN_ACTION_LOW)) == ESP_OK;
// stop() applies a continuous force level (hold_on=true). Remove that same
// continuous-force action; hold_on=false addresses a different, one-shot
// force mechanism and would leave the safe level permanently active.
ok = ok && mcpwm_generator_set_force_level(mcpwmGenerator, -1, true) == ESP_OK;
ok = ok && mcpwm_timer_start_stop(mcpwmTimer, MCPWM_TIMER_START_NO_STOP) == ESP_OK;
if (!ok) {
mcpwm_generator_set_force_level(mcpwmGenerator, TX_LIGHT_OFF_GPIO_LEVEL, true);
return false;
}
const uint32_t actualPulseNs = static_cast<uint32_t>(
(static_cast<uint64_t>(activeTicks) * 1000000000ULL + MCPWM_RESOLUTION_HZ / 2U) /
MCPWM_RESOLUTION_HZ);
a = {hz, hz, pulseNs, actualPulseNs, 100.0f * activeTicks / periodTicks,
periodResolutionBits(periodTicks)};
mcpwmFrequencyHz = hz;
running_ = true;
return true;
#endif
} }
void PwmGenerator::stop() { void PwmGenerator::stop() {
#if CONFIG_IDF_TARGET_ESP32C3
if (running_) ledcDetach(GPIO_PWM); if (running_) ledcDetach(GPIO_PWM);
pinMode(GPIO_PWM, OUTPUT); digitalWrite(GPIO_PWM, PWM_SAFE_LEVEL); pinMode(GPIO_PWM, OUTPUT); digitalWrite(GPIO_PWM, TX_LIGHT_OFF_GPIO_LEVEL);
#elif CONFIG_IDF_TARGET_ESP32S3
if (mcpwmGenerator) mcpwm_generator_set_force_level(
mcpwmGenerator, TX_LIGHT_OFF_GPIO_LEVEL, true);
if (running_ && mcpwmTimer) {
mcpwm_timer_start_stop(mcpwmTimer, MCPWM_TIMER_STOP_EMPTY);
const uint32_t waitUs = mcpwmFrequencyHz ? (1000000U / mcpwmFrequencyHz + 2U) : 2U;
delayMicroseconds(waitUs);
}
mcpwmFrequencyHz = 0;
#endif
running_ = false; running_ = false;
} }
void PwmGenerator::active() {
// First detach/stop the PWM peripheral, then apply the same active level
// that denotes the pulse during a running test.
stop();
#if CONFIG_IDF_TARGET_ESP32C3
digitalWrite(GPIO_PWM, activeLightOn_ ? TX_LIGHT_ON_GPIO_LEVEL :
TX_LIGHT_OFF_GPIO_LEVEL);
#elif CONFIG_IDF_TARGET_ESP32S3
const uint8_t level = activeLightOn_ ? TX_LIGHT_ON_GPIO_LEVEL :
TX_LIGHT_OFF_GPIO_LEVEL;
if (mcpwmGenerator) mcpwm_generator_set_force_level(mcpwmGenerator, level, true);
else {
pinMode(GPIO_PWM, OUTPUT);
digitalWrite(GPIO_PWM, level);
}
#endif
}
void PwmGenerator::lightOn() {
stop();
#if CONFIG_IDF_TARGET_ESP32C3
digitalWrite(GPIO_PWM, TX_LIGHT_ON_GPIO_LEVEL);
#elif CONFIG_IDF_TARGET_ESP32S3
if (mcpwmGenerator)
mcpwm_generator_set_force_level(mcpwmGenerator, TX_LIGHT_ON_GPIO_LEVEL, true);
else {
pinMode(GPIO_PWM, OUTPUT);
digitalWrite(GPIO_PWM, TX_LIGHT_ON_GPIO_LEVEL);
}
#endif
}

View File

@@ -1,14 +1,27 @@
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
struct ActualPwm { uint32_t requestedHz; uint32_t actualHz; float actualDutyPct; uint8_t bits; }; struct ActualPwm {
uint32_t requestedHz;
uint32_t actualHz;
uint32_t requestedPulseNs;
uint32_t actualPulseNs;
float actualDutyPct;
uint8_t bits;
};
class PwmGenerator { class PwmGenerator {
public: public:
void begin(); void begin();
bool start(uint32_t frequencyHz, uint8_t dutyPct, ActualPwm &actual); void configureActiveLight(bool lightOn) { activeLightOn_ = lightOn; }
bool start(uint32_t frequencyHz, uint32_t pulseNs, ActualPwm &actual);
// Hold the optical level selected as the active TX pulse.
void active();
// Hold actual optical light ON, independently of HH/HL/LH/LL.
void lightOn();
void stop(); void stop();
bool running() const { return running_; } bool running() const { return running_; }
private: private:
bool running_ = false; bool running_ = false;
bool activeLightOn_ = true;
}; };

View File

@@ -23,16 +23,22 @@ bool Radio::begin() {
if (esp_wifi_set_channel(ESPNOW_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE) != ESP_OK) { if (esp_wifi_set_channel(ESPNOW_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE) != ESP_OK) {
Log::event("ESP-NOW", "Wi-Fi channel setup FAILED"); return false; Log::event("ESP-NOW", "Wi-Fi channel setup FAILED"); return false;
} }
queue_ = xQueueCreate(8, sizeof(ReceivedPacket)); if (!queue_) queue_ = xQueueCreate(8, sizeof(ReceivedPacket));
if (!queue_) { Log::event("ESP-NOW", "receive queue creation FAILED"); return false; } if (!heartbeatQueue_) heartbeatQueue_ = xQueueCreate(4, sizeof(ReceivedPacket));
if (!heartbeatTask_ && xTaskCreate(heartbeatTaskEntry, "radio-hb", 2048, this, 5, &heartbeatTask_) != pdPASS)
heartbeatTask_ = nullptr;
if (!queue_ || !heartbeatQueue_ || !heartbeatTask_) {
Log::event("ESP-NOW", "receive/heartbeat service creation FAILED"); return false;
}
if (esp_now_init() != ESP_OK) { if (esp_now_init() != ESP_OK) {
vQueueDelete(queue_); queue_ = nullptr;
Log::event("ESP-NOW", "initialization FAILED"); return false; Log::event("ESP-NOW", "initialization FAILED"); return false;
} }
const esp_err_t rateResult = esp_wifi_config_espnow_rate(WIFI_IF_STA, WIFI_PHY_RATE_1M_L); const esp_err_t rateResult = esp_wifi_config_espnow_rate(WIFI_IF_STA, WIFI_PHY_RATE_1M_L);
instance_ = this; instance_ = this;
if (esp_now_register_recv_cb(onReceive) != ESP_OK) { end(); return false; } if (esp_now_register_recv_cb(onReceive) != ESP_OK) {
active_ = true; instance_ = nullptr; esp_now_deinit(); return false;
}
active_ = true; __atomic_store_n(&lastValidRxMs_, millis(), __ATOMIC_RELAXED);
const bool ok = ensurePeer(BROADCAST_MAC); const bool ok = ensurePeer(BROADCAST_MAC);
uint8_t primaryChannel = 0; uint8_t primaryChannel = 0;
wifi_second_chan_t secondaryChannel = WIFI_SECOND_CHAN_NONE; wifi_second_chan_t secondaryChannel = WIFI_SECOND_CHAN_NONE;
@@ -44,12 +50,45 @@ bool Radio::begin() {
} }
void Radio::end() { void Radio::end() {
if (active_) { esp_now_unregister_recv_cb(); esp_now_deinit(); } const bool wasActive = active_; active_ = false;
if (queue_) { vQueueDelete(queue_); queue_ = nullptr; } if (wasActive) { esp_now_unregister_recv_cb(); esp_now_deinit(); }
active_ = false; if (instance_ == this) instance_ = nullptr; if (queue_) xQueueReset(queue_);
if (heartbeatQueue_) xQueueReset(heartbeatQueue_);
if (instance_ == this) instance_ = nullptr;
windowedReceive_ = false;
if (wasActive) WiFi.mode(WIFI_OFF);
Log::event("ESP-NOW", "stopped"); Log::event("ESP-NOW", "stopped");
} }
bool Radio::setWindowedReceive(bool enabled) {
if (!active_) return false;
if (windowedReceive_ == enabled) return true;
const uint16_t window = enabled ? SLAVE_LISTEN_WINDOW_MS : UINT16_MAX;
const uint16_t interval = enabled ? SLAVE_LISTEN_INTERVAL_MS :
ESP_WIFI_CONNECTIONLESS_INTERVAL_DEFAULT_MODE;
const esp_err_t windowResult = esp_now_set_wake_window(window);
const esp_err_t intervalResult = esp_wifi_connectionless_module_set_wake_interval(interval);
const bool sleepResult = WiFi.setSleep(enabled);
const bool ok = windowResult == ESP_OK && intervalResult == ESP_OK && sleepResult;
bool continuousRestored = true;
if (!ok) {
continuousRestored = esp_now_set_wake_window(UINT16_MAX) == ESP_OK;
continuousRestored =
esp_wifi_connectionless_module_set_wake_interval(
ESP_WIFI_CONNECTIONLESS_INTERVAL_DEFAULT_MODE) == ESP_OK && continuousRestored;
continuousRestored = WiFi.setSleep(false) && continuousRestored;
windowedReceive_ = false;
} else windowedReceive_ = enabled;
Log::printf("ESP-NOW", "RX power-save %s window=%ums interval=%ums %s%s",
enabled ? "ON" : "OFF", window, interval,
ok ? "OK" : "FAILED; continuous RX restore ",
ok ? "" : (continuousRestored ? "OK" : "FAILED"));
return ok;
}
bool Radio::ensurePeer(const uint8_t mac[6]) { bool Radio::ensurePeer(const uint8_t mac[6]) {
if (esp_now_is_peer_exist(mac)) return true; if (esp_now_is_peer_exist(mac)) return true;
esp_now_peer_info_t peer = {}; esp_now_peer_info_t peer = {};
@@ -72,7 +111,8 @@ bool Radio::sendTo(const uint8_t mac[6], ProtocolPacket p) {
finalizePacket(p); finalizePacket(p);
const bool ok = esp_now_send(mac, reinterpret_cast<const uint8_t *>(&p), sizeof(p)) == ESP_OK; const bool ok = esp_now_send(mac, reinterpret_cast<const uint8_t *>(&p), sizeof(p)) == ESP_OK;
const MessageType type = static_cast<MessageType>(p.type); const MessageType type = static_cast<MessageType>(p.type);
if (type != MessageType::HEARTBEAT && type != MessageType::HEARTBEAT_ACK) if (type != MessageType::DISCOVER && type != MessageType::HEARTBEAT &&
type != MessageType::HEARTBEAT_ACK && type != MessageType::PROGRESS)
Log::printf("ESP-NOW", "TX %s to %s session=%08lX stage=%u seq=%u %s", Log::printf("ESP-NOW", "TX %s to %s session=%08lX stage=%u seq=%u %s",
messageName(type), peer, p.session, p.stage, p.sequence, ok ? "QUEUED" : "FAILED"); messageName(type), peer, p.session, p.stage, p.sequence, ok ? "QUEUED" : "FAILED");
return ok; return ok;
@@ -97,16 +137,37 @@ void Radio::maintainChannel() {
restored ? "RESTORED" : "FAILED"); restored ? "RESTORED" : "FAILED");
} }
void Radio::flush() { if (queue_) xQueueReset(queue_); } void Radio::flush() {
if (queue_) xQueueReset(queue_);
if (heartbeatQueue_) xQueueReset(heartbeatQueue_);
}
void Radio::onReceive(const esp_now_recv_info_t *info, const uint8_t *data, int length) { void Radio::onReceive(const esp_now_recv_info_t *info, const uint8_t *data, int length) {
if (!instance_ || !instance_->queue_ || !info || length != sizeof(ProtocolPacket)) return; if (!instance_ || !instance_->queue_ || !info || length != sizeof(ProtocolPacket)) return;
ReceivedPacket item; ReceivedPacket item;
memcpy(item.mac, info->src_addr, 6); memcpy(&item.packet, data, sizeof(item.packet)); memcpy(item.mac, info->src_addr, 6); memcpy(&item.packet, data, sizeof(item.packet));
if (!validPacket(item.packet)) return; if (!validPacket(item.packet)) return;
__atomic_store_n(&instance_->lastValidRxMs_, millis(), __ATOMIC_RELAXED);
if (static_cast<MessageType>(item.packet.type) == MessageType::HEARTBEAT && instance_->heartbeatQueue_)
xQueueSend(instance_->heartbeatQueue_, &item, 0);
xQueueSend(instance_->queue_, &item, 0); // Wi-Fi task callback: copy only, never block xQueueSend(instance_->queue_, &item, 0); // Wi-Fi task callback: copy only, never block
} }
void Radio::heartbeatTaskEntry(void *context) {
static_cast<Radio *>(context)->heartbeatTaskLoop();
}
void Radio::heartbeatTaskLoop() {
ReceivedPacket item;
for (;;) {
if (xQueueReceive(heartbeatQueue_, &item, portMAX_DELAY) != pdTRUE || !active_) continue;
ProtocolPacket ack = item.packet;
ack.type = static_cast<uint8_t>(MessageType::HEARTBEAT_ACK);
finalizePacket(ack);
esp_now_send(item.mac, reinterpret_cast<const uint8_t *>(&ack), sizeof(ack));
}
}
void Radio::macText(const uint8_t mac[6], char *out, size_t n) { void Radio::macText(const uint8_t mac[6], char *out, size_t n) {
snprintf(out, n, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); snprintf(out, n, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
} }

View File

@@ -12,15 +12,23 @@ class Radio {
bool sendBroadcast(ProtocolPacket packet); bool sendBroadcast(ProtocolPacket packet);
bool sendTo(const uint8_t mac[6], ProtocolPacket packet); bool sendTo(const uint8_t mac[6], ProtocolPacket packet);
bool receive(ReceivedPacket &received); bool receive(ReceivedPacket &received);
bool setWindowedReceive(bool enabled);
void flush(); void flush();
uint32_t lastReceiveMs() const { return __atomic_load_n(&lastValidRxMs_, __ATOMIC_RELAXED); }
static void macText(const uint8_t mac[6], char *out, size_t size); static void macText(const uint8_t mac[6], char *out, size_t size);
private: private:
static void onReceive(const esp_now_recv_info_t *info, const uint8_t *data, int length); static void onReceive(const esp_now_recv_info_t *info, const uint8_t *data, int length);
static void heartbeatTaskEntry(void *context);
void heartbeatTaskLoop();
bool ensurePeer(const uint8_t mac[6]); bool ensurePeer(const uint8_t mac[6]);
void maintainChannel(); void maintainChannel();
static Radio *instance_; static Radio *instance_;
QueueHandle_t queue_ = nullptr; QueueHandle_t queue_ = nullptr;
bool active_ = false; QueueHandle_t heartbeatQueue_ = nullptr;
TaskHandle_t heartbeatTask_ = nullptr;
volatile bool active_ = false;
volatile uint32_t lastValidRxMs_ = 0;
uint32_t lastChannelCheckMs_ = 0; uint32_t lastChannelCheckMs_ = 0;
bool windowedReceive_ = false;
}; };

View File

@@ -1,41 +1,71 @@
#include "Receiver.h" #include "Receiver.h"
#include "Config.h" #include "Log.h"
#include <string.h> #include <math.h>
#if !OPTICAL_USE_RMT_DMA #if !OPTICAL_USE_MCPWM_CAPTURE
#include <esp_cpu.h> #include <esp_cpu.h>
#include <esp32-hal-cpu.h> #include <esp32-hal-cpu.h>
#endif #endif
uint32_t PulseReceiver::tickHz() const { uint32_t PulseReceiver::tickHz() const {
#if OPTICAL_USE_RMT_DMA #if OPTICAL_USE_MCPWM_CAPTURE
return CAPTURE_RESOLUTION_HZ; return captureResolutionHz_;
#else
return cpuTickHz_;
#endif
}
uint32_t PulseReceiver::plannedTickHz(uint32_t expectedHz, float expectedDutyPct) const {
if (!expectedHz || expectedDutyPct <= 0.0f || expectedDutyPct >= 100.0f) return 0;
#if OPTICAL_USE_MCPWM_CAPTURE
return captureResolutionHz_ ? captureResolutionHz_ :
MCPWM_CAPTURE_RESOLUTION_HZ;
#else #else
return cpuTickHz_; return cpuTickHz_;
#endif #endif
} }
bool PulseReceiver::begin() { bool PulseReceiver::begin() {
#if OPTICAL_USE_RMT_DMA queue_ = xQueueCreate(512, sizeof(Edge));
queue_ = xQueueCreate(RMT_QUEUE_BLOCKS, sizeof(SymbolBlock));
rmt_rx_channel_config_t cfg = {};
cfg.clk_src = RMT_CLK_SRC_DEFAULT; cfg.resolution_hz = CAPTURE_RESOLUTION_HZ;
cfg.gpio_num = static_cast<gpio_num_t>(GPIO_RX);
cfg.flags.invert_in = RX_SIGNAL_INVERTED;
#if CONFIG_IDF_TARGET_ESP32S3
cfg.mem_block_symbols = 512;
cfg.flags.with_dma = true;
#else
// C3 has 48 RMT symbols per channel and no RMT DMA. A request for 512
// consumes all available blocks and fails with "no free rx channels".
cfg.mem_block_symbols = RMT_MIN_RECEIVE_SYMBOLS;
cfg.flags.with_dma = false; // C3 uses hardware RMT ping-pong partial reception
#endif
if (!queue_ || rmt_new_rx_channel(&cfg, &channel_) != ESP_OK) return false;
rmt_rx_event_callbacks_t callbacks = {}; callbacks.on_recv_done = onRmt;
return rmt_rx_register_event_callbacks(channel_, &callbacks, this) == ESP_OK;
#else
queue_ = xQueueCreate(256, sizeof(Edge));
if (!queue_) return false; if (!queue_) return false;
#if OPTICAL_USE_MCPWM_CAPTURE
// PWM generation uses MCPWM group 0. Group 1 is dedicated to input capture,
// so RX cannot exhaust or conflict with the generator's resources.
mcpwm_capture_timer_config_t timerConfig = {};
timerConfig.group_id = 1;
timerConfig.clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT;
timerConfig.resolution_hz = MCPWM_CAPTURE_RESOLUTION_HZ;
if (mcpwm_new_capture_timer(&timerConfig, &captureTimer_) != ESP_OK) return false;
if (mcpwm_capture_timer_get_resolution(captureTimer_, &captureResolutionHz_) != ESP_OK ||
!captureResolutionHz_) return false;
mcpwm_capture_channel_config_t channelConfig = {};
channelConfig.gpio_num = GPIO_RX;
// ACK pulses are sub-microsecond and consecutive acknowledgements can be
// only 1 us apart. A low-priority capture interrupt can leave the channel
// status pending long enough for the next timestamp to overwrite it.
channelConfig.intr_priority = 3;
channelConfig.prescale = 1;
channelConfig.flags.pos_edge = true;
channelConfig.flags.neg_edge = false;
if (mcpwm_new_capture_channel(captureTimer_, &channelConfig, &risingChannel_) != ESP_OK)
return false;
mcpwm_capture_event_callbacks_t callbacks = {};
callbacks.on_cap = onCapture;
if (mcpwm_capture_channel_register_event_callbacks(
risingChannel_, &callbacks, this) != ESP_OK) return false;
channelConfig.flags.pos_edge = false;
channelConfig.flags.neg_edge = true;
if (mcpwm_new_capture_channel(captureTimer_, &channelConfig, &fallingChannel_) != ESP_OK)
return false;
if (mcpwm_capture_channel_register_event_callbacks(
fallingChannel_, &callbacks, this) != ESP_OK) return false;
// The TX channel is created immediately before a driver test. Only the end
// of the active PWM pulse is armed; handling its start here would occupy the
// shared MCPWM ISR during the RX acknowledgement only ~300 ns later.
return true;
#else
pinMode(GPIO_RX, INPUT); pinMode(GPIO_RX, INPUT);
cpuTickHz_ = getCpuFrequencyMhz() * 1000000UL; cpuTickHz_ = getCpuFrequencyMhz() * 1000000UL;
attachInterruptArg(GPIO_RX, onGpio, this, CHANGE); attachInterruptArg(GPIO_RX, onGpio, this, CHANGE);
@@ -43,138 +73,402 @@ bool PulseReceiver::begin() {
#endif #endif
} }
bool PulseReceiver::start(uint32_t expectedHz) { bool PulseReceiver::start(uint32_t expectedHz, float expectedDutyPct,
bool activeLightOn) {
(void)activeLightOn;
if (!plannedTickHz(expectedHz, expectedDutyPct)) return false;
expectedHz_ = expectedHz;
expectedDutyPct_ = expectedDutyPct;
#if !OPTICAL_USE_MCPWM_CAPTURE
cpuTickHz_ = getCpuFrequencyMhz() * 1000000UL;
if (!cpuTickHz_) return false;
#endif
resetStream(); resetStream();
#if OPTICAL_USE_RMT_DMA Log::event("CAPTURE", "RX optical polarity will be detected automatically");
// In partial RX mode the callback is delivered when this user buffer fills. return startCapture(false);
// Keep chunks near 5 ms so low-frequency input is reported before NO SIGNAL. }
uint64_t symbols = (static_cast<uint64_t>(expectedHz) * RMT_TARGET_CHUNK_US + 999999ULL) / 1000000ULL;
if (symbols < RMT_MIN_RECEIVE_SYMBOLS) symbols = RMT_MIN_RECEIVE_SYMBOLS; #if OPTICAL_USE_MCPWM_CAPTURE
if (symbols > RMT_MAX_RECEIVE_SYMBOLS) symbols = RMT_MAX_RECEIVE_SYMBOLS; bool PulseReceiver::configureDriverTxCapture(bool risingEdge) {
receiveChunkSymbols_ = static_cast<uint16_t>(symbols); if (running_) return false;
if (rmt_enable(channel_) != ESP_OK) return false; if (txChannel_) {
rmt_receive_config_t cfg = {}; if (mcpwm_del_capture_channel(txChannel_) != ESP_OK) return false;
cfg.signal_range_min_ns = 20; txChannel_ = nullptr;
const uint64_t maxNs = 4000000000ULL / (expectedHz ? expectedHz : 1); }
cfg.signal_range_max_ns = maxNs > 100000000ULL ? 100000000UL : static_cast<uint32_t>(maxNs); mcpwm_capture_channel_config_t config = {};
cfg.flags.en_partial_rx = true; config.gpio_num = GPIO_PWM;
if (rmt_receive(channel_, receiveBuffer_, config.intr_priority = 3;
receiveChunkSymbols_ * sizeof(receiveBuffer_[0]), &cfg) != ESP_OK) { config.prescale = 1;
rmt_disable(channel_); return false; config.flags.pos_edge = risingEdge;
config.flags.neg_edge = !risingEdge;
config.flags.io_loop_back = true;
if (mcpwm_new_capture_channel(captureTimer_, &config, &txChannel_) != ESP_OK)
return false;
mcpwm_capture_event_callbacks_t callbacks = {};
callbacks.on_cap = onCapture;
return mcpwm_capture_channel_register_event_callbacks(
txChannel_, &callbacks, this) == ESP_OK;
}
#endif
bool PulseReceiver::startDriver(uint32_t frequencyHz, uint32_t pulseNs,
bool activeTxLightOn) {
#if OPTICAL_USE_MCPWM_CAPTURE
if (!frequencyHz || !pulseNs || !tickHz() || running_) return false;
resetStream();
const uint64_t pulseTicks =
(static_cast<uint64_t>(pulseNs) * tickHz() + 500000000ULL) /
1000000000ULL;
const uint32_t periodTicks = tickHz() / frequencyHz;
if (!pulseTicks || pulseTicks >= periodTicks || pulseTicks > UINT32_MAX)
return false;
driverPulseTicks_ = static_cast<uint32_t>(pulseTicks);
driverReleaseSlackTicks_ = static_cast<uint32_t>(
(static_cast<uint64_t>(DRIVER_RESPONSE_TIMEOUT_NS) * tickHz() +
999999999ULL) / 1000000000ULL);
const uint8_t activeRawLevel = activeTxLightOn ?
TX_LIGHT_ON_GPIO_LEVEL : TX_LIGHT_OFF_GPIO_LEVEL;
const bool pulseEndIsRising = activeRawLevel == LOW;
if (!driverReleaseSlackTicks_ ||
!configureDriverTxCapture(pulseEndIsRising)) return false;
return startCapture(true);
#else
return false;
#endif
}
bool PulseReceiver::startCapture(bool withTx) {
#if OPTICAL_USE_MCPWM_CAPTURE
// Progress updates keep one capture session alive. Pulse-width stages stop
// capture only after PWM is quiet, so resetStream never races the ISR.
if (running_) return true;
if (mcpwm_capture_timer_enable(captureTimer_) != ESP_OK) return false;
if (mcpwm_capture_channel_enable(risingChannel_) != ESP_OK) {
mcpwm_capture_timer_disable(captureTimer_);
return false;
}
if (mcpwm_capture_channel_enable(fallingChannel_) != ESP_OK) {
mcpwm_capture_channel_disable(risingChannel_);
mcpwm_capture_timer_disable(captureTimer_);
return false;
}
if (withTx && mcpwm_capture_channel_enable(txChannel_) != ESP_OK) {
mcpwm_capture_channel_disable(fallingChannel_);
mcpwm_capture_channel_disable(risingChannel_);
mcpwm_capture_timer_disable(captureTimer_);
return false;
}
txCaptureEnabled_ = withTx;
running_ = true;
if (mcpwm_capture_timer_start(captureTimer_) != ESP_OK) {
running_ = false;
if (txCaptureEnabled_) mcpwm_capture_channel_disable(txChannel_);
txCaptureEnabled_ = false;
mcpwm_capture_channel_disable(fallingChannel_);
mcpwm_capture_channel_disable(risingChannel_);
mcpwm_capture_timer_disable(captureTimer_);
return false;
} }
#else #else
(void)expectedHz; if (withTx) return false;
running_ = true;
#endif #endif
running_ = true; return true; return true;
} }
void PulseReceiver::stop() { void PulseReceiver::stop() {
#if OPTICAL_USE_RMT_DMA const bool wasRunning = running_;
if (running_) rmt_disable(channel_);
#endif
running_ = false; running_ = false;
#if OPTICAL_USE_MCPWM_CAPTURE
if (wasRunning) {
// Mask capture interrupts before stopping the shared capture timer.
if (txCaptureEnabled_) mcpwm_capture_channel_disable(txChannel_);
txCaptureEnabled_ = false;
mcpwm_capture_channel_disable(fallingChannel_);
mcpwm_capture_channel_disable(risingChannel_);
mcpwm_capture_timer_stop(captureTimer_);
mcpwm_capture_timer_disable(captureTimer_);
}
#else
(void)wasRunning;
#endif
} }
void PulseReceiver::resetStream() { void PulseReceiver::resetStream() {
if (queue_) xQueueReset(queue_); if (queue_) xQueueReset(queue_);
overflow_ = false; droppedItems_ = 0; haveRise_ = haveFall_ = haveRawTick_ = false; haveReorderEdge_ = false;
lastRawTick_ = 0; tickEpoch_ = rise_ = fall_ = 0; __atomic_store_n(&driverRingWrite_, 0U, __ATOMIC_RELEASE);
#if OPTICAL_USE_RMT_DMA __atomic_store_n(&driverRingRead_, 0U, __ATOMIC_RELEASE);
block_ = {}; blockIndex_ = 0; phase_ = 0; haveLevel_ = false; level_ = false; rmtTick_ = 0; haveLastDriverEdge_ = false;
#endif lastDriverEdge_ = {};
driverPulseTicks_ = 0;
driverReleaseSlackTicks_ = 0;
droppedItems_ = 0;
polarityKnown_ = false;
activeStartRising_ = false;
polarityEdgeCount_ = 0;
memset(polarityEdges_, 0, sizeof(polarityEdges_));
waitingForActiveEnd_ = true;
activeStart_ = activeEnd_ = 0;
haveRawTick_ = false;
lastRawTick_ = 0;
tickEpoch_ = 0;
} }
bool PulseReceiver::consumeEdge(const Edge &e, PulsePeriod &out) { PulseReceiver::TimedEdge PulseReceiver::extendEdge(const Edge &e) {
if (haveRawTick_ && e.tick < lastRawTick_ && lastRawTick_ - e.tick > 0x80000000UL) if (haveRawTick_ && e.tick < lastRawTick_ && lastRawTick_ - e.tick > 0x80000000UL)
tickEpoch_ += 0x100000000ULL; tickEpoch_ += 0x100000000ULL;
haveRawTick_ = true; lastRawTick_ = e.tick; haveRawTick_ = true; lastRawTick_ = e.tick;
const uint64_t tick = tickEpoch_ + e.tick; return {tickEpoch_ + e.tick, e.rising != 0};
if (e.rising) {
if (!haveRise_) { rise_ = tick; haveRise_ = true; haveFall_ = false; return false; }
if (!haveFall_) { overflow_ = true; rise_ = tick; return false; }
const uint32_t period = static_cast<uint32_t>(tick - rise_);
const uint32_t active = fall_ - rise_;
out = {rise_, period, active}; rise_ = tick; haveFall_ = false;
return true;
}
// Reception can begin in the middle of a HIGH pulse. In that case the first
// observable edge is falling and there is no complete period to validate.
// Ignore only this leading partial pulse and synchronize on the next rise.
if (!haveRise_) return false;
if (haveFall_) { overflow_ = true; return false; }
fall_ = tick; haveFall_ = true; return false;
} }
bool PulseReceiver::overflowed() { bool PulseReceiver::consumeEdge(const Edge &rawEdge, PulsePeriod &out) {
const bool value = overflow_; overflow_ = false; return value; const TimedEdge edge = extendEdge(rawEdge);
if (polarityKnown_) {
// Deliberately ignore edge type after synchronization. A PWM waveform is
// just alternating intervals: active, inactive, active, inactive. An
// extra or missing edge therefore becomes a concrete wrong pulse/period
// instead of an ambiguous GLITCH state.
if (waitingForActiveEnd_) {
activeEnd_ = edge.tick;
waitingForActiveEnd_ = false;
return false;
}
const uint64_t periodTicks = edge.tick - activeStart_;
const uint64_t activeTicks = activeEnd_ - activeStart_;
out = {activeStart_, static_cast<uint32_t>(periodTicks),
static_cast<uint32_t>(activeTicks), tickHz()};
activeStart_ = edge.tick;
waitingForActiveEnd_ = true;
return true;
}
// Optical mode does not use the DRIVER level setting. Compare the first two
// alternating intervals with the configured active duration and select the
// level that is actually present at RX. Three edges are enough to determine
// polarity and, when the first interval is active, produce the first period.
polarityEdges_[polarityEdgeCount_++] = edge;
if (polarityEdgeCount_ < 3U) return false;
const uint64_t firstInterval =
polarityEdges_[1].tick - polarityEdges_[0].tick;
const uint64_t secondInterval =
polarityEdges_[2].tick - polarityEdges_[1].tick;
const uint64_t expectedPeriod = tickHz() / expectedHz_;
const uint64_t expectedActive = static_cast<uint64_t>(
expectedPeriod * expectedDutyPct_ / 100.0f + 0.5f);
const uint64_t firstError = firstInterval > expectedActive ?
firstInterval - expectedActive : expectedActive - firstInterval;
const uint64_t secondError = secondInterval > expectedActive ?
secondInterval - expectedActive : expectedActive - secondInterval;
const bool firstIntervalIsActive = firstError <= secondError;
activeStartRising_ = firstIntervalIsActive ? polarityEdges_[0].rising :
polarityEdges_[1].rising;
polarityKnown_ = true;
polarityEdgeCount_ = 0;
if (firstIntervalIsActive) {
out = {polarityEdges_[0].tick,
static_cast<uint32_t>(polarityEdges_[2].tick - polarityEdges_[0].tick),
static_cast<uint32_t>(firstInterval), tickHz()};
activeStart_ = polarityEdges_[2].tick;
waitingForActiveEnd_ = true;
return true;
}
activeStart_ = polarityEdges_[1].tick;
activeEnd_ = polarityEdges_[2].tick;
waitingForActiveEnd_ = false;
return false;
} }
uint32_t PulseReceiver::takeDroppedItems() { uint32_t PulseReceiver::takeDroppedItems() {
return __atomic_exchange_n(&droppedItems_, 0, __ATOMIC_RELAXED); return __atomic_exchange_n(&droppedItems_, 0, __ATOMIC_RELAXED);
} }
#if OPTICAL_USE_RMT_DMA #if OPTICAL_USE_MCPWM_CAPTURE
bool IRAM_ATTR PulseReceiver::onRmt(rmt_channel_handle_t, const rmt_rx_done_event_data_t *data, void *ctx) { bool IRAM_ATTR PulseReceiver::onCapture(mcpwm_cap_channel_handle_t channel,
const mcpwm_capture_event_data_t *data,
void *ctx) {
PulseReceiver *self = static_cast<PulseReceiver *>(ctx); PulseReceiver *self = static_cast<PulseReceiver *>(ctx);
if (!self->running_) return false;
const bool rawRising = data->cap_edge == MCPWM_CAP_EDGE_POS;
const Edge edge = {data->cap_value, static_cast<uint8_t>(rawRising),
static_cast<uint8_t>(channel == self->txChannel_ ? CaptureSource::TX :
CaptureSource::RX)};
if (self->txCaptureEnabled_) {
// All channels in one MCPWM group are dispatched serially by the same
// group ISR. Keep this callback shorter than the minimum interval between
// equal RX edges (about 2.1 us at W=2 us): a spinlock and several atomic
// RMW operations here can leave the channel pending until its capture
// register is overwritten by the next edge.
if (self->haveLastDriverEdge_ &&
self->lastDriverEdge_.tick == edge.tick &&
self->lastDriverEdge_.rising == edge.rising &&
self->lastDriverEdge_.source == edge.source) {
// The same channel callback can be delivered twice while several MCPWM
// capture status bits are pending. Two physical edges cannot have the
// same source, direction and 12.5 ns hardware timestamp.
return false;
}
const uint16_t write = self->driverRingWrite_;
const uint16_t next = static_cast<uint16_t>(
(write + 1U) & (DRIVER_RING_CAPACITY - 1U));
if (next == self->driverRingRead_) {
++self->droppedItems_;
} else {
self->driverRing_[write] = edge;
self->lastDriverEdge_ = edge;
self->haveLastDriverEdge_ = true;
asm volatile("memw" ::: "memory");
self->driverRingWrite_ = next;
}
return false;
}
BaseType_t wake = pdFALSE; BaseType_t wake = pdFALSE;
size_t offset = 0; if (xQueueSendFromISR(self->queue_, &edge, &wake) != pdTRUE)
while (offset < data->num_symbols) { __atomic_fetch_add(&self->droppedItems_, 1U, __ATOMIC_RELAXED);
SymbolBlock &b = self->isrBlock_;
b.count = static_cast<uint16_t>((data->num_symbols - offset) > BLOCK_SYMBOLS ?
BLOCK_SYMBOLS : (data->num_symbols - offset));
memcpy(b.symbols, data->received_symbols + offset, b.count * sizeof(rmt_symbol_word_t));
if (xQueueSendFromISR(self->queue_, &b, &wake) != pdTRUE)
__atomic_fetch_add(&self->droppedItems_, b.count, __ATOMIC_RELAXED);
offset += b.count;
}
return wake == pdTRUE; return wake == pdTRUE;
} }
bool PulseReceiver::nextRmtEdge(Edge &edge) {
for (;;) {
if (blockIndex_ >= block_.count) {
if (xQueueReceive(queue_, &block_, 0) != pdTRUE) return false;
blockIndex_ = 0; phase_ = 0;
}
const rmt_symbol_word_t &s = block_.symbols[blockIndex_];
const bool nextLevel = phase_ == 0 ? s.level0 : s.level1;
const uint32_t duration = phase_ == 0 ? s.duration0 : s.duration1;
phase_ ^= 1;
if (phase_ == 0) ++blockIndex_;
if (!duration) continue;
if (!haveLevel_) { haveLevel_ = true; level_ = nextLevel; rmtTick_ += duration; continue; }
if (nextLevel != level_) {
level_ = nextLevel; edge = {rmtTick_, static_cast<uint8_t>(nextLevel)};
rmtTick_ += duration; return true;
}
rmtTick_ += duration;
}
}
size_t PulseReceiver::readPeriods(PulsePeriod *periods, size_t capacity) {
size_t count = 0;
Edge e;
while (count < capacity && nextRmtEdge(e))
if (consumeEdge(e, periods[count])) ++count;
return count;
}
#else #else
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)); if (!self->running_) return;
if (RX_SIGNAL_INVERTED) level = !level; const bool level = gpio_get_level(static_cast<gpio_num_t>(GPIO_RX));
Edge e = {esp_cpu_get_cycle_count(), static_cast<uint8_t>(level)}; const Edge edge = {esp_cpu_get_cycle_count(), static_cast<uint8_t>(level),
static_cast<uint8_t>(CaptureSource::RX)};
BaseType_t wake = pdFALSE; BaseType_t wake = pdFALSE;
if (xQueueSendFromISR(self->queue_, &e, &wake) != pdTRUE) if (xQueueSendFromISR(self->queue_, &edge, &wake) != pdTRUE)
__atomic_fetch_add(&self->droppedItems_, 1U, __ATOMIC_RELAXED); __atomic_fetch_add(&self->droppedItems_, 1U, __ATOMIC_RELAXED);
if (wake) portYIELD_FROM_ISR(); if (wake) portYIELD_FROM_ISR();
} }
#endif
size_t PulseReceiver::readPeriods(PulsePeriod *periods, size_t capacity) { bool PulseReceiver::nextOrderedEdge(Edge &edge, TickType_t waitTicks) {
if (!haveReorderEdge_) {
if (xQueueReceive(queue_, &reorderEdge_, waitTicks) != pdTRUE) return false;
haveReorderEdge_ = true;
}
Edge next = {};
// Keep one-event look-ahead. If both channel interrupts were pending while
// OLED/I2C ran, the MCPWM driver may dispatch them by channel number rather
// than timestamp. The signed modular comparison restores their real order.
if (xQueueReceive(queue_, &next, waitTicks) != pdTRUE) return false;
if (static_cast<int32_t>(next.tick - reorderEdge_.tick) < 0) {
edge = next;
} else {
edge = reorderEdge_;
reorderEdge_ = next;
}
return true;
}
size_t PulseReceiver::readPeriods(PulsePeriod *periods, size_t capacity,
TickType_t waitTicks) {
size_t count = 0; size_t count = 0;
Edge e; Edge edge = {};
while (count < capacity && xQueueReceive(queue_, &e, 0) == pdTRUE) while (count < capacity && nextOrderedEdge(edge, count ? 0 : waitTicks)) {
if (consumeEdge(e, periods[count])) ++count; if (consumeEdge(edge, periods[count])) {
periods[count].activeTickHz = tickHz();
++count;
}
}
return count;
}
size_t PulseReceiver::readEvents(CaptureEvent *events, size_t capacity,
TickType_t waitTicks) {
if (!events || capacity < 3U || !txCaptureEnabled_ || !driverPulseTicks_ ||
!driverReleaseSlackTicks_) return 0;
constexpr size_t MAX_BATCH = 64;
const size_t limit = capacity < MAX_BATCH ? capacity : MAX_BATCH;
Edge ordered[MAX_BATCH] = {};
uint16_t read = __atomic_load_n(&driverRingRead_, __ATOMIC_RELAXED);
if (read == __atomic_load_n(&driverRingWrite_, __ATOMIC_ACQUIRE) && waitTicks) {
vTaskDelay(waitTicks);
read = __atomic_load_n(&driverRingRead_, __ATOMIC_RELAXED);
}
// Work on one immutable producer snapshot. RX belonging to a pulse start is
// deliberately retained until that pulse's captured end arrives: only then
// can the missing start interrupt be reconstructed and sorted before RX.
const uint16_t write = __atomic_load_n(&driverRingWrite_, __ATOMIC_ACQUIRE);
uint16_t scan = read;
bool haveLatestTxEnd = false;
bool haveReleaseTxEnd = false;
uint32_t latestTxEnd = 0;
uint32_t releaseTxEnd = 0;
size_t projectedCount = 0;
while (scan != write) {
const Edge &edge = driverRing_[scan];
const size_t needed = edge.source == static_cast<uint8_t>(CaptureSource::TX)
? 2U : 1U;
// Reserve one output slot for WINDOW_END.
if (projectedCount + needed + 1U > limit) break;
projectedCount += needed;
if (edge.source == static_cast<uint8_t>(CaptureSource::TX)) {
if (haveLatestTxEnd) {
releaseTxEnd = latestTxEnd;
haveReleaseTxEnd = true;
}
latestTxEnd = edge.tick;
haveLatestTxEnd = true;
}
scan = static_cast<uint16_t>((scan + 1U) % DRIVER_RING_CAPACITY);
}
// Keep the newest TX period in the ring. Arrival of the following TX end
// proves that the previous end's ACK/fault window has completely elapsed.
if (!haveReleaseTxEnd) {
if (waitTicks) vTaskDelay(waitTicks);
return 0;
}
const uint32_t releaseThrough = releaseTxEnd + driverReleaseSlackTicks_;
size_t count = 0;
while (read != write) {
const Edge edge = driverRing_[read];
if (edge.source == static_cast<uint8_t>(CaptureSource::TX) &&
static_cast<int32_t>(edge.tick - releaseTxEnd) > 0)
break;
if (edge.source == static_cast<uint8_t>(CaptureSource::RX) &&
static_cast<int32_t>(edge.tick - releaseThrough) > 0)
break;
const size_t needed = edge.source == static_cast<uint8_t>(CaptureSource::TX)
? 2U : 1U;
if (count + needed > limit) break;
read = static_cast<uint16_t>((read + 1U) % DRIVER_RING_CAPACITY);
if (edge.source == static_cast<uint8_t>(CaptureSource::TX)) {
Edge pulseStart = edge;
pulseStart.tick -= driverPulseTicks_;
pulseStart.rising = !edge.rising;
ordered[count++] = pulseStart;
}
ordered[count++] = edge;
}
__atomic_store_n(&driverRingRead_, read, __ATOMIC_RELEASE);
// MCPWM channels share one timer but their callbacks can be dispatched in
// channel order when several interrupts are pending. Restore the hardware
// order inside the captured batch using the common timestamp.
for (size_t i = 1; i < count; ++i) {
const Edge key = ordered[i];
size_t j = i;
while (j && static_cast<int32_t>(ordered[j - 1].tick - key.tick) > 0) {
ordered[j] = ordered[j - 1];
--j;
}
ordered[j] = key;
}
for (size_t i = 0; i < count; ++i) {
const TimedEdge timed = extendEdge(ordered[i]);
events[i] = {timed.tick, timed.rising,
static_cast<CaptureSource>(ordered[i].source)};
}
const Edge marker = {releaseThrough, 0,
static_cast<uint8_t>(CaptureSource::WINDOW_END)};
const TimedEdge timedMarker = extendEdge(marker);
events[count++] = {timedMarker.tick, false, CaptureSource::WINDOW_END};
return count; return count;
} }
#endif

View File

@@ -1,63 +1,90 @@
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
#include <esp_idf_version.h> #include <esp_idf_version.h>
#include <driver/gpio.h>
#include "Config.h" #include "Config.h"
#include "Core.h" #include "Core.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) #if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
#define OPTICAL_USE_RMT_DMA 1 #define OPTICAL_USE_MCPWM_CAPTURE 1
#include <driver/rmt_rx.h> #include <driver/mcpwm_cap.h>
#else #else
#define OPTICAL_USE_RMT_DMA 0 #define OPTICAL_USE_MCPWM_CAPTURE 0
#include <driver/gpio.h>
#endif #endif
enum class CaptureSource : uint8_t { RX, TX, WINDOW_END };
struct CaptureEvent {
uint64_t tick;
bool rising;
CaptureSource source;
};
class PulseReceiver { class PulseReceiver {
public: public:
bool begin(); bool begin();
bool start(uint32_t expectedHz); bool start(uint32_t expectedHz, float expectedDutyPct, bool activeLightOn);
bool startDriver(uint32_t frequencyHz, uint32_t pulseNs,
bool activeTxLightOn);
void stop(); void stop();
void resetStream(); void resetStream();
size_t readPeriods(PulsePeriod *periods, size_t capacity); size_t readPeriods(PulsePeriod *periods, size_t capacity, TickType_t waitTicks = 0);
bool overflowed(); size_t readEvents(CaptureEvent *events, size_t capacity, TickType_t waitTicks = 0);
uint32_t takeDroppedItems(); uint32_t takeDroppedItems();
uint32_t tickHz() const; uint32_t tickHz() const;
uint16_t receiveChunkSymbols() const { return receiveChunkSymbols_; } uint32_t pulseTickHz() const { return tickHz(); }
bool highRateBackend() const { uint32_t plannedTickHz(uint32_t expectedHz, float expectedDutyPct) const;
#if OPTICAL_USE_RMT_DMA && CONFIG_IDF_TARGET_ESP32S3 uint32_t plannedPulseTickHz(uint32_t expectedHz, float expectedDutyPct) const {
return true; return plannedTickHz(expectedHz, expectedDutyPct);
#else
return false;
#endif
} }
private: uint16_t receiveChunkSymbols() const { return 1; }
struct Edge { uint32_t tick; uint8_t rising; }; bool highRateBackend() const { return OPTICAL_USE_MCPWM_CAPTURE; }
bool consumeEdge(const Edge &edge, PulsePeriod &period);
#if OPTICAL_USE_RMT_DMA private:
static constexpr size_t BLOCK_SYMBOLS = RMT_MAX_RECEIVE_SYMBOLS; struct Edge { uint32_t tick; uint8_t rising; uint8_t source; };
struct SymbolBlock { uint16_t count; rmt_symbol_word_t symbols[BLOCK_SYMBOLS]; }; static constexpr uint16_t DRIVER_RING_CAPACITY = 512;
static bool IRAM_ATTR onRmt(rmt_channel_handle_t, const rmt_rx_done_event_data_t *, void *); static_assert((DRIVER_RING_CAPACITY & (DRIVER_RING_CAPACITY - 1U)) == 0,
bool nextRmtEdge(Edge &edge); "driver capture ring must be a power of two");
rmt_channel_handle_t channel_ = nullptr; struct TimedEdge { uint64_t tick; bool rising; };
rmt_symbol_word_t receiveBuffer_[RMT_MAX_RECEIVE_SYMBOLS]; bool startCapture(bool withTx);
uint16_t receiveChunkSymbols_ = 0; bool consumeEdge(const Edge &edge, PulsePeriod &period);
SymbolBlock isrBlock_ = {}; bool nextOrderedEdge(Edge &edge, TickType_t waitTicks);
SymbolBlock block_ = {}; TimedEdge extendEdge(const Edge &edge);
uint16_t blockIndex_ = 0; #if OPTICAL_USE_MCPWM_CAPTURE
uint8_t phase_ = 0; bool configureDriverTxCapture(bool risingEdge);
bool haveLevel_ = false; static bool IRAM_ATTR onCapture(mcpwm_cap_channel_handle_t,
bool level_ = false; const mcpwm_capture_event_data_t *, void *);
uint32_t rmtTick_ = 0; mcpwm_cap_timer_handle_t captureTimer_ = nullptr;
mcpwm_cap_channel_handle_t risingChannel_ = nullptr;
mcpwm_cap_channel_handle_t fallingChannel_ = nullptr;
mcpwm_cap_channel_handle_t txChannel_ = nullptr;
uint32_t captureResolutionHz_ = 0;
#else #else
static void IRAM_ATTR onGpio(void *ctx); static void IRAM_ATTR onGpio(void *ctx);
uint32_t cpuTickHz_ = 0; uint32_t cpuTickHz_ = 0;
#endif #endif
QueueHandle_t queue_ = nullptr; QueueHandle_t queue_ = nullptr;
volatile bool overflow_ = false; Edge driverRing_[DRIVER_RING_CAPACITY] = {};
volatile uint16_t driverRingWrite_ = 0;
volatile uint16_t driverRingRead_ = 0;
portMUX_TYPE driverRingMux_ = portMUX_INITIALIZER_UNLOCKED;
Edge lastDriverEdge_ = {};
bool haveLastDriverEdge_ = false;
uint32_t driverPulseTicks_ = 0;
uint32_t driverReleaseSlackTicks_ = 0;
Edge reorderEdge_ = {};
bool haveReorderEdge_ = false;
volatile uint32_t droppedItems_ = 0; volatile uint32_t droppedItems_ = 0;
bool running_ = false; volatile bool running_ = false;
bool haveRise_ = false, haveFall_ = false, haveRawTick_ = false; volatile bool txCaptureEnabled_ = false;
uint32_t expectedHz_ = 0;
float expectedDutyPct_ = 50.0f;
bool polarityKnown_ = false, activeStartRising_ = false;
TimedEdge polarityEdges_[3] = {};
uint8_t polarityEdgeCount_ = 0;
bool waitingForActiveEnd_ = true;
uint64_t activeStart_ = 0, activeEnd_ = 0;
bool haveRawTick_ = false;
uint32_t lastRawTick_ = 0; uint32_t lastRawTick_ = 0;
uint64_t tickEpoch_ = 0, rise_ = 0, fall_ = 0; uint64_t tickEpoch_ = 0;
}; };

View File

@@ -3,20 +3,36 @@
#include "Log.h" #include "Log.h"
#include <Preferences.h> #include <Preferences.h>
namespace { constexpr uint16_t SETTINGS_VERSION = 1; constexpr char NAMESPACE[] = "opt-test"; } // 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 { void SettingsStore::defaults(Settings &s) const {
s = {SETTINGS_VERSION, static_cast<uint8_t>(Role::SOLO), 0, 4, 1, 2, 3, 2, 2, 0}; // 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, static_cast<uint8_t>(TestGroup::OPTICS),
static_cast<uint8_t>(BoardTest::ADC), 0};
s.checksum = settingsChecksum(s); s.checksum = settingsChecksum(s);
} }
bool SettingsStore::valid(const Settings &s) const { bool SettingsStore::valid(const Settings &s) const {
return s.version == SETTINGS_VERSION && s.role <= static_cast<uint8_t>(Role::SLAVE) && return s.version == SETTINGS_VERSION && s.role <= static_cast<uint8_t>(Role::SLAVE) &&
s.startIndex < countOf(START_FREQ_OPTIONS_HZ) && s.endIndex < countOf(END_FREQ_OPTIONS_HZ) && s.testGroup <= static_cast<uint8_t>(TestGroup::BOARD) &&
s.stepIndex < countOf(STEP_OPTIONS_HZ) && s.accuracyIndex < countOf(ACCURACY_OPTIONS_PCT) && s.boardTest <= static_cast<uint8_t>(BoardTest::RX_INPUT) &&
s.timeIndex < countOf(TEST_TIME_OPTIONS_MS) && s.repeatIndex < countOf(REPEAT_OPTIONS) && s.testKind <= static_cast<uint8_t>(TestKind::DRIVER) &&
s.dutyIndex < countOf(DUTY_OPTIONS_PCT) && s.checksum == settingsChecksum(s) && s.lightCode <= static_cast<uint8_t>(LightCode::LL) &&
END_FREQ_OPTIONS_HZ[s.endIndex] > START_FREQ_OPTIONS_HZ[s.startIndex]; (s.testKind != static_cast<uint8_t>(TestKind::DRIVER) ||
s.role == static_cast<uint8_t>(Role::SOLO)) &&
s.frequencyIndex < countOf(PWM_FREQUENCY_OPTIONS_HZ) &&
s.maxPulseIndex < countOf(MAX_PULSE_OPTIONS_NS) &&
s.minPulseIndex < countOf(MIN_PULSE_OPTIONS_NS) &&
MAX_PULSE_OPTIONS_NS[s.maxPulseIndex] >= MIN_PULSE_OPTIONS_NS[s.minPulseIndex] &&
static_cast<uint64_t>(MAX_PULSE_OPTIONS_NS[s.maxPulseIndex]) *
PWM_FREQUENCY_OPTIONS_HZ[s.frequencyIndex] < 1000000000ULL &&
s.accuracyIndex < countOf(ACCURACY_OPTIONS_PCT) &&
s.timeIndex < countOf(TEST_TIME_OPTIONS_MS) &&
s.checksum == settingsChecksum(s);
} }
bool SettingsStore::load(Settings &s) { bool SettingsStore::load(Settings &s) {
@@ -39,8 +55,7 @@ bool SettingsStore::save(Settings &s) {
} }
TestParams SettingsStore::params(const Settings &s) const { TestParams SettingsStore::params(const Settings &s) const {
return {START_FREQ_OPTIONS_HZ[s.startIndex], END_FREQ_OPTIONS_HZ[s.endIndex], return {PWM_FREQUENCY_OPTIONS_HZ[s.frequencyIndex],
STEP_OPTIONS_HZ[s.stepIndex], ACCURACY_OPTIONS_PCT[s.accuracyIndex], MAX_PULSE_OPTIONS_NS[s.maxPulseIndex], MIN_PULSE_OPTIONS_NS[s.minPulseIndex],
TEST_TIME_OPTIONS_MS[s.timeIndex], REPEAT_OPTIONS[s.repeatIndex], ACCURACY_OPTIONS_PCT[s.accuracyIndex], TEST_TIME_OPTIONS_MS[s.timeIndex]};
DUTY_OPTIONS_PCT[s.dutyIndex]};
} }

View File

@@ -0,0 +1,1866 @@
G04*
G04 #@! TF.GenerationSoftware,Altium Limited,Altium Designer,19.1.9 (167)*
G04*
G04 Layer_Physical_Order=3*
G04 Layer_Color=16711680*
%FSLAX44Y44*%
%MOMM*%
G71*
G01*
G75*
%ADD19R,1.2000X1.4000*%
%ADD20R,1.3000X1.5000*%
%ADD21R,1.5000X1.3000*%
%ADD22R,1.4000X1.2000*%
%ADD43C,0.8000*%
%ADD44C,1.5240*%
%ADD45C,1.7000*%
%ADD46O,2.4000X1.8000*%
%ADD47C,1.5000*%
%ADD48R,1.5000X1.5000*%
%ADD49R,1.5000X1.5000*%
%ADD50C,3.0000*%
%ADD51C,2.0000*%
%ADD52C,1.8000*%
%ADD53R,1.8000X1.8000*%
%ADD54C,1.6000*%
%ADD55R,1.6000X1.6000*%
%ADD56R,1.7000X1.7000*%
%ADD57R,1.6000X1.6000*%
%ADD58C,1.6000*%
G04:AMPARAMS|DCode=59|XSize=2.3mm|YSize=1.8mm|CornerRadius=0.225mm|HoleSize=0mm|Usage=FLASHONLY|Rotation=0.000|XOffset=0mm|YOffset=0mm|HoleType=Round|Shape=RoundedRectangle|*
%AMROUNDEDRECTD59*
21,1,2.3000,1.3500,0,0,0.0*
21,1,1.8500,1.8000,0,0,0.0*
1,1,0.4500,0.9250,-0.6750*
1,1,0.4500,-0.9250,-0.6750*
1,1,0.4500,-0.9250,0.6750*
1,1,0.4500,0.9250,0.6750*
%
%ADD59ROUNDEDRECTD59*%
G36*
X164097Y490000D02*
X167924Y480761D01*
X133581Y446419D01*
X132139Y444539D01*
X131232Y442350D01*
X130922Y440000D01*
Y403760D01*
X93581Y366419D01*
X92139Y364539D01*
X91232Y362349D01*
X90922Y360000D01*
Y320166D01*
X88190Y317602D01*
X80922Y313286D01*
X80000Y313408D01*
X76737Y312978D01*
X73696Y311719D01*
X71085Y309715D01*
X69081Y307104D01*
X67822Y304063D01*
X67392Y300800D01*
X67822Y297537D01*
X69081Y294496D01*
X71085Y290946D01*
Y285254D01*
X69081Y281704D01*
X67822Y278663D01*
X67392Y275400D01*
X67822Y272137D01*
X69001Y269289D01*
X69081Y269096D01*
X71085Y263368D01*
Y262032D01*
X69081Y256304D01*
X69001Y256110D01*
X67822Y253263D01*
X67392Y250000D01*
X67822Y246737D01*
X69001Y243890D01*
X69081Y243696D01*
X71085Y237968D01*
Y236632D01*
X69081Y230904D01*
X69001Y230711D01*
X67822Y227863D01*
X67392Y224600D01*
X67774Y221700D01*
X67803Y220779D01*
X67500Y211700D01*
X67500Y205831D01*
Y202700D01*
X80000D01*
X92500D01*
Y205831D01*
X92500Y211700D01*
X92197Y220779D01*
X92226Y221700D01*
X92608Y224600D01*
X92178Y227863D01*
X90999Y230711D01*
X90919Y230904D01*
X88915Y236632D01*
Y237968D01*
X90919Y243696D01*
X90999Y243890D01*
X99504Y246871D01*
X102289Y246922D01*
X105950Y244321D01*
X108864Y235075D01*
X107468Y233374D01*
X106075Y230768D01*
X105217Y227941D01*
X104927Y225000D01*
X105217Y222059D01*
X106075Y219232D01*
X107468Y216626D01*
X109342Y214342D01*
X111626Y212468D01*
X114232Y211075D01*
X117059Y210217D01*
X120000Y209927D01*
X122941Y210217D01*
X125768Y211075D01*
X128374Y212468D01*
X130658Y214342D01*
X132532Y216626D01*
X133925Y219232D01*
X134783Y222059D01*
X135073Y225000D01*
X134783Y227941D01*
X133925Y230768D01*
X132532Y233374D01*
X130658Y235658D01*
Y239342D01*
X132532Y241626D01*
X133925Y244232D01*
X134783Y247059D01*
X135073Y250000D01*
X134783Y252941D01*
X133925Y255768D01*
X132532Y258374D01*
X130658Y260658D01*
Y264342D01*
X132532Y266626D01*
X133925Y269232D01*
X134783Y272059D01*
X135073Y275000D01*
X134783Y277941D01*
X133925Y280768D01*
X133814Y280976D01*
X163040Y310203D01*
X171433Y304691D01*
X170289Y300921D01*
X169903Y297000D01*
X170289Y293079D01*
X171433Y289309D01*
X173290Y285835D01*
X175789Y282789D01*
X178835Y280290D01*
X182309Y278433D01*
X186079Y277289D01*
X190000Y276903D01*
X193921Y277289D01*
X197691Y278433D01*
X201165Y280290D01*
X204211Y282789D01*
X206710Y285835D01*
X208567Y289309D01*
X209711Y293079D01*
X210097Y297000D01*
X209711Y300921D01*
X208567Y304691D01*
X206710Y308165D01*
X204211Y311211D01*
X201165Y313710D01*
X197691Y315567D01*
X193921Y316711D01*
X190000Y317097D01*
X186079Y316711D01*
X182309Y315567D01*
X176798Y323960D01*
X251419Y398581D01*
X252861Y400461D01*
X253768Y402650D01*
X254078Y405000D01*
X254078Y405000D01*
Y422710D01*
X255700Y423794D01*
X265700Y418449D01*
Y416500D01*
X275700D01*
Y430000D01*
X282700D01*
Y416500D01*
X292700D01*
Y416500D01*
X302700Y416634D01*
X302990Y416596D01*
X399804Y319781D01*
X401684Y318339D01*
X403873Y317432D01*
X406223Y317122D01*
X406223Y317122D01*
X460219D01*
X464046Y307884D01*
X462081Y305919D01*
X460639Y304039D01*
X459732Y301849D01*
X459422Y299500D01*
Y295947D01*
X458384Y294083D01*
X451000Y287617D01*
X447476Y287153D01*
X444192Y285792D01*
X441372Y283628D01*
X439208Y280808D01*
X437848Y277524D01*
X437384Y274000D01*
X437474Y273312D01*
X343240Y179078D01*
X255051D01*
X254628Y179628D01*
X251808Y181792D01*
X248524Y183153D01*
X245000Y183616D01*
X241476Y183153D01*
X238192Y181792D01*
X235372Y179628D01*
X233208Y176808D01*
X231847Y173524D01*
X231383Y170000D01*
X231847Y166476D01*
X233208Y163192D01*
X235372Y160372D01*
X238192Y158208D01*
X241476Y156847D01*
X245000Y156383D01*
X248524Y156847D01*
X251808Y158208D01*
X254628Y160372D01*
X255051Y160922D01*
X347000D01*
X349350Y161232D01*
X351539Y162139D01*
X353419Y163581D01*
X449422Y259585D01*
X452330Y259219D01*
X458384Y253917D01*
X459422Y252052D01*
Y229017D01*
X458732Y227349D01*
X458422Y225000D01*
Y151500D01*
Y111983D01*
X440579Y94140D01*
X430295Y95964D01*
X427979Y103612D01*
X428256Y104044D01*
X429565Y107206D01*
X430012Y110600D01*
X429565Y113994D01*
X428256Y117156D01*
X426172Y119872D01*
X423456Y121955D01*
X420294Y123265D01*
X416900Y123712D01*
X413506Y123265D01*
X410344Y121955D01*
X407628Y119872D01*
X405751Y117425D01*
X405545Y117156D01*
X405545Y117156D01*
X404235Y113994D01*
X404096Y113873D01*
X394626Y115381D01*
X394100Y117005D01*
Y131500D01*
X374969D01*
X369100Y131500D01*
X360021Y131197D01*
X359100Y131226D01*
X356200Y131608D01*
X352937Y131178D01*
X350089Y129999D01*
X349896Y129919D01*
X344168Y127915D01*
X342832D01*
X337104Y129919D01*
X336911Y129999D01*
X334063Y131178D01*
X330800Y131608D01*
X327537Y131178D01*
X324689Y129999D01*
X324496Y129919D01*
X318768Y127915D01*
X317432D01*
X311704Y129919D01*
X311511Y129999D01*
X308663Y131178D01*
X305400Y131608D01*
X302137Y131178D01*
X299289Y129999D01*
X299096Y129919D01*
X293368Y127915D01*
X292032D01*
X286304Y129919D01*
X286110Y129999D01*
X283263Y131178D01*
X280000Y131608D01*
X276737Y131178D01*
X273890Y129999D01*
X273696Y129919D01*
X267968Y127915D01*
X266632D01*
X260904Y129919D01*
X260711Y129999D01*
X257863Y131178D01*
X254600Y131608D01*
X251337Y131178D01*
X248296Y129919D01*
X245685Y127915D01*
X243681Y125304D01*
X242422Y122263D01*
X241992Y119000D01*
X242422Y115737D01*
X243681Y112696D01*
X245685Y110085D01*
X248296Y108081D01*
X251337Y106822D01*
X254600Y106392D01*
X257863Y106822D01*
X260711Y108001D01*
X260904Y108081D01*
X266632Y110085D01*
X267968D01*
X273696Y108081D01*
X273890Y108001D01*
X276737Y106822D01*
X280000Y106392D01*
X283263Y106822D01*
X286110Y108001D01*
X286304Y108081D01*
X292032Y110085D01*
X293368D01*
X299096Y108081D01*
X299289Y108001D01*
X302137Y106822D01*
X305400Y106392D01*
X308663Y106822D01*
X311511Y108001D01*
X311704Y108081D01*
X317432Y110085D01*
X318768D01*
X324496Y108081D01*
X324689Y108001D01*
X327537Y106822D01*
X330800Y106392D01*
X334063Y106822D01*
X336911Y108001D01*
X337104Y108081D01*
X342832Y110085D01*
X344168D01*
X349896Y108081D01*
X350089Y108001D01*
X352937Y106822D01*
X356200Y106392D01*
X359100Y106774D01*
X360021Y106803D01*
X369100Y106500D01*
X374969Y106500D01*
X387162Y106500D01*
X394100Y106500D01*
X401250Y101782D01*
X400661Y93062D01*
X398781Y91619D01*
X388581Y81419D01*
X387138Y79539D01*
X386232Y77350D01*
X385922Y75000D01*
Y20000D01*
X380924Y11588D01*
X380250Y10817D01*
X379037Y10000D01*
X78018D01*
X70541Y18662D01*
X70270Y20000D01*
X70598Y25000D01*
X70459Y27122D01*
X70599Y27572D01*
X74176Y32213D01*
X76301Y34210D01*
X76500Y34316D01*
Y50000D01*
Y64055D01*
X73345Y63640D01*
X69940Y62229D01*
X67015Y59985D01*
X64164Y59185D01*
X57318Y58759D01*
X54880Y59314D01*
X52758Y61175D01*
X47799Y64489D01*
X42449Y67127D01*
X36802Y69044D01*
X30952Y70208D01*
X25000Y70598D01*
X20000Y70270D01*
X18662Y70541D01*
X10000Y78018D01*
X10000Y421982D01*
X18662Y429459D01*
X20000Y429730D01*
X25000Y429402D01*
X30952Y429793D01*
X36802Y430956D01*
X42449Y432873D01*
X47799Y435511D01*
X52758Y438825D01*
X54880Y440686D01*
X57318Y441241D01*
X64164Y440815D01*
X67015Y440015D01*
X69940Y437771D01*
X73345Y436360D01*
X77000Y435879D01*
X83000D01*
X86655Y436360D01*
X90060Y437771D01*
X92985Y440015D01*
X95229Y442940D01*
X96640Y446345D01*
X97121Y450000D01*
X96640Y453655D01*
X95229Y457060D01*
X92985Y459985D01*
X90060Y462229D01*
X86655Y463640D01*
X83000Y464121D01*
X79423D01*
X76301Y465790D01*
X74176Y467786D01*
X70599Y472428D01*
X70459Y472878D01*
X70598Y475000D01*
X70270Y480000D01*
X70541Y481338D01*
X78018Y490000D01*
X164097D01*
D02*
G37*
G36*
X411300Y477685D02*
Y451685D01*
X411300D01*
X411502Y441685D01*
X411188Y439300D01*
X411635Y435906D01*
X412944Y432744D01*
X415028Y429492D01*
Y423707D01*
X412944Y420456D01*
X411635Y417294D01*
X411188Y413900D01*
X411635Y410506D01*
X412944Y407344D01*
X415028Y404092D01*
Y398307D01*
X413883Y396520D01*
X403618Y394286D01*
X397078Y399210D01*
Y431000D01*
X396768Y433349D01*
X395862Y435539D01*
X394419Y437419D01*
X374419Y457419D01*
X372539Y458862D01*
X370349Y459768D01*
X368000Y460078D01*
X261000D01*
X258650Y459768D01*
X256461Y458862D01*
X254581Y457419D01*
X238581Y441419D01*
X237139Y439539D01*
X236232Y437350D01*
X235922Y435000D01*
Y411411D01*
X228717Y404247D01*
X219362Y406739D01*
X219229Y407060D01*
X216985Y409985D01*
X214060Y412229D01*
X210655Y413640D01*
X207500Y414055D01*
Y400000D01*
X204000D01*
Y396500D01*
X187340D01*
X187360Y396345D01*
X188771Y392940D01*
X191015Y390015D01*
X193940Y387771D01*
X197345Y386360D01*
X199144Y386124D01*
X201909Y381424D01*
X203041Y375879D01*
X143178Y316016D01*
X135139Y319366D01*
X133547Y320527D01*
X133153Y323524D01*
X131792Y326808D01*
X131437Y327271D01*
X129628Y330568D01*
Y336932D01*
X131437Y340229D01*
X131792Y340692D01*
X133153Y343976D01*
X133616Y347500D01*
X133153Y351024D01*
X131792Y354308D01*
X129628Y357128D01*
X126808Y359292D01*
X126429Y359449D01*
X123556Y367558D01*
X123570Y370733D01*
X146419Y393581D01*
X147861Y395461D01*
X148768Y397650D01*
X149078Y400000D01*
Y436240D01*
X191260Y478422D01*
X401606D01*
X411300Y477685D01*
D02*
G37*
G36*
X484344Y476097D02*
X487971Y468422D01*
X487835Y468093D01*
X487388Y464700D01*
X487835Y461306D01*
X489145Y458144D01*
X491228Y454892D01*
Y449107D01*
X489145Y445856D01*
X487835Y442693D01*
X487388Y439300D01*
X487835Y435906D01*
X489145Y432744D01*
X491228Y429492D01*
Y423707D01*
X489145Y420456D01*
X487835Y417294D01*
X487388Y413900D01*
X487835Y410506D01*
X489145Y407344D01*
X491228Y404092D01*
Y398307D01*
X489145Y395056D01*
X487835Y391894D01*
X487388Y388500D01*
X487835Y385106D01*
X489145Y381944D01*
X491228Y379228D01*
X491566Y378969D01*
X493557Y370432D01*
X493335Y366596D01*
X485817Y359078D01*
X435760D01*
X428927Y365910D01*
X428642Y368208D01*
X430774Y376812D01*
X431410Y377570D01*
X433572Y379228D01*
X435655Y381944D01*
X436921Y385000D01*
X424300D01*
Y392000D01*
X436921D01*
X435655Y395056D01*
X433572Y398307D01*
Y404092D01*
X435655Y407344D01*
X436965Y410506D01*
X437412Y413900D01*
X436965Y417294D01*
X435655Y420456D01*
X433572Y423707D01*
Y429492D01*
X435655Y432744D01*
X436965Y435906D01*
X437412Y439300D01*
X437098Y441685D01*
X437300Y451685D01*
X437300Y451685D01*
Y477685D01*
X446994Y478422D01*
X482039D01*
X484344Y476097D01*
D02*
G37*
G36*
X688391Y490000D02*
X689849Y480000D01*
X688841Y478646D01*
X680904Y473792D01*
X680500Y473763D01*
Y460000D01*
Y446237D01*
X680904Y446208D01*
X688841Y441354D01*
X690000Y439798D01*
Y404002D01*
X688841Y402446D01*
X680904Y397592D01*
X680500Y397562D01*
Y383800D01*
Y370037D01*
X680904Y370008D01*
X688841Y365154D01*
X690000Y363598D01*
Y347455D01*
X687030Y344417D01*
X680000Y340176D01*
X678900Y340321D01*
X675245Y339840D01*
X671840Y338429D01*
X668915Y336185D01*
X663485D01*
X660560Y338429D01*
X657155Y339840D01*
X654671Y340167D01*
X640000Y354838D01*
Y366000D01*
X623226D01*
X614800Y369800D01*
Y380300D01*
X600800D01*
Y387300D01*
X614800D01*
Y397800D01*
X614800Y397800D01*
X614750Y405700D01*
X600800D01*
Y412700D01*
X614460D01*
X614440Y412855D01*
X613029Y416260D01*
X610785Y419185D01*
Y424615D01*
X613029Y427540D01*
X614440Y430945D01*
X614921Y434600D01*
X614440Y438255D01*
X613029Y441660D01*
X610785Y444585D01*
Y450015D01*
X613029Y452940D01*
X614440Y456345D01*
X614921Y460000D01*
X614440Y463655D01*
X613029Y467060D01*
X610785Y469985D01*
X607860Y472229D01*
X604455Y473640D01*
X600800Y474121D01*
X597145Y473640D01*
X596475Y473362D01*
X589076Y480761D01*
X592903Y490000D01*
X688391D01*
D02*
G37*
G36*
X671840Y161571D02*
X675245Y160160D01*
X678900Y159679D01*
X680000Y159824D01*
X687030Y155583D01*
X690000Y152545D01*
Y136402D01*
X688841Y134846D01*
X680904Y129992D01*
X680500Y129963D01*
Y116200D01*
Y102437D01*
X680904Y102408D01*
X688841Y97554D01*
X690000Y95998D01*
Y60202D01*
X688841Y58646D01*
X680904Y53792D01*
X680500Y53762D01*
Y40000D01*
Y26237D01*
X680904Y26208D01*
X688841Y21354D01*
X689849Y20000D01*
X688391Y10000D01*
X590472D01*
X587271Y19010D01*
X593995Y26000D01*
X614800D01*
Y37422D01*
X618500D01*
X620849Y37732D01*
X623039Y38639D01*
X624919Y40081D01*
X625838Y41000D01*
X637000D01*
Y59000D01*
Y81000D01*
X625838D01*
X614751Y92086D01*
X614440Y94455D01*
X613029Y97860D01*
X610785Y100785D01*
Y106215D01*
X613029Y109140D01*
X614440Y112545D01*
X614921Y116200D01*
X614440Y119855D01*
X613029Y123260D01*
X610785Y126185D01*
X607860Y128429D01*
X604455Y129840D01*
X600800Y130321D01*
X597145Y129840D01*
X593740Y128429D01*
X590815Y126185D01*
X590119Y125278D01*
X555060D01*
X530251Y150087D01*
X530221Y153207D01*
X532930Y160900D01*
X542233Y163571D01*
X544840Y161571D01*
X548245Y160160D01*
X551900Y159679D01*
X555555Y160160D01*
X558960Y161571D01*
X561885Y163815D01*
X567315D01*
X570240Y161571D01*
X573645Y160160D01*
X577300Y159679D01*
X580955Y160160D01*
X584360Y161571D01*
X587285Y163815D01*
X592715D01*
X595640Y161571D01*
X599045Y160160D01*
X602700Y159679D01*
X606355Y160160D01*
X609760Y161571D01*
X612685Y163815D01*
X618115D01*
X621040Y161571D01*
X624445Y160160D01*
X628100Y159679D01*
X631755Y160160D01*
X635160Y161571D01*
X638085Y163815D01*
X643515D01*
X646440Y161571D01*
X649845Y160160D01*
X653500Y159679D01*
X657155Y160160D01*
X660560Y161571D01*
X663485Y163815D01*
X668915D01*
X671840Y161571D01*
D02*
G37*
G36*
X528146Y99016D02*
X528372Y97958D01*
X528270Y95509D01*
X527125Y88971D01*
X526120Y87198D01*
X525977Y87043D01*
X520581Y81647D01*
X519138Y79767D01*
X518232Y77577D01*
X518090Y76500D01*
X514000D01*
Y76500D01*
X507668Y77098D01*
X505281Y80637D01*
X505721Y81700D01*
X493100D01*
X480479D01*
X481745Y78644D01*
X477582Y69405D01*
X477173Y68978D01*
X465000D01*
X462650Y68668D01*
X461919Y68365D01*
X456073Y69816D01*
X452700Y80586D01*
X468694Y96579D01*
X480087Y94622D01*
X481698Y91837D01*
X481629Y91477D01*
X480479Y88700D01*
X493100D01*
X505721D01*
X504455Y91756D01*
X502372Y95007D01*
Y100793D01*
X504455Y104044D01*
X505765Y107206D01*
X505771Y107249D01*
X514258Y111112D01*
X515943Y111219D01*
X528146Y99016D01*
D02*
G37*
%LPC*%
G36*
X83500Y414055D02*
Y403500D01*
X96660D01*
X96640Y403655D01*
X95229Y407060D01*
X92985Y409985D01*
X90060Y412229D01*
X86655Y413640D01*
X83500Y414055D01*
D02*
G37*
G36*
X76500Y414055D02*
X73345Y413640D01*
X69940Y412229D01*
X67015Y409985D01*
X64771Y407060D01*
X63360Y403655D01*
X63340Y403500D01*
X76500D01*
Y414055D01*
D02*
G37*
G36*
X96660Y396500D02*
X83500D01*
Y385945D01*
X86655Y386360D01*
X90060Y387771D01*
X92985Y390015D01*
X95229Y392940D01*
X96640Y396345D01*
X96660Y396500D01*
D02*
G37*
G36*
X76500D02*
X63340D01*
X63360Y396345D01*
X64771Y392940D01*
X67015Y390015D01*
X69940Y387771D01*
X73345Y386360D01*
X76500Y385945D01*
Y396500D01*
D02*
G37*
G36*
X92500Y195700D02*
X83500D01*
Y186700D01*
X92500D01*
Y195700D01*
D02*
G37*
G36*
X76500D02*
X67500D01*
Y186700D01*
X76500D01*
Y195700D01*
D02*
G37*
G36*
X190000Y223097D02*
X186079Y222711D01*
X182309Y221567D01*
X178835Y219710D01*
X175789Y217211D01*
X173290Y214165D01*
X171433Y210691D01*
X170289Y206921D01*
X169903Y203000D01*
X170289Y199079D01*
X171433Y195309D01*
X173290Y191835D01*
X175789Y188789D01*
X178835Y186290D01*
X182309Y184433D01*
X186079Y183289D01*
X190000Y182903D01*
X193921Y183289D01*
X197691Y184433D01*
X201165Y186290D01*
X204211Y188789D01*
X206710Y191835D01*
X208567Y195309D01*
X209711Y199079D01*
X210097Y203000D01*
X209711Y206921D01*
X208567Y210691D01*
X206710Y214165D01*
X204211Y217211D01*
X201165Y219710D01*
X197691Y221567D01*
X193921Y222711D01*
X190000Y223097D01*
D02*
G37*
G36*
X83000Y114121D02*
X77000D01*
X73345Y113640D01*
X69940Y112229D01*
X67015Y109985D01*
X64771Y107060D01*
X63360Y103655D01*
X62879Y100000D01*
X63360Y96345D01*
X64771Y92940D01*
X67015Y90015D01*
X69940Y87771D01*
X73345Y86360D01*
X77000Y85879D01*
X83000D01*
X86655Y86360D01*
X90060Y87771D01*
X92985Y90015D01*
X95229Y92940D01*
X96640Y96345D01*
X97121Y100000D01*
X96640Y103655D01*
X95229Y107060D01*
X92985Y109985D01*
X90060Y112229D01*
X86655Y113640D01*
X83000Y114121D01*
D02*
G37*
G36*
X340000Y75000D02*
X330000D01*
Y66000D01*
X340000D01*
Y75000D01*
D02*
G37*
G36*
X207500Y64055D02*
Y53500D01*
X220660D01*
X220640Y53655D01*
X219229Y57060D01*
X216985Y59985D01*
X214060Y62229D01*
X210655Y63640D01*
X207500Y64055D01*
D02*
G37*
G36*
X83500D02*
Y53500D01*
X96660D01*
X96640Y53655D01*
X95229Y57060D01*
X92985Y59985D01*
X90060Y62229D01*
X86655Y63640D01*
X83500Y64055D01*
D02*
G37*
G36*
X200500D02*
X197345Y63640D01*
X193940Y62229D01*
X191015Y59985D01*
X188771Y57060D01*
X187360Y53655D01*
X187340Y53500D01*
X200500D01*
Y64055D01*
D02*
G37*
G36*
X220660Y46500D02*
X207500D01*
Y35945D01*
X210655Y36360D01*
X214060Y37771D01*
X216985Y40015D01*
X219229Y42940D01*
X220640Y46345D01*
X220660Y46500D01*
D02*
G37*
G36*
X96660D02*
X83500D01*
Y35945D01*
X86655Y36360D01*
X90060Y37771D01*
X92985Y40015D01*
X95229Y42940D01*
X96640Y46345D01*
X96660Y46500D01*
D02*
G37*
G36*
X200500D02*
X187340D01*
X187360Y46345D01*
X188771Y42940D01*
X191015Y40015D01*
X193940Y37771D01*
X197345Y36360D01*
X200500Y35945D01*
Y46500D01*
D02*
G37*
G36*
X207000Y114121D02*
X201000D01*
X197345Y113640D01*
X193940Y112229D01*
X191015Y109985D01*
X188771Y107060D01*
X187360Y103655D01*
X186879Y100000D01*
X187360Y96345D01*
X188771Y92940D01*
X191015Y90015D01*
X193940Y87771D01*
X197345Y86360D01*
X201000Y85879D01*
X207000D01*
X210655Y86360D01*
X212609Y87170D01*
X216265Y85727D01*
X221499Y80874D01*
X221384Y80000D01*
X221847Y76476D01*
X223208Y73192D01*
X225372Y70372D01*
X226553Y69465D01*
X227139Y68051D01*
X228581Y66171D01*
X249671Y45081D01*
X251433Y43729D01*
X253437Y41725D01*
Y33750D01*
X253687Y31857D01*
X254417Y30094D01*
X255579Y28579D01*
X257094Y27417D01*
X258857Y26687D01*
X260750Y26437D01*
X279250D01*
X281143Y26687D01*
X282906Y27417D01*
X284421Y28579D01*
X285583Y30094D01*
X285926Y30922D01*
X295000D01*
X297349Y31232D01*
X299539Y32139D01*
X301419Y33581D01*
X302838Y35000D01*
X315000Y35000D01*
Y35000D01*
X315000D01*
Y35000D01*
X340000D01*
Y52000D01*
Y61000D01*
X327500D01*
Y63500D01*
X325000D01*
Y75000D01*
X325000D01*
X315000Y75000D01*
Y75000D01*
X315000D01*
Y75000D01*
X305000D01*
Y63500D01*
X300000D01*
Y75000D01*
X290000D01*
X284421Y81421D01*
X282906Y82583D01*
X281143Y83313D01*
X279250Y83562D01*
X272500D01*
Y69500D01*
X267500D01*
Y83562D01*
X260750D01*
X258857Y83313D01*
X258297Y83081D01*
X254986Y83226D01*
X249883Y84782D01*
X246903Y86541D01*
X246792Y86808D01*
X244628Y89628D01*
X241808Y91792D01*
X238524Y93153D01*
X235000Y93616D01*
X231476Y93153D01*
X229865Y92485D01*
X225703Y94227D01*
X220970Y98856D01*
X221121Y100000D01*
X220640Y103655D01*
X219229Y107060D01*
X216985Y109985D01*
X214060Y112229D01*
X210655Y113640D01*
X207000Y114121D01*
D02*
G37*
G36*
Y464121D02*
X201000D01*
X197345Y463640D01*
X193940Y462229D01*
X191015Y459985D01*
X188771Y457060D01*
X187360Y453655D01*
X186879Y450000D01*
X187360Y446345D01*
X188771Y442940D01*
X191015Y440015D01*
X193940Y437771D01*
X197345Y436360D01*
X201000Y435879D01*
X207000D01*
X210655Y436360D01*
X214060Y437771D01*
X216985Y440015D01*
X219229Y442940D01*
X220640Y446345D01*
X221121Y450000D01*
X220640Y453655D01*
X219229Y457060D01*
X216985Y459985D01*
X214060Y462229D01*
X210655Y463640D01*
X207000Y464121D01*
D02*
G37*
G36*
X200500Y414055D02*
X197345Y413640D01*
X193940Y412229D01*
X191015Y409985D01*
X188771Y407060D01*
X187360Y403655D01*
X187340Y403500D01*
X200500D01*
Y414055D01*
D02*
G37*
G36*
X673500Y473660D02*
X673345Y473640D01*
X669940Y472229D01*
X667015Y469985D01*
X664771Y467060D01*
X663360Y463655D01*
X663340Y463500D01*
X673500D01*
Y473660D01*
D02*
G37*
G36*
Y456500D02*
X663340D01*
X663360Y456345D01*
X664771Y452940D01*
X667015Y450015D01*
X669940Y447771D01*
X673345Y446360D01*
X673500Y446340D01*
Y456500D01*
D02*
G37*
G36*
Y397460D02*
X673345Y397440D01*
X669940Y396029D01*
X667015Y393785D01*
X664771Y390860D01*
X663360Y387455D01*
X663340Y387300D01*
X673500D01*
Y397460D01*
D02*
G37*
G36*
Y380300D02*
X663340D01*
X663360Y380145D01*
X664771Y376740D01*
X667015Y373815D01*
X669940Y371571D01*
X673345Y370160D01*
X673500Y370140D01*
Y380300D01*
D02*
G37*
G36*
X673500Y129860D02*
X673345Y129840D01*
X669940Y128429D01*
X667015Y126185D01*
X664771Y123260D01*
X663360Y119855D01*
X663340Y119700D01*
X673500D01*
Y129860D01*
D02*
G37*
G36*
Y112700D02*
X663340D01*
X663360Y112545D01*
X664771Y109140D01*
X667015Y106215D01*
X669940Y103971D01*
X673345Y102560D01*
X673500Y102540D01*
Y112700D01*
D02*
G37*
G36*
X673500Y53660D02*
X673345Y53640D01*
X669940Y52229D01*
X667015Y49985D01*
X664771Y47060D01*
X663360Y43655D01*
X663340Y43500D01*
X673500D01*
Y53660D01*
D02*
G37*
G36*
Y36500D02*
X663340D01*
X663360Y36345D01*
X664771Y32940D01*
X667015Y30015D01*
X669940Y27771D01*
X673345Y26360D01*
X673500Y26340D01*
Y36500D01*
D02*
G37*
%LPD*%
D19*
X537000Y435000D02*
D03*
X555000D02*
D03*
X537000Y470000D02*
D03*
X555000D02*
D03*
X549000Y97000D02*
D03*
X567000D02*
D03*
X517000Y256000D02*
D03*
X499000D02*
D03*
X537000Y256000D02*
D03*
X555000D02*
D03*
X611000Y354000D02*
D03*
X629000D02*
D03*
X447500Y35000D02*
D03*
X465500D02*
D03*
D20*
X555000Y380000D02*
D03*
X572000D02*
D03*
X555000Y405000D02*
D03*
X572000D02*
D03*
X542500Y64000D02*
D03*
X525500D02*
D03*
X465000Y55000D02*
D03*
X448000D02*
D03*
D21*
X327500Y63500D02*
D03*
Y46500D02*
D03*
X302500Y63500D02*
D03*
Y46500D02*
D03*
D22*
X625000Y52000D02*
D03*
Y70000D02*
D03*
D43*
X535437Y470750D02*
X537000Y469187D01*
X546000Y94000D02*
X550000D01*
X507500Y132500D02*
X546000Y94000D01*
X500000Y255000D02*
Y285000D01*
X499000Y252000D02*
X501000Y254000D01*
X235000Y72590D02*
X256090Y51500D01*
X235000Y72590D02*
Y80000D01*
X256090Y51500D02*
X256500D01*
X267500Y40500D01*
X270000D01*
X270500Y40000D02*
X295000D01*
X270000Y40500D02*
X270500Y40000D01*
X295000D02*
X301500Y46500D01*
X302500D01*
X327500D01*
X245000Y170000D02*
X347000D01*
X451000Y274000D01*
X467500Y225000D02*
X468500Y226000D01*
X467500Y151500D02*
Y225000D01*
X468500Y226000D02*
Y299500D01*
X571000Y405000D02*
X572000Y404000D01*
X100000Y360000D02*
X140000Y400000D01*
X100000Y268130D02*
Y360000D01*
Y268130D02*
X118130Y250000D01*
X120000D01*
Y225000D02*
Y250000D01*
X593800Y460000D02*
Y463200D01*
X574999Y198000D02*
Y247999D01*
X663000Y305000D02*
X678900Y320900D01*
X618000Y305000D02*
X663000D01*
X575000Y262000D02*
X618000Y305000D01*
X575000Y248000D02*
Y262000D01*
X140000Y400000D02*
Y440000D01*
X611000Y354000D02*
Y359000D01*
X600000Y370000D02*
X611000Y359000D01*
X600000Y370000D02*
Y381000D01*
X586000Y356000D02*
X600000Y370000D01*
X554000Y356000D02*
X586000D01*
X526500Y328500D02*
X554000Y356000D01*
X569500Y487500D02*
X593800Y463200D01*
X187500Y487500D02*
X569500D01*
X499000Y386000D02*
X502000Y389000D01*
X561400Y467000D02*
X593800Y434600D01*
X557000Y467000D02*
X561400D01*
X588200Y409200D02*
X600800D01*
X584000Y405000D02*
X588200Y409200D01*
X572000Y405000D02*
X584000D01*
X542500Y63000D02*
X566000D01*
X593800Y90800D01*
X587913Y62913D02*
X591313D01*
X572500Y47500D02*
X587913Y62913D01*
X532272Y47500D02*
X572500D01*
X579800Y26000D02*
X597300Y43500D01*
X624000Y52000D02*
X625000D01*
X618500Y46500D02*
X624000Y52000D01*
X607300Y46500D02*
X618500D01*
X600800Y40000D02*
X607300Y46500D01*
X624000Y70000D02*
X625000D01*
X603200Y90800D02*
X624000Y70000D01*
X600800Y90800D02*
X603200D01*
X596287Y88313D02*
X600687D01*
X591900Y381900D02*
X592000Y382000D01*
X526500Y326200D02*
Y328500D01*
X495200Y326200D02*
X501100D01*
X577300Y331144D02*
Y331200D01*
X555000Y271000D02*
X580000Y296000D01*
X555000Y256000D02*
Y271000D01*
X611000Y354000D02*
X613000Y356000D01*
X573900Y381900D02*
X591900D01*
X572000Y380000D02*
X573900Y381900D01*
X629000Y353000D02*
Y354000D01*
Y353000D02*
X653500Y328500D01*
Y326200D02*
Y328500D01*
X527000Y388000D02*
Y425000D01*
X526577Y387000D02*
X528000D01*
X432000Y350000D02*
X489577D01*
X527000Y425000D02*
X537000Y435000D01*
X527000Y388000D02*
X528000Y387000D01*
X535617Y432383D02*
X535999Y432000D01*
X489577Y350000D02*
X526577Y387000D01*
X481500Y291500D02*
X495000Y305000D01*
X500000Y255000D02*
X501000Y254000D01*
X481500Y165823D02*
Y291500D01*
X495000Y305000D02*
X558277D01*
X468500Y299500D02*
X495200Y326200D01*
X499000Y252000D02*
X499000Y252000D01*
X543999Y188217D02*
Y231999D01*
X542500Y186718D02*
X543999Y188217D01*
X542500Y186718D02*
X551900Y177318D01*
X368000Y451000D02*
X388000Y431000D01*
X261000Y451000D02*
X368000D01*
X245000Y435000D02*
X261000Y451000D01*
X388000Y394000D02*
Y431000D01*
Y394000D02*
X432000Y350000D01*
X525000Y175000D02*
X526500Y176500D01*
X520000Y219563D02*
Y224000D01*
Y219563D02*
X526500Y213063D01*
X500499Y174401D02*
X501100Y173800D01*
X500499Y174401D02*
Y231248D01*
X515000Y245749D01*
Y254000D01*
X517000Y256000D01*
X526500Y176500D02*
Y213063D01*
X551900Y173800D02*
Y177318D01*
X537000Y256000D02*
X537000Y256000D01*
X517000Y256000D02*
X537000D01*
X497323Y150000D02*
X517500D01*
X551300Y116200D01*
X481500Y165823D02*
X497323Y150000D01*
X551300Y116200D02*
X593800D01*
X571000Y405000D02*
X572000D01*
Y380000D02*
Y404000D01*
X555000Y379000D02*
Y380000D01*
X552500Y376500D02*
X555000Y379000D01*
X548415Y376500D02*
X552500D01*
X501100Y329185D02*
X548415Y376500D01*
X501100Y326200D02*
Y329185D01*
X140000Y440000D02*
X187500Y487500D01*
X245000Y405000D02*
Y435000D01*
X120000Y280000D02*
X245000Y405000D01*
X120000Y275000D02*
Y280000D01*
X555000Y469000D02*
Y470000D01*
Y469000D02*
X557000Y467000D01*
X406223Y326200D02*
X495200D01*
X304600Y427823D02*
X406223Y326200D01*
X304600Y427823D02*
Y430000D01*
X555000Y405000D02*
Y435000D01*
X506550Y470750D02*
X535437D01*
X500500Y464700D02*
X506550Y470750D01*
X537000Y469187D02*
Y470000D01*
Y460728D02*
Y469187D01*
Y460728D02*
X553000Y444728D01*
Y437000D02*
Y444728D01*
Y437000D02*
X555000Y435000D01*
X554000Y384000D02*
Y405000D01*
Y405000D02*
X554000Y405000D01*
X554000Y405000D02*
X554000Y405000D01*
X465000Y30000D02*
Y55000D01*
X448000Y35000D02*
Y55000D01*
X467500Y108223D02*
Y151500D01*
X419077Y59800D02*
X467500Y108223D01*
X416900Y59800D02*
X419077D01*
X432900Y14900D02*
X447000Y29000D01*
X400100Y14900D02*
X432900D01*
X395000Y20000D02*
X400100Y14900D01*
X395000Y20000D02*
Y75000D01*
X405200Y85200D01*
X416900D01*
X447000Y29000D02*
Y30000D01*
X448000Y31000D01*
Y35000D01*
X465000Y59900D02*
X493000D01*
X495587Y57313D01*
X467500Y151500D02*
X486500Y132500D01*
X574999Y247999D02*
X575000Y248000D01*
X678900Y320900D02*
Y326200D01*
X504075Y57313D02*
X535387Y26000D01*
X579800D01*
X527000Y52772D02*
X532272Y47500D01*
X527000Y52772D02*
Y62500D01*
X525500Y64000D02*
X527000Y62500D01*
X495587Y57313D02*
X504075D01*
X567000Y96000D02*
Y97000D01*
X564000Y93000D02*
X567000Y96000D01*
X564000Y86272D02*
Y93000D01*
X558478Y80750D02*
X564000Y86272D01*
X532522Y80750D02*
X558478D01*
X527000Y75228D02*
X532522Y80750D01*
X527000Y65500D02*
Y75228D01*
X525500Y64000D02*
X527000Y65500D01*
X593800Y90800D02*
X596287Y88313D01*
X591313Y62913D02*
X593800Y65400D01*
X486500Y132500D02*
X507500D01*
X500500Y388500D02*
X501000Y389000D01*
X499000Y386000D02*
X499000D01*
X502000Y389000D02*
X506000D01*
X558277Y305000D02*
X577300Y324023D01*
Y326200D01*
X571000Y405000D02*
X571000Y405000D01*
X501000Y389000D02*
X502000D01*
D44*
X650000Y250000D02*
D03*
X25000Y475000D02*
D03*
Y25000D02*
D03*
D45*
X120000Y347500D02*
D03*
Y320000D02*
D03*
X355400Y430000D02*
D03*
X330000D02*
D03*
X304600D02*
D03*
X587000Y299000D02*
D03*
X235000Y80000D02*
D03*
X245000Y170000D02*
D03*
X574999Y198000D02*
D03*
X528000Y387000D02*
D03*
X500000Y285000D02*
D03*
X543999Y231999D02*
D03*
X451000Y274000D02*
D03*
X520000Y224000D02*
D03*
D46*
X80000Y450000D02*
D03*
X204000D02*
D03*
X80000Y400000D02*
D03*
X204000D02*
D03*
X80000Y100000D02*
D03*
X204000D02*
D03*
X80000Y50000D02*
D03*
X204000D02*
D03*
D47*
X80000Y300800D02*
D03*
Y275400D02*
D03*
Y250000D02*
D03*
Y224600D02*
D03*
X254600Y119000D02*
D03*
X356200D02*
D03*
X330800D02*
D03*
X305400D02*
D03*
X280000D02*
D03*
D48*
X80000Y199200D02*
D03*
D49*
X381600Y119000D02*
D03*
D50*
X190000Y203000D02*
D03*
Y297000D02*
D03*
D51*
X120000Y225000D02*
D03*
Y250000D02*
D03*
Y275000D02*
D03*
D52*
X677000Y383800D02*
D03*
Y460000D02*
D03*
X600800D02*
D03*
Y434600D02*
D03*
Y409200D02*
D03*
X677000Y40000D02*
D03*
Y116200D02*
D03*
X600800D02*
D03*
Y90800D02*
D03*
Y65400D02*
D03*
X678900Y326200D02*
D03*
X653500D02*
D03*
X628100D02*
D03*
X602700D02*
D03*
X577300D02*
D03*
X551900D02*
D03*
X526500D02*
D03*
X501100D02*
D03*
X678900Y173800D02*
D03*
X653500D02*
D03*
X628100D02*
D03*
X602700D02*
D03*
X577300D02*
D03*
X551900D02*
D03*
X526500D02*
D03*
X501100D02*
D03*
D53*
X600800Y383800D02*
D03*
Y40000D02*
D03*
D54*
X416900Y34400D02*
D03*
Y59800D02*
D03*
Y85200D02*
D03*
Y110600D02*
D03*
X493100D02*
D03*
Y85200D02*
D03*
Y59800D02*
D03*
D55*
Y34400D02*
D03*
D56*
X279200Y430000D02*
D03*
D57*
X424300Y464684D02*
D03*
D58*
Y439300D02*
D03*
Y413900D02*
D03*
Y388500D02*
D03*
X500500D02*
D03*
Y413900D02*
D03*
Y439300D02*
D03*
Y464700D02*
D03*
D59*
X270000Y69500D02*
D03*
Y40500D02*
D03*
M02*

View File

@@ -0,0 +1,456 @@
G04*
G04 #@! TF.GenerationSoftware,Altium Limited,Altium Designer,19.1.9 (167)*
G04*
G04 Layer_Color=32896*
%FSLAX44Y44*%
%MOMM*%
G71*
G01*
G75*
%ADD11C,0.2540*%
%ADD12C,0.2000*%
%ADD14C,0.1000*%
%ADD61C,0.1270*%
D11*
X269843Y100196D02*
X272383Y102735D01*
X277461D01*
X280000Y100196D01*
Y90039D01*
X277461Y87500D01*
X272383D01*
X269843Y90039D01*
X264765Y87500D02*
X259687D01*
X262226D01*
Y102735D01*
X264765Y100196D01*
X679000Y347000D02*
Y362235D01*
X671383D01*
X668843Y359696D01*
Y354618D01*
X671383Y352078D01*
X679000D01*
X673922D02*
X668843Y347000D01*
X663765D02*
X658687D01*
X661226D01*
Y362235D01*
X663765Y359696D01*
X651069D02*
X648530Y362235D01*
X643452D01*
X640912Y359696D01*
Y349539D01*
X643452Y347000D01*
X648530D01*
X651069Y349539D01*
Y359696D01*
X567000Y274000D02*
Y289235D01*
X559383D01*
X556843Y286696D01*
Y281618D01*
X559383Y279078D01*
X567000D01*
X561922D02*
X556843Y274000D01*
X551765Y276539D02*
X549226Y274000D01*
X544147D01*
X541608Y276539D01*
Y286696D01*
X544147Y289235D01*
X549226D01*
X551765Y286696D01*
Y284157D01*
X549226Y281618D01*
X541608D01*
X534000Y274000D02*
Y289235D01*
X526382D01*
X523843Y286696D01*
Y281618D01*
X526382Y279078D01*
X534000D01*
X528922D02*
X523843Y274000D01*
X518765Y286696D02*
X516226Y289235D01*
X511147D01*
X508608Y286696D01*
Y284157D01*
X511147Y281618D01*
X508608Y279078D01*
Y276539D01*
X511147Y274000D01*
X516226D01*
X518765Y276539D01*
Y279078D01*
X516226Y281618D01*
X518765Y284157D01*
Y286696D01*
X516226Y281618D02*
X511147D01*
X569843Y68696D02*
X572383Y71235D01*
X577461D01*
X580000Y68696D01*
Y58539D01*
X577461Y56000D01*
X572383D01*
X569843Y58539D01*
X564765Y68696D02*
X562226Y71235D01*
X557147D01*
X554608Y68696D01*
Y66157D01*
X557147Y63618D01*
X554608Y61078D01*
Y58539D01*
X557147Y56000D01*
X562226D01*
X564765Y58539D01*
Y61078D01*
X562226Y63618D01*
X564765Y66157D01*
Y68696D01*
X562226Y63618D02*
X557147D01*
X648000Y65696D02*
X645461Y68235D01*
X640383D01*
X637843Y65696D01*
Y55539D01*
X640383Y53000D01*
X645461D01*
X648000Y55539D01*
Y65696D01*
X469000Y7500D02*
Y22735D01*
X461382D01*
X458843Y20196D01*
Y15117D01*
X461382Y12578D01*
X469000D01*
X463922D02*
X458843Y7500D01*
X443608Y22735D02*
X453765D01*
Y15117D01*
X448687D01*
X453765D01*
Y7500D01*
X456343Y80196D02*
X458882Y82735D01*
X463961D01*
X466500Y80196D01*
Y70039D01*
X463961Y67500D01*
X458882D01*
X456343Y70039D01*
X441108Y82735D02*
X451265D01*
Y75118D01*
X446187D01*
X451265D01*
Y67500D01*
X535500Y89000D02*
Y104235D01*
X527882D01*
X525343Y101696D01*
Y96618D01*
X527882Y94078D01*
X535500D01*
X530422D02*
X525343Y89000D01*
X520265Y101696D02*
X517726Y104235D01*
X512647D01*
X510108Y101696D01*
Y99157D01*
X512647Y96618D01*
X515187D01*
X512647D01*
X510108Y94078D01*
Y91539D01*
X512647Y89000D01*
X517726D01*
X520265Y91539D01*
X529843Y412696D02*
X532383Y415235D01*
X537461D01*
X540000Y412696D01*
Y402539D01*
X537461Y400000D01*
X532383D01*
X529843Y402539D01*
X514608Y415235D02*
X524765D01*
Y407617D01*
X519687Y410157D01*
X517147D01*
X514608Y407617D01*
Y402539D01*
X517147Y400000D01*
X522226D01*
X524765Y402539D01*
X529843Y387696D02*
X532383Y390235D01*
X537461D01*
X540000Y387696D01*
Y377539D01*
X537461Y375000D01*
X532383D01*
X529843Y377539D01*
X517147Y375000D02*
Y390235D01*
X524765Y382617D01*
X514608D01*
D12*
X619000Y61000D02*
X631000D01*
X546000Y429000D02*
Y441000D01*
Y464000D02*
Y476000D01*
X558000Y91000D02*
Y103000D01*
X508000Y250000D02*
Y262000D01*
X546000Y250000D02*
Y262000D01*
X620000Y348000D02*
Y360000D01*
X456500Y29000D02*
Y41000D01*
X328335Y88331D02*
X330002Y89997D01*
X333334D01*
X335000Y88331D01*
Y81666D01*
X333334Y80000D01*
X330002D01*
X328335Y81666D01*
X325003Y89997D02*
X318339D01*
Y88331D01*
X325003Y81666D01*
Y80000D01*
X303335Y88331D02*
X305002Y89997D01*
X308334D01*
X310000Y88331D01*
Y81666D01*
X308334Y80000D01*
X305002D01*
X303335Y81666D01*
X293339Y89997D02*
X296671Y88331D01*
X300003Y84998D01*
Y81666D01*
X298337Y80000D01*
X295005D01*
X293339Y81666D01*
Y83332D01*
X295005Y84998D01*
X300003D01*
X583000Y473000D02*
Y482997D01*
X578002D01*
X576335Y481331D01*
Y477998D01*
X578002Y476332D01*
X583000D01*
X579668D02*
X576335Y473000D01*
X566339D02*
X573003D01*
X566339Y479664D01*
Y481331D01*
X568005Y482997D01*
X571337D01*
X573003Y481331D01*
X580000Y443000D02*
Y452997D01*
X575002D01*
X573335Y451331D01*
Y447998D01*
X575002Y446332D01*
X580000D01*
X576668D02*
X573335Y443000D01*
X570003D02*
X566671D01*
X568337D01*
Y452997D01*
X570003Y451331D01*
D14*
X337660Y37474D02*
Y72526D01*
X330040D02*
X337660D01*
X330040Y37474D02*
X337660D01*
X317340Y72526D02*
X324960D01*
X317340Y37474D02*
X324960D01*
X317340D02*
Y72526D01*
X312660Y37474D02*
Y72526D01*
X305040D02*
X312660D01*
X305040Y37474D02*
X312660D01*
X292340Y72526D02*
X299960D01*
X292340Y37474D02*
X299960D01*
X292340D02*
Y72526D01*
X634652Y43474D02*
X634652Y57190D01*
X615348Y43474D02*
X634652D01*
X615348Y57190D02*
X615348Y43474D01*
X634652Y64810D02*
Y78526D01*
X615348D02*
X634652D01*
X615348Y64810D02*
Y78526D01*
X545974Y390160D02*
X581026D01*
X545974Y382540D02*
Y390160D01*
X581026Y382540D02*
Y390160D01*
X545974Y369840D02*
Y377460D01*
X581026Y369840D02*
Y377460D01*
X545974Y369840D02*
X581026D01*
X545974Y415160D02*
X581026D01*
X545974Y407540D02*
Y415160D01*
X581026Y407540D02*
Y415160D01*
X545974Y394840D02*
Y402460D01*
X581026Y394840D02*
Y402460D01*
X545974Y394840D02*
X581026D01*
X516474Y53840D02*
X551526D01*
Y61460D01*
X516474Y53840D02*
Y61460D01*
X551526Y66540D02*
Y74160D01*
X516474Y66540D02*
Y74160D01*
X551526D01*
X438974Y44840D02*
X474026D01*
Y52460D01*
X438974Y44840D02*
Y52460D01*
X474026Y57540D02*
Y65160D01*
X438974Y57540D02*
Y65160D01*
X474026D01*
X528474Y425348D02*
X542190Y425348D01*
X528474Y425348D02*
Y444652D01*
X542190Y444652D01*
X549810Y425348D02*
X563526D01*
Y444652D01*
X549810D02*
X563526D01*
X528474Y460348D02*
X542190Y460348D01*
X528474Y460348D02*
Y479652D01*
X542190Y479652D01*
X549810Y460348D02*
X563526D01*
Y479652D01*
X549810D02*
X563526D01*
X540474Y87348D02*
X554190Y87348D01*
X540474Y87348D02*
Y106652D01*
X554190Y106652D01*
X561810Y87348D02*
X575526D01*
Y106652D01*
X561810D02*
X575526D01*
X511810Y265652D02*
X525526Y265652D01*
Y246348D02*
Y265652D01*
X511810Y246348D02*
X525526Y246348D01*
X490474Y265652D02*
X504190D01*
X490474Y246348D02*
Y265652D01*
Y246348D02*
X504190D01*
X528474Y246348D02*
X542190Y246348D01*
X528474Y246348D02*
Y265652D01*
X542190Y265652D01*
X549810Y246348D02*
X563526D01*
Y265652D01*
X549810D02*
X563526D01*
X602474Y344348D02*
X616190Y344348D01*
X602474Y344348D02*
Y363652D01*
X616190Y363652D01*
X623810Y344348D02*
X637526D01*
Y363652D01*
X623810D02*
X637526D01*
X438974Y25348D02*
X452690Y25348D01*
X438974Y25348D02*
Y44652D01*
X452690Y44652D01*
X460310Y25348D02*
X474026D01*
Y44652D01*
X460310D02*
X474026D01*
D61*
X255000Y40000D02*
Y66000D01*
X285000Y42000D02*
X288000D01*
Y40000D02*
Y42000D01*
X285000Y40000D02*
X288000D01*
X285000Y41000D02*
X288000D01*
X285000Y40000D02*
Y66000D01*
M02*

View File

@@ -0,0 +1,89 @@
G04*
G04 #@! TF.GenerationSoftware,Altium Limited,Altium Designer,19.1.9 (167)*
G04*
G04 Layer_Color=128*
%FSLAX44Y44*%
%MOMM*%
G71*
G01*
G75*
%ADD19R,1.2000X1.4000*%
%ADD20R,1.3000X1.5000*%
%ADD21R,1.5000X1.3000*%
%ADD22R,1.4000X1.2000*%
G04:AMPARAMS|DCode=59|XSize=2.3mm|YSize=1.8mm|CornerRadius=0.225mm|HoleSize=0mm|Usage=FLASHONLY|Rotation=0.000|XOffset=0mm|YOffset=0mm|HoleType=Round|Shape=RoundedRectangle|*
%AMROUNDEDRECTD59*
21,1,2.3000,1.3500,0,0,0.0*
21,1,1.8500,1.8000,0,0,0.0*
1,1,0.4500,0.9250,-0.6750*
1,1,0.4500,-0.9250,-0.6750*
1,1,0.4500,-0.9250,0.6750*
1,1,0.4500,0.9250,0.6750*
%
%ADD59ROUNDEDRECTD59*%
D19*
X537000Y435000D02*
D03*
X555000D02*
D03*
X537000Y470000D02*
D03*
X555000D02*
D03*
X549000Y97000D02*
D03*
X567000D02*
D03*
X517000Y256000D02*
D03*
X499000D02*
D03*
X537000Y256000D02*
D03*
X555000D02*
D03*
X611000Y354000D02*
D03*
X629000D02*
D03*
X447500Y35000D02*
D03*
X465500D02*
D03*
D20*
X555000Y380000D02*
D03*
X572000D02*
D03*
X555000Y405000D02*
D03*
X572000D02*
D03*
X542500Y64000D02*
D03*
X525500D02*
D03*
X465000Y55000D02*
D03*
X448000D02*
D03*
D21*
X327500Y63500D02*
D03*
Y46500D02*
D03*
X302500Y63500D02*
D03*
Y46500D02*
D03*
D22*
X625000Y52000D02*
D03*
Y70000D02*
D03*
D59*
X270000Y69500D02*
D03*
Y40500D02*
D03*
M02*

View File

@@ -0,0 +1,291 @@
G04*
G04 #@! TF.GenerationSoftware,Altium Limited,Altium Designer,19.1.9 (167)*
G04*
G04 Layer_Color=16711935*
%FSLAX44Y44*%
%MOMM*%
G71*
G01*
G75*
%ADD24R,1.3500X1.5500*%
%ADD25R,1.4500X1.6500*%
%ADD26R,1.6500X1.4500*%
%ADD27R,1.5500X1.3500*%
%ADD28C,1.6740*%
%ADD29C,1.8500*%
%ADD30O,2.6000X2.0000*%
%ADD31C,1.6500*%
%ADD32R,1.6500X1.6500*%
%ADD33R,1.6500X1.6500*%
%ADD34C,3.1500*%
%ADD35C,2.1500*%
%ADD36C,1.9500*%
%ADD37R,1.9500X1.9500*%
%ADD38C,1.7500*%
%ADD39R,1.7500X1.7500*%
%ADD40R,1.8500X1.8500*%
%ADD41R,1.7500X1.7500*%
%ADD42C,1.7500*%
G04:AMPARAMS|DCode=60|XSize=2.45mm|YSize=1.95mm|CornerRadius=0.3mm|HoleSize=0mm|Usage=FLASHONLY|Rotation=0.000|XOffset=0mm|YOffset=0mm|HoleType=Round|Shape=RoundedRectangle|*
%AMROUNDEDRECTD60*
21,1,2.4500,1.3500,0,0,0.0*
21,1,1.8500,1.9500,0,0,0.0*
1,1,0.6000,0.9250,-0.6750*
1,1,0.6000,-0.9250,-0.6750*
1,1,0.6000,-0.9250,0.6750*
1,1,0.6000,0.9250,0.6750*
%
%ADD60ROUNDEDRECTD60*%
D24*
X537000Y435000D02*
D03*
X555000D02*
D03*
X537000Y470000D02*
D03*
X555000D02*
D03*
X549000Y97000D02*
D03*
X567000D02*
D03*
X517000Y256000D02*
D03*
X499000D02*
D03*
X537000Y256000D02*
D03*
X555000D02*
D03*
X611000Y354000D02*
D03*
X629000D02*
D03*
X447500Y35000D02*
D03*
X465500D02*
D03*
D25*
X555000Y380000D02*
D03*
X572000D02*
D03*
X555000Y405000D02*
D03*
X572000D02*
D03*
X542500Y64000D02*
D03*
X525500D02*
D03*
X465000Y55000D02*
D03*
X448000D02*
D03*
D26*
X327500Y63500D02*
D03*
Y46500D02*
D03*
X302500Y63500D02*
D03*
Y46500D02*
D03*
D27*
X625000Y52000D02*
D03*
Y70000D02*
D03*
D28*
X650000Y250000D02*
D03*
X25000Y475000D02*
D03*
Y25000D02*
D03*
D29*
X120000Y347500D02*
D03*
Y320000D02*
D03*
X355400Y430000D02*
D03*
X330000D02*
D03*
X304600D02*
D03*
X587000Y299000D02*
D03*
X235000Y80000D02*
D03*
X245000Y170000D02*
D03*
X574999Y198000D02*
D03*
X528000Y387000D02*
D03*
X500000Y285000D02*
D03*
X543999Y231999D02*
D03*
X451000Y274000D02*
D03*
X520000Y224000D02*
D03*
D30*
X80000Y450000D02*
D03*
X204000D02*
D03*
X80000Y400000D02*
D03*
X204000D02*
D03*
X80000Y100000D02*
D03*
X204000D02*
D03*
X80000Y50000D02*
D03*
X204000D02*
D03*
D31*
X80000Y300800D02*
D03*
Y275400D02*
D03*
Y250000D02*
D03*
Y224600D02*
D03*
X254600Y119000D02*
D03*
X356200D02*
D03*
X330800D02*
D03*
X305400D02*
D03*
X280000D02*
D03*
D32*
X80000Y199200D02*
D03*
D33*
X381600Y119000D02*
D03*
D34*
X190000Y203000D02*
D03*
Y297000D02*
D03*
D35*
X120000Y225000D02*
D03*
Y250000D02*
D03*
Y275000D02*
D03*
D36*
X677000Y383800D02*
D03*
Y460000D02*
D03*
X600800D02*
D03*
Y434600D02*
D03*
Y409200D02*
D03*
X677000Y40000D02*
D03*
Y116200D02*
D03*
X600800D02*
D03*
Y90800D02*
D03*
Y65400D02*
D03*
X678900Y326200D02*
D03*
X653500D02*
D03*
X628100D02*
D03*
X602700D02*
D03*
X577300D02*
D03*
X551900D02*
D03*
X526500D02*
D03*
X501100D02*
D03*
X678900Y173800D02*
D03*
X653500D02*
D03*
X628100D02*
D03*
X602700D02*
D03*
X577300D02*
D03*
X551900D02*
D03*
X526500D02*
D03*
X501100D02*
D03*
D37*
X600800Y383800D02*
D03*
Y40000D02*
D03*
D38*
X416900Y34400D02*
D03*
Y59800D02*
D03*
Y85200D02*
D03*
Y110600D02*
D03*
X493100D02*
D03*
Y85200D02*
D03*
Y59800D02*
D03*
D39*
Y34400D02*
D03*
D40*
X279200Y430000D02*
D03*
D41*
X424300Y464684D02*
D03*
D42*
Y439300D02*
D03*
Y413900D02*
D03*
Y388500D02*
D03*
X500500D02*
D03*
Y413900D02*
D03*
Y439300D02*
D03*
Y464700D02*
D03*
D60*
X270000Y69500D02*
D03*
Y40500D02*
D03*
M02*

View File

@@ -0,0 +1,10 @@
G04*
G04 #@! TF.GenerationSoftware,Altium Limited,Altium Designer,19.1.9 (167)*
G04*
G04 Layer_Color=16711935*
%FSLAX44Y44*%
%MOMM*%
G71*
G01*
G75*
M02*

View File

@@ -0,0 +1,1965 @@
G04*
G04 #@! TF.GenerationSoftware,Altium Limited,Altium Designer,19.1.9 (167)*
G04*
G04 Layer_Physical_Order=1*
G04 Layer_Color=255*
%FSLAX44Y44*%
%MOMM*%
G71*
G01*
G75*
%ADD18R,2.5000X2.2000*%
%ADD19R,1.2000X1.4000*%
%ADD20R,1.3000X1.5000*%
%ADD21R,1.5000X1.3000*%
%ADD22R,1.4000X1.2000*%
%ADD43C,0.8000*%
%ADD44C,1.5240*%
%ADD45C,1.7000*%
%ADD46O,2.4000X1.8000*%
%ADD47C,1.5000*%
%ADD48R,1.5000X1.5000*%
%ADD49R,1.5000X1.5000*%
%ADD50C,3.0000*%
%ADD51C,2.0000*%
%ADD52C,1.8000*%
%ADD53R,1.8000X1.8000*%
%ADD54C,1.6000*%
%ADD55R,1.6000X1.6000*%
%ADD56R,1.7000X1.7000*%
%ADD57R,1.6000X1.6000*%
%ADD58C,1.6000*%
G36*
X692500Y471011D02*
X687500Y469314D01*
X686985Y469985D01*
X684060Y472229D01*
X680655Y473640D01*
X680500Y473660D01*
Y460000D01*
Y446340D01*
X680655Y446360D01*
X684060Y447771D01*
X686985Y450015D01*
X687500Y450686D01*
X692500Y448989D01*
Y394811D01*
X687500Y393114D01*
X686985Y393785D01*
X684060Y396029D01*
X680655Y397440D01*
X680500Y397460D01*
Y383800D01*
Y370140D01*
X680655Y370160D01*
X684060Y371571D01*
X686985Y373815D01*
X687500Y374486D01*
X692500Y372789D01*
Y339432D01*
X691977Y338962D01*
X687500Y337248D01*
X685960Y338429D01*
X682555Y339840D01*
X678900Y340321D01*
X675245Y339840D01*
X671840Y338429D01*
X668915Y336185D01*
X663485D01*
X660560Y338429D01*
X657155Y339840D01*
X654671Y340167D01*
X640000Y354838D01*
Y366000D01*
X617697D01*
X614800Y369800D01*
Y380300D01*
X600800D01*
X586800D01*
Y371078D01*
X578760D01*
X556419Y393419D01*
X554539Y394861D01*
X552350Y395768D01*
X550000Y396078D01*
X538051D01*
X537628Y396628D01*
X534808Y398792D01*
X533444Y399357D01*
X532003Y404067D01*
X532131Y405116D01*
X552537Y425522D01*
X590119D01*
X590815Y424615D01*
Y419185D01*
X588571Y416260D01*
X587160Y412855D01*
X587140Y412700D01*
X600800D01*
X614460D01*
X614440Y412855D01*
X613029Y416260D01*
X610785Y419185D01*
Y424615D01*
X613029Y427540D01*
X614440Y430945D01*
X614921Y434600D01*
X614440Y438255D01*
X613029Y441660D01*
X610785Y444585D01*
Y450015D01*
X613029Y452940D01*
X614440Y456345D01*
X614921Y460000D01*
X614440Y463655D01*
X613029Y467060D01*
X610785Y469985D01*
X607860Y472229D01*
X604455Y473640D01*
X600800Y474121D01*
X597145Y473640D01*
X593740Y472229D01*
X590815Y469985D01*
X588571Y467060D01*
X587160Y463655D01*
X586679Y460000D01*
X587160Y456345D01*
X588571Y452940D01*
X590815Y450015D01*
Y448389D01*
X586191Y443678D01*
X548777D01*
X546428Y443368D01*
X544239Y442461D01*
X542359Y441019D01*
X534078Y432738D01*
X529078Y434809D01*
Y447377D01*
X528768Y449726D01*
X527861Y451916D01*
X526419Y453796D01*
X526419Y453796D01*
X513324Y466891D01*
X513165Y468093D01*
X511855Y471256D01*
X509772Y473971D01*
X507056Y476055D01*
X503894Y477365D01*
X500500Y477812D01*
X497106Y477365D01*
X493944Y476055D01*
X491228Y473971D01*
X490125Y472534D01*
X488281Y471119D01*
X459581Y442419D01*
X458139Y440539D01*
X457232Y438349D01*
X456922Y436000D01*
Y362301D01*
X457232Y359952D01*
X458139Y357763D01*
X459581Y355883D01*
X487245Y328219D01*
X486979Y326200D01*
X487460Y322545D01*
X488871Y319140D01*
X491115Y316215D01*
X494040Y313971D01*
X494807Y313653D01*
X494819Y313563D01*
X495726Y311374D01*
X497169Y309494D01*
X503458Y303204D01*
X501123Y298468D01*
X500000Y298616D01*
X496476Y298152D01*
X493192Y296792D01*
X490372Y294628D01*
X488208Y291808D01*
X486847Y288524D01*
X486504Y285917D01*
X486159Y285401D01*
X485130Y284407D01*
X481636Y282754D01*
X481601Y282768D01*
X479251Y283078D01*
X461051D01*
X460628Y283628D01*
X457808Y285792D01*
X454524Y287153D01*
X451000Y287617D01*
X447476Y287153D01*
X444192Y285792D01*
X441372Y283628D01*
X439208Y280808D01*
X437848Y277524D01*
X437735Y276673D01*
X432456Y274881D01*
X410578Y296759D01*
Y376814D01*
X413332Y378308D01*
X415578Y378806D01*
X417744Y377144D01*
X420800Y375878D01*
Y388500D01*
X424300D01*
Y392000D01*
X436921D01*
X435655Y395056D01*
X433572Y397771D01*
X433031Y398186D01*
X432878Y398493D01*
Y403907D01*
X433031Y404213D01*
X433572Y404628D01*
X435655Y407344D01*
X436965Y410506D01*
X437412Y413900D01*
X436965Y417294D01*
X435655Y420456D01*
X433572Y423172D01*
X433031Y423586D01*
X432878Y423893D01*
Y429307D01*
X433031Y429613D01*
X433572Y430028D01*
X435655Y432744D01*
X436965Y435906D01*
X437412Y439300D01*
X436965Y442693D01*
X435655Y445856D01*
X435019Y446685D01*
X437300Y451685D01*
X437300D01*
Y477685D01*
X411300D01*
Y458609D01*
X406300Y456538D01*
X391419Y471419D01*
X389539Y472862D01*
X387349Y473768D01*
X385000Y474078D01*
X225000D01*
X225000Y474078D01*
X222651Y473768D01*
X220461Y472862D01*
X218581Y471419D01*
X210759Y463596D01*
X210655Y463640D01*
X207000Y464121D01*
X201000D01*
X197345Y463640D01*
X193940Y462229D01*
X191015Y459985D01*
X188771Y457060D01*
X187360Y453655D01*
X186879Y450000D01*
X187360Y446345D01*
X188771Y442940D01*
X191015Y440015D01*
X193940Y437771D01*
X197345Y436360D01*
X201000Y435879D01*
X207000D01*
X210655Y436360D01*
X214060Y437771D01*
X216985Y440015D01*
X219229Y442940D01*
X220640Y446345D01*
X220861Y448023D01*
X228760Y455922D01*
X331552D01*
X333465Y451303D01*
X324494Y442332D01*
X323192Y441792D01*
X320372Y439628D01*
X314228D01*
X311408Y441792D01*
X308124Y443153D01*
X304600Y443616D01*
X301076Y443153D01*
X297792Y441792D01*
X297700Y441722D01*
X292700Y443500D01*
Y443500D01*
X282700D01*
Y430000D01*
Y416500D01*
X292700D01*
Y416500D01*
X297700Y418278D01*
X297792Y418208D01*
X301076Y416847D01*
X304600Y416384D01*
X308124Y416847D01*
X311408Y418208D01*
X314228Y420372D01*
X320372D01*
X323192Y418208D01*
X326476Y416847D01*
X330000Y416384D01*
X333524Y416847D01*
X336808Y418208D01*
X339628Y420372D01*
X345772D01*
X348592Y418208D01*
X351876Y416847D01*
X355400Y416384D01*
X358924Y416847D01*
X359907Y417255D01*
X365922Y411240D01*
Y276615D01*
X366232Y274266D01*
X367139Y272076D01*
X368581Y270196D01*
X418196Y220581D01*
X420076Y219139D01*
X422266Y218232D01*
X424615Y217922D01*
X424615Y217922D01*
X478449D01*
X478969Y217181D01*
X480146Y212922D01*
X479881Y212719D01*
X479881Y212719D01*
X416240Y149078D01*
X245000D01*
X242651Y148768D01*
X240461Y147861D01*
X238581Y146419D01*
X206283Y114121D01*
X201000D01*
X197345Y113640D01*
X193940Y112229D01*
X191015Y109985D01*
X188771Y107060D01*
X187360Y103655D01*
X186879Y100000D01*
X187360Y96345D01*
X188771Y92940D01*
X191015Y90015D01*
X193940Y87771D01*
X197345Y86360D01*
X201000Y85879D01*
X207000D01*
X210655Y86360D01*
X214060Y87771D01*
X216985Y90015D01*
X219229Y92940D01*
X220640Y96345D01*
X221121Y100000D01*
X220739Y102901D01*
X237131Y119293D01*
X237927Y119245D01*
X242215Y117306D01*
X242422Y115737D01*
X243681Y112696D01*
X245685Y110085D01*
X248296Y108081D01*
X251337Y106822D01*
X254600Y106392D01*
X257863Y106822D01*
X260904Y108081D01*
X263515Y110085D01*
X264318Y111131D01*
X265495Y111518D01*
X269104D01*
X270282Y111131D01*
X271085Y110085D01*
X273696Y108081D01*
X276737Y106822D01*
X280000Y106392D01*
X283263Y106822D01*
X286304Y108081D01*
X288915Y110085D01*
X289718Y111131D01*
X290896Y111518D01*
X294505D01*
X295682Y111131D01*
X296485Y110085D01*
X299096Y108081D01*
X302137Y106822D01*
X305400Y106392D01*
X308663Y106822D01*
X311704Y108081D01*
X314315Y110085D01*
X315118Y111131D01*
X316296Y111518D01*
X319904D01*
X321082Y111131D01*
X321885Y110085D01*
X324496Y108081D01*
X327537Y106822D01*
X330800Y106392D01*
X334063Y106822D01*
X337104Y108081D01*
X339715Y110085D01*
X340518Y111131D01*
X341696Y111518D01*
X345304D01*
X346482Y111131D01*
X347285Y110085D01*
X349896Y108081D01*
X352937Y106822D01*
X356200Y106392D01*
X359463Y106822D01*
X362504Y108081D01*
X364100Y109306D01*
X367569Y108225D01*
X369100Y107065D01*
Y106500D01*
X394100D01*
Y130922D01*
X420000D01*
X422350Y131232D01*
X424539Y132139D01*
X426419Y133581D01*
X460803Y167965D01*
X465422Y166052D01*
Y151000D01*
X463500D01*
Y139338D01*
X448162Y124000D01*
X437000D01*
Y102000D01*
Y96838D01*
X435640Y95478D01*
X427568D01*
X425374Y99904D01*
X425449Y100550D01*
X425631Y100913D01*
X426172Y101328D01*
X428256Y104044D01*
X429565Y107206D01*
X430012Y110600D01*
X429565Y113994D01*
X428256Y117156D01*
X426172Y119872D01*
X423456Y121955D01*
X420294Y123265D01*
X416900Y123712D01*
X413506Y123265D01*
X410344Y121955D01*
X407628Y119872D01*
X405545Y117156D01*
X404235Y113994D01*
X403788Y110600D01*
X404235Y107206D01*
X405545Y104044D01*
X407628Y101328D01*
X408169Y100913D01*
X408322Y100607D01*
Y95193D01*
X408169Y94887D01*
X407628Y94472D01*
X405545Y91756D01*
X405263Y91076D01*
X404267Y90281D01*
X400019Y88686D01*
X399206Y88703D01*
X399049Y88768D01*
X396700Y89078D01*
X245051D01*
X244628Y89628D01*
X241808Y91792D01*
X238524Y93153D01*
X235000Y93616D01*
X231476Y93153D01*
X228192Y91792D01*
X225372Y89628D01*
X223208Y86808D01*
X221847Y83524D01*
X221384Y80000D01*
X221847Y76476D01*
X223208Y73192D01*
X225372Y70372D01*
X225922Y69949D01*
Y61000D01*
X220000D01*
X220000Y61000D01*
X215160Y61385D01*
X214060Y62229D01*
X210655Y63640D01*
X207500Y64055D01*
Y50000D01*
X200500D01*
Y64055D01*
X197345Y63640D01*
X193940Y62229D01*
X191015Y59985D01*
X188771Y57060D01*
X188501Y56408D01*
X182741Y55097D01*
X72578Y165260D01*
Y186700D01*
X76500D01*
Y199200D01*
X80000D01*
Y202700D01*
X92500D01*
Y205721D01*
X97500Y207792D01*
X141500Y163792D01*
Y158000D01*
X181500D01*
Y160922D01*
X189000D01*
Y158000D01*
X215500D01*
Y170000D01*
X220500D01*
Y158000D01*
X229000D01*
Y160867D01*
X229548Y161169D01*
X234000Y162159D01*
X235372Y160372D01*
X238192Y158208D01*
X241476Y156847D01*
X245000Y156383D01*
X248524Y156847D01*
X251808Y158208D01*
X254628Y160372D01*
X256792Y163192D01*
X258153Y166476D01*
X258616Y170000D01*
X258153Y173524D01*
X256792Y176808D01*
X254628Y179628D01*
X251808Y181792D01*
X248524Y183153D01*
X245000Y183616D01*
X244312Y183526D01*
X235419Y192419D01*
X233539Y193862D01*
X231349Y194768D01*
X229000Y195078D01*
X213722D01*
X211016Y197777D01*
X209809Y200078D01*
X210097Y203000D01*
X209711Y206921D01*
X208567Y210691D01*
X206710Y214165D01*
X204211Y217211D01*
X201165Y219710D01*
X197691Y221567D01*
X193921Y222711D01*
X190000Y223097D01*
X186079Y222711D01*
X182309Y221567D01*
X178835Y219710D01*
X175789Y217211D01*
X173290Y214165D01*
X171433Y210691D01*
X170289Y206921D01*
X169903Y203000D01*
X170289Y199079D01*
X171433Y195309D01*
X173290Y191835D01*
X175789Y188789D01*
X177970Y187000D01*
X177381Y183750D01*
X176380Y182000D01*
X148968D01*
X125269Y205699D01*
X125557Y208800D01*
X126547Y211491D01*
X128374Y212468D01*
X130658Y214342D01*
X132532Y216626D01*
X133925Y219232D01*
X134783Y222059D01*
X135073Y225000D01*
X134783Y227941D01*
X133925Y230768D01*
X132532Y233374D01*
X130658Y235658D01*
Y239342D01*
X132532Y241626D01*
X133925Y244232D01*
X134783Y247059D01*
X135073Y250000D01*
X134783Y252941D01*
X133925Y255768D01*
X132532Y258374D01*
X130658Y260658D01*
Y264342D01*
X132532Y266626D01*
X133925Y269232D01*
X134783Y272059D01*
X135073Y275000D01*
X134783Y277941D01*
X133925Y280768D01*
X132532Y283374D01*
X130658Y285658D01*
X128374Y287532D01*
X125768Y288925D01*
X122941Y289783D01*
X120000Y290073D01*
X117059Y289783D01*
X114232Y288925D01*
X111626Y287532D01*
X109342Y285658D01*
X107468Y283374D01*
X106075Y280768D01*
X105217Y277941D01*
X104927Y275000D01*
X105217Y272059D01*
X106075Y269232D01*
X107468Y266626D01*
X109342Y264342D01*
Y260658D01*
X107468Y258374D01*
X106075Y255768D01*
X105217Y252941D01*
X104927Y250000D01*
X105217Y247059D01*
X106075Y244232D01*
X106532Y243377D01*
X102472Y240366D01*
X92573Y250265D01*
X92178Y253263D01*
X90919Y256304D01*
X88915Y258915D01*
X87869Y259718D01*
X87482Y260896D01*
Y264505D01*
X87869Y265682D01*
X88915Y266485D01*
X90919Y269096D01*
X92178Y272137D01*
X92608Y275400D01*
X92390Y277053D01*
X93105Y277767D01*
X93253Y277881D01*
X122022Y306650D01*
X123524Y306847D01*
X126808Y308208D01*
X129628Y310372D01*
X131792Y313192D01*
X133153Y316476D01*
X133616Y320000D01*
X133153Y323524D01*
X131792Y326808D01*
X129628Y329628D01*
X128075Y330820D01*
X127646Y332477D01*
Y335023D01*
X128075Y336680D01*
X129628Y337872D01*
X131792Y340692D01*
X133153Y343976D01*
X133616Y347500D01*
X133153Y351024D01*
X131792Y354308D01*
X129628Y357128D01*
X126808Y359292D01*
X123524Y360653D01*
X120000Y361116D01*
X116476Y360653D01*
X113192Y359292D01*
X110372Y357128D01*
X108208Y354308D01*
X106847Y351024D01*
X106383Y347500D01*
X106474Y346812D01*
X80581Y320919D01*
X79139Y319039D01*
X78232Y316849D01*
X77922Y314500D01*
Y313454D01*
X77554Y313086D01*
X76737Y312978D01*
X73696Y311719D01*
X71085Y309715D01*
X69081Y307104D01*
X67822Y304063D01*
X67392Y300800D01*
X67822Y297537D01*
X69081Y294496D01*
X71085Y291885D01*
X72131Y291082D01*
X72518Y289904D01*
Y286296D01*
X72131Y285118D01*
X71085Y284315D01*
X69081Y281704D01*
X67822Y278663D01*
X67392Y275400D01*
X67822Y272137D01*
X69081Y269096D01*
X71085Y266485D01*
X72131Y265682D01*
X72518Y264505D01*
Y260896D01*
X72131Y259718D01*
X71085Y258915D01*
X69081Y256304D01*
X67822Y253263D01*
X67392Y250000D01*
X67822Y246737D01*
X69081Y243696D01*
X71085Y241085D01*
X72131Y240282D01*
X72518Y239104D01*
Y235495D01*
X72131Y234318D01*
X71085Y233515D01*
X69081Y230904D01*
X67822Y227863D01*
X67714Y227046D01*
X64602Y223934D01*
X62738Y222504D01*
X57081Y216847D01*
X55638Y214967D01*
X54732Y212777D01*
X54422Y210428D01*
Y161500D01*
X54732Y159151D01*
X55638Y156961D01*
X57081Y155081D01*
X193581Y18581D01*
X195461Y17139D01*
X197651Y16232D01*
X200000Y15922D01*
X200000Y15922D01*
X367500D01*
X369850Y16232D01*
X372039Y17139D01*
X373919Y18581D01*
X384338Y29000D01*
X400000D01*
Y29000D01*
X405000Y29159D01*
X405545Y27844D01*
X407628Y25128D01*
X410344Y23045D01*
X413506Y21735D01*
X416900Y21288D01*
X420294Y21735D01*
X423456Y23045D01*
X426172Y25128D01*
X428256Y27844D01*
X429565Y31006D01*
X432000Y34246D01*
X432500Y34206D01*
X437000Y33000D01*
Y33000D01*
X480100D01*
Y21400D01*
X506100D01*
Y47400D01*
X506100D01*
X503808Y52400D01*
X504455Y53244D01*
X505765Y56406D01*
X506212Y59800D01*
X505765Y63194D01*
X504455Y66356D01*
X502372Y69072D01*
X501831Y69487D01*
X501678Y69793D01*
Y75207D01*
X501831Y75513D01*
X502372Y75928D01*
X504455Y78644D01*
X505721Y81700D01*
X493100D01*
X480479D01*
X481745Y78644D01*
X482239Y78000D01*
X479773Y73000D01*
X472500D01*
Y61500D01*
X467500D01*
Y73000D01*
X452000D01*
Y61500D01*
X447000D01*
Y73000D01*
X442966D01*
X441749Y77632D01*
X443939Y78539D01*
X445819Y79981D01*
X449838Y84000D01*
X461000D01*
Y102000D01*
Y111162D01*
X476838Y127000D01*
X485657D01*
X486651Y122000D01*
X486544Y121955D01*
X483828Y119872D01*
X481745Y117156D01*
X480479Y114100D01*
X493100D01*
X505721D01*
X504455Y117156D01*
X502372Y119872D01*
X499656Y121955D01*
X499548Y122000D01*
X500543Y127000D01*
X503500D01*
Y136500D01*
X492500D01*
Y139000D01*
X490000D01*
Y151000D01*
X483578D01*
Y166453D01*
X488578Y167448D01*
X488871Y166740D01*
X491115Y163815D01*
X494040Y161571D01*
X497445Y160160D01*
X501100Y159679D01*
X504755Y160160D01*
X508160Y161571D01*
X511085Y163815D01*
X516515D01*
X519440Y161571D01*
X522845Y160160D01*
X526500Y159679D01*
X530155Y160160D01*
X533560Y161571D01*
X536485Y163815D01*
X541915D01*
X544840Y161571D01*
X548245Y160160D01*
X551900Y159679D01*
X555555Y160160D01*
X558960Y161571D01*
X561885Y163815D01*
X567315D01*
X570240Y161571D01*
X573645Y160160D01*
X577300Y159679D01*
X580955Y160160D01*
X584360Y161571D01*
X587285Y163815D01*
X592715D01*
X595640Y161571D01*
X599045Y160160D01*
X602700Y159679D01*
X606355Y160160D01*
X609760Y161571D01*
X612685Y163815D01*
X618115D01*
X621040Y161571D01*
X624445Y160160D01*
X628100Y159679D01*
X631755Y160160D01*
X635160Y161571D01*
X638085Y163815D01*
X643515D01*
X646440Y161571D01*
X649845Y160160D01*
X653500Y159679D01*
X657155Y160160D01*
X660560Y161571D01*
X663485Y163815D01*
X668915D01*
X671840Y161571D01*
X675245Y160160D01*
X678900Y159679D01*
X682555Y160160D01*
X685960Y161571D01*
X687500Y162752D01*
X691977Y161038D01*
X692500Y160568D01*
Y127211D01*
X687500Y125514D01*
X686985Y126185D01*
X684060Y128429D01*
X680655Y129840D01*
X680500Y129860D01*
Y116200D01*
Y102540D01*
X680655Y102560D01*
X684060Y103971D01*
X686985Y106215D01*
X687500Y106886D01*
X692500Y105189D01*
Y51011D01*
X687500Y49314D01*
X686985Y49985D01*
X684060Y52229D01*
X680655Y53640D01*
X680500Y53660D01*
Y40000D01*
Y26340D01*
X680655Y26360D01*
X684060Y27771D01*
X686985Y30015D01*
X687500Y30686D01*
X692500Y28989D01*
Y9092D01*
X71942D01*
X71617Y9546D01*
X69222Y14092D01*
X70208Y19048D01*
X70598Y25000D01*
X70208Y30952D01*
X69927Y32364D01*
X71698Y34436D01*
X74253Y36241D01*
X76500Y35945D01*
Y50000D01*
Y64055D01*
X73345Y63640D01*
X69940Y62229D01*
X67015Y59985D01*
X64771Y57060D01*
X64541Y56504D01*
X58741Y55534D01*
X57242Y57242D01*
X52758Y61175D01*
X47799Y64489D01*
X42449Y67127D01*
X36802Y69044D01*
X30952Y70208D01*
X25000Y70598D01*
X19048Y70208D01*
X17500Y69900D01*
X12500Y74003D01*
Y425997D01*
X17500Y430100D01*
X19048Y429793D01*
X25000Y429402D01*
X30952Y429793D01*
X36802Y430956D01*
X42449Y432873D01*
X47799Y435511D01*
X52758Y438825D01*
X57242Y442758D01*
X58741Y444466D01*
X64541Y443496D01*
X64771Y442940D01*
X67015Y440015D01*
X69940Y437771D01*
X73345Y436360D01*
X77000Y435879D01*
X83000D01*
X86655Y436360D01*
X90060Y437771D01*
X92985Y440015D01*
X95229Y442940D01*
X96640Y446345D01*
X97121Y450000D01*
X96640Y453655D01*
X95229Y457060D01*
X92985Y459985D01*
X90060Y462229D01*
X86655Y463640D01*
X83000Y464121D01*
X77000D01*
X74253Y463759D01*
X71698Y465564D01*
X69927Y467635D01*
X70208Y469048D01*
X70598Y475000D01*
X70208Y480952D01*
X69583Y484092D01*
X73069Y489092D01*
X692500D01*
Y471011D01*
D02*
G37*
G36*
X570240Y313971D02*
X573645Y312560D01*
X574261Y312479D01*
X574930Y311651D01*
X576255Y307174D01*
X575208Y305808D01*
X573848Y302524D01*
X573844Y302500D01*
X587000D01*
Y295500D01*
X573844D01*
X573848Y295476D01*
X575208Y292192D01*
X577372Y289372D01*
X580110Y287270D01*
X580193Y287201D01*
X581263Y281352D01*
X576489Y276578D01*
X542364D01*
X517824Y276578D01*
X517640Y276793D01*
X515915Y281577D01*
X520838Y286500D01*
X534000D01*
Y299000D01*
X536500D01*
Y301500D01*
X548000D01*
Y307549D01*
X551889Y312066D01*
X552716Y312187D01*
X555555Y312560D01*
X558960Y313971D01*
X561885Y316215D01*
X567315D01*
X570240Y313971D01*
D02*
G37*
%LPC*%
G36*
X673500Y473660D02*
X673345Y473640D01*
X669940Y472229D01*
X667015Y469985D01*
X664771Y467060D01*
X663360Y463655D01*
X663340Y463500D01*
X673500D01*
Y473660D01*
D02*
G37*
G36*
Y456500D02*
X663340D01*
X663360Y456345D01*
X664771Y452940D01*
X667015Y450015D01*
X669940Y447771D01*
X673345Y446360D01*
X673500Y446340D01*
Y456500D01*
D02*
G37*
G36*
X275700Y443500D02*
X265700D01*
Y433500D01*
X275700D01*
Y443500D01*
D02*
G37*
G36*
Y426500D02*
X265700D01*
Y416500D01*
X275700D01*
Y426500D01*
D02*
G37*
G36*
X207500Y414055D02*
Y403500D01*
X220660D01*
X220640Y403655D01*
X219229Y407060D01*
X216985Y409985D01*
X214060Y412229D01*
X210655Y413640D01*
X207500Y414055D01*
D02*
G37*
G36*
X83500D02*
Y403500D01*
X96660D01*
X96640Y403655D01*
X95229Y407060D01*
X92985Y409985D01*
X90060Y412229D01*
X86655Y413640D01*
X83500Y414055D01*
D02*
G37*
G36*
X200500Y414055D02*
X197345Y413640D01*
X193940Y412229D01*
X191015Y409985D01*
X188771Y407060D01*
X187360Y403655D01*
X187340Y403500D01*
X200500D01*
Y414055D01*
D02*
G37*
G36*
X76500D02*
X73345Y413640D01*
X69940Y412229D01*
X67015Y409985D01*
X64771Y407060D01*
X63360Y403655D01*
X63340Y403500D01*
X76500D01*
Y414055D01*
D02*
G37*
G36*
X673500Y397460D02*
X673345Y397440D01*
X669940Y396029D01*
X667015Y393785D01*
X664771Y390860D01*
X663360Y387455D01*
X663340Y387300D01*
X673500D01*
Y397460D01*
D02*
G37*
G36*
X614460Y405700D02*
X600800D01*
X587140D01*
X587160Y405545D01*
X588298Y402800D01*
X586800Y397800D01*
X586800D01*
Y387300D01*
X600800D01*
X614800D01*
Y397800D01*
X614800Y397800D01*
X613302Y402800D01*
X614440Y405545D01*
X614460Y405700D01*
D02*
G37*
G36*
X220660Y396500D02*
X207500D01*
Y385945D01*
X210655Y386360D01*
X214060Y387771D01*
X216985Y390015D01*
X219229Y392940D01*
X220640Y396345D01*
X220660Y396500D01*
D02*
G37*
G36*
X96660D02*
X83500D01*
Y385945D01*
X86655Y386360D01*
X90060Y387771D01*
X92985Y390015D01*
X95229Y392940D01*
X96640Y396345D01*
X96660Y396500D01*
D02*
G37*
G36*
X200500D02*
X187340D01*
X187360Y396345D01*
X188771Y392940D01*
X191015Y390015D01*
X193940Y387771D01*
X197345Y386360D01*
X200500Y385945D01*
Y396500D01*
D02*
G37*
G36*
X76500D02*
X63340D01*
X63360Y396345D01*
X64771Y392940D01*
X67015Y390015D01*
X69940Y387771D01*
X73345Y386360D01*
X76500Y385945D01*
Y396500D01*
D02*
G37*
G36*
X436921Y385000D02*
X427800D01*
Y375878D01*
X430856Y377144D01*
X433572Y379228D01*
X435655Y381944D01*
X436921Y385000D01*
D02*
G37*
G36*
X673500Y380300D02*
X663340D01*
X663360Y380145D01*
X664771Y376740D01*
X667015Y373815D01*
X669940Y371571D01*
X673345Y370160D01*
X673500Y370140D01*
Y380300D01*
D02*
G37*
G36*
X190000Y317097D02*
X186079Y316711D01*
X182309Y315567D01*
X178835Y313710D01*
X175789Y311211D01*
X173290Y308165D01*
X171433Y304691D01*
X170289Y300921D01*
X169903Y297000D01*
X170289Y293079D01*
X171433Y289309D01*
X173290Y285835D01*
X175789Y282789D01*
X178835Y280290D01*
X182309Y278433D01*
X186079Y277289D01*
X190000Y276903D01*
X193921Y277289D01*
X197691Y278433D01*
X201165Y280290D01*
X204211Y282789D01*
X206710Y285835D01*
X208567Y289309D01*
X209711Y293079D01*
X210097Y297000D01*
X209711Y300921D01*
X208567Y304691D01*
X206710Y308165D01*
X204211Y311211D01*
X201165Y313710D01*
X197691Y315567D01*
X193921Y316711D01*
X190000Y317097D01*
D02*
G37*
G36*
X92500Y195700D02*
X83500D01*
Y186700D01*
X92500D01*
Y195700D01*
D02*
G37*
G36*
X503500Y151000D02*
X495000D01*
Y141500D01*
X503500D01*
Y151000D01*
D02*
G37*
G36*
X673500Y129860D02*
X673345Y129840D01*
X669940Y128429D01*
X667015Y126185D01*
X664771Y123260D01*
X663360Y119855D01*
X663340Y119700D01*
X673500D01*
Y129860D01*
D02*
G37*
G36*
Y112700D02*
X663340D01*
X663360Y112545D01*
X664771Y109140D01*
X667015Y106215D01*
X669940Y103971D01*
X673345Y102560D01*
X673500Y102540D01*
Y112700D01*
D02*
G37*
G36*
X505721Y107100D02*
X493100D01*
X480479D01*
X481745Y104044D01*
X483828Y101328D01*
X484369Y100913D01*
X484522Y100607D01*
Y95193D01*
X484369Y94887D01*
X483828Y94472D01*
X481745Y91756D01*
X480479Y88700D01*
X493100D01*
X505721D01*
X504455Y91756D01*
X502372Y94472D01*
X501831Y94887D01*
X501678Y95193D01*
Y100607D01*
X501831Y100913D01*
X502372Y101328D01*
X504455Y104044D01*
X505721Y107100D01*
D02*
G37*
G36*
X83000Y114121D02*
X77000D01*
X73345Y113640D01*
X69940Y112229D01*
X67015Y109985D01*
X64771Y107060D01*
X63360Y103655D01*
X62879Y100000D01*
X63360Y96345D01*
X64771Y92940D01*
X67015Y90015D01*
X69940Y87771D01*
X73345Y86360D01*
X77000Y85879D01*
X83000D01*
X86655Y86360D01*
X90060Y87771D01*
X92985Y90015D01*
X95229Y92940D01*
X96640Y96345D01*
X97121Y100000D01*
X96640Y103655D01*
X95229Y107060D01*
X92985Y109985D01*
X90060Y112229D01*
X86655Y113640D01*
X83000Y114121D01*
D02*
G37*
G36*
X83500Y64055D02*
Y53500D01*
X96660D01*
X96640Y53655D01*
X95229Y57060D01*
X92985Y59985D01*
X90060Y62229D01*
X86655Y63640D01*
X83500Y64055D01*
D02*
G37*
G36*
X673500Y53660D02*
X673345Y53640D01*
X669940Y52229D01*
X667015Y49985D01*
X664771Y47060D01*
X663360Y43655D01*
X663340Y43500D01*
X673500D01*
Y53660D01*
D02*
G37*
G36*
X96660Y46500D02*
X83500D01*
Y35945D01*
X86655Y36360D01*
X90060Y37771D01*
X92985Y40015D01*
X95229Y42940D01*
X96640Y46345D01*
X96660Y46500D01*
D02*
G37*
G36*
X673500Y36500D02*
X663340D01*
X663360Y36345D01*
X664771Y32940D01*
X667015Y30015D01*
X669940Y27771D01*
X673345Y26360D01*
X673500Y26340D01*
Y36500D01*
D02*
G37*
G36*
X600800Y130321D02*
X597145Y129840D01*
X593740Y128429D01*
X590815Y126185D01*
X588571Y123260D01*
X587160Y119855D01*
X586679Y116200D01*
X587160Y112545D01*
X588571Y109140D01*
X590815Y106215D01*
Y100785D01*
X588571Y97860D01*
X587160Y94455D01*
X586679Y90800D01*
X587160Y87145D01*
X588571Y83740D01*
X590815Y80815D01*
Y75385D01*
X588571Y72460D01*
X587160Y69055D01*
X586679Y65400D01*
X587160Y61745D01*
X588298Y59000D01*
X586800Y54000D01*
X586800D01*
Y26000D01*
X614800D01*
Y54000D01*
X614800Y54000D01*
X613302Y59000D01*
X614440Y61745D01*
X614921Y65400D01*
X614440Y69055D01*
X613029Y72460D01*
X610785Y75385D01*
Y80815D01*
X613029Y83740D01*
X614440Y87145D01*
X614921Y90800D01*
X614440Y94455D01*
X613029Y97860D01*
X610785Y100785D01*
Y106215D01*
X613029Y109140D01*
X614440Y112545D01*
X614921Y116200D01*
X614440Y119855D01*
X613029Y123260D01*
X610785Y126185D01*
X607860Y128429D01*
X604455Y129840D01*
X600800Y130321D01*
D02*
G37*
G36*
X548000Y296500D02*
X539000D01*
Y286500D01*
X548000D01*
Y296500D01*
D02*
G37*
%LPD*%
D18*
X382500Y45000D02*
D03*
X237500D02*
D03*
D19*
X200000Y170000D02*
D03*
X218000D02*
D03*
X170500D02*
D03*
X152500D02*
D03*
X474500Y139000D02*
D03*
X492500D02*
D03*
X629000Y354000D02*
D03*
X611000D02*
D03*
D20*
X536500Y299000D02*
D03*
X519500D02*
D03*
X523500Y352000D02*
D03*
X506500D02*
D03*
D21*
X449500Y44500D02*
D03*
Y61500D02*
D03*
X470000Y44500D02*
D03*
Y61500D02*
D03*
D22*
X449000Y113000D02*
D03*
Y95000D02*
D03*
D43*
X367500Y25000D02*
X385000Y42500D01*
X200000Y25000D02*
X367500D01*
X63500Y161500D02*
X200000Y25000D01*
X432884Y44500D02*
X449500D01*
X417584Y59800D02*
X432884Y44500D01*
X449500D02*
X470000D01*
X416900Y59800D02*
X417584D01*
X396700Y80000D02*
X416900Y59800D01*
X235000Y80000D02*
X396700D01*
X63500Y161500D02*
Y210428D01*
X148130Y170000D02*
X152500D01*
X101000Y217130D02*
X148130Y170000D01*
X63500Y210428D02*
X69157Y216085D01*
X69591D01*
X78106Y224600D01*
X80000D01*
X101000Y217130D02*
Y229000D01*
X235000Y50000D02*
Y80000D01*
X118278Y315743D02*
Y318278D01*
X86834Y284300D02*
X118278Y315743D01*
X86800Y284300D02*
X86834D01*
X80000Y277500D02*
X86800Y284300D01*
X80000Y275400D02*
Y277500D01*
X137500Y318278D02*
X140778Y315000D01*
X170000D01*
X118278Y318278D02*
X120000Y320000D01*
X87000Y309694D02*
Y314500D01*
X80000Y302694D02*
X87000Y309694D01*
X80000Y300800D02*
Y302694D01*
X87000Y314500D02*
X120000Y347500D01*
X200000Y177728D02*
X208272Y186000D01*
X200000Y170000D02*
Y177728D01*
X229000Y186000D02*
X245000Y170000D01*
X208272Y186000D02*
X229000D01*
X170500Y170000D02*
X200000D01*
X78661Y223261D02*
X80000Y224600D01*
X524350Y328350D02*
X525000Y329000D01*
X523500Y327500D02*
X524350Y328350D01*
X523000Y332700D02*
Y354500D01*
X80000Y250000D02*
X101000Y229000D01*
X628100Y315351D02*
Y326200D01*
X580249Y267500D02*
X628100Y315351D01*
X542364Y267500D02*
X580249D01*
X598477Y231000D02*
X653500Y175977D01*
X571249Y231000D02*
X598477D01*
X653500Y173800D02*
Y175977D01*
X628100Y173800D02*
Y175977D01*
X588577Y215500D02*
X628100Y175977D01*
X552248Y215500D02*
X588577D01*
X570299Y193300D02*
X574999Y198000D01*
X653250Y326450D02*
X653500Y326200D01*
X500500Y388500D02*
X502677D01*
X548777Y434600D01*
X593800D01*
X533728Y368500D02*
X555228Y347000D01*
X492923Y368500D02*
X533728D01*
X528000Y387000D02*
X550000D01*
X575000Y362000D01*
X591000D01*
X520000Y431223D02*
Y447377D01*
X502677Y413900D02*
X520000Y431223D01*
X653500Y326200D02*
Y328500D01*
X629000Y353000D02*
X653500Y328500D01*
X629000Y353000D02*
Y354000D01*
X599000D02*
X611000D01*
X591000Y362000D02*
X599000Y354000D01*
X555228Y347000D02*
X584077D01*
X602700Y328377D01*
Y326200D02*
Y328377D01*
X479000Y382423D02*
X492923Y368500D01*
X500000Y285000D02*
X501722Y286722D01*
X508222D01*
X519500Y298000D01*
Y299000D01*
X526500Y322682D02*
Y326200D01*
Y322682D02*
X535000Y314182D01*
Y300500D02*
Y314182D01*
Y300500D02*
X536500Y299000D01*
X503587Y315913D02*
Y323713D01*
X501100Y326200D02*
X503587Y323713D01*
Y315913D02*
X519500Y300000D01*
Y299000D02*
Y300000D01*
X440000Y254500D02*
X547749Y254500D01*
X401500Y292999D02*
X440000Y254500D01*
X547749Y254500D02*
X571249Y231000D01*
X543048Y206300D02*
X552248Y215500D01*
X486300Y206300D02*
X543048D01*
X543999Y231999D02*
Y233501D01*
X536000Y241500D02*
X543999Y233501D01*
X479251Y274000D02*
X485751Y267500D01*
X451000Y274000D02*
X479251D01*
X485751Y267500D02*
X542364Y267500D01*
X431500Y241500D02*
X512751D01*
X388500Y284500D02*
X431500Y241500D01*
X512751D02*
X512751Y241500D01*
X388500Y284500D02*
Y431500D01*
X517000Y227000D02*
X520000Y224000D01*
X424615Y227000D02*
X517000D01*
X375000Y276615D02*
X424615Y227000D01*
X512751Y241500D02*
X536000D01*
X493023Y193300D02*
X570299D01*
X474500Y174777D02*
X493023Y193300D01*
X523000Y354500D02*
X523500Y355000D01*
X524350Y328350D02*
X526500Y326200D01*
X523000Y329700D02*
X524350Y328350D01*
X225000Y465000D02*
X385000D01*
X210000Y450000D02*
X225000Y465000D01*
X204000Y450000D02*
X210000D01*
X204000Y100000D02*
X205000D01*
X245000Y140000D02*
X420000D01*
X205000Y100000D02*
X245000Y140000D01*
X401500Y292999D02*
Y448500D01*
X385000Y465000D02*
X401500Y448500D01*
X368000Y452000D02*
X388500Y431500D01*
X347000Y452000D02*
X368000D01*
X375000Y276615D02*
Y415000D01*
X360000Y430000D02*
X375000Y415000D01*
X355400Y430000D02*
X360000D01*
X330000Y435000D02*
X347000Y452000D01*
X330000Y430000D02*
Y435000D01*
X420000Y140000D02*
X486300Y206300D01*
X448000Y95000D02*
X449000D01*
X439400Y86400D02*
X448000Y95000D01*
X418100Y86400D02*
X439400D01*
X416900Y85200D02*
X418100Y86400D01*
X479000Y382423D02*
Y419977D01*
X498323Y439300D01*
X500500D01*
X506000Y328000D02*
Y351500D01*
X501100Y326200D02*
X504200D01*
X470000Y61500D02*
X472000D01*
X449500D02*
X470000D01*
X506000Y354500D02*
X506500Y355000D01*
X474500Y139000D02*
Y174777D01*
Y137500D02*
Y139000D01*
X450000Y113000D02*
X474500Y137500D01*
X449000Y113000D02*
X450000D01*
X470000Y61500D02*
Y65000D01*
X490200Y85200D02*
X493100D01*
X470000Y65000D02*
X490200Y85200D01*
X492800Y110900D02*
X493100Y110600D01*
X492800Y110900D02*
Y134700D01*
X492500Y135000D02*
X492800Y134700D01*
X500500Y464700D02*
X502677D01*
X466000Y362301D02*
X496151Y332151D01*
X466000Y436000D02*
X494700Y464700D01*
X500500D01*
X502677D02*
X520000Y447377D01*
X499600Y413000D02*
X500000D01*
X502000Y463200D02*
Y464000D01*
X498000Y414000D02*
Y415041D01*
X466000Y362301D02*
Y436000D01*
X500500Y413900D02*
X502677D01*
D44*
X650000Y250000D02*
D03*
X25000Y475000D02*
D03*
Y25000D02*
D03*
D45*
X120000Y347500D02*
D03*
Y320000D02*
D03*
X355400Y430000D02*
D03*
X330000D02*
D03*
X304600D02*
D03*
X587000Y299000D02*
D03*
X235000Y80000D02*
D03*
X245000Y170000D02*
D03*
X574999Y198000D02*
D03*
X528000Y387000D02*
D03*
X500000Y285000D02*
D03*
X543999Y231999D02*
D03*
X451000Y274000D02*
D03*
X520000Y224000D02*
D03*
D46*
X80000Y450000D02*
D03*
X204000D02*
D03*
X80000Y400000D02*
D03*
X204000D02*
D03*
X80000Y100000D02*
D03*
X204000D02*
D03*
X80000Y50000D02*
D03*
X204000D02*
D03*
D47*
X80000Y300800D02*
D03*
Y275400D02*
D03*
Y250000D02*
D03*
Y224600D02*
D03*
X254600Y119000D02*
D03*
X356200D02*
D03*
X330800D02*
D03*
X305400D02*
D03*
X280000D02*
D03*
D48*
X80000Y199200D02*
D03*
D49*
X381600Y119000D02*
D03*
D50*
X190000Y203000D02*
D03*
Y297000D02*
D03*
D51*
X120000Y225000D02*
D03*
Y250000D02*
D03*
Y275000D02*
D03*
D52*
X677000Y383800D02*
D03*
Y460000D02*
D03*
X600800D02*
D03*
Y434600D02*
D03*
Y409200D02*
D03*
X677000Y40000D02*
D03*
Y116200D02*
D03*
X600800D02*
D03*
Y90800D02*
D03*
Y65400D02*
D03*
X678900Y326200D02*
D03*
X653500D02*
D03*
X628100D02*
D03*
X602700D02*
D03*
X577300D02*
D03*
X551900D02*
D03*
X526500D02*
D03*
X501100D02*
D03*
X678900Y173800D02*
D03*
X653500D02*
D03*
X628100D02*
D03*
X602700D02*
D03*
X577300D02*
D03*
X551900D02*
D03*
X526500D02*
D03*
X501100D02*
D03*
D53*
X600800Y383800D02*
D03*
Y40000D02*
D03*
D54*
X416900Y34400D02*
D03*
Y59800D02*
D03*
Y85200D02*
D03*
Y110600D02*
D03*
X493100D02*
D03*
Y85200D02*
D03*
Y59800D02*
D03*
D55*
Y34400D02*
D03*
D56*
X279200Y430000D02*
D03*
D57*
X424300Y464684D02*
D03*
D58*
Y439300D02*
D03*
Y413900D02*
D03*
Y388500D02*
D03*
X500500D02*
D03*
Y413900D02*
D03*
Y439300D02*
D03*
Y464700D02*
D03*
M02*

View File

@@ -0,0 +1,1099 @@
G04*
G04 #@! TF.GenerationSoftware,Altium Limited,Altium Designer,19.1.9 (167)*
G04*
G04 Layer_Color=65535*
%FSLAX44Y44*%
%MOMM*%
G71*
G01*
G75*
%ADD10C,0.1524*%
%ADD11C,0.2540*%
%ADD12C,0.2000*%
%ADD13C,0.1500*%
%ADD14C,0.1000*%
%ADD15C,0.1800*%
%ADD16C,0.2500*%
%ADD17C,0.1540*%
G36*
X125490Y58490D02*
Y91510D01*
X158510D01*
Y58490D01*
X125490D01*
D02*
G37*
G36*
Y408490D02*
Y441510D01*
X158510D01*
Y408490D01*
X125490D01*
D02*
G37*
D10*
X202960Y475800D02*
G03*
X192800Y485960I-10160J0D01*
G01*
X154617Y375792D02*
G03*
X154929Y474127I-12617J49208D01*
G01*
X192800Y364040D02*
G03*
X202960Y374200I0J10160D01*
G01*
X154477Y386323D02*
G03*
X154477Y463677I-12477J38677D01*
G01*
X129523D02*
G03*
X129523Y386323I12477J-38677D01*
G01*
X91200Y485960D02*
G03*
X81040Y475800I0J-10160D01*
G01*
X129382Y474208D02*
G03*
X129072Y375873I12617J-49208D01*
G01*
X81040Y374200D02*
G03*
X91200Y364040I10160J0D01*
G01*
X81040Y24200D02*
G03*
X91200Y14040I10160J0D01*
G01*
X129382Y124208D02*
G03*
X129072Y25873I12617J-49208D01*
G01*
X91200Y135960D02*
G03*
X81040Y125800I0J-10160D01*
G01*
X129523Y113677D02*
G03*
X129523Y36323I12477J-38677D01*
G01*
X154477D02*
G03*
X154477Y113677I-12477J38677D01*
G01*
X192800Y14040D02*
G03*
X202960Y24200I0J10160D01*
G01*
X154617Y25792D02*
G03*
X154929Y124127I-12617J49208D01*
G01*
X202960Y125800D02*
G03*
X192800Y135960I-10160J0D01*
G01*
D11*
X411900Y477400D02*
G03*
X411900Y477400I-300J0D01*
G01*
X449700D02*
G03*
X475100Y477400I12700J0D01*
G01*
X67300Y186500D02*
X92700D01*
Y313500D01*
X67300Y186500D02*
Y313500D01*
X92700D01*
X407000Y373000D02*
Y482000D01*
Y373000D02*
X516000D01*
X407000Y482000D02*
X516000D01*
Y373000D02*
Y482000D01*
X332153Y304294D02*
X321996Y299216D01*
X332153Y294137D01*
X321996Y281441D02*
Y286520D01*
X324536Y289059D01*
X329614D01*
X332153Y286520D01*
Y281441D01*
X329614Y278902D01*
X327075D01*
Y289059D01*
X332153Y273824D02*
X321996D01*
X327075D01*
X329614Y271285D01*
X332153Y268745D01*
Y266206D01*
X321996Y258589D02*
X324536D01*
Y256050D01*
X321996D01*
Y258589D01*
Y230658D02*
Y225579D01*
Y228119D01*
X337231D01*
X334692Y230658D01*
X321996Y217962D02*
X324536D01*
Y215423D01*
X321996D01*
Y217962D01*
X334692Y205266D02*
X337231Y202727D01*
Y197649D01*
X334692Y195109D01*
X324536D01*
X321996Y197649D01*
Y202727D01*
X324536Y205266D01*
X334692D01*
X312853Y337303D02*
X297618D01*
Y344921D01*
X300158Y347460D01*
X305236D01*
X307775Y344921D01*
Y337303D01*
X297618Y332225D02*
Y327147D01*
Y329686D01*
X307775D01*
Y332225D01*
X292540Y314451D02*
Y311912D01*
X295079Y309372D01*
X307775D01*
Y316990D01*
X305236Y319529D01*
X300158D01*
X297618Y316990D01*
Y309372D01*
Y304294D02*
Y299216D01*
Y301755D01*
X307775D01*
Y304294D01*
X310314Y289059D02*
X307775D01*
Y291598D01*
Y286520D01*
Y289059D01*
X300158D01*
X297618Y286520D01*
X307775Y276363D02*
Y271285D01*
X305236Y268745D01*
X297618D01*
Y276363D01*
X300158Y278902D01*
X302697Y276363D01*
Y268745D01*
X297618Y263667D02*
Y258589D01*
Y261128D01*
X312853D01*
Y263667D01*
X297618Y250971D02*
X307775Y240814D01*
Y233197D02*
Y228119D01*
X305236Y225579D01*
X297618D01*
Y233197D01*
X300158Y235736D01*
X302697Y233197D01*
Y225579D01*
X297618Y220501D02*
X307775D01*
Y212884D01*
X305236Y210344D01*
X297618D01*
X307775Y202727D02*
Y197649D01*
X305236Y195109D01*
X297618D01*
Y202727D01*
X300158Y205266D01*
X302697Y202727D01*
Y195109D01*
X297618Y190031D02*
Y184953D01*
Y187492D01*
X312853D01*
Y190031D01*
X297618Y174796D02*
Y169718D01*
X300158Y167178D01*
X305236D01*
X307775Y169718D01*
Y174796D01*
X305236Y177335D01*
X300158D01*
X297618Y174796D01*
X292540Y157022D02*
Y154482D01*
X295079Y151943D01*
X307775D01*
Y159561D01*
X305236Y162100D01*
X300158D01*
X297618Y159561D01*
Y151943D01*
X257657Y105196D02*
X255117Y107735D01*
X250039D01*
X247500Y105196D01*
Y102657D01*
X250039Y100118D01*
X255117D01*
X257657Y97578D01*
Y95039D01*
X255117Y92500D01*
X250039D01*
X247500Y95039D01*
X262735Y107735D02*
Y92500D01*
X267813Y97578D01*
X272892Y92500D01*
Y107735D01*
X277970Y89961D02*
X288127D01*
X293205Y92500D02*
Y107735D01*
X300823D01*
X303362Y105196D01*
Y100118D01*
X300823Y97578D01*
X293205D01*
X316058Y107735D02*
X310979D01*
X308440Y105196D01*
Y95039D01*
X310979Y92500D01*
X316058D01*
X318597Y95039D01*
Y105196D01*
X316058Y107735D01*
X323675D02*
Y92500D01*
X328754Y97578D01*
X333832Y92500D01*
Y107735D01*
X349067D02*
X338910D01*
Y92500D01*
X349067D01*
X338910Y100118D02*
X343989D01*
X354146Y92500D02*
Y107735D01*
X361763D01*
X364302Y105196D01*
Y100118D01*
X361763Y97578D01*
X354146D01*
X359224D02*
X364302Y92500D01*
X45000Y260000D02*
X60235D01*
Y252383D01*
X57696Y249843D01*
X52618D01*
X50078Y252383D01*
Y260000D01*
X45000Y244765D02*
Y239687D01*
Y242226D01*
X60235D01*
X57696Y244765D01*
X560157Y303196D02*
X557617Y305735D01*
X552539D01*
X550000Y303196D01*
Y293039D01*
X552539Y290500D01*
X557617D01*
X560157Y293039D01*
X575392Y290500D02*
X565235D01*
X575392Y300657D01*
Y303196D01*
X572853Y305735D01*
X567774D01*
X565235Y303196D01*
X546157Y357196D02*
X543618Y359735D01*
X538539D01*
X536000Y357196D01*
Y347039D01*
X538539Y344500D01*
X543618D01*
X546157Y347039D01*
X551235Y357196D02*
X553774Y359735D01*
X558853D01*
X561392Y357196D01*
Y354657D01*
X558853Y352118D01*
X556313D01*
X558853D01*
X561392Y349578D01*
Y347039D01*
X558853Y344500D01*
X553774D01*
X551235Y347039D01*
X148000Y140000D02*
Y155235D01*
X155618D01*
X158157Y152696D01*
Y147618D01*
X155618Y145078D01*
X148000D01*
X153078D02*
X158157Y140000D01*
X173392Y155235D02*
X168313Y152696D01*
X163235Y147618D01*
Y142539D01*
X165774Y140000D01*
X170853D01*
X173392Y142539D01*
Y145078D01*
X170853Y147618D01*
X163235D01*
X195000Y140000D02*
Y155235D01*
X202617D01*
X205157Y152696D01*
Y147618D01*
X202617Y145078D01*
X195000D01*
X200078D02*
X205157Y140000D01*
X210235Y155235D02*
X220392D01*
Y152696D01*
X210235Y142539D01*
Y140000D01*
X641000Y347000D02*
Y362235D01*
X648617D01*
X651157Y359696D01*
Y354618D01*
X648617Y352078D01*
X641000D01*
X646078D02*
X651157Y347000D01*
X656235D02*
X661313D01*
X658774D01*
Y362235D01*
X656235Y359696D01*
X668931Y347000D02*
X674009D01*
X671470D01*
Y362235D01*
X668931Y359696D01*
X150000Y295000D02*
X165235D01*
Y287383D01*
X162696Y284843D01*
X157618D01*
X155078Y287383D01*
Y295000D01*
X165235Y272147D02*
Y277226D01*
X162696Y279765D01*
X152539D01*
X150000Y277226D01*
Y272147D01*
X152539Y269608D01*
X162696D01*
X165235Y272147D01*
Y264530D02*
Y254373D01*
Y259452D01*
X150000D01*
X157618Y249295D02*
Y239138D01*
X165235Y234060D02*
X150000D01*
X157618D01*
Y223903D01*
X165235D01*
X150000D01*
X162696Y208668D02*
X165235Y211207D01*
Y216285D01*
X162696Y218825D01*
X152539D01*
X150000Y216285D01*
Y211207D01*
X152539Y208668D01*
X157618D01*
Y213746D01*
X614235Y274843D02*
Y285000D01*
X599000D01*
Y274843D01*
X606618Y285000D02*
Y279922D01*
X611696Y259608D02*
X614235Y262147D01*
Y267226D01*
X611696Y269765D01*
X609157D01*
X606618Y267226D01*
Y262147D01*
X604078Y259608D01*
X601539D01*
X599000Y262147D01*
Y267226D01*
X601539Y269765D01*
X599000Y254530D02*
X614235D01*
Y246912D01*
X611696Y244373D01*
X606618D01*
X604078Y246912D01*
Y254530D01*
X611696Y239295D02*
X614235Y236756D01*
Y231677D01*
X611696Y229138D01*
X609157D01*
X606618Y231677D01*
Y234216D01*
Y231677D01*
X604078Y229138D01*
X601539D01*
X599000Y231677D01*
Y236756D01*
X601539Y239295D01*
X599000Y213903D02*
Y224060D01*
X609157Y213903D01*
X611696D01*
X614235Y216442D01*
Y221521D01*
X611696Y224060D01*
X526235Y105382D02*
Y110461D01*
X523696Y113000D01*
X513539D01*
X511000Y110461D01*
Y105382D01*
X513539Y102843D01*
X523696D01*
X526235Y105382D01*
X511000Y97765D02*
X526235D01*
Y90147D01*
X523696Y87608D01*
X518618D01*
X516078Y90147D01*
Y97765D01*
X511000Y82530D02*
X521157D01*
X526235Y77452D01*
X521157Y72373D01*
X511000D01*
X518618D01*
Y82530D01*
X523696Y67295D02*
X526235Y64756D01*
Y59677D01*
X523696Y57138D01*
X521157D01*
X518618Y59677D01*
Y62216D01*
Y59677D01*
X516078Y57138D01*
X513539D01*
X511000Y59677D01*
Y64756D01*
X513539Y67295D01*
X523696Y52060D02*
X526235Y49521D01*
Y44442D01*
X523696Y41903D01*
X521157D01*
X518618Y44442D01*
X516078Y41903D01*
X513539D01*
X511000Y44442D01*
Y49521D01*
X513539Y52060D01*
X516078D01*
X518618Y49521D01*
X521157Y52060D01*
X523696D01*
X518618Y49521D02*
Y44442D01*
X523696Y36825D02*
X526235Y34286D01*
Y29207D01*
X523696Y26668D01*
X513539D01*
X511000Y29207D01*
Y34286D01*
X513539Y36825D01*
X523696D01*
X435000Y132000D02*
Y147235D01*
X442617D01*
X445157Y144696D01*
Y139618D01*
X442617Y137078D01*
X435000D01*
X440078D02*
X445157Y132000D01*
X460392Y147235D02*
X450235D01*
Y139618D01*
X455313Y142157D01*
X457853D01*
X460392Y139618D01*
Y134539D01*
X457853Y132000D01*
X452774D01*
X450235Y134539D01*
X562235Y456183D02*
Y461261D01*
X559696Y463800D01*
X549539D01*
X547000Y461261D01*
Y456183D01*
X549539Y453643D01*
X559696D01*
X562235Y456183D01*
X547000Y448565D02*
X562235D01*
Y440947D01*
X559696Y438408D01*
X554618D01*
X552078Y440947D01*
Y448565D01*
X562235Y433330D02*
Y423173D01*
Y428251D01*
X547000D01*
X544461Y418095D02*
Y407938D01*
X562235Y402860D02*
Y392703D01*
Y397781D01*
X547000D01*
X562235Y387625D02*
X547000Y377468D01*
X562235D02*
X547000Y387625D01*
X470235Y455000D02*
Y444843D01*
X467696D01*
X457539Y455000D01*
X455000D01*
X470235Y429608D02*
Y439765D01*
X462617D01*
X465157Y434687D01*
Y432147D01*
X462617Y429608D01*
X457539D01*
X455000Y432147D01*
Y437226D01*
X457539Y439765D01*
X455000Y416912D02*
X470235D01*
X462617Y424530D01*
Y414373D01*
X470235Y399138D02*
Y409295D01*
X462617D01*
X465157Y404216D01*
Y401677D01*
X462617Y399138D01*
X457539D01*
X455000Y401677D01*
Y406756D01*
X457539Y409295D01*
X455000Y394060D02*
Y388981D01*
Y391521D01*
X470235D01*
X467696Y394060D01*
X562235Y112382D02*
Y117461D01*
X559696Y120000D01*
X549539D01*
X547000Y117461D01*
Y112382D01*
X549539Y109843D01*
X559696D01*
X562235Y112382D01*
X547000Y104765D02*
X562235D01*
Y97147D01*
X559696Y94608D01*
X554618D01*
X552078Y97147D01*
Y104765D01*
X562235Y89530D02*
Y79373D01*
Y84452D01*
X547000D01*
X544461Y74295D02*
Y64138D01*
X547000Y59060D02*
X562235D01*
Y51442D01*
X559696Y48903D01*
X554618D01*
X552078Y51442D01*
Y59060D01*
Y53981D02*
X547000Y48903D01*
X562235Y43825D02*
X547000Y33668D01*
X562235D02*
X547000Y43825D01*
X297617Y405235D02*
X292539D01*
X290000Y402696D01*
Y392539D01*
X292539Y390000D01*
X297617D01*
X300157Y392539D01*
Y402696D01*
X297617Y405235D01*
X305235D02*
Y390000D01*
X315392D01*
X330627Y405235D02*
X320470D01*
Y390000D01*
X330627D01*
X320470Y397617D02*
X325549D01*
X335705Y405235D02*
Y390000D01*
X343323D01*
X345862Y392539D01*
Y402696D01*
X343323Y405235D01*
X335705D01*
X57696Y162343D02*
X60235Y164883D01*
Y169961D01*
X57696Y172500D01*
X55157D01*
X52618Y169961D01*
Y164883D01*
X50078Y162343D01*
X47539D01*
X45000Y164883D01*
Y169961D01*
X47539Y172500D01*
X60235Y157265D02*
X45000D01*
X50078Y152187D01*
X45000Y147108D01*
X60235D01*
X42461Y142030D02*
Y131873D01*
X57696Y116638D02*
X60235Y119177D01*
Y124256D01*
X57696Y126795D01*
X55157D01*
X52618Y124256D01*
Y119177D01*
X50078Y116638D01*
X47539D01*
X45000Y119177D01*
Y124256D01*
X47539Y126795D01*
X60235Y111560D02*
Y101403D01*
Y106481D01*
X45000D01*
Y96325D02*
X55157D01*
X60235Y91246D01*
X55157Y86168D01*
X45000D01*
X52618D01*
Y96325D01*
X45000Y81090D02*
X60235D01*
Y73472D01*
X57696Y70933D01*
X52618D01*
X50078Y73472D01*
Y81090D01*
Y76011D02*
X45000Y70933D01*
X60235Y65855D02*
Y55698D01*
Y60776D01*
X45000D01*
X62696Y434843D02*
X65235Y437383D01*
Y442461D01*
X62696Y445000D01*
X60157D01*
X57618Y442461D01*
Y437383D01*
X55078Y434843D01*
X52539D01*
X50000Y437383D01*
Y442461D01*
X52539Y445000D01*
X65235Y429765D02*
X50000D01*
X55078Y424687D01*
X50000Y419608D01*
X65235D01*
X47461Y414530D02*
Y404373D01*
X50000Y399295D02*
X65235D01*
X60157Y394216D01*
X65235Y389138D01*
X50000D01*
X65235Y376442D02*
Y381521D01*
X62696Y384060D01*
X52539D01*
X50000Y381521D01*
Y376442D01*
X52539Y373903D01*
X62696D01*
X65235Y376442D01*
Y368825D02*
X50000D01*
Y361207D01*
X52539Y358668D01*
X62696D01*
X65235Y361207D01*
Y368825D01*
Y343433D02*
Y353590D01*
X50000D01*
Y343433D01*
X57618Y353590D02*
Y348511D01*
D12*
X466000Y20500D02*
G03*
X444000Y20500I-11000J0D01*
G01*
X310000Y18000D02*
Y72000D01*
X283000Y45000D02*
X337000D01*
X365000Y5000D02*
Y85000D01*
X255000Y5000D02*
X365000D01*
X255000D02*
Y85000D01*
X365000D01*
X125490Y408490D02*
X158510D01*
X125490Y441510D02*
X158510D01*
Y408490D02*
Y441510D01*
X125490Y408490D02*
Y441510D01*
X126760Y364040D02*
X192800D01*
X161050Y405950D02*
Y444050D01*
X126760Y364040D02*
Y370390D01*
X91200Y485960D02*
X192800D01*
X122950Y405950D02*
X161050D01*
X122950Y444050D02*
X161050D01*
X122950Y405950D02*
Y444050D01*
X106440Y370390D02*
X126760D01*
X106440Y364040D02*
Y370390D01*
X91200Y364040D02*
X106440D01*
X209000Y164000D02*
Y176000D01*
X161500Y164000D02*
Y176000D01*
X403000Y20500D02*
Y124500D01*
Y20500D02*
X507000D01*
X403000Y124500D02*
X507000D01*
Y20500D02*
Y124500D01*
X135000Y201000D02*
Y299000D01*
X244999Y201000D02*
Y299000D01*
X91200Y14040D02*
X106440D01*
Y20390D01*
X126760D01*
X122950Y55950D02*
Y94050D01*
X161050D01*
X122950Y55950D02*
X161050D01*
X91200Y135960D02*
X192800D01*
X126760Y14040D02*
Y20390D01*
X161050Y55950D02*
Y94050D01*
X126760Y14040D02*
X192800D01*
X125490Y58490D02*
Y91510D01*
X158510Y58490D02*
Y91510D01*
X125490D02*
X158510D01*
X125490Y58490D02*
X158510D01*
X443000Y104000D02*
X455000D01*
X483500Y133000D02*
Y145000D01*
X620000Y348000D02*
Y360000D01*
D13*
X130800Y285000D02*
G03*
X130800Y285000I-800J0D01*
G01*
X256200Y140000D02*
Y444000D01*
Y140000D02*
X378200D01*
X256200Y444000D02*
X378200D01*
Y140000D02*
Y444000D01*
X141497Y354635D02*
X131500D01*
Y349636D01*
X133166Y347970D01*
X139831D01*
X141497Y349636D01*
Y354635D01*
X136498Y344638D02*
Y337974D01*
X141497Y327135D02*
X131500D01*
Y322136D01*
X133166Y320470D01*
X139831D01*
X141497Y322136D01*
Y327135D01*
X136498Y317138D02*
Y310473D01*
X139831Y313806D02*
X133166D01*
D14*
X191474Y160348D02*
X205190D01*
X212810Y160348D02*
X226526Y160348D01*
X191474D02*
Y179652D01*
X205190D01*
X226526Y160348D02*
Y179652D01*
X212810Y179652D02*
X226526Y179652D01*
X165310Y160348D02*
X179026Y160348D01*
Y179652D01*
X165310Y179652D02*
X179026Y179652D01*
X143974Y160348D02*
X157690D01*
X143974Y179652D02*
X157690D01*
X143974Y160348D02*
Y179652D01*
X545526Y301540D02*
Y309160D01*
Y288840D02*
Y296460D01*
X510474Y309160D02*
X545526D01*
X510474Y301540D02*
Y309160D01*
Y288840D02*
X545526D01*
X510474D02*
Y296460D01*
X532526Y354540D02*
Y362160D01*
Y341840D02*
Y349460D01*
X497474Y362160D02*
X532526D01*
X497474Y354540D02*
Y362160D01*
Y341840D02*
X532526D01*
X497474D02*
Y349460D01*
X459660Y35474D02*
Y70526D01*
X452040Y35474D02*
X459660D01*
X452040Y70526D02*
X459660D01*
X439340Y35474D02*
X446960D01*
X439340Y70526D02*
X446960D01*
X439340Y35474D02*
Y70526D01*
X480160Y35474D02*
Y70526D01*
X472540Y35474D02*
X480160D01*
X472540Y70526D02*
X480160D01*
X459840Y35474D02*
X467460D01*
X459840Y70526D02*
X467460D01*
X459840Y35474D02*
Y70526D01*
X458652Y121526D02*
X458652Y107810D01*
X439348Y121526D02*
X458652D01*
X439348Y107810D02*
X439348Y121526D01*
X458652Y86474D02*
Y100190D01*
X439348Y86474D02*
X458652D01*
X439348D02*
Y100190D01*
X465974Y148652D02*
X479690Y148652D01*
X465974Y129348D02*
Y148652D01*
Y129348D02*
X479690Y129348D01*
X487310Y148652D02*
X501026D01*
Y129348D02*
Y148652D01*
X487310Y129348D02*
X501026D01*
X623810Y344348D02*
X637526Y344348D01*
Y363652D01*
X623810Y363652D02*
X637526Y363652D01*
X602474Y344348D02*
X616190D01*
X602474D02*
Y363652D01*
X616190D01*
D15*
X485000Y158000D02*
Y190000D01*
Y158000D02*
X694000D01*
X485000Y190000D02*
X694000D01*
X484000Y310000D02*
Y342000D01*
Y310000D02*
X694000D01*
X484000Y342000D02*
X694000D01*
Y158000D02*
Y190000D01*
Y310000D02*
Y342000D01*
D16*
X572250Y21000D02*
Y134000D01*
Y21000D02*
X696000D01*
X572250Y134000D02*
X696000D01*
Y21000D02*
Y134000D01*
X572250Y366000D02*
Y477000D01*
Y366000D02*
X696000D01*
X572250Y477000D02*
X696000D01*
Y366000D02*
Y477000D01*
D17*
X477000Y91000D02*
X461765D01*
Y98617D01*
X464304Y101157D01*
X469383D01*
X471922Y98617D01*
Y91000D01*
Y96078D02*
X477000Y101157D01*
Y113853D02*
X461765D01*
X469383Y106235D01*
Y116392D01*
X440664Y82331D02*
X438998Y83997D01*
X435666D01*
X434000Y82331D01*
Y75666D01*
X435666Y74000D01*
X438998D01*
X440664Y75666D01*
X443997Y74000D02*
X447329D01*
X445663D01*
Y83997D01*
X443997Y82331D01*
X452327D02*
X453994Y83997D01*
X457326D01*
X458992Y82331D01*
Y75666D01*
X457326Y74000D01*
X453994D01*
X452327Y75666D01*
Y82331D01*
M02*

View File

@@ -0,0 +1,60 @@
G04*
G04 #@! TF.GenerationSoftware,Altium Limited,Altium Designer,19.1.9 (167)*
G04*
G04 Layer_Color=8421504*
%FSLAX44Y44*%
%MOMM*%
G71*
G01*
G75*
%ADD18R,2.5000X2.2000*%
%ADD19R,1.2000X1.4000*%
%ADD20R,1.3000X1.5000*%
%ADD21R,1.5000X1.3000*%
%ADD22R,1.4000X1.2000*%
D18*
X382500Y45000D02*
D03*
X237500D02*
D03*
D19*
X200000Y170000D02*
D03*
X218000D02*
D03*
X170500D02*
D03*
X152500D02*
D03*
X474500Y139000D02*
D03*
X492500D02*
D03*
X629000Y354000D02*
D03*
X611000D02*
D03*
D20*
X536500Y299000D02*
D03*
X519500D02*
D03*
X523500Y352000D02*
D03*
X506500D02*
D03*
D21*
X449500Y44500D02*
D03*
Y61500D02*
D03*
X470000Y44500D02*
D03*
Y61500D02*
D03*
D22*
X449000Y113000D02*
D03*
Y95000D02*
D03*
M02*

View File

@@ -0,0 +1,262 @@
G04*
G04 #@! TF.GenerationSoftware,Altium Limited,Altium Designer,19.1.9 (167)*
G04*
G04 Layer_Color=8388736*
%FSLAX44Y44*%
%MOMM*%
G71*
G01*
G75*
%ADD23R,2.6500X2.3500*%
%ADD24R,1.3500X1.5500*%
%ADD25R,1.4500X1.6500*%
%ADD26R,1.6500X1.4500*%
%ADD27R,1.5500X1.3500*%
%ADD28C,1.6740*%
%ADD29C,1.8500*%
%ADD30O,2.6000X2.0000*%
%ADD31C,1.6500*%
%ADD32R,1.6500X1.6500*%
%ADD33R,1.6500X1.6500*%
%ADD34C,3.1500*%
%ADD35C,2.1500*%
%ADD36C,1.9500*%
%ADD37R,1.9500X1.9500*%
%ADD38C,1.7500*%
%ADD39R,1.7500X1.7500*%
%ADD40R,1.8500X1.8500*%
%ADD41R,1.7500X1.7500*%
%ADD42C,1.7500*%
D23*
X382500Y45000D02*
D03*
X237500D02*
D03*
D24*
X200000Y170000D02*
D03*
X218000D02*
D03*
X170500D02*
D03*
X152500D02*
D03*
X474500Y139000D02*
D03*
X492500D02*
D03*
X629000Y354000D02*
D03*
X611000D02*
D03*
D25*
X536500Y299000D02*
D03*
X519500D02*
D03*
X523500Y352000D02*
D03*
X506500D02*
D03*
D26*
X449500Y44500D02*
D03*
Y61500D02*
D03*
X470000Y44500D02*
D03*
Y61500D02*
D03*
D27*
X449000Y113000D02*
D03*
Y95000D02*
D03*
D28*
X650000Y250000D02*
D03*
X25000Y475000D02*
D03*
Y25000D02*
D03*
D29*
X120000Y347500D02*
D03*
Y320000D02*
D03*
X355400Y430000D02*
D03*
X330000D02*
D03*
X304600D02*
D03*
X587000Y299000D02*
D03*
X235000Y80000D02*
D03*
X245000Y170000D02*
D03*
X574999Y198000D02*
D03*
X528000Y387000D02*
D03*
X500000Y285000D02*
D03*
X543999Y231999D02*
D03*
X451000Y274000D02*
D03*
X520000Y224000D02*
D03*
D30*
X80000Y450000D02*
D03*
X204000D02*
D03*
X80000Y400000D02*
D03*
X204000D02*
D03*
X80000Y100000D02*
D03*
X204000D02*
D03*
X80000Y50000D02*
D03*
X204000D02*
D03*
D31*
X80000Y300800D02*
D03*
Y275400D02*
D03*
Y250000D02*
D03*
Y224600D02*
D03*
X254600Y119000D02*
D03*
X356200D02*
D03*
X330800D02*
D03*
X305400D02*
D03*
X280000D02*
D03*
D32*
X80000Y199200D02*
D03*
D33*
X381600Y119000D02*
D03*
D34*
X190000Y203000D02*
D03*
Y297000D02*
D03*
D35*
X120000Y225000D02*
D03*
Y250000D02*
D03*
Y275000D02*
D03*
D36*
X677000Y383800D02*
D03*
Y460000D02*
D03*
X600800D02*
D03*
Y434600D02*
D03*
Y409200D02*
D03*
X677000Y40000D02*
D03*
Y116200D02*
D03*
X600800D02*
D03*
Y90800D02*
D03*
Y65400D02*
D03*
X678900Y326200D02*
D03*
X653500D02*
D03*
X628100D02*
D03*
X602700D02*
D03*
X577300D02*
D03*
X551900D02*
D03*
X526500D02*
D03*
X501100D02*
D03*
X678900Y173800D02*
D03*
X653500D02*
D03*
X628100D02*
D03*
X602700D02*
D03*
X577300D02*
D03*
X551900D02*
D03*
X526500D02*
D03*
X501100D02*
D03*
D37*
X600800Y383800D02*
D03*
Y40000D02*
D03*
D38*
X416900Y34400D02*
D03*
Y59800D02*
D03*
Y85200D02*
D03*
Y110600D02*
D03*
X493100D02*
D03*
Y85200D02*
D03*
Y59800D02*
D03*
D39*
Y34400D02*
D03*
D40*
X279200Y430000D02*
D03*
D41*
X424300Y464684D02*
D03*
D42*
Y439300D02*
D03*
Y413900D02*
D03*
Y388500D02*
D03*
X500500D02*
D03*
Y413900D02*
D03*
Y439300D02*
D03*
Y464700D02*
D03*
M02*

View File

@@ -0,0 +1,108 @@
M48
;Layer_Color=9474304
;FILE_FORMAT=4:4
METRIC,LZ
;TYPE=PLATED
T1F00S00C0.8000
T2F00S00C0.9000
T3F00S00C1.0000
T4F00S00C1.1000
T5F00S00C1.2000
T6F00S00C2.2000
;TYPE=NON_PLATED
T7F00S00C3.1000
%
T01
X004169Y000344
Y000598
X004931
Y000344
Y000852
Y001106
X004169
Y000852
T02
X00574999Y00198
X00587Y00299
X005Y00285
X00451Y00274
X0052Y00224
X00543999Y00231999
X00528Y00387
X003816Y00119
X003562
X003308
X003054
X0028
X002546
X00235Y0008
X00245Y0017
X0008Y001992
Y002246
Y0025
Y002754
Y003008
X0012Y0032
Y003475
T03
X005773Y001738
X006027
X006281
X006535
X006789
Y003262
X006535
X006281
X006027
X005773
X005519
X005265
X005011
X005005Y003885
Y004139
Y004393
X004243
Y004139
Y003885
X003554Y0043
X0033
X003046
X002792
X005011Y001738
X005265
X005519
X0012Y00225
Y0025
Y00275
X004243Y00464684
X005005Y004647
T04
X006008Y0004
X00677
Y001162
X006008
Y000908
Y000654
Y003838
Y004092
Y004346
X00677Y003838
Y0046
X006008
T05
X0008Y0005
X00204
Y001
Y004
Y0045
X0008
Y004
Y001
T06
X0019Y00203
Y00297
T07
X00025Y00025
X0065Y0025
X00025Y00475
M30

Binary file not shown.

BIN
PCB/Libs/1208YD.PcbLib Normal file

Binary file not shown.

BIN
PCB/Libs/1208YD.SchLib Normal file

Binary file not shown.

BIN
PCB/Libs/BPW34.PcbLib Normal file

Binary file not shown.

View File

@@ -0,0 +1,2544 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('Open CASCADE Model'),'2;1');
FILE_NAME('Open CASCADE Shape Model','2026-08-09T10:52:04',('Author'),(
'Open CASCADE'),'Open CASCADE STEP processor 7.9','Open CASCADE 7.9'
,'Unknown');
FILE_SCHEMA(('AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }'));
ENDSEC;
DATA;
#1 = APPLICATION_PROTOCOL_DEFINITION('international standard',
'automotive_design',2000,#2);
#2 = APPLICATION_CONTEXT(
'core data for automotive mechanical design processes');
#3 = SHAPE_DEFINITION_REPRESENTATION(#4,#10);
#4 = PRODUCT_DEFINITION_SHAPE('','',#5);
#5 = PRODUCT_DEFINITION('design','',#6,#9);
#6 = PRODUCT_DEFINITION_FORMATION('','',#7);
#7 = PRODUCT('BPW34_VERTICAL_P5.08mm','BPW34_VERTICAL_P5.08mm','',(#8));
#8 = PRODUCT_CONTEXT('',#2,'mechanical');
#9 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design');
#10 = SHAPE_REPRESENTATION('',(#11,#15,#19,#23,#27,#31),#35);
#11 = AXIS2_PLACEMENT_3D('',#12,#13,#14);
#12 = CARTESIAN_POINT('',(0.,0.,0.));
#13 = DIRECTION('',(0.,0.,1.));
#14 = DIRECTION('',(1.,0.,-0.));
#15 = AXIS2_PLACEMENT_3D('',#16,#17,#18);
#16 = CARTESIAN_POINT('',(0.,0.,0.));
#17 = DIRECTION('',(0.,0.,1.));
#18 = DIRECTION('',(1.,0.,-0.));
#19 = AXIS2_PLACEMENT_3D('',#20,#21,#22);
#20 = CARTESIAN_POINT('',(0.,0.,0.));
#21 = DIRECTION('',(0.,0.,1.));
#22 = DIRECTION('',(1.,0.,-0.));
#23 = AXIS2_PLACEMENT_3D('',#24,#25,#26);
#24 = CARTESIAN_POINT('',(0.,0.,0.));
#25 = DIRECTION('',(0.,0.,1.));
#26 = DIRECTION('',(1.,0.,-0.));
#27 = AXIS2_PLACEMENT_3D('',#28,#29,#30);
#28 = CARTESIAN_POINT('',(0.,0.,0.));
#29 = DIRECTION('',(0.,0.,1.));
#30 = DIRECTION('',(1.,0.,-0.));
#31 = AXIS2_PLACEMENT_3D('',#32,#33,#34);
#32 = CARTESIAN_POINT('',(0.,0.,0.));
#33 = DIRECTION('',(0.,0.,1.));
#34 = DIRECTION('',(1.,0.,-0.));
#35 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3)
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#39)) GLOBAL_UNIT_ASSIGNED_CONTEXT(
(#36,#37,#38)) REPRESENTATION_CONTEXT('Context #1',
'3D Context with UNIT and UNCERTAINTY') );
#36 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) );
#37 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) );
#38 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() );
#39 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-07),#36,
'distance_accuracy_value','confusion accuracy');
#40 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#7));
#41 = SHAPE_DEFINITION_REPRESENTATION(#42,#48);
#42 = PRODUCT_DEFINITION_SHAPE('','',#43);
#43 = PRODUCT_DEFINITION('design','',#44,#47);
#44 = PRODUCT_DEFINITION_FORMATION('','',#45);
#45 = PRODUCT('364e6857-93c7-11f1-9269-04bad62a3558',
'364e6857-93c7-11f1-9269-04bad62a3558','',(#46));
#46 = PRODUCT_CONTEXT('',#2,'mechanical');
#47 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design');
#48 = ADVANCED_BREP_SHAPE_REPRESENTATION('',(#11,#49),#379);
#49 = MANIFOLD_SOLID_BREP('',#50);
#50 = CLOSED_SHELL('',(#51,#171,#271,#318,#365,#372));
#51 = ADVANCED_FACE('',(#52),#66,.F.);
#52 = FACE_BOUND('',#53,.F.);
#53 = EDGE_LOOP('',(#54,#89,#117,#145));
#54 = ORIENTED_EDGE('',*,*,#55,.F.);
#55 = EDGE_CURVE('',#56,#58,#60,.T.);
#56 = VERTEX_POINT('',#57);
#57 = CARTESIAN_POINT('',(-2.79,-0.19,-2.6));
#58 = VERTEX_POINT('',#59);
#59 = CARTESIAN_POINT('',(-2.79,-0.19,2.7));
#60 = SURFACE_CURVE('',#61,(#65,#77),.PCURVE_S1.);
#61 = LINE('',#62,#63);
#62 = CARTESIAN_POINT('',(-2.79,-0.19,-2.6));
#63 = VECTOR('',#64,1.);
#64 = DIRECTION('',(0.,0.,1.));
#65 = PCURVE('',#66,#71);
#66 = PLANE('',#67);
#67 = AXIS2_PLACEMENT_3D('',#68,#69,#70);
#68 = CARTESIAN_POINT('',(-2.79,-0.19,-2.6));
#69 = DIRECTION('',(1.,0.,0.));
#70 = DIRECTION('',(0.,0.,1.));
#71 = DEFINITIONAL_REPRESENTATION('',(#72),#76);
#72 = LINE('',#73,#74);
#73 = CARTESIAN_POINT('',(0.,0.));
#74 = VECTOR('',#75,1.);
#75 = DIRECTION('',(1.,0.));
#76 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#77 = PCURVE('',#78,#83);
#78 = PLANE('',#79);
#79 = AXIS2_PLACEMENT_3D('',#80,#81,#82);
#80 = CARTESIAN_POINT('',(-2.79,-0.19,-2.6));
#81 = DIRECTION('',(0.,1.,0.));
#82 = DIRECTION('',(0.,0.,1.));
#83 = DEFINITIONAL_REPRESENTATION('',(#84),#88);
#84 = LINE('',#85,#86);
#85 = CARTESIAN_POINT('',(0.,0.));
#86 = VECTOR('',#87,1.);
#87 = DIRECTION('',(1.,0.));
#88 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#89 = ORIENTED_EDGE('',*,*,#90,.T.);
#90 = EDGE_CURVE('',#56,#91,#93,.T.);
#91 = VERTEX_POINT('',#92);
#92 = CARTESIAN_POINT('',(-2.79,0.19,-2.6));
#93 = SURFACE_CURVE('',#94,(#98,#105),.PCURVE_S1.);
#94 = LINE('',#95,#96);
#95 = CARTESIAN_POINT('',(-2.79,-0.19,-2.6));
#96 = VECTOR('',#97,1.);
#97 = DIRECTION('',(0.,1.,0.));
#98 = PCURVE('',#66,#99);
#99 = DEFINITIONAL_REPRESENTATION('',(#100),#104);
#100 = LINE('',#101,#102);
#101 = CARTESIAN_POINT('',(0.,0.));
#102 = VECTOR('',#103,1.);
#103 = DIRECTION('',(0.,-1.));
#104 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#105 = PCURVE('',#106,#111);
#106 = PLANE('',#107);
#107 = AXIS2_PLACEMENT_3D('',#108,#109,#110);
#108 = CARTESIAN_POINT('',(-2.79,-0.19,-2.6));
#109 = DIRECTION('',(0.,0.,1.));
#110 = DIRECTION('',(1.,0.,0.));
#111 = DEFINITIONAL_REPRESENTATION('',(#112),#116);
#112 = LINE('',#113,#114);
#113 = CARTESIAN_POINT('',(0.,0.));
#114 = VECTOR('',#115,1.);
#115 = DIRECTION('',(0.,1.));
#116 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#117 = ORIENTED_EDGE('',*,*,#118,.T.);
#118 = EDGE_CURVE('',#91,#119,#121,.T.);
#119 = VERTEX_POINT('',#120);
#120 = CARTESIAN_POINT('',(-2.79,0.19,2.7));
#121 = SURFACE_CURVE('',#122,(#126,#133),.PCURVE_S1.);
#122 = LINE('',#123,#124);
#123 = CARTESIAN_POINT('',(-2.79,0.19,-2.6));
#124 = VECTOR('',#125,1.);
#125 = DIRECTION('',(0.,0.,1.));
#126 = PCURVE('',#66,#127);
#127 = DEFINITIONAL_REPRESENTATION('',(#128),#132);
#128 = LINE('',#129,#130);
#129 = CARTESIAN_POINT('',(0.,-0.38));
#130 = VECTOR('',#131,1.);
#131 = DIRECTION('',(1.,0.));
#132 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#133 = PCURVE('',#134,#139);
#134 = PLANE('',#135);
#135 = AXIS2_PLACEMENT_3D('',#136,#137,#138);
#136 = CARTESIAN_POINT('',(-2.79,0.19,-2.6));
#137 = DIRECTION('',(0.,1.,0.));
#138 = DIRECTION('',(0.,0.,1.));
#139 = DEFINITIONAL_REPRESENTATION('',(#140),#144);
#140 = LINE('',#141,#142);
#141 = CARTESIAN_POINT('',(0.,0.));
#142 = VECTOR('',#143,1.);
#143 = DIRECTION('',(1.,0.));
#144 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#145 = ORIENTED_EDGE('',*,*,#146,.F.);
#146 = EDGE_CURVE('',#58,#119,#147,.T.);
#147 = SURFACE_CURVE('',#148,(#152,#159),.PCURVE_S1.);
#148 = LINE('',#149,#150);
#149 = CARTESIAN_POINT('',(-2.79,-0.19,2.7));
#150 = VECTOR('',#151,1.);
#151 = DIRECTION('',(0.,1.,0.));
#152 = PCURVE('',#66,#153);
#153 = DEFINITIONAL_REPRESENTATION('',(#154),#158);
#154 = LINE('',#155,#156);
#155 = CARTESIAN_POINT('',(5.3,0.));
#156 = VECTOR('',#157,1.);
#157 = DIRECTION('',(0.,-1.));
#158 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#159 = PCURVE('',#160,#165);
#160 = PLANE('',#161);
#161 = AXIS2_PLACEMENT_3D('',#162,#163,#164);
#162 = CARTESIAN_POINT('',(-2.79,-0.19,2.7));
#163 = DIRECTION('',(0.,0.,1.));
#164 = DIRECTION('',(1.,0.,0.));
#165 = DEFINITIONAL_REPRESENTATION('',(#166),#170);
#166 = LINE('',#167,#168);
#167 = CARTESIAN_POINT('',(0.,0.));
#168 = VECTOR('',#169,1.);
#169 = DIRECTION('',(0.,1.));
#170 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#171 = ADVANCED_FACE('',(#172),#186,.T.);
#172 = FACE_BOUND('',#173,.T.);
#173 = EDGE_LOOP('',(#174,#204,#227,#250));
#174 = ORIENTED_EDGE('',*,*,#175,.F.);
#175 = EDGE_CURVE('',#176,#178,#180,.T.);
#176 = VERTEX_POINT('',#177);
#177 = CARTESIAN_POINT('',(-2.29,-0.19,-2.6));
#178 = VERTEX_POINT('',#179);
#179 = CARTESIAN_POINT('',(-2.29,-0.19,2.7));
#180 = SURFACE_CURVE('',#181,(#185,#197),.PCURVE_S1.);
#181 = LINE('',#182,#183);
#182 = CARTESIAN_POINT('',(-2.29,-0.19,-2.6));
#183 = VECTOR('',#184,1.);
#184 = DIRECTION('',(0.,0.,1.));
#185 = PCURVE('',#186,#191);
#186 = PLANE('',#187);
#187 = AXIS2_PLACEMENT_3D('',#188,#189,#190);
#188 = CARTESIAN_POINT('',(-2.29,-0.19,-2.6));
#189 = DIRECTION('',(1.,0.,0.));
#190 = DIRECTION('',(0.,0.,1.));
#191 = DEFINITIONAL_REPRESENTATION('',(#192),#196);
#192 = LINE('',#193,#194);
#193 = CARTESIAN_POINT('',(0.,0.));
#194 = VECTOR('',#195,1.);
#195 = DIRECTION('',(1.,0.));
#196 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#197 = PCURVE('',#78,#198);
#198 = DEFINITIONAL_REPRESENTATION('',(#199),#203);
#199 = LINE('',#200,#201);
#200 = CARTESIAN_POINT('',(0.,0.5));
#201 = VECTOR('',#202,1.);
#202 = DIRECTION('',(1.,0.));
#203 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#204 = ORIENTED_EDGE('',*,*,#205,.T.);
#205 = EDGE_CURVE('',#176,#206,#208,.T.);
#206 = VERTEX_POINT('',#207);
#207 = CARTESIAN_POINT('',(-2.29,0.19,-2.6));
#208 = SURFACE_CURVE('',#209,(#213,#220),.PCURVE_S1.);
#209 = LINE('',#210,#211);
#210 = CARTESIAN_POINT('',(-2.29,-0.19,-2.6));
#211 = VECTOR('',#212,1.);
#212 = DIRECTION('',(0.,1.,0.));
#213 = PCURVE('',#186,#214);
#214 = DEFINITIONAL_REPRESENTATION('',(#215),#219);
#215 = LINE('',#216,#217);
#216 = CARTESIAN_POINT('',(0.,0.));
#217 = VECTOR('',#218,1.);
#218 = DIRECTION('',(0.,-1.));
#219 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#220 = PCURVE('',#106,#221);
#221 = DEFINITIONAL_REPRESENTATION('',(#222),#226);
#222 = LINE('',#223,#224);
#223 = CARTESIAN_POINT('',(0.5,0.));
#224 = VECTOR('',#225,1.);
#225 = DIRECTION('',(0.,1.));
#226 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#227 = ORIENTED_EDGE('',*,*,#228,.T.);
#228 = EDGE_CURVE('',#206,#229,#231,.T.);
#229 = VERTEX_POINT('',#230);
#230 = CARTESIAN_POINT('',(-2.29,0.19,2.7));
#231 = SURFACE_CURVE('',#232,(#236,#243),.PCURVE_S1.);
#232 = LINE('',#233,#234);
#233 = CARTESIAN_POINT('',(-2.29,0.19,-2.6));
#234 = VECTOR('',#235,1.);
#235 = DIRECTION('',(0.,0.,1.));
#236 = PCURVE('',#186,#237);
#237 = DEFINITIONAL_REPRESENTATION('',(#238),#242);
#238 = LINE('',#239,#240);
#239 = CARTESIAN_POINT('',(0.,-0.38));
#240 = VECTOR('',#241,1.);
#241 = DIRECTION('',(1.,0.));
#242 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#243 = PCURVE('',#134,#244);
#244 = DEFINITIONAL_REPRESENTATION('',(#245),#249);
#245 = LINE('',#246,#247);
#246 = CARTESIAN_POINT('',(0.,0.5));
#247 = VECTOR('',#248,1.);
#248 = DIRECTION('',(1.,0.));
#249 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#250 = ORIENTED_EDGE('',*,*,#251,.F.);
#251 = EDGE_CURVE('',#178,#229,#252,.T.);
#252 = SURFACE_CURVE('',#253,(#257,#264),.PCURVE_S1.);
#253 = LINE('',#254,#255);
#254 = CARTESIAN_POINT('',(-2.29,-0.19,2.7));
#255 = VECTOR('',#256,1.);
#256 = DIRECTION('',(0.,1.,0.));
#257 = PCURVE('',#186,#258);
#258 = DEFINITIONAL_REPRESENTATION('',(#259),#263);
#259 = LINE('',#260,#261);
#260 = CARTESIAN_POINT('',(5.3,0.));
#261 = VECTOR('',#262,1.);
#262 = DIRECTION('',(0.,-1.));
#263 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#264 = PCURVE('',#160,#265);
#265 = DEFINITIONAL_REPRESENTATION('',(#266),#270);
#266 = LINE('',#267,#268);
#267 = CARTESIAN_POINT('',(0.5,0.));
#268 = VECTOR('',#269,1.);
#269 = DIRECTION('',(0.,1.));
#270 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#271 = ADVANCED_FACE('',(#272),#78,.F.);
#272 = FACE_BOUND('',#273,.F.);
#273 = EDGE_LOOP('',(#274,#295,#296,#317));
#274 = ORIENTED_EDGE('',*,*,#275,.F.);
#275 = EDGE_CURVE('',#56,#176,#276,.T.);
#276 = SURFACE_CURVE('',#277,(#281,#288),.PCURVE_S1.);
#277 = LINE('',#278,#279);
#278 = CARTESIAN_POINT('',(-2.79,-0.19,-2.6));
#279 = VECTOR('',#280,1.);
#280 = DIRECTION('',(1.,0.,0.));
#281 = PCURVE('',#78,#282);
#282 = DEFINITIONAL_REPRESENTATION('',(#283),#287);
#283 = LINE('',#284,#285);
#284 = CARTESIAN_POINT('',(0.,0.));
#285 = VECTOR('',#286,1.);
#286 = DIRECTION('',(0.,1.));
#287 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#288 = PCURVE('',#106,#289);
#289 = DEFINITIONAL_REPRESENTATION('',(#290),#294);
#290 = LINE('',#291,#292);
#291 = CARTESIAN_POINT('',(0.,0.));
#292 = VECTOR('',#293,1.);
#293 = DIRECTION('',(1.,0.));
#294 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#295 = ORIENTED_EDGE('',*,*,#55,.T.);
#296 = ORIENTED_EDGE('',*,*,#297,.T.);
#297 = EDGE_CURVE('',#58,#178,#298,.T.);
#298 = SURFACE_CURVE('',#299,(#303,#310),.PCURVE_S1.);
#299 = LINE('',#300,#301);
#300 = CARTESIAN_POINT('',(-2.79,-0.19,2.7));
#301 = VECTOR('',#302,1.);
#302 = DIRECTION('',(1.,0.,0.));
#303 = PCURVE('',#78,#304);
#304 = DEFINITIONAL_REPRESENTATION('',(#305),#309);
#305 = LINE('',#306,#307);
#306 = CARTESIAN_POINT('',(5.3,0.));
#307 = VECTOR('',#308,1.);
#308 = DIRECTION('',(0.,1.));
#309 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#310 = PCURVE('',#160,#311);
#311 = DEFINITIONAL_REPRESENTATION('',(#312),#316);
#312 = LINE('',#313,#314);
#313 = CARTESIAN_POINT('',(0.,0.));
#314 = VECTOR('',#315,1.);
#315 = DIRECTION('',(1.,0.));
#316 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#317 = ORIENTED_EDGE('',*,*,#175,.F.);
#318 = ADVANCED_FACE('',(#319),#134,.T.);
#319 = FACE_BOUND('',#320,.T.);
#320 = EDGE_LOOP('',(#321,#342,#343,#364));
#321 = ORIENTED_EDGE('',*,*,#322,.F.);
#322 = EDGE_CURVE('',#91,#206,#323,.T.);
#323 = SURFACE_CURVE('',#324,(#328,#335),.PCURVE_S1.);
#324 = LINE('',#325,#326);
#325 = CARTESIAN_POINT('',(-2.79,0.19,-2.6));
#326 = VECTOR('',#327,1.);
#327 = DIRECTION('',(1.,0.,0.));
#328 = PCURVE('',#134,#329);
#329 = DEFINITIONAL_REPRESENTATION('',(#330),#334);
#330 = LINE('',#331,#332);
#331 = CARTESIAN_POINT('',(0.,0.));
#332 = VECTOR('',#333,1.);
#333 = DIRECTION('',(0.,1.));
#334 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#335 = PCURVE('',#106,#336);
#336 = DEFINITIONAL_REPRESENTATION('',(#337),#341);
#337 = LINE('',#338,#339);
#338 = CARTESIAN_POINT('',(0.,0.38));
#339 = VECTOR('',#340,1.);
#340 = DIRECTION('',(1.,0.));
#341 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#342 = ORIENTED_EDGE('',*,*,#118,.T.);
#343 = ORIENTED_EDGE('',*,*,#344,.T.);
#344 = EDGE_CURVE('',#119,#229,#345,.T.);
#345 = SURFACE_CURVE('',#346,(#350,#357),.PCURVE_S1.);
#346 = LINE('',#347,#348);
#347 = CARTESIAN_POINT('',(-2.79,0.19,2.7));
#348 = VECTOR('',#349,1.);
#349 = DIRECTION('',(1.,0.,0.));
#350 = PCURVE('',#134,#351);
#351 = DEFINITIONAL_REPRESENTATION('',(#352),#356);
#352 = LINE('',#353,#354);
#353 = CARTESIAN_POINT('',(5.3,0.));
#354 = VECTOR('',#355,1.);
#355 = DIRECTION('',(0.,1.));
#356 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#357 = PCURVE('',#160,#358);
#358 = DEFINITIONAL_REPRESENTATION('',(#359),#363);
#359 = LINE('',#360,#361);
#360 = CARTESIAN_POINT('',(0.,0.38));
#361 = VECTOR('',#362,1.);
#362 = DIRECTION('',(1.,0.));
#363 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#364 = ORIENTED_EDGE('',*,*,#228,.F.);
#365 = ADVANCED_FACE('',(#366),#106,.F.);
#366 = FACE_BOUND('',#367,.F.);
#367 = EDGE_LOOP('',(#368,#369,#370,#371));
#368 = ORIENTED_EDGE('',*,*,#90,.F.);
#369 = ORIENTED_EDGE('',*,*,#275,.T.);
#370 = ORIENTED_EDGE('',*,*,#205,.T.);
#371 = ORIENTED_EDGE('',*,*,#322,.F.);
#372 = ADVANCED_FACE('',(#373),#160,.T.);
#373 = FACE_BOUND('',#374,.T.);
#374 = EDGE_LOOP('',(#375,#376,#377,#378));
#375 = ORIENTED_EDGE('',*,*,#146,.F.);
#376 = ORIENTED_EDGE('',*,*,#297,.T.);
#377 = ORIENTED_EDGE('',*,*,#251,.T.);
#378 = ORIENTED_EDGE('',*,*,#344,.F.);
#379 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3)
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#383)) GLOBAL_UNIT_ASSIGNED_CONTEXT
((#380,#381,#382)) REPRESENTATION_CONTEXT('Context #1',
'3D Context with UNIT and UNCERTAINTY') );
#380 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) );
#381 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) );
#382 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() );
#383 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-07),#380,
'distance_accuracy_value','confusion accuracy');
#384 = CONTEXT_DEPENDENT_SHAPE_REPRESENTATION(#385,#387);
#385 = ( REPRESENTATION_RELATIONSHIP('','',#48,#10)
REPRESENTATION_RELATIONSHIP_WITH_TRANSFORMATION(#386)
SHAPE_REPRESENTATION_RELATIONSHIP() );
#386 = ITEM_DEFINED_TRANSFORMATION('','',#11,#15);
#387 = PRODUCT_DEFINITION_SHAPE('Placement','Placement of an item',#388
);
#388 = NEXT_ASSEMBLY_USAGE_OCCURRENCE('56',
'364e6857-93c7-11f1-9269-04bad62a3558','',#5,#43,$);
#389 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#45));
#390 = SHAPE_DEFINITION_REPRESENTATION(#391,#397);
#391 = PRODUCT_DEFINITION_SHAPE('','',#392);
#392 = PRODUCT_DEFINITION('design','',#393,#396);
#393 = PRODUCT_DEFINITION_FORMATION('','',#394);
#394 = PRODUCT('364ea3ca-93c7-11f1-bdcd-04bad62a3558',
'364ea3ca-93c7-11f1-bdcd-04bad62a3558','',(#395));
#395 = PRODUCT_CONTEXT('',#2,'mechanical');
#396 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design');
#397 = ADVANCED_BREP_SHAPE_REPRESENTATION('',(#11,#398),#728);
#398 = MANIFOLD_SOLID_BREP('',#399);
#399 = CLOSED_SHELL('',(#400,#520,#620,#667,#714,#721));
#400 = ADVANCED_FACE('',(#401),#415,.F.);
#401 = FACE_BOUND('',#402,.F.);
#402 = EDGE_LOOP('',(#403,#438,#466,#494));
#403 = ORIENTED_EDGE('',*,*,#404,.F.);
#404 = EDGE_CURVE('',#405,#407,#409,.T.);
#405 = VERTEX_POINT('',#406);
#406 = CARTESIAN_POINT('',(2.29,-0.19,-2.6));
#407 = VERTEX_POINT('',#408);
#408 = CARTESIAN_POINT('',(2.29,-0.19,2.7));
#409 = SURFACE_CURVE('',#410,(#414,#426),.PCURVE_S1.);
#410 = LINE('',#411,#412);
#411 = CARTESIAN_POINT('',(2.29,-0.19,-2.6));
#412 = VECTOR('',#413,1.);
#413 = DIRECTION('',(0.,0.,1.));
#414 = PCURVE('',#415,#420);
#415 = PLANE('',#416);
#416 = AXIS2_PLACEMENT_3D('',#417,#418,#419);
#417 = CARTESIAN_POINT('',(2.29,-0.19,-2.6));
#418 = DIRECTION('',(1.,0.,0.));
#419 = DIRECTION('',(0.,0.,1.));
#420 = DEFINITIONAL_REPRESENTATION('',(#421),#425);
#421 = LINE('',#422,#423);
#422 = CARTESIAN_POINT('',(0.,0.));
#423 = VECTOR('',#424,1.);
#424 = DIRECTION('',(1.,0.));
#425 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#426 = PCURVE('',#427,#432);
#427 = PLANE('',#428);
#428 = AXIS2_PLACEMENT_3D('',#429,#430,#431);
#429 = CARTESIAN_POINT('',(2.29,-0.19,-2.6));
#430 = DIRECTION('',(0.,1.,0.));
#431 = DIRECTION('',(0.,0.,1.));
#432 = DEFINITIONAL_REPRESENTATION('',(#433),#437);
#433 = LINE('',#434,#435);
#434 = CARTESIAN_POINT('',(0.,0.));
#435 = VECTOR('',#436,1.);
#436 = DIRECTION('',(1.,0.));
#437 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#438 = ORIENTED_EDGE('',*,*,#439,.T.);
#439 = EDGE_CURVE('',#405,#440,#442,.T.);
#440 = VERTEX_POINT('',#441);
#441 = CARTESIAN_POINT('',(2.29,0.19,-2.6));
#442 = SURFACE_CURVE('',#443,(#447,#454),.PCURVE_S1.);
#443 = LINE('',#444,#445);
#444 = CARTESIAN_POINT('',(2.29,-0.19,-2.6));
#445 = VECTOR('',#446,1.);
#446 = DIRECTION('',(0.,1.,0.));
#447 = PCURVE('',#415,#448);
#448 = DEFINITIONAL_REPRESENTATION('',(#449),#453);
#449 = LINE('',#450,#451);
#450 = CARTESIAN_POINT('',(0.,0.));
#451 = VECTOR('',#452,1.);
#452 = DIRECTION('',(0.,-1.));
#453 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#454 = PCURVE('',#455,#460);
#455 = PLANE('',#456);
#456 = AXIS2_PLACEMENT_3D('',#457,#458,#459);
#457 = CARTESIAN_POINT('',(2.29,-0.19,-2.6));
#458 = DIRECTION('',(0.,0.,1.));
#459 = DIRECTION('',(1.,0.,0.));
#460 = DEFINITIONAL_REPRESENTATION('',(#461),#465);
#461 = LINE('',#462,#463);
#462 = CARTESIAN_POINT('',(0.,0.));
#463 = VECTOR('',#464,1.);
#464 = DIRECTION('',(0.,1.));
#465 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#466 = ORIENTED_EDGE('',*,*,#467,.T.);
#467 = EDGE_CURVE('',#440,#468,#470,.T.);
#468 = VERTEX_POINT('',#469);
#469 = CARTESIAN_POINT('',(2.29,0.19,2.7));
#470 = SURFACE_CURVE('',#471,(#475,#482),.PCURVE_S1.);
#471 = LINE('',#472,#473);
#472 = CARTESIAN_POINT('',(2.29,0.19,-2.6));
#473 = VECTOR('',#474,1.);
#474 = DIRECTION('',(0.,0.,1.));
#475 = PCURVE('',#415,#476);
#476 = DEFINITIONAL_REPRESENTATION('',(#477),#481);
#477 = LINE('',#478,#479);
#478 = CARTESIAN_POINT('',(0.,-0.38));
#479 = VECTOR('',#480,1.);
#480 = DIRECTION('',(1.,0.));
#481 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#482 = PCURVE('',#483,#488);
#483 = PLANE('',#484);
#484 = AXIS2_PLACEMENT_3D('',#485,#486,#487);
#485 = CARTESIAN_POINT('',(2.29,0.19,-2.6));
#486 = DIRECTION('',(0.,1.,0.));
#487 = DIRECTION('',(0.,0.,1.));
#488 = DEFINITIONAL_REPRESENTATION('',(#489),#493);
#489 = LINE('',#490,#491);
#490 = CARTESIAN_POINT('',(0.,0.));
#491 = VECTOR('',#492,1.);
#492 = DIRECTION('',(1.,0.));
#493 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#494 = ORIENTED_EDGE('',*,*,#495,.F.);
#495 = EDGE_CURVE('',#407,#468,#496,.T.);
#496 = SURFACE_CURVE('',#497,(#501,#508),.PCURVE_S1.);
#497 = LINE('',#498,#499);
#498 = CARTESIAN_POINT('',(2.29,-0.19,2.7));
#499 = VECTOR('',#500,1.);
#500 = DIRECTION('',(0.,1.,0.));
#501 = PCURVE('',#415,#502);
#502 = DEFINITIONAL_REPRESENTATION('',(#503),#507);
#503 = LINE('',#504,#505);
#504 = CARTESIAN_POINT('',(5.3,0.));
#505 = VECTOR('',#506,1.);
#506 = DIRECTION('',(0.,-1.));
#507 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#508 = PCURVE('',#509,#514);
#509 = PLANE('',#510);
#510 = AXIS2_PLACEMENT_3D('',#511,#512,#513);
#511 = CARTESIAN_POINT('',(2.29,-0.19,2.7));
#512 = DIRECTION('',(0.,0.,1.));
#513 = DIRECTION('',(1.,0.,0.));
#514 = DEFINITIONAL_REPRESENTATION('',(#515),#519);
#515 = LINE('',#516,#517);
#516 = CARTESIAN_POINT('',(0.,0.));
#517 = VECTOR('',#518,1.);
#518 = DIRECTION('',(0.,1.));
#519 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#520 = ADVANCED_FACE('',(#521),#535,.T.);
#521 = FACE_BOUND('',#522,.T.);
#522 = EDGE_LOOP('',(#523,#553,#576,#599));
#523 = ORIENTED_EDGE('',*,*,#524,.F.);
#524 = EDGE_CURVE('',#525,#527,#529,.T.);
#525 = VERTEX_POINT('',#526);
#526 = CARTESIAN_POINT('',(2.79,-0.19,-2.6));
#527 = VERTEX_POINT('',#528);
#528 = CARTESIAN_POINT('',(2.79,-0.19,2.7));
#529 = SURFACE_CURVE('',#530,(#534,#546),.PCURVE_S1.);
#530 = LINE('',#531,#532);
#531 = CARTESIAN_POINT('',(2.79,-0.19,-2.6));
#532 = VECTOR('',#533,1.);
#533 = DIRECTION('',(0.,0.,1.));
#534 = PCURVE('',#535,#540);
#535 = PLANE('',#536);
#536 = AXIS2_PLACEMENT_3D('',#537,#538,#539);
#537 = CARTESIAN_POINT('',(2.79,-0.19,-2.6));
#538 = DIRECTION('',(1.,0.,0.));
#539 = DIRECTION('',(0.,0.,1.));
#540 = DEFINITIONAL_REPRESENTATION('',(#541),#545);
#541 = LINE('',#542,#543);
#542 = CARTESIAN_POINT('',(0.,0.));
#543 = VECTOR('',#544,1.);
#544 = DIRECTION('',(1.,0.));
#545 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#546 = PCURVE('',#427,#547);
#547 = DEFINITIONAL_REPRESENTATION('',(#548),#552);
#548 = LINE('',#549,#550);
#549 = CARTESIAN_POINT('',(0.,0.5));
#550 = VECTOR('',#551,1.);
#551 = DIRECTION('',(1.,0.));
#552 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#553 = ORIENTED_EDGE('',*,*,#554,.T.);
#554 = EDGE_CURVE('',#525,#555,#557,.T.);
#555 = VERTEX_POINT('',#556);
#556 = CARTESIAN_POINT('',(2.79,0.19,-2.6));
#557 = SURFACE_CURVE('',#558,(#562,#569),.PCURVE_S1.);
#558 = LINE('',#559,#560);
#559 = CARTESIAN_POINT('',(2.79,-0.19,-2.6));
#560 = VECTOR('',#561,1.);
#561 = DIRECTION('',(0.,1.,0.));
#562 = PCURVE('',#535,#563);
#563 = DEFINITIONAL_REPRESENTATION('',(#564),#568);
#564 = LINE('',#565,#566);
#565 = CARTESIAN_POINT('',(0.,0.));
#566 = VECTOR('',#567,1.);
#567 = DIRECTION('',(0.,-1.));
#568 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#569 = PCURVE('',#455,#570);
#570 = DEFINITIONAL_REPRESENTATION('',(#571),#575);
#571 = LINE('',#572,#573);
#572 = CARTESIAN_POINT('',(0.5,0.));
#573 = VECTOR('',#574,1.);
#574 = DIRECTION('',(0.,1.));
#575 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#576 = ORIENTED_EDGE('',*,*,#577,.T.);
#577 = EDGE_CURVE('',#555,#578,#580,.T.);
#578 = VERTEX_POINT('',#579);
#579 = CARTESIAN_POINT('',(2.79,0.19,2.7));
#580 = SURFACE_CURVE('',#581,(#585,#592),.PCURVE_S1.);
#581 = LINE('',#582,#583);
#582 = CARTESIAN_POINT('',(2.79,0.19,-2.6));
#583 = VECTOR('',#584,1.);
#584 = DIRECTION('',(0.,0.,1.));
#585 = PCURVE('',#535,#586);
#586 = DEFINITIONAL_REPRESENTATION('',(#587),#591);
#587 = LINE('',#588,#589);
#588 = CARTESIAN_POINT('',(0.,-0.38));
#589 = VECTOR('',#590,1.);
#590 = DIRECTION('',(1.,0.));
#591 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#592 = PCURVE('',#483,#593);
#593 = DEFINITIONAL_REPRESENTATION('',(#594),#598);
#594 = LINE('',#595,#596);
#595 = CARTESIAN_POINT('',(0.,0.5));
#596 = VECTOR('',#597,1.);
#597 = DIRECTION('',(1.,0.));
#598 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#599 = ORIENTED_EDGE('',*,*,#600,.F.);
#600 = EDGE_CURVE('',#527,#578,#601,.T.);
#601 = SURFACE_CURVE('',#602,(#606,#613),.PCURVE_S1.);
#602 = LINE('',#603,#604);
#603 = CARTESIAN_POINT('',(2.79,-0.19,2.7));
#604 = VECTOR('',#605,1.);
#605 = DIRECTION('',(0.,1.,0.));
#606 = PCURVE('',#535,#607);
#607 = DEFINITIONAL_REPRESENTATION('',(#608),#612);
#608 = LINE('',#609,#610);
#609 = CARTESIAN_POINT('',(5.3,0.));
#610 = VECTOR('',#611,1.);
#611 = DIRECTION('',(0.,-1.));
#612 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#613 = PCURVE('',#509,#614);
#614 = DEFINITIONAL_REPRESENTATION('',(#615),#619);
#615 = LINE('',#616,#617);
#616 = CARTESIAN_POINT('',(0.5,0.));
#617 = VECTOR('',#618,1.);
#618 = DIRECTION('',(0.,1.));
#619 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#620 = ADVANCED_FACE('',(#621),#427,.F.);
#621 = FACE_BOUND('',#622,.F.);
#622 = EDGE_LOOP('',(#623,#644,#645,#666));
#623 = ORIENTED_EDGE('',*,*,#624,.F.);
#624 = EDGE_CURVE('',#405,#525,#625,.T.);
#625 = SURFACE_CURVE('',#626,(#630,#637),.PCURVE_S1.);
#626 = LINE('',#627,#628);
#627 = CARTESIAN_POINT('',(2.29,-0.19,-2.6));
#628 = VECTOR('',#629,1.);
#629 = DIRECTION('',(1.,0.,0.));
#630 = PCURVE('',#427,#631);
#631 = DEFINITIONAL_REPRESENTATION('',(#632),#636);
#632 = LINE('',#633,#634);
#633 = CARTESIAN_POINT('',(0.,0.));
#634 = VECTOR('',#635,1.);
#635 = DIRECTION('',(0.,1.));
#636 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#637 = PCURVE('',#455,#638);
#638 = DEFINITIONAL_REPRESENTATION('',(#639),#643);
#639 = LINE('',#640,#641);
#640 = CARTESIAN_POINT('',(0.,0.));
#641 = VECTOR('',#642,1.);
#642 = DIRECTION('',(1.,0.));
#643 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#644 = ORIENTED_EDGE('',*,*,#404,.T.);
#645 = ORIENTED_EDGE('',*,*,#646,.T.);
#646 = EDGE_CURVE('',#407,#527,#647,.T.);
#647 = SURFACE_CURVE('',#648,(#652,#659),.PCURVE_S1.);
#648 = LINE('',#649,#650);
#649 = CARTESIAN_POINT('',(2.29,-0.19,2.7));
#650 = VECTOR('',#651,1.);
#651 = DIRECTION('',(1.,0.,0.));
#652 = PCURVE('',#427,#653);
#653 = DEFINITIONAL_REPRESENTATION('',(#654),#658);
#654 = LINE('',#655,#656);
#655 = CARTESIAN_POINT('',(5.3,0.));
#656 = VECTOR('',#657,1.);
#657 = DIRECTION('',(0.,1.));
#658 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#659 = PCURVE('',#509,#660);
#660 = DEFINITIONAL_REPRESENTATION('',(#661),#665);
#661 = LINE('',#662,#663);
#662 = CARTESIAN_POINT('',(0.,0.));
#663 = VECTOR('',#664,1.);
#664 = DIRECTION('',(1.,0.));
#665 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#666 = ORIENTED_EDGE('',*,*,#524,.F.);
#667 = ADVANCED_FACE('',(#668),#483,.T.);
#668 = FACE_BOUND('',#669,.T.);
#669 = EDGE_LOOP('',(#670,#691,#692,#713));
#670 = ORIENTED_EDGE('',*,*,#671,.F.);
#671 = EDGE_CURVE('',#440,#555,#672,.T.);
#672 = SURFACE_CURVE('',#673,(#677,#684),.PCURVE_S1.);
#673 = LINE('',#674,#675);
#674 = CARTESIAN_POINT('',(2.29,0.19,-2.6));
#675 = VECTOR('',#676,1.);
#676 = DIRECTION('',(1.,0.,0.));
#677 = PCURVE('',#483,#678);
#678 = DEFINITIONAL_REPRESENTATION('',(#679),#683);
#679 = LINE('',#680,#681);
#680 = CARTESIAN_POINT('',(0.,0.));
#681 = VECTOR('',#682,1.);
#682 = DIRECTION('',(0.,1.));
#683 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#684 = PCURVE('',#455,#685);
#685 = DEFINITIONAL_REPRESENTATION('',(#686),#690);
#686 = LINE('',#687,#688);
#687 = CARTESIAN_POINT('',(0.,0.38));
#688 = VECTOR('',#689,1.);
#689 = DIRECTION('',(1.,0.));
#690 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#691 = ORIENTED_EDGE('',*,*,#467,.T.);
#692 = ORIENTED_EDGE('',*,*,#693,.T.);
#693 = EDGE_CURVE('',#468,#578,#694,.T.);
#694 = SURFACE_CURVE('',#695,(#699,#706),.PCURVE_S1.);
#695 = LINE('',#696,#697);
#696 = CARTESIAN_POINT('',(2.29,0.19,2.7));
#697 = VECTOR('',#698,1.);
#698 = DIRECTION('',(1.,0.,0.));
#699 = PCURVE('',#483,#700);
#700 = DEFINITIONAL_REPRESENTATION('',(#701),#705);
#701 = LINE('',#702,#703);
#702 = CARTESIAN_POINT('',(5.3,0.));
#703 = VECTOR('',#704,1.);
#704 = DIRECTION('',(0.,1.));
#705 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#706 = PCURVE('',#509,#707);
#707 = DEFINITIONAL_REPRESENTATION('',(#708),#712);
#708 = LINE('',#709,#710);
#709 = CARTESIAN_POINT('',(0.,0.38));
#710 = VECTOR('',#711,1.);
#711 = DIRECTION('',(1.,0.));
#712 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#713 = ORIENTED_EDGE('',*,*,#577,.F.);
#714 = ADVANCED_FACE('',(#715),#455,.F.);
#715 = FACE_BOUND('',#716,.F.);
#716 = EDGE_LOOP('',(#717,#718,#719,#720));
#717 = ORIENTED_EDGE('',*,*,#439,.F.);
#718 = ORIENTED_EDGE('',*,*,#624,.T.);
#719 = ORIENTED_EDGE('',*,*,#554,.T.);
#720 = ORIENTED_EDGE('',*,*,#671,.F.);
#721 = ADVANCED_FACE('',(#722),#509,.T.);
#722 = FACE_BOUND('',#723,.T.);
#723 = EDGE_LOOP('',(#724,#725,#726,#727));
#724 = ORIENTED_EDGE('',*,*,#495,.F.);
#725 = ORIENTED_EDGE('',*,*,#646,.T.);
#726 = ORIENTED_EDGE('',*,*,#600,.T.);
#727 = ORIENTED_EDGE('',*,*,#693,.F.);
#728 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3)
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#732)) GLOBAL_UNIT_ASSIGNED_CONTEXT
((#729,#730,#731)) REPRESENTATION_CONTEXT('Context #1',
'3D Context with UNIT and UNCERTAINTY') );
#729 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) );
#730 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) );
#731 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() );
#732 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-07),#729,
'distance_accuracy_value','confusion accuracy');
#733 = CONTEXT_DEPENDENT_SHAPE_REPRESENTATION(#734,#736);
#734 = ( REPRESENTATION_RELATIONSHIP('','',#397,#10)
REPRESENTATION_RELATIONSHIP_WITH_TRANSFORMATION(#735)
SHAPE_REPRESENTATION_RELATIONSHIP() );
#735 = ITEM_DEFINED_TRANSFORMATION('','',#11,#19);
#736 = PRODUCT_DEFINITION_SHAPE('Placement','Placement of an item',#737
);
#737 = NEXT_ASSEMBLY_USAGE_OCCURRENCE('57',
'364ea3ca-93c7-11f1-bdcd-04bad62a3558','',#5,#392,$);
#738 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#394));
#739 = SHAPE_DEFINITION_REPRESENTATION(#740,#746);
#740 = PRODUCT_DEFINITION_SHAPE('','',#741);
#741 = PRODUCT_DEFINITION('design','',#742,#745);
#742 = PRODUCT_DEFINITION_FORMATION('','',#743);
#743 = PRODUCT('3650c6ce-93c7-11f1-9448-04bad62a3558',
'3650c6ce-93c7-11f1-9448-04bad62a3558','',(#744));
#744 = PRODUCT_CONTEXT('',#2,'mechanical');
#745 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design');
#746 = ADVANCED_BREP_SHAPE_REPRESENTATION('',(#11,#747),#1413);
#747 = MANIFOLD_SOLID_BREP('',#748);
#748 = CLOSED_SHELL('',(#749,#869,#1069,#1119,#1173,#1298,#1325,#1352,
#1379,#1406));
#749 = ADVANCED_FACE('',(#750),#764,.F.);
#750 = FACE_BOUND('',#751,.F.);
#751 = EDGE_LOOP('',(#752,#787,#815,#843));
#752 = ORIENTED_EDGE('',*,*,#753,.F.);
#753 = EDGE_CURVE('',#754,#756,#758,.T.);
#754 = VERTEX_POINT('',#755);
#755 = CARTESIAN_POINT('',(-2.325,-0.75,6.45));
#756 = VERTEX_POINT('',#757);
#757 = CARTESIAN_POINT('',(-2.325,0.75,6.45));
#758 = SURFACE_CURVE('',#759,(#763,#775),.PCURVE_S1.);
#759 = LINE('',#760,#761);
#760 = CARTESIAN_POINT('',(-2.325,-1.,6.45));
#761 = VECTOR('',#762,1.);
#762 = DIRECTION('',(0.,1.,0.));
#763 = PCURVE('',#764,#769);
#764 = PLANE('',#765);
#765 = AXIS2_PLACEMENT_3D('',#766,#767,#768);
#766 = CARTESIAN_POINT('',(-2.325,-1.,2.15));
#767 = DIRECTION('',(1.,0.,0.));
#768 = DIRECTION('',(0.,0.,1.));
#769 = DEFINITIONAL_REPRESENTATION('',(#770),#774);
#770 = LINE('',#771,#772);
#771 = CARTESIAN_POINT('',(4.3,0.));
#772 = VECTOR('',#773,1.);
#773 = DIRECTION('',(0.,-1.));
#774 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#775 = PCURVE('',#776,#781);
#776 = PLANE('',#777);
#777 = AXIS2_PLACEMENT_3D('',#778,#779,#780);
#778 = CARTESIAN_POINT('',(-2.325,-1.,6.45));
#779 = DIRECTION('',(0.,0.,1.));
#780 = DIRECTION('',(1.,0.,0.));
#781 = DEFINITIONAL_REPRESENTATION('',(#782),#786);
#782 = LINE('',#783,#784);
#783 = CARTESIAN_POINT('',(0.,0.));
#784 = VECTOR('',#785,1.);
#785 = DIRECTION('',(0.,1.));
#786 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#787 = ORIENTED_EDGE('',*,*,#788,.F.);
#788 = EDGE_CURVE('',#789,#754,#791,.T.);
#789 = VERTEX_POINT('',#790);
#790 = CARTESIAN_POINT('',(-2.325,-0.75,2.15));
#791 = SURFACE_CURVE('',#792,(#796,#803),.PCURVE_S1.);
#792 = LINE('',#793,#794);
#793 = CARTESIAN_POINT('',(-2.325,-0.75,2.15));
#794 = VECTOR('',#795,1.);
#795 = DIRECTION('',(0.,0.,1.));
#796 = PCURVE('',#764,#797);
#797 = DEFINITIONAL_REPRESENTATION('',(#798),#802);
#798 = LINE('',#799,#800);
#799 = CARTESIAN_POINT('',(0.,-0.25));
#800 = VECTOR('',#801,1.);
#801 = DIRECTION('',(1.,0.));
#802 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#803 = PCURVE('',#804,#809);
#804 = CYLINDRICAL_SURFACE('',#805,0.25);
#805 = AXIS2_PLACEMENT_3D('',#806,#807,#808);
#806 = CARTESIAN_POINT('',(-2.075,-0.75,2.15));
#807 = DIRECTION('',(0.,0.,1.));
#808 = DIRECTION('',(-1.,-0.,0.));
#809 = DEFINITIONAL_REPRESENTATION('',(#810),#814);
#810 = LINE('',#811,#812);
#811 = CARTESIAN_POINT('',(0.,0.));
#812 = VECTOR('',#813,1.);
#813 = DIRECTION('',(0.,1.));
#814 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#815 = ORIENTED_EDGE('',*,*,#816,.T.);
#816 = EDGE_CURVE('',#789,#817,#819,.T.);
#817 = VERTEX_POINT('',#818);
#818 = CARTESIAN_POINT('',(-2.325,0.75,2.15));
#819 = SURFACE_CURVE('',#820,(#824,#831),.PCURVE_S1.);
#820 = LINE('',#821,#822);
#821 = CARTESIAN_POINT('',(-2.325,-1.,2.15));
#822 = VECTOR('',#823,1.);
#823 = DIRECTION('',(0.,1.,0.));
#824 = PCURVE('',#764,#825);
#825 = DEFINITIONAL_REPRESENTATION('',(#826),#830);
#826 = LINE('',#827,#828);
#827 = CARTESIAN_POINT('',(0.,0.));
#828 = VECTOR('',#829,1.);
#829 = DIRECTION('',(0.,-1.));
#830 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#831 = PCURVE('',#832,#837);
#832 = PLANE('',#833);
#833 = AXIS2_PLACEMENT_3D('',#834,#835,#836);
#834 = CARTESIAN_POINT('',(-2.325,-1.,2.15));
#835 = DIRECTION('',(0.,0.,1.));
#836 = DIRECTION('',(1.,0.,0.));
#837 = DEFINITIONAL_REPRESENTATION('',(#838),#842);
#838 = LINE('',#839,#840);
#839 = CARTESIAN_POINT('',(0.,0.));
#840 = VECTOR('',#841,1.);
#841 = DIRECTION('',(0.,1.));
#842 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#843 = ORIENTED_EDGE('',*,*,#844,.T.);
#844 = EDGE_CURVE('',#817,#756,#845,.T.);
#845 = SURFACE_CURVE('',#846,(#850,#857),.PCURVE_S1.);
#846 = LINE('',#847,#848);
#847 = CARTESIAN_POINT('',(-2.325,0.75,2.15));
#848 = VECTOR('',#849,1.);
#849 = DIRECTION('',(0.,0.,1.));
#850 = PCURVE('',#764,#851);
#851 = DEFINITIONAL_REPRESENTATION('',(#852),#856);
#852 = LINE('',#853,#854);
#853 = CARTESIAN_POINT('',(0.,-1.75));
#854 = VECTOR('',#855,1.);
#855 = DIRECTION('',(1.,0.));
#856 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#857 = PCURVE('',#858,#863);
#858 = CYLINDRICAL_SURFACE('',#859,0.25);
#859 = AXIS2_PLACEMENT_3D('',#860,#861,#862);
#860 = CARTESIAN_POINT('',(-2.075,0.75,2.15));
#861 = DIRECTION('',(0.,0.,1.));
#862 = DIRECTION('',(-1.,-0.,0.));
#863 = DEFINITIONAL_REPRESENTATION('',(#864),#868);
#864 = LINE('',#865,#866);
#865 = CARTESIAN_POINT('',(-0.,0.));
#866 = VECTOR('',#867,1.);
#867 = DIRECTION('',(-0.,1.));
#868 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#869 = ADVANCED_FACE('',(#870),#776,.T.);
#870 = FACE_BOUND('',#871,.T.);
#871 = EDGE_LOOP('',(#872,#873,#897,#925,#958,#986,#1015,#1043));
#872 = ORIENTED_EDGE('',*,*,#753,.F.);
#873 = ORIENTED_EDGE('',*,*,#874,.T.);
#874 = EDGE_CURVE('',#754,#875,#877,.T.);
#875 = VERTEX_POINT('',#876);
#876 = CARTESIAN_POINT('',(-2.075,-1.,6.45));
#877 = SURFACE_CURVE('',#878,(#883,#890),.PCURVE_S1.);
#878 = CIRCLE('',#879,0.25);
#879 = AXIS2_PLACEMENT_3D('',#880,#881,#882);
#880 = CARTESIAN_POINT('',(-2.075,-0.75,6.45));
#881 = DIRECTION('',(0.,0.,1.));
#882 = DIRECTION('',(0.,-1.,0.));
#883 = PCURVE('',#776,#884);
#884 = DEFINITIONAL_REPRESENTATION('',(#885),#889);
#885 = CIRCLE('',#886,0.25);
#886 = AXIS2_PLACEMENT_2D('',#887,#888);
#887 = CARTESIAN_POINT('',(0.25,0.25));
#888 = DIRECTION('',(0.,-1.));
#889 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#890 = PCURVE('',#804,#891);
#891 = DEFINITIONAL_REPRESENTATION('',(#892),#896);
#892 = LINE('',#893,#894);
#893 = CARTESIAN_POINT('',(-4.712388980385,4.3));
#894 = VECTOR('',#895,1.);
#895 = DIRECTION('',(1.,0.));
#896 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#897 = ORIENTED_EDGE('',*,*,#898,.T.);
#898 = EDGE_CURVE('',#875,#899,#901,.T.);
#899 = VERTEX_POINT('',#900);
#900 = CARTESIAN_POINT('',(2.075,-1.,6.45));
#901 = SURFACE_CURVE('',#902,(#906,#913),.PCURVE_S1.);
#902 = LINE('',#903,#904);
#903 = CARTESIAN_POINT('',(-2.325,-1.,6.45));
#904 = VECTOR('',#905,1.);
#905 = DIRECTION('',(1.,0.,0.));
#906 = PCURVE('',#776,#907);
#907 = DEFINITIONAL_REPRESENTATION('',(#908),#912);
#908 = LINE('',#909,#910);
#909 = CARTESIAN_POINT('',(0.,0.));
#910 = VECTOR('',#911,1.);
#911 = DIRECTION('',(1.,0.));
#912 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#913 = PCURVE('',#914,#919);
#914 = PLANE('',#915);
#915 = AXIS2_PLACEMENT_3D('',#916,#917,#918);
#916 = CARTESIAN_POINT('',(-2.325,-1.,2.15));
#917 = DIRECTION('',(0.,1.,0.));
#918 = DIRECTION('',(0.,0.,1.));
#919 = DEFINITIONAL_REPRESENTATION('',(#920),#924);
#920 = LINE('',#921,#922);
#921 = CARTESIAN_POINT('',(4.3,0.));
#922 = VECTOR('',#923,1.);
#923 = DIRECTION('',(0.,1.));
#924 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#925 = ORIENTED_EDGE('',*,*,#926,.F.);
#926 = EDGE_CURVE('',#927,#899,#929,.T.);
#927 = VERTEX_POINT('',#928);
#928 = CARTESIAN_POINT('',(2.325,-0.75,6.45));
#929 = SURFACE_CURVE('',#930,(#935,#946),.PCURVE_S1.);
#930 = CIRCLE('',#931,0.25);
#931 = AXIS2_PLACEMENT_3D('',#932,#933,#934);
#932 = CARTESIAN_POINT('',(2.075,-0.75,6.45));
#933 = DIRECTION('',(-0.,-0.,-1.));
#934 = DIRECTION('',(0.,-1.,0.));
#935 = PCURVE('',#776,#936);
#936 = DEFINITIONAL_REPRESENTATION('',(#937),#945);
#937 = ( BOUNDED_CURVE() B_SPLINE_CURVE(2,(#938,#939,#940,#941,#942,#943
,#944),.UNSPECIFIED.,.T.,.F.) B_SPLINE_CURVE_WITH_KNOTS((1,2,2,2,2,1),(
-2.094395102393,0.,2.094395102393,4.188790204786,6.28318530718,
8.377580409573),.UNSPECIFIED.) CURVE() GEOMETRIC_REPRESENTATION_ITEM()
RATIONAL_B_SPLINE_CURVE((1.,0.5,1.,0.5,1.,0.5,1.)) REPRESENTATION_ITEM(
'') );
#938 = CARTESIAN_POINT('',(4.4,0.));
#939 = CARTESIAN_POINT('',(3.966987298108,0.));
#940 = CARTESIAN_POINT('',(4.183493649054,0.375));
#941 = CARTESIAN_POINT('',(4.4,0.75));
#942 = CARTESIAN_POINT('',(4.616506350946,0.375));
#943 = CARTESIAN_POINT('',(4.833012701892,3.885780586188E-16));
#944 = CARTESIAN_POINT('',(4.4,0.));
#945 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#946 = PCURVE('',#947,#952);
#947 = CYLINDRICAL_SURFACE('',#948,0.25);
#948 = AXIS2_PLACEMENT_3D('',#949,#950,#951);
#949 = CARTESIAN_POINT('',(2.075,-0.75,2.15));
#950 = DIRECTION('',(0.,0.,1.));
#951 = DIRECTION('',(1.,0.,-0.));
#952 = DEFINITIONAL_REPRESENTATION('',(#953),#957);
#953 = LINE('',#954,#955);
#954 = CARTESIAN_POINT('',(4.712388980385,4.3));
#955 = VECTOR('',#956,1.);
#956 = DIRECTION('',(-1.,0.));
#957 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#958 = ORIENTED_EDGE('',*,*,#959,.T.);
#959 = EDGE_CURVE('',#927,#960,#962,.T.);
#960 = VERTEX_POINT('',#961);
#961 = CARTESIAN_POINT('',(2.325,0.75,6.45));
#962 = SURFACE_CURVE('',#963,(#967,#974),.PCURVE_S1.);
#963 = LINE('',#964,#965);
#964 = CARTESIAN_POINT('',(2.325,-1.,6.45));
#965 = VECTOR('',#966,1.);
#966 = DIRECTION('',(0.,1.,0.));
#967 = PCURVE('',#776,#968);
#968 = DEFINITIONAL_REPRESENTATION('',(#969),#973);
#969 = LINE('',#970,#971);
#970 = CARTESIAN_POINT('',(4.65,0.));
#971 = VECTOR('',#972,1.);
#972 = DIRECTION('',(0.,1.));
#973 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#974 = PCURVE('',#975,#980);
#975 = PLANE('',#976);
#976 = AXIS2_PLACEMENT_3D('',#977,#978,#979);
#977 = CARTESIAN_POINT('',(2.325,-1.,2.15));
#978 = DIRECTION('',(1.,0.,0.));
#979 = DIRECTION('',(0.,0.,1.));
#980 = DEFINITIONAL_REPRESENTATION('',(#981),#985);
#981 = LINE('',#982,#983);
#982 = CARTESIAN_POINT('',(4.3,0.));
#983 = VECTOR('',#984,1.);
#984 = DIRECTION('',(0.,-1.));
#985 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#986 = ORIENTED_EDGE('',*,*,#987,.T.);
#987 = EDGE_CURVE('',#960,#988,#990,.T.);
#988 = VERTEX_POINT('',#989);
#989 = CARTESIAN_POINT('',(2.075,1.,6.45));
#990 = SURFACE_CURVE('',#991,(#996,#1003),.PCURVE_S1.);
#991 = CIRCLE('',#992,0.25);
#992 = AXIS2_PLACEMENT_3D('',#993,#994,#995);
#993 = CARTESIAN_POINT('',(2.075,0.75,6.45));
#994 = DIRECTION('',(0.,0.,1.));
#995 = DIRECTION('',(0.,-1.,0.));
#996 = PCURVE('',#776,#997);
#997 = DEFINITIONAL_REPRESENTATION('',(#998),#1002);
#998 = CIRCLE('',#999,0.25);
#999 = AXIS2_PLACEMENT_2D('',#1000,#1001);
#1000 = CARTESIAN_POINT('',(4.4,1.75));
#1001 = DIRECTION('',(0.,-1.));
#1002 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1003 = PCURVE('',#1004,#1009);
#1004 = CYLINDRICAL_SURFACE('',#1005,0.25);
#1005 = AXIS2_PLACEMENT_3D('',#1006,#1007,#1008);
#1006 = CARTESIAN_POINT('',(2.075,0.75,2.15));
#1007 = DIRECTION('',(0.,0.,1.));
#1008 = DIRECTION('',(1.,0.,-0.));
#1009 = DEFINITIONAL_REPRESENTATION('',(#1010),#1014);
#1010 = LINE('',#1011,#1012);
#1011 = CARTESIAN_POINT('',(-1.570796326795,4.3));
#1012 = VECTOR('',#1013,1.);
#1013 = DIRECTION('',(1.,0.));
#1014 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1015 = ORIENTED_EDGE('',*,*,#1016,.F.);
#1016 = EDGE_CURVE('',#1017,#988,#1019,.T.);
#1017 = VERTEX_POINT('',#1018);
#1018 = CARTESIAN_POINT('',(-2.075,1.,6.45));
#1019 = SURFACE_CURVE('',#1020,(#1024,#1031),.PCURVE_S1.);
#1020 = LINE('',#1021,#1022);
#1021 = CARTESIAN_POINT('',(-2.325,1.,6.45));
#1022 = VECTOR('',#1023,1.);
#1023 = DIRECTION('',(1.,0.,0.));
#1024 = PCURVE('',#776,#1025);
#1025 = DEFINITIONAL_REPRESENTATION('',(#1026),#1030);
#1026 = LINE('',#1027,#1028);
#1027 = CARTESIAN_POINT('',(0.,2.));
#1028 = VECTOR('',#1029,1.);
#1029 = DIRECTION('',(1.,0.));
#1030 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1031 = PCURVE('',#1032,#1037);
#1032 = PLANE('',#1033);
#1033 = AXIS2_PLACEMENT_3D('',#1034,#1035,#1036);
#1034 = CARTESIAN_POINT('',(-2.325,1.,2.15));
#1035 = DIRECTION('',(0.,1.,0.));
#1036 = DIRECTION('',(0.,0.,1.));
#1037 = DEFINITIONAL_REPRESENTATION('',(#1038),#1042);
#1038 = LINE('',#1039,#1040);
#1039 = CARTESIAN_POINT('',(4.3,0.));
#1040 = VECTOR('',#1041,1.);
#1041 = DIRECTION('',(0.,1.));
#1042 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1043 = ORIENTED_EDGE('',*,*,#1044,.F.);
#1044 = EDGE_CURVE('',#756,#1017,#1045,.T.);
#1045 = SURFACE_CURVE('',#1046,(#1051,#1062),.PCURVE_S1.);
#1046 = CIRCLE('',#1047,0.25);
#1047 = AXIS2_PLACEMENT_3D('',#1048,#1049,#1050);
#1048 = CARTESIAN_POINT('',(-2.075,0.75,6.45));
#1049 = DIRECTION('',(-0.,-0.,-1.));
#1050 = DIRECTION('',(0.,-1.,0.));
#1051 = PCURVE('',#776,#1052);
#1052 = DEFINITIONAL_REPRESENTATION('',(#1053),#1061);
#1053 = ( BOUNDED_CURVE() B_SPLINE_CURVE(2,(#1054,#1055,#1056,#1057,
#1058,#1059,#1060),.UNSPECIFIED.,.T.,.F.) B_SPLINE_CURVE_WITH_KNOTS((1,2
,2,2,2,1),(-2.094395102393,0.,2.094395102393,4.188790204786,
6.28318530718,8.377580409573),.UNSPECIFIED.) CURVE()
GEOMETRIC_REPRESENTATION_ITEM() RATIONAL_B_SPLINE_CURVE((1.,0.5,1.,0.5,
1.,0.5,1.)) REPRESENTATION_ITEM('') );
#1054 = CARTESIAN_POINT('',(0.25,1.5));
#1055 = CARTESIAN_POINT('',(-0.183012701892,1.5));
#1056 = CARTESIAN_POINT('',(3.349364905389E-02,1.875));
#1057 = CARTESIAN_POINT('',(0.25,2.25));
#1058 = CARTESIAN_POINT('',(0.466506350946,1.875));
#1059 = CARTESIAN_POINT('',(0.683012701892,1.5));
#1060 = CARTESIAN_POINT('',(0.25,1.5));
#1061 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1062 = PCURVE('',#858,#1063);
#1063 = DEFINITIONAL_REPRESENTATION('',(#1064),#1068);
#1064 = LINE('',#1065,#1066);
#1065 = CARTESIAN_POINT('',(1.570796326795,4.3));
#1066 = VECTOR('',#1067,1.);
#1067 = DIRECTION('',(-1.,0.));
#1068 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1069 = ADVANCED_FACE('',(#1070),#804,.T.);
#1070 = FACE_BOUND('',#1071,.T.);
#1071 = EDGE_LOOP('',(#1072,#1096,#1117,#1118));
#1072 = ORIENTED_EDGE('',*,*,#1073,.T.);
#1073 = EDGE_CURVE('',#789,#1074,#1076,.T.);
#1074 = VERTEX_POINT('',#1075);
#1075 = CARTESIAN_POINT('',(-2.075,-1.,2.15));
#1076 = SURFACE_CURVE('',#1077,(#1082,#1089),.PCURVE_S1.);
#1077 = CIRCLE('',#1078,0.25);
#1078 = AXIS2_PLACEMENT_3D('',#1079,#1080,#1081);
#1079 = CARTESIAN_POINT('',(-2.075,-0.75,2.15));
#1080 = DIRECTION('',(0.,0.,1.));
#1081 = DIRECTION('',(0.,-1.,0.));
#1082 = PCURVE('',#804,#1083);
#1083 = DEFINITIONAL_REPRESENTATION('',(#1084),#1088);
#1084 = LINE('',#1085,#1086);
#1085 = CARTESIAN_POINT('',(-4.712388980385,0.));
#1086 = VECTOR('',#1087,1.);
#1087 = DIRECTION('',(1.,0.));
#1088 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1089 = PCURVE('',#832,#1090);
#1090 = DEFINITIONAL_REPRESENTATION('',(#1091),#1095);
#1091 = CIRCLE('',#1092,0.25);
#1092 = AXIS2_PLACEMENT_2D('',#1093,#1094);
#1093 = CARTESIAN_POINT('',(0.25,0.25));
#1094 = DIRECTION('',(0.,-1.));
#1095 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1096 = ORIENTED_EDGE('',*,*,#1097,.T.);
#1097 = EDGE_CURVE('',#1074,#875,#1098,.T.);
#1098 = SURFACE_CURVE('',#1099,(#1103,#1110),.PCURVE_S1.);
#1099 = LINE('',#1100,#1101);
#1100 = CARTESIAN_POINT('',(-2.075,-1.,2.15));
#1101 = VECTOR('',#1102,1.);
#1102 = DIRECTION('',(0.,0.,1.));
#1103 = PCURVE('',#804,#1104);
#1104 = DEFINITIONAL_REPRESENTATION('',(#1105),#1109);
#1105 = LINE('',#1106,#1107);
#1106 = CARTESIAN_POINT('',(1.570796326795,0.));
#1107 = VECTOR('',#1108,1.);
#1108 = DIRECTION('',(0.,1.));
#1109 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1110 = PCURVE('',#914,#1111);
#1111 = DEFINITIONAL_REPRESENTATION('',(#1112),#1116);
#1112 = LINE('',#1113,#1114);
#1113 = CARTESIAN_POINT('',(0.,0.25));
#1114 = VECTOR('',#1115,1.);
#1115 = DIRECTION('',(1.,0.));
#1116 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1117 = ORIENTED_EDGE('',*,*,#874,.F.);
#1118 = ORIENTED_EDGE('',*,*,#788,.F.);
#1119 = ADVANCED_FACE('',(#1120),#858,.T.);
#1120 = FACE_BOUND('',#1121,.F.);
#1121 = EDGE_LOOP('',(#1122,#1150,#1171,#1172));
#1122 = ORIENTED_EDGE('',*,*,#1123,.T.);
#1123 = EDGE_CURVE('',#817,#1124,#1126,.T.);
#1124 = VERTEX_POINT('',#1125);
#1125 = CARTESIAN_POINT('',(-2.075,1.,2.15));
#1126 = SURFACE_CURVE('',#1127,(#1132,#1139),.PCURVE_S1.);
#1127 = CIRCLE('',#1128,0.25);
#1128 = AXIS2_PLACEMENT_3D('',#1129,#1130,#1131);
#1129 = CARTESIAN_POINT('',(-2.075,0.75,2.15));
#1130 = DIRECTION('',(-0.,-0.,-1.));
#1131 = DIRECTION('',(0.,-1.,0.));
#1132 = PCURVE('',#858,#1133);
#1133 = DEFINITIONAL_REPRESENTATION('',(#1134),#1138);
#1134 = LINE('',#1135,#1136);
#1135 = CARTESIAN_POINT('',(1.570796326795,-0.));
#1136 = VECTOR('',#1137,1.);
#1137 = DIRECTION('',(-1.,0.));
#1138 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1139 = PCURVE('',#832,#1140);
#1140 = DEFINITIONAL_REPRESENTATION('',(#1141),#1149);
#1141 = ( BOUNDED_CURVE() B_SPLINE_CURVE(2,(#1142,#1143,#1144,#1145,
#1146,#1147,#1148),.UNSPECIFIED.,.T.,.F.) B_SPLINE_CURVE_WITH_KNOTS((1,2
,2,2,2,1),(-2.094395102393,0.,2.094395102393,4.188790204786,
6.28318530718,8.377580409573),.UNSPECIFIED.) CURVE()
GEOMETRIC_REPRESENTATION_ITEM() RATIONAL_B_SPLINE_CURVE((1.,0.5,1.,0.5,
1.,0.5,1.)) REPRESENTATION_ITEM('') );
#1142 = CARTESIAN_POINT('',(0.25,1.5));
#1143 = CARTESIAN_POINT('',(-0.183012701892,1.5));
#1144 = CARTESIAN_POINT('',(3.349364905389E-02,1.875));
#1145 = CARTESIAN_POINT('',(0.25,2.25));
#1146 = CARTESIAN_POINT('',(0.466506350946,1.875));
#1147 = CARTESIAN_POINT('',(0.683012701892,1.5));
#1148 = CARTESIAN_POINT('',(0.25,1.5));
#1149 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1150 = ORIENTED_EDGE('',*,*,#1151,.T.);
#1151 = EDGE_CURVE('',#1124,#1017,#1152,.T.);
#1152 = SURFACE_CURVE('',#1153,(#1157,#1164),.PCURVE_S1.);
#1153 = LINE('',#1154,#1155);
#1154 = CARTESIAN_POINT('',(-2.075,1.,2.15));
#1155 = VECTOR('',#1156,1.);
#1156 = DIRECTION('',(0.,0.,1.));
#1157 = PCURVE('',#858,#1158);
#1158 = DEFINITIONAL_REPRESENTATION('',(#1159),#1163);
#1159 = LINE('',#1160,#1161);
#1160 = CARTESIAN_POINT('',(-1.570796326795,0.));
#1161 = VECTOR('',#1162,1.);
#1162 = DIRECTION('',(-0.,1.));
#1163 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1164 = PCURVE('',#1032,#1165);
#1165 = DEFINITIONAL_REPRESENTATION('',(#1166),#1170);
#1166 = LINE('',#1167,#1168);
#1167 = CARTESIAN_POINT('',(0.,0.25));
#1168 = VECTOR('',#1169,1.);
#1169 = DIRECTION('',(1.,0.));
#1170 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1171 = ORIENTED_EDGE('',*,*,#1044,.F.);
#1172 = ORIENTED_EDGE('',*,*,#844,.F.);
#1173 = ADVANCED_FACE('',(#1174),#832,.F.);
#1174 = FACE_BOUND('',#1175,.F.);
#1175 = EDGE_LOOP('',(#1176,#1177,#1178,#1201,#1229,#1252,#1276,#1297));
#1176 = ORIENTED_EDGE('',*,*,#816,.F.);
#1177 = ORIENTED_EDGE('',*,*,#1073,.T.);
#1178 = ORIENTED_EDGE('',*,*,#1179,.T.);
#1179 = EDGE_CURVE('',#1074,#1180,#1182,.T.);
#1180 = VERTEX_POINT('',#1181);
#1181 = CARTESIAN_POINT('',(2.075,-1.,2.15));
#1182 = SURFACE_CURVE('',#1183,(#1187,#1194),.PCURVE_S1.);
#1183 = LINE('',#1184,#1185);
#1184 = CARTESIAN_POINT('',(-2.325,-1.,2.15));
#1185 = VECTOR('',#1186,1.);
#1186 = DIRECTION('',(1.,0.,0.));
#1187 = PCURVE('',#832,#1188);
#1188 = DEFINITIONAL_REPRESENTATION('',(#1189),#1193);
#1189 = LINE('',#1190,#1191);
#1190 = CARTESIAN_POINT('',(0.,0.));
#1191 = VECTOR('',#1192,1.);
#1192 = DIRECTION('',(1.,0.));
#1193 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1194 = PCURVE('',#914,#1195);
#1195 = DEFINITIONAL_REPRESENTATION('',(#1196),#1200);
#1196 = LINE('',#1197,#1198);
#1197 = CARTESIAN_POINT('',(0.,0.));
#1198 = VECTOR('',#1199,1.);
#1199 = DIRECTION('',(0.,1.));
#1200 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1201 = ORIENTED_EDGE('',*,*,#1202,.F.);
#1202 = EDGE_CURVE('',#1203,#1180,#1205,.T.);
#1203 = VERTEX_POINT('',#1204);
#1204 = CARTESIAN_POINT('',(2.325,-0.75,2.15));
#1205 = SURFACE_CURVE('',#1206,(#1211,#1222),.PCURVE_S1.);
#1206 = CIRCLE('',#1207,0.25);
#1207 = AXIS2_PLACEMENT_3D('',#1208,#1209,#1210);
#1208 = CARTESIAN_POINT('',(2.075,-0.75,2.15));
#1209 = DIRECTION('',(-0.,-0.,-1.));
#1210 = DIRECTION('',(0.,-1.,0.));
#1211 = PCURVE('',#832,#1212);
#1212 = DEFINITIONAL_REPRESENTATION('',(#1213),#1221);
#1213 = ( BOUNDED_CURVE() B_SPLINE_CURVE(2,(#1214,#1215,#1216,#1217,
#1218,#1219,#1220),.UNSPECIFIED.,.T.,.F.) B_SPLINE_CURVE_WITH_KNOTS((1,2
,2,2,2,1),(-2.094395102393,0.,2.094395102393,4.188790204786,
6.28318530718,8.377580409573),.UNSPECIFIED.) CURVE()
GEOMETRIC_REPRESENTATION_ITEM() RATIONAL_B_SPLINE_CURVE((1.,0.5,1.,0.5,
1.,0.5,1.)) REPRESENTATION_ITEM('') );
#1214 = CARTESIAN_POINT('',(4.4,0.));
#1215 = CARTESIAN_POINT('',(3.966987298108,0.));
#1216 = CARTESIAN_POINT('',(4.183493649054,0.375));
#1217 = CARTESIAN_POINT('',(4.4,0.75));
#1218 = CARTESIAN_POINT('',(4.616506350946,0.375));
#1219 = CARTESIAN_POINT('',(4.833012701892,3.885780586188E-16));
#1220 = CARTESIAN_POINT('',(4.4,0.));
#1221 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1222 = PCURVE('',#947,#1223);
#1223 = DEFINITIONAL_REPRESENTATION('',(#1224),#1228);
#1224 = LINE('',#1225,#1226);
#1225 = CARTESIAN_POINT('',(4.712388980385,-0.));
#1226 = VECTOR('',#1227,1.);
#1227 = DIRECTION('',(-1.,0.));
#1228 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1229 = ORIENTED_EDGE('',*,*,#1230,.T.);
#1230 = EDGE_CURVE('',#1203,#1231,#1233,.T.);
#1231 = VERTEX_POINT('',#1232);
#1232 = CARTESIAN_POINT('',(2.325,0.75,2.15));
#1233 = SURFACE_CURVE('',#1234,(#1238,#1245),.PCURVE_S1.);
#1234 = LINE('',#1235,#1236);
#1235 = CARTESIAN_POINT('',(2.325,-1.,2.15));
#1236 = VECTOR('',#1237,1.);
#1237 = DIRECTION('',(0.,1.,0.));
#1238 = PCURVE('',#832,#1239);
#1239 = DEFINITIONAL_REPRESENTATION('',(#1240),#1244);
#1240 = LINE('',#1241,#1242);
#1241 = CARTESIAN_POINT('',(4.65,0.));
#1242 = VECTOR('',#1243,1.);
#1243 = DIRECTION('',(0.,1.));
#1244 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1245 = PCURVE('',#975,#1246);
#1246 = DEFINITIONAL_REPRESENTATION('',(#1247),#1251);
#1247 = LINE('',#1248,#1249);
#1248 = CARTESIAN_POINT('',(0.,0.));
#1249 = VECTOR('',#1250,1.);
#1250 = DIRECTION('',(0.,-1.));
#1251 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1252 = ORIENTED_EDGE('',*,*,#1253,.T.);
#1253 = EDGE_CURVE('',#1231,#1254,#1256,.T.);
#1254 = VERTEX_POINT('',#1255);
#1255 = CARTESIAN_POINT('',(2.075,1.,2.15));
#1256 = SURFACE_CURVE('',#1257,(#1262,#1269),.PCURVE_S1.);
#1257 = CIRCLE('',#1258,0.25);
#1258 = AXIS2_PLACEMENT_3D('',#1259,#1260,#1261);
#1259 = CARTESIAN_POINT('',(2.075,0.75,2.15));
#1260 = DIRECTION('',(0.,0.,1.));
#1261 = DIRECTION('',(0.,-1.,0.));
#1262 = PCURVE('',#832,#1263);
#1263 = DEFINITIONAL_REPRESENTATION('',(#1264),#1268);
#1264 = CIRCLE('',#1265,0.25);
#1265 = AXIS2_PLACEMENT_2D('',#1266,#1267);
#1266 = CARTESIAN_POINT('',(4.4,1.75));
#1267 = DIRECTION('',(0.,-1.));
#1268 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1269 = PCURVE('',#1004,#1270);
#1270 = DEFINITIONAL_REPRESENTATION('',(#1271),#1275);
#1271 = LINE('',#1272,#1273);
#1272 = CARTESIAN_POINT('',(-1.570796326795,0.));
#1273 = VECTOR('',#1274,1.);
#1274 = DIRECTION('',(1.,0.));
#1275 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1276 = ORIENTED_EDGE('',*,*,#1277,.F.);
#1277 = EDGE_CURVE('',#1124,#1254,#1278,.T.);
#1278 = SURFACE_CURVE('',#1279,(#1283,#1290),.PCURVE_S1.);
#1279 = LINE('',#1280,#1281);
#1280 = CARTESIAN_POINT('',(-2.325,1.,2.15));
#1281 = VECTOR('',#1282,1.);
#1282 = DIRECTION('',(1.,0.,0.));
#1283 = PCURVE('',#832,#1284);
#1284 = DEFINITIONAL_REPRESENTATION('',(#1285),#1289);
#1285 = LINE('',#1286,#1287);
#1286 = CARTESIAN_POINT('',(0.,2.));
#1287 = VECTOR('',#1288,1.);
#1288 = DIRECTION('',(1.,0.));
#1289 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1290 = PCURVE('',#1032,#1291);
#1291 = DEFINITIONAL_REPRESENTATION('',(#1292),#1296);
#1292 = LINE('',#1293,#1294);
#1293 = CARTESIAN_POINT('',(0.,0.));
#1294 = VECTOR('',#1295,1.);
#1295 = DIRECTION('',(0.,1.));
#1296 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1297 = ORIENTED_EDGE('',*,*,#1123,.F.);
#1298 = ADVANCED_FACE('',(#1299),#914,.F.);
#1299 = FACE_BOUND('',#1300,.F.);
#1300 = EDGE_LOOP('',(#1301,#1302,#1303,#1304));
#1301 = ORIENTED_EDGE('',*,*,#1179,.F.);
#1302 = ORIENTED_EDGE('',*,*,#1097,.T.);
#1303 = ORIENTED_EDGE('',*,*,#898,.T.);
#1304 = ORIENTED_EDGE('',*,*,#1305,.F.);
#1305 = EDGE_CURVE('',#1180,#899,#1306,.T.);
#1306 = SURFACE_CURVE('',#1307,(#1311,#1318),.PCURVE_S1.);
#1307 = LINE('',#1308,#1309);
#1308 = CARTESIAN_POINT('',(2.075,-1.,2.15));
#1309 = VECTOR('',#1310,1.);
#1310 = DIRECTION('',(0.,0.,1.));
#1311 = PCURVE('',#914,#1312);
#1312 = DEFINITIONAL_REPRESENTATION('',(#1313),#1317);
#1313 = LINE('',#1314,#1315);
#1314 = CARTESIAN_POINT('',(0.,4.4));
#1315 = VECTOR('',#1316,1.);
#1316 = DIRECTION('',(1.,0.));
#1317 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1318 = PCURVE('',#947,#1319);
#1319 = DEFINITIONAL_REPRESENTATION('',(#1320),#1324);
#1320 = LINE('',#1321,#1322);
#1321 = CARTESIAN_POINT('',(-1.570796326795,0.));
#1322 = VECTOR('',#1323,1.);
#1323 = DIRECTION('',(-0.,1.));
#1324 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1325 = ADVANCED_FACE('',(#1326),#1032,.T.);
#1326 = FACE_BOUND('',#1327,.T.);
#1327 = EDGE_LOOP('',(#1328,#1329,#1330,#1331));
#1328 = ORIENTED_EDGE('',*,*,#1277,.F.);
#1329 = ORIENTED_EDGE('',*,*,#1151,.T.);
#1330 = ORIENTED_EDGE('',*,*,#1016,.T.);
#1331 = ORIENTED_EDGE('',*,*,#1332,.F.);
#1332 = EDGE_CURVE('',#1254,#988,#1333,.T.);
#1333 = SURFACE_CURVE('',#1334,(#1338,#1345),.PCURVE_S1.);
#1334 = LINE('',#1335,#1336);
#1335 = CARTESIAN_POINT('',(2.075,1.,2.15));
#1336 = VECTOR('',#1337,1.);
#1337 = DIRECTION('',(0.,0.,1.));
#1338 = PCURVE('',#1032,#1339);
#1339 = DEFINITIONAL_REPRESENTATION('',(#1340),#1344);
#1340 = LINE('',#1341,#1342);
#1341 = CARTESIAN_POINT('',(0.,4.4));
#1342 = VECTOR('',#1343,1.);
#1343 = DIRECTION('',(1.,0.));
#1344 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1345 = PCURVE('',#1004,#1346);
#1346 = DEFINITIONAL_REPRESENTATION('',(#1347),#1351);
#1347 = LINE('',#1348,#1349);
#1348 = CARTESIAN_POINT('',(1.570796326795,0.));
#1349 = VECTOR('',#1350,1.);
#1350 = DIRECTION('',(0.,1.));
#1351 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1352 = ADVANCED_FACE('',(#1353),#947,.T.);
#1353 = FACE_BOUND('',#1354,.F.);
#1354 = EDGE_LOOP('',(#1355,#1356,#1357,#1358));
#1355 = ORIENTED_EDGE('',*,*,#1202,.T.);
#1356 = ORIENTED_EDGE('',*,*,#1305,.T.);
#1357 = ORIENTED_EDGE('',*,*,#926,.F.);
#1358 = ORIENTED_EDGE('',*,*,#1359,.F.);
#1359 = EDGE_CURVE('',#1203,#927,#1360,.T.);
#1360 = SURFACE_CURVE('',#1361,(#1365,#1372),.PCURVE_S1.);
#1361 = LINE('',#1362,#1363);
#1362 = CARTESIAN_POINT('',(2.325,-0.75,2.15));
#1363 = VECTOR('',#1364,1.);
#1364 = DIRECTION('',(0.,0.,1.));
#1365 = PCURVE('',#947,#1366);
#1366 = DEFINITIONAL_REPRESENTATION('',(#1367),#1371);
#1367 = LINE('',#1368,#1369);
#1368 = CARTESIAN_POINT('',(-0.,0.));
#1369 = VECTOR('',#1370,1.);
#1370 = DIRECTION('',(-0.,1.));
#1371 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1372 = PCURVE('',#975,#1373);
#1373 = DEFINITIONAL_REPRESENTATION('',(#1374),#1378);
#1374 = LINE('',#1375,#1376);
#1375 = CARTESIAN_POINT('',(0.,-0.25));
#1376 = VECTOR('',#1377,1.);
#1377 = DIRECTION('',(1.,0.));
#1378 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1379 = ADVANCED_FACE('',(#1380),#1004,.T.);
#1380 = FACE_BOUND('',#1381,.T.);
#1381 = EDGE_LOOP('',(#1382,#1383,#1384,#1385));
#1382 = ORIENTED_EDGE('',*,*,#1253,.T.);
#1383 = ORIENTED_EDGE('',*,*,#1332,.T.);
#1384 = ORIENTED_EDGE('',*,*,#987,.F.);
#1385 = ORIENTED_EDGE('',*,*,#1386,.F.);
#1386 = EDGE_CURVE('',#1231,#960,#1387,.T.);
#1387 = SURFACE_CURVE('',#1388,(#1392,#1399),.PCURVE_S1.);
#1388 = LINE('',#1389,#1390);
#1389 = CARTESIAN_POINT('',(2.325,0.75,2.15));
#1390 = VECTOR('',#1391,1.);
#1391 = DIRECTION('',(0.,0.,1.));
#1392 = PCURVE('',#1004,#1393);
#1393 = DEFINITIONAL_REPRESENTATION('',(#1394),#1398);
#1394 = LINE('',#1395,#1396);
#1395 = CARTESIAN_POINT('',(0.,0.));
#1396 = VECTOR('',#1397,1.);
#1397 = DIRECTION('',(0.,1.));
#1398 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1399 = PCURVE('',#975,#1400);
#1400 = DEFINITIONAL_REPRESENTATION('',(#1401),#1405);
#1401 = LINE('',#1402,#1403);
#1402 = CARTESIAN_POINT('',(0.,-1.75));
#1403 = VECTOR('',#1404,1.);
#1404 = DIRECTION('',(1.,0.));
#1405 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1406 = ADVANCED_FACE('',(#1407),#975,.T.);
#1407 = FACE_BOUND('',#1408,.T.);
#1408 = EDGE_LOOP('',(#1409,#1410,#1411,#1412));
#1409 = ORIENTED_EDGE('',*,*,#959,.F.);
#1410 = ORIENTED_EDGE('',*,*,#1359,.F.);
#1411 = ORIENTED_EDGE('',*,*,#1230,.T.);
#1412 = ORIENTED_EDGE('',*,*,#1386,.T.);
#1413 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3)
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#1417))
GLOBAL_UNIT_ASSIGNED_CONTEXT((#1414,#1415,#1416)) REPRESENTATION_CONTEXT
('Context #1','3D Context with UNIT and UNCERTAINTY') );
#1414 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) );
#1415 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) );
#1416 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() );
#1417 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-07),#1414,
'distance_accuracy_value','confusion accuracy');
#1418 = CONTEXT_DEPENDENT_SHAPE_REPRESENTATION(#1419,#1421);
#1419 = ( REPRESENTATION_RELATIONSHIP('','',#746,#10)
REPRESENTATION_RELATIONSHIP_WITH_TRANSFORMATION(#1420)
SHAPE_REPRESENTATION_RELATIONSHIP() );
#1420 = ITEM_DEFINED_TRANSFORMATION('','',#11,#23);
#1421 = PRODUCT_DEFINITION_SHAPE('Placement','Placement of an item',
#1422);
#1422 = NEXT_ASSEMBLY_USAGE_OCCURRENCE('58',
'3650c6ce-93c7-11f1-9448-04bad62a3558','',#5,#741,$);
#1423 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#743));
#1424 = SHAPE_DEFINITION_REPRESENTATION(#1425,#1431);
#1425 = PRODUCT_DEFINITION_SHAPE('','',#1426);
#1426 = PRODUCT_DEFINITION('design','',#1427,#1430);
#1427 = PRODUCT_DEFINITION_FORMATION('','',#1428);
#1428 = PRODUCT('365114ab-93c7-11f1-8c4c-04bad62a3558',
'365114ab-93c7-11f1-8c4c-04bad62a3558','',(#1429));
#1429 = PRODUCT_CONTEXT('',#2,'mechanical');
#1430 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design');
#1431 = ADVANCED_BREP_SHAPE_REPRESENTATION('',(#11,#1432),#1762);
#1432 = MANIFOLD_SOLID_BREP('',#1433);
#1433 = CLOSED_SHELL('',(#1434,#1554,#1654,#1701,#1748,#1755));
#1434 = ADVANCED_FACE('',(#1435),#1449,.F.);
#1435 = FACE_BOUND('',#1436,.F.);
#1436 = EDGE_LOOP('',(#1437,#1472,#1500,#1528));
#1437 = ORIENTED_EDGE('',*,*,#1438,.F.);
#1438 = EDGE_CURVE('',#1439,#1441,#1443,.T.);
#1439 = VERTEX_POINT('',#1440);
#1440 = CARTESIAN_POINT('',(-1.5,-0.95,2.8));
#1441 = VERTEX_POINT('',#1442);
#1442 = CARTESIAN_POINT('',(-1.5,-0.95,5.8));
#1443 = SURFACE_CURVE('',#1444,(#1448,#1460),.PCURVE_S1.);
#1444 = LINE('',#1445,#1446);
#1445 = CARTESIAN_POINT('',(-1.5,-0.95,2.8));
#1446 = VECTOR('',#1447,1.);
#1447 = DIRECTION('',(0.,0.,1.));
#1448 = PCURVE('',#1449,#1454);
#1449 = PLANE('',#1450);
#1450 = AXIS2_PLACEMENT_3D('',#1451,#1452,#1453);
#1451 = CARTESIAN_POINT('',(-1.5,-0.95,2.8));
#1452 = DIRECTION('',(1.,0.,0.));
#1453 = DIRECTION('',(0.,0.,1.));
#1454 = DEFINITIONAL_REPRESENTATION('',(#1455),#1459);
#1455 = LINE('',#1456,#1457);
#1456 = CARTESIAN_POINT('',(0.,0.));
#1457 = VECTOR('',#1458,1.);
#1458 = DIRECTION('',(1.,0.));
#1459 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1460 = PCURVE('',#1461,#1466);
#1461 = PLANE('',#1462);
#1462 = AXIS2_PLACEMENT_3D('',#1463,#1464,#1465);
#1463 = CARTESIAN_POINT('',(-1.5,-0.95,2.8));
#1464 = DIRECTION('',(0.,1.,0.));
#1465 = DIRECTION('',(0.,0.,1.));
#1466 = DEFINITIONAL_REPRESENTATION('',(#1467),#1471);
#1467 = LINE('',#1468,#1469);
#1468 = CARTESIAN_POINT('',(0.,0.));
#1469 = VECTOR('',#1470,1.);
#1470 = DIRECTION('',(1.,0.));
#1471 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1472 = ORIENTED_EDGE('',*,*,#1473,.T.);
#1473 = EDGE_CURVE('',#1439,#1474,#1476,.T.);
#1474 = VERTEX_POINT('',#1475);
#1475 = CARTESIAN_POINT('',(-1.5,-0.83,2.8));
#1476 = SURFACE_CURVE('',#1477,(#1481,#1488),.PCURVE_S1.);
#1477 = LINE('',#1478,#1479);
#1478 = CARTESIAN_POINT('',(-1.5,-0.95,2.8));
#1479 = VECTOR('',#1480,1.);
#1480 = DIRECTION('',(0.,1.,0.));
#1481 = PCURVE('',#1449,#1482);
#1482 = DEFINITIONAL_REPRESENTATION('',(#1483),#1487);
#1483 = LINE('',#1484,#1485);
#1484 = CARTESIAN_POINT('',(0.,0.));
#1485 = VECTOR('',#1486,1.);
#1486 = DIRECTION('',(0.,-1.));
#1487 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1488 = PCURVE('',#1489,#1494);
#1489 = PLANE('',#1490);
#1490 = AXIS2_PLACEMENT_3D('',#1491,#1492,#1493);
#1491 = CARTESIAN_POINT('',(-1.5,-0.95,2.8));
#1492 = DIRECTION('',(0.,0.,1.));
#1493 = DIRECTION('',(1.,0.,0.));
#1494 = DEFINITIONAL_REPRESENTATION('',(#1495),#1499);
#1495 = LINE('',#1496,#1497);
#1496 = CARTESIAN_POINT('',(0.,0.));
#1497 = VECTOR('',#1498,1.);
#1498 = DIRECTION('',(0.,1.));
#1499 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1500 = ORIENTED_EDGE('',*,*,#1501,.T.);
#1501 = EDGE_CURVE('',#1474,#1502,#1504,.T.);
#1502 = VERTEX_POINT('',#1503);
#1503 = CARTESIAN_POINT('',(-1.5,-0.83,5.8));
#1504 = SURFACE_CURVE('',#1505,(#1509,#1516),.PCURVE_S1.);
#1505 = LINE('',#1506,#1507);
#1506 = CARTESIAN_POINT('',(-1.5,-0.83,2.8));
#1507 = VECTOR('',#1508,1.);
#1508 = DIRECTION('',(0.,0.,1.));
#1509 = PCURVE('',#1449,#1510);
#1510 = DEFINITIONAL_REPRESENTATION('',(#1511),#1515);
#1511 = LINE('',#1512,#1513);
#1512 = CARTESIAN_POINT('',(0.,-0.12));
#1513 = VECTOR('',#1514,1.);
#1514 = DIRECTION('',(1.,0.));
#1515 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1516 = PCURVE('',#1517,#1522);
#1517 = PLANE('',#1518);
#1518 = AXIS2_PLACEMENT_3D('',#1519,#1520,#1521);
#1519 = CARTESIAN_POINT('',(-1.5,-0.83,2.8));
#1520 = DIRECTION('',(0.,1.,0.));
#1521 = DIRECTION('',(0.,0.,1.));
#1522 = DEFINITIONAL_REPRESENTATION('',(#1523),#1527);
#1523 = LINE('',#1524,#1525);
#1524 = CARTESIAN_POINT('',(0.,0.));
#1525 = VECTOR('',#1526,1.);
#1526 = DIRECTION('',(1.,0.));
#1527 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1528 = ORIENTED_EDGE('',*,*,#1529,.F.);
#1529 = EDGE_CURVE('',#1441,#1502,#1530,.T.);
#1530 = SURFACE_CURVE('',#1531,(#1535,#1542),.PCURVE_S1.);
#1531 = LINE('',#1532,#1533);
#1532 = CARTESIAN_POINT('',(-1.5,-0.95,5.8));
#1533 = VECTOR('',#1534,1.);
#1534 = DIRECTION('',(0.,1.,0.));
#1535 = PCURVE('',#1449,#1536);
#1536 = DEFINITIONAL_REPRESENTATION('',(#1537),#1541);
#1537 = LINE('',#1538,#1539);
#1538 = CARTESIAN_POINT('',(3.,0.));
#1539 = VECTOR('',#1540,1.);
#1540 = DIRECTION('',(0.,-1.));
#1541 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1542 = PCURVE('',#1543,#1548);
#1543 = PLANE('',#1544);
#1544 = AXIS2_PLACEMENT_3D('',#1545,#1546,#1547);
#1545 = CARTESIAN_POINT('',(-1.5,-0.95,5.8));
#1546 = DIRECTION('',(0.,0.,1.));
#1547 = DIRECTION('',(1.,0.,0.));
#1548 = DEFINITIONAL_REPRESENTATION('',(#1549),#1553);
#1549 = LINE('',#1550,#1551);
#1550 = CARTESIAN_POINT('',(0.,0.));
#1551 = VECTOR('',#1552,1.);
#1552 = DIRECTION('',(0.,1.));
#1553 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1554 = ADVANCED_FACE('',(#1555),#1569,.T.);
#1555 = FACE_BOUND('',#1556,.T.);
#1556 = EDGE_LOOP('',(#1557,#1587,#1610,#1633));
#1557 = ORIENTED_EDGE('',*,*,#1558,.F.);
#1558 = EDGE_CURVE('',#1559,#1561,#1563,.T.);
#1559 = VERTEX_POINT('',#1560);
#1560 = CARTESIAN_POINT('',(1.5,-0.95,2.8));
#1561 = VERTEX_POINT('',#1562);
#1562 = CARTESIAN_POINT('',(1.5,-0.95,5.8));
#1563 = SURFACE_CURVE('',#1564,(#1568,#1580),.PCURVE_S1.);
#1564 = LINE('',#1565,#1566);
#1565 = CARTESIAN_POINT('',(1.5,-0.95,2.8));
#1566 = VECTOR('',#1567,1.);
#1567 = DIRECTION('',(0.,0.,1.));
#1568 = PCURVE('',#1569,#1574);
#1569 = PLANE('',#1570);
#1570 = AXIS2_PLACEMENT_3D('',#1571,#1572,#1573);
#1571 = CARTESIAN_POINT('',(1.5,-0.95,2.8));
#1572 = DIRECTION('',(1.,0.,0.));
#1573 = DIRECTION('',(0.,0.,1.));
#1574 = DEFINITIONAL_REPRESENTATION('',(#1575),#1579);
#1575 = LINE('',#1576,#1577);
#1576 = CARTESIAN_POINT('',(0.,0.));
#1577 = VECTOR('',#1578,1.);
#1578 = DIRECTION('',(1.,0.));
#1579 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1580 = PCURVE('',#1461,#1581);
#1581 = DEFINITIONAL_REPRESENTATION('',(#1582),#1586);
#1582 = LINE('',#1583,#1584);
#1583 = CARTESIAN_POINT('',(0.,3.));
#1584 = VECTOR('',#1585,1.);
#1585 = DIRECTION('',(1.,0.));
#1586 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1587 = ORIENTED_EDGE('',*,*,#1588,.T.);
#1588 = EDGE_CURVE('',#1559,#1589,#1591,.T.);
#1589 = VERTEX_POINT('',#1590);
#1590 = CARTESIAN_POINT('',(1.5,-0.83,2.8));
#1591 = SURFACE_CURVE('',#1592,(#1596,#1603),.PCURVE_S1.);
#1592 = LINE('',#1593,#1594);
#1593 = CARTESIAN_POINT('',(1.5,-0.95,2.8));
#1594 = VECTOR('',#1595,1.);
#1595 = DIRECTION('',(0.,1.,0.));
#1596 = PCURVE('',#1569,#1597);
#1597 = DEFINITIONAL_REPRESENTATION('',(#1598),#1602);
#1598 = LINE('',#1599,#1600);
#1599 = CARTESIAN_POINT('',(0.,0.));
#1600 = VECTOR('',#1601,1.);
#1601 = DIRECTION('',(0.,-1.));
#1602 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1603 = PCURVE('',#1489,#1604);
#1604 = DEFINITIONAL_REPRESENTATION('',(#1605),#1609);
#1605 = LINE('',#1606,#1607);
#1606 = CARTESIAN_POINT('',(3.,0.));
#1607 = VECTOR('',#1608,1.);
#1608 = DIRECTION('',(0.,1.));
#1609 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1610 = ORIENTED_EDGE('',*,*,#1611,.T.);
#1611 = EDGE_CURVE('',#1589,#1612,#1614,.T.);
#1612 = VERTEX_POINT('',#1613);
#1613 = CARTESIAN_POINT('',(1.5,-0.83,5.8));
#1614 = SURFACE_CURVE('',#1615,(#1619,#1626),.PCURVE_S1.);
#1615 = LINE('',#1616,#1617);
#1616 = CARTESIAN_POINT('',(1.5,-0.83,2.8));
#1617 = VECTOR('',#1618,1.);
#1618 = DIRECTION('',(0.,0.,1.));
#1619 = PCURVE('',#1569,#1620);
#1620 = DEFINITIONAL_REPRESENTATION('',(#1621),#1625);
#1621 = LINE('',#1622,#1623);
#1622 = CARTESIAN_POINT('',(0.,-0.12));
#1623 = VECTOR('',#1624,1.);
#1624 = DIRECTION('',(1.,0.));
#1625 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1626 = PCURVE('',#1517,#1627);
#1627 = DEFINITIONAL_REPRESENTATION('',(#1628),#1632);
#1628 = LINE('',#1629,#1630);
#1629 = CARTESIAN_POINT('',(0.,3.));
#1630 = VECTOR('',#1631,1.);
#1631 = DIRECTION('',(1.,0.));
#1632 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1633 = ORIENTED_EDGE('',*,*,#1634,.F.);
#1634 = EDGE_CURVE('',#1561,#1612,#1635,.T.);
#1635 = SURFACE_CURVE('',#1636,(#1640,#1647),.PCURVE_S1.);
#1636 = LINE('',#1637,#1638);
#1637 = CARTESIAN_POINT('',(1.5,-0.95,5.8));
#1638 = VECTOR('',#1639,1.);
#1639 = DIRECTION('',(0.,1.,0.));
#1640 = PCURVE('',#1569,#1641);
#1641 = DEFINITIONAL_REPRESENTATION('',(#1642),#1646);
#1642 = LINE('',#1643,#1644);
#1643 = CARTESIAN_POINT('',(3.,0.));
#1644 = VECTOR('',#1645,1.);
#1645 = DIRECTION('',(0.,-1.));
#1646 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1647 = PCURVE('',#1543,#1648);
#1648 = DEFINITIONAL_REPRESENTATION('',(#1649),#1653);
#1649 = LINE('',#1650,#1651);
#1650 = CARTESIAN_POINT('',(3.,0.));
#1651 = VECTOR('',#1652,1.);
#1652 = DIRECTION('',(0.,1.));
#1653 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1654 = ADVANCED_FACE('',(#1655),#1461,.F.);
#1655 = FACE_BOUND('',#1656,.F.);
#1656 = EDGE_LOOP('',(#1657,#1678,#1679,#1700));
#1657 = ORIENTED_EDGE('',*,*,#1658,.F.);
#1658 = EDGE_CURVE('',#1439,#1559,#1659,.T.);
#1659 = SURFACE_CURVE('',#1660,(#1664,#1671),.PCURVE_S1.);
#1660 = LINE('',#1661,#1662);
#1661 = CARTESIAN_POINT('',(-1.5,-0.95,2.8));
#1662 = VECTOR('',#1663,1.);
#1663 = DIRECTION('',(1.,0.,0.));
#1664 = PCURVE('',#1461,#1665);
#1665 = DEFINITIONAL_REPRESENTATION('',(#1666),#1670);
#1666 = LINE('',#1667,#1668);
#1667 = CARTESIAN_POINT('',(0.,0.));
#1668 = VECTOR('',#1669,1.);
#1669 = DIRECTION('',(0.,1.));
#1670 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1671 = PCURVE('',#1489,#1672);
#1672 = DEFINITIONAL_REPRESENTATION('',(#1673),#1677);
#1673 = LINE('',#1674,#1675);
#1674 = CARTESIAN_POINT('',(0.,0.));
#1675 = VECTOR('',#1676,1.);
#1676 = DIRECTION('',(1.,0.));
#1677 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1678 = ORIENTED_EDGE('',*,*,#1438,.T.);
#1679 = ORIENTED_EDGE('',*,*,#1680,.T.);
#1680 = EDGE_CURVE('',#1441,#1561,#1681,.T.);
#1681 = SURFACE_CURVE('',#1682,(#1686,#1693),.PCURVE_S1.);
#1682 = LINE('',#1683,#1684);
#1683 = CARTESIAN_POINT('',(-1.5,-0.95,5.8));
#1684 = VECTOR('',#1685,1.);
#1685 = DIRECTION('',(1.,0.,0.));
#1686 = PCURVE('',#1461,#1687);
#1687 = DEFINITIONAL_REPRESENTATION('',(#1688),#1692);
#1688 = LINE('',#1689,#1690);
#1689 = CARTESIAN_POINT('',(3.,0.));
#1690 = VECTOR('',#1691,1.);
#1691 = DIRECTION('',(0.,1.));
#1692 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1693 = PCURVE('',#1543,#1694);
#1694 = DEFINITIONAL_REPRESENTATION('',(#1695),#1699);
#1695 = LINE('',#1696,#1697);
#1696 = CARTESIAN_POINT('',(0.,0.));
#1697 = VECTOR('',#1698,1.);
#1698 = DIRECTION('',(1.,0.));
#1699 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1700 = ORIENTED_EDGE('',*,*,#1558,.F.);
#1701 = ADVANCED_FACE('',(#1702),#1517,.T.);
#1702 = FACE_BOUND('',#1703,.T.);
#1703 = EDGE_LOOP('',(#1704,#1725,#1726,#1747));
#1704 = ORIENTED_EDGE('',*,*,#1705,.F.);
#1705 = EDGE_CURVE('',#1474,#1589,#1706,.T.);
#1706 = SURFACE_CURVE('',#1707,(#1711,#1718),.PCURVE_S1.);
#1707 = LINE('',#1708,#1709);
#1708 = CARTESIAN_POINT('',(-1.5,-0.83,2.8));
#1709 = VECTOR('',#1710,1.);
#1710 = DIRECTION('',(1.,0.,0.));
#1711 = PCURVE('',#1517,#1712);
#1712 = DEFINITIONAL_REPRESENTATION('',(#1713),#1717);
#1713 = LINE('',#1714,#1715);
#1714 = CARTESIAN_POINT('',(0.,0.));
#1715 = VECTOR('',#1716,1.);
#1716 = DIRECTION('',(0.,1.));
#1717 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1718 = PCURVE('',#1489,#1719);
#1719 = DEFINITIONAL_REPRESENTATION('',(#1720),#1724);
#1720 = LINE('',#1721,#1722);
#1721 = CARTESIAN_POINT('',(0.,0.12));
#1722 = VECTOR('',#1723,1.);
#1723 = DIRECTION('',(1.,0.));
#1724 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1725 = ORIENTED_EDGE('',*,*,#1501,.T.);
#1726 = ORIENTED_EDGE('',*,*,#1727,.T.);
#1727 = EDGE_CURVE('',#1502,#1612,#1728,.T.);
#1728 = SURFACE_CURVE('',#1729,(#1733,#1740),.PCURVE_S1.);
#1729 = LINE('',#1730,#1731);
#1730 = CARTESIAN_POINT('',(-1.5,-0.83,5.8));
#1731 = VECTOR('',#1732,1.);
#1732 = DIRECTION('',(1.,0.,0.));
#1733 = PCURVE('',#1517,#1734);
#1734 = DEFINITIONAL_REPRESENTATION('',(#1735),#1739);
#1735 = LINE('',#1736,#1737);
#1736 = CARTESIAN_POINT('',(3.,0.));
#1737 = VECTOR('',#1738,1.);
#1738 = DIRECTION('',(0.,1.));
#1739 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1740 = PCURVE('',#1543,#1741);
#1741 = DEFINITIONAL_REPRESENTATION('',(#1742),#1746);
#1742 = LINE('',#1743,#1744);
#1743 = CARTESIAN_POINT('',(0.,0.12));
#1744 = VECTOR('',#1745,1.);
#1745 = DIRECTION('',(1.,0.));
#1746 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1747 = ORIENTED_EDGE('',*,*,#1611,.F.);
#1748 = ADVANCED_FACE('',(#1749),#1489,.F.);
#1749 = FACE_BOUND('',#1750,.F.);
#1750 = EDGE_LOOP('',(#1751,#1752,#1753,#1754));
#1751 = ORIENTED_EDGE('',*,*,#1473,.F.);
#1752 = ORIENTED_EDGE('',*,*,#1658,.T.);
#1753 = ORIENTED_EDGE('',*,*,#1588,.T.);
#1754 = ORIENTED_EDGE('',*,*,#1705,.F.);
#1755 = ADVANCED_FACE('',(#1756),#1543,.T.);
#1756 = FACE_BOUND('',#1757,.T.);
#1757 = EDGE_LOOP('',(#1758,#1759,#1760,#1761));
#1758 = ORIENTED_EDGE('',*,*,#1529,.F.);
#1759 = ORIENTED_EDGE('',*,*,#1680,.T.);
#1760 = ORIENTED_EDGE('',*,*,#1634,.T.);
#1761 = ORIENTED_EDGE('',*,*,#1727,.F.);
#1762 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3)
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#1766))
GLOBAL_UNIT_ASSIGNED_CONTEXT((#1763,#1764,#1765)) REPRESENTATION_CONTEXT
('Context #1','3D Context with UNIT and UNCERTAINTY') );
#1763 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) );
#1764 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) );
#1765 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() );
#1766 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-07),#1763,
'distance_accuracy_value','confusion accuracy');
#1767 = CONTEXT_DEPENDENT_SHAPE_REPRESENTATION(#1768,#1770);
#1768 = ( REPRESENTATION_RELATIONSHIP('','',#1431,#10)
REPRESENTATION_RELATIONSHIP_WITH_TRANSFORMATION(#1769)
SHAPE_REPRESENTATION_RELATIONSHIP() );
#1769 = ITEM_DEFINED_TRANSFORMATION('','',#11,#27);
#1770 = PRODUCT_DEFINITION_SHAPE('Placement','Placement of an item',
#1771);
#1771 = NEXT_ASSEMBLY_USAGE_OCCURRENCE('59',
'365114ab-93c7-11f1-8c4c-04bad62a3558','',#5,#1426,$);
#1772 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#1428));
#1773 = SHAPE_DEFINITION_REPRESENTATION(#1774,#1780);
#1774 = PRODUCT_DEFINITION_SHAPE('','',#1775);
#1775 = PRODUCT_DEFINITION('design','',#1776,#1779);
#1776 = PRODUCT_DEFINITION_FORMATION('','',#1777);
#1777 = PRODUCT('36516288-93c7-11f1-ba5d-04bad62a3558',
'36516288-93c7-11f1-ba5d-04bad62a3558','',(#1778));
#1778 = PRODUCT_CONTEXT('',#2,'mechanical');
#1779 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design');
#1780 = ADVANCED_BREP_SHAPE_REPRESENTATION('',(#11,#1781),#2111);
#1781 = MANIFOLD_SOLID_BREP('',#1782);
#1782 = CLOSED_SHELL('',(#1783,#1903,#2003,#2050,#2097,#2104));
#1783 = ADVANCED_FACE('',(#1784),#1798,.F.);
#1784 = FACE_BOUND('',#1785,.F.);
#1785 = EDGE_LOOP('',(#1786,#1821,#1849,#1877));
#1786 = ORIENTED_EDGE('',*,*,#1787,.F.);
#1787 = EDGE_CURVE('',#1788,#1790,#1792,.T.);
#1788 = VERTEX_POINT('',#1789);
#1789 = CARTESIAN_POINT('',(-2.27,-1.1,2.5));
#1790 = VERTEX_POINT('',#1791);
#1791 = CARTESIAN_POINT('',(-2.27,-1.1,6.05));
#1792 = SURFACE_CURVE('',#1793,(#1797,#1809),.PCURVE_S1.);
#1793 = LINE('',#1794,#1795);
#1794 = CARTESIAN_POINT('',(-2.27,-1.1,2.5));
#1795 = VECTOR('',#1796,1.);
#1796 = DIRECTION('',(0.,0.,1.));
#1797 = PCURVE('',#1798,#1803);
#1798 = PLANE('',#1799);
#1799 = AXIS2_PLACEMENT_3D('',#1800,#1801,#1802);
#1800 = CARTESIAN_POINT('',(-2.27,-1.1,2.5));
#1801 = DIRECTION('',(1.,0.,0.));
#1802 = DIRECTION('',(0.,0.,1.));
#1803 = DEFINITIONAL_REPRESENTATION('',(#1804),#1808);
#1804 = LINE('',#1805,#1806);
#1805 = CARTESIAN_POINT('',(0.,0.));
#1806 = VECTOR('',#1807,1.);
#1807 = DIRECTION('',(1.,0.));
#1808 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1809 = PCURVE('',#1810,#1815);
#1810 = PLANE('',#1811);
#1811 = AXIS2_PLACEMENT_3D('',#1812,#1813,#1814);
#1812 = CARTESIAN_POINT('',(-2.27,-1.1,2.5));
#1813 = DIRECTION('',(0.,1.,0.));
#1814 = DIRECTION('',(0.,0.,1.));
#1815 = DEFINITIONAL_REPRESENTATION('',(#1816),#1820);
#1816 = LINE('',#1817,#1818);
#1817 = CARTESIAN_POINT('',(0.,0.));
#1818 = VECTOR('',#1819,1.);
#1819 = DIRECTION('',(1.,0.));
#1820 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1821 = ORIENTED_EDGE('',*,*,#1822,.T.);
#1822 = EDGE_CURVE('',#1788,#1823,#1825,.T.);
#1823 = VERTEX_POINT('',#1824);
#1824 = CARTESIAN_POINT('',(-2.27,-1.02,2.5));
#1825 = SURFACE_CURVE('',#1826,(#1830,#1837),.PCURVE_S1.);
#1826 = LINE('',#1827,#1828);
#1827 = CARTESIAN_POINT('',(-2.27,-1.1,2.5));
#1828 = VECTOR('',#1829,1.);
#1829 = DIRECTION('',(0.,1.,0.));
#1830 = PCURVE('',#1798,#1831);
#1831 = DEFINITIONAL_REPRESENTATION('',(#1832),#1836);
#1832 = LINE('',#1833,#1834);
#1833 = CARTESIAN_POINT('',(0.,0.));
#1834 = VECTOR('',#1835,1.);
#1835 = DIRECTION('',(0.,-1.));
#1836 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1837 = PCURVE('',#1838,#1843);
#1838 = PLANE('',#1839);
#1839 = AXIS2_PLACEMENT_3D('',#1840,#1841,#1842);
#1840 = CARTESIAN_POINT('',(-2.27,-1.1,2.5));
#1841 = DIRECTION('',(0.,0.,1.));
#1842 = DIRECTION('',(1.,0.,0.));
#1843 = DEFINITIONAL_REPRESENTATION('',(#1844),#1848);
#1844 = LINE('',#1845,#1846);
#1845 = CARTESIAN_POINT('',(0.,0.));
#1846 = VECTOR('',#1847,1.);
#1847 = DIRECTION('',(0.,1.));
#1848 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1849 = ORIENTED_EDGE('',*,*,#1850,.T.);
#1850 = EDGE_CURVE('',#1823,#1851,#1853,.T.);
#1851 = VERTEX_POINT('',#1852);
#1852 = CARTESIAN_POINT('',(-2.27,-1.02,6.05));
#1853 = SURFACE_CURVE('',#1854,(#1858,#1865),.PCURVE_S1.);
#1854 = LINE('',#1855,#1856);
#1855 = CARTESIAN_POINT('',(-2.27,-1.02,2.5));
#1856 = VECTOR('',#1857,1.);
#1857 = DIRECTION('',(0.,0.,1.));
#1858 = PCURVE('',#1798,#1859);
#1859 = DEFINITIONAL_REPRESENTATION('',(#1860),#1864);
#1860 = LINE('',#1861,#1862);
#1861 = CARTESIAN_POINT('',(0.,-8.E-02));
#1862 = VECTOR('',#1863,1.);
#1863 = DIRECTION('',(1.,0.));
#1864 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1865 = PCURVE('',#1866,#1871);
#1866 = PLANE('',#1867);
#1867 = AXIS2_PLACEMENT_3D('',#1868,#1869,#1870);
#1868 = CARTESIAN_POINT('',(-2.27,-1.02,2.5));
#1869 = DIRECTION('',(0.,1.,0.));
#1870 = DIRECTION('',(0.,0.,1.));
#1871 = DEFINITIONAL_REPRESENTATION('',(#1872),#1876);
#1872 = LINE('',#1873,#1874);
#1873 = CARTESIAN_POINT('',(0.,0.));
#1874 = VECTOR('',#1875,1.);
#1875 = DIRECTION('',(1.,0.));
#1876 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1877 = ORIENTED_EDGE('',*,*,#1878,.F.);
#1878 = EDGE_CURVE('',#1790,#1851,#1879,.T.);
#1879 = SURFACE_CURVE('',#1880,(#1884,#1891),.PCURVE_S1.);
#1880 = LINE('',#1881,#1882);
#1881 = CARTESIAN_POINT('',(-2.27,-1.1,6.05));
#1882 = VECTOR('',#1883,1.);
#1883 = DIRECTION('',(0.,1.,0.));
#1884 = PCURVE('',#1798,#1885);
#1885 = DEFINITIONAL_REPRESENTATION('',(#1886),#1890);
#1886 = LINE('',#1887,#1888);
#1887 = CARTESIAN_POINT('',(3.55,0.));
#1888 = VECTOR('',#1889,1.);
#1889 = DIRECTION('',(0.,-1.));
#1890 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1891 = PCURVE('',#1892,#1897);
#1892 = PLANE('',#1893);
#1893 = AXIS2_PLACEMENT_3D('',#1894,#1895,#1896);
#1894 = CARTESIAN_POINT('',(-2.27,-1.1,6.05));
#1895 = DIRECTION('',(0.,0.,1.));
#1896 = DIRECTION('',(1.,0.,0.));
#1897 = DEFINITIONAL_REPRESENTATION('',(#1898),#1902);
#1898 = LINE('',#1899,#1900);
#1899 = CARTESIAN_POINT('',(0.,0.));
#1900 = VECTOR('',#1901,1.);
#1901 = DIRECTION('',(0.,1.));
#1902 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1903 = ADVANCED_FACE('',(#1904),#1918,.T.);
#1904 = FACE_BOUND('',#1905,.T.);
#1905 = EDGE_LOOP('',(#1906,#1936,#1959,#1982));
#1906 = ORIENTED_EDGE('',*,*,#1907,.F.);
#1907 = EDGE_CURVE('',#1908,#1910,#1912,.T.);
#1908 = VERTEX_POINT('',#1909);
#1909 = CARTESIAN_POINT('',(-2.05,-1.1,2.5));
#1910 = VERTEX_POINT('',#1911);
#1911 = CARTESIAN_POINT('',(-2.05,-1.1,6.05));
#1912 = SURFACE_CURVE('',#1913,(#1917,#1929),.PCURVE_S1.);
#1913 = LINE('',#1914,#1915);
#1914 = CARTESIAN_POINT('',(-2.05,-1.1,2.5));
#1915 = VECTOR('',#1916,1.);
#1916 = DIRECTION('',(0.,0.,1.));
#1917 = PCURVE('',#1918,#1923);
#1918 = PLANE('',#1919);
#1919 = AXIS2_PLACEMENT_3D('',#1920,#1921,#1922);
#1920 = CARTESIAN_POINT('',(-2.05,-1.1,2.5));
#1921 = DIRECTION('',(1.,0.,0.));
#1922 = DIRECTION('',(0.,0.,1.));
#1923 = DEFINITIONAL_REPRESENTATION('',(#1924),#1928);
#1924 = LINE('',#1925,#1926);
#1925 = CARTESIAN_POINT('',(0.,0.));
#1926 = VECTOR('',#1927,1.);
#1927 = DIRECTION('',(1.,0.));
#1928 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1929 = PCURVE('',#1810,#1930);
#1930 = DEFINITIONAL_REPRESENTATION('',(#1931),#1935);
#1931 = LINE('',#1932,#1933);
#1932 = CARTESIAN_POINT('',(0.,0.22));
#1933 = VECTOR('',#1934,1.);
#1934 = DIRECTION('',(1.,0.));
#1935 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1936 = ORIENTED_EDGE('',*,*,#1937,.T.);
#1937 = EDGE_CURVE('',#1908,#1938,#1940,.T.);
#1938 = VERTEX_POINT('',#1939);
#1939 = CARTESIAN_POINT('',(-2.05,-1.02,2.5));
#1940 = SURFACE_CURVE('',#1941,(#1945,#1952),.PCURVE_S1.);
#1941 = LINE('',#1942,#1943);
#1942 = CARTESIAN_POINT('',(-2.05,-1.1,2.5));
#1943 = VECTOR('',#1944,1.);
#1944 = DIRECTION('',(0.,1.,0.));
#1945 = PCURVE('',#1918,#1946);
#1946 = DEFINITIONAL_REPRESENTATION('',(#1947),#1951);
#1947 = LINE('',#1948,#1949);
#1948 = CARTESIAN_POINT('',(0.,0.));
#1949 = VECTOR('',#1950,1.);
#1950 = DIRECTION('',(0.,-1.));
#1951 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1952 = PCURVE('',#1838,#1953);
#1953 = DEFINITIONAL_REPRESENTATION('',(#1954),#1958);
#1954 = LINE('',#1955,#1956);
#1955 = CARTESIAN_POINT('',(0.22,0.));
#1956 = VECTOR('',#1957,1.);
#1957 = DIRECTION('',(0.,1.));
#1958 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1959 = ORIENTED_EDGE('',*,*,#1960,.T.);
#1960 = EDGE_CURVE('',#1938,#1961,#1963,.T.);
#1961 = VERTEX_POINT('',#1962);
#1962 = CARTESIAN_POINT('',(-2.05,-1.02,6.05));
#1963 = SURFACE_CURVE('',#1964,(#1968,#1975),.PCURVE_S1.);
#1964 = LINE('',#1965,#1966);
#1965 = CARTESIAN_POINT('',(-2.05,-1.02,2.5));
#1966 = VECTOR('',#1967,1.);
#1967 = DIRECTION('',(0.,0.,1.));
#1968 = PCURVE('',#1918,#1969);
#1969 = DEFINITIONAL_REPRESENTATION('',(#1970),#1974);
#1970 = LINE('',#1971,#1972);
#1971 = CARTESIAN_POINT('',(0.,-8.E-02));
#1972 = VECTOR('',#1973,1.);
#1973 = DIRECTION('',(1.,0.));
#1974 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1975 = PCURVE('',#1866,#1976);
#1976 = DEFINITIONAL_REPRESENTATION('',(#1977),#1981);
#1977 = LINE('',#1978,#1979);
#1978 = CARTESIAN_POINT('',(0.,0.22));
#1979 = VECTOR('',#1980,1.);
#1980 = DIRECTION('',(1.,0.));
#1981 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1982 = ORIENTED_EDGE('',*,*,#1983,.F.);
#1983 = EDGE_CURVE('',#1910,#1961,#1984,.T.);
#1984 = SURFACE_CURVE('',#1985,(#1989,#1996),.PCURVE_S1.);
#1985 = LINE('',#1986,#1987);
#1986 = CARTESIAN_POINT('',(-2.05,-1.1,6.05));
#1987 = VECTOR('',#1988,1.);
#1988 = DIRECTION('',(0.,1.,0.));
#1989 = PCURVE('',#1918,#1990);
#1990 = DEFINITIONAL_REPRESENTATION('',(#1991),#1995);
#1991 = LINE('',#1992,#1993);
#1992 = CARTESIAN_POINT('',(3.55,0.));
#1993 = VECTOR('',#1994,1.);
#1994 = DIRECTION('',(0.,-1.));
#1995 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#1996 = PCURVE('',#1892,#1997);
#1997 = DEFINITIONAL_REPRESENTATION('',(#1998),#2002);
#1998 = LINE('',#1999,#2000);
#1999 = CARTESIAN_POINT('',(0.22,0.));
#2000 = VECTOR('',#2001,1.);
#2001 = DIRECTION('',(0.,1.));
#2002 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#2003 = ADVANCED_FACE('',(#2004),#1810,.F.);
#2004 = FACE_BOUND('',#2005,.F.);
#2005 = EDGE_LOOP('',(#2006,#2027,#2028,#2049));
#2006 = ORIENTED_EDGE('',*,*,#2007,.F.);
#2007 = EDGE_CURVE('',#1788,#1908,#2008,.T.);
#2008 = SURFACE_CURVE('',#2009,(#2013,#2020),.PCURVE_S1.);
#2009 = LINE('',#2010,#2011);
#2010 = CARTESIAN_POINT('',(-2.27,-1.1,2.5));
#2011 = VECTOR('',#2012,1.);
#2012 = DIRECTION('',(1.,0.,0.));
#2013 = PCURVE('',#1810,#2014);
#2014 = DEFINITIONAL_REPRESENTATION('',(#2015),#2019);
#2015 = LINE('',#2016,#2017);
#2016 = CARTESIAN_POINT('',(0.,0.));
#2017 = VECTOR('',#2018,1.);
#2018 = DIRECTION('',(0.,1.));
#2019 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#2020 = PCURVE('',#1838,#2021);
#2021 = DEFINITIONAL_REPRESENTATION('',(#2022),#2026);
#2022 = LINE('',#2023,#2024);
#2023 = CARTESIAN_POINT('',(0.,0.));
#2024 = VECTOR('',#2025,1.);
#2025 = DIRECTION('',(1.,0.));
#2026 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#2027 = ORIENTED_EDGE('',*,*,#1787,.T.);
#2028 = ORIENTED_EDGE('',*,*,#2029,.T.);
#2029 = EDGE_CURVE('',#1790,#1910,#2030,.T.);
#2030 = SURFACE_CURVE('',#2031,(#2035,#2042),.PCURVE_S1.);
#2031 = LINE('',#2032,#2033);
#2032 = CARTESIAN_POINT('',(-2.27,-1.1,6.05));
#2033 = VECTOR('',#2034,1.);
#2034 = DIRECTION('',(1.,0.,0.));
#2035 = PCURVE('',#1810,#2036);
#2036 = DEFINITIONAL_REPRESENTATION('',(#2037),#2041);
#2037 = LINE('',#2038,#2039);
#2038 = CARTESIAN_POINT('',(3.55,0.));
#2039 = VECTOR('',#2040,1.);
#2040 = DIRECTION('',(0.,1.));
#2041 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#2042 = PCURVE('',#1892,#2043);
#2043 = DEFINITIONAL_REPRESENTATION('',(#2044),#2048);
#2044 = LINE('',#2045,#2046);
#2045 = CARTESIAN_POINT('',(0.,0.));
#2046 = VECTOR('',#2047,1.);
#2047 = DIRECTION('',(1.,0.));
#2048 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#2049 = ORIENTED_EDGE('',*,*,#1907,.F.);
#2050 = ADVANCED_FACE('',(#2051),#1866,.T.);
#2051 = FACE_BOUND('',#2052,.T.);
#2052 = EDGE_LOOP('',(#2053,#2074,#2075,#2096));
#2053 = ORIENTED_EDGE('',*,*,#2054,.F.);
#2054 = EDGE_CURVE('',#1823,#1938,#2055,.T.);
#2055 = SURFACE_CURVE('',#2056,(#2060,#2067),.PCURVE_S1.);
#2056 = LINE('',#2057,#2058);
#2057 = CARTESIAN_POINT('',(-2.27,-1.02,2.5));
#2058 = VECTOR('',#2059,1.);
#2059 = DIRECTION('',(1.,0.,0.));
#2060 = PCURVE('',#1866,#2061);
#2061 = DEFINITIONAL_REPRESENTATION('',(#2062),#2066);
#2062 = LINE('',#2063,#2064);
#2063 = CARTESIAN_POINT('',(0.,0.));
#2064 = VECTOR('',#2065,1.);
#2065 = DIRECTION('',(0.,1.));
#2066 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#2067 = PCURVE('',#1838,#2068);
#2068 = DEFINITIONAL_REPRESENTATION('',(#2069),#2073);
#2069 = LINE('',#2070,#2071);
#2070 = CARTESIAN_POINT('',(0.,8.E-02));
#2071 = VECTOR('',#2072,1.);
#2072 = DIRECTION('',(1.,0.));
#2073 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#2074 = ORIENTED_EDGE('',*,*,#1850,.T.);
#2075 = ORIENTED_EDGE('',*,*,#2076,.T.);
#2076 = EDGE_CURVE('',#1851,#1961,#2077,.T.);
#2077 = SURFACE_CURVE('',#2078,(#2082,#2089),.PCURVE_S1.);
#2078 = LINE('',#2079,#2080);
#2079 = CARTESIAN_POINT('',(-2.27,-1.02,6.05));
#2080 = VECTOR('',#2081,1.);
#2081 = DIRECTION('',(1.,0.,0.));
#2082 = PCURVE('',#1866,#2083);
#2083 = DEFINITIONAL_REPRESENTATION('',(#2084),#2088);
#2084 = LINE('',#2085,#2086);
#2085 = CARTESIAN_POINT('',(3.55,0.));
#2086 = VECTOR('',#2087,1.);
#2087 = DIRECTION('',(0.,1.));
#2088 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#2089 = PCURVE('',#1892,#2090);
#2090 = DEFINITIONAL_REPRESENTATION('',(#2091),#2095);
#2091 = LINE('',#2092,#2093);
#2092 = CARTESIAN_POINT('',(0.,8.E-02));
#2093 = VECTOR('',#2094,1.);
#2094 = DIRECTION('',(1.,0.));
#2095 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#2096 = ORIENTED_EDGE('',*,*,#1960,.F.);
#2097 = ADVANCED_FACE('',(#2098),#1838,.F.);
#2098 = FACE_BOUND('',#2099,.F.);
#2099 = EDGE_LOOP('',(#2100,#2101,#2102,#2103));
#2100 = ORIENTED_EDGE('',*,*,#1822,.F.);
#2101 = ORIENTED_EDGE('',*,*,#2007,.T.);
#2102 = ORIENTED_EDGE('',*,*,#1937,.T.);
#2103 = ORIENTED_EDGE('',*,*,#2054,.F.);
#2104 = ADVANCED_FACE('',(#2105),#1892,.T.);
#2105 = FACE_BOUND('',#2106,.T.);
#2106 = EDGE_LOOP('',(#2107,#2108,#2109,#2110));
#2107 = ORIENTED_EDGE('',*,*,#1878,.F.);
#2108 = ORIENTED_EDGE('',*,*,#2029,.T.);
#2109 = ORIENTED_EDGE('',*,*,#1983,.T.);
#2110 = ORIENTED_EDGE('',*,*,#2076,.F.);
#2111 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3)
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#2115))
GLOBAL_UNIT_ASSIGNED_CONTEXT((#2112,#2113,#2114)) REPRESENTATION_CONTEXT
('Context #1','3D Context with UNIT and UNCERTAINTY') );
#2112 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) );
#2113 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) );
#2114 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() );
#2115 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-07),#2112,
'distance_accuracy_value','confusion accuracy');
#2116 = CONTEXT_DEPENDENT_SHAPE_REPRESENTATION(#2117,#2119);
#2117 = ( REPRESENTATION_RELATIONSHIP('','',#1780,#10)
REPRESENTATION_RELATIONSHIP_WITH_TRANSFORMATION(#2118)
SHAPE_REPRESENTATION_RELATIONSHIP() );
#2118 = ITEM_DEFINED_TRANSFORMATION('','',#11,#31);
#2119 = PRODUCT_DEFINITION_SHAPE('Placement','Placement of an item',
#2120);
#2120 = NEXT_ASSEMBLY_USAGE_OCCURRENCE('60',
'36516288-93c7-11f1-ba5d-04bad62a3558','',#5,#1775,$);
#2121 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#1777));
#2122 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION('',(
#2123),#2111);
#2123 = STYLED_ITEM('color',(#2124),#1781);
#2124 = PRESENTATION_STYLE_ASSIGNMENT((#2125));
#2125 = SURFACE_STYLE_USAGE(.BOTH.,#2126);
#2126 = SURFACE_SIDE_STYLE('',(#2127));
#2127 = SURFACE_STYLE_FILL_AREA(#2128);
#2128 = FILL_AREA_STYLE('',(#2129));
#2129 = FILL_AREA_STYLE_COLOUR('',#2130);
#2130 = COLOUR_RGB('',0.250000000672,0.27999999963,0.299999996101);
#2131 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION('',(
#2132),#1413);
#2132 = STYLED_ITEM('color',(#2133),#747);
#2133 = PRESENTATION_STYLE_ASSIGNMENT((#2134));
#2134 = SURFACE_STYLE_USAGE(.BOTH.,#2135);
#2135 = SURFACE_SIDE_STYLE('',(#2136,#2140));
#2136 = SURFACE_STYLE_FILL_AREA(#2137);
#2137 = FILL_AREA_STYLE('',(#2138));
#2138 = FILL_AREA_STYLE_COLOUR('',#2139);
#2139 = COLOUR_RGB('',0.720000003324,0.78000000177,0.800000010877);
#2140 = SURFACE_STYLE_RENDERING_WITH_PROPERTIES(.NORMAL_SHADING.,#2139,(
#2141));
#2141 = SURFACE_STYLE_TRANSPARENT(0.420000016689);
#2142 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION('',(
#2143),#728);
#2143 = STYLED_ITEM('color',(#2144),#398);
#2144 = PRESENTATION_STYLE_ASSIGNMENT((#2145));
#2145 = SURFACE_STYLE_USAGE(.BOTH.,#2146);
#2146 = SURFACE_SIDE_STYLE('',(#2147));
#2147 = SURFACE_STYLE_FILL_AREA(#2148);
#2148 = FILL_AREA_STYLE('',(#2149));
#2149 = FILL_AREA_STYLE_COLOUR('',#2150);
#2150 = COLOUR_RGB('',0.720000003324,0.740000011925,0.760000013601);
#2151 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION('',(
#2152),#1762);
#2152 = STYLED_ITEM('color',(#2153),#1432);
#2153 = PRESENTATION_STYLE_ASSIGNMENT((#2154));
#2154 = SURFACE_STYLE_USAGE(.BOTH.,#2155);
#2155 = SURFACE_SIDE_STYLE('',(#2156));
#2156 = SURFACE_STYLE_FILL_AREA(#2157);
#2157 = FILL_AREA_STYLE('',(#2158));
#2158 = FILL_AREA_STYLE_COLOUR('',#2159);
#2159 = COLOUR_RGB('',9.999999991362E-02,0.1599999992,0.190000001765);
#2160 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION('',(
#2161),#379);
#2161 = STYLED_ITEM('color',(#2162),#49);
#2162 = PRESENTATION_STYLE_ASSIGNMENT((#2163));
#2163 = SURFACE_STYLE_USAGE(.BOTH.,#2164);
#2164 = SURFACE_SIDE_STYLE('',(#2165));
#2165 = SURFACE_STYLE_FILL_AREA(#2166);
#2166 = FILL_AREA_STYLE('',(#2167));
#2167 = FILL_AREA_STYLE_COLOUR('',#2150);
ENDSEC;
END-ISO-10303-21;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More