Files
OptoTest/README.md

265 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Optical Channel Tester — ESP32-C3 / ESP32-S3
Полностью рабочий Arduino IDE-проект для строгой проверки оптического цифрового канала. Устройство формирует PWM на выбранной частоте, последовательно уменьшает длительность импульса по заданному списку и проверяет ответ канала: частоту и длительность импульса. Поддерживаются роли `SOLO`, `MASTER` и `SLAVE`; роли выбираются только вручную.
## Что реализовано
- неблокирующий конечный автомат без `pulseIn()` и длинных `delay()`;
- аппаратные LEDC (C3) и MCPWM (S3) с расчётом реально получившихся частоты и длительности импульса;
- Arduino-ESP32 3.3.10: на S3 два аппаратных канала MCPWM Capture из отдельной группы фиксируют RISING и FALLING в независимых регистрах общего 32-битного таймера 80 МГц;
- безопасный fallback для старых Arduino-ESP32 3.x через GPIO edge ISR и аппаратный CPU cycle counter (верхний предел при этом автоматически ограничен 100 кГц);
- отбрасывание ровно `PWM_SETTLE_CYCLES` полных периодов;
- потоковая статистика без хранения всех периодов;
- немедленный FAIL по первому отдельному импульсу или периоду вне допуска;
- десять измерительных участков на каждой длительности импульса с промежуточным отображением результата;
- ESP-NOW discovery, handshake, CRC, session/stage/sequence, ACK, повторы и защита от старых пакетов;
- SSD1306 128×32: каждый экран всегда состоит ровно из двух строк;
- две кнопки, debounce, long press и repeat; после long press ложный short click не создаётся;
- Preferences/NVS с версией структуры, checksum, проверкой индексов и восстановлением defaults;
- работа через Serial при отсутствующем OLED;
- безопасное выключение PWM при PASS, FAIL, ABORT и потере связи.
- полный журнал действий в Serial с временными метками; OLED необязателен.
## Файлы
Arduino sketch находится в каталоге `OpticalChannelTester`:
- `OpticalChannelTester.ino` — стандартная точка входа Arduino IDE;
- `Config.h` — GPIO, тайм-ауты, пределы и все пользовательские массивы;
- `Core.*` — последовательность длительностей, ALL, допуски, статистика;
- `Buttons.*` — автомат двух кнопок;
- `SettingsStore.*` — NVS;
- `Display.*` — OLED и компактное форматирование;
- `Log.*` — журнал действий и Serial-зеркало интерфейса;
- `Pwm.*` — LEDC / MCPWM;
- `Receiver.*` — MCPWM Capture на S3 / совместимый GPIO fallback;
- `Measurement.*` — строгая проверка периодов;
- `Protocol.*`, `Radio.*` — ESP-NOW;
- `App.*` — общий конечный автомат SOLO/MASTER/SLAVE.
## Требования Arduino IDE
Проверенная конфигурация:
- Arduino IDE 2.x;
- пакет плат **esp32 by Espressif Systems 3.3.10**;
- **Adafruit GFX Library 1.12.1**;
- **Adafruit SSD1306 2.5.15**;
- Adafruit BusIO устанавливается Library Manager как зависимость.
В Arduino IDE откройте `OpticalChannelTester/OpticalChannelTester.ino`. Не переносите один `.ino` отдельно: остальные вкладки являются частью скетча.
Для C3 выберите подходящую плату ESP32-C3, например `ESP32C3 Dev Module`. Для S3 — `ESP32S3 Dev Module`. Затем выберите порт и нажмите Verify/Upload.
## GPIO
Все назначения находятся в начале `Config.h` и могут быть изменены до сборки.
Сейчас в `Config.h` включён `#define MAKETKA`. В этом профиле используются следующие назначения:
| Сигнал | ESP32-C3 | ESP32-S3 |
|---|---:|---:|
| PWM output | GPIO 3 | GPIO 12 |
| Optical RX input | GPIO 4 | GPIO 13 |
| START | GPIO 1 | GPIO 10 |
| MODE | GPIO 0 | GPIO 9 |
| OLED SDA | GPIO 6 | GPIO 44 |
| OLED SCL | GPIO 7 | GPIO 1 |
Если закомментировать `#define MAKETKA`, включается профиль производственной PCB:
| Сигнал | ESP32-C3 | ESP32-S3 |
|---|---:|---:|
| PWM output | GPIO 3 | GPIO 12 |
| Optical RX input | GPIO 4 | GPIO 13 |
| START | GPIO 10 | GPIO 4 |
| MODE | GPIO 20 | GPIO 5 |
| VBAT | GPIO 2 | GPIO 11 |
| Analog RX | GPIO 0 | GPIO 9 |
| OLED SDA | GPIO 6 | GPIO 44 |
| OLED SCL | GPIO 7 | GPIO 1 |
Оба профиля рассчитаны на установку S3 с совмещением контактов `5V` и `GND` с модулем C3. Номера GPIO S3 выбраны по тому же физическому ряду разъёма, а не по совпадению номера GPIO.
На ESP32-C3 PWM формируется через LEDC с аппаратным дробным делителем Q10.8. На ESP32-S3 используется отдельный MCPWM со счётчиком 20 МГц: это позволяет формировать 500 Гц в пределах 16-битного счётчика и сохраняет шаг импульса 50 нс. `PWM_ACTIVE_LEVEL` задаёт электрический уровень активной части тестового импульса на обеих платах; остальная часть работающего PWM имеет противоположный уровень. `PWM_SAFE_LEVEL` применяется только при остановленном PWM и во сне. Перед началом точки прошивка проверяет, что фактические частота и длительность импульса укладываются в выбранную точность.
### OLED
Подключите SSD1306 128×32: `VCC → 3.3 V`, `GND → GND`, `SDA/SCL` по таблице. Адрес по умолчанию `0x3C`. Если OLED не отвечает, тест продолжает работать и пишет диагностику в Serial 115200.
### Работа вообще без OLED
OLED можно не подключать. Откройте Serial Monitor на **115200 baud**: каждый экран всегда дублируется одной строкой вида:
```text
[ 1250][UI ] MODE: SOLO | START=RUN
```
Полоса прогресса учитывает номер длительности импульса и десять измерительных участков внутри неё.
В Serial также выводятся:
- каждое распознанное нажатие START/MODE и текущее состояние автомата;
- вход, изменение и сохранение каждого пункта меню;
- загрузка, проверка и сохранение NVS;
- запуск/остановка PWM и реальные параметры LEDC;
- начало этапа, число отбрасываемых периодов и параметры измерительного окна;
- все действия ESP-NOW, peer, session/stage/sequence, ACK и повторы;
- статистика этапа, первый плохой период и итоговая причина завершения;
- все строки, которые были бы показаны на OLED.
Во время строгого измерительного окна отдельные импульсы намеренно не печатаются: они проверяются потоково, а итоговая статистика выводится после окна. Это предотвращает влияние Serial на точность и переполнение очереди аппаратных фронтов. Журнал действий включён параметрами в `Config.h`:
```cpp
constexpr bool SERIAL_ACTION_LOG = true;
constexpr bool SERIAL_LOG_TIMESTAMPS = true;
```
### Кнопки
По умолчанию задано:
```cpp
#define BUTTON_ACTIVE_LEVEL LOW
```
Поэтому каждая кнопка подключается между своим GPIO и GND, а прошивка автоматически включает `INPUT_PULLUP`.
Если установить `BUTTON_ACTIVE_LEVEL HIGH`, подключайте кнопку между GPIO и 3.3 V; автоматически будет использован `INPUT_PULLDOWN`. Других изменений логики не требуется.
## Подключение сигнала
### SOLO
```text
ESP GPIO_PWM -> вход передатчика оптического канала
выход приёмника оптического канала -> ESP GPIO_RX
GND ESP -> GND входной/выходной электроники (если канал не гальванически развязан)
```
### MASTER / SLAVE
```text
MASTER GPIO_PWM -> вход передатчика проверяемого канала
выход приёмника проверяемого канала -> SLAVE GPIO_RX
MASTER <~~~~ ESP-NOW Wi-Fi channel 6 ~~~~> SLAVE
```
На обеих ESP должны совпадать `ESPNOW_WIFI_CHANNEL` и версия прошивки. Slave сначала переводится в `SLAVE READY` кнопкой START, затем START нажимается на Master.
Пока бодрствующий Slave находится в `SLAVE READY`, радиоприём работает периодически: окно 20 мс каждые 100 мс, а собственный оптический передатчик Slave отключён уровнем `PWM_SAFE_LEVEL`. При переходе Slave в light sleep ESP-NOW и Wi-Fi полностью отключаются, поэтому радиопакет не может его разбудить. Master во время поиска посылает серию `DISCOVER` каждые 20 мс и одновременно переключает оптический выход между `PWM_ACTIVE_LEVEL` и `PWM_SAFE_LEVEL` каждые `OPTICAL_WAKE_HALF_PERIOD_MS`. Изменение уровня на оптическом входе пробуждает Slave через GPIO; после этого Slave запускает ESP-NOW и принимает повторяемый `DISCOVER`. Постоянный HIGH или LOW сам по себе повторных пробуждений не вызывает. После обнаружения Master прекращает wake-сигнал, а Slave включает непрерывный радиоприём на всю тестовую сессию. Параметры задаются в `Config.h`.
**GPIO ESP32 допускают только логические уровни 0…3.3 В.** Не подавайте 5 В на `GPIO_RX`; применяйте согласование уровня. Полярность выхода оптического приёмника задаётся однозначно его активным уровнем:
Базовый активный уровень входа задаёт `RX_ACTIVE_LEVEL`, но во время теста полярность определяется автоматически по первому полному периоду и фиксируется до следующего запуска приёмника. Прошивка сравнивает длительности обоих уровней с заданным импульсом и использует более близкую. Например, для 2 кГц и 200 мкс автоматически выбирается 200 мкс, а не дополнение 300 мкс. При заполнении 50% обе полярности эквивалентны.
## Управление
В ожидании:
- MODE short: `SOLO → MASTER → SLAVE → SOLO`, выбранная роль сохраняется;
- MODE long: открыть настройки;
- START short: начать тест;
- START long во время теста: ABORT, PWM немедленно выключается.
В настройках:
- MODE short: следующий параметр;
- MODE long: проверить диапазон, сохранить один раз в NVS и выйти;
- START short: следующее значение;
- START long: предыдущее значение, затем autorepeat при удержании.
Удержание обеих кнопок минимум 1.5 с при включении восстанавливает defaults.
### Энергосбережение
В ожидании процессор работает на 80 МГц, во время теста и связанной MASTER/SLAVE-сессии — на 160 МГц. Пока Solo или Master бодрствует и тест не идёт, выход передатчика удерживается на `PWM_ACTIVE_LEVEL`; в режиме Slave передатчик всегда отключён уровнем `PWM_SAFE_LEVEL`. При запуске теста, остановленном PWM и после тайм-аута сна также используется `PWM_SAFE_LEVEL` — уровень полного выключения с минимальным потреблением тока. После тайм-аута бездействия в `IDLE`, `FINISHED` или `SLAVE READY` OLED выключается, а ESP переходит в непрерывный light sleep без периодических таймерных пробуждений. Пока открыто меню настроек, light sleep запрещён, поэтому кнопки остаются отзывчивыми независимо от времени настройки. Экран и текущее состояние сохраняются. START и MODE будят любую роль, а фронт на оптическом входе дополнительно будит Slave. После пробуждения прошивка полностью перезапускает драйверы USB Serial и I²C, а в режиме Slave также восстанавливает ESP-NOW.
START и MODE будят устройство. Первое, пробуждающее нажатие не выполняет действие и полностью подавляется до отпускания кнопки; действие выполняет только следующее нажатие. Оптический wake-сигнал также немедленно выводит Slave из энергосбережения. Тайм-аут задаётся `IDLE_POWER_SAVE_TIMEOUT_MS` в `Config.h`.
## Настройки теста
Меню содержит пять параметров:
1. частота ШИМ из `PWM_FREQUENCY_OPTIONS_HZ`;
2. максимальная длительность импульса из отдельного `MAX_PULSE_OPTIONS_NS`;
3. минимальная длительность импульса из отдельного `MIN_PULSE_OPTIONS_NS`;
4. точность;
5. время выборки.
Тест работает на одной выбранной частоте и проходит длительности из списка от максимальной к минимальной. Значения, равные периоду или превышающие его, автоматически исключаются при выборе частоты. На S3 минимальная длительность ограничивается расчётной точностью генератора и MCPWM Capture 80 МГц: длинная пауза не снижает точность короткого импульса. Меню не позволяет задать точку, которой аппаратно не хватает выбранного допуска. Сохранённые индексы и их сочетания проверяются и приводятся к допустимому диапазону.
MAX и MIN имеют независимые массивы и независимые индексы. Сам тест проходит отдельный полный список `TEST_PULSE_WIDTHS_NS`. Все пункты меню зациклены: после последнего допустимого значения выбирается первое, а при движении назад перед первым выбирается последнее.
`ALL` пересчитывается после изменения частоты, границ импульса или времени выборки и отображается с точностью до целой секунды. Для каждой точки учитывается:
```text
max(TEST_TIME, periods / RX_PROCESSING_PERIODS_PER_SECOND)
+ PWM_SETTLE_CYCLES / actualFrequency × 10
+ OLED_PROGRESS_UPDATE_MS × 11
```
`TEST TIME` — это чистое время выборки сигнала, а не полная длительность этапа. На высоких частотах полная длительность заметно возрастает из-за проверки каждого периода. Расчёт использует консервативную производительность 300 тысяч периодов в секунду.
## Строгая проверка
После каждой перенастройки PWM приёмник:
1. автоматически определяет полярность Rx; отдельные каналы MCPWM Capture S3 фиксируют RISING и FALLING в независимых регистрах общего 32-битного таймера 80 МГц, чтобы близкий второй фронт не перезаписал первый;
2. отбрасывает ровно `PWM_SETTLE_CYCLES` полных периодов;
3. очищает статистику;
4. непрерывно держит MCPWM Capture включённым на всём измерительном этапе, проверяет каждый полный импульс ровно один раз и на десяти границах TEST TIME показывает промежуточный результат; границы прогресса не останавливают capture и не выбрасывают пересекающий их импульс. Между длительностями сначала останавливается PWM, затем capture, поэтому очистка очереди не пересекается с ISR.
Каждый полный период и каждый активный импульс проверяются отдельно с выбранным относительным допуском 1%, 2%, 5% или 10%. Поэтому единичное искажение не растворяется в среднем остальных импульсов. После трёх начальных фронтов приёмник работает как простой автомат чередующихся интервалов «импульс — пауза»: тип последующих фронтов не используется, а лишний или потерянный фронт превращается в конкретный `PULSE OUT` или `PERIOD OUT`, не в абстрактный `GLITCH`. Для одного измерения допускается только коррекция на один такт приёмного таймера в сторону заданного значения — это неизбежная неопределённость фиксации фронта. Если после неё конкретная частота или длительность остаётся вне допуска, тест немедленно завершается ошибкой. Накопленное среднее используется только для строк `F:..., P:...` и итогового журнала. Переполнение очереди фронтов немедленно даёт `DATA LOSS ERROR`.
Во время этапа OLED показывает заданную точку и измеренный ответ, например:
```text
TEST: 2kHz, 2us
F:2.000k, P:2.00u
```
При ошибке сохраняется прежний двухстрочный стиль с конкретным значением,
вышедшим за допуск:
```text
FAIL AT 2kHz, 200us
PULSE OUT 300.01u
```
В MASTER/SLAVE передаются выбранная и фактическая длительность импульса и измеренный результат. На принимающей стороне полярность Rx определяется автоматически.
Незавершённый период в начале и конце временного участка не учитывается. Общая статистика и отображаемое среднее по всем принятым полным периодам этапа сохраняются, но решение PASS/FAIL принимается по каждому импульсу отдельно.
Причины: `NO SIGNAL`, `PERIOD OUT`, `DUTY OUT` (в интерфейсе — ошибка длительности импульса), `EXTRA EDGE`, `GLITCH`, `LOST EDGE`, `DATA LOSS ERROR`, `LINK LOST`, `UNSUPPORTED`, `RESOLUTION`, `ABORTED`.
### Пределы
- C3 и S3: настраиваемый строгий предел до 1 МГц;
- выше строгого аппаратного предела выдаётся `UNSUPPORTED`, ослабленной проверки нет;
- комбинации, для которых PWM или аппаратный capture не дают выбранную точность, отсекаются ещё в меню; повторная проверка перед стартом остаётся защитой от аппаратного сбоя.
Реальный предел зависит от конкретной платы, разводки, формы фронтов, Wi-Fi нагрузки и оптического оборудования. Начинайте проверку с 2 кГц и импульсов 200…2 мкс.
## Первый запуск SOLO
1. Соберите схему SOLO и ещё раз убедитесь, что на `GPIO_RX` не бывает напряжения выше 3.3 В.
2. Откройте Serial Monitor на 115200 baud.
3. Включите плату. На OLED должно быть `MODE: SOLO` / `START=RUN`.
4. Если выбран другой режим, коротко нажимайте MODE до SOLO.
5. Для первого опыта оставьте defaults: 2 кГц, импульсы 200…2 мкс, accuracy 5%. Полярность Rx определяется автоматически.
6. Коротко нажмите START.
7. Serial покажет запрошенные и фактические параметры PWM, список длительностей, ALL и статистику каждой точки.
8. Успех: `PASS ...` / `START=AGAIN`. Ошибка: `FAIL AT ...` и точная причина.
## Проверка проекта
Целевая версия проекта — Arduino-ESP32 3.3.10. В доступной среде исходный код собран Arduino CLI с этой версией:
| Target | Arduino-ESP32 | Flash | RAM | Результат |
|---|---:|---:|---:|---|
| ESP32-C3 | 3.3.10 | 1,045,075 B (79%) | 41,012 B (12%) | PASS |
| ESP32-S3 | 3.3.10 | 976,233 B (74%) | 49,812 B (15%) | PASS |
Физическое оборудование в этой среде недоступно, поэтому реальные оптические фронты, MCPWM Capture под длительной нагрузкой, дальность ESP-NOW и электрическая совместимость должны быть проверены на ваших платах. Сборка и программные тесты не выдаются за аппаратный тест.